blob: 4eebc611676e1789fa18025abec77d05e09403af [file] [log] [blame]
<!doctype html>
<!--
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
<title>iron-location</title>
<script src="../../webcomponentsjs/webcomponents-lite.js"></script>
<script src="../../web-component-tester/browser.js"></script>
<link rel="import" href="../../polymer/polymer.html">
<link rel="import" href="../../promise-polyfill/promise-polyfill.html">
<link rel="import" href="../iron-location.html">
<link rel="import" href="./redirection.html">
<style>
#safari-cooldown {
font-size: 18px;
max-width: 300px;
}
#safari-cooldown div {
margin-bottom: 20px;
}
</style>
</head>
<body>
<div id='safari-cooldown' hidden>
<div>Danger: URL overheating.</div>
<div>
Safari requires a cooldown period before we call
pushState/replaceState any more.
</div>
<div>Testing will resume after 30 seconds.</div>
<div>
<a href="https://forums.developer.apple.com/thread/36650">More info</a>
</div>
</div>
<test-fixture id='Solo'>
<template>
<iron-location></iron-location>
</template>
</test-fixture>
<test-fixture id='Together'>
<template>
<div>
<iron-location id='one'></iron-location>
<iron-location id='two'></iron-location>
</div>
</template>
</test-fixture>
<test-fixture id='RedirectHash'>
<template>
<redirect-hash></redirect-hash>
</template>
</test-fixture>
<test-fixture id='RedirectPath'>
<template>
<redirect-path></redirect-path>
</template>
</test-fixture>
<test-fixture id='RedirectQuery'>
<template>
<redirect-query></redirect-query>
</template>
</test-fixture>
<script>
'use strict';
function timePasses(ms) {
return new Promise(function(resolve) {
window.setTimeout(function() {
resolve();
}, ms);
});
}
function makeAbsoluteUrl(path) {
return window.location.protocol + '//' + window.location.host + path;
}
// A window.history.replaceState wrapper that's smart about baseURI.
function replaceState(path) {
window.history.replaceState({}, '', makeAbsoluteUrl(path));
}
/**
* We run the iron location tests with a couple different page configurations
* (e.g. with and withoug a base URI), so we define the test set as a function
* that we call in each of these configurations.
*/
function ironLocationTests() {
suite('when used solo', function() {
var urlElem;
var toRemove;
setup(function() {
replaceState('/');
urlElem = fixture('Solo');
toRemove = [];
});
teardown(function() {
for (var i = 0; i < toRemove.length; i++) {
document.body.removeChild(toRemove[i]);
}
});
test('basic functionality with #hash urls', function() {
// Initialized to the window location's hash.
expect(window.location.hash).to.be.equal(urlElem.hash);
// Changing the urlElem's hash changes the URL
urlElem.hash = '/lol/ok';
expect(window.location.hash).to.be.equal('#/lol/ok');
// Changing the hash via normal means changes the urlElem.
var anchor = document.createElement('a');
var base =
window.location.protocol + '//' + window.location.host +
window.location.pathname;
anchor.href = base + '#/wat';
document.body.appendChild(anchor);
anchor.click();
// In IE10 it appears that the hashchange event is asynchronous.
return timePasses(10).then(function() {
expect(urlElem.hash).to.be.equal('/wat');
});
});
test('basic functionality with paths', function() {
// Initialized to the window location's path.
expect(window.location.pathname).to.be.equal(urlElem.path);
// Changing the urlElem's path changes the URL
urlElem.path = '/foo/bar';
expect(window.location.pathname).to.be.equal('/foo/bar');
// Changing the path and sending a custom event on the window changes
// the urlElem.
replaceState('/baz');
window.dispatchEvent(new CustomEvent('location-changed'));
expect(urlElem.path).to.be.equal('/baz');
});
function makeTemporaryIronLocation() {
var ironLocation = document.createElement('iron-location');
document.body.appendChild(ironLocation);
toRemove.push(ironLocation);
return ironLocation
}
test('dealing with paths with unusual characters', function() {
var pathEncodingExamples = {
'/foo': '/foo',
'/': '/',
'/foo bar': '/foo%20bar',
'/foo#bar': '/foo%23bar',
'/foo?xyz': '/foo%3Fxyz',
'/foo\'bar\'baz': '/foo\'bar\'baz',
};
for (var plainTextPath in pathEncodingExamples) {
var encodedPath = pathEncodingExamples[plainTextPath];
urlElem.path = plainTextPath;
expect(window.location.pathname).to.be.equal(encodedPath);
expect(urlElem.path).to.be.equal(plainTextPath);
var temporaryIronLocation = makeTemporaryIronLocation();
expect(temporaryIronLocation.path).to.be.equal(plainTextPath);
}
});
test('dealing with hashes with unusual characters', function() {
var hashEncodingExamples = {
'foo': '#foo',
'': '',
'foo bar': ['#foo%20bar', '#foo bar'],
'foo#bar': '#foo#bar',
'foo?bar': '#foo?bar',
'/foo\'bar\'baz': ['#/foo%27bar%27baz', '#/foo\'bar\'baz'],
};
for (var plainTextHash in hashEncodingExamples) {
var encodedHashes = hashEncodingExamples[plainTextHash];
if (typeof encodedHashes === 'string') {
encodedHashes = [encodedHashes];
}
urlElem.hash = plainTextHash;
expect(encodedHashes).to.contain(window.location.hash);
expect(urlElem.hash).to.be.equal(plainTextHash);
expect(makeTemporaryIronLocation().hash).to.be.equal(plainTextHash);
}
});
test('dealing with queries with unusual characters', function() {
var queryEncodingExamples = {
'foo': '?foo',
'': '',
'foo bar': '?foo%20bar',
'foo#bar': '?foo%23bar',
'foo?bar': '?foo?bar',
'/foo\'bar\'baz': ['?/foo%27bar%27baz', '?/foo\'bar\'baz'],
'foo/bar/baz': '?foo/bar/baz'
};
for (var plainTextQuery in queryEncodingExamples) {
var encodedQueries = queryEncodingExamples[plainTextQuery];
if (typeof encodedQueries === 'string') {
encodedQueries = [encodedQueries];
}
var ironLocationQuery = encodedQueries.map(function(value) {
return value.substring(1);
});
expect(urlElem._initialized).to.be.eq(true);
urlElem.query = plainTextQuery;
expect(encodedQueries).to.contain(window.location.search);
expect(ironLocationQuery).to.contain(urlElem.query);
expect(ironLocationQuery).to.contain(makeTemporaryIronLocation().query);
urlElem.query = 'dummyValue';
urlElem.query = ironLocationQuery[0];
expect(encodedQueries).to.contain(window.location.search);
expect(ironLocationQuery).to.contain(urlElem.query);
expect(ironLocationQuery).to.contain(makeTemporaryIronLocation().query);
}
});
test('assigning to a relative path URL', function() {
urlElem.path = '/foo/bar';
expect(window.location.pathname).to.be.equal('/foo/bar');
// A relative path is treated as an absolute one, just with a
// missing leading slash.
urlElem.path = 'baz';
expect(window.location.pathname).to.be.equal('/baz');
});
test('basic functionality with ?key=value params', function() {
// Initialized to the window location's params.
expect(urlElem.query).to.be.eq('');
// Changing location.search and sending a custom event on the window
// changes the urlElem.
replaceState('/?greeting=hello&target=world');
window.dispatchEvent(new CustomEvent('location-changed'));
expect(urlElem.query).to.be.equal('greeting=hello&target=world');
// Changing the urlElem's query changes the URL.
urlElem.query = 'greeting=hello&target=world&another=key';
expect(window.location.search).to.be.equal(
'?greeting=hello&target=world&another=key');
});
});
suite('does not spam the user\'s history', function() {
var replaceStateCalls, pushStateCalls;
var nativeReplaceState, nativePushState;
setup(function() {
replaceStateCalls = pushStateCalls = 0;
nativeReplaceState = window.history.replaceState;
nativePushState = window.history.pushState;
window.history.replaceState = function() {
replaceStateCalls++;
};
window.history.pushState = function() {
pushStateCalls++;
};
});
teardown(function() {
window.history.replaceState = nativeReplaceState;
window.history.pushState = nativePushState;
});
test('when a change happens immediately after ' +
'the iron-location is attached', function() {
var ironLocation = fixture('Solo');
expect(pushStateCalls).to.be.equal(0);
expect(replaceStateCalls).to.be.equal(0);
ironLocation.path = '/foo';
expect(replaceStateCalls).to.be.equal(1);
expect(pushStateCalls).to.be.equal(0);
});
suite('when intercepting links', function() {
/**
* Clicks the given link while an iron-location element with the given
* urlSpaceRegex is in the same document. Returns whether the
* iron-location prevented the default behavior of the click.
*
* No matter what, it prevents the default behavior of the click itself
* to ensure that no navigation occurs (as that would interrupt
* running and reporting these tests!)
*/
function isClickCaptured(anchor, urlSpaceRegex) {
var defaultWasPrevented;
function handler(event) {
expect(event.target).to.be.eq(anchor);
defaultWasPrevented = event.defaultPrevented;
event.preventDefault();
expect(event.defaultPrevented).to.be.eq(true);
}
window.addEventListener('click', handler);
var ironLocation = fixture('Solo');
if (urlSpaceRegex != null) {
ironLocation.urlSpaceRegex = urlSpaceRegex;
}
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
window.removeEventListener('click', handler);
return defaultWasPrevented;
}
test('simple link to / is intercepted', function() {
var anchor = document.createElement('a');
if (document.baseURI !== window.location.href) {
anchor.href = makeAbsoluteUrl('/');
} else {
anchor.href = '/';
}
expect(isClickCaptured(anchor)).to.be.eq(true);
expect(pushStateCalls).to.be.equal(1);
});
test('link that matches url space is intercepted', function() {
var anchor = document.createElement('a');
anchor.href = makeAbsoluteUrl('/foo');
expect(isClickCaptured(anchor, '/fo+')).to.be.eq(true);
expect(pushStateCalls).to.be.equal(1);
});
test('link that doesn\'t match url space isn\'t intercepted', function() {
var anchor = document.createElement('a');
anchor.href = makeAbsoluteUrl('/bar');
expect(isClickCaptured(anchor, '/fo+')).to.be.eq(false);
expect(pushStateCalls).to.be.equal(0);
});
test('link to another domain isn\'t intercepted', function() {
var anchor = document.createElement('a');
anchor.href = 'http://example.com/';
expect(isClickCaptured(anchor)).to.be.eq(false);
expect(pushStateCalls).to.be.equal(0);
});
test('a link with target=_blank isn\'t intercepted', function() {
var anchor = document.createElement('a');
anchor.href = makeAbsoluteUrl('/');
anchor.target = '_blank';
expect(isClickCaptured(anchor)).to.be.eq(false);
expect(pushStateCalls).to.be.equal(0);
});
test('a link with an href to the ' +
'current page shouldn\'t add to history.', function() {
var anchor = document.createElement('a');
anchor.href = window.location.href;
// The click is captured, but it doesn't add to history.
expect(isClickCaptured(anchor)).to.be.equal(true);
expect(pushStateCalls).to.be.equal(0);
});
test('a click that has already been defaultPrevented ' +
'shouldn\'t result in a navigation', function() {
fixture('Solo');
var anchor = document.createElement('a');
anchor.href = makeAbsoluteUrl('/');
anchor.addEventListener('click', function(event) {
event.preventDefault();
});
document.body.appendChild(anchor);
var originalPushState = window.history.pushState;
var count = 0;
window.history.pushState = function() {
count++;
}
anchor.click();
window.history.pushState = originalPushState;
expect(count).to.be.equal(0);
})
});
});
suite('when used with other iron-location elements', function() {
var otherUrlElem;
var urlElem;
setup(function() {
var elems = fixture('Together');
urlElem = elems.querySelector('#one');
otherUrlElem = elems.querySelector('#two');
});
function assertHaveSameUrls(urlElemOne, urlElemTwo) {
expect(urlElemOne.path).to.be.equal(urlElemTwo.path);
expect(urlElemOne.hash).to.be.equal(urlElemTwo.hash);
expect(urlElemOne.query).to.be.equal(urlElemTwo.query);
}
test('coordinate their changes (by firing events on window)', function() {
assertHaveSameUrls(urlElem, otherUrlElem);
urlElem.path = '/a/b/c';
assertHaveSameUrls(urlElem, otherUrlElem);
otherUrlElem.query = 'alsdkjflaksjfd=alksjfdlkajsdfl';
assertHaveSameUrls(urlElem, otherUrlElem);
urlElem.hash = 'lkjljifosjkldfjlksjfldsjf';
assertHaveSameUrls(urlElem, otherUrlElem);
});
});
suite('supports doing synchronous redirection', function() {
test('of the hash portion of the URL', function() {
expect(window.location.hash).to.be.equal('');
var redirector = fixture('RedirectHash');
expect(window.location.hash).to.be.equal('#redirectedTo');
expect(redirector.hash).to.be.equal('redirectedTo');
redirector.hash = 'newHash';
expect(window.location.hash).to.be.equal('#redirectedTo');
expect(redirector.hash).to.be.equal('redirectedTo');
});
test('of the path portion of the URL', function() {
expect(window.location.pathname).to.not.be.equal('/redirectedTo');
var redirector = fixture('RedirectPath');
expect(window.location.pathname).to.be.equal('/redirectedTo');
expect(redirector.path).to.be.equal('/redirectedTo');
redirector.path = '/newPath';
expect(window.location.pathname).to.be.equal('/redirectedTo');
expect(redirector.path).to.be.equal('/redirectedTo');
});
test('of the query portion of the URL', function() {
expect(window.location.search).to.be.equal('');
var redirector = fixture('RedirectQuery');
expect(window.location.search).to.be.equal('?redirectedTo');
expect(redirector.query).to.be.equal('redirectedTo');
redirector.query = 'newQuery';
expect(window.location.search).to.be.equal('?redirectedTo');
expect(redirector.query).to.be.equal('redirectedTo');
});
});
}
suite('<iron-location>', function () {
var initialUrl;
setup(function() {
initialUrl = window.location.href;
});
teardown(function(){
window.history.replaceState({}, '', initialUrl);
});
// This is as dumb as it looks. See #safari-cooldown in the dom above.
var cooldownFunction = function() {};
if (/^Apple/.test(navigator.vendor)) {
cooldownFunction = function(done) {
var cooldownPeriod = 30 * 1000;
this.timeout(cooldownPeriod + 5000);
var cooldownMessage = document.querySelector('#safari-cooldown');
cooldownMessage.removeAttribute('hidden');
setTimeout(function() {
done();
cooldownMessage.setAttribute('hidden', 'hidden');
}, cooldownPeriod);
};
}
suite('without a base URI', function() {
ironLocationTests();
suiteTeardown(cooldownFunction);
});
suite('with a base URI', function() {
var baseElem;
setup(function() {
expect(document.baseURI).to.be.equal(window.location.href);
baseElem = document.createElement('base');
var href = 'https://example.com/i/dont/exist/obviously'
baseElem.href = href;
document.head.appendChild(baseElem);
expect(document.baseURI).to.be.equal(href);
});
teardown(function() {
document.head.removeChild(baseElem);
});
suiteTeardown(cooldownFunction);
ironLocationTests();
});
});
</script>
</body>