1<!doctype html> 2<!-- 3@license 4Copyright (c) 2016 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>app-route</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="../app-route.html"> 20 <link rel="import" href="./redirection.html"> 21</head> 22<body> 23 <test-fixture id="BasicRoute"> 24 <template> 25 <app-route pattern='/user/:username'> 26 </app-route> 27 </template> 28 </test-fixture> 29 30 <test-fixture id="ChainedRoutes"> 31 <template is="dom-template"> 32 <app-route 33 pattern="/foo/:foo" 34 route="{{numberOneTopRoute}}" 35 data="{{fooData}}" 36 tail="{{fooRoute}}"> 37 </app-route> 38 39 <app-route 40 pattern="/bar/:bar" 41 route="{{fooRoute}}" 42 data="{{barData}}"> 43 </app-route> 44 45 <app-route 46 pattern="/baz/:baz" 47 route="{{fooRoute}}" 48 data="{{bazData}}"> 49 </app-route> 50 </template> 51 </test-fixture> 52 53 <test-fixture id="Redirection"> 54 <template> 55 <redirect-app-route></redirect-app-route> 56 </template> 57 </test-fixture> 58 59<script> 60 'use strict'; 61 62 function fixtureChainedRoutes(route) { 63 var routes = fixture('ChainedRoutes', { 64 numberOneTopRoute: { 65 path: route.path || '', 66 prefix: route.prefix || '', 67 __queryParams: route.__queryParams || {} 68 } 69 }); 70 71 return { 72 foo: routes[0], 73 bar: routes[1], 74 baz: routes[2] 75 }; 76 } 77 78 suite('<app-route>', function () { 79 var route; 80 81 setup(function() { 82 route = fixture('BasicRoute'); 83 84 // This works around a bug in `dom-template` that is somehow 85 // exaserbated by the `app-route` implementation. A reduced test case 86 // is hard to come by. Track polymerelements/test-fixture#31 and remove 87 // this when that has been resolved: 88 var tmpl = document.querySelector('#ChainedRoutes').fixtureTemplates[0]; 89 tmpl._parentProps = {}; 90 }); 91 92 test('it parses a path', function() { 93 route.route = { 94 prefix: '', 95 path: '/user/papyrus/details', 96 __queryParams: {} 97 } 98 expect(route.tail.prefix).to.be.equal('/user/papyrus'); 99 expect(route.tail.path).to.be.equal('/details'); 100 expect(route.data.username).to.be.equal('papyrus'); 101 }); 102 103 test('it bidirectionally maps changes between tail and route', function() { 104 route.route = { 105 prefix: '', 106 path: '/user/papyrus/details', 107 __queryParams: {} 108 }; 109 110 route.set('tail.path', '/messages'); 111 expect(route.route.path).to.be.deep.equal('/user/papyrus/messages'); 112 route.set('route.path', '/user/toriel'); 113 expect(route.tail).to.be.deep.equal({ 114 prefix: '/user/toriel', 115 path: '', 116 __queryParams: {} 117 }); 118 }); 119 120 test('it creates data as described by pattern', function() { 121 route.route = { 122 prefix: '', 123 path: '/user/sans' 124 }; 125 126 expect(route.data).to.be.deep.equal({username: 'sans'}); 127 expect(route.active).to.be.equal(true); 128 129 route.pattern = '/user/:username/likes/:count'; 130 131 // At the moment, we don't reset data when we no longer match. 132 expect(route.data).to.be.deep.equal({username: 'sans'}); 133 expect(route.active).to.be.equal(false); 134 135 route.set('route.path', "/does/not/match"); 136 137 expect(route.data).to.be.deep.equal({username: 'sans'}); 138 expect(route.active).to.be.equal(false); 139 140 route.set('route.path', '/user/undyne/likes/20'); 141 expect(route.data).to.be.deep.equal({username: 'undyne', count: '20'}); 142 expect(route.active).to.be.equal(true); 143 }); 144 145 test('changing data changes the path', function() { 146 route.route = { 147 prefix: '', 148 path: '/user/asgore' 149 }; 150 151 expect(route.data).to.be.deep.equal({username: 'asgore'}); 152 route.data = {username: 'toriel'}; 153 expect(route.route.path).to.be.equal('/user/toriel'); 154 }); 155 156 suite('propagating data', function() { 157 test('data is empty if no routes in the tree have matched', function() { 158 var routes = fixtureChainedRoutes({ path: '' }); 159 160 expect(routes.foo.data).to.be.eql({}); 161 expect(routes.bar.data).to.be.eql({}); 162 expect(routes.baz.data).to.be.eql({}); 163 }); 164 165 test('limits propagation to last matched route', function() { 166 var routes = fixtureChainedRoutes({ path: '/foo/123' }); 167 168 expect(routes.foo.data).to.be.eql({ foo: '123' }); 169 expect(routes.bar.data).to.be.eql({}); 170 expect(routes.baz.data).to.be.eql({}); 171 }); 172 173 test('propagates data to matching chained routes', function() { 174 var routes = fixtureChainedRoutes({ path: '/foo/123/bar/abc' }); 175 176 expect(routes.foo.data).to.be.eql({ foo: '123' }); 177 expect(routes.bar.data).to.be.eql({ bar: 'abc' }); 178 expect(routes.baz.data).to.be.eql({}); 179 }); 180 181 test('chained route state is untouched when deactivated', function() { 182 var routes = fixtureChainedRoutes({ path: '/foo/123/bar/abc' }); 183 184 routes.foo.set('route.path', '/foo/321/baz/zyx'); 185 186 expect(routes.foo.data).to.be.eql({ foo: '321' }); 187 expect(routes.bar.data).to.be.eql({ bar: 'abc' }); 188 expect(routes.baz.data).to.be.eql({ baz: 'zyx' }); 189 }); 190 191 suite('updating the global path', function() { 192 test('happens when data changes if the route is active', function() { 193 var routes = fixtureChainedRoutes({ path: '/foo/123/bar/abc' }); 194 195 expect(routes.bar.active).to.be.eql(true); 196 routes.bar.set('data.bar', 'cba'); 197 expect(routes.foo.route.path).to.be.eql('/foo/123/bar/cba'); 198 }); 199 200 test('ignores changes when the route is inactive', function() { 201 var routes = fixtureChainedRoutes({ path: '/foo/123/bar/abc' }); 202 203 expect(routes.baz.active).to.be.eql(false); 204 routes.baz.set('data.baz', 'cba'); 205 expect(routes.foo.route.path).to.be.eql('/foo/123/bar/abc'); 206 }); 207 208 test('ignores changes after a route deactives', function() { 209 var routes = fixtureChainedRoutes({ path: '/foo/123/bar/abc' }); 210 211 routes.foo.set('route.path', '/foo/123/baz/zyx'); 212 213 expect(routes.bar.active).to.be.eql(false); 214 expect(routes.baz.active).to.be.eql(true); 215 routes.bar.set('data.bar', 'cba'); 216 expect(routes.foo.route.path).to.be.eql('/foo/123/baz/zyx'); 217 }); 218 }); 219 }); 220 221 suite('propagating query params', function() { 222 test('query params are empty if no routes match', function() { 223 var routes = fixtureChainedRoutes({ path: '', __queryParams: { 224 qux: 'zot' 225 }}); 226 expect(routes.foo.queryParams).to.be.eql({}); 227 expect(routes.bar.queryParams).to.be.eql({}); 228 expect(routes.baz.queryParams).to.be.eql({}); 229 }); 230 231 test('updates query params for all matched routes', function() { 232 var routes = fixtureChainedRoutes({ path: '/foo/123/bar/abc', __queryParams: { 233 qux: 'zot' 234 }}); 235 expect(routes.foo.queryParams).to.be.eql({ qux: 'zot' }); 236 expect(routes.bar.queryParams).to.be.eql({ qux: 'zot' }); 237 expect(routes.baz.queryParams).to.be.eql({}); 238 }); 239 240 test('retains query params after routes deactivate', function() { 241 var routes = fixtureChainedRoutes({ path: '/foo/123/bar/abc', __queryParams: { 242 qux: 'zot' 243 }}); 244 routes.foo.set('route.path', '/foo/123/baz/xyz') 245 routes.foo.set('queryParams', { 246 qux: 'quux' 247 }); 248 expect(routes.foo.queryParams).to.be.eql({ qux: 'quux' }); 249 expect(routes.bar.queryParams).to.be.eql({ qux: 'zot' }); 250 expect(routes.baz.queryParams).to.be.eql({ qux: 'quux' }); 251 }); 252 253 suite('updating global query params', function() { 254 test('happens when query params change on active routes', function() { 255 var routes = fixtureChainedRoutes({ path: '/foo/123/bar/abc', __queryParams: { 256 qux: 'zot' 257 }}); 258 259 routes.bar.set('queryParams', { qux: 'quux' }); 260 261 expect(routes.foo.queryParams).to.be.eql({ qux: 'quux' }); 262 expect(routes.bar.queryParams).to.be.eql({ qux: 'quux' }); 263 expect(routes.baz.queryParams).to.be.eql({}); 264 }); 265 266 test('updates are ignored for routes that are inactive', function() { 267 var routes = fixtureChainedRoutes({ path: '/foo/123/bar/abc', __queryParams: { 268 qux: 'zot' 269 }}); 270 271 routes.baz.set('queryParams', { qux: 'quux' }); 272 273 expect(routes.foo.queryParams).to.be.eql({ qux: 'zot' }); 274 expect(routes.bar.queryParams).to.be.eql({ qux: 'zot' }); 275 expect(routes.baz.queryParams).to.be.eql({ qux: 'quux' }); 276 }); 277 278 test('doesn\'t generate excess query-params-changed events', function() { 279 var routes = fixtureChainedRoutes({}); 280 var appRoutes = [routes.foo, routes.bar, routes.baz]; 281 var numChanges = 0; 282 for (var i = 0; i < appRoutes.length; i++) { 283 appRoutes[i].addEventListener('query-params-changed', function() { 284 numChanges++; 285 }); 286 } 287 288 // Messing with paths but not query params shouldn't generate any 289 // change events. 290 expect(numChanges).to.be.equal(0); 291 routes.foo.set('route.path', '/foo/123/bar/456'); 292 expect(numChanges).to.be.equal(0); 293 routes.foo.set('route.path', '/foo/456/baz/789'); 294 expect(numChanges).to.be.equal(0); 295 296 // Changing queryParams here should update foo and baz 297 routes.foo.set('route.__queryParams', {key: 'value'}); 298 expect(numChanges).to.be.equal(2); 299 // Then this should update bar 300 routes.foo.set('route.path', '/foo/123/bar/456'); 301 expect(numChanges).to.be.equal(3); 302 303 // Changing back to baz shouldn't generate a change event. 304 routes.foo.set('route.path', '/foo/456/baz/789'); 305 expect(numChanges).to.be.equal(3); 306 307 routes.foo.set('route.__queryParams', {}); 308 expect(numChanges).to.be.equal(5); 309 routes.foo.set('route.path', '/foo/123/bar/456'); 310 expect(numChanges).to.be.equal(6); 311 312 }); 313 }); 314 }); 315 316 suite('handles reentrent changes to its properties', function() { 317 var initialUrl; 318 setup(function() { 319 initialUrl = window.location.href; 320 }); 321 322 teardown(function() { 323 window.history.replaceState({}, '', initialUrl); 324 }); 325 326 test('changing path in response to path changing', function() { 327 var r = fixture('Redirection'); 328 r.addEventListener('route-changed', function() { 329 r.set('route.path', '/bar/baz'); 330 }); 331 r.set('route.path', '/foo'); 332 expect(window.location.pathname).to.be.equal('/bar/baz'); 333 expect(r.data).to.be.deep.equal({page: 'bar'}); 334 expect(r.route.path).to.be.equal('/bar/baz'); 335 expect(r.tail.path).to.be.equal('/baz'); 336 }); 337 338 test('changing data wholesale in response to path changing', function() { 339 var r = fixture('Redirection'); 340 r.set('data.page', 'bar'); 341 r.addEventListener('route-changed', function(e) { 342 if (e.detail.path === 'route.path' && r.route.path === '/foo/baz') { 343 r.data = {page: 'bar'}; 344 } 345 }); 346 r.set('route.path', '/foo/baz'); 347 expect(window.location.pathname).to.be.equal('/bar'); 348 expect(r.data).to.be.deep.equal({page: 'bar'}); 349 expect(r.route.path).to.be.equal('/bar'); 350 expect(r.tail.path).to.be.equal(''); 351 }); 352 353 test('changing a data piece in response to path changing', function() { 354 var r = fixture('Redirection'); 355 r.set('data.page', 'bar'); 356 r.addEventListener('route-changed', function(e) { 357 r.set('data.page', 'bar'); 358 }); 359 r.set('route.path', '/foo/baz'); 360 expect(window.location.pathname).to.be.equal('/bar'); 361 expect(r.data).to.be.deep.equal({page: 'bar'}); 362 expect(r.route.path).to.be.equal('/bar'); 363 expect(r.tail.path).to.be.equal(''); 364 }); 365 366 test('changing the tail in response to path changing', function() { 367 var r = fixture('Redirection'); 368 r.addEventListener('route-changed', function() { 369 r.set('tail.path', '/bar'); 370 }); 371 r.set('route.path', '/foo'); 372 expect(window.location.pathname).to.be.equal('/foo/bar'); 373 expect(r.data).to.be.deep.equal({page: 'foo'}); 374 expect(r.route.path).to.be.equal('/foo/bar'); 375 expect(r.tail.path).to.be.equal('/bar'); 376 377 r.set('route.path', '/foo/baz'); 378 expect(window.location.pathname).to.be.equal('/foo/bar'); 379 expect(r.data).to.be.deep.equal({page: 'foo'}); 380 expect(r.route.path).to.be.equal('/foo/bar'); 381 expect(r.tail.path).to.be.equal('/bar'); 382 }); 383 384 test('changing the path in response to data changing', function() { 385 var r = fixture('Redirection'); 386 r.addEventListener('data-changed', function() { 387 r.set('route.path', '/bar'); 388 }); 389 r.set('data', {page: 'foo'}); 390 expect(window.location.pathname).to.be.equal('/bar'); 391 expect(r.data).to.be.deep.equal({page: 'bar'}); 392 expect(r.route.path).to.be.equal('/bar'); 393 expect(r.tail.path).to.be.equal(''); 394 }); 395 396 test('changing data in response to data changing', function() { 397 var r = fixture('Redirection'); 398 r.addEventListener('data-changed', function() { 399 r.set('data.page', 'bar'); 400 }); 401 r.set('data', {page: 'foo'}); 402 expect(window.location.pathname).to.be.equal('/bar'); 403 expect(r.data).to.be.deep.equal({page: 'bar'}); 404 expect(r.route.path).to.be.equal('/bar'); 405 expect(r.tail.path).to.be.equal(''); 406 }); 407 408 test('changing the data object wholesale in response to data changing', function() { 409 var r = fixture('Redirection'); 410 r.addEventListener('data-changed', function() { 411 if (r.data.page == 'foo') { 412 r.set('data', {page: 'bar'}); 413 } 414 }); 415 r.set('data', {page: 'foo'}); 416 expect(window.location.pathname).to.be.equal('/bar'); 417 expect(r.data).to.be.deep.equal({page: 'bar'}); 418 expect(r.route.path).to.be.equal('/bar'); 419 expect(r.tail.path).to.be.equal(''); 420 }); 421 422 test('changing the tail in response to data changing', function() { 423 var r = fixture('Redirection'); 424 r.addEventListener('data-changed', function() { 425 r.set('tail.path', '/bar'); 426 }); 427 r.set('data', {page: 'foo'}); 428 expect(window.location.pathname).to.be.equal('/foo/bar'); 429 expect(r.data).to.be.deep.equal({page: 'foo'}); 430 expect(r.route.path).to.be.equal('/foo/bar'); 431 expect(r.tail.path).to.be.equal('/bar'); 432 }); 433 434 test('changing the path in response to tail changing', function() { 435 var r = fixture('Redirection'); 436 r.set('route.path', '/foo/'); 437 r.addEventListener('tail-changed', function() { 438 r.set('route.path', '/baz' + r.tail.path); 439 }); 440 r.set('tail.path', '/bar'); 441 expect(window.location.pathname).to.be.equal('/baz/bar'); 442 expect(r.data).to.be.deep.equal({page: 'baz'}); 443 expect(r.route.path).to.be.equal('/baz/bar'); 444 expect(r.tail.path).to.be.equal('/bar'); 445 }); 446 447 test('changing the data in response to tail changing', function() { 448 var r = fixture('Redirection'); 449 r.set('route.path', '/foo/'); 450 r.addEventListener('tail-changed', function() { 451 r.set('data.page', 'baz'); 452 }); 453 r.set('tail.path', '/bar'); 454 expect(window.location.pathname).to.be.equal('/baz'); 455 expect(r.data).to.be.deep.equal({page: 'baz'}); 456 expect(r.route.path).to.be.equal('/baz'); 457 expect(r.tail.path).to.be.equal(''); 458 }); 459 460 test('changing the data object wholesale in response to tail changing', function() { 461 var r = fixture('Redirection'); 462 r.set('route.path', '/foo/'); 463 r.addEventListener('tail-changed', function() { 464 r.set('data', {page: 'baz'}); 465 }); 466 r.set('tail.path', '/bar'); 467 expect(window.location.pathname).to.be.equal('/baz'); 468 expect(r.data).to.be.deep.equal({page: 'baz'}); 469 expect(r.route.path).to.be.equal('/baz'); 470 expect(r.tail.path).to.be.equal(''); 471 }); 472 473 test('changing the tail in response to tail changing', function() { 474 var r = fixture('Redirection'); 475 r.set('route.path', '/foo/'); 476 r.addEventListener('tail-changed', function() { 477 r.set('tail.path', '/baz'); 478 }); 479 r.set('tail.path', '/bar'); 480 expect(window.location.pathname).to.be.equal('/foo/baz'); 481 expect(r.data).to.be.deep.equal({page: 'foo'}); 482 expect(r.route.path).to.be.equal('/foo/baz'); 483 expect(r.tail.path).to.be.equal('/baz'); 484 }); 485 }); 486 }); 487</script> 488</body> 489