Skip to content

Commit 7e9aead

Browse files
committed
Merge pull request #522 from gaearon/reimplement-ignore-scroll
Add ignoreScrollBehavior to new API
2 parents 3a5b404 + 5fe6c08 commit 7e9aead

File tree

5 files changed

+218
-112
lines changed

5 files changed

+218
-112
lines changed

modules/__tests__/Router-test.js

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var React = require('react');
44
var Route = require('../components/Route');
55
var RouteHandler = require('../components/RouteHandler');
66
var TestLocation = require('../locations/TestLocation');
7+
var ScrollToTopBehavior = require('../behaviors/ScrollToTopBehavior');
78
var getWindowScrollPosition = require('../utils/getWindowScrollPosition');
89
var Router = require('../index');
910

@@ -211,6 +212,75 @@ describe('Router.run', function () {
211212
});
212213
});
213214

215+
describe('ScrollToTop scrolling', function () {
216+
var BigPage = React.createClass({
217+
render: function () {
218+
return <div style={{ width: 10000, height: 10000, background: 'green' }}/>;
219+
}
220+
});
221+
222+
var routes = [
223+
<Route name="one" handler={BigPage}/>,
224+
<Route name="two" handler={BigPage}/>
225+
];
226+
227+
describe('when a page is scrolled', function () {
228+
var position, div, renderCount;
229+
beforeEach(function (done) {
230+
TestLocation.history = [ '/one' ];
231+
232+
div = document.createElement('div');
233+
document.body.appendChild(div);
234+
235+
renderCount = 0;
236+
237+
Router.create({
238+
routes: routes,
239+
location: TestLocation,
240+
scrollBehavior: ScrollToTopBehavior
241+
}).run(function (Handler) {
242+
React.render(<Handler/>, div, function () {
243+
if (renderCount === 0) {
244+
position = { x: 20, y: 50 };
245+
window.scrollTo(position.x, position.y);
246+
247+
setTimeout(function () {
248+
expect(getWindowScrollPosition()).toEqual(position);
249+
done();
250+
}, 20);
251+
}
252+
253+
renderCount += 1;
254+
});
255+
});
256+
});
257+
258+
afterEach(function () {
259+
div.parentNode.removeChild(div);
260+
});
261+
262+
describe('navigating to a new page', function () {
263+
beforeEach(function () {
264+
TestLocation.push('/two');
265+
});
266+
267+
it('resets the scroll position', function () {
268+
expect(getWindowScrollPosition()).toEqual({ x: 0, y: 0 });
269+
});
270+
271+
describe('then returning to the previous page', function () {
272+
beforeEach(function () {
273+
TestLocation.pop();
274+
});
275+
276+
it('resets the scroll position', function () {
277+
expect(getWindowScrollPosition()).toEqual({ x: 0, y: 0});
278+
});
279+
});
280+
});
281+
});
282+
});
283+
214284
describe('ImitateBrowserBehavior scrolling', function () {
215285
var BigPage = React.createClass({
216286
render: function () {
@@ -276,6 +346,116 @@ describe('Router.run', function () {
276346
});
277347
});
278348

349+
describe('ignoreScrollBehavior', function () {
350+
var routes = (
351+
<Route handler={Nested}>
352+
<Route handler={Foo} ignoreScrollBehavior>
353+
<Route handler={Foo} path='/feed' />
354+
<Route handler={Foo} path='/discover' />
355+
</Route>
356+
<Route path='/search/:q' handler={Foo} ignoreScrollBehavior />
357+
<Route path='/users/:id/posts' handler={Foo} />
358+
<Route path='/about' handler={Foo} />
359+
</Route>
360+
);
361+
362+
var div, didUpdateScroll;
363+
beforeEach(function (done) {
364+
TestLocation.history = [ '/feed' ];
365+
366+
div = document.createElement('div');
367+
document.body.appendChild(div);
368+
369+
var MockScrollBehavior = {
370+
updateScrollPosition() {
371+
didUpdateScroll = true;
372+
}
373+
};
374+
375+
Router.create({
376+
routes: routes,
377+
location: TestLocation,
378+
scrollBehavior: MockScrollBehavior
379+
}).run(function (Handler) {
380+
React.render(<Handler/>, div, function () {
381+
done();
382+
});
383+
});
384+
});
385+
386+
afterEach(function () {
387+
div.parentNode.removeChild(div);
388+
didUpdateScroll = false;
389+
});
390+
391+
it('calls updateScroll the first time', function () {
392+
expect(didUpdateScroll).toBe(true);
393+
});
394+
395+
describe('decides whether to update scroll on transition', function () {
396+
beforeEach(function () {
397+
didUpdateScroll = false;
398+
});
399+
400+
afterEach(function () {
401+
TestLocation.pop();
402+
});
403+
404+
it('calls updateScroll when no ancestors ignore scroll', function () {
405+
TestLocation.push('/about');
406+
expect(didUpdateScroll).toBe(true);
407+
});
408+
409+
it('calls updateScroll when no ancestors ignore scroll although source and target do', function () {
410+
TestLocation.push('/search/foo');
411+
expect(didUpdateScroll).toBe(true);
412+
});
413+
414+
it('calls updateScroll when route does not ignore scroll and only params change', function () {
415+
TestLocation.replace('/users/3/posts');
416+
didUpdateScroll = false;
417+
418+
TestLocation.push('/users/5/posts');
419+
expect(didUpdateScroll).toBe(true);
420+
});
421+
422+
it('calls updateScroll when route does not ignore scroll and both params and query change', function () {
423+
TestLocation.replace('/users/3/posts');
424+
didUpdateScroll = false;
425+
426+
TestLocation.push('/users/5/posts?page=2');
427+
expect(didUpdateScroll).toBe(true);
428+
});
429+
430+
it('does not call updateScroll when route does not ignore scroll but only query changes', function () {
431+
TestLocation.replace('/users/3/posts');
432+
didUpdateScroll = false;
433+
434+
TestLocation.push('/users/3/posts?page=2');
435+
expect(didUpdateScroll).toBe(false);
436+
});
437+
438+
it('does not call updateScroll when common ancestor ignores scroll', function () {
439+
TestLocation.push('/discover');
440+
expect(didUpdateScroll).toBe(false);
441+
});
442+
443+
it('does not call updateScroll when route ignores scroll', function () {
444+
TestLocation.replace('/search/foo');
445+
didUpdateScroll = false;
446+
447+
TestLocation.push('/search/bar');
448+
expect(didUpdateScroll).toBe(false);
449+
450+
TestLocation.replace('/search/bar?safe=0');
451+
expect(didUpdateScroll).toBe(false);
452+
453+
TestLocation.replace('/search/whatever');
454+
expect(didUpdateScroll).toBe(false);
455+
});
456+
});
457+
});
458+
279459
describe('makePath', function () {
280460
var router;
281461
beforeEach(function () {

modules/components/__tests__/Routes-test.js

Lines changed: 0 additions & 109 deletions
This file was deleted.

modules/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ exports.HashLocation = require('./locations/HashLocation');
99
exports.HistoryLocation = require('./locations/HistoryLocation');
1010
exports.RefreshLocation = require('./locations/RefreshLocation');
1111

12+
exports.ImitateBrowserBehavior = require('./behaviors/ImitateBrowserBehavior');
13+
exports.ScrollToTopBehavior = require('./behaviors/ScrollToTopBehavior');
14+
1215
exports.Navigation = require('./mixins/Navigation');
1316
exports.State = require('./mixins/State');
1417

modules/mixins/Scrolling.js

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
var invariant = require('react/lib/invariant');
22
var canUseDOM = require('react/lib/ExecutionEnvironment').canUseDOM;
33
var getWindowScrollPosition = require('../utils/getWindowScrollPosition');
4+
var Path = require('../utils/Path');
5+
6+
function shouldUpdateScroll(state, prevState) {
7+
if (!prevState) {
8+
return true;
9+
}
10+
11+
var path = state.path;
12+
var routes = state.routes;
13+
var prevPath = prevState.path;
14+
var prevRoutes = prevState.routes;
15+
16+
if (Path.withoutQuery(path) === Path.withoutQuery(prevPath)) {
17+
return false;
18+
}
19+
20+
var sharedAncestorRoutes = routes.filter(function (route) {
21+
return prevRoutes.indexOf(route) !== -1;
22+
});
23+
24+
return !sharedAncestorRoutes.some(function (route) {
25+
return route.ignoreScrollBehavior;
26+
});
27+
}
428

529
/**
630
* Provides the router with the ability to manage window scroll position
@@ -25,8 +49,8 @@ var Scrolling = {
2549
this._scrollHistory[this.state.path] = getWindowScrollPosition();
2650
},
2751

28-
componentDidUpdate: function () {
29-
this._updateScroll();
52+
componentDidUpdate: function (prevProps, prevState) {
53+
this._updateScroll(prevState);
3054
},
3155

3256
componentWillUnmount: function () {
@@ -40,7 +64,11 @@ var Scrolling = {
4064
return this._scrollHistory[path] || null;
4165
},
4266

43-
_updateScroll: function () {
67+
_updateScroll: function (prevState) {
68+
if (!shouldUpdateScroll(this.state, prevState)) {
69+
return;
70+
}
71+
4472
var scrollBehavior = this.getScrollBehavior();
4573

4674
if (scrollBehavior)

modules/utils/createRoutesFromChildren.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ function createRoute(element, parentRoute, namedRoutes) {
5555

5656
var route = { name: props.name };
5757

58+
if (props.ignoreScrollBehavior) {
59+
route.ignoreScrollBehavior = true;
60+
}
61+
5862
if (type === Redirect.type) {
5963
route.handler = createRedirectHandler(props.to, props.params, props.query);
6064
props.path = props.path || props.from || '*';

0 commit comments

Comments
 (0)