From 6192285887a40dac09fab69bdb77f0e89a8d7e8c Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sat, 11 Oct 2014 21:54:52 +0400 Subject: [PATCH] [added] to opt out of scroll behavior for itself and descendants --- modules/components/Route.js | 3 +- modules/components/Routes.js | 21 ++++- modules/components/__tests__/Routes-test.js | 89 +++++++++++++++++++++ 3 files changed, 108 insertions(+), 5 deletions(-) diff --git a/modules/components/Route.js b/modules/components/Route.js index 0ac233fb36..447bf61dd8 100644 --- a/modules/components/Route.js +++ b/modules/components/Route.js @@ -53,7 +53,8 @@ var Route = React.createClass({ handler: React.PropTypes.any.isRequired, getAsyncProps: React.PropTypes.func, path: React.PropTypes.string, - name: React.PropTypes.string + name: React.PropTypes.string, + ignoreScrollBehavior: React.PropTypes.bool }, render: function () { diff --git a/modules/components/Routes.js b/modules/components/Routes.js index 5e6fe7afc3..1982ede42b 100644 --- a/modules/components/Routes.js +++ b/modules/components/Routes.js @@ -163,6 +163,16 @@ function updateMatchComponents(matches, refs) { } } +function shouldUpdateScroll(currentMatches, previousMatches) { + var commonMatches = currentMatches.filter(function (match) { + return previousMatches.indexOf(match) !== -1; + }); + + return !commonMatches.some(function (match) { + return match.route.props.ignoreScrollBehavior; + }); +} + function returnNull() { return null; } @@ -285,16 +295,19 @@ var Routes = React.createClass({ } else if (abortReason) { this.goBack(); } else { - this._handleStateChange = this.handleStateChange.bind(this, path, actionType); + this._handleStateChange = this.handleStateChange.bind(this, path, actionType, this.state.matches); this.setState(nextState); } }); }, - handleStateChange: function (path, actionType) { - updateMatchComponents(this.state.matches, this.refs); + handleStateChange: function (path, actionType, previousMatches) { + var currentMatches = this.state.matches; + updateMatchComponents(currentMatches, this.refs); - this.updateScroll(path, actionType); + if (shouldUpdateScroll(currentMatches, previousMatches)) { + this.updateScroll(path, actionType); + } if (this.props.onChange) this.props.onChange.call(this); diff --git a/modules/components/__tests__/Routes-test.js b/modules/components/__tests__/Routes-test.js index 9fcd05a367..5ae4e661e9 100644 --- a/modules/components/__tests__/Routes-test.js +++ b/modules/components/__tests__/Routes-test.js @@ -69,4 +69,93 @@ describe('A Routes', function () { }); }); + describe('that considers ignoreScrollBehavior when calling updateScroll', function () { + var component; + beforeEach(function () { + component = ReactTestUtils.renderIntoDocument( + Routes({ location: 'none' }, + Route({ handler: NullHandler, ignoreScrollBehavior: true }, + Route({ path: '/feed', handler: NullHandler }), + Route({ path: '/discover', handler: NullHandler }) + ), + Route({ path: '/search', handler: NullHandler, ignoreScrollBehavior: true }), + Route({ path: '/about', handler: NullHandler }) + ) + ); + }); + + function spyOnUpdateScroll(action) { + var didCall = false; + + var realUpdateScroll = component.updateScroll; + component.updateScroll = function mockUpdateScroll() { + didCall = true; + realUpdateScroll.apply(component, arguments); + }; + + try { + action(); + } finally { + component.updateScroll = realUpdateScroll; + } + + return didCall; + } + + afterEach(function () { + React.unmountComponentAtNode(component.getDOMNode()); + }); + + it('calls updateScroll when no ancestors ignore scroll', function () { + component.updateLocation('/feed'); + + var calledUpdateScroll = spyOnUpdateScroll(function () { + component.updateLocation('/about'); + }); + + expect(calledUpdateScroll).toEqual(true); + }); + + it('calls updateScroll when no ancestors ignore scroll even though source and target do', function () { + component.updateLocation('/feed'); + + var calledUpdateScroll = spyOnUpdateScroll(function () { + component.updateLocation('/search'); + }); + + expect(calledUpdateScroll).toEqual(true); + }); + + it('calls updateScroll when source is same as target and does not ignore scroll', function () { + component.updateLocation('/about'); + + var calledUpdateScroll = spyOnUpdateScroll(function () { + component.updateLocation('/about?page=2'); + }); + + expect(calledUpdateScroll).toEqual(true); + }); + + it('does not call updateScroll when common ancestor ignores scroll', function () { + component.updateLocation('/feed'); + + var calledUpdateScroll = spyOnUpdateScroll(function () { + component.updateLocation('/discover'); + }); + + expect(calledUpdateScroll).toEqual(false); + }); + + it('does not call updateScroll when source is same as target and ignores scroll', function () { + component.updateLocation('/search'); + + var calledUpdateScroll = spyOnUpdateScroll(function () { + component.updateLocation('/search?q=test'); + }); + + expect(calledUpdateScroll).toEqual(false); + }); + + }); + });