Skip to content

Commit 2e2e9f7

Browse files
committed
Use mock location to test Backbone.history.
1 parent 7bcd6ad commit 2e2e9f7

File tree

7 files changed

+221
-176
lines changed

7 files changed

+221
-176
lines changed

backbone.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -954,9 +954,10 @@
954954

955955
// Handles cross-browser history management, based on URL fragments. If the
956956
// browser does not support `onhashchange`, falls back to polling.
957-
var History = Backbone.History = function() {
957+
var History = Backbone.History = function(options) {
958958
this.handlers = [];
959959
_.bindAll(this, 'checkUrl');
960+
this.location = options && options.location || root.location;
960961
};
961962

962963
// Cached regex for cleaning leading hashes and slashes .
@@ -977,9 +978,8 @@
977978

978979
// Gets the true hash value. Cannot use location.hash directly due to bug
979980
// in Firefox where location.hash will always be decoded.
980-
getHash: function(windowOverride) {
981-
var loc = windowOverride ? windowOverride.location : window.location;
982-
var match = loc.href.match(/#(.*)$/);
981+
getHash: function(window) {
982+
var match = (window || this).location.href.match(/#(.*)$/);
983983
return match ? match[1] : '';
984984
},
985985

@@ -988,7 +988,7 @@
988988
getFragment: function(fragment, forcePushState) {
989989
if (fragment == null) {
990990
if (this._hasPushState || !this._wantsHashChange || forcePushState) {
991-
fragment = window.location.pathname;
991+
fragment = this.location.pathname;
992992
} else {
993993
fragment = this.getHash();
994994
}
@@ -1031,14 +1031,14 @@
10311031
// Determine if we need to change the base url, for a pushState link
10321032
// opened by a non-pushState browser.
10331033
this.fragment = fragment;
1034-
var loc = window.location;
1034+
var loc = this.location;
10351035
var atRoot = (loc.pathname == this.options.root) && !loc.search;
10361036

10371037
// If we've started off with a route from a `pushState`-enabled browser,
10381038
// but we're currently in a browser that doesn't support it...
10391039
if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
10401040
this.fragment = this.getFragment(null, true);
1041-
window.location.replace(this.options.root + window.location.search + '#' + this.fragment);
1041+
this.location.replace(this.options.root + this.location.search + '#' + this.fragment);
10421042
// Return immediately as browser will do redirect to new url
10431043
return true;
10441044

@@ -1072,7 +1072,9 @@
10721072
// calls `loadUrl`, normalizing across the hidden iframe.
10731073
checkUrl: function(e) {
10741074
var current = this.getFragment();
1075-
if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
1075+
if (current == this.fragment && this.iframe) {
1076+
current = this.getFragment(this.getHash(this.iframe));
1077+
}
10761078
if (current == this.fragment) return false;
10771079
if (this.iframe) this.navigate(current);
10781080
this.loadUrl() || this.loadUrl(this.getHash());
@@ -1115,7 +1117,7 @@
11151117
// fragment to store history.
11161118
} else if (this._wantsHashChange) {
11171119
this.fragment = frag;
1118-
this._updateHash(window.location, frag, options.replace);
1120+
this._updateHash(this.location, frag, options.replace);
11191121
if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
11201122
// Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
11211123
// When replace is true, we don't want this.
@@ -1126,7 +1128,7 @@
11261128
// If you've told us that you explicitly don't want fallback hashchange-
11271129
// based history, then `navigate` becomes a page refresh.
11281130
} else {
1129-
return window.location.assign(fullFrag);
1131+
return this.location.assign(fullFrag);
11301132
}
11311133
if (options.trigger) this.loadUrl(fragment);
11321134
},
@@ -1135,7 +1137,7 @@
11351137
// a new one to the browser history.
11361138
_updateHash: function(location, fragment, replace) {
11371139
if (replace) {
1138-
location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
1140+
location.replace(location.href.replace(/(javascript:|#).*$/, '') + '#' + fragment);
11391141
} else {
11401142
location.hash = fragment;
11411143
}

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3805,7 +3805,7 @@ <h2 id="changelog">Change Log</h2>
38053805

38063806
</div>
38073807

3808-
<script src="test/vendor/underscore-1.3.1.js"></script>
3808+
<script src="test/vendor/underscore.js"></script>
38093809
<script src="test/vendor/jquery-1.7.1.js"></script>
38103810
<script src="test/vendor/json2.js"></script>
38113811
<script src="backbone.js"></script>

test/router.js

Lines changed: 84 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
$(document).ready(function() {
22

33
var router = null;
4+
var location = null;
45
var lastRoute = null;
56
var lastArgs = [];
67

@@ -9,10 +10,33 @@ $(document).ready(function() {
910
lastArgs = args;
1011
}
1112

13+
var Location = function(href) {
14+
this.replace(href);
15+
};
16+
17+
_.extend(Location.prototype, {
18+
19+
replace: function(href) {
20+
_.extend(this, _.pick($('<a></a>', {href: href})[0],
21+
'href',
22+
'hash',
23+
'search',
24+
'fragment',
25+
'pathname'
26+
));
27+
},
28+
29+
toString: function() {
30+
return this.href;
31+
}
32+
33+
});
34+
1235
module("Backbone.Router", {
1336

1437
setup: function() {
15-
Backbone.history = null;
38+
location = new Location('http://example.com');
39+
Backbone.history = new Backbone.History({location: location});
1640
router = new Router({testing: 101});
1741
Backbone.history.interval = 9;
1842
Backbone.history.start({pushState: false});
@@ -101,24 +125,20 @@ $(document).ready(function() {
101125
equal(router.testing, 101);
102126
});
103127

104-
asyncTest("Router: routes (simple)", 4, function() {
105-
window.location.hash = 'search/news';
106-
setTimeout(function() {
107-
equal(router.query, 'news');
108-
equal(router.page, undefined);
109-
equal(lastRoute, 'search');
110-
equal(lastArgs[0], 'news');
111-
start();
112-
}, 10);
128+
test("Router: routes (simple)", 4, function() {
129+
location.replace('http://example.com#search/news');
130+
Backbone.history.checkUrl();
131+
equal(router.query, 'news');
132+
equal(router.page, undefined);
133+
equal(lastRoute, 'search');
134+
equal(lastArgs[0], 'news');
113135
});
114136

115-
asyncTest("Router: routes (two part)", 2, function() {
116-
window.location.hash = 'search/nyc/p10';
117-
setTimeout(function() {
118-
equal(router.query, 'nyc');
119-
equal(router.page, '10');
120-
start();
121-
}, 10);
137+
test("Router: routes (two part)", 2, function() {
138+
location.replace('http://example.com#search/nyc/p10');
139+
Backbone.history.checkUrl();
140+
equal(router.query, 'nyc');
141+
equal(router.page, '10');
122142
});
123143

124144
test("Router: routes via navigate", 2, function() {
@@ -145,22 +165,12 @@ $(document).ready(function() {
145165
});
146166
});
147167

148-
test("Router: doesn't fire routes to the same place twice", 6, function() {
149-
equal(router.count, 0);
150-
router.navigate('counter', {trigger: true});
151-
equal(router.count, 1);
152-
router.navigate('/counter', {trigger: true});
153-
router.navigate('/counter', {trigger: true});
154-
equal(router.count, 1);
155-
router.navigate('search/counter', {trigger: true});
156-
router.navigate('counter', {trigger: true});
157-
equal(router.count, 2);
158-
Backbone.history.stop();
159-
router.navigate('search/counter', {trigger: true});
160-
router.navigate('counter', {trigger: true});
161-
equal(router.count, 2);
162-
Backbone.history.start();
163-
equal(router.count, 3);
168+
test("loadUrl is not called for identical routes.", 0, function() {
169+
Backbone.history.loadUrl = function(){ ok(false); };
170+
location.replace('http://example.com#route');
171+
Backbone.history.navigate('route');
172+
Backbone.history.navigate('/route');
173+
Backbone.history.navigate('/route');
164174
});
165175

166176
test("Router: use implicit callback if none provided", 1, function() {
@@ -169,73 +179,49 @@ $(document).ready(function() {
169179
equal(router.count, 1);
170180
});
171181

172-
asyncTest("Router: routes via navigate with {replace: true}", 2, function() {
173-
var historyLength = window.history.length;
174-
router.navigate('search/manhattan/start_here');
175-
router.navigate('search/manhattan/then_here');
176-
router.navigate('search/manhattan/finally_here', {replace: true});
177-
178-
equal(window.location.hash, "#search/manhattan/finally_here");
179-
window.history.go(-1);
180-
setTimeout(function() {
181-
equal(window.location.hash, "#search/manhattan/start_here");
182-
start();
183-
}, 500);
182+
test("Router: routes via navigate with {replace: true}", 1, function() {
183+
location.replace('http://example.com#start_here');
184+
Backbone.history.checkUrl();
185+
location.replace = function(href) {
186+
strictEqual(href, new Location('http://example.com#end_here').href);
187+
};
188+
Backbone.history.navigate('end_here', {replace: true});
184189
});
185190

186-
asyncTest("Router: routes (splats)", 1, function() {
187-
window.location.hash = 'splat/long-list/of/splatted_99args/end';
188-
setTimeout(function() {
189-
equal(router.args, 'long-list/of/splatted_99args');
190-
start();
191-
}, 10);
191+
test("Router: routes (splats)", 1, function() {
192+
location.replace('http://example.com#splat/long-list/of/splatted_99args/end');
193+
Backbone.history.checkUrl();
194+
equal(router.args, 'long-list/of/splatted_99args');
192195
});
193196

194-
asyncTest("Router: routes (complex)", 3, function() {
195-
window.location.hash = 'one/two/three/complex-part/four/five/six/seven';
196-
setTimeout(function() {
197-
equal(router.first, 'one/two/three');
198-
equal(router.part, 'part');
199-
equal(router.rest, 'four/five/six/seven');
200-
start();
201-
}, 10);
197+
test("Router: routes (complex)", 3, function() {
198+
location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven');
199+
Backbone.history.checkUrl();
200+
equal(router.first, 'one/two/three');
201+
equal(router.part, 'part');
202+
equal(router.rest, 'four/five/six/seven');
202203
});
203204

204-
asyncTest("Router: routes (query)", 5, function() {
205-
window.location.hash = 'mandel?a=b&c=d';
206-
setTimeout(function() {
207-
equal(router.entity, 'mandel');
208-
equal(router.queryArgs, 'a=b&c=d');
209-
equal(lastRoute, 'query');
210-
equal(lastArgs[0], 'mandel');
211-
equal(lastArgs[1], 'a=b&c=d');
212-
start();
213-
}, 10);
205+
test("Router: routes (query)", 5, function() {
206+
location.replace('http://example.com#mandel?a=b&c=d');
207+
Backbone.history.checkUrl();
208+
equal(router.entity, 'mandel');
209+
equal(router.queryArgs, 'a=b&c=d');
210+
equal(lastRoute, 'query');
211+
equal(lastArgs[0], 'mandel');
212+
equal(lastArgs[1], 'a=b&c=d');
214213
});
215214

216-
asyncTest("Router: routes (anything)", 1, function() {
217-
window.location.hash = 'doesnt-match-a-route';
218-
setTimeout(function() {
219-
equal(router.anything, 'doesnt-match-a-route');
220-
start();
221-
window.location.hash = '';
222-
}, 10);
215+
test("Router: routes (anything)", 1, function() {
216+
location.replace('http://example.com#doesnt-match-a-route');
217+
Backbone.history.checkUrl();
218+
equal(router.anything, 'doesnt-match-a-route');
223219
});
224220

225-
asyncTest("Router: fires event when router doesn't have callback on it", 1, function() {
226-
try{
227-
var callbackFired = false;
228-
var myCallback = function(){ callbackFired = true; };
229-
router.on("route:noCallback", myCallback);
230-
window.location.hash = "noCallback";
231-
setTimeout(function(){
232-
equal(callbackFired, true);
233-
start();
234-
window.location.hash = '';
235-
}, 10);
236-
} catch (err) {
237-
ok(false, "an exception was thrown trying to fire the router event with no router handler callback");
238-
}
221+
test("Router: fires event when router doesn't have callback on it", 1, function() {
222+
router.on("route:noCallback", function(){ ok(true); });
223+
location.replace('http://example.com#noCallback');
224+
Backbone.history.checkUrl();
239225
});
240226

241227
test("#933, #908 - leading slash", 2, function() {
@@ -265,17 +251,14 @@ $(document).ready(function() {
265251
equal(router.rest, 'has%20space');
266252
});
267253

268-
asyncTest("Router: correctly handles URLs with % (#868)", 3, function() {
269-
window.location.hash = 'search/fat%3A1.5%25';
270-
setTimeout(function() {
271-
window.location.hash = 'search/fat';
272-
setTimeout(function() {
273-
equal(router.query, 'fat');
274-
equal(router.page, undefined);
275-
equal(lastRoute, 'search');
276-
start();
277-
}, 50);
278-
}, 50);
254+
test("Router: correctly handles URLs with % (#868)", 3, function() {
255+
location.replace('http://example.com#search/fat%3A1.5%25');
256+
Backbone.history.checkUrl();
257+
location.replace('http://example.com#search/fat');
258+
Backbone.history.checkUrl();
259+
equal(router.query, 'fat');
260+
equal(router.page, undefined);
261+
equal(lastRoute, 'search');
279262
});
280263

281264
});

test/test-ender.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<script type="text/javascript" src="vendor/ender-jeesh.js"></script>
88
<script type="text/javascript" src="vendor/qunit.js"></script>
99
<script type="text/javascript" src="vendor/jslitmus.js"></script>
10-
<script type="text/javascript" src="vendor/underscore-1.3.1.js"></script>
10+
<script type="text/javascript" src="vendor/underscore.js"></script>
1111
<script type="text/javascript" src="../backbone.js"></script>
1212

1313
<script type="text/javascript" src="events.js"></script>

test/test-zepto.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<script type="text/javascript" src="vendor/zepto-0.6.js"></script>
88
<script type="text/javascript" src="vendor/qunit.js"></script>
99
<script type="text/javascript" src="vendor/jslitmus.js"></script>
10-
<script type="text/javascript" src="vendor/underscore-1.3.1.js"></script>
10+
<script type="text/javascript" src="vendor/underscore.js"></script>
1111
<script type="text/javascript" src="../backbone.js"></script>
1212

1313
<script type="text/javascript" src="events.js"></script>

test/test.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<script src="vendor/jquery-1.7.1.js"></script>
99
<script src="vendor/qunit.js"></script>
1010
<script src="vendor/jslitmus.js"></script>
11-
<script src="vendor/underscore-1.3.1.js"></script>
11+
<script src="vendor/underscore.js"></script>
1212
<script src="../backbone.js"></script>
1313

1414
<script src="noconflict.js"></script>

0 commit comments

Comments
 (0)