UNPKG

spincycle

Version:

A reactive message router and object manager that lets clients subscribe to object property changes on the server

500 lines (448 loc) 18 kB
<!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>