Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ignoreScrollBehavior to new API #522

Merged
merged 3 commits into from
Nov 26, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 180 additions & 0 deletions modules/__tests__/Router-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var React = require('react');
var Route = require('../components/Route');
var RouteHandler = require('../components/RouteHandler');
var TestLocation = require('../locations/TestLocation');
var ScrollToTopBehavior = require('../behaviors/ScrollToTopBehavior');
var getWindowScrollPosition = require('../utils/getWindowScrollPosition');
var Router = require('../index');

Expand Down Expand Up @@ -211,6 +212,75 @@ describe('Router.run', function () {
});
});

describe('ScrollToTop scrolling', function () {
var BigPage = React.createClass({
render: function () {
return <div style={{ width: 10000, height: 10000, background: 'green' }}/>;
}
});

var routes = [
<Route name="one" handler={BigPage}/>,
<Route name="two" handler={BigPage}/>
];

describe('when a page is scrolled', function () {
var position, div, renderCount;
beforeEach(function (done) {
TestLocation.history = [ '/one' ];

div = document.createElement('div');
document.body.appendChild(div);

renderCount = 0;

Router.create({
routes: routes,
location: TestLocation,
scrollBehavior: ScrollToTopBehavior
}).run(function (Handler) {
React.render(<Handler/>, div, function () {
if (renderCount === 0) {
position = { x: 20, y: 50 };
window.scrollTo(position.x, position.y);

setTimeout(function () {
expect(getWindowScrollPosition()).toEqual(position);
done();
}, 20);
}

renderCount += 1;
});
});
});

afterEach(function () {
div.parentNode.removeChild(div);
});

describe('navigating to a new page', function () {
beforeEach(function () {
TestLocation.push('/two');
});

it('resets the scroll position', function () {
expect(getWindowScrollPosition()).toEqual({ x: 0, y: 0 });
});

describe('then returning to the previous page', function () {
beforeEach(function () {
TestLocation.pop();
});

it('resets the scroll position', function () {
expect(getWindowScrollPosition()).toEqual({ x: 0, y: 0});
});
});
});
});
});

describe('ImitateBrowserBehavior scrolling', function () {
var BigPage = React.createClass({
render: function () {
Expand Down Expand Up @@ -276,6 +346,116 @@ describe('Router.run', function () {
});
});

describe('ignoreScrollBehavior', function () {
var routes = (
<Route handler={Nested}>
<Route handler={Foo} ignoreScrollBehavior>
<Route handler={Foo} path='/feed' />
<Route handler={Foo} path='/discover' />
</Route>
<Route path='/search/:q' handler={Foo} ignoreScrollBehavior />
<Route path='/users/:id/posts' handler={Foo} />
<Route path='/about' handler={Foo} />
</Route>
);

var div, didUpdateScroll;
beforeEach(function (done) {
TestLocation.history = [ '/feed' ];

div = document.createElement('div');
document.body.appendChild(div);

var MockScrollBehavior = {
updateScrollPosition() {
didUpdateScroll = true;
}
};

Router.create({
routes: routes,
location: TestLocation,
scrollBehavior: MockScrollBehavior
}).run(function (Handler) {
React.render(<Handler/>, div, function () {
done();
});
});
});

afterEach(function () {
div.parentNode.removeChild(div);
didUpdateScroll = false;
});

it('calls updateScroll the first time', function () {
expect(didUpdateScroll).toBe(true);
});

describe('decides whether to update scroll on transition', function () {
beforeEach(function () {
didUpdateScroll = false;
});

afterEach(function () {
TestLocation.pop();
});

it('calls updateScroll when no ancestors ignore scroll', function () {
TestLocation.push('/about');
expect(didUpdateScroll).toBe(true);
});

it('calls updateScroll when no ancestors ignore scroll although source and target do', function () {
TestLocation.push('/search/foo');
expect(didUpdateScroll).toBe(true);
});

it('calls updateScroll when route does not ignore scroll and only params change', function () {
TestLocation.replace('/users/3/posts');
didUpdateScroll = false;

TestLocation.push('/users/5/posts');
expect(didUpdateScroll).toBe(true);
});

it('calls updateScroll when route does not ignore scroll and both params and query change', function () {
TestLocation.replace('/users/3/posts');
didUpdateScroll = false;

TestLocation.push('/users/5/posts?page=2');
expect(didUpdateScroll).toBe(true);
});

it('does not call updateScroll when route does not ignore scroll but only query changes', function () {
TestLocation.replace('/users/3/posts');
didUpdateScroll = false;

TestLocation.push('/users/3/posts?page=2');
expect(didUpdateScroll).toBe(false);
});

it('does not call updateScroll when common ancestor ignores scroll', function () {
TestLocation.push('/discover');
expect(didUpdateScroll).toBe(false);
});

it('does not call updateScroll when route ignores scroll', function () {
TestLocation.replace('/search/foo');
didUpdateScroll = false;

TestLocation.push('/search/bar');
expect(didUpdateScroll).toBe(false);

TestLocation.replace('/search/bar?safe=0');
expect(didUpdateScroll).toBe(false);

TestLocation.replace('/search/whatever');
expect(didUpdateScroll).toBe(false);
});
});
});

describe('makePath', function () {
var router;
beforeEach(function () {
Expand Down
109 changes: 0 additions & 109 deletions modules/components/__tests__/Routes-test.js

This file was deleted.

3 changes: 3 additions & 0 deletions modules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ exports.HashLocation = require('./locations/HashLocation');
exports.HistoryLocation = require('./locations/HistoryLocation');
exports.RefreshLocation = require('./locations/RefreshLocation');

exports.ImitateBrowserBehavior = require('./behaviors/ImitateBrowserBehavior');
exports.ScrollToTopBehavior = require('./behaviors/ScrollToTopBehavior');

exports.Navigation = require('./mixins/Navigation');
exports.State = require('./mixins/State');

Expand Down
34 changes: 31 additions & 3 deletions modules/mixins/Scrolling.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
var invariant = require('react/lib/invariant');
var canUseDOM = require('react/lib/ExecutionEnvironment').canUseDOM;
var getWindowScrollPosition = require('../utils/getWindowScrollPosition');
var Path = require('../utils/Path');

function shouldUpdateScroll(state, prevState) {
if (!prevState) {
return true;
}

var path = state.path;
var routes = state.routes;
var prevPath = prevState.path;
var prevRoutes = prevState.routes;

if (Path.withoutQuery(path) === Path.withoutQuery(prevPath)) {
return false;
}

var sharedAncestorRoutes = routes.filter(function (route) {
return prevRoutes.indexOf(route) !== -1;
});

return !sharedAncestorRoutes.some(function (route) {
return route.ignoreScrollBehavior;
});
}

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

componentDidUpdate: function () {
this._updateScroll();
componentDidUpdate: function (prevProps, prevState) {
this._updateScroll(prevState);
},

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

_updateScroll: function () {
_updateScroll: function (prevState) {
if (!shouldUpdateScroll(this.state, prevState)) {
return;
}

var scrollBehavior = this.getScrollBehavior();

if (scrollBehavior)
Expand Down
4 changes: 4 additions & 0 deletions modules/utils/createRoutesFromChildren.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ function createRoute(element, parentRoute, namedRoutes) {

var route = { name: props.name };

if (props.ignoreScrollBehavior) {
route.ignoreScrollBehavior = true;
}

if (type === Redirect.type) {
route.handler = createRedirectHandler(props.to, props.params, props.query);
props.path = props.path || props.from || '*';
Expand Down