| <!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> |