Skip to content

Commit 6878120

Browse files
committed
[added] onAbortedTransition, onActiveStateChange, onTransitionError Routes props
Also, removed Routes.handleAsyncError and Routes.handleCancelledTransition
1 parent 58073ca commit 6878120

File tree

4 files changed

+212
-101
lines changed

4 files changed

+212
-101
lines changed

modules/components/Routes.js

+39-29
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,36 @@ var NAMED_LOCATIONS = {
3030
disabled: RefreshLocation // TODO: Remove
3131
};
3232

33+
/**
34+
* The default handler for aborted transitions. Redirects replace
35+
* the current URL and all others roll it back.
36+
*/
37+
function defaultAbortedTransitionHandler(transition) {
38+
var reason = transition.abortReason;
39+
40+
if (reason instanceof Redirect) {
41+
replaceWith(reason.to, reason.params, reason.query);
42+
} else {
43+
goBack();
44+
}
45+
}
46+
47+
/**
48+
* The default handler for active state updates.
49+
*/
50+
function defaultActiveStateChangeHandler(state) {
51+
ActiveStore.updateState(state);
52+
}
53+
54+
/**
55+
* The default handler for errors that were thrown asynchronously
56+
* while transitioning. The default behavior is to re-throw the
57+
* error so that it isn't silently swallowed.
58+
*/
59+
function defaultTransitionErrorHandler(error) {
60+
throw error; // This error probably originated in a transition hook.
61+
}
62+
3363
/**
3464
* The <Routes> component configures the route hierarchy and renders the
3565
* route matching the current location when rendered into a document.
@@ -39,33 +69,10 @@ var NAMED_LOCATIONS = {
3969
var Routes = React.createClass({
4070
displayName: 'Routes',
4171

42-
statics: {
43-
44-
/**
45-
* Handles errors that were thrown asynchronously. By default, the
46-
* error is re-thrown so we don't swallow them silently.
47-
*/
48-
handleAsyncError: function (error, route) {
49-
throw error; // This error probably originated in a transition hook.
50-
},
51-
52-
/**
53-
* Handles aborted transitions. By default, redirects replace the
54-
* current URL and all others roll it back.
55-
*/
56-
handleAbortedTransition: function (transition, routes) {
57-
var reason = transition.abortReason;
58-
59-
if (reason instanceof Redirect) {
60-
replaceWith(reason.to, reason.params, reason.query);
61-
} else {
62-
goBack();
63-
}
64-
}
65-
66-
},
67-
6872
propTypes: {
73+
onAbortedTransition: React.PropTypes.func.isRequired,
74+
onActiveStateChange: React.PropTypes.func.isRequired,
75+
onTransitionError: React.PropTypes.func.isRequired,
6976
preserveScrollPosition: React.PropTypes.bool,
7077
location: function (props, propName, componentName) {
7178
var location = props[propName];
@@ -77,6 +84,9 @@ var Routes = React.createClass({
7784

7885
getDefaultProps: function () {
7986
return {
87+
onAbortedTransition: defaultAbortedTransitionHandler,
88+
onActiveStateChange: defaultActiveStateChangeHandler,
89+
onTransitionError: defaultTransitionErrorHandler,
8090
preserveScrollPosition: false,
8191
location: HashLocation
8292
};
@@ -176,9 +186,9 @@ var Routes = React.createClass({
176186

177187
var promise = syncWithTransition(routes, transition).then(function (newState) {
178188
if (transition.isAborted) {
179-
Routes.handleAbortedTransition(transition, routes);
189+
routes.props.onAbortedTransition(transition);
180190
} else if (newState) {
181-
ActiveStore.updateState(newState);
191+
routes.props.onActiveStateChange(newState);
182192
}
183193

184194
return transition;
@@ -188,7 +198,7 @@ var Routes = React.createClass({
188198
promise = promise.then(undefined, function (error) {
189199
// Use setTimeout to break the promise chain.
190200
setTimeout(function () {
191-
Routes.handleAsyncError(error, routes);
201+
routes.props.onTransitionError(error);
192202
});
193203
});
194204
}

specs/Route.spec.js

+82-72
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require('./helper');
22
var Route = require('../modules/components/Route');
33
var Routes = require('../modules/components/Routes');
4+
var URLStore = require('../modules/stores/URLStore');
45

56
var App = React.createClass({
67
displayName: 'App',
@@ -9,113 +10,122 @@ var App = React.createClass({
910
}
1011
});
1112

12-
describe('a Route that matches a URL', function () {
13-
it('returns an array', function () {
14-
var routes = renderComponent(
15-
Routes(null,
16-
Route({ handler: App },
17-
Route({ path: '/a/b/c', handler: App })
18-
)
19-
)
20-
);
21-
22-
var matches = routes.match('/a/b/c');
23-
assert(matches);
24-
expect(matches.length).toEqual(2);
25-
26-
var rootMatch = getRootMatch(matches);
27-
expect(rootMatch.params).toEqual({});
13+
describe('Route', function() {
2814

29-
removeComponent(routes);
15+
afterEach(function() {
16+
URLStore.teardown();
17+
window.location.hash = '';
3018
});
3119

32-
describe('that contains dynamic segments', function () {
33-
it('returns an array with the correct params', function () {
20+
describe('a Route that matches a URL', function () {
21+
it('returns an array', function () {
3422
var routes = renderComponent(
3523
Routes(null,
3624
Route({ handler: App },
37-
Route({ path: '/posts/:id/edit', handler: App })
25+
Route({ path: '/a/b/c', handler: App })
3826
)
3927
)
4028
);
4129

42-
var matches = routes.match('/posts/abc/edit');
30+
var matches = routes.match('/a/b/c');
4331
assert(matches);
4432
expect(matches.length).toEqual(2);
4533

4634
var rootMatch = getRootMatch(matches);
47-
expect(rootMatch.params).toEqual({ id: 'abc' });
35+
expect(rootMatch.params).toEqual({});
36+
37+
// this causes tests to fail, no clue why ...
38+
//removeComponent(routes);
39+
});
40+
41+
describe('that contains dynamic segments', function () {
42+
it('returns an array with the correct params', function () {
43+
var routes = renderComponent(
44+
Routes(null,
45+
Route({ handler: App },
46+
Route({ path: '/posts/:id/edit', handler: App })
47+
)
48+
)
49+
);
50+
51+
var matches = routes.match('/posts/abc/edit');
52+
assert(matches);
53+
expect(matches.length).toEqual(2);
54+
55+
var rootMatch = getRootMatch(matches);
56+
expect(rootMatch.params).toEqual({ id: 'abc' });
4857

49-
removeComponent(routes);
58+
//removeComponent(routes);
59+
});
5060
});
5161
});
52-
});
5362

54-
describe('a Route that does not match the URL', function () {
55-
it('returns null', function () {
56-
var routes = renderComponent(
57-
Routes(null,
58-
Route({ handler: App },
59-
Route({ path: '/a/b/c', handler: App })
63+
describe('a Route that does not match the URL', function () {
64+
it('returns null', function () {
65+
var routes = renderComponent(
66+
Routes(null,
67+
Route({ handler: App },
68+
Route({ path: '/a/b/c', handler: App })
69+
)
6070
)
61-
)
62-
);
71+
);
6372

64-
expect(routes.match('/not-found')).toBe(null);
73+
expect(routes.match('/not-found')).toBe(null);
6574

66-
removeComponent(routes);
75+
//removeComponent(routes);
76+
});
6777
});
68-
});
6978

70-
describe('a nested Route that matches the URL', function () {
71-
it('returns the appropriate params for each match', function () {
72-
var routes = renderComponent(
73-
Routes(null,
74-
Route({ handler: App },
75-
Route({ name: 'posts', path: '/posts/:id', handler: App },
76-
Route({ name: 'comment', path: '/posts/:id/comments/:commentId', handler: App })
79+
describe('a nested Route that matches the URL', function () {
80+
it('returns the appropriate params for each match', function () {
81+
var routes = renderComponent(
82+
Routes(null,
83+
Route({ handler: App },
84+
Route({ name: 'posts', path: '/posts/:id', handler: App },
85+
Route({ name: 'comment', path: '/posts/:id/comments/:commentId', handler: App })
86+
)
7787
)
7888
)
79-
)
80-
);
89+
);
8190

82-
var matches = routes.match('/posts/abc/comments/123');
83-
assert(matches);
84-
expect(matches.length).toEqual(3);
91+
var matches = routes.match('/posts/abc/comments/123');
92+
assert(matches);
93+
expect(matches.length).toEqual(3);
8594

86-
var rootMatch = getRootMatch(matches);
87-
expect(rootMatch.route.props.name).toEqual('comment');
88-
expect(rootMatch.params).toEqual({ id: 'abc', commentId: '123' });
95+
var rootMatch = getRootMatch(matches);
96+
expect(rootMatch.route.props.name).toEqual('comment');
97+
expect(rootMatch.params).toEqual({ id: 'abc', commentId: '123' });
8998

90-
var postsMatch = matches[1];
91-
expect(postsMatch.route.props.name).toEqual('posts');
92-
expect(postsMatch.params).toEqual({ id: 'abc' });
99+
var postsMatch = matches[1];
100+
expect(postsMatch.route.props.name).toEqual('posts');
101+
expect(postsMatch.params).toEqual({ id: 'abc' });
93102

94-
removeComponent(routes);
103+
//removeComponent(routes);
104+
});
95105
});
96-
});
97106

98-
describe('multiple nested Router that match the URL', function () {
99-
it('returns the first one in the subtree, depth-first', function () {
100-
var routes = renderComponent(
101-
Routes(null,
102-
Route({ handler: App },
103-
Route({ path: '/a', handler: App },
104-
Route({ path: '/a/b', name: 'expected', handler: App })
105-
),
106-
Route({ path: '/a/b', handler: App })
107+
describe('multiple nested Router that match the URL', function () {
108+
it('returns the first one in the subtree, depth-first', function () {
109+
var routes = renderComponent(
110+
Routes(null,
111+
Route({ handler: App },
112+
Route({ path: '/a', handler: App },
113+
Route({ path: '/a/b', name: 'expected', handler: App })
114+
),
115+
Route({ path: '/a/b', handler: App })
116+
)
107117
)
108-
)
109-
);
118+
);
110119

111-
var matches = routes.match('/a/b');
112-
assert(matches);
113-
expect(matches.length).toEqual(3);
120+
var matches = routes.match('/a/b');
121+
assert(matches);
122+
expect(matches.length).toEqual(3);
114123

115-
var rootMatch = getRootMatch(matches);
116-
expect(rootMatch.route.props.name).toEqual('expected');
124+
var rootMatch = getRootMatch(matches);
125+
expect(rootMatch.route.props.name).toEqual('expected');
117126

118-
removeComponent(routes);
127+
//removeComponent(routes);
128+
});
119129
});
120130
});
121131

specs/Routes.spec.js

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
require('./helper');
2+
var URLStore = require('../modules/stores/URLStore');
3+
var Route = require('../modules/components/Route');
4+
var Routes = require('../modules/components/Routes');
5+
6+
describe('Routes', function() {
7+
8+
afterEach(function() {
9+
URLStore.teardown();
10+
window.location.hash = '';
11+
});
12+
13+
describe('a change in active state', function () {
14+
it('triggers onActiveStateChange', function (done) {
15+
var App = React.createClass({
16+
render: function () {
17+
return React.DOM.div();
18+
}
19+
});
20+
21+
function handleActiveStateChange(state) {
22+
assert(state);
23+
removeComponent(routes);
24+
done();
25+
}
26+
27+
var routes = renderComponent(
28+
Routes({ onActiveStateChange: handleActiveStateChange },
29+
Route({ handler: App })
30+
)
31+
);
32+
});
33+
});
34+
35+
describe('a cancelled transition', function () {
36+
it('triggers onCancelledTransition', function (done) {
37+
var App = React.createClass({
38+
statics: {
39+
willTransitionTo: function (transition) {
40+
transition.abort();
41+
}
42+
},
43+
render: function () {
44+
return React.DOM.div();
45+
}
46+
});
47+
48+
function handleCancelledTransition(transition) {
49+
assert(transition);
50+
removeComponent(routes);
51+
done();
52+
}
53+
54+
var routes = renderComponent(
55+
Routes({ onCancelledTransition: handleCancelledTransition },
56+
Route({ handler: App })
57+
)
58+
);
59+
});
60+
});
61+
62+
describe('an error in a transition hook', function () {
63+
it('triggers onTransitionError', function (done) {
64+
var App = React.createClass({
65+
statics: {
66+
willTransitionTo: function (transition) {
67+
throw new Error('boom!');
68+
}
69+
},
70+
render: function () {
71+
return React.DOM.div();
72+
}
73+
});
74+
75+
function handleTransitionError(error) {
76+
assert(error);
77+
expect(error.message).toEqual('boom!');
78+
removeComponent(routes);
79+
done();
80+
}
81+
82+
var routes = renderComponent(
83+
Routes({ onTransitionError: handleTransitionError },
84+
Route({ handler: App })
85+
)
86+
);
87+
});
88+
});
89+
90+
});

0 commit comments

Comments
 (0)