Socket
Socket
Sign inDemoInstall

react-router

Package Overview
Dependencies
Maintainers
2
Versions
506
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-router - npm Package Compare versions

Comparing version 0.9.3 to 0.9.4

modules/utils/__tests__/ServerRendering-test.js

15

CHANGELOG.md

@@ -0,5 +1,18 @@

v0.9.4 - Mon, 13 Oct 2014 19:53:10 GMT
--------------------------------------
- [0cd20cf](../../commit/0cd20cf) Revert "[removed] "static" <Route> props"
- [db26b7d](../../commit/db26b7d) [removed] "static" <Route> props
- [e571c27](../../commit/e571c27) [fixed] Add .active class to <Link>s with absolute hrefs
- [ea5a380](../../commit/ea5a380) [fixed] Make sure onChange is fired at synchronous first render
- [dee374f](../../commit/dee374f) [fixed] Listen to path changes caused by initial redirect, fixes #360
- [d47d7dd](../../commit/d47d7dd) [fixed] potential infinite loop during transitions
- [1b1a62b](../../commit/1b1a62b) [added] Server-side rendering
- [c7ca87e](../../commit/c7ca87e) [added] <Routes onError>
v0.9.3 - Wed, 08 Oct 2014 14:44:52 GMT
--------------------------------------
-
- [caf3a2b](../../commit/caf3a2b) [fixed] scrollBehavior='none' on path update

@@ -6,0 +19,0 @@

9

docs/api/components/Routes.md

@@ -32,3 +32,3 @@ API: `Routes` (component)

- `'imitateBrowser'` - default, imitates what browsers do in a typical
- `'browser'` - default, imitates what browsers do in a typical
page reload scenario: preserves scroll positions when using the back

@@ -43,2 +43,6 @@ button, scrolls up when you come to a new route by clicking a link,

### `onError`
Called when a transition throws an error.
#### signature

@@ -48,4 +52,3 @@

Example
-------
#### Example

@@ -52,0 +55,0 @@ ```jsx

@@ -29,3 +29,17 @@ API: `Router`

Router.Transitions
// methods
Router.renderRoutesToString
```
Methods
-------
### `renderRoutesToString(routes, path, callback)`
We will document this more when the data loading story finalizes.
### `renderRoutesToStaticMarkup(routes, path, callback)`
We will document this more when the data loading story finalizes.

@@ -383,4 +383,5 @@ React Router Guide

var Routes = Router.Routes;
var NotFoundRoute = Router.NotFoundRoute;
var DefaultRoute = Router.DefaultRoute;
var Link = Router.Link;
```

@@ -16,3 +16,7 @@ var LocationActions = require('../actions/LocationActions');

case LocationActions.POP:
window.scrollTo(position.x, position.y);
if (position) {
window.scrollTo(position.x, position.y);
} else {
window.scrollTo(0, 0);
}
break;

@@ -19,0 +23,0 @@ }

@@ -15,4 +15,2 @@ var assert = require('assert');

afterEach(require('../../stores/PathStore').teardown);
describe('A DefaultRoute', function () {

@@ -69,5 +67,5 @@

var component, rootRoute, defaultRoute;
beforeEach(function () {
beforeEach(function (done) {
component = ReactTestUtils.renderIntoDocument(
Routes({ location: 'none', initialPath: '/users/5' },
Routes({ location: 'none' },
rootRoute = Route({ name: 'user', path: '/users/:id', handler: NullHandler },

@@ -80,3 +78,5 @@ Route({ name: 'home', path: '/users/:id/home', handler: NullHandler }),

)
)
);
component.dispatch('/users/5', done);
});

@@ -83,0 +83,0 @@

@@ -12,4 +12,2 @@ var assert = require('assert');

afterEach(require('../../stores/PathStore').teardown);
describe('with params and a query', function () {

@@ -23,8 +21,14 @@ var HomeHandler = React.createClass({

var component;
beforeEach(function () {
beforeEach(function (done) {
component = ReactTestUtils.renderIntoDocument(
Routes({ location: 'none', initialPath: '/mjackson/home' },
Routes({ location: 'none' },
Route({ name: 'home', path: '/:username/home', handler: HomeHandler })
)
);
component.dispatch('/mjackson/home', function (error, abortReason, nextState) {
expect(error).toBe(null);
expect(abortReason).toBe(null);
component.setState(nextState, done);
});
});

@@ -50,8 +54,14 @@

var component;
beforeEach(function () {
beforeEach(function (done) {
component = ReactTestUtils.renderIntoDocument(
Routes({ location: 'none', initialPath: '/' },
Routes({ location: 'none' },
DefaultRoute({ name: 'home', handler: HomeHandler })
)
);
component.dispatch('/', function (error, abortReason, nextState) {
expect(error).toBe(null);
expect(abortReason).toBe(null);
component.setState(nextState, done);
});
});

@@ -63,3 +73,3 @@

it('has its active class name', function () {
it('is active', function () {
var linkComponent = component.getActiveComponent().refs.link;

@@ -70,2 +80,34 @@ expect(linkComponent.getClassName()).toEqual('a-link highlight');

describe('when the path it links to is active', function () {
var HomeHandler = React.createClass({
render: function () {
return Link({ ref: 'link', to: '/home', className: 'a-link', activeClassName: 'highlight' });
}
});
var component;
beforeEach(function (done) {
component = ReactTestUtils.renderIntoDocument(
Routes({ location: 'none' },
Route({ path: '/home', handler: HomeHandler })
)
);
component.dispatch('/home', function (error, abortReason, nextState) {
expect(error).toBe(null);
expect(abortReason).toBe(null);
component.setState(nextState, done);
});
});
afterEach(function () {
React.unmountComponentAtNode(component.getDOMNode());
});
it('is active', function () {
var linkComponent = component.getActiveComponent().refs.link;
expect(linkComponent.getClassName()).toEqual('a-link highlight');
});
});
});

@@ -66,5 +66,5 @@ var assert = require('assert');

var component, rootRoute, notFoundRoute;
beforeEach(function () {
beforeEach(function (done) {
component = ReactTestUtils.renderIntoDocument(
Routes({ location: 'none', initialPath: '/users/5' },
Routes({ location: 'none' },
rootRoute = Route({ name: 'user', path: '/users/:id', handler: NullHandler },

@@ -77,3 +77,5 @@ Route({ name: 'home', path: '/users/:id/home', handler: NullHandler }),

)
)
);
component.dispatch('/users/5', done);
});

@@ -80,0 +82,0 @@

var React = require('react');
var classSet = require('react/lib/cx');
var merge = require('react/lib/merge');

@@ -83,8 +84,11 @@ var ActiveState = require('../mixins/ActiveState');

getClassName: function () {
var className = this.props.className || '';
var classNames = {};
if (this.props.className)
classNames[this.props.className] = true;
if (this.isActive(this.props.to, this.props.params, this.props.query))
className += ' ' + this.props.activeClassName;
classNames[this.props.activeClassName] = true;
return className;
return classSet(classNames);
},

@@ -91,0 +95,0 @@

@@ -28,6 +28,2 @@ var React = require('react');

*
* Unlike Ember, a nested route's path does not build upon that of its parents.
* This may seem like it creates more work up front in specifying URLs, but it
* has the nice benefit of decoupling nested UI from "nested" URLs.
*
* The preferred way to configure a router is using JSX. The XML-like syntax is

@@ -34,0 +30,0 @@ * a great way to visualize how routes are laid out in an application.

var React = require('react');
var warning = require('react/lib/warning');
var invariant = require('react/lib/invariant');
var canUseDOM = require('react/lib/ExecutionEnvironment').canUseDOM;
var copyProperties = require('react/lib/copyProperties');
var PathStore = require('../stores/PathStore');
var HashLocation = require('../locations/HashLocation');
var ActiveContext = require('../mixins/ActiveContext');
var LocationContext = require('../mixins/LocationContext');
var RouteContext = require('../mixins/RouteContext');
var ScrollContext = require('../mixins/ScrollContext');
var reversedArray = require('../utils/reversedArray');

@@ -33,3 +35,3 @@ var Transition = require('../utils/Transition');

var rootParams = getRootMatch(matches).params;
params = route.props.paramNames.reduce(function (params, paramName) {

@@ -76,76 +78,3 @@ params[paramName] = rootParams[paramName];

function updateMatchComponents(matches, refs) {
var i = 0, component;
while (component = refs.__activeRoute__) {
matches[i++].component = component;
refs = component.refs;
}
}
/**
* Computes the next state for the given component and calls
* callback(error, nextState) when finished. Also runs all
* transition hooks along the way.
*/
function computeNextState(component, transition, callback) {
if (component.state.path === transition.path)
return callback(); // Nothing to do!
var currentMatches = component.state.matches;
var nextMatches = component.match(transition.path);
warning(
nextMatches,
'No route matches path "' + transition.path + '". Make sure you have ' +
'<Route path="' + transition.path + '"> somewhere in your routes'
);
if (!nextMatches)
nextMatches = [];
var fromMatches, toMatches;
if (currentMatches.length) {
updateMatchComponents(currentMatches, component.refs);
fromMatches = currentMatches.filter(function (match) {
return !hasMatch(nextMatches, match);
});
toMatches = nextMatches.filter(function (match) {
return !hasMatch(currentMatches, match);
});
} else {
fromMatches = [];
toMatches = nextMatches;
}
var query = Path.extractQuery(transition.path) || {};
runTransitionFromHooks(fromMatches, transition, function (error) {
if (error || transition.isAborted)
return callback(error);
runTransitionToHooks(toMatches, transition, query, function (error) {
if (error || transition.isAborted)
return callback(error);
var matches = currentMatches.slice(0, currentMatches.length - fromMatches.length).concat(toMatches);
var rootMatch = getRootMatch(matches);
var params = (rootMatch && rootMatch.params) || {};
var routes = matches.map(function (match) {
return match.route;
});
callback(null, {
path: transition.path,
matches: matches,
activeRoutes: routes,
activeParams: params,
activeQuery: query
});
});
});
}
/**
* Calls the willTransitionFrom hook of all handlers in the given matches

@@ -226,2 +155,15 @@ * serially in reverse with the transition object and the current instance of

function updateMatchComponents(matches, refs) {
var match;
for (var i = 0, len = matches.length; i < len; ++i) {
match = matches[i];
match.component = refs.__activeRoute__;
if (match.component == null)
break; // End of the tree.
refs = match.component.refs;
}
}
function returnNull() {

@@ -231,76 +173,29 @@ return null;

function computeHandlerProps(matches, query) {
var handler = returnNull;
var props = {
ref: null,
params: null,
query: null,
activeRouteHandler: handler,
key: null
};
reversedArray(matches).forEach(function (match) {
var route = match.route;
props = Route.getUnreservedProps(route.props);
props.ref = '__activeRoute__';
props.params = match.params;
props.query = query;
props.activeRouteHandler = handler;
// TODO: Can we remove addHandlerKey?
if (route.props.addHandlerKey)
props.key = Path.injectParams(route.props.path, match.params);
handler = function (props, addedProps) {
if (arguments.length > 2 && typeof arguments[2] !== 'undefined')
throw new Error('Passing children to a route handler is not supported');
return route.props.handler(
copyProperties(props, addedProps)
);
}.bind(this, props);
function routeIsActive(activeRoutes, routeName) {
return activeRoutes.some(function (route) {
return route.props.name === routeName;
});
return props;
}
var BrowserTransitionHandling = {
function paramsAreActive(activeParams, params) {
for (var property in params)
if (String(activeParams[property]) !== String(params[property]))
return false;
handleTransitionError: function (component, error) {
throw error; // This error probably originated in a transition hook.
},
return true;
}
handleAbortedTransition: function (component, transition) {
var reason = transition.abortReason;
function queryIsActive(activeQuery, query) {
for (var property in query)
if (String(activeQuery[property]) !== String(query[property]))
return false;
if (reason instanceof Redirect) {
component.replaceWith(reason.to, reason.params, reason.query);
} else {
component.goBack();
}
}
return true;
}
};
function defaultTransitionErrorHandler(error) {
// Throw so we don't silently swallow async errors.
throw error; // This error probably originated in a transition hook.
}
var ServerTransitionHandling = {
handleTransitionError: function (component, error) {
// TODO
},
handleAbortedTransition: function (component, transition) {
// TODO
}
};
var TransitionHandling = canUseDOM ? BrowserTransitionHandling : ServerTransitionHandling;
var ActiveContext = require('../mixins/ActiveContext');
var LocationContext = require('../mixins/LocationContext');
var RouteContext = require('../mixins/RouteContext');
var ScrollContext = require('../mixins/ScrollContext');
/**

@@ -316,51 +211,44 @@ * The <Routes> component configures the route hierarchy and renders the

mixins: [ ActiveContext, LocationContext, RouteContext, ScrollContext ],
mixins: [ RouteContext, ActiveContext, LocationContext, ScrollContext ],
propTypes: {
initialPath: React.PropTypes.string,
onChange: React.PropTypes.func
initialMatches: React.PropTypes.array,
onChange: React.PropTypes.func,
onError: React.PropTypes.func.isRequired
},
getInitialState: function () {
getDefaultProps: function () {
return {
matches: []
initialPath: null,
initialMatches: [],
onError: defaultTransitionErrorHandler
};
},
componentWillMount: function () {
this.handlePathChange(this.props.initialPath);
getInitialState: function () {
return {
path: this.props.initialPath,
matches: this.props.initialMatches
};
},
componentDidMount: function () {
PathStore.addChangeListener(this.handlePathChange);
},
warning(
this._owner == null,
'<Routes> should be rendered directly using React.renderComponent, not ' +
'inside some other component\'s render method'
);
componentWillUnmount: function () {
PathStore.removeChangeListener(this.handlePathChange);
if (this._handleStateChange) {
this._handleStateChange();
delete this._handleStateChange;
}
},
handlePathChange: function (_path) {
var path = _path || PathStore.getCurrentPath();
var actionType = PathStore.getCurrentActionType();
if (this.state.path === path)
return; // Nothing to do!
if (this.state.path)
this.recordScroll(this.state.path);
var self = this;
this.dispatch(path, function (error, transition) {
if (error) {
TransitionHandling.handleTransitionError(self, error);
} else if (transition.isAborted) {
TransitionHandling.handleAbortedTransition(self, transition);
} else {
self.updateScroll(path, actionType);
if (self.props.onChange)
self.props.onChange.call(self);
}
});
componentDidUpdate: function () {
if (this._handleStateChange) {
this._handleStateChange();
delete this._handleStateChange;
}
},

@@ -386,31 +274,102 @@

match: function (path) {
return findMatches(Path.withoutQuery(path), this.getRoutes(), this.props.defaultRoute, this.props.notFoundRoute);
var routes = this.getRoutes();
return findMatches(Path.withoutQuery(path), routes, this.props.defaultRoute, this.props.notFoundRoute);
},
updateLocation: function (path, actionType) {
if (this.state.path === path)
return; // Nothing to do!
if (this.state.path)
this.recordScroll(this.state.path);
this.dispatch(path, function (error, abortReason, nextState) {
if (error) {
this.props.onError.call(this, error);
} else if (abortReason instanceof Redirect) {
this.replaceWith(abortReason.to, abortReason.params, abortReason.query);
} else if (abortReason) {
this.goBack();
} else {
this._handleStateChange = this.handleStateChange.bind(this, path, actionType);
this.setState(nextState);
}
});
},
handleStateChange: function (path, actionType) {
updateMatchComponents(this.state.matches, this.refs);
this.updateScroll(path, actionType);
if (this.props.onChange)
this.props.onChange.call(this);
},
/**
* Performs a transition to the given path and calls callback(error, transition)
* with the Transition object when the transition is finished and the component's
* state has been updated accordingly.
* Performs a transition to the given path and calls callback(error, abortReason, nextState)
* when the transition is finished. If there was an error, the first argument will not be null.
* Otherwise, if the transition was aborted for some reason, it will be given in the second arg.
*
* In a transition, the router first determines which routes are involved by
* beginning with the current route, up the route tree to the first parent route
* that is shared with the destination route, and back down the tree to the
* destination route. The willTransitionFrom hook is invoked on all route handlers
* we're transitioning away from, in reverse nesting order. Likewise, the
* In a transition, the router first determines which routes are involved by beginning with the
* current route, up the route tree to the first parent route that is shared with the destination
* route, and back down the tree to the destination route. The willTransitionFrom hook is invoked
* on all route handlers we're transitioning away from, in reverse nesting order. Likewise, the
* willTransitionTo hook is invoked on all route handlers we're transitioning to.
*
* Both willTransitionFrom and willTransitionTo hooks may either abort or redirect
* the transition. To resolve asynchronously, they may use transition.wait(promise).
* Both willTransitionFrom and willTransitionTo hooks may either abort or redirect the transition.
* To resolve asynchronously, they may use transition.wait(promise). If no hooks wait, the
* transition will be synchronous.
*/
dispatch: function (path, callback) {
var transition = new Transition(this, path);
var self = this;
var currentMatches = this.state ? this.state.matches : []; // No state server-side.
var nextMatches = this.match(path) || [];
computeNextState(this, transition, function (error, nextState) {
if (error || nextState == null)
return callback(error, transition);
warning(
nextMatches.length,
'No route matches path "%s". Make sure you have <Route path="%s"> somewhere in your <Routes>',
path, path
);
self.setState(nextState, function () {
callback(null, transition);
var fromMatches, toMatches;
if (currentMatches.length) {
fromMatches = currentMatches.filter(function (match) {
return !hasMatch(nextMatches, match);
});
toMatches = nextMatches.filter(function (match) {
return !hasMatch(currentMatches, match);
});
} else {
fromMatches = [];
toMatches = nextMatches;
}
var callbackScope = this;
var query = Path.extractQuery(path) || {};
runTransitionFromHooks(fromMatches, transition, function (error) {
if (error || transition.isAborted)
return callback.call(callbackScope, error, transition.abortReason);
runTransitionToHooks(toMatches, transition, query, function (error) {
if (error || transition.isAborted)
return callback.call(callbackScope, error, transition.abortReason);
var matches = currentMatches.slice(0, currentMatches.length - fromMatches.length).concat(toMatches);
var rootMatch = getRootMatch(matches);
var params = (rootMatch && rootMatch.params) || {};
var routes = matches.map(function (match) {
return match.route;
});
callback.call(callbackScope, null, null, {
path: path,
matches: matches,
activeRoutes: routes,
activeParams: params,
activeQuery: query
});
});
});

@@ -423,3 +382,38 @@ },

getHandlerProps: function () {
return computeHandlerProps(this.state.matches, this.state.activeQuery);
var matches = this.state.matches;
var query = this.state.activeQuery;
var handler = returnNull;
var props = {
ref: null,
params: null,
query: null,
activeRouteHandler: handler,
key: null
};
reversedArray(matches).forEach(function (match) {
var route = match.route;
props = Route.getUnreservedProps(route.props);
props.ref = '__activeRoute__';
props.params = match.params;
props.query = query;
props.activeRouteHandler = handler;
// TODO: Can we remove addHandlerKey?
if (route.props.addHandlerKey)
props.key = Path.injectParams(route.props.path, match.params);
handler = function (props, addedProps) {
if (arguments.length > 2 && typeof arguments[2] !== 'undefined')
throw new Error('Passing children to a route handler is not supported');
return route.props.handler(
copyProperties(props, addedProps)
);
}.bind(this, props);
});
return props;
},

@@ -455,4 +449,4 @@

route,
'Unable to find a route named "' + to + '". Make sure you have ' +
'a <Route name="' + to + '"> defined somewhere in your <Routes>'
'Unable to find a route named "%s". Make sure you have <Route name="%s"> somewhere in your <Routes>',
to, to
);

@@ -523,2 +517,14 @@

/**
* Returns true if the given route, params, and query are active.
*/
isActive: function (to, params, query) {
if (Path.isAbsolute(to))
return to === this.getCurrentPath();
return routeIsActive(this.getActiveRoutes(), to) &&
paramsAreActive(this.getActiveParams(), params) &&
(query == null || queryIsActive(this.getActiveQuery(), query));
},
render: function () {

@@ -541,3 +547,4 @@ var match = this.state.matches[0];

replaceWith: React.PropTypes.func.isRequired,
goBack: React.PropTypes.func.isRequired
goBack: React.PropTypes.func.isRequired,
isActive: React.PropTypes.func.isRequired
},

@@ -552,3 +559,4 @@

replaceWith: this.replaceWith,
goBack: this.goBack
goBack: this.goBack,
isActive: this.isActive
};

@@ -555,0 +563,0 @@ }

@@ -11,1 +11,4 @@ exports.DefaultRoute = require('./components/DefaultRoute');

exports.Navigation = require('./mixins/Navigation');
exports.renderRoutesToString = require('./utils/ServerRendering').renderRoutesToString;
exports.renderRoutesToStaticMarkup = require('./utils/ServerRendering').renderRoutesToStaticMarkup;
var assert = require('assert');
var expect = require('expect');
var React = require('react/addons');
var ReactTestUtils = React.addons.TestUtils;
var Routes = require('../../components/Routes');
var Route = require('../../components/Route');
var ActiveContext = require('../ActiveContext');

@@ -10,3 +11,2 @@ describe('ActiveContext', function () {

var App = React.createClass({
mixins: [ ActiveContext ],
render: function () {

@@ -18,17 +18,16 @@ return null;

describe('when a route is active', function () {
var route;
beforeEach(function () {
route = Route({ name: 'products', handler: App });
});
describe('and it has no params', function () {
var component;
beforeEach(function () {
beforeEach(function (done) {
component = ReactTestUtils.renderIntoDocument(
App({
initialActiveState: {
activeRoutes: [ route ]
}
})
Routes({ location: 'none' },
Route({ name: 'home', handler: App })
)
);
component.dispatch('/home', function (error, abortReason, nextState) {
expect(error).toBe(null);
expect(abortReason).toBe(null);
component.setState(nextState, done);
});
});

@@ -41,3 +40,3 @@

it('is active', function () {
assert(component.isActive('products'));
assert(component.isActive('home'));
});

@@ -48,12 +47,14 @@ });

var component;
beforeEach(function () {
beforeEach(function (done) {
component = ReactTestUtils.renderIntoDocument(
App({
initialActiveState: {
activeRoutes: [ route ],
activeParams: { id: '123', show: 'true', variant: 456 },
activeQuery: { search: 'abc', limit: 789 }
}
})
Routes({ location: 'none' },
Route({ name: 'products', path: '/products/:id/:variant', handler: App })
)
);
component.dispatch('/products/123/456?search=abc&limit=789', function (error, abortReason, nextState) {
expect(error).toBe(null);
expect(abortReason).toBe(null);
component.setState(nextState, done);
});
});

@@ -73,3 +74,3 @@

it('is active', function () {
assert(component.isActive('products', { id: 123 }, { search: 'abc', limit: '789' }));
assert(component.isActive('products', { id: 123 }, { search: 'abc' }));
});

@@ -80,3 +81,3 @@ });

it('is not active', function () {
assert(component.isActive('products', { id: 123 }, { search: 'def', limit: '123' }) === false);
assert(component.isActive('products', { id: 123 }, { search: 'def' }) === false);
});

@@ -88,11 +89,14 @@ });

var component;
beforeEach(function () {
beforeEach(function (done) {
component = ReactTestUtils.renderIntoDocument(
App({
initialActiveState: {
activeRoutes: [ route ],
activeParams: { id: 123 }
}
})
Routes({ location: 'none' },
Route({ name: 'products', path: '/products/:id', handler: App })
)
);
component.dispatch('/products/123', function (error, abortReason, nextState) {
expect(error).toBe(null);
expect(abortReason).toBe(null);
component.setState(nextState, done);
});
});

@@ -99,0 +103,0 @@

@@ -23,8 +23,14 @@ var assert = require('assert');

var component;
beforeEach(function () {
beforeEach(function (done) {
component = ReactTestUtils.renderIntoDocument(
Routes({ initialPath: '/anybody/home' },
Routes({ location: 'none', onChange: done },
Route({ name: 'home', path: '/:username/home', handler: NavigationHandler })
)
);
component.dispatch('/anybody/home', function (error, abortReason, nextState) {
expect(error).toBe(null);
expect(abortReason).toBe(null);
component.setState(nextState, done);
});
});

@@ -45,8 +51,14 @@

var component;
beforeEach(function () {
beforeEach(function (done) {
component = ReactTestUtils.renderIntoDocument(
Routes({ initialPath: '/home' },
Routes({ location: 'none' },
Route({ name: 'home', handler: NavigationHandler })
)
);
component.dispatch('/home', function (error, abortReason, nextState) {
expect(error).toBe(null);
expect(abortReason).toBe(null);
component.setState(nextState, done);
});
});

@@ -72,8 +84,14 @@

var component;
beforeEach(function () {
beforeEach(function (done) {
component = ReactTestUtils.renderIntoDocument(
Routes({ location: 'hash', initialPath: '/home' },
Routes({ location: 'hash' },
Route({ name: 'home', handler: NavigationHandler })
)
);
component.dispatch('/home', function (error, abortReason, nextState) {
expect(error).toBe(null);
expect(abortReason).toBe(null);
component.setState(nextState, done);
});
});

@@ -94,8 +112,14 @@

var component;
beforeEach(function () {
beforeEach(function (done) {
component = ReactTestUtils.renderIntoDocument(
Routes({ location: 'history', initialPath: '/home' },
Routes({ location: 'history' },
Route({ name: 'home', handler: NavigationHandler })
)
);
component.dispatch('/home', function (error, abortReason, nextState) {
expect(error).toBe(null);
expect(abortReason).toBe(null);
component.setState(nextState, done);
});
});

@@ -102,0 +126,0 @@

var React = require('react');
var copyProperties = require('react/lib/copyProperties');
function routeIsActive(activeRoutes, routeName) {
return activeRoutes.some(function (route) {
return route.props.name === routeName;
});
}
function paramsAreActive(activeParams, params) {
for (var property in params)
if (String(activeParams[property]) !== String(params[property]))
return false;
return true;
}
function queryIsActive(activeQuery, query) {
for (var property in query)
if (String(activeQuery[property]) !== String(query[property]))
return false;
return true;
}
/**
* A mixin for components that store the active state of routes, URL
* parameters, and query.
* A mixin for components that store the active state of routes,
* URL parameters, and query.
*/

@@ -33,3 +11,5 @@ var ActiveContext = {

propTypes: {
initialActiveState: React.PropTypes.object
initialActiveRoutes: React.PropTypes.array.isRequired,
initialActiveParams: React.PropTypes.object.isRequired,
initialActiveQuery: React.PropTypes.object.isRequired
},

@@ -39,3 +19,5 @@

return {
initialActiveState: {}
initialActiveRoutes: [],
initialActiveParams: {},
initialActiveQuery: {}
};

@@ -45,8 +27,6 @@ },

getInitialState: function () {
var state = this.props.initialActiveState;
return {
activeRoutes: state.activeRoutes || [],
activeParams: state.activeParams || {},
activeQuery: state.activeQuery || {}
activeRoutes: this.props.initialActiveRoutes,
activeParams: this.props.initialActiveParams,
activeQuery: this.props.initialActiveQuery
};

@@ -76,21 +56,6 @@ },

/**
* Returns true if the route with the given name, URL parameters, and
* query are all currently active.
*/
isActive: function (routeName, params, query) {
var isActive = routeIsActive(this.state.activeRoutes, routeName) &&
paramsAreActive(this.state.activeParams, params);
if (query)
return isActive && queryIsActive(this.state.activeQuery, query);
return isActive;
},
childContextTypes: {
activeRoutes: React.PropTypes.array.isRequired,
activeParams: React.PropTypes.object.isRequired,
activeQuery: React.PropTypes.object.isRequired,
isActive: React.PropTypes.func.isRequired
activeQuery: React.PropTypes.object.isRequired
},

@@ -102,4 +67,3 @@

activeParams: this.getActiveParams(),
activeQuery: this.getActiveQuery(),
isActive: this.isActive
activeQuery: this.getActiveQuery()
};

@@ -106,0 +70,0 @@ }

@@ -40,18 +40,2 @@ var React = require('react');

getInitialState: function () {
var location = this.props.location;
if (typeof location === 'string')
location = NAMED_LOCATIONS[location];
// Automatically fall back to full page refreshes in
// browsers that do not support HTML5 history.
if (location === HistoryLocation && !supportsHistory())
location = RefreshLocation;
return {
location: location
};
},
componentWillMount: function () {

@@ -65,6 +49,21 @@ var location = this.getLocation();

if (location)
if (location) {
PathStore.setup(location);
PathStore.addChangeListener(this.handlePathChange);
if (this.updateLocation)
this.updateLocation(PathStore.getCurrentPath(), PathStore.getCurrentActionType());
}
},
componentWillUnmount: function () {
if (this.getLocation())
PathStore.removeChangeListener(this.handlePathChange);
},
handlePathChange: function () {
if (this.updateLocation)
this.updateLocation(PathStore.getCurrentPath(), PathStore.getCurrentActionType());
},
/**

@@ -74,3 +73,17 @@ * Returns the location object this component uses.

getLocation: function () {
return this.state.location;
if (this._location == null) {
var location = this.props.location;
if (typeof location === 'string')
location = NAMED_LOCATIONS[location];
// Automatically fall back to full page refreshes in
// browsers that do not support HTML5 history.
if (location === HistoryLocation && !supportsHistory())
location = RefreshLocation;
this._location = location;
}
return this._location;
},

@@ -77,0 +90,0 @@

@@ -122,9 +122,5 @@ var React = require('react');

getInitialState: function () {
var namedRoutes = {};
return {
routes: processRoutes(this.props.children, this, namedRoutes),
namedRoutes: namedRoutes
};
_processRoutes: function () {
this._namedRoutes = {};
this._routes = processRoutes(this.props.children, this, this._namedRoutes);
},

@@ -136,3 +132,6 @@

getRoutes: function () {
return this.state.routes;
if (this._routes == null)
this._processRoutes();
return this._routes;
},

@@ -144,3 +143,6 @@

getNamedRoutes: function () {
return this.state.namedRoutes;
if (this._namedRoutes == null)
this._processRoutes();
return this._namedRoutes;
},

@@ -152,3 +154,4 @@

getRouteByName: function (routeName) {
return this.state.namedRoutes[routeName] || null;
var namedRoutes = this.getNamedRoutes();
return namedRoutes[routeName] || null;
},

@@ -155,0 +158,0 @@

@@ -49,28 +49,12 @@ var React = require('react');

getInitialState: function () {
var behavior = this.props.scrollBehavior;
if (typeof behavior === 'string')
behavior = NAMED_SCROLL_BEHAVIORS[behavior];
return {
scrollBehavior: behavior
};
},
componentWillMount: function () {
var behavior = this.getScrollBehavior();
invariant(
behavior == null || canUseDOM,
this.getScrollBehavior() == null || canUseDOM,
'Cannot use scroll behavior without a DOM'
);
if (behavior != null)
this._scrollPositions = {};
},
recordScroll: function (path) {
if (this._scrollPositions)
this._scrollPositions[path] = getWindowScrollPosition();
var positions = this.getScrollPositions();
positions[path] = getWindowScrollPosition();
},

@@ -80,6 +64,6 @@

var behavior = this.getScrollBehavior();
var position = this.getScrollPosition(path) || null;
if (behavior != null && this._scrollPositions.hasOwnProperty(path)) {
behavior.updateScrollPosition(this._scrollPositions[path], actionType);
}
if (behavior)
behavior.updateScrollPosition(position, actionType);
},

@@ -91,5 +75,32 @@

getScrollBehavior: function () {
return this.state.scrollBehavior;
if (this._scrollBehavior == null) {
var behavior = this.props.scrollBehavior;
if (typeof behavior === 'string')
behavior = NAMED_SCROLL_BEHAVIORS[behavior];
this._scrollBehavior = behavior;
}
return this._scrollBehavior;
},
/**
* Returns a hash of URL paths to their last known scroll positions.
*/
getScrollPositions: function () {
if (this._scrollPositions == null)
this._scrollPositions = {};
return this._scrollPositions;
},
/**
* Returns the last known scroll position for the given URL path.
*/
getScrollPosition: function (path) {
var positions = this.getScrollPositions();
return positions[path];
},
childContextTypes: {

@@ -96,0 +107,0 @@ scrollBehavior: React.PropTypes.object // Not required on the server.

@@ -50,2 +50,30 @@ var expect = require('expect');

describe('and the pattern is optional', function () {
var pattern = 'comments/:id?/edit'
describe('and the path matches with supplied param', function () {
it('returns an object with the params', function () {
expect(Path.extractParams(pattern, 'comments/123/edit')).toEqual({ id: '123' });
});
});
describe('and the path matches without supplied param', function () {
it('returns an object with param set to null', function () {
expect(Path.extractParams(pattern, 'comments//edit')).toEqual({id: null});
});
});
});
describe('and the pattern and forward slash are optional', function () {
var pattern = 'comments/:id?/?edit'
describe('and the path matches with supplied param', function () {
it('returns an object with the params', function () {
expect(Path.extractParams(pattern, 'comments/123/edit')).toEqual({ id: '123' });
});
});
describe('and the path matches without supplied param', function () {
it('returns an object with param set to null', function () {
expect(Path.extractParams(pattern, 'comments/edit')).toEqual({id: null});
});
});
});
describe('and the path does not match', function () {

@@ -170,2 +198,26 @@ it('returns null', function () {

describe('and a param is optional', function () {
var pattern = 'comments/:id?/edit';
it('returns the correct path when param is supplied', function () {
expect(Path.injectParams(pattern, {id:'123'})).toEqual('comments/123/edit');
});
it('returns the correct path when param is not supplied', function () {
expect(Path.injectParams(pattern, {})).toEqual('comments//edit');
});
});
describe('and a param and forward slash are optional', function () {
var pattern = 'comments/:id?/?edit';
it('returns the correct path when param is supplied', function () {
expect(Path.injectParams(pattern, {id:'123'})).toEqual('comments/123/edit');
});
it('returns the correct path when param is not supplied', function () {
expect(Path.injectParams(pattern, {})).toEqual('comments/edit');
});
});
describe('and all params are present', function () {

@@ -172,0 +224,0 @@ it('returns the correct path', function () {

@@ -18,3 +18,4 @@ var invariant = require('react/lib/invariant');

var paramCompileMatcher = /:([a-zA-Z_$][a-zA-Z0-9_$]*)|[*.()\[\]\\+|{}^$]/g;
var paramInjectMatcher = /:([a-zA-Z_$][a-zA-Z0-9_$]*)|[*]/g;
var paramInjectMatcher = /:([a-zA-Z_$][a-zA-Z0-9_$?]*[?]?)|[*]/g;
var paramInjectTrailingSlashMatcher = /\/\/\?|\/\?/g;
var queryMatcher = /\?(.+)/;

@@ -90,6 +91,14 @@

invariant(
params[paramName] != null,
'Missing "' + paramName + '" parameter for path "' + pattern + '"'
);
// If param is optional don't check for existence
if (paramName.slice(-1) !== '?') {
invariant(
params[paramName] != null,
'Missing "' + paramName + '" parameter for path "' + pattern + '"'
);
} else {
paramName = paramName.slice(0, -1)
if (params[paramName] == null) {
return '';
}
}

@@ -109,3 +118,3 @@ var segment;

return encodeURLPath(segment);
});
}).replace(paramInjectTrailingSlashMatcher, '/');
},

@@ -112,0 +121,0 @@

{
"name": "react-router",
"version": "0.9.3",
"version": "0.9.4",
"description": "A complete routing library for React.js",

@@ -26,4 +26,6 @@ "main": "./modules/index",

"browserify-shim": "3.6.0",
"bundle-loader": "0.5.1",
"envify": "1.2.0",
"expect": "0.1.1",
"jsx-loader": "0.11.2",
"karma": "0.12.16",

@@ -39,3 +41,5 @@ "karma-browserify": "^0.2.1",

"rf-release": "0.3.2",
"uglify-js": "2.4.15"
"uglify-js": "2.4.15",
"webpack": "1.4.5",
"webpack-dev-server": "1.6.5"
},

@@ -42,0 +46,0 @@ "peerDependencies": {

@@ -14,2 +14,4 @@ React Router

[Try it out on JSBin](http://jsbin.com/sixose/1/edit)
Important Notes

@@ -51,3 +53,3 @@ ---------------

There is also a UMD build available on bower, find the library on
There is also a global build available on bower, find the library on
`window.ReactRouter`.

@@ -60,3 +62,3 @@

- Modular construction of route hierarchy
- Fully asynchronous transition hooks
- Sync and async transition hooks
- Transition abort / redirect / retry

@@ -67,3 +69,6 @@ - Dynamic segments

- Multiple root routes
- Hash or HTML5 history URLs
- Hash or HTML5 history (with fallback) URLs
- Declarative Redirect routes
- Declarative NotFound routes
- Browser scroll behavior with transitions

@@ -70,0 +75,0 @@ Check out the `examples` directory to see how simple previously complex UI

@@ -16,2 +16,3 @@ require('./modules/components/__tests__/DefaultRoute-test');

require('./modules/utils/__tests__/Path-test');
require('./modules/utils/__tests__/ServerRendering-test');

@@ -18,0 +19,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc