Skip to content

Commit 5c108d1

Browse files
committed
Some tweaks around $state.transition plus more tests (#41)
1 parent fa36560 commit 5c108d1

File tree

2 files changed

+93
-32
lines changed

2 files changed

+93
-32
lines changed

src/state.js

+6-8
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
136136
params: {},
137137
current: root.self,
138138
$current: root,
139-
140139
transition: null,
141-
$transition: $q.when(root.self),
142-
143140

144141
transitionTo: transitionTo,
145142

@@ -165,12 +162,13 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
165162
locals = toLocals[keep] = state.locals;
166163
}
167164

168-
// If we're going to the same state and all locals are kept, we've got nothing to do. But
169-
// update 'transition' anyway, as we still want to cancel any other pending transitions.
165+
// If we're going to the same state and all locals are kept, we've got nothing to do.
166+
// But clear 'transition', as we still want to cancel any other pending transitions.
170167
// TODO: We may not want to bump 'transition' if we're called from a location change that we've initiated ourselves,
171168
// because we might accidentally abort a legitimate transition initiated from code?
172169
if (to === from && locals === from.locals) {
173-
return $state.$transition = $q.when($state.current);
170+
$state.transition = null;
171+
return $q.when($state.current);
174172
}
175173

176174
// TODO: should we be passing from and to $stateParams as well?
@@ -193,10 +191,10 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
193191
// and return a promise for the new state. We also keep track of what the
194192
// current promise is, so that we can detect overlapping transitions and
195193
// keep only the outcome of the last transition.
196-
var transition = $state.transition = $state.$transition = resolved.then(function () {
194+
var transition = $state.transition = resolved.then(function () {
197195
var l, entering, exiting;
198196

199-
if ($state.transition !== transition) return; // superseded by a new transition
197+
if ($state.transition !== transition) return $q.reject(new Error('transition superseded'));
200198

201199
// Exit 'from' states not kept
202200
for (l=fromPath.length-1; l>=keep; l--) {

test/stateSpec.js

+87-24
Original file line numberDiff line numberDiff line change
@@ -2,56 +2,119 @@ describe('state', function () {
22

33
beforeEach(module('ui.state'));
44

5-
describe('(transitions and related promises)', function () {
6-
var A = { data: {} };
7-
8-
beforeEach(module(function ($stateProvider) {
9-
$stateProvider.state('A', A)
10-
}));
5+
var A = { data: {} }, B = {}, C = {};
6+
7+
beforeEach(module(function ($stateProvider) {
8+
$stateProvider
9+
.state('A', A)
10+
.state('B', B)
11+
.state('C', C);
12+
}));
13+
14+
var log = '';
15+
function eventLogger(event, to, from) {
16+
log += event.name + '(' + to.name + ',' + from.name + ');';
17+
}
18+
19+
beforeEach(inject(function ($rootScope) {
20+
$rootScope.$on('$stateChangeStart', eventLogger);
21+
$rootScope.$on('$stateChangeSuccess', eventLogger);
22+
}));
23+
24+
function $get(what) {
25+
return jasmine.getEnv().currentSpec.$injector.get(what);
26+
}
1127

12-
it('.current is always defined', inject(function ($state) {
28+
function initStateTo(state) {
29+
var $state = $get('$state'), $q = $get('$q');
30+
$state.transitionTo(state, {});
31+
$q.flush();
32+
expect($state.current).toBe(state);
33+
log = '';
34+
}
35+
36+
37+
describe('.current', function () {
38+
it('is always defined', inject(function ($state) {
1339
expect($state.current).toBeDefined();
1440
}));
1541

16-
it('.$current is always defined', inject(function ($state) {
42+
it('updates asynchronously as the transitionTo() promise is resolved', inject(function ($state, $q) {
43+
var trans = $state.transitionTo(A, {});
44+
expect($state.current).not.toBe(A);
45+
$q.flush();
46+
expect($state.current).toBe(A);
47+
}));
48+
});
49+
50+
51+
describe('$current', function () {
52+
it('is always defined', inject(function ($state) {
1753
expect($state.$current).toBeDefined();
1854
}));
1955

20-
it('.$current wraps the raw state object', inject(function ($state, $q) {
56+
it('wraps the raw state object', inject(function ($state, $q) {
2157
$state.transitionTo(A, {});
2258
$q.flush();
2359
expect($state.$current.data).toBe(A.data); // 'data' is reserved for app use
2460
}));
61+
});
62+
63+
64+
describe('.transition', function () {
65+
it('is null when no transition is taking place', inject(function ($state, $q) {
66+
expect($state.transition).toBeNull();
67+
$state.transitionTo(A, {});
68+
$q.flush();
69+
expect($state.transition).toBeNull();
70+
}));
71+
72+
it('is the current transition', inject(function ($state, $q) {
73+
var trans = $state.transitionTo(A, {});
74+
expect($state.transition).toBe(trans);
75+
}));
76+
});
2577

26-
it('.transitionTo() returns a promise for the target state', inject(function ($state, $q) {
78+
79+
describe('.transitionTo()', function () {
80+
it('returns a promise for the target state', inject(function ($state, $q) {
2781
var trans = $state.transitionTo(A, {});
2882
$q.flush();
2983
expect(resolvedValue(trans)).toBe(A);
3084
}));
3185

32-
it('.current updates asynchronously as the transitionTo() promise is resolved', inject(function ($state, $q) {
33-
var trans = $state.transitionTo(A, {});
34-
expect($state.current).not.toBe(A);
86+
it('is a no-op when passing the current state and identical parameters', inject(function ($state, $q) {
87+
initStateTo(A);
88+
var trans = $state.transitionTo(A, {}); // no-op
89+
expect(trans).toBeDefined(); // but we still get a valid promise
3590
$q.flush();
91+
expect(resolvedValue(trans)).toBe(A);
3692
expect($state.current).toBe(A);
93+
expect(log).toBe('');
3794
}));
3895

39-
it('.$transition is always the current or last transition', inject(function ($state, $q) {
40-
expect($state.$transition).toBeDefined();
96+
it('aborts pending transitions (last call wins)', inject(function ($state, $q) {
97+
initStateTo(A);
98+
var superseded = $state.transitionTo(B, {});
99+
$state.transitionTo(C, {});
41100
$q.flush();
42-
expect(resolvedValue($state.$transition)).toBe($state.current);
43-
var trans = $state.transitionTo(A, {});
44-
expect($state.$transition).toBeDefined();
45-
expect($state.$transition).toBe(trans);
46-
$q.flush();
47-
expect($state.$transition).toBe(trans);
101+
expect($state.current).toBe(C);
102+
expect(resolvedError(superseded)).toBeDefined();
103+
expect(log).toBe(
104+
'$stateChangeStart(B,A);' +
105+
'$stateChangeStart(C,A);' +
106+
'$stateChangeSuccess(C,A);');
48107
}));
49108

50-
it('.transition is null when no transition is taking place', inject(function ($state, $q) {
51-
expect($state.transition).toBeNull();
109+
it('aborts pending transitions even when going back to the curren state', inject(function ($state, $q) {
110+
initStateTo(A);
111+
var superseded = $state.transitionTo(B, {});
52112
$state.transitionTo(A, {});
53113
$q.flush();
54-
expect($state.transition).toBeNull();
114+
expect($state.current).toBe(A);
115+
expect(resolvedError(superseded)).toBeDefined();
116+
expect(log).toBe(
117+
'$stateChangeStart(B,A);');
55118
}));
56119
});
57120
});

0 commit comments

Comments
 (0)