1<!doctype html> 2<!-- 3@license 4Copyright (c) 2015 The Polymer Project Authors. All rights reserved. 5This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 6The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 7The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 8Code distributed by Google as part of the polymer project is also 9subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 10--> 11<html> 12<head> 13 <title>iron-location</title> 14 15 <script src="../../webcomponentsjs/webcomponents-lite.js"></script> 16 <script src="../../web-component-tester/browser.js"></script> 17 18 <link rel="import" href="../../polymer/polymer.html"> 19 <link rel="import" href="../../promise-polyfill/promise-polyfill.html"> 20 <link rel="import" href="../iron-location.html"> 21 <link rel="import" href="./redirection.html"> 22 <style> 23 #safari-cooldown { 24 font-size: 18px; 25 max-width: 300px; 26 } 27 #safari-cooldown div { 28 margin-bottom: 20px; 29 } 30 </style> 31</head> 32<body> 33 34 <div id='safari-cooldown' hidden> 35 <div>Danger: URL overheating.</div> 36 <div> 37 Safari requires a cooldown period before we call 38 pushState/replaceState any more. 39 </div> 40 <div>Testing will resume after 30 seconds.</div> 41 <div> 42 <a href="https://forums.developer.apple.com/thread/36650">More info</a> 43 </div> 44 </div> 45 46<test-fixture id='Solo'> 47 <template> 48 <iron-location></iron-location> 49 </template> 50</test-fixture> 51<test-fixture id='Together'> 52 <template> 53 <div> 54 <iron-location id='one'></iron-location> 55 <iron-location id='two'></iron-location> 56 </div> 57 </template> 58</test-fixture> 59<test-fixture id='RedirectHash'> 60 <template> 61 <redirect-hash></redirect-hash> 62 </template> 63</test-fixture> 64<test-fixture id='RedirectPath'> 65 <template> 66 <redirect-path></redirect-path> 67 </template> 68</test-fixture> 69<test-fixture id='RedirectQuery'> 70 <template> 71 <redirect-query></redirect-query> 72 </template> 73</test-fixture> 74 75<script> 76 'use strict'; 77 78 function timePasses(ms) { 79 return new Promise(function(resolve) { 80 window.setTimeout(function() { 81 resolve(); 82 }, ms); 83 }); 84 } 85 86 function makeAbsoluteUrl(path) { 87 return window.location.protocol + '//' + window.location.host + path; 88 } 89 90 // A window.history.replaceState wrapper that's smart about baseURI. 91 function replaceState(path) { 92 window.history.replaceState({}, '', makeAbsoluteUrl(path)); 93 } 94 95 /** 96 * We run the iron location tests with a couple different page configurations 97 * (e.g. with and withoug a base URI), so we define the test set as a function 98 * that we call in each of these configurations. 99 */ 100 function ironLocationTests() { 101 suite('when used solo', function() { 102 var urlElem; 103 var toRemove; 104 setup(function() { 105 replaceState('/'); 106 urlElem = fixture('Solo'); 107 toRemove = []; 108 }); 109 teardown(function() { 110 for (var i = 0; i < toRemove.length; i++) { 111 document.body.removeChild(toRemove[i]); 112 } 113 }); 114 test('basic functionality with #hash urls', function() { 115 // Initialized to the window location's hash. 116 expect(window.location.hash).to.be.equal(urlElem.hash); 117 118 // Changing the urlElem's hash changes the URL 119 urlElem.hash = '/lol/ok'; 120 expect(window.location.hash).to.be.equal('#/lol/ok'); 121 122 // Changing the hash via normal means changes the urlElem. 123 var anchor = document.createElement('a'); 124 var base = 125 window.location.protocol + '//' + window.location.host + 126 window.location.pathname; 127 anchor.href = base + '#/wat'; 128 document.body.appendChild(anchor); 129 anchor.click(); 130 // In IE10 it appears that the hashchange event is asynchronous. 131 return timePasses(10).then(function() { 132 expect(urlElem.hash).to.be.equal('/wat'); 133 }); 134 }); 135 test('basic functionality with paths', function() { 136 // Initialized to the window location's path. 137 expect(window.location.pathname).to.be.equal(urlElem.path); 138 139 // Changing the urlElem's path changes the URL 140 urlElem.path = '/foo/bar'; 141 expect(window.location.pathname).to.be.equal('/foo/bar'); 142 143 // Changing the path and sending a custom event on the window changes 144 // the urlElem. 145 replaceState('/baz'); 146 window.dispatchEvent(new CustomEvent('location-changed')); 147 expect(urlElem.path).to.be.equal('/baz'); 148 }); 149 function makeTemporaryIronLocation() { 150 var ironLocation = document.createElement('iron-location'); 151 document.body.appendChild(ironLocation); 152 toRemove.push(ironLocation); 153 return ironLocation 154 } 155 test('dealing with paths with unusual characters', function() { 156 var pathEncodingExamples = { 157 '/foo': '/foo', 158 '/': '/', 159 '/foo bar': '/foo%20bar', 160 '/foo#bar': '/foo%23bar', 161 '/foo?xyz': '/foo%3Fxyz', 162 '/foo\'bar\'baz': '/foo\'bar\'baz', 163 }; 164 165 for (var plainTextPath in pathEncodingExamples) { 166 var encodedPath = pathEncodingExamples[plainTextPath]; 167 168 urlElem.path = plainTextPath; 169 expect(window.location.pathname).to.be.equal(encodedPath); 170 expect(urlElem.path).to.be.equal(plainTextPath); 171 var temporaryIronLocation = makeTemporaryIronLocation(); 172 expect(temporaryIronLocation.path).to.be.equal(plainTextPath); 173 } 174 }); 175 test('dealing with hashes with unusual characters', function() { 176 var hashEncodingExamples = { 177 'foo': '#foo', 178 '': '', 179 'foo bar': ['#foo%20bar', '#foo bar'], 180 'foo#bar': '#foo#bar', 181 'foo?bar': '#foo?bar', 182 '/foo\'bar\'baz': ['#/foo%27bar%27baz', '#/foo\'bar\'baz'], 183 }; 184 for (var plainTextHash in hashEncodingExamples) { 185 var encodedHashes = hashEncodingExamples[plainTextHash]; 186 if (typeof encodedHashes === 'string') { 187 encodedHashes = [encodedHashes]; 188 } 189 190 urlElem.hash = plainTextHash; 191 expect(encodedHashes).to.contain(window.location.hash); 192 expect(urlElem.hash).to.be.equal(plainTextHash); 193 expect(makeTemporaryIronLocation().hash).to.be.equal(plainTextHash); 194 } 195 }); 196 test('dealing with queries with unusual characters', function() { 197 var queryEncodingExamples = { 198 'foo': '?foo', 199 '': '', 200 'foo bar': '?foo%20bar', 201 'foo#bar': '?foo%23bar', 202 'foo?bar': '?foo?bar', 203 '/foo\'bar\'baz': ['?/foo%27bar%27baz', '?/foo\'bar\'baz'], 204 'foo/bar/baz': '?foo/bar/baz' 205 }; 206 for (var plainTextQuery in queryEncodingExamples) { 207 var encodedQueries = queryEncodingExamples[plainTextQuery]; 208 if (typeof encodedQueries === 'string') { 209 encodedQueries = [encodedQueries]; 210 } 211 212 var ironLocationQuery = encodedQueries.map(function(value) { 213 return value.substring(1); 214 }); 215 216 expect(urlElem._initialized).to.be.eq(true); 217 urlElem.query = plainTextQuery; 218 expect(encodedQueries).to.contain(window.location.search); 219 expect(ironLocationQuery).to.contain(urlElem.query); 220 expect(ironLocationQuery).to.contain(makeTemporaryIronLocation().query); 221 222 urlElem.query = 'dummyValue'; 223 urlElem.query = ironLocationQuery[0]; 224 225 expect(encodedQueries).to.contain(window.location.search); 226 expect(ironLocationQuery).to.contain(urlElem.query); 227 expect(ironLocationQuery).to.contain(makeTemporaryIronLocation().query); 228 } 229 }); 230 test('assigning to a relative path URL', function() { 231 urlElem.path = '/foo/bar'; 232 expect(window.location.pathname).to.be.equal('/foo/bar'); 233 234 // A relative path is treated as an absolute one, just with a 235 // missing leading slash. 236 urlElem.path = 'baz'; 237 expect(window.location.pathname).to.be.equal('/baz'); 238 }); 239 test('basic functionality with ?key=value params', function() { 240 // Initialized to the window location's params. 241 expect(urlElem.query).to.be.eq(''); 242 243 // Changing location.search and sending a custom event on the window 244 // changes the urlElem. 245 replaceState('/?greeting=hello&target=world'); 246 window.dispatchEvent(new CustomEvent('location-changed')); 247 expect(urlElem.query).to.be.equal('greeting=hello&target=world'); 248 249 // Changing the urlElem's query changes the URL. 250 urlElem.query = 'greeting=hello&target=world&another=key'; 251 expect(window.location.search).to.be.equal( 252 '?greeting=hello&target=world&another=key'); 253 }); 254 }); 255 suite('does not spam the user\'s history', function() { 256 var replaceStateCalls, pushStateCalls; 257 var nativeReplaceState, nativePushState; 258 setup(function() { 259 replaceStateCalls = pushStateCalls = 0; 260 nativeReplaceState = window.history.replaceState; 261 nativePushState = window.history.pushState; 262 window.history.replaceState = function() { 263 replaceStateCalls++; 264 }; 265 window.history.pushState = function() { 266 pushStateCalls++; 267 }; 268 }); 269 teardown(function() { 270 window.history.replaceState = nativeReplaceState; 271 window.history.pushState = nativePushState; 272 }); 273 test('when a change happens immediately after ' + 274 'the iron-location is attached', function() { 275 var ironLocation = fixture('Solo'); 276 expect(pushStateCalls).to.be.equal(0); 277 expect(replaceStateCalls).to.be.equal(0); 278 ironLocation.path = '/foo'; 279 expect(replaceStateCalls).to.be.equal(1); 280 expect(pushStateCalls).to.be.equal(0); 281 }); 282 283 suite('when intercepting links', function() { 284 /** 285 * Clicks the given link while an iron-location element with the given 286 * urlSpaceRegex is in the same document. Returns whether the 287 * iron-location prevented the default behavior of the click. 288 * 289 * No matter what, it prevents the default behavior of the click itself 290 * to ensure that no navigation occurs (as that would interrupt 291 * running and reporting these tests!) 292 */ 293 function isClickCaptured(anchor, urlSpaceRegex) { 294 var defaultWasPrevented; 295 function handler(event) { 296 expect(event.target).to.be.eq(anchor); 297 defaultWasPrevented = event.defaultPrevented; 298 event.preventDefault(); 299 expect(event.defaultPrevented).to.be.eq(true); 300 } 301 window.addEventListener('click', handler); 302 var ironLocation = fixture('Solo'); 303 if (urlSpaceRegex != null) { 304 ironLocation.urlSpaceRegex = urlSpaceRegex; 305 } 306 document.body.appendChild(anchor); 307 anchor.click(); 308 document.body.removeChild(anchor); 309 window.removeEventListener('click', handler); 310 return defaultWasPrevented; 311 } 312 313 test('simple link to / is intercepted', function() { 314 var anchor = document.createElement('a'); 315 if (document.baseURI !== window.location.href) { 316 anchor.href = makeAbsoluteUrl('/'); 317 } else { 318 anchor.href = '/'; 319 } 320 321 expect(isClickCaptured(anchor)).to.be.eq(true); 322 expect(pushStateCalls).to.be.equal(1); 323 }); 324 325 test('link that matches url space is intercepted', function() { 326 var anchor = document.createElement('a'); 327 anchor.href = makeAbsoluteUrl('/foo'); 328 329 expect(isClickCaptured(anchor, '/fo+')).to.be.eq(true); 330 expect(pushStateCalls).to.be.equal(1); 331 }); 332 333 test('link that doesn\'t match url space isn\'t intercepted', function() { 334 var anchor = document.createElement('a'); 335 anchor.href = makeAbsoluteUrl('/bar'); 336 337 expect(isClickCaptured(anchor, '/fo+')).to.be.eq(false); 338 expect(pushStateCalls).to.be.equal(0); 339 }); 340 341 test('link to another domain isn\'t intercepted', function() { 342 var anchor = document.createElement('a'); 343 anchor.href = 'http://example.com/'; 344 345 expect(isClickCaptured(anchor)).to.be.eq(false); 346 expect(pushStateCalls).to.be.equal(0); 347 }); 348 349 test('a link with target=_blank isn\'t intercepted', function() { 350 var anchor = document.createElement('a'); 351 anchor.href = makeAbsoluteUrl('/'); 352 anchor.target = '_blank'; 353 354 expect(isClickCaptured(anchor)).to.be.eq(false); 355 expect(pushStateCalls).to.be.equal(0); 356 }); 357 358 test('a link with an href to the ' + 359 'current page shouldn\'t add to history.', function() { 360 var anchor = document.createElement('a'); 361 anchor.href = window.location.href; 362 363 // The click is captured, but it doesn't add to history. 364 expect(isClickCaptured(anchor)).to.be.equal(true); 365 expect(pushStateCalls).to.be.equal(0); 366 }); 367 368 test('a click that has already been defaultPrevented ' + 369 'shouldn\'t result in a navigation', function() { 370 fixture('Solo'); 371 var anchor = document.createElement('a'); 372 anchor.href = makeAbsoluteUrl('/'); 373 anchor.addEventListener('click', function(event) { 374 event.preventDefault(); 375 }); 376 document.body.appendChild(anchor); 377 378 var originalPushState = window.history.pushState; 379 var count = 0; 380 window.history.pushState = function() { 381 count++; 382 } 383 anchor.click(); 384 window.history.pushState = originalPushState; 385 386 expect(count).to.be.equal(0); 387 }) 388 }); 389 }); 390 suite('when used with other iron-location elements', function() { 391 var otherUrlElem; 392 var urlElem; 393 setup(function() { 394 var elems = fixture('Together'); 395 urlElem = elems.querySelector('#one'); 396 otherUrlElem = elems.querySelector('#two'); 397 }); 398 function assertHaveSameUrls(urlElemOne, urlElemTwo) { 399 expect(urlElemOne.path).to.be.equal(urlElemTwo.path); 400 expect(urlElemOne.hash).to.be.equal(urlElemTwo.hash); 401 expect(urlElemOne.query).to.be.equal(urlElemTwo.query); 402 } 403 test('coordinate their changes (by firing events on window)', function() { 404 assertHaveSameUrls(urlElem, otherUrlElem); 405 406 urlElem.path = '/a/b/c'; 407 assertHaveSameUrls(urlElem, otherUrlElem); 408 409 otherUrlElem.query = 'alsdkjflaksjfd=alksjfdlkajsdfl'; 410 assertHaveSameUrls(urlElem, otherUrlElem); 411 412 urlElem.hash = 'lkjljifosjkldfjlksjfldsjf'; 413 assertHaveSameUrls(urlElem, otherUrlElem); 414 }); 415 }); 416 417 suite('supports doing synchronous redirection', function() { 418 test('of the hash portion of the URL', function() { 419 expect(window.location.hash).to.be.equal(''); 420 var redirector = fixture('RedirectHash'); 421 expect(window.location.hash).to.be.equal('#redirectedTo'); 422 expect(redirector.hash).to.be.equal('redirectedTo'); 423 redirector.hash = 'newHash'; 424 expect(window.location.hash).to.be.equal('#redirectedTo'); 425 expect(redirector.hash).to.be.equal('redirectedTo'); 426 }); 427 428 test('of the path portion of the URL', function() { 429 expect(window.location.pathname).to.not.be.equal('/redirectedTo'); 430 var redirector = fixture('RedirectPath'); 431 expect(window.location.pathname).to.be.equal('/redirectedTo'); 432 expect(redirector.path).to.be.equal('/redirectedTo'); 433 redirector.path = '/newPath'; 434 expect(window.location.pathname).to.be.equal('/redirectedTo'); 435 expect(redirector.path).to.be.equal('/redirectedTo'); 436 }); 437 438 test('of the query portion of the URL', function() { 439 expect(window.location.search).to.be.equal(''); 440 var redirector = fixture('RedirectQuery'); 441 expect(window.location.search).to.be.equal('?redirectedTo'); 442 expect(redirector.query).to.be.equal('redirectedTo'); 443 redirector.query = 'newQuery'; 444 expect(window.location.search).to.be.equal('?redirectedTo'); 445 expect(redirector.query).to.be.equal('redirectedTo'); 446 }); 447 }); 448 } 449 450 suite('<iron-location>', function () { 451 var initialUrl; 452 setup(function() { 453 initialUrl = window.location.href; 454 }); 455 teardown(function(){ 456 window.history.replaceState({}, '', initialUrl); 457 }); 458 459 // This is as dumb as it looks. See #safari-cooldown in the dom above. 460 var cooldownFunction = function() {}; 461 if (/^Apple/.test(navigator.vendor)) { 462 cooldownFunction = function(done) { 463 var cooldownPeriod = 30 * 1000; 464 this.timeout(cooldownPeriod + 5000); 465 var cooldownMessage = document.querySelector('#safari-cooldown'); 466 cooldownMessage.removeAttribute('hidden'); 467 setTimeout(function() { 468 done(); 469 cooldownMessage.setAttribute('hidden', 'hidden'); 470 }, cooldownPeriod); 471 }; 472 } 473 474 suite('without a base URI', function() { 475 ironLocationTests(); 476 477 suiteTeardown(cooldownFunction); 478 }); 479 480 suite('with a base URI', function() { 481 var baseElem; 482 setup(function() { 483 expect(document.baseURI).to.be.equal(window.location.href); 484 baseElem = document.createElement('base'); 485 var href = 'https://example.com/i/dont/exist/obviously' 486 baseElem.href = href; 487 document.head.appendChild(baseElem); 488 expect(document.baseURI).to.be.equal(href); 489 }); 490 teardown(function() { 491 document.head.removeChild(baseElem); 492 }); 493 suiteTeardown(cooldownFunction); 494 ironLocationTests(); 495 }); 496 }); 497 498</script> 499</body> 500