test_advanced.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. //-----------------------------------------------------------------------------
  2. QUnit.module("advanced");
  3. //-----------------------------------------------------------------------------
  4. test("multiple 'from' states for the same event", function() {
  5. var fsm = StateMachine.create({
  6. initial: 'green',
  7. events: [
  8. { name: 'warn', from: 'green', to: 'yellow' },
  9. { name: 'panic', from: ['green', 'yellow'], to: 'red' },
  10. { name: 'calm', from: 'red', to: 'yellow' },
  11. { name: 'clear', from: ['yellow', 'red'], to: 'green' },
  12. ]});
  13. equal(fsm.current, 'green', "initial state should be green");
  14. ok(fsm.can('warn'), "should be able to warn from green state")
  15. ok(fsm.can('panic'), "should be able to panic from green state")
  16. ok(fsm.cannot('calm'), "should NOT be able to calm from green state")
  17. ok(fsm.cannot('clear'), "should NOT be able to clear from green state")
  18. fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow");
  19. fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red");
  20. fsm.calm(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow");
  21. fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green");
  22. fsm.panic(); equal(fsm.current, 'red', "panic event should transition from green to red");
  23. fsm.clear(); equal(fsm.current, 'green', "clear event should transition from red to green");
  24. });
  25. //-----------------------------------------------------------------------------
  26. test("multiple 'to' states for the same event", function() {
  27. var fsm = StateMachine.create({
  28. initial: 'hungry',
  29. events: [
  30. { name: 'eat', from: 'hungry', to: 'satisfied' },
  31. { name: 'eat', from: 'satisfied', to: 'full' },
  32. { name: 'eat', from: 'full', to: 'sick' },
  33. { name: 'rest', from: ['hungry', 'satisfied', 'full', 'sick'], to: 'hungry' },
  34. ]});
  35. equal(fsm.current, 'hungry');
  36. ok(fsm.can('eat'));
  37. ok(fsm.can('rest'));
  38. fsm.eat();
  39. equal(fsm.current, 'satisfied');
  40. fsm.eat();
  41. equal(fsm.current, 'full');
  42. fsm.eat();
  43. equal(fsm.current, 'sick');
  44. fsm.rest();
  45. equal(fsm.current, 'hungry');
  46. });
  47. //-----------------------------------------------------------------------------
  48. test("no-op transitions (github issue #5) with multiple from states", function() {
  49. var fsm = StateMachine.create({
  50. initial: 'green',
  51. events: [
  52. { name: 'warn', from: 'green', to: 'yellow' },
  53. { name: 'panic', from: ['green', 'yellow'], to: 'red' },
  54. { name: 'noop', from: ['green', 'yellow'] }, // NOTE: 'to' not specified
  55. { name: 'calm', from: 'red', to: 'yellow' },
  56. { name: 'clear', from: ['yellow', 'red'], to: 'green' },
  57. ]});
  58. equal(fsm.current, 'green', "initial state should be green");
  59. ok(fsm.can('warn'), "should be able to warn from green state")
  60. ok(fsm.can('panic'), "should be able to panic from green state")
  61. ok(fsm.can('noop'), "should be able to noop from green state")
  62. ok(fsm.cannot('calm'), "should NOT be able to calm from green state")
  63. ok(fsm.cannot('clear'), "should NOT be able to clear from green state")
  64. fsm.noop(); equal(fsm.current, 'green', "noop event should not transition");
  65. fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow");
  66. ok(fsm.cannot('warn'), "should NOT be able to warn from yellow state")
  67. ok(fsm.can('panic'), "should be able to panic from yellow state")
  68. ok(fsm.can('noop'), "should be able to noop from yellow state")
  69. ok(fsm.cannot('calm'), "should NOT be able to calm from yellow state")
  70. ok(fsm.can('clear'), "should be able to clear from yellow state")
  71. fsm.noop(); equal(fsm.current, 'yellow', "noop event should not transition");
  72. fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red");
  73. ok(fsm.cannot('warn'), "should NOT be able to warn from red state")
  74. ok(fsm.cannot('panic'), "should NOT be able to panic from red state")
  75. ok(fsm.cannot('noop'), "should NOT be able to noop from red state")
  76. ok(fsm.can('calm'), "should be able to calm from red state")
  77. ok(fsm.can('clear'), "should be able to clear from red state")
  78. });
  79. //-----------------------------------------------------------------------------
  80. test("callbacks are called when appropriate for multiple 'from' and 'to' transitions", function() {
  81. var called = [];
  82. var fsm = StateMachine.create({
  83. initial: 'hungry',
  84. events: [
  85. { name: 'eat', from: 'hungry', to: 'satisfied' },
  86. { name: 'eat', from: 'satisfied', to: 'full' },
  87. { name: 'eat', from: 'full', to: 'sick' },
  88. { name: 'rest', from: ['hungry', 'satisfied', 'full', 'sick'], to: 'hungry' },
  89. ],
  90. callbacks: {
  91. // generic callbacks
  92. onbeforeevent: function(event,from,to) { called.push('onbefore(' + event + ')'); },
  93. onafterevent: function(event,from,to) { called.push('onafter(' + event + ')'); },
  94. onleavestate: function(event,from,to) { called.push('onleave(' + from + ')'); },
  95. onenterstate: function(event,from,to) { called.push('onenter(' + to + ')'); },
  96. onchangestate: function(event,from,to) { called.push('onchange(' + from + ',' + to + ')'); },
  97. // specific state callbacks
  98. onenterhungry: function() { called.push('onenterhungry'); },
  99. onleavehungry: function() { called.push('onleavehungry'); },
  100. onentersatisfied: function() { called.push('onentersatisfied'); },
  101. onleavesatisfied: function() { called.push('onleavesatisfied'); },
  102. onenterfull: function() { called.push('onenterfull'); },
  103. onleavefull: function() { called.push('onleavefull'); },
  104. onentersick: function() { called.push('onentersick'); },
  105. onleavesick: function() { called.push('onleavesick'); },
  106. // specific event callbacks
  107. onbeforeeat: function() { called.push('onbeforeeat'); },
  108. onaftereat: function() { called.push('onaftereat'); },
  109. onbeforerest: function() { called.push('onbeforerest'); },
  110. onafterrest: function() { called.push('onafterrest'); }
  111. }
  112. });
  113. called = [];
  114. fsm.eat();
  115. deepEqual(called, [
  116. 'onbeforeeat',
  117. 'onbefore(eat)',
  118. 'onleavehungry',
  119. 'onleave(hungry)',
  120. 'onentersatisfied',
  121. 'onenter(satisfied)',
  122. 'onchange(hungry,satisfied)',
  123. 'onaftereat',
  124. 'onafter(eat)'
  125. ]);
  126. called = [];
  127. fsm.eat();
  128. deepEqual(called, [
  129. 'onbeforeeat',
  130. 'onbefore(eat)',
  131. 'onleavesatisfied',
  132. 'onleave(satisfied)',
  133. 'onenterfull',
  134. 'onenter(full)',
  135. 'onchange(satisfied,full)',
  136. 'onaftereat',
  137. 'onafter(eat)',
  138. ]);
  139. called = [];
  140. fsm.eat();
  141. deepEqual(called, [
  142. 'onbeforeeat',
  143. 'onbefore(eat)',
  144. 'onleavefull',
  145. 'onleave(full)',
  146. 'onentersick',
  147. 'onenter(sick)',
  148. 'onchange(full,sick)',
  149. 'onaftereat',
  150. 'onafter(eat)'
  151. ]);
  152. called = [];
  153. fsm.rest();
  154. deepEqual(called, [
  155. 'onbeforerest',
  156. 'onbefore(rest)',
  157. 'onleavesick',
  158. 'onleave(sick)',
  159. 'onenterhungry',
  160. 'onenter(hungry)',
  161. 'onchange(sick,hungry)',
  162. 'onafterrest',
  163. 'onafter(rest)'
  164. ]);
  165. });
  166. //-----------------------------------------------------------------------------
  167. test("callbacks are called when appropriate for prototype based state machine", function() {
  168. var myFSM = function() {
  169. this.called = [];
  170. this.startup();
  171. };
  172. myFSM.prototype = {
  173. // generic callbacks
  174. onbeforeevent: function(event,from,to) { this.called.push('onbefore(' + event + ')'); },
  175. onafterevent: function(event,from,to) { this.called.push('onafter(' + event + ')'); },
  176. onleavestate: function(event,from,to) { this.called.push('onleave(' + from + ')'); },
  177. onenterstate: function(event,from,to) { this.called.push('onenter(' + to + ')'); },
  178. onchangestate: function(event,from,to) { this.called.push('onchange(' + from + ',' + to + ')'); },
  179. // specific state callbacks
  180. onenternone: function() { this.called.push('onenternone'); },
  181. onleavenone: function() { this.called.push('onleavenone'); },
  182. onentergreen: function() { this.called.push('onentergreen'); },
  183. onleavegreen: function() { this.called.push('onleavegreen'); },
  184. onenteryellow : function() { this.called.push('onenteryellow'); },
  185. onleaveyellow: function() { this.called.push('onleaveyellow'); },
  186. onenterred: function() { this.called.push('onenterred'); },
  187. onleavered: function() { this.called.push('onleavered'); },
  188. // specific event callbacks
  189. onbeforestartup: function() { this.called.push('onbeforestartup'); },
  190. onafterstartup: function() { this.called.push('onafterstartup'); },
  191. onbeforewarn: function() { this.called.push('onbeforewarn'); },
  192. onafterwarn: function() { this.called.push('onafterwarn'); },
  193. onbeforepanic: function() { this.called.push('onbeforepanic'); },
  194. onafterpanic: function() { this.called.push('onafterpanic'); },
  195. onbeforeclear: function() { this.called.push('onbeforeclear'); },
  196. onafterclear: function() { this.called.push('onafterclear'); }
  197. };
  198. StateMachine.create({
  199. target: myFSM.prototype,
  200. events: [
  201. { name: 'startup', from: 'none', to: 'green' },
  202. { name: 'warn', from: 'green', to: 'yellow' },
  203. { name: 'panic', from: 'yellow', to: 'red' },
  204. { name: 'clear', from: 'yellow', to: 'green' }
  205. ]
  206. });
  207. var a = new myFSM();
  208. var b = new myFSM();
  209. equal(a.current, 'green', 'start with correct state');
  210. equal(b.current, 'green', 'start with correct state');
  211. deepEqual(a.called, ['onbeforestartup', 'onbefore(startup)', 'onleavenone', 'onleave(none)', 'onentergreen', 'onenter(green)', 'onchange(none,green)', 'onafterstartup', 'onafter(startup)']);
  212. deepEqual(b.called, ['onbeforestartup', 'onbefore(startup)', 'onleavenone', 'onleave(none)', 'onentergreen', 'onenter(green)', 'onchange(none,green)', 'onafterstartup', 'onafter(startup)']);
  213. a.called = [];
  214. b.called = [];
  215. a.warn();
  216. equal(a.current, 'yellow', 'maintain independent current state');
  217. equal(b.current, 'green', 'maintain independent current state');
  218. deepEqual(a.called, ['onbeforewarn', 'onbefore(warn)', 'onleavegreen', 'onleave(green)', 'onenteryellow', 'onenter(yellow)', 'onchange(green,yellow)', 'onafterwarn', 'onafter(warn)']);
  219. deepEqual(b.called, []);
  220. });
  221. //-----------------------------------------------------------------------------
  222. test("double wildcard transition does not change current state", function() {
  223. var fsm = StateMachine.create({
  224. initial: 'green',
  225. events: [
  226. { name: 'warn', from: 'green', to: 'yellow' },
  227. { name: 'panic', from: ['green', 'yellow'], to: 'red' },
  228. { name: 'noop', from: ['green', 'yellow'] }, // NOTE: 'to' not specified
  229. { name: 'calm', from: 'red', to: 'yellow' },
  230. { name: 'clear', from: ['yellow', 'red'], to: 'green' },
  231. { name: 'lightup', from: '*' }, // Note: "double wildcard"
  232. { name: 'lightup', from: 'green', to: 'yellow' }
  233. ]});
  234. equal(fsm.current, 'green', "start with correct state");
  235. fsm.lightup(); equal(fsm.current, 'yellow', "lightup event should switch green to yellow");
  236. fsm.lightup(); equal(fsm.current, 'yellow', "lightup event should have no effect effect in other state than green");
  237. });