test_basics.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. //-----------------------------------------------------------------------------
  2. QUnit.module("basic");
  3. //-----------------------------------------------------------------------------
  4. test("standalone state machine", 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. equal(fsm.current, 'green', "initial state should be green");
  14. fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow");
  15. fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red");
  16. fsm.calm(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow");
  17. fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green");
  18. });
  19. //-----------------------------------------------------------------------------
  20. test("targeted state machine", function() {
  21. StateMachine.create({
  22. target: this,
  23. initial: 'green',
  24. events: [
  25. { name: 'warn', from: 'green', to: 'yellow' },
  26. { name: 'panic', from: 'yellow', to: 'red' },
  27. { name: 'calm', from: 'red', to: 'yellow' },
  28. { name: 'clear', from: 'yellow', to: 'green' }
  29. ]});
  30. equal(this.current, 'green', "initial state should be green");
  31. this.warn(); equal(this.current, 'yellow', "warn event should transition from green to yellow");
  32. this.panic(); equal(this.current, 'red', "panic event should transition from yellow to red");
  33. this.calm(); equal(this.current, 'yellow', "calm event should transition from red to yellow");
  34. this.clear(); equal(this.current, 'green', "clear event should transition from yellow to green");
  35. });
  36. //-----------------------------------------------------------------------------
  37. test("can & cannot", function() {
  38. var fsm = StateMachine.create({
  39. initial: 'green',
  40. events: [
  41. { name: 'warn', from: 'green', to: 'yellow' },
  42. { name: 'panic', from: 'yellow', to: 'red' },
  43. { name: 'calm', from: 'red', to: 'yellow' },
  44. ]});
  45. equal(fsm.current, 'green', "initial state should be green");
  46. ok(fsm.can('warn'), "should be able to warn from green state")
  47. ok(fsm.cannot('panic'), "should NOT be able to panic from green state")
  48. ok(fsm.cannot('calm'), "should NOT be able to calm from green state")
  49. fsm.warn();
  50. equal(fsm.current, 'yellow', "current state should be yellow");
  51. ok(fsm.cannot('warn'), "should NOT be able to warn from yellow state")
  52. ok(fsm.can('panic'), "should be able to panic from yellow state")
  53. ok(fsm.cannot('calm'), "should NOT be able to calm from yellow state")
  54. fsm.panic();
  55. equal(fsm.current, 'red', "current state should be red");
  56. ok(fsm.cannot('warn'), "should NOT be able to warn from red state")
  57. ok(fsm.cannot('panic'), "should NOT be able to panic from red state")
  58. ok(fsm.can('calm'), "should be able to calm from red state")
  59. equal(fsm.can('jibber'), false, "unknown event should not crash")
  60. equal(fsm.cannot('jabber'), true, "unknown event should not crash")
  61. });
  62. //-----------------------------------------------------------------------------
  63. test("is", function() {
  64. var fsm = StateMachine.create({
  65. initial: 'green',
  66. events: [
  67. { name: 'warn', from: 'green', to: 'yellow' },
  68. { name: 'panic', from: 'yellow', to: 'red' },
  69. { name: 'calm', from: 'red', to: 'yellow' },
  70. { name: 'clear', from: 'yellow', to: 'green' }
  71. ]});
  72. equal(fsm.current, 'green', "initial state should be green");
  73. equal(fsm.is('green'), true, 'current state should match');
  74. equal(fsm.is('yellow'), false, 'current state should NOT match');
  75. equal(fsm.is(['green', 'red']), true, 'current state should match when included in array');
  76. equal(fsm.is(['yellow', 'red']), false, 'current state should NOT match when not included in array');
  77. fsm.warn();
  78. equal(fsm.current, 'yellow', "current state should be yellow");
  79. equal(fsm.is('green'), false, 'current state should NOT match');
  80. equal(fsm.is('yellow'), true, 'current state should match');
  81. equal(fsm.is(['green', 'red']), false, 'current state should NOT match when not included in array');
  82. equal(fsm.is(['yellow', 'red']), true, 'current state should match when included in array');
  83. });
  84. //-----------------------------------------------------------------------------
  85. test("states", function() {
  86. var fsm = StateMachine.create({
  87. initial: 'green',
  88. events: [
  89. { name: 'warn', from: 'green', to: 'yellow' },
  90. { name: 'panic', from: 'yellow', to: 'red' },
  91. { name: 'calm', from: 'red', to: 'yellow' },
  92. { name: 'clear', from: 'yellow', to: 'green' },
  93. { name: 'finish', from: 'green', to: 'done' },
  94. ]});
  95. deepEqual(fsm.states(), [ 'done', 'green', 'none', 'red', 'yellow' ]);
  96. });
  97. //-----------------------------------------------------------------------------
  98. test("transitions", function() {
  99. var fsm = StateMachine.create({
  100. initial: 'green',
  101. events: [
  102. { name: 'warn', from: 'green', to: 'yellow' },
  103. { name: 'panic', from: 'yellow', to: 'red' },
  104. { name: 'calm', from: 'red', to: 'yellow' },
  105. { name: 'clear', from: 'yellow', to: 'green' },
  106. { name: 'finish', from: 'green', to: 'done' },
  107. ]});
  108. equal(fsm.current, 'green', 'current state should be yellow');
  109. deepEqual(fsm.transitions(), ['warn', 'finish'], 'current transition(s) should be yellow');
  110. fsm.warn();
  111. equal(fsm.current, 'yellow', 'current state should be yellow');
  112. deepEqual(fsm.transitions(), ['panic', 'clear'], 'current transition(s) should be panic and clear');
  113. fsm.panic();
  114. equal(fsm.current, 'red', 'current state should be red');
  115. deepEqual(fsm.transitions(), ['calm'], 'current transition(s) should be calm');
  116. fsm.calm();
  117. equal(fsm.current, 'yellow', 'current state should be yellow');
  118. deepEqual(fsm.transitions(), ['panic', 'clear'], 'current transion(s) should be panic and clear');
  119. fsm.clear();
  120. equal(fsm.current, 'green', 'current state should be green');
  121. deepEqual(fsm.transitions(), ['warn', 'finish'], 'current transion(s) should be warn');
  122. fsm.finish();
  123. equal(fsm.current, 'done', 'current state should be done');
  124. deepEqual(fsm.transitions(), [], 'current transition(s) should be empty');
  125. });
  126. //-----------------------------------------------------------------------------
  127. test("transitions with multiple from states", function() {
  128. var fsm = StateMachine.create({
  129. events: [
  130. { name: 'start', from: 'none', to: 'green' },
  131. { name: 'warn', from: ['green', 'red'], to: 'yellow' },
  132. { name: 'panic', from: ['green', 'yellow'], to: 'red' },
  133. { name: 'clear', from: ['red', 'yellow'], to: 'green' }
  134. ]
  135. });
  136. equal(fsm.current, 'none', 'current state should be none');
  137. deepEqual(fsm.transitions(), ['start'], 'current transition(s) should be start');
  138. fsm.start();
  139. equal(fsm.current, 'green', 'current state should be green');
  140. deepEqual(fsm.transitions(), ['warn', 'panic'], 'current transition(s) should be warn and panic');
  141. fsm.warn();
  142. equal(fsm.current, 'yellow', 'current state should be yellow');
  143. deepEqual(fsm.transitions(), ['panic', 'clear'], 'current transition(s) should be panic and clear');
  144. fsm.panic();
  145. equal(fsm.current, 'red', 'current state should be red');
  146. deepEqual(fsm.transitions(), ['warn', 'clear'], 'current transition(s) should be warn and clear');
  147. fsm.clear();
  148. equal(fsm.current, 'green', 'current state should be green');
  149. deepEqual(fsm.transitions(), ['warn', 'panic'], 'current transition(s) should be warn and panic');
  150. });
  151. //-----------------------------------------------------------------------------
  152. test("isFinished", function() {
  153. var fsm = StateMachine.create({
  154. initial: 'green', terminal: 'red',
  155. events: [
  156. { name: 'warn', from: 'green', to: 'yellow' },
  157. { name: 'panic', from: 'yellow', to: 'red' }
  158. ]});
  159. equal(fsm.current, 'green');
  160. equal(fsm.isFinished(), false);
  161. fsm.warn();
  162. equal(fsm.current, 'yellow');
  163. equal(fsm.isFinished(), false);
  164. fsm.panic();
  165. equal(fsm.current, 'red');
  166. equal(fsm.isFinished(), true);
  167. });
  168. //-----------------------------------------------------------------------------
  169. test("isFinished - without specifying terminal state", function() {
  170. var fsm = StateMachine.create({
  171. initial: 'green',
  172. events: [
  173. { name: 'warn', from: 'green', to: 'yellow' },
  174. { name: 'panic', from: 'yellow', to: 'red' }
  175. ]});
  176. equal(fsm.current, 'green');
  177. equal(fsm.isFinished(), false);
  178. fsm.warn();
  179. equal(fsm.current, 'yellow');
  180. equal(fsm.isFinished(), false);
  181. fsm.panic();
  182. equal(fsm.current, 'red');
  183. equal(fsm.isFinished(), false);
  184. });
  185. //-----------------------------------------------------------------------------
  186. test("inappropriate events", function() {
  187. var fsm = StateMachine.create({
  188. initial: 'green',
  189. events: [
  190. { name: 'warn', from: 'green', to: 'yellow' },
  191. { name: 'panic', from: 'yellow', to: 'red' },
  192. { name: 'calm', from: 'red', to: 'yellow' },
  193. ]});
  194. equal(fsm.current, 'green', "initial state should be green");
  195. throws(fsm.panic.bind(fsm), /event panic inappropriate in current state green/);
  196. throws(fsm.calm.bind(fsm), /event calm inappropriate in current state green/);
  197. fsm.warn();
  198. equal(fsm.current, 'yellow', "current state should be yellow");
  199. throws(fsm.warn.bind(fsm), /event warn inappropriate in current state yellow/);
  200. throws(fsm.calm.bind(fsm), /event calm inappropriate in current state yellow/);
  201. fsm.panic();
  202. equal(fsm.current, 'red', "current state should be red");
  203. throws(fsm.warn.bind(fsm), /event warn inappropriate in current state red/);
  204. throws(fsm.panic.bind(fsm), /event panic inappropriate in current state red/);
  205. });
  206. //-----------------------------------------------------------------------------
  207. test("inappropriate event handling can be customized", function() {
  208. var fsm = StateMachine.create({
  209. error: function(name, from, to, args, error, msg) { return msg; }, // return error message instead of throwing an exception
  210. initial: 'green',
  211. events: [
  212. { name: 'warn', from: 'green', to: 'yellow' },
  213. { name: 'panic', from: 'yellow', to: 'red' },
  214. { name: 'calm', from: 'red', to: 'yellow' }
  215. ]});
  216. equal(fsm.current, 'green', "initial state should be green");
  217. equal(fsm.panic(), 'event panic inappropriate in current state green');
  218. equal(fsm.calm(), 'event calm inappropriate in current state green');
  219. fsm.warn();
  220. equal(fsm.current, 'yellow', "current state should be yellow");
  221. equal(fsm.warn(), 'event warn inappropriate in current state yellow');
  222. equal(fsm.calm(), 'event calm inappropriate in current state yellow');
  223. fsm.panic();
  224. equal(fsm.current, 'red', "current state should be red");
  225. equal(fsm.warn(), 'event warn inappropriate in current state red');
  226. equal(fsm.panic(), 'event panic inappropriate in current state red');
  227. });
  228. //-----------------------------------------------------------------------------
  229. test("event is cancelable", function() {
  230. var fsm = StateMachine.create({
  231. initial: 'green',
  232. events: [
  233. { name: 'warn', from: 'green', to: 'yellow' },
  234. { name: 'panic', from: 'yellow', to: 'red' },
  235. { name: 'calm', from: 'red', to: 'yellow' }
  236. ]});
  237. equal(fsm.current, 'green', 'initial state should be green');
  238. fsm.onbeforewarn = function() { return false; }
  239. fsm.warn();
  240. equal(fsm.current, 'green', 'state should STAY green when event is cancelled');
  241. });
  242. //-----------------------------------------------------------------------------
  243. test("callbacks are ordered correctly", function() {
  244. var called = [];
  245. var fsm = StateMachine.create({
  246. initial: 'green',
  247. events: [
  248. { name: 'warn', from: 'green', to: 'yellow' },
  249. { name: 'panic', from: 'yellow', to: 'red' },
  250. { name: 'calm', from: 'red', to: 'yellow' },
  251. { name: 'clear', from: 'yellow', to: 'green' }
  252. ],
  253. callbacks: {
  254. // generic callbacks
  255. onbeforeevent: function(event,frmo,to) { called.push('onbefore(' + event + ')'); },
  256. onafterevent: function(event,frmo,to) { called.push('onafter(' + event + ')'); },
  257. onleavestate: function(event,from,to) { called.push('onleave(' + from + ')'); },
  258. onenterstate: function(event,from,to) { called.push('onenter(' + to + ')'); },
  259. onchangestate: function(event,from,to) { called.push('onchange(' + from + ',' + to + ')'); },
  260. // specific state callbacks
  261. onentergreen: function() { called.push('onentergreen'); },
  262. onenteryellow: function() { called.push('onenteryellow'); },
  263. onenterred: function() { called.push('onenterred'); },
  264. onleavegreen: function() { called.push('onleavegreen'); },
  265. onleaveyellow: function() { called.push('onleaveyellow'); },
  266. onleavered: function() { called.push('onleavered'); },
  267. // specific event callbacks
  268. onbeforewarn: function() { called.push('onbeforewarn'); },
  269. onbeforepanic: function() { called.push('onbeforepanic'); },
  270. onbeforecalm: function() { called.push('onbeforecalm'); },
  271. onbeforeclear: function() { called.push('onbeforeclear'); },
  272. onafterwarn: function() { called.push('onafterwarn'); },
  273. onafterpanic: function() { called.push('onafterpanic'); },
  274. onaftercalm: function() { called.push('onaftercalm'); },
  275. onafterclear: function() { called.push('onafterclear'); },
  276. }
  277. });
  278. called = [];
  279. fsm.warn();
  280. deepEqual(called, [
  281. 'onbeforewarn',
  282. 'onbefore(warn)',
  283. 'onleavegreen',
  284. 'onleave(green)',
  285. 'onenteryellow',
  286. 'onenter(yellow)',
  287. 'onchange(green,yellow)',
  288. 'onafterwarn',
  289. 'onafter(warn)'
  290. ]);
  291. called = [];
  292. fsm.panic();
  293. deepEqual(called, [
  294. 'onbeforepanic',
  295. 'onbefore(panic)',
  296. 'onleaveyellow',
  297. 'onleave(yellow)',
  298. 'onenterred',
  299. 'onenter(red)',
  300. 'onchange(yellow,red)',
  301. 'onafterpanic',
  302. 'onafter(panic)'
  303. ]);
  304. called = [];
  305. fsm.calm();
  306. deepEqual(called, [
  307. 'onbeforecalm',
  308. 'onbefore(calm)',
  309. 'onleavered',
  310. 'onleave(red)',
  311. 'onenteryellow',
  312. 'onenter(yellow)',
  313. 'onchange(red,yellow)',
  314. 'onaftercalm',
  315. 'onafter(calm)'
  316. ]);
  317. called = [];
  318. fsm.clear();
  319. deepEqual(called, [
  320. 'onbeforeclear',
  321. 'onbefore(clear)',
  322. 'onleaveyellow',
  323. 'onleave(yellow)',
  324. 'onentergreen',
  325. 'onenter(green)',
  326. 'onchange(yellow,green)',
  327. 'onafterclear',
  328. 'onafter(clear)'
  329. ]);
  330. });
  331. //-----------------------------------------------------------------------------
  332. test("callbacks are ordered correctly - for same state transition", function() {
  333. var called = [];
  334. var fsm = StateMachine.create({
  335. initial: 'waiting',
  336. events: [
  337. { name: 'data', from: ['waiting', 'receipt'], to: 'receipt' },
  338. { name: 'nothing', from: ['waiting', 'receipt'], to: 'waiting' },
  339. { name: 'error', from: ['waiting', 'receipt'], to: 'error' } // bad practice to have event name same as state name - but I'll let it slide just this once
  340. ],
  341. callbacks: {
  342. // generic callbacks
  343. onbeforeevent: function(event,frmo,to) { called.push('onbefore(' + event + ')'); },
  344. onafterevent: function(event,frmo,to) { called.push('onafter(' + event + ')'); },
  345. onleavestate: function(event,from,to) { called.push('onleave(' + from + ')'); },
  346. onenterstate: function(event,from,to) { called.push('onenter(' + to + ')'); },
  347. onchangestate: function(event,from,to) { called.push('onchange(' + from + ',' + to + ')'); },
  348. // specific state callbacks
  349. onenterwaiting: function() { called.push('onenterwaiting'); },
  350. onenterreceipt: function() { called.push('onenterreceipt'); },
  351. onentererror: function() { called.push('onentererror'); },
  352. onleavewaiting: function() { called.push('onleavewaiting'); },
  353. onleavereceipt: function() { called.push('onleavereceipt'); },
  354. onleaveerror: function() { called.push('onleaveerror'); },
  355. // specific event callbacks
  356. onbeforedata: function() { called.push('onbeforedata'); },
  357. onbeforenothing: function() { called.push('onbeforenothing'); },
  358. onbeforeerror: function() { called.push('onbeforeerror'); },
  359. onafterdata: function() { called.push('onafterdata'); },
  360. onafternothing: function() { called.push('onafternothing'); },
  361. onaftereerror: function() { called.push('onaftererror'); },
  362. }
  363. });
  364. called = [];
  365. fsm.data();
  366. deepEqual(called, [
  367. 'onbeforedata',
  368. 'onbefore(data)',
  369. 'onleavewaiting',
  370. 'onleave(waiting)',
  371. 'onenterreceipt',
  372. 'onenter(receipt)',
  373. 'onchange(waiting,receipt)',
  374. 'onafterdata',
  375. 'onafter(data)'
  376. ]);
  377. called = [];
  378. fsm.data(); // same-state transition
  379. deepEqual(called, [ // so NO enter/leave/change state callbacks are fired
  380. 'onbeforedata',
  381. 'onbefore(data)',
  382. 'onafterdata',
  383. 'onafter(data)'
  384. ]);
  385. called = [];
  386. fsm.data(); // same-state transition
  387. deepEqual(called, [ // so NO enter/leave/change state callbacks are fired
  388. 'onbeforedata',
  389. 'onbefore(data)',
  390. 'onafterdata',
  391. 'onafter(data)'
  392. ]);
  393. called = [];
  394. fsm.nothing();
  395. deepEqual(called, [
  396. 'onbeforenothing',
  397. 'onbefore(nothing)',
  398. 'onleavereceipt',
  399. 'onleave(receipt)',
  400. 'onenterwaiting',
  401. 'onenter(waiting)',
  402. 'onchange(receipt,waiting)',
  403. 'onafternothing',
  404. 'onafter(nothing)'
  405. ]);
  406. });
  407. //-----------------------------------------------------------------------------
  408. test("callback arguments are correct", function() {
  409. var expected = { event: 'startup', from: 'none', to: 'green' }; // first expected callback
  410. var verify_expected = function(event,from,to,a,b,c) {
  411. equal(event, expected.event)
  412. equal(from, expected.from)
  413. equal(to, expected.to)
  414. equal(a, expected.a)
  415. equal(b, expected.b)
  416. equal(c, expected.c)
  417. };
  418. var fsm = StateMachine.create({
  419. initial: 'green',
  420. events: [
  421. { name: 'warn', from: 'green', to: 'yellow' },
  422. { name: 'panic', from: 'yellow', to: 'red' },
  423. { name: 'calm', from: 'red', to: 'yellow' },
  424. { name: 'clear', from: 'yellow', to: 'green' }
  425. ],
  426. callbacks: {
  427. // generic callbacks
  428. onbeforeevent: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  429. onafterevent: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  430. onleavestate: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  431. onenterstate: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  432. onchangestate: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  433. // specific state callbacks
  434. onentergreen: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  435. onenteryellow: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  436. onenterred: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  437. onleavegreen: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  438. onleaveyellow: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  439. onleavered: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  440. // specific event callbacks
  441. onbeforewarn: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  442. onbeforepanic: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  443. onbeforecalm: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  444. onbeforeclear: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  445. onafterwarn: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  446. onafterpanic: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  447. onaftercalm: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); },
  448. onafterclear: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }
  449. }
  450. });
  451. expected = { event: 'warn', from: 'green', to: 'yellow', a: 1, b: 2, c: 3 };
  452. fsm.warn(1,2,3);
  453. expected = { event: 'panic', from: 'yellow', to: 'red', a: 4, b: 5, c: 6 };
  454. fsm.panic(4,5,6);
  455. expected = { event: 'calm', from: 'red', to: 'yellow', a: 'foo', b: 'bar', c: null };
  456. fsm.calm('foo', 'bar');
  457. expected = { event: 'clear', from: 'yellow', to: 'green', a: null, b: null, c: null };
  458. fsm.clear();
  459. });
  460. //-----------------------------------------------------------------------------
  461. test("exceptions in caller-provided callbacks are not swallowed (github issue #17)", function() {
  462. var fsm = StateMachine.create({
  463. initial: 'green',
  464. events: [
  465. { name: 'warn', from: 'green', to: 'yellow' },
  466. { name: 'panic', from: 'yellow', to: 'red' },
  467. { name: 'calm', from: 'red', to: 'yellow' }
  468. ],
  469. callbacks: {
  470. onenteryellow: function() { throw 'oops'; }
  471. }});
  472. equal(fsm.current, 'green', "initial state should be green");
  473. throws(fsm.warn.bind(fsm), /oops/);
  474. });
  475. //-----------------------------------------------------------------------------
  476. test("no-op transitions (github issue #5)", function() {
  477. var fsm = StateMachine.create({
  478. initial: 'green',
  479. events: [
  480. { name: 'noop', from: 'green', /* no-op */ },
  481. { name: 'warn', from: 'green', to: 'yellow' },
  482. { name: 'panic', from: 'yellow', to: 'red' },
  483. { name: 'calm', from: 'red', to: 'yellow' },
  484. { name: 'clear', from: 'yellow', to: 'green' }
  485. ]});
  486. equal(fsm.current, 'green', "initial state should be green");
  487. ok(fsm.can('noop'), "should be able to noop from green state")
  488. ok(fsm.can('warn'), "should be able to warn from green state")
  489. fsm.noop(); equal(fsm.current, 'green', "noop event should not cause a transition (there is no 'to' specified)");
  490. fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow");
  491. ok(fsm.cannot('noop'), "should NOT be able to noop from yellow state")
  492. ok(fsm.cannot('warn'), "should NOT be able to warn from yellow state")
  493. });
  494. //-----------------------------------------------------------------------------
  495. test("wildcard 'from' allows event from any state (github issue #11)", function() {
  496. var fsm = StateMachine.create({
  497. initial: 'stopped',
  498. events: [
  499. { name: 'prepare', from: 'stopped', to: 'ready' },
  500. { name: 'start', from: 'ready', to: 'running' },
  501. { name: 'resume', from: 'paused', to: 'running' },
  502. { name: 'pause', from: 'running', to: 'paused' },
  503. { name: 'stop', from: '*', to: 'stopped' }
  504. ]});
  505. equal(fsm.current, 'stopped', "initial state should be stopped");
  506. fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready");
  507. fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from ready to stopped");
  508. fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready");
  509. fsm.start(); equal(fsm.current, 'running', "start event should transition from ready to running");
  510. fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from running to stopped");
  511. fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready");
  512. fsm.start(); equal(fsm.current, 'running', "start event should transition from ready to running");
  513. fsm.pause(); equal(fsm.current, 'paused', "pause event should transition from running to paused");
  514. fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from paused to stopped");
  515. deepEqual(fsm.transitions(), ["prepare", "stop"], "ensure wildcard event (stop) is included in available transitions")
  516. fsm.prepare(); deepEqual(fsm.transitions(), ["start", "stop"], "ensure wildcard event (stop) is included in available transitions")
  517. fsm.start(); deepEqual(fsm.transitions(), ["pause", "stop"], "ensure wildcard event (stop) is included in available transitions")
  518. fsm.stop(); deepEqual(fsm.transitions(), ["prepare", "stop"], "ensure wildcard event (stop) is included in available transitions")
  519. });
  520. //-----------------------------------------------------------------------------
  521. test("missing 'from' allows event from any state (github issue #11) ", function() {
  522. var fsm = StateMachine.create({
  523. initial: 'stopped',
  524. events: [
  525. { name: 'prepare', from: 'stopped', to: 'ready' },
  526. { name: 'start', from: 'ready', to: 'running' },
  527. { name: 'resume', from: 'paused', to: 'running' },
  528. { name: 'pause', from: 'running', to: 'paused' },
  529. { name: 'stop', /* any from state */ to: 'stopped' }
  530. ]});
  531. equal(fsm.current, 'stopped', "initial state should be stopped");
  532. fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready");
  533. fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from ready to stopped");
  534. fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready");
  535. fsm.start(); equal(fsm.current, 'running', "start event should transition from ready to running");
  536. fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from running to stopped");
  537. fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready");
  538. fsm.start(); equal(fsm.current, 'running', "start event should transition from ready to running");
  539. fsm.pause(); equal(fsm.current, 'paused', "pause event should transition from running to paused");
  540. fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from paused to stopped");
  541. });
  542. //-----------------------------------------------------------------------------
  543. test("event return values (github issue #12) ", function() {
  544. var fsm = StateMachine.create({
  545. initial: 'stopped',
  546. events: [
  547. { name: 'prepare', from: 'stopped', to: 'ready' },
  548. { name: 'fake', from: 'ready', to: 'running' },
  549. { name: 'start', from: 'ready', to: 'running' }
  550. ],
  551. callbacks: {
  552. onbeforefake: function(event,from,to,a,b,c) { return false; }, // this event will be cancelled
  553. onleaveready: function(event,from,to,a,b,c) { return StateMachine.ASYNC; } // this state transition is ASYNC
  554. }
  555. });
  556. equal(fsm.current, 'stopped', "initial state should be stopped");
  557. equal(fsm.prepare(), StateMachine.Result.SUCCEEDED, "expected event to have SUCCEEDED");
  558. equal(fsm.current, 'ready', "prepare event should transition from stopped to ready");
  559. equal(fsm.fake(), StateMachine.Result.CANCELLED, "expected event to have been CANCELLED");
  560. equal(fsm.current, 'ready', "cancelled event should not cause a transition");
  561. equal(fsm.start(), StateMachine.Result.PENDING, "expected event to cause a PENDING asynchronous transition");
  562. equal(fsm.current, 'ready', "async transition hasn't happened yet");
  563. equal(fsm.transition(), StateMachine.Result.SUCCEEDED, "expected async transition to have SUCCEEDED");
  564. equal(fsm.current, 'running', "async transition should now be complete");
  565. });