test_async.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. //-----------------------------------------------------------------------------
  2. QUnit.module("async");
  3. //-----------------------------------------------------------------------------
  4. test("state transitions", function() {
  5. var fsm = StateMachine.create({
  6. initial: 'green',
  7. events: [
  8. { name: 'warn', from: 'green', to: 'yellow' },
  9. { name: 'panic', from: 'yellow', to: 'red' },
  10. { name: 'calm', from: 'red', to: 'yellow' },
  11. { name: 'clear', from: 'yellow', to: 'green' }
  12. ],
  13. callbacks: {
  14. onleavegreen: function() { return StateMachine.ASYNC; },
  15. onleaveyellow: function() { return StateMachine.ASYNC; },
  16. onleavered: function() { return StateMachine.ASYNC; }
  17. }
  18. });
  19. equal(fsm.current, 'green', "initial state should be green");
  20. fsm.warn(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet");
  21. fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow");
  22. fsm.panic(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
  23. fsm.transition(); equal(fsm.current, 'red', "panic event should transition from yellow to red");
  24. fsm.calm(); equal(fsm.current, 'red', "should still be red because we haven't transitioned yet");
  25. fsm.transition(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow");
  26. fsm.clear(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
  27. fsm.transition(); equal(fsm.current, 'green', "clear event should transition from yellow to green");
  28. });
  29. //-----------------------------------------------------------------------------
  30. test("state transitions with delays", function() {
  31. stop(); // doing async stuff - dont run next qunit test until I call start() below
  32. var fsm = StateMachine.create({
  33. initial: 'green',
  34. events: [
  35. { name: 'warn', from: 'green', to: 'yellow' },
  36. { name: 'panic', from: 'yellow', to: 'red' },
  37. { name: 'calm', from: 'red', to: 'yellow' },
  38. { name: 'clear', from: 'yellow', to: 'green' }
  39. ],
  40. callbacks: {
  41. onleavegreen: function() { return StateMachine.ASYNC; },
  42. onleaveyellow: function() { return StateMachine.ASYNC; },
  43. onleavered: function() { return StateMachine.ASYNC; }
  44. }
  45. });
  46. equal(fsm.current, 'green', "initial state should be green");
  47. fsm.warn(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet");
  48. setTimeout(function() {
  49. fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow");
  50. fsm.panic(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
  51. setTimeout(function() {
  52. fsm.transition(); equal(fsm.current, 'red', "panic event should transition from yellow to red");
  53. fsm.calm(); equal(fsm.current, 'red', "should still be red because we haven't transitioned yet");
  54. setTimeout(function() {
  55. fsm.transition(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow");
  56. fsm.clear(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
  57. setTimeout(function() {
  58. fsm.transition(); equal(fsm.current, 'green', "clear event should transition from yellow to green");
  59. start();
  60. }, 10);
  61. }, 10);
  62. }, 10);
  63. }, 10);
  64. });
  65. //-----------------------------------------------------------------------------
  66. test("state transition fired during onleavestate callback - immediate", function() {
  67. var fsm = StateMachine.create({
  68. initial: 'green',
  69. events: [
  70. { name: 'warn', from: 'green', to: 'yellow' },
  71. { name: 'panic', from: 'yellow', to: 'red' },
  72. { name: 'calm', from: 'red', to: 'yellow' },
  73. { name: 'clear', from: 'yellow', to: 'green' }
  74. ],
  75. callbacks: {
  76. onleavegreen: function() { this.transition(); return StateMachine.ASYNC; },
  77. onleaveyellow: function() { this.transition(); return StateMachine.ASYNC; },
  78. onleavered: function() { this.transition(); return StateMachine.ASYNC; }
  79. }
  80. });
  81. equal(fsm.current, 'green', "initial state should be green");
  82. fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow");
  83. fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red");
  84. fsm.calm(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow");
  85. fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green");
  86. });
  87. //-----------------------------------------------------------------------------
  88. test("state transition fired during onleavestate callback - with delay", function() {
  89. stop(); // doing async stuff - dont run next qunit test until I call start() below
  90. var fsm = StateMachine.create({
  91. initial: 'green',
  92. events: [
  93. { name: 'panic', from: 'green', to: 'red' }
  94. ],
  95. callbacks: {
  96. onleavegreen: function() { setTimeout(function() { fsm.transition(); }, 10); return StateMachine.ASYNC; },
  97. onenterred: function() {
  98. equal(fsm.current, 'red', "panic event should transition from green to red");
  99. start();
  100. }
  101. }
  102. });
  103. equal(fsm.current, 'green', "initial state should be green");
  104. fsm.panic(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet");
  105. });
  106. //-----------------------------------------------------------------------------
  107. test("state transition fired during onleavestate callback - but forgot to return ASYNC!", function() {
  108. var fsm = StateMachine.create({
  109. initial: 'green',
  110. events: [
  111. { name: 'warn', from: 'green', to: 'yellow' },
  112. { name: 'panic', from: 'yellow', to: 'red' },
  113. { name: 'calm', from: 'red', to: 'yellow' },
  114. { name: 'clear', from: 'yellow', to: 'green' }
  115. ],
  116. callbacks: {
  117. onleavegreen: function() { this.transition(); /* return StateMachine.ASYNC; */ },
  118. onleaveyellow: function() { this.transition(); /* return StateMachine.ASYNC; */ },
  119. onleavered: function() { this.transition(); /* return StateMachine.ASYNC; */ }
  120. }
  121. });
  122. equal(fsm.current, 'green', "initial state should be green");
  123. fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow");
  124. fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red");
  125. fsm.calm(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow");
  126. fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green");
  127. });
  128. //-----------------------------------------------------------------------------
  129. test("state transitions sometimes synchronous and sometimes asynchronous", function() {
  130. var fsm = StateMachine.create({
  131. initial: 'green',
  132. events: [
  133. { name: 'warn', from: 'green', to: 'yellow' },
  134. { name: 'panic', from: 'yellow', to: 'red' },
  135. { name: 'calm', from: 'red', to: 'yellow' },
  136. { name: 'clear', from: 'yellow', to: 'green' }
  137. ]
  138. });
  139. // default behavior is synchronous
  140. equal(fsm.current, 'green', "initial state should be green");
  141. fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow");
  142. fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red");
  143. fsm.calm(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow");
  144. fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green");
  145. // but add callbacks that return ASYNC and it magically becomes asynchronous
  146. fsm.onleavegreen = function() { return StateMachine.ASYNC; }
  147. fsm.onleaveyellow = function() { return StateMachine.ASYNC; }
  148. fsm.onleavered = function() { return StateMachine.ASYNC; }
  149. equal(fsm.current, 'green', "initial state should be green");
  150. fsm.warn(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet");
  151. fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow");
  152. fsm.panic(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
  153. fsm.transition(); equal(fsm.current, 'red', "panic event should transition from yellow to red");
  154. fsm.calm(); equal(fsm.current, 'red', "should still be red because we haven't transitioned yet");
  155. fsm.transition(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow");
  156. fsm.clear(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
  157. fsm.transition(); equal(fsm.current, 'green', "clear event should transition from yellow to green");
  158. // this allows you to make on-the-fly decisions about whether async or not ...
  159. fsm.onleavegreen = function(event, from, to, async) {
  160. if (async) {
  161. setTimeout(function() {
  162. fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow");
  163. start(); // move on to next test
  164. }, 10);
  165. return StateMachine.ASYNC;
  166. }
  167. }
  168. fsm.onleaveyellow = fsm.onleavered = null;
  169. fsm.warn(false); equal(fsm.current, 'yellow', "expected synchronous transition from green to yellow");
  170. fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green");
  171. fsm.warn(true); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet");
  172. stop(); // doing async stuff - dont run next qunit test until I call start() in callback above
  173. });
  174. //-----------------------------------------------------------------------------
  175. test("state transition fired without completing previous transition", function() {
  176. var fsm = StateMachine.create({
  177. initial: 'green',
  178. events: [
  179. { name: 'warn', from: 'green', to: 'yellow' },
  180. { name: 'panic', from: 'yellow', to: 'red' },
  181. { name: 'calm', from: 'red', to: 'yellow' },
  182. { name: 'clear', from: 'yellow', to: 'green' }
  183. ],
  184. callbacks: {
  185. onleavegreen: function() { return StateMachine.ASYNC; },
  186. onleaveyellow: function() { return StateMachine.ASYNC; },
  187. onleavered: function() { return StateMachine.ASYNC; }
  188. }
  189. });
  190. equal(fsm.current, 'green', "initial state should be green");
  191. fsm.warn(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet");
  192. fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow");
  193. fsm.panic(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
  194. throws(fsm.calm.bind(fsm), /event calm inappropriate because previous transition did not complete/);
  195. });
  196. //-----------------------------------------------------------------------------
  197. test("state transition can be cancelled (github issue #22)", function() {
  198. var fsm = StateMachine.create({
  199. initial: 'green',
  200. events: [
  201. { name: 'warn', from: 'green', to: 'yellow' },
  202. { name: 'panic', from: 'yellow', to: 'red' },
  203. { name: 'calm', from: 'red', to: 'yellow' },
  204. { name: 'clear', from: 'yellow', to: 'green' }
  205. ],
  206. callbacks: {
  207. onleavegreen: function() { return StateMachine.ASYNC; },
  208. onleaveyellow: function() { return StateMachine.ASYNC; },
  209. onleavered: function() { return StateMachine.ASYNC; }
  210. }
  211. });
  212. equal(fsm.current, 'green', "initial state should be green");
  213. fsm.warn(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet");
  214. fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow");
  215. fsm.panic(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
  216. equal(fsm.can('panic'), false, "but cannot panic a 2nd time because a transition is still pending")
  217. throws(fsm.panic.bind(fsm), /event panic inappropriate because previous transition did not complete/);
  218. fsm.transition.cancel();
  219. equal(fsm.current, 'yellow', "should still be yellow because we cancelled the async transition");
  220. equal(fsm.can('panic'), true, "can now panic again because we cancelled previous async transition");
  221. fsm.panic();
  222. fsm.transition();
  223. equal(fsm.current, 'red', "should finally be red now that we completed the async transition");
  224. });
  225. //-----------------------------------------------------------------------------
  226. test("callbacks are ordered correctly", function() {
  227. var called = [];
  228. var fsm = StateMachine.create({
  229. initial: 'green',
  230. events: [
  231. { name: 'warn', from: 'green', to: 'yellow' },
  232. { name: 'panic', from: 'yellow', to: 'red' },
  233. { name: 'calm', from: 'red', to: 'yellow' },
  234. { name: 'clear', from: 'yellow', to: 'green' },
  235. ],
  236. callbacks: {
  237. // generic callbacks
  238. onbeforeevent: function(event,from,to) { called.push('onbefore(' + event + ')'); },
  239. onafterevent: function(event,from,to) { called.push('onafter(' + event + ')'); },
  240. onleavestate: function(event,from,to) { called.push('onleave(' + from + ')'); },
  241. onenterstate: function(event,from,to) { called.push('onenter(' + to + ')'); },
  242. onchangestate: function(event,from,to) { called.push('onchange(' + from + ',' + to + ')'); },
  243. // specific state callbacks
  244. onentergreen: function() { called.push('onentergreen'); },
  245. onenteryellow: function() { called.push('onenteryellow'); },
  246. onenterred: function() { called.push('onenterred'); },
  247. onleavegreen: function() { called.push('onleavegreen'); return StateMachine.ASYNC; },
  248. onleaveyellow: function() { called.push('onleaveyellow'); return StateMachine.ASYNC; },
  249. onleavered: function() { called.push('onleavered'); return StateMachine.ASYNC; },
  250. // specific event callbacks
  251. onbeforewarn: function() { called.push('onbeforewarn'); },
  252. onbeforepanic: function() { called.push('onbeforepanic'); },
  253. onbeforecalm: function() { called.push('onbeforecalm'); },
  254. onbeforeclear: function() { called.push('onbeforeclear'); },
  255. onafterwarn: function() { called.push('onafterwarn'); },
  256. onafterpanic: function() { called.push('onafterpanic'); },
  257. onaftercalm: function() { called.push('onaftercalm'); },
  258. onafterclear: function() { called.push('onafterclear'); }
  259. }
  260. });
  261. called = [];
  262. fsm.warn(); deepEqual(called, ['onbeforewarn', 'onbefore(warn)', 'onleavegreen', 'onleave(green)']);
  263. fsm.transition(); deepEqual(called, ['onbeforewarn', 'onbefore(warn)', 'onleavegreen', 'onleave(green)', 'onenteryellow', 'onenter(yellow)', 'onchange(green,yellow)', 'onafterwarn', 'onafter(warn)']);
  264. called = [];
  265. fsm.panic(); deepEqual(called, ['onbeforepanic', 'onbefore(panic)', 'onleaveyellow', 'onleave(yellow)']);
  266. fsm.transition(); deepEqual(called, ['onbeforepanic', 'onbefore(panic)', 'onleaveyellow', 'onleave(yellow)', 'onenterred', 'onenter(red)', 'onchange(yellow,red)', 'onafterpanic', 'onafter(panic)']);
  267. called = [];
  268. fsm.calm(); deepEqual(called, ['onbeforecalm', 'onbefore(calm)', 'onleavered', 'onleave(red)']);
  269. fsm.transition(); deepEqual(called, ['onbeforecalm', 'onbefore(calm)', 'onleavered', 'onleave(red)', 'onenteryellow', 'onenter(yellow)', 'onchange(red,yellow)', 'onaftercalm', 'onafter(calm)']);
  270. called = [];
  271. fsm.clear(); deepEqual(called, ['onbeforeclear', 'onbefore(clear)', 'onleaveyellow', 'onleave(yellow)']);
  272. fsm.transition(); deepEqual(called, ['onbeforeclear', 'onbefore(clear)', 'onleaveyellow', 'onleave(yellow)', 'onentergreen', 'onenter(green)', 'onchange(yellow,green)', 'onafterclear', 'onafter(clear)']);
  273. });
  274. //-----------------------------------------------------------------------------
  275. test("cannot fire event during existing transition", function() {
  276. var fsm = StateMachine.create({
  277. initial: 'green',
  278. events: [
  279. { name: 'warn', from: 'green', to: 'yellow' },
  280. { name: 'panic', from: 'yellow', to: 'red' },
  281. { name: 'calm', from: 'red', to: 'yellow' },
  282. { name: 'clear', from: 'yellow', to: 'green' }
  283. ],
  284. callbacks: {
  285. onleavegreen: function() { return StateMachine.ASYNC; },
  286. onleaveyellow: function() { return StateMachine.ASYNC; },
  287. onleavered: function() { return StateMachine.ASYNC; }
  288. }
  289. });
  290. equal(fsm.current, 'green', "initial state should be green");
  291. equal(fsm.can('warn'), true, "should be able to warn");
  292. equal(fsm.can('panic'), false, "should NOT be able to panic");
  293. equal(fsm.can('calm'), false, "should NOT be able to calm");
  294. equal(fsm.can('clear'), false, "should NOT be able to clear");
  295. fsm.warn();
  296. equal(fsm.current, 'green', "should still be green because we haven't transitioned yet");
  297. equal(fsm.can('warn'), false, "should NOT be able to warn - during transition");
  298. equal(fsm.can('panic'), false, "should NOT be able to panic - during transition");
  299. equal(fsm.can('calm'), false, "should NOT be able to calm - during transition");
  300. equal(fsm.can('clear'), false, "should NOT be able to clear - during transition");
  301. fsm.transition();
  302. equal(fsm.current, 'yellow', "warn event should transition from green to yellow");
  303. equal(fsm.can('warn'), false, "should NOT be able to warn");
  304. equal(fsm.can('panic'), true, "should be able to panic");
  305. equal(fsm.can('calm'), false, "should NOT be able to calm");
  306. equal(fsm.can('clear'), true, "should be able to clear");
  307. fsm.panic();
  308. equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet");
  309. equal(fsm.can('warn'), false, "should NOT be able to warn - during transition");
  310. equal(fsm.can('panic'), false, "should NOT be able to panic - during transition");
  311. equal(fsm.can('calm'), false, "should NOT be able to calm - during transition");
  312. equal(fsm.can('clear'), false, "should NOT be able to clear - during transition");
  313. fsm.transition();
  314. equal(fsm.current, 'red', "panic event should transition from yellow to red");
  315. equal(fsm.can('warn'), false, "should NOT be able to warn");
  316. equal(fsm.can('panic'), false, "should NOT be able to panic");
  317. equal(fsm.can('calm'), true, "should be able to calm");
  318. equal(fsm.can('clear'), false, "should NOT be able to clear");
  319. });
  320. //-----------------------------------------------------------------------------