Skip to content

Commit 3e63c18

Browse files
committed
added qunit tests for async state transitions
1 parent 6533ce8 commit 3e63c18

3 files changed

Lines changed: 167 additions & 2 deletions

File tree

state-machine.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ StateMachine = {
8585
buildEvent: function(name, map) {
8686
return function() {
8787

88+
if (this.transition)
89+
throw "event " + name + " innapropriate because previous async transition from " + this.transition.from + " to " + this.transition.to + " did not complete"
90+
8891
if (this.cannot(name))
8992
throw "event " + name + " innapropriate in current state " + this.current;
9093

@@ -98,9 +101,11 @@ StateMachine = {
98101
return;
99102

100103
if (this.current != to) {
101-
this.transition = function() { StateMachine.transition.call(self, from, to, args); self.transition = null; };
104+
this.transition = function() { StateMachine.transition.call(self, from, to, args); self.transition = null; };
105+
this.transition.from = from;
106+
this.transition.to = to;
102107
StateMachine.exitState.call(this, this.current, arguments);
103-
if (!async)
108+
if (!async && this.transition) // if not async OR user already called transition method (e.g. in an onleavestate hook)
104109
this.transition();
105110
}
106111

test/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<script src="qunit/qunit.js"></script>
77
<script src="../state-machine.js"></script>
88
<script src="test_basics.js"></script>
9+
<script src="test_async.js"></script>
910
<script src="test_advanced.js"></script>
1011
<script src="test_initialize.js"></script>
1112
</head>

test/test_async.js

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
//-----------------------------------------------------------------------------
2+
3+
module("async");
4+
5+
//-----------------------------------------------------------------------------
6+
7+
test("state transitions", function() {
8+
9+
var fsm = StateMachine.create({
10+
initial: 'green',
11+
events: [
12+
{ name: 'warn', from: 'green', to: 'yellow', async: true },
13+
{ name: 'panic', from: 'yellow', to: 'red', async: true },
14+
{ name: 'calm', from: 'red', to: 'yellow', async: true },
15+
{ name: 'clear', from: 'yellow', to: 'green', async: true }
16+
]});
17+
18+
equals(fsm.current, 'green', "initial state should be green");
19+
fsm.warn(); equals(fsm.current, 'green', "should still be green because we haven't transitioned yet");
20+
fsm.transition(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow");
21+
fsm.panic(); equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
22+
fsm.transition(); equals(fsm.current, 'red', "panic event should transition from yellow to red");
23+
fsm.calm(); equals(fsm.current, 'red', "should still be red because we haven't transitioned yet");
24+
fsm.transition(); equals(fsm.current, 'yellow', "calm event should transition from red to yellow");
25+
fsm.clear(); equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
26+
fsm.transition(); equals(fsm.current, 'green', "clear event should transition from yellow to green");
27+
28+
});
29+
30+
//-----------------------------------------------------------------------------
31+
32+
test("state transitions with delays", function() {
33+
34+
stop(); // doing async stuff - dont run next qunit test until I call start() below
35+
36+
var fsm = StateMachine.create({
37+
initial: 'green',
38+
events: [
39+
{ name: 'warn', from: 'green', to: 'yellow', async: true },
40+
{ name: 'panic', from: 'yellow', to: 'red', async: true },
41+
{ name: 'calm', from: 'red', to: 'yellow', async: true },
42+
{ name: 'clear', from: 'yellow', to: 'green', async: true }
43+
]});
44+
45+
equals(fsm.current, 'green', "initial state should be green");
46+
fsm.warn(); equals(fsm.current, 'green', "should still be green because we haven't transitioned yet");
47+
setTimeout(function() {
48+
fsm.transition(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow");
49+
fsm.panic(); equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
50+
setTimeout(function() {
51+
fsm.transition(); equals(fsm.current, 'red', "panic event should transition from yellow to red");
52+
fsm.calm(); equals(fsm.current, 'red', "should still be red because we haven't transitioned yet");
53+
setTimeout(function() {
54+
fsm.transition(); equals(fsm.current, 'yellow', "calm event should transition from red to yellow");
55+
fsm.clear(); equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
56+
setTimeout(function() {
57+
fsm.transition(); equals(fsm.current, 'green', "clear event should transition from yellow to green");
58+
start();
59+
}, 10);
60+
}, 10);
61+
}, 10);
62+
}, 10);
63+
64+
});
65+
66+
//-----------------------------------------------------------------------------
67+
68+
test("state transition fired imediately during onleavestate hook", function() {
69+
70+
var fsm = StateMachine.create({
71+
initial: 'green',
72+
events: [
73+
{ name: 'warn', from: 'green', to: 'yellow', async: true },
74+
{ name: 'panic', from: 'yellow', to: 'red', async: true },
75+
{ name: 'calm', from: 'red', to: 'yellow', async: true },
76+
{ name: 'clear', from: 'yellow', to: 'green', async: true }
77+
]});
78+
79+
fsm.onleavegreen = function() { this.transition(); }
80+
fsm.onleaveyellow = function() { this.transition(); }
81+
fsm.onleavered = function() { this.transition(); }
82+
83+
equals(fsm.current, 'green', "initial state should be green");
84+
85+
fsm.warn(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow");
86+
fsm.panic(); equals(fsm.current, 'red', "panic event should transition from yellow to red");
87+
fsm.calm(); equals(fsm.current, 'yellow', "calm event should transition from red to yellow");
88+
fsm.clear(); equals(fsm.current, 'green', "clear event should transition from yellow to green");
89+
90+
});
91+
92+
//-----------------------------------------------------------------------------
93+
94+
test("state transition fired during onleavestate hook with delay", function() {
95+
96+
stop(); // doing async stuff - dont run next qunit test until I call start() below
97+
98+
var fsm = StateMachine.create({
99+
initial: 'green',
100+
events: [
101+
{ name: 'panic', from: 'green', to: 'red', async: true }
102+
]});
103+
104+
fsm.onleavegreen = function() { setTimeout(function() { fsm.transition(); }, 10); }
105+
fsm.onenterred = function() {
106+
equals(fsm.current, 'red', "panic event should transition from green to red");
107+
start();
108+
}
109+
110+
equals(fsm.current, 'green', "initial state should be green");
111+
fsm.panic(); equals(fsm.current, 'green', "should still be green because we haven't transitioned yet");
112+
113+
});
114+
115+
//-----------------------------------------------------------------------------
116+
117+
test("state transition fired during onleavestate hook - with MIXED async and non-async transitions!", function() {
118+
119+
var fsm = StateMachine.create({
120+
initial: 'green',
121+
events: [
122+
{ name: 'warn', from: 'green', to: 'yellow', async: true }, // leave green async
123+
{ name: 'panic', from: 'green', to: 'red' }, // leave green non-async
124+
{ name: 'reset', from: ['yellow', 'red'], to: 'green' }
125+
]});
126+
127+
fsm.onleavegreen = function() { this.transition(); }
128+
129+
equals(fsm.current, 'green', "initial state should be green");
130+
fsm.warn(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow");
131+
fsm.reset(); equals(fsm.current, 'green', "reset event should transition from yellow to green");
132+
fsm.panic(); equals(fsm.current, 'red', "panic event should transition from green to red");
133+
134+
});
135+
136+
//-----------------------------------------------------------------------------
137+
138+
test("state transition fired without completing previous transition", function() {
139+
140+
var fsm = StateMachine.create({
141+
initial: 'green',
142+
events: [
143+
{ name: 'warn', from: 'green', to: 'yellow', async: true },
144+
{ name: 'panic', from: 'yellow', to: 'red', async: true },
145+
{ name: 'calm', from: 'red', to: 'yellow', async: true },
146+
{ name: 'clear', from: 'yellow', to: 'green', async: true }
147+
]});
148+
149+
equals(fsm.current, 'green', "initial state should be green");
150+
fsm.warn(); equals(fsm.current, 'green', "should still be green because we haven't transitioned yet");
151+
fsm.transition(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow");
152+
fsm.panic(); equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
153+
154+
raises(fsm.calm.bind(fsm), /event calm innapropriate because previous async transition from yellow to red did not complete/);
155+
156+
});
157+
158+
//-----------------------------------------------------------------------------
159+

0 commit comments

Comments
 (0)