• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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