1(function(QUnit) {
2
3  var router = null;
4  var location = null;
5  var lastRoute = null;
6  var lastArgs = [];
7
8  var onRoute = function(routerParam, route, args) {
9    lastRoute = route;
10    lastArgs = args;
11  };
12
13  var Location = function(href) {
14    this.replace(href);
15  };
16
17  _.extend(Location.prototype, {
18
19    parser: document.createElement('a'),
20
21    replace: function(href) {
22      this.parser.href = href;
23      _.extend(this, _.pick(this.parser,
24        'href',
25        'hash',
26        'host',
27        'search',
28        'fragment',
29        'pathname',
30        'protocol'
31     ));
32
33      // In IE, anchor.pathname does not contain a leading slash though
34      // window.location.pathname does.
35      if (!/^\//.test(this.pathname)) this.pathname = '/' + this.pathname;
36    },
37
38    toString: function() {
39      return this.href;
40    }
41
42  });
43
44  QUnit.module('Backbone.Router', {
45
46    beforeEach: function() {
47      location = new Location('http://example.com');
48      Backbone.history = _.extend(new Backbone.History, {location: location});
49      router = new Router({testing: 101});
50      Backbone.history.interval = 9;
51      Backbone.history.start({pushState: false});
52      lastRoute = null;
53      lastArgs = [];
54      Backbone.history.on('route', onRoute);
55    },
56
57    afterEach: function() {
58      Backbone.history.stop();
59      Backbone.history.off('route', onRoute);
60    }
61
62  });
63
64  var ExternalObject = {
65    value: 'unset',
66
67    routingFunction: function(value) {
68      this.value = value;
69    }
70  };
71  ExternalObject.routingFunction = _.bind(ExternalObject.routingFunction, ExternalObject);
72
73  var Router = Backbone.Router.extend({
74
75    count: 0,
76
77    routes: {
78      'noCallback': 'noCallback',
79      'counter': 'counter',
80      'search/:query': 'search',
81      'search/:query/p:page': 'search',
82      'charñ': 'charUTF',
83      'char%C3%B1': 'charEscaped',
84      'contacts': 'contacts',
85      'contacts/new': 'newContact',
86      'contacts/:id': 'loadContact',
87      'route-event/:arg': 'routeEvent',
88      'optional(/:item)': 'optionalItem',
89      'named/optional/(y:z)': 'namedOptional',
90      'splat/*args/end': 'splat',
91      ':repo/compare/*from...*to': 'github',
92      'decode/:named/*splat': 'decode',
93      '*first/complex-*part/*rest': 'complex',
94      'query/:entity': 'query',
95      'function/:value': ExternalObject.routingFunction,
96      '*anything': 'anything'
97    },
98
99    preinitialize: function(options) {
100      this.testpreinit = 'foo';
101    },
102
103    initialize: function(options) {
104      this.testing = options.testing;
105      this.route('implicit', 'implicit');
106    },
107
108    counter: function() {
109      this.count++;
110    },
111
112    implicit: function() {
113      this.count++;
114    },
115
116    search: function(query, page) {
117      this.query = query;
118      this.page = page;
119    },
120
121    charUTF: function() {
122      this.charType = 'UTF';
123    },
124
125    charEscaped: function() {
126      this.charType = 'escaped';
127    },
128
129    contacts: function() {
130      this.contact = 'index';
131    },
132
133    newContact: function() {
134      this.contact = 'new';
135    },
136
137    loadContact: function() {
138      this.contact = 'load';
139    },
140
141    optionalItem: function(arg) {
142      this.arg = arg !== void 0 ? arg : null;
143    },
144
145    splat: function(args) {
146      this.args = args;
147    },
148
149    github: function(repo, from, to) {
150      this.repo = repo;
151      this.from = from;
152      this.to = to;
153    },
154
155    complex: function(first, part, rest) {
156      this.first = first;
157      this.part = part;
158      this.rest = rest;
159    },
160
161    query: function(entity, args) {
162      this.entity    = entity;
163      this.queryArgs = args;
164    },
165
166    anything: function(whatever) {
167      this.anything = whatever;
168    },
169
170    namedOptional: function(z) {
171      this.z = z;
172    },
173
174    decode: function(named, path) {
175      this.named = named;
176      this.path = path;
177    },
178
179    routeEvent: function(arg) {
180    }
181
182  });
183
184  QUnit.test('initialize', function(assert) {
185    assert.expect(1);
186    assert.equal(router.testing, 101);
187  });
188
189  QUnit.test('preinitialize', function(assert) {
190    assert.expect(1);
191    assert.equal(router.testpreinit, 'foo');
192  });
193
194  QUnit.test('routes (simple)', function(assert) {
195    assert.expect(4);
196    location.replace('http://example.com#search/news');
197    Backbone.history.checkUrl();
198    assert.equal(router.query, 'news');
199    assert.equal(router.page, void 0);
200    assert.equal(lastRoute, 'search');
201    assert.equal(lastArgs[0], 'news');
202  });
203
204  QUnit.test('routes (simple, but unicode)', function(assert) {
205    assert.expect(4);
206    location.replace('http://example.com#search/тест');
207    Backbone.history.checkUrl();
208    assert.equal(router.query, 'тест');
209    assert.equal(router.page, void 0);
210    assert.equal(lastRoute, 'search');
211    assert.equal(lastArgs[0], 'тест');
212  });
213
214  QUnit.test('routes (two part)', function(assert) {
215    assert.expect(2);
216    location.replace('http://example.com#search/nyc/p10');
217    Backbone.history.checkUrl();
218    assert.equal(router.query, 'nyc');
219    assert.equal(router.page, '10');
220  });
221
222  QUnit.test('routes via navigate', function(assert) {
223    assert.expect(2);
224    Backbone.history.navigate('search/manhattan/p20', {trigger: true});
225    assert.equal(router.query, 'manhattan');
226    assert.equal(router.page, '20');
227  });
228
229  QUnit.test('routes via navigate with params', function(assert) {
230    assert.expect(1);
231    Backbone.history.navigate('query/test?a=b', {trigger: true});
232    assert.equal(router.queryArgs, 'a=b');
233  });
234
235  QUnit.test('routes via navigate for backwards-compatibility', function(assert) {
236    assert.expect(2);
237    Backbone.history.navigate('search/manhattan/p20', true);
238    assert.equal(router.query, 'manhattan');
239    assert.equal(router.page, '20');
240  });
241
242  QUnit.test('reports matched route via nagivate', function(assert) {
243    assert.expect(1);
244    assert.ok(Backbone.history.navigate('search/manhattan/p20', true));
245  });
246
247  QUnit.test('route precedence via navigate', function(assert) {
248    assert.expect(6);
249
250    // Check both 0.9.x and backwards-compatibility options
251    _.each([{trigger: true}, true], function(options) {
252      Backbone.history.navigate('contacts', options);
253      assert.equal(router.contact, 'index');
254      Backbone.history.navigate('contacts/new', options);
255      assert.equal(router.contact, 'new');
256      Backbone.history.navigate('contacts/foo', options);
257      assert.equal(router.contact, 'load');
258    });
259  });
260
261  QUnit.test('loadUrl is not called for identical routes.', function(assert) {
262    assert.expect(0);
263    Backbone.history.loadUrl = function() { assert.ok(false); };
264    location.replace('http://example.com#route');
265    Backbone.history.navigate('route');
266    Backbone.history.navigate('/route');
267    Backbone.history.navigate('/route');
268  });
269
270  QUnit.test('use implicit callback if none provided', function(assert) {
271    assert.expect(1);
272    router.count = 0;
273    router.navigate('implicit', {trigger: true});
274    assert.equal(router.count, 1);
275  });
276
277  QUnit.test('routes via navigate with {replace: true}', function(assert) {
278    assert.expect(1);
279    location.replace('http://example.com#start_here');
280    Backbone.history.checkUrl();
281    location.replace = function(href) {
282      assert.strictEqual(href, new Location('http://example.com#end_here').href);
283    };
284    Backbone.history.navigate('end_here', {replace: true});
285  });
286
287  QUnit.test('routes (splats)', function(assert) {
288    assert.expect(1);
289    location.replace('http://example.com#splat/long-list/of/splatted_99args/end');
290    Backbone.history.checkUrl();
291    assert.equal(router.args, 'long-list/of/splatted_99args');
292  });
293
294  QUnit.test('routes (github)', function(assert) {
295    assert.expect(3);
296    location.replace('http://example.com#backbone/compare/1.0...braddunbar:with/slash');
297    Backbone.history.checkUrl();
298    assert.equal(router.repo, 'backbone');
299    assert.equal(router.from, '1.0');
300    assert.equal(router.to, 'braddunbar:with/slash');
301  });
302
303  QUnit.test('routes (optional)', function(assert) {
304    assert.expect(2);
305    location.replace('http://example.com#optional');
306    Backbone.history.checkUrl();
307    assert.ok(!router.arg);
308    location.replace('http://example.com#optional/thing');
309    Backbone.history.checkUrl();
310    assert.equal(router.arg, 'thing');
311  });
312
313  QUnit.test('routes (complex)', function(assert) {
314    assert.expect(3);
315    location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven');
316    Backbone.history.checkUrl();
317    assert.equal(router.first, 'one/two/three');
318    assert.equal(router.part, 'part');
319    assert.equal(router.rest, 'four/five/six/seven');
320  });
321
322  QUnit.test('routes (query)', function(assert) {
323    assert.expect(5);
324    location.replace('http://example.com#query/mandel?a=b&c=d');
325    Backbone.history.checkUrl();
326    assert.equal(router.entity, 'mandel');
327    assert.equal(router.queryArgs, 'a=b&c=d');
328    assert.equal(lastRoute, 'query');
329    assert.equal(lastArgs[0], 'mandel');
330    assert.equal(lastArgs[1], 'a=b&c=d');
331  });
332
333  QUnit.test('routes (anything)', function(assert) {
334    assert.expect(1);
335    location.replace('http://example.com#doesnt-match-a-route');
336    Backbone.history.checkUrl();
337    assert.equal(router.anything, 'doesnt-match-a-route');
338  });
339
340  QUnit.test('routes (function)', function(assert) {
341    assert.expect(3);
342    router.on('route', function(name) {
343      assert.ok(name === '');
344    });
345    assert.equal(ExternalObject.value, 'unset');
346    location.replace('http://example.com#function/set');
347    Backbone.history.checkUrl();
348    assert.equal(ExternalObject.value, 'set');
349  });
350
351  QUnit.test('Decode named parameters, not splats.', function(assert) {
352    assert.expect(2);
353    location.replace('http://example.com#decode/a%2Fb/c%2Fd/e');
354    Backbone.history.checkUrl();
355    assert.strictEqual(router.named, 'a/b');
356    assert.strictEqual(router.path, 'c/d/e');
357  });
358
359  QUnit.test('fires event when router doesn\'t have callback on it', function(assert) {
360    assert.expect(1);
361    router.on('route:noCallback', function() { assert.ok(true); });
362    location.replace('http://example.com#noCallback');
363    Backbone.history.checkUrl();
364  });
365
366  QUnit.test('No events are triggered if #execute returns false.', function(assert) {
367    assert.expect(1);
368    var MyRouter = Backbone.Router.extend({
369
370      routes: {
371        foo: function() {
372          assert.ok(true);
373        }
374      },
375
376      execute: function(callback, args) {
377        callback.apply(this, args);
378        return false;
379      }
380
381    });
382
383    var myRouter = new MyRouter;
384
385    myRouter.on('route route:foo', function() {
386      assert.ok(false);
387    });
388
389    Backbone.history.on('route', function() {
390      assert.ok(false);
391    });
392
393    location.replace('http://example.com#foo');
394    Backbone.history.checkUrl();
395  });
396
397  QUnit.test('#933, #908 - leading slash', function(assert) {
398    assert.expect(2);
399    location.replace('http://example.com/root/foo');
400
401    Backbone.history.stop();
402    Backbone.history = _.extend(new Backbone.History, {location: location});
403    Backbone.history.start({root: '/root', hashChange: false, silent: true});
404    assert.strictEqual(Backbone.history.getFragment(), 'foo');
405
406    Backbone.history.stop();
407    Backbone.history = _.extend(new Backbone.History, {location: location});
408    Backbone.history.start({root: '/root/', hashChange: false, silent: true});
409    assert.strictEqual(Backbone.history.getFragment(), 'foo');
410  });
411
412  QUnit.test('#967 - Route callback gets passed encoded values.', function(assert) {
413    assert.expect(3);
414    var route = 'has%2Fslash/complex-has%23hash/has%20space';
415    Backbone.history.navigate(route, {trigger: true});
416    assert.strictEqual(router.first, 'has/slash');
417    assert.strictEqual(router.part, 'has#hash');
418    assert.strictEqual(router.rest, 'has space');
419  });
420
421  QUnit.test('correctly handles URLs with % (#868)', function(assert) {
422    assert.expect(3);
423    location.replace('http://example.com#search/fat%3A1.5%25');
424    Backbone.history.checkUrl();
425    location.replace('http://example.com#search/fat');
426    Backbone.history.checkUrl();
427    assert.equal(router.query, 'fat');
428    assert.equal(router.page, void 0);
429    assert.equal(lastRoute, 'search');
430  });
431
432  QUnit.test('#2666 - Hashes with UTF8 in them.', function(assert) {
433    assert.expect(2);
434    Backbone.history.navigate('charñ', {trigger: true});
435    assert.equal(router.charType, 'UTF');
436    Backbone.history.navigate('char%C3%B1', {trigger: true});
437    assert.equal(router.charType, 'UTF');
438  });
439
440  QUnit.test('#1185 - Use pathname when hashChange is not wanted.', function(assert) {
441    assert.expect(1);
442    Backbone.history.stop();
443    location.replace('http://example.com/path/name#hash');
444    Backbone.history = _.extend(new Backbone.History, {location: location});
445    Backbone.history.start({hashChange: false});
446    var fragment = Backbone.history.getFragment();
447    assert.strictEqual(fragment, location.pathname.replace(/^\//, ''));
448  });
449
450  QUnit.test('#1206 - Strip leading slash before location.assign.', function(assert) {
451    assert.expect(1);
452    Backbone.history.stop();
453    location.replace('http://example.com/root/');
454    Backbone.history = _.extend(new Backbone.History, {location: location});
455    Backbone.history.start({hashChange: false, root: '/root/'});
456    location.assign = function(pathname) {
457      assert.strictEqual(pathname, '/root/fragment');
458    };
459    Backbone.history.navigate('/fragment');
460  });
461
462  QUnit.test('#1387 - Root fragment without trailing slash.', function(assert) {
463    assert.expect(1);
464    Backbone.history.stop();
465    location.replace('http://example.com/root');
466    Backbone.history = _.extend(new Backbone.History, {location: location});
467    Backbone.history.start({hashChange: false, root: '/root/', silent: true});
468    assert.strictEqual(Backbone.history.getFragment(), '');
469  });
470
471  QUnit.test('#1366 - History does not prepend root to fragment.', function(assert) {
472    assert.expect(2);
473    Backbone.history.stop();
474    location.replace('http://example.com/root/');
475    Backbone.history = _.extend(new Backbone.History, {
476      location: location,
477      history: {
478        pushState: function(state, title, url) {
479          assert.strictEqual(url, '/root/x');
480        }
481      }
482    });
483    Backbone.history.start({
484      root: '/root/',
485      pushState: true,
486      hashChange: false
487    });
488    Backbone.history.navigate('x');
489    assert.strictEqual(Backbone.history.fragment, 'x');
490  });
491
492  QUnit.test('Normalize root.', function(assert) {
493    assert.expect(1);
494    Backbone.history.stop();
495    location.replace('http://example.com/root');
496    Backbone.history = _.extend(new Backbone.History, {
497      location: location,
498      history: {
499        pushState: function(state, title, url) {
500          assert.strictEqual(url, '/root/fragment');
501        }
502      }
503    });
504    Backbone.history.start({
505      pushState: true,
506      root: '/root',
507      hashChange: false
508    });
509    Backbone.history.navigate('fragment');
510  });
511
512  QUnit.test('Normalize root.', function(assert) {
513    assert.expect(1);
514    Backbone.history.stop();
515    location.replace('http://example.com/root#fragment');
516    Backbone.history = _.extend(new Backbone.History, {
517      location: location,
518      history: {
519        pushState: function(state, title, url) {},
520        replaceState: function(state, title, url) {
521          assert.strictEqual(url, '/root/fragment');
522        }
523      }
524    });
525    Backbone.history.start({
526      pushState: true,
527      root: '/root'
528    });
529  });
530
531  QUnit.test('Normalize root.', function(assert) {
532    assert.expect(1);
533    Backbone.history.stop();
534    location.replace('http://example.com/root');
535    Backbone.history = _.extend(new Backbone.History, {location: location});
536    Backbone.history.loadUrl = function() { assert.ok(true); };
537    Backbone.history.start({
538      pushState: true,
539      root: '/root'
540    });
541  });
542
543  QUnit.test('Normalize root - leading slash.', function(assert) {
544    assert.expect(1);
545    Backbone.history.stop();
546    location.replace('http://example.com/root');
547    Backbone.history = _.extend(new Backbone.History, {
548      location: location,
549      history: {
550        pushState: function() {},
551        replaceState: function() {}
552      }
553    });
554    Backbone.history.start({root: 'root'});
555    assert.strictEqual(Backbone.history.root, '/root/');
556  });
557
558  QUnit.test('Transition from hashChange to pushState.', function(assert) {
559    assert.expect(1);
560    Backbone.history.stop();
561    location.replace('http://example.com/root#x/y');
562    Backbone.history = _.extend(new Backbone.History, {
563      location: location,
564      history: {
565        pushState: function() {},
566        replaceState: function(state, title, url) {
567          assert.strictEqual(url, '/root/x/y');
568        }
569      }
570    });
571    Backbone.history.start({
572      root: 'root',
573      pushState: true
574    });
575  });
576
577  QUnit.test('#1619: Router: Normalize empty root', function(assert) {
578    assert.expect(1);
579    Backbone.history.stop();
580    location.replace('http://example.com/');
581    Backbone.history = _.extend(new Backbone.History, {
582      location: location,
583      history: {
584        pushState: function() {},
585        replaceState: function() {}
586      }
587    });
588    Backbone.history.start({root: ''});
589    assert.strictEqual(Backbone.history.root, '/');
590  });
591
592  QUnit.test('#1619: Router: nagivate with empty root', function(assert) {
593    assert.expect(1);
594    Backbone.history.stop();
595    location.replace('http://example.com/');
596    Backbone.history = _.extend(new Backbone.History, {
597      location: location,
598      history: {
599        pushState: function(state, title, url) {
600          assert.strictEqual(url, '/fragment');
601        }
602      }
603    });
604    Backbone.history.start({
605      pushState: true,
606      root: '',
607      hashChange: false
608    });
609    Backbone.history.navigate('fragment');
610  });
611
612  QUnit.test('Transition from pushState to hashChange.', function(assert) {
613    assert.expect(1);
614    Backbone.history.stop();
615    location.replace('http://example.com/root/x/y?a=b');
616    location.replace = function(url) {
617      assert.strictEqual(url, '/root#x/y?a=b');
618    };
619    Backbone.history = _.extend(new Backbone.History, {
620      location: location,
621      history: {
622        pushState: null,
623        replaceState: null
624      }
625    });
626    Backbone.history.start({
627      root: 'root',
628      pushState: true
629    });
630  });
631
632  QUnit.test('#1695 - hashChange to pushState with search.', function(assert) {
633    assert.expect(1);
634    Backbone.history.stop();
635    location.replace('http://example.com/root#x/y?a=b');
636    Backbone.history = _.extend(new Backbone.History, {
637      location: location,
638      history: {
639        pushState: function() {},
640        replaceState: function(state, title, url) {
641          assert.strictEqual(url, '/root/x/y?a=b');
642        }
643      }
644    });
645    Backbone.history.start({
646      root: 'root',
647      pushState: true
648    });
649  });
650
651  QUnit.test('#1746 - Router allows empty route.', function(assert) {
652    assert.expect(1);
653    var MyRouter = Backbone.Router.extend({
654      routes: {'': 'empty'},
655      empty: function() {},
656      route: function(route) {
657        assert.strictEqual(route, '');
658      }
659    });
660    new MyRouter;
661  });
662
663  QUnit.test('#1794 - Trailing space in fragments.', function(assert) {
664    assert.expect(1);
665    var history = new Backbone.History;
666    assert.strictEqual(history.getFragment('fragment   '), 'fragment');
667  });
668
669  QUnit.test('#1820 - Leading slash and trailing space.', function(assert) {
670    assert.expect(1);
671    var history = new Backbone.History;
672    assert.strictEqual(history.getFragment('/fragment '), 'fragment');
673  });
674
675  QUnit.test('#1980 - Optional parameters.', function(assert) {
676    assert.expect(2);
677    location.replace('http://example.com#named/optional/y');
678    Backbone.history.checkUrl();
679    assert.strictEqual(router.z, undefined);
680    location.replace('http://example.com#named/optional/y123');
681    Backbone.history.checkUrl();
682    assert.strictEqual(router.z, '123');
683  });
684
685  QUnit.test('#2062 - Trigger "route" event on router instance.', function(assert) {
686    assert.expect(2);
687    router.on('route', function(name, args) {
688      assert.strictEqual(name, 'routeEvent');
689      assert.deepEqual(args, ['x', null]);
690    });
691    location.replace('http://example.com#route-event/x');
692    Backbone.history.checkUrl();
693  });
694
695  QUnit.test('#2255 - Extend routes by making routes a function.', function(assert) {
696    assert.expect(1);
697    var RouterBase = Backbone.Router.extend({
698      routes: function() {
699        return {
700          home: 'root',
701          index: 'index.html'
702        };
703      }
704    });
705
706    var RouterExtended = RouterBase.extend({
707      routes: function() {
708        var _super = RouterExtended.__super__.routes;
709        return _.extend(_super(), {show: 'show', search: 'search'});
710      }
711    });
712
713    var myRouter = new RouterExtended();
714    assert.deepEqual({home: 'root', index: 'index.html', show: 'show', search: 'search'}, myRouter.routes);
715  });
716
717  QUnit.test('#2538 - hashChange to pushState only if both requested.', function(assert) {
718    assert.expect(0);
719    Backbone.history.stop();
720    location.replace('http://example.com/root?a=b#x/y');
721    Backbone.history = _.extend(new Backbone.History, {
722      location: location,
723      history: {
724        pushState: function() {},
725        replaceState: function() { assert.ok(false); }
726      }
727    });
728    Backbone.history.start({
729      root: 'root',
730      pushState: true,
731      hashChange: false
732    });
733  });
734
735  QUnit.test('No hash fallback.', function(assert) {
736    assert.expect(0);
737    Backbone.history.stop();
738    Backbone.history = _.extend(new Backbone.History, {
739      location: location,
740      history: {
741        pushState: function() {},
742        replaceState: function() {}
743      }
744    });
745
746    var MyRouter = Backbone.Router.extend({
747      routes: {
748        hash: function() { assert.ok(false); }
749      }
750    });
751    var myRouter = new MyRouter;
752
753    location.replace('http://example.com/');
754    Backbone.history.start({
755      pushState: true,
756      hashChange: false
757    });
758    location.replace('http://example.com/nomatch#hash');
759    Backbone.history.checkUrl();
760  });
761
762  QUnit.test('#2656 - No trailing slash on root.', function(assert) {
763    assert.expect(1);
764    Backbone.history.stop();
765    Backbone.history = _.extend(new Backbone.History, {
766      location: location,
767      history: {
768        pushState: function(state, title, url) {
769          assert.strictEqual(url, '/root');
770        }
771      }
772    });
773    location.replace('http://example.com/root/path');
774    Backbone.history.start({pushState: true, hashChange: false, root: 'root'});
775    Backbone.history.navigate('');
776  });
777
778  QUnit.test('#2656 - No trailing slash on root.', function(assert) {
779    assert.expect(1);
780    Backbone.history.stop();
781    Backbone.history = _.extend(new Backbone.History, {
782      location: location,
783      history: {
784        pushState: function(state, title, url) {
785          assert.strictEqual(url, '/');
786        }
787      }
788    });
789    location.replace('http://example.com/path');
790    Backbone.history.start({pushState: true, hashChange: false});
791    Backbone.history.navigate('');
792  });
793
794  QUnit.test('#2656 - No trailing slash on root.', function(assert) {
795    assert.expect(1);
796    Backbone.history.stop();
797    Backbone.history = _.extend(new Backbone.History, {
798      location: location,
799      history: {
800        pushState: function(state, title, url) {
801          assert.strictEqual(url, '/root?x=1');
802        }
803      }
804    });
805    location.replace('http://example.com/root/path');
806    Backbone.history.start({pushState: true, hashChange: false, root: 'root'});
807    Backbone.history.navigate('?x=1');
808  });
809
810  QUnit.test('#2765 - Fragment matching sans query/hash.', function(assert) {
811    assert.expect(2);
812    Backbone.history.stop();
813    Backbone.history = _.extend(new Backbone.History, {
814      location: location,
815      history: {
816        pushState: function(state, title, url) {
817          assert.strictEqual(url, '/path?query#hash');
818        }
819      }
820    });
821
822    var MyRouter = Backbone.Router.extend({
823      routes: {
824        path: function() { assert.ok(true); }
825      }
826    });
827    var myRouter = new MyRouter;
828
829    location.replace('http://example.com/');
830    Backbone.history.start({pushState: true, hashChange: false});
831    Backbone.history.navigate('path?query#hash', true);
832  });
833
834  QUnit.test('Do not decode the search params.', function(assert) {
835    assert.expect(1);
836    var MyRouter = Backbone.Router.extend({
837      routes: {
838        path: function(params) {
839          assert.strictEqual(params, 'x=y%3Fz');
840        }
841      }
842    });
843    var myRouter = new MyRouter;
844    Backbone.history.navigate('path?x=y%3Fz', true);
845  });
846
847  QUnit.test('Navigate to a hash url.', function(assert) {
848    assert.expect(1);
849    Backbone.history.stop();
850    Backbone.history = _.extend(new Backbone.History, {location: location});
851    Backbone.history.start({pushState: true});
852    var MyRouter = Backbone.Router.extend({
853      routes: {
854        path: function(params) {
855          assert.strictEqual(params, 'x=y');
856        }
857      }
858    });
859    var myRouter = new MyRouter;
860    location.replace('http://example.com/path?x=y#hash');
861    Backbone.history.checkUrl();
862  });
863
864  QUnit.test('#navigate to a hash url.', function(assert) {
865    assert.expect(1);
866    Backbone.history.stop();
867    Backbone.history = _.extend(new Backbone.History, {location: location});
868    Backbone.history.start({pushState: true});
869    var MyRouter = Backbone.Router.extend({
870      routes: {
871        path: function(params) {
872          assert.strictEqual(params, 'x=y');
873        }
874      }
875    });
876    var myRouter = new MyRouter;
877    Backbone.history.navigate('path?x=y#hash', true);
878  });
879
880  QUnit.test('unicode pathname', function(assert) {
881    assert.expect(1);
882    location.replace('http://example.com/myyjä');
883    Backbone.history.stop();
884    Backbone.history = _.extend(new Backbone.History, {location: location});
885    var MyRouter = Backbone.Router.extend({
886      routes: {
887        myyjä: function() {
888          assert.ok(true);
889        }
890      }
891    });
892    new MyRouter;
893    Backbone.history.start({pushState: true});
894  });
895
896  QUnit.test('unicode pathname with % in a parameter', function(assert) {
897    assert.expect(1);
898    location.replace('http://example.com/myyjä/foo%20%25%3F%2f%40%25%20bar');
899    location.pathname = '/myyj%C3%A4/foo%20%25%3F%2f%40%25%20bar';
900    Backbone.history.stop();
901    Backbone.history = _.extend(new Backbone.History, {location: location});
902    var MyRouter = Backbone.Router.extend({
903      routes: {
904        'myyjä/:query': function(query) {
905          assert.strictEqual(query, 'foo %?/@% bar');
906        }
907      }
908    });
909    new MyRouter;
910    Backbone.history.start({pushState: true});
911  });
912
913  QUnit.test('newline in route', function(assert) {
914    assert.expect(1);
915    location.replace('http://example.com/stuff%0Anonsense?param=foo%0Abar');
916    Backbone.history.stop();
917    Backbone.history = _.extend(new Backbone.History, {location: location});
918    var MyRouter = Backbone.Router.extend({
919      routes: {
920        'stuff\nnonsense': function() {
921          assert.ok(true);
922        }
923      }
924    });
925    new MyRouter;
926    Backbone.history.start({pushState: true});
927  });
928
929  QUnit.test('Router#execute receives callback, args, name.', function(assert) {
930    assert.expect(3);
931    location.replace('http://example.com#foo/123/bar?x=y');
932    Backbone.history.stop();
933    Backbone.history = _.extend(new Backbone.History, {location: location});
934    var MyRouter = Backbone.Router.extend({
935      routes: {'foo/:id/bar': 'foo'},
936      foo: function() {},
937      execute: function(callback, args, name) {
938        assert.strictEqual(callback, this.foo);
939        assert.deepEqual(args, ['123', 'x=y']);
940        assert.strictEqual(name, 'foo');
941      }
942    });
943    var myRouter = new MyRouter;
944    Backbone.history.start();
945  });
946
947  QUnit.test('pushState to hashChange with only search params.', function(assert) {
948    assert.expect(1);
949    Backbone.history.stop();
950    location.replace('http://example.com?a=b');
951    location.replace = function(url) {
952      assert.strictEqual(url, '/#?a=b');
953    };
954    Backbone.history = _.extend(new Backbone.History, {
955      location: location,
956      history: null
957    });
958    Backbone.history.start({pushState: true});
959  });
960
961  QUnit.test('#3123 - History#navigate decodes before comparison.', function(assert) {
962    assert.expect(1);
963    Backbone.history.stop();
964    location.replace('http://example.com/shop/search?keyword=short%20dress');
965    Backbone.history = _.extend(new Backbone.History, {
966      location: location,
967      history: {
968        pushState: function() { assert.ok(false); },
969        replaceState: function() { assert.ok(false); }
970      }
971    });
972    Backbone.history.start({pushState: true});
973    Backbone.history.navigate('shop/search?keyword=short%20dress', true);
974    assert.strictEqual(Backbone.history.fragment, 'shop/search?keyword=short dress');
975  });
976
977  QUnit.test('#3175 - Urls in the params', function(assert) {
978    assert.expect(1);
979    Backbone.history.stop();
980    location.replace('http://example.com#login?a=value&backUrl=https%3A%2F%2Fwww.msn.com%2Fidp%2Fidpdemo%3Fspid%3Dspdemo%26target%3Db');
981    Backbone.history = _.extend(new Backbone.History, {location: location});
982    var myRouter = new Backbone.Router;
983    myRouter.route('login', function(params) {
984      assert.strictEqual(params, 'a=value&backUrl=https%3A%2F%2Fwww.msn.com%2Fidp%2Fidpdemo%3Fspid%3Dspdemo%26target%3Db');
985    });
986    Backbone.history.start();
987  });
988
989  QUnit.test('#3358 - pushState to hashChange transition with search params', function(assert) {
990    assert.expect(1);
991    Backbone.history.stop();
992    location.replace('http://example.com/root?foo=bar');
993    location.replace = function(url) {
994      assert.strictEqual(url, '/root#?foo=bar');
995    };
996    Backbone.history = _.extend(new Backbone.History, {
997      location: location,
998      history: {
999        pushState: undefined,
1000        replaceState: undefined
1001      }
1002    });
1003    Backbone.history.start({root: '/root', pushState: true});
1004  });
1005
1006  QUnit.test('Paths that don\'t match the root should not match no root', function(assert) {
1007    assert.expect(0);
1008    location.replace('http://example.com/foo');
1009    Backbone.history.stop();
1010    Backbone.history = _.extend(new Backbone.History, {location: location});
1011    var MyRouter = Backbone.Router.extend({
1012      routes: {
1013        foo: function() {
1014          assert.ok(false, 'should not match unless root matches');
1015        }
1016      }
1017    });
1018    var myRouter = new MyRouter;
1019    Backbone.history.start({root: 'root', pushState: true});
1020  });
1021
1022  QUnit.test('Paths that don\'t match the root should not match roots of the same length', function(assert) {
1023    assert.expect(0);
1024    location.replace('http://example.com/xxxx/foo');
1025    Backbone.history.stop();
1026    Backbone.history = _.extend(new Backbone.History, {location: location});
1027    var MyRouter = Backbone.Router.extend({
1028      routes: {
1029        foo: function() {
1030          assert.ok(false, 'should not match unless root matches');
1031        }
1032      }
1033    });
1034    var myRouter = new MyRouter;
1035    Backbone.history.start({root: 'root', pushState: true});
1036  });
1037
1038  QUnit.test('roots with regex characters', function(assert) {
1039    assert.expect(1);
1040    location.replace('http://example.com/x+y.z/foo');
1041    Backbone.history.stop();
1042    Backbone.history = _.extend(new Backbone.History, {location: location});
1043    var MyRouter = Backbone.Router.extend({
1044      routes: {foo: function() { assert.ok(true); }}
1045    });
1046    var myRouter = new MyRouter;
1047    Backbone.history.start({root: 'x+y.z', pushState: true});
1048  });
1049
1050  QUnit.test('roots with unicode characters', function(assert) {
1051    assert.expect(1);
1052    location.replace('http://example.com/®ooτ/foo');
1053    Backbone.history.stop();
1054    Backbone.history = _.extend(new Backbone.History, {location: location});
1055    var MyRouter = Backbone.Router.extend({
1056      routes: {foo: function() { assert.ok(true); }}
1057    });
1058    var myRouter = new MyRouter;
1059    Backbone.history.start({root: '®ooτ', pushState: true});
1060  });
1061
1062  QUnit.test('roots without slash', function(assert) {
1063    assert.expect(1);
1064    location.replace('http://example.com/®ooτ');
1065    Backbone.history.stop();
1066    Backbone.history = _.extend(new Backbone.History, {location: location});
1067    var MyRouter = Backbone.Router.extend({
1068      routes: {'': function() { assert.ok(true); }}
1069    });
1070    var myRouter = new MyRouter;
1071    Backbone.history.start({root: '®ooτ', pushState: true});
1072  });
1073
1074  QUnit.test('#4025 - navigate updates URL hash as is', function(assert) {
1075    assert.expect(1);
1076    var route = 'search/has%20space';
1077    Backbone.history.navigate(route);
1078    assert.strictEqual(location.hash, '#' + route);
1079  });
1080
1081})(QUnit);
1082