From cf5863741cd5d76ad047eb6a07788cb394cbf843 Mon Sep 17 00:00:00 2001 From: Julien Sanchez Date: Wed, 19 Sep 2012 09:38:13 +0200 Subject: [PATCH 01/87] Fix event return codes and tests --- state-machine.js | 14 +++++++------- test/test_basics.js | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/state-machine.js b/state-machine.js index 7d9e0c9..81aab3c 100644 --- a/state-machine.js +++ b/state-machine.js @@ -106,11 +106,11 @@ return this.error(name, from, to, args, StateMachine.Error.INVALID_TRANSITION, "event " + name + " inappropriate in current state " + this.current); if (false === StateMachine.beforeEvent(this, name, from, to, args)) - return StateMachine.CANCELLED; + return StateMachine.Result.CANCELLED; if (from === to) { StateMachine.afterEvent(this, name, from, to, args); - return StateMachine.NOTRANSITION; + return StateMachine.Result.NOTRANSITION; } // prepare a transition method for use EITHER lower down, or by caller if they want an async transition (indicated by an ASYNC return value from leaveState) @@ -121,6 +121,7 @@ StateMachine.enterState( fsm, name, from, to, args); StateMachine.changeState(fsm, name, from, to, args); StateMachine.afterEvent( fsm, name, from, to, args); + return StateMachine.Result.SUCCEEDED; }; this.transition.cancel = function() { // provide a way for caller to cancel async transition if desired (issue #22) fsm.transition = null; @@ -130,15 +131,14 @@ var leave = StateMachine.leaveState(this, name, from, to, args); if (false === leave) { this.transition = null; - return StateMachine.CANCELLED; + return StateMachine.Result.CANCELLED; } - else if ("async" === leave) { - return StateMachine.ASYNC; + else if (StateMachine.ASYNC === leave) { + return StateMachine.Result.ASYNC; } else { if (this.transition) - this.transition(); // in case user manually called transition() but forgot to return ASYNC - return StateMachine.SUCCEEDED; + return this.transition(); // in case user manually called transition() but forgot to return ASYNC } }; diff --git a/test/test_basics.js b/test/test_basics.js index 86bb82e..f23d1a7 100644 --- a/test/test_basics.js +++ b/test/test_basics.js @@ -448,16 +448,16 @@ test("event return values (github issue #12) ", function() { equals(fsm.current, 'stopped', "initial state should be stopped"); - equals(fsm.prepare(), StateMachine.SUCCEEDED, "expected event to have SUCCEEDED"); + equals(fsm.prepare(), StateMachine.Result.SUCCEEDED, "expected event to have SUCCEEDED"); equals(fsm.current, 'ready', "prepare event should transition from stopped to ready"); - equals(fsm.fake(), StateMachine.CANCELLED, "expected event to have been CANCELLED"); + equals(fsm.fake(), StateMachine.Result.CANCELLED, "expected event to have been CANCELLED"); equals(fsm.current, 'ready', "cancelled event should not cause a transition"); - equals(fsm.start(), StateMachine.ASYNC, "expected event to cause an ASYNC transition"); + equals(fsm.start(), StateMachine.Result.ASYNC, "expected event to cause an ASYNC transition"); equals(fsm.current, 'ready', "async transition hasn't happened yet"); - equals(fsm.transition(), StateMachine.SUCCEEDED, "expected async transition to have SUCCEEDED"); + equals(fsm.transition(), StateMachine.Result.SUCCEEDED, "expected async transition to have SUCCEEDED"); equals(fsm.current, 'running', "async transition should now be complete"); }); From 41db8e81a997815d11903b7af16bccbe7433488c Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 26 Jan 2013 09:47:16 -0800 Subject: [PATCH 02/87] Tweaks after pull #34 - release notes, minified version, whitespace alignment --- RELEASE_NOTES.md | 1 + state-machine.min.js | 2 +- test/test_basics.js | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 74ded02..2663242 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,7 @@ Version 2.2.0 (unreleased) -------------------------- + * Fixed 'undefined' event return codes (issue #34) - pull from gentooboontoo (thanks!) * Allow async event transition to be cancelled (issue #22) Version 2.1.0 (January 7th 2012) diff --git a/state-machine.min.js b/state-machine.min.js index bed37b8..39c025e 100644 --- a/state-machine.min.js +++ b/state-machine.min.js @@ -1 +1 @@ -(function(b){var a={VERSION:"2.2.0",Result:{SUCCEEDED:1,NOTRANSITION:2,CANCELLED:3,ASYNC:4},Error:{INVALID_TRANSITION:100,PENDING_TRANSITION:200,INVALID_CALLBACK:300},WILDCARD:"*",ASYNC:"async",create:function(g,h){var j=(typeof g.initial=="string")?{state:g.initial}:g.initial;var f=h||g.target||{};var l=g.events||[];var i=g.callbacks||{};var d={};var k=function(m){var p=(m.from instanceof Array)?m.from:(m.from?[m.from]:[a.WILDCARD]);d[m.name]=d[m.name]||{};for(var o=0;o Date: Sat, 26 Jan 2013 09:51:42 -0800 Subject: [PATCH 03/87] clarify a slightly confusing comment --- state-machine.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/state-machine.js b/state-machine.js index 81aab3c..b6da0a2 100644 --- a/state-machine.js +++ b/state-machine.js @@ -12,7 +12,7 @@ SUCCEEDED: 1, // the event transitioned successfully from one state to another NOTRANSITION: 2, // the event was successfull but no state transition was necessary CANCELLED: 3, // the event was cancelled by the caller in a beforeEvent callback - ASYNC: 4 // the event is asynchronous and the caller is in control of when the transition occurs + ASYNC: 4 // the event is asynchronous and the caller is in control of when the transition occurs }, Error: { @@ -137,8 +137,8 @@ return StateMachine.Result.ASYNC; } else { - if (this.transition) - return this.transition(); // in case user manually called transition() but forgot to return ASYNC + if (this.transition) // need to check in case user manually called transition() but forgot to return StateMachine.ASYNC + return this.transition(); } }; From 4f584f051270459e7819347afd6077c92d5bc574 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 26 Jan 2013 11:15:17 -0800 Subject: [PATCH 04/87] Renamed Result.ASYNC to Result.PENDING to avoid confusion with existing ASYNC constant (that is used to request an asynchronous transition) --- state-machine.js | 4 ++-- test/test_basics.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/state-machine.js b/state-machine.js index b6da0a2..0740fa5 100644 --- a/state-machine.js +++ b/state-machine.js @@ -12,7 +12,7 @@ SUCCEEDED: 1, // the event transitioned successfully from one state to another NOTRANSITION: 2, // the event was successfull but no state transition was necessary CANCELLED: 3, // the event was cancelled by the caller in a beforeEvent callback - ASYNC: 4 // the event is asynchronous and the caller is in control of when the transition occurs + PENDING: 4 // the event is asynchronous and the caller is in control of when the transition occurs }, Error: { @@ -134,7 +134,7 @@ return StateMachine.Result.CANCELLED; } else if (StateMachine.ASYNC === leave) { - return StateMachine.Result.ASYNC; + return StateMachine.Result.PENDING; } else { if (this.transition) // need to check in case user manually called transition() but forgot to return StateMachine.ASYNC diff --git a/test/test_basics.js b/test/test_basics.js index d9a4478..56b16d1 100644 --- a/test/test_basics.js +++ b/test/test_basics.js @@ -454,7 +454,7 @@ test("event return values (github issue #12) ", function() { equals(fsm.fake(), StateMachine.Result.CANCELLED, "expected event to have been CANCELLED"); equals(fsm.current, 'ready', "cancelled event should not cause a transition"); - equals(fsm.start(), StateMachine.Result.ASYNC, "expected event to cause an ASYNC transition"); + equals(fsm.start(), StateMachine.Result.PENDING, "expected event to cause a PENDING asynchronous transition"); equals(fsm.current, 'ready', "async transition hasn't happened yet"); equals(fsm.transition(), StateMachine.Result.SUCCEEDED, "expected async transition to have SUCCEEDED"); From f5eb7e300d1f2312651240c91ff3e33052dd65b3 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 26 Jan 2013 12:52:53 -0800 Subject: [PATCH 05/87] [issue #28] - Added generalized callbacks for intercepting all events and state transitions (instead of having a callback per-event/state) * onbeforeevent * onleavestate * onenterstate * onafterevent E.g. * `onbeforeevent` is called for every event, while `onbeforeGO` is only called before the GO event. * `onleavestate` is called for every state, while `onleaveRED` is only called when leaving the RED state. * `onenterstate` is called for every state, while `onenterGREEN` is only called when entering the GREEN state. * `onafterevent` is called for every event, while `onafterGO` is only called after the GO event. NOTE: deprecated the legacy `onchangestate` callback (its the same as `onenterstate`) --- README.md | 52 +++++++++++++----- RELEASE_NOTES.md | 2 + state-machine.js | 41 ++++++++++++-- state-machine.min.js | 2 +- test/test_advanced.js | 80 +++++++++++++++++++++++---- test/test_async.js | 26 ++++++--- test/test_basics.js | 118 +++++++++++++++++++++++++++++++++++----- test/test_initialize.js | 70 ++++++++++++++++++------ 8 files changed, 320 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index e312227..45719ae 100644 --- a/README.md +++ b/README.md @@ -89,25 +89,47 @@ the same name if you prefer the verbose approach. Callbacks ========= -4 callbacks are available if your state machine has methods using the following naming conventions: +4 types of callback are available using the following naming conventions: - * onbefore**event** - fired before the event - * onleave**state** - fired when leaving the old state - * onenter**state** - fired when entering the new state - * onafter**event** - fired after the event + * `onbeforeEVENT` - fired before the event + * `onleaveSTATE` - fired when leaving the old state + * `onenterSTATE` - fired when entering the new state + * `onafterEVENT` - fired after the event + +>> (using your specific EVENT and STATE names) You can affect the event in 3 ways: - * return `false` from an `onbeforeevent` handler to cancel the event. - * return `false` from an `onleavestate` handler to cancel the event. - * return `ASYNC` from an `onleavestate` handler to perform an asynchronous state transition (see next section) + * return `false` from an `onbeforeEVENT` handler to cancel the event. + * return `false` from an `onleaveSTATE` handler to cancel the event. + * return `ASYNC` from an `onleaveSTATE` handler to perform an asynchronous state transition (see next section) For convenience, the 2 most useful callbacks can be shortened: - * on**event** - convenience shorthand for onafter**event** - * on**state** - convenience shorthand for onenter**state** + * `onEVENT` - convenience shorthand for `onafterEVENT` + * `onSTATE` - convenience shorthand for `onenterSTATE` + +In addition, 4 general-purpose callbacks can be used to capture **all** event and state changes: + + * `onbeforeevent` - fired before any event + * `onleavestate` - fired when leaving any state + * `onenterstate` - fired when entering any state + * `onafterevent` - fired after any event + +The order in which callbacks occur is as follows: + +>> assume event **go** transitions from **red** state to **green** + + * `onbeforego` - specific handler for the **go** event only + * `onbeforeevent` - generic handler for all events + * `onleavered` - specific handler for the **red** state only + * `onleavestate` - generic handler for all states + * `onentergreen` - specific handler for the **green** state only + * `onenterstate` - generic handler for all states + * `onaftergo` - specific handler for the **go** event only + * `onafterevent` - generic handler for all events -In addition, a generic `onchangestate()` callback can be used to call a single function for _all_ state changes: +>> NOTE: the legacy `onchangestate` handler has been deprecated and will be removed in a future version All callbacks will be passed the same arguments: @@ -141,10 +163,10 @@ Callbacks can be specified when the state machine is first created: Additionally, they can be added and removed from the state machine at any time: - fsm.ongreen = null; - fsm.onyellow = null; - fsm.onred = null; - fsm.onchangestate = function(event, from, to) { document.body.className = to; }; + fsm.ongreen = null; + fsm.onyellow = null; + fsm.onred = null; + fsm.onenterstate = function(event, from, to) { document.body.className = to; }; Asynchronous State Transitions ============================== diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 2663242..9a6006f 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,8 @@ Version 2.2.0 (unreleased) -------------------------- + * Added generic event callbacks 'onbeforeevent' and 'onafterevent' (issue #28) + * Added generic state callbacks 'onleavestate' and 'onenterstate' (issue #28) * Fixed 'undefined' event return codes (issue #34) - pull from gentooboontoo (thanks!) * Allow async event transition to be cancelled (issue #22) diff --git a/state-machine.js b/state-machine.js index 0740fa5..42e1099 100644 --- a/state-machine.js +++ b/state-machine.js @@ -85,12 +85,43 @@ } }, - beforeEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbefore' + name], name, from, to, args); }, - afterEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafter' + name] || fsm['on' + name], name, from, to, args); }, - leaveState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleave' + from], name, from, to, args); }, - enterState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenter' + to] || fsm['on' + to], name, from, to, args); }, - changeState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onchangestate'], name, from, to, args); }, + beforeAnyEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbeforeevent'], name, from, to, args); }, + afterAnyEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafterevent'], name, from, to, args); }, + leaveAnyState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleavestate'], name, from, to, args); }, + enterAnyState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenterstate'], name, from, to, args); }, + changeState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onchangestate'], name, from, to, args); }, + + beforeThisEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbefore' + name], name, from, to, args); }, + afterThisEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafter' + name] || fsm['on' + name], name, from, to, args); }, + leaveThisState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleave' + from], name, from, to, args); }, + enterThisState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenter' + to] || fsm['on' + to], name, from, to, args); }, + + beforeEvent: function(fsm, name, from, to, args) { + if ((false === StateMachine.beforeThisEvent(fsm, name, from, to, args)) || + (false === StateMachine.beforeAnyEvent( fsm, name, from, to, args))) + return false; + }, + + afterEvent: function(fsm, name, from, to, args) { + StateMachine.afterThisEvent(fsm, name, from, to, args); + StateMachine.afterAnyEvent( fsm, name, from, to, args); + }, + + leaveState: function(fsm, name, from, to, args) { + var specific = StateMachine.leaveThisState(fsm, name, from, to, args), + general = StateMachine.leaveAnyState( fsm, name, from, to, args); + if ((false === specific) || (false === general)) + return false; + else if ((StateMachine.ASYNC === specific) || (StateMachine.ASYNC === general)) + return StateMachine.ASYNC; + }, + + enterState: function(fsm, name, from, to, args) { + StateMachine.enterThisState(fsm, name, from, to, args); + StateMachine.enterAnyState( fsm, name, from, to, args); + }, + //=========================================================================== buildEvent: function(name, map) { return function() { diff --git a/state-machine.min.js b/state-machine.min.js index 39c025e..2ae741d 100644 --- a/state-machine.min.js +++ b/state-machine.min.js @@ -1 +1 @@ -(function(b){var a={VERSION:"2.2.0",Result:{SUCCEEDED:1,NOTRANSITION:2,CANCELLED:3,ASYNC:4},Error:{INVALID_TRANSITION:100,PENDING_TRANSITION:200,INVALID_CALLBACK:300},WILDCARD:"*",ASYNC:"async",create:function(g,h){var j=(typeof g.initial=="string")?{state:g.initial}:g.initial;var f=h||g.target||{};var l=g.events||[];var i=g.callbacks||{};var d={};var k=function(m){var p=(m.from instanceof Array)?m.from:(m.from?[m.from]:[a.WILDCARD]);d[m.name]=d[m.name]||{};for(var o=0;o Date: Sat, 26 Jan 2013 13:13:58 -0800 Subject: [PATCH 06/87] trying to improve the 'Callbacks' section of the README --- README.md | 55 ++++++++++++++++++++++++------------------------ state-machine.js | 4 ++-- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 45719ae..dec4e0e 100644 --- a/README.md +++ b/README.md @@ -89,20 +89,14 @@ the same name if you prefer the verbose approach. Callbacks ========= -4 types of callback are available using the following naming conventions: +4 types of callback are available by attaching methods to your StateMachine using the following naming conventions: * `onbeforeEVENT` - fired before the event * `onleaveSTATE` - fired when leaving the old state * `onenterSTATE` - fired when entering the new state * `onafterEVENT` - fired after the event ->> (using your specific EVENT and STATE names) - -You can affect the event in 3 ways: - - * return `false` from an `onbeforeEVENT` handler to cancel the event. - * return `false` from an `onleaveSTATE` handler to cancel the event. - * return `ASYNC` from an `onleaveSTATE` handler to perform an asynchronous state transition (see next section) +>> (using your **specific** EVENT and STATE names) For convenience, the 2 most useful callbacks can be shortened: @@ -111,25 +105,10 @@ For convenience, the 2 most useful callbacks can be shortened: In addition, 4 general-purpose callbacks can be used to capture **all** event and state changes: - * `onbeforeevent` - fired before any event - * `onleavestate` - fired when leaving any state - * `onenterstate` - fired when entering any state - * `onafterevent` - fired after any event - -The order in which callbacks occur is as follows: - ->> assume event **go** transitions from **red** state to **green** - - * `onbeforego` - specific handler for the **go** event only - * `onbeforeevent` - generic handler for all events - * `onleavered` - specific handler for the **red** state only - * `onleavestate` - generic handler for all states - * `onentergreen` - specific handler for the **green** state only - * `onenterstate` - generic handler for all states - * `onaftergo` - specific handler for the **go** event only - * `onafterevent` - generic handler for all events - ->> NOTE: the legacy `onchangestate` handler has been deprecated and will be removed in a future version + * `onbeforeevent` - fired before *any* event + * `onleavestate` - fired when leaving *any* state + * `onenterstate` - fired when entering *any* state + * `onafterevent` - fired after *any* event All callbacks will be passed the same arguments: @@ -168,6 +147,28 @@ Additionally, they can be added and removed from the state machine at any time: fsm.onred = null; fsm.onenterstate = function(event, from, to) { document.body.className = to; }; + +The order in which callbacks occur is as follows: + +>> assume event **go** transitions from **red** state to **green** + + * `onbeforego` - specific handler for the **go** event only + * `onbeforeevent` - generic handler for all events + * `onleavered` - specific handler for the **red** state only + * `onleavestate` - generic handler for all states + * `onentergreen` - specific handler for the **green** state only + * `onenterstate` - generic handler for all states + * `onaftergo` - specific handler for the **go** event only + * `onafterevent` - generic handler for all events + +>> NOTE: the legacy `onchangestate` handler has been deprecated and will be removed in a future version + +You can affect the event in 3 ways: + + * return `false` from an `onbeforeEVENT` handler to cancel the event. + * return `false` from an `onleaveSTATE` handler to cancel the event. + * return `ASYNC` from an `onleaveSTATE` handler to perform an asynchronous state transition (see next section) + Asynchronous State Transitions ============================== diff --git a/state-machine.js b/state-machine.js index 42e1099..5415b4c 100644 --- a/state-machine.js +++ b/state-machine.js @@ -86,9 +86,9 @@ }, beforeAnyEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbeforeevent'], name, from, to, args); }, - afterAnyEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafterevent'], name, from, to, args); }, + afterAnyEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafterevent'] || fsm['onevent'], name, from, to, args); }, leaveAnyState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleavestate'], name, from, to, args); }, - enterAnyState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenterstate'], name, from, to, args); }, + enterAnyState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenterstate'] || fsm['onstate'], name, from, to, args); }, changeState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onchangestate'], name, from, to, args); }, beforeThisEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbefore' + name], name, from, to, args); }, From f7ebd5db90e8d6b448fe0d93017927023efd34f1 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 26 Jan 2013 14:22:02 -0800 Subject: [PATCH 07/87] [issue #30] - Missing license-required attribution --- LICENSE | 2 +- state-machine.js | 9 +++++++++ state-machine.min.js | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 8ad703c..d666110 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012 Jake Gordon and contributors +Copyright (c) 2012, 2013 Jake Gordon and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/state-machine.js b/state-machine.js index 5415b4c..17657b2 100644 --- a/state-machine.js +++ b/state-machine.js @@ -1,3 +1,12 @@ +/* + + Javascript State Machine Library - https://github.com/jakesgordon/javascript-state-machine + + Copyright (c) 2012, 2013 Jake Gordon and contributors + Released under the MIT license - https://github.com/jakesgordon/javascript-state-machine/blob/master/LICENSE + +*/ + (function (window) { var StateMachine = { diff --git a/state-machine.min.js b/state-machine.min.js index 2ae741d..6c59c00 100644 --- a/state-machine.min.js +++ b/state-machine.min.js @@ -1 +1 @@ -(function(b){var a={VERSION:"2.2.0",Result:{SUCCEEDED:1,NOTRANSITION:2,CANCELLED:3,PENDING:4},Error:{INVALID_TRANSITION:100,PENDING_TRANSITION:200,INVALID_CALLBACK:300},WILDCARD:"*",ASYNC:"async",create:function(g,h){var j=(typeof g.initial=="string")?{state:g.initial}:g.initial;var f=h||g.target||{};var l=g.events||[];var i=g.callbacks||{};var d={};var k=function(m){var p=(m.from instanceof Array)?m.from:(m.from?[m.from]:[a.WILDCARD]);d[m.name]=d[m.name]||{};for(var o=0;o Date: Sat, 26 Jan 2013 15:17:16 -0800 Subject: [PATCH 08/87] extended `fsm.is()` to accept an array of states (in addition to just a single state) --- README.md | 2 +- RELEASE_NOTES.md | 3 ++- state-machine.js | 2 +- state-machine.min.js | 2 +- test/test_basics.js | 31 +++++++++++++++++++++++++++++++ 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dec4e0e..1fd5018 100644 --- a/README.md +++ b/README.md @@ -274,7 +274,7 @@ state and you would need to provide an event to take it out of this state: fsm.startup(); alert(fsm.current); // "green" -If you specify the name of your initial event (as in all the earlier examples), then an +If you specify the name of your initial state (as in all the earlier examples), then an implicit `startup` event will be created for you and fired when the state machine is constructed. var fsm = StateMachine.create({ diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 9a6006f..817c6d8 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,7 @@ Version 2.2.0 (unreleased) -------------------------- - + + * extended `fsm.is()` to accept an array of states (in addition to a single state) * Added generic event callbacks 'onbeforeevent' and 'onafterevent' (issue #28) * Added generic state callbacks 'onleavestate' and 'onenterstate' (issue #28) * Fixed 'undefined' event return codes (issue #34) - pull from gentooboontoo (thanks!) diff --git a/state-machine.js b/state-machine.js index 17657b2..f500c98 100644 --- a/state-machine.js +++ b/state-machine.js @@ -69,7 +69,7 @@ } fsm.current = 'none'; - fsm.is = function(state) { return this.current == state; }; + fsm.is = function(state) { return (state instanceof Array) ? (state.indexOf(this.current) >= 0) : (this.current === state); }; fsm.can = function(event) { return !this.transition && (map[event].hasOwnProperty(this.current) || map[event].hasOwnProperty(StateMachine.WILDCARD)); } fsm.cannot = function(event) { return !this.can(event); }; fsm.error = cfg.error || function(name, from, to, args, error, msg, e) { throw e || msg; }; // default behavior when something unexpected happens is to throw an exception, but caller can override this behavior if desired (see github issue #3 and #17) diff --git a/state-machine.min.js b/state-machine.min.js index 6c59c00..b68949d 100644 --- a/state-machine.min.js +++ b/state-machine.min.js @@ -1 +1 @@ -(function(b){var a={VERSION:"2.2.0",Result:{SUCCEEDED:1,NOTRANSITION:2,CANCELLED:3,PENDING:4},Error:{INVALID_TRANSITION:100,PENDING_TRANSITION:200,INVALID_CALLBACK:300},WILDCARD:"*",ASYNC:"async",create:function(g,h){var j=(typeof g.initial=="string")?{state:g.initial}:g.initial;var f=h||g.target||{};var l=g.events||[];var i=g.callbacks||{};var d={};var k=function(m){var p=(m.from instanceof Array)?m.from:(m.from?[m.from]:[a.WILDCARD]);d[m.name]=d[m.name]||{};for(var o=0;o=0):(this.current===m)};f.can=function(m){return !this.transition&&(d[m].hasOwnProperty(this.current)||d[m].hasOwnProperty(a.WILDCARD))};f.cannot=function(m){return !this.can(m)};f.error=g.error||function(o,s,r,n,m,q,p){throw p||q};if(j&&!j.defer){f[j.event]()}return f},doCallback:function(h,f,d,j,i,c){if(f){try{return f.apply(h,[d,j,i].concat(c))}catch(g){return h.error(d,j,i,c,a.Error.INVALID_CALLBACK,"an exception occurred in a caller-provided callback function",g)}}},beforeAnyEvent:function(e,d,g,f,c){return a.doCallback(e,e.onbeforeevent,d,g,f,c)},afterAnyEvent:function(e,d,g,f,c){return a.doCallback(e,e.onafterevent||e.onevent,d,g,f,c)},leaveAnyState:function(e,d,g,f,c){return a.doCallback(e,e.onleavestate,d,g,f,c)},enterAnyState:function(e,d,g,f,c){return a.doCallback(e,e.onenterstate||e.onstate,d,g,f,c)},changeState:function(e,d,g,f,c){return a.doCallback(e,e.onchangestate,d,g,f,c)},beforeThisEvent:function(e,d,g,f,c){return a.doCallback(e,e["onbefore"+d],d,g,f,c)},afterThisEvent:function(e,d,g,f,c){return a.doCallback(e,e["onafter"+d]||e["on"+d],d,g,f,c)},leaveThisState:function(e,d,g,f,c){return a.doCallback(e,e["onleave"+g],d,g,f,c)},enterThisState:function(e,d,g,f,c){return a.doCallback(e,e["onenter"+f]||e["on"+f],d,g,f,c)},beforeEvent:function(e,d,g,f,c){if((false===a.beforeThisEvent(e,d,g,f,c))||(false===a.beforeAnyEvent(e,d,g,f,c))){return false}},afterEvent:function(e,d,g,f,c){a.afterThisEvent(e,d,g,f,c);a.afterAnyEvent(e,d,g,f,c)},leaveState:function(g,f,i,h,e){var d=a.leaveThisState(g,f,i,h,e),c=a.leaveAnyState(g,f,i,h,e);if((false===d)||(false===c)){return false}else{if((a.ASYNC===d)||(a.ASYNC===c)){return a.ASYNC}}},enterState:function(e,d,g,f,c){a.enterThisState(e,d,g,f,c);a.enterAnyState(e,d,g,f,c)},buildEvent:function(c,d){return function(){var i=this.current;var h=d[i]||d[a.WILDCARD]||i;var f=Array.prototype.slice.call(arguments);if(this.transition){return this.error(c,i,h,f,a.Error.PENDING_TRANSITION,"event "+c+" inappropriate because previous transition did not complete")}if(this.cannot(c)){return this.error(c,i,h,f,a.Error.INVALID_TRANSITION,"event "+c+" inappropriate in current state "+this.current)}if(false===a.beforeEvent(this,c,i,h,f)){return a.Result.CANCELLED}if(i===h){a.afterEvent(this,c,i,h,f);return a.Result.NOTRANSITION}var g=this;this.transition=function(){g.transition=null;g.current=h;a.enterState(g,c,i,h,f);a.changeState(g,c,i,h,f);a.afterEvent(g,c,i,h,f);return a.Result.SUCCEEDED};this.transition.cancel=function(){g.transition=null;a.afterEvent(g,c,i,h,f)};var e=a.leaveState(this,c,i,h,f);if(false===e){this.transition=null;return a.Result.CANCELLED}else{if(a.ASYNC===e){return a.Result.PENDING}else{if(this.transition){return this.transition()}}}}}};if("function"===typeof define){define(function(c){return a})}else{b.StateMachine=a}}(this)); \ No newline at end of file diff --git a/test/test_basics.js b/test/test_basics.js index 87ecf0a..758ac82 100644 --- a/test/test_basics.js +++ b/test/test_basics.js @@ -80,6 +80,37 @@ test("can & cannot", function() { //----------------------------------------------------------------------------- +test("is", function() { + + var fsm = StateMachine.create({ + initial: 'green', + events: [ + { name: 'warn', from: 'green', to: 'yellow' }, + { name: 'panic', from: 'yellow', to: 'red' }, + { name: 'calm', from: 'red', to: 'yellow' }, + { name: 'clear', from: 'yellow', to: 'green' } + ]}); + + equals(fsm.current, 'green', "initial state should be green"); + + equals(fsm.is('green'), true, 'current state should match'); + equals(fsm.is('yellow'), false, 'current state should NOT match'); + equals(fsm.is(['green', 'red']), true, 'current state should match when included in array'); + equals(fsm.is(['yellow', 'red']), false, 'current state should NOT match when not included in array'); + + fsm.warn(); + + equals(fsm.current, 'yellow', "current state should be yellow"); + + equals(fsm.is('green'), false, 'current state should NOT match'); + equals(fsm.is('yellow'), true, 'current state should match'); + equals(fsm.is(['green', 'red']), false, 'current state should NOT match when not included in array'); + equals(fsm.is(['yellow', 'red']), true, 'current state should match when included in array'); + +}); + +//----------------------------------------------------------------------------- + test("inappropriate events", function() { var fsm = StateMachine.create({ From 6319311cf1aa39e41b9bf30e59c7bafc9bd64ea1 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 26 Jan 2013 15:31:22 -0800 Subject: [PATCH 09/87] Added optional `final` state(s) and `isFinished()` helper method (issue #23) --- RELEASE_NOTES.md | 1 + state-machine.js | 3 +++ state-machine.min.js | 2 +- test/test_basics.js | 47 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 817c6d8..7f81cb4 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,7 @@ Version 2.2.0 (unreleased) -------------------------- + * Added optional `final` state(s) and `isFinished()` helper method (issue #23) * extended `fsm.is()` to accept an array of states (in addition to a single state) * Added generic event callbacks 'onbeforeevent' and 'onafterevent' (issue #28) * Added generic state callbacks 'onleavestate' and 'onenterstate' (issue #28) diff --git a/state-machine.js b/state-machine.js index f500c98..61aba92 100644 --- a/state-machine.js +++ b/state-machine.js @@ -38,6 +38,7 @@ create: function(cfg, target) { var initial = (typeof cfg.initial == 'string') ? { state: cfg.initial } : cfg.initial; // allow for a simple string, or an object with { state: 'foo', event: 'setup', defer: true|false } + var terminal = cfg.terminal || cfg['final']; var fsm = target || cfg.target || {}; var events = cfg.events || []; var callbacks = cfg.callbacks || {}; @@ -74,6 +75,8 @@ fsm.cannot = function(event) { return !this.can(event); }; fsm.error = cfg.error || function(name, from, to, args, error, msg, e) { throw e || msg; }; // default behavior when something unexpected happens is to throw an exception, but caller can override this behavior if desired (see github issue #3 and #17) + fsm.isFinished = function() { return this.is(terminal); }; + if (initial && !initial.defer) fsm[initial.event](); diff --git a/state-machine.min.js b/state-machine.min.js index b68949d..7561648 100644 --- a/state-machine.min.js +++ b/state-machine.min.js @@ -1 +1 @@ -(function(b){var a={VERSION:"2.2.0",Result:{SUCCEEDED:1,NOTRANSITION:2,CANCELLED:3,PENDING:4},Error:{INVALID_TRANSITION:100,PENDING_TRANSITION:200,INVALID_CALLBACK:300},WILDCARD:"*",ASYNC:"async",create:function(g,h){var j=(typeof g.initial=="string")?{state:g.initial}:g.initial;var f=h||g.target||{};var l=g.events||[];var i=g.callbacks||{};var d={};var k=function(m){var p=(m.from instanceof Array)?m.from:(m.from?[m.from]:[a.WILDCARD]);d[m.name]=d[m.name]||{};for(var o=0;o=0):(this.current===m)};f.can=function(m){return !this.transition&&(d[m].hasOwnProperty(this.current)||d[m].hasOwnProperty(a.WILDCARD))};f.cannot=function(m){return !this.can(m)};f.error=g.error||function(o,s,r,n,m,q,p){throw p||q};if(j&&!j.defer){f[j.event]()}return f},doCallback:function(h,f,d,j,i,c){if(f){try{return f.apply(h,[d,j,i].concat(c))}catch(g){return h.error(d,j,i,c,a.Error.INVALID_CALLBACK,"an exception occurred in a caller-provided callback function",g)}}},beforeAnyEvent:function(e,d,g,f,c){return a.doCallback(e,e.onbeforeevent,d,g,f,c)},afterAnyEvent:function(e,d,g,f,c){return a.doCallback(e,e.onafterevent||e.onevent,d,g,f,c)},leaveAnyState:function(e,d,g,f,c){return a.doCallback(e,e.onleavestate,d,g,f,c)},enterAnyState:function(e,d,g,f,c){return a.doCallback(e,e.onenterstate||e.onstate,d,g,f,c)},changeState:function(e,d,g,f,c){return a.doCallback(e,e.onchangestate,d,g,f,c)},beforeThisEvent:function(e,d,g,f,c){return a.doCallback(e,e["onbefore"+d],d,g,f,c)},afterThisEvent:function(e,d,g,f,c){return a.doCallback(e,e["onafter"+d]||e["on"+d],d,g,f,c)},leaveThisState:function(e,d,g,f,c){return a.doCallback(e,e["onleave"+g],d,g,f,c)},enterThisState:function(e,d,g,f,c){return a.doCallback(e,e["onenter"+f]||e["on"+f],d,g,f,c)},beforeEvent:function(e,d,g,f,c){if((false===a.beforeThisEvent(e,d,g,f,c))||(false===a.beforeAnyEvent(e,d,g,f,c))){return false}},afterEvent:function(e,d,g,f,c){a.afterThisEvent(e,d,g,f,c);a.afterAnyEvent(e,d,g,f,c)},leaveState:function(g,f,i,h,e){var d=a.leaveThisState(g,f,i,h,e),c=a.leaveAnyState(g,f,i,h,e);if((false===d)||(false===c)){return false}else{if((a.ASYNC===d)||(a.ASYNC===c)){return a.ASYNC}}},enterState:function(e,d,g,f,c){a.enterThisState(e,d,g,f,c);a.enterAnyState(e,d,g,f,c)},buildEvent:function(c,d){return function(){var i=this.current;var h=d[i]||d[a.WILDCARD]||i;var f=Array.prototype.slice.call(arguments);if(this.transition){return this.error(c,i,h,f,a.Error.PENDING_TRANSITION,"event "+c+" inappropriate because previous transition did not complete")}if(this.cannot(c)){return this.error(c,i,h,f,a.Error.INVALID_TRANSITION,"event "+c+" inappropriate in current state "+this.current)}if(false===a.beforeEvent(this,c,i,h,f)){return a.Result.CANCELLED}if(i===h){a.afterEvent(this,c,i,h,f);return a.Result.NOTRANSITION}var g=this;this.transition=function(){g.transition=null;g.current=h;a.enterState(g,c,i,h,f);a.changeState(g,c,i,h,f);a.afterEvent(g,c,i,h,f);return a.Result.SUCCEEDED};this.transition.cancel=function(){g.transition=null;a.afterEvent(g,c,i,h,f)};var e=a.leaveState(this,c,i,h,f);if(false===e){this.transition=null;return a.Result.CANCELLED}else{if(a.ASYNC===e){return a.Result.PENDING}else{if(this.transition){return this.transition()}}}}}};if("function"===typeof define){define(function(c){return a})}else{b.StateMachine=a}}(this)); \ No newline at end of file +(function(b){var a={VERSION:"2.2.0",Result:{SUCCEEDED:1,NOTRANSITION:2,CANCELLED:3,PENDING:4},Error:{INVALID_TRANSITION:100,PENDING_TRANSITION:200,INVALID_CALLBACK:300},WILDCARD:"*",ASYNC:"async",create:function(h,i){var k=(typeof h.initial=="string")?{state:h.initial}:h.initial;var g=h.terminal||h["final"];var f=i||h.target||{};var m=h.events||[];var j=h.callbacks||{};var d={};var l=function(o){var q=(o.from instanceof Array)?o.from:(o.from?[o.from]:[a.WILDCARD]);d[o.name]=d[o.name]||{};for(var p=0;p=0):(this.current===n)};f.can=function(n){return !this.transition&&(d[n].hasOwnProperty(this.current)||d[n].hasOwnProperty(a.WILDCARD))};f.cannot=function(n){return !this.can(n)};f.error=h.error||function(p,t,s,o,n,r,q){throw q||r};f.isFinished=function(){return this.is(g)};if(k&&!k.defer){f[k.event]()}return f},doCallback:function(h,f,d,j,i,c){if(f){try{return f.apply(h,[d,j,i].concat(c))}catch(g){return h.error(d,j,i,c,a.Error.INVALID_CALLBACK,"an exception occurred in a caller-provided callback function",g)}}},beforeAnyEvent:function(e,d,g,f,c){return a.doCallback(e,e.onbeforeevent,d,g,f,c)},afterAnyEvent:function(e,d,g,f,c){return a.doCallback(e,e.onafterevent||e.onevent,d,g,f,c)},leaveAnyState:function(e,d,g,f,c){return a.doCallback(e,e.onleavestate,d,g,f,c)},enterAnyState:function(e,d,g,f,c){return a.doCallback(e,e.onenterstate||e.onstate,d,g,f,c)},changeState:function(e,d,g,f,c){return a.doCallback(e,e.onchangestate,d,g,f,c)},beforeThisEvent:function(e,d,g,f,c){return a.doCallback(e,e["onbefore"+d],d,g,f,c)},afterThisEvent:function(e,d,g,f,c){return a.doCallback(e,e["onafter"+d]||e["on"+d],d,g,f,c)},leaveThisState:function(e,d,g,f,c){return a.doCallback(e,e["onleave"+g],d,g,f,c)},enterThisState:function(e,d,g,f,c){return a.doCallback(e,e["onenter"+f]||e["on"+f],d,g,f,c)},beforeEvent:function(e,d,g,f,c){if((false===a.beforeThisEvent(e,d,g,f,c))||(false===a.beforeAnyEvent(e,d,g,f,c))){return false}},afterEvent:function(e,d,g,f,c){a.afterThisEvent(e,d,g,f,c);a.afterAnyEvent(e,d,g,f,c)},leaveState:function(g,f,i,h,e){var d=a.leaveThisState(g,f,i,h,e),c=a.leaveAnyState(g,f,i,h,e);if((false===d)||(false===c)){return false}else{if((a.ASYNC===d)||(a.ASYNC===c)){return a.ASYNC}}},enterState:function(e,d,g,f,c){a.enterThisState(e,d,g,f,c);a.enterAnyState(e,d,g,f,c)},buildEvent:function(c,d){return function(){var i=this.current;var h=d[i]||d[a.WILDCARD]||i;var f=Array.prototype.slice.call(arguments);if(this.transition){return this.error(c,i,h,f,a.Error.PENDING_TRANSITION,"event "+c+" inappropriate because previous transition did not complete")}if(this.cannot(c)){return this.error(c,i,h,f,a.Error.INVALID_TRANSITION,"event "+c+" inappropriate in current state "+this.current)}if(false===a.beforeEvent(this,c,i,h,f)){return a.Result.CANCELLED}if(i===h){a.afterEvent(this,c,i,h,f);return a.Result.NOTRANSITION}var g=this;this.transition=function(){g.transition=null;g.current=h;a.enterState(g,c,i,h,f);a.changeState(g,c,i,h,f);a.afterEvent(g,c,i,h,f);return a.Result.SUCCEEDED};this.transition.cancel=function(){g.transition=null;a.afterEvent(g,c,i,h,f)};var e=a.leaveState(this,c,i,h,f);if(false===e){this.transition=null;return a.Result.CANCELLED}else{if(a.ASYNC===e){return a.Result.PENDING}else{if(this.transition){return this.transition()}}}}}};if("function"===typeof define){define(function(c){return a})}else{b.StateMachine=a}}(this)); \ No newline at end of file diff --git a/test/test_basics.js b/test/test_basics.js index 758ac82..9c5a567 100644 --- a/test/test_basics.js +++ b/test/test_basics.js @@ -111,6 +111,53 @@ test("is", function() { //----------------------------------------------------------------------------- +test("isFinished", function() { + + var fsm = StateMachine.create({ + initial: 'green', terminal: 'red', + events: [ + { name: 'warn', from: 'green', to: 'yellow' }, + { name: 'panic', from: 'yellow', to: 'red' } + ]}); + + equals(fsm.current, 'green'); + equals(fsm.isFinished(), false); + + fsm.warn(); + equals(fsm.current, 'yellow'); + equals(fsm.isFinished(), false); + + fsm.panic(); + equals(fsm.current, 'red'); + equals(fsm.isFinished(), true); + +}); + +//----------------------------------------------------------------------------- + +test("isFinished - without specifying terminal state", function() { + + var fsm = StateMachine.create({ + initial: 'green', + events: [ + { name: 'warn', from: 'green', to: 'yellow' }, + { name: 'panic', from: 'yellow', to: 'red' } + ]}); + + equals(fsm.current, 'green'); + equals(fsm.isFinished(), false); + + fsm.warn(); + equals(fsm.current, 'yellow'); + equals(fsm.isFinished(), false); + + fsm.panic(); + equals(fsm.current, 'red'); + equals(fsm.isFinished(), false); + +}); +//----------------------------------------------------------------------------- + test("inappropriate events", function() { var fsm = StateMachine.create({ From 33d62e6238c296cfb4ab4228692b575340fbbe50 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 26 Jan 2013 17:05:56 -0800 Subject: [PATCH 10/87] Formal release of v2.2.0 - updated RELEASE_NOTES and links --- README.md | 2 +- RELEASE_NOTES.md | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1fd5018..4fc60d1 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Javascript Finite State Machine (v2.2.0) This standalone javascript micro-framework provides a finite state machine for your pleasure. * You can find the [code here](https://github.com/jakesgordon/javascript-state-machine) - * You can find a [description here](http://codeincomplete.com/posts/2012/1/7/javascript_state_machine_v2_1_0/) + * You can find a [description here](http://codeincomplete.com/posts/2013/1/26/javascript_state_machine_v2_2_0/) * You can find a [working demo here](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/example/) Download diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7f81cb4..d96fac8 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,5 @@ -Version 2.2.0 (unreleased) --------------------------- +Version 2.2.0 (January 26th 2013) +--------------------------------- * Added optional `final` state(s) and `isFinished()` helper method (issue #23) * extended `fsm.is()` to accept an array of states (in addition to a single state) @@ -7,6 +7,7 @@ Version 2.2.0 (unreleased) * Added generic state callbacks 'onleavestate' and 'onenterstate' (issue #28) * Fixed 'undefined' event return codes (issue #34) - pull from gentooboontoo (thanks!) * Allow async event transition to be cancelled (issue #22) + * [read more...](http://codeincomplete.com/posts/2013/1/26/javascript_state_machine_v2_2_0/) Version 2.1.0 (January 7th 2012) -------------------------------- From 6e6fe696b143d68cd6c4678651bbf8d6766e0fe9 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 15 Mar 2014 12:59:04 -0700 Subject: [PATCH 11/87] Started 2.3.0 branch for (upcoming) minor release --- LICENSE | 2 +- README.md | 2 +- RELEASE_NOTES.md | 5 +++++ state-machine.js | 4 ++-- state-machine.min.js | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/LICENSE b/LICENSE index d666110..9cb35cd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012, 2013 Jake Gordon and contributors +Copyright (c) 2012, 2013, 2014, Jake Gordon and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 4fc60d1..df31893 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Javascript Finite State Machine (v2.2.0) +Javascript Finite State Machine (v2.3.0) ======================================== This standalone javascript micro-framework provides a finite state machine for your pleasure. diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d96fac8..29a9be4 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,8 @@ +Version 2.3.0 (March ?? 2014) +----------------------------- + + * minor updates in progress + Version 2.2.0 (January 26th 2013) --------------------------------- diff --git a/state-machine.js b/state-machine.js index 61aba92..aa9fce8 100644 --- a/state-machine.js +++ b/state-machine.js @@ -2,7 +2,7 @@ Javascript State Machine Library - https://github.com/jakesgordon/javascript-state-machine - Copyright (c) 2012, 2013 Jake Gordon and contributors + Copyright (c) 2012, 2013, 2014, Jake Gordon and contributors Released under the MIT license - https://github.com/jakesgordon/javascript-state-machine/blob/master/LICENSE */ @@ -13,7 +13,7 @@ //--------------------------------------------------------------------------- - VERSION: "2.2.0", + VERSION: "2.3.0", //--------------------------------------------------------------------------- diff --git a/state-machine.min.js b/state-machine.min.js index 7561648..c880aea 100644 --- a/state-machine.min.js +++ b/state-machine.min.js @@ -1 +1 @@ -(function(b){var a={VERSION:"2.2.0",Result:{SUCCEEDED:1,NOTRANSITION:2,CANCELLED:3,PENDING:4},Error:{INVALID_TRANSITION:100,PENDING_TRANSITION:200,INVALID_CALLBACK:300},WILDCARD:"*",ASYNC:"async",create:function(h,i){var k=(typeof h.initial=="string")?{state:h.initial}:h.initial;var g=h.terminal||h["final"];var f=i||h.target||{};var m=h.events||[];var j=h.callbacks||{};var d={};var l=function(o){var q=(o.from instanceof Array)?o.from:(o.from?[o.from]:[a.WILDCARD]);d[o.name]=d[o.name]||{};for(var p=0;p=0):(this.current===n)};f.can=function(n){return !this.transition&&(d[n].hasOwnProperty(this.current)||d[n].hasOwnProperty(a.WILDCARD))};f.cannot=function(n){return !this.can(n)};f.error=h.error||function(p,t,s,o,n,r,q){throw q||r};f.isFinished=function(){return this.is(g)};if(k&&!k.defer){f[k.event]()}return f},doCallback:function(h,f,d,j,i,c){if(f){try{return f.apply(h,[d,j,i].concat(c))}catch(g){return h.error(d,j,i,c,a.Error.INVALID_CALLBACK,"an exception occurred in a caller-provided callback function",g)}}},beforeAnyEvent:function(e,d,g,f,c){return a.doCallback(e,e.onbeforeevent,d,g,f,c)},afterAnyEvent:function(e,d,g,f,c){return a.doCallback(e,e.onafterevent||e.onevent,d,g,f,c)},leaveAnyState:function(e,d,g,f,c){return a.doCallback(e,e.onleavestate,d,g,f,c)},enterAnyState:function(e,d,g,f,c){return a.doCallback(e,e.onenterstate||e.onstate,d,g,f,c)},changeState:function(e,d,g,f,c){return a.doCallback(e,e.onchangestate,d,g,f,c)},beforeThisEvent:function(e,d,g,f,c){return a.doCallback(e,e["onbefore"+d],d,g,f,c)},afterThisEvent:function(e,d,g,f,c){return a.doCallback(e,e["onafter"+d]||e["on"+d],d,g,f,c)},leaveThisState:function(e,d,g,f,c){return a.doCallback(e,e["onleave"+g],d,g,f,c)},enterThisState:function(e,d,g,f,c){return a.doCallback(e,e["onenter"+f]||e["on"+f],d,g,f,c)},beforeEvent:function(e,d,g,f,c){if((false===a.beforeThisEvent(e,d,g,f,c))||(false===a.beforeAnyEvent(e,d,g,f,c))){return false}},afterEvent:function(e,d,g,f,c){a.afterThisEvent(e,d,g,f,c);a.afterAnyEvent(e,d,g,f,c)},leaveState:function(g,f,i,h,e){var d=a.leaveThisState(g,f,i,h,e),c=a.leaveAnyState(g,f,i,h,e);if((false===d)||(false===c)){return false}else{if((a.ASYNC===d)||(a.ASYNC===c)){return a.ASYNC}}},enterState:function(e,d,g,f,c){a.enterThisState(e,d,g,f,c);a.enterAnyState(e,d,g,f,c)},buildEvent:function(c,d){return function(){var i=this.current;var h=d[i]||d[a.WILDCARD]||i;var f=Array.prototype.slice.call(arguments);if(this.transition){return this.error(c,i,h,f,a.Error.PENDING_TRANSITION,"event "+c+" inappropriate because previous transition did not complete")}if(this.cannot(c)){return this.error(c,i,h,f,a.Error.INVALID_TRANSITION,"event "+c+" inappropriate in current state "+this.current)}if(false===a.beforeEvent(this,c,i,h,f)){return a.Result.CANCELLED}if(i===h){a.afterEvent(this,c,i,h,f);return a.Result.NOTRANSITION}var g=this;this.transition=function(){g.transition=null;g.current=h;a.enterState(g,c,i,h,f);a.changeState(g,c,i,h,f);a.afterEvent(g,c,i,h,f);return a.Result.SUCCEEDED};this.transition.cancel=function(){g.transition=null;a.afterEvent(g,c,i,h,f)};var e=a.leaveState(this,c,i,h,f);if(false===e){this.transition=null;return a.Result.CANCELLED}else{if(a.ASYNC===e){return a.Result.PENDING}else{if(this.transition){return this.transition()}}}}}};if("function"===typeof define){define(function(c){return a})}else{b.StateMachine=a}}(this)); \ No newline at end of file +(function(b){var a={VERSION:"2.3.0",Result:{SUCCEEDED:1,NOTRANSITION:2,CANCELLED:3,PENDING:4},Error:{INVALID_TRANSITION:100,PENDING_TRANSITION:200,INVALID_CALLBACK:300},WILDCARD:"*",ASYNC:"async",create:function(h,i){var k=(typeof h.initial=="string")?{state:h.initial}:h.initial;var g=h.terminal||h["final"];var f=i||h.target||{};var m=h.events||[];var j=h.callbacks||{};var d={};var l=function(o){var q=(o.from instanceof Array)?o.from:(o.from?[o.from]:[a.WILDCARD]);d[o.name]=d[o.name]||{};for(var p=0;p=0):(this.current===n)};f.can=function(n){return !this.transition&&(d[n].hasOwnProperty(this.current)||d[n].hasOwnProperty(a.WILDCARD))};f.cannot=function(n){return !this.can(n)};f.error=h.error||function(p,t,s,o,n,r,q){throw q||r};f.isFinished=function(){return this.is(g)};if(k&&!k.defer){f[k.event]()}return f},doCallback:function(h,f,d,j,i,c){if(f){try{return f.apply(h,[d,j,i].concat(c))}catch(g){return h.error(d,j,i,c,a.Error.INVALID_CALLBACK,"an exception occurred in a caller-provided callback function",g)}}},beforeAnyEvent:function(e,d,g,f,c){return a.doCallback(e,e.onbeforeevent,d,g,f,c)},afterAnyEvent:function(e,d,g,f,c){return a.doCallback(e,e.onafterevent||e.onevent,d,g,f,c)},leaveAnyState:function(e,d,g,f,c){return a.doCallback(e,e.onleavestate,d,g,f,c)},enterAnyState:function(e,d,g,f,c){return a.doCallback(e,e.onenterstate||e.onstate,d,g,f,c)},changeState:function(e,d,g,f,c){return a.doCallback(e,e.onchangestate,d,g,f,c)},beforeThisEvent:function(e,d,g,f,c){return a.doCallback(e,e["onbefore"+d],d,g,f,c)},afterThisEvent:function(e,d,g,f,c){return a.doCallback(e,e["onafter"+d]||e["on"+d],d,g,f,c)},leaveThisState:function(e,d,g,f,c){return a.doCallback(e,e["onleave"+g],d,g,f,c)},enterThisState:function(e,d,g,f,c){return a.doCallback(e,e["onenter"+f]||e["on"+f],d,g,f,c)},beforeEvent:function(e,d,g,f,c){if((false===a.beforeThisEvent(e,d,g,f,c))||(false===a.beforeAnyEvent(e,d,g,f,c))){return false}},afterEvent:function(e,d,g,f,c){a.afterThisEvent(e,d,g,f,c);a.afterAnyEvent(e,d,g,f,c)},leaveState:function(g,f,i,h,e){var d=a.leaveThisState(g,f,i,h,e),c=a.leaveAnyState(g,f,i,h,e);if((false===d)||(false===c)){return false}else{if((a.ASYNC===d)||(a.ASYNC===c)){return a.ASYNC}}},enterState:function(e,d,g,f,c){a.enterThisState(e,d,g,f,c);a.enterAnyState(e,d,g,f,c)},buildEvent:function(c,d){return function(){var i=this.current;var h=d[i]||d[a.WILDCARD]||i;var f=Array.prototype.slice.call(arguments);if(this.transition){return this.error(c,i,h,f,a.Error.PENDING_TRANSITION,"event "+c+" inappropriate because previous transition did not complete")}if(this.cannot(c)){return this.error(c,i,h,f,a.Error.INVALID_TRANSITION,"event "+c+" inappropriate in current state "+this.current)}if(false===a.beforeEvent(this,c,i,h,f)){return a.Result.CANCELLED}if(i===h){a.afterEvent(this,c,i,h,f);return a.Result.NOTRANSITION}var g=this;this.transition=function(){g.transition=null;g.current=h;a.enterState(g,c,i,h,f);a.changeState(g,c,i,h,f);a.afterEvent(g,c,i,h,f);return a.Result.SUCCEEDED};this.transition.cancel=function(){g.transition=null;a.afterEvent(g,c,i,h,f)};var e=a.leaveState(this,c,i,h,f);if(false===e){this.transition=null;return a.Result.CANCELLED}else{if(a.ASYNC===e){return a.Result.PENDING}else{if(this.transition){return this.transition()}}}}}};if("function"===typeof define){define(function(c){return a})}else{b.StateMachine=a}}(this)); \ No newline at end of file From db5f5b77914dd5b2560be321687fbafbca8fd67a Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 15 Mar 2014 13:07:49 -0700 Subject: [PATCH 12/87] upgrade to latest version of qunit --- test/qunit/qunit.css | 120 +- test/qunit/qunit.js | 2972 ++++++++++++++++++++++++------------- test/requirejs/index.html | 10 +- test/test_advanced.js | 34 +- test/test_async.js | 176 +-- test/test_basics.js | 174 +-- 6 files changed, 2167 insertions(+), 1319 deletions(-) mode change 100755 => 100644 test/qunit/qunit.css mode change 100755 => 100644 test/qunit/qunit.js diff --git a/test/qunit/qunit.css b/test/qunit/qunit.css old mode 100755 new mode 100644 index a4d1105..93026e3 --- a/test/qunit/qunit.css +++ b/test/qunit/qunit.css @@ -1,13 +1,12 @@ -/** - * QUnit - A JavaScript Unit Testing Framework +/*! + * QUnit 1.14.0 + * http://qunitjs.com/ * - * http://docs.jquery.com/QUnit + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license + * http://jquery.org/license * - * Copyright (c) 2011 John Resig, Jörn Zaefferer - * Dual licensed under the MIT (MIT-LICENSE.txt) - * or GPL (GPL-LICENSE.txt) licenses. - * Pulled Live from Git Wed Jun 1 17:25:01 UTC 2011 - * Last Commit: d4f23f8a882d13b71768503e2db9fa33ef169ba0 + * Date: 2014-01-31T16:40Z */ /** Font Family and Sizes */ @@ -22,7 +21,7 @@ /** Resets */ -#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { +#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { margin: 0; padding: 0; } @@ -33,27 +32,29 @@ #qunit-header { padding: 0.5em 0 0.5em 1em; - color: #8699a4; - background-color: #0d3349; + color: #8699A4; + background-color: #0D3349; font-size: 1.5em; line-height: 1em; - font-weight: normal; + font-weight: 400; - border-radius: 15px 15px 0 0; - -moz-border-radius: 15px 15px 0 0; - -webkit-border-top-right-radius: 15px; - -webkit-border-top-left-radius: 15px; + border-radius: 5px 5px 0 0; } #qunit-header a { text-decoration: none; - color: #c2ccd1; + color: #C2CCD1; } #qunit-header a:hover, #qunit-header a:focus { - color: #fff; + color: #FFF; +} + +#qunit-testrunner-toolbar label { + display: inline-block; + padding: 0 0.5em 0 0.1em; } #qunit-banner { @@ -63,16 +64,20 @@ #qunit-testrunner-toolbar { padding: 0.5em 0 0.5em 2em; color: #5E740B; - background-color: #eee; + background-color: #EEE; + overflow: hidden; } #qunit-userAgent { padding: 0.5em 0 0.5em 2.5em; - background-color: #2b81af; - color: #fff; + background-color: #2B81AF; + color: #FFF; text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; } +#qunit-modulefilter-container { + float: right; +} /** Tests: Pass/Fail */ @@ -82,7 +87,7 @@ #qunit-tests li { padding: 0.4em 0.5em 0.4em 2.5em; - border-bottom: 1px solid #fff; + border-bottom: 1px solid #FFF; list-style-position: inside; } @@ -96,7 +101,7 @@ #qunit-tests li a { padding: 0.5em; - color: #c2ccd1; + color: #C2CCD1; text-decoration: none; } #qunit-tests li a:hover, @@ -104,30 +109,33 @@ color: #000; } -#qunit-tests ol { +#qunit-tests li .runtime { + float: right; + font-size: smaller; +} + +.qunit-assert-list { margin-top: 0.5em; padding: 0.5em; - background-color: #fff; + background-color: #FFF; - border-radius: 15px; - -moz-border-radius: 15px; - -webkit-border-radius: 15px; + border-radius: 5px; +} - box-shadow: inset 0px 2px 13px #999; - -moz-box-shadow: inset 0px 2px 13px #999; - -webkit-box-shadow: inset 0px 2px 13px #999; +.qunit-collapsed { + display: none; } #qunit-tests table { border-collapse: collapse; - margin-top: .2em; + margin-top: 0.2em; } #qunit-tests th { text-align: right; vertical-align: top; - padding: 0 .5em 0 0; + padding: 0 0.5em 0 0; } #qunit-tests td { @@ -141,27 +149,26 @@ } #qunit-tests del { - background-color: #e0f2be; - color: #374e0c; + background-color: #E0F2BE; + color: #374E0C; text-decoration: none; } #qunit-tests ins { - background-color: #ffcaca; + background-color: #FFCACA; color: #500; text-decoration: none; } /*** Test Counts */ -#qunit-tests b.counts { color: black; } +#qunit-tests b.counts { color: #000; } #qunit-tests b.passed { color: #5E740B; } #qunit-tests b.failed { color: #710909; } #qunit-tests li li { - margin: 0.5em; - padding: 0.4em 0.5em 0.4em 0.5em; - background-color: #fff; + padding: 5px; + background-color: #FFF; border-bottom: none; list-style-position: inside; } @@ -169,16 +176,16 @@ /*** Passing Styles */ #qunit-tests li li.pass { - color: #5E740B; - background-color: #fff; - border-left: 26px solid #C6E746; + color: #3C510C; + background-color: #FFF; + border-left: 10px solid #C6E746; } #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } #qunit-tests .pass .test-name { color: #366097; } #qunit-tests .pass .test-actual, -#qunit-tests .pass .test-expected { color: #999999; } +#qunit-tests .pass .test-expected { color: #999; } #qunit-banner.qunit-pass { background-color: #C6E746; } @@ -186,23 +193,21 @@ #qunit-tests li li.fail { color: #710909; - background-color: #fff; - border-left: 26px solid #EE5757; + background-color: #FFF; + border-left: 10px solid #EE5757; + white-space: pre; } #qunit-tests > li:last-child { - border-radius: 0 0 15px 15px; - -moz-border-radius: 0 0 15px 15px; - -webkit-border-bottom-right-radius: 15px; - -webkit-border-bottom-left-radius: 15px; + border-radius: 0 0 5px 5px; } -#qunit-tests .fail { color: #000000; background-color: #EE5757; } +#qunit-tests .fail { color: #000; background-color: #EE5757; } #qunit-tests .fail .test-name, -#qunit-tests .fail .module-name { color: #000000; } +#qunit-tests .fail .module-name { color: #000; } #qunit-tests .fail .test-actual { color: #EE5757; } -#qunit-tests .fail .test-expected { color: green; } +#qunit-tests .fail .test-expected { color: #008000; } #qunit-banner.qunit-fail { background-color: #EE5757; } @@ -212,10 +217,13 @@ #qunit-testresult { padding: 0.5em 0.5em 0.5em 2.5em; - color: #2b81af; + color: #2B81AF; background-color: #D2E0E6; - border-bottom: 1px solid white; + border-bottom: 1px solid #FFF; +} +#qunit-testresult .module-name { + font-weight: 700; } /** Fixture */ @@ -224,4 +232,6 @@ position: absolute; top: -10000px; left: -10000px; + width: 1000px; + height: 1000px; } diff --git a/test/qunit/qunit.js b/test/qunit/qunit.js old mode 100755 new mode 100644 index 713ce5d..0e279fd --- a/test/qunit/qunit.js +++ b/test/qunit/qunit.js @@ -1,514 +1,358 @@ -/** - * QUnit - A JavaScript Unit Testing Framework +/*! + * QUnit 1.14.0 + * http://qunitjs.com/ * - * http://docs.jquery.com/QUnit + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license + * http://jquery.org/license * - * Copyright (c) 2011 John Resig, Jörn Zaefferer - * Dual licensed under the MIT (MIT-LICENSE.txt) - * or GPL (GPL-LICENSE.txt) licenses. - * Pulled Live from Git Wed Jun 1 17:25:01 UTC 2011 - * Last Commit: d4f23f8a882d13b71768503e2db9fa33ef169ba0 + * Date: 2014-01-31T16:40Z */ -(function(window) { - -var defined = { - setTimeout: typeof window.setTimeout !== "undefined", - sessionStorage: (function() { - try { - return !!sessionStorage.getItem; - } catch(e){ - return false; - } - })() -}; - -var testId = 0; - -var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { - this.name = name; - this.testName = testName; - this.expected = expected; - this.testEnvironmentArg = testEnvironmentArg; - this.async = async; - this.callback = callback; - this.assertions = []; -}; -Test.prototype = { - init: function() { - var tests = id("qunit-tests"); - if (tests) { - var b = document.createElement("strong"); - b.innerHTML = "Running " + this.name; - var li = document.createElement("li"); - li.appendChild( b ); - li.className = "running"; - li.id = this.id = "test-output" + testId++; - tests.appendChild( li ); - } - }, - setup: function() { - if (this.module != config.previousModule) { - if ( config.previousModule ) { - QUnit.moduleDone( { - name: config.previousModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - } ); - } - config.previousModule = this.module; - config.moduleStats = { all: 0, bad: 0 }; - QUnit.moduleStart( { - name: this.module - } ); - } - - config.current = this; - this.testEnvironment = extend({ - setup: function() {}, - teardown: function() {} - }, this.moduleTestEnvironment); - if (this.testEnvironmentArg) { - extend(this.testEnvironment, this.testEnvironmentArg); - } - - QUnit.testStart( { - name: this.testName - } ); - - // allow utility functions to access the current test environment - // TODO why?? - QUnit.current_testEnvironment = this.testEnvironment; - - try { - if ( !config.pollution ) { - saveGlobal(); - } - - this.testEnvironment.setup.call(this.testEnvironment); - } catch(e) { - QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); - } - }, - run: function() { - if ( this.async ) { - QUnit.stop(); - } - - if ( config.notrycatch ) { - this.callback.call(this.testEnvironment); - return; - } - try { - this.callback.call(this.testEnvironment); - } catch(e) { - fail("Test " + this.testName + " died, exception and test follows", e, this.callback); - QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); - // else next test will carry the responsibility - saveGlobal(); - - // Restart the tests if they're blocking - if ( config.blocking ) { - start(); +(function( window ) { + +var QUnit, + assert, + config, + onErrorFnPrev, + testId = 0, + fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + // Keep a local reference to Date (GH-283) + Date = window.Date, + setTimeout = window.setTimeout, + clearTimeout = window.clearTimeout, + defined = { + document: typeof window.document !== "undefined", + setTimeout: typeof window.setTimeout !== "undefined", + sessionStorage: (function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch( e ) { + return false; } - } - }, - teardown: function() { - try { - this.testEnvironment.teardown.call(this.testEnvironment); - checkPollution(); - } catch(e) { - QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); - } + }()) }, - finish: function() { - if ( this.expected && this.expected != this.assertions.length ) { - QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); - } - - var good = 0, bad = 0, - tests = id("qunit-tests"); - - config.stats.all += this.assertions.length; - config.moduleStats.all += this.assertions.length; - - if ( tests ) { - var ol = document.createElement("ol"); - - for ( var i = 0; i < this.assertions.length; i++ ) { - var assertion = this.assertions[i]; - - var li = document.createElement("li"); - li.className = assertion.result ? "pass" : "fail"; - li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); - ol.appendChild( li ); - - if ( assertion.result ) { - good++; - } else { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - - // store result when possible - if ( QUnit.config.reorder && defined.sessionStorage ) { - if (bad) { - sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); - } else { - sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); - } - } - - if (bad == 0) { - ol.style.display = "none"; + /** + * Provides a normalized error string, correcting an issue + * with IE 7 (and prior) where Error.prototype.toString is + * not properly implemented + * + * Based on http://es5.github.com/#x15.11.4.4 + * + * @param {String|Error} error + * @return {String} error message + */ + errorString = function( error ) { + var name, message, + errorString = error.toString(); + if ( errorString.substring( 0, 7 ) === "[object" ) { + name = error.name ? error.name.toString() : "Error"; + message = error.message ? error.message.toString() : ""; + if ( name && message ) { + return name + ": " + message; + } else if ( name ) { + return name; + } else if ( message ) { + return message; + } else { + return "Error"; } - - var b = document.createElement("strong"); - b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; - - var a = document.createElement("a"); - a.innerHTML = "Rerun"; - a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); - - addEvent(b, "click", function() { - var next = b.nextSibling.nextSibling, - display = next.style.display; - next.style.display = display === "none" ? "block" : "none"; - }); - - addEvent(b, "dblclick", function(e) { - var target = e && e.target ? e.target : window.event.srcElement; - if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { - target = target.parentNode; - } - if ( window.location && target.nodeName.toLowerCase() === "strong" ) { - window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); - } - }); - - var li = id(this.id); - li.className = bad ? "fail" : "pass"; - li.removeChild( li.firstChild ); - li.appendChild( b ); - li.appendChild( a ); - li.appendChild( ol ); - } else { - for ( var i = 0; i < this.assertions.length; i++ ) { - if ( !this.assertions[i].result ) { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - } - - try { - QUnit.reset(); - } catch(e) { - fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); + return errorString; } - - QUnit.testDone( { - name: this.testName, - failed: bad, - passed: this.assertions.length - bad, - total: this.assertions.length - } ); }, - - queue: function() { - var test = this; - synchronize(function() { - test.init(); - }); - function run() { - // each of these can by async - synchronize(function() { - test.setup(); - }); - synchronize(function() { - test.run(); - }); - synchronize(function() { - test.teardown(); - }); - synchronize(function() { - test.finish(); - }); + /** + * Makes a clone of an object using only Array or Object as base, + * and copies over the own enumerable properties. + * + * @param {Object} obj + * @return {Object} New object with only the own properties (recursively). + */ + objectValues = function( obj ) { + // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392. + /*jshint newcap: false */ + var key, val, + vals = QUnit.is( "array", obj ) ? [] : {}; + for ( key in obj ) { + if ( hasOwn.call( obj, key ) ) { + val = obj[key]; + vals[key] = val === Object(val) ? objectValues(val) : val; + } } - // defer when previous test run passed, if storage is available - var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); - if (bad) { - run(); - } else { - synchronize(run); - }; - } + return vals; + }; -}; -var QUnit = { +// Root QUnit object. +// `QUnit` initialized at top of scope +QUnit = { // call on start of module test to prepend name to all tests - module: function(name, testEnvironment) { + module: function( name, testEnvironment ) { config.currentModule = name; - config.currentModuleTestEnviroment = testEnvironment; + config.currentModuleTestEnvironment = testEnvironment; + config.modules[name] = true; }, - asyncTest: function(testName, expected, callback) { + asyncTest: function( testName, expected, callback ) { if ( arguments.length === 2 ) { callback = expected; - expected = 0; + expected = null; } - QUnit.test(testName, expected, callback, true); + QUnit.test( testName, expected, callback, true ); }, - test: function(testName, expected, callback, async) { - var name = '' + testName + '', testEnvironmentArg; + test: function( testName, expected, callback, async ) { + var test, + nameHtml = "" + escapeText( testName ) + ""; if ( arguments.length === 2 ) { callback = expected; expected = null; } - // is 2nd argument a testEnvironment? - if ( expected && typeof expected === 'object') { - testEnvironmentArg = expected; - expected = null; - } if ( config.currentModule ) { - name = '' + config.currentModule + ": " + name; + nameHtml = "" + escapeText( config.currentModule ) + ": " + nameHtml; } - if ( !validTest(config.currentModule + ": " + testName) ) { + test = new Test({ + nameHtml: nameHtml, + testName: testName, + expected: expected, + async: async, + callback: callback, + module: config.currentModule, + moduleTestEnvironment: config.currentModuleTestEnvironment, + stack: sourceFromStacktrace( 2 ) + }); + + if ( !validTest( test ) ) { return; } - var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); - test.module = config.currentModule; - test.moduleTestEnvironment = config.currentModuleTestEnviroment; test.queue(); }, - /** - * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. - */ - expect: function(asserts) { - config.current.expected = asserts; - }, - - /** - * Asserts true. - * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); - */ - ok: function(a, msg) { - a = !!a; - var details = { - result: a, - message: msg - }; - msg = escapeHtml(msg); - QUnit.log(details); - config.current.assertions.push({ - result: a, - message: msg - }); - }, - - /** - * Checks that the first two arguments are equal, with an optional message. - * Prints out both actual and expected values. - * - * Prefered to ok( actual == expected, message ) - * - * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); - * - * @param Object actual - * @param Object expected - * @param String message (optional) - */ - equal: function(actual, expected, message) { - QUnit.push(expected == actual, actual, expected, message); - }, - - notEqual: function(actual, expected, message) { - QUnit.push(expected != actual, actual, expected, message); - }, - - deepEqual: function(actual, expected, message) { - QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); - }, - - notDeepEqual: function(actual, expected, message) { - QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); - }, - - strictEqual: function(actual, expected, message) { - QUnit.push(expected === actual, actual, expected, message); - }, - - notStrictEqual: function(actual, expected, message) { - QUnit.push(expected !== actual, actual, expected, message); - }, - - raises: function(block, expected, message) { - var actual, ok = false; - - if (typeof expected === 'string') { - message = expected; - expected = null; - } - - try { - block(); - } catch (e) { - actual = e; + // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through. + expect: function( asserts ) { + if (arguments.length === 1) { + config.current.expected = asserts; + } else { + return config.current.expected; } + }, - if (actual) { - // we don't want to validate thrown error - if (!expected) { - ok = true; - // expected is a regexp - } else if (QUnit.objectType(expected) === "regexp") { - ok = expected.test(actual); - // expected is a constructor - } else if (actual instanceof expected) { - ok = true; - // expected is a validation function which returns true is validation passed - } else if (expected.call({}, actual) === true) { - ok = true; - } + start: function( count ) { + // QUnit hasn't been initialized yet. + // Note: RequireJS (et al) may delay onLoad + if ( config.semaphore === undefined ) { + QUnit.begin(function() { + // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first + setTimeout(function() { + QUnit.start( count ); + }); + }); + return; } - QUnit.ok(ok, message); - }, - - start: function() { - config.semaphore--; - if (config.semaphore > 0) { - // don't start until equal number of stop-calls + config.semaphore -= count || 1; + // don't start until equal number of stop-calls + if ( config.semaphore > 0 ) { return; } - if (config.semaphore < 0) { - // ignore if start is called more often then stop + // ignore if start is called more often then stop + if ( config.semaphore < 0 ) { config.semaphore = 0; + QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); + return; } // A slight delay, to avoid any current callbacks if ( defined.setTimeout ) { - window.setTimeout(function() { + setTimeout(function() { + if ( config.semaphore > 0 ) { + return; + } if ( config.timeout ) { - clearTimeout(config.timeout); + clearTimeout( config.timeout ); } config.blocking = false; - process(); + process( true ); }, 13); } else { config.blocking = false; - process(); + process( true ); } }, - stop: function(timeout) { - config.semaphore++; + stop: function( count ) { + config.semaphore += count || 1; config.blocking = true; - if ( timeout && defined.setTimeout ) { - clearTimeout(config.timeout); - config.timeout = window.setTimeout(function() { + if ( config.testTimeout && defined.setTimeout ) { + clearTimeout( config.timeout ); + config.timeout = setTimeout(function() { QUnit.ok( false, "Test timed out" ); + config.semaphore = 1; QUnit.start(); - }, timeout); + }, config.testTimeout ); } } }; -// Backwards compatibility, deprecated -QUnit.equals = QUnit.equal; -QUnit.same = QUnit.deepEqual; +// We use the prototype to distinguish between properties that should +// be exposed as globals (and in exports) and those that shouldn't +(function() { + function F() {} + F.prototype = QUnit; + QUnit = new F(); + // Make F QUnit's constructor so that we can add to the prototype later + QUnit.constructor = F; +}()); -// Maintain internal state -var config = { +/** + * Config object: Maintain internal state + * Later exposed as QUnit.config + * `config` initialized at top of scope + */ +config = { // The queue of tests to run queue: [], // block until document ready blocking: true, + // when enabled, show only failing tests + // gets persisted through sessionStorage and can be changed in UI via checkbox + hidepassed: false, + // by default, run previously failed tests first // very useful in combination with "Hide passed tests" checked reorder: true, - noglobals: false, - notrycatch: false + // by default, modify document.title when suite is done + altertitle: true, + + // by default, scroll to top of the page when suite is done + scrolltop: true, + + // when enabled, all tests must call expect() + requireExpects: false, + + // add checkboxes that are persisted in the query-string + // when enabled, the id is set to `true` as a `QUnit.config` property + urlConfig: [ + { + id: "noglobals", + label: "Check for Globals", + tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." + }, + { + id: "notrycatch", + label: "No try-catch", + tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." + } + ], + + // Set of all modules. + modules: {}, + + // logging callback queues + begin: [], + done: [], + log: [], + testStart: [], + testDone: [], + moduleStart: [], + moduleDone: [] }; -// Load paramaters +// Initialize more QUnit.config and QUnit.urlParams (function() { - var location = window.location || { search: "", protocol: "file:" }, + var i, current, + location = window.location || { search: "", protocol: "file:" }, params = location.search.slice( 1 ).split( "&" ), length = params.length, - urlParams = {}, - current; + urlParams = {}; if ( params[ 0 ] ) { - for ( var i = 0; i < length; i++ ) { + for ( i = 0; i < length; i++ ) { current = params[ i ].split( "=" ); current[ 0 ] = decodeURIComponent( current[ 0 ] ); + // allow just a key to turn on a flag, e.g., test.html?noglobals current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; - urlParams[ current[ 0 ] ] = current[ 1 ]; - if ( current[ 0 ] in config ) { - config[ current[ 0 ] ] = current[ 1 ]; + if ( urlParams[ current[ 0 ] ] ) { + urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] ); + } else { + urlParams[ current[ 0 ] ] = current[ 1 ]; } } } QUnit.urlParams = urlParams; + + // String search anywhere in moduleName+testName config.filter = urlParams.filter; + // Exact match of the module name + config.module = urlParams.module; + + config.testNumber = []; + if ( urlParams.testNumber ) { + + // Ensure that urlParams.testNumber is an array + urlParams.testNumber = [].concat( urlParams.testNumber ); + for ( i = 0; i < urlParams.testNumber.length; i++ ) { + current = urlParams.testNumber[ i ]; + config.testNumber.push( parseInt( current, 10 ) ); + } + } + // Figure out if we're running the tests from a server or not - QUnit.isLocal = !!(location.protocol === 'file:'); -})(); + QUnit.isLocal = location.protocol === "file:"; +}()); -// Expose the API as global variables, unless an 'exports' -// object exists, in that case we assume we're in CommonJS -if ( typeof exports === "undefined" || typeof require === "undefined" ) { - extend(window, QUnit); - window.QUnit = QUnit; -} else { - extend(exports, QUnit); - exports.QUnit = QUnit; -} +extend( QUnit, { -// define these after exposing globals to keep them in these QUnit namespace only -extend(QUnit, { config: config, // Initialize the configuration options init: function() { - extend(config, { + extend( config, { stats: { all: 0, bad: 0 }, moduleStats: { all: 0, bad: 0 }, - started: +new Date, + started: +new Date(), updateRate: 1000, blocking: false, autostart: true, autorun: false, filter: "", queue: [], - semaphore: 0 + semaphore: 1 }); - var tests = id( "qunit-tests" ), - banner = id( "qunit-banner" ), - result = id( "qunit-testresult" ); + var tests, banner, result, + qunit = id( "qunit" ); + + if ( qunit ) { + qunit.innerHTML = + "

" + escapeText( document.title ) + "

" + + "

" + + "
" + + "

" + + "
    "; + } + + tests = id( "qunit-tests" ); + banner = id( "qunit-banner" ); + result = id( "qunit-testresult" ); if ( tests ) { tests.innerHTML = ""; @@ -527,112 +371,101 @@ extend(QUnit, { result.id = "qunit-testresult"; result.className = "result"; tests.parentNode.insertBefore( result, tests ); - result.innerHTML = 'Running...
     '; + result.innerHTML = "Running...
     "; } }, - /** - * Resets the test setup. Useful for tests that modify the DOM. - * - * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. - */ + // Resets the test setup. Useful for tests that modify the DOM. + /* + DEPRECATED: Use multiple tests instead of resetting inside a test. + Use testStart or testDone for custom cleanup. + This method will throw an error in 2.0, and will be removed in 2.1 + */ reset: function() { - if ( window.jQuery ) { - jQuery( "#qunit-fixture" ).html( config.fixture ); - } else { - var main = id( 'qunit-fixture' ); - if ( main ) { - main.innerHTML = config.fixture; - } - } - }, - - /** - * Trigger an event on an element. - * - * @example triggerEvent( document.body, "click" ); - * - * @param DOMElement elem - * @param String type - */ - triggerEvent: function( elem, type, event ) { - if ( document.createEvent ) { - event = document.createEvent("MouseEvents"); - event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, - 0, 0, 0, 0, 0, false, false, false, false, 0, null); - elem.dispatchEvent( event ); - - } else if ( elem.fireEvent ) { - elem.fireEvent("on"+type); + var fixture = id( "qunit-fixture" ); + if ( fixture ) { + fixture.innerHTML = config.fixture; } }, // Safe object type checking is: function( type, obj ) { - return QUnit.objectType( obj ) == type; + return QUnit.objectType( obj ) === type; }, objectType: function( obj ) { - if (typeof obj === "undefined") { - return "undefined"; - - // consider: typeof null === object + if ( typeof obj === "undefined" ) { + return "undefined"; } - if (obj === null) { - return "null"; + + // Consider: typeof null === object + if ( obj === null ) { + return "null"; } - var type = Object.prototype.toString.call( obj ) - .match(/^\[object\s(.*)\]$/)[1] || ''; + var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), + type = match && match[1] || ""; - switch (type) { - case 'Number': - if (isNaN(obj)) { - return "nan"; - } else { - return "number"; - } - case 'String': - case 'Boolean': - case 'Array': - case 'Date': - case 'RegExp': - case 'Function': - return type.toLowerCase(); + switch ( type ) { + case "Number": + if ( isNaN(obj) ) { + return "nan"; + } + return "number"; + case "String": + case "Boolean": + case "Array": + case "Date": + case "RegExp": + case "Function": + return type.toLowerCase(); } - if (typeof obj === "object") { - return "object"; + if ( typeof obj === "object" ) { + return "object"; } return undefined; }, - push: function(result, actual, expected, message) { - var details = { - result: result, - message: message, - actual: actual, - expected: expected - }; + push: function( result, actual, expected, message ) { + if ( !config.current ) { + throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); + } + + var output, source, + details = { + module: config.current.module, + name: config.current.testName, + result: result, + message: message, + actual: actual, + expected: expected + }; + + message = escapeText( message ) || ( result ? "okay" : "failed" ); + message = "" + message + ""; + output = message; + + if ( !result ) { + expected = escapeText( QUnit.jsDump.parse(expected) ); + actual = escapeText( QUnit.jsDump.parse(actual) ); + output += ""; + + if ( actual !== expected ) { + output += ""; + output += ""; + } - message = escapeHtml(message) || (result ? "okay" : "failed"); - message = '' + message + ""; - expected = escapeHtml(QUnit.jsDump.parse(expected)); - actual = escapeHtml(QUnit.jsDump.parse(actual)); - var output = message + '
    Expected:
    " + expected + "
    Result:
    " + actual + "
    Diff:
    " + QUnit.diff( expected, actual ) + "
    '; - if (actual != expected) { - output += ''; - output += ''; - } - if (!result) { - var source = sourceFromStacktrace(); - if (source) { + source = sourceFromStacktrace(); + + if ( source ) { details.source = source; - output += ''; + output += ""; } + + output += "
    Expected:
    ' + expected + '
    Result:
    ' + actual + '
    Diff:
    ' + QUnit.diff(expected, actual) +'
    Source:
    ' + escapeHtml(source) + '
    Source:
    " + escapeText( source ) + "
    "; } - output += ""; - QUnit.log(details); + runLoggingCallbacks( "log", QUnit, details ); config.current.assertions.push({ result: !!result, @@ -640,239 +473,556 @@ extend(QUnit, { }); }, + pushFailure: function( message, source, actual ) { + if ( !config.current ) { + throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); + } + + var output, + details = { + module: config.current.module, + name: config.current.testName, + result: false, + message: message + }; + + message = escapeText( message ) || "error"; + message = "" + message + ""; + output = message; + + output += ""; + + if ( actual ) { + output += ""; + } + + if ( source ) { + details.source = source; + output += ""; + } + + output += "
    Result:
    " + escapeText( actual ) + "
    Source:
    " + escapeText( source ) + "
    "; + + runLoggingCallbacks( "log", QUnit, details ); + + config.current.assertions.push({ + result: false, + message: output + }); + }, + url: function( params ) { params = extend( extend( {}, QUnit.urlParams ), params ); - var querystring = "?", - key; + var key, + querystring = "?"; + for ( key in params ) { - querystring += encodeURIComponent( key ) + "=" + - encodeURIComponent( params[ key ] ) + "&"; + if ( hasOwn.call( params, key ) ) { + querystring += encodeURIComponent( key ) + "=" + + encodeURIComponent( params[ key ] ) + "&"; + } } - return window.location.pathname + querystring.slice( 0, -1 ); + return window.location.protocol + "//" + window.location.host + + window.location.pathname + querystring.slice( 0, -1 ); }, + extend: extend, + id: id, + addEvent: addEvent, + addClass: addClass, + hasClass: hasClass, + removeClass: removeClass + // load, equiv, jsDump, diff: Attached later +}); + +/** + * @deprecated: Created for backwards compatibility with test runner that set the hook function + * into QUnit.{hook}, instead of invoking it and passing the hook function. + * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. + * Doing this allows us to tell if the following methods have been overwritten on the actual + * QUnit object. + */ +extend( QUnit.constructor.prototype, { + // Logging callbacks; all receive a single argument with the listed properties // run test/logs.html for any related changes - begin: function() {}, + begin: registerLoggingCallback( "begin" ), + // done: { failed, passed, total, runtime } - done: function() {}, + done: registerLoggingCallback( "done" ), + // log: { result, actual, expected, message } - log: function() {}, + log: registerLoggingCallback( "log" ), + // testStart: { name } - testStart: function() {}, - // testDone: { name, failed, passed, total } - testDone: function() {}, + testStart: registerLoggingCallback( "testStart" ), + + // testDone: { name, failed, passed, total, runtime } + testDone: registerLoggingCallback( "testDone" ), + // moduleStart: { name } - moduleStart: function() {}, + moduleStart: registerLoggingCallback( "moduleStart" ), + // moduleDone: { name, failed, passed, total } - moduleDone: function() {} + moduleDone: registerLoggingCallback( "moduleDone" ) }); -if ( typeof document === "undefined" || document.readyState === "complete" ) { +if ( !defined.document || document.readyState === "complete" ) { config.autorun = true; } -addEvent(window, "load", function() { - QUnit.begin({}); +QUnit.load = function() { + runLoggingCallbacks( "begin", QUnit, {} ); // Initialize the config, saving the execution queue - var oldconfig = extend({}, config); + var banner, filter, i, j, label, len, main, ol, toolbar, val, selection, + urlConfigContainer, moduleFilter, userAgent, + numModules = 0, + moduleNames = [], + moduleFilterHtml = "", + urlConfigHtml = "", + oldconfig = extend( {}, config ); + QUnit.init(); extend(config, oldconfig); config.blocking = false; - var userAgent = id("qunit-userAgent"); + len = config.urlConfig.length; + + for ( i = 0; i < len; i++ ) { + val = config.urlConfig[i]; + if ( typeof val === "string" ) { + val = { + id: val, + label: val + }; + } + config[ val.id ] = QUnit.urlParams[ val.id ]; + if ( !val.value || typeof val.value === "string" ) { + urlConfigHtml += ""; + } else { + urlConfigHtml += ""; + } + } + for ( i in config.modules ) { + if ( config.modules.hasOwnProperty( i ) ) { + moduleNames.push(i); + } + } + numModules = moduleNames.length; + moduleNames.sort( function( a, b ) { + return a.localeCompare( b ); + }); + moduleFilterHtml += ""; + + // `userAgent` initialized at top of scope + userAgent = id( "qunit-userAgent" ); if ( userAgent ) { userAgent.innerHTML = navigator.userAgent; } - var banner = id("qunit-header"); + + // `banner` initialized at top of scope + banner = id( "qunit-header" ); if ( banner ) { - banner.innerHTML = ' ' + banner.innerHTML + ' ' + - '' + - ''; - addEvent( banner, "change", function( event ) { - var params = {}; - params[ event.target.name ] = event.target.checked ? true : undefined; - window.location = QUnit.url( params ); - }); + banner.innerHTML = "" + banner.innerHTML + " "; } - var toolbar = id("qunit-testrunner-toolbar"); + // `toolbar` initialized at top of scope + toolbar = id( "qunit-testrunner-toolbar" ); if ( toolbar ) { - var filter = document.createElement("input"); + // `filter` initialized at top of scope + filter = document.createElement( "input" ); filter.type = "checkbox"; filter.id = "qunit-filter-pass"; + addEvent( filter, "click", function() { - var ol = document.getElementById("qunit-tests"); + var tmp, + ol = id( "qunit-tests" ); + if ( filter.checked ) { ol.className = ol.className + " hidepass"; } else { - var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; - ol.className = tmp.replace(/ hidepass /, " "); + tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; + ol.className = tmp.replace( / hidepass /, " " ); } if ( defined.sessionStorage ) { if (filter.checked) { - sessionStorage.setItem("qunit-filter-passed-tests", "true"); + sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); } else { - sessionStorage.removeItem("qunit-filter-passed-tests"); + sessionStorage.removeItem( "qunit-filter-passed-tests" ); } } }); - if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { + + if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { filter.checked = true; - var ol = document.getElementById("qunit-tests"); + // `ol` initialized at top of scope + ol = id( "qunit-tests" ); ol.className = ol.className + " hidepass"; } toolbar.appendChild( filter ); - var label = document.createElement("label"); - label.setAttribute("for", "qunit-filter-pass"); + // `label` initialized at top of scope + label = document.createElement( "label" ); + label.setAttribute( "for", "qunit-filter-pass" ); + label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." ); label.innerHTML = "Hide passed tests"; toolbar.appendChild( label ); + + urlConfigContainer = document.createElement("span"); + urlConfigContainer.innerHTML = urlConfigHtml; + // For oldIE support: + // * Add handlers to the individual elements instead of the container + // * Use "click" instead of "change" for checkboxes + // * Fallback from event.target to event.srcElement + addEvents( urlConfigContainer.getElementsByTagName("input"), "click", function( event ) { + var params = {}, + target = event.target || event.srcElement; + params[ target.name ] = target.checked ? + target.defaultValue || true : + undefined; + window.location = QUnit.url( params ); + }); + addEvents( urlConfigContainer.getElementsByTagName("select"), "change", function( event ) { + var params = {}, + target = event.target || event.srcElement; + params[ target.name ] = target.options[ target.selectedIndex ].value || undefined; + window.location = QUnit.url( params ); + }); + toolbar.appendChild( urlConfigContainer ); + + if (numModules > 1) { + moduleFilter = document.createElement( "span" ); + moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); + moduleFilter.innerHTML = moduleFilterHtml; + addEvent( moduleFilter.lastChild, "change", function() { + var selectBox = moduleFilter.getElementsByTagName("select")[0], + selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); + + window.location = QUnit.url({ + module: ( selectedModule === "" ) ? undefined : selectedModule, + // Remove any existing filters + filter: undefined, + testNumber: undefined + }); + }); + toolbar.appendChild(moduleFilter); + } } - var main = id('qunit-fixture'); + // `main` initialized at top of scope + main = id( "qunit-fixture" ); if ( main ) { config.fixture = main.innerHTML; } - if (config.autostart) { + if ( config.autostart ) { QUnit.start(); } -}); +}; + +if ( defined.document ) { + addEvent( window, "load", QUnit.load ); +} + +// `onErrorFnPrev` initialized at top of scope +// Preserve other handlers +onErrorFnPrev = window.onerror; + +// Cover uncaught exceptions +// Returning true will suppress the default browser handler, +// returning false will let it run. +window.onerror = function ( error, filePath, linerNr ) { + var ret = false; + if ( onErrorFnPrev ) { + ret = onErrorFnPrev( error, filePath, linerNr ); + } + + // Treat return value as window.onerror itself does, + // Only do our handling if not suppressed. + if ( ret !== true ) { + if ( QUnit.config.current ) { + if ( QUnit.config.current.ignoreGlobalErrors ) { + return true; + } + QUnit.pushFailure( error, filePath + ":" + linerNr ); + } else { + QUnit.test( "global failure", extend( function() { + QUnit.pushFailure( error, filePath + ":" + linerNr ); + }, { validTest: validTest } ) ); + } + return false; + } + + return ret; +}; function done() { config.autorun = true; // Log the last module results - if ( config.currentModule ) { - QUnit.moduleDone( { - name: config.currentModule, + if ( config.previousModule ) { + runLoggingCallbacks( "moduleDone", QUnit, { + name: config.previousModule, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all - } ); + }); } + delete config.previousModule; - var banner = id("qunit-banner"), - tests = id("qunit-tests"), - runtime = +new Date - config.started, + var i, key, + banner = id( "qunit-banner" ), + tests = id( "qunit-tests" ), + runtime = +new Date() - config.started, passed = config.stats.all - config.stats.bad, html = [ - 'Tests completed in ', + "Tests completed in ", runtime, - ' milliseconds.
    ', - '', + " milliseconds.
    ", + "", passed, - ' tests of ', + " assertions of ", config.stats.all, - ' passed, ', + " passed, ", config.stats.bad, - ' failed.' - ].join(''); + "
    failed." + ].join( "" ); if ( banner ) { - banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); + banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); } if ( tests ) { id( "qunit-testresult" ).innerHTML = html; } - if ( typeof document !== "undefined" && document.title ) { + if ( config.altertitle && defined.document && document.title ) { // show ✖ for good, ✔ for bad suite result in title // use escape sequences in case file gets loaded with non-utf-8-charset - document.title = (config.stats.bad ? "\u2716" : "\u2714") + " " + document.title; + document.title = [ + ( config.stats.bad ? "\u2716" : "\u2714" ), + document.title.replace( /^[\u2714\u2716] /i, "" ) + ].join( " " ); + } + + // clear own sessionStorage items if all tests passed + if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { + // `key` & `i` initialized at top of scope + for ( i = 0; i < sessionStorage.length; i++ ) { + key = sessionStorage.key( i++ ); + if ( key.indexOf( "qunit-test-" ) === 0 ) { + sessionStorage.removeItem( key ); + } + } } - QUnit.done( { + // scroll back to top to show results + if ( config.scrolltop && window.scrollTo ) { + window.scrollTo(0, 0); + } + + runLoggingCallbacks( "done", QUnit, { failed: config.stats.bad, passed: passed, total: config.stats.all, runtime: runtime - } ); + }); } -function validTest( name ) { - var filter = config.filter, - run = false; +/** @return Boolean: true if this test should be ran */ +function validTest( test ) { + var include, + filter = config.filter && config.filter.toLowerCase(), + module = config.module && config.module.toLowerCase(), + fullName = ( test.module + ": " + test.testName ).toLowerCase(); - if ( !filter ) { + // Internally-generated tests are always valid + if ( test.callback && test.callback.validTest === validTest ) { + delete test.callback.validTest; return true; } - var not = filter.charAt( 0 ) === "!"; - if ( not ) { - filter = filter.slice( 1 ); + if ( config.testNumber.length > 0 ) { + if ( inArray( test.testNumber, config.testNumber ) < 0 ) { + return false; + } + } + + if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { + return false; + } + + if ( !filter ) { + return true; } - if ( name.indexOf( filter ) !== -1 ) { - return !not; + include = filter.charAt( 0 ) !== "!"; + if ( !include ) { + filter = filter.slice( 1 ); } - if ( not ) { - run = true; + // If the filter matches, we need to honour include + if ( fullName.indexOf( filter ) !== -1 ) { + return include; } - return run; + // Otherwise, do the opposite + return !include; } -// so far supports only Firefox, Chrome and Opera (buggy) -// could be extended in the future to use something like https://github.com/csnover/TraceKit -function sourceFromStacktrace() { +// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) +// Later Safari and IE10 are supposed to support error.stack as well +// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack +function extractStacktrace( e, offset ) { + offset = offset === undefined ? 3 : offset; + + var stack, include, i; + + if ( e.stacktrace ) { + // Opera + return e.stacktrace.split( "\n" )[ offset + 3 ]; + } else if ( e.stack ) { + // Firefox, Chrome + stack = e.stack.split( "\n" ); + if (/^error$/i.test( stack[0] ) ) { + stack.shift(); + } + if ( fileName ) { + include = []; + for ( i = offset; i < stack.length; i++ ) { + if ( stack[ i ].indexOf( fileName ) !== -1 ) { + break; + } + include.push( stack[ i ] ); + } + if ( include.length ) { + return include.join( "\n" ); + } + } + return stack[ offset ]; + } else if ( e.sourceURL ) { + // Safari, PhantomJS + // hopefully one day Safari provides actual stacktraces + // exclude useless self-reference for generated Error objects + if ( /qunit.js$/.test( e.sourceURL ) ) { + return; + } + // for actual exceptions, this is useful + return e.sourceURL + ":" + e.line; + } +} +function sourceFromStacktrace( offset ) { try { throw new Error(); } catch ( e ) { - if (e.stacktrace) { - // Opera - return e.stacktrace.split("\n")[6]; - } else if (e.stack) { - // Firefox, Chrome - return e.stack.split("\n")[4]; - } + return extractStacktrace( e, offset ); } } -function escapeHtml(s) { - if (!s) { +/** + * Escape text for attribute or text content. + */ +function escapeText( s ) { + if ( !s ) { return ""; } s = s + ""; - return s.replace(/[\&"<>\\]/g, function(s) { - switch(s) { - case "&": return "&"; - case "\\": return "\\\\"; - case '"': return '\"'; - case "<": return "<"; - case ">": return ">"; - default: return s; + // Both single quotes and double quotes (for attributes) + return s.replace( /['"<>&]/g, function( s ) { + switch( s ) { + case "'": + return "'"; + case "\"": + return """; + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; } }); } -function synchronize( callback ) { +function synchronize( callback, last ) { config.queue.push( callback ); if ( config.autorun && !config.blocking ) { - process(); + process( last ); } } -function process() { - var start = (new Date()).getTime(); +function process( last ) { + function next() { + process( last ); + } + var start = new Date().getTime(); + config.depth = config.depth ? config.depth + 1 : 1; while ( config.queue.length && !config.blocking ) { - if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { + if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { config.queue.shift()(); } else { - window.setTimeout( process, 13 ); + setTimeout( next, 13 ); break; } } - if (!config.blocking && !config.queue.length) { - done(); - } + config.depth--; + if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { + done(); + } } function saveGlobal() { @@ -880,33 +1030,44 @@ function saveGlobal() { if ( config.noglobals ) { for ( var key in window ) { - config.pollution.push( key ); + if ( hasOwn.call( window, key ) ) { + // in Opera sometimes DOM element ids show up here, ignore them + if ( /^qunit-test-output/.test( key ) ) { + continue; + } + config.pollution.push( key ); + } } } } -function checkPollution( name ) { - var old = config.pollution; +function checkPollution() { + var newGlobals, + deletedGlobals, + old = config.pollution; + saveGlobal(); - var newGlobals = diff( config.pollution, old ); + newGlobals = diff( config.pollution, old ); if ( newGlobals.length > 0 ) { - ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); + QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); } - var deletedGlobals = diff( old, config.pollution ); + deletedGlobals = diff( old, config.pollution ); if ( deletedGlobals.length > 0 ) { - ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); + QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); } } // returns a new Array with the elements that are in a but not in b function diff( a, b ) { - var result = a.slice(); - for ( var i = 0; i < result.length; i++ ) { - for ( var j = 0; j < b.length; j++ ) { + var i, j, + result = a.slice(); + + for ( i = 0; i < result.length; i++ ) { + for ( j = 0; j < b.length; j++ ) { if ( result[i] === b[j] ) { - result.splice(i, 1); + result.splice( i, 1 ); i--; break; } @@ -915,528 +1076,1190 @@ function diff( a, b ) { return result; } -function fail(message, exception, callback) { - if ( typeof console !== "undefined" && console.error && console.warn ) { - console.error(message); - console.error(exception); - console.warn(callback.toString()); - - } else if ( window.opera && opera.postError ) { - opera.postError(message, exception, callback.toString); - } -} - -function extend(a, b) { +function extend( a, b ) { for ( var prop in b ) { - if ( b[prop] === undefined ) { - delete a[prop]; - } else { - a[prop] = b[prop]; + if ( hasOwn.call( b, prop ) ) { + // Avoid "Member not found" error in IE8 caused by messing with window.constructor + if ( !( prop === "constructor" && a === window ) ) { + if ( b[ prop ] === undefined ) { + delete a[ prop ]; + } else { + a[ prop ] = b[ prop ]; + } + } } } return a; } -function addEvent(elem, type, fn) { +/** + * @param {HTMLElement} elem + * @param {string} type + * @param {Function} fn + */ +function addEvent( elem, type, fn ) { if ( elem.addEventListener ) { + + // Standards-based browsers elem.addEventListener( type, fn, false ); } else if ( elem.attachEvent ) { + + // support: IE <9 elem.attachEvent( "on" + type, fn ); } else { - fn(); - } -} -function id(name) { - return !!(typeof document !== "undefined" && document && document.getElementById) && - document.getElementById( name ); + // Caller must ensure support for event listeners is present + throw new Error( "addEvent() was called in a context without event listener support" ); + } } -// Test for equality any JavaScript type. -// Discussions and reference: http://philrathe.com/articles/equiv -// Test suites: http://philrathe.com/tests/equiv -// Author: Philippe Rathé -QUnit.equiv = function () { - - var innerEquiv; // the real equiv function - var callers = []; // stack to decide between skip/abort functions - var parents = []; // stack to avoiding loops from circular referencing - - // Call the o related callback with the given arguments. - function bindCallbacks(o, callbacks, args) { - var prop = QUnit.objectType(o); - if (prop) { - if (QUnit.objectType(callbacks[prop]) === "function") { - return callbacks[prop].apply(callbacks, args); - } else { - return callbacks[prop]; // or undefined - } - } - } - - var callbacks = function () { - - // for string, boolean, number and null - function useStrictEquality(b, a) { - if (b instanceof a.constructor || a instanceof b.constructor) { - // to catch short annotaion VS 'new' annotation of a declaration - // e.g. var i = 1; - // var j = new Number(1); - return a == b; - } else { - return a === b; - } - } - - return { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, - - "nan": function (b) { - return isNaN(b); - }, - - "date": function (b, a) { - return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); - }, - - "regexp": function (b, a) { - return QUnit.objectType(b) === "regexp" && - a.source === b.source && // the regex itself - a.global === b.global && // and its modifers (gmi) ... - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline; - }, - - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function": function () { - var caller = callers[callers.length - 1]; - return caller !== Object && - typeof caller !== "undefined"; - }, - - "array": function (b, a) { - var i, j, loop; - var len; - - // b could be an object literal here - if ( ! (QUnit.objectType(b) === "array")) { - return false; - } - - len = a.length; - if (len !== b.length) { // safe and faster - return false; - } - - //track reference to avoid circular references - parents.push(a); - for (i = 0; i < len; i++) { - loop = false; - for(j=0;j -1; +} - var jsDump = { - parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance - var parser = this.parsers[ type || this.typeOf(obj) ]; - type = typeof parser; +function addClass( elem, name ) { + if ( !hasClass( elem, name ) ) { + elem.className += (elem.className ? " " : "") + name; + } +} - return type == 'function' ? parser.call( this, obj ) : - type == 'string' ? parser : - this.parsers.error; - }, - typeOf:function( obj ) { - var type; - if ( obj === null ) { - type = "null"; - } else if (typeof obj === "undefined") { - type = "undefined"; - } else if (QUnit.is("RegExp", obj)) { - type = "regexp"; - } else if (QUnit.is("Date", obj)) { - type = "date"; - } else if (QUnit.is("Function", obj)) { - type = "function"; - } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { - type = "window"; - } else if (obj.nodeType === 9) { - type = "document"; - } else if (obj.nodeType) { - type = "node"; - } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) { - type = "array"; - } else { - type = typeof obj; - } - return type; - }, - separator:function() { - return this.multiline ? this.HTML ? '
    ' : '\n' : this.HTML ? ' ' : ' '; - }, - indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing - if ( !this.multiline ) - return ''; - var chr = this.indentChar; - if ( this.HTML ) - chr = chr.replace(/\t/g,' ').replace(/ /g,' '); - return Array( this._depth_ + (extra||0) ).join(chr); - }, - up:function( a ) { - this._depth_ += a || 1; - }, - down:function( a ) { - this._depth_ -= a || 1; - }, - setParser:function( name, parser ) { - this.parsers[name] = parser; - }, - // The next 3 are exposed so you can use them - quote:quote, - literal:literal, - join:join, - // - _depth_: 1, - // This is the list of parsers, to modify them, use jsDump.setParser - parsers:{ - window: '[Window]', - document: '[Document]', - error:'[ERROR]', //when no parser is found, shouldn't happen - unknown: '[Unknown]', - 'null':'null', - 'undefined':'undefined', - 'function':function( fn ) { - var ret = 'function', - name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE - if ( name ) - ret += ' ' + name; - ret += '('; - - ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); - return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); - }, - array: array, - nodelist: array, - arguments: array, - object:function( map ) { - var ret = [ ]; - QUnit.jsDump.up(); - for ( var key in map ) - ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) ); - QUnit.jsDump.down(); - return join( '{', ret, '}' ); - }, - node:function( node ) { - var open = QUnit.jsDump.HTML ? '<' : '<', - close = QUnit.jsDump.HTML ? '>' : '>'; +function removeClass( elem, name ) { + var set = " " + elem.className + " "; + // Class name may appear multiple times + while ( set.indexOf(" " + name + " ") > -1 ) { + set = set.replace(" " + name + " " , " "); + } + // If possible, trim it for prettiness, but not necessarily + elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, ""); +} - var tag = node.nodeName.toLowerCase(), - ret = open + tag; +function id( name ) { + return defined.document && document.getElementById && document.getElementById( name ); +} - for ( var a in QUnit.jsDump.DOMAttrs ) { - var val = node[QUnit.jsDump.DOMAttrs[a]]; - if ( val ) - ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); - } - return ret + close + open + '/' + tag + close; - }, - functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function - var l = fn.length; - if ( !l ) return ''; - - var args = Array(l); - while ( l-- ) - args[l] = String.fromCharCode(97+l);//97 is 'a' - return ' ' + args.join(', ') + ' '; - }, - key:quote, //object calls it internally, the key part of an item in a map - functionCode:'[code]', //function calls it internally, it's the content of the function - attribute:quote, //node calls it internally, it's an html attribute value - string:quote, - date:quote, - regexp:literal, //regex - number:literal, - 'boolean':literal - }, - DOMAttrs:{//attributes to dump from nodes, name=>realName - id:'id', - name:'name', - 'class':'className' - }, - HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) - indentChar:' ',//indentation unit - multiline:true //if true, items in a collection, are separated by a \n, else just a space. +function registerLoggingCallback( key ) { + return function( callback ) { + config[key].push( callback ); }; +} - return jsDump; -})(); - -// from Sizzle.js -function getText( elems ) { - var ret = "", elem; - - for ( var i = 0; elems[i]; i++ ) { - elem = elems[i]; - - // Get the text from text nodes and CDATA nodes - if ( elem.nodeType === 3 || elem.nodeType === 4 ) { - ret += elem.nodeValue; - - // Traverse everything else, except comment nodes - } else if ( elem.nodeType !== 8 ) { - ret += getText( elem.childNodes ); +// Supports deprecated method of completely overwriting logging callbacks +function runLoggingCallbacks( key, scope, args ) { + var i, callbacks; + if ( QUnit.hasOwnProperty( key ) ) { + QUnit[ key ].call(scope, args ); + } else { + callbacks = config[ key ]; + for ( i = 0; i < callbacks.length; i++ ) { + callbacks[ i ].call( scope, args ); } } +} - return ret; -}; +// from jquery.js +function inArray( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } -/* - * Javascript Diff Algorithm - * By John Resig (http://ejohn.org/) - * Modified by Chu Alan "sprite" - * - * Released under the MIT license. - * - * More Info: - * http://ejohn.org/projects/javascript-diff-algorithm/ - * - * Usage: QUnit.diff(expected, actual) - * - * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" - */ -QUnit.diff = (function() { - function diff(o, n){ - var ns = new Object(); - var os = new Object(); - - for (var i = 0; i < n.length; i++) { - if (ns[n[i]] == null) - ns[n[i]] = { - rows: new Array(), - o: null - }; - ns[n[i]].rows.push(i); + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; } + } - for (var i = 0; i < o.length; i++) { - if (os[o[i]] == null) - os[o[i]] = { - rows: new Array(), - n: null - }; - os[o[i]].rows.push(i); - } + return -1; +} - for (var i in ns) { - if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { - n[ns[i].rows[0]] = { - text: n[ns[i].rows[0]], - row: os[i].rows[0] - }; - o[os[i].rows[0]] = { - text: o[os[i].rows[0]], - row: ns[i].rows[0] - }; - } - } +function Test( settings ) { + extend( this, settings ); + this.assertions = []; + this.testNumber = ++Test.count; +} - for (var i = 0; i < n.length - 1; i++) { - if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && - n[i + 1] == o[n[i].row + 1]) { - n[i + 1] = { - text: n[i + 1], - row: n[i].row + 1 - }; - o[n[i].row + 1] = { - text: o[n[i].row + 1], - row: i + 1 - }; - } - } +Test.count = 0; - for (var i = n.length - 1; i > 0; i--) { - if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && - n[i - 1] == o[n[i].row - 1]) { - n[i - 1] = { - text: n[i - 1], - row: n[i].row - 1 - }; - o[n[i].row - 1] = { - text: o[n[i].row - 1], - row: i - 1 - }; - } - } +Test.prototype = { + init: function() { + var a, b, li, + tests = id( "qunit-tests" ); - return { - o: o, - n: n - }; - } + if ( tests ) { + b = document.createElement( "strong" ); + b.innerHTML = this.nameHtml; - return function(o, n){ - o = o.replace(/\s+$/, ''); - n = n.replace(/\s+$/, ''); - var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); + // `a` initialized at top of scope + a = document.createElement( "a" ); + a.innerHTML = "Rerun"; + a.href = QUnit.url({ testNumber: this.testNumber }); - var str = ""; + li = document.createElement( "li" ); + li.appendChild( b ); + li.appendChild( a ); + li.className = "running"; + li.id = this.id = "qunit-test-output" + testId++; - var oSpace = o.match(/\s+/g); - if (oSpace == null) { - oSpace = [" "]; - } - else { - oSpace.push(" "); - } - var nSpace = n.match(/\s+/g); - if (nSpace == null) { - nSpace = [" "]; - } - else { - nSpace.push(" "); + tests.appendChild( li ); } - - if (out.n.length == 0) { - for (var i = 0; i < out.o.length; i++) { - str += '' + out.o[i] + oSpace[i] + ""; + }, + setup: function() { + if ( + // Emit moduleStart when we're switching from one module to another + this.module !== config.previousModule || + // They could be equal (both undefined) but if the previousModule property doesn't + // yet exist it means this is the first test in a suite that isn't wrapped in a + // module, in which case we'll just emit a moduleStart event for 'undefined'. + // Without this, reporters can get testStart before moduleStart which is a problem. + !hasOwn.call( config, "previousModule" ) + ) { + if ( hasOwn.call( config, "previousModule" ) ) { + runLoggingCallbacks( "moduleDone", QUnit, { + name: config.previousModule, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all + }); } - } + config.previousModule = this.module; + config.moduleStats = { all: 0, bad: 0 }; + runLoggingCallbacks( "moduleStart", QUnit, { + name: this.module + }); + } + + config.current = this; + + this.testEnvironment = extend({ + setup: function() {}, + teardown: function() {} + }, this.moduleTestEnvironment ); + + this.started = +new Date(); + runLoggingCallbacks( "testStart", QUnit, { + name: this.testName, + module: this.module + }); + + /*jshint camelcase:false */ + + + /** + * Expose the current test environment. + * + * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead. + */ + QUnit.current_testEnvironment = this.testEnvironment; + + /*jshint camelcase:true */ + + if ( !config.pollution ) { + saveGlobal(); + } + if ( config.notrycatch ) { + this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); + return; + } + try { + this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); + } catch( e ) { + QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); + } + }, + run: function() { + config.current = this; + + var running = id( "qunit-testresult" ); + + if ( running ) { + running.innerHTML = "Running:
    " + this.nameHtml; + } + + if ( this.async ) { + QUnit.stop(); + } + + this.callbackStarted = +new Date(); + + if ( config.notrycatch ) { + this.callback.call( this.testEnvironment, QUnit.assert ); + this.callbackRuntime = +new Date() - this.callbackStarted; + return; + } + + try { + this.callback.call( this.testEnvironment, QUnit.assert ); + this.callbackRuntime = +new Date() - this.callbackStarted; + } catch( e ) { + this.callbackRuntime = +new Date() - this.callbackStarted; + + QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); + // else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if ( config.blocking ) { + QUnit.start(); + } + } + }, + teardown: function() { + config.current = this; + if ( config.notrycatch ) { + if ( typeof this.callbackRuntime === "undefined" ) { + this.callbackRuntime = +new Date() - this.callbackStarted; + } + this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); + return; + } else { + try { + this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); + } catch( e ) { + QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); + } + } + checkPollution(); + }, + finish: function() { + config.current = this; + if ( config.requireExpects && this.expected === null ) { + QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); + } else if ( this.expected !== null && this.expected !== this.assertions.length ) { + QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); + } else if ( this.expected === null && !this.assertions.length ) { + QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); + } + + var i, assertion, a, b, time, li, ol, + test = this, + good = 0, + bad = 0, + tests = id( "qunit-tests" ); + + this.runtime = +new Date() - this.started; + config.stats.all += this.assertions.length; + config.moduleStats.all += this.assertions.length; + + if ( tests ) { + ol = document.createElement( "ol" ); + ol.className = "qunit-assert-list"; + + for ( i = 0; i < this.assertions.length; i++ ) { + assertion = this.assertions[i]; + + li = document.createElement( "li" ); + li.className = assertion.result ? "pass" : "fail"; + li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); + ol.appendChild( li ); + + if ( assertion.result ) { + good++; + } else { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + + // store result when possible + if ( QUnit.config.reorder && defined.sessionStorage ) { + if ( bad ) { + sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); + } else { + sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); + } + } + + if ( bad === 0 ) { + addClass( ol, "qunit-collapsed" ); + } + + // `b` initialized at top of scope + b = document.createElement( "strong" ); + b.innerHTML = this.nameHtml + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; + + addEvent(b, "click", function() { + var next = b.parentNode.lastChild, + collapsed = hasClass( next, "qunit-collapsed" ); + ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); + }); + + addEvent(b, "dblclick", function( e ) { + var target = e && e.target ? e.target : window.event.srcElement; + if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { + target = target.parentNode; + } + if ( window.location && target.nodeName.toLowerCase() === "strong" ) { + window.location = QUnit.url({ testNumber: test.testNumber }); + } + }); + + // `time` initialized at top of scope + time = document.createElement( "span" ); + time.className = "runtime"; + time.innerHTML = this.runtime + " ms"; + + // `li` initialized at top of scope + li = id( this.id ); + li.className = bad ? "fail" : "pass"; + li.removeChild( li.firstChild ); + a = li.firstChild; + li.appendChild( b ); + li.appendChild( a ); + li.appendChild( time ); + li.appendChild( ol ); + + } else { + for ( i = 0; i < this.assertions.length; i++ ) { + if ( !this.assertions[i].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + } + + runLoggingCallbacks( "testDone", QUnit, { + name: this.testName, + module: this.module, + failed: bad, + passed: this.assertions.length - bad, + total: this.assertions.length, + runtime: this.runtime, + // DEPRECATED: this property will be removed in 2.0.0, use runtime instead + duration: this.runtime + }); + + QUnit.reset(); + + config.current = undefined; + }, + + queue: function() { + var bad, + test = this; + + synchronize(function() { + test.init(); + }); + function run() { + // each of these can by async + synchronize(function() { + test.setup(); + }); + synchronize(function() { + test.run(); + }); + synchronize(function() { + test.teardown(); + }); + synchronize(function() { + test.finish(); + }); + } + + // `bad` initialized at top of scope + // defer when previous test run passed, if storage is available + bad = QUnit.config.reorder && defined.sessionStorage && + +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); + + if ( bad ) { + run(); + } else { + synchronize( run, true ); + } + } +}; + +// `assert` initialized at top of scope +// Assert helpers +// All of these must either call QUnit.push() or manually do: +// - runLoggingCallbacks( "log", .. ); +// - config.current.assertions.push({ .. }); +assert = QUnit.assert = { + /** + * Asserts rough true-ish result. + * @name ok + * @function + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); + */ + ok: function( result, msg ) { + if ( !config.current ) { + throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); + } + result = !!result; + msg = msg || ( result ? "okay" : "failed" ); + + var source, + details = { + module: config.current.module, + name: config.current.testName, + result: result, + message: msg + }; + + msg = "" + escapeText( msg ) + ""; + + if ( !result ) { + source = sourceFromStacktrace( 2 ); + if ( source ) { + details.source = source; + msg += "
    Source:
    " +
    +					escapeText( source ) +
    +					"
    "; + } + } + runLoggingCallbacks( "log", QUnit, details ); + config.current.assertions.push({ + result: result, + message: msg + }); + }, + + /** + * Assert that the first two arguments are equal, with an optional message. + * Prints out both actual and expected values. + * @name equal + * @function + * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); + */ + equal: function( actual, expected, message ) { + /*jshint eqeqeq:false */ + QUnit.push( expected == actual, actual, expected, message ); + }, + + /** + * @name notEqual + * @function + */ + notEqual: function( actual, expected, message ) { + /*jshint eqeqeq:false */ + QUnit.push( expected != actual, actual, expected, message ); + }, + + /** + * @name propEqual + * @function + */ + propEqual: function( actual, expected, message ) { + actual = objectValues(actual); + expected = objectValues(expected); + QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); + }, + + /** + * @name notPropEqual + * @function + */ + notPropEqual: function( actual, expected, message ) { + actual = objectValues(actual); + expected = objectValues(expected); + QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); + }, + + /** + * @name deepEqual + * @function + */ + deepEqual: function( actual, expected, message ) { + QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); + }, + + /** + * @name notDeepEqual + * @function + */ + notDeepEqual: function( actual, expected, message ) { + QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); + }, + + /** + * @name strictEqual + * @function + */ + strictEqual: function( actual, expected, message ) { + QUnit.push( expected === actual, actual, expected, message ); + }, + + /** + * @name notStrictEqual + * @function + */ + notStrictEqual: function( actual, expected, message ) { + QUnit.push( expected !== actual, actual, expected, message ); + }, + + "throws": function( block, expected, message ) { + var actual, + expectedOutput = expected, + ok = false; + + // 'expected' is optional + if ( !message && typeof expected === "string" ) { + message = expected; + expected = null; + } + + config.current.ignoreGlobalErrors = true; + try { + block.call( config.current.testEnvironment ); + } catch (e) { + actual = e; + } + config.current.ignoreGlobalErrors = false; + + if ( actual ) { + + // we don't want to validate thrown error + if ( !expected ) { + ok = true; + expectedOutput = null; + + // expected is an Error object + } else if ( expected instanceof Error ) { + ok = actual instanceof Error && + actual.name === expected.name && + actual.message === expected.message; + + // expected is a regexp + } else if ( QUnit.objectType( expected ) === "regexp" ) { + ok = expected.test( errorString( actual ) ); + + // expected is a string + } else if ( QUnit.objectType( expected ) === "string" ) { + ok = expected === errorString( actual ); + + // expected is a constructor + } else if ( actual instanceof expected ) { + ok = true; + + // expected is a validation function which returns true is validation passed + } else if ( expected.call( {}, actual ) === true ) { + expectedOutput = null; + ok = true; + } + + QUnit.push( ok, actual, expectedOutput, message ); + } else { + QUnit.pushFailure( message, null, "No exception was thrown." ); + } + } +}; + +/** + * @deprecated since 1.8.0 + * Kept assertion helpers in root for backwards compatibility. + */ +extend( QUnit.constructor.prototype, assert ); + +/** + * @deprecated since 1.9.0 + * Kept to avoid TypeErrors for undefined methods. + */ +QUnit.constructor.prototype.raises = function() { + QUnit.push( false, false, false, "QUnit.raises has been deprecated since 2012 (fad3c1ea), use QUnit.throws instead" ); +}; + +/** + * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 + * Kept to avoid TypeErrors for undefined methods. + */ +QUnit.constructor.prototype.equals = function() { + QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); +}; +QUnit.constructor.prototype.same = function() { + QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); +}; + +// Test for equality any JavaScript type. +// Author: Philippe Rathé +QUnit.equiv = (function() { + + // Call the o related callback with the given arguments. + function bindCallbacks( o, callbacks, args ) { + var prop = QUnit.objectType( o ); + if ( prop ) { + if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { + return callbacks[ prop ].apply( callbacks, args ); + } else { + return callbacks[ prop ]; // or undefined + } + } + } + + // the real equiv function + var innerEquiv, + // stack to decide between skip/abort functions + callers = [], + // stack to avoiding loops from circular referencing + parents = [], + parentsB = [], + + getProto = Object.getPrototypeOf || function ( obj ) { + /*jshint camelcase:false */ + return obj.__proto__; + }, + callbacks = (function () { + + // for string, boolean, number and null + function useStrictEquality( b, a ) { + /*jshint eqeqeq:false */ + if ( b instanceof a.constructor || a instanceof b.constructor ) { + // to catch short annotation VS 'new' annotation of a + // declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; + } + } + + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function( b ) { + return isNaN( b ); + }, + + "date": function( b, a ) { + return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function( b, a ) { + return QUnit.objectType( b ) === "regexp" && + // the regex itself + a.source === b.source && + // and its modifiers + a.global === b.global && + // (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline && + a.sticky === b.sticky; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function() { + var caller = callers[callers.length - 1]; + return caller !== Object && typeof caller !== "undefined"; + }, + + "array": function( b, a ) { + var i, j, len, loop, aCircular, bCircular; + + // b could be an object literal here + if ( QUnit.objectType( b ) !== "array" ) { + return false; + } + + len = a.length; + if ( len !== b.length ) { + // safe and faster + return false; + } + + // track reference to avoid circular references + parents.push( a ); + parentsB.push( b ); + for ( i = 0; i < len; i++ ) { + loop = false; + for ( j = 0; j < parents.length; j++ ) { + aCircular = parents[j] === a[i]; + bCircular = parentsB[j] === b[i]; + if ( aCircular || bCircular ) { + if ( a[i] === b[i] || aCircular && bCircular ) { + loop = true; + } else { + parents.pop(); + parentsB.pop(); + return false; + } + } + } + if ( !loop && !innerEquiv(a[i], b[i]) ) { + parents.pop(); + parentsB.pop(); + return false; + } + } + parents.pop(); + parentsB.pop(); + return true; + }, + + "object": function( b, a ) { + /*jshint forin:false */ + var i, j, loop, aCircular, bCircular, + // Default to true + eq = true, + aProperties = [], + bProperties = []; + + // comparing constructors is more strict than using + // instanceof + if ( a.constructor !== b.constructor ) { + // Allow objects with no prototype to be equivalent to + // objects with Object as their constructor. + if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || + ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { + return false; + } + } + + // stack constructor before traversing properties + callers.push( a.constructor ); + + // track reference to avoid circular references + parents.push( a ); + parentsB.push( b ); + + // be strict: don't ensure hasOwnProperty and go deep + for ( i in a ) { + loop = false; + for ( j = 0; j < parents.length; j++ ) { + aCircular = parents[j] === a[i]; + bCircular = parentsB[j] === b[i]; + if ( aCircular || bCircular ) { + if ( a[i] === b[i] || aCircular && bCircular ) { + loop = true; + } else { + eq = false; + break; + } + } + } + aProperties.push(i); + if ( !loop && !innerEquiv(a[i], b[i]) ) { + eq = false; + break; + } + } + + parents.pop(); + parentsB.pop(); + callers.pop(); // unstack, we are done + + for ( i in b ) { + bProperties.push( i ); // collect b's properties + } + + // Ensures identical properties name + return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); + } + }; + }()); + + innerEquiv = function() { // can take multiple arguments + var args = [].slice.apply( arguments ); + if ( args.length < 2 ) { + return true; // end transition + } + + return (function( a, b ) { + if ( a === b ) { + return true; // catch the most you can + } else if ( a === null || b === null || typeof a === "undefined" || + typeof b === "undefined" || + QUnit.objectType(a) !== QUnit.objectType(b) ) { + return false; // don't lose time with error prone cases + } else { + return bindCallbacks(a, callbacks, [ b, a ]); + } + + // apply transition with (1..n) arguments + }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) ); + }; + + return innerEquiv; +}()); + +/** + * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | + * http://flesler.blogspot.com Licensed under BSD + * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 + * + * @projectDescription Advanced and extensible data dumping for Javascript. + * @version 1.0.0 + * @author Ariel Flesler + * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} + */ +QUnit.jsDump = (function() { + function quote( str ) { + return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; + } + function literal( o ) { + return o + ""; + } + function join( pre, arr, post ) { + var s = jsDump.separator(), + base = jsDump.indent(), + inner = jsDump.indent(1); + if ( arr.join ) { + arr = arr.join( "," + s + inner ); + } + if ( !arr ) { + return pre + post; + } + return [ pre, inner + arr, base + post ].join(s); + } + function array( arr, stack ) { + var i = arr.length, ret = new Array(i); + this.up(); + while ( i-- ) { + ret[i] = this.parse( arr[i] , undefined , stack); + } + this.down(); + return join( "[", ret, "]" ); + } + + var reName = /^function (\w+)/, + jsDump = { + // type is used mostly internally, you can fix a (custom)type in advance + parse: function( obj, type, stack ) { + stack = stack || [ ]; + var inStack, res, + parser = this.parsers[ type || this.typeOf(obj) ]; + + type = typeof parser; + inStack = inArray( obj, stack ); + + if ( inStack !== -1 ) { + return "recursion(" + (inStack - stack.length) + ")"; + } + if ( type === "function" ) { + stack.push( obj ); + res = parser.call( this, obj, stack ); + stack.pop(); + return res; + } + return ( type === "string" ) ? parser : this.parsers.error; + }, + typeOf: function( obj ) { + var type; + if ( obj === null ) { + type = "null"; + } else if ( typeof obj === "undefined" ) { + type = "undefined"; + } else if ( QUnit.is( "regexp", obj) ) { + type = "regexp"; + } else if ( QUnit.is( "date", obj) ) { + type = "date"; + } else if ( QUnit.is( "function", obj) ) { + type = "function"; + } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { + type = "window"; + } else if ( obj.nodeType === 9 ) { + type = "document"; + } else if ( obj.nodeType ) { + type = "node"; + } else if ( + // native arrays + toString.call( obj ) === "[object Array]" || + // NodeList objects + ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) + ) { + type = "array"; + } else if ( obj.constructor === Error.prototype.constructor ) { + type = "error"; + } else { + type = typeof obj; + } + return type; + }, + separator: function() { + return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; + }, + // extra can be a number, shortcut for increasing-calling-decreasing + indent: function( extra ) { + if ( !this.multiline ) { + return ""; + } + var chr = this.indentChar; + if ( this.HTML ) { + chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); + } + return new Array( this.depth + ( extra || 0 ) ).join(chr); + }, + up: function( a ) { + this.depth += a || 1; + }, + down: function( a ) { + this.depth -= a || 1; + }, + setParser: function( name, parser ) { + this.parsers[name] = parser; + }, + // The next 3 are exposed so you can use them + quote: quote, + literal: literal, + join: join, + // + depth: 1, + // This is the list of parsers, to modify them, use jsDump.setParser + parsers: { + window: "[Window]", + document: "[Document]", + error: function(error) { + return "Error(\"" + error.message + "\")"; + }, + unknown: "[Unknown]", + "null": "null", + "undefined": "undefined", + "function": function( fn ) { + var ret = "function", + // functions never have name in IE + name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; + + if ( name ) { + ret += " " + name; + } + ret += "( "; + + ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); + return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); + }, + array: array, + nodelist: array, + "arguments": array, + object: function( map, stack ) { + /*jshint forin:false */ + var ret = [ ], keys, key, val, i; + QUnit.jsDump.up(); + keys = []; + for ( key in map ) { + keys.push( key ); + } + keys.sort(); + for ( i = 0; i < keys.length; i++ ) { + key = keys[ i ]; + val = map[ key ]; + ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); + } + QUnit.jsDump.down(); + return join( "{", ret, "}" ); + }, + node: function( node ) { + var len, i, val, + open = QUnit.jsDump.HTML ? "<" : "<", + close = QUnit.jsDump.HTML ? ">" : ">", + tag = node.nodeName.toLowerCase(), + ret = open + tag, + attrs = node.attributes; + + if ( attrs ) { + for ( i = 0, len = attrs.length; i < len; i++ ) { + val = attrs[i].nodeValue; + // IE6 includes all attributes in .attributes, even ones not explicitly set. + // Those have values like undefined, null, 0, false, "" or "inherit". + if ( val && val !== "inherit" ) { + ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); + } + } + } + ret += close; + + // Show content of TextNode or CDATASection + if ( node.nodeType === 3 || node.nodeType === 4 ) { + ret += node.nodeValue; + } + + return ret + open + "/" + tag + close; + }, + // function calls it internally, it's the arguments part of the function + functionArgs: function( fn ) { + var args, + l = fn.length; + + if ( !l ) { + return ""; + } + + args = new Array(l); + while ( l-- ) { + // 97 is 'a' + args[l] = String.fromCharCode(97+l); + } + return " " + args.join( ", " ) + " "; + }, + // object calls it internally, the key part of an item in a map + key: quote, + // function calls it internally, it's the content of the function + functionCode: "[code]", + // node calls it internally, it's an html attribute value + attribute: quote, + string: quote, + date: quote, + regexp: literal, + number: literal, + "boolean": literal + }, + // if true, entities are escaped ( <, >, \t, space and \n ) + HTML: false, + // indentation unit + indentChar: " ", + // if true, items in a collection, are separated by a \n, else just a space. + multiline: true + }; + + return jsDump; +}()); + +/* + * Javascript Diff Algorithm + * By John Resig (http://ejohn.org/) + * Modified by Chu Alan "sprite" + * + * Released under the MIT license. + * + * More Info: + * http://ejohn.org/projects/javascript-diff-algorithm/ + * + * Usage: QUnit.diff(expected, actual) + * + * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" + */ +QUnit.diff = (function() { + /*jshint eqeqeq:false, eqnull:true */ + function diff( o, n ) { + var i, + ns = {}, + os = {}; + + for ( i = 0; i < n.length; i++ ) { + if ( !hasOwn.call( ns, n[i] ) ) { + ns[ n[i] ] = { + rows: [], + o: null + }; + } + ns[ n[i] ].rows.push( i ); + } + + for ( i = 0; i < o.length; i++ ) { + if ( !hasOwn.call( os, o[i] ) ) { + os[ o[i] ] = { + rows: [], + n: null + }; + } + os[ o[i] ].rows.push( i ); + } + + for ( i in ns ) { + if ( hasOwn.call( ns, i ) ) { + if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { + n[ ns[i].rows[0] ] = { + text: n[ ns[i].rows[0] ], + row: os[i].rows[0] + }; + o[ os[i].rows[0] ] = { + text: o[ os[i].rows[0] ], + row: ns[i].rows[0] + }; + } + } + } + + for ( i = 0; i < n.length - 1; i++ ) { + if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && + n[ i + 1 ] == o[ n[i].row + 1 ] ) { + + n[ i + 1 ] = { + text: n[ i + 1 ], + row: n[i].row + 1 + }; + o[ n[i].row + 1 ] = { + text: o[ n[i].row + 1 ], + row: i + 1 + }; + } + } + + for ( i = n.length - 1; i > 0; i-- ) { + if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && + n[ i - 1 ] == o[ n[i].row - 1 ]) { + + n[ i - 1 ] = { + text: n[ i - 1 ], + row: n[i].row - 1 + }; + o[ n[i].row - 1 ] = { + text: o[ n[i].row - 1 ], + row: i - 1 + }; + } + } + + return { + o: o, + n: n + }; + } + + return function( o, n ) { + o = o.replace( /\s+$/, "" ); + n = n.replace( /\s+$/, "" ); + + var i, pre, + str = "", + out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), + oSpace = o.match(/\s+/g), + nSpace = n.match(/\s+/g); + + if ( oSpace == null ) { + oSpace = [ " " ]; + } else { - if (out.n[0].text == null) { - for (n = 0; n < out.o.length && out.o[n].text == null; n++) { - str += '' + out.o[n] + oSpace[n] + ""; + oSpace.push( " " ); + } + + if ( nSpace == null ) { + nSpace = [ " " ]; + } + else { + nSpace.push( " " ); + } + + if ( out.n.length === 0 ) { + for ( i = 0; i < out.o.length; i++ ) { + str += "" + out.o[i] + oSpace[i] + ""; + } + } + else { + if ( out.n[0].text == null ) { + for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { + str += "" + out.o[n] + oSpace[n] + ""; } } - for (var i = 0; i < out.n.length; i++) { + for ( i = 0; i < out.n.length; i++ ) { if (out.n[i].text == null) { - str += '' + out.n[i] + nSpace[i] + ""; + str += "" + out.n[i] + nSpace[i] + ""; } else { - var pre = ""; + // `pre` initialized at top of scope + pre = ""; - for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { - pre += '' + out.o[n] + oSpace[n] + ""; + for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { + pre += "" + out.o[n] + oSpace[n] + ""; } str += " " + out.n[i].text + nSpace[i] + pre; } @@ -1445,6 +2268,21 @@ QUnit.diff = (function() { return str; }; -})(); +}()); + +// For browser, export only select globals +if ( typeof window !== "undefined" ) { + extend( window, QUnit.constructor.prototype ); + window.QUnit = QUnit; +} + +// For CommonJS environments, export everything +if ( typeof module !== "undefined" && module.exports ) { + module.exports = QUnit; +} + -})(this); +// Get a reference to the global object, like window in browsers +}( (function() { + return this; +})() )); diff --git a/test/requirejs/index.html b/test/requirejs/index.html index da87af8..0ff0aa8 100755 --- a/test/requirejs/index.html +++ b/test/requirejs/index.html @@ -21,12 +21,12 @@ { name: 'clear', from: 'yellow', to: 'green' } ]}); - equals(fsm.current, 'green', "initial state should be green"); + equal(fsm.current, 'green', "initial state should be green"); - fsm.warn(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equals(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equals(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equals(fsm.current, 'green', "clear event should transition from yellow to green"); + fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); + fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); + fsm.calm(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); + fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); }); diff --git a/test/test_advanced.js b/test/test_advanced.js index 3cc7615..d4e2de8 100644 --- a/test/test_advanced.js +++ b/test/test_advanced.js @@ -15,20 +15,20 @@ test("multiple 'from' states for the same event", function() { { name: 'clear', from: ['yellow', 'red'], to: 'green' }, ]}); - equals(fsm.current, 'green', "initial state should be green"); + equal(fsm.current, 'green', "initial state should be green"); ok(fsm.can('warn'), "should be able to warn from green state") ok(fsm.can('panic'), "should be able to panic from green state") ok(fsm.cannot('calm'), "should NOT be able to calm from green state") ok(fsm.cannot('clear'), "should NOT be able to clear from green state") - fsm.warn(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equals(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equals(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equals(fsm.current, 'green', "clear event should transition from yellow to green"); + fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); + fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); + fsm.calm(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); + fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); - fsm.panic(); equals(fsm.current, 'red', "panic event should transition from green to red"); - fsm.clear(); equals(fsm.current, 'green', "clear event should transition from red to green"); + fsm.panic(); equal(fsm.current, 'red', "panic event should transition from green to red"); + fsm.clear(); equal(fsm.current, 'green', "clear event should transition from red to green"); }); @@ -45,22 +45,22 @@ test("multiple 'to' states for the same event", function() { { name: 'rest', from: ['hungry', 'satisfied', 'full', 'sick'], to: 'hungry' }, ]}); - equals(fsm.current, 'hungry'); + equal(fsm.current, 'hungry'); ok(fsm.can('eat')); ok(fsm.can('rest')); fsm.eat(); - equals(fsm.current, 'satisfied'); + equal(fsm.current, 'satisfied'); fsm.eat(); - equals(fsm.current, 'full'); + equal(fsm.current, 'full'); fsm.eat(); - equals(fsm.current, 'sick'); + equal(fsm.current, 'sick'); fsm.rest(); - equals(fsm.current, 'hungry'); + equal(fsm.current, 'hungry'); }); @@ -78,7 +78,7 @@ test("no-op transitions (github issue #5) with multiple from states", function() { name: 'clear', from: ['yellow', 'red'], to: 'green' }, ]}); - equals(fsm.current, 'green', "initial state should be green"); + equal(fsm.current, 'green', "initial state should be green"); ok(fsm.can('warn'), "should be able to warn from green state") ok(fsm.can('panic'), "should be able to panic from green state") @@ -86,8 +86,8 @@ test("no-op transitions (github issue #5) with multiple from states", function() ok(fsm.cannot('calm'), "should NOT be able to calm from green state") ok(fsm.cannot('clear'), "should NOT be able to clear from green state") - fsm.noop(); equals(fsm.current, 'green', "noop event should not transition"); - fsm.warn(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow"); + fsm.noop(); equal(fsm.current, 'green', "noop event should not transition"); + fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); ok(fsm.cannot('warn'), "should NOT be able to warn from yellow state") ok(fsm.can('panic'), "should be able to panic from yellow state") @@ -95,8 +95,8 @@ test("no-op transitions (github issue #5) with multiple from states", function() ok(fsm.cannot('calm'), "should NOT be able to calm from yellow state") ok(fsm.can('clear'), "should be able to clear from yellow state") - fsm.noop(); equals(fsm.current, 'yellow', "noop event should not transition"); - fsm.panic(); equals(fsm.current, 'red', "panic event should transition from yellow to red"); + fsm.noop(); equal(fsm.current, 'yellow', "noop event should not transition"); + fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); ok(fsm.cannot('warn'), "should NOT be able to warn from red state") ok(fsm.cannot('panic'), "should NOT be able to panic from red state") diff --git a/test/test_async.js b/test/test_async.js index db98bd1..0b4999c 100644 --- a/test/test_async.js +++ b/test/test_async.js @@ -21,15 +21,15 @@ test("state transitions", function() { } }); - equals(fsm.current, 'green', "initial state should be green"); - fsm.warn(); equals(fsm.current, 'green', "should still be green because we haven't transitioned yet"); - fsm.transition(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); - fsm.transition(); equals(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equals(fsm.current, 'red', "should still be red because we haven't transitioned yet"); - fsm.transition(); equals(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); - fsm.transition(); equals(fsm.current, 'green', "clear event should transition from yellow to green"); + equal(fsm.current, 'green', "initial state should be green"); + fsm.warn(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet"); + fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); + fsm.panic(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); + fsm.transition(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); + fsm.calm(); equal(fsm.current, 'red', "should still be red because we haven't transitioned yet"); + fsm.transition(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); + fsm.clear(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); + fsm.transition(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); }); @@ -54,19 +54,19 @@ test("state transitions with delays", function() { } }); - equals(fsm.current, 'green', "initial state should be green"); - fsm.warn(); equals(fsm.current, 'green', "should still be green because we haven't transitioned yet"); + equal(fsm.current, 'green', "initial state should be green"); + fsm.warn(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet"); setTimeout(function() { - fsm.transition(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); + fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); + fsm.panic(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); setTimeout(function() { - fsm.transition(); equals(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equals(fsm.current, 'red', "should still be red because we haven't transitioned yet"); + fsm.transition(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); + fsm.calm(); equal(fsm.current, 'red', "should still be red because we haven't transitioned yet"); setTimeout(function() { - fsm.transition(); equals(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); + fsm.transition(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); + fsm.clear(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); setTimeout(function() { - fsm.transition(); equals(fsm.current, 'green', "clear event should transition from yellow to green"); + fsm.transition(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); start(); }, 10); }, 10); @@ -94,12 +94,12 @@ test("state transition fired during onleavestate callback - immediate", function } }); - equals(fsm.current, 'green', "initial state should be green"); + equal(fsm.current, 'green', "initial state should be green"); - fsm.warn(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equals(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equals(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equals(fsm.current, 'green', "clear event should transition from yellow to green"); + fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); + fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); + fsm.calm(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); + fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); }); @@ -117,14 +117,14 @@ test("state transition fired during onleavestate callback - with delay", functio callbacks: { onleavegreen: function() { setTimeout(function() { fsm.transition(); }, 10); return StateMachine.ASYNC; }, onenterred: function() { - equals(fsm.current, 'red', "panic event should transition from green to red"); + equal(fsm.current, 'red', "panic event should transition from green to red"); start(); } } }); - equals(fsm.current, 'green', "initial state should be green"); - fsm.panic(); equals(fsm.current, 'green', "should still be green because we haven't transitioned yet"); + equal(fsm.current, 'green', "initial state should be green"); + fsm.panic(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet"); }); @@ -147,12 +147,12 @@ test("state transition fired during onleavestate callback - but forgot to return } }); - equals(fsm.current, 'green', "initial state should be green"); + equal(fsm.current, 'green', "initial state should be green"); - fsm.warn(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equals(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equals(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equals(fsm.current, 'green', "clear event should transition from yellow to green"); + fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); + fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); + fsm.calm(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); + fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); }); @@ -172,11 +172,11 @@ test("state transitions sometimes synchronous and sometimes asynchronous", funct // default behavior is synchronous - equals(fsm.current, 'green', "initial state should be green"); - fsm.warn(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equals(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equals(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equals(fsm.current, 'green', "clear event should transition from yellow to green"); + equal(fsm.current, 'green', "initial state should be green"); + fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); + fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); + fsm.calm(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); + fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); // but add callbacks that return ASYNC and it magically becomes asynchronous @@ -184,22 +184,22 @@ test("state transitions sometimes synchronous and sometimes asynchronous", funct fsm.onleaveyellow = function() { return StateMachine.ASYNC; } fsm.onleavered = function() { return StateMachine.ASYNC; } - equals(fsm.current, 'green', "initial state should be green"); - fsm.warn(); equals(fsm.current, 'green', "should still be green because we haven't transitioned yet"); - fsm.transition(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); - fsm.transition(); equals(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equals(fsm.current, 'red', "should still be red because we haven't transitioned yet"); - fsm.transition(); equals(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); - fsm.transition(); equals(fsm.current, 'green', "clear event should transition from yellow to green"); + equal(fsm.current, 'green', "initial state should be green"); + fsm.warn(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet"); + fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); + fsm.panic(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); + fsm.transition(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); + fsm.calm(); equal(fsm.current, 'red', "should still be red because we haven't transitioned yet"); + fsm.transition(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); + fsm.clear(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); + fsm.transition(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); // this allows you to make on-the-fly decisions about whether async or not ... fsm.onleavegreen = function(event, from, to, async) { if (async) { setTimeout(function() { - fsm.transition(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow"); + fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); start(); // move on to next test }, 10); return StateMachine.ASYNC; @@ -207,9 +207,9 @@ test("state transitions sometimes synchronous and sometimes asynchronous", funct } fsm.onleaveyellow = fsm.onleavered = null; - fsm.warn(false); equals(fsm.current, 'yellow', "expected synchronous transition from green to yellow"); - fsm.clear(); equals(fsm.current, 'green', "clear event should transition from yellow to green"); - fsm.warn(true); equals(fsm.current, 'green', "should still be green because we haven't transitioned yet"); + fsm.warn(false); equal(fsm.current, 'yellow', "expected synchronous transition from green to yellow"); + fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); + fsm.warn(true); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet"); stop(); // doing async stuff - dont run next qunit test until I call start() in callback above @@ -235,12 +235,12 @@ test("state transition fired without completing previous transition", function() } }); - equals(fsm.current, 'green', "initial state should be green"); - fsm.warn(); equals(fsm.current, 'green', "should still be green because we haven't transitioned yet"); - fsm.transition(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); + equal(fsm.current, 'green', "initial state should be green"); + fsm.warn(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet"); + fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); + fsm.panic(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); - raises(fsm.calm.bind(fsm), /event calm inappropriate because previous transition did not complete/); + throws(fsm.calm.bind(fsm), /event calm inappropriate because previous transition did not complete/); }); @@ -263,23 +263,23 @@ test("state transition can be cancelled (github issue #22)", function() { } }); - equals(fsm.current, 'green', "initial state should be green"); - fsm.warn(); equals(fsm.current, 'green', "should still be green because we haven't transitioned yet"); - fsm.transition(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); - equals(fsm.can('panic'), false, "but cannot panic a 2nd time because a transition is still pending") + equal(fsm.current, 'green', "initial state should be green"); + fsm.warn(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet"); + fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); + fsm.panic(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); + equal(fsm.can('panic'), false, "but cannot panic a 2nd time because a transition is still pending") - raises(fsm.panic.bind(fsm), /event panic inappropriate because previous transition did not complete/); + throws(fsm.panic.bind(fsm), /event panic inappropriate because previous transition did not complete/); fsm.transition.cancel(); - equals(fsm.current, 'yellow', "should still be yellow because we cancelled the async transition"); - equals(fsm.can('panic'), true, "can now panic again because we cancelled previous async transition"); + equal(fsm.current, 'yellow', "should still be yellow because we cancelled the async transition"); + equal(fsm.can('panic'), true, "can now panic again because we cancelled previous async transition"); fsm.panic(); fsm.transition(); - equals(fsm.current, 'red', "should finally be red now that we completed the async transition"); + equal(fsm.current, 'red', "should finally be red now that we completed the async transition"); }); @@ -363,43 +363,43 @@ test("cannot fire event during existing transition", function() { } }); - equals(fsm.current, 'green', "initial state should be green"); - equals(fsm.can('warn'), true, "should be able to warn"); - equals(fsm.can('panic'), false, "should NOT be able to panic"); - equals(fsm.can('calm'), false, "should NOT be able to calm"); - equals(fsm.can('clear'), false, "should NOT be able to clear"); + equal(fsm.current, 'green', "initial state should be green"); + equal(fsm.can('warn'), true, "should be able to warn"); + equal(fsm.can('panic'), false, "should NOT be able to panic"); + equal(fsm.can('calm'), false, "should NOT be able to calm"); + equal(fsm.can('clear'), false, "should NOT be able to clear"); fsm.warn(); - equals(fsm.current, 'green', "should still be green because we haven't transitioned yet"); - equals(fsm.can('warn'), false, "should NOT be able to warn - during transition"); - equals(fsm.can('panic'), false, "should NOT be able to panic - during transition"); - equals(fsm.can('calm'), false, "should NOT be able to calm - during transition"); - equals(fsm.can('clear'), false, "should NOT be able to clear - during transition"); + equal(fsm.current, 'green', "should still be green because we haven't transitioned yet"); + equal(fsm.can('warn'), false, "should NOT be able to warn - during transition"); + equal(fsm.can('panic'), false, "should NOT be able to panic - during transition"); + equal(fsm.can('calm'), false, "should NOT be able to calm - during transition"); + equal(fsm.can('clear'), false, "should NOT be able to clear - during transition"); fsm.transition(); - equals(fsm.current, 'yellow', "warn event should transition from green to yellow"); - equals(fsm.can('warn'), false, "should NOT be able to warn"); - equals(fsm.can('panic'), true, "should be able to panic"); - equals(fsm.can('calm'), false, "should NOT be able to calm"); - equals(fsm.can('clear'), true, "should be able to clear"); + equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); + equal(fsm.can('warn'), false, "should NOT be able to warn"); + equal(fsm.can('panic'), true, "should be able to panic"); + equal(fsm.can('calm'), false, "should NOT be able to calm"); + equal(fsm.can('clear'), true, "should be able to clear"); fsm.panic(); - equals(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); - equals(fsm.can('warn'), false, "should NOT be able to warn - during transition"); - equals(fsm.can('panic'), false, "should NOT be able to panic - during transition"); - equals(fsm.can('calm'), false, "should NOT be able to calm - during transition"); - equals(fsm.can('clear'), false, "should NOT be able to clear - during transition"); + equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); + equal(fsm.can('warn'), false, "should NOT be able to warn - during transition"); + equal(fsm.can('panic'), false, "should NOT be able to panic - during transition"); + equal(fsm.can('calm'), false, "should NOT be able to calm - during transition"); + equal(fsm.can('clear'), false, "should NOT be able to clear - during transition"); fsm.transition(); - equals(fsm.current, 'red', "panic event should transition from yellow to red"); - equals(fsm.can('warn'), false, "should NOT be able to warn"); - equals(fsm.can('panic'), false, "should NOT be able to panic"); - equals(fsm.can('calm'), true, "should be able to calm"); - equals(fsm.can('clear'), false, "should NOT be able to clear"); + equal(fsm.current, 'red', "panic event should transition from yellow to red"); + equal(fsm.can('warn'), false, "should NOT be able to warn"); + equal(fsm.can('panic'), false, "should NOT be able to panic"); + equal(fsm.can('calm'), true, "should be able to calm"); + equal(fsm.can('clear'), false, "should NOT be able to clear"); }); diff --git a/test/test_basics.js b/test/test_basics.js index 9c5a567..150d7a7 100644 --- a/test/test_basics.js +++ b/test/test_basics.js @@ -15,12 +15,12 @@ test("standalone state machine", function() { { name: 'clear', from: 'yellow', to: 'green' } ]}); - equals(fsm.current, 'green', "initial state should be green"); + equal(fsm.current, 'green', "initial state should be green"); - fsm.warn(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equals(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equals(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equals(fsm.current, 'green', "clear event should transition from yellow to green"); + fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); + fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); + fsm.calm(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); + fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); }); @@ -38,12 +38,12 @@ test("targeted state machine", function() { { name: 'clear', from: 'yellow', to: 'green' } ]}); - equals(this.current, 'green', "initial state should be green"); + equal(this.current, 'green', "initial state should be green"); - this.warn(); equals(this.current, 'yellow', "warn event should transition from green to yellow"); - this.panic(); equals(this.current, 'red', "panic event should transition from yellow to red"); - this.calm(); equals(this.current, 'yellow', "calm event should transition from red to yellow"); - this.clear(); equals(this.current, 'green', "clear event should transition from yellow to green"); + this.warn(); equal(this.current, 'yellow', "warn event should transition from green to yellow"); + this.panic(); equal(this.current, 'red', "panic event should transition from yellow to red"); + this.calm(); equal(this.current, 'yellow', "calm event should transition from red to yellow"); + this.clear(); equal(this.current, 'green', "clear event should transition from yellow to green"); }); //----------------------------------------------------------------------------- @@ -58,20 +58,20 @@ test("can & cannot", function() { { name: 'calm', from: 'red', to: 'yellow' }, ]}); - equals(fsm.current, 'green', "initial state should be green"); + equal(fsm.current, 'green', "initial state should be green"); ok(fsm.can('warn'), "should be able to warn from green state") ok(fsm.cannot('panic'), "should NOT be able to panic from green state") ok(fsm.cannot('calm'), "should NOT be able to calm from green state") fsm.warn(); - equals(fsm.current, 'yellow', "current state should be yellow"); + equal(fsm.current, 'yellow', "current state should be yellow"); ok(fsm.cannot('warn'), "should NOT be able to warn from yellow state") ok(fsm.can('panic'), "should be able to panic from yellow state") ok(fsm.cannot('calm'), "should NOT be able to calm from yellow state") fsm.panic(); - equals(fsm.current, 'red', "current state should be red"); + equal(fsm.current, 'red', "current state should be red"); ok(fsm.cannot('warn'), "should NOT be able to warn from red state") ok(fsm.cannot('panic'), "should NOT be able to panic from red state") ok(fsm.can('calm'), "should be able to calm from red state") @@ -91,21 +91,21 @@ test("is", function() { { name: 'clear', from: 'yellow', to: 'green' } ]}); - equals(fsm.current, 'green', "initial state should be green"); + equal(fsm.current, 'green', "initial state should be green"); - equals(fsm.is('green'), true, 'current state should match'); - equals(fsm.is('yellow'), false, 'current state should NOT match'); - equals(fsm.is(['green', 'red']), true, 'current state should match when included in array'); - equals(fsm.is(['yellow', 'red']), false, 'current state should NOT match when not included in array'); + equal(fsm.is('green'), true, 'current state should match'); + equal(fsm.is('yellow'), false, 'current state should NOT match'); + equal(fsm.is(['green', 'red']), true, 'current state should match when included in array'); + equal(fsm.is(['yellow', 'red']), false, 'current state should NOT match when not included in array'); fsm.warn(); - equals(fsm.current, 'yellow', "current state should be yellow"); + equal(fsm.current, 'yellow', "current state should be yellow"); - equals(fsm.is('green'), false, 'current state should NOT match'); - equals(fsm.is('yellow'), true, 'current state should match'); - equals(fsm.is(['green', 'red']), false, 'current state should NOT match when not included in array'); - equals(fsm.is(['yellow', 'red']), true, 'current state should match when included in array'); + equal(fsm.is('green'), false, 'current state should NOT match'); + equal(fsm.is('yellow'), true, 'current state should match'); + equal(fsm.is(['green', 'red']), false, 'current state should NOT match when not included in array'); + equal(fsm.is(['yellow', 'red']), true, 'current state should match when included in array'); }); @@ -120,16 +120,16 @@ test("isFinished", function() { { name: 'panic', from: 'yellow', to: 'red' } ]}); - equals(fsm.current, 'green'); - equals(fsm.isFinished(), false); + equal(fsm.current, 'green'); + equal(fsm.isFinished(), false); fsm.warn(); - equals(fsm.current, 'yellow'); - equals(fsm.isFinished(), false); + equal(fsm.current, 'yellow'); + equal(fsm.isFinished(), false); fsm.panic(); - equals(fsm.current, 'red'); - equals(fsm.isFinished(), true); + equal(fsm.current, 'red'); + equal(fsm.isFinished(), true); }); @@ -144,16 +144,16 @@ test("isFinished - without specifying terminal state", function() { { name: 'panic', from: 'yellow', to: 'red' } ]}); - equals(fsm.current, 'green'); - equals(fsm.isFinished(), false); + equal(fsm.current, 'green'); + equal(fsm.isFinished(), false); fsm.warn(); - equals(fsm.current, 'yellow'); - equals(fsm.isFinished(), false); + equal(fsm.current, 'yellow'); + equal(fsm.isFinished(), false); fsm.panic(); - equals(fsm.current, 'red'); - equals(fsm.isFinished(), false); + equal(fsm.current, 'red'); + equal(fsm.isFinished(), false); }); //----------------------------------------------------------------------------- @@ -168,20 +168,20 @@ test("inappropriate events", function() { { name: 'calm', from: 'red', to: 'yellow' }, ]}); - equals(fsm.current, 'green', "initial state should be green"); + equal(fsm.current, 'green', "initial state should be green"); - raises(fsm.panic.bind(fsm), /event panic inappropriate in current state green/); - raises(fsm.calm.bind(fsm), /event calm inappropriate in current state green/); + throws(fsm.panic.bind(fsm), /event panic inappropriate in current state green/); + throws(fsm.calm.bind(fsm), /event calm inappropriate in current state green/); fsm.warn(); - equals(fsm.current, 'yellow', "current state should be yellow"); - raises(fsm.warn.bind(fsm), /event warn inappropriate in current state yellow/); - raises(fsm.calm.bind(fsm), /event calm inappropriate in current state yellow/); + equal(fsm.current, 'yellow', "current state should be yellow"); + throws(fsm.warn.bind(fsm), /event warn inappropriate in current state yellow/); + throws(fsm.calm.bind(fsm), /event calm inappropriate in current state yellow/); fsm.panic(); - equals(fsm.current, 'red', "current state should be red"); - raises(fsm.warn.bind(fsm), /event warn inappropriate in current state red/); - raises(fsm.panic.bind(fsm), /event panic inappropriate in current state red/); + equal(fsm.current, 'red', "current state should be red"); + throws(fsm.warn.bind(fsm), /event warn inappropriate in current state red/); + throws(fsm.panic.bind(fsm), /event panic inappropriate in current state red/); }); @@ -198,20 +198,20 @@ test("inappropriate event handling can be customized", function() { { name: 'calm', from: 'red', to: 'yellow' } ]}); - equals(fsm.current, 'green', "initial state should be green"); + equal(fsm.current, 'green', "initial state should be green"); - equals(fsm.panic(), 'event panic inappropriate in current state green'); - equals(fsm.calm(), 'event calm inappropriate in current state green'); + equal(fsm.panic(), 'event panic inappropriate in current state green'); + equal(fsm.calm(), 'event calm inappropriate in current state green'); fsm.warn(); - equals(fsm.current, 'yellow', "current state should be yellow"); - equals(fsm.warn(), 'event warn inappropriate in current state yellow'); - equals(fsm.calm(), 'event calm inappropriate in current state yellow'); + equal(fsm.current, 'yellow', "current state should be yellow"); + equal(fsm.warn(), 'event warn inappropriate in current state yellow'); + equal(fsm.calm(), 'event calm inappropriate in current state yellow'); fsm.panic(); - equals(fsm.current, 'red', "current state should be red"); - equals(fsm.warn(), 'event warn inappropriate in current state red'); - equals(fsm.panic(), 'event panic inappropriate in current state red'); + equal(fsm.current, 'red', "current state should be red"); + equal(fsm.warn(), 'event warn inappropriate in current state red'); + equal(fsm.panic(), 'event panic inappropriate in current state red'); }); @@ -507,9 +507,9 @@ test("exceptions in caller-provided callbacks are not swallowed (github issue #1 onenteryellow: function() { throw 'oops'; } }}); - equals(fsm.current, 'green', "initial state should be green"); + equal(fsm.current, 'green', "initial state should be green"); - raises(fsm.warn.bind(fsm), /oops/); + throws(fsm.warn.bind(fsm), /oops/); }); //----------------------------------------------------------------------------- @@ -526,13 +526,13 @@ test("no-op transitions (github issue #5)", function() { { name: 'clear', from: 'yellow', to: 'green' } ]}); - equals(fsm.current, 'green', "initial state should be green"); + equal(fsm.current, 'green', "initial state should be green"); ok(fsm.can('noop'), "should be able to noop from green state") ok(fsm.can('warn'), "should be able to warn from green state") - fsm.noop(); equals(fsm.current, 'green', "noop event should not cause a transition (there is no 'to' specified)"); - fsm.warn(); equals(fsm.current, 'yellow', "warn event should transition from green to yellow"); + fsm.noop(); equal(fsm.current, 'green', "noop event should not cause a transition (there is no 'to' specified)"); + fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); ok(fsm.cannot('noop'), "should NOT be able to noop from yellow state") ok(fsm.cannot('warn'), "should NOT be able to warn from yellow state") @@ -553,19 +553,19 @@ test("wildcard 'from' allows event from any state (github issue #11)", function( { name: 'stop', from: '*', to: 'stopped' } ]}); - equals(fsm.current, 'stopped', "initial state should be stopped"); + equal(fsm.current, 'stopped', "initial state should be stopped"); - fsm.prepare(); equals(fsm.current, 'ready', "prepare event should transition from stopped to ready"); - fsm.stop(); equals(fsm.current, 'stopped', "stop event should transition from ready to stopped"); + fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready"); + fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from ready to stopped"); - fsm.prepare(); equals(fsm.current, 'ready', "prepare event should transition from stopped to ready"); - fsm.start(); equals(fsm.current, 'running', "start event should transition from ready to running"); - fsm.stop(); equals(fsm.current, 'stopped', "stop event should transition from running to stopped"); + fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready"); + fsm.start(); equal(fsm.current, 'running', "start event should transition from ready to running"); + fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from running to stopped"); - fsm.prepare(); equals(fsm.current, 'ready', "prepare event should transition from stopped to ready"); - fsm.start(); equals(fsm.current, 'running', "start event should transition from ready to running"); - fsm.pause(); equals(fsm.current, 'paused', "pause event should transition from running to paused"); - fsm.stop(); equals(fsm.current, 'stopped', "stop event should transition from paused to stopped"); + fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready"); + fsm.start(); equal(fsm.current, 'running', "start event should transition from ready to running"); + fsm.pause(); equal(fsm.current, 'paused', "pause event should transition from running to paused"); + fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from paused to stopped"); }); @@ -583,19 +583,19 @@ test("missing 'from' allows event from any state (github issue #11) ", function( { name: 'stop', /* any from state */ to: 'stopped' } ]}); - equals(fsm.current, 'stopped', "initial state should be stopped"); + equal(fsm.current, 'stopped', "initial state should be stopped"); - fsm.prepare(); equals(fsm.current, 'ready', "prepare event should transition from stopped to ready"); - fsm.stop(); equals(fsm.current, 'stopped', "stop event should transition from ready to stopped"); + fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready"); + fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from ready to stopped"); - fsm.prepare(); equals(fsm.current, 'ready', "prepare event should transition from stopped to ready"); - fsm.start(); equals(fsm.current, 'running', "start event should transition from ready to running"); - fsm.stop(); equals(fsm.current, 'stopped', "stop event should transition from running to stopped"); + fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready"); + fsm.start(); equal(fsm.current, 'running', "start event should transition from ready to running"); + fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from running to stopped"); - fsm.prepare(); equals(fsm.current, 'ready', "prepare event should transition from stopped to ready"); - fsm.start(); equals(fsm.current, 'running', "start event should transition from ready to running"); - fsm.pause(); equals(fsm.current, 'paused', "pause event should transition from running to paused"); - fsm.stop(); equals(fsm.current, 'stopped', "stop event should transition from paused to stopped"); + fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready"); + fsm.start(); equal(fsm.current, 'running', "start event should transition from ready to running"); + fsm.pause(); equal(fsm.current, 'paused', "pause event should transition from running to paused"); + fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from paused to stopped"); }); @@ -616,19 +616,19 @@ test("event return values (github issue #12) ", function() { } }); - equals(fsm.current, 'stopped', "initial state should be stopped"); + equal(fsm.current, 'stopped', "initial state should be stopped"); - equals(fsm.prepare(), StateMachine.Result.SUCCEEDED, "expected event to have SUCCEEDED"); - equals(fsm.current, 'ready', "prepare event should transition from stopped to ready"); + equal(fsm.prepare(), StateMachine.Result.SUCCEEDED, "expected event to have SUCCEEDED"); + equal(fsm.current, 'ready', "prepare event should transition from stopped to ready"); - equals(fsm.fake(), StateMachine.Result.CANCELLED, "expected event to have been CANCELLED"); - equals(fsm.current, 'ready', "cancelled event should not cause a transition"); + equal(fsm.fake(), StateMachine.Result.CANCELLED, "expected event to have been CANCELLED"); + equal(fsm.current, 'ready', "cancelled event should not cause a transition"); - equals(fsm.start(), StateMachine.Result.PENDING, "expected event to cause a PENDING asynchronous transition"); - equals(fsm.current, 'ready', "async transition hasn't happened yet"); + equal(fsm.start(), StateMachine.Result.PENDING, "expected event to cause a PENDING asynchronous transition"); + equal(fsm.current, 'ready', "async transition hasn't happened yet"); - equals(fsm.transition(), StateMachine.Result.SUCCEEDED, "expected async transition to have SUCCEEDED"); - equals(fsm.current, 'running', "async transition should now be complete"); + equal(fsm.transition(), StateMachine.Result.SUCCEEDED, "expected async transition to have SUCCEEDED"); + equal(fsm.current, 'running', "async transition should now be complete"); }); From c7839286cbc85732a75d3263b28b0caf6e0fb66a Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 15 Mar 2014 13:56:50 -0700 Subject: [PATCH 13/87] Added nodejs support (finally) along with node-qunit test/runner.js --- .gitignore | 1 + README.md | 5 +++-- package.json | 11 +++++++++++ state-machine.js | 23 +++++++++++++++++++---- test/runner.js | 22 ++++++++++++++++++++++ test/test_advanced.js | 2 +- test/test_async.js | 2 +- test/test_basics.js | 2 +- test/test_classes.js | 2 +- test/test_initialize.js | 2 +- 10 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 .gitignore create mode 100644 package.json create mode 100644 test/runner.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/README.md b/README.md index df31893..6a24b20 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,13 @@ Alternatively: * Minified version provided in state-machine.min.js * No 3rd party library is required * Demo can be found in /index.html - * QUnit tests can be found in /test/index.html + * QUnit (browser) tests can be found in /test/index.html + * QUnit (headless) tests can be run with "node test/runner.js" (after installing node-qunit with "npm install") Usage ===== -Include `state-machine.min.js` in your application. +Include `state-machine.js` in your web application, or, for nodejs `require("state-machine.js")`. In its simplest form, create a standalone state machine using: diff --git a/package.json b/package.json new file mode 100644 index 0000000..10080f0 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "state-machine", + "description": "A simple finite state machine library", + "homepage": "https://github.com/jakesgordon/javascript-state-machine", + "keywords": ["state machine", "server", "client"], + "author": "Jake Gordon ", + "repository": {"type": "git", "url": "git://github.com/jakesgordon/javascript-state-machine.git"}, + "main": "state-machine.js", + "devDependencies": [ "qunit" ], + "version": "2.3.0" +} diff --git a/state-machine.js b/state-machine.js index aa9fce8..c1ba6ed 100644 --- a/state-machine.js +++ b/state-machine.js @@ -7,7 +7,7 @@ */ -(function (window) { +(function () { var StateMachine = { @@ -191,12 +191,27 @@ //=========================================================================== - if ("function" === typeof define) { + //====== + // NODE + //====== + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = StateMachine; + } + exports.StateMachine = StateMachine; + } + //============ + // AMD/REQUIRE + //============ + else if (typeof define === 'function') { define(function(require) { return StateMachine; }); } - else { + //======== + // BROWSER + //======== + else if (window) { window.StateMachine = StateMachine; } -}(this)); +}()); diff --git a/test/runner.js b/test/runner.js new file mode 100644 index 0000000..464fa3b --- /dev/null +++ b/test/runner.js @@ -0,0 +1,22 @@ +// +// To run tests via nodejs you must have nodejs and npm installed +// +// > npm install # to install node-qunit +// > node test/runner +// + +var runner = require("qunit"); + +runner.run({ + + code: "./state-machine.js", + + tests: [ + "test/test_basics.js", + "test/test_advanced.js", + "test/test_classes.js", + "test/test_async.js", + "test/test_initialize.js" + ] + +}); diff --git a/test/test_advanced.js b/test/test_advanced.js index d4e2de8..7246468 100644 --- a/test/test_advanced.js +++ b/test/test_advanced.js @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- -module("advanced"); +QUnit.module("advanced"); //----------------------------------------------------------------------------- diff --git a/test/test_async.js b/test/test_async.js index 0b4999c..17fc6a5 100644 --- a/test/test_async.js +++ b/test/test_async.js @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- -module("async"); +QUnit.module("async"); //----------------------------------------------------------------------------- diff --git a/test/test_basics.js b/test/test_basics.js index 150d7a7..8237a11 100644 --- a/test/test_basics.js +++ b/test/test_basics.js @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- -module("basic"); +QUnit.module("basic"); //----------------------------------------------------------------------------- diff --git a/test/test_classes.js b/test/test_classes.js index cf8da61..4c6dc2b 100644 --- a/test/test_classes.js +++ b/test/test_classes.js @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- -module("classes"); +QUnit.module("classes"); //----------------------------------------------------------------------------- diff --git a/test/test_initialize.js b/test/test_initialize.js index 385dac7..ae2e2a0 100644 --- a/test/test_initialize.js +++ b/test/test_initialize.js @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- -module("special initialization options", { +QUnit.module("special initialization options", { setup: function() { this.called = []; From 215f3962227f1823f3e2007e600e5c41636ee845 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 15 Mar 2014 14:07:26 -0700 Subject: [PATCH 14/87] added nodejs support to release notes --- RELEASE_NOTES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 29a9be4..14285b4 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,7 +1,8 @@ Version 2.3.0 (March ?? 2014) ----------------------------- - * minor updates in progress + * Added support for nodejs (finally) + * Added ability to run tests in console via nodejs ("npm install" to get node-qunit, then "node test/runner.js") Version 2.2.0 (January 26th 2013) --------------------------------- From 6d61d924d3810ed815d05a689ff2ee7644cc89ca Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 15 Mar 2014 14:33:06 -0700 Subject: [PATCH 15/87] only define as a requirejs module when define.amd is present --- state-machine.js | 2 +- state-machine.min.js | 2 +- test/requirejs/index.html | 8 +++----- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/state-machine.js b/state-machine.js index c1ba6ed..8248bd1 100644 --- a/state-machine.js +++ b/state-machine.js @@ -203,7 +203,7 @@ //============ // AMD/REQUIRE //============ - else if (typeof define === 'function') { + else if (typeof define === 'function' && define.amd) { define(function(require) { return StateMachine; }); } //======== diff --git a/state-machine.min.js b/state-machine.min.js index c880aea..c07e7e0 100644 --- a/state-machine.min.js +++ b/state-machine.min.js @@ -1 +1 @@ -(function(b){var a={VERSION:"2.3.0",Result:{SUCCEEDED:1,NOTRANSITION:2,CANCELLED:3,PENDING:4},Error:{INVALID_TRANSITION:100,PENDING_TRANSITION:200,INVALID_CALLBACK:300},WILDCARD:"*",ASYNC:"async",create:function(h,i){var k=(typeof h.initial=="string")?{state:h.initial}:h.initial;var g=h.terminal||h["final"];var f=i||h.target||{};var m=h.events||[];var j=h.callbacks||{};var d={};var l=function(o){var q=(o.from instanceof Array)?o.from:(o.from?[o.from]:[a.WILDCARD]);d[o.name]=d[o.name]||{};for(var p=0;p=0):(this.current===n)};f.can=function(n){return !this.transition&&(d[n].hasOwnProperty(this.current)||d[n].hasOwnProperty(a.WILDCARD))};f.cannot=function(n){return !this.can(n)};f.error=h.error||function(p,t,s,o,n,r,q){throw q||r};f.isFinished=function(){return this.is(g)};if(k&&!k.defer){f[k.event]()}return f},doCallback:function(h,f,d,j,i,c){if(f){try{return f.apply(h,[d,j,i].concat(c))}catch(g){return h.error(d,j,i,c,a.Error.INVALID_CALLBACK,"an exception occurred in a caller-provided callback function",g)}}},beforeAnyEvent:function(e,d,g,f,c){return a.doCallback(e,e.onbeforeevent,d,g,f,c)},afterAnyEvent:function(e,d,g,f,c){return a.doCallback(e,e.onafterevent||e.onevent,d,g,f,c)},leaveAnyState:function(e,d,g,f,c){return a.doCallback(e,e.onleavestate,d,g,f,c)},enterAnyState:function(e,d,g,f,c){return a.doCallback(e,e.onenterstate||e.onstate,d,g,f,c)},changeState:function(e,d,g,f,c){return a.doCallback(e,e.onchangestate,d,g,f,c)},beforeThisEvent:function(e,d,g,f,c){return a.doCallback(e,e["onbefore"+d],d,g,f,c)},afterThisEvent:function(e,d,g,f,c){return a.doCallback(e,e["onafter"+d]||e["on"+d],d,g,f,c)},leaveThisState:function(e,d,g,f,c){return a.doCallback(e,e["onleave"+g],d,g,f,c)},enterThisState:function(e,d,g,f,c){return a.doCallback(e,e["onenter"+f]||e["on"+f],d,g,f,c)},beforeEvent:function(e,d,g,f,c){if((false===a.beforeThisEvent(e,d,g,f,c))||(false===a.beforeAnyEvent(e,d,g,f,c))){return false}},afterEvent:function(e,d,g,f,c){a.afterThisEvent(e,d,g,f,c);a.afterAnyEvent(e,d,g,f,c)},leaveState:function(g,f,i,h,e){var d=a.leaveThisState(g,f,i,h,e),c=a.leaveAnyState(g,f,i,h,e);if((false===d)||(false===c)){return false}else{if((a.ASYNC===d)||(a.ASYNC===c)){return a.ASYNC}}},enterState:function(e,d,g,f,c){a.enterThisState(e,d,g,f,c);a.enterAnyState(e,d,g,f,c)},buildEvent:function(c,d){return function(){var i=this.current;var h=d[i]||d[a.WILDCARD]||i;var f=Array.prototype.slice.call(arguments);if(this.transition){return this.error(c,i,h,f,a.Error.PENDING_TRANSITION,"event "+c+" inappropriate because previous transition did not complete")}if(this.cannot(c)){return this.error(c,i,h,f,a.Error.INVALID_TRANSITION,"event "+c+" inappropriate in current state "+this.current)}if(false===a.beforeEvent(this,c,i,h,f)){return a.Result.CANCELLED}if(i===h){a.afterEvent(this,c,i,h,f);return a.Result.NOTRANSITION}var g=this;this.transition=function(){g.transition=null;g.current=h;a.enterState(g,c,i,h,f);a.changeState(g,c,i,h,f);a.afterEvent(g,c,i,h,f);return a.Result.SUCCEEDED};this.transition.cancel=function(){g.transition=null;a.afterEvent(g,c,i,h,f)};var e=a.leaveState(this,c,i,h,f);if(false===e){this.transition=null;return a.Result.CANCELLED}else{if(a.ASYNC===e){return a.Result.PENDING}else{if(this.transition){return this.transition()}}}}}};if("function"===typeof define){define(function(c){return a})}else{b.StateMachine=a}}(this)); \ No newline at end of file +(function(){var a={VERSION:"2.3.0",Result:{SUCCEEDED:1,NOTRANSITION:2,CANCELLED:3,PENDING:4},Error:{INVALID_TRANSITION:100,PENDING_TRANSITION:200,INVALID_CALLBACK:300},WILDCARD:"*",ASYNC:"async",create:function(g,h){var j=(typeof g.initial=="string")?{state:g.initial}:g.initial;var f=g.terminal||g["final"];var e=h||g.target||{};var l=g.events||[];var i=g.callbacks||{};var c={};var k=function(m){var p=(m.from instanceof Array)?m.from:(m.from?[m.from]:[a.WILDCARD]);c[m.name]=c[m.name]||{};for(var o=0;o=0):(this.current===m)};e.can=function(m){return !this.transition&&(c[m].hasOwnProperty(this.current)||c[m].hasOwnProperty(a.WILDCARD))};e.cannot=function(m){return !this.can(m)};e.error=g.error||function(o,s,r,n,m,q,p){throw p||q};e.isFinished=function(){return this.is(f)};if(j&&!j.defer){e[j.event]()}return e},doCallback:function(g,d,c,i,h,b){if(d){try{return d.apply(g,[c,i,h].concat(b))}catch(f){return g.error(c,i,h,b,a.Error.INVALID_CALLBACK,"an exception occurred in a caller-provided callback function",f)}}},beforeAnyEvent:function(d,c,f,e,b){return a.doCallback(d,d.onbeforeevent,c,f,e,b)},afterAnyEvent:function(d,c,f,e,b){return a.doCallback(d,d.onafterevent||d.onevent,c,f,e,b)},leaveAnyState:function(d,c,f,e,b){return a.doCallback(d,d.onleavestate,c,f,e,b)},enterAnyState:function(d,c,f,e,b){return a.doCallback(d,d.onenterstate||d.onstate,c,f,e,b)},changeState:function(d,c,f,e,b){return a.doCallback(d,d.onchangestate,c,f,e,b)},beforeThisEvent:function(d,c,f,e,b){return a.doCallback(d,d["onbefore"+c],c,f,e,b)},afterThisEvent:function(d,c,f,e,b){return a.doCallback(d,d["onafter"+c]||d["on"+c],c,f,e,b)},leaveThisState:function(d,c,f,e,b){return a.doCallback(d,d["onleave"+f],c,f,e,b)},enterThisState:function(d,c,f,e,b){return a.doCallback(d,d["onenter"+e]||d["on"+e],c,f,e,b)},beforeEvent:function(d,c,f,e,b){if((false===a.beforeThisEvent(d,c,f,e,b))||(false===a.beforeAnyEvent(d,c,f,e,b))){return false}},afterEvent:function(d,c,f,e,b){a.afterThisEvent(d,c,f,e,b);a.afterAnyEvent(d,c,f,e,b)},leaveState:function(f,e,h,g,d){var c=a.leaveThisState(f,e,h,g,d),b=a.leaveAnyState(f,e,h,g,d);if((false===c)||(false===b)){return false}else{if((a.ASYNC===c)||(a.ASYNC===b)){return a.ASYNC}}},enterState:function(d,c,f,e,b){a.enterThisState(d,c,f,e,b);a.enterAnyState(d,c,f,e,b)},buildEvent:function(b,c){return function(){var h=this.current;var g=c[h]||c[a.WILDCARD]||h;var e=Array.prototype.slice.call(arguments);if(this.transition){return this.error(b,h,g,e,a.Error.PENDING_TRANSITION,"event "+b+" inappropriate because previous transition did not complete")}if(this.cannot(b)){return this.error(b,h,g,e,a.Error.INVALID_TRANSITION,"event "+b+" inappropriate in current state "+this.current)}if(false===a.beforeEvent(this,b,h,g,e)){return a.Result.CANCELLED}if(h===g){a.afterEvent(this,b,h,g,e);return a.Result.NOTRANSITION}var f=this;this.transition=function(){f.transition=null;f.current=g;a.enterState(f,b,h,g,e);a.changeState(f,b,h,g,e);a.afterEvent(f,b,h,g,e);return a.Result.SUCCEEDED};this.transition.cancel=function(){f.transition=null;a.afterEvent(f,b,h,g,e)};var d=a.leaveState(this,b,h,g,e);if(false===d){this.transition=null;return a.Result.CANCELLED}else{if(a.ASYNC===d){return a.Result.PENDING}else{if(this.transition){return this.transition()}}}}}};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports){exports=module.exports=a}exports.StateMachine=a}else{if(typeof define==="function"&&define.amd){define(function(b){return a})}else{if(window){window.StateMachine=a}}}}()); \ No newline at end of file diff --git a/test/requirejs/index.html b/test/requirejs/index.html index 0ff0aa8..09908f1 100755 --- a/test/requirejs/index.html +++ b/test/requirejs/index.html @@ -6,13 +6,11 @@ + +Or for npm: + + var StateMachine = require('javascript-state-machine'); In its simplest form, create a standalone state machine using: From 489f4bd8f9237d0e9415aec5421e1c620a74e800 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sun, 20 Nov 2016 14:06:35 -0800 Subject: [PATCH 59/87] document fsm.states() method in readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7148dad..1c1fed0 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ along with the following members: * fsm.can(e) - return true if event `e` can be fired in the current state * fsm.cannot(e) - return true if event `e` cannot be fired in the current state * fsm.transitions() - return list of events that are allowed from the current state + * fsm.states() - return list of all possible states. # Multiple 'from' and 'to' states for a single event From d5a00dd3e4ed94b5077e2a41760a14e6a119c7ec Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sun, 20 Nov 2016 14:14:41 -0800 Subject: [PATCH 60/87] more readme --- README.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1c1fed0..4386ed5 100644 --- a/README.md +++ b/README.md @@ -340,21 +340,20 @@ define a custom `error` handler: * Run tests in console with `npm test` * Please include tests with pull requests. -# Release Notes - -See [RELEASE NOTES](https://github.com/jakesgordon/javascript-state-machine/blob/master/RELEASE_NOTES.md) file. - # Related Links * You can find the [code on github](https://github.com/jakesgordon/javascript-state-machine) * You can find a [working demo here](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/example/) + * [v2.3 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2-3-0/) - 3/15/2014 + * [v2.2 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2-2-0/) - 1/26/2013 + * [v2.1 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2-1-0/) - 1/7/2012 + * [v2.0 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2) - 8/19/2011 + * [v1.2 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v1-2-0) - 6/21/2011 + * [v1.0 release announcement](http://codeincomplete.com/posts/javascript-state-machine) - 6/1/2011 - * [v2.3 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2-3-0/) - * [v2.2 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2-2-0/) - * [v2.1 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2-1-0/) - * [v2.0 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2) - * [v1.2 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v1-2-0) - * [v1.0 release announcement](http://codeincomplete.com/posts/javascript-state-machine) +# Release Notes + +See [RELEASE NOTES](https://github.com/jakesgordon/javascript-state-machine/blob/master/RELEASE_NOTES.md) file. # License From c7538b4fce486a84de7a456b72ce7dc15201c818 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sun, 20 Nov 2016 14:18:43 -0800 Subject: [PATCH 61/87] more readme --- README.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 4386ed5..d794b7d 100644 --- a/README.md +++ b/README.md @@ -73,10 +73,10 @@ This example will create an object with 2 event methods: The `rest` event will always transition to the `hungry` state, while the `eat` event will transition to a state that is dependent on the current state. ->> NOTE: The `rest` event could use a wildcard '*' for the 'from' state if it should be +> NOTE: The `rest` event could use a wildcard '*' for the 'from' state if it should be allowed from any current state. ->> NOTE: The `rest` event in the above example can also be specified as multiple events with +> NOTE: The `rest` event in the above example can also be specified as multiple events with the same name if you prefer the verbose approach. # Callbacks @@ -88,7 +88,7 @@ the same name if you prefer the verbose approach. * `onenterSTATE` - fired when entering the new state * `onafterEVENT` - fired after the event ->> (using your **specific** EVENT and STATE names) +> (using your **specific** EVENT and STATE names) For convenience, the 2 most useful callbacks can be shortened: @@ -142,7 +142,7 @@ Additionally, they can be added and removed from the state machine at any time: The order in which callbacks occur is as follows: ->> assume event **go** transitions from **red** state to **green** +> assume event **go** transitions from **red** state to **green** * `onbeforego` - specific handler for the **go** event only * `onbeforeevent` - generic handler for all events @@ -153,7 +153,7 @@ The order in which callbacks occur is as follows: * `onaftergo` - specific handler for the **go** event only * `onafterevent` - generic handler for all events ->> NOTE: the legacy `onchangestate` handler has been deprecated and will be removed in a future version +> NOTE: the legacy `onchangestate` handler has been deprecated and will be removed in a future version You can affect the event in 3 ways: @@ -207,7 +207,7 @@ For example, using jQuery effects: } }); ->> _NOTE: If you decide to cancel the ASYNC event, you can call `fsm.transition.cancel();` +> NOTE: If you decide to cancel the ASYNC event, you can call `fsm.transition.cancel();` # State Machine Classes @@ -242,8 +242,8 @@ instances: This should be easy to adjust to fit your appropriate mechanism for object construction. ->> _NOTE: the `startup` event can be given any name, but it must be present in some form to - ensure that each instance constructed is initialized with its own unique `current` state._ +> NOTE: the `startup` event can be given any name, but it must be present in some form to + ensure that each instance constructed is initialized with its own unique `current` state. # Initialization Options @@ -302,11 +302,11 @@ same as the first example in this section where you simply define your own start So you have a number of choices available to you when initializing your state machine. ->> _IMPORTANT NOTE: if you are using the pattern described in the previous section "State Machine - Classes", and wish to declare an `initial` state in this manner, you MUST use the `defer: true` - attribute and manually call the starting event in your constructor function. This will ensure - that each instance gets its own unique `current` state, rather than an (unwanted) shared - `current` state on the prototype object itself._ +> IMPORTANT NOTE: if you are using the pattern described in the previous section "State Machine + Classes", and wish to declare an `initial` state in this manner, you MUST use the `defer: true` + attribute and manually call the starting event in your constructor function. This will ensure + that each instance gets its own unique `current` state, rather than an (unwanted) shared + `current` state on the prototype object itself. # Handling Failures @@ -344,12 +344,12 @@ define a custom `error` handler: * You can find the [code on github](https://github.com/jakesgordon/javascript-state-machine) * You can find a [working demo here](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/example/) - * [v2.3 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2-3-0/) - 3/15/2014 - * [v2.2 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2-2-0/) - 1/26/2013 - * [v2.1 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2-1-0/) - 1/7/2012 - * [v2.0 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2) - 8/19/2011 - * [v1.2 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v1-2-0) - 6/21/2011 - * [v1.0 release announcement](http://codeincomplete.com/posts/javascript-state-machine) - 6/1/2011 + * [v2.3 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2-3-0/) + * [v2.2 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2-2-0/) + * [v2.1 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2-1-0/) + * [v2.0 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2) + * [v1.2 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v1-2-0) + * [v1.0 release announcement](http://codeincomplete.com/posts/javascript-state-machine) # Release Notes From 1d6526f0c73419269c21a35d4093c5ad6847c795 Mon Sep 17 00:00:00 2001 From: Vitaly Puzrin Date: Mon, 21 Nov 2016 03:03:34 +0400 Subject: [PATCH 62/87] Restrict list of published files in npm --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 630643f..a888691 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,9 @@ "url": "git://github.com/jakesgordon/javascript-state-machine.git" }, "main": "state-machine.js", + "files": [ + "state-machine.js" + ], "devDependencies": { "local-web-server": "~1.2.6", "qunit": "~0.9.1", From 7ebe918aa92fdb30ece709795849622fe34a50cf Mon Sep 17 00:00:00 2001 From: Vitaly Puzrin Date: Mon, 21 Nov 2016 03:07:31 +0400 Subject: [PATCH 63/87] Add Travis-CI config --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c86adcc --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - '4' + - '6' +sudo: false From c944086d7a200a1f72c327eea13824f4726fa955 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sun, 20 Nov 2016 15:48:07 -0800 Subject: [PATCH 64/87] include minified state-machine.min.js in npm files --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a888691..a350191 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ }, "main": "state-machine.js", "files": [ - "state-machine.js" + "state-machine.js", + "state-machine.min.js", + "LICENSE" ], "devDependencies": { "local-web-server": "~1.2.6", From e22f7e522b44e6c5fcc7fbd0b6b2074ce365bf15 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sun, 20 Nov 2016 16:02:04 -0800 Subject: [PATCH 65/87] added travis-ci build status badge to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d794b7d..3ad375c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Javascript Finite State Machine (v2.4.0) +[![Build Status](https://travis-ci.org/jakesgordon/javascript-state-machine.svg?branch=master)](https://travis-ci.org/jakesgordon/javascript-state-machine) + A standalone library for finite state machines. # Download From adcd3a4e6b66c498f78ec76874f8b15cb2a8b4ec Mon Sep 17 00:00:00 2001 From: Vitaly Puzrin Date: Mon, 21 Nov 2016 04:08:48 +0400 Subject: [PATCH 66/87] readme: move version number from header to badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ad375c..f5af586 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ -# Javascript Finite State Machine (v2.4.0) +# Javascript Finite State Machine [![Build Status](https://travis-ci.org/jakesgordon/javascript-state-machine.svg?branch=master)](https://travis-ci.org/jakesgordon/javascript-state-machine) +[![NPM version](https://img.shields.io/npm/v/javascript-state-machine.svg?style=flat)](https://www.npmjs.org/package/javascript-state-machine) A standalone library for finite state machines. From e53c8e7230b3c7c21838e21b641f233f8ca0a55d Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sun, 20 Nov 2016 16:27:52 -0800 Subject: [PATCH 67/87] Update RELEASE_NOTES.md --- RELEASE_NOTES.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index b6f5bb8..c0aba13 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,7 +2,7 @@ FUTURE Version 3.0.0 (ETA - Early 2017) ------------------------------ - I know, I know, I've neglected this library for far too long, so lets get it back on track before the end of 2016... + I know, I know, I've neglected this library for far too long, so lets get it back on track... ADD: Promise based async transitions ADD: conditional transitions @@ -12,8 +12,8 @@ ADD: undo/redo history ADD: use camel casing for callback/observer methods (instead of all lower case) -Version 2.4.0 (ETA - November 2016) ------------------------------------ +Version 2.4.0 (November 20 2016) +-------------------------------- * added npm install instructions to readme * fix for javascript error when running in jasmine/node (issue #88) From 90f11cccb47633dfc591858e2aef9d2aabd613fe Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sun, 20 Nov 2016 17:29:57 -0800 Subject: [PATCH 68/87] put version badge before build status badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f5af586..2b71e5e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Javascript Finite State Machine -[![Build Status](https://travis-ci.org/jakesgordon/javascript-state-machine.svg?branch=master)](https://travis-ci.org/jakesgordon/javascript-state-machine) [![NPM version](https://img.shields.io/npm/v/javascript-state-machine.svg?style=flat)](https://www.npmjs.org/package/javascript-state-machine) +[![Build Status](https://travis-ci.org/jakesgordon/javascript-state-machine.svg?branch=master)](https://travis-ci.org/jakesgordon/javascript-state-machine) A standalone library for finite state machines. From ca1a2f380384d83f85ddc669c9798a9a96d6aca6 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 7 Jan 2017 12:11:09 -0800 Subject: [PATCH 69/87] v3 big rewrite (development occurred in a private repository so this is a BIG commit) --- .gitignore | 2 + LICENSE | 24 +- README.md | 413 +-- RELEASE_NOTES.md | 29 +- bin/examples | 60 + bin/minify | 40 + bower.json | 29 - demo/demo.js | 78 - dist/state-machine-history.js | 187 ++ dist/state-machine-history.min.js | 1 + dist/state-machine-visualize.js | 269 ++ dist/state-machine-visualize.min.js | 1 + dist/state-machine.js | 644 +++++ dist/state-machine.min.js | 1 + docs/async-transitions.md | 59 + docs/commercial-license.md | 180 ++ docs/contributing.md | 59 + docs/data-and-methods.md | 64 + docs/error-handling.md | 56 + docs/initialization.md | 57 + docs/lifecycle-events.md | 148 ++ docs/state-history.md | 127 + docs/state-machine-factory.md | 104 + docs/states-and-transitions.md | 156 ++ docs/upgrading-from-v2.md | 378 +++ docs/visualization.md | 211 ++ examples/atm.dot | 31 + examples/atm.js | 33 + examples/atm.png | Bin 0 -> 98712 bytes examples/atm.svg | 174 ++ {demo => examples/demo}/demo.css | 0 examples/demo/demo.js | 84 + .../demo}/images/alerts.green.png | Bin {demo => examples/demo}/images/alerts.red.png | Bin .../demo}/images/alerts.yellow.png | Bin examples/horizontal_door.dot | 7 + examples/horizontal_door.js | 16 + examples/horizontal_door.png | Bin 0 -> 11279 bytes examples/horizontal_door.svg | 35 + examples/matter.dot | 10 + examples/matter.js | 18 + examples/matter.png | Bin 0 -> 14731 bytes examples/matter.svg | 52 + examples/vertical_door.dot | 6 + examples/vertical_door.js | 16 + examples/vertical_door.png | Bin 0 -> 8246 bytes examples/vertical_door.svg | 35 + examples/wizard.dot | 13 + examples/wizard.js | 18 + examples/wizard.png | Bin 0 -> 28377 bytes examples/wizard.svg | 69 + index.html | 6 +- lib/history.js | 187 ++ lib/state-machine.js | 644 +++++ lib/visualize.js | 269 ++ package.json | 58 +- src/app.js | 102 + src/config.js | 161 ++ src/jsm.js | 181 ++ src/plugin.js | 40 + src/plugin/history.js | 83 + src/plugin/visualize.js | 161 ++ src/util/camelize.js | 9 + src/util/exception.js | 9 + src/util/mixin.js | 13 + state-machine.js | 230 -- state-machine.min.js | 1 - test/basics.js | 130 + test/construction.js | 229 ++ test/defaults.js | 62 + test/empty.js | 79 + test/errors.js | 160 ++ test/goto.js | 195 ++ test/helpers/lifecycle_logger.js | 25 + test/index.html | 22 - test/introspection.js | 204 ++ test/issues.js | 103 + test/lifecycle.js | 880 +++++++ test/observers.js | 151 ++ test/plugin/history.js | 493 ++++ test/plugin/visualize.js | 443 ++++ test/plugins.js | 151 ++ test/qunit/qunit.css | 237 -- test/qunit/qunit.js | 2288 ----------------- test/requirejs/index.html | 42 - test/requirejs/require.js | 33 - test/runner.js | 22 - test/test_advanced.js | 302 --- test/test_async.js | 408 --- test/test_basics.js | 733 ------ test/test_classes.js | 92 - test/test_initialize.js | 122 - test/transitions.js | 223 ++ test/util/camelize.js | 14 + test/util/mixin.js | 65 + test/wildcards.js | 212 ++ webpack.config.js | 43 + 97 files changed, 9308 insertions(+), 5003 deletions(-) create mode 100755 bin/examples create mode 100755 bin/minify delete mode 100644 bower.json delete mode 100644 demo/demo.js create mode 100644 dist/state-machine-history.js create mode 100644 dist/state-machine-history.min.js create mode 100644 dist/state-machine-visualize.js create mode 100644 dist/state-machine-visualize.min.js create mode 100644 dist/state-machine.js create mode 100644 dist/state-machine.min.js create mode 100644 docs/async-transitions.md create mode 100644 docs/commercial-license.md create mode 100644 docs/contributing.md create mode 100644 docs/data-and-methods.md create mode 100644 docs/error-handling.md create mode 100644 docs/initialization.md create mode 100644 docs/lifecycle-events.md create mode 100644 docs/state-history.md create mode 100644 docs/state-machine-factory.md create mode 100644 docs/states-and-transitions.md create mode 100644 docs/upgrading-from-v2.md create mode 100644 docs/visualization.md create mode 100644 examples/atm.dot create mode 100644 examples/atm.js create mode 100644 examples/atm.png create mode 100644 examples/atm.svg rename {demo => examples/demo}/demo.css (100%) create mode 100644 examples/demo/demo.js rename {demo => examples/demo}/images/alerts.green.png (100%) rename {demo => examples/demo}/images/alerts.red.png (100%) rename {demo => examples/demo}/images/alerts.yellow.png (100%) create mode 100644 examples/horizontal_door.dot create mode 100644 examples/horizontal_door.js create mode 100644 examples/horizontal_door.png create mode 100644 examples/horizontal_door.svg create mode 100644 examples/matter.dot create mode 100644 examples/matter.js create mode 100644 examples/matter.png create mode 100644 examples/matter.svg create mode 100644 examples/vertical_door.dot create mode 100644 examples/vertical_door.js create mode 100644 examples/vertical_door.png create mode 100644 examples/vertical_door.svg create mode 100644 examples/wizard.dot create mode 100644 examples/wizard.js create mode 100644 examples/wizard.png create mode 100644 examples/wizard.svg create mode 100644 lib/history.js create mode 100644 lib/state-machine.js create mode 100644 lib/visualize.js create mode 100644 src/app.js create mode 100644 src/config.js create mode 100644 src/jsm.js create mode 100644 src/plugin.js create mode 100644 src/plugin/history.js create mode 100644 src/plugin/visualize.js create mode 100644 src/util/camelize.js create mode 100644 src/util/exception.js create mode 100644 src/util/mixin.js delete mode 100755 state-machine.js delete mode 100644 state-machine.min.js create mode 100644 test/basics.js create mode 100644 test/construction.js create mode 100644 test/defaults.js create mode 100644 test/empty.js create mode 100644 test/errors.js create mode 100644 test/goto.js create mode 100644 test/helpers/lifecycle_logger.js delete mode 100755 test/index.html create mode 100644 test/introspection.js create mode 100644 test/issues.js create mode 100644 test/lifecycle.js create mode 100644 test/observers.js create mode 100644 test/plugin/history.js create mode 100644 test/plugin/visualize.js create mode 100644 test/plugins.js delete mode 100644 test/qunit/qunit.css delete mode 100644 test/qunit/qunit.js delete mode 100755 test/requirejs/index.html delete mode 100755 test/requirejs/require.js delete mode 100644 test/runner.js delete mode 100755 test/test_advanced.js delete mode 100644 test/test_async.js delete mode 100644 test/test_basics.js delete mode 100644 test/test_classes.js delete mode 100644 test/test_initialize.js create mode 100644 test/transitions.js create mode 100644 test/util/camelize.js create mode 100644 test/util/mixin.js create mode 100644 test/wildcards.js create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore index 3c3629e..1fd04da 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ node_modules +coverage +.nyc_output diff --git a/LICENSE b/LICENSE index 701eca9..f4b0142 100644 --- a/LICENSE +++ b/LICENSE @@ -1,20 +1,10 @@ -Copyright (c) 2012, 2013, 2014, 2015, 2016, 2017, Jake Gordon and contributors +Copyright (c) Jake Gordon -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +"javascript-state-machine" is dual-licensed: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + * "javascript-state-machine" is available as an Open Source project licensed under the terms + of the LGPLv3 license. Please see + for license text. + * "javascript-state-machine" is also available under a commercial license (with support). Please + read docs/commercial-license.md and contact jake@codeincomplete.com for more details. diff --git a/README.md b/README.md index 2b71e5e..acdf35a 100644 --- a/README.md +++ b/README.md @@ -1,366 +1,145 @@ -# Javascript Finite State Machine +# Javascript State Machine -[![NPM version](https://img.shields.io/npm/v/javascript-state-machine.svg?style=flat)](https://www.npmjs.org/package/javascript-state-machine) -[![Build Status](https://travis-ci.org/jakesgordon/javascript-state-machine.svg?branch=master)](https://travis-ci.org/jakesgordon/javascript-state-machine) +A library for finite state machines. -A standalone library for finite state machines. +![matter state machine](examples/matter.png) -# Download +
    -Using npm: - - npm install javascript-state-machine - -Or download the source from [state-machine.js](https://github.com/jakesgordon/javascript-state-machine/raw/master/state-machine.js), -or the [minified version](https://github.com/jakesgordon/javascript-state-machine/raw/master/state-machine.min.js) - -# Usage - -Include `state-machine.js` in your web application: - - - -Or for npm: - - var StateMachine = require('javascript-state-machine'); - -In its simplest form, create a standalone state machine using: - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ]}); - -... will create an object with a method for each event: - - * fsm.warn() - transition from 'green' to 'yellow' - * fsm.panic() - transition from 'yellow' to 'red' - * fsm.calm() - transition from 'red' to 'yellow' - * fsm.clear() - transition from 'yellow' to 'green' - -along with the following members: - - * fsm.current - contains the current state - * fsm.is(s) - return true if state `s` is the current state - * fsm.can(e) - return true if event `e` can be fired in the current state - * fsm.cannot(e) - return true if event `e` cannot be fired in the current state - * fsm.transitions() - return list of events that are allowed from the current state - * fsm.states() - return list of all possible states. - -# Multiple 'from' and 'to' states for a single event - -If an event is allowed **from** multiple states, and always transitions to the same -state, then simply provide an array of states in the `from` attribute of an event. However, -if an event is allowed from multiple states, but should transition **to** a different -state depending on the current state, then provide multiple event entries with -the same name: - - var fsm = StateMachine.create({ - initial: 'hungry', - events: [ - { name: 'eat', from: 'hungry', to: 'satisfied' }, - { name: 'eat', from: 'satisfied', to: 'full' }, - { name: 'eat', from: 'full', to: 'sick' }, - { name: 'rest', from: ['hungry', 'satisfied', 'full', 'sick'], to: 'hungry' }, - ]}); - -This example will create an object with 2 event methods: - - * fsm.eat() - * fsm.rest() - -The `rest` event will always transition to the `hungry` state, while the `eat` event -will transition to a state that is dependent on the current state. - -> NOTE: The `rest` event could use a wildcard '*' for the 'from' state if it should be -allowed from any current state. - -> NOTE: The `rest` event in the above example can also be specified as multiple events with -the same name if you prefer the verbose approach. - -# Callbacks - -4 types of callback are available by attaching methods to your StateMachine using the following naming conventions: - - * `onbeforeEVENT` - fired before the event - * `onleaveSTATE` - fired when leaving the old state - * `onenterSTATE` - fired when entering the new state - * `onafterEVENT` - fired after the event +### NOTE for existing users -> (using your **specific** EVENT and STATE names) +> **VERSION 3.0** Is a significant rewrite from earlier versions. + Existing 2.x users should be sure to read the [Upgrade Guide](docs/upgrading-from-v2.md). -For convenience, the 2 most useful callbacks can be shortened: +
    - * `onEVENT` - convenience shorthand for `onafterEVENT` - * `onSTATE` - convenience shorthand for `onenterSTATE` +# Installation -In addition, 4 general-purpose callbacks can be used to capture **all** event and state changes: +In a browser: - * `onbeforeevent` - fired before *any* event - * `onleavestate` - fired when leaving *any* state - * `onenterstate` - fired when entering *any* state - * `onafterevent` - fired after *any* event +```html + +``` -All callbacks will be passed the same arguments: +> after downloading the [source](dist/state-machine.js) or the [minified version](dist/state-machine.min.js) - * **event** name - * **from** state - * **to** state - * _(followed by any arguments you passed into the original event method)_ - -Callbacks can be specified when the state machine is first created: - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ], - callbacks: { - onpanic: function(event, from, to, msg) { alert('panic! ' + msg); }, - onclear: function(event, from, to, msg) { alert('thanks to ' + msg); }, - ongreen: function(event, from, to) { document.body.className = 'green'; }, - onyellow: function(event, from, to) { document.body.className = 'yellow'; }, - onred: function(event, from, to) { document.body.className = 'red'; }, - } - }); - - fsm.panic('killer bees'); - fsm.clear('sedatives in the honey pots'); - ... - -Additionally, they can be added and removed from the state machine at any time: - - fsm.ongreen = null; - fsm.onyellow = null; - fsm.onred = null; - fsm.onenterstate = function(event, from, to) { document.body.className = to; }; - - -The order in which callbacks occur is as follows: - -> assume event **go** transitions from **red** state to **green** - - * `onbeforego` - specific handler for the **go** event only - * `onbeforeevent` - generic handler for all events - * `onleavered` - specific handler for the **red** state only - * `onleavestate` - generic handler for all states - * `onentergreen` - specific handler for the **green** state only - * `onenterstate` - generic handler for all states - * `onaftergo` - specific handler for the **go** event only - * `onafterevent` - generic handler for all events - -> NOTE: the legacy `onchangestate` handler has been deprecated and will be removed in a future version - -You can affect the event in 3 ways: - - * return `false` from an `onbeforeEVENT` handler to cancel the event. - * return `false` from an `onleaveSTATE` handler to cancel the event. - * return `ASYNC` from an `onleaveSTATE` handler to perform an asynchronous state transition (see next section) - -# Asynchronous State Transitions - -Sometimes, you need to execute some asynchronous code during a state transition and ensure the -new state is not entered until your code has completed. - -A good example of this is when you transition out of a `menu` state, perhaps you want to gradually -fade the menu away, or slide it off the screen and don't want to transition to your `game` state -until after that animation has been performed. - -You can now return `StateMachine.ASYNC` from your `onleavestate` handler and the state machine -will be _'put on hold'_ until you are ready to trigger the transition using the new `transition()` -method. - -For example, using jQuery effects: - - var fsm = StateMachine.create({ - - initial: 'menu', - - events: [ - { name: 'play', from: 'menu', to: 'game' }, - { name: 'quit', from: 'game', to: 'menu' } - ], +Using npm: - callbacks: { +```shell + npm install --save-dev javascript-state-machine +``` - onentermenu: function() { $('#menu').show(); }, - onentergame: function() { $('#game').show(); }, +In Node.js: - onleavemenu: function() { - $('#menu').fadeOut('fast', function() { - fsm.transition(); - }); - return StateMachine.ASYNC; // tell StateMachine to defer next state until we call transition (in fadeOut callback above) - }, +```javascript + var StateMachine = require('javascript-state-machine'); +``` - onleavegame: function() { - $('#game').slideUp('slow', function() { - fsm.transition(); - }; - return StateMachine.ASYNC; // tell StateMachine to defer next state until we call transition (in slideUp callback above) - } +# Usage - } - }); +A state machine can be constructed using: -> NOTE: If you decide to cancel the ASYNC event, you can call `fsm.transition.cancel();` +```javascript + var fsm = new StateMachine({ + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid' }, + { name: 'freeze', from: 'liquid', to: 'solid' }, + { name: 'vaporize', from: 'liquid', to: 'gas' }, + { name: 'condense', from: 'gas', to: 'liquid' } + ], + methods: { + onMelt: function() { console.log('I melted') }, + onFreeze: function() { console.log('I froze') }, + onVaporize: function() { console.log('I vaporized') }, + onCondense: function() { console.log('I condensed') } + } + }); +``` -# State Machine Classes +... which creates an object with a current state property: -You can also turn all instances of a _class_ into an FSM by applying -the state machine functionality to the prototype, including your callbacks -in your prototype, and providing a `startup` event for use when constructing -instances: + * `fsm.state` - MyFSM = function() { // my constructor function - this.startup(); - }; +... methods to transition to a different state: - MyFSM.prototype = { + * `fsm.melt()` + * `fsm.freeze()` + * `fsm.vaporize()` + * `fsm.condense()` - onpanic: function(event, from, to) { alert('panic'); }, - onclear: function(event, from, to) { alert('all is clear'); }, +... observer methods called automatically during the lifecycle of a transition: - // my other prototype methods + * `onMelt()` + * `onFreeze()` + * `onVaporize()` + * `onCondense()` - }; +... along with the following helper methods: - StateMachine.create({ - target: MyFSM.prototype, - events: [ - { name: 'startup', from: 'none', to: 'green' }, - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ]}); + * `fsm.is(s)` - return true if state `s` is the current state + * `fsm.can(t)` - return true if transition `t` can occur from the current state + * `fsm.cannot(t)` - return true if transition `t` cannot occur from the current state + * `fsm.transitions()` - return list of transitions that are allowed from the current state + * `fsm.allTransitions()` - return list of all possible transitions + * `fsm.allStates()` - return list of all possible states +# Terminology -This should be easy to adjust to fit your appropriate mechanism for object construction. +A state machine consists of a set of [**States**](docs/states-and-transitions.md) -> NOTE: the `startup` event can be given any name, but it must be present in some form to - ensure that each instance constructed is initialized with its own unique `current` state. + * solid + * liquid + * gas -# Initialization Options +A state machine changes state by using [**Transitions**](docs/states-and-transitions.md) -How the state machine should initialize can depend on your application requirements, so -the library provides a number of simple options. + * melt + * freeze + * vaporize + * condense -By default, if you don't specify any initial state, the state machine will be in the `'none'` -state and you would need to provide an event to take it out of this state: +A state machine can perform actions during a transition by observing [**Lifecycle Events**](docs/lifecycle-events.md) - var fsm = StateMachine.create({ - events: [ - { name: 'startup', from: 'none', to: 'green' }, - { name: 'panic', from: 'green', to: 'red' }, - { name: 'calm', from: 'red', to: 'green' }, - ]}); - alert(fsm.current); // "none" - fsm.startup(); - alert(fsm.current); // "green" + * onBeforeMelt + * onAfterMelt + * onLeaveSolid + * onEnterLiquid + * ... -If you specify the name of your initial state (as in all the earlier examples), then an -implicit `startup` event will be created for you and fired when the state machine is constructed. +A state machine can also have arbitrary [**Data and Methods**](docs/data-and-methods.md). - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'panic', from: 'green', to: 'red' }, - { name: 'calm', from: 'red', to: 'green' }, - ]}); - alert(fsm.current); // "green" +Multiple instances of a state machine can be created using a [**State Machine Factory**](docs/state-machine-factory.md). -If your object already has a `startup` method you can use a different name for the initial event +# Documentation - var fsm = StateMachine.create({ - initial: { state: 'green', event: 'init' }, - events: [ - { name: 'panic', from: 'green', to: 'red' }, - { name: 'calm', from: 'red', to: 'green' }, - ]}); - alert(fsm.current); // "green" - -Finally, if you want to wait to call the initial state transition event until a later date you -can `defer` it: - - var fsm = StateMachine.create({ - initial: { state: 'green', event: 'init', defer: true }, - events: [ - { name: 'panic', from: 'green', to: 'red' }, - { name: 'calm', from: 'red', to: 'green' }, - ]}); - alert(fsm.current); // "none" - fsm.init(); - alert(fsm.current); // "green" - -Of course, we have now come full circle, this last example is pretty much functionally the -same as the first example in this section where you simply define your own startup event. - -So you have a number of choices available to you when initializing your state machine. - -> IMPORTANT NOTE: if you are using the pattern described in the previous section "State Machine - Classes", and wish to declare an `initial` state in this manner, you MUST use the `defer: true` - attribute and manually call the starting event in your constructor function. This will ensure - that each instance gets its own unique `current` state, rather than an (unwanted) shared - `current` state on the prototype object itself. - -# Handling Failures - -By default, if you try to call an event method that is not allowed in the current state, the -state machine will throw an exception. If you prefer to handle the problem yourself, you can -define a custom `error` handler: - - var fsm = StateMachine.create({ - initial: 'green', - error: function(eventName, from, to, args, errorCode, errorMessage, originalException) { - return 'event ' + eventName + ' was naughty :- ' + errorMessage; - }, - events: [ - { name: 'panic', from: 'green', to: 'red' }, - { name: 'calm', from: 'red', to: 'green' }, - ]}); - alert(fsm.calm()); // "event calm was naughty :- event not allowed in current state green" +Read more about + * [States and Transitions](docs/states-and-transitions.md) + * [Data and Methods](docs/data-and-methods.md) + * [Lifecycle Events](docs/lifecycle-events.md) + * [Asynchronous Transitions](docs/async-transitions.md) + * [Initialization](docs/initialization.md) + * [Error Handling](docs/error-handling.md) + * [State History](docs/state-history.md) + * [Visualization](docs/visualization.md) + * [State Machine Factory](docs/state-machine-factory.md) + * [Upgrading from 2.x](docs/upgrading-from-v2.md) + # Contributing - > git clone git@github.com:jakesgordon/javascript-state-machine - > cd javascript-state-machine - - > npm install # install dev dependencies - > npm start # run a local dev server - - * Source code - `state-machine.js` - * Minified code - `state-machine.min.js` (build with `npm run minify`) - * Browse demo at `/` - * Run tests in browser at `/test/` - * Run tests in console with `npm test` - * Please include tests with pull requests. - -# Related Links +You can [Contribute](docs/contributing.md) to this project with issues or pull requests. - * You can find the [code on github](https://github.com/jakesgordon/javascript-state-machine) - * You can find a [working demo here](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/example/) - * [v2.3 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2-3-0/) - * [v2.2 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2-2-0/) - * [v2.1 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2-1-0/) - * [v2.0 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v2) - * [v1.2 release announcement](http://codeincomplete.com/posts/javascript-state-machine-v1-2-0) - * [v1.0 release announcement](http://codeincomplete.com/posts/javascript-state-machine) +You might also want to support this project by purchasing a [commercial license](docs/commercial-license.md). # Release Notes -See [RELEASE NOTES](https://github.com/jakesgordon/javascript-state-machine/blob/master/RELEASE_NOTES.md) file. +See [RELEASE NOTES](RELEASE_NOTES.md) file. # License -See [LICENSE](https://github.com/jakesgordon/javascript-state-machine/blob/master/LICENSE) file. +Dual-licensed under the [LGPL](http://www.gnu.org/licenses/lgpl-3.0.html) for the open source community and also available with support +under a [commercial license](docs/commercial-license.md). # Contact diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index c0aba13..ca0b628 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,17 +1,24 @@ +Version 3.0.0 (ETA - January 2017) +---------------------------------- - FUTURE Version 3.0.0 (ETA - Early 2017) - ------------------------------ +**IMPORTANT NOTE**: this version includes **breaking changes** that will require code updates. - I know, I know, I've neglected this library for far too long, so lets get it back on track... +Please read [UPGRADING FROM 2.x](docs/upgrading-from-v2.md) for details. Highlights include: + + * Improved Construction. + * Arbitrary Data and Methods. + * Observable Transitions + * Conditional Transitions + * Promise-based Asynchronous Transitions + * Improved Transition Lifecycle Events + * State History + * Visualization + * Webpack build system + * ... + +
    +
    - ADD: Promise based async transitions - ADD: conditional transitions - ADD: observable transitions - ADD: composable state machines - ADD: better introspection - ADD: undo/redo history - ADD: use camel casing for callback/observer methods (instead of all lower case) - Version 2.4.0 (November 20 2016) -------------------------------- diff --git a/bin/examples b/bin/examples new file mode 100755 index 0000000..e726661 --- /dev/null +++ b/bin/examples @@ -0,0 +1,60 @@ +#!/usr/bin/env node + +//================================================================================================= +// +// This script is used to regenerate the example visualizations +// +//================================================================================================= + +var fs = require('fs'), + path = require('path'), + child = require('child_process'); + +//------------------------------------------------------------------------------------------------- + +fs.readdirSync('examples') + .filter(function(file) { return path.extname(file) === ".js" }) + .map(visualize); + +//------------------------------------------------------------------------------------------------- + +function visualize(example) { + var name = path.basename(example, '.js'), + fsm = require('../examples/' + example), + dot = fsm.visualize(), + svg = dot2svg(dot), + png = dot2png(dot); + console.log('visualizing examples/' + example); + fs.writeFileSync('examples/' + name + '.dot', dot); + fs.writeFileSync('examples/' + name + '.svg', svg); + fs.writeFileSync('examples/' + name + '.png', png, 'binary'); +} + +//------------------------------------------------------------------------------------------------- + +function dot2svg(dot) { + var result = child.spawnSync("dot", ["-Tsvg"], { input: dot }); + if (result.error) + dotError(result.error.errno); + return result.stdout.toString(); +} + +//------------------------------------------------------------------------------------------------- + +function dot2png(dot) { + var result = child.spawnSync("dot", ["-Tpng"], { input: dot }); + if (result.error) + dotError(result.error.errno); + return result.stdout; +} + +//------------------------------------------------------------------------------------------------- + +function dotError(errno) { + if (errno === 'ENOENT') + throw new Error("dot program not found. Install graphviz (http://graphviz.org)") + else + throw new Error("unexpected error: " + errno) +} + +//------------------------------------------------------------------------------------------------- diff --git a/bin/minify b/bin/minify new file mode 100755 index 0000000..88fe76c --- /dev/null +++ b/bin/minify @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +//================================================================================================= +// +// This script is used (by npm run build) to minify the distributed source code +// +//================================================================================================= + +var fs = require('fs-sync'), + path = require('path'), + uglify = require('uglify-js'), + target = 'dist'; + +//------------------------------------------------------------------------------------------------- + +fs.expand("lib/**/*.js") + .map(minify); + +//------------------------------------------------------------------------------------------------- + +function minify(file) { + var name = output_name(file), + expanded = path.join(target, name + '.js'), + minified = path.join(target, name + '.min.js') + + console.log('copied ' + file + ' to ' + expanded + ' and minified as ' + minified); + + fs.copy(file, expanded, { force: true }); + fs.write(minified, uglify.minify(expanded).code); +} + +function output_name(file) { + var name = path.basename(file, '.js'); + if (name === 'state-machine') + return 'state-machine' + else + return 'state-machine-' + name +} + +//------------------------------------------------------------------------------------------------- diff --git a/bower.json b/bower.json deleted file mode 100644 index 7f1f489..0000000 --- a/bower.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "javascript-state-machine", - "homepage": "https://github.com/jakesgordon/javascript-state-machine", - "authors": [ - "Jake Gordon " - ], - "description": "a simple finite state machine library", - "main": ["state-machine.js", "state-machine.min.js"], - "moduleType": [ - "amd", - "globals", - "node" - ], - "keywords": [ - "state machine", - "server", - "client" - ], - "license": "MIT", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "demo", - "index.html", - ".gitignore" - ] -} diff --git a/demo/demo.js b/demo/demo.js deleted file mode 100644 index 4789c85..0000000 --- a/demo/demo.js +++ /dev/null @@ -1,78 +0,0 @@ -Demo = function() { - - var output = document.getElementById('output'), - demo = document.getElementById('demo'), - panic = document.getElementById('panic'), - warn = document.getElementById('warn'), - calm = document.getElementById('calm'), - clear = document.getElementById('clear'), - count = 0; - - var log = function(msg, separate) { - count = count + (separate ? 1 : 0); - output.value = count + ": " + msg + "\n" + (separate ? "\n" : "") + output.value; - demo.className = fsm.current; - panic.disabled = fsm.cannot('panic'); - warn.disabled = fsm.cannot('warn'); - calm.disabled = fsm.cannot('calm'); - clear.disabled = fsm.cannot('clear'); - }; - - var fsm = StateMachine.create({ - - events: [ - { name: 'start', from: 'none', to: 'green' }, - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'green', to: 'red' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'red', to: 'green' }, - { name: 'clear', from: 'yellow', to: 'green' }, - ], - - callbacks: { - onbeforestart: function(event, from, to) { log("STARTING UP"); }, - onstart: function(event, from, to) { log("READY"); }, - - onbeforewarn: function(event, from, to) { log("START EVENT: warn!", true); }, - onbeforepanic: function(event, from, to) { log("START EVENT: panic!", true); }, - onbeforecalm: function(event, from, to) { log("START EVENT: calm!", true); }, - onbeforeclear: function(event, from, to) { log("START EVENT: clear!", true); }, - - onwarn: function(event, from, to) { log("FINISH EVENT: warn!"); }, - onpanic: function(event, from, to) { log("FINISH EVENT: panic!"); }, - oncalm: function(event, from, to) { log("FINISH EVENT: calm!"); }, - onclear: function(event, from, to) { log("FINISH EVENT: clear!"); }, - - onleavegreen: function(event, from, to) { log("LEAVE STATE: green"); }, - onleaveyellow: function(event, from, to) { log("LEAVE STATE: yellow"); }, - onleavered: function(event, from, to) { log("LEAVE STATE: red"); async(to); return StateMachine.ASYNC; }, - - ongreen: function(event, from, to) { log("ENTER STATE: green"); }, - onyellow: function(event, from, to) { log("ENTER STATE: yellow"); }, - onred: function(event, from, to) { log("ENTER STATE: red"); }, - - onchangestate: function(event, from, to) { log("CHANGED STATE: " + from + " to " + to); } - } - }); - - var async = function(to) { - pending(to, 3); - setTimeout(function() { - pending(to, 2); - setTimeout(function() { - pending(to, 1); - setTimeout(function() { - fsm.transition(); // trigger deferred state transition - }, 1000); - }, 1000); - }, 1000); - }; - - var pending = function(to, n) { log("PENDING STATE: " + to + " in ..." + n); }; - - fsm.start(); - return fsm; - -}(); - diff --git a/dist/state-machine-history.js b/dist/state-machine-history.js new file mode 100644 index 0000000..d604994 --- /dev/null +++ b/dist/state-machine-history.js @@ -0,0 +1,187 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define("StateMachineHistory", [], factory); + else if(typeof exports === 'object') + exports["StateMachineHistory"] = factory(); + else + root["StateMachineHistory"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.l = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // identity function for calling harmony imports with the correct context +/******/ __webpack_require__.i = function(value) { return value; }; + +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; + +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; + +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 1); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = function(label) { + var n, word, words = label.split(/[_-]/), result = words[0]; + for(n = 1 ; n < words.length ; n++) { + result = result + words[n].charAt(0).toUpperCase() + words[n].substring(1); + } + return result; +} + + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +//------------------------------------------------------------------------------------------------- + +var camelize = __webpack_require__(0); + +//------------------------------------------------------------------------------------------------- + +module.exports = function(options) { options = options || {}; + + var past = camelize(options.name || options.past || 'history'), + future = camelize( options.future || 'future'), + clear = camelize('clear-' + past), + back = camelize(past + '-back'), + forward = camelize(past + '-forward'), + canBack = camelize('can-' + back), + canForward = camelize('can-' + forward), + max = options.max; + + var plugin = { + + configure: function(config) { + config.addTransitionLifecycleNames(back); + config.addTransitionLifecycleNames(forward); + }, + + init: function(instance) { + instance[past] = []; + instance[future] = []; + }, + + lifecycle: function(instance, lifecycle) { + if (lifecycle.event === 'onEnterState') { + instance[past].push(lifecycle.to); + if (max && instance[past].length > max) + instance[past].shift(); + if (lifecycle.transition !== back && lifecycle.transition !== forward) + instance[future].length = 0; + } + }, + + methods: {}, + properties: {} + + } + + plugin.methods[clear] = function() { + this[past].length = 0 + this[future].length = 0 + } + + plugin.properties[canBack] = { + get: function() { + return this[past].length > 1 + } + } + + plugin.properties[canForward] = { + get: function() { + return this[future].length > 0 + } + } + + plugin.methods[back] = function() { + if (!this[canBack]) + throw Error('no history'); + var from = this[past].pop(), + to = this[past].pop(); + this[future].push(from); + this._fsm.transit(back, from, to, []); + } + + plugin.methods[forward] = function() { + if (!this[canForward]) + throw Error('no history'); + var from = this.state, + to = this[future].pop(); + this._fsm.transit(forward, from, to, []); + } + + return plugin; + +} + + +/***/ } +/******/ ]); +}); \ No newline at end of file diff --git a/dist/state-machine-history.min.js b/dist/state-machine-history.min.js new file mode 100644 index 0000000..c186024 --- /dev/null +++ b/dist/state-machine-history.min.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("StateMachineHistory",[],e):"object"==typeof exports?exports.StateMachineHistory=e():t.StateMachineHistory=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,r){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:r})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=1)}([function(t,e,n){"use strict";t.exports=function(t){var e,n=t.split(/[_-]/),r=n[0];for(e=1;ef&&t[e].shift(),r.transition!==i&&r.transition!==s&&(t[n].length=0))},methods:{},properties:{}};return a.methods[o]=function(){this[e].length=0,this[n].length=0},a.properties[u]={get:function(){return this[e].length>1}},a.properties[c]={get:function(){return this[n].length>0}},a.methods[i]=function(){if(!this[u])throw Error("no history");var t=this[e].pop(),r=this[e].pop();this[n].push(t),this._fsm.transit(i,t,r,[])},a.methods[s]=function(){if(!this[c])throw Error("no history");var t=this.state,e=this[n].pop();this._fsm.transit(s,t,e,[])},a}}])}); \ No newline at end of file diff --git a/dist/state-machine-visualize.js b/dist/state-machine-visualize.js new file mode 100644 index 0000000..531bf03 --- /dev/null +++ b/dist/state-machine-visualize.js @@ -0,0 +1,269 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define("StateMachineVisualize", [], factory); + else if(typeof exports === 'object') + exports["StateMachineVisualize"] = factory(); + else + root["StateMachineVisualize"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.l = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // identity function for calling harmony imports with the correct context +/******/ __webpack_require__.i = function(value) { return value; }; + +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; + +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; + +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 1); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = function(target, sources) { + var n, source, key; + for(n = 1 ; n < arguments.length ; n++) { + source = arguments[n]; + for(key in source) { + if (source.hasOwnProperty(key)) + target[key] = source[key]; + } + } + return target; +} + + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +//------------------------------------------------------------------------------------------------- + +var mixin = __webpack_require__(0) + +//------------------------------------------------------------------------------------------------- + +function visualize(fsm, options) { + return dotify(dotcfg(fsm, options)); +} + +//------------------------------------------------------------------------------------------------- + +function dotcfg(fsm, options) { + + options = options || {} + + var config = dotcfg.fetch(fsm), + name = options.name, + rankdir = dotcfg.rankdir(options.orientation), + states = dotcfg.states(config, options), + transitions = dotcfg.transitions(config, options), + result = { } + + if (name) + result.name = name + + if (rankdir) + result.rankdir = rankdir + + if (states && states.length > 0) + result.states = states + + if (transitions && transitions.length > 0) + result.transitions = transitions + + return result +} + +//------------------------------------------------------------------------------------------------- + +dotcfg.fetch = function(fsm) { + return (typeof fsm === 'function') ? fsm.prototype._fsm.config + : fsm._fsm.config +} + +dotcfg.rankdir = function(orientation) { + if (orientation === 'horizontal') + return 'LR'; + else if (orientation === 'vertical') + return 'TB'; +} + +dotcfg.states = function(config, options) { + var index, states = config.states; + if (!options.init) { // if not showing init transition, then slice out the implied init :from state + index = states.indexOf(config.init.from); + states = states.slice(0, index).concat(states.slice(index+1)); + } + return states; +} + +dotcfg.transitions = function(config, options) { + var n, max, transition, + init = config.init, + transitions = config.source.transitions || [], // easier to visualize using the ORIGINAL transition declarations rather than our run-time mapping + output = []; + if (options.init && init.active) + dotcfg.transition(init.name, init.from, init.to, init.dot, config, options, output) + for (n = 0, max = transitions.length ; n < max ; n++) { + transition = config.source.transitions[n] + dotcfg.transition(transition.name, transition.from, transition.to, transition.dot, config, options, output) + } + return output +} + +dotcfg.transition = function(name, from, to, dot, config, options, output) { + var n, max, wildcard = config.defaults.wildcard + + if (Array.isArray(from)) { + for(n = 0, max = from.length ; n < max ; n++) + dotcfg.transition(name, from[n], to, dot, config, options, output) + } + else if (from === wildcard || from === undefined) { + for(n = 0, max = config.states.length ; n < max ; n++) + dotcfg.transition(name, config.states[n], to, dot, config, options, output) + } + else if (to === wildcard || to === undefined) { + dotcfg.transition(name, from, from, dot, config, options, output) + } + else if (typeof to === 'function') { + // do nothing, can't display conditional transition + } + else { + output.push(mixin({}, { from: from, to: to, label: pad(name) }, dot || {})) + } + +} + +//------------------------------------------------------------------------------------------------- + +function pad(name) { + return " " + name + " " +} + +function quote(name) { + return "\"" + name + "\"" +} + +function dotify(dotcfg) { + + dotcfg = dotcfg || {}; + + var name = dotcfg.name || 'fsm', + states = dotcfg.states || [], + transitions = dotcfg.transitions || [], + rankdir = dotcfg.rankdir, + output = [], + n, max; + + output.push("digraph " + quote(name) + " {") + if (rankdir) + output.push(" rankdir=" + rankdir + ";") + for(n = 0, max = states.length ; n < max ; n++) + output.push(dotify.state(states[n])) + for(n = 0, max = transitions.length ; n < max ; n++) + output.push(dotify.edge(transitions[n])) + output.push("}") + return output.join("\n") + +} + +dotify.state = function(state) { + return " " + quote(state) + ";" +} + +dotify.edge = function(edge) { + return " " + quote(edge.from) + " -> " + quote(edge.to) + dotify.edge.attr(edge) + ";" +} + +dotify.edge.attr = function(edge) { + var n, max, key, keys = Object.keys(edge).sort(), output = []; + for(n = 0, max = keys.length ; n < max ; n++) { + key = keys[n]; + if (key !== 'from' && key !== 'to') + output.push(key + "=" + quote(edge[key])) + } + return output.length > 0 ? " [ " + output.join(" ; ") + " ]" : "" +} + +//------------------------------------------------------------------------------------------------- + +visualize.dotcfg = dotcfg; +visualize.dotify = dotify; + +//------------------------------------------------------------------------------------------------- + +module.exports = visualize; + +//------------------------------------------------------------------------------------------------- + + +/***/ } +/******/ ]); +}); \ No newline at end of file diff --git a/dist/state-machine-visualize.min.js b/dist/state-machine-visualize.min.js new file mode 100644 index 0000000..28a9d5a --- /dev/null +++ b/dist/state-machine-visualize.min.js @@ -0,0 +1 @@ +!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define("StateMachineVisualize",[],n):"object"==typeof exports?exports.StateMachineVisualize=n():t.StateMachineVisualize=n()}(this,function(){return function(t){function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var e={};return n.m=t,n.c=e,n.i=function(t){return t},n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="",n(n.s=1)}([function(t,n,e){"use strict";t.exports=function(t,n){var e,r,o;for(e=1;e0&&(u.states=s),a&&a.length>0&&(u.transitions=a),u}function i(t){return" "+t+" "}function s(t){return'"'+t+'"'}function a(t){t=t||{};var n,e,r=t.name||"fsm",o=t.states||[],i=t.transitions||[],u=t.rankdir,f=[];for(f.push("digraph "+s(r)+" {"),u&&f.push(" rankdir="+u+";"),n=0,e=o.length;n "+s(t.to)+a.edge.attr(t)+";"},a.edge.attr=function(t){var n,e,r,o=Object.keys(t).sort(),i=[];for(n=0,e=o.length;n0?" [ "+i.join(" ; ")+" ]":""},r.dotcfg=o,r.dotify=a,t.exports=r}])}); \ No newline at end of file diff --git a/dist/state-machine.js b/dist/state-machine.js new file mode 100644 index 0000000..6ac51b3 --- /dev/null +++ b/dist/state-machine.js @@ -0,0 +1,644 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define("StateMachine", [], factory); + else if(typeof exports === 'object') + exports["StateMachine"] = factory(); + else + root["StateMachine"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.l = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // identity function for calling harmony imports with the correct context +/******/ __webpack_require__.i = function(value) { return value; }; + +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; + +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; + +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 6); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = function(target, sources) { + var n, source, key; + for(n = 1 ; n < arguments.length ; n++) { + source = arguments[n]; + for(key in source) { + if (source.hasOwnProperty(key)) + target[key] = source[key]; + } + } + return target; +} + + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +//------------------------------------------------------------------------------------------------- + +var mixin = __webpack_require__(0); + +//------------------------------------------------------------------------------------------------- + +module.exports = { + + build: function(target, config) { + var n, max, plugin, plugins = config.plugins; + for(n = 0, max = plugins.length ; n < max ; n++) { + plugin = plugins[n]; + if (plugin.methods) + mixin(target, plugin.methods); + if (plugin.properties) + Object.defineProperties(target, plugin.properties); + } + }, + + hook: function(fsm, name, additional) { + var n, max, method, plugin, + plugins = fsm.config.plugins, + args = [fsm.context]; + + if (additional) + args = args.concat(additional) + + for(n = 0, max = plugins.length ; n < max ; n++) { + plugin = plugins[n] + method = plugins[n][name] + if (method) + method.apply(plugin, args); + } + } + +} + +//------------------------------------------------------------------------------------------------- + + +/***/ }, +/* 2 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = function(label) { + var n, word, words = label.split(/[_-]/), result = words[0]; + for(n = 1 ; n < words.length ; n++) { + result = result + words[n].charAt(0).toUpperCase() + words[n].substring(1); + } + return result; +} + + +/***/ }, +/* 3 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +//------------------------------------------------------------------------------------------------- + +var mixin = __webpack_require__(0), + camelize = __webpack_require__(2); + +//------------------------------------------------------------------------------------------------- + +function Config(options, StateMachine) { + + options = options || {}; + + this.source = options; // preserving original options helps with visualize plugin + this.defaults = StateMachine.defaults; + this.states = []; + this.transitions = []; + this.map = {}; + this.lifecycle = this.configureLifecycle(); + this.init = this.configureInitTransition(options.init); + this.data = this.configureData(options.data); + this.methods = this.configureMethods(options.methods); + + this.map[this.defaults.wildcard] = {}; + + this.configureTransitions(options.transitions || []); + + this.plugins = this.configurePlugins(options.plugins, StateMachine.plugin); + +} + +//------------------------------------------------------------------------------------------------- + +mixin(Config.prototype, { + + addState: function(name) { + if (!this.map[name]) { + this.states.push(name); + this.addStateLifecycleNames(name); + this.map[name] = {}; + } + }, + + addStateLifecycleNames: function(name) { + this.lifecycle.onEnter[name] = camelize('on-enter-' + name); + this.lifecycle.onLeave[name] = camelize('on-leave-' + name); + this.lifecycle.on[name] = camelize('on-' + name); + }, + + addTransition: function(name) { + if (this.transitions.indexOf(name) < 0) { + this.transitions.push(name); + this.addTransitionLifecycleNames(name); + } + }, + + addTransitionLifecycleNames: function(name) { + this.lifecycle.onBefore[name] = camelize('on-before-' + name); + this.lifecycle.onAfter[name] = camelize('on-after-' + name); + this.lifecycle.on[name] = camelize('on-' + name); + }, + + mapTransition: function(transition) { + var name = transition.name, + from = transition.from, + to = transition.to; + this.addState(from); + if (typeof to !== 'function') + this.addState(to); + this.addTransition(name); + this.map[from][name] = transition; + return transition; + }, + + configureLifecycle: function() { + return { + onBefore: { transition: camelize('on-before-transition') }, + onAfter: { transition: camelize('on-after-transition') }, + onEnter: { state: camelize('on-enter-state') }, + onLeave: { state: camelize('on-leave-state') }, + on: { transition: camelize('on-transition') } + }; + }, + + configureInitTransition: function(init) { + if (typeof init === 'string') { + return this.mapTransition(mixin({}, this.defaults.init, { to: init, active: true })); + } + else if (typeof init === 'object') { + return this.mapTransition(mixin({}, this.defaults.init, init, { active: true })); + } + else { + this.addState(this.defaults.init.from); + return this.defaults.init; + } + }, + + configureData: function(data) { + if (typeof data === 'function') + return data; + else if (typeof data === 'object') + return function() { return data; } + else + return function() { return {}; } + }, + + configureMethods: function(methods) { + return methods || {}; + }, + + configurePlugins: function(plugins, builtin) { + plugins = plugins || []; + var n, max, plugin; + for(n = 0, max = plugins.length ; n < max ; n++) { + plugin = plugins[n]; + if (typeof plugin === 'function') + plugins[n] = plugin = plugin() + if (plugin.configure) + plugin.configure(this); + } + return plugins + }, + + configureTransitions: function(transitions) { + var i, n, transition, from, to, wildcard = this.defaults.wildcard; + for(n = 0 ; n < transitions.length ; n++) { + transition = transitions[n]; + from = Array.isArray(transition.from) ? transition.from : [transition.from || wildcard] + to = transition.to || wildcard; + for(i = 0 ; i < from.length ; i++) { + this.mapTransition({ name: transition.name, from: from[i], to: to }); + } + } + }, + + transitionFor: function(state, transition) { + var wildcard = this.defaults.wildcard; + return this.map[state][transition] || + this.map[wildcard][transition]; + }, + + transitionsFor: function(state) { + var wildcard = this.defaults.wildcard; + return Object.keys(this.map[state]).concat(Object.keys(this.map[wildcard])); + }, + + allStates: function() { + return this.states; + }, + + allTransitions: function() { + return this.transitions; + } + +}); + +//------------------------------------------------------------------------------------------------- + +module.exports = Config; + +//------------------------------------------------------------------------------------------------- + + +/***/ }, +/* 4 */ +/***/ function(module, exports, __webpack_require__) { + + +var mixin = __webpack_require__(0), + Exception = __webpack_require__(5), + plugin = __webpack_require__(1), + UNOBSERVED = [ null, [] ]; + +//------------------------------------------------------------------------------------------------- + +function JSM(context, config) { + this.context = context; + this.config = config; + this.state = config.init.from; + this.observers = [context]; +} + +//------------------------------------------------------------------------------------------------- + +mixin(JSM.prototype, { + + init: function(args) { + mixin(this.context, this.config.data.apply(this.context, args)); + plugin.hook(this, 'init'); + if (this.config.init.active) + return this.fire(this.config.init.name, []); + }, + + is: function(state) { + return Array.isArray(state) ? (state.indexOf(this.state) >= 0) : (this.state === state); + }, + + isPending: function() { + return this.pending; + }, + + can: function(transition) { + return !this.isPending() && !!this.seek(transition); + }, + + cannot: function(transition) { + return !this.can(transition); + }, + + allStates: function() { + return this.config.allStates(); + }, + + allTransitions: function() { + return this.config.allTransitions(); + }, + + transitions: function() { + return this.config.transitionsFor(this.state); + }, + + seek: function(transition, args) { + var wildcard = this.config.defaults.wildcard, + entry = this.config.transitionFor(this.state, transition), + to = entry && entry.to; + if (typeof to === 'function') + return to.apply(this.context, args); + else if (to === wildcard) + return this.state + else + return to + }, + + fire: function(transition, args) { + return this.transit(transition, this.state, this.seek(transition, args), args); + }, + + transit: function(transition, from, to, args) { + + var lifecycle = this.config.lifecycle, + changed = from !== to; + + if (!to) + return this.context.onInvalidTransition(transition, from, to); + + if (this.isPending()) + return this.context.onPendingTransition(transition, from, to); + + this.config.addState(to); // might need to add this state if it's unknown (e.g. conditional transition or goto) + + this.beginTransit(); + + args.unshift({ // this context will be passed to each lifecycle event observer + transition: transition, + from: from, + to: to, + fsm: this.context + }); + + return this.observeEvents([ + this.observersForEvent(lifecycle.onBefore.transition), + this.observersForEvent(lifecycle.onBefore[transition]), + changed ? this.observersForEvent(lifecycle.onLeave.state) : UNOBSERVED, + changed ? this.observersForEvent(lifecycle.onLeave[from]) : UNOBSERVED, + this.observersForEvent(lifecycle.on.transition), + changed ? [ 'doTransit', [ this ] ] : UNOBSERVED, + changed ? this.observersForEvent(lifecycle.onEnter.state) : UNOBSERVED, + changed ? this.observersForEvent(lifecycle.onEnter[to]) : UNOBSERVED, + changed ? this.observersForEvent(lifecycle.on[to]) : UNOBSERVED, + this.observersForEvent(lifecycle.onAfter.transition), + this.observersForEvent(lifecycle.onAfter[transition]), + this.observersForEvent(lifecycle.on[transition]) + ], args); + }, + + beginTransit: function() { this.pending = true; }, + endTransit: function(result) { this.pending = false; return result; }, + doTransit: function(lifecycle) { this.state = lifecycle.to; }, + + observe: function(args) { + if (args.length === 2) { + var observer = {}; + observer[args[0]] = args[1]; + this.observers.push(observer); + } + else { + this.observers.push(args[0]); + } + }, + + observersForEvent: function(event) { // TODO: this could be cached + var n = 0, max = this.observers.length, observer, result = []; + for( ; n < max ; n++) { + observer = this.observers[n]; + if (observer[event]) + result.push(observer); + } + return [ event, result, true ] + }, + + observeEvents: function(events, args, previousEvent) { + if (events.length === 0) { + return this.endTransit(true); + } + + var event = events[0][0], + observers = events[0][1], + pluggable = events[0][2]; + + args[0].event = event; + if (event && pluggable && event !== previousEvent) + plugin.hook(this, 'lifecycle', args); + + if (observers.length === 0) { + events.shift(); + return this.observeEvents(events, args, event); + } + else { + var observer = observers.shift(), + result = observer[event].apply(observer, args); + if (result && typeof result.then === 'function') { + return result.then(this.observeEvents.bind(this, events, args, event)) + .catch(this.endTransit.bind(this)) + } + else if (result === false) { + return this.endTransit(false); + } + else { + return this.observeEvents(events, args, event); + } + } + }, + + onInvalidTransition: function(transition, from, to) { + throw new Exception("transition is invalid in current state", transition, from, to, this.state); + }, + + onPendingTransition: function(transition, from, to) { + throw new Exception("transition is invalid while previous transition is still in progress", transition, from, to, this.state); + } + +}); + +//------------------------------------------------------------------------------------------------- + +module.exports = JSM; + +//------------------------------------------------------------------------------------------------- + + +/***/ }, +/* 5 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = function(message, transition, from, to, current) { + this.message = message; + this.transition = transition; + this.from = from; + this.to = to; + this.current = current; +} + + +/***/ }, +/* 6 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +//----------------------------------------------------------------------------------------------- + +var mixin = __webpack_require__(0), + camelize = __webpack_require__(2), + plugin = __webpack_require__(1), + Config = __webpack_require__(3), + JSM = __webpack_require__(4); + +//----------------------------------------------------------------------------------------------- + +var PublicMethods = { + is: function(state) { return this._fsm.is(state) }, + can: function(transition) { return this._fsm.can(transition) }, + cannot: function(transition) { return this._fsm.cannot(transition) }, + observe: function() { return this._fsm.observe(arguments) }, + transitions: function() { return this._fsm.transitions() }, + allTransitions: function() { return this._fsm.allTransitions() }, + allStates: function() { return this._fsm.allStates() }, + onInvalidTransition: function(t, from, to) { return this._fsm.onInvalidTransition(t, from, to) }, + onPendingTransition: function(t, from, to) { return this._fsm.onPendingTransition(t, from, to) }, +} + +var PublicProperties = { + state: { + configurable: false, + enumerable: true, + get: function() { + return this._fsm.state; + }, + set: function(state) { + throw Error('use transitions to change state') + } + } +} + +//----------------------------------------------------------------------------------------------- + +function StateMachine(options) { + return apply(this || {}, options); +} + +function factory() { + var cstor, options; + if (typeof arguments[0] === 'function') { + cstor = arguments[0]; + options = arguments[1] || {}; + } + else { + cstor = function() { this._fsm.apply(this, arguments) }; + options = arguments[0] || {}; + } + var config = new Config(options, StateMachine); + build(cstor.prototype, config); + cstor.prototype._fsm.config = config; // convenience access to shared config without needing an instance + return cstor; +} + +//------------------------------------------------------------------------------------------------- + +function apply(instance, options) { + var config = new Config(options, StateMachine); + build(instance, config); + instance._fsm(); + return instance; +} + +function build(target, config) { + if ((typeof target !== 'object') || Array.isArray(target)) + throw Error('StateMachine can only be applied to objects'); + plugin.build(target, config); + Object.defineProperties(target, PublicProperties); + mixin(target, PublicMethods); + mixin(target, config.methods); + config.allTransitions().forEach(function(transition) { + target[camelize(transition)] = function() { + return this._fsm.fire(transition, [].slice.call(arguments)) + } + }); + target._fsm = function() { + this._fsm = new JSM(this, config); + this._fsm.init(arguments); + } +} + +//----------------------------------------------------------------------------------------------- + +StateMachine.version = '3.0.0'; +StateMachine.factory = factory; +StateMachine.apply = apply; +StateMachine.defaults = { + wildcard: '*', + init: { + name: 'init', + from: 'none' + } +} + +//=============================================================================================== + +module.exports = StateMachine; + + +/***/ } +/******/ ]); +}); \ No newline at end of file diff --git a/dist/state-machine.min.js b/dist/state-machine.min.js new file mode 100644 index 0000000..ddf671a --- /dev/null +++ b/dist/state-machine.min.js @@ -0,0 +1 @@ +!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define("StateMachine",[],n):"object"==typeof exports?exports.StateMachine=n():t.StateMachine=n()}(this,function(){return function(t){function n(e){if(i[e])return i[e].exports;var s=i[e]={i:e,l:!1,exports:{}};return t[e].call(s.exports,s,s.exports,n),s.l=!0,s.exports}var i={};return n.m=t,n.c=i,n.i=function(t){return t},n.d=function(t,i,e){n.o(t,i)||Object.defineProperty(t,i,{configurable:!1,enumerable:!0,get:e})},n.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(i,"a",i),i},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="",n(n.s=6)}([function(t,n,i){"use strict";t.exports=function(t,n){var i,e,s;for(i=1;i=0:this.state===t},isPending:function(){return this.pending},can:function(t){return!this.isPending()&&!!this.seek(t)},cannot:function(t){return!this.can(t)},allStates:function(){return this.config.allStates()},allTransitions:function(){return this.config.allTransitions()},transitions:function(){return this.config.transitionsFor(this.state)},seek:function(t,n){var i=this.config.defaults.wildcard,e=this.config.transitionFor(this.state,t),s=e&&e.to;return"function"==typeof s?s.apply(this.context,n):s===i?this.state:s},fire:function(t,n){return this.transit(t,this.state,this.seek(t,n),n)},transit:function(t,n,i,e){var s=this.config.lifecycle,r=n!==i;return i?this.isPending()?this.context.onPendingTransition(t,n,i):(this.config.addState(i),this.beginTransit(),e.unshift({transition:t,from:n,to:i,fsm:this.context}),this.observeEvents([this.observersForEvent(s.onBefore.transition),this.observersForEvent(s.onBefore[t]),r?this.observersForEvent(s.onLeave.state):a,r?this.observersForEvent(s.onLeave[n]):a,this.observersForEvent(s.on.transition),r?["doTransit",[this]]:a,r?this.observersForEvent(s.onEnter.state):a,r?this.observersForEvent(s.onEnter[i]):a,r?this.observersForEvent(s.on[i]):a,this.observersForEvent(s.onAfter.transition),this.observersForEvent(s.onAfter[t]),this.observersForEvent(s.on[t])],e)):this.context.onInvalidTransition(t,n,i)},beginTransit:function(){this.pending=!0},endTransit:function(t){return this.pending=!1,t},doTransit:function(t){this.state=t.to},observe:function(t){if(2===t.length){var n={};n[t[0]]=t[1],this.observers.push(n)}else this.observers.push(t[0])},observersForEvent:function(t){for(var n,i=0,e=this.observers.length,s=[];i You should be familiar with the state machine [Lifecycle Events](lifecycle-events.md) before reading this article. + +Sometimes, you need to execute some asynchronous code during a state transition and ensure the new +state is not entered until your code has completed. A good example of this is when you transition +out of a state and want to gradually fade a UI component away, or slide it off the screen, and +don't want to transition to the next state until after that animation has completed. + +You can achieve this by returning a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) +object from any of the [Lifecycle Events](lifecycle-events.md). + +Returning a Promise from a lifecycle event will cause the lifecycle for that transition to +pause. It can be continued by resolving the promise, or cancelled by rejecting the promise. + +For example (using jQuery effects): + +```javascript + var fsm = new StateMachine({ + + init: 'menu', + + transitions: [ + { name: 'play', from: 'menu', to: 'game' }, + { name: 'quit', from: 'game', to: 'menu' } + ], + + methods: { + + onEnterMenu: function() { + return new Promise(function(resolve, reject) { + $('#menu').fadeIn('fast', resolve) + }) + }, + + onEnterGame: function() { + return new Promise(function(resolve, reject) { + $('#game').fadeIn('fast', resolve) + }) + }, + + onLeaveMenu: function() { + return new Promise(function(resolve, reject) { + $('#menu').fadeOut('fast', resolve) + }) + }, + + onLeaveGame: function() { + return new Promise(function(resolve, reject) { + $('#game').fadeOut('fast', resolve) + }) + } + } + + }) +``` + +> Be sure that you always resolve (or reject) your Promise eventually, otherwise the state + machine will be stuck forever within that pending transition. diff --git a/docs/commercial-license.md b/docs/commercial-license.md new file mode 100644 index 0000000..af8b6cd --- /dev/null +++ b/docs/commercial-license.md @@ -0,0 +1,180 @@ +# Javascript State Machine Commercial License + +All `javascript-state-machine` features are available in both the open source and the commercially +licensed versions. The commercial license allows for commercial use and provides priority support. + +| | Open Source | | Commercial License | +|--------------------------|---------------|---|-----------------------------| +| All Existing v2 Features | ✔ | | ✔ | +| Promise-based Async | ✔ | | ✔ | +| Observable Transitions | ✔ | | ✔ | +| Conditional Transitions | ✔ | | ✔ | +| State History | ✔ | | ✔ | +| Support | Github Issues | | **Priority Email (1 year)** | +| License | LGPL | | **Commercial** | +| Price | Free | | **$149** | + +## PURCHASE NOW + +A commercial license purchasing page will be available soon. + +Please email [jake@codeincomplete.com](mailto:jake@codeincomplete.com) for more details. + +## Commercial License Summary + + * License does not expire + * Commercial use allowed + * Can be used for unlimited projects + * Can modify source-code but cannot distribute modifications (derivative works) + * Email support provided for 1 year (from time of purchase) + +## Commercial License Example + + + Javascript State Machine - Terms and Conditions. + + 1. Preamble. + + This Agreement, signed on [DATE] (hereinafter: Effective Date) governs the relationship + between [COMPANY], a Business Entity, (hereinafter: Licensee) and + Jake Gordon, a private person (hereinafter: Licensor). This Agreement sets the terms, + rights, restrictions and obligations on using Javascript State Machine + (hereinafter: The Software) created and owned by Licensor, as detailed herein + + 2. License Grant. + + Licensor hereby grants Licensee a Personal, Non-assignable & non-transferable, Perpetual, + Commercial, Royalty free, Including the rights to create but not distribute derivative + works, Non-exclusive license, all with accordance with the terms set forth and other legal + restrictions set forth in 3rd party software used while running Software. + + 2.1. Limited: Licensee may use Software for the purpose of: + + 2.1.1. Running Software on Licensee’s Website[s] and Server[s]; + 2.1.2. Allowing 3rd Parties to run Software on Licensee’s Website[s] and Server[s]; + 2.1.3. Publishing Software’s output to Licensee and 3rd Parties; + 2.1.4. Distribute verbatim copies of Software’s output (including compiled binaries); + 2.1.5. Modify Software to suit Licensee’s needs and specifications. + + 2.2. This license is granted perpetually, as long as you do not materially breach it. + 2.3. Binary Restricted: Licensee may sublicense Software as a part of a larger work + containing more than Software, distributed solely in Object or Binary form under a + personal, non-sublicensable, limited license. Such redistribution shall be limited + to unlimited codebases. + 2.4. Non Assignable & Non-Transferable: Licensee may not assign or transfer his rights + and duties under this license. + 2.5. Commercial, Royalty Free: Licensee may use Software for any purpose, including + paid-services, without any royalties + 2.6. Including the Right to Create Derivative Works: Licensee may create derivative works + based on Software, including amending Software’s source code, modifying it, integrating + it into a larger work or removing portions of Software, as long as no distribution of + the derivative works is made + 2.7. With support & maintenance: Licensor shall provide Licensee support and maintenance as follows - + + 1 year (from time of purchase) email support with 48hr response time + + 3. Term & Termination: The Term of this license shall be until terminated. Licensor may + terminate this Agreement, including Licensee’s license in the case where Licensee: + + 3.1. became insolvent or otherwise entered into any liquidation process; or + 3.2. exported The Software to any jurisdiction where licensor may not enforce his + rights under this agreements in; or + 3.3. Licensee was in breach of any of this license's terms and conditions and such + breach was not cured, immediately upon notification; or + 3.4. Licensee in breach of any of the terms of clause 2 to this license; or + 3.5. Licensee otherwise entered into any arrangement which caused Licensor to be + unable to enforce his rights under this License. + + 4. Payment: In consideration of the License granted under clause 2, Licensee shall pay + Licensor a fee, via Credit-Card, PayPal or any other mean which Licensor may deem + adequate. Failure to perform payment shall construe as material breach of this Agreement. + + 5. Upgrades, Updates and Fixes: Licensor may provide Licensee, from time to time, with + Upgrades, Updates or Fixes, as detailed herein and according to his sole discretion. + Licensee hereby warrants to keep The Software up-to-date and install all relevant + updates and fixes, and may, at his sole discretion, purchase upgrades, according to + the rates set by Licensor. Licensor shall provide any update or Fix free of charge; + however, nothing in this Agreement shall require Licensor to provide Updates or Fixes. + + 5.1. Upgrades: for the purpose of this license, an Upgrade shall be a material amendment + in The Software, which contains new features and or major performance improvements + and shall be marked as a new version number. For example, should Licensee purchase + The Software under version 1.X.X, an upgrade shall commence under number 2.0.0. + 5.2. Updates: for the purpose of this license, an update shall be a minor amendment in The + Software, which may contain new features or minor improvements and shall be marked as + a new sub-version number. For example, should Licensee purchase The Software under + version 1.1.X, an upgrade shall commence under number 1.2.0. + 5.3. Fix: for the purpose of this license, a fix shall be a minor amendment in The + Software, intended to remove bugs or alter minor features which impair the The + Software's functionality. A fix shall be marked as a new sub-sub-version number. + For example, should Licensee purchase Software under version 1.1.1, an upgrade + shall commence under number 1.1.2. + + 6. Support: Software is provided with limited support, as detailed in the Software’s SLA + detailed under the License Grant. Licensor shall provide support via electronic mail + and on regular business days and hours. + + 6.1. Bug Notification: Licensee may provide Licensor of details regarding any bug, + defect or failure in The Software promptly and with no delay from such event; + Licensee shall comply with Licensor's request for information regarding bugs, + defects or failures and furnish him with information, screenshots and try to + reproduce such bugs, defects or failures. + 6.2. Feature Request: Licensee may request additional features in Software, provided, + however, that (i) Licensee shall waive any claim or right in such feature should + feature be developed by Licensor; (ii) Licensee shall be prohibited from developing + the feature, or disclose such feature request, or feature, to any 3rd party directly + competing with Licensor or any 3rd party which may be, following the development + of such feature, in direct competition with Licensor; (iii) Licensee warrants that + feature does not infringe any 3rd party patent, trademark, trade-secret or any + other intellectual property right; and (iv) Licensee developed, envisioned or + created the feature solely by himself. + + 7. Liability: To the extent permitted under Law, The Software is provided under an + AS-IS basis. Licensor shall never, and without any limit, be liable for any damage, + cost, expense or any other payment incurred by Licensee as a result of Software’s + actions, failure, bugs and/or any other interaction between The Software and + Licensee’s end-equipment, computers, other software or any 3rd party, end-equipment, + computer or services. Moreover, Licensor shall never be liable for any defect in + source code written by Licensee when relying on The Software or using The Software’s + source code. + + 8. Warranty: + 8.1. Intellectual Property: Licensor hereby warrants that The Software does not violate + or infringe any 3rd party claims in regards to intellectual property, patents and/or + trademarks and that to the best of its knowledge no legal action has been taken + against it for any infringement or violation of any 3rd party intellectual + property rights. + 8.2. No-Warranty: The Software is provided without any warranty; Licensor hereby + disclaims any warranty that The Software shall be error free, without defects + or code which may cause damage to Licensee’s computers or to Licensee, and + that Software shall be functional. Licensee shall be solely liable to any + damage, defect or loss incurred as a result of operating software and undertake + the risks contained in running The Software on License’s Server[s] and Website[s]. + 8.3. Prior Inspection: Licensee hereby states that he inspected The Software thoroughly + and found it satisfactory and adequate to his needs, that it does not interfere + with his regular operation and that it does meet the standards and scope of his + computer systems and architecture. Licensee found that The Software interacts with + his development, website and server environment and that it does not infringe any of + End User License Agreement of any software Licensee may use in performing his + services. Licensee hereby waives any claims regarding The Software's + incompatibility, performance, results and features, and warrants that he + inspected the The Software. + + 9. No Refunds: Licensee warrants that he inspected The Software according to + clause 7(c) and that it is adequate to his needs. Accordingly, as The Software + is intangible goods, Licensee shall not be, ever, entitled to any refund, rebate, + compensation or restitution for any reason whatsoever, even if The Software + contains material flaws. + + 10. Indemnification: Licensee hereby warrants to hold Licensor harmless and indemnify + Licensor for any lawsuit brought against it in regards to Licensee’s use of The + Software in means that violate, breach or otherwise circumvent this license, + Licensor's intellectual property rights or Licensor's title in The Software. + Licensor shall promptly notify Licensee in case of such legal action and request + Licensee’s consent prior to any settlement in relation to such lawsuit or claim. + + 11. Governing Law, Jurisdiction: Licensee hereby agrees not to initiate class-action + lawsuits against Licensor in relation to this license and to compensate Licensor + for any legal fees, cost or attorney fees should any claim brought by Licensee + against Licensor be denied, in part or in full. + diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..01e5e74 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,59 @@ +# Contributing + +The `javascript-state-machine` library is built using: + + * [Webpack 2](https://webpack.js.org/concepts/) - for bundling javascript modules together + * [UglifyJS2](https://github.com/mishoo/UglifyJS2) - for minifying bundled javascript files + * [Ava](https://github.com/avajs/ava) - for testing + +The directory structure includes: + +```shell + /bin # - build scripts + /dist # - minified bundles for distribution + /docs # - documentation + /examples # - example visualizations + /lib # - bundled source code for npm + /src # - source code + /test # - unit tests + + package.json # - npm configuration + webpack.config.js # - webpack configuration + + LICENSE # - the project licensing terms + README.md # - the project readme + RELEASE_NOTES.md # - the project release notes + +``` + +Build time dependencies can be installed using npm: + +```shell + > npm install +``` + +A number of npm scripts are available: + +```shell + > npm run test # run unit tests + > npm run build # bundle and minify files for distribution + > npm run watch # run tests if source files change +``` + +## Source Code + +The source code is written in es5 syntax and should be supported by all [es5 compatible browsers](http://caniuse.com/#feat=es5). +[Babel](https://babeljs.io/) is **NOT** used for this project. Webpack is used to +bundle modules together for distribution. + +## Submitting Pull Requests + +Generally speaking, please raise an issue first and lets discuss the problem and the +proposed solution. The next step would be a pull-request - fantastic and thank you for helping out - but +please try to... + + * ensure the tests pass (`npm test`). + * rebuild distribution files (`npm run build`). + * include tests for your changes. + * include documentation for your changes. + * include a great commit message. diff --git a/docs/data-and-methods.md b/docs/data-and-methods.md new file mode 100644 index 0000000..28aa7a6 --- /dev/null +++ b/docs/data-and-methods.md @@ -0,0 +1,64 @@ +# Data and Methods + +In addition to [States](states-and-transitions.md) and [Transitions](states-and-transitions.md), a state machine can +also contain arbitrary data and methods: + +```javascript + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' } + ], + data: { + color: 'red' + }, + methods: { + describe: function() { + console.log('I am ' + this.color); + } + } + }); + + fsm.state; // 'A' + fsm.color; // 'red' + fsm.describe(); // 'I am red' +``` + +## Data and State Machine Factories + +If you are constructing multiple instances from a [State Machine Factory](state-machine-factory.md) then the +`data` object will be shared amongst them. This is almost certainly **NOT** what you want! To +ensure that each instance gets unique data you should use a `data` method instead: + +```javascript + var FSM = StateMachine.factory({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' } + ], + data: function(color) { // <-- use a method that can be called for each instance + return { + color: color + } + }, + methods: { + describe: function() { + console.log('I am ' + this.color); + } + } + }); + + var a = new FSM('red'), + b = new FSM('blue'); + + a.state; // 'A' + b.state; // 'A' + + a.color; // 'red' + b.color; // 'blue' + + a.describe(); // 'I am red' + b.describe(); // 'I am blue' +``` + +> NOTE: that arguments used when constructing each instance are passed thru to the `data` method directly. diff --git a/docs/error-handling.md b/docs/error-handling.md new file mode 100644 index 0000000..c79f14c --- /dev/null +++ b/docs/error-handling.md @@ -0,0 +1,56 @@ +# Error Handling + +## Invalid Transitions + +By default, if you try to fire a transition that is not allowed in the current state, the +state machine will throw an exception. If you prefer to handle the problem yourself, you can +define a custom `onInvalidTransition` handler: + +```javascript + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'reset', from: 'B', to: 'A' } + ], + methods: { + onInvalidTransition: function(transition, from, to) { + throw new Exception("transition not allowed from that state"); + } + } + }); + + fsm.state; // 'A' + fsm.can('step'); // true + fsm.can('reset'); // false + + fsm.reset(); // <-- throws "transition not allowed from that state" +``` + +## Pending Transitions + +By default, if you try to fire a transition during a [Lifecycle Event](lifecycle-events.md) for a +pending transition, the state machine will throw an exception. If you prefer to handle the problem +yourself, you can define a custom `onPendingTransition` handler: + +```javascript + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' } + ], + methods: { + onLeaveA: function() { + this.step(); // <-- uh oh, trying to transition from within a lifecycle event is not allowed + }, + onPendingTransition: function(transition, from, to) { + throw new Exception("transition already in progress"); + } + } + }); + + fsm.state; // 'A' + fsm.can('step'), // true + fsm.step(); // <-- throws "transition already in progress" +``` diff --git a/docs/initialization.md b/docs/initialization.md new file mode 100644 index 0000000..39151ff --- /dev/null +++ b/docs/initialization.md @@ -0,0 +1,57 @@ +# Initialization Options + +## Explicit Init Transition + +By default, if you don't specify an initial state, the state machine will be in the `none` +state, no lifecycle events will fire during construction, and you will need to provide an +explicit transition to advance out of this state: + +```javascript + var fsm = new StateMachine({ + transitions: [ + { name: 'init', from: 'none', to: 'A' }, + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' } + ] + }); + fsm.state; // 'none' + fsm.init(); // 'init()' transition is fired explicitly + fsm.state; // 'A' +``` + +## Implicit Init Transition + +If you specify the name of your initial state (as in most of the examples in this documentation), +then an implicit `init` transition will be created for you and fired (along with appropriate +lifecycle events) when the state machine is constructed. + +> This is the most common initialization strategy, and the one you should use 90% of the time + +```javascript + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' } + ] + }); // 'init()' transition fires from 'none' to 'A' during construction + fsm.state; // 'A' +``` + +## Initialization and State Machine Factories + +For [State Machine Factories](state-machine-factory.md), the `init` transition +is triggered for each constructed instance. + +```javascript + var FSM = StateMachine.factory({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' } + ] + }); + + var fsm1 = new FSM(), // 'init()' transition fires from 'none' to 'A' for fsm1 + fsm2 = new FSM(); // 'init()' transition fires from 'none' to 'A' for fsm2 +``` diff --git a/docs/lifecycle-events.md b/docs/lifecycle-events.md new file mode 100644 index 0000000..bb3381b --- /dev/null +++ b/docs/lifecycle-events.md @@ -0,0 +1,148 @@ +# Lifecycle Events + +In order to track or perform an action when a transition occurs, five +general-purpose lifecycle events can be observed: + + * `onBeforeTransition` - fired before any transition + * `onLeaveState` - fired when leaving any state + * `onTransition` - fired during any transition + * `onEnterState` - fired when entering any state + * `onAfterTransition` - fired after any transition + +In addition to the general-purpose events, transitions can be observed +using your specific transition and state names: + + * `onBefore` - fired before a specific TRANSITION begins + * `onLeave` - fired when leaving a specific STATE + * `onEnter` - fired when entering a specific STATE + * `onAfter` - fired after a specific TRANSITION completes + +For convenience, the 2 most useful events can be shortened: + + * `on` - convenience shorthand for `onAfter` + * `on` - convenience shorthand for `onEnter` + +## Observing Lifecycle Events + +Individual lifecycle events can be observed using an observer method: + +```javascript + fsm.observe('onStep', function() { + console.log('stepped'); + }); +``` + +Multiple events can be observed using an observer object: + +```javascript + fsm.observe({ + onStep: function() { console.log('stepped'); } + onA: function() { console.log('entered state A'); } + onB: function() { console.log('entered state B'); } + }); +``` + +A state machine always observes its own lifecycle events: + +```javascript + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' } + ], + methods: { + onStep: function() { console.log('stepped'); } + onA: function() { console.log('entered state A'); } + onB: function() { console.log('entered state B'); } + } + }); +``` + +## Lifecycle Event Arguments + +Observers will be passed a single argument containing a `lifecycle` object with the following attributes: + + * **transition** - the transition name + * **from** - the previous state + * **to** - the next state + +In addition to the `lifecycle` argument, the observer will receive any arbitrary arguments passed +into the transition method + +```javascript + var fsm = new StateMachine({ + transitions: [ + { name: 'step', from: 'A', to: 'B' } + ], + methods: { + onTransition: function(lifecycle, arg1, arg2) { + console.log(lifecycle.transition); // 'step' + console.log(lifecycle.from); // 'A' + console.log(lifecycle.to); // 'B' + console.log(arg1); // 42 + console.log(arg2); // 'hello' + } + } + }); + + fsm.step(42, 'hello'); +``` + +## Lifecycle Event Names + +Lifecycle event names always use standard javascipt camelCase, even if your transition and +state names do not: + +```javascript + var fsm = new StateMachine({ + transitions: [ + { name: 'do-with-dash', from: 'has-dash', to: 'has_underscore' }, + { name: 'do_with_underscore', from: 'has_underscore', to: 'alreadyCamelized' }, + { name: 'doAlreadyCamelized', from: 'alreadyCamelize', to: 'has-dash' } + ], + methods: { + onBeforeDoWithDash: function() { /* ... */ }, + onBeforeDoWithUnderscore: function() { /* ... */ }, + onBeforeDoAlreadyCamelized: function() { /* ... */ }, + onLeaveHasDash: function() { /* ... */ }, + onLeaveHasUnderscore: function() { /* ... */ }, + onLeaveAlreadyCamelized: function() { /* ... */ }, + onEnterHasDash: function() { /* ... */ }, + onEnterHasUnderscore: function() { /* ... */ }, + onEnterAlreadyCamelized: function() { /* ... */ }, + onAfterDoWithDash: function() { /* ... */ }, + onAfterDoWithUnderscore: function() { /* ... */ }, + onAfterDoAlreadyCamelized: function() { /* ... */ } + } + }); +``` + +# Lifecycle Events Listed in Order + +To recap, the lifecycle of a transition occurs in the following order: + + * `onBeforeTransition` - fired before any transition + * `onBefore` - fired before a specific TRANSITION + * `onLeaveState` - fired when leaving any state + * `onLeave` - fired when leaving a specific STATE + * `onTransition` - fired during any transition + * `onEnterState` - fired when entering any state + * `onEnter` - fired when entering a specific STATE + * `on` - convenience shorthand for `onEnter` + * `onAfterTransition` - fired after any transition + * `onAfter` - fired after a specific TRANSITION + * `on` - convenience shorthand for `onAfter` + +# Cancelling a Transition + +Any observer can cancel a transition by explicitly returning `false` during any of the following +lifecycle events: + + * `onBeforeTransition` + * `onBefore` + * `onLeaveState` + * `onLeave` + * `onTransition` + +All subsequent lifecycle events will be cancelled and the state will remain unchanged. + diff --git a/docs/state-history.md b/docs/state-history.md new file mode 100644 index 0000000..7e2ac85 --- /dev/null +++ b/docs/state-history.md @@ -0,0 +1,127 @@ +# Remembering State History + +By default, a state machine only tracks its current state. If you wish to track the state history +you can extend the state machine with the `state-machine-history` plugin. + +```javascript + var StateMachineHistory = require('javascript-state-machine/lib/history') +``` + +```javascript + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'D' } + ], + plugins: [ + new StateMachineHistory() // <-- plugin enabled here + ] + }) + + fsm.history; // [ 'A' ] + fsm.step(); + fsm.history; // [ 'A', 'B' ] + fsm.step(); + fsm.history; // [ 'A', 'B', 'C' ] + + fsm.clearHistory(); + + fsm.history; // [ ] + +``` +## Traversing History + +You can traverse back through history using the `historyBack` and `historyForward` methods: + +```javascript + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'D' } + ] + }) + + fsm.step(); + fsm.step(); + fsm.step(); + + fsm.state; // 'D' + fsm.history; // [ 'A', 'B', 'C', 'D' ] + + fsm.historyBack(); + + fsm.state; // 'C' + fsm.history; // [ 'A', 'B', 'C' ] + + fsm.historyBack(); + + fsm.state; // 'B' + fsm.history; // [ 'A', 'B' ] + + fsm.historyForward(); + + fsm.state; // 'C' + fsm.history; // [ 'A', 'B', 'C' ] +``` + +You can test if history traversal is allowed using the following properties: + +```javascript + fsm.canHistoryBack; // true/false + fsm.canHistoryForward; // true/false +``` + +A full set of [Lifecycle Events](lifecycle-events.md) will still apply when traversing history with +`historyBack` and `historyForward`. + +## Limiting History + +By default, the state machine history is unbounded and will continue to grow until cleared. You +can limit storage to only the last N states by configuring the plugin: + +``` javascript + var fsm = new StateMachine({ + plugins: [ + new StateMachineHistory({ max: 100 }) // <-- plugin configuration + ] + }) +``` + +## Customizing History + +If the `history` terminology clashes with your existing state machine attributes or methods, you +can enable the plugin with a different name: + +```javascript + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'D' } + ], + plugins: [ + new StateMachineHistory({ name: 'memory' }) + ] + }) + + fsm.step(); + fsm.step(); + + fsm.memory; // [ 'A', 'B', 'C' ] + + fsm.memoryBack(); + fsm.memory; // [ 'A', 'B' ] + + fsm.memoryForward(); + fsm.memory; // [ 'A', 'B', 'C' ] + + fsm.clearMemory(); + fsm.memory; // [ ] +``` + diff --git a/docs/state-machine-factory.md b/docs/state-machine-factory.md new file mode 100644 index 0000000..9f5e906 --- /dev/null +++ b/docs/state-machine-factory.md @@ -0,0 +1,104 @@ +# State Machine Factory + +Most examples in this documentation construct a single state machine instance, for example: + +```javascript + var fsm = new StateMachine({ + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid' }, + { name: 'freeze', from: 'liquid', to: 'solid' }, + { name: 'vaporize', from: 'liquid', to: 'gas' }, + { name: 'condense', from: 'gas', to: 'liquid' } + ] + }); +``` + +If you wish to construct multiple instances using the same configuration you should use a State +Machine Factory. A State Machine Factory provides a javascript constructor function (e.g. a 'class') +that can be instantiated multiple times: + +```javascript + var Matter = StateMachine.factory({ // <-- the factory is constructed here + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid' }, + { name: 'freeze', from: 'liquid', to: 'solid' }, + { name: 'vaporize', from: 'liquid', to: 'gas' }, + { name: 'condense', from: 'gas', to: 'liquid' } + ] + }); + + var a = new Matter(), // <-- instances are constructed here + b = new Matter(), + c = new Matter(); + + b.melt(); + c.melt(); + c.vaporize(); + + a.state; // solid + b.state; // liquid + c.state; // gas +``` + +Using the factory, each state machine instance is a unique javascript object. Each instance manages +its own `state` property, but methods are shared via the normal javascript prototype mechanism. + +> NOTE: be aware of special case handling required for [Data and State Machine Factories](data-and-methods.md#data-and-state-machine-factories) + +## Applying State Machine Behavior to Existing Objects + +Occasionally, you may wish to apply state machine behavior to an already existing +object (e.g. a react component). You can achieve this using the `StateMachine.apply` method: + +```javascript + var component = { /* ... */ }; + + StateMachine.apply(component, { + init: 'A', + transitions: { + { name: 'step', from: 'A', to: 'B' } + } + }); +``` + +> Be careful not to use state or transition names that will clash with existing object properties. + +## Applying State Machine Factory Behavior to Existing Classes + +You can also apply state machine factory behavior to an existing class, however you must now +take responsibility for initialization by calling `this._fsm()` from within your class +constructor method: + +```javascript + function Person(name) { + this.name = name; + this._fsm(); // <-- IMPORTANT + } + + Person.prototype = { + speak: function() { + console.log('my name is ' + this.name + ' and I am ' + this.state); + } + } + + StateMachine.factory(Person, { + init: 'idle', + transitions: { + { name: 'sleep', from: 'idle', to: 'sleeping' }, + { name: 'wake', from: 'sleeping', to: 'idle' } + } + }); + + var amy = new Person('amy'), + bob = new Person('bob'); + + bob.sleep(); + + amy.state; // 'idle' + bob.state; // 'sleeping' + + amy.speak(); // 'my name is amy and I am idle' + bob.speak(); // 'my name is bob and I am sleeping' +``` diff --git a/docs/states-and-transitions.md b/docs/states-and-transitions.md new file mode 100644 index 0000000..3e28413 --- /dev/null +++ b/docs/states-and-transitions.md @@ -0,0 +1,156 @@ +# States and Transitions + +![matter state machine](../examples/matter.png) + +A state machine consists of a set of **states**, e.g: + + * solid + * liquid + * gas + +.. and a set of **transitions**, e.g: + + * melt + * freeze + * vaporize + * condense + +```javascript + var fsm = new StateMachine({ + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid' }, + { name: 'freeze', from: 'liquid', to: 'solid' }, + { name: 'vaporize', from: 'liquid', to: 'gas' }, + { name: 'condense', from: 'gas', to: 'liquid' } + ] + }); + + fsm.state; // 'solid' + fsm.melt(); + fsm.state; // 'liquid' + fsm.vaporise(); + fsm.state; // 'gas' +``` + +## Multiple states for a transition + +![wizard state machine](../examples/wizard.png) + +If a transition is allowed `from` multiple states then declare the transitions with the same name: + +```javascript + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'D' } +``` + +If a transition with multiple `from` states always transitions `to` the same state, e.g: + +```javascript + { name: 'reset', from: 'B', to: 'A' }, + { name: 'reset', from: 'C', to: 'A' }, + { name: 'reset', from: 'D', to: 'A' } +``` + +... then it can be abbreviated using an array of `from` states: + +```javascript + { name: 'reset', from: [ 'B', 'C', 'D' ], to: 'A' } +``` + +Combining these into a single example: + +```javascript + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'D' }, + { name: 'reset', from: [ 'B', 'C', 'D' ], to: 'A' } + ] + }) +``` + +This example will create an object with 2 transition methods: + + * `fsm.step()` + * `fsm.reset()` + +The `reset` transition will always end up in the `A` state, while the `step` transition +will end up in a state that is dependent on the current state. + +## Wildcard Transitions + +If a transition is appropriate from **any** state, then a wildcard '*' `from` state can be used: + +```javascript + var fsm = new StateMachine({ + transitions: [ + // ... + { name: 'reset', from: '*', to: 'A' } + ] + }); +``` + +## Conditional Transitions + +A transition can choose the target state at run-time by providing a function as the `to` attribute: + +```javascript + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: '*', to: function(n) { return increaseCharacter(this.state, n || 1) } } + ] + }); + + fsm.state; // A + fsm.step(); + fsm.state; // B + fsm.step(5); + fsm.state; // G + + // helper method to perform (c = c + n) on the 1st character in str + function increaseCharacter(str, n) { + return String.fromCharCode(str.charCodeAt(0) + n); + } +``` + +The `allStates` method will only include conditional states once they have been seen at run-time: + +```javascript + fsm.state; // A + fsm.allStates(); // [ 'A' ] + fsm.step(); + fsm.state; // B + fsm.allStates(); // [ 'A', 'B' ] + fsm.step(5); + fsm.state; // G + fsm.allStates(); // [ 'A', 'B', 'G' ] +``` + +## GOTO - Changing State Without a Transition + +You can use a conditional transition, combined with a wildcard `from`, to implement +arbitrary `goto` behavior: + +```javascript + var fsm = new StateMachine({ + init: 'A' + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'D' }, + { name: 'goto', from: '*', to: function(s) { return s } } + ] + }) + + fsm.state; // 'A' + fsm.goto('D'); + fsm.state; // 'D' +``` + +A full set of [Lifecycle Events](lifecycle-events.md) still apply when using `goto`. + diff --git a/docs/upgrading-from-v2.md b/docs/upgrading-from-v2.md new file mode 100644 index 0000000..42f8150 --- /dev/null +++ b/docs/upgrading-from-v2.md @@ -0,0 +1,378 @@ +# Upgrading from Version 2.x + +Version 3.0 is a significant rewrite from earlier versions in order to support more +advanced use cases and to improve the existing use cases. Unfortunately, many of these +updates are incompatible with earlier versions, so changes are required in your code when you upgrade +to version 3.x. We want to tackle those all in one swoop and avoid any more big-bang changes +in the future. + +Please read this article carefully if you are upgrading from version 2.x to 3.x. + +> A [summary](#upgrade-summary) of the changes required can be found at the end of the article. + +### Table of Contents + + * [**Construction**](#construction) - constructing single instances follows a more idomatic javascript pattern. + * [**State Machine Factory**](#state-machine-factory) - constructing multiple instances from a class has been simplified. + * [**Data and Methods**](#data-and-methods) - A state machine can now have additional data and methods. + * [**Renamed Terminology**](#renamed-terminology) - A more consistent terminology has been applied. + * [**Lifecycle Events**](#lifecycle-events) - (previously called 'callbacks') are camelCased and observable. + * [**Async Transitions**](#promise-based-asynchronous-transitions) - Asynchronous transitions now use standard Promises. + * [**Conditional Transitions**](#conditional-transitions) - A transition can now dynamically choose its target state at run-time. + * [**Goto**](#goto) - The state can be changed without a defined transition using `goto`. + * [**State History**](#state-history) - The state history can now be retained and traversed with back/forward semantics. + * [**Visualization**](#visualization) - A state machine can now be visualized using GraphViz. + * [**Build System**](#build-system) - A new webpack-based build system has been implemented. + +## Construction + +Constructing a single state machine now follows a more idiomatic javascript pattern: + +Version 2.x: + +```javascript + var fsm = StateMachine.create({ /* ... */ }) +``` + +**Version 3.x**: + +```javascript + var fsm = new StateMachine({ /* ... */ }) // <-- more idomatic +``` + +## State Machine Factory + +Constructing multiple instances from a state machine 'class' has been simplified: + +Version 2.x: + +```javascript + function FSM() { } + + StateMachine.create({ + target: FSM.prototype, + // ... + }) + + var a = new FSM(), + b = new FSM(); +``` + +**Version 3.x**: + +```javascript + var FSM = StateMachine.factory({ /* ... */ }), // <-- generate a factory (a constructor function) + a = new FSM(), // <-- then create instances + b = new FSM(); +``` + +## Data and Methods + +A state machine can now have additional (arbitrary) data and methods defined: + +Version 2.x: _not supported_. + +**Version 3.x**: + +```javascript + var fsm = new StateMachine({ + data: { + color: 'red' + }, + methods: { + speak: function() { console.log('hello') } + } + }); + + fsm.color; // 'red' + fsm.speak(); // 'hello' +``` + +## Renamed Terminology + +A more consistent terminology has been applied: + + * A state machine consists of a set of [**States**](states-and-transitions.md). + * A state machine changes state by using [**Transitions**](states-and-transitions.md). + * A state machine can perform actions during a transition by observing [**Lifecycle Events**](lifecycle-events.md). + * A state machine can also have arbitrary [**Data and Methods**](data-and-methods.md). + +Version 2.x: + +```javascript + var fsm = StateMachine.create({ + initial: 'ready', + events: [ /* ... */ ], + callbacks: { /* ... */ } + }); + + fsm.current; // 'ready' +``` + +**Version 3.x**: + +```javascript + var fsm = new StateMachine({ + init: 'ready', // <-- renamed s/initial/init/ + transitions: [ /* ... */ ], // <-- renamed s/events/transitions/ + data: { /* ... */ }, // <-- new + methods: { /* ... */ } // <-- renamed s/callbacks/methods/ + // ... which can contain arbitrary methods AND lifecycle event callbacks + }); + + fsm.state; // 'ready' // <-- renamed s/current/state/ +``` + +## Lifecycle Events + +**Callbacks** have been renamed **Lifecycle Events** and are now declared as `methods` on the +state machine using a more traditional javascript camelCase for the method names: + +Version 2.x: + +```javascript + var fsm = StateMachine.create({ + initial: 'initial-state', + events: [ + { name: 'do-something', from: 'initial-state', to: 'final-state' } + ], + callbacks: { + onbeforedosomething: function() { /* ... */ }, + onleaveinitialstate: function() { /* ... */ }, + onenterfinalstate: function() { /* ... */ }, + onafterdosomething: function() { /* ... */ } + } + }) +``` + +**Version 3.x**: + +```javascript + var fsm = new StateMachine({ + init: 'initial-state', + transitions: [ + { name: 'do-something', from: 'initial-state', to: 'final-state' } + ], + methods: { // <-- renamed s/callbacks/methods/ + onBeforeDoSomething: function() { /* ... */ }, // <-- camelCase naming convention + onLeaveInitialState: function() { /* ... */ }, // <-- + onEnterFinalState: function() { /* ... */ }, // <-- + onAfterDoSomething: function() { /* ... */ } // <-- + } + }) +``` + +
    +Lifecycle events are now passed information in a single `lifecycle` argument: + +Version 2.x: + +```javascript + var fsm = StateMachine.create({ + events: [ + { name: 'step', from: 'none', to: 'complete' } + ], + callbacks: { + onbeforestep: function(event, from, to) { + console.log('event: ' + event); // 'step' + console.log('from: ' + from); // 'none' + console.log('to: ' + to); // 'complete' + }, + } + }); +``` + +**Version 3.x**: + +```javascript + var fsm = new StateMachine({ + transitions: [ + { name: 'step', from: 'none', to: 'complete' } + ], + methods: { + onBeforeStep: function(lifecycle) { // <-- combined into a single argument + console.log('transition: ' + lifecycle.transition); // 'step' + console.log('from: ' + lifecycle.from); // 'none' + console.log('to: ' + lifecycle.to); // 'complete' + } + } + }); +``` + +> This change allows us to include additional information in the future without having to have a ridiculous +number of arguments to lifecycle event observer methods + +
    +Lifecycle events are also now observable by others: + +Version 2.x: _not supported_. + +**Version 3.x**: + +```javascript + var fsm = new StateMachine({ /* ... */ }); + + // observe individual lifecycle events with observer methods + fsm.observe('onBeforeTransition', function() { /* ... */ }); + fsm.observe('onLeaveState', function() { /* ... */ }); + + // or observe multiple lifecycle events with an observer object + fsm.observe({ + onBeforeTransition: function() { /* ... */ }, + onLeaveState: function() { /* ... */ } + }); +``` + +
    +The general purpose lifecycle events now use the word `transition` instead of `event` and +occur **before** their specialized versions: + +Version 2.x, the lifecycle order was: + + * `onbefore` + * `onbeforeevent` + * `onleave` + * `onleavestate` + * `onenter` + * `onenterstate` + * `on` + * `onafter` + * `onafterevent` + * `on` + +**Version 3.x**, the lifecycle order is: + + * `onBeforeTransition` - fired before any transition + * `onBefore` - fired before a specific TRANSITION + * `onLeaveState` - fired when leaving any state + * `onLeave` - fired when leaving a specific STATE + * `onTransition` - fired during any transition + * `onEnterState` - fired when entering any state + * `onEnter` - fired when entering a specific STATE + * `on` - convenience shorthand for `onEnter` + * `onAfterTransition` - fired after any transition + * `onAfter` - fired after a specific TRANSITION + * `on` - convenience shorthand for `onAfter` + +> For more details, read [Lifecycle Events](lifecycle-events.md) + +## Promise-Based Asynchronous Transitions + +Asynchronous transitions are now implemented using standard javascript [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). + +If you return a Promise from **any** lifecycle event then the entire lifecycle for that transition +is put on hold until that Promise gets resolved. If the promise is rejected then the transition +is cancelled. + +Version 2.x: + +```javascript + var fsm = StateMachine.create({ + events: [ + { name: 'step', from: 'none', to: 'complete' } + ], + callbacks: { + onbeforestep: function() { + $('#ui').fadeOut('fast', function() { + fsm.transition(); + }); + return StateMachine.ASYNC; + } + } + }); +``` + +**Version 3.x**: + +```javascript + var fsm = new StateMachine({ + transitions: [ + { name: 'step', from: 'none', to: 'complete' } + ], + methods: { + onBeforeStep: function() { + return new Promise(function(resolve, reject) { // <-- return a Promise instead of StateMachine.ASYNC + $('#ui').fadeOut('fast', resolve); // <-- resolve the promise instead of calling .transition() + }); + } + } + }); +``` + +> For more details, read [Asynchronous Transitions](async-transitions.md) + +## Conditional Transitions + +A transition can now be conditional and choose the target state at run-time by providing a function +as the `to` attribute. + +Version 2.x: _not supported_. + +**Version 3.x**: See [Conditional Transitions](states-and-transitions.md#conditional-transitions) + +## Goto + +The state can now be changed without the need for a predefined transition using a conditional `goto` +transition: + +Version 2.x: _not_supported_. + +**Version 3.x**: See [Goto](states-and-transitions.md#goto---changing-state-without-a-transition) + +## State History + +A state machine can now track and traverse (back/forward) its state history. + +Version 2.x: _not supported_. + +**Version 3.x**: See [State History](state-history.md) + +## Visualization + +A state machine can now be visualized as a directed graph using GraphViz `.dot` syntax. + +Version 2.x: _not_supported_. + +**Version 3.x**: See [Visualization](visualization.md) + +## Build System + +A new [Webpack](https://webpack.js.org/concepts/) based build system has been provided along +with an [Ava](https://github.com/avajs/ava) based unit test suite. + +Version 2.x: _not_supported_. + +**Version 3.x**: See [Contributing](contributing.md) + +## Other Breaking Changes in Version 3.0 + +`isFinished` is no longer built-in, you can easily add it to your state machine with a custom method: + +```javascript + var fsm = new StateMachine({ + methods: { + isFinished: function() { return this.state === 'done' } + } + }) +``` + +# UPGRADE SUMMARY + +The following list summarizes the above changes you might need when upgrading to version 3.0 + + * replace `StateMachine.create()` with `new StateMachine()` + * rename: + * `initial` to `init` + * `events` to `transitions` + * `callbacks` to `methods` + * `fsm.current` to `fsm.state` + * update your callback methods: + * rename them to use traditional javascript `camelCasing` + * refactor them to use the single `lifecycle` argument instead of individual `event,from,to` arguments + * update any asynchronous callback methods: + * return a `Promise` instead of `StateMachine.ASYNC` + * `resolve()` the promise when ready instead of calling `fsm.transition()` + * replace `StateMachine.create({ target: FOO })` with: + * if FOO is a class - `StateMachine.factory(FOO, {})` + * if FOO is an object - `StateMachine.apply(FOO, {})` + diff --git a/docs/visualization.md b/docs/visualization.md new file mode 100644 index 0000000..e944dcf --- /dev/null +++ b/docs/visualization.md @@ -0,0 +1,211 @@ +# Visualization + +It can be very helpful to visualize your state machine as a directed graph. This is possible +with the open source [GraphViz](http://www.graphviz.org/) library if we convert from our +state machine configuration to the `.dot` language expected by GraphViz using the +`visualize` method: + +```javascript + var visualize = require('javascript-state-machine/lib/visualize'); + + var fsm = new StateMachine({ + init: 'open', + transitions: [ + { name: 'close', from: 'open', to: 'closed' }, + { name: 'open', from: 'closed', to: 'open' } + ] + }); + + visualize(fsm) +``` + +Generates the following .dot syntax: + +```dot + digraph "fsm" { + "closed"; + "open"; + "closed" -> "open" [ label=" open " ]; + "open" -> "closed" [ label=" close " ]; + } +``` + +Which GraphViz displays as: + +![door](../examples/vertical_door.png) + +## Enhanced Visualization + +You can customize the generated `.dot` output - and hence the graphviz visualization - by attaching +`dot` attributes to your transitions and (optionally) declaring an `orientation`: + +```javascript + var fsm = new StateMachine({ + init: 'closed', + transitions: [ + { name: 'open', from: 'closed', to: 'open', dot: { color: 'blue', headport: 'n', tailport: 'n' } }, + { name: 'close', from: 'open', to: 'closed', dot: { color: 'red', headport: 's', tailport: 's' } } + ] + }); + visualize(fsm, { name: 'door', orientation: 'horizontal' }); +``` + +Generates the following (enhanced) `.dot` syntax: + +```dot + digraph "door" { + rankdir=LR; + "closed"; + "open"; + "closed" -> "open" [ color="blue" ; headport="n" ; label=" open " ; tailport="n" ]; + "open" -> "closed" [ color="red" ; headport="s" ; label=" close " ; tailport="s" ]; + } +``` + +Which GraphViz displays as: + +![door](../examples/horizontal_door.png) + +## Visualizing State Machine Factories + +You can use the same `visualize` method to generate `.dot` output for a state machine factory: + +```javascript + var Matter = StateMachine.factory({ + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid', dot: { headport: 'nw' } }, + { name: 'freeze', from: 'liquid', to: 'solid', dot: { headport: 'se' } }, + { name: 'vaporize', from: 'liquid', to: 'gas', dot: { headport: 'nw' } }, + { name: 'condense', from: 'gas', to: 'liquid', dot: { headport: 'se' } } + ] + }); + + visualize(Matter, { name: 'matter', orientation: 'horizontal' }) +``` + +Generates the following .dot syntax: + +```dot + digraph "matter" { + rankdir=LR; + "solid"; + "liquid"; + "gas"; + "solid" -> "liquid" [ headport="nw" ; label=" melt " ]; + "liquid" -> "solid" [ headport="se" ; label=" freeze " ]; + "liquid" -> "gas" [ headport="nw" ; label=" vaporize " ]; + "gas" -> "liquid" [ headport="se" ; label=" condense " ]; + } +``` + +Which GraphViz displays as: + +![matter](../examples/matter.png) + +## Other Examples + +```javascript + var Wizard = StateMachine.factory({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B', dot: { headport: 'w', tailport: 'ne' } }, + { name: 'step', from: 'B', to: 'C', dot: { headport: 'w', tailport: 'e' } }, + { name: 'step', from: 'C', to: 'D', dot: { headport: 'w', tailport: 'e' } }, + { name: 'reset', from: [ 'B', 'C', 'D' ], to: 'A', dot: { headport: 'se', tailport: 's' } } + ] + }); + + visualize(Wizard, { orientation: 'horizontal' }) +``` + +Generates: + +```dot + digraph "wizard" { + rankdir=LR; + "A"; + "B"; + "C"; + "D"; + "A" -> "B" [ headport="w" ; label=" step " ; tailport="ne" ]; + "B" -> "C" [ headport="w" ; label=" step " ; tailport="e" ]; + "C" -> "D" [ headport="w" ; label=" step " ; tailport="e" ]; + "B" -> "A" [ headport="se" ; label=" reset " ; tailport="s" ]; + "C" -> "A" [ headport="se" ; label=" reset " ; tailport="s" ]; + "D" -> "A" [ headport="se" ; label=" reset " ; tailport="s" ]; + } +``` + +Displays: + +![wizard](../examples/wizard.png) + +```javascript + var ATM = StateMachine.factory({ + init: 'ready', + transitions: [ + { name: 'insert-card', from: 'ready', to: 'pin' }, + { name: 'confirm', from: 'pin', to: 'action' }, + { name: 'reject', from: 'pin', to: 'return-card' }, + { name: 'withdraw', from: 'return-card', to: 'ready' }, + + { name: 'deposit', from: 'action', to: 'deposit-account' }, + { name: 'provide', from: 'deposit-account', to: 'deposit-amount' }, + { name: 'provide', from: 'deposit-amount', to: 'confirm-deposit' }, + { name: 'confirm', from: 'confirm-deposit', to: 'collect-envelope' }, + { name: 'provide', from: 'collect-envelope', to: 'continue' }, + + { name: 'withdraw', from: 'action', to: 'withdrawal-account' }, + { name: 'provide', from: 'withdrawal-account', to: 'withdrawal-amount' }, + { name: 'provide', from: 'withdrawal-amount', to: 'confirm-withdrawal' }, + { name: 'confirm', from: 'confirm-withdrawal', to: 'dispense-cash' }, + { name: 'withdraw', from: 'dispense-cash', to: 'continue' }, + + { name: 'continue', from: 'continue', to: 'action' }, + { name: 'finish', from: 'continue', to: 'return-card' } + ] + }) + + visualize(ATM) +``` + +Generates: + +```dot + digraph "ATM" { + "ready"; + "pin"; + "action"; + "return-card"; + "deposit-account"; + "deposit-amount"; + "confirm-deposit"; + "collect-envelope"; + "continue"; + "withdrawal-account"; + "withdrawal-amount"; + "confirm-withdrawal"; + "dispense-cash"; + "ready" -> "pin" [ label=" insert-card " ]; + "pin" -> "action" [ label=" confirm " ]; + "pin" -> "return-card" [ label=" reject " ]; + "return-card" -> "ready" [ label=" withdraw " ]; + "action" -> "deposit-account" [ label=" deposit " ]; + "deposit-account" -> "deposit-amount" [ label=" provide " ]; + "deposit-amount" -> "confirm-deposit" [ label=" provide " ]; + "confirm-deposit" -> "collect-envelope" [ label=" confirm " ]; + "collect-envelope" -> "continue" [ label=" provide " ]; + "action" -> "withdrawal-account" [ label=" withdraw " ]; + "withdrawal-account" -> "withdrawal-amount" [ label=" provide " ]; + "withdrawal-amount" -> "confirm-withdrawal" [ label=" provide " ]; + "confirm-withdrawal" -> "dispense-cash" [ label=" confirm " ]; + "dispense-cash" -> "continue" [ label=" withdraw " ]; + "continue" -> "action" [ label=" continue " ]; + "continue" -> "return-card" [ label=" finish " ]; + } +``` + +Displays: + +![atm](../examples/atm.png) diff --git a/examples/atm.dot b/examples/atm.dot new file mode 100644 index 0000000..9f76543 --- /dev/null +++ b/examples/atm.dot @@ -0,0 +1,31 @@ +digraph "ATM" { + "ready"; + "pin"; + "action"; + "return-card"; + "deposit-account"; + "deposit-amount"; + "confirm-deposit"; + "collect-envelope"; + "continue"; + "withdrawal-account"; + "withdrawal-amount"; + "confirm-withdrawal"; + "dispense-cash"; + "ready" -> "pin" [ label=" insert-card " ]; + "pin" -> "action" [ label=" confirm " ]; + "pin" -> "return-card" [ label=" reject " ]; + "return-card" -> "ready" [ label=" withdraw " ]; + "action" -> "deposit-account" [ label=" deposit " ]; + "deposit-account" -> "deposit-amount" [ label=" provide " ]; + "deposit-amount" -> "confirm-deposit" [ label=" provide " ]; + "confirm-deposit" -> "collect-envelope" [ label=" confirm " ]; + "collect-envelope" -> "continue" [ label=" provide " ]; + "action" -> "withdrawal-account" [ label=" withdraw " ]; + "withdrawal-account" -> "withdrawal-amount" [ label=" provide " ]; + "withdrawal-amount" -> "confirm-withdrawal" [ label=" provide " ]; + "confirm-withdrawal" -> "dispense-cash" [ label=" confirm " ]; + "dispense-cash" -> "continue" [ label=" withdraw " ]; + "continue" -> "action" [ label=" continue " ]; + "continue" -> "return-card" [ label=" finish " ]; +} \ No newline at end of file diff --git a/examples/atm.js b/examples/atm.js new file mode 100644 index 0000000..0113e55 --- /dev/null +++ b/examples/atm.js @@ -0,0 +1,33 @@ +var StateMachine = require('../src/app'), + visualize = require('../src/plugin/visualize'); + +var ATM = StateMachine.factory({ + init: 'ready', + transitions: [ + { name: 'insert-card', from: 'ready', to: 'pin' }, + { name: 'confirm', from: 'pin', to: 'action' }, + { name: 'reject', from: 'pin', to: 'return-card' }, + { name: 'withdraw', from: 'return-card', to: 'ready' }, + + { name: 'deposit', from: 'action', to: 'deposit-account' }, + { name: 'provide', from: 'deposit-account', to: 'deposit-amount' }, + { name: 'provide', from: 'deposit-amount', to: 'confirm-deposit' }, + { name: 'confirm', from: 'confirm-deposit', to: 'collect-envelope' }, + { name: 'provide', from: 'collect-envelope', to: 'continue' }, + + { name: 'withdraw', from: 'action', to: 'withdrawal-account' }, + { name: 'provide', from: 'withdrawal-account', to: 'withdrawal-amount' }, + { name: 'provide', from: 'withdrawal-amount', to: 'confirm-withdrawal' }, + { name: 'confirm', from: 'confirm-withdrawal', to: 'dispense-cash' }, + { name: 'withdraw', from: 'dispense-cash', to: 'continue' }, + + { name: 'continue', from: 'continue', to: 'action' }, + { name: 'finish', from: 'continue', to: 'return-card' } + ] +}) + +ATM.visualize = function() { + return visualize(ATM, { name: 'ATM' }) +} + +module.exports = ATM diff --git a/examples/atm.png b/examples/atm.png new file mode 100644 index 0000000000000000000000000000000000000000..a699d3968486c09484bdc1259cbecc291bb9755b GIT binary patch literal 98712 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

    f&XbJ+<^_yz+dVxRMT3 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 + + + + + +ATM + + +ready + +ready + + +pin + +pin + + +ready->pin + + + insert-card + + +action + +action + + +pin->action + + + confirm + + +return-card + +return-card + + +pin->return-card + + + reject + + +deposit-account + +deposit-account + + +action->deposit-account + + + deposit + + +withdrawal-account + +withdrawal-account + + +action->withdrawal-account + + + withdraw + + +return-card->ready + + + withdraw + + +deposit-amount + +deposit-amount + + +deposit-account->deposit-amount + + + provide + + +confirm-deposit + +confirm-deposit + + +deposit-amount->confirm-deposit + + + provide + + +collect-envelope + +collect-envelope + + +confirm-deposit->collect-envelope + + + confirm + + +continue + +continue + + +collect-envelope->continue + + + provide + + +continue->action + + + continue + + +continue->return-card + + + finish + + +withdrawal-amount + +withdrawal-amount + + +withdrawal-account->withdrawal-amount + + + provide + + +confirm-withdrawal + +confirm-withdrawal + + +withdrawal-amount->confirm-withdrawal + + + provide + + +dispense-cash + +dispense-cash + + +confirm-withdrawal->dispense-cash + + + confirm + + +dispense-cash->continue + + + withdraw + + + diff --git a/demo/demo.css b/examples/demo/demo.css similarity index 100% rename from demo/demo.css rename to examples/demo/demo.css diff --git a/examples/demo/demo.js b/examples/demo/demo.js new file mode 100644 index 0000000..f2b2b95 --- /dev/null +++ b/examples/demo/demo.js @@ -0,0 +1,84 @@ +Demo = function() { + + var output = document.getElementById('output'), + demo = document.getElementById('demo'), + panic = document.getElementById('panic'), + warn = document.getElementById('warn'), + calm = document.getElementById('calm'), + clear = document.getElementById('clear'), + count = 0; + + var log = function(msg, separate) { + count = count + (separate ? 1 : 0); + output.value = count + ": " + msg + "\n" + (separate ? "\n" : "") + output.value; + refreshUI(); + }; + + var refreshUI = function() { + setTimeout(function() { + demo.className = fsm.state; + panic.disabled = fsm.cannot('panic', true); + warn.disabled = fsm.cannot('warn', true); + calm.disabled = fsm.cannot('calm', true); + clear.disabled = fsm.cannot('clear', true); + }, 0); // defer until end of current tick to allow fsm to complete transaction + }; + + var fsm = new StateMachine({ + + transitions: [ + { name: 'start', from: 'none', to: 'green' }, + { name: 'warn', from: 'green', to: 'yellow' }, + { name: 'panic', from: 'green', to: 'red' }, + { name: 'panic', from: 'yellow', to: 'red' }, + { name: 'calm', from: 'red', to: 'yellow' }, + { name: 'clear', from: 'red', to: 'green' }, + { name: 'clear', from: 'yellow', to: 'green' }, + ], + + methods: { + + onBeforeTransition: function(lifecycle) { + log("BEFORE: " + lifecycle.transition, true); + }, + + onLeaveState: function(lifecycle) { + log("LEAVE: " + lifecycle.from); + }, + + onEnterState: function(lifecycle) { + log("ENTER: " + lifecycle.to); + }, + + onAfterTransition: function(lifecycle) { + log("AFTER: " + lifecycle.transition); + }, + + onTransition: function(lifecycle) { + log("DURING: " + lifecycle.transition + " (from " + lifecycle.from + " to " + lifecycle.to + ")"); + }, + + onLeaveRed: function(lifecycle) { + return new Promise(function(resolve, reject) { + var msg = lifecycle.transition + ' to ' + lifecycle.to; + log("PENDING " + msg + " in ...3"); + setTimeout(function() { + log("PENDING " + msg + " in ...2"); + setTimeout(function() { + log("PENDING " + msg + " in ...1"); + setTimeout(function() { + resolve(); + }, 1000); + }, 1000); + }, 1000); + }); + } + + } + }); + + fsm.start(); + return fsm; + +}(); + diff --git a/demo/images/alerts.green.png b/examples/demo/images/alerts.green.png similarity index 100% rename from demo/images/alerts.green.png rename to examples/demo/images/alerts.green.png diff --git a/demo/images/alerts.red.png b/examples/demo/images/alerts.red.png similarity index 100% rename from demo/images/alerts.red.png rename to examples/demo/images/alerts.red.png diff --git a/demo/images/alerts.yellow.png b/examples/demo/images/alerts.yellow.png similarity index 100% rename from demo/images/alerts.yellow.png rename to examples/demo/images/alerts.yellow.png diff --git a/examples/horizontal_door.dot b/examples/horizontal_door.dot new file mode 100644 index 0000000..008113f --- /dev/null +++ b/examples/horizontal_door.dot @@ -0,0 +1,7 @@ +digraph "door" { + rankdir=LR; + "closed"; + "open"; + "closed" -> "open" [ color="blue" ; headport="n" ; label=" open " ; tailport="n" ]; + "open" -> "closed" [ color="red" ; headport="s" ; label=" close " ; tailport="s" ]; +} \ No newline at end of file diff --git a/examples/horizontal_door.js b/examples/horizontal_door.js new file mode 100644 index 0000000..1a2f676 --- /dev/null +++ b/examples/horizontal_door.js @@ -0,0 +1,16 @@ +var StateMachine = require('../src/app'), + visualize = require('../src/plugin/visualize'); + +var Door = StateMachine.factory({ + init: 'closed', + transitions: [ + { name: 'open', from: 'closed', to: 'open', dot: { color: 'blue', headport: 'n', tailport: 'n' } }, + { name: 'close', from: 'open', to: 'closed', dot: { color: 'red', headport: 's', tailport: 's' } } + ] +}); + +Door.visualize = function() { + return visualize(Door, { name: 'door', orientation: 'horizontal' }) +} + +module.exports = Door diff --git a/examples/horizontal_door.png b/examples/horizontal_door.png new file mode 100644 index 0000000000000000000000000000000000000000..65d8ddbe9d405b72e89d190d62c42da38ca16cbe GIT binary patch literal 11279 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 literal 0 HcmV?d00001 diff --git a/examples/horizontal_door.svg b/examples/horizontal_door.svg new file mode 100644 index 0000000..217e038 --- /dev/null +++ b/examples/horizontal_door.svg @@ -0,0 +1,35 @@ + + + + + + +door + + +closed + +closed + + +open + +open + + +closed:n->open:n + + + open + + +open:s->closed:s + + + close + + + diff --git a/examples/matter.dot b/examples/matter.dot new file mode 100644 index 0000000..9a5b12e --- /dev/null +++ b/examples/matter.dot @@ -0,0 +1,10 @@ +digraph "matter" { + rankdir=LR; + "solid"; + "liquid"; + "gas"; + "solid" -> "liquid" [ headport="nw" ; label=" melt " ]; + "liquid" -> "solid" [ headport="se" ; label=" freeze " ]; + "liquid" -> "gas" [ headport="nw" ; label=" vaporize " ]; + "gas" -> "liquid" [ headport="se" ; label=" condense " ]; +} \ No newline at end of file diff --git a/examples/matter.js b/examples/matter.js new file mode 100644 index 0000000..c3b960f --- /dev/null +++ b/examples/matter.js @@ -0,0 +1,18 @@ +var StateMachine = require('../src/app'), + visualize = require('../src/plugin/visualize'); + +var Matter = StateMachine.factory({ + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid', dot: { headport: 'nw' } }, + { name: 'freeze', from: 'liquid', to: 'solid', dot: { headport: 'se' } }, + { name: 'vaporize', from: 'liquid', to: 'gas', dot: { headport: 'nw' } }, + { name: 'condense', from: 'gas', to: 'liquid', dot: { headport: 'se' } } + ] +}); + +Matter.visualize = function() { + return visualize(Matter, { name: 'matter', orientation: 'horizontal' }) +} + +module.exports = Matter diff --git a/examples/matter.png b/examples/matter.png new file mode 100644 index 0000000000000000000000000000000000000000..cde3b89e88eff344c373cbdf183ce39c9c49c594 GIT binary patch literal 14731 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 Ykyx-k{`k^voUSB}Y3QrxAF&GgU(=( + + + + + +matter + + +solid + +solid + + +liquid + +liquid + + +solid->liquid:nw + + + melt + + +liquid->solid:se + + + freeze + + +gas + +gas + + +liquid->gas:nw + + + vaporize + + +gas->liquid:se + + + condense + + + diff --git a/examples/vertical_door.dot b/examples/vertical_door.dot new file mode 100644 index 0000000..822dad8 --- /dev/null +++ b/examples/vertical_door.dot @@ -0,0 +1,6 @@ +digraph "fsm" { + "closed"; + "open"; + "closed" -> "open" [ label=" open " ]; + "open" -> "closed" [ label=" close " ]; +} \ No newline at end of file diff --git a/examples/vertical_door.js b/examples/vertical_door.js new file mode 100644 index 0000000..b619195 --- /dev/null +++ b/examples/vertical_door.js @@ -0,0 +1,16 @@ +var StateMachine = require('../src/app'), + visualize = require('../src/plugin/visualize'); + +var Door = StateMachine.factory({ + init: 'closed', + transitions: [ + { name: 'open', from: 'closed', to: 'open' }, + { name: 'close', from: 'open', to: 'closed' } + ] +}); + +Door.visualize = function() { + return visualize(Door) +} + +module.exports = Door diff --git a/examples/vertical_door.png b/examples/vertical_door.png new file mode 100644 index 0000000000000000000000000000000000000000..c29023dc98f77f72923c970ffc21dd834b78711d GIT binary patch literal 8246 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 literal 0 HcmV?d00001 diff --git a/examples/vertical_door.svg b/examples/vertical_door.svg new file mode 100644 index 0000000..12dc09b --- /dev/null +++ b/examples/vertical_door.svg @@ -0,0 +1,35 @@ + + + + + + +fsm + + +closed + +closed + + +open + +open + + +closed->open + + + open + + +open->closed + + + close + + + diff --git a/examples/wizard.dot b/examples/wizard.dot new file mode 100644 index 0000000..c7e6f25 --- /dev/null +++ b/examples/wizard.dot @@ -0,0 +1,13 @@ +digraph "wizard" { + rankdir=LR; + "A"; + "B"; + "C"; + "D"; + "A" -> "B" [ headport="w" ; label=" step " ; tailport="ne" ]; + "B" -> "C" [ headport="w" ; label=" step " ; tailport="e" ]; + "C" -> "D" [ headport="w" ; label=" step " ; tailport="e" ]; + "B" -> "A" [ headport="se" ; label=" reset " ; tailport="s" ]; + "C" -> "A" [ headport="se" ; label=" reset " ; tailport="s" ]; + "D" -> "A" [ headport="se" ; label=" reset " ; tailport="s" ]; +} \ No newline at end of file diff --git a/examples/wizard.js b/examples/wizard.js new file mode 100644 index 0000000..8d1aa4c --- /dev/null +++ b/examples/wizard.js @@ -0,0 +1,18 @@ +var StateMachine = require('../src/app'), + visualize = require('../src/plugin/visualize'); + +var Wizard = StateMachine.factory({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B', dot: { headport: 'w', tailport: 'ne' } }, + { name: 'step', from: 'B', to: 'C', dot: { headport: 'w', tailport: 'e' } }, + { name: 'step', from: 'C', to: 'D', dot: { headport: 'w', tailport: 'e' } }, + { name: 'reset', from: [ 'B', 'C', 'D' ], to: 'A', dot: { headport: 'se', tailport: 's' } } + ] +}); + +Wizard.visualize = function() { + return visualize(Wizard, { name: 'wizard', orientation: 'horizontal' }) +} + +module.exports = Wizard diff --git a/examples/wizard.png b/examples/wizard.png new file mode 100644 index 0000000000000000000000000000000000000000..74945ca3515d6b7a6326b55991834dd53ad20700 GIT binary patch literal 28377 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

    nVX&N3Kl`}Qn4Zw5^3WmXLo%NBCk#LCUP0F27 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%0Die@2c1_T?1)MZu;NfS&!KyK9Rw=TJ+5`C> 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 JBbr&?{|^Ly&lLaw literal 0 HcmV?d00001 diff --git a/examples/wizard.svg b/examples/wizard.svg new file mode 100644 index 0000000..46edc96 --- /dev/null +++ b/examples/wizard.svg @@ -0,0 +1,69 @@ + + + + + + +wizard + + +A + +A + + +B + +B + + +A:ne->B:w + + + step + + +B:s->A:se + + + reset + + +C + +C + + +B:e->C:w + + + step + + +C:s->A:se + + + reset + + +D + +D + + +C:e->D:w + + + step + + +D:s->A:se + + + reset + + + diff --git a/index.html b/index.html index 2d6cb62..7beb6b8 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ Javascript Finite State Machine - + @@ -32,8 +32,8 @@

    Finite State Machine

    - - + + diff --git a/lib/history.js b/lib/history.js new file mode 100644 index 0000000..d604994 --- /dev/null +++ b/lib/history.js @@ -0,0 +1,187 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define("StateMachineHistory", [], factory); + else if(typeof exports === 'object') + exports["StateMachineHistory"] = factory(); + else + root["StateMachineHistory"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.l = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // identity function for calling harmony imports with the correct context +/******/ __webpack_require__.i = function(value) { return value; }; + +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; + +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; + +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 1); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = function(label) { + var n, word, words = label.split(/[_-]/), result = words[0]; + for(n = 1 ; n < words.length ; n++) { + result = result + words[n].charAt(0).toUpperCase() + words[n].substring(1); + } + return result; +} + + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +//------------------------------------------------------------------------------------------------- + +var camelize = __webpack_require__(0); + +//------------------------------------------------------------------------------------------------- + +module.exports = function(options) { options = options || {}; + + var past = camelize(options.name || options.past || 'history'), + future = camelize( options.future || 'future'), + clear = camelize('clear-' + past), + back = camelize(past + '-back'), + forward = camelize(past + '-forward'), + canBack = camelize('can-' + back), + canForward = camelize('can-' + forward), + max = options.max; + + var plugin = { + + configure: function(config) { + config.addTransitionLifecycleNames(back); + config.addTransitionLifecycleNames(forward); + }, + + init: function(instance) { + instance[past] = []; + instance[future] = []; + }, + + lifecycle: function(instance, lifecycle) { + if (lifecycle.event === 'onEnterState') { + instance[past].push(lifecycle.to); + if (max && instance[past].length > max) + instance[past].shift(); + if (lifecycle.transition !== back && lifecycle.transition !== forward) + instance[future].length = 0; + } + }, + + methods: {}, + properties: {} + + } + + plugin.methods[clear] = function() { + this[past].length = 0 + this[future].length = 0 + } + + plugin.properties[canBack] = { + get: function() { + return this[past].length > 1 + } + } + + plugin.properties[canForward] = { + get: function() { + return this[future].length > 0 + } + } + + plugin.methods[back] = function() { + if (!this[canBack]) + throw Error('no history'); + var from = this[past].pop(), + to = this[past].pop(); + this[future].push(from); + this._fsm.transit(back, from, to, []); + } + + plugin.methods[forward] = function() { + if (!this[canForward]) + throw Error('no history'); + var from = this.state, + to = this[future].pop(); + this._fsm.transit(forward, from, to, []); + } + + return plugin; + +} + + +/***/ } +/******/ ]); +}); \ No newline at end of file diff --git a/lib/state-machine.js b/lib/state-machine.js new file mode 100644 index 0000000..6ac51b3 --- /dev/null +++ b/lib/state-machine.js @@ -0,0 +1,644 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define("StateMachine", [], factory); + else if(typeof exports === 'object') + exports["StateMachine"] = factory(); + else + root["StateMachine"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.l = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // identity function for calling harmony imports with the correct context +/******/ __webpack_require__.i = function(value) { return value; }; + +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; + +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; + +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 6); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = function(target, sources) { + var n, source, key; + for(n = 1 ; n < arguments.length ; n++) { + source = arguments[n]; + for(key in source) { + if (source.hasOwnProperty(key)) + target[key] = source[key]; + } + } + return target; +} + + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +//------------------------------------------------------------------------------------------------- + +var mixin = __webpack_require__(0); + +//------------------------------------------------------------------------------------------------- + +module.exports = { + + build: function(target, config) { + var n, max, plugin, plugins = config.plugins; + for(n = 0, max = plugins.length ; n < max ; n++) { + plugin = plugins[n]; + if (plugin.methods) + mixin(target, plugin.methods); + if (plugin.properties) + Object.defineProperties(target, plugin.properties); + } + }, + + hook: function(fsm, name, additional) { + var n, max, method, plugin, + plugins = fsm.config.plugins, + args = [fsm.context]; + + if (additional) + args = args.concat(additional) + + for(n = 0, max = plugins.length ; n < max ; n++) { + plugin = plugins[n] + method = plugins[n][name] + if (method) + method.apply(plugin, args); + } + } + +} + +//------------------------------------------------------------------------------------------------- + + +/***/ }, +/* 2 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = function(label) { + var n, word, words = label.split(/[_-]/), result = words[0]; + for(n = 1 ; n < words.length ; n++) { + result = result + words[n].charAt(0).toUpperCase() + words[n].substring(1); + } + return result; +} + + +/***/ }, +/* 3 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +//------------------------------------------------------------------------------------------------- + +var mixin = __webpack_require__(0), + camelize = __webpack_require__(2); + +//------------------------------------------------------------------------------------------------- + +function Config(options, StateMachine) { + + options = options || {}; + + this.source = options; // preserving original options helps with visualize plugin + this.defaults = StateMachine.defaults; + this.states = []; + this.transitions = []; + this.map = {}; + this.lifecycle = this.configureLifecycle(); + this.init = this.configureInitTransition(options.init); + this.data = this.configureData(options.data); + this.methods = this.configureMethods(options.methods); + + this.map[this.defaults.wildcard] = {}; + + this.configureTransitions(options.transitions || []); + + this.plugins = this.configurePlugins(options.plugins, StateMachine.plugin); + +} + +//------------------------------------------------------------------------------------------------- + +mixin(Config.prototype, { + + addState: function(name) { + if (!this.map[name]) { + this.states.push(name); + this.addStateLifecycleNames(name); + this.map[name] = {}; + } + }, + + addStateLifecycleNames: function(name) { + this.lifecycle.onEnter[name] = camelize('on-enter-' + name); + this.lifecycle.onLeave[name] = camelize('on-leave-' + name); + this.lifecycle.on[name] = camelize('on-' + name); + }, + + addTransition: function(name) { + if (this.transitions.indexOf(name) < 0) { + this.transitions.push(name); + this.addTransitionLifecycleNames(name); + } + }, + + addTransitionLifecycleNames: function(name) { + this.lifecycle.onBefore[name] = camelize('on-before-' + name); + this.lifecycle.onAfter[name] = camelize('on-after-' + name); + this.lifecycle.on[name] = camelize('on-' + name); + }, + + mapTransition: function(transition) { + var name = transition.name, + from = transition.from, + to = transition.to; + this.addState(from); + if (typeof to !== 'function') + this.addState(to); + this.addTransition(name); + this.map[from][name] = transition; + return transition; + }, + + configureLifecycle: function() { + return { + onBefore: { transition: camelize('on-before-transition') }, + onAfter: { transition: camelize('on-after-transition') }, + onEnter: { state: camelize('on-enter-state') }, + onLeave: { state: camelize('on-leave-state') }, + on: { transition: camelize('on-transition') } + }; + }, + + configureInitTransition: function(init) { + if (typeof init === 'string') { + return this.mapTransition(mixin({}, this.defaults.init, { to: init, active: true })); + } + else if (typeof init === 'object') { + return this.mapTransition(mixin({}, this.defaults.init, init, { active: true })); + } + else { + this.addState(this.defaults.init.from); + return this.defaults.init; + } + }, + + configureData: function(data) { + if (typeof data === 'function') + return data; + else if (typeof data === 'object') + return function() { return data; } + else + return function() { return {}; } + }, + + configureMethods: function(methods) { + return methods || {}; + }, + + configurePlugins: function(plugins, builtin) { + plugins = plugins || []; + var n, max, plugin; + for(n = 0, max = plugins.length ; n < max ; n++) { + plugin = plugins[n]; + if (typeof plugin === 'function') + plugins[n] = plugin = plugin() + if (plugin.configure) + plugin.configure(this); + } + return plugins + }, + + configureTransitions: function(transitions) { + var i, n, transition, from, to, wildcard = this.defaults.wildcard; + for(n = 0 ; n < transitions.length ; n++) { + transition = transitions[n]; + from = Array.isArray(transition.from) ? transition.from : [transition.from || wildcard] + to = transition.to || wildcard; + for(i = 0 ; i < from.length ; i++) { + this.mapTransition({ name: transition.name, from: from[i], to: to }); + } + } + }, + + transitionFor: function(state, transition) { + var wildcard = this.defaults.wildcard; + return this.map[state][transition] || + this.map[wildcard][transition]; + }, + + transitionsFor: function(state) { + var wildcard = this.defaults.wildcard; + return Object.keys(this.map[state]).concat(Object.keys(this.map[wildcard])); + }, + + allStates: function() { + return this.states; + }, + + allTransitions: function() { + return this.transitions; + } + +}); + +//------------------------------------------------------------------------------------------------- + +module.exports = Config; + +//------------------------------------------------------------------------------------------------- + + +/***/ }, +/* 4 */ +/***/ function(module, exports, __webpack_require__) { + + +var mixin = __webpack_require__(0), + Exception = __webpack_require__(5), + plugin = __webpack_require__(1), + UNOBSERVED = [ null, [] ]; + +//------------------------------------------------------------------------------------------------- + +function JSM(context, config) { + this.context = context; + this.config = config; + this.state = config.init.from; + this.observers = [context]; +} + +//------------------------------------------------------------------------------------------------- + +mixin(JSM.prototype, { + + init: function(args) { + mixin(this.context, this.config.data.apply(this.context, args)); + plugin.hook(this, 'init'); + if (this.config.init.active) + return this.fire(this.config.init.name, []); + }, + + is: function(state) { + return Array.isArray(state) ? (state.indexOf(this.state) >= 0) : (this.state === state); + }, + + isPending: function() { + return this.pending; + }, + + can: function(transition) { + return !this.isPending() && !!this.seek(transition); + }, + + cannot: function(transition) { + return !this.can(transition); + }, + + allStates: function() { + return this.config.allStates(); + }, + + allTransitions: function() { + return this.config.allTransitions(); + }, + + transitions: function() { + return this.config.transitionsFor(this.state); + }, + + seek: function(transition, args) { + var wildcard = this.config.defaults.wildcard, + entry = this.config.transitionFor(this.state, transition), + to = entry && entry.to; + if (typeof to === 'function') + return to.apply(this.context, args); + else if (to === wildcard) + return this.state + else + return to + }, + + fire: function(transition, args) { + return this.transit(transition, this.state, this.seek(transition, args), args); + }, + + transit: function(transition, from, to, args) { + + var lifecycle = this.config.lifecycle, + changed = from !== to; + + if (!to) + return this.context.onInvalidTransition(transition, from, to); + + if (this.isPending()) + return this.context.onPendingTransition(transition, from, to); + + this.config.addState(to); // might need to add this state if it's unknown (e.g. conditional transition or goto) + + this.beginTransit(); + + args.unshift({ // this context will be passed to each lifecycle event observer + transition: transition, + from: from, + to: to, + fsm: this.context + }); + + return this.observeEvents([ + this.observersForEvent(lifecycle.onBefore.transition), + this.observersForEvent(lifecycle.onBefore[transition]), + changed ? this.observersForEvent(lifecycle.onLeave.state) : UNOBSERVED, + changed ? this.observersForEvent(lifecycle.onLeave[from]) : UNOBSERVED, + this.observersForEvent(lifecycle.on.transition), + changed ? [ 'doTransit', [ this ] ] : UNOBSERVED, + changed ? this.observersForEvent(lifecycle.onEnter.state) : UNOBSERVED, + changed ? this.observersForEvent(lifecycle.onEnter[to]) : UNOBSERVED, + changed ? this.observersForEvent(lifecycle.on[to]) : UNOBSERVED, + this.observersForEvent(lifecycle.onAfter.transition), + this.observersForEvent(lifecycle.onAfter[transition]), + this.observersForEvent(lifecycle.on[transition]) + ], args); + }, + + beginTransit: function() { this.pending = true; }, + endTransit: function(result) { this.pending = false; return result; }, + doTransit: function(lifecycle) { this.state = lifecycle.to; }, + + observe: function(args) { + if (args.length === 2) { + var observer = {}; + observer[args[0]] = args[1]; + this.observers.push(observer); + } + else { + this.observers.push(args[0]); + } + }, + + observersForEvent: function(event) { // TODO: this could be cached + var n = 0, max = this.observers.length, observer, result = []; + for( ; n < max ; n++) { + observer = this.observers[n]; + if (observer[event]) + result.push(observer); + } + return [ event, result, true ] + }, + + observeEvents: function(events, args, previousEvent) { + if (events.length === 0) { + return this.endTransit(true); + } + + var event = events[0][0], + observers = events[0][1], + pluggable = events[0][2]; + + args[0].event = event; + if (event && pluggable && event !== previousEvent) + plugin.hook(this, 'lifecycle', args); + + if (observers.length === 0) { + events.shift(); + return this.observeEvents(events, args, event); + } + else { + var observer = observers.shift(), + result = observer[event].apply(observer, args); + if (result && typeof result.then === 'function') { + return result.then(this.observeEvents.bind(this, events, args, event)) + .catch(this.endTransit.bind(this)) + } + else if (result === false) { + return this.endTransit(false); + } + else { + return this.observeEvents(events, args, event); + } + } + }, + + onInvalidTransition: function(transition, from, to) { + throw new Exception("transition is invalid in current state", transition, from, to, this.state); + }, + + onPendingTransition: function(transition, from, to) { + throw new Exception("transition is invalid while previous transition is still in progress", transition, from, to, this.state); + } + +}); + +//------------------------------------------------------------------------------------------------- + +module.exports = JSM; + +//------------------------------------------------------------------------------------------------- + + +/***/ }, +/* 5 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = function(message, transition, from, to, current) { + this.message = message; + this.transition = transition; + this.from = from; + this.to = to; + this.current = current; +} + + +/***/ }, +/* 6 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +//----------------------------------------------------------------------------------------------- + +var mixin = __webpack_require__(0), + camelize = __webpack_require__(2), + plugin = __webpack_require__(1), + Config = __webpack_require__(3), + JSM = __webpack_require__(4); + +//----------------------------------------------------------------------------------------------- + +var PublicMethods = { + is: function(state) { return this._fsm.is(state) }, + can: function(transition) { return this._fsm.can(transition) }, + cannot: function(transition) { return this._fsm.cannot(transition) }, + observe: function() { return this._fsm.observe(arguments) }, + transitions: function() { return this._fsm.transitions() }, + allTransitions: function() { return this._fsm.allTransitions() }, + allStates: function() { return this._fsm.allStates() }, + onInvalidTransition: function(t, from, to) { return this._fsm.onInvalidTransition(t, from, to) }, + onPendingTransition: function(t, from, to) { return this._fsm.onPendingTransition(t, from, to) }, +} + +var PublicProperties = { + state: { + configurable: false, + enumerable: true, + get: function() { + return this._fsm.state; + }, + set: function(state) { + throw Error('use transitions to change state') + } + } +} + +//----------------------------------------------------------------------------------------------- + +function StateMachine(options) { + return apply(this || {}, options); +} + +function factory() { + var cstor, options; + if (typeof arguments[0] === 'function') { + cstor = arguments[0]; + options = arguments[1] || {}; + } + else { + cstor = function() { this._fsm.apply(this, arguments) }; + options = arguments[0] || {}; + } + var config = new Config(options, StateMachine); + build(cstor.prototype, config); + cstor.prototype._fsm.config = config; // convenience access to shared config without needing an instance + return cstor; +} + +//------------------------------------------------------------------------------------------------- + +function apply(instance, options) { + var config = new Config(options, StateMachine); + build(instance, config); + instance._fsm(); + return instance; +} + +function build(target, config) { + if ((typeof target !== 'object') || Array.isArray(target)) + throw Error('StateMachine can only be applied to objects'); + plugin.build(target, config); + Object.defineProperties(target, PublicProperties); + mixin(target, PublicMethods); + mixin(target, config.methods); + config.allTransitions().forEach(function(transition) { + target[camelize(transition)] = function() { + return this._fsm.fire(transition, [].slice.call(arguments)) + } + }); + target._fsm = function() { + this._fsm = new JSM(this, config); + this._fsm.init(arguments); + } +} + +//----------------------------------------------------------------------------------------------- + +StateMachine.version = '3.0.0'; +StateMachine.factory = factory; +StateMachine.apply = apply; +StateMachine.defaults = { + wildcard: '*', + init: { + name: 'init', + from: 'none' + } +} + +//=============================================================================================== + +module.exports = StateMachine; + + +/***/ } +/******/ ]); +}); \ No newline at end of file diff --git a/lib/visualize.js b/lib/visualize.js new file mode 100644 index 0000000..531bf03 --- /dev/null +++ b/lib/visualize.js @@ -0,0 +1,269 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define("StateMachineVisualize", [], factory); + else if(typeof exports === 'object') + exports["StateMachineVisualize"] = factory(); + else + root["StateMachineVisualize"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.l = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // identity function for calling harmony imports with the correct context +/******/ __webpack_require__.i = function(value) { return value; }; + +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; + +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; + +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 1); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = function(target, sources) { + var n, source, key; + for(n = 1 ; n < arguments.length ; n++) { + source = arguments[n]; + for(key in source) { + if (source.hasOwnProperty(key)) + target[key] = source[key]; + } + } + return target; +} + + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + +"use strict"; + + +//------------------------------------------------------------------------------------------------- + +var mixin = __webpack_require__(0) + +//------------------------------------------------------------------------------------------------- + +function visualize(fsm, options) { + return dotify(dotcfg(fsm, options)); +} + +//------------------------------------------------------------------------------------------------- + +function dotcfg(fsm, options) { + + options = options || {} + + var config = dotcfg.fetch(fsm), + name = options.name, + rankdir = dotcfg.rankdir(options.orientation), + states = dotcfg.states(config, options), + transitions = dotcfg.transitions(config, options), + result = { } + + if (name) + result.name = name + + if (rankdir) + result.rankdir = rankdir + + if (states && states.length > 0) + result.states = states + + if (transitions && transitions.length > 0) + result.transitions = transitions + + return result +} + +//------------------------------------------------------------------------------------------------- + +dotcfg.fetch = function(fsm) { + return (typeof fsm === 'function') ? fsm.prototype._fsm.config + : fsm._fsm.config +} + +dotcfg.rankdir = function(orientation) { + if (orientation === 'horizontal') + return 'LR'; + else if (orientation === 'vertical') + return 'TB'; +} + +dotcfg.states = function(config, options) { + var index, states = config.states; + if (!options.init) { // if not showing init transition, then slice out the implied init :from state + index = states.indexOf(config.init.from); + states = states.slice(0, index).concat(states.slice(index+1)); + } + return states; +} + +dotcfg.transitions = function(config, options) { + var n, max, transition, + init = config.init, + transitions = config.source.transitions || [], // easier to visualize using the ORIGINAL transition declarations rather than our run-time mapping + output = []; + if (options.init && init.active) + dotcfg.transition(init.name, init.from, init.to, init.dot, config, options, output) + for (n = 0, max = transitions.length ; n < max ; n++) { + transition = config.source.transitions[n] + dotcfg.transition(transition.name, transition.from, transition.to, transition.dot, config, options, output) + } + return output +} + +dotcfg.transition = function(name, from, to, dot, config, options, output) { + var n, max, wildcard = config.defaults.wildcard + + if (Array.isArray(from)) { + for(n = 0, max = from.length ; n < max ; n++) + dotcfg.transition(name, from[n], to, dot, config, options, output) + } + else if (from === wildcard || from === undefined) { + for(n = 0, max = config.states.length ; n < max ; n++) + dotcfg.transition(name, config.states[n], to, dot, config, options, output) + } + else if (to === wildcard || to === undefined) { + dotcfg.transition(name, from, from, dot, config, options, output) + } + else if (typeof to === 'function') { + // do nothing, can't display conditional transition + } + else { + output.push(mixin({}, { from: from, to: to, label: pad(name) }, dot || {})) + } + +} + +//------------------------------------------------------------------------------------------------- + +function pad(name) { + return " " + name + " " +} + +function quote(name) { + return "\"" + name + "\"" +} + +function dotify(dotcfg) { + + dotcfg = dotcfg || {}; + + var name = dotcfg.name || 'fsm', + states = dotcfg.states || [], + transitions = dotcfg.transitions || [], + rankdir = dotcfg.rankdir, + output = [], + n, max; + + output.push("digraph " + quote(name) + " {") + if (rankdir) + output.push(" rankdir=" + rankdir + ";") + for(n = 0, max = states.length ; n < max ; n++) + output.push(dotify.state(states[n])) + for(n = 0, max = transitions.length ; n < max ; n++) + output.push(dotify.edge(transitions[n])) + output.push("}") + return output.join("\n") + +} + +dotify.state = function(state) { + return " " + quote(state) + ";" +} + +dotify.edge = function(edge) { + return " " + quote(edge.from) + " -> " + quote(edge.to) + dotify.edge.attr(edge) + ";" +} + +dotify.edge.attr = function(edge) { + var n, max, key, keys = Object.keys(edge).sort(), output = []; + for(n = 0, max = keys.length ; n < max ; n++) { + key = keys[n]; + if (key !== 'from' && key !== 'to') + output.push(key + "=" + quote(edge[key])) + } + return output.length > 0 ? " [ " + output.join(" ; ") + " ]" : "" +} + +//------------------------------------------------------------------------------------------------- + +visualize.dotcfg = dotcfg; +visualize.dotify = dotify; + +//------------------------------------------------------------------------------------------------- + +module.exports = visualize; + +//------------------------------------------------------------------------------------------------- + + +/***/ } +/******/ ]); +}); \ No newline at end of file diff --git a/package.json b/package.json index a350191..8d0f5b1 100644 --- a/package.json +++ b/package.json @@ -1,32 +1,58 @@ { "name": "javascript-state-machine", - "description": "A simple finite state machine library", + "description": "A finite state machine library", "homepage": "https://github.com/jakesgordon/javascript-state-machine", + "repository": { + "type": "git", + "url": "git://github.com/jakesgordon/javascript-state-machine.git" + }, "keywords": [ + "finite state machine", "state machine", "server", "client" ], - "author": "Jake Gordon ", - "repository": { - "type": "git", - "url": "git://github.com/jakesgordon/javascript-state-machine.git" + "author": { + "name": "Jake Gordon", + "email": "jake@codeincomplete.com" }, - "main": "state-machine.js", + "maintainers": [ + { + "name": "Jake Gordon", + "email": "jake@codeincomplete.com" + } + ], + "license": "LGPL-3.0", + "main": "lib/state-machine.js", "files": [ - "state-machine.js", - "state-machine.min.js", - "LICENSE" + "lib/**/*.js", + "dist/**/*.js" ], + "directories": {}, "devDependencies": { - "local-web-server": "~1.2.6", - "qunit": "~0.9.1", - "uglify-js": "^2.7.4" + "ava": "^0.17.0", + "fs-sync": "^1.0.3", + "glob": "^7.1.1", + "nyc": "^10.0.0", + "pascal-case": "^2.0.0", + "uglify-js": "^2.7.5", + "webpack": "^2.2.0-rc.1" }, - "version": "2.4.0", + "version": "3.0.0", "scripts": { - "start": "ws --rewrite '/test -> /test/'", - "test": "node test/runner", - "minify": "uglifyjs state-machine.js --output state-machine.min.js --compress --mangle --stats" + "start": "npm run watch", + "build": "npm run bundle && npm run minify", + "bundle": "webpack", + "minify": "bin/minify", + "watch": "ava --watch", + "test": "nyc ava -v && nyc report --reporter=html" + }, + "ava": { + "files": [ + "test/**/*.js" + ], + "source": [ + "src/**/*.js" + ] } } diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..672c99f --- /dev/null +++ b/src/app.js @@ -0,0 +1,102 @@ +'use strict' + +//----------------------------------------------------------------------------------------------- + +var mixin = require('./util/mixin'), + camelize = require('./util/camelize'), + plugin = require('./plugin'), + Config = require('./config'), + JSM = require('./jsm'); + +//----------------------------------------------------------------------------------------------- + +var PublicMethods = { + is: function(state) { return this._fsm.is(state) }, + can: function(transition) { return this._fsm.can(transition) }, + cannot: function(transition) { return this._fsm.cannot(transition) }, + observe: function() { return this._fsm.observe(arguments) }, + transitions: function() { return this._fsm.transitions() }, + allTransitions: function() { return this._fsm.allTransitions() }, + allStates: function() { return this._fsm.allStates() }, + onInvalidTransition: function(t, from, to) { return this._fsm.onInvalidTransition(t, from, to) }, + onPendingTransition: function(t, from, to) { return this._fsm.onPendingTransition(t, from, to) }, +} + +var PublicProperties = { + state: { + configurable: false, + enumerable: true, + get: function() { + return this._fsm.state; + }, + set: function(state) { + throw Error('use transitions to change state') + } + } +} + +//----------------------------------------------------------------------------------------------- + +function StateMachine(options) { + return apply(this || {}, options); +} + +function factory() { + var cstor, options; + if (typeof arguments[0] === 'function') { + cstor = arguments[0]; + options = arguments[1] || {}; + } + else { + cstor = function() { this._fsm.apply(this, arguments) }; + options = arguments[0] || {}; + } + var config = new Config(options, StateMachine); + build(cstor.prototype, config); + cstor.prototype._fsm.config = config; // convenience access to shared config without needing an instance + return cstor; +} + +//------------------------------------------------------------------------------------------------- + +function apply(instance, options) { + var config = new Config(options, StateMachine); + build(instance, config); + instance._fsm(); + return instance; +} + +function build(target, config) { + if ((typeof target !== 'object') || Array.isArray(target)) + throw Error('StateMachine can only be applied to objects'); + plugin.build(target, config); + Object.defineProperties(target, PublicProperties); + mixin(target, PublicMethods); + mixin(target, config.methods); + config.allTransitions().forEach(function(transition) { + target[camelize(transition)] = function() { + return this._fsm.fire(transition, [].slice.call(arguments)) + } + }); + target._fsm = function() { + this._fsm = new JSM(this, config); + this._fsm.init(arguments); + } +} + +//----------------------------------------------------------------------------------------------- + +StateMachine.version = '3.0.0'; +StateMachine.factory = factory; +StateMachine.apply = apply; +StateMachine.defaults = { + wildcard: '*', + init: { + name: 'init', + from: 'none' + } +} + +//=============================================================================================== + +module.exports = StateMachine; diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..771a3d2 --- /dev/null +++ b/src/config.js @@ -0,0 +1,161 @@ +'use strict' + +//------------------------------------------------------------------------------------------------- + +var mixin = require('./util/mixin'), + camelize = require('./util/camelize'); + +//------------------------------------------------------------------------------------------------- + +function Config(options, StateMachine) { + + options = options || {}; + + this.source = options; // preserving original options helps with visualize plugin + this.defaults = StateMachine.defaults; + this.states = []; + this.transitions = []; + this.map = {}; + this.lifecycle = this.configureLifecycle(); + this.init = this.configureInitTransition(options.init); + this.data = this.configureData(options.data); + this.methods = this.configureMethods(options.methods); + + this.map[this.defaults.wildcard] = {}; + + this.configureTransitions(options.transitions || []); + + this.plugins = this.configurePlugins(options.plugins, StateMachine.plugin); + +} + +//------------------------------------------------------------------------------------------------- + +mixin(Config.prototype, { + + addState: function(name) { + if (!this.map[name]) { + this.states.push(name); + this.addStateLifecycleNames(name); + this.map[name] = {}; + } + }, + + addStateLifecycleNames: function(name) { + this.lifecycle.onEnter[name] = camelize('on-enter-' + name); + this.lifecycle.onLeave[name] = camelize('on-leave-' + name); + this.lifecycle.on[name] = camelize('on-' + name); + }, + + addTransition: function(name) { + if (this.transitions.indexOf(name) < 0) { + this.transitions.push(name); + this.addTransitionLifecycleNames(name); + } + }, + + addTransitionLifecycleNames: function(name) { + this.lifecycle.onBefore[name] = camelize('on-before-' + name); + this.lifecycle.onAfter[name] = camelize('on-after-' + name); + this.lifecycle.on[name] = camelize('on-' + name); + }, + + mapTransition: function(transition) { + var name = transition.name, + from = transition.from, + to = transition.to; + this.addState(from); + if (typeof to !== 'function') + this.addState(to); + this.addTransition(name); + this.map[from][name] = transition; + return transition; + }, + + configureLifecycle: function() { + return { + onBefore: { transition: camelize('on-before-transition') }, + onAfter: { transition: camelize('on-after-transition') }, + onEnter: { state: camelize('on-enter-state') }, + onLeave: { state: camelize('on-leave-state') }, + on: { transition: camelize('on-transition') } + }; + }, + + configureInitTransition: function(init) { + if (typeof init === 'string') { + return this.mapTransition(mixin({}, this.defaults.init, { to: init, active: true })); + } + else if (typeof init === 'object') { + return this.mapTransition(mixin({}, this.defaults.init, init, { active: true })); + } + else { + this.addState(this.defaults.init.from); + return this.defaults.init; + } + }, + + configureData: function(data) { + if (typeof data === 'function') + return data; + else if (typeof data === 'object') + return function() { return data; } + else + return function() { return {}; } + }, + + configureMethods: function(methods) { + return methods || {}; + }, + + configurePlugins: function(plugins, builtin) { + plugins = plugins || []; + var n, max, plugin; + for(n = 0, max = plugins.length ; n < max ; n++) { + plugin = plugins[n]; + if (typeof plugin === 'function') + plugins[n] = plugin = plugin() + if (plugin.configure) + plugin.configure(this); + } + return plugins + }, + + configureTransitions: function(transitions) { + var i, n, transition, from, to, wildcard = this.defaults.wildcard; + for(n = 0 ; n < transitions.length ; n++) { + transition = transitions[n]; + from = Array.isArray(transition.from) ? transition.from : [transition.from || wildcard] + to = transition.to || wildcard; + for(i = 0 ; i < from.length ; i++) { + this.mapTransition({ name: transition.name, from: from[i], to: to }); + } + } + }, + + transitionFor: function(state, transition) { + var wildcard = this.defaults.wildcard; + return this.map[state][transition] || + this.map[wildcard][transition]; + }, + + transitionsFor: function(state) { + var wildcard = this.defaults.wildcard; + return Object.keys(this.map[state]).concat(Object.keys(this.map[wildcard])); + }, + + allStates: function() { + return this.states; + }, + + allTransitions: function() { + return this.transitions; + } + +}); + +//------------------------------------------------------------------------------------------------- + +module.exports = Config; + +//------------------------------------------------------------------------------------------------- diff --git a/src/jsm.js b/src/jsm.js new file mode 100644 index 0000000..14eb805 --- /dev/null +++ b/src/jsm.js @@ -0,0 +1,181 @@ + +var mixin = require('./util/mixin'), + Exception = require('./util/exception'), + plugin = require('./plugin'), + UNOBSERVED = [ null, [] ]; + +//------------------------------------------------------------------------------------------------- + +function JSM(context, config) { + this.context = context; + this.config = config; + this.state = config.init.from; + this.observers = [context]; +} + +//------------------------------------------------------------------------------------------------- + +mixin(JSM.prototype, { + + init: function(args) { + mixin(this.context, this.config.data.apply(this.context, args)); + plugin.hook(this, 'init'); + if (this.config.init.active) + return this.fire(this.config.init.name, []); + }, + + is: function(state) { + return Array.isArray(state) ? (state.indexOf(this.state) >= 0) : (this.state === state); + }, + + isPending: function() { + return this.pending; + }, + + can: function(transition) { + return !this.isPending() && !!this.seek(transition); + }, + + cannot: function(transition) { + return !this.can(transition); + }, + + allStates: function() { + return this.config.allStates(); + }, + + allTransitions: function() { + return this.config.allTransitions(); + }, + + transitions: function() { + return this.config.transitionsFor(this.state); + }, + + seek: function(transition, args) { + var wildcard = this.config.defaults.wildcard, + entry = this.config.transitionFor(this.state, transition), + to = entry && entry.to; + if (typeof to === 'function') + return to.apply(this.context, args); + else if (to === wildcard) + return this.state + else + return to + }, + + fire: function(transition, args) { + return this.transit(transition, this.state, this.seek(transition, args), args); + }, + + transit: function(transition, from, to, args) { + + var lifecycle = this.config.lifecycle, + changed = from !== to; + + if (!to) + return this.context.onInvalidTransition(transition, from, to); + + if (this.isPending()) + return this.context.onPendingTransition(transition, from, to); + + this.config.addState(to); // might need to add this state if it's unknown (e.g. conditional transition or goto) + + this.beginTransit(); + + args.unshift({ // this context will be passed to each lifecycle event observer + transition: transition, + from: from, + to: to, + fsm: this.context + }); + + return this.observeEvents([ + this.observersForEvent(lifecycle.onBefore.transition), + this.observersForEvent(lifecycle.onBefore[transition]), + changed ? this.observersForEvent(lifecycle.onLeave.state) : UNOBSERVED, + changed ? this.observersForEvent(lifecycle.onLeave[from]) : UNOBSERVED, + this.observersForEvent(lifecycle.on.transition), + changed ? [ 'doTransit', [ this ] ] : UNOBSERVED, + changed ? this.observersForEvent(lifecycle.onEnter.state) : UNOBSERVED, + changed ? this.observersForEvent(lifecycle.onEnter[to]) : UNOBSERVED, + changed ? this.observersForEvent(lifecycle.on[to]) : UNOBSERVED, + this.observersForEvent(lifecycle.onAfter.transition), + this.observersForEvent(lifecycle.onAfter[transition]), + this.observersForEvent(lifecycle.on[transition]) + ], args); + }, + + beginTransit: function() { this.pending = true; }, + endTransit: function(result) { this.pending = false; return result; }, + doTransit: function(lifecycle) { this.state = lifecycle.to; }, + + observe: function(args) { + if (args.length === 2) { + var observer = {}; + observer[args[0]] = args[1]; + this.observers.push(observer); + } + else { + this.observers.push(args[0]); + } + }, + + observersForEvent: function(event) { // TODO: this could be cached + var n = 0, max = this.observers.length, observer, result = []; + for( ; n < max ; n++) { + observer = this.observers[n]; + if (observer[event]) + result.push(observer); + } + return [ event, result, true ] + }, + + observeEvents: function(events, args, previousEvent) { + if (events.length === 0) { + return this.endTransit(true); + } + + var event = events[0][0], + observers = events[0][1], + pluggable = events[0][2]; + + args[0].event = event; + if (event && pluggable && event !== previousEvent) + plugin.hook(this, 'lifecycle', args); + + if (observers.length === 0) { + events.shift(); + return this.observeEvents(events, args, event); + } + else { + var observer = observers.shift(), + result = observer[event].apply(observer, args); + if (result && typeof result.then === 'function') { + return result.then(this.observeEvents.bind(this, events, args, event)) + .catch(this.endTransit.bind(this)) + } + else if (result === false) { + return this.endTransit(false); + } + else { + return this.observeEvents(events, args, event); + } + } + }, + + onInvalidTransition: function(transition, from, to) { + throw new Exception("transition is invalid in current state", transition, from, to, this.state); + }, + + onPendingTransition: function(transition, from, to) { + throw new Exception("transition is invalid while previous transition is still in progress", transition, from, to, this.state); + } + +}); + +//------------------------------------------------------------------------------------------------- + +module.exports = JSM; + +//------------------------------------------------------------------------------------------------- diff --git a/src/plugin.js b/src/plugin.js new file mode 100644 index 0000000..491e25a --- /dev/null +++ b/src/plugin.js @@ -0,0 +1,40 @@ +'use strict' + +//------------------------------------------------------------------------------------------------- + +var mixin = require('./util/mixin'); + +//------------------------------------------------------------------------------------------------- + +module.exports = { + + build: function(target, config) { + var n, max, plugin, plugins = config.plugins; + for(n = 0, max = plugins.length ; n < max ; n++) { + plugin = plugins[n]; + if (plugin.methods) + mixin(target, plugin.methods); + if (plugin.properties) + Object.defineProperties(target, plugin.properties); + } + }, + + hook: function(fsm, name, additional) { + var n, max, method, plugin, + plugins = fsm.config.plugins, + args = [fsm.context]; + + if (additional) + args = args.concat(additional) + + for(n = 0, max = plugins.length ; n < max ; n++) { + plugin = plugins[n] + method = plugins[n][name] + if (method) + method.apply(plugin, args); + } + } + +} + +//------------------------------------------------------------------------------------------------- diff --git a/src/plugin/history.js b/src/plugin/history.js new file mode 100644 index 0000000..ccfcd98 --- /dev/null +++ b/src/plugin/history.js @@ -0,0 +1,83 @@ +'use strict' + +//------------------------------------------------------------------------------------------------- + +var camelize = require('../util/camelize'); + +//------------------------------------------------------------------------------------------------- + +module.exports = function(options) { options = options || {}; + + var past = camelize(options.name || options.past || 'history'), + future = camelize( options.future || 'future'), + clear = camelize('clear-' + past), + back = camelize(past + '-back'), + forward = camelize(past + '-forward'), + canBack = camelize('can-' + back), + canForward = camelize('can-' + forward), + max = options.max; + + var plugin = { + + configure: function(config) { + config.addTransitionLifecycleNames(back); + config.addTransitionLifecycleNames(forward); + }, + + init: function(instance) { + instance[past] = []; + instance[future] = []; + }, + + lifecycle: function(instance, lifecycle) { + if (lifecycle.event === 'onEnterState') { + instance[past].push(lifecycle.to); + if (max && instance[past].length > max) + instance[past].shift(); + if (lifecycle.transition !== back && lifecycle.transition !== forward) + instance[future].length = 0; + } + }, + + methods: {}, + properties: {} + + } + + plugin.methods[clear] = function() { + this[past].length = 0 + this[future].length = 0 + } + + plugin.properties[canBack] = { + get: function() { + return this[past].length > 1 + } + } + + plugin.properties[canForward] = { + get: function() { + return this[future].length > 0 + } + } + + plugin.methods[back] = function() { + if (!this[canBack]) + throw Error('no history'); + var from = this[past].pop(), + to = this[past].pop(); + this[future].push(from); + this._fsm.transit(back, from, to, []); + } + + plugin.methods[forward] = function() { + if (!this[canForward]) + throw Error('no history'); + var from = this.state, + to = this[future].pop(); + this._fsm.transit(forward, from, to, []); + } + + return plugin; + +} diff --git a/src/plugin/visualize.js b/src/plugin/visualize.js new file mode 100644 index 0000000..3544ee9 --- /dev/null +++ b/src/plugin/visualize.js @@ -0,0 +1,161 @@ +'use strict' + +//------------------------------------------------------------------------------------------------- + +var mixin = require('../util/mixin') + +//------------------------------------------------------------------------------------------------- + +function visualize(fsm, options) { + return dotify(dotcfg(fsm, options)); +} + +//------------------------------------------------------------------------------------------------- + +function dotcfg(fsm, options) { + + options = options || {} + + var config = dotcfg.fetch(fsm), + name = options.name, + rankdir = dotcfg.rankdir(options.orientation), + states = dotcfg.states(config, options), + transitions = dotcfg.transitions(config, options), + result = { } + + if (name) + result.name = name + + if (rankdir) + result.rankdir = rankdir + + if (states && states.length > 0) + result.states = states + + if (transitions && transitions.length > 0) + result.transitions = transitions + + return result +} + +//------------------------------------------------------------------------------------------------- + +dotcfg.fetch = function(fsm) { + return (typeof fsm === 'function') ? fsm.prototype._fsm.config + : fsm._fsm.config +} + +dotcfg.rankdir = function(orientation) { + if (orientation === 'horizontal') + return 'LR'; + else if (orientation === 'vertical') + return 'TB'; +} + +dotcfg.states = function(config, options) { + var index, states = config.states; + if (!options.init) { // if not showing init transition, then slice out the implied init :from state + index = states.indexOf(config.init.from); + states = states.slice(0, index).concat(states.slice(index+1)); + } + return states; +} + +dotcfg.transitions = function(config, options) { + var n, max, transition, + init = config.init, + transitions = config.source.transitions || [], // easier to visualize using the ORIGINAL transition declarations rather than our run-time mapping + output = []; + if (options.init && init.active) + dotcfg.transition(init.name, init.from, init.to, init.dot, config, options, output) + for (n = 0, max = transitions.length ; n < max ; n++) { + transition = config.source.transitions[n] + dotcfg.transition(transition.name, transition.from, transition.to, transition.dot, config, options, output) + } + return output +} + +dotcfg.transition = function(name, from, to, dot, config, options, output) { + var n, max, wildcard = config.defaults.wildcard + + if (Array.isArray(from)) { + for(n = 0, max = from.length ; n < max ; n++) + dotcfg.transition(name, from[n], to, dot, config, options, output) + } + else if (from === wildcard || from === undefined) { + for(n = 0, max = config.states.length ; n < max ; n++) + dotcfg.transition(name, config.states[n], to, dot, config, options, output) + } + else if (to === wildcard || to === undefined) { + dotcfg.transition(name, from, from, dot, config, options, output) + } + else if (typeof to === 'function') { + // do nothing, can't display conditional transition + } + else { + output.push(mixin({}, { from: from, to: to, label: pad(name) }, dot || {})) + } + +} + +//------------------------------------------------------------------------------------------------- + +function pad(name) { + return " " + name + " " +} + +function quote(name) { + return "\"" + name + "\"" +} + +function dotify(dotcfg) { + + dotcfg = dotcfg || {}; + + var name = dotcfg.name || 'fsm', + states = dotcfg.states || [], + transitions = dotcfg.transitions || [], + rankdir = dotcfg.rankdir, + output = [], + n, max; + + output.push("digraph " + quote(name) + " {") + if (rankdir) + output.push(" rankdir=" + rankdir + ";") + for(n = 0, max = states.length ; n < max ; n++) + output.push(dotify.state(states[n])) + for(n = 0, max = transitions.length ; n < max ; n++) + output.push(dotify.edge(transitions[n])) + output.push("}") + return output.join("\n") + +} + +dotify.state = function(state) { + return " " + quote(state) + ";" +} + +dotify.edge = function(edge) { + return " " + quote(edge.from) + " -> " + quote(edge.to) + dotify.edge.attr(edge) + ";" +} + +dotify.edge.attr = function(edge) { + var n, max, key, keys = Object.keys(edge).sort(), output = []; + for(n = 0, max = keys.length ; n < max ; n++) { + key = keys[n]; + if (key !== 'from' && key !== 'to') + output.push(key + "=" + quote(edge[key])) + } + return output.length > 0 ? " [ " + output.join(" ; ") + " ]" : "" +} + +//------------------------------------------------------------------------------------------------- + +visualize.dotcfg = dotcfg; +visualize.dotify = dotify; + +//------------------------------------------------------------------------------------------------- + +module.exports = visualize; + +//------------------------------------------------------------------------------------------------- diff --git a/src/util/camelize.js b/src/util/camelize.js new file mode 100644 index 0000000..87c7540 --- /dev/null +++ b/src/util/camelize.js @@ -0,0 +1,9 @@ +'use strict' + +module.exports = function(label) { + var n, word, words = label.split(/[_-]/), result = words[0]; + for(n = 1 ; n < words.length ; n++) { + result = result + words[n].charAt(0).toUpperCase() + words[n].substring(1); + } + return result; +} diff --git a/src/util/exception.js b/src/util/exception.js new file mode 100644 index 0000000..cf62304 --- /dev/null +++ b/src/util/exception.js @@ -0,0 +1,9 @@ +'use strict' + +module.exports = function(message, transition, from, to, current) { + this.message = message; + this.transition = transition; + this.from = from; + this.to = to; + this.current = current; +} diff --git a/src/util/mixin.js b/src/util/mixin.js new file mode 100644 index 0000000..330664b --- /dev/null +++ b/src/util/mixin.js @@ -0,0 +1,13 @@ +'use strict' + +module.exports = function(target, sources) { + var n, source, key; + for(n = 1 ; n < arguments.length ; n++) { + source = arguments[n]; + for(key in source) { + if (source.hasOwnProperty(key)) + target[key] = source[key]; + } + } + return target; +} diff --git a/state-machine.js b/state-machine.js deleted file mode 100755 index e917a2a..0000000 --- a/state-machine.js +++ /dev/null @@ -1,230 +0,0 @@ -/* - - Javascript State Machine Library - https://github.com/jakesgordon/javascript-state-machine - - Copyright (c) 2012, 2013, 2014, 2015, Jake Gordon and contributors - Released under the MIT license - https://github.com/jakesgordon/javascript-state-machine/blob/master/LICENSE - -*/ - -(function () { - - var StateMachine = { - - //--------------------------------------------------------------------------- - - VERSION: "2.4.0", - - //--------------------------------------------------------------------------- - - Result: { - SUCCEEDED: 1, // the event transitioned successfully from one state to another - NOTRANSITION: 2, // the event was successfull but no state transition was necessary - CANCELLED: 3, // the event was cancelled by the caller in a beforeEvent callback - PENDING: 4 // the event is asynchronous and the caller is in control of when the transition occurs - }, - - Error: { - INVALID_TRANSITION: 100, // caller tried to fire an event that was innapropriate in the current state - PENDING_TRANSITION: 200, // caller tried to fire an event while an async transition was still pending - INVALID_CALLBACK: 300 // caller provided callback function threw an exception - }, - - WILDCARD: '*', - ASYNC: 'async', - - //--------------------------------------------------------------------------- - - create: function(cfg, target) { - - var initial = (typeof cfg.initial == 'string') ? { state: cfg.initial } : cfg.initial; // allow for a simple string, or an object with { state: 'foo', event: 'setup', defer: true|false } - var terminal = cfg.terminal || cfg['final']; - var fsm = target || cfg.target || {}; - var events = cfg.events || []; - var callbacks = cfg.callbacks || {}; - var map = {}; // track state transitions allowed for an event { event: { from: [ to ] } } - var transitions = {}; // track events allowed from a state { state: [ event ] } - - var add = function(e) { - var from = Array.isArray(e.from) ? e.from : (e.from ? [e.from] : [StateMachine.WILDCARD]); // allow 'wildcard' transition if 'from' is not specified - map[e.name] = map[e.name] || {}; - for (var n = 0 ; n < from.length ; n++) { - transitions[from[n]] = transitions[from[n]] || []; - transitions[from[n]].push(e.name); - - map[e.name][from[n]] = e.to || from[n]; // allow no-op transition if 'to' is not specified - } - if (e.to) - transitions[e.to] = transitions[e.to] || []; - }; - - if (initial) { - initial.event = initial.event || 'startup'; - add({ name: initial.event, from: 'none', to: initial.state }); - } - - for(var n = 0 ; n < events.length ; n++) - add(events[n]); - - for(var name in map) { - if (map.hasOwnProperty(name)) - fsm[name] = StateMachine.buildEvent(name, map[name]); - } - - for(var name in callbacks) { - if (callbacks.hasOwnProperty(name)) - fsm[name] = callbacks[name] - } - - fsm.current = 'none'; - fsm.is = function(state) { return Array.isArray(state) ? (state.indexOf(this.current) >= 0) : (this.current === state); }; - fsm.can = function(event) { return !this.transition && (map[event] !== undefined) && (map[event].hasOwnProperty(this.current) || map[event].hasOwnProperty(StateMachine.WILDCARD)); } - fsm.cannot = function(event) { return !this.can(event); }; - fsm.transitions = function() { return (transitions[this.current] || []).concat(transitions[StateMachine.WILDCARD] || []); }; - fsm.isFinished = function() { return this.is(terminal); }; - fsm.error = cfg.error || function(name, from, to, args, error, msg, e) { throw e || msg; }; // default behavior when something unexpected happens is to throw an exception, but caller can override this behavior if desired (see github issue #3 and #17) - fsm.states = function() { return Object.keys(transitions).sort() }; - - if (initial && !initial.defer) - fsm[initial.event](); - - return fsm; - - }, - - //=========================================================================== - - doCallback: function(fsm, func, name, from, to, args) { - if (func) { - try { - return func.apply(fsm, [name, from, to].concat(args)); - } - catch(e) { - return fsm.error(name, from, to, args, StateMachine.Error.INVALID_CALLBACK, "an exception occurred in a caller-provided callback function", e); - } - } - }, - - beforeAnyEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbeforeevent'], name, from, to, args); }, - afterAnyEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafterevent'] || fsm['onevent'], name, from, to, args); }, - leaveAnyState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleavestate'], name, from, to, args); }, - enterAnyState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenterstate'] || fsm['onstate'], name, from, to, args); }, - changeState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onchangestate'], name, from, to, args); }, - - beforeThisEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbefore' + name], name, from, to, args); }, - afterThisEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafter' + name] || fsm['on' + name], name, from, to, args); }, - leaveThisState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleave' + from], name, from, to, args); }, - enterThisState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenter' + to] || fsm['on' + to], name, from, to, args); }, - - beforeEvent: function(fsm, name, from, to, args) { - if ((false === StateMachine.beforeThisEvent(fsm, name, from, to, args)) || - (false === StateMachine.beforeAnyEvent( fsm, name, from, to, args))) - return false; - }, - - afterEvent: function(fsm, name, from, to, args) { - StateMachine.afterThisEvent(fsm, name, from, to, args); - StateMachine.afterAnyEvent( fsm, name, from, to, args); - }, - - leaveState: function(fsm, name, from, to, args) { - var specific = StateMachine.leaveThisState(fsm, name, from, to, args), - general = StateMachine.leaveAnyState( fsm, name, from, to, args); - if ((false === specific) || (false === general)) - return false; - else if ((StateMachine.ASYNC === specific) || (StateMachine.ASYNC === general)) - return StateMachine.ASYNC; - }, - - enterState: function(fsm, name, from, to, args) { - StateMachine.enterThisState(fsm, name, from, to, args); - StateMachine.enterAnyState( fsm, name, from, to, args); - }, - - //=========================================================================== - - buildEvent: function(name, map) { - return function() { - - var from = this.current; - var to = map[from] || (map[StateMachine.WILDCARD] != StateMachine.WILDCARD ? map[StateMachine.WILDCARD] : from) || from; - var args = Array.prototype.slice.call(arguments); // turn arguments into pure array - - if (this.transition) - return this.error(name, from, to, args, StateMachine.Error.PENDING_TRANSITION, "event " + name + " inappropriate because previous transition did not complete"); - - if (this.cannot(name)) - return this.error(name, from, to, args, StateMachine.Error.INVALID_TRANSITION, "event " + name + " inappropriate in current state " + this.current); - - if (false === StateMachine.beforeEvent(this, name, from, to, args)) - return StateMachine.Result.CANCELLED; - - if (from === to) { - StateMachine.afterEvent(this, name, from, to, args); - return StateMachine.Result.NOTRANSITION; - } - - // prepare a transition method for use EITHER lower down, or by caller if they want an async transition (indicated by an ASYNC return value from leaveState) - var fsm = this; - this.transition = function() { - fsm.transition = null; // this method should only ever be called once - fsm.current = to; - StateMachine.enterState( fsm, name, from, to, args); - StateMachine.changeState(fsm, name, from, to, args); - StateMachine.afterEvent( fsm, name, from, to, args); - return StateMachine.Result.SUCCEEDED; - }; - this.transition.cancel = function() { // provide a way for caller to cancel async transition if desired (issue #22) - fsm.transition = null; - StateMachine.afterEvent(fsm, name, from, to, args); - } - - var leave = StateMachine.leaveState(this, name, from, to, args); - if (false === leave) { - this.transition = null; - return StateMachine.Result.CANCELLED; - } - else if (StateMachine.ASYNC === leave) { - return StateMachine.Result.PENDING; - } - else { - if (this.transition) // need to check in case user manually called transition() but forgot to return StateMachine.ASYNC - return this.transition(); - } - - }; - } - - }; // StateMachine - - //=========================================================================== - - //====== - // NODE - //====== - if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - exports = module.exports = StateMachine; - } - exports.StateMachine = StateMachine; - } - //============ - // AMD/REQUIRE - //============ - else if (typeof define === 'function' && define.amd) { - define(function(require) { return StateMachine; }); - } - //======== - // BROWSER - //======== - else if (typeof window !== 'undefined') { - window.StateMachine = StateMachine; - } - //=========== - // WEB WORKER - //=========== - else if (typeof self !== 'undefined') { - self.StateMachine = StateMachine; - } - -}()); diff --git a/state-machine.min.js b/state-machine.min.js deleted file mode 100644 index 9093160..0000000 --- a/state-machine.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){var t={VERSION:"2.4.0",Result:{SUCCEEDED:1,NOTRANSITION:2,CANCELLED:3,PENDING:4},Error:{INVALID_TRANSITION:100,PENDING_TRANSITION:200,INVALID_CALLBACK:300},WILDCARD:"*",ASYNC:"async",create:function(e,n){var r="string"==typeof e.initial?{state:e.initial}:e.initial,i=e.terminal||e.final,a=n||e.target||{},o=e.events||[],u=e.callbacks||{},s={},c={},f=function(e){var n=Array.isArray(e.from)?e.from:e.from?[e.from]:[t.WILDCARD];s[e.name]=s[e.name]||{};for(var r=0;r=0:this.current===t},a.can=function(e){return!this.transition&&void 0!==s[e]&&(s[e].hasOwnProperty(this.current)||s[e].hasOwnProperty(t.WILDCARD))},a.cannot=function(t){return!this.can(t)},a.transitions=function(){return(c[this.current]||[]).concat(c[t.WILDCARD]||[])},a.isFinished=function(){return this.is(i)},a.error=e.error||function(t,e,n,r,i,a,o){throw o||a},a.states=function(){return Object.keys(c).sort()},r&&!r.defer&&a[r.event](),a},doCallback:function(e,n,r,i,a,o){if(n)try{return n.apply(e,[r,i,a].concat(o))}catch(n){return e.error(r,i,a,o,t.Error.INVALID_CALLBACK,"an exception occurred in a caller-provided callback function",n)}},beforeAnyEvent:function(e,n,r,i,a){return t.doCallback(e,e.onbeforeevent,n,r,i,a)},afterAnyEvent:function(e,n,r,i,a){return t.doCallback(e,e.onafterevent||e.onevent,n,r,i,a)},leaveAnyState:function(e,n,r,i,a){return t.doCallback(e,e.onleavestate,n,r,i,a)},enterAnyState:function(e,n,r,i,a){return t.doCallback(e,e.onenterstate||e.onstate,n,r,i,a)},changeState:function(e,n,r,i,a){return t.doCallback(e,e.onchangestate,n,r,i,a)},beforeThisEvent:function(e,n,r,i,a){return t.doCallback(e,e["onbefore"+n],n,r,i,a)},afterThisEvent:function(e,n,r,i,a){return t.doCallback(e,e["onafter"+n]||e["on"+n],n,r,i,a)},leaveThisState:function(e,n,r,i,a){return t.doCallback(e,e["onleave"+r],n,r,i,a)},enterThisState:function(e,n,r,i,a){return t.doCallback(e,e["onenter"+i]||e["on"+i],n,r,i,a)},beforeEvent:function(e,n,r,i,a){if(!1===t.beforeThisEvent(e,n,r,i,a)||!1===t.beforeAnyEvent(e,n,r,i,a))return!1},afterEvent:function(e,n,r,i,a){t.afterThisEvent(e,n,r,i,a),t.afterAnyEvent(e,n,r,i,a)},leaveState:function(e,n,r,i,a){var o=t.leaveThisState(e,n,r,i,a),u=t.leaveAnyState(e,n,r,i,a);return!1!==o&&!1!==u&&(t.ASYNC===o||t.ASYNC===u?t.ASYNC:void 0)},enterState:function(e,n,r,i,a){t.enterThisState(e,n,r,i,a),t.enterAnyState(e,n,r,i,a)},buildEvent:function(e,n){return function(){var r=this.current,i=n[r]||(n[t.WILDCARD]!=t.WILDCARD?n[t.WILDCARD]:r)||r,a=Array.prototype.slice.call(arguments);if(this.transition)return this.error(e,r,i,a,t.Error.PENDING_TRANSITION,"event "+e+" inappropriate because previous transition did not complete");if(this.cannot(e))return this.error(e,r,i,a,t.Error.INVALID_TRANSITION,"event "+e+" inappropriate in current state "+this.current);if(!1===t.beforeEvent(this,e,r,i,a))return t.Result.CANCELLED;if(r===i)return t.afterEvent(this,e,r,i,a),t.Result.NOTRANSITION;var o=this;this.transition=function(){return o.transition=null,o.current=i,t.enterState(o,e,r,i,a),t.changeState(o,e,r,i,a),t.afterEvent(o,e,r,i,a),t.Result.SUCCEEDED},this.transition.cancel=function(){o.transition=null,t.afterEvent(o,e,r,i,a)};var u=t.leaveState(this,e,r,i,a);return!1===u?(this.transition=null,t.Result.CANCELLED):t.ASYNC===u?t.Result.PENDING:this.transition?this.transition():void 0}}};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=t),exports.StateMachine=t):"function"==typeof define&&define.amd?define(function(e){return t}):"undefined"!=typeof window?window.StateMachine=t:"undefined"!=typeof self&&(self.StateMachine=t)}(); \ No newline at end of file diff --git a/test/basics.js b/test/basics.js new file mode 100644 index 0000000..fdd2fc4 --- /dev/null +++ b/test/basics.js @@ -0,0 +1,130 @@ +import test from 'ava'; +import StateMachine from '../src/app'; + +//------------------------------------------------------------------------------------------------- + +test('version', t => { + t.is(StateMachine.version, '3.0.0'); +}); + +//------------------------------------------------------------------------------------------------- + +test('state machine', t => { + + var fsm = new StateMachine({ + init: 'green', + transitions: [ + { name: 'warn', from: 'green', to: 'yellow' }, + { name: 'panic', from: 'yellow', to: 'red' }, + { name: 'calm', from: 'red', to: 'yellow' }, + { name: 'clear', from: 'yellow', to: 'green' } + ] + }) + + t.is(fsm.state, 'green') + + fsm.warn(); t.is(fsm.state, 'yellow') + fsm.panic(); t.is(fsm.state, 'red') + fsm.calm(); t.is(fsm.state, 'yellow') + fsm.clear(); t.is(fsm.state, 'green') + +}); + +//----------------------------------------------------------------------------- + +test('state machine factory', t => { + + var Alarm = StateMachine.factory({ + init: 'green', + transitions: [ + { name: 'warn', from: 'green', to: 'yellow' }, + { name: 'panic', from: 'yellow', to: 'red' }, + { name: 'calm', from: 'red', to: 'yellow' }, + { name: 'clear', from: 'yellow', to: 'green' } + ] + }), + a = new Alarm(), + b = new Alarm(); + + t.is(a.state, 'green') + t.is(b.state, 'green') + + a.warn(); t.is(a.state, 'yellow'); t.is(b.state, 'green') + a.panic(); t.is(a.state, 'red'); t.is(b.state, 'green') + a.calm(); t.is(a.state, 'yellow'); t.is(b.state, 'green') + a.clear(); t.is(a.state, 'green'); t.is(b.state, 'green') + + b.warn(); t.is(a.state, 'green'); t.is(b.state, 'yellow') + b.panic(); t.is(a.state, 'green'); t.is(b.state, 'red') + b.calm(); t.is(a.state, 'green'); t.is(b.state, 'yellow') + b.clear(); t.is(a.state, 'green'); t.is(b.state, 'green') + +}); + +//----------------------------------------------------------------------------- + +test('state machine - applied to existing object', t => { + + var obj = { name: 'alarm' } + + StateMachine.apply(obj, { + init: 'green', + transitions: [ + { name: 'warn', from: 'green', to: 'yellow' }, + { name: 'panic', from: 'yellow', to: 'red' }, + { name: 'calm', from: 'red', to: 'yellow' }, + { name: 'clear', from: 'yellow', to: 'green' } + ] + }); + + t.is(obj.name, 'alarm'); + t.is(obj.state, 'green'); + + obj.warn(); t.is(obj.state, 'yellow') + obj.panic(); t.is(obj.state, 'red') + obj.calm(); t.is(obj.state, 'yellow') + obj.clear(); t.is(obj.state, 'green') + +}); + +//----------------------------------------------------------------------------- + +test('state machine factory - applied to existing class', t => { + + function Alarm(name) { + this.name = name + this._fsm(); // manual step needed to construct this FSM instance + } + + StateMachine.factory(Alarm, { + init: 'green', + transitions: [ + { name: 'warn', from: 'green', to: 'yellow' }, + { name: 'panic', from: 'yellow', to: 'red' }, + { name: 'calm', from: 'red', to: 'yellow' }, + { name: 'clear', from: 'yellow', to: 'green' } + ] + }); + + var a = new Alarm('A'), + b = new Alarm('B'); + + t.is(a.name, 'A') + t.is(b.name, 'B') + + t.is(a.state, 'green') + t.is(b.state, 'green') + + a.warn(); t.is(a.state, 'yellow'); t.is(b.state, 'green') + a.panic(); t.is(a.state, 'red'); t.is(b.state, 'green') + a.calm(); t.is(a.state, 'yellow'); t.is(b.state, 'green') + a.clear(); t.is(a.state, 'green'); t.is(b.state, 'green') + + b.warn(); t.is(a.state, 'green'); t.is(b.state, 'yellow') + b.panic(); t.is(a.state, 'green'); t.is(b.state, 'red') + b.calm(); t.is(a.state, 'green'); t.is(b.state, 'yellow') + b.clear(); t.is(a.state, 'green'); t.is(b.state, 'green') + +}); + +//----------------------------------------------------------------------------- diff --git a/test/construction.js b/test/construction.js new file mode 100644 index 0000000..911b5a3 --- /dev/null +++ b/test/construction.js @@ -0,0 +1,229 @@ +import test from 'ava' +import StateMachine from '../src/app' + +//------------------------------------------------------------------------------------------------- + +test('singleton construction', t => { + + var fsm = new StateMachine({ + transitions: [ + { name: 'init', from: 'none', to: 'A' }, + { name: 'step1', from: 'A', to: 'B' }, + { name: 'step2', from: 'B', to: 'C' } + ] + }); + + t.is(fsm.state, 'none') + + t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B', 'C' ]) + t.deepEqual(fsm.allTransitions(), [ 'init', 'step1', 'step2' ]) + t.deepEqual(fsm.transitions(), [ 'init' ]) + +}) + + +//------------------------------------------------------------------------------------------------- + +test('singleton construction - with init state', t => { + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step1', from: 'A', to: 'B' }, + { name: 'step2', from: 'B', to: 'C' } + ] + }); + + t.is(fsm.state, 'A') + + t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B', 'C' ]) + t.deepEqual(fsm.allTransitions(), [ 'init', 'step1', 'step2' ]) + t.deepEqual(fsm.transitions(), [ 'step1' ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('singleton construction - with init state and transition', t => { + + var fsm = new StateMachine({ + init: { name: 'boot', to: 'A' }, + transitions: [ + { name: 'step1', from: 'A', to: 'B' }, + { name: 'step2', from: 'B', to: 'C' } + ] + }); + + t.is(fsm.state, 'A') + + t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B', 'C' ]) + t.deepEqual(fsm.allTransitions(), [ 'boot', 'step1', 'step2' ]) + t.deepEqual(fsm.transitions(), [ 'step1' ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('singleton construction - with init state, transition, AND from state', t => { + + var fsm = new StateMachine({ + init: { name: 'boot', from: 'booting', to: 'A' }, + transitions: [ + { name: 'step1', from: 'A', to: 'B' }, + { name: 'step2', from: 'B', to: 'C' } + ] + }); + + t.is(fsm.state, 'A') + + t.deepEqual(fsm.allStates(), [ 'booting', 'A', 'B', 'C' ]) + t.deepEqual(fsm.allTransitions(), [ 'boot', 'step1', 'step2' ]) + t.deepEqual(fsm.transitions(), [ 'step1' ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('singleton construction - with custom data and methods', t => { + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step1', from: 'A', to: 'B' }, + { name: 'step2', from: 'B', to: 'C' } + ], + data: { + value: 42 + }, + methods: { + talk: function() { + return this.state + ' - ' + this.value + } + } + }); + + t.is(fsm.state, 'A') + t.is(fsm.value, 42) + t.is(fsm.talk(), 'A - 42') + + fsm.step1() + + t.is(fsm.state, 'B') + t.is(fsm.value, 42) + t.is(fsm.talk(), 'B - 42') + + fsm.value = 99 + + t.is(fsm.state, 'B') + t.is(fsm.value, 99) + t.is(fsm.talk(), 'B - 99') + +}) + +//------------------------------------------------------------------------------------------------- + +test('factory construction', t => { + + var MyClass = StateMachine.factory({ + init: 'A', + transitions: [ + { name: 'step1', from: 'A', to: 'B' }, + { name: 'step2', from: 'B', to: 'C' } + ] + }); + + var fsm1 = new MyClass(), + fsm2 = new MyClass(), + fsm3 = new MyClass(); + + fsm2.step1() + fsm3.step1() + fsm3.step2() + + t.is(fsm1.state, 'A') + t.is(fsm2.state, 'B') + t.is(fsm3.state, 'C') + + t.deepEqual(fsm1.allStates(), [ 'none', 'A', 'B', 'C' ]) + t.deepEqual(fsm2.allStates(), [ 'none', 'A', 'B', 'C' ]) + t.deepEqual(fsm3.allStates(), [ 'none', 'A', 'B', 'C' ]) + + t.deepEqual(fsm1.allTransitions(), [ 'init', 'step1', 'step2' ]) + t.deepEqual(fsm2.allTransitions(), [ 'init', 'step1', 'step2' ]) + t.deepEqual(fsm3.allTransitions(), [ 'init', 'step1', 'step2' ]) + + t.deepEqual(fsm1.transitions(), [ 'step1' ]) + t.deepEqual(fsm2.transitions(), [ 'step2' ]) + t.deepEqual(fsm3.transitions(), [ ]) + + t.is(fsm1.allStates, MyClass.prototype.allStates) + t.is(fsm2.allStates, MyClass.prototype.allStates) + t.is(fsm3.allStates, MyClass.prototype.allStates) + +}) + +//------------------------------------------------------------------------------------------------- + +test('factory construction - with custom data and methods', t => { + + var MyClass = StateMachine.factory({ + init: 'A', + transitions: [ + { name: 'step1', from: 'A', to: 'B' }, + { name: 'step2', from: 'B', to: 'C' } + ], + data: function(value) { + return { + value: value + } + }, + methods: { + talk: function() { + return this.state + ' - ' + this.value + } + } + }); + + var fsm1 = new MyClass(1), + fsm2 = new MyClass(2), + fsm3 = new MyClass(3); + + t.is(fsm1.state, 'A') + t.is(fsm2.state, 'A') + t.is(fsm3.state, 'A') + + t.is(fsm1.talk(), 'A - 1') + t.is(fsm2.talk(), 'A - 2') + t.is(fsm3.talk(), 'A - 3') + + fsm2.step1() + fsm3.step1() + fsm3.step2() + + t.is(fsm1.state, 'A') + t.is(fsm2.state, 'B') + t.is(fsm3.state, 'C') + + t.is(fsm1.talk(), 'A - 1') + t.is(fsm2.talk(), 'B - 2') + t.is(fsm3.talk(), 'C - 3') + + t.deepEqual(fsm1.allStates(), [ 'none', 'A', 'B', 'C' ]) + t.deepEqual(fsm2.allStates(), [ 'none', 'A', 'B', 'C' ]) + t.deepEqual(fsm3.allStates(), [ 'none', 'A', 'B', 'C' ]) + + t.deepEqual(fsm1.allTransitions(), [ 'init', 'step1', 'step2' ]) + t.deepEqual(fsm2.allTransitions(), [ 'init', 'step1', 'step2' ]) + t.deepEqual(fsm3.allTransitions(), [ 'init', 'step1', 'step2' ]) + + t.deepEqual(fsm1.transitions(), [ 'step1' ]) + t.deepEqual(fsm2.transitions(), [ 'step2' ]) + t.deepEqual(fsm3.transitions(), [ ]) + + t.is(fsm1.allStates, MyClass.prototype.allStates) + t.is(fsm2.allStates, MyClass.prototype.allStates) + t.is(fsm3.allStates, MyClass.prototype.allStates) + +}) + +//------------------------------------------------------------------------------------------------- diff --git a/test/defaults.js b/test/defaults.js new file mode 100644 index 0000000..da1bc08 --- /dev/null +++ b/test/defaults.js @@ -0,0 +1,62 @@ +import test from 'ava'; +import StateMachine from '../src/app'; + +//------------------------------------------------------------------------------------------------- + +const defaults = JSON.stringify(StateMachine.defaults); + +test.afterEach.always('restore defaults', t => { + StateMachine.defaults = JSON.parse(defaults); +}); + +//------------------------------------------------------------------------------------------------- + +test.serial('override global initialization defaults', t => { + + StateMachine.defaults.init = { + name: 'boot', + from: 'booting' + } + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step1', from: 'A', to: 'B' }, + { name: 'step2', from: 'B', to: 'C' } + ] + }); + + t.is(fsm.state, 'A'); + + t.deepEqual(fsm.allStates(), [ 'booting', 'A', 'B', 'C' ]); + t.deepEqual(fsm.allTransitions(), [ 'boot', 'step1', 'step2' ]); + t.deepEqual(fsm.transitions(), [ 'step1' ]); + +}); + +//------------------------------------------------------------------------------------------------- + +test.serial('override global initialization defaults (again)', t => { + + StateMachine.defaults.init = { + name: 'start', + from: 'unknown' + } + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step1', from: 'A', to: 'B' }, + { name: 'step2', from: 'B', to: 'C' } + ] + }); + + t.is(fsm.state, 'A'); + + t.deepEqual(fsm.allStates(), [ 'unknown', 'A', 'B', 'C' ]); + t.deepEqual(fsm.allTransitions(), [ 'start', 'step1', 'step2' ]); + t.deepEqual(fsm.transitions(), [ 'step1' ]); + +}); + +//------------------------------------------------------------------------------------------------- diff --git a/test/empty.js b/test/empty.js new file mode 100644 index 0000000..ad2ce62 --- /dev/null +++ b/test/empty.js @@ -0,0 +1,79 @@ +import test from 'ava' +import StateMachine from '../src/app' + +//------------------------------------------------------------------------------------------------- + +test('empty state machine', t => { + + var fsm = new StateMachine(); + + t.is(fsm.state, 'none') + + t.deepEqual(fsm.allStates(), [ 'none' ]) + t.deepEqual(fsm.allTransitions(), [ ]) + t.deepEqual(fsm.transitions(), [ ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('empty state machine - but caller forgot new keyword', t => { + + var fsm = StateMachine() // NOTE: missing 'new' + + t.is(fsm.state, 'none') + + t.deepEqual(fsm.allStates(), [ 'none' ]) + t.deepEqual(fsm.allTransitions(), [ ]) + t.deepEqual(fsm.transitions(), [ ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('empty state machine - applied to existing object', t => { + + var fsm = {}; + + StateMachine.apply(fsm) + + t.is(fsm.state, 'none') + + t.deepEqual(fsm.allStates(), [ 'none' ]) + t.deepEqual(fsm.allTransitions(), [ ]) + t.deepEqual(fsm.transitions(), [ ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('empty state machine factory', t => { + + var FSM = StateMachine.factory(), + fsm = new FSM(); + + t.is(fsm.state, 'none') + t.deepEqual(fsm.allStates(), [ 'none' ]) + t.deepEqual(fsm.allTransitions(), [ ]) + t.deepEqual(fsm.transitions(), [ ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('empty state machine factory - applied to existing class', t => { + + var FSM = function() { this._fsm() }; + + StateMachine.factory(FSM) + + var fsm = new FSM() + + t.is(fsm.state, 'none') + t.deepEqual(fsm.allStates(), [ 'none' ]) + t.deepEqual(fsm.allTransitions(), [ ]) + t.deepEqual(fsm.transitions(), [ ]) + +}) + +//------------------------------------------------------------------------------------------------- diff --git a/test/errors.js b/test/errors.js new file mode 100644 index 0000000..fb852ca --- /dev/null +++ b/test/errors.js @@ -0,0 +1,160 @@ +import test from 'ava' +import StateMachine from '../src/app' + +//------------------------------------------------------------------------------------------------- + +test('state cannot be modified directly', t => { + + var fsm = new StateMachine({ + transitions: [ + { name: 'step', from: 'none', to: 'complete' } + ] + }) + + t.is(fsm.state, 'none') + var error = t.throws(() => { + fsm.state = 'other' + }) + t.is(error.message, 'use transitions to change state') + t.is(fsm.state, 'none') + +}) + +//------------------------------------------------------------------------------------------------- + +test('StateMachine.apply only allowed on objects', t => { + + var config = { + transitions: [ + { name: 'step', from: 'none', to: 'complete' } + ] + }; + + var error = t.throws(() => { + StateMachine.apply(function() {}, config) + }) + t.is(error.message, 'StateMachine can only be applied to objects') + + error = t.throws(() => { + StateMachine.apply([], config) + }) + t.is(error.message, 'StateMachine can only be applied to objects') + + error = t.throws(() => { + StateMachine.apply(42, config) + }) + t.is(error.message, 'StateMachine can only be applied to objects') + +}) + +//------------------------------------------------------------------------------------------------- + +test('invalid transition raises an exception', t => { + + var fsm = new StateMachine({ + transitions: [ + { name: 'step1', from: 'none', to: 'A' }, + { name: 'step2', from: 'A', to: 'B' } + ] + }); + + t.is(fsm.state, 'none') + t.is(fsm.can('step1'), true) + t.is(fsm.can('step2'), false) + + const error = t.throws(() => { + fsm.step2(); + }) + + t.is(error.message, 'transition is invalid in current state') + t.is(error.transition, 'step2') + t.is(error.from, 'none') + t.is(error.to, undefined) + t.is(error.current, 'none') + +}) + +//------------------------------------------------------------------------------------------------- + +test('invalid transition handler can be customized', t => { + + var fsm = new StateMachine({ + transitions: [ + { name: 'step1', from: 'none', to: 'A' }, + { name: 'step2', from: 'A', to: 'B' } + ], + methods: { + onInvalidTransition: function() { return 'custom error'; } + } + }); + + t.is(fsm.state, 'none') + t.is(fsm.can('step1'), true) + t.is(fsm.can('step2'), false) + t.is(fsm.step2(), 'custom error') + t.is(fsm.state, 'none') + +}) + +//------------------------------------------------------------------------------------------------- + +test('fire transition while existing transition is still in process raises an exception', t => { + + var fsm = new StateMachine({ + transitions: [ + { name: 'step', from: 'none', to: 'A' }, + { name: 'other', from: '*', to: 'X' } + ], + methods: { + onBeforeStep: function() { this.other(); }, + onBeforeOther: function() { t.fail('should never happen') }, + onEnterX: function() { t.fail('should never happen') } + } + }); + + t.is(fsm.state, 'none') + t.is(fsm.can('step'), true) + t.is(fsm.can('other'), true) + + const error = t.throws(() => { + fsm.step() + }) + + t.is(error.message, 'transition is invalid while previous transition is still in progress') + t.is(error.transition, 'other') + t.is(error.from, 'none') + t.is(error.to, 'X') + t.is(error.current, 'none') + + t.is(fsm.state, 'none', 'entire transition was cancelled by the exception') + +}) + +//------------------------------------------------------------------------------------------------- + +test('pending transition handler can be customized', t => { + + var error = "", + fsm = new StateMachine({ + transitions: [ + { name: 'step', from: 'none', to: 'A' }, + { name: 'other', from: '*', to: 'X' } + ], + methods: { + onBeforeStep: function() { error = this.other(); return false }, + onPendingTransition: function() { return 'custom error' }, + onBeforeOther: function() { t.fail('should never happen') }, + onEnterX: function() { t.fail('should never happen') } + } + }); + + t.is(fsm.state, 'none') + t.is(fsm.can('step'), true) + t.is(fsm.can('other'), true) + t.is(fsm.step(), false) + t.is(fsm.state, 'none') + t.is(error, 'custom error') + +}) + +//------------------------------------------------------------------------------------------------- diff --git a/test/goto.js b/test/goto.js new file mode 100644 index 0000000..e8fcbcc --- /dev/null +++ b/test/goto.js @@ -0,0 +1,195 @@ +import test from 'ava' +import StateMachine from '../src/app' +import LifecycleLogger from './helpers/lifecycle_logger' + +//------------------------------------------------------------------------------------------------- + +function goto(state) { + return state +} + +//------------------------------------------------------------------------------------------------- + +test('goto transition', t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'D' }, + { name: 'goto', from: '*', to: goto } + ], + methods: { + onBeforeTransition: logger, + onBeforeInit: logger, + onBeforeStep: logger, + onBeforeGoto: logger, + onLeaveState: logger, + onLeaveNone: logger, + onLeaveA: logger, + onLeaveB: logger, + onLeaveC: logger, + onLeaveD: logger, + onTransition: logger, + onEnterState: logger, + onEnterNone: logger, + onEnterA: logger, + onEnterB: logger, + onEnterC: logger, + onEnterD: logger, + onAfterTransition: logger, + onAfterInit: logger, + onAfterStep: logger, + onAfterGoto: logger + } + }); + + t.is(fsm.state, 'A') + t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B', 'C', 'D' ]) + t.deepEqual(fsm.allTransitions(), [ 'init', 'step', 'goto' ]) + + logger.clear() + + fsm.goto('C') + + t.is(fsm.state, 'C') + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'goto', from: 'A', to: 'C', current: 'A', args: [ 'C' ] }, + { event: 'onBeforeGoto', transition: 'goto', from: 'A', to: 'C', current: 'A', args: [ 'C' ] }, + { event: 'onLeaveState', transition: 'goto', from: 'A', to: 'C', current: 'A', args: [ 'C' ] }, + { event: 'onLeaveA', transition: 'goto', from: 'A', to: 'C', current: 'A', args: [ 'C' ] }, + { event: 'onTransition', transition: 'goto', from: 'A', to: 'C', current: 'A', args: [ 'C' ] }, + { event: 'onEnterState', transition: 'goto', from: 'A', to: 'C', current: 'C', args: [ 'C' ] }, + { event: 'onEnterC', transition: 'goto', from: 'A', to: 'C', current: 'C', args: [ 'C' ] }, + { event: 'onAfterTransition', transition: 'goto', from: 'A', to: 'C', current: 'C', args: [ 'C' ] }, + { event: 'onAfterGoto', transition: 'goto', from: 'A', to: 'C', current: 'C', args: [ 'C' ] } + ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('goto can have additional arguments', t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'D' }, + { name: 'goto', from: '*', to: goto } + ], + methods: { + onStep: logger, + onGoto: logger + } + }); + + t.is(fsm.state, 'A') + t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B', 'C', 'D' ]) + t.deepEqual(fsm.allTransitions(), [ 'init', 'step', 'goto' ]) + + logger.clear() + + fsm.goto('C', 'with', 4, 'additional', 'arguments') + + t.is(fsm.state, 'C') + t.deepEqual(logger.log, [ + { event: 'onGoto', transition: 'goto', from: 'A', to: 'C', current: 'C', args: [ 'C', 'with', 4, 'additional', 'arguments' ] } + ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('goto can go to an unknown state', t => { + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'D' }, + { name: 'goto', from: '*', to: goto } + ] + }) + + t.is(fsm.state, 'A') + t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B', 'C', 'D' ]); + + fsm.goto('B') + t.is(fsm.state, 'B') + t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B', 'C', 'D' ]); + + fsm.goto('X') + t.is(fsm.state, 'X') + t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B', 'C', 'D', 'X' ]); + +}) + +//------------------------------------------------------------------------------------------------- + +test('goto can be configured with a custom name', t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'D' }, + { name: 'jump', from: '*', to: goto } + ], + methods: { + onBeforeTransition: logger, + onBeforeInit: logger, + onBeforeStep: logger, + onBeforeJump: logger, + onLeaveState: logger, + onLeaveNone: logger, + onLeaveA: logger, + onLeaveB: logger, + onLeaveC: logger, + onLeaveD: logger, + onTransition: logger, + onEnterState: logger, + onEnterNone: logger, + onEnterA: logger, + onEnterB: logger, + onEnterC: logger, + onEnterD: logger, + onAfterTransition: logger, + onAfterInit: logger, + onAfterStep: logger, + onAfterJump: logger + } + }); + + t.is(fsm.state, 'A') + t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B', 'C', 'D' ]) + t.deepEqual(fsm.allTransitions(), [ 'init', 'step', 'jump' ]) + t.is(fsm.goto, undefined) + + logger.clear() + + fsm.jump('C') + + t.is(fsm.state, 'C') + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'jump', from: 'A', to: 'C', current: 'A', args: [ 'C' ] }, + { event: 'onBeforeJump', transition: 'jump', from: 'A', to: 'C', current: 'A', args: [ 'C' ] }, + { event: 'onLeaveState', transition: 'jump', from: 'A', to: 'C', current: 'A', args: [ 'C' ] }, + { event: 'onLeaveA', transition: 'jump', from: 'A', to: 'C', current: 'A', args: [ 'C' ] }, + { event: 'onTransition', transition: 'jump', from: 'A', to: 'C', current: 'A', args: [ 'C' ] }, + { event: 'onEnterState', transition: 'jump', from: 'A', to: 'C', current: 'C', args: [ 'C' ] }, + { event: 'onEnterC', transition: 'jump', from: 'A', to: 'C', current: 'C', args: [ 'C' ] }, + { event: 'onAfterTransition', transition: 'jump', from: 'A', to: 'C', current: 'C', args: [ 'C' ] }, + { event: 'onAfterJump', transition: 'jump', from: 'A', to: 'C', current: 'C', args: [ 'C' ] } + ]) + +}) + +//------------------------------------------------------------------------------------------------- diff --git a/test/helpers/lifecycle_logger.js b/test/helpers/lifecycle_logger.js new file mode 100644 index 0000000..c74d229 --- /dev/null +++ b/test/helpers/lifecycle_logger.js @@ -0,0 +1,25 @@ + +module.exports = function() { + + let entries = [], + logger = function(lifecycle) { + var entry = { + event: lifecycle.event, + transition: lifecycle.transition, + from: lifecycle.from, + to: lifecycle.to, + current: lifecycle.fsm.state + } + if (arguments.length > 1) + entry.args = [].slice.call(arguments, 1); + entries.push(entry); + }; + + logger.clear = function() { + entries.length = 0; + } + + logger.log = entries; + + return logger; +} diff --git a/test/index.html b/test/index.html deleted file mode 100755 index 2cc8628..0000000 --- a/test/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - Finite State Machine Tests - - - - - - - - - - -

    QUnit Test Suite

    -

    -
    -

    -
      -
      test markup
      - - diff --git a/test/introspection.js b/test/introspection.js new file mode 100644 index 0000000..be9e176 --- /dev/null +++ b/test/introspection.js @@ -0,0 +1,204 @@ +import test from 'ava'; +import StateMachine from '../src/app'; + +//----------------------------------------------------------------------------- + +test('is', t => { + + var fsm = new StateMachine({ + init: 'green', + transitions: [ + { name: 'warn', from: 'green', to: 'yellow' }, + { name: 'panic', from: 'yellow', to: 'red' }, + { name: 'calm', from: 'red', to: 'yellow' }, + { name: 'clear', from: 'yellow', to: 'green' } + ]}); + + t.is(fsm.state, 'green') + + t.is(fsm.is('green'), true) + t.is(fsm.is('yellow'), false) + t.is(fsm.is(['green', 'red']), true, 'current state should match when included in array') + t.is(fsm.is(['yellow', 'red']), false, 'current state should NOT match when not included in array') + + fsm.warn() + + t.is(fsm.state, 'yellow') + t.is(fsm.is('green'), false) + t.is(fsm.is('yellow'), true) + t.is(fsm.is(['green', 'red']), false, 'current state should NOT match when not included in array') + t.is(fsm.is(['yellow', 'red']), true, 'current state should match when included in array') + +}); + +//----------------------------------------------------------------------------- + +test('can & cannot', t => { + + var fsm = new StateMachine({ + init: 'green', + transitions: [ + { name: 'warn', from: 'green', to: 'yellow' }, + { name: 'panic', from: 'yellow', to: 'red' }, + { name: 'calm', from: 'red', to: 'yellow' }, + ] + }); + + t.is(fsm.state, 'green') + t.is(fsm.can('warn'), true) + t.is(fsm.can('panic'), false) + t.is(fsm.can('calm'), false) + t.is(fsm.cannot('warn'), false) + t.is(fsm.cannot('panic'), true) + t.is(fsm.cannot('calm'), true) + + fsm.warn(); + t.is(fsm.state, 'yellow') + t.is(fsm.can('warn'), false) + t.is(fsm.can('panic'), true) + t.is(fsm.can('calm'), false) + t.is(fsm.cannot('warn'), true) + t.is(fsm.cannot('panic'), false) + t.is(fsm.cannot('calm'), true) + + fsm.panic(); + t.is(fsm.state, 'red') + t.is(fsm.can('warn'), false) + t.is(fsm.can('panic'), false) + t.is(fsm.can('calm'), true) + t.is(fsm.cannot('warn'), true) + t.is(fsm.cannot('panic'), true) + t.is(fsm.cannot('calm'), false) + + t.is(fsm.can('jibber'), false, "unknown event should not crash") + t.is(fsm.cannot('jabber'), true, "unknown event should not crash") + +}); + +//----------------------------------------------------------------------------- + +test('can is always false during lifecycle events', t => { + + t.plan(81); + + var fsm = new StateMachine({ + init: 'green', + transitions: [ + { name: 'warn', from: 'green', to: 'yellow' }, + { name: 'panic', from: 'yellow', to: 'red' }, + { name: 'calm', from: 'red', to: 'yellow' }, + ], + methods: { + assertTransitionsNotAllowed: function() { + t.false(this.can('warn')) + t.false(this.can('panic')) + t.false(this.can('calm')) + }, + onBeforeTransition: function() { this.assertTransitionsNotAllowed(); }, + onBeforeWarn: function() { this.assertTransitionsNotAllowed(); }, + onBeforePanic: function() { this.assertTransitionsNotAllowed(); }, + onBeforeCalm: function() { this.assertTransitionsNotAllowed(); }, + onLeaveState: function() { this.assertTransitionsNotAllowed(); }, + onLeaveNone: function() { this.assertTransitionsNotAllowed(); }, + onLeaveGreen: function() { this.assertTransitionsNotAllowed(); }, + onLeaveYellow: function() { this.assertTransitionsNotAllowed(); }, + onLeaveRed: function() { this.assertTransitionsNotAllowed(); }, + onTransition: function() { this.assertTransitionsNotAllowed(); }, + onEnterState: function() { this.assertTransitionsNotAllowed(); }, + onEnterNone: function() { this.assertTransitionsNotAllowed(); }, + onEnterGreen: function() { this.assertTransitionsNotAllowed(); }, + onEnterYellow: function() { this.assertTransitionsNotAllowed(); }, + onEnterRed: function() { this.assertTransitionsNotAllowed(); }, + onAfterTransition: function() { this.assertTransitionsNotAllowed(); }, + onAfterInit: function() { this.assertTransitionsNotAllowed(); }, + onAfterWarn: function() { this.assertTransitionsNotAllowed(); }, + onAfterPanic: function() { this.assertTransitionsNotAllowed(); }, + onAfterCalm: function() { this.assertTransitionsNotAllowed(); } + } + }); + + t.is(fsm.state, 'green') + fsm.warn() + t.is(fsm.state, 'yellow') + fsm.panic() + t.is(fsm.state, 'red') + +}); + +//----------------------------------------------------------------------------- + +test('all states', t => { + + var fsm = new StateMachine({ + init: 'green', + transitions: [ + { name: 'warn', from: 'green', to: 'yellow' }, + { name: 'panic', from: 'yellow', to: 'red' }, + { name: 'calm', from: 'red', to: 'yellow' }, + { name: 'clear', from: 'yellow', to: 'green' }, + { name: 'finish', from: 'green', to: 'done' }, + ]}); + + t.deepEqual(fsm.allStates(), [ 'none', 'green', 'yellow', 'red', 'done' ]); + +}); + +//----------------------------------------------------------------------------- + +test("all transitions", t => { + + var fsm = new StateMachine({ + init: 'green', + transitions: [ + { name: 'warn', from: 'green', to: 'yellow' }, + { name: 'panic', from: 'yellow', to: 'red' }, + { name: 'calm', from: 'red', to: 'yellow' }, + { name: 'clear', from: 'yellow', to: 'green' }, + { name: 'finish', from: 'green', to: 'done' }, + ]}); + + t.deepEqual(fsm.allTransitions(), [ + 'init', 'warn', 'panic', 'calm', 'clear', 'finish' + ]); +}) + +//----------------------------------------------------------------------------- + +test("valid transitions", t => { + + var fsm = new StateMachine({ + init: 'green', + transitions: [ + { name: 'warn', from: 'green', to: 'yellow' }, + { name: 'panic', from: 'yellow', to: 'red' }, + { name: 'calm', from: 'red', to: 'yellow' }, + { name: 'clear', from: 'yellow', to: 'green' }, + { name: 'finish', from: 'green', to: 'done' }, + ]}); + + t.is(fsm.state, 'green') + t.deepEqual(fsm.transitions(), ['warn', 'finish']) + + fsm.warn(); + t.is(fsm.state, 'yellow') + t.deepEqual(fsm.transitions(), ['panic', 'clear']) + + fsm.panic(); + t.is(fsm.state, 'red') + t.deepEqual(fsm.transitions(), ['calm']) + + fsm.calm(); + t.is(fsm.state, 'yellow') + t.deepEqual(fsm.transitions(), ['panic', 'clear']) + + fsm.clear(); + t.is(fsm.state, 'green') + t.deepEqual(fsm.transitions(), ['warn', 'finish']) + + fsm.finish(); + t.is(fsm.state, 'done') + t.deepEqual(fsm.transitions(), []) + +}); + +//----------------------------------------------------------------------------- diff --git a/test/issues.js b/test/issues.js new file mode 100644 index 0000000..ff7741d --- /dev/null +++ b/test/issues.js @@ -0,0 +1,103 @@ +import test from 'ava' +import StateMachine from '../src/app' +import LifecycleLogger from './helpers/lifecycle_logger' + +//------------------------------------------------------------------------------------------------- + +test('github issue #12 - transition return values', t => { + + var fsm = new StateMachine({ + transitions: [ + { name: 'init', from: 'none', to: 'A' }, + { name: 'cancelled', from: 'A', to: 'X' }, + { name: 'async', from: 'A', to: 'X' } + ], + methods: { + onBeforeCancelled: function() { return false; }, + onBeforeAsync: function() { return new Promise(function(resolve, reject) {}); } + } + }); + + t.is(fsm.init(), true, 'successful (synchronous) transition returns true') + t.is(fsm.cancelled(), false, 'cancelled (synchronous) transition returns true') + + var promise = fsm.async(); + t.is(typeof promise.then, 'function', 'asynchronous transition returns a promise'); + +}) + +//------------------------------------------------------------------------------------------------- + +test('github issue #17 - exceptions in lifecycle events are NOT swallowed', t => { + + var fsm = new StateMachine({ + transitions: [ + { name: 'step', from: 'none', to: 'complete' } + ], + methods: { + onTransition: function() { throw Error('oops') } + } + }); + + t.is(fsm.state, 'none') + + const error = t.throws(() => { + fsm.step(); + }) + + t.is(error.message, 'oops') + +}) + +//------------------------------------------------------------------------------------------------- + +test('github issue #19 - lifecycle events have correct this when applying StateMachine to a custom class', t => { + + var FSM = function() { + this.stepped = false; + this._fsm(); + } + + FSM.prototype.onStep = function(lifecycle) { this.stepped = true } + + StateMachine.factory(FSM, { + transitions: [ + { name: 'step', from: 'none', to: 'complete' } + ] + }) + + var a = new FSM(), + b = new FSM(); + + t.is(a.state, 'none') + t.is(b.state, 'none') + t.is(a.stepped, false) + t.is(b.stepped, false) + + a.step(); + + t.is(a.state, 'complete') + t.is(b.state, 'none') + t.is(a.stepped, true) + t.is(b.stepped, false) + +}); + +//------------------------------------------------------------------------------------------------- + +test('github issue #64 - double wildcard transition does not change state', t => { + + var fsm = new StateMachine({ + transitions: [ + { name: 'step', from: '*' /* no-op */ } + ] + }); + + t.is(fsm.state, 'none') + + fsm.step(); t.is(fsm.state, 'none') + fsm.step(); t.is(fsm.state, 'none') + +}) + +//------------------------------------------------------------------------------------------------- diff --git a/test/lifecycle.js b/test/lifecycle.js new file mode 100644 index 0000000..4375d65 --- /dev/null +++ b/test/lifecycle.js @@ -0,0 +1,880 @@ +import test from 'ava' +import StateMachine from '../src/app' +import LifecycleLogger from './helpers/lifecycle_logger' + +//------------------------------------------------------------------------------------------------- + +test('lifecycle events occur in correct order', t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + transitions: [ + { name: 'step', from: 'none', to: 'complete' } + ], + methods: { + onBeforeTransition: logger, + onBeforeStep: logger, + onLeaveState: logger, + onLeaveNone: logger, + onLeaveComplete: logger, + onTransition: logger, + onEnterState: logger, + onEnterNone: logger, + onEnterComplete: logger, + onAfterTransition: logger, + onAfterStep: logger + } + }); + + t.is(fsm.state, 'none') + + fsm.step() + + t.is(fsm.state, 'complete') + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onBeforeStep', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveState', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveNone', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onEnterState', transition: 'step', from: 'none', to: 'complete', current: 'complete' }, + { event: 'onEnterComplete', transition: 'step', from: 'none', to: 'complete', current: 'complete' }, + { event: 'onAfterTransition', transition: 'step', from: 'none', to: 'complete', current: 'complete' }, + { event: 'onAfterStep', transition: 'step', from: 'none', to: 'complete', current: 'complete' } + ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('lifecycle events occur in correct order - for same state transition', t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + transitions: [ + { name: 'noop', from: 'none', to: 'none' } + ], + methods: { + onBeforeTransition: logger, + onBeforeNoop: logger, + onLeaveState: logger, + onLeaveNone: logger, + onLeaveComplete: logger, + onTransition: logger, + onEnterState: logger, + onEnterNone: logger, + onEnterComplete: logger, + onAfterTransition: logger, + onAfterNoop: logger + } + }); + + t.is(fsm.state, 'none') + + fsm.noop() + + t.is(fsm.state, 'none') + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onBeforeNoop', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onTransition', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onAfterTransition', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onAfterNoop', transition: 'noop', from: 'none', to: 'none', current: 'none' } + ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('lifecycle events using shortcut names', t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid' }, + { name: 'freeze', from: 'liquid', to: 'solid' }, + { name: 'vaporize', from: 'liquid', to: 'gas' }, + { name: 'condense', from: 'gas', to: 'liquid' } + ], + methods: { + onNone: logger, + onSolid: logger, + onLiquid: logger, + onGas: logger, + onInit: logger, + onMelt: logger, + onFreeze: logger, + onVaporize: logger, + onCondense: logger + } + }); + + t.is(fsm.state, 'solid') + + t.deepEqual(logger.log, [ + { event: "onSolid", transition: "init", from: "none", to: "solid", current: "solid" }, + { event: "onInit", transition: "init", from: "none", to: "solid", current: "solid" } + ]) + + logger.clear() + fsm.melt() + t.is(fsm.state, 'liquid') + + t.deepEqual(logger.log, [ + { event: "onLiquid", transition: "melt", from: "solid", to: "liquid", current: "liquid" }, + { event: "onMelt", transition: "melt", from: "solid", to: "liquid", current: "liquid" } + ]); + +}) + +//------------------------------------------------------------------------------------------------- + +test('lifecycle events with dash or underscore are camelized', t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + init: 'has-dash', + transitions: [ + { name: 'do-with-dash', from: 'has-dash', to: 'has_underscore' }, + { name: 'do_with_underscore', from: 'has_underscore', to: 'alreadyCamelized' }, + { name: 'doAlreadyCamelized', from: 'alreadyCamelized', to: 'has-dash' } + ], + methods: { + onBeforeTransition: logger, + onBeforeInit: logger, + onBeforeDoWithDash: logger, + onBeforeDoWithUnderscore: logger, + onBeforeDoAlreadyCamelized: logger, + onLeaveState: logger, + onLeaveNone: logger, + onLeaveHasDash: logger, + onLeaveHasUnderscore: logger, + onLeaveAlreadyCamelized: logger, + onTransition: logger, + onEnterState: logger, + onEnterNone: logger, + onEnterHasDash: logger, + onEnterHasUnderscore: logger, + onEnterAlreadyCamelized: logger, + onAfterTransition: logger, + onAfterInit: logger, + onAfterDoWithDash: logger, + onAfterDoWithUnderscore: logger, + onAfterDoAlreadyCamelized: logger + } + }); + + t.is(fsm.state, 'has-dash') + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'init', from: 'none', to: 'has-dash', current: 'none' }, + { event: 'onBeforeInit', transition: 'init', from: 'none', to: 'has-dash', current: 'none' }, + { event: 'onLeaveState', transition: 'init', from: 'none', to: 'has-dash', current: 'none' }, + { event: 'onLeaveNone', transition: 'init', from: 'none', to: 'has-dash', current: 'none' }, + { event: 'onTransition', transition: 'init', from: 'none', to: 'has-dash', current: 'none' }, + { event: 'onEnterState', transition: 'init', from: 'none', to: 'has-dash', current: 'has-dash' }, + { event: 'onEnterHasDash', transition: 'init', from: 'none', to: 'has-dash', current: 'has-dash' }, + { event: 'onAfterTransition', transition: 'init', from: 'none', to: 'has-dash', current: 'has-dash' }, + { event: 'onAfterInit', transition: 'init', from: 'none', to: 'has-dash', current: 'has-dash' } + ]) + + logger.clear() + fsm.doWithDash() + t.is(fsm.state, 'has_underscore') + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'do-with-dash', from: 'has-dash', to: 'has_underscore', current: 'has-dash' }, + { event: 'onBeforeDoWithDash', transition: 'do-with-dash', from: 'has-dash', to: 'has_underscore', current: 'has-dash' }, + { event: 'onLeaveState', transition: 'do-with-dash', from: 'has-dash', to: 'has_underscore', current: 'has-dash' }, + { event: 'onLeaveHasDash', transition: 'do-with-dash', from: 'has-dash', to: 'has_underscore', current: 'has-dash' }, + { event: 'onTransition', transition: 'do-with-dash', from: 'has-dash', to: 'has_underscore', current: 'has-dash' }, + { event: 'onEnterState', transition: 'do-with-dash', from: 'has-dash', to: 'has_underscore', current: 'has_underscore' }, + { event: 'onEnterHasUnderscore', transition: 'do-with-dash', from: 'has-dash', to: 'has_underscore', current: 'has_underscore' }, + { event: 'onAfterTransition', transition: 'do-with-dash', from: 'has-dash', to: 'has_underscore', current: 'has_underscore' }, + { event: 'onAfterDoWithDash', transition: 'do-with-dash', from: 'has-dash', to: 'has_underscore', current: 'has_underscore' } + ]) + + logger.clear() + fsm.doWithUnderscore() + t.is(fsm.state, 'alreadyCamelized') + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'do_with_underscore', from: 'has_underscore', to: 'alreadyCamelized', current: 'has_underscore' }, + { event: 'onBeforeDoWithUnderscore', transition: 'do_with_underscore', from: 'has_underscore', to: 'alreadyCamelized', current: 'has_underscore' }, + { event: 'onLeaveState', transition: 'do_with_underscore', from: 'has_underscore', to: 'alreadyCamelized', current: 'has_underscore' }, + { event: 'onLeaveHasUnderscore', transition: 'do_with_underscore', from: 'has_underscore', to: 'alreadyCamelized', current: 'has_underscore' }, + { event: 'onTransition', transition: 'do_with_underscore', from: 'has_underscore', to: 'alreadyCamelized', current: 'has_underscore' }, + { event: 'onEnterState', transition: 'do_with_underscore', from: 'has_underscore', to: 'alreadyCamelized', current: 'alreadyCamelized' }, + { event: 'onEnterAlreadyCamelized', transition: 'do_with_underscore', from: 'has_underscore', to: 'alreadyCamelized', current: 'alreadyCamelized' }, + { event: 'onAfterTransition', transition: 'do_with_underscore', from: 'has_underscore', to: 'alreadyCamelized', current: 'alreadyCamelized' }, + { event: 'onAfterDoWithUnderscore', transition: 'do_with_underscore', from: 'has_underscore', to: 'alreadyCamelized', current: 'alreadyCamelized' } + ]) + + logger.clear() + fsm.doAlreadyCamelized() + t.is(fsm.state, 'has-dash') + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'doAlreadyCamelized', from: 'alreadyCamelized', to: 'has-dash', current: 'alreadyCamelized' }, + { event: 'onBeforeDoAlreadyCamelized', transition: 'doAlreadyCamelized', from: 'alreadyCamelized', to: 'has-dash', current: 'alreadyCamelized' }, + { event: 'onLeaveState', transition: 'doAlreadyCamelized', from: 'alreadyCamelized', to: 'has-dash', current: 'alreadyCamelized' }, + { event: 'onLeaveAlreadyCamelized', transition: 'doAlreadyCamelized', from: 'alreadyCamelized', to: 'has-dash', current: 'alreadyCamelized' }, + { event: 'onTransition', transition: 'doAlreadyCamelized', from: 'alreadyCamelized', to: 'has-dash', current: 'alreadyCamelized' }, + { event: 'onEnterState', transition: 'doAlreadyCamelized', from: 'alreadyCamelized', to: 'has-dash', current: 'has-dash' }, + { event: 'onEnterHasDash', transition: 'doAlreadyCamelized', from: 'alreadyCamelized', to: 'has-dash', current: 'has-dash' }, + { event: 'onAfterTransition', transition: 'doAlreadyCamelized', from: 'alreadyCamelized', to: 'has-dash', current: 'has-dash' }, + { event: 'onAfterDoAlreadyCamelized', transition: 'doAlreadyCamelized', from: 'alreadyCamelized', to: 'has-dash', current: 'has-dash' } + ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('lifecycle events receive arbitrary transition arguments', t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + transitions: [ + { name: 'init', from: 'none', to: 'A' }, + { name: 'step', from: 'A', to: 'B' } + ], + methods: { + onBeforeTransition: logger, + onBeforeInit: logger, + onBeforeStep: logger, + onLeaveState: logger, + onLeaveNone: logger, + onLeaveA: logger, + onLeaveB: logger, + onTransition: logger, + onEnterState: logger, + onEnterNone: logger, + onEnterA: logger, + onEnterB: logger, + onAfterTransition: logger, + onAfterInit: logger, + onAfterStep: logger + } + }); + + t.is(fsm.state, 'none') + t.deepEqual(logger.log, []) + + fsm.init() + + t.is(fsm.state, 'A') + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'init', from: 'none', to: 'A', current: 'none' }, + { event: 'onBeforeInit', transition: 'init', from: 'none', to: 'A', current: 'none' }, + { event: 'onLeaveState', transition: 'init', from: 'none', to: 'A', current: 'none' }, + { event: 'onLeaveNone', transition: 'init', from: 'none', to: 'A', current: 'none' }, + { event: 'onTransition', transition: 'init', from: 'none', to: 'A', current: 'none' }, + { event: 'onEnterState', transition: 'init', from: 'none', to: 'A', current: 'A' }, + { event: 'onEnterA', transition: 'init', from: 'none', to: 'A', current: 'A' }, + { event: 'onAfterTransition', transition: 'init', from: 'none', to: 'A', current: 'A' }, + { event: 'onAfterInit', transition: 'init', from: 'none', to: 'A', current: 'A' } + ]) + logger.clear() + + fsm.step('with', 4, 'more', 'arguments') + + t.is(fsm.state, 'B') + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'A', to: 'B', current: 'A', args: [ 'with', 4, 'more', 'arguments' ] }, + { event: 'onBeforeStep', transition: 'step', from: 'A', to: 'B', current: 'A', args: [ 'with', 4, 'more', 'arguments' ] }, + { event: 'onLeaveState', transition: 'step', from: 'A', to: 'B', current: 'A', args: [ 'with', 4, 'more', 'arguments' ] }, + { event: 'onLeaveA', transition: 'step', from: 'A', to: 'B', current: 'A', args: [ 'with', 4, 'more', 'arguments' ] }, + { event: 'onTransition', transition: 'step', from: 'A', to: 'B', current: 'A', args: [ 'with', 4, 'more', 'arguments' ] }, + { event: 'onEnterState', transition: 'step', from: 'A', to: 'B', current: 'B', args: [ 'with', 4, 'more', 'arguments' ] }, + { event: 'onEnterB', transition: 'step', from: 'A', to: 'B', current: 'B', args: [ 'with', 4, 'more', 'arguments' ] }, + { event: 'onAfterTransition', transition: 'step', from: 'A', to: 'B', current: 'B', args: [ 'with', 4, 'more', 'arguments' ] }, + { event: 'onAfterStep', transition: 'step', from: 'A', to: 'B', current: 'B', args: [ 'with', 4, 'more', 'arguments' ] } + ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('lifecycle events are cancelable', t => { + + var FSM = StateMachine.factory({ + transitions: [ + { name: 'step', from: 'none', to: 'complete' } + ], + data: function(cancel) { + return { + cancel: cancel, + logger: new LifecycleLogger() + } + }, + methods: { + onBeforeTransition: function(lifecycle) { this.logger(lifecycle); return lifecycle.event !== this.cancel }, + onBeforeStep: function(lifecycle) { this.logger(lifecycle); return lifecycle.event !== this.cancel }, + onEnterState: function(lifecycle) { this.logger(lifecycle); return lifecycle.event !== this.cancel }, + onEnterNone: function(lifecycle) { this.logger(lifecycle); return lifecycle.event !== this.cancel }, + onEnterComplete: function(lifecycle) { this.logger(lifecycle); return lifecycle.event !== this.cancel }, + onTransition: function(lifecycle) { this.logger(lifecycle); return lifecycle.event !== this.cancel }, + onLeaveState: function(lifecycle) { this.logger(lifecycle); return lifecycle.event !== this.cancel }, + onLeaveNone: function(lifecycle) { this.logger(lifecycle); return lifecycle.event !== this.cancel }, + onLeaveComplete: function(lifecycle) { this.logger(lifecycle); return lifecycle.event !== this.cancel }, + onAfterTransition: function(lifecycle) { this.logger(lifecycle); return lifecycle.event !== this.cancel }, + onAfterStep: function(lifecycle) { this.logger(lifecycle); return lifecycle.event !== this.cancel } + } + }); + + var cancelledBeforeTransition = new FSM('onBeforeTransition'), + cancelledBeforeStep = new FSM('onBeforeStep'), + cancelledLeaveState = new FSM('onLeaveState'), + cancelledLeaveNone = new FSM('onLeaveNone'), + cancelledTransition = new FSM('onTransition'), + cancelledEnterState = new FSM('onEnterState'), + cancelledEnterComplete = new FSM('onEnterComplete'), + cancelledAfterTransition = new FSM('onAfterTransition'), + cancelledAfterStep = new FSM('onAfterStep'); + + t.is(cancelledBeforeTransition.state, 'none') + t.is(cancelledBeforeStep.state, 'none') + t.is(cancelledLeaveState.state, 'none') + t.is(cancelledLeaveNone.state, 'none') + t.is(cancelledTransition.state, 'none') + t.is(cancelledEnterState.state, 'none') + t.is(cancelledEnterComplete.state, 'none') + t.is(cancelledAfterTransition.state, 'none') + t.is(cancelledAfterStep.state, 'none') + + cancelledBeforeTransition.step() + cancelledBeforeStep.step() + cancelledLeaveState.step() + cancelledLeaveNone.step() + cancelledTransition.step() + cancelledEnterState.step() + cancelledEnterComplete.step() + cancelledAfterTransition.step() + cancelledAfterStep.step() + + t.is(cancelledBeforeTransition.state, 'none') + t.is(cancelledBeforeStep.state, 'none') + t.is(cancelledLeaveState.state, 'none') + t.is(cancelledLeaveNone.state, 'none') + t.is(cancelledTransition.state, 'none') + t.is(cancelledEnterState.state, 'complete') + t.is(cancelledEnterComplete.state, 'complete') + t.is(cancelledAfterTransition.state, 'complete') + t.is(cancelledAfterStep.state, 'complete') + + t.deepEqual(cancelledBeforeTransition.logger.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' } + ]) + + t.deepEqual(cancelledBeforeStep.logger.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onBeforeStep', transition: 'step', from: 'none', to: 'complete', current: 'none' } + ]) + + t.deepEqual(cancelledLeaveState.logger.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onBeforeStep', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveState', transition: 'step', from: 'none', to: 'complete', current: 'none' } + ]) + + t.deepEqual(cancelledLeaveNone.logger.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onBeforeStep', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveState', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveNone', transition: 'step', from: 'none', to: 'complete', current: 'none' } + ]) + + t.deepEqual(cancelledTransition.logger.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onBeforeStep', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveState', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveNone', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' } + ]) + + t.deepEqual(cancelledEnterState.logger.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onBeforeStep', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveState', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveNone', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onEnterState', transition: 'step', from: 'none', to: 'complete', current: 'complete' } + ]) + + t.deepEqual(cancelledEnterComplete.logger.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onBeforeStep', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveState', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveNone', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onEnterState', transition: 'step', from: 'none', to: 'complete', current: 'complete' }, + { event: 'onEnterComplete', transition: 'step', from: 'none', to: 'complete', current: 'complete' } + ]) + + t.deepEqual(cancelledAfterTransition.logger.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onBeforeStep', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveState', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveNone', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onEnterState', transition: 'step', from: 'none', to: 'complete', current: 'complete' }, + { event: 'onEnterComplete', transition: 'step', from: 'none', to: 'complete', current: 'complete' }, + { event: 'onAfterTransition', transition: 'step', from: 'none', to: 'complete', current: 'complete' } + ]) + + t.deepEqual(cancelledAfterStep.logger.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onBeforeStep', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveState', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveNone', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onEnterState', transition: 'step', from: 'none', to: 'complete', current: 'complete' }, + { event: 'onEnterComplete', transition: 'step', from: 'none', to: 'complete', current: 'complete' }, + { event: 'onAfterTransition', transition: 'step', from: 'none', to: 'complete', current: 'complete' }, + { event: 'onAfterStep', transition: 'step', from: 'none', to: 'complete', current: 'complete' } + ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('lifecycle events can be deferred using a promise', t => { + return new Promise(function(resolveTest, rejectTest) { + + var logger = new LifecycleLogger(), + start = Date.now(), + pause = function(ms) { return new Promise(function(resolve, reject) { setTimeout(resolve, ms); }); }, + fsm = new StateMachine({ + transitions: [ + { name: 'step', from: 'none', to: 'complete' } + ], + methods: { + onBeforeTransition: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, + onBeforeStep: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, + onEnterState: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, + onEnterNone: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, + onEnterComplete: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, + onTransition: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, + onLeaveState: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, + onLeaveNone: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, + onLeaveComplete: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, + onAfterTransition: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, + onAfterStep: function(lifecycle, a, b) { logger(lifecycle, a, b); return done(); } + } + }); + + function done() { + var duration = Date.now() - start; + t.is(fsm.state, 'complete') + t.is(duration > 600, true) + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onBeforeStep', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onLeaveState', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onLeaveNone', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onTransition', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onEnterState', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, + { event: 'onEnterComplete', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, + { event: 'onAfterTransition', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, + { event: 'onAfterStep', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, + ]) + resolveTest() + } + + fsm.step('additional', 'arguments') + + }); +}); + +//------------------------------------------------------------------------------------------------- + +test('lifecycle events can be cancelled using a promise', t => { + return new Promise(function(resolveTest, rejectTest) { + + var logger = new LifecycleLogger(), + start = Date.now(), + pause = function(ms) { + return new Promise(function(resolve, reject) { + setTimeout(resolve, ms); + }); + }, + cancel = function(ms) { + return new Promise(function(resolve, reject) { + setTimeout(function() { + reject(); + done(); + }, ms); + }); + }, + fsm = new StateMachine({ + transitions: [ + { name: 'step', from: 'none', to: 'complete' } + ], + methods: { + onBeforeTransition: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, + onBeforeStep: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, + onEnterState: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, + onEnterNone: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, + onEnterComplete: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, + onTransition: function(lifecycle, a, b) { logger(lifecycle, a, b); return cancel(100); }, + onLeaveState: function(lifecycle, a, b) { logger(lifecycle, a, b); }, + onLeaveNone: function(lifecycle, a, b) { logger(lifecycle, a, b); }, + onLeaveComplete: function(lifecycle, a, b) { logger(lifecycle, a, b); }, + onAfterTransition: function(lifecycle, a, b) { logger(lifecycle, a, b); }, + onAfterStep: function(lifecycle, a, b) { logger(lifecycle, a, b); } + } + }); + + function done() { + var duration = Date.now() - start; + t.is(fsm.state, 'none'); + t.is(duration > 300, true); + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onBeforeStep', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onLeaveState', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onLeaveNone', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onTransition', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] } + ]); + resolveTest(); + } + + fsm.step('additional', 'arguments') + + }) +}) + +//------------------------------------------------------------------------------------------------- + +test('transition cannot fire while lifecycle event is in progress', t => { + + t.plan(20); + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'other', from: '*', to: 'X' } + ], + methods: { + + onBeforeStep: function(lifecycle) { + t.false(this.can('other')); + const error = t.throws(function() { + fsm.other(); + }); + t.is(error.message, 'transition is invalid while previous transition is still in progress'); + t.is(error.transition, 'other'); + t.is(error.from, 'A'); + t.is(error.to, 'X'); + t.is(error.current, 'A'); + }, + + onAfterStep: function(lifecycle) { + t.false(this.can('other')); + const error = t.throws(function() { + fsm.other(); + }); + t.is(error.message, 'transition is invalid while previous transition is still in progress'); + t.is(error.transition, 'other'); + t.is(error.from, 'B'); + t.is(error.to, 'X'); + t.is(error.current, 'B'); + }, + + onBeforeOther: function(lifecycle) { t.fail('should never happen') }, + onAfterOther: function(lifecycle) { t.fail('should never happen') }, + onLeaveA: function(lifecycle) { t.false(this.can('other')) }, + onEnterB: function(lifecycle) { t.false(this.can('other')) }, + onLeaveB: function(lifecycle) { t.fail('should never happen') }, + onEnterX: function(lifecycle) { t.fail('should never happen') }, + onLeaveX: function(lifecycle) { t.fail('should never happen') } + + } + }); + + t.is(fsm.state, 'A') + t.true(fsm.can('other')) + + fsm.step() + + t.is(fsm.state, 'B') + t.true(fsm.can('other')) + +}) + +//------------------------------------------------------------------------------------------------- + +test('transition cannot fire while asynchronous lifecycle event is in progress', t => { + return new Promise(function(resolveTest, rejectTest) { + + t.plan(20); + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'other', from: '*', to: 'X' } + ], + methods: { + + onBeforeStep: function(lifecycle) { + return new Promise(function(resolve, reject) { + setTimeout(function() { + t.false(fsm.can('other')); + const error = t.throws(function() { + fsm.other(); + }); + t.is(error.message, 'transition is invalid while previous transition is still in progress'); + t.is(error.transition, 'other'); + t.is(error.from, 'A'); + t.is(error.to, 'X'); + t.is(error.current, 'A'); + resolve(); + }, 200); + }); + }, + + onAfterStep: function(lifecycle) { + return new Promise(function(resolve, reject) { + setTimeout(function() { + t.false(fsm.can('other')); + const error = t.throws(function() { + fsm.other(); + }); + t.is(error.message, 'transition is invalid while previous transition is still in progress'); + t.is(error.transition, 'other'); + t.is(error.from, 'B'); + t.is(error.to, 'X'); + t.is(error.current, 'B'); + resolve(); + setTimeout(done, 0); // HACK - let lifecycle finish before calling done() + }, 200); + }); + }, + + onBeforeOther: function(lifecycle) { t.fail('should never happen') }, + onAfterOther: function(lifecycle) { t.fail('should never happen') }, + onLeaveA: function(lifecycle) { t.false(this.can('other')) }, + onEnterB: function(lifecycle) { t.false(this.can('other')) }, + onLeaveB: function(lifecycle) { t.fail('should never happen') }, + onEnterX: function(lifecycle) { t.fail('should never happen') }, + onLeaveX: function(lifecycle) { t.fail('should never happen') } + + } + }); + + t.is(fsm.state, 'A') + t.true(fsm.can('other')) + + function done() { + t.is(fsm.state, 'B'); + t.true(fsm.can('other')); + resolveTest(); + } + + fsm.step(); // kick off the async behavior + + }) +}) + +//------------------------------------------------------------------------------------------------- + +test('lifecycle events for transitions with multiple :from or :to states', t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + init: 'hungry', + transitions: [ + { name: 'eat', from: 'hungry', to: 'satisfied' }, + { name: 'eat', from: 'satisfied', to: 'full' }, + { name: 'rest', from: [ 'satisfied', 'full' ], to: 'hungry' } + ], + methods: { + onBeforeTransition: logger, + onBeforeEat: logger, + onBeforeRest: logger, + onLeaveState: logger, + onLeaveHungry: logger, + onLeaveSatisfied: logger, + onLeaveFull: logger, + onTransition: logger, + onEnterState: logger, + onEnterHungry: logger, + onEnterSatisfied: logger, + onEnterFull: logger, + onAfterTransition: logger, + onAfterEat: logger, + onAfterRest: logger + } + }); + + + t.is(fsm.state, 'hungry') + logger.clear() + + fsm.eat() + t.is(fsm.state, 'satisfied') + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'eat', from: 'hungry', to: 'satisfied', current: 'hungry' }, + { event: 'onBeforeEat', transition: 'eat', from: 'hungry', to: 'satisfied', current: 'hungry' }, + { event: 'onLeaveState', transition: 'eat', from: 'hungry', to: 'satisfied', current: 'hungry' }, + { event: 'onLeaveHungry', transition: 'eat', from: 'hungry', to: 'satisfied', current: 'hungry' }, + { event: 'onTransition', transition: 'eat', from: 'hungry', to: 'satisfied', current: 'hungry' }, + { event: 'onEnterState', transition: 'eat', from: 'hungry', to: 'satisfied', current: 'satisfied' }, + { event: 'onEnterSatisfied', transition: 'eat', from: 'hungry', to: 'satisfied', current: 'satisfied' }, + { event: 'onAfterTransition', transition: 'eat', from: 'hungry', to: 'satisfied', current: 'satisfied' }, + { event: 'onAfterEat', transition: 'eat', from: 'hungry', to: 'satisfied', current: 'satisfied' } + ]) + + logger.clear() + fsm.eat() + t.is(fsm.state, 'full') + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'eat', from: 'satisfied', to: 'full', current: 'satisfied' }, + { event: 'onBeforeEat', transition: 'eat', from: 'satisfied', to: 'full', current: 'satisfied' }, + { event: 'onLeaveState', transition: 'eat', from: 'satisfied', to: 'full', current: 'satisfied' }, + { event: 'onLeaveSatisfied', transition: 'eat', from: 'satisfied', to: 'full', current: 'satisfied' }, + { event: 'onTransition', transition: 'eat', from: 'satisfied', to: 'full', current: 'satisfied' }, + { event: 'onEnterState', transition: 'eat', from: 'satisfied', to: 'full', current: 'full' }, + { event: 'onEnterFull', transition: 'eat', from: 'satisfied', to: 'full', current: 'full' }, + { event: 'onAfterTransition', transition: 'eat', from: 'satisfied', to: 'full', current: 'full' }, + { event: 'onAfterEat', transition: 'eat', from: 'satisfied', to: 'full', current: 'full' } + ]) + + logger.clear() + fsm.rest() + t.is(fsm.state, 'hungry') + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'rest', from: 'full', to: 'hungry', current: 'full' }, + { event: 'onBeforeRest', transition: 'rest', from: 'full', to: 'hungry', current: 'full' }, + { event: 'onLeaveState', transition: 'rest', from: 'full', to: 'hungry', current: 'full' }, + { event: 'onLeaveFull', transition: 'rest', from: 'full', to: 'hungry', current: 'full' }, + { event: 'onTransition', transition: 'rest', from: 'full', to: 'hungry', current: 'full' }, + { event: 'onEnterState', transition: 'rest', from: 'full', to: 'hungry', current: 'hungry' }, + { event: 'onEnterHungry', transition: 'rest', from: 'full', to: 'hungry', current: 'hungry' }, + { event: 'onAfterTransition', transition: 'rest', from: 'full', to: 'hungry', current: 'hungry' }, + { event: 'onAfterRest', transition: 'rest', from: 'full', to: 'hungry', current: 'hungry' } + ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('lifecycle events for factory generated state machines', t => { + + var FSM = StateMachine.factory({ + transitions: [ + { name: 'stepA', from: 'none', to: 'A' }, + { name: 'stepB', from: 'none', to: 'B' } + ], + data: function(name) { + return { + name: name, + logger: new LifecycleLogger() + } + }, + methods: { + onBeforeTransition: function(lifecycle) { this.logger(lifecycle) }, + onBeforeStepA: function(lifecycle) { this.logger(lifecycle) }, + onBeforeStepB: function(lifecycle) { this.logger(lifecycle) }, + onLeaveState: function(lifecycle) { this.logger(lifecycle) }, + onLeaveNone: function(lifecycle) { this.logger(lifecycle) }, + onLeaveA: function(lifecycle) { this.logger(lifecycle) }, + onLeaveB: function(lifecycle) { this.logger(lifecycle) }, + onTransition: function(lifecycle) { this.logger(lifecycle) }, + onEnterState: function(lifecycle) { this.logger(lifecycle) }, + onEnterNone: function(lifecycle) { this.logger(lifecycle) }, + onEnterA: function(lifecycle) { this.logger(lifecycle) }, + onEnterB: function(lifecycle) { this.logger(lifecycle) }, + onAfterTransition: function(lifecycle) { this.logger(lifecycle) }, + onAfterStepA: function(lifecycle) { this.logger(lifecycle) }, + onAfterStepB: function(lifecycle) { this.logger(lifecycle) } + } + }); + + var a = new FSM('a'), + b = new FSM('b'); + + t.is(a.state, 'none') + t.is(b.state, 'none') + + t.deepEqual(a.logger.log, []) + t.deepEqual(b.logger.log, []) + + a.stepA() + b.stepB() + + t.is(a.state, 'A') + t.is(b.state, 'B') + + t.deepEqual(a.logger.log, [ + { event: 'onBeforeTransition', transition: 'stepA', from: 'none', to: 'A', current: 'none' }, + { event: 'onBeforeStepA', transition: 'stepA', from: 'none', to: 'A', current: 'none' }, + { event: 'onLeaveState', transition: 'stepA', from: 'none', to: 'A', current: 'none' }, + { event: 'onLeaveNone', transition: 'stepA', from: 'none', to: 'A', current: 'none' }, + { event: 'onTransition', transition: 'stepA', from: 'none', to: 'A', current: 'none' }, + { event: 'onEnterState', transition: 'stepA', from: 'none', to: 'A', current: 'A' }, + { event: 'onEnterA', transition: 'stepA', from: 'none', to: 'A', current: 'A' }, + { event: 'onAfterTransition', transition: 'stepA', from: 'none', to: 'A', current: 'A' }, + { event: 'onAfterStepA', transition: 'stepA', from: 'none', to: 'A', current: 'A' } + ]) + + t.deepEqual(b.logger.log, [ + { event: 'onBeforeTransition', transition: 'stepB', from: 'none', to: 'B', current: 'none' }, + { event: 'onBeforeStepB', transition: 'stepB', from: 'none', to: 'B', current: 'none' }, + { event: 'onLeaveState', transition: 'stepB', from: 'none', to: 'B', current: 'none' }, + { event: 'onLeaveNone', transition: 'stepB', from: 'none', to: 'B', current: 'none' }, + { event: 'onTransition', transition: 'stepB', from: 'none', to: 'B', current: 'none' }, + { event: 'onEnterState', transition: 'stepB', from: 'none', to: 'B', current: 'B' }, + { event: 'onEnterB', transition: 'stepB', from: 'none', to: 'B', current: 'B' }, + { event: 'onAfterTransition', transition: 'stepB', from: 'none', to: 'B', current: 'B' }, + { event: 'onAfterStepB', transition: 'stepB', from: 'none', to: 'B', current: 'B' } + ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('lifecycle events for custom init transition', t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + init: { name: 'boot', from: 'booting', to: 'complete' }, + methods: { + onBeforeTransition: logger, + onBeforeInit: logger, + onBeforeBoot: logger, + onLeaveState: logger, + onLeaveNone: logger, + onLeaveBooting: logger, + onLeaveComplete: logger, + onTransition: logger, + onEnterState: logger, + onEnterNone: logger, + onEnterBooting: logger, + onEnterComplete: logger, + onAfterTransition: logger, + onAfterInit: logger, + onAfterBoot: logger + } + }); + + t.is(fsm.state, 'complete') + + t.deepEqual(fsm.allStates(), [ 'booting', 'complete' ]) + t.deepEqual(fsm.allTransitions(), [ 'boot' ]) + t.deepEqual(fsm.transitions(), [ ]) + + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'boot', from: 'booting', to: 'complete', current: 'booting' }, + { event: 'onBeforeBoot', transition: 'boot', from: 'booting', to: 'complete', current: 'booting' }, + { event: 'onLeaveState', transition: 'boot', from: 'booting', to: 'complete', current: 'booting' }, + { event: 'onLeaveBooting', transition: 'boot', from: 'booting', to: 'complete', current: 'booting' }, + { event: 'onTransition', transition: 'boot', from: 'booting', to: 'complete', current: 'booting' }, + { event: 'onEnterState', transition: 'boot', from: 'booting', to: 'complete', current: 'complete' }, + { event: 'onEnterComplete', transition: 'boot', from: 'booting', to: 'complete', current: 'complete' }, + { event: 'onAfterTransition', transition: 'boot', from: 'booting', to: 'complete', current: 'complete' }, + { event: 'onAfterBoot', transition: 'boot', from: 'booting', to: 'complete', current: 'complete' } + ]) + +}) + +//------------------------------------------------------------------------------------------------- diff --git a/test/observers.js b/test/observers.js new file mode 100644 index 0000000..f20ab6f --- /dev/null +++ b/test/observers.js @@ -0,0 +1,151 @@ +import test from 'ava' +import StateMachine from '../src/app' +import LifecycleLogger from './helpers/lifecycle_logger' + +//------------------------------------------------------------------------------------------------- + +test('lifecycle events can be observed by external observer methods', t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + transitions: [ + { name: 'step', from: 'none', to: 'complete' } + ] + }); + + fsm.observe("onBeforeTransition", logger) + fsm.observe("onBeforeStep", logger) + fsm.observe("onLeaveState", logger) + fsm.observe("onLeaveNone", logger) + fsm.observe("onLeaveComplete", logger) + fsm.observe("onTransition", logger) + fsm.observe("onEnterState", logger) + fsm.observe("onEnterNone", logger) + fsm.observe("onEnterComplete", logger) + fsm.observe("onAfterTransition", logger) + fsm.observe("onAfterStep", logger) + + fsm.step('additional', 'arguments') + + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onBeforeStep', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onLeaveState', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onLeaveNone', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onTransition', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onEnterState', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, + { event: 'onEnterComplete', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, + { event: 'onAfterTransition', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, + { event: 'onAfterStep', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] } + ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('lifecycle events can be observed by external observer classes', t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + transitions: [ + { name: 'step', from: 'none', to: 'complete' } + ] + }); + + fsm.observe({ + "onBeforeTransition": logger, + "onBeforeStep": logger, + "onLeaveState": logger, + "onLeaveNone": logger, + "onLeaveComplete": logger, + "onTransition": logger, + "onEnterState": logger, + "onEnterNone": logger, + "onEnterComplete": logger, + "onAfterTransition": logger, + "onAfterStep": logger, + }) + + fsm.step('additional', 'arguments') + + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onBeforeStep', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onLeaveState', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onLeaveNone', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onTransition', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onEnterState', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, + { event: 'onEnterComplete', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, + { event: 'onAfterTransition', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, + { event: 'onAfterStep', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] } + ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('lifecycle events can be observed by multiple observers', t => { + + var logger1 = new LifecycleLogger(), + logger2 = new LifecycleLogger(), + fsm = new StateMachine({ + transitions: [ + { name: 'step', from: 'none', to: 'complete' } + ] + }); + + fsm.observe("onBeforeTransition", logger1) + fsm.observe("onBeforeStep", logger1) + fsm.observe("onLeaveState", logger1) + fsm.observe("onLeaveNone", logger1) + fsm.observe("onLeaveComplete", logger1) + fsm.observe("onTransition", logger1) + fsm.observe("onEnterState", logger1) + fsm.observe("onEnterNone", logger1) + fsm.observe("onEnterComplete", logger1) + fsm.observe("onAfterTransition", logger1) + fsm.observe("onAfterStep", logger1) + + fsm.observe({ + "onBeforeTransition": logger2, + "onBeforeStep": logger2, + "onLeaveState": logger2, + "onLeaveNone": logger2, + "onLeaveComplete": logger2, + "onTransition": logger2, + "onEnterState": logger2, + "onEnterNone": logger2, + "onEnterComplete": logger2, + "onAfterTransition": logger2, + "onAfterStep": logger2, + }) + + fsm.step('additional', 'arguments') + + t.deepEqual(logger1.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onBeforeStep', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onLeaveState', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onLeaveNone', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onTransition', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onEnterState', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, + { event: 'onEnterComplete', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, + { event: 'onAfterTransition', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, + { event: 'onAfterStep', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] } + ]) + + t.deepEqual(logger2.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onBeforeStep', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onLeaveState', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onLeaveNone', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onTransition', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, + { event: 'onEnterState', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, + { event: 'onEnterComplete', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, + { event: 'onAfterTransition', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, + { event: 'onAfterStep', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] } + ]) + +}) + +//------------------------------------------------------------------------------------------------- diff --git a/test/plugin/history.js b/test/plugin/history.js new file mode 100644 index 0000000..00f73ef --- /dev/null +++ b/test/plugin/history.js @@ -0,0 +1,493 @@ +import test from 'ava' +import StateMachine from '../../src/app' +import StateMachineHistory from '../../src/plugin/history' +import LifecycleLogger from '../helpers/lifecycle_logger' + +//------------------------------------------------------------------------------------------------- + +test('history', t => { + + var fsm = new StateMachine({ + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid' }, + { name: 'freeze', from: 'liquid', to: 'solid' }, + { name: 'vaporize', from: 'liquid', to: 'gas' }, + { name: 'condense', from: 'gas', to: 'liquid' } + ], + plugins: [ + StateMachineHistory + ] + }) + + t.is(fsm.state, 'solid'); t.deepEqual(fsm.history, [ 'solid' ]) + fsm.melt(); t.is(fsm.state, 'liquid'); t.deepEqual(fsm.history, [ 'solid', 'liquid' ]) + fsm.vaporize(); t.is(fsm.state, 'gas'); t.deepEqual(fsm.history, [ 'solid', 'liquid', 'gas' ]) + fsm.condense(); t.is(fsm.state, 'liquid'); t.deepEqual(fsm.history, [ 'solid', 'liquid', 'gas', 'liquid' ]); + +}) + +//------------------------------------------------------------------------------------------------- + +test('history can be cleared', t => { + + var fsm = new StateMachine({ + transitions: [ + { name: 'init', from: 'none', to: 'A' }, + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'A' } + ], + plugins: [ + StateMachineHistory + ] + }) + + fsm.init() + fsm.step() + + t.is(fsm.state, 'B') + t.deepEqual(fsm.history, ['A', 'B']) + + fsm.clearHistory() + + t.is(fsm.state, 'B') + t.deepEqual(fsm.history, []) + +}) + +//------------------------------------------------------------------------------------------------- + +test('history does not record no-op transitions', t => { + + var fsm = new StateMachine({ + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid' }, + { name: 'freeze', from: 'liquid', to: 'solid' }, + { name: 'vaporize', from: 'liquid', to: 'gas' }, + { name: 'condense', from: 'gas', to: 'liquid' }, + { name: 'noop', from: '*', to: '*' } + ], + plugins: [ + StateMachineHistory + ] + }) + + t.is(fsm.state, 'solid'); t.deepEqual(fsm.history, [ 'solid' ]) + fsm.noop(); t.is(fsm.state, 'solid'); t.deepEqual(fsm.history, [ 'solid' ]) + fsm.melt(); t.is(fsm.state, 'liquid'); t.deepEqual(fsm.history, [ 'solid', 'liquid' ]) + fsm.noop(); t.is(fsm.state, 'liquid'); t.deepEqual(fsm.history, [ 'solid', 'liquid' ]) + fsm.vaporize(); t.is(fsm.state, 'gas'); t.deepEqual(fsm.history, [ 'solid', 'liquid', 'gas' ]) + fsm.noop(); t.is(fsm.state, 'gas'); t.deepEqual(fsm.history, [ 'solid', 'liquid', 'gas' ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('history with configurable names', t => { + + var fsm = new StateMachine({ + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid' }, + { name: 'freeze', from: 'liquid', to: 'solid' }, + { name: 'vaporize', from: 'liquid', to: 'gas' }, + { name: 'condense', from: 'gas', to: 'liquid' } + ], + plugins: [ + new StateMachineHistory({ name: 'memory', future: 'yonder' }) + ] + }) + + t.is(fsm.state, 'solid'); t.deepEqual(fsm.memory, [ 'solid' ]) + fsm.melt(); t.is(fsm.state, 'liquid'); t.deepEqual(fsm.memory, [ 'solid', 'liquid' ]) + fsm.vaporize(); t.is(fsm.state, 'gas'); t.deepEqual(fsm.memory, [ 'solid', 'liquid', 'gas' ]) + fsm.condense(); t.is(fsm.state, 'liquid'); t.deepEqual(fsm.memory, [ 'solid', 'liquid', 'gas', 'liquid' ]) + + t.is(fsm.canMemoryBack, true) + t.is(fsm.canMemoryForward, false) + t.deepEqual(fsm.yonder, [ ]) + + fsm.memoryBack() + + t.is(fsm.state, 'gas') + t.deepEqual(fsm.memory, [ 'solid', 'liquid', 'gas' ]) + t.deepEqual(fsm.yonder, [ 'liquid' ]) + + fsm.clearMemory() + t.deepEqual(fsm.memory, []) + t.deepEqual(fsm.yonder, []) + +}) + +//------------------------------------------------------------------------------------------------- + +test('history, by default, just keeps growing', t => { + + var fsm = new StateMachine({ + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid' }, + { name: 'freeze', from: 'liquid', to: 'solid' }, + { name: 'vaporize', from: 'liquid', to: 'gas' }, + { name: 'condense', from: 'gas', to: 'liquid' } + ], + plugins: [ + new StateMachineHistory() + ] + }) + + t.is(fsm.state, 'solid') + t.deepEqual(fsm.history, [ 'solid' ]) + + fsm.melt(); t.deepEqual(fsm.history, [ 'solid', 'liquid' ]) + fsm.vaporize(); t.deepEqual(fsm.history, [ 'solid', 'liquid', 'gas' ]) + fsm.condense(); t.deepEqual(fsm.history, [ 'solid', 'liquid', 'gas', 'liquid' ]) + fsm.freeze(); t.deepEqual(fsm.history, [ 'solid', 'liquid', 'gas', 'liquid', 'solid' ]) + fsm.melt(); t.deepEqual(fsm.history, [ 'solid', 'liquid', 'gas', 'liquid', 'solid', 'liquid' ]) + fsm.vaporize(); t.deepEqual(fsm.history, [ 'solid', 'liquid', 'gas', 'liquid', 'solid', 'liquid', 'gas' ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('history can be limited to N entries', t => { + + var fsm = new StateMachine({ + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid' }, + { name: 'freeze', from: 'liquid', to: 'solid' }, + { name: 'vaporize', from: 'liquid', to: 'gas' }, + { name: 'condense', from: 'gas', to: 'liquid' } + ], + plugins: [ + new StateMachineHistory({ max: 3 }) + ] + }) + + t.is(fsm.state, 'solid') + t.deepEqual(fsm.history, [ 'solid' ]) + + fsm.melt(); t.deepEqual(fsm.history, [ 'solid', 'liquid' ]) + fsm.vaporize(); t.deepEqual(fsm.history, [ 'solid', 'liquid', 'gas' ]) + fsm.condense(); t.deepEqual(fsm.history, [ 'liquid', 'gas', 'liquid' ]) + fsm.freeze(); t.deepEqual(fsm.history, [ 'gas', 'liquid', 'solid' ]) + fsm.melt(); t.deepEqual(fsm.history, [ 'liquid', 'solid', 'liquid' ]) + fsm.vaporize(); t.deepEqual(fsm.history, [ 'solid', 'liquid', 'gas' ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('history back and forward', t => { + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'D' } + ], + plugins: [ + StateMachineHistory + ] + }) + + t.is(fsm.state, 'A') + t.is(fsm.canHistoryBack, false) + t.deepEqual(fsm.history, [ 'A' ]) + t.deepEqual(fsm.future, [ ]) + + var error = t.throws(() => { + fsm.historyBack() + }) + t.is(error.message, 'no history') + + fsm.step() + fsm.step() + fsm.step() + + t.is(fsm.state, 'D') + t.is(fsm.canHistoryBack, true) + t.is(fsm.canHistoryForward, false) + t.deepEqual(fsm.history, [ 'A', 'B', 'C', 'D' ]) + t.deepEqual(fsm.future, []) + + fsm.historyBack() + t.is(fsm.state, 'C') + t.deepEqual(fsm.history, [ 'A', 'B', 'C' ]) + t.deepEqual(fsm.future, [ 'D' ]) + t.is(fsm.canHistoryBack, true) + t.is(fsm.canHistoryForward, true) + + fsm.historyBack() + t.is(fsm.state, 'B') + t.deepEqual(fsm.history, [ 'A', 'B' ]) + t.deepEqual(fsm.future, [ 'D', 'C' ]) + t.is(fsm.canHistoryBack, true) + t.is(fsm.canHistoryForward, true) + + fsm.historyBack() + t.is(fsm.state, 'A') + t.deepEqual(fsm.history, [ 'A' ]) + t.deepEqual(fsm.future, [ 'D', 'C', 'B' ]) + t.is(fsm.canHistoryBack, false) + t.is(fsm.canHistoryForward, true) + + fsm.historyForward() + t.is(fsm.state, 'B') + t.deepEqual(fsm.history, [ 'A', 'B' ]) + t.deepEqual(fsm.future, [ 'D', 'C' ]) + t.is(fsm.canHistoryBack, true) + t.is(fsm.canHistoryForward, true) + + fsm.historyForward() + t.is(fsm.state, 'C') + t.deepEqual(fsm.history, [ 'A', 'B', 'C' ]) + t.deepEqual(fsm.future, [ 'D' ]) + t.is(fsm.canHistoryBack, true) + t.is(fsm.canHistoryForward, true) + + fsm.step() + t.is(fsm.state, 'D') + t.deepEqual(fsm.history, [ 'A', 'B', 'C', 'D' ]) + t.deepEqual(fsm.future, [ ]) + t.is(fsm.canHistoryBack, true) + t.is(fsm.canHistoryForward, false) + + error = t.throws(() => { + fsm.historyForward() + }) + t.is(error.message, 'no history') + +}) + +//------------------------------------------------------------------------------------------------- + +test('history back and forward lifecycle events', t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'D' } + ], + methods: { + onBeforeTransition: logger, + onBeforeStep: logger, + onBeforeHistoryBack: logger, + onBeforeHistoryForward: logger, + onLeaveState: logger, + onLeaveA: logger, + onLeaveB: logger, + onLeaveC: logger, + onLeaveD: logger, + onTransition: logger, + onEnterState: logger, + onEnterA: logger, + onEnterB: logger, + onEnterC: logger, + onEnterD: logger, + onAfterTransition: logger, + onAfterStep: logger, + onAfterHistoryBack: logger, + onAfterHistoryForward: logger + }, + plugins: [ + StateMachineHistory + ] + }) + + fsm.step() + fsm.step() + fsm.step() + logger.clear() + + t.is(fsm.state, 'D') + t.deepEqual(fsm.history, [ 'A', 'B', 'C', 'D' ]) + t.deepEqual(fsm.future, [ ]) + + fsm.historyBack() + + t.is(fsm.state, 'C') + t.deepEqual(fsm.history, [ 'A', 'B', 'C' ]) + t.deepEqual(fsm.future, [ 'D' ]) + + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'historyBack', from: 'D', to: 'C', current: 'D' }, + { event: 'onBeforeHistoryBack', transition: 'historyBack', from: 'D', to: 'C', current: 'D' }, + { event: 'onLeaveState', transition: 'historyBack', from: 'D', to: 'C', current: 'D' }, + { event: 'onLeaveD', transition: 'historyBack', from: 'D', to: 'C', current: 'D' }, + { event: 'onTransition', transition: 'historyBack', from: 'D', to: 'C', current: 'D' }, + { event: 'onEnterState', transition: 'historyBack', from: 'D', to: 'C', current: 'C' }, + { event: 'onEnterC', transition: 'historyBack', from: 'D', to: 'C', current: 'C' }, + { event: 'onAfterTransition', transition: 'historyBack', from: 'D', to: 'C', current: 'C' }, + { event: 'onAfterHistoryBack', transition: 'historyBack', from: 'D', to: 'C', current: 'C' } + ]) + + logger.clear() + + fsm.historyForward() + + t.is(fsm.state, 'D') + t.deepEqual(fsm.history, [ 'A', 'B', 'C', 'D' ]) + t.deepEqual(fsm.future, [ ]) + + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'historyForward', from: 'C', to: 'D', current: 'C' }, + { event: 'onBeforeHistoryForward', transition: 'historyForward', from: 'C', to: 'D', current: 'C' }, + { event: 'onLeaveState', transition: 'historyForward', from: 'C', to: 'D', current: 'C' }, + { event: 'onLeaveC', transition: 'historyForward', from: 'C', to: 'D', current: 'C' }, + { event: 'onTransition', transition: 'historyForward', from: 'C', to: 'D', current: 'C' }, + { event: 'onEnterState', transition: 'historyForward', from: 'C', to: 'D', current: 'D' }, + { event: 'onEnterD', transition: 'historyForward', from: 'C', to: 'D', current: 'D' }, + { event: 'onAfterTransition', transition: 'historyForward', from: 'C', to: 'D', current: 'D' }, + { event: 'onAfterHistoryForward', transition: 'historyForward', from: 'C', to: 'D', current: 'D' } + ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('history can be used with a state machine factory', t => { + + var FSM = StateMachine.factory({ + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid' }, + { name: 'freeze', from: 'liquid', to: 'solid' }, + { name: 'vaporize', from: 'liquid', to: 'gas' }, + { name: 'condense', from: 'gas', to: 'liquid' } + ], + plugins: [ + StateMachineHistory + ] + }) + + var a = new FSM(), + b = new FSM(); + + t.is(a.state, 'solid') + t.is(b.state, 'solid') + t.deepEqual(a.history, [ 'solid' ]) + t.deepEqual(b.history, [ 'solid' ]) + + a.melt() + a.vaporize() + a.condense() + a.freeze() + + t.is(a.state, 'solid') + t.is(b.state, 'solid') + t.deepEqual(a.history, [ 'solid', 'liquid', 'gas', 'liquid', 'solid' ]) + t.deepEqual(b.history, [ 'solid' ]) + + b.melt() + b.freeze() + + t.is(a.state, 'solid') + t.is(b.state, 'solid') + + t.deepEqual(a.history, [ 'solid', 'liquid', 'gas', 'liquid', 'solid' ]) + t.deepEqual(b.history, [ 'solid', 'liquid', 'solid' ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('history can be used with a singleton state machine applied to existing object', t => { + + var fsm = { + name: 'jake' + } + + StateMachine.apply(fsm, { + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid' }, + { name: 'freeze', from: 'liquid', to: 'solid' }, + { name: 'vaporize', from: 'liquid', to: 'gas' }, + { name: 'condense', from: 'gas', to: 'liquid' } + ], + plugins: [ + StateMachineHistory + ] + }) + + t.is(fsm.name, 'jake') + t.is(fsm.state, 'solid') + t.deepEqual(fsm.history, [ 'solid' ]) + + fsm.melt(); + t.is(fsm.state, 'liquid') + t.deepEqual(fsm.history, [ 'solid', 'liquid' ]) + + fsm.vaporize(); + t.is(fsm.state, 'gas') + t.deepEqual(fsm.history, [ 'solid', 'liquid', 'gas' ]) + + fsm.condense() + t.is(fsm.state, 'liquid') + t.deepEqual(fsm.history, [ 'solid', 'liquid', 'gas', 'liquid' ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test('history can be used with a state machine factory applied to existing class', t => { + + function FSM(name) { + this.name = name + this._fsm() + } + + StateMachine.factory(FSM, { + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid' }, + { name: 'freeze', from: 'liquid', to: 'solid' }, + { name: 'vaporize', from: 'liquid', to: 'gas' }, + { name: 'condense', from: 'gas', to: 'liquid' } + ], + plugins: [ + StateMachineHistory + ] + }) + + var a = new FSM('A'), + b = new FSM('B'); + + t.is(a.name, 'A') + t.is(b.name, 'B') + + t.is(a.state, 'solid') + t.is(b.state, 'solid') + t.deepEqual(a.history, [ 'solid' ]) + t.deepEqual(b.history, [ 'solid' ]) + + a.melt() + a.vaporize() + a.condense() + a.freeze() + + t.is(a.state, 'solid') + t.is(b.state, 'solid') + t.deepEqual(a.history, [ 'solid', 'liquid', 'gas', 'liquid', 'solid' ]) + t.deepEqual(b.history, [ 'solid' ]) + + b.melt() + b.freeze() + + t.is(a.state, 'solid') + t.is(b.state, 'solid') + + t.deepEqual(a.history, [ 'solid', 'liquid', 'gas', 'liquid', 'solid' ]) + t.deepEqual(b.history, [ 'solid', 'liquid', 'solid' ]) + +}) + +//------------------------------------------------------------------------------------------------- diff --git a/test/plugin/visualize.js b/test/plugin/visualize.js new file mode 100644 index 0000000..a2b9388 --- /dev/null +++ b/test/plugin/visualize.js @@ -0,0 +1,443 @@ +import test from 'ava' +import StateMachine from '../../src/app' +import visualize from '../../src/plugin/visualize' + +var dotcfg = visualize.dotcfg, // converts FSM to DOT CONFIG + dotify = visualize.dotify; // converts DOT CONFIG to DOT OUTPUT + +//------------------------------------------------------------------------------------------------- + +test('visualize state machine', t => { + + var fsm = new StateMachine({ + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid' }, + { name: 'freeze', from: 'liquid', to: 'solid' }, + { name: 'vaporize', from: 'liquid', to: 'gas' }, + { name: 'condense', from: 'gas', to: 'liquid' } + ] + }) + + t.is(visualize(fsm), `digraph "fsm" { + "solid"; + "liquid"; + "gas"; + "solid" -> "liquid" [ label=" melt " ]; + "liquid" -> "solid" [ label=" freeze " ]; + "liquid" -> "gas" [ label=" vaporize " ]; + "gas" -> "liquid" [ label=" condense " ]; +}`) +}) + +//------------------------------------------------------------------------------------------------- + +test('visualize state machine factory', t => { + + var FSM = StateMachine.factory({ + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid' }, + { name: 'freeze', from: 'liquid', to: 'solid' }, + { name: 'vaporize', from: 'liquid', to: 'gas' }, + { name: 'condense', from: 'gas', to: 'liquid' } + ] + }) + + t.is(visualize(FSM), `digraph "fsm" { + "solid"; + "liquid"; + "gas"; + "solid" -> "liquid" [ label=" melt " ]; + "liquid" -> "solid" [ label=" freeze " ]; + "liquid" -> "gas" [ label=" vaporize " ]; + "gas" -> "liquid" [ label=" condense " ]; +}`) +}) + +//------------------------------------------------------------------------------------------------- + +test('visualize with custom .dot markup', t => { + + var fsm = new StateMachine({ + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid', dot: { color: 'red', headport: 'nw', tailport: 'ne' } }, + { name: 'freeze', from: 'liquid', to: 'solid', dot: { color: 'grey', headport: 'se', tailport: 'sw' } }, + { name: 'vaporize', from: 'liquid', to: 'gas', dot: { color: 'yellow', headport: 'nw', tailport: 'ne' } }, + { name: 'condense', from: 'gas', to: 'liquid', dot: { color: 'brown', headport: 'se', tailport: 'sw' } } + ] + }) + + t.is(visualize(fsm, { name: 'matter', orientation: 'horizontal' }), `digraph "matter" { + rankdir=LR; + "solid"; + "liquid"; + "gas"; + "solid" -> "liquid" [ color="red" ; headport="nw" ; label=" melt " ; tailport="ne" ]; + "liquid" -> "solid" [ color="grey" ; headport="se" ; label=" freeze " ; tailport="sw" ]; + "liquid" -> "gas" [ color="yellow" ; headport="nw" ; label=" vaporize " ; tailport="ne" ]; + "gas" -> "liquid" [ color="brown" ; headport="se" ; label=" condense " ; tailport="sw" ]; +}`) +}) + +//================================================================================================= +// TEST FSM => DOTCFG +//================================================================================================= + +test('dotcfg simple state machine', t => { + + var fsm = new StateMachine({ + init: 'solid', + transitions: [ + { name: 'melt', from: 'solid', to: 'liquid' }, + { name: 'freeze', from: 'liquid', to: 'solid' }, + { name: 'vaporize', from: 'liquid', to: 'gas' }, + { name: 'condense', from: 'gas', to: 'liquid' } + ] + }) + + t.deepEqual(dotcfg(fsm), { + states: [ 'solid', 'liquid', 'gas' ], + transitions: [ + { from: 'solid', to: 'liquid', label: ' melt ' }, + { from: 'liquid', to: 'solid', label: ' freeze ' }, + { from: 'liquid', to: 'gas', label: ' vaporize ' }, + { from: 'gas', to: 'liquid', label: ' condense ' } + ] + }) + +}) + +//------------------------------------------------------------------------------------------------- + +test('dotcfg for state machine - optionally include :init transition', t => { + + var fsm = new StateMachine({ + init: { name: 'boot', from: 'booting', to: 'ready', dot: { color: 'red' } } + }) + + t.deepEqual(dotcfg(fsm, { init: false }), { + states: [ 'ready' ] + }) + + t.deepEqual(dotcfg(fsm, { init: true }), { + states: [ 'booting', 'ready' ], + transitions: [ + { from: 'booting', to: 'ready', label: ' boot ', color: 'red' } + ] + }) + +}) + +//------------------------------------------------------------------------------------------------- + +test('dotcfg for fsm with multiple transitions with same :name', t => { + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'D' } + ] + }) + + t.deepEqual(dotcfg(fsm), { + states: [ 'A', 'B', 'C', 'D' ], + transitions: [ + { from: 'A', to: 'B', label: ' step ' }, + { from: 'B', to: 'C', label: ' step ' }, + { from: 'C', to: 'D', label: ' step ' } + ] + }) + +}) + +//------------------------------------------------------------------------------------------------- + +test('dotcfg for fsm transition with multiple :from', t => { + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'D' }, + { name: 'reset', from: [ 'A', 'B' ], to: 'A' } + ] + }) + + t.deepEqual(dotcfg(fsm), { + states: [ 'A', 'B', 'C', 'D' ], + transitions: [ + { from: 'A', to: 'B', label: ' step ' }, + { from: 'B', to: 'C', label: ' step ' }, + { from: 'C', to: 'D', label: ' step ' }, + { from: 'A', to: 'A', label: ' reset ' }, + { from: 'B', to: 'A', label: ' reset ' } + ] + }) + +}) + +//------------------------------------------------------------------------------------------------- + +test('dotcfg for fsm with wildcard/missing :from', t => { + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'D' }, + { name: 'reset', from: '*', to: 'A' }, + { name: 'finish', /* missing */ to: 'X' } + ] + }) + + t.deepEqual(dotcfg(fsm), { + states: [ 'A', 'B', 'C', 'D', 'X' ], + transitions: [ + { from: 'A', to: 'B', label: ' step ' }, + { from: 'B', to: 'C', label: ' step ' }, + { from: 'C', to: 'D', label: ' step ' }, + { from: 'none', to: 'A', label: ' reset ' }, + { from: 'A', to: 'A', label: ' reset ' }, + { from: 'B', to: 'A', label: ' reset ' }, + { from: 'C', to: 'A', label: ' reset ' }, + { from: 'D', to: 'A', label: ' reset ' }, + { from: 'X', to: 'A', label: ' reset ' }, + { from: 'none', to: 'X', label: ' finish ' }, + { from: 'A', to: 'X', label: ' finish ' }, + { from: 'B', to: 'X', label: ' finish ' }, + { from: 'C', to: 'X', label: ' finish ' }, + { from: 'D', to: 'X', label: ' finish ' }, + { from: 'X', to: 'X', label: ' finish ' } + ] + }) + +}) + +//------------------------------------------------------------------------------------------------- + +test('dotcfg for fsm with wildcard/missing :to', t => { + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'step', from: 'B', to: 'C' }, + { name: 'step', from: 'C', to: 'D' }, + { name: 'stay', from: 'A', to: 'A' }, + { name: 'stay', from: 'B', to: '*' }, + { name: 'stay', from: 'C' /* missing */ }, + { name: 'noop', from: '*', to: '*' } + ] + }) + + t.deepEqual(dotcfg(fsm), { + states: [ 'A', 'B', 'C', 'D' ], + transitions: [ + { from: 'A', to: 'B', label: ' step ' }, + { from: 'B', to: 'C', label: ' step ' }, + { from: 'C', to: 'D', label: ' step ' }, + { from: 'A', to: 'A', label: ' stay ' }, + { from: 'B', to: 'B', label: ' stay ' }, + { from: 'C', to: 'C', label: ' stay ' }, + { from: 'none', to: 'none', label: ' noop ' }, + { from: 'A', to: 'A', label: ' noop ' }, + { from: 'B', to: 'B', label: ' noop ' }, + { from: 'C', to: 'C', label: ' noop ' }, + { from: 'D', to: 'D', label: ' noop ' } + ] + }) + +}) + +//------------------------------------------------------------------------------------------------- + +test('dotcfg for fsm - conditional transition is not displayed', t => { + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: '*', to: function(n) { return this.skip(n) } }, + ], + methods: { + skip: function(amount) { + var code = this.state.charCodeAt(0); + return String.fromCharCode(code + (amount || 1)); + } + } + }); + + t.deepEqual(dotcfg(fsm), { + states: [ 'A' ] + }) + +}) + +//------------------------------------------------------------------------------------------------- + +test('dotcfg with custom transition .dot edge markup', t => { + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B', dot: { color: "red", headport: 'nw', tailport: 'ne', label: 'A2B' } }, + { name: 'step', from: 'B', to: 'C', dot: { color: "green", headport: 'sw', tailport: 'se', label: 'B2C' } } + ] + }) + + t.deepEqual(dotcfg(fsm), { + states: [ 'A', 'B', 'C' ], + transitions: [ + { from: 'A', to: 'B', label: 'A2B', color: "red", headport: "nw", tailport: "ne" }, + { from: 'B', to: 'C', label: 'B2C', color: "green", headport: "sw", tailport: "se" } + ] + }) + +}) + +//------------------------------------------------------------------------------------------------- + +test('dotcfg with custom name', t => { + + var fsm = new StateMachine(); + + t.deepEqual(dotcfg(fsm, { name: 'bob' }), { + name: 'bob', + }) + +}) + +//------------------------------------------------------------------------------------------------- + +test('dotcfg with custom orientation', t => { + + var fsm = new StateMachine(); + + t.deepEqual(dotcfg(fsm, { orientation: 'horizontal' }), { + rankdir: 'LR', + }) + + t.deepEqual(dotcfg(fsm, { orientation: 'vertical' }), { + rankdir: 'TB', + }) + +}) + +//------------------------------------------------------------------------------------------------- + +test('dotcfg for empty state machine', t => { + + var fsm = new StateMachine(); + + t.deepEqual(dotcfg(fsm), {}) + +}) + +//================================================================================================= +// TEST DOTCFG => DOT OUTPUT +//================================================================================================= + +test('dotify empty', t => { + var expected = `digraph "fsm" { +}` + t.is(dotify(), expected) + t.is(dotify({}), expected) +}) + +//------------------------------------------------------------------------------------------------- + +test('dotify name', t => { + t.is(dotify({ name: 'bob' }), `digraph "bob" { +}`) +}) + +//------------------------------------------------------------------------------------------------- + +test('dotify rankdir', t => { + t.is(dotify({ rankdir: 'LR' }), `digraph "fsm" { + rankdir=LR; +}`) +}) + +//------------------------------------------------------------------------------------------------- + +test('dotify states', t => { + var states = [ 'A', 'B' ]; + t.is(dotify({ states: states }), `digraph "fsm" { + "A"; + "B"; +}`) +}) + +//------------------------------------------------------------------------------------------------- + +test('dotify transitions', t => { + var transitions = [ + { from: 'A', to: 'B' }, + { from: 'B', to: 'C' }, + ]; + t.is(dotify({ transitions: transitions }), `digraph "fsm" { + "A" -> "B"; + "B" -> "C"; +}`) +}) + +//------------------------------------------------------------------------------------------------- + +test('dotify transitions with labels', t => { + var transitions = [ + { from: 'A', to: 'B', label: 'first' }, + { from: 'B', to: 'C', label: 'second' } + ]; + t.is(dotify({ transitions: transitions }), `digraph "fsm" { + "A" -> "B" [ label="first" ]; + "B" -> "C" [ label="second" ]; +}`) +}) + +//------------------------------------------------------------------------------------------------- + +test('dotify transitions with custom .dot edge markup', t => { + var transitions = [ + { from: 'A', to: 'B', label: 'first', color: 'red', headport: 'nw', tailport: 'ne' }, + { from: 'B', to: 'A', label: 'second', color: 'green', headport: 'se', tailport: 'sw' } + ] + t.is(dotify({ transitions: transitions }), `digraph "fsm" { + "A" -> "B" [ color="red" ; headport="nw" ; label="first" ; tailport="ne" ]; + "B" -> "A" [ color="green" ; headport="se" ; label="second" ; tailport="sw" ]; +}`) +}) + +//------------------------------------------------------------------------------------------------- + +test('dotify kitchen sink', t => { + var name = "my fsm", + rankdir = "LR", + states = [ 'none', 'solid', 'liquid', 'gas' ], + transitions = [ + { from: 'none', to: 'solid', color: 'red', label: 'init' }, + { from: 'solid', to: 'liquid', color: 'red', label: 'melt' }, + { from: 'liquid', to: 'solid', color: 'green', label: 'freeze' }, + { from: 'liquid', to: 'gas', color: 'red', label: 'vaporize' }, + { from: 'gas', to: 'liquid', color: 'green', label: 'condense' } + ]; + t.is(dotify({ name: name, rankdir: rankdir, states: states, transitions: transitions }), `digraph "my fsm" { + rankdir=LR; + "none"; + "solid"; + "liquid"; + "gas"; + "none" -> "solid" [ color="red" ; label="init" ]; + "solid" -> "liquid" [ color="red" ; label="melt" ]; + "liquid" -> "solid" [ color="green" ; label="freeze" ]; + "liquid" -> "gas" [ color="red" ; label="vaporize" ]; + "gas" -> "liquid" [ color="green" ; label="condense" ]; +}`) +}) + +//================================================================================================= diff --git a/test/plugins.js b/test/plugins.js new file mode 100644 index 0000000..486125b --- /dev/null +++ b/test/plugins.js @@ -0,0 +1,151 @@ +import test from 'ava' +import StateMachine from '../src/app' +import LifecycleLogger from './helpers/lifecycle_logger' + +//------------------------------------------------------------------------------------------------- + +test('an empty plugin object', t => { + + var plugin = { + init: function(instance) { + instance.plugged = true + } + }; + + var fsm = new StateMachine({ + plugins: [ plugin ] + }); + + t.is(fsm.state, 'none') + t.is(fsm.plugged, true) + +}) + +//------------------------------------------------------------------------------------------------- + +test('an empty plugin function', t => { + + var plugin = function() { + return { + init: function(instance) { + instance.plugged = true + } + } + }; + + var fsm = new StateMachine({ + plugins: [ plugin ] + }); + + t.is(fsm.state, 'none') + t.is(fsm.plugged, true) + +}) + +//------------------------------------------------------------------------------------------------- + +test('an empty plugin function with configuration', t => { + + var plugin = function(value) { + return { + init: function(instance) { + instance.plugged = value + } + } + }; + + var fsm = new StateMachine({ + plugins: [ new plugin(42) ] + }); + + t.is(fsm.state, 'none') + t.is(fsm.plugged, 42) + +}) + +//------------------------------------------------------------------------------------------------- + +test('plugin can add methods', t => { + + var plugin = { + methods: { + foo: function() { return 'FOO' }, + bar: function() { return 'BAR' } + } + }; + + var fsm = new StateMachine({ + plugins: [ plugin ] + }); + + t.is(fsm.state, 'none') + t.is(fsm.foo(), 'FOO') + t.is(fsm.bar(), 'BAR') + +}) + +//------------------------------------------------------------------------------------------------- + +test('plugin can add properties', t => { + + var plugin = { + properties: { + color: { get: function() { return 'red' } } + } + }; + + var fsm = new StateMachine({ + plugins: [ plugin ] + }); + + t.is(fsm.state, 'none') + t.is(fsm.color, 'red') + +}) + +//------------------------------------------------------------------------------------------------- + +test('plugin lifecycle hook', t => { + + var plugin = { + + init: function(instance) { + instance.logger = new LifecycleLogger(); + }, + + lifecycle: function(instance, lifecycle) { + instance.logger(lifecycle) + } + + }; + + var fsm = new StateMachine({ + transitions: [ + { name: 'step', from: 'none', to: 'complete' } + ], + plugins: [ plugin ] + }); + + t.is(fsm.state, 'none') + t.deepEqual(fsm.logger.log, []) + + fsm.step() + + t.is(fsm.state, 'complete') + t.deepEqual(fsm.logger.log, [ + { event: 'onBeforeTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onBeforeStep', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveState', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onLeaveNone', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onTransition', transition: 'step', from: 'none', to: 'complete', current: 'none' }, + { event: 'onEnterState', transition: 'step', from: 'none', to: 'complete', current: 'complete' }, + { event: 'onEnterComplete', transition: 'step', from: 'none', to: 'complete', current: 'complete' }, + { event: 'onComplete', transition: 'step', from: 'none', to: 'complete', current: 'complete' }, + { event: 'onAfterTransition', transition: 'step', from: 'none', to: 'complete', current: 'complete' }, + { event: 'onAfterStep', transition: 'step', from: 'none', to: 'complete', current: 'complete' }, + { event: 'onStep', transition: 'step', from: 'none', to: 'complete', current: 'complete' } + ]) + +}) + +//------------------------------------------------------------------------------------------------- diff --git a/test/qunit/qunit.css b/test/qunit/qunit.css deleted file mode 100644 index 93026e3..0000000 --- a/test/qunit/qunit.css +++ /dev/null @@ -1,237 +0,0 @@ -/*! - * QUnit 1.14.0 - * http://qunitjs.com/ - * - * Copyright 2013 jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2014-01-31T16:40Z - */ - -/** Font Family and Sizes */ - -#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { - font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; -} - -#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } -#qunit-tests { font-size: smaller; } - - -/** Resets */ - -#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { - margin: 0; - padding: 0; -} - - -/** Header */ - -#qunit-header { - padding: 0.5em 0 0.5em 1em; - - color: #8699A4; - background-color: #0D3349; - - font-size: 1.5em; - line-height: 1em; - font-weight: 400; - - border-radius: 5px 5px 0 0; -} - -#qunit-header a { - text-decoration: none; - color: #C2CCD1; -} - -#qunit-header a:hover, -#qunit-header a:focus { - color: #FFF; -} - -#qunit-testrunner-toolbar label { - display: inline-block; - padding: 0 0.5em 0 0.1em; -} - -#qunit-banner { - height: 5px; -} - -#qunit-testrunner-toolbar { - padding: 0.5em 0 0.5em 2em; - color: #5E740B; - background-color: #EEE; - overflow: hidden; -} - -#qunit-userAgent { - padding: 0.5em 0 0.5em 2.5em; - background-color: #2B81AF; - color: #FFF; - text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; -} - -#qunit-modulefilter-container { - float: right; -} - -/** Tests: Pass/Fail */ - -#qunit-tests { - list-style-position: inside; -} - -#qunit-tests li { - padding: 0.4em 0.5em 0.4em 2.5em; - border-bottom: 1px solid #FFF; - list-style-position: inside; -} - -#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { - display: none; -} - -#qunit-tests li strong { - cursor: pointer; -} - -#qunit-tests li a { - padding: 0.5em; - color: #C2CCD1; - text-decoration: none; -} -#qunit-tests li a:hover, -#qunit-tests li a:focus { - color: #000; -} - -#qunit-tests li .runtime { - float: right; - font-size: smaller; -} - -.qunit-assert-list { - margin-top: 0.5em; - padding: 0.5em; - - background-color: #FFF; - - border-radius: 5px; -} - -.qunit-collapsed { - display: none; -} - -#qunit-tests table { - border-collapse: collapse; - margin-top: 0.2em; -} - -#qunit-tests th { - text-align: right; - vertical-align: top; - padding: 0 0.5em 0 0; -} - -#qunit-tests td { - vertical-align: top; -} - -#qunit-tests pre { - margin: 0; - white-space: pre-wrap; - word-wrap: break-word; -} - -#qunit-tests del { - background-color: #E0F2BE; - color: #374E0C; - text-decoration: none; -} - -#qunit-tests ins { - background-color: #FFCACA; - color: #500; - text-decoration: none; -} - -/*** Test Counts */ - -#qunit-tests b.counts { color: #000; } -#qunit-tests b.passed { color: #5E740B; } -#qunit-tests b.failed { color: #710909; } - -#qunit-tests li li { - padding: 5px; - background-color: #FFF; - border-bottom: none; - list-style-position: inside; -} - -/*** Passing Styles */ - -#qunit-tests li li.pass { - color: #3C510C; - background-color: #FFF; - border-left: 10px solid #C6E746; -} - -#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } -#qunit-tests .pass .test-name { color: #366097; } - -#qunit-tests .pass .test-actual, -#qunit-tests .pass .test-expected { color: #999; } - -#qunit-banner.qunit-pass { background-color: #C6E746; } - -/*** Failing Styles */ - -#qunit-tests li li.fail { - color: #710909; - background-color: #FFF; - border-left: 10px solid #EE5757; - white-space: pre; -} - -#qunit-tests > li:last-child { - border-radius: 0 0 5px 5px; -} - -#qunit-tests .fail { color: #000; background-color: #EE5757; } -#qunit-tests .fail .test-name, -#qunit-tests .fail .module-name { color: #000; } - -#qunit-tests .fail .test-actual { color: #EE5757; } -#qunit-tests .fail .test-expected { color: #008000; } - -#qunit-banner.qunit-fail { background-color: #EE5757; } - - -/** Result */ - -#qunit-testresult { - padding: 0.5em 0.5em 0.5em 2.5em; - - color: #2B81AF; - background-color: #D2E0E6; - - border-bottom: 1px solid #FFF; -} -#qunit-testresult .module-name { - font-weight: 700; -} - -/** Fixture */ - -#qunit-fixture { - position: absolute; - top: -10000px; - left: -10000px; - width: 1000px; - height: 1000px; -} diff --git a/test/qunit/qunit.js b/test/qunit/qunit.js deleted file mode 100644 index 0e279fd..0000000 --- a/test/qunit/qunit.js +++ /dev/null @@ -1,2288 +0,0 @@ -/*! - * QUnit 1.14.0 - * http://qunitjs.com/ - * - * Copyright 2013 jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2014-01-31T16:40Z - */ - -(function( window ) { - -var QUnit, - assert, - config, - onErrorFnPrev, - testId = 0, - fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - // Keep a local reference to Date (GH-283) - Date = window.Date, - setTimeout = window.setTimeout, - clearTimeout = window.clearTimeout, - defined = { - document: typeof window.document !== "undefined", - setTimeout: typeof window.setTimeout !== "undefined", - sessionStorage: (function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch( e ) { - return false; - } - }()) - }, - /** - * Provides a normalized error string, correcting an issue - * with IE 7 (and prior) where Error.prototype.toString is - * not properly implemented - * - * Based on http://es5.github.com/#x15.11.4.4 - * - * @param {String|Error} error - * @return {String} error message - */ - errorString = function( error ) { - var name, message, - errorString = error.toString(); - if ( errorString.substring( 0, 7 ) === "[object" ) { - name = error.name ? error.name.toString() : "Error"; - message = error.message ? error.message.toString() : ""; - if ( name && message ) { - return name + ": " + message; - } else if ( name ) { - return name; - } else if ( message ) { - return message; - } else { - return "Error"; - } - } else { - return errorString; - } - }, - /** - * Makes a clone of an object using only Array or Object as base, - * and copies over the own enumerable properties. - * - * @param {Object} obj - * @return {Object} New object with only the own properties (recursively). - */ - objectValues = function( obj ) { - // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392. - /*jshint newcap: false */ - var key, val, - vals = QUnit.is( "array", obj ) ? [] : {}; - for ( key in obj ) { - if ( hasOwn.call( obj, key ) ) { - val = obj[key]; - vals[key] = val === Object(val) ? objectValues(val) : val; - } - } - return vals; - }; - - -// Root QUnit object. -// `QUnit` initialized at top of scope -QUnit = { - - // call on start of module test to prepend name to all tests - module: function( name, testEnvironment ) { - config.currentModule = name; - config.currentModuleTestEnvironment = testEnvironment; - config.modules[name] = true; - }, - - asyncTest: function( testName, expected, callback ) { - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - QUnit.test( testName, expected, callback, true ); - }, - - test: function( testName, expected, callback, async ) { - var test, - nameHtml = "" + escapeText( testName ) + ""; - - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - if ( config.currentModule ) { - nameHtml = "" + escapeText( config.currentModule ) + ": " + nameHtml; - } - - test = new Test({ - nameHtml: nameHtml, - testName: testName, - expected: expected, - async: async, - callback: callback, - module: config.currentModule, - moduleTestEnvironment: config.currentModuleTestEnvironment, - stack: sourceFromStacktrace( 2 ) - }); - - if ( !validTest( test ) ) { - return; - } - - test.queue(); - }, - - // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through. - expect: function( asserts ) { - if (arguments.length === 1) { - config.current.expected = asserts; - } else { - return config.current.expected; - } - }, - - start: function( count ) { - // QUnit hasn't been initialized yet. - // Note: RequireJS (et al) may delay onLoad - if ( config.semaphore === undefined ) { - QUnit.begin(function() { - // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first - setTimeout(function() { - QUnit.start( count ); - }); - }); - return; - } - - config.semaphore -= count || 1; - // don't start until equal number of stop-calls - if ( config.semaphore > 0 ) { - return; - } - // ignore if start is called more often then stop - if ( config.semaphore < 0 ) { - config.semaphore = 0; - QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); - return; - } - // A slight delay, to avoid any current callbacks - if ( defined.setTimeout ) { - setTimeout(function() { - if ( config.semaphore > 0 ) { - return; - } - if ( config.timeout ) { - clearTimeout( config.timeout ); - } - - config.blocking = false; - process( true ); - }, 13); - } else { - config.blocking = false; - process( true ); - } - }, - - stop: function( count ) { - config.semaphore += count || 1; - config.blocking = true; - - if ( config.testTimeout && defined.setTimeout ) { - clearTimeout( config.timeout ); - config.timeout = setTimeout(function() { - QUnit.ok( false, "Test timed out" ); - config.semaphore = 1; - QUnit.start(); - }, config.testTimeout ); - } - } -}; - -// We use the prototype to distinguish between properties that should -// be exposed as globals (and in exports) and those that shouldn't -(function() { - function F() {} - F.prototype = QUnit; - QUnit = new F(); - // Make F QUnit's constructor so that we can add to the prototype later - QUnit.constructor = F; -}()); - -/** - * Config object: Maintain internal state - * Later exposed as QUnit.config - * `config` initialized at top of scope - */ -config = { - // The queue of tests to run - queue: [], - - // block until document ready - blocking: true, - - // when enabled, show only failing tests - // gets persisted through sessionStorage and can be changed in UI via checkbox - hidepassed: false, - - // by default, run previously failed tests first - // very useful in combination with "Hide passed tests" checked - reorder: true, - - // by default, modify document.title when suite is done - altertitle: true, - - // by default, scroll to top of the page when suite is done - scrolltop: true, - - // when enabled, all tests must call expect() - requireExpects: false, - - // add checkboxes that are persisted in the query-string - // when enabled, the id is set to `true` as a `QUnit.config` property - urlConfig: [ - { - id: "noglobals", - label: "Check for Globals", - tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." - }, - { - id: "notrycatch", - label: "No try-catch", - tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." - } - ], - - // Set of all modules. - modules: {}, - - // logging callback queues - begin: [], - done: [], - log: [], - testStart: [], - testDone: [], - moduleStart: [], - moduleDone: [] -}; - -// Initialize more QUnit.config and QUnit.urlParams -(function() { - var i, current, - location = window.location || { search: "", protocol: "file:" }, - params = location.search.slice( 1 ).split( "&" ), - length = params.length, - urlParams = {}; - - if ( params[ 0 ] ) { - for ( i = 0; i < length; i++ ) { - current = params[ i ].split( "=" ); - current[ 0 ] = decodeURIComponent( current[ 0 ] ); - - // allow just a key to turn on a flag, e.g., test.html?noglobals - current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; - if ( urlParams[ current[ 0 ] ] ) { - urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] ); - } else { - urlParams[ current[ 0 ] ] = current[ 1 ]; - } - } - } - - QUnit.urlParams = urlParams; - - // String search anywhere in moduleName+testName - config.filter = urlParams.filter; - - // Exact match of the module name - config.module = urlParams.module; - - config.testNumber = []; - if ( urlParams.testNumber ) { - - // Ensure that urlParams.testNumber is an array - urlParams.testNumber = [].concat( urlParams.testNumber ); - for ( i = 0; i < urlParams.testNumber.length; i++ ) { - current = urlParams.testNumber[ i ]; - config.testNumber.push( parseInt( current, 10 ) ); - } - } - - // Figure out if we're running the tests from a server or not - QUnit.isLocal = location.protocol === "file:"; -}()); - -extend( QUnit, { - - config: config, - - // Initialize the configuration options - init: function() { - extend( config, { - stats: { all: 0, bad: 0 }, - moduleStats: { all: 0, bad: 0 }, - started: +new Date(), - updateRate: 1000, - blocking: false, - autostart: true, - autorun: false, - filter: "", - queue: [], - semaphore: 1 - }); - - var tests, banner, result, - qunit = id( "qunit" ); - - if ( qunit ) { - qunit.innerHTML = - "

      " + escapeText( document.title ) + "

      " + - "

      " + - "
      " + - "

      " + - "
        "; - } - - tests = id( "qunit-tests" ); - banner = id( "qunit-banner" ); - result = id( "qunit-testresult" ); - - if ( tests ) { - tests.innerHTML = ""; - } - - if ( banner ) { - banner.className = ""; - } - - if ( result ) { - result.parentNode.removeChild( result ); - } - - if ( tests ) { - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...
         "; - } - }, - - // Resets the test setup. Useful for tests that modify the DOM. - /* - DEPRECATED: Use multiple tests instead of resetting inside a test. - Use testStart or testDone for custom cleanup. - This method will throw an error in 2.0, and will be removed in 2.1 - */ - reset: function() { - var fixture = id( "qunit-fixture" ); - if ( fixture ) { - fixture.innerHTML = config.fixture; - } - }, - - // Safe object type checking - is: function( type, obj ) { - return QUnit.objectType( obj ) === type; - }, - - objectType: function( obj ) { - if ( typeof obj === "undefined" ) { - return "undefined"; - } - - // Consider: typeof null === object - if ( obj === null ) { - return "null"; - } - - var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), - type = match && match[1] || ""; - - switch ( type ) { - case "Number": - if ( isNaN(obj) ) { - return "nan"; - } - return "number"; - case "String": - case "Boolean": - case "Array": - case "Date": - case "RegExp": - case "Function": - return type.toLowerCase(); - } - if ( typeof obj === "object" ) { - return "object"; - } - return undefined; - }, - - push: function( result, actual, expected, message ) { - if ( !config.current ) { - throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); - } - - var output, source, - details = { - module: config.current.module, - name: config.current.testName, - result: result, - message: message, - actual: actual, - expected: expected - }; - - message = escapeText( message ) || ( result ? "okay" : "failed" ); - message = "" + message + ""; - output = message; - - if ( !result ) { - expected = escapeText( QUnit.jsDump.parse(expected) ); - actual = escapeText( QUnit.jsDump.parse(actual) ); - output += ""; - - if ( actual !== expected ) { - output += ""; - output += ""; - } - - source = sourceFromStacktrace(); - - if ( source ) { - details.source = source; - output += ""; - } - - output += "
        Expected:
        " + expected + "
        Result:
        " + actual + "
        Diff:
        " + QUnit.diff( expected, actual ) + "
        Source:
        " + escapeText( source ) + "
        "; - } - - runLoggingCallbacks( "log", QUnit, details ); - - config.current.assertions.push({ - result: !!result, - message: output - }); - }, - - pushFailure: function( message, source, actual ) { - if ( !config.current ) { - throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); - } - - var output, - details = { - module: config.current.module, - name: config.current.testName, - result: false, - message: message - }; - - message = escapeText( message ) || "error"; - message = "" + message + ""; - output = message; - - output += ""; - - if ( actual ) { - output += ""; - } - - if ( source ) { - details.source = source; - output += ""; - } - - output += "
        Result:
        " + escapeText( actual ) + "
        Source:
        " + escapeText( source ) + "
        "; - - runLoggingCallbacks( "log", QUnit, details ); - - config.current.assertions.push({ - result: false, - message: output - }); - }, - - url: function( params ) { - params = extend( extend( {}, QUnit.urlParams ), params ); - var key, - querystring = "?"; - - for ( key in params ) { - if ( hasOwn.call( params, key ) ) { - querystring += encodeURIComponent( key ) + "=" + - encodeURIComponent( params[ key ] ) + "&"; - } - } - return window.location.protocol + "//" + window.location.host + - window.location.pathname + querystring.slice( 0, -1 ); - }, - - extend: extend, - id: id, - addEvent: addEvent, - addClass: addClass, - hasClass: hasClass, - removeClass: removeClass - // load, equiv, jsDump, diff: Attached later -}); - -/** - * @deprecated: Created for backwards compatibility with test runner that set the hook function - * into QUnit.{hook}, instead of invoking it and passing the hook function. - * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. - * Doing this allows us to tell if the following methods have been overwritten on the actual - * QUnit object. - */ -extend( QUnit.constructor.prototype, { - - // Logging callbacks; all receive a single argument with the listed properties - // run test/logs.html for any related changes - begin: registerLoggingCallback( "begin" ), - - // done: { failed, passed, total, runtime } - done: registerLoggingCallback( "done" ), - - // log: { result, actual, expected, message } - log: registerLoggingCallback( "log" ), - - // testStart: { name } - testStart: registerLoggingCallback( "testStart" ), - - // testDone: { name, failed, passed, total, runtime } - testDone: registerLoggingCallback( "testDone" ), - - // moduleStart: { name } - moduleStart: registerLoggingCallback( "moduleStart" ), - - // moduleDone: { name, failed, passed, total } - moduleDone: registerLoggingCallback( "moduleDone" ) -}); - -if ( !defined.document || document.readyState === "complete" ) { - config.autorun = true; -} - -QUnit.load = function() { - runLoggingCallbacks( "begin", QUnit, {} ); - - // Initialize the config, saving the execution queue - var banner, filter, i, j, label, len, main, ol, toolbar, val, selection, - urlConfigContainer, moduleFilter, userAgent, - numModules = 0, - moduleNames = [], - moduleFilterHtml = "", - urlConfigHtml = "", - oldconfig = extend( {}, config ); - - QUnit.init(); - extend(config, oldconfig); - - config.blocking = false; - - len = config.urlConfig.length; - - for ( i = 0; i < len; i++ ) { - val = config.urlConfig[i]; - if ( typeof val === "string" ) { - val = { - id: val, - label: val - }; - } - config[ val.id ] = QUnit.urlParams[ val.id ]; - if ( !val.value || typeof val.value === "string" ) { - urlConfigHtml += ""; - } else { - urlConfigHtml += ""; - } - } - for ( i in config.modules ) { - if ( config.modules.hasOwnProperty( i ) ) { - moduleNames.push(i); - } - } - numModules = moduleNames.length; - moduleNames.sort( function( a, b ) { - return a.localeCompare( b ); - }); - moduleFilterHtml += ""; - - // `userAgent` initialized at top of scope - userAgent = id( "qunit-userAgent" ); - if ( userAgent ) { - userAgent.innerHTML = navigator.userAgent; - } - - // `banner` initialized at top of scope - banner = id( "qunit-header" ); - if ( banner ) { - banner.innerHTML = "
        " + banner.innerHTML + " "; - } - - // `toolbar` initialized at top of scope - toolbar = id( "qunit-testrunner-toolbar" ); - if ( toolbar ) { - // `filter` initialized at top of scope - filter = document.createElement( "input" ); - filter.type = "checkbox"; - filter.id = "qunit-filter-pass"; - - addEvent( filter, "click", function() { - var tmp, - ol = id( "qunit-tests" ); - - if ( filter.checked ) { - ol.className = ol.className + " hidepass"; - } else { - tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; - ol.className = tmp.replace( / hidepass /, " " ); - } - if ( defined.sessionStorage ) { - if (filter.checked) { - sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); - } else { - sessionStorage.removeItem( "qunit-filter-passed-tests" ); - } - } - }); - - if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { - filter.checked = true; - // `ol` initialized at top of scope - ol = id( "qunit-tests" ); - ol.className = ol.className + " hidepass"; - } - toolbar.appendChild( filter ); - - // `label` initialized at top of scope - label = document.createElement( "label" ); - label.setAttribute( "for", "qunit-filter-pass" ); - label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." ); - label.innerHTML = "Hide passed tests"; - toolbar.appendChild( label ); - - urlConfigContainer = document.createElement("span"); - urlConfigContainer.innerHTML = urlConfigHtml; - // For oldIE support: - // * Add handlers to the individual elements instead of the container - // * Use "click" instead of "change" for checkboxes - // * Fallback from event.target to event.srcElement - addEvents( urlConfigContainer.getElementsByTagName("input"), "click", function( event ) { - var params = {}, - target = event.target || event.srcElement; - params[ target.name ] = target.checked ? - target.defaultValue || true : - undefined; - window.location = QUnit.url( params ); - }); - addEvents( urlConfigContainer.getElementsByTagName("select"), "change", function( event ) { - var params = {}, - target = event.target || event.srcElement; - params[ target.name ] = target.options[ target.selectedIndex ].value || undefined; - window.location = QUnit.url( params ); - }); - toolbar.appendChild( urlConfigContainer ); - - if (numModules > 1) { - moduleFilter = document.createElement( "span" ); - moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); - moduleFilter.innerHTML = moduleFilterHtml; - addEvent( moduleFilter.lastChild, "change", function() { - var selectBox = moduleFilter.getElementsByTagName("select")[0], - selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); - - window.location = QUnit.url({ - module: ( selectedModule === "" ) ? undefined : selectedModule, - // Remove any existing filters - filter: undefined, - testNumber: undefined - }); - }); - toolbar.appendChild(moduleFilter); - } - } - - // `main` initialized at top of scope - main = id( "qunit-fixture" ); - if ( main ) { - config.fixture = main.innerHTML; - } - - if ( config.autostart ) { - QUnit.start(); - } -}; - -if ( defined.document ) { - addEvent( window, "load", QUnit.load ); -} - -// `onErrorFnPrev` initialized at top of scope -// Preserve other handlers -onErrorFnPrev = window.onerror; - -// Cover uncaught exceptions -// Returning true will suppress the default browser handler, -// returning false will let it run. -window.onerror = function ( error, filePath, linerNr ) { - var ret = false; - if ( onErrorFnPrev ) { - ret = onErrorFnPrev( error, filePath, linerNr ); - } - - // Treat return value as window.onerror itself does, - // Only do our handling if not suppressed. - if ( ret !== true ) { - if ( QUnit.config.current ) { - if ( QUnit.config.current.ignoreGlobalErrors ) { - return true; - } - QUnit.pushFailure( error, filePath + ":" + linerNr ); - } else { - QUnit.test( "global failure", extend( function() { - QUnit.pushFailure( error, filePath + ":" + linerNr ); - }, { validTest: validTest } ) ); - } - return false; - } - - return ret; -}; - -function done() { - config.autorun = true; - - // Log the last module results - if ( config.previousModule ) { - runLoggingCallbacks( "moduleDone", QUnit, { - name: config.previousModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - }); - } - delete config.previousModule; - - var i, key, - banner = id( "qunit-banner" ), - tests = id( "qunit-tests" ), - runtime = +new Date() - config.started, - passed = config.stats.all - config.stats.bad, - html = [ - "Tests completed in ", - runtime, - " milliseconds.
        ", - "", - passed, - " assertions of ", - config.stats.all, - " passed, ", - config.stats.bad, - " failed." - ].join( "" ); - - if ( banner ) { - banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); - } - - if ( tests ) { - id( "qunit-testresult" ).innerHTML = html; - } - - if ( config.altertitle && defined.document && document.title ) { - // show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8-charset - document.title = [ - ( config.stats.bad ? "\u2716" : "\u2714" ), - document.title.replace( /^[\u2714\u2716] /i, "" ) - ].join( " " ); - } - - // clear own sessionStorage items if all tests passed - if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { - // `key` & `i` initialized at top of scope - for ( i = 0; i < sessionStorage.length; i++ ) { - key = sessionStorage.key( i++ ); - if ( key.indexOf( "qunit-test-" ) === 0 ) { - sessionStorage.removeItem( key ); - } - } - } - - // scroll back to top to show results - if ( config.scrolltop && window.scrollTo ) { - window.scrollTo(0, 0); - } - - runLoggingCallbacks( "done", QUnit, { - failed: config.stats.bad, - passed: passed, - total: config.stats.all, - runtime: runtime - }); -} - -/** @return Boolean: true if this test should be ran */ -function validTest( test ) { - var include, - filter = config.filter && config.filter.toLowerCase(), - module = config.module && config.module.toLowerCase(), - fullName = ( test.module + ": " + test.testName ).toLowerCase(); - - // Internally-generated tests are always valid - if ( test.callback && test.callback.validTest === validTest ) { - delete test.callback.validTest; - return true; - } - - if ( config.testNumber.length > 0 ) { - if ( inArray( test.testNumber, config.testNumber ) < 0 ) { - return false; - } - } - - if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { - return false; - } - - if ( !filter ) { - return true; - } - - include = filter.charAt( 0 ) !== "!"; - if ( !include ) { - filter = filter.slice( 1 ); - } - - // If the filter matches, we need to honour include - if ( fullName.indexOf( filter ) !== -1 ) { - return include; - } - - // Otherwise, do the opposite - return !include; -} - -// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) -// Later Safari and IE10 are supposed to support error.stack as well -// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack -function extractStacktrace( e, offset ) { - offset = offset === undefined ? 3 : offset; - - var stack, include, i; - - if ( e.stacktrace ) { - // Opera - return e.stacktrace.split( "\n" )[ offset + 3 ]; - } else if ( e.stack ) { - // Firefox, Chrome - stack = e.stack.split( "\n" ); - if (/^error$/i.test( stack[0] ) ) { - stack.shift(); - } - if ( fileName ) { - include = []; - for ( i = offset; i < stack.length; i++ ) { - if ( stack[ i ].indexOf( fileName ) !== -1 ) { - break; - } - include.push( stack[ i ] ); - } - if ( include.length ) { - return include.join( "\n" ); - } - } - return stack[ offset ]; - } else if ( e.sourceURL ) { - // Safari, PhantomJS - // hopefully one day Safari provides actual stacktraces - // exclude useless self-reference for generated Error objects - if ( /qunit.js$/.test( e.sourceURL ) ) { - return; - } - // for actual exceptions, this is useful - return e.sourceURL + ":" + e.line; - } -} -function sourceFromStacktrace( offset ) { - try { - throw new Error(); - } catch ( e ) { - return extractStacktrace( e, offset ); - } -} - -/** - * Escape text for attribute or text content. - */ -function escapeText( s ) { - if ( !s ) { - return ""; - } - s = s + ""; - // Both single quotes and double quotes (for attributes) - return s.replace( /['"<>&]/g, function( s ) { - switch( s ) { - case "'": - return "'"; - case "\"": - return """; - case "<": - return "<"; - case ">": - return ">"; - case "&": - return "&"; - } - }); -} - -function synchronize( callback, last ) { - config.queue.push( callback ); - - if ( config.autorun && !config.blocking ) { - process( last ); - } -} - -function process( last ) { - function next() { - process( last ); - } - var start = new Date().getTime(); - config.depth = config.depth ? config.depth + 1 : 1; - - while ( config.queue.length && !config.blocking ) { - if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { - config.queue.shift()(); - } else { - setTimeout( next, 13 ); - break; - } - } - config.depth--; - if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { - done(); - } -} - -function saveGlobal() { - config.pollution = []; - - if ( config.noglobals ) { - for ( var key in window ) { - if ( hasOwn.call( window, key ) ) { - // in Opera sometimes DOM element ids show up here, ignore them - if ( /^qunit-test-output/.test( key ) ) { - continue; - } - config.pollution.push( key ); - } - } - } -} - -function checkPollution() { - var newGlobals, - deletedGlobals, - old = config.pollution; - - saveGlobal(); - - newGlobals = diff( config.pollution, old ); - if ( newGlobals.length > 0 ) { - QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); - } - - deletedGlobals = diff( old, config.pollution ); - if ( deletedGlobals.length > 0 ) { - QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); - } -} - -// returns a new Array with the elements that are in a but not in b -function diff( a, b ) { - var i, j, - result = a.slice(); - - for ( i = 0; i < result.length; i++ ) { - for ( j = 0; j < b.length; j++ ) { - if ( result[i] === b[j] ) { - result.splice( i, 1 ); - i--; - break; - } - } - } - return result; -} - -function extend( a, b ) { - for ( var prop in b ) { - if ( hasOwn.call( b, prop ) ) { - // Avoid "Member not found" error in IE8 caused by messing with window.constructor - if ( !( prop === "constructor" && a === window ) ) { - if ( b[ prop ] === undefined ) { - delete a[ prop ]; - } else { - a[ prop ] = b[ prop ]; - } - } - } - } - - return a; -} - -/** - * @param {HTMLElement} elem - * @param {string} type - * @param {Function} fn - */ -function addEvent( elem, type, fn ) { - if ( elem.addEventListener ) { - - // Standards-based browsers - elem.addEventListener( type, fn, false ); - } else if ( elem.attachEvent ) { - - // support: IE <9 - elem.attachEvent( "on" + type, fn ); - } else { - - // Caller must ensure support for event listeners is present - throw new Error( "addEvent() was called in a context without event listener support" ); - } -} - -/** - * @param {Array|NodeList} elems - * @param {string} type - * @param {Function} fn - */ -function addEvents( elems, type, fn ) { - var i = elems.length; - while ( i-- ) { - addEvent( elems[i], type, fn ); - } -} - -function hasClass( elem, name ) { - return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; -} - -function addClass( elem, name ) { - if ( !hasClass( elem, name ) ) { - elem.className += (elem.className ? " " : "") + name; - } -} - -function removeClass( elem, name ) { - var set = " " + elem.className + " "; - // Class name may appear multiple times - while ( set.indexOf(" " + name + " ") > -1 ) { - set = set.replace(" " + name + " " , " "); - } - // If possible, trim it for prettiness, but not necessarily - elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, ""); -} - -function id( name ) { - return defined.document && document.getElementById && document.getElementById( name ); -} - -function registerLoggingCallback( key ) { - return function( callback ) { - config[key].push( callback ); - }; -} - -// Supports deprecated method of completely overwriting logging callbacks -function runLoggingCallbacks( key, scope, args ) { - var i, callbacks; - if ( QUnit.hasOwnProperty( key ) ) { - QUnit[ key ].call(scope, args ); - } else { - callbacks = config[ key ]; - for ( i = 0; i < callbacks.length; i++ ) { - callbacks[ i ].call( scope, args ); - } - } -} - -// from jquery.js -function inArray( elem, array ) { - if ( array.indexOf ) { - return array.indexOf( elem ); - } - - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; - } - } - - return -1; -} - -function Test( settings ) { - extend( this, settings ); - this.assertions = []; - this.testNumber = ++Test.count; -} - -Test.count = 0; - -Test.prototype = { - init: function() { - var a, b, li, - tests = id( "qunit-tests" ); - - if ( tests ) { - b = document.createElement( "strong" ); - b.innerHTML = this.nameHtml; - - // `a` initialized at top of scope - a = document.createElement( "a" ); - a.innerHTML = "Rerun"; - a.href = QUnit.url({ testNumber: this.testNumber }); - - li = document.createElement( "li" ); - li.appendChild( b ); - li.appendChild( a ); - li.className = "running"; - li.id = this.id = "qunit-test-output" + testId++; - - tests.appendChild( li ); - } - }, - setup: function() { - if ( - // Emit moduleStart when we're switching from one module to another - this.module !== config.previousModule || - // They could be equal (both undefined) but if the previousModule property doesn't - // yet exist it means this is the first test in a suite that isn't wrapped in a - // module, in which case we'll just emit a moduleStart event for 'undefined'. - // Without this, reporters can get testStart before moduleStart which is a problem. - !hasOwn.call( config, "previousModule" ) - ) { - if ( hasOwn.call( config, "previousModule" ) ) { - runLoggingCallbacks( "moduleDone", QUnit, { - name: config.previousModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - }); - } - config.previousModule = this.module; - config.moduleStats = { all: 0, bad: 0 }; - runLoggingCallbacks( "moduleStart", QUnit, { - name: this.module - }); - } - - config.current = this; - - this.testEnvironment = extend({ - setup: function() {}, - teardown: function() {} - }, this.moduleTestEnvironment ); - - this.started = +new Date(); - runLoggingCallbacks( "testStart", QUnit, { - name: this.testName, - module: this.module - }); - - /*jshint camelcase:false */ - - - /** - * Expose the current test environment. - * - * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead. - */ - QUnit.current_testEnvironment = this.testEnvironment; - - /*jshint camelcase:true */ - - if ( !config.pollution ) { - saveGlobal(); - } - if ( config.notrycatch ) { - this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); - return; - } - try { - this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); - } catch( e ) { - QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); - } - }, - run: function() { - config.current = this; - - var running = id( "qunit-testresult" ); - - if ( running ) { - running.innerHTML = "Running:
        " + this.nameHtml; - } - - if ( this.async ) { - QUnit.stop(); - } - - this.callbackStarted = +new Date(); - - if ( config.notrycatch ) { - this.callback.call( this.testEnvironment, QUnit.assert ); - this.callbackRuntime = +new Date() - this.callbackStarted; - return; - } - - try { - this.callback.call( this.testEnvironment, QUnit.assert ); - this.callbackRuntime = +new Date() - this.callbackStarted; - } catch( e ) { - this.callbackRuntime = +new Date() - this.callbackStarted; - - QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); - // else next test will carry the responsibility - saveGlobal(); - - // Restart the tests if they're blocking - if ( config.blocking ) { - QUnit.start(); - } - } - }, - teardown: function() { - config.current = this; - if ( config.notrycatch ) { - if ( typeof this.callbackRuntime === "undefined" ) { - this.callbackRuntime = +new Date() - this.callbackStarted; - } - this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); - return; - } else { - try { - this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); - } catch( e ) { - QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); - } - } - checkPollution(); - }, - finish: function() { - config.current = this; - if ( config.requireExpects && this.expected === null ) { - QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); - } else if ( this.expected !== null && this.expected !== this.assertions.length ) { - QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); - } else if ( this.expected === null && !this.assertions.length ) { - QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); - } - - var i, assertion, a, b, time, li, ol, - test = this, - good = 0, - bad = 0, - tests = id( "qunit-tests" ); - - this.runtime = +new Date() - this.started; - config.stats.all += this.assertions.length; - config.moduleStats.all += this.assertions.length; - - if ( tests ) { - ol = document.createElement( "ol" ); - ol.className = "qunit-assert-list"; - - for ( i = 0; i < this.assertions.length; i++ ) { - assertion = this.assertions[i]; - - li = document.createElement( "li" ); - li.className = assertion.result ? "pass" : "fail"; - li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); - ol.appendChild( li ); - - if ( assertion.result ) { - good++; - } else { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - - // store result when possible - if ( QUnit.config.reorder && defined.sessionStorage ) { - if ( bad ) { - sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); - } else { - sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); - } - } - - if ( bad === 0 ) { - addClass( ol, "qunit-collapsed" ); - } - - // `b` initialized at top of scope - b = document.createElement( "strong" ); - b.innerHTML = this.nameHtml + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; - - addEvent(b, "click", function() { - var next = b.parentNode.lastChild, - collapsed = hasClass( next, "qunit-collapsed" ); - ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); - }); - - addEvent(b, "dblclick", function( e ) { - var target = e && e.target ? e.target : window.event.srcElement; - if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { - target = target.parentNode; - } - if ( window.location && target.nodeName.toLowerCase() === "strong" ) { - window.location = QUnit.url({ testNumber: test.testNumber }); - } - }); - - // `time` initialized at top of scope - time = document.createElement( "span" ); - time.className = "runtime"; - time.innerHTML = this.runtime + " ms"; - - // `li` initialized at top of scope - li = id( this.id ); - li.className = bad ? "fail" : "pass"; - li.removeChild( li.firstChild ); - a = li.firstChild; - li.appendChild( b ); - li.appendChild( a ); - li.appendChild( time ); - li.appendChild( ol ); - - } else { - for ( i = 0; i < this.assertions.length; i++ ) { - if ( !this.assertions[i].result ) { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - } - - runLoggingCallbacks( "testDone", QUnit, { - name: this.testName, - module: this.module, - failed: bad, - passed: this.assertions.length - bad, - total: this.assertions.length, - runtime: this.runtime, - // DEPRECATED: this property will be removed in 2.0.0, use runtime instead - duration: this.runtime - }); - - QUnit.reset(); - - config.current = undefined; - }, - - queue: function() { - var bad, - test = this; - - synchronize(function() { - test.init(); - }); - function run() { - // each of these can by async - synchronize(function() { - test.setup(); - }); - synchronize(function() { - test.run(); - }); - synchronize(function() { - test.teardown(); - }); - synchronize(function() { - test.finish(); - }); - } - - // `bad` initialized at top of scope - // defer when previous test run passed, if storage is available - bad = QUnit.config.reorder && defined.sessionStorage && - +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); - - if ( bad ) { - run(); - } else { - synchronize( run, true ); - } - } -}; - -// `assert` initialized at top of scope -// Assert helpers -// All of these must either call QUnit.push() or manually do: -// - runLoggingCallbacks( "log", .. ); -// - config.current.assertions.push({ .. }); -assert = QUnit.assert = { - /** - * Asserts rough true-ish result. - * @name ok - * @function - * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); - */ - ok: function( result, msg ) { - if ( !config.current ) { - throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); - } - result = !!result; - msg = msg || ( result ? "okay" : "failed" ); - - var source, - details = { - module: config.current.module, - name: config.current.testName, - result: result, - message: msg - }; - - msg = "" + escapeText( msg ) + ""; - - if ( !result ) { - source = sourceFromStacktrace( 2 ); - if ( source ) { - details.source = source; - msg += "
        Source:
        " +
        -					escapeText( source ) +
        -					"
        "; - } - } - runLoggingCallbacks( "log", QUnit, details ); - config.current.assertions.push({ - result: result, - message: msg - }); - }, - - /** - * Assert that the first two arguments are equal, with an optional message. - * Prints out both actual and expected values. - * @name equal - * @function - * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); - */ - equal: function( actual, expected, message ) { - /*jshint eqeqeq:false */ - QUnit.push( expected == actual, actual, expected, message ); - }, - - /** - * @name notEqual - * @function - */ - notEqual: function( actual, expected, message ) { - /*jshint eqeqeq:false */ - QUnit.push( expected != actual, actual, expected, message ); - }, - - /** - * @name propEqual - * @function - */ - propEqual: function( actual, expected, message ) { - actual = objectValues(actual); - expected = objectValues(expected); - QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name notPropEqual - * @function - */ - notPropEqual: function( actual, expected, message ) { - actual = objectValues(actual); - expected = objectValues(expected); - QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name deepEqual - * @function - */ - deepEqual: function( actual, expected, message ) { - QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name notDeepEqual - * @function - */ - notDeepEqual: function( actual, expected, message ) { - QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name strictEqual - * @function - */ - strictEqual: function( actual, expected, message ) { - QUnit.push( expected === actual, actual, expected, message ); - }, - - /** - * @name notStrictEqual - * @function - */ - notStrictEqual: function( actual, expected, message ) { - QUnit.push( expected !== actual, actual, expected, message ); - }, - - "throws": function( block, expected, message ) { - var actual, - expectedOutput = expected, - ok = false; - - // 'expected' is optional - if ( !message && typeof expected === "string" ) { - message = expected; - expected = null; - } - - config.current.ignoreGlobalErrors = true; - try { - block.call( config.current.testEnvironment ); - } catch (e) { - actual = e; - } - config.current.ignoreGlobalErrors = false; - - if ( actual ) { - - // we don't want to validate thrown error - if ( !expected ) { - ok = true; - expectedOutput = null; - - // expected is an Error object - } else if ( expected instanceof Error ) { - ok = actual instanceof Error && - actual.name === expected.name && - actual.message === expected.message; - - // expected is a regexp - } else if ( QUnit.objectType( expected ) === "regexp" ) { - ok = expected.test( errorString( actual ) ); - - // expected is a string - } else if ( QUnit.objectType( expected ) === "string" ) { - ok = expected === errorString( actual ); - - // expected is a constructor - } else if ( actual instanceof expected ) { - ok = true; - - // expected is a validation function which returns true is validation passed - } else if ( expected.call( {}, actual ) === true ) { - expectedOutput = null; - ok = true; - } - - QUnit.push( ok, actual, expectedOutput, message ); - } else { - QUnit.pushFailure( message, null, "No exception was thrown." ); - } - } -}; - -/** - * @deprecated since 1.8.0 - * Kept assertion helpers in root for backwards compatibility. - */ -extend( QUnit.constructor.prototype, assert ); - -/** - * @deprecated since 1.9.0 - * Kept to avoid TypeErrors for undefined methods. - */ -QUnit.constructor.prototype.raises = function() { - QUnit.push( false, false, false, "QUnit.raises has been deprecated since 2012 (fad3c1ea), use QUnit.throws instead" ); -}; - -/** - * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 - * Kept to avoid TypeErrors for undefined methods. - */ -QUnit.constructor.prototype.equals = function() { - QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); -}; -QUnit.constructor.prototype.same = function() { - QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); -}; - -// Test for equality any JavaScript type. -// Author: Philippe Rathé -QUnit.equiv = (function() { - - // Call the o related callback with the given arguments. - function bindCallbacks( o, callbacks, args ) { - var prop = QUnit.objectType( o ); - if ( prop ) { - if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { - return callbacks[ prop ].apply( callbacks, args ); - } else { - return callbacks[ prop ]; // or undefined - } - } - } - - // the real equiv function - var innerEquiv, - // stack to decide between skip/abort functions - callers = [], - // stack to avoiding loops from circular referencing - parents = [], - parentsB = [], - - getProto = Object.getPrototypeOf || function ( obj ) { - /*jshint camelcase:false */ - return obj.__proto__; - }, - callbacks = (function () { - - // for string, boolean, number and null - function useStrictEquality( b, a ) { - /*jshint eqeqeq:false */ - if ( b instanceof a.constructor || a instanceof b.constructor ) { - // to catch short annotation VS 'new' annotation of a - // declaration - // e.g. var i = 1; - // var j = new Number(1); - return a == b; - } else { - return a === b; - } - } - - return { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, - - "nan": function( b ) { - return isNaN( b ); - }, - - "date": function( b, a ) { - return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); - }, - - "regexp": function( b, a ) { - return QUnit.objectType( b ) === "regexp" && - // the regex itself - a.source === b.source && - // and its modifiers - a.global === b.global && - // (gmi) ... - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline && - a.sticky === b.sticky; - }, - - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function": function() { - var caller = callers[callers.length - 1]; - return caller !== Object && typeof caller !== "undefined"; - }, - - "array": function( b, a ) { - var i, j, len, loop, aCircular, bCircular; - - // b could be an object literal here - if ( QUnit.objectType( b ) !== "array" ) { - return false; - } - - len = a.length; - if ( len !== b.length ) { - // safe and faster - return false; - } - - // track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - for ( i = 0; i < len; i++ ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[j] === a[i]; - bCircular = parentsB[j] === b[i]; - if ( aCircular || bCircular ) { - if ( a[i] === b[i] || aCircular && bCircular ) { - loop = true; - } else { - parents.pop(); - parentsB.pop(); - return false; - } - } - } - if ( !loop && !innerEquiv(a[i], b[i]) ) { - parents.pop(); - parentsB.pop(); - return false; - } - } - parents.pop(); - parentsB.pop(); - return true; - }, - - "object": function( b, a ) { - /*jshint forin:false */ - var i, j, loop, aCircular, bCircular, - // Default to true - eq = true, - aProperties = [], - bProperties = []; - - // comparing constructors is more strict than using - // instanceof - if ( a.constructor !== b.constructor ) { - // Allow objects with no prototype to be equivalent to - // objects with Object as their constructor. - if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || - ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { - return false; - } - } - - // stack constructor before traversing properties - callers.push( a.constructor ); - - // track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - - // be strict: don't ensure hasOwnProperty and go deep - for ( i in a ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[j] === a[i]; - bCircular = parentsB[j] === b[i]; - if ( aCircular || bCircular ) { - if ( a[i] === b[i] || aCircular && bCircular ) { - loop = true; - } else { - eq = false; - break; - } - } - } - aProperties.push(i); - if ( !loop && !innerEquiv(a[i], b[i]) ) { - eq = false; - break; - } - } - - parents.pop(); - parentsB.pop(); - callers.pop(); // unstack, we are done - - for ( i in b ) { - bProperties.push( i ); // collect b's properties - } - - // Ensures identical properties name - return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); - } - }; - }()); - - innerEquiv = function() { // can take multiple arguments - var args = [].slice.apply( arguments ); - if ( args.length < 2 ) { - return true; // end transition - } - - return (function( a, b ) { - if ( a === b ) { - return true; // catch the most you can - } else if ( a === null || b === null || typeof a === "undefined" || - typeof b === "undefined" || - QUnit.objectType(a) !== QUnit.objectType(b) ) { - return false; // don't lose time with error prone cases - } else { - return bindCallbacks(a, callbacks, [ b, a ]); - } - - // apply transition with (1..n) arguments - }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) ); - }; - - return innerEquiv; -}()); - -/** - * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | - * http://flesler.blogspot.com Licensed under BSD - * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 - * - * @projectDescription Advanced and extensible data dumping for Javascript. - * @version 1.0.0 - * @author Ariel Flesler - * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} - */ -QUnit.jsDump = (function() { - function quote( str ) { - return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; - } - function literal( o ) { - return o + ""; - } - function join( pre, arr, post ) { - var s = jsDump.separator(), - base = jsDump.indent(), - inner = jsDump.indent(1); - if ( arr.join ) { - arr = arr.join( "," + s + inner ); - } - if ( !arr ) { - return pre + post; - } - return [ pre, inner + arr, base + post ].join(s); - } - function array( arr, stack ) { - var i = arr.length, ret = new Array(i); - this.up(); - while ( i-- ) { - ret[i] = this.parse( arr[i] , undefined , stack); - } - this.down(); - return join( "[", ret, "]" ); - } - - var reName = /^function (\w+)/, - jsDump = { - // type is used mostly internally, you can fix a (custom)type in advance - parse: function( obj, type, stack ) { - stack = stack || [ ]; - var inStack, res, - parser = this.parsers[ type || this.typeOf(obj) ]; - - type = typeof parser; - inStack = inArray( obj, stack ); - - if ( inStack !== -1 ) { - return "recursion(" + (inStack - stack.length) + ")"; - } - if ( type === "function" ) { - stack.push( obj ); - res = parser.call( this, obj, stack ); - stack.pop(); - return res; - } - return ( type === "string" ) ? parser : this.parsers.error; - }, - typeOf: function( obj ) { - var type; - if ( obj === null ) { - type = "null"; - } else if ( typeof obj === "undefined" ) { - type = "undefined"; - } else if ( QUnit.is( "regexp", obj) ) { - type = "regexp"; - } else if ( QUnit.is( "date", obj) ) { - type = "date"; - } else if ( QUnit.is( "function", obj) ) { - type = "function"; - } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { - type = "window"; - } else if ( obj.nodeType === 9 ) { - type = "document"; - } else if ( obj.nodeType ) { - type = "node"; - } else if ( - // native arrays - toString.call( obj ) === "[object Array]" || - // NodeList objects - ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) - ) { - type = "array"; - } else if ( obj.constructor === Error.prototype.constructor ) { - type = "error"; - } else { - type = typeof obj; - } - return type; - }, - separator: function() { - return this.multiline ? this.HTML ? "
        " : "\n" : this.HTML ? " " : " "; - }, - // extra can be a number, shortcut for increasing-calling-decreasing - indent: function( extra ) { - if ( !this.multiline ) { - return ""; - } - var chr = this.indentChar; - if ( this.HTML ) { - chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); - } - return new Array( this.depth + ( extra || 0 ) ).join(chr); - }, - up: function( a ) { - this.depth += a || 1; - }, - down: function( a ) { - this.depth -= a || 1; - }, - setParser: function( name, parser ) { - this.parsers[name] = parser; - }, - // The next 3 are exposed so you can use them - quote: quote, - literal: literal, - join: join, - // - depth: 1, - // This is the list of parsers, to modify them, use jsDump.setParser - parsers: { - window: "[Window]", - document: "[Document]", - error: function(error) { - return "Error(\"" + error.message + "\")"; - }, - unknown: "[Unknown]", - "null": "null", - "undefined": "undefined", - "function": function( fn ) { - var ret = "function", - // functions never have name in IE - name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; - - if ( name ) { - ret += " " + name; - } - ret += "( "; - - ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); - return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); - }, - array: array, - nodelist: array, - "arguments": array, - object: function( map, stack ) { - /*jshint forin:false */ - var ret = [ ], keys, key, val, i; - QUnit.jsDump.up(); - keys = []; - for ( key in map ) { - keys.push( key ); - } - keys.sort(); - for ( i = 0; i < keys.length; i++ ) { - key = keys[ i ]; - val = map[ key ]; - ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); - } - QUnit.jsDump.down(); - return join( "{", ret, "}" ); - }, - node: function( node ) { - var len, i, val, - open = QUnit.jsDump.HTML ? "<" : "<", - close = QUnit.jsDump.HTML ? ">" : ">", - tag = node.nodeName.toLowerCase(), - ret = open + tag, - attrs = node.attributes; - - if ( attrs ) { - for ( i = 0, len = attrs.length; i < len; i++ ) { - val = attrs[i].nodeValue; - // IE6 includes all attributes in .attributes, even ones not explicitly set. - // Those have values like undefined, null, 0, false, "" or "inherit". - if ( val && val !== "inherit" ) { - ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); - } - } - } - ret += close; - - // Show content of TextNode or CDATASection - if ( node.nodeType === 3 || node.nodeType === 4 ) { - ret += node.nodeValue; - } - - return ret + open + "/" + tag + close; - }, - // function calls it internally, it's the arguments part of the function - functionArgs: function( fn ) { - var args, - l = fn.length; - - if ( !l ) { - return ""; - } - - args = new Array(l); - while ( l-- ) { - // 97 is 'a' - args[l] = String.fromCharCode(97+l); - } - return " " + args.join( ", " ) + " "; - }, - // object calls it internally, the key part of an item in a map - key: quote, - // function calls it internally, it's the content of the function - functionCode: "[code]", - // node calls it internally, it's an html attribute value - attribute: quote, - string: quote, - date: quote, - regexp: literal, - number: literal, - "boolean": literal - }, - // if true, entities are escaped ( <, >, \t, space and \n ) - HTML: false, - // indentation unit - indentChar: " ", - // if true, items in a collection, are separated by a \n, else just a space. - multiline: true - }; - - return jsDump; -}()); - -/* - * Javascript Diff Algorithm - * By John Resig (http://ejohn.org/) - * Modified by Chu Alan "sprite" - * - * Released under the MIT license. - * - * More Info: - * http://ejohn.org/projects/javascript-diff-algorithm/ - * - * Usage: QUnit.diff(expected, actual) - * - * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" - */ -QUnit.diff = (function() { - /*jshint eqeqeq:false, eqnull:true */ - function diff( o, n ) { - var i, - ns = {}, - os = {}; - - for ( i = 0; i < n.length; i++ ) { - if ( !hasOwn.call( ns, n[i] ) ) { - ns[ n[i] ] = { - rows: [], - o: null - }; - } - ns[ n[i] ].rows.push( i ); - } - - for ( i = 0; i < o.length; i++ ) { - if ( !hasOwn.call( os, o[i] ) ) { - os[ o[i] ] = { - rows: [], - n: null - }; - } - os[ o[i] ].rows.push( i ); - } - - for ( i in ns ) { - if ( hasOwn.call( ns, i ) ) { - if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { - n[ ns[i].rows[0] ] = { - text: n[ ns[i].rows[0] ], - row: os[i].rows[0] - }; - o[ os[i].rows[0] ] = { - text: o[ os[i].rows[0] ], - row: ns[i].rows[0] - }; - } - } - } - - for ( i = 0; i < n.length - 1; i++ ) { - if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && - n[ i + 1 ] == o[ n[i].row + 1 ] ) { - - n[ i + 1 ] = { - text: n[ i + 1 ], - row: n[i].row + 1 - }; - o[ n[i].row + 1 ] = { - text: o[ n[i].row + 1 ], - row: i + 1 - }; - } - } - - for ( i = n.length - 1; i > 0; i-- ) { - if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && - n[ i - 1 ] == o[ n[i].row - 1 ]) { - - n[ i - 1 ] = { - text: n[ i - 1 ], - row: n[i].row - 1 - }; - o[ n[i].row - 1 ] = { - text: o[ n[i].row - 1 ], - row: i - 1 - }; - } - } - - return { - o: o, - n: n - }; - } - - return function( o, n ) { - o = o.replace( /\s+$/, "" ); - n = n.replace( /\s+$/, "" ); - - var i, pre, - str = "", - out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), - oSpace = o.match(/\s+/g), - nSpace = n.match(/\s+/g); - - if ( oSpace == null ) { - oSpace = [ " " ]; - } - else { - oSpace.push( " " ); - } - - if ( nSpace == null ) { - nSpace = [ " " ]; - } - else { - nSpace.push( " " ); - } - - if ( out.n.length === 0 ) { - for ( i = 0; i < out.o.length; i++ ) { - str += "" + out.o[i] + oSpace[i] + ""; - } - } - else { - if ( out.n[0].text == null ) { - for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { - str += "" + out.o[n] + oSpace[n] + ""; - } - } - - for ( i = 0; i < out.n.length; i++ ) { - if (out.n[i].text == null) { - str += "" + out.n[i] + nSpace[i] + ""; - } - else { - // `pre` initialized at top of scope - pre = ""; - - for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { - pre += "" + out.o[n] + oSpace[n] + ""; - } - str += " " + out.n[i].text + nSpace[i] + pre; - } - } - } - - return str; - }; -}()); - -// For browser, export only select globals -if ( typeof window !== "undefined" ) { - extend( window, QUnit.constructor.prototype ); - window.QUnit = QUnit; -} - -// For CommonJS environments, export everything -if ( typeof module !== "undefined" && module.exports ) { - module.exports = QUnit; -} - - -// Get a reference to the global object, like window in browsers -}( (function() { - return this; -})() )); diff --git a/test/requirejs/index.html b/test/requirejs/index.html deleted file mode 100755 index 09908f1..0000000 --- a/test/requirejs/index.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - Finite State Machine Tests - USING REQUIREJS INCLUDE MECHANISM - - - - - - -

        QUnit Test Suite

        -

        -
        -

        -
          -
          test markup
          - - diff --git a/test/requirejs/require.js b/test/requirejs/require.js deleted file mode 100755 index ed535a8..0000000 --- a/test/requirejs/require.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - RequireJS 1.0.6 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. - Available via the MIT or new BSD license. - see: http://github.com/jrburke/requirejs for details -*/ -var requirejs,require,define; -(function(){function J(a){return N.call(a)==="[object Function]"}function F(a){return N.call(a)==="[object Array]"}function Z(a,c,l){for(var j in c)if(!(j in K)&&(!(j in a)||l))a[j]=c[j];return d}function O(a,c,d){a=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+a);if(d)a.originalError=d;return a}function $(a,c,d){var j,k,s;for(j=0;s=c[j];j++){s=typeof s==="string"?{name:s}:s;k=s.location;if(d&&(!k||k.indexOf("/")!==0&&k.indexOf(":")===-1))k=d+"/"+(k||s.name);a[s.name]={name:s.name,location:k|| -s.name,main:(s.main||"main").replace(ea,"").replace(aa,"")}}}function U(a,c){a.holdReady?a.holdReady(c):c?a.readyWait+=1:a.ready(!0)}function fa(a){function c(b,f){var g,m;if(b&&b.charAt(0)===".")if(f){q.pkgs[f]?f=[f]:(f=f.split("/"),f=f.slice(0,f.length-1));g=b=f.concat(b.split("/"));var a;for(m=0;a=g[m];m++)if(a===".")g.splice(m,1),m-=1;else if(a==="..")if(m===1&&(g[2]===".."||g[0]===".."))break;else m>0&&(g.splice(m-1,2),m-=2);m=q.pkgs[g=b[0]];b=b.join("/");m&&b===g+"/"+m.main&&(b=g)}else b.indexOf("./")=== -0&&(b=b.substring(2));return b}function l(b,f){var g=b?b.indexOf("!"):-1,m=null,a=f?f.name:null,h=b,e,d;g!==-1&&(m=b.substring(0,g),b=b.substring(g+1,b.length));m&&(m=c(m,a));b&&(m?e=(g=n[m])&&g.normalize?g.normalize(b,function(b){return c(b,a)}):c(b,a):(e=c(b,a),d=F[e],d||(d=i.nameToUrl(b,null,f),F[e]=d)));return{prefix:m,name:e,parentMap:f,url:d,originalName:h,fullName:m?m+"!"+(e||""):e}}function j(){var b=!0,f=q.priorityWait,g,a;if(f){for(a=0;g=f[a];a++)if(!r[g]){b=!1;break}b&&delete q.priorityWait}return b} -function k(b,f,g){return function(){var a=ga.call(arguments,0),c;if(g&&J(c=a[a.length-1]))c.__requireJsBuild=!0;a.push(f);return b.apply(null,a)}}function s(b,f,g){f=k(g||i.require,b,f);Z(f,{nameToUrl:k(i.nameToUrl,b),toUrl:k(i.toUrl,b),defined:k(i.requireDefined,b),specified:k(i.requireSpecified,b),isBrowser:d.isBrowser});return f}function p(b){var f,g,a,c=b.callback,h=b.map,e=h.fullName,ba=b.deps;a=b.listeners;if(c&&J(c)){if(q.catchError.define)try{g=d.execCb(e,b.callback,ba,n[e])}catch(j){f=j}else g= -d.execCb(e,b.callback,ba,n[e]);if(e)(c=b.cjsModule)&&c.exports!==void 0&&c.exports!==n[e]?g=n[e]=b.cjsModule.exports:g===void 0&&b.usingExports?g=n[e]:(n[e]=g,G[e]&&(S[e]=!0))}else e&&(g=n[e]=c,G[e]&&(S[e]=!0));if(w[b.id])delete w[b.id],b.isDone=!0,i.waitCount-=1,i.waitCount===0&&(I=[]);delete L[e];if(d.onResourceLoad&&!b.placeholder)d.onResourceLoad(i,h,b.depArray);if(f)return g=(e?l(e).url:"")||f.fileName||f.sourceURL,a=f.moduleTree,f=O("defineerror",'Error evaluating module "'+e+'" at location "'+ -g+'":\n'+f+"\nfileName:"+g+"\nlineNumber: "+(f.lineNumber||f.line),f),f.moduleName=e,f.moduleTree=a,d.onError(f);for(f=0;c=a[f];f++)c(g)}function t(b,f){return function(g){b.depDone[f]||(b.depDone[f]=!0,b.deps[f]=g,b.depCount-=1,b.depCount||p(b))}}function o(b,f){var g=f.map,a=g.fullName,c=g.name,h=M[b]||(M[b]=n[b]),e;if(!f.loading)f.loading=!0,e=function(b){f.callback=function(){return b};p(f);r[f.id]=!0;z()},e.fromText=function(b,f){var g=P;r[b]=!1;i.scriptCount+=1;i.fake[b]=!0;g&&(P=!1);d.exec(f); -g&&(P=!0);i.completeLoad(b)},a in n?e(n[a]):h.load(c,s(g.parentMap,!0,function(b,a){var c=[],e,m;for(e=0;m=b[e];e++)m=l(m,g.parentMap),b[e]=m.fullName,m.prefix||c.push(b[e]);f.moduleDeps=(f.moduleDeps||[]).concat(c);return i.require(b,a)}),e,q)}function x(b){w[b.id]||(w[b.id]=b,I.push(b),i.waitCount+=1)}function C(b){this.listeners.push(b)}function u(b,f){var g=b.fullName,a=b.prefix,c=a?M[a]||(M[a]=n[a]):null,h,e;g&&(h=L[g]);if(!h&&(e=!0,h={id:(a&&!c?N++ +"__p@:":"")+(g||"__r@"+N++),map:b,depCount:0, -depDone:[],depCallbacks:[],deps:[],listeners:[],add:C},A[h.id]=!0,g&&(!a||M[a])))L[g]=h;a&&!c?(g=l(a),a in n&&!n[a]&&(delete n[a],delete Q[g.url]),a=u(g,!0),a.add(function(){var f=l(b.originalName,b.parentMap),f=u(f,!0);h.placeholder=!0;f.add(function(b){h.callback=function(){return b};p(h)})})):e&&f&&(r[h.id]=!1,i.paused.push(h),x(h));return h}function B(b,f,a,c){var b=l(b,c),d=b.name,h=b.fullName,e=u(b),j=e.id,k=e.deps,o;if(h){if(h in n||r[j]===!0||h==="jquery"&&q.jQuery&&q.jQuery!==a().fn.jquery)return; -A[j]=!0;r[j]=!0;h==="jquery"&&a&&V(a())}e.depArray=f;e.callback=a;for(a=0;a0)){if(q.priorityWait)if(j())z();else return;for(h in r)if(!(h in K)&&(c=!0,!r[h]))if(b)a+=h+" ";else if(l=!0,h.indexOf("!")===-1){k=[];break}else(e=L[h]&&L[h].moduleDeps)&&k.push.apply(k,e);if(c||i.waitCount){if(b&&a)return b=O("timeout","Load timeout for modules: "+a),b.requireType="timeout",b.requireModules=a,b.contextName=i.contextName,d.onError(b);if(l&&k.length)for(a= -0;h=w[k[a]];a++)if(h=E(h,{})){y(h,{});break}if(!b&&(l||i.scriptCount)){if((H||ca)&&!W)W=setTimeout(function(){W=0;D()},50)}else{if(i.waitCount){for(a=0;h=I[a];a++)y(h,{});i.paused.length&&z();X<5&&(X+=1,D())}X=0;d.checkReadyState()}}}}var i,z,q={waitSeconds:7,baseUrl:"./",paths:{},pkgs:{},catchError:{}},R=[],A={require:!0,exports:!0,module:!0},F={},n={},r={},w={},I=[],Q={},N=0,L={},M={},G={},S={},Y=0;V=function(b){if(!i.jQuery&&(b=b||(typeof jQuery!=="undefined"?jQuery:null))&&!(q.jQuery&&b.fn.jquery!== -q.jQuery)&&("holdReady"in b||"readyWait"in b))if(i.jQuery=b,v(["jquery",[],function(){return jQuery}]),i.scriptCount)U(b,!0),i.jQueryIncremented=!0};z=function(){var b,a,c,l,k,h;i.takeGlobalQueue();Y+=1;if(i.scriptCount<=0)i.scriptCount=0;for(;R.length;)if(b=R.shift(),b[0]===null)return d.onError(O("mismatch","Mismatched anonymous define() module: "+b[b.length-1]));else v(b);if(!q.priorityWait||j())for(;i.paused.length;){k=i.paused;i.pausedCount+=k.length;i.paused=[];for(l=0;b=k[l];l++)a=b.map,c= -a.url,h=a.fullName,a.prefix?o(a.prefix,b):!Q[c]&&!r[h]&&(d.load(i,h,c),c.indexOf("empty:")!==0&&(Q[c]=!0));i.startTime=(new Date).getTime();i.pausedCount-=k.length}Y===1&&D();Y-=1};i={contextName:a,config:q,defQueue:R,waiting:w,waitCount:0,specified:A,loaded:r,urlMap:F,urlFetched:Q,scriptCount:0,defined:n,paused:[],pausedCount:0,plugins:M,needFullExec:G,fake:{},fullExec:S,managerCallbacks:L,makeModuleMap:l,normalize:c,configure:function(b){var a,c,d;b.baseUrl&&b.baseUrl.charAt(b.baseUrl.length-1)!== -"/"&&(b.baseUrl+="/");a=q.paths;d=q.pkgs;Z(q,b,!0);if(b.paths){for(c in b.paths)c in K||(a[c]=b.paths[c]);q.paths=a}if((a=b.packagePaths)||b.packages){if(a)for(c in a)c in K||$(d,a[c],c);b.packages&&$(d,b.packages);q.pkgs=d}if(b.priority)c=i.requireWait,i.requireWait=!1,z(),i.require(b.priority),z(),i.requireWait=c,q.priorityWait=b.priority;if(b.deps||b.callback)i.require(b.deps||[],b.callback)},requireDefined:function(b,a){return l(b,a).fullName in n},requireSpecified:function(b,a){return l(b,a).fullName in -A},require:function(b,c,g){if(typeof b==="string"){if(J(c))return d.onError(O("requireargs","Invalid require call"));if(d.get)return d.get(i,b,c);c=l(b,c);b=c.fullName;return!(b in n)?d.onError(O("notloaded","Module name '"+c.fullName+"' has not been loaded yet for context: "+a)):n[b]}(b&&b.length||c)&&B(null,b,c,g);if(!i.requireWait)for(;!i.scriptCount&&i.paused.length;)z();return i.require},takeGlobalQueue:function(){T.length&&(ia.apply(i.defQueue,[i.defQueue.length-1,0].concat(T)),T=[])},completeLoad:function(b){var a; -for(i.takeGlobalQueue();R.length;)if(a=R.shift(),a[0]===null){a[0]=b;break}else if(a[0]===b)break;else v(a),a=null;a?v(a):v([b,[],b==="jquery"&&typeof jQuery!=="undefined"?function(){return jQuery}:null]);d.isAsync&&(i.scriptCount-=1);z();d.isAsync||(i.scriptCount-=1)},toUrl:function(b,a){var c=b.lastIndexOf("."),d=null;c!==-1&&(d=b.substring(c,b.length),b=b.substring(0,c));return i.nameToUrl(b,d,a)},nameToUrl:function(b,a,g){var l,k,h,e,j=i.config,b=c(b,g&&g.fullName);if(d.jsExtRegExp.test(b))a= -b+(a?a:"");else{l=j.paths;k=j.pkgs;g=b.split("/");for(e=g.length;e>0;e--)if(h=g.slice(0,e).join("/"),l[h]){g.splice(0,e,l[h]);break}else if(h=k[h]){b=b===h.name?h.location+"/"+h.main:h.location;g.splice(0,e,b);break}a=g.join("/")+(a||".js");a=(a.charAt(0)==="/"||a.match(/^\w+:/)?"":j.baseUrl)+a}return j.urlArgs?a+((a.indexOf("?")===-1?"?":"&")+j.urlArgs):a}};i.jQueryCheck=V;i.resume=z;return i}function ja(){var a,c,d;if(B&&B.readyState==="interactive")return B;a=document.getElementsByTagName("script"); -for(c=a.length-1;c>-1&&(d=a[c]);c--)if(d.readyState==="interactive")return B=d;return null}var ka=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,la=/require\(\s*["']([^'"\s]+)["']\s*\)/g,ea=/^\.\//,aa=/\.js$/,N=Object.prototype.toString,t=Array.prototype,ga=t.slice,ia=t.splice,H=!!(typeof window!=="undefined"&&navigator&&document),ca=!H&&typeof importScripts!=="undefined",ma=H&&navigator.platform==="PLAYSTATION 3"?/^complete$/:/^(complete|loaded)$/,da=typeof opera!=="undefined"&&opera.toString()==="[object Opera]", -K={},C={},T=[],B=null,X=0,P=!1,ha={require:!0,module:!0,exports:!0},d,t={},I,x,u,D,o,v,E,A,y,V,W;if(typeof define==="undefined"){if(typeof requirejs!=="undefined")if(J(requirejs))return;else t=requirejs,requirejs=void 0;typeof require!=="undefined"&&!J(require)&&(t=require,require=void 0);d=requirejs=function(a,c,d){var j="_",k;!F(a)&&typeof a!=="string"&&(k=a,F(c)?(a=c,c=d):a=[]);if(k&&k.context)j=k.context;d=C[j]||(C[j]=fa(j));k&&d.configure(k);return d.require(a,c)};d.config=function(a){return d(a)}; -require||(require=d);d.toUrl=function(a){return C._.toUrl(a)};d.version="1.0.6";d.jsExtRegExp=/^\/|:|\?|\.js$/;x=d.s={contexts:C,skipAsync:{}};if(d.isAsync=d.isBrowser=H)if(u=x.head=document.getElementsByTagName("head")[0],D=document.getElementsByTagName("base")[0])u=x.head=D.parentNode;d.onError=function(a){throw a;};d.load=function(a,c,l){d.resourcesReady(!1);a.scriptCount+=1;d.attach(l,a,c);if(a.jQuery&&!a.jQueryIncremented)U(a.jQuery,!0),a.jQueryIncremented=!0};define=function(a,c,d){var j,k; -typeof a!=="string"&&(d=c,c=a,a=null);F(c)||(d=c,c=[]);!c.length&&J(d)&&d.length&&(d.toString().replace(ka,"").replace(la,function(a,d){c.push(d)}),c=(d.length===1?["require"]:["require","exports","module"]).concat(c));if(P&&(j=I||ja()))a||(a=j.getAttribute("data-requiremodule")),k=C[j.getAttribute("data-requirecontext")];(k?k.defQueue:T).push([a,c,d])};define.amd={multiversion:!0,plugins:!0,jQuery:!0};d.exec=function(a){return eval(a)};d.execCb=function(a,c,d,j){return c.apply(j,d)};d.addScriptToDom= -function(a){I=a;D?u.insertBefore(a,D):u.appendChild(a);I=null};d.onScriptLoad=function(a){var c=a.currentTarget||a.srcElement,l;if(a.type==="load"||c&&ma.test(c.readyState))B=null,a=c.getAttribute("data-requirecontext"),l=c.getAttribute("data-requiremodule"),C[a].completeLoad(l),c.detachEvent&&!da?c.detachEvent("onreadystatechange",d.onScriptLoad):c.removeEventListener("load",d.onScriptLoad,!1)};d.attach=function(a,c,l,j,k,o){var p;if(H)return j=j||d.onScriptLoad,p=c&&c.config&&c.config.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml", -"html:script"):document.createElement("script"),p.type=k||c&&c.config.scriptType||"text/javascript",p.charset="utf-8",p.async=!x.skipAsync[a],c&&p.setAttribute("data-requirecontext",c.contextName),p.setAttribute("data-requiremodule",l),p.attachEvent&&!da?(P=!0,o?p.onreadystatechange=function(){if(p.readyState==="loaded")p.onreadystatechange=null,p.attachEvent("onreadystatechange",j),o(p)}:p.attachEvent("onreadystatechange",j)):p.addEventListener("load",j,!1),p.src=a,o||d.addScriptToDom(p),p;else ca&& -(importScripts(a),c.completeLoad(l));return null};if(H){o=document.getElementsByTagName("script");for(A=o.length-1;A>-1&&(v=o[A]);A--){if(!u)u=v.parentNode;if(E=v.getAttribute("data-main")){if(!t.baseUrl)o=E.split("/"),v=o.pop(),o=o.length?o.join("/")+"/":"./",t.baseUrl=o,E=v.replace(aa,"");t.deps=t.deps?t.deps.concat(E):[E];break}}}d.checkReadyState=function(){var a=x.contexts,c;for(c in a)if(!(c in K)&&a[c].waitCount)return;d.resourcesReady(!0)};d.resourcesReady=function(a){var c,l;d.resourcesDone= -a;if(d.resourcesDone)for(l in a=x.contexts,a)if(!(l in K)&&(c=a[l],c.jQueryIncremented))U(c.jQuery,!1),c.jQueryIncremented=!1};d.pageLoaded=function(){if(document.readyState!=="complete")document.readyState="complete"};if(H&&document.addEventListener&&!document.readyState)document.readyState="loading",window.addEventListener("load",d.pageLoaded,!1);d(t);if(d.isAsync&&typeof setTimeout!=="undefined")y=x.contexts[t.context||"_"],y.requireWait=!0,setTimeout(function(){y.requireWait=!1;y.scriptCount|| -y.resume();d.checkReadyState()},0)}})(); diff --git a/test/runner.js b/test/runner.js deleted file mode 100644 index 464fa3b..0000000 --- a/test/runner.js +++ /dev/null @@ -1,22 +0,0 @@ -// -// To run tests via nodejs you must have nodejs and npm installed -// -// > npm install # to install node-qunit -// > node test/runner -// - -var runner = require("qunit"); - -runner.run({ - - code: "./state-machine.js", - - tests: [ - "test/test_basics.js", - "test/test_advanced.js", - "test/test_classes.js", - "test/test_async.js", - "test/test_initialize.js" - ] - -}); diff --git a/test/test_advanced.js b/test/test_advanced.js deleted file mode 100755 index 62cd9cb..0000000 --- a/test/test_advanced.js +++ /dev/null @@ -1,302 +0,0 @@ -//----------------------------------------------------------------------------- - -QUnit.module("advanced"); - -//----------------------------------------------------------------------------- - -test("multiple 'from' states for the same event", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: ['green', 'yellow'], to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: ['yellow', 'red'], to: 'green' }, - ]}); - - equal(fsm.current, 'green', "initial state should be green"); - - ok(fsm.can('warn'), "should be able to warn from green state") - ok(fsm.can('panic'), "should be able to panic from green state") - ok(fsm.cannot('calm'), "should NOT be able to calm from green state") - ok(fsm.cannot('clear'), "should NOT be able to clear from green state") - - fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); - - fsm.panic(); equal(fsm.current, 'red', "panic event should transition from green to red"); - fsm.clear(); equal(fsm.current, 'green', "clear event should transition from red to green"); - -}); - -//----------------------------------------------------------------------------- - -test("multiple 'to' states for the same event", function() { - - var fsm = StateMachine.create({ - initial: 'hungry', - events: [ - { name: 'eat', from: 'hungry', to: 'satisfied' }, - { name: 'eat', from: 'satisfied', to: 'full' }, - { name: 'eat', from: 'full', to: 'sick' }, - { name: 'rest', from: ['hungry', 'satisfied', 'full', 'sick'], to: 'hungry' }, - ]}); - - equal(fsm.current, 'hungry'); - - ok(fsm.can('eat')); - ok(fsm.can('rest')); - - fsm.eat(); - equal(fsm.current, 'satisfied'); - - fsm.eat(); - equal(fsm.current, 'full'); - - fsm.eat(); - equal(fsm.current, 'sick'); - - fsm.rest(); - equal(fsm.current, 'hungry'); - -}); - -//----------------------------------------------------------------------------- - -test("no-op transitions (github issue #5) with multiple from states", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: ['green', 'yellow'], to: 'red' }, - { name: 'noop', from: ['green', 'yellow'] }, // NOTE: 'to' not specified - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: ['yellow', 'red'], to: 'green' }, - ]}); - - equal(fsm.current, 'green', "initial state should be green"); - - ok(fsm.can('warn'), "should be able to warn from green state") - ok(fsm.can('panic'), "should be able to panic from green state") - ok(fsm.can('noop'), "should be able to noop from green state") - ok(fsm.cannot('calm'), "should NOT be able to calm from green state") - ok(fsm.cannot('clear'), "should NOT be able to clear from green state") - - fsm.noop(); equal(fsm.current, 'green', "noop event should not transition"); - fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); - - ok(fsm.cannot('warn'), "should NOT be able to warn from yellow state") - ok(fsm.can('panic'), "should be able to panic from yellow state") - ok(fsm.can('noop'), "should be able to noop from yellow state") - ok(fsm.cannot('calm'), "should NOT be able to calm from yellow state") - ok(fsm.can('clear'), "should be able to clear from yellow state") - - fsm.noop(); equal(fsm.current, 'yellow', "noop event should not transition"); - fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); - - ok(fsm.cannot('warn'), "should NOT be able to warn from red state") - ok(fsm.cannot('panic'), "should NOT be able to panic from red state") - ok(fsm.cannot('noop'), "should NOT be able to noop from red state") - ok(fsm.can('calm'), "should be able to calm from red state") - ok(fsm.can('clear'), "should be able to clear from red state") - -}); - -//----------------------------------------------------------------------------- - -test("callbacks are called when appropriate for multiple 'from' and 'to' transitions", function() { - - var called = []; - - var fsm = StateMachine.create({ - initial: 'hungry', - events: [ - { name: 'eat', from: 'hungry', to: 'satisfied' }, - { name: 'eat', from: 'satisfied', to: 'full' }, - { name: 'eat', from: 'full', to: 'sick' }, - { name: 'rest', from: ['hungry', 'satisfied', 'full', 'sick'], to: 'hungry' }, - ], - callbacks: { - - // generic callbacks - onbeforeevent: function(event,from,to) { called.push('onbefore(' + event + ')'); }, - onafterevent: function(event,from,to) { called.push('onafter(' + event + ')'); }, - onleavestate: function(event,from,to) { called.push('onleave(' + from + ')'); }, - onenterstate: function(event,from,to) { called.push('onenter(' + to + ')'); }, - onchangestate: function(event,from,to) { called.push('onchange(' + from + ',' + to + ')'); }, - - // specific state callbacks - onenterhungry: function() { called.push('onenterhungry'); }, - onleavehungry: function() { called.push('onleavehungry'); }, - onentersatisfied: function() { called.push('onentersatisfied'); }, - onleavesatisfied: function() { called.push('onleavesatisfied'); }, - onenterfull: function() { called.push('onenterfull'); }, - onleavefull: function() { called.push('onleavefull'); }, - onentersick: function() { called.push('onentersick'); }, - onleavesick: function() { called.push('onleavesick'); }, - - // specific event callbacks - onbeforeeat: function() { called.push('onbeforeeat'); }, - onaftereat: function() { called.push('onaftereat'); }, - onbeforerest: function() { called.push('onbeforerest'); }, - onafterrest: function() { called.push('onafterrest'); } - } - }); - - called = []; - fsm.eat(); - deepEqual(called, [ - 'onbeforeeat', - 'onbefore(eat)', - 'onleavehungry', - 'onleave(hungry)', - 'onentersatisfied', - 'onenter(satisfied)', - 'onchange(hungry,satisfied)', - 'onaftereat', - 'onafter(eat)' - ]); - - called = []; - fsm.eat(); - deepEqual(called, [ - 'onbeforeeat', - 'onbefore(eat)', - 'onleavesatisfied', - 'onleave(satisfied)', - 'onenterfull', - 'onenter(full)', - 'onchange(satisfied,full)', - 'onaftereat', - 'onafter(eat)', - ]); - - called = []; - fsm.eat(); - deepEqual(called, [ - 'onbeforeeat', - 'onbefore(eat)', - 'onleavefull', - 'onleave(full)', - 'onentersick', - 'onenter(sick)', - 'onchange(full,sick)', - 'onaftereat', - 'onafter(eat)' - ]); - - called = []; - fsm.rest(); - deepEqual(called, [ - 'onbeforerest', - 'onbefore(rest)', - 'onleavesick', - 'onleave(sick)', - 'onenterhungry', - 'onenter(hungry)', - 'onchange(sick,hungry)', - 'onafterrest', - 'onafter(rest)' - ]); - -}); - -//----------------------------------------------------------------------------- - -test("callbacks are called when appropriate for prototype based state machine", function() { - - var myFSM = function() { - this.called = []; - this.startup(); - }; - - myFSM.prototype = { - - // generic callbacks - onbeforeevent: function(event,from,to) { this.called.push('onbefore(' + event + ')'); }, - onafterevent: function(event,from,to) { this.called.push('onafter(' + event + ')'); }, - onleavestate: function(event,from,to) { this.called.push('onleave(' + from + ')'); }, - onenterstate: function(event,from,to) { this.called.push('onenter(' + to + ')'); }, - onchangestate: function(event,from,to) { this.called.push('onchange(' + from + ',' + to + ')'); }, - - // specific state callbacks - onenternone: function() { this.called.push('onenternone'); }, - onleavenone: function() { this.called.push('onleavenone'); }, - onentergreen: function() { this.called.push('onentergreen'); }, - onleavegreen: function() { this.called.push('onleavegreen'); }, - onenteryellow : function() { this.called.push('onenteryellow'); }, - onleaveyellow: function() { this.called.push('onleaveyellow'); }, - onenterred: function() { this.called.push('onenterred'); }, - onleavered: function() { this.called.push('onleavered'); }, - - // specific event callbacks - onbeforestartup: function() { this.called.push('onbeforestartup'); }, - onafterstartup: function() { this.called.push('onafterstartup'); }, - onbeforewarn: function() { this.called.push('onbeforewarn'); }, - onafterwarn: function() { this.called.push('onafterwarn'); }, - onbeforepanic: function() { this.called.push('onbeforepanic'); }, - onafterpanic: function() { this.called.push('onafterpanic'); }, - onbeforeclear: function() { this.called.push('onbeforeclear'); }, - onafterclear: function() { this.called.push('onafterclear'); } - }; - - StateMachine.create({ - target: myFSM.prototype, - events: [ - { name: 'startup', from: 'none', to: 'green' }, - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'clear', from: 'yellow', to: 'green' } - ] - }); - - var a = new myFSM(); - var b = new myFSM(); - - equal(a.current, 'green', 'start with correct state'); - equal(b.current, 'green', 'start with correct state'); - - deepEqual(a.called, ['onbeforestartup', 'onbefore(startup)', 'onleavenone', 'onleave(none)', 'onentergreen', 'onenter(green)', 'onchange(none,green)', 'onafterstartup', 'onafter(startup)']); - deepEqual(b.called, ['onbeforestartup', 'onbefore(startup)', 'onleavenone', 'onleave(none)', 'onentergreen', 'onenter(green)', 'onchange(none,green)', 'onafterstartup', 'onafter(startup)']); - - a.called = []; - b.called = []; - - a.warn(); - - equal(a.current, 'yellow', 'maintain independent current state'); - equal(b.current, 'green', 'maintain independent current state'); - - deepEqual(a.called, ['onbeforewarn', 'onbefore(warn)', 'onleavegreen', 'onleave(green)', 'onenteryellow', 'onenter(yellow)', 'onchange(green,yellow)', 'onafterwarn', 'onafter(warn)']); - deepEqual(b.called, []); - -}); - - - -//----------------------------------------------------------------------------- - -test("double wildcard transition does not change current state", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: ['green', 'yellow'], to: 'red' }, - { name: 'noop', from: ['green', 'yellow'] }, // NOTE: 'to' not specified - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: ['yellow', 'red'], to: 'green' }, - { name: 'lightup', from: '*' }, // Note: "double wildcard" - { name: 'lightup', from: 'green', to: 'yellow' } - ]}); - - equal(fsm.current, 'green', "start with correct state"); - - fsm.lightup(); equal(fsm.current, 'yellow', "lightup event should switch green to yellow"); - fsm.lightup(); equal(fsm.current, 'yellow', "lightup event should have no effect effect in other state than green"); - -}); \ No newline at end of file diff --git a/test/test_async.js b/test/test_async.js deleted file mode 100644 index 17fc6a5..0000000 --- a/test/test_async.js +++ /dev/null @@ -1,408 +0,0 @@ -//----------------------------------------------------------------------------- - -QUnit.module("async"); - -//----------------------------------------------------------------------------- - -test("state transitions", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ], - callbacks: { - onleavegreen: function() { return StateMachine.ASYNC; }, - onleaveyellow: function() { return StateMachine.ASYNC; }, - onleavered: function() { return StateMachine.ASYNC; } - } - }); - - equal(fsm.current, 'green', "initial state should be green"); - fsm.warn(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet"); - fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); - fsm.transition(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equal(fsm.current, 'red', "should still be red because we haven't transitioned yet"); - fsm.transition(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); - fsm.transition(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); - -}); - -//----------------------------------------------------------------------------- - -test("state transitions with delays", function() { - - stop(); // doing async stuff - dont run next qunit test until I call start() below - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ], - callbacks: { - onleavegreen: function() { return StateMachine.ASYNC; }, - onleaveyellow: function() { return StateMachine.ASYNC; }, - onleavered: function() { return StateMachine.ASYNC; } - } - }); - - equal(fsm.current, 'green', "initial state should be green"); - fsm.warn(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet"); - setTimeout(function() { - fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); - setTimeout(function() { - fsm.transition(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equal(fsm.current, 'red', "should still be red because we haven't transitioned yet"); - setTimeout(function() { - fsm.transition(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); - setTimeout(function() { - fsm.transition(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); - start(); - }, 10); - }, 10); - }, 10); - }, 10); - -}); - -//----------------------------------------------------------------------------- - -test("state transition fired during onleavestate callback - immediate", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ], - callbacks: { - onleavegreen: function() { this.transition(); return StateMachine.ASYNC; }, - onleaveyellow: function() { this.transition(); return StateMachine.ASYNC; }, - onleavered: function() { this.transition(); return StateMachine.ASYNC; } - } - }); - - equal(fsm.current, 'green', "initial state should be green"); - - fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); - -}); - -//----------------------------------------------------------------------------- - -test("state transition fired during onleavestate callback - with delay", function() { - - stop(); // doing async stuff - dont run next qunit test until I call start() below - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'panic', from: 'green', to: 'red' } - ], - callbacks: { - onleavegreen: function() { setTimeout(function() { fsm.transition(); }, 10); return StateMachine.ASYNC; }, - onenterred: function() { - equal(fsm.current, 'red', "panic event should transition from green to red"); - start(); - } - } - }); - - equal(fsm.current, 'green', "initial state should be green"); - fsm.panic(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet"); - -}); - -//----------------------------------------------------------------------------- - -test("state transition fired during onleavestate callback - but forgot to return ASYNC!", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ], - callbacks: { - onleavegreen: function() { this.transition(); /* return StateMachine.ASYNC; */ }, - onleaveyellow: function() { this.transition(); /* return StateMachine.ASYNC; */ }, - onleavered: function() { this.transition(); /* return StateMachine.ASYNC; */ } - } - }); - - equal(fsm.current, 'green', "initial state should be green"); - - fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); - -}); - -//----------------------------------------------------------------------------- - -test("state transitions sometimes synchronous and sometimes asynchronous", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ] - }); - - // default behavior is synchronous - - equal(fsm.current, 'green', "initial state should be green"); - fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); - - // but add callbacks that return ASYNC and it magically becomes asynchronous - - fsm.onleavegreen = function() { return StateMachine.ASYNC; } - fsm.onleaveyellow = function() { return StateMachine.ASYNC; } - fsm.onleavered = function() { return StateMachine.ASYNC; } - - equal(fsm.current, 'green', "initial state should be green"); - fsm.warn(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet"); - fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); - fsm.transition(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equal(fsm.current, 'red', "should still be red because we haven't transitioned yet"); - fsm.transition(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); - fsm.transition(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); - - // this allows you to make on-the-fly decisions about whether async or not ... - - fsm.onleavegreen = function(event, from, to, async) { - if (async) { - setTimeout(function() { - fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); - start(); // move on to next test - }, 10); - return StateMachine.ASYNC; - } - } - fsm.onleaveyellow = fsm.onleavered = null; - - fsm.warn(false); equal(fsm.current, 'yellow', "expected synchronous transition from green to yellow"); - fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); - fsm.warn(true); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet"); - - stop(); // doing async stuff - dont run next qunit test until I call start() in callback above - -}); - -//----------------------------------------------------------------------------- - - -test("state transition fired without completing previous transition", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ], - callbacks: { - onleavegreen: function() { return StateMachine.ASYNC; }, - onleaveyellow: function() { return StateMachine.ASYNC; }, - onleavered: function() { return StateMachine.ASYNC; } - } - }); - - equal(fsm.current, 'green', "initial state should be green"); - fsm.warn(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet"); - fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); - - throws(fsm.calm.bind(fsm), /event calm inappropriate because previous transition did not complete/); - -}); - -//----------------------------------------------------------------------------- - -test("state transition can be cancelled (github issue #22)", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ], - callbacks: { - onleavegreen: function() { return StateMachine.ASYNC; }, - onleaveyellow: function() { return StateMachine.ASYNC; }, - onleavered: function() { return StateMachine.ASYNC; } - } - }); - - equal(fsm.current, 'green', "initial state should be green"); - fsm.warn(); equal(fsm.current, 'green', "should still be green because we haven't transitioned yet"); - fsm.transition(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); - equal(fsm.can('panic'), false, "but cannot panic a 2nd time because a transition is still pending") - - throws(fsm.panic.bind(fsm), /event panic inappropriate because previous transition did not complete/); - - fsm.transition.cancel(); - - equal(fsm.current, 'yellow', "should still be yellow because we cancelled the async transition"); - equal(fsm.can('panic'), true, "can now panic again because we cancelled previous async transition"); - - fsm.panic(); - fsm.transition(); - - equal(fsm.current, 'red', "should finally be red now that we completed the async transition"); - -}); - -//----------------------------------------------------------------------------- - -test("callbacks are ordered correctly", function() { - - var called = []; - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' }, - ], - callbacks: { - - // generic callbacks - onbeforeevent: function(event,from,to) { called.push('onbefore(' + event + ')'); }, - onafterevent: function(event,from,to) { called.push('onafter(' + event + ')'); }, - onleavestate: function(event,from,to) { called.push('onleave(' + from + ')'); }, - onenterstate: function(event,from,to) { called.push('onenter(' + to + ')'); }, - onchangestate: function(event,from,to) { called.push('onchange(' + from + ',' + to + ')'); }, - - // specific state callbacks - onentergreen: function() { called.push('onentergreen'); }, - onenteryellow: function() { called.push('onenteryellow'); }, - onenterred: function() { called.push('onenterred'); }, - onleavegreen: function() { called.push('onleavegreen'); return StateMachine.ASYNC; }, - onleaveyellow: function() { called.push('onleaveyellow'); return StateMachine.ASYNC; }, - onleavered: function() { called.push('onleavered'); return StateMachine.ASYNC; }, - - // specific event callbacks - onbeforewarn: function() { called.push('onbeforewarn'); }, - onbeforepanic: function() { called.push('onbeforepanic'); }, - onbeforecalm: function() { called.push('onbeforecalm'); }, - onbeforeclear: function() { called.push('onbeforeclear'); }, - onafterwarn: function() { called.push('onafterwarn'); }, - onafterpanic: function() { called.push('onafterpanic'); }, - onaftercalm: function() { called.push('onaftercalm'); }, - onafterclear: function() { called.push('onafterclear'); } - } - }); - - called = []; - fsm.warn(); deepEqual(called, ['onbeforewarn', 'onbefore(warn)', 'onleavegreen', 'onleave(green)']); - fsm.transition(); deepEqual(called, ['onbeforewarn', 'onbefore(warn)', 'onleavegreen', 'onleave(green)', 'onenteryellow', 'onenter(yellow)', 'onchange(green,yellow)', 'onafterwarn', 'onafter(warn)']); - - called = []; - fsm.panic(); deepEqual(called, ['onbeforepanic', 'onbefore(panic)', 'onleaveyellow', 'onleave(yellow)']); - fsm.transition(); deepEqual(called, ['onbeforepanic', 'onbefore(panic)', 'onleaveyellow', 'onleave(yellow)', 'onenterred', 'onenter(red)', 'onchange(yellow,red)', 'onafterpanic', 'onafter(panic)']); - - called = []; - fsm.calm(); deepEqual(called, ['onbeforecalm', 'onbefore(calm)', 'onleavered', 'onleave(red)']); - fsm.transition(); deepEqual(called, ['onbeforecalm', 'onbefore(calm)', 'onleavered', 'onleave(red)', 'onenteryellow', 'onenter(yellow)', 'onchange(red,yellow)', 'onaftercalm', 'onafter(calm)']); - - called = []; - fsm.clear(); deepEqual(called, ['onbeforeclear', 'onbefore(clear)', 'onleaveyellow', 'onleave(yellow)']); - fsm.transition(); deepEqual(called, ['onbeforeclear', 'onbefore(clear)', 'onleaveyellow', 'onleave(yellow)', 'onentergreen', 'onenter(green)', 'onchange(yellow,green)', 'onafterclear', 'onafter(clear)']); - -}); - -//----------------------------------------------------------------------------- - -test("cannot fire event during existing transition", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ], - callbacks: { - onleavegreen: function() { return StateMachine.ASYNC; }, - onleaveyellow: function() { return StateMachine.ASYNC; }, - onleavered: function() { return StateMachine.ASYNC; } - } - }); - - equal(fsm.current, 'green', "initial state should be green"); - equal(fsm.can('warn'), true, "should be able to warn"); - equal(fsm.can('panic'), false, "should NOT be able to panic"); - equal(fsm.can('calm'), false, "should NOT be able to calm"); - equal(fsm.can('clear'), false, "should NOT be able to clear"); - - fsm.warn(); - - equal(fsm.current, 'green', "should still be green because we haven't transitioned yet"); - equal(fsm.can('warn'), false, "should NOT be able to warn - during transition"); - equal(fsm.can('panic'), false, "should NOT be able to panic - during transition"); - equal(fsm.can('calm'), false, "should NOT be able to calm - during transition"); - equal(fsm.can('clear'), false, "should NOT be able to clear - during transition"); - - fsm.transition(); - - equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); - equal(fsm.can('warn'), false, "should NOT be able to warn"); - equal(fsm.can('panic'), true, "should be able to panic"); - equal(fsm.can('calm'), false, "should NOT be able to calm"); - equal(fsm.can('clear'), true, "should be able to clear"); - - fsm.panic(); - - equal(fsm.current, 'yellow', "should still be yellow because we haven't transitioned yet"); - equal(fsm.can('warn'), false, "should NOT be able to warn - during transition"); - equal(fsm.can('panic'), false, "should NOT be able to panic - during transition"); - equal(fsm.can('calm'), false, "should NOT be able to calm - during transition"); - equal(fsm.can('clear'), false, "should NOT be able to clear - during transition"); - - fsm.transition(); - - equal(fsm.current, 'red', "panic event should transition from yellow to red"); - equal(fsm.can('warn'), false, "should NOT be able to warn"); - equal(fsm.can('panic'), false, "should NOT be able to panic"); - equal(fsm.can('calm'), true, "should be able to calm"); - equal(fsm.can('clear'), false, "should NOT be able to clear"); - -}); - -//----------------------------------------------------------------------------- - - diff --git a/test/test_basics.js b/test/test_basics.js deleted file mode 100644 index 209479c..0000000 --- a/test/test_basics.js +++ /dev/null @@ -1,733 +0,0 @@ -//----------------------------------------------------------------------------- - -QUnit.module("basic"); - -//----------------------------------------------------------------------------- - -test("standalone state machine", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ]}); - - equal(fsm.current, 'green', "initial state should be green"); - - fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); - fsm.panic(); equal(fsm.current, 'red', "panic event should transition from yellow to red"); - fsm.calm(); equal(fsm.current, 'yellow', "calm event should transition from red to yellow"); - fsm.clear(); equal(fsm.current, 'green', "clear event should transition from yellow to green"); - -}); - -//----------------------------------------------------------------------------- - -test("targeted state machine", function() { - - StateMachine.create({ - target: this, - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ]}); - - equal(this.current, 'green', "initial state should be green"); - - this.warn(); equal(this.current, 'yellow', "warn event should transition from green to yellow"); - this.panic(); equal(this.current, 'red', "panic event should transition from yellow to red"); - this.calm(); equal(this.current, 'yellow', "calm event should transition from red to yellow"); - this.clear(); equal(this.current, 'green', "clear event should transition from yellow to green"); -}); - -//----------------------------------------------------------------------------- - -test("can & cannot", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - ]}); - - equal(fsm.current, 'green', "initial state should be green"); - - ok(fsm.can('warn'), "should be able to warn from green state") - ok(fsm.cannot('panic'), "should NOT be able to panic from green state") - ok(fsm.cannot('calm'), "should NOT be able to calm from green state") - - fsm.warn(); - equal(fsm.current, 'yellow', "current state should be yellow"); - ok(fsm.cannot('warn'), "should NOT be able to warn from yellow state") - ok(fsm.can('panic'), "should be able to panic from yellow state") - ok(fsm.cannot('calm'), "should NOT be able to calm from yellow state") - - fsm.panic(); - equal(fsm.current, 'red', "current state should be red"); - ok(fsm.cannot('warn'), "should NOT be able to warn from red state") - ok(fsm.cannot('panic'), "should NOT be able to panic from red state") - ok(fsm.can('calm'), "should be able to calm from red state") - - equal(fsm.can('jibber'), false, "unknown event should not crash") - equal(fsm.cannot('jabber'), true, "unknown event should not crash") - -}); - -//----------------------------------------------------------------------------- - -test("is", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ]}); - - equal(fsm.current, 'green', "initial state should be green"); - - equal(fsm.is('green'), true, 'current state should match'); - equal(fsm.is('yellow'), false, 'current state should NOT match'); - equal(fsm.is(['green', 'red']), true, 'current state should match when included in array'); - equal(fsm.is(['yellow', 'red']), false, 'current state should NOT match when not included in array'); - - fsm.warn(); - - equal(fsm.current, 'yellow', "current state should be yellow"); - - equal(fsm.is('green'), false, 'current state should NOT match'); - equal(fsm.is('yellow'), true, 'current state should match'); - equal(fsm.is(['green', 'red']), false, 'current state should NOT match when not included in array'); - equal(fsm.is(['yellow', 'red']), true, 'current state should match when included in array'); - -}); - -//----------------------------------------------------------------------------- - -test("states", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' }, - { name: 'finish', from: 'green', to: 'done' }, - ]}); - - deepEqual(fsm.states(), [ 'done', 'green', 'none', 'red', 'yellow' ]); - -}); - -//----------------------------------------------------------------------------- - -test("transitions", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' }, - { name: 'finish', from: 'green', to: 'done' }, - ]}); - - equal(fsm.current, 'green', 'current state should be yellow'); - deepEqual(fsm.transitions(), ['warn', 'finish'], 'current transition(s) should be yellow'); - - fsm.warn(); - equal(fsm.current, 'yellow', 'current state should be yellow'); - deepEqual(fsm.transitions(), ['panic', 'clear'], 'current transition(s) should be panic and clear'); - - fsm.panic(); - equal(fsm.current, 'red', 'current state should be red'); - deepEqual(fsm.transitions(), ['calm'], 'current transition(s) should be calm'); - - fsm.calm(); - equal(fsm.current, 'yellow', 'current state should be yellow'); - deepEqual(fsm.transitions(), ['panic', 'clear'], 'current transion(s) should be panic and clear'); - - fsm.clear(); - equal(fsm.current, 'green', 'current state should be green'); - deepEqual(fsm.transitions(), ['warn', 'finish'], 'current transion(s) should be warn'); - - fsm.finish(); - equal(fsm.current, 'done', 'current state should be done'); - deepEqual(fsm.transitions(), [], 'current transition(s) should be empty'); - -}); - -//----------------------------------------------------------------------------- - -test("transitions with multiple from states", function() { - - var fsm = StateMachine.create({ - events: [ - { name: 'start', from: 'none', to: 'green' }, - { name: 'warn', from: ['green', 'red'], to: 'yellow' }, - { name: 'panic', from: ['green', 'yellow'], to: 'red' }, - { name: 'clear', from: ['red', 'yellow'], to: 'green' } - ] - }); - - equal(fsm.current, 'none', 'current state should be none'); - deepEqual(fsm.transitions(), ['start'], 'current transition(s) should be start'); - - fsm.start(); - equal(fsm.current, 'green', 'current state should be green'); - deepEqual(fsm.transitions(), ['warn', 'panic'], 'current transition(s) should be warn and panic'); - - fsm.warn(); - equal(fsm.current, 'yellow', 'current state should be yellow'); - deepEqual(fsm.transitions(), ['panic', 'clear'], 'current transition(s) should be panic and clear'); - - fsm.panic(); - equal(fsm.current, 'red', 'current state should be red'); - deepEqual(fsm.transitions(), ['warn', 'clear'], 'current transition(s) should be warn and clear'); - - fsm.clear(); - equal(fsm.current, 'green', 'current state should be green'); - deepEqual(fsm.transitions(), ['warn', 'panic'], 'current transition(s) should be warn and panic'); - -}); - -//----------------------------------------------------------------------------- - -test("isFinished", function() { - - var fsm = StateMachine.create({ - initial: 'green', terminal: 'red', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' } - ]}); - - equal(fsm.current, 'green'); - equal(fsm.isFinished(), false); - - fsm.warn(); - equal(fsm.current, 'yellow'); - equal(fsm.isFinished(), false); - - fsm.panic(); - equal(fsm.current, 'red'); - equal(fsm.isFinished(), true); - -}); - -//----------------------------------------------------------------------------- - -test("isFinished - without specifying terminal state", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' } - ]}); - - equal(fsm.current, 'green'); - equal(fsm.isFinished(), false); - - fsm.warn(); - equal(fsm.current, 'yellow'); - equal(fsm.isFinished(), false); - - fsm.panic(); - equal(fsm.current, 'red'); - equal(fsm.isFinished(), false); - -}); -//----------------------------------------------------------------------------- - -test("inappropriate events", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - ]}); - - equal(fsm.current, 'green', "initial state should be green"); - - throws(fsm.panic.bind(fsm), /event panic inappropriate in current state green/); - throws(fsm.calm.bind(fsm), /event calm inappropriate in current state green/); - - fsm.warn(); - equal(fsm.current, 'yellow', "current state should be yellow"); - throws(fsm.warn.bind(fsm), /event warn inappropriate in current state yellow/); - throws(fsm.calm.bind(fsm), /event calm inappropriate in current state yellow/); - - fsm.panic(); - equal(fsm.current, 'red', "current state should be red"); - throws(fsm.warn.bind(fsm), /event warn inappropriate in current state red/); - throws(fsm.panic.bind(fsm), /event panic inappropriate in current state red/); - -}); - -//----------------------------------------------------------------------------- - -test("inappropriate event handling can be customized", function() { - - var fsm = StateMachine.create({ - error: function(name, from, to, args, error, msg) { return msg; }, // return error message instead of throwing an exception - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' } - ]}); - - equal(fsm.current, 'green', "initial state should be green"); - - equal(fsm.panic(), 'event panic inappropriate in current state green'); - equal(fsm.calm(), 'event calm inappropriate in current state green'); - - fsm.warn(); - equal(fsm.current, 'yellow', "current state should be yellow"); - equal(fsm.warn(), 'event warn inappropriate in current state yellow'); - equal(fsm.calm(), 'event calm inappropriate in current state yellow'); - - fsm.panic(); - equal(fsm.current, 'red', "current state should be red"); - equal(fsm.warn(), 'event warn inappropriate in current state red'); - equal(fsm.panic(), 'event panic inappropriate in current state red'); - -}); - -//----------------------------------------------------------------------------- - -test("event is cancelable", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' } - ]}); - - equal(fsm.current, 'green', 'initial state should be green'); - - fsm.onbeforewarn = function() { return false; } - fsm.warn(); - - equal(fsm.current, 'green', 'state should STAY green when event is cancelled'); - -}); - -//----------------------------------------------------------------------------- - -test("callbacks are ordered correctly", function() { - - var called = []; - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ], - callbacks: { - - // generic callbacks - onbeforeevent: function(event,frmo,to) { called.push('onbefore(' + event + ')'); }, - onafterevent: function(event,frmo,to) { called.push('onafter(' + event + ')'); }, - onleavestate: function(event,from,to) { called.push('onleave(' + from + ')'); }, - onenterstate: function(event,from,to) { called.push('onenter(' + to + ')'); }, - onchangestate: function(event,from,to) { called.push('onchange(' + from + ',' + to + ')'); }, - - // specific state callbacks - onentergreen: function() { called.push('onentergreen'); }, - onenteryellow: function() { called.push('onenteryellow'); }, - onenterred: function() { called.push('onenterred'); }, - onleavegreen: function() { called.push('onleavegreen'); }, - onleaveyellow: function() { called.push('onleaveyellow'); }, - onleavered: function() { called.push('onleavered'); }, - - // specific event callbacks - onbeforewarn: function() { called.push('onbeforewarn'); }, - onbeforepanic: function() { called.push('onbeforepanic'); }, - onbeforecalm: function() { called.push('onbeforecalm'); }, - onbeforeclear: function() { called.push('onbeforeclear'); }, - onafterwarn: function() { called.push('onafterwarn'); }, - onafterpanic: function() { called.push('onafterpanic'); }, - onaftercalm: function() { called.push('onaftercalm'); }, - onafterclear: function() { called.push('onafterclear'); }, - - } - }); - - called = []; - fsm.warn(); - deepEqual(called, [ - 'onbeforewarn', - 'onbefore(warn)', - 'onleavegreen', - 'onleave(green)', - 'onenteryellow', - 'onenter(yellow)', - 'onchange(green,yellow)', - 'onafterwarn', - 'onafter(warn)' - ]); - - called = []; - fsm.panic(); - deepEqual(called, [ - 'onbeforepanic', - 'onbefore(panic)', - 'onleaveyellow', - 'onleave(yellow)', - 'onenterred', - 'onenter(red)', - 'onchange(yellow,red)', - 'onafterpanic', - 'onafter(panic)' - ]); - - called = []; - fsm.calm(); - deepEqual(called, [ - 'onbeforecalm', - 'onbefore(calm)', - 'onleavered', - 'onleave(red)', - 'onenteryellow', - 'onenter(yellow)', - 'onchange(red,yellow)', - 'onaftercalm', - 'onafter(calm)' - ]); - - called = []; - fsm.clear(); - deepEqual(called, [ - 'onbeforeclear', - 'onbefore(clear)', - 'onleaveyellow', - 'onleave(yellow)', - 'onentergreen', - 'onenter(green)', - 'onchange(yellow,green)', - 'onafterclear', - 'onafter(clear)' - ]); - -}); - -//----------------------------------------------------------------------------- - -test("callbacks are ordered correctly - for same state transition", function() { - - var called = []; - - var fsm = StateMachine.create({ - initial: 'waiting', - events: [ - { name: 'data', from: ['waiting', 'receipt'], to: 'receipt' }, - { name: 'nothing', from: ['waiting', 'receipt'], to: 'waiting' }, - { 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 - ], - callbacks: { - - // generic callbacks - onbeforeevent: function(event,frmo,to) { called.push('onbefore(' + event + ')'); }, - onafterevent: function(event,frmo,to) { called.push('onafter(' + event + ')'); }, - onleavestate: function(event,from,to) { called.push('onleave(' + from + ')'); }, - onenterstate: function(event,from,to) { called.push('onenter(' + to + ')'); }, - onchangestate: function(event,from,to) { called.push('onchange(' + from + ',' + to + ')'); }, - - // specific state callbacks - onenterwaiting: function() { called.push('onenterwaiting'); }, - onenterreceipt: function() { called.push('onenterreceipt'); }, - onentererror: function() { called.push('onentererror'); }, - onleavewaiting: function() { called.push('onleavewaiting'); }, - onleavereceipt: function() { called.push('onleavereceipt'); }, - onleaveerror: function() { called.push('onleaveerror'); }, - - // specific event callbacks - onbeforedata: function() { called.push('onbeforedata'); }, - onbeforenothing: function() { called.push('onbeforenothing'); }, - onbeforeerror: function() { called.push('onbeforeerror'); }, - onafterdata: function() { called.push('onafterdata'); }, - onafternothing: function() { called.push('onafternothing'); }, - onaftereerror: function() { called.push('onaftererror'); }, - } - }); - - called = []; - fsm.data(); - deepEqual(called, [ - 'onbeforedata', - 'onbefore(data)', - 'onleavewaiting', - 'onleave(waiting)', - 'onenterreceipt', - 'onenter(receipt)', - 'onchange(waiting,receipt)', - 'onafterdata', - 'onafter(data)' - ]); - - called = []; - fsm.data(); // same-state transition - deepEqual(called, [ // so NO enter/leave/change state callbacks are fired - 'onbeforedata', - 'onbefore(data)', - 'onafterdata', - 'onafter(data)' - ]); - - called = []; - fsm.data(); // same-state transition - deepEqual(called, [ // so NO enter/leave/change state callbacks are fired - 'onbeforedata', - 'onbefore(data)', - 'onafterdata', - 'onafter(data)' - ]); - - called = []; - fsm.nothing(); - deepEqual(called, [ - 'onbeforenothing', - 'onbefore(nothing)', - 'onleavereceipt', - 'onleave(receipt)', - 'onenterwaiting', - 'onenter(waiting)', - 'onchange(receipt,waiting)', - 'onafternothing', - 'onafter(nothing)' - ]); - -}); - -//----------------------------------------------------------------------------- - -test("callback arguments are correct", function() { - - var expected = { event: 'startup', from: 'none', to: 'green' }; // first expected callback - - var verify_expected = function(event,from,to,a,b,c) { - equal(event, expected.event) - equal(from, expected.from) - equal(to, expected.to) - equal(a, expected.a) - equal(b, expected.b) - equal(c, expected.c) - }; - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ], - callbacks: { - - // generic callbacks - onbeforeevent: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - onafterevent: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - onleavestate: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - onenterstate: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - onchangestate: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - - // specific state callbacks - onentergreen: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - onenteryellow: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - onenterred: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - onleavegreen: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - onleaveyellow: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - onleavered: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - - // specific event callbacks - onbeforewarn: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - onbeforepanic: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - onbeforecalm: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - onbeforeclear: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - onafterwarn: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - onafterpanic: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - onaftercalm: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); }, - onafterclear: function(event,from,to,a,b,c) { verify_expected(event,from,to,a,b,c); } - } - }); - - expected = { event: 'warn', from: 'green', to: 'yellow', a: 1, b: 2, c: 3 }; - fsm.warn(1,2,3); - - expected = { event: 'panic', from: 'yellow', to: 'red', a: 4, b: 5, c: 6 }; - fsm.panic(4,5,6); - - expected = { event: 'calm', from: 'red', to: 'yellow', a: 'foo', b: 'bar', c: null }; - fsm.calm('foo', 'bar'); - - expected = { event: 'clear', from: 'yellow', to: 'green', a: null, b: null, c: null }; - fsm.clear(); - -}); - -//----------------------------------------------------------------------------- - -test("exceptions in caller-provided callbacks are not swallowed (github issue #17)", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' } - ], - callbacks: { - onenteryellow: function() { throw 'oops'; } - }}); - - equal(fsm.current, 'green', "initial state should be green"); - - throws(fsm.warn.bind(fsm), /oops/); -}); - -//----------------------------------------------------------------------------- - -test("no-op transitions (github issue #5)", function() { - - var fsm = StateMachine.create({ - initial: 'green', - events: [ - { name: 'noop', from: 'green', /* no-op */ }, - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'calm', from: 'red', to: 'yellow' }, - { name: 'clear', from: 'yellow', to: 'green' } - ]}); - - equal(fsm.current, 'green', "initial state should be green"); - - ok(fsm.can('noop'), "should be able to noop from green state") - ok(fsm.can('warn'), "should be able to warn from green state") - - fsm.noop(); equal(fsm.current, 'green', "noop event should not cause a transition (there is no 'to' specified)"); - fsm.warn(); equal(fsm.current, 'yellow', "warn event should transition from green to yellow"); - - ok(fsm.cannot('noop'), "should NOT be able to noop from yellow state") - ok(fsm.cannot('warn'), "should NOT be able to warn from yellow state") - -}); - -//----------------------------------------------------------------------------- - -test("wildcard 'from' allows event from any state (github issue #11)", function() { - - var fsm = StateMachine.create({ - initial: 'stopped', - events: [ - { name: 'prepare', from: 'stopped', to: 'ready' }, - { name: 'start', from: 'ready', to: 'running' }, - { name: 'resume', from: 'paused', to: 'running' }, - { name: 'pause', from: 'running', to: 'paused' }, - { name: 'stop', from: '*', to: 'stopped' } - ]}); - - equal(fsm.current, 'stopped', "initial state should be stopped"); - - fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready"); - fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from ready to stopped"); - - fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready"); - fsm.start(); equal(fsm.current, 'running', "start event should transition from ready to running"); - fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from running to stopped"); - - fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready"); - fsm.start(); equal(fsm.current, 'running', "start event should transition from ready to running"); - fsm.pause(); equal(fsm.current, 'paused', "pause event should transition from running to paused"); - fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from paused to stopped"); - - deepEqual(fsm.transitions(), ["prepare", "stop"], "ensure wildcard event (stop) is included in available transitions") - fsm.prepare(); deepEqual(fsm.transitions(), ["start", "stop"], "ensure wildcard event (stop) is included in available transitions") - fsm.start(); deepEqual(fsm.transitions(), ["pause", "stop"], "ensure wildcard event (stop) is included in available transitions") - fsm.stop(); deepEqual(fsm.transitions(), ["prepare", "stop"], "ensure wildcard event (stop) is included in available transitions") - -}); - -//----------------------------------------------------------------------------- - -test("missing 'from' allows event from any state (github issue #11) ", function() { - - var fsm = StateMachine.create({ - initial: 'stopped', - events: [ - { name: 'prepare', from: 'stopped', to: 'ready' }, - { name: 'start', from: 'ready', to: 'running' }, - { name: 'resume', from: 'paused', to: 'running' }, - { name: 'pause', from: 'running', to: 'paused' }, - { name: 'stop', /* any from state */ to: 'stopped' } - ]}); - - equal(fsm.current, 'stopped', "initial state should be stopped"); - - fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready"); - fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from ready to stopped"); - - fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready"); - fsm.start(); equal(fsm.current, 'running', "start event should transition from ready to running"); - fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from running to stopped"); - - fsm.prepare(); equal(fsm.current, 'ready', "prepare event should transition from stopped to ready"); - fsm.start(); equal(fsm.current, 'running', "start event should transition from ready to running"); - fsm.pause(); equal(fsm.current, 'paused', "pause event should transition from running to paused"); - fsm.stop(); equal(fsm.current, 'stopped', "stop event should transition from paused to stopped"); - -}); - -//----------------------------------------------------------------------------- - -test("event return values (github issue #12) ", function() { - - var fsm = StateMachine.create({ - initial: 'stopped', - events: [ - { name: 'prepare', from: 'stopped', to: 'ready' }, - { name: 'fake', from: 'ready', to: 'running' }, - { name: 'start', from: 'ready', to: 'running' } - ], - callbacks: { - onbeforefake: function(event,from,to,a,b,c) { return false; }, // this event will be cancelled - onleaveready: function(event,from,to,a,b,c) { return StateMachine.ASYNC; } // this state transition is ASYNC - } - }); - - equal(fsm.current, 'stopped', "initial state should be stopped"); - - equal(fsm.prepare(), StateMachine.Result.SUCCEEDED, "expected event to have SUCCEEDED"); - equal(fsm.current, 'ready', "prepare event should transition from stopped to ready"); - - equal(fsm.fake(), StateMachine.Result.CANCELLED, "expected event to have been CANCELLED"); - equal(fsm.current, 'ready', "cancelled event should not cause a transition"); - - equal(fsm.start(), StateMachine.Result.PENDING, "expected event to cause a PENDING asynchronous transition"); - equal(fsm.current, 'ready', "async transition hasn't happened yet"); - - equal(fsm.transition(), StateMachine.Result.SUCCEEDED, "expected async transition to have SUCCEEDED"); - equal(fsm.current, 'running', "async transition should now be complete"); - -}); - diff --git a/test/test_classes.js b/test/test_classes.js deleted file mode 100644 index 4c6dc2b..0000000 --- a/test/test_classes.js +++ /dev/null @@ -1,92 +0,0 @@ -//----------------------------------------------------------------------------- - -QUnit.module("classes"); - -//----------------------------------------------------------------------------- - -test("prototype based state machine", function() { - - var myFSM = function() { - this.counter = 42; - this.startup(); - }; - - myFSM.prototype = { - onwarn: function() { this.counter++; } - } - - StateMachine.create({ - target: myFSM.prototype, - events: [ - { name: 'startup', from: 'none', to: 'green' }, - { name: 'warn', from: 'green', to: 'yellow' }, - { name: 'panic', from: 'yellow', to: 'red' }, - { name: 'clear', from: 'yellow', to: 'green' } - ] - }); - - var a = new myFSM(); - var b = new myFSM(); - - equal(a.current, 'green', 'start with correct state'); - equal(b.current, 'green', 'start with correct state'); - - equal(a.counter, 42, 'start with correct counter'); - equal(b.counter, 42, 'start with correct counter'); - - a.warn(); - - equal(a.current, 'yellow', 'maintain independent current state'); - equal(b.current, 'green', 'maintain independent current state'); - - equal(a.counter, 43, 'counter for (a) should have incremented'); - equal(b.counter, 42, 'counter for (b) should remain untouched'); - - ok(a.hasOwnProperty('current'), "each instance should have its own current state"); - ok(b.hasOwnProperty('current'), "each instance should have its own current state"); - ok(!a.hasOwnProperty('warn'), "each instance should NOT have its own event methods"); - ok(!b.hasOwnProperty('warn'), "each instance should NOT have its own event methods"); - ok(a.warn === b.warn, "each instance should share event methods"); - ok(a.warn === a.__proto__.warn, "each instance event methods come from its shared prototype"); - ok(b.warn === b.__proto__.warn, "each instance event methods come from its shared prototype"); - -}); - -//----------------------------------------------------------------------------- - -test("github issue 19", function() { - - var Foo = function() { - this.counter = 7; - this.initFSM(); - }; - - Foo.prototype.onenterready = function() { this.counter++; }; - Foo.prototype.onenterrunning = function() { this.counter++; }; - - StateMachine.create({ - target : Foo.prototype, - initial: { state: 'ready', event: 'initFSM', defer: true }, // unfortunately, trying to apply an IMMEDIATE initial state wont work on prototype based FSM, it MUST be deferred and called in the constructor for each instance - events : [{name: 'execute', from: 'ready', to: 'running'}, - {name: 'abort', from: 'running', to: 'ready'}] - }); - - var foo = new Foo(); - var bar = new Foo(); - - equal(foo.current, 'ready', 'start with correct state'); - equal(bar.current, 'ready', 'start with correct state'); - - equal(foo.counter, 8, 'start with correct counter 7 (from constructor) + 1 (from onenterready)'); - equal(bar.counter, 8, 'start with correct counter 7 (from constructor) + 1 (from onenterready)'); - - foo.execute(); // transition foo, but NOT bar - - equal(foo.current, 'running', 'changed state'); - equal(bar.current, 'ready', 'state remains the same'); - - equal(foo.counter, 9, 'incremented counter during onenterrunning'); - equal(bar.counter, 8, 'counter remains the same'); - -}); - diff --git a/test/test_initialize.js b/test/test_initialize.js deleted file mode 100644 index ae2e2a0..0000000 --- a/test/test_initialize.js +++ /dev/null @@ -1,122 +0,0 @@ -//----------------------------------------------------------------------------- - -QUnit.module("special initialization options", { - - setup: function() { - this.called = []; - this.onbeforeevent = function(event,from,to) { this.called.push('onbefore(' + event + ')'); }, - this.onafterevent = function(event,from,to) { this.called.push('onafter(' + event + ')'); }, - this.onleavestate = function(event,from,to) { this.called.push('onleave(' + from + ')'); }, - this.onenterstate = function(event,from,to) { this.called.push('onenter(' + to + ')'); }, - this.onchangestate = function(event,from,to) { this.called.push('onchange(' + from + ',' + to + ')'); }; - this.onbeforeinit = function() { this.called.push("onbeforeinit"); }; - this.onafterinit = function() { this.called.push("onafterinit"); }; - this.onbeforestartup = function() { this.called.push("onbeforestartup"); }; - this.onafterstartup = function() { this.called.push("onafterstartup"); }; - this.onbeforepanic = function() { this.called.push("onbeforepanic"); }; - this.onafterpanic = function() { this.called.push("onafterpanic"); }; - this.onbeforecalm = function() { this.called.push("onbeforecalm"); }; - this.onaftercalm = function() { this.called.push("onaftercalm"); }; - this.onenternone = function() { this.called.push("onenternone"); }; - this.onentergreen = function() { this.called.push("onentergreen"); }; - this.onenterred = function() { this.called.push("onenterred"); }; - this.onleavenone = function() { this.called.push("onleavenone"); }; - this.onleavegreen = function() { this.called.push("onleavegreen"); }; - this.onleavered = function() { this.called.push("onleavered"); }; - } - -}); - -//----------------------------------------------------------------------------- - -test("initial state defaults to 'none'", function() { - StateMachine.create({ - target: this, - events: [ - { name: 'panic', from: 'green', to: 'red' }, - { name: 'calm', from: 'red', to: 'green' } - ]}); - equal(this.current, 'none'); - deepEqual(this.called, []); -}); - -//----------------------------------------------------------------------------- - -test("initial state can be specified", function() { - StateMachine.create({ - target: this, - initial: 'green', - events: [ - { name: 'panic', from: 'green', to: 'red' }, - { name: 'calm', from: 'red', to: 'green' } - ]}); - equal(this.current, 'green'); - deepEqual(this.called, [ - "onbeforestartup", - "onbefore(startup)", - "onleavenone", - "onleave(none)", - "onentergreen", - "onenter(green)", - "onchange(none,green)", - "onafterstartup", - "onafter(startup)" - ]); -}); - -//----------------------------------------------------------------------------- - -test("startup event name can be specified", function() { - StateMachine.create({ - target: this, - initial: { state: 'green', event: 'init' }, - events: [ - { name: 'panic', from: 'green', to: 'red' }, - { name: 'calm', from: 'red', to: 'green' } - ]}); - equal(this.current, 'green'); - deepEqual(this.called, [ - "onbeforeinit", - "onbefore(init)", - "onleavenone", - "onleave(none)", - "onentergreen", - "onenter(green)", - "onchange(none,green)", - "onafterinit", - "onafter(init)" - ]); -}); - -//----------------------------------------------------------------------------- - -test("startup event can be deferred", function() { - StateMachine.create({ - target: this, - initial: { state: 'green', event: 'init', defer: true }, - events: [ - { name: 'panic', from: 'green', to: 'red' }, - { name: 'calm', from: 'red', to: 'green' } - ]}); - equal(this.current, 'none'); - deepEqual(this.called, []); - - this.init(); - - equal(this.current, 'green'); - deepEqual(this.called, [ - "onbeforeinit", - "onbefore(init)", - "onleavenone", - "onleave(none)", - "onentergreen", - "onenter(green)", - "onchange(none,green)", - "onafterinit", - "onafter(init)" - ]); -}); - -//----------------------------------------------------------------------------- - - diff --git a/test/transitions.js b/test/transitions.js new file mode 100644 index 0000000..9f37304 --- /dev/null +++ b/test/transitions.js @@ -0,0 +1,223 @@ +import test from 'ava' +import StateMachine from '../src/app' +import LifecycleLogger from './helpers/lifecycle_logger' + +//----------------------------------------------------------------------------- + +test('basic transition from state to state', t => { + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step1', from: 'A', to: 'B' }, + { name: 'step2', from: 'B', to: 'C' }, + { name: 'step3', from: 'C', to: 'D' } + ] + }); + + t.is(fsm.state, 'A') + + fsm.step1(); t.is(fsm.state, 'B') + fsm.step2(); t.is(fsm.state, 'C') + fsm.step3(); t.is(fsm.state, 'D') + +}) + +//----------------------------------------------------------------------------- + +test('multiple transitions with same name', t => { + + var fsm = new StateMachine({ + init: 'hungry', + transitions: [ + { name: 'eat', from: 'hungry', to: 'satisfied' }, + { name: 'eat', from: 'satisfied', to: 'full' }, + { name: 'eat', from: 'full', to: 'sick' }, + { name: 'rest', from: '*', to: 'hungry' } + ] + }); + + t.is(fsm.state, 'hungry') + t.is(fsm.can('eat'), true) + t.is(fsm.can('rest'), true) + + fsm.eat() + + t.is(fsm.state, 'satisfied') + t.is(fsm.can('eat'), true) + t.is(fsm.can('rest'), true) + + fsm.eat() + + t.is(fsm.state, 'full') + t.is(fsm.can('eat'), true) + t.is(fsm.can('rest'), true) + + fsm.eat() + + t.is(fsm.state, 'sick') + t.is(fsm.can('eat'), false) + t.is(fsm.can('rest'), true) + + fsm.rest() + + t.is(fsm.state, 'hungry') + t.is(fsm.can('eat'), true) + t.is(fsm.can('rest'), true) + +}) + +//----------------------------------------------------------------------------- + +test('transitions with multiple from states', t => { + + var fsm = new StateMachine({ + transitions: [ + { name: 'start', from: 'none', to: 'green' }, + { name: 'warn', from: ['green', 'red'], to: 'yellow' }, + { name: 'panic', from: ['green', 'yellow'], to: 'red' }, + { name: 'clear', from: ['red', 'yellow'], to: 'green' } + ] + }); + + t.deepEqual(fsm.allStates(), [ 'none', 'green', 'yellow', 'red' ]) + t.deepEqual(fsm.allTransitions(), [ 'start', 'warn', 'panic', 'clear' ]) + + t.is(fsm.state, 'none') + t.is(fsm.can('start'), true) + t.is(fsm.can('warn'), false) + t.is(fsm.can('panic'), false) + t.is(fsm.can('clear'), false) + t.deepEqual(fsm.transitions(), ['start']) + + fsm.start() + t.is(fsm.state, 'green') + t.is(fsm.can('start'), false) + t.is(fsm.can('warn'), true) + t.is(fsm.can('panic'), true) + t.is(fsm.can('clear'), false) + t.deepEqual(fsm.transitions(), ['warn', 'panic']) + + fsm.warn() + t.is(fsm.state, 'yellow') + t.is(fsm.can('start'), false) + t.is(fsm.can('warn'), false) + t.is(fsm.can('panic'), true) + t.is(fsm.can('clear'), true) + t.deepEqual(fsm.transitions(), ['panic', 'clear']) + + fsm.panic() + t.is(fsm.state, 'red') + t.is(fsm.can('start'), false) + t.is(fsm.can('warn'), true) + t.is(fsm.can('panic'), false) + t.is(fsm.can('clear'), true) + t.deepEqual(fsm.transitions(), ['warn', 'clear']) + + fsm.clear() + t.is(fsm.state, 'green') + t.is(fsm.can('start'), false) + t.is(fsm.can('warn'), true) + t.is(fsm.can('panic'), true) + t.is(fsm.can('clear'), false) + t.deepEqual(fsm.transitions(), ['warn', 'panic']) + +}) + +//------------------------------------------------------------------------------------------------- + +test("transition methods with dash or underscore are camelized", t => { + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'do-with-dash', from: 'A', to: 'B' }, + { name: 'do_with_underscore', from: 'B', to: 'C' }, + { name: 'doAlreadyCamelized', from: 'C', to: 'D' } + ] + }); + + t.is(fsm.state, 'A') + fsm.doWithDash(); t.is(fsm.state, 'B') + fsm.doWithUnderscore(); t.is(fsm.state, 'C') + fsm.doAlreadyCamelized(); t.is(fsm.state, 'D') + +}) + +//------------------------------------------------------------------------------------------------- + +test('conditional transitions', t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: '*', to: function(n) { return this.skip(n) } }, + ], + methods: { + skip: function(amount) { + var code = this.state.charCodeAt(0); + return String.fromCharCode(code + (amount || 1)); + }, + onBeforeTransition: logger, + onBeforeInit: logger, + onBeforeStep: logger, + onLeaveState: logger, + onLeaveNone: logger, + onLeaveA: logger, + onLeaveB: logger, + onLeaveG: logger, + onTransition: logger, + onEnterState: logger, + onEnterNone: logger, + onEnterA: logger, + onEnterB: logger, + onEnterG: logger, + onAfterTransition: logger, + onAfterInit: logger, + onAfterStep: logger + } + }); + + t.is(fsm.state, 'A') + + t.deepEqual(fsm.allStates(), [ 'none', 'A' ]); + + fsm.step(); t.is(fsm.state, 'B'); t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B' ]) + fsm.step(5); t.is(fsm.state, 'G'); t.deepEqual(fsm.allStates(), [ 'none', 'A', 'B', 'G' ]) + + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'init', from: 'none', to: 'A', current: 'none' }, + { event: 'onBeforeInit', transition: 'init', from: 'none', to: 'A', current: 'none' }, + { event: 'onLeaveState', transition: 'init', from: 'none', to: 'A', current: 'none' }, + { event: 'onLeaveNone', transition: 'init', from: 'none', to: 'A', current: 'none' }, + { event: 'onTransition', transition: 'init', from: 'none', to: 'A', current: 'none' }, + { event: 'onEnterState', transition: 'init', from: 'none', to: 'A', current: 'A' }, + { event: 'onEnterA', transition: 'init', from: 'none', to: 'A', current: 'A' }, + { event: 'onAfterTransition', transition: 'init', from: 'none', to: 'A', current: 'A' }, + { event: 'onAfterInit', transition: 'init', from: 'none', to: 'A', current: 'A' }, + + { event: 'onBeforeTransition', transition: 'step', from: 'A', to: 'B', current: 'A' }, + { event: 'onBeforeStep', transition: 'step', from: 'A', to: 'B', current: 'A' }, + { event: 'onLeaveState', transition: 'step', from: 'A', to: 'B', current: 'A' }, + { event: 'onLeaveA', transition: 'step', from: 'A', to: 'B', current: 'A' }, + { event: 'onTransition', transition: 'step', from: 'A', to: 'B', current: 'A' }, + { event: 'onEnterState', transition: 'step', from: 'A', to: 'B', current: 'B' }, + { event: 'onEnterB', transition: 'step', from: 'A', to: 'B', current: 'B' }, + { event: 'onAfterTransition', transition: 'step', from: 'A', to: 'B', current: 'B' }, + { event: 'onAfterStep', transition: 'step', from: 'A', to: 'B', current: 'B' }, + + { event: 'onBeforeTransition', transition: 'step', from: 'B', to: 'G', current: 'B', args: [ 5 ] }, + { event: 'onBeforeStep', transition: 'step', from: 'B', to: 'G', current: 'B', args: [ 5 ] }, + { event: 'onLeaveState', transition: 'step', from: 'B', to: 'G', current: 'B', args: [ 5 ] }, + { event: 'onLeaveB', transition: 'step', from: 'B', to: 'G', current: 'B', args: [ 5 ] }, + { event: 'onTransition', transition: 'step', from: 'B', to: 'G', current: 'B', args: [ 5 ] }, + { event: 'onEnterState', transition: 'step', from: 'B', to: 'G', current: 'G', args: [ 5 ] }, + { event: 'onEnterG', transition: 'step', from: 'B', to: 'G', current: 'G', args: [ 5 ] }, + { event: 'onAfterTransition', transition: 'step', from: 'B', to: 'G', current: 'G', args: [ 5 ] }, + { event: 'onAfterStep', transition: 'step', from: 'B', to: 'G', current: 'G', args: [ 5 ] }, + ]) + +}) + +//------------------------------------------------------------------------------------------------- diff --git a/test/util/camelize.js b/test/util/camelize.js new file mode 100644 index 0000000..9e3bb04 --- /dev/null +++ b/test/util/camelize.js @@ -0,0 +1,14 @@ +import test from 'ava'; +import camelize from '../../src/util/camelize'; + +test('camelize', t => { + t.is(camelize(""), ""); + t.is(camelize("word"), "word"); + t.is(camelize("Word"), "Word"); + t.is(camelize("WORD"), "WORD"); + t.is(camelize("word-with-dash"), "wordWithDash"); + t.is(camelize("word_with_underscore"), "wordWithUnderscore"); + t.is(camelize("word--with--double--dash"), "wordWithDoubleDash"); + t.is(camelize("word_WITH_mixed_CASE"), "wordWITHMixedCASE"); + t.is(camelize("alreadyCamelized"), "alreadyCamelized"); +}); diff --git a/test/util/mixin.js b/test/util/mixin.js new file mode 100644 index 0000000..0fb3561 --- /dev/null +++ b/test/util/mixin.js @@ -0,0 +1,65 @@ +import test from 'ava'; +import mixin from '../../src/util/mixin'; + +//------------------------------------------------------------------------------------------------- + +test('mixin', t => { + + var a = { first: 'Jake', key: 'a' }, + b = { last: 'Gordon', key: 'b' }; + + t.deepEqual(mixin({}, a), { first: 'Jake', key: 'a' }); + t.deepEqual(mixin({}, b), { last: 'Gordon', key: 'b' }); + t.deepEqual(mixin({}, a, b), { first: 'Jake', last: 'Gordon', key: 'b' }); + t.deepEqual(mixin({}, b, a), { first: 'Jake', last: 'Gordon', key: 'a' }); + +}); + +//------------------------------------------------------------------------------------------------- + +test('mixin only mixes in owned properties', t => { + + var MyClass = function(name) { this.name = name } + + MyClass.prototype = { + answer: 42 + } + + var a = new MyClass('a'), + b = new MyClass('b'); + + t.is(a.name, 'a'); + t.is(a.answer, 42); + t.is(b.name, 'b'); + t.is(b.answer, 42); + + t.is(a.hasOwnProperty('name'), true); + t.is(a.hasOwnProperty('answer'), false); + t.is(b.hasOwnProperty('name'), true); + t.is(b.hasOwnProperty('answer'), false); + + t.deepEqual(mixin({}, a), { name: 'a' }); + t.deepEqual(mixin({}, b), { name: 'b' }); + t.deepEqual(mixin({}, a, b), { name: 'b' }); + t.deepEqual(mixin({}, b, a), { name: 'a' }); + + b.answer = 99; + + t.is(a.name, 'a'); + t.is(a.answer, 42); + t.is(b.name, 'b'); + t.is(b.answer, 99); + + t.is(a.hasOwnProperty('name'), true); + t.is(a.hasOwnProperty('answer'), false); + t.is(b.hasOwnProperty('name'), true); + t.is(b.hasOwnProperty('answer'), true); + + t.deepEqual(mixin({}, a), { name: 'a' }); + t.deepEqual(mixin({}, b), { name: 'b', answer: 99 }); + t.deepEqual(mixin({}, a, b), { name: 'b', answer: 99 }); + t.deepEqual(mixin({}, b, a), { name: 'a', answer: 99 }); + +}); + +//------------------------------------------------------------------------------------------------- diff --git a/test/wildcards.js b/test/wildcards.js new file mode 100644 index 0000000..a97b2af --- /dev/null +++ b/test/wildcards.js @@ -0,0 +1,212 @@ +import test from 'ava' +import StateMachine from '../src/app' + +//----------------------------------------------------------------------------- + +test('wildcard :from allows transition from any state', t => { + + var fsm = new StateMachine({ + init: 'stopped', + transitions: [ + { name: 'prepare', from: 'stopped', to: 'ready' }, + { name: 'start', from: 'ready', to: 'running' }, + { name: 'resume', from: 'paused', to: 'running' }, + { name: 'pause', from: 'running', to: 'paused' }, + { name: 'stop', from: '*', to: 'stopped' } + ]}); + + t.is(fsm.state, 'stopped', "initial state should be stopped"); + + fsm.prepare(); t.is(fsm.state, 'ready') + fsm.stop(); t.is(fsm.state, 'stopped') + + fsm.prepare(); t.is(fsm.state, 'ready') + fsm.start(); t.is(fsm.state, 'running') + fsm.stop(); t.is(fsm.state, 'stopped') + + fsm.prepare(); t.is(fsm.state, 'ready') + fsm.start(); t.is(fsm.state, 'running') + fsm.pause(); t.is(fsm.state, 'paused') + fsm.stop(); t.is(fsm.state, 'stopped') + fsm.stop(); t.is(fsm.state, 'stopped') + + t.deepEqual(fsm.transitions(), ["prepare", "stop"], "ensure wildcard transition (stop) is included in available transitions") + fsm.prepare(); t.deepEqual(fsm.transitions(), ["start", "stop"], "ensure wildcard transition (stop) is included in available transitions") + fsm.start(); t.deepEqual(fsm.transitions(), ["pause", "stop"], "ensure wildcard transition (stop) is included in available transitions") + fsm.stop(); t.deepEqual(fsm.transitions(), ["prepare", "stop"], "ensure wildcard transition (stop) is included in available transitions") + +}) + +//----------------------------------------------------------------------------- + +test('missing :from allows transition from any state', t => { + + var fsm = new StateMachine({ + init: 'stopped', + transitions: [ + { name: 'prepare', from: 'stopped', to: 'ready' }, + { name: 'start', from: 'ready', to: 'running' }, + { name: 'resume', from: 'paused', to: 'running' }, + { name: 'pause', from: 'running', to: 'paused' }, + { name: 'stop', /* any from state */ to: 'stopped' } + ]}); + + t.is(fsm.state, 'stopped', "initial state should be stopped") + + fsm.prepare(); t.is(fsm.state, 'ready') + fsm.stop(); t.is(fsm.state, 'stopped') + + fsm.prepare(); t.is(fsm.state, 'ready') + fsm.start(); t.is(fsm.state, 'running') + fsm.stop(); t.is(fsm.state, 'stopped') + + fsm.prepare(); t.is(fsm.state, 'ready') + fsm.start(); t.is(fsm.state, 'running') + fsm.pause(); t.is(fsm.state, 'paused') + fsm.stop(); t.is(fsm.state, 'stopped') + + t.deepEqual(fsm.transitions(), ["prepare", "stop"], "ensure missing :from transition (stop) is included in available transitions") + fsm.prepare(); t.deepEqual(fsm.transitions(), ["start", "stop"], "ensure missing :from transition (stop) is included in available transitions") + fsm.start(); t.deepEqual(fsm.transitions(), ["pause", "stop"], "ensure missing :from transition (stop) is included in available transitions") + fsm.stop(); t.deepEqual(fsm.transitions(), ["prepare", "stop"], "ensure missing :from transition (stop) is included in available transitions") + +}) + +//----------------------------------------------------------------------------- + +test('wildcard :from allows transition to a state that is never declared in any other :from transition ', t => { + + var fsm = new StateMachine({ + transitions: [ + { name: 'step', from: 'none', to: 'mystery' }, // NOTE: 'mystery' is only ever declared in :to, never :from + { name: 'other', from: '*', to: 'complete' } + ] + }); + + t.is(fsm.state, 'none') + t.is(fsm.can('step'), true) + t.is(fsm.can('other'), true) + + fsm.step() + + t.is(fsm.state, 'mystery') + t.is(fsm.can('step'), false) + t.is(fsm.can('other'), true) + +}) + +//----------------------------------------------------------------------------- + +test('wildcard :to allows no-op transitions', t => { + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'stayA', from: 'A', to: '*' }, + { name: 'stayB', from: 'B', to: '*' }, + { name: 'noop', from: '*', to: '*' }, + { name: 'step', from: 'A', to: 'B' } + ] + }); + + t.is(fsm.state, 'A') + t.is(fsm.can('noop'), true) + t.is(fsm.can('step'), true) + t.is(fsm.can('stayA'), true) + t.is(fsm.can('stayB'), false) + + fsm.stayA(); t.is(fsm.state, 'A') + fsm.noop(); t.is(fsm.state, 'A') + + fsm.step(); + + t.is(fsm.state, 'B') + t.is(fsm.can('noop'), true) + t.is(fsm.can('step'), false) + t.is(fsm.can('stayA'), false) + t.is(fsm.can('stayB'), true) + + fsm.stayB(); t.is(fsm.state, 'B') + fsm.noop(); t.is(fsm.state, 'B') + +}) + +//----------------------------------------------------------------------------- + +test('missing :to allows no-op transitions', t => { + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'stayA', from: 'A' /* no-op */ }, + { name: 'stayB', from: 'B' /* no-op */ }, + { name: 'noop', from: '*' /* no-op */ }, + { name: 'step', from: 'A', to: 'B' } + ] + }); + + t.is(fsm.state, 'A') + t.is(fsm.can('noop'), true) + t.is(fsm.can('step'), true) + t.is(fsm.can('stayA'), true) + t.is(fsm.can('stayB'), false) + + fsm.stayA(); t.is(fsm.state, 'A') + fsm.noop(); t.is(fsm.state, 'A') + + fsm.step(); + + t.is(fsm.state, 'B') + t.is(fsm.can('noop'), true) + t.is(fsm.can('step'), false) + t.is(fsm.can('stayA'), false) + t.is(fsm.can('stayB'), true) + + fsm.stayB(); t.is(fsm.state, 'B') + fsm.noop(); t.is(fsm.state, 'B') + +}) + +//----------------------------------------------------------------------------- + +test('no-op transitions with multiple from states', t => { + + var fsm = new StateMachine({ + init: 'A', + transitions: [ + { name: 'step', from: 'A', to: 'B' }, + { name: 'noop1', from: ['A', 'B'] /* no-op */ }, + { name: 'noop2', from: '*' /* no-op */ }, + { name: 'noop3', from: ['A', 'B'], to: '*' }, + { name: 'noop4', from: '*', to: '*' } + ] + }); + + t.is(fsm.state, 'A') + t.is(fsm.can('step'), true) + t.is(fsm.can('noop1'), true) + t.is(fsm.can('noop2'), true) + t.is(fsm.can('noop3'), true) + t.is(fsm.can('noop4'), true) + + fsm.noop1(); t.is(fsm.state, 'A') + fsm.noop2(); t.is(fsm.state, 'A') + fsm.noop3(); t.is(fsm.state, 'A') + fsm.noop4(); t.is(fsm.state, 'A') + + fsm.step(); + t.is(fsm.state, 'B') + t.is(fsm.can('step'), false) + t.is(fsm.can('noop1'), true) + t.is(fsm.can('noop2'), true) + t.is(fsm.can('noop3'), true) + t.is(fsm.can('noop4'), true) + + fsm.noop1(); t.is(fsm.state, 'B') + fsm.noop2(); t.is(fsm.state, 'B') + fsm.noop3(); t.is(fsm.state, 'B') + fsm.noop4(); t.is(fsm.state, 'B') + +}) + +//----------------------------------------------------------------------------- diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..497dba2 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,43 @@ +module.exports = function(env) { + + 'use strict' + + const webpack = require('webpack'), + glob = require('glob'), + path = require('path'), + pascalize = require('pascal-case'), + source = 'src', + output = 'lib', + config = []; + + config.push({ + name: 'state-machine', + library: 'StateMachine', + entry: 'app' + }) + + glob.sync("src/plugin/*.js").forEach(function(plugin) { + const name = path.basename(plugin, '.js'); + config.push({ + library: pascalize('state-machine-' + name), + entry: 'plugin/' + name, + name: name + }) + }); + + return config.map(function(cfg) { + return { + entry: cfg.entry, + resolve: { + modules: [ source ] + }, + output: { + filename: path.join(output, cfg.name + '.js'), + library: cfg.library, + libraryTarget: 'umd', + umdNamedDefine: true + } + } + }); + +} From 73c0ee4c28be8737c793f7c8f5396396b1b8e67f Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 7 Jan 2017 12:14:04 -0800 Subject: [PATCH 70/87] added npm & build badges to readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index acdf35a..bd46ea3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Javascript State Machine +[![NPM version](https://img.shields.io/npm/v/javascript-state-machine.svg?style=flat)](https://www.npmjs.org/package/javascript-state-machine) +[![Build Status](https://travis-ci.org/jakesgordon/javascript-state-machine.svg?branch=master)](https://travis-ci.org/jakesgordon/javascript-state-machine) + A library for finite state machines. ![matter state machine](examples/matter.png) From d7470a73e23d2ab9a281f7feed42bbbed3ce20f8 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 7 Jan 2017 12:17:35 -0800 Subject: [PATCH 71/87] added 'use strict' to test/helpers/lifecycle_logger.js --- test/helpers/lifecycle_logger.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/helpers/lifecycle_logger.js b/test/helpers/lifecycle_logger.js index c74d229..8c47b3f 100644 --- a/test/helpers/lifecycle_logger.js +++ b/test/helpers/lifecycle_logger.js @@ -1,6 +1,8 @@ module.exports = function() { + 'use strict' + let entries = [], logger = function(lifecycle) { var entry = { From b10fcb1b3c9e36f7019f3355517802e23ac95a17 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 7 Jan 2017 12:20:20 -0800 Subject: [PATCH 72/87] ensure tests pass in node v4 --- test/helpers/lifecycle_logger.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/helpers/lifecycle_logger.js b/test/helpers/lifecycle_logger.js index 8c47b3f..fc9eb34 100644 --- a/test/helpers/lifecycle_logger.js +++ b/test/helpers/lifecycle_logger.js @@ -3,7 +3,7 @@ module.exports = function() { 'use strict' - let entries = [], + var entries = [], logger = function(lifecycle) { var entry = { event: lifecycle.event, From 604952845071caa8ef68e6877f80ea180544c3c3 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Tue, 10 Jan 2017 09:49:35 -0800 Subject: [PATCH 73/87] added note to README to indicate this is for v3.0.0-rc.1 --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index bd46ea3..493a709 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,14 @@ # Javascript State Machine +
          +> **IMPORTANT**: This is documentation for pre-release version **3.0.0-rc.1** + +[![Build Status](https://travis-ci.org/jakesgordon/javascript-state-machine.svg?branch=v3)](https://travis-ci.org/jakesgordon/javascript-state-machine) + + A library for finite state machines. From e60a8966ae0bf35d728040c31e3de73047ef3765 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Tue, 10 Jan 2017 09:52:39 -0800 Subject: [PATCH 74/87] install instructions should use explicit pre-release version (for now) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 493a709..20c3a9b 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ In a browser: Using npm: ```shell - npm install --save-dev javascript-state-machine + npm install --save-dev javascript-state-machine@3.0.0-rc.1 # note explicit pre-release version ``` In Node.js: From efb57ea1c1489860c79bb03533677a1406a3e954 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Tue, 10 Jan 2017 09:56:37 -0800 Subject: [PATCH 75/87] ensure using v3.0.0-rc.1 throughout --- RELEASE_NOTES.md | 4 ++-- dist/state-machine.js | 2 +- dist/state-machine.min.js | 2 +- lib/state-machine.js | 2 +- package.json | 2 +- src/app.js | 2 +- test/basics.js | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index ca0b628..5d64d17 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,5 @@ -Version 3.0.0 (ETA - January 2017) ----------------------------------- +Version 3.0.0-rc.1 (January 10 2017) +------------------------------------ **IMPORTANT NOTE**: this version includes **breaking changes** that will require code updates. diff --git a/dist/state-machine.js b/dist/state-machine.js index 6ac51b3..ac1c3d6 100644 --- a/dist/state-machine.js +++ b/dist/state-machine.js @@ -623,7 +623,7 @@ function build(target, config) { //----------------------------------------------------------------------------------------------- -StateMachine.version = '3.0.0'; +StateMachine.version = '3.0.0-rc.1'; StateMachine.factory = factory; StateMachine.apply = apply; StateMachine.defaults = { diff --git a/dist/state-machine.min.js b/dist/state-machine.min.js index ddf671a..0120acd 100644 --- a/dist/state-machine.min.js +++ b/dist/state-machine.min.js @@ -1 +1 @@ -!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define("StateMachine",[],n):"object"==typeof exports?exports.StateMachine=n():t.StateMachine=n()}(this,function(){return function(t){function n(e){if(i[e])return i[e].exports;var s=i[e]={i:e,l:!1,exports:{}};return t[e].call(s.exports,s,s.exports,n),s.l=!0,s.exports}var i={};return n.m=t,n.c=i,n.i=function(t){return t},n.d=function(t,i,e){n.o(t,i)||Object.defineProperty(t,i,{configurable:!1,enumerable:!0,get:e})},n.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(i,"a",i),i},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="",n(n.s=6)}([function(t,n,i){"use strict";t.exports=function(t,n){var i,e,s;for(i=1;i=0:this.state===t},isPending:function(){return this.pending},can:function(t){return!this.isPending()&&!!this.seek(t)},cannot:function(t){return!this.can(t)},allStates:function(){return this.config.allStates()},allTransitions:function(){return this.config.allTransitions()},transitions:function(){return this.config.transitionsFor(this.state)},seek:function(t,n){var i=this.config.defaults.wildcard,e=this.config.transitionFor(this.state,t),s=e&&e.to;return"function"==typeof s?s.apply(this.context,n):s===i?this.state:s},fire:function(t,n){return this.transit(t,this.state,this.seek(t,n),n)},transit:function(t,n,i,e){var s=this.config.lifecycle,r=n!==i;return i?this.isPending()?this.context.onPendingTransition(t,n,i):(this.config.addState(i),this.beginTransit(),e.unshift({transition:t,from:n,to:i,fsm:this.context}),this.observeEvents([this.observersForEvent(s.onBefore.transition),this.observersForEvent(s.onBefore[t]),r?this.observersForEvent(s.onLeave.state):a,r?this.observersForEvent(s.onLeave[n]):a,this.observersForEvent(s.on.transition),r?["doTransit",[this]]:a,r?this.observersForEvent(s.onEnter.state):a,r?this.observersForEvent(s.onEnter[i]):a,r?this.observersForEvent(s.on[i]):a,this.observersForEvent(s.onAfter.transition),this.observersForEvent(s.onAfter[t]),this.observersForEvent(s.on[t])],e)):this.context.onInvalidTransition(t,n,i)},beginTransit:function(){this.pending=!0},endTransit:function(t){return this.pending=!1,t},doTransit:function(t){this.state=t.to},observe:function(t){if(2===t.length){var n={};n[t[0]]=t[1],this.observers.push(n)}else this.observers.push(t[0])},observersForEvent:function(t){for(var n,i=0,e=this.observers.length,s=[];i=0:this.state===t},isPending:function(){return this.pending},can:function(t){return!this.isPending()&&!!this.seek(t)},cannot:function(t){return!this.can(t)},allStates:function(){return this.config.allStates()},allTransitions:function(){return this.config.allTransitions()},transitions:function(){return this.config.transitionsFor(this.state)},seek:function(t,n){var i=this.config.defaults.wildcard,e=this.config.transitionFor(this.state,t),s=e&&e.to;return"function"==typeof s?s.apply(this.context,n):s===i?this.state:s},fire:function(t,n){return this.transit(t,this.state,this.seek(t,n),n)},transit:function(t,n,i,e){var s=this.config.lifecycle,r=n!==i;return i?this.isPending()?this.context.onPendingTransition(t,n,i):(this.config.addState(i),this.beginTransit(),e.unshift({transition:t,from:n,to:i,fsm:this.context}),this.observeEvents([this.observersForEvent(s.onBefore.transition),this.observersForEvent(s.onBefore[t]),r?this.observersForEvent(s.onLeave.state):a,r?this.observersForEvent(s.onLeave[n]):a,this.observersForEvent(s.on.transition),r?["doTransit",[this]]:a,r?this.observersForEvent(s.onEnter.state):a,r?this.observersForEvent(s.onEnter[i]):a,r?this.observersForEvent(s.on[i]):a,this.observersForEvent(s.onAfter.transition),this.observersForEvent(s.onAfter[t]),this.observersForEvent(s.on[t])],e)):this.context.onInvalidTransition(t,n,i)},beginTransit:function(){this.pending=!0},endTransit:function(t){return this.pending=!1,t},doTransit:function(t){this.state=t.to},observe:function(t){if(2===t.length){var n={};n[t[0]]=t[1],this.observers.push(n)}else this.observers.push(t[0])},observersForEvent:function(t){for(var n,i=0,e=this.observers.length,s=[];i { - t.is(StateMachine.version, '3.0.0'); + t.is(StateMachine.version, '3.0.0-rc.1'); }); //------------------------------------------------------------------------------------------------- From c0333e624a81ad8c43248009648fb31d9ddfd7c8 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Tue, 10 Jan 2017 10:35:12 -0800 Subject: [PATCH 76/87] add link to v3 documentation from the v2 readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2b71e5e..6a26653 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ [![NPM version](https://img.shields.io/npm/v/javascript-state-machine.svg?style=flat)](https://www.npmjs.org/package/javascript-state-machine) [![Build Status](https://travis-ci.org/jakesgordon/javascript-state-machine.svg?branch=master)](https://travis-ci.org/jakesgordon/javascript-state-machine) +> **NOTE**: for a a sneak preview of what's coming next, [read the documentation for v3.0.0-rc.1](https://github.com/jakesgordon/javascript-state-machine/tree/v3) + A standalone library for finite state machines. # Download From a7e5a2a659498b8a5a2fe0d85be25b0849d5a2a1 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Tue, 10 Jan 2017 12:12:00 -0800 Subject: [PATCH 77/87] add (undocumented) config flag to opt in to be notified of enter/leave lifecycle events even if the transition does NOT change state --- .ackrc | 4 ++ dist/state-machine-visualize.js | 4 +- dist/state-machine-visualize.min.js | 2 +- dist/state-machine.js | 4 +- dist/state-machine.min.js | 2 +- lib/state-machine.js | 4 +- lib/visualize.js | 4 +- src/config.js | 2 +- src/jsm.js | 2 +- src/plugin/visualize.js | 4 +- test/transitions.js | 88 +++++++++++++++++++++++++++++ 11 files changed, 106 insertions(+), 14 deletions(-) create mode 100644 .ackrc diff --git a/.ackrc b/.ackrc new file mode 100644 index 0000000..df1e9a8 --- /dev/null +++ b/.ackrc @@ -0,0 +1,4 @@ +--ignore-dir=coverage +--ignore-dir=node_modules +--ignore-dir=.nyc_output + diff --git a/dist/state-machine-visualize.js b/dist/state-machine-visualize.js index 531bf03..6cfdd7e 100644 --- a/dist/state-machine-visualize.js +++ b/dist/state-machine-visualize.js @@ -167,12 +167,12 @@ dotcfg.states = function(config, options) { dotcfg.transitions = function(config, options) { var n, max, transition, init = config.init, - transitions = config.source.transitions || [], // easier to visualize using the ORIGINAL transition declarations rather than our run-time mapping + transitions = config.options.transitions || [], // easier to visualize using the ORIGINAL transition declarations rather than our run-time mapping output = []; if (options.init && init.active) dotcfg.transition(init.name, init.from, init.to, init.dot, config, options, output) for (n = 0, max = transitions.length ; n < max ; n++) { - transition = config.source.transitions[n] + transition = config.options.transitions[n] dotcfg.transition(transition.name, transition.from, transition.to, transition.dot, config, options, output) } return output diff --git a/dist/state-machine-visualize.min.js b/dist/state-machine-visualize.min.js index 28a9d5a..6e5e296 100644 --- a/dist/state-machine-visualize.min.js +++ b/dist/state-machine-visualize.min.js @@ -1 +1 @@ -!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define("StateMachineVisualize",[],n):"object"==typeof exports?exports.StateMachineVisualize=n():t.StateMachineVisualize=n()}(this,function(){return function(t){function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var e={};return n.m=t,n.c=e,n.i=function(t){return t},n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="",n(n.s=1)}([function(t,n,e){"use strict";t.exports=function(t,n){var e,r,o;for(e=1;e0&&(u.states=s),a&&a.length>0&&(u.transitions=a),u}function i(t){return" "+t+" "}function s(t){return'"'+t+'"'}function a(t){t=t||{};var n,e,r=t.name||"fsm",o=t.states||[],i=t.transitions||[],u=t.rankdir,f=[];for(f.push("digraph "+s(r)+" {"),u&&f.push(" rankdir="+u+";"),n=0,e=o.length;n "+s(t.to)+a.edge.attr(t)+";"},a.edge.attr=function(t){var n,e,r,o=Object.keys(t).sort(),i=[];for(n=0,e=o.length;n0?" [ "+i.join(" ; ")+" ]":""},r.dotcfg=o,r.dotify=a,t.exports=r}])}); \ No newline at end of file +!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define("StateMachineVisualize",[],n):"object"==typeof exports?exports.StateMachineVisualize=n():t.StateMachineVisualize=n()}(this,function(){return function(t){function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var e={};return n.m=t,n.c=e,n.i=function(t){return t},n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="",n(n.s=1)}([function(t,n,e){"use strict";t.exports=function(t,n){var e,r,o;for(e=1;e0&&(u.states=s),a&&a.length>0&&(u.transitions=a),u}function i(t){return" "+t+" "}function s(t){return'"'+t+'"'}function a(t){t=t||{};var n,e,r=t.name||"fsm",o=t.states||[],i=t.transitions||[],u=t.rankdir,f=[];for(f.push("digraph "+s(r)+" {"),u&&f.push(" rankdir="+u+";"),n=0,e=o.length;n "+s(t.to)+a.edge.attr(t)+";"},a.edge.attr=function(t){var n,e,r,o=Object.keys(t).sort(),i=[];for(n=0,e=o.length;n0?" [ "+i.join(" ; ")+" ]":""},r.dotcfg=o,r.dotify=a,t.exports=r}])}); \ No newline at end of file diff --git a/dist/state-machine.js b/dist/state-machine.js index ac1c3d6..152edfa 100644 --- a/dist/state-machine.js +++ b/dist/state-machine.js @@ -177,7 +177,7 @@ function Config(options, StateMachine) { options = options || {}; - this.source = options; // preserving original options helps with visualize plugin + this.options = options; // preserving original options can be useful (e.g visualize plugin) this.defaults = StateMachine.defaults; this.states = []; this.transitions = []; @@ -404,7 +404,7 @@ mixin(JSM.prototype, { transit: function(transition, from, to, args) { var lifecycle = this.config.lifecycle, - changed = from !== to; + changed = this.config.options.observeUnchangedState || (from !== to); if (!to) return this.context.onInvalidTransition(transition, from, to); diff --git a/dist/state-machine.min.js b/dist/state-machine.min.js index 0120acd..38f2d60 100644 --- a/dist/state-machine.min.js +++ b/dist/state-machine.min.js @@ -1 +1 @@ -!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define("StateMachine",[],n):"object"==typeof exports?exports.StateMachine=n():t.StateMachine=n()}(this,function(){return function(t){function n(e){if(i[e])return i[e].exports;var s=i[e]={i:e,l:!1,exports:{}};return t[e].call(s.exports,s,s.exports,n),s.l=!0,s.exports}var i={};return n.m=t,n.c=i,n.i=function(t){return t},n.d=function(t,i,e){n.o(t,i)||Object.defineProperty(t,i,{configurable:!1,enumerable:!0,get:e})},n.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(i,"a",i),i},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="",n(n.s=6)}([function(t,n,i){"use strict";t.exports=function(t,n){var i,e,s;for(i=1;i=0:this.state===t},isPending:function(){return this.pending},can:function(t){return!this.isPending()&&!!this.seek(t)},cannot:function(t){return!this.can(t)},allStates:function(){return this.config.allStates()},allTransitions:function(){return this.config.allTransitions()},transitions:function(){return this.config.transitionsFor(this.state)},seek:function(t,n){var i=this.config.defaults.wildcard,e=this.config.transitionFor(this.state,t),s=e&&e.to;return"function"==typeof s?s.apply(this.context,n):s===i?this.state:s},fire:function(t,n){return this.transit(t,this.state,this.seek(t,n),n)},transit:function(t,n,i,e){var s=this.config.lifecycle,r=n!==i;return i?this.isPending()?this.context.onPendingTransition(t,n,i):(this.config.addState(i),this.beginTransit(),e.unshift({transition:t,from:n,to:i,fsm:this.context}),this.observeEvents([this.observersForEvent(s.onBefore.transition),this.observersForEvent(s.onBefore[t]),r?this.observersForEvent(s.onLeave.state):a,r?this.observersForEvent(s.onLeave[n]):a,this.observersForEvent(s.on.transition),r?["doTransit",[this]]:a,r?this.observersForEvent(s.onEnter.state):a,r?this.observersForEvent(s.onEnter[i]):a,r?this.observersForEvent(s.on[i]):a,this.observersForEvent(s.onAfter.transition),this.observersForEvent(s.onAfter[t]),this.observersForEvent(s.on[t])],e)):this.context.onInvalidTransition(t,n,i)},beginTransit:function(){this.pending=!0},endTransit:function(t){return this.pending=!1,t},doTransit:function(t){this.state=t.to},observe:function(t){if(2===t.length){var n={};n[t[0]]=t[1],this.observers.push(n)}else this.observers.push(t[0])},observersForEvent:function(t){for(var n,i=0,e=this.observers.length,s=[];i=0:this.state===t},isPending:function(){return this.pending},can:function(t){return!this.isPending()&&!!this.seek(t)},cannot:function(t){return!this.can(t)},allStates:function(){return this.config.allStates()},allTransitions:function(){return this.config.allTransitions()},transitions:function(){return this.config.transitionsFor(this.state)},seek:function(t,n){var i=this.config.defaults.wildcard,e=this.config.transitionFor(this.state,t),s=e&&e.to;return"function"==typeof s?s.apply(this.context,n):s===i?this.state:s},fire:function(t,n){return this.transit(t,this.state,this.seek(t,n),n)},transit:function(t,n,i,e){var s=this.config.lifecycle,r=this.config.options.observeUnchangedState||n!==i;return i?this.isPending()?this.context.onPendingTransition(t,n,i):(this.config.addState(i),this.beginTransit(),e.unshift({transition:t,from:n,to:i,fsm:this.context}),this.observeEvents([this.observersForEvent(s.onBefore.transition),this.observersForEvent(s.onBefore[t]),r?this.observersForEvent(s.onLeave.state):a,r?this.observersForEvent(s.onLeave[n]):a,this.observersForEvent(s.on.transition),r?["doTransit",[this]]:a,r?this.observersForEvent(s.onEnter.state):a,r?this.observersForEvent(s.onEnter[i]):a,r?this.observersForEvent(s.on[i]):a,this.observersForEvent(s.onAfter.transition),this.observersForEvent(s.onAfter[t]),this.observersForEvent(s.on[t])],e)):this.context.onInvalidTransition(t,n,i)},beginTransit:function(){this.pending=!0},endTransit:function(t){return this.pending=!1,t},doTransit:function(t){this.state=t.to},observe:function(t){if(2===t.length){var n={};n[t[0]]=t[1],this.observers.push(n)}else this.observers.push(t[0])},observersForEvent:function(t){for(var n,i=0,e=this.observers.length,s=[];i { //------------------------------------------------------------------------------------------------- +test("transitions that dont change state, dont trigger enter/leave lifecycle events", t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + transitions: [ + { name: 'noop', from: 'none', to: 'none' } + ], + methods: { + onBeforeTransition: logger, + onBeforeNoop: logger, + onLeaveState: logger, + onLeaveNone: logger, + onTransition: logger, + onEnterState: logger, + onEnterNone: logger, + onNone: logger, + onAfterTransition: logger, + onAfterNoop: logger, + onNoop: logger + } + }) + + t.is(fsm.state, 'none') + t.deepEqual(logger.log, []) + + fsm.noop() + + t.is(fsm.state, 'none') + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onBeforeNoop', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onTransition', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onAfterTransition', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onAfterNoop', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onNoop', transition: 'noop', from: 'none', to: 'none', current: 'none' } + ]) + +}) + +//------------------------------------------------------------------------------------------------- + +test("transitions that dont change state, can be configured to trigger enter/leave lifecycle events", t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + observeUnchangedState: true, + transitions: [ + { name: 'noop', from: 'none', to: 'none' } + ], + methods: { + onBeforeTransition: logger, + onBeforeNoop: logger, + onLeaveState: logger, + onLeaveNone: logger, + onTransition: logger, + onEnterState: logger, + onEnterNone: logger, + onNone: logger, + onAfterTransition: logger, + onAfterNoop: logger, + onNoop: logger + } + }) + + t.is(fsm.state, 'none') + t.deepEqual(logger.log, []) + + fsm.noop() + + t.is(fsm.state, 'none') + t.deepEqual(logger.log, [ + { event: 'onBeforeTransition', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onBeforeNoop', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onLeaveState', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onLeaveNone', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onTransition', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onEnterState', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onEnterNone', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onNone', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onAfterTransition', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onAfterNoop', transition: 'noop', from: 'none', to: 'none', current: 'none' }, + { event: 'onNoop', transition: 'noop', from: 'none', to: 'none', current: 'none' } + ]) + +}) + +//------------------------------------------------------------------------------------------------- + test("transition methods with dash or underscore are camelized", t => { var fsm = new StateMachine({ From c1bf918223a545399de42ab185cb7b1c18c5e064 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 10 Jun 2017 11:28:12 -0700 Subject: [PATCH 78/87] Fixed issue #107 - lifecycle event name breaks for all uppercase state/transition. --- package-lock.json | 4644 +++++++++++++++++++++++++++++++++++++++++ src/config.js | 22 +- src/plugin/history.js | 10 +- src/util/camelize.js | 30 +- test/lifecycle.js | 50 + test/util/camelize.js | 8 +- 6 files changed, 4741 insertions(+), 23 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ce5765a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4644 @@ +{ + "name": "javascript-state-machine", + "version": "3.0.0-rc.1", + "lockfileVersion": 1, + "dependencies": { + "acorn": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.0.3.tgz", + "integrity": "sha1-xGDfCEkUY/AozLguqzcwvwEIez0=", + "dev": true + }, + "acorn-dynamic-import": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", + "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", + "dev": true, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true + } + } + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true + }, + "ansi-align": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-1.1.0.tgz", + "integrity": "sha1-LwwWWIKXOa3V67FeawxuNCPwFro=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "anymatch": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz", + "integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc=", + "dev": true + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true + }, + "arr-exclude": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/arr-exclude/-/arr-exclude-1.0.0.tgz", + "integrity": "sha1-38fC5VKicHI8zaBM8xKMjL/lxjE=", + "dev": true + }, + "arr-flatten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.3.tgz", + "integrity": "sha1-onTthawIhJtr14R8RYB0XcUa37E=", + "dev": true + }, + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asn1.js": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz", + "integrity": "sha1-SLokC0WpKA6UdImQull9IWYX/UA=", + "dev": true + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true + }, + "async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.4.1.tgz", + "integrity": "sha1-YqVrJ5yYoR0JhwlqAcw+6463u9c=", + "dev": true + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "auto-bind": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-0.1.0.tgz", + "integrity": "sha1-einvyMI4jT1XjgL8LfUxyB/8HuE=", + "dev": true + }, + "ava": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/ava/-/ava-0.17.0.tgz", + "integrity": "sha1-NZ4qiWFoAe8Dkpw88QqdT45FHQI=", + "dev": true + }, + "ava-files": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ava-files/-/ava-files-0.2.0.tgz", + "integrity": "sha1-x7i24uDOpjtXpuJ+DbFFx8Gc/iA=", + "dev": true + }, + "ava-init": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ava-init/-/ava-init-0.1.6.tgz", + "integrity": "sha1-7xntCyS2vzWdrW+63xoF2DY5XJE=", + "dev": true + }, + "babel-code-frame": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", + "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", + "dev": true + }, + "babel-core": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.25.0.tgz", + "integrity": "sha1-fdQrBGPHQunVKW3rPsZ6kyLa1yk=", + "dev": true + }, + "babel-generator": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.25.0.tgz", + "integrity": "sha1-M6GvcNXyiQrrRlpKd5PB32qeqfw=", + "dev": true + }, + "babel-helper-bindify-decorators": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz", + "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=", + "dev": true + }, + "babel-helper-builder-binary-assignment-operator-visitor": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", + "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", + "dev": true + }, + "babel-helper-call-delegate": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", + "dev": true + }, + "babel-helper-define-map": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.24.1.tgz", + "integrity": "sha1-epdH8ljYlH0y1RX2qhx70CIEoIA=", + "dev": true + }, + "babel-helper-explode-assignable-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", + "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", + "dev": true + }, + "babel-helper-explode-class": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz", + "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=", + "dev": true + }, + "babel-helper-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", + "dev": true + }, + "babel-helper-get-function-arity": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", + "dev": true + }, + "babel-helper-hoist-variables": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", + "dev": true + }, + "babel-helper-optimise-call-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", + "dev": true + }, + "babel-helper-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz", + "integrity": "sha1-024i+rEAjXnYhkjjIRaGgShFbOg=", + "dev": true + }, + "babel-helper-remap-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", + "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", + "dev": true + }, + "babel-helper-replace-supers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", + "dev": true + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true + }, + "babel-plugin-ava-throws-helper": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-ava-throws-helper/-/babel-plugin-ava-throws-helper-0.1.0.tgz", + "integrity": "sha1-lREHcIoSIIAmv4ykzvGKh7ybDP4=", + "dev": true + }, + "babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", + "dev": true + }, + "babel-plugin-detective": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-detective/-/babel-plugin-detective-2.0.0.tgz", + "integrity": "sha1-bmQug8IqM1J5dU6+LXVNJjX0nxM=", + "dev": true + }, + "babel-plugin-espower": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/babel-plugin-espower/-/babel-plugin-espower-2.3.2.tgz", + "integrity": "sha1-VRa4/NsmyfDh2BYHSfbkxl5xJx4=", + "dev": true + }, + "babel-plugin-syntax-async-functions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", + "dev": true + }, + "babel-plugin-syntax-async-generators": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz", + "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=", + "dev": true + }, + "babel-plugin-syntax-class-properties": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", + "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", + "dev": true + }, + "babel-plugin-syntax-decorators": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz", + "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=", + "dev": true + }, + "babel-plugin-syntax-dynamic-import": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", + "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", + "dev": true + }, + "babel-plugin-syntax-exponentiation-operator": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", + "dev": true + }, + "babel-plugin-syntax-object-rest-spread": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", + "dev": true + }, + "babel-plugin-syntax-trailing-function-commas": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", + "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", + "dev": true + }, + "babel-plugin-transform-async-generator-functions": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz", + "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", + "dev": true + }, + "babel-plugin-transform-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", + "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", + "dev": true + }, + "babel-plugin-transform-class-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", + "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", + "dev": true + }, + "babel-plugin-transform-decorators": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz", + "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=", + "dev": true + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", + "dev": true + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", + "dev": true + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz", + "integrity": "sha1-dsKV3DpHQbFmWt/TFnIV3P8ypXY=", + "dev": true + }, + "babel-plugin-transform-es2015-classes": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", + "dev": true + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", + "dev": true + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", + "dev": true + }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", + "dev": true + }, + "babel-plugin-transform-es2015-for-of": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", + "dev": true + }, + "babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", + "dev": true + }, + "babel-plugin-transform-es2015-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", + "dev": true + }, + "babel-plugin-transform-es2015-modules-amd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", + "dev": true + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz", + "integrity": "sha1-0+MQtA72ZKNmIiAAl8bUQCmPK/4=", + "dev": true + }, + "babel-plugin-transform-es2015-modules-systemjs": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", + "dev": true + }, + "babel-plugin-transform-es2015-modules-umd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", + "dev": true + }, + "babel-plugin-transform-es2015-object-super": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "dev": true + }, + "babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", + "dev": true + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", + "dev": true + }, + "babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "dev": true + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", + "dev": true + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "dev": true + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", + "dev": true + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", + "dev": true + }, + "babel-plugin-transform-exponentiation-operator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", + "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", + "dev": true + }, + "babel-plugin-transform-object-rest-spread": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz", + "integrity": "sha1-h11ryb52HFiirj/u5dxIldjH+SE=", + "dev": true + }, + "babel-plugin-transform-regenerator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz", + "integrity": "sha1-uNowWtQ8PJm0hI5P5AN7dw0jxBg=", + "dev": true + }, + "babel-plugin-transform-runtime": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz", + "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", + "dev": true + }, + "babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", + "dev": true + }, + "babel-preset-es2015": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", + "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", + "dev": true + }, + "babel-preset-es2015-node4": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/babel-preset-es2015-node4/-/babel-preset-es2015-node4-2.1.1.tgz", + "integrity": "sha1-4x8pCFm1hhnIz6JB0bC8kA+UHNs=", + "dev": true + }, + "babel-preset-stage-2": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz", + "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=", + "dev": true + }, + "babel-preset-stage-3": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz", + "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", + "dev": true + }, + "babel-register": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.24.1.tgz", + "integrity": "sha1-fhDhOi9xBlvfrVoXh7pFvKbe118=", + "dev": true + }, + "babel-runtime": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=", + "dev": true + }, + "babel-template": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz", + "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE=", + "dev": true + }, + "babel-traverse": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", + "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE=", + "dev": true + }, + "babel-types": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz", + "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4=", + "dev": true + }, + "babylon": { + "version": "6.17.3", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.3.tgz", + "integrity": "sha512-mq0x3HCAGGmQyZXviOVe5TRsw37Ijy3D43jCqt/9WVf+onx2dUgW3PosnqCbScAFhRO9DGs8nxoMzU0iiosMqQ==", + "dev": true + }, + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + }, + "base64-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz", + "integrity": "sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=", + "dev": true + }, + "big.js": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz", + "integrity": "sha1-TK2iGTZS6zyp7I5VyQFWacmAaXg=", + "dev": true + }, + "binary-extensions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.8.0.tgz", + "integrity": "sha1-SOyNFt9Dd+rl+liEaCSAr02Vx3Q=", + "dev": true + }, + "bluebird": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", + "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=", + "dev": true + }, + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=", + "dev": true + }, + "boxen": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-0.6.0.tgz", + "integrity": "sha1-g2TUJIrDT/DvGy8r9JpsYM4NgbY=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", + "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=", + "dev": true + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.0.6.tgz", + "integrity": "sha1-Xncl297x/Vkw1OurSFZ85FHEigo=", + "dev": true + }, + "browserify-cipher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", + "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", + "dev": true + }, + "browserify-des": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", + "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", + "dev": true + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true + }, + "browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true + }, + "buf-compare": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buf-compare/-/buf-compare-1.0.1.tgz", + "integrity": "sha1-/vKNqLgROgoNtEMLC2Rntpcws0o=", + "dev": true + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "caching-transform": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-1.0.1.tgz", + "integrity": "sha1-bb2y8g+Nj7znnz6U6dF0Lc31wKE=", + "dev": true + }, + "call-matcher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-matcher/-/call-matcher-1.0.1.tgz", + "integrity": "sha1-UTTQd5hPcSpU2tPL9i3ijc5BbKg=", + "dev": true + }, + "call-signature": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/call-signature/-/call-signature-0.0.2.tgz", + "integrity": "sha1-qEq8glpV70yysCi9dOIFpluaSZY=", + "dev": true + }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true + }, + "capture-stack-trace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", + "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", + "dev": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true + }, + "ci-info": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.0.0.tgz", + "integrity": "sha1-3FKF8rTiUYIWg2gcOBwziPRuxTQ=", + "dev": true + }, + "cipher-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.3.tgz", + "integrity": "sha1-7qvxlEGc6QDaMBjCB9IS8qbfCgc=", + "dev": true + }, + "clean-yaml-object": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz", + "integrity": "sha1-Y/sRDcLOGoTcIfbZM0h20BCui2g=", + "dev": true + }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", + "dev": true + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true + }, + "cli-spinners": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-0.1.2.tgz", + "integrity": "sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=", + "dev": true + }, + "cli-truncate": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", + "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", + "dev": true + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "co-with-promise": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co-with-promise/-/co-with-promise-4.6.0.tgz", + "integrity": "sha1-QT59tvWJOmC5Qs9JLEvsk9tBWrc=", + "dev": true, + "dependencies": { + "pinkie": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz", + "integrity": "sha1-Wkfyi6EBXQIBvae/DzWOR77Ix+Q=", + "dev": true + }, + "pinkie-promise": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz", + "integrity": "sha1-0dpn9UglY7t89X8oauKCLs+/NnA=", + "dev": true + } + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "common-path-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-1.0.0.tgz", + "integrity": "sha1-zVL28HEuC6q5fW+XModPIvR3UsA=", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "configstore": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-2.1.0.tgz", + "integrity": "sha1-c3o6cDbpiGECqmCZ5HuzOrGroaE=", + "dev": true + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "convert-source-map": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", + "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=", + "dev": true + }, + "core-assert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/core-assert/-/core-assert-0.2.1.tgz", + "integrity": "sha1-+F4s+b/tKPdzzIs/pcW2m9wC/j8=", + "dev": true + }, + "core-js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "create-ecdh": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", + "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", + "dev": true + }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "dev": true + }, + "create-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", + "dev": true + }, + "create-hmac": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", + "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", + "dev": true + }, + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "dev": true + }, + "crypto-browserify": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.0.tgz", + "integrity": "sha1-NlKgkGq5sqfgw85mpAjpV6JIVSI=", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "date-time": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/date-time/-/date-time-0.1.1.tgz", + "integrity": "sha1-7S9tk9l5DOL9ZtW1/z7dW7y/Owc=", + "dev": true + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "deep-extend": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "dev": true + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", + "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", + "dev": true + }, + "domain-browser": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "dev": true + }, + "dot-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", + "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", + "dev": true + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true + }, + "eastasianwidth": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.1.1.tgz", + "integrity": "sha1-RNZW3p2kFWlEZzNTZfsxR7hXK3w=", + "dev": true + }, + "elliptic": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", + "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", + "dev": true + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "empower-core": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/empower-core/-/empower-core-0.6.2.tgz", + "integrity": "sha1-Wt71ZgiOMfuoC6CjbfR9cJQWkUQ=", + "dev": true + }, + "enhanced-resolve": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.1.0.tgz", + "integrity": "sha1-n0tib1dyRe3PSyrYPYbhf09CHew=", + "dev": true + }, + "errno": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz", + "integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=", + "dev": true + }, + "error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "espower-location-detector": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/espower-location-detector/-/espower-location-detector-1.0.0.tgz", + "integrity": "sha1-oXt+zFnTDheeK+9z+0E3cEyzMbU=", + "dev": true + }, + "espurify": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/espurify/-/espurify-1.7.0.tgz", + "integrity": "sha1-HFz2y8zDLm9jk4C9T5kfq5up0iY=", + "dev": true + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.0.tgz", + "integrity": "sha1-SXtmrZ/vZc18CKYYCCS6FHa2blM=", + "dev": true + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fill-range": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "dev": true + }, + "filled-array": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filled-array/-/filled-array-1.1.0.tgz", + "integrity": "sha1-w8T2xmO5I0WamqKZEtLQMfFQf4Q=", + "dev": true + }, + "find-cache-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", + "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", + "dev": true + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true + }, + "fn-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fn-name/-/fn-name-2.0.1.tgz", + "integrity": "sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc=", + "dev": true + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true + }, + "fs-sync": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fs-sync/-/fs-sync-1.0.4.tgz", + "integrity": "sha1-L5Tq3jGGLsCp8zocJUbfsaPz0a4=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.1.tgz", + "integrity": "sha1-8Z/Sj0Pur3YWgOUZogPE0LPTGv8=", + "dev": true, + "optional": true, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "asn1": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true, + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "dev": true + }, + "boom": { + "version": "2.10.1", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "buffer-shims": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "caseless": { + "version": "0.11.0", + "bundled": true, + "dev": true, + "optional": true + }, + "chalk": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "commander": { + "version": "2.9.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "debug": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "deep-extend": { + "version": "0.4.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "extend": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "fstream": { + "version": "1.0.10", + "bundled": true, + "dev": true + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.3", + "bundled": true, + "dev": true, + "optional": true + }, + "generate-function": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "generate-object-property": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "getpass": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.1", + "bundled": true, + "dev": true + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "graceful-readlink": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "har-validator": { + "version": "2.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "has-ansi": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "dev": true, + "optional": true + }, + "hoek": { + "version": "2.16.3", + "bundled": true, + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-my-json-valid": { + "version": "2.15.0", + "bundled": true, + "dev": true, + "optional": true + }, + "is-property": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "jsonpointer": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "jsprim": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "optional": true + }, + "mime-db": { + "version": "1.26.0", + "bundled": true, + "dev": true + }, + "mime-types": { + "version": "2.1.14", + "bundled": true, + "dev": true + }, + "minimatch": { + "version": "3.0.3", + "bundled": true, + "dev": true + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true + }, + "ms": { + "version": "0.7.1", + "bundled": true, + "dev": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.33", + "bundled": true, + "dev": true, + "optional": true + }, + "nopt": { + "version": "3.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npmlog": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "optional": true + }, + "pinkie-promise": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true, + "dev": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true + }, + "qs": { + "version": "6.3.1", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "optional": true, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.2", + "bundled": true, + "dev": true, + "optional": true + }, + "request": { + "version": "2.79.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rimraf": { + "version": "2.5.4", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "dev": true, + "optional": true + }, + "sshpk": { + "version": "1.10.2", + "bundled": true, + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "stringstream": { + "version": "0.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "supports-color": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "dev": true + }, + "tar-pack": { + "version": "3.3.0", + "bundled": true, + "dev": true, + "optional": true, + "dependencies": { + "once": { + "version": "1.3.3", + "bundled": true, + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "2.1.5", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "tough-cookie": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "optional": true + }, + "tunnel-agent": { + "version": "0.4.3", + "bundled": true, + "dev": true, + "optional": true + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "dev": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "uuid": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "xtend": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "dev": true + }, + "get-port": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-2.1.0.tgz", + "integrity": "sha1-h4P53OvR7qSVozThpqJR54iHqxo=", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true + }, + "got": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz", + "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=", + "dev": true + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true + }, + "has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", + "dev": true + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "hash-base": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", + "dev": true + }, + "hash.js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz", + "integrity": "sha1-EzL/ABVsCg/92CNgE9B7d6BFFXM=", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true + }, + "hosted-git-info": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.4.2.tgz", + "integrity": "sha1-AHa59GonBQbduq6lZJaJdGBhKmc=", + "dev": true + }, + "https-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", + "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.17.tgz", + "integrity": "sha1-T9qjs4rLwsAxsEXQ7c3+HsqxjI0=", + "dev": true + }, + "ieee754": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", + "dev": true + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", + "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", + "dev": true + }, + "interpret": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz", + "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=", + "dev": true + }, + "invariant": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", + "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", + "dev": true + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "irregular-plurals": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.2.0.tgz", + "integrity": "sha1-OPKZg0uowAwwvpxVThNyaXUv86w=", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true + }, + "is-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", + "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true + }, + "is-ci": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.0.10.tgz", + "integrity": "sha1-9zkzayYyNlBhqdSCcM1WrjNpMY4=", + "dev": true + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true + }, + "is-error": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-error/-/is-error-2.2.1.tgz", + "integrity": "sha1-aEqW2EB2V3yY9M20DG0mpRI78Zw=", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true + }, + "is-generator-fn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz", + "integrity": "sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "dev": true + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-observable": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-0.2.0.tgz", + "integrity": "sha1-s2ExHYPG5dcmyr9eJQsCNxBvWuI=", + "dev": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "dev": true + }, + "is-retry-allowed": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-url": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.2.tgz", + "integrity": "sha1-SYkFpZO/R8wtnn9zg3K792lsfyY=", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true + }, + "js-tokens": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz", + "integrity": "sha1-COnxMkhKLEWjCQfp3E1VZ7fxFNc=", + "dev": true + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-loader": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.4.tgz", + "integrity": "sha1-i6oTZaYy9Yo8RtIBdfxgAsluN94=", + "dev": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true + }, + "last-line-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/last-line-stream/-/last-line-stream-1.0.0.tgz", + "integrity": "sha1-0bZNafhv8kry0EiDos7uFFIKVgA=", + "dev": true + }, + "latest-version": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-2.0.0.tgz", + "integrity": "sha1-VvjWE5YghHuAF/jx9NeOIRMkFos=", + "dev": true + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "lazy-req": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/lazy-req/-/lazy-req-1.1.0.tgz", + "integrity": "sha1-va6+rTD42CQDnODOFJ1Nqge6H6w=", + "dev": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true + }, + "loader-runner": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", + "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=", + "dev": true + }, + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=", + "dev": true + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", + "dev": true + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loose-envify": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", + "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "dev": true + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true + }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, + "lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", + "dev": true + }, + "lru-cache": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.0.tgz", + "integrity": "sha512-aHGs865JXz6bkB4AHL+3AhyvTFKL3iZamKVWjIUKnXOXyasJvqPK8WAjOnAQKQZVpeXDVz19u1DD0r/12bWAdQ==", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "matcher": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-0.1.2.tgz", + "integrity": "sha1-7yDL3mTCTFDMYa9bg+4LG4/wAQE=", + "dev": true + }, + "max-timeout": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/max-timeout/-/max-timeout-1.0.0.tgz", + "integrity": "sha1-to9povmeC0dv1Msj4gWcp1BxXh8=", + "dev": true + }, + "md5-hex": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-1.3.0.tgz", + "integrity": "sha1-0sSv6YPENwZiF5uMrRRSGRNQRsQ=", + "dev": true + }, + "md5-o-matic": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/md5-o-matic/-/md5-o-matic-0.1.1.tgz", + "integrity": "sha1-givM1l4RfFFPqxdrJZRdVBAKA8M=", + "dev": true + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true + }, + "miller-rabin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.0.tgz", + "integrity": "sha1-SmL7HUKTPAVYOYL0xxb2+55sbT0=", + "dev": true + }, + "minimalistic-assert": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", + "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true + }, + "ms": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.3.tgz", + "integrity": "sha1-cIFVpeROM/X9D8U+gdDUCpG+H/8=", + "dev": true + }, + "multimatch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", + "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", + "dev": true + }, + "nan": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz", + "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U=", + "dev": true, + "optional": true + }, + "no-case": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.1.tgz", + "integrity": "sha1-euuhxzpSGEJlVUt9wDuvcg34AIE=", + "dev": true + }, + "node-libs-browser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.0.0.tgz", + "integrity": "sha1-o6WeyXAkmFtG6Vg3lkb5bEthZkY=", + "dev": true, + "dependencies": { + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "node-status-codes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", + "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=", + "dev": true + }, + "normalize-package-data": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.8.tgz", + "integrity": "sha1-2Bntoqne29H/pWPqQHHZNngilbs=", + "dev": true + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "nyc": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-10.3.2.tgz", + "integrity": "sha1-8n9NkfKp2zbCT1dP9cbv/wIz3kY=", + "dev": true, + "dependencies": { + "align-text": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "bundled": true, + "dev": true + }, + "append-transform": { + "version": "0.4.0", + "bundled": true, + "dev": true + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "arr-diff": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "arr-flatten": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "arrify": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "async": { + "version": "1.5.2", + "bundled": true, + "dev": true + }, + "babel-code-frame": { + "version": "6.22.0", + "bundled": true, + "dev": true + }, + "babel-generator": { + "version": "6.24.1", + "bundled": true, + "dev": true + }, + "babel-messages": { + "version": "6.23.0", + "bundled": true, + "dev": true + }, + "babel-runtime": { + "version": "6.23.0", + "bundled": true, + "dev": true + }, + "babel-template": { + "version": "6.24.1", + "bundled": true, + "dev": true + }, + "babel-traverse": { + "version": "6.24.1", + "bundled": true, + "dev": true + }, + "babel-types": { + "version": "6.24.1", + "bundled": true, + "dev": true + }, + "babylon": { + "version": "6.17.0", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.7", + "bundled": true, + "dev": true + }, + "braces": { + "version": "1.8.5", + "bundled": true, + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "caching-transform": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "camelcase": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true + }, + "center-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true + }, + "chalk": { + "version": "1.1.3", + "bundled": true, + "dev": true + }, + "cliui": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "optional": true, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "commondir": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "convert-source-map": { + "version": "1.5.0", + "bundled": true, + "dev": true + }, + "core-js": { + "version": "2.4.1", + "bundled": true, + "dev": true + }, + "cross-spawn": { + "version": "4.0.2", + "bundled": true, + "dev": true + }, + "debug": { + "version": "2.6.6", + "bundled": true, + "dev": true + }, + "debug-log": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "default-require-extensions": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "detect-indent": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "error-ex": { + "version": "1.3.1", + "bundled": true, + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "esutils": { + "version": "2.0.2", + "bundled": true, + "dev": true + }, + "expand-brackets": { + "version": "0.1.5", + "bundled": true, + "dev": true + }, + "expand-range": { + "version": "1.8.2", + "bundled": true, + "dev": true + }, + "extglob": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "filename-regex": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "fill-range": { + "version": "2.2.3", + "bundled": true, + "dev": true + }, + "find-cache-dir": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "for-in": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "for-own": { + "version": "0.1.5", + "bundled": true, + "dev": true + }, + "foreground-child": { + "version": "1.5.6", + "bundled": true, + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "get-caller-file": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "glob": { + "version": "7.1.1", + "bundled": true, + "dev": true + }, + "glob-base": { + "version": "0.3.0", + "bundled": true, + "dev": true + }, + "glob-parent": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "globals": { + "version": "9.17.0", + "bundled": true, + "dev": true + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "handlebars": { + "version": "4.0.8", + "bundled": true, + "dev": true, + "dependencies": { + "source-map": { + "version": "0.4.4", + "bundled": true, + "dev": true + } + } + }, + "has-ansi": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "has-flag": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "hosted-git-info": { + "version": "2.4.2", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "invariant": { + "version": "2.2.2", + "bundled": true, + "dev": true + }, + "invert-kv": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "is-buffer": { + "version": "1.1.5", + "bundled": true, + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-dotfile": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "bundled": true, + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "is-number": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "is-posix-bracket": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "isobject": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "istanbul-lib-coverage": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.0.6", + "bundled": true, + "dev": true + }, + "istanbul-lib-instrument": { + "version": "1.7.1", + "bundled": true, + "dev": true + }, + "istanbul-lib-report": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "dependencies": { + "supports-color": { + "version": "3.2.3", + "bundled": true, + "dev": true + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "istanbul-reports": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "js-tokens": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "jsesc": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "3.2.0", + "bundled": true, + "dev": true + }, + "lazy-cache": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "optional": true + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "load-json-file": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "lodash": { + "version": "4.17.4", + "bundled": true, + "dev": true + }, + "longest": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "loose-envify": { + "version": "1.3.1", + "bundled": true, + "dev": true + }, + "lru-cache": { + "version": "4.0.2", + "bundled": true, + "dev": true + }, + "md5-hex": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "md5-o-matic": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "merge-source-map": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "micromatch": { + "version": "2.3.11", + "bundled": true, + "dev": true + }, + "minimatch": { + "version": "3.0.3", + "bundled": true, + "dev": true + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true + }, + "ms": { + "version": "0.7.3", + "bundled": true, + "dev": true + }, + "normalize-package-data": { + "version": "2.3.8", + "bundled": true, + "dev": true + }, + "normalize-path": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "object.omit": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true + }, + "optimist": { + "version": "0.6.1", + "bundled": true, + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "1.4.0", + "bundled": true, + "dev": true + }, + "parse-glob": { + "version": "3.0.4", + "bundled": true, + "dev": true + }, + "parse-json": { + "version": "2.2.0", + "bundled": true, + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "path-type": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "pify": { + "version": "2.3.0", + "bundled": true, + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "bundled": true, + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "pkg-dir": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "preserve": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "randomatic": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "read-pkg-up": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "regenerator-runtime": { + "version": "0.10.5", + "bundled": true, + "dev": true + }, + "regex-cache": { + "version": "0.4.3", + "bundled": true, + "dev": true + }, + "remove-trailing-separator": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "bundled": true, + "dev": true + }, + "repeating": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "resolve-from": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "right-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true + }, + "rimraf": { + "version": "2.6.1", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "slide": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "source-map": { + "version": "0.5.6", + "bundled": true, + "dev": true + }, + "spawn-wrap": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "dependencies": { + "signal-exit": { + "version": "2.1.2", + "bundled": true, + "dev": true + } + } + }, + "spdx-correct": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "spdx-license-ids": { + "version": "1.2.2", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "strip-bom": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "test-exclude": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "uglify-js": { + "version": "2.8.22", + "bundled": true, + "dev": true, + "optional": true, + "dependencies": { + "yargs": { + "version": "3.10.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "validate-npm-package-license": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "which": { + "version": "1.2.14", + "bundled": true, + "dev": true + }, + "which-module": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "window-size": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "wordwrap": { + "version": "0.0.3", + "bundled": true, + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "1.3.4", + "bundled": true, + "dev": true + }, + "y18n": { + "version": "3.2.1", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "yargs": { + "version": "7.1.0", + "bundled": true, + "dev": true, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "cliui": { + "version": "3.2.0", + "bundled": true, + "dev": true + } + } + }, + "yargs-parser": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "bundled": true, + "dev": true + } + } + } + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true + }, + "observable-to-promise": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/observable-to-promise/-/observable-to-promise-0.4.0.tgz", + "integrity": "sha1-KK/nFkUwjy1B1x9HrT/s4aN35Ss=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "option-chain": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/option-chain/-/option-chain-0.1.1.tgz", + "integrity": "sha1-6bgR4AbxwPVIAvKClb/Ilw+Nz70=", + "dev": true + }, + "os-browserify": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.2.1.tgz", + "integrity": "sha1-Y/xMzuXS13Y9Jrv4YBB45sLgBE8=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", + "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", + "dev": true + }, + "package-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-1.2.0.tgz", + "integrity": "sha1-AD5WzVe3NqbtYRTMK4FUJnJ3DkQ=", + "dev": true + }, + "package-json": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz", + "integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=", + "dev": true + }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "parse-asn1": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", + "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", + "dev": true + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true + }, + "parse-ms": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", + "integrity": "sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0=", + "dev": true + }, + "pascal-case": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz", + "integrity": "sha1-LVeNNFX2YNpl7KGO+VtODekSdh4=", + "dev": true + }, + "path-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true + }, + "pbkdf2": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.12.tgz", + "integrity": "sha1-vjZ4XFBn6kjYBv+SMojF91C2uKI=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true + }, + "pkg-conf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-1.1.3.tgz", + "integrity": "sha1-N45W1v0T6Iv7b0ol33qD+qvduls=", + "dev": true + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true + }, + "plur": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz", + "integrity": "sha1-dIJFLBoPUI4+NE6uwxLJHCncZVo=", + "dev": true + }, + "power-assert-context-formatter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/power-assert-context-formatter/-/power-assert-context-formatter-1.1.1.tgz", + "integrity": "sha1-7bo1LT7YpgMRTWZyZazOYNaJzN8=", + "dev": true + }, + "power-assert-context-traversal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/power-assert-context-traversal/-/power-assert-context-traversal-1.1.1.tgz", + "integrity": "sha1-iMq8oNE7Y1nwfT0+ivppkmRXftk=", + "dev": true + }, + "power-assert-renderer-assertion": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/power-assert-renderer-assertion/-/power-assert-renderer-assertion-1.1.1.tgz", + "integrity": "sha1-y/wOd+AIao+Wrz8djme57n4ozpg=", + "dev": true + }, + "power-assert-renderer-base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/power-assert-renderer-base/-/power-assert-renderer-base-1.1.1.tgz", + "integrity": "sha1-lqZQxv0F7hvB9mtUrWFELIs/Y+s=", + "dev": true + }, + "power-assert-renderer-diagram": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/power-assert-renderer-diagram/-/power-assert-renderer-diagram-1.1.2.tgz", + "integrity": "sha1-ZV+PcRk1qbbVQbhjJ2VHF8Y3qYY=", + "dev": true + }, + "power-assert-renderer-succinct": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/power-assert-renderer-succinct/-/power-assert-renderer-succinct-1.1.1.tgz", + "integrity": "sha1-wqRosjgiq9b4Diq6UyI0ewnfR24=", + "dev": true + }, + "power-assert-util-string-width": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/power-assert-util-string-width/-/power-assert-util-string-width-1.1.1.tgz", + "integrity": "sha1-vmWet5N/3S5smncmjar2S9W3xZI=", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "pretty-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-2.1.0.tgz", + "integrity": "sha1-QlfCVt8/sLRR1q/6qwIYhBJpgdw=", + "dev": true, + "dependencies": { + "plur": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-1.0.0.tgz", + "integrity": "sha1-24XGgU9eXlo7Se/CjWBP7GKXUVY=", + "dev": true + } + } + }, + "private": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.7.tgz", + "integrity": "sha1-aM5eih7woju1cMwoU3tTMqumPvE=", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "prr": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz", + "integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "public-encrypt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", + "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "randomatic": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "dev": true, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true + } + } + }, + "randombytes": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz", + "integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==", + "dev": true, + "dependencies": { + "safe-buffer": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.0.tgz", + "integrity": "sha512-aSLEDudu6OoRr/2rU609gRmnYboRLxgDG1z9o2Q0os7236FwvcqIOO8r8U5JUEwivZOhDaKlFO4SbPTJYyBEyQ==", + "dev": true + } + } + }, + "rc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", + "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", + "dev": true, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "read-all-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", + "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true + }, + "readable-stream": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.11.tgz", + "integrity": "sha512-h+8+r3MKEhkiVrwdKL8aWs1oc1VvBu33ueshOvS26RsZQ3Amhx/oO3TKe4lApSV9ueY6as8EAh7mtuFjdlhg9Q==", + "dev": true + }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true + }, + "regenerate": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.2.tgz", + "integrity": "sha1-0ZQcZ7rUN+G+dkM63Vs4X5WxkmA=", + "dev": true + }, + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "dev": true + }, + "regenerator-transform": { + "version": "0.9.11", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.9.11.tgz", + "integrity": "sha1-On0GdSDLe3F2dp61/4aGkb7+EoM=", + "dev": true + }, + "regex-cache": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz", + "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=", + "dev": true + }, + "regexpu-core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", + "dev": true + }, + "registry-auth-token": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz", + "integrity": "sha1-+w0yie4Nmtosu1KvXf5mywcNMAY=", + "dev": true + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dev": true + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz", + "integrity": "sha1-abBi2XhyetFNxrVrpKt3L9jXBRE=", + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "require-precompiled": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/require-precompiled/-/require-precompiled-0.1.0.tgz", + "integrity": "sha1-WhtS63Dr7UPrmC6XTIWrWVceVvo=", + "dev": true + }, + "resolve-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-1.0.0.tgz", + "integrity": "sha1-Tq7qQe0EDRcCRX32SkKysH0kb58=", + "dev": true + }, + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true + }, + "rimraf": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "dev": true + }, + "ripemd160": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", + "dev": true + }, + "safe-buffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", + "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=", + "dev": true + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "sha.js": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.8.tgz", + "integrity": "sha1-NwaMLEdra69ALRSknGf1l5IfY08=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "dev": true + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "dev": true + }, + "source-list-map": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-1.1.2.tgz", + "integrity": "sha1-mIkBnRAkzOVc3AaUmDN+9hhqEaE=", + "dev": true + }, + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "dev": true + }, + "source-map-support": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", + "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=", + "dev": true + }, + "spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "dev": true + }, + "spdx-expression-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "dev": true + }, + "spdx-license-ids": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "dev": true + }, + "stack-utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-0.4.0.tgz", + "integrity": "sha1-lAy4L8z6hOj/Lz/fKT/ngBa+zNE=", + "dev": true + }, + "stream-browserify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "dev": true + }, + "stream-http": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.1.tgz", + "integrity": "sha1-VGpRdBrVprB+njGwsQRBqRffUoo=", + "dev": true + }, + "string_decoder": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz", + "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true + }, + "stringifier": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/stringifier/-/stringifier-1.3.0.tgz", + "integrity": "sha1-3vGDQvaTPbDy2/yaoCF1tEjBeVk=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "symbol": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/symbol/-/symbol-0.2.3.tgz", + "integrity": "sha1-O5hzuKkB5Hxu/iFSajrDcu8ou8c=", + "dev": true + }, + "symbol-observable": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-0.2.4.tgz", + "integrity": "sha1-lag9smGG1q9+ehjb2XYKL4bQj0A=", + "dev": true + }, + "tapable": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.6.tgz", + "integrity": "sha1-IGvo4YiGC1FEJTdebxrom/sB/Y0=", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "the-argv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/the-argv/-/the-argv-1.0.0.tgz", + "integrity": "sha1-AIRwUAVzDdhNt1UlPJMa45jblSI=", + "dev": true + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true + }, + "time-require": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/time-require/-/time-require-0.1.2.tgz", + "integrity": "sha1-+eEss3D8JgXhFARYK6VO9corLZg=", + "dev": true, + "dependencies": { + "ansi-styles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", + "dev": true + }, + "chalk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", + "dev": true + }, + "parse-ms": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-0.1.2.tgz", + "integrity": "sha1-3T+iXtbC78e93hKtm0bBY6opIk4=", + "dev": true + }, + "pretty-ms": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-0.2.2.tgz", + "integrity": "sha1-2oeaaC/zOjcBEEbxPWJ/Z8c7hPY=", + "dev": true + }, + "strip-ansi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", + "dev": true + } + } + }, + "timed-out": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-3.1.3.tgz", + "integrity": "sha1-lYYL/MXHbCd/j4Mm/Q9bLiDrohc=", + "dev": true + }, + "timers-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.2.tgz", + "integrity": "sha1-q0iDz1l9zVCvIRNJoA+8pWrIa4Y=", + "dev": true + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", + "dev": true + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "type-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/type-name/-/type-name-2.0.2.tgz", + "integrity": "sha1-7+fUEj2KxSr/9/QMfk3sUmYAj7Q=", + "dev": true + }, + "uglify-js": { + "version": "2.8.28", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.28.tgz", + "integrity": "sha512-WqKNbmNJKzIdIEQu/U2ytgGBbhCy2PVks94GoetczOAJ/zCgVu2CuO7gguI5KPFGPtUtI1dmPQl6h0D4cPzypA==", + "dev": true + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "uid2": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", + "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=", + "dev": true + }, + "unique-temp-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-temp-dir/-/unique-temp-dir-1.0.0.tgz", + "integrity": "sha1-bc6VsmgcoAPuv7MEpBX5y6vMU4U=", + "dev": true + }, + "unzip-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", + "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=", + "dev": true + }, + "update-notifier": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-1.0.3.tgz", + "integrity": "sha1-j5LFFUgr1oMbfJMBPnD4dVLHz1o=", + "dev": true + }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, + "upper-case-first": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz", + "integrity": "sha1-XXm+3P8UQZUY/S7bCgUHybaFkRU=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "dev": true + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true + }, + "watchpack": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.3.1.tgz", + "integrity": "sha1-fYaTkHsozmAT5/NhCqKhrPB9rYc=", + "dev": true + }, + "webpack": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-2.6.1.tgz", + "integrity": "sha1-LgRX8KuxrF3zqxBsacZy8jZ4Xwc=", + "dev": true, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true + }, + "yargs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", + "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", + "dev": true + } + } + }, + "webpack-sources": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-0.2.3.tgz", + "integrity": "sha1-F8Yr+vE8cH+dAsR54Nzd6DgGl/s=", + "dev": true + }, + "which": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "dev": true + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "widest-line": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-1.0.0.tgz", + "integrity": "sha1-DAnIXCqUaD0Nfq+O4JfVZL8OEFw=", + "dev": true + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", + "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", + "dev": true + }, + "write-json-file": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-1.2.0.tgz", + "integrity": "sha1-LV3+lqvDyIkFfJOXGqQAXvtUgTQ=", + "dev": true + }, + "write-pkg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/write-pkg/-/write-pkg-1.0.0.tgz", + "integrity": "sha1-rriqnU14jh2JPfsIVJaLVDqRn1c=", + "dev": true + }, + "xdg-basedir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz", + "integrity": "sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + } + } + }, + "yargs-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", + "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", + "dev": true, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + } + } + } + } +} diff --git a/src/config.js b/src/config.js index 72e8a2c..21601df 100644 --- a/src/config.js +++ b/src/config.js @@ -42,9 +42,9 @@ mixin(Config.prototype, { }, addStateLifecycleNames: function(name) { - this.lifecycle.onEnter[name] = camelize('on-enter-' + name); - this.lifecycle.onLeave[name] = camelize('on-leave-' + name); - this.lifecycle.on[name] = camelize('on-' + name); + this.lifecycle.onEnter[name] = camelize.prepended('onEnter', name); + this.lifecycle.onLeave[name] = camelize.prepended('onLeave', name); + this.lifecycle.on[name] = camelize.prepended('on', name); }, addTransition: function(name) { @@ -55,9 +55,9 @@ mixin(Config.prototype, { }, addTransitionLifecycleNames: function(name) { - this.lifecycle.onBefore[name] = camelize('on-before-' + name); - this.lifecycle.onAfter[name] = camelize('on-after-' + name); - this.lifecycle.on[name] = camelize('on-' + name); + this.lifecycle.onBefore[name] = camelize.prepended('onBefore', name); + this.lifecycle.onAfter[name] = camelize.prepended('onAfter', name); + this.lifecycle.on[name] = camelize.prepended('on', name); }, mapTransition: function(transition) { @@ -74,11 +74,11 @@ mixin(Config.prototype, { configureLifecycle: function() { return { - onBefore: { transition: camelize('on-before-transition') }, - onAfter: { transition: camelize('on-after-transition') }, - onEnter: { state: camelize('on-enter-state') }, - onLeave: { state: camelize('on-leave-state') }, - on: { transition: camelize('on-transition') } + onBefore: { transition: 'onBeforeTransition' }, + onAfter: { transition: 'onAfterTransition' }, + onEnter: { state: 'onEnterState' }, + onLeave: { state: 'onLeaveState' }, + on: { transition: 'onTransition' } }; }, diff --git a/src/plugin/history.js b/src/plugin/history.js index ccfcd98..46799c0 100644 --- a/src/plugin/history.js +++ b/src/plugin/history.js @@ -10,11 +10,11 @@ module.exports = function(options) { options = options || {}; var past = camelize(options.name || options.past || 'history'), future = camelize( options.future || 'future'), - clear = camelize('clear-' + past), - back = camelize(past + '-back'), - forward = camelize(past + '-forward'), - canBack = camelize('can-' + back), - canForward = camelize('can-' + forward), + clear = camelize.prepended('clear', past), + back = camelize.prepended(past, 'back'), + forward = camelize.prepended(past, 'forward'), + canBack = camelize.prepended('can', back), + canForward = camelize.prepended('can', forward), max = options.max; var plugin = { diff --git a/src/util/camelize.js b/src/util/camelize.js index 87c7540..b2645fd 100644 --- a/src/util/camelize.js +++ b/src/util/camelize.js @@ -1,9 +1,33 @@ 'use strict' -module.exports = function(label) { - var n, word, words = label.split(/[_-]/), result = words[0]; +//------------------------------------------------------------------------------------------------- + +function camelize(label) { + + if (label.length === 0) + return label; + + var n, result, word, words = label.split(/[_-]/); + + // single word with first character already lowercase, return untouched + if ((words.length === 1) && (words[0][0].toLowerCase() === words[0][0])) + return label; + + result = words[0].toLowerCase(); for(n = 1 ; n < words.length ; n++) { - result = result + words[n].charAt(0).toUpperCase() + words[n].substring(1); + result = result + words[n].charAt(0).toUpperCase() + words[n].substring(1).toLowerCase(); } + return result; } + +//------------------------------------------------------------------------------------------------- + +camelize.prepended = function(prepend, label) { + label = camelize(label); + return prepend + label[0].toUpperCase() + label.substring(1); +} + +//------------------------------------------------------------------------------------------------- + +module.exports = camelize; diff --git a/test/lifecycle.js b/test/lifecycle.js index 4375d65..281efe3 100644 --- a/test/lifecycle.js +++ b/test/lifecycle.js @@ -227,6 +227,56 @@ test('lifecycle events with dash or underscore are camelized', t => { //------------------------------------------------------------------------------------------------- +test('lifecycle event names that are all uppercase are camelized', t => { + + var logger = new LifecycleLogger(), + fsm = new StateMachine({ + init: 'FIRST', + transitions: [ + { name: 'GO', from: 'FIRST', to: 'SECOND_STATE' }, + { name: 'DO_IT', from: 'SECOND_STATE', to: 'FIRST' } + ], + methods: { + onBeforeGo: logger, + onBeforeDoIt: logger, + onLeaveFirst: logger, + onLeaveSecondState: logger, + onEnterFirst: logger, + onEnterSecondState: logger, + onAfterGo: logger, + onAfterDoIt: logger + } + }); + + t.is(fsm.state, 'FIRST') + t.deepEqual(logger.log, [ + { event: 'onEnterFirst', transition: 'init', from: 'none', to: 'FIRST', current: 'FIRST' }, + ]) + + logger.clear() + fsm.go() + t.is(fsm.state, 'SECOND_STATE') + t.deepEqual(logger.log, [ + { event: 'onBeforeGo', transition: 'GO', from: 'FIRST', to: 'SECOND_STATE', current: 'FIRST' }, + { event: 'onLeaveFirst', transition: 'GO', from: 'FIRST', to: 'SECOND_STATE', current: 'FIRST' }, + { event: 'onEnterSecondState', transition: 'GO', from: 'FIRST', to: 'SECOND_STATE', current: 'SECOND_STATE' }, + { event: 'onAfterGo', transition: 'GO', from: 'FIRST', to: 'SECOND_STATE', current: 'SECOND_STATE' } + ]) + + logger.clear(); + fsm.doIt(); + t.is(fsm.state, 'FIRST') + t.deepEqual(logger.log, [ + { event: 'onBeforeDoIt', transition: 'DO_IT', from: 'SECOND_STATE', to: 'FIRST', current: 'SECOND_STATE' }, + { event: 'onLeaveSecondState', transition: 'DO_IT', from: 'SECOND_STATE', to: 'FIRST', current: 'SECOND_STATE' }, + { event: 'onEnterFirst', transition: 'DO_IT', from: 'SECOND_STATE', to: 'FIRST', current: 'FIRST' }, + { event: 'onAfterDoIt', transition: 'DO_IT', from: 'SECOND_STATE', to: 'FIRST', current: 'FIRST' } + ]) + +}); + +//------------------------------------------------------------------------------------------------- + test('lifecycle events receive arbitrary transition arguments', t => { var logger = new LifecycleLogger(), diff --git a/test/util/camelize.js b/test/util/camelize.js index 9e3bb04..84318a2 100644 --- a/test/util/camelize.js +++ b/test/util/camelize.js @@ -4,11 +4,11 @@ import camelize from '../../src/util/camelize'; test('camelize', t => { t.is(camelize(""), ""); t.is(camelize("word"), "word"); - t.is(camelize("Word"), "Word"); - t.is(camelize("WORD"), "WORD"); + t.is(camelize("Word"), "word"); + t.is(camelize("WORD"), "word"); t.is(camelize("word-with-dash"), "wordWithDash"); t.is(camelize("word_with_underscore"), "wordWithUnderscore"); t.is(camelize("word--with--double--dash"), "wordWithDoubleDash"); - t.is(camelize("word_WITH_mixed_CASE"), "wordWITHMixedCASE"); - t.is(camelize("alreadyCamelized"), "alreadyCamelized"); + t.is(camelize("word_WITH_mixed_CASE"), "wordWithMixedCase"); + t.is(camelize("alreadyCamelizedString"), "alreadyCamelizedString"); }); From 9143d575f6bb77e8969678651cd43169db7b63b3 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 10 Jun 2017 14:18:58 -0700 Subject: [PATCH 79/87] fix issue #106 - forward resolved async value to end of lifecycle --- .gitignore | 1 + src/jsm.js | 8 ++++---- test/lifecycle.js | 19 ++++++++++--------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 1fd04da..89d2c73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules coverage .nyc_output +*.swp diff --git a/src/jsm.js b/src/jsm.js index 6288f1f..b6b56f4 100644 --- a/src/jsm.js +++ b/src/jsm.js @@ -131,9 +131,9 @@ mixin(JSM.prototype, { return [ event, result, true ] }, - observeEvents: function(events, args, previousEvent) { + observeEvents: function(events, args, previousEvent, previousResult) { if (events.length === 0) { - return this.endTransit(true); + return this.endTransit(previousResult === undefined ? true : previousResult); } var event = events[0][0], @@ -146,7 +146,7 @@ mixin(JSM.prototype, { if (observers.length === 0) { events.shift(); - return this.observeEvents(events, args, event); + return this.observeEvents(events, args, event, previousResult); } else { var observer = observers.shift(), @@ -159,7 +159,7 @@ mixin(JSM.prototype, { return this.endTransit(false); } else { - return this.observeEvents(events, args, event); + return this.observeEvents(events, args, event, result); } } }, diff --git a/test/lifecycle.js b/test/lifecycle.js index 281efe3..e793a57 100644 --- a/test/lifecycle.js +++ b/test/lifecycle.js @@ -490,7 +490,7 @@ test('lifecycle events can be deferred using a promise', t => { var logger = new LifecycleLogger(), start = Date.now(), - pause = function(ms) { return new Promise(function(resolve, reject) { setTimeout(resolve, ms); }); }, + pause = function(ms) { return new Promise(function(resolve, reject) { setTimeout(function() { resolve('resolved') }, ms); }); }, fsm = new StateMachine({ transitions: [ { name: 'step', from: 'none', to: 'complete' } @@ -506,11 +506,11 @@ test('lifecycle events can be deferred using a promise', t => { onLeaveNone: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, onLeaveComplete: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, onAfterTransition: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); }, - onAfterStep: function(lifecycle, a, b) { logger(lifecycle, a, b); return done(); } + onAfterStep: function(lifecycle, a, b) { logger(lifecycle, a, b); return pause(100); } } }); - function done() { + function done(answer) { var duration = Date.now() - start; t.is(fsm.state, 'complete') t.is(duration > 600, true) @@ -525,10 +525,12 @@ test('lifecycle events can be deferred using a promise', t => { { event: 'onAfterTransition', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, { event: 'onAfterStep', transition: 'step', from: 'none', to: 'complete', current: 'complete', args: [ 'additional', 'arguments' ] }, ]) + t.is(answer, 'resolved'); resolveTest() } fsm.step('additional', 'arguments') + .then(done); }); }); @@ -542,15 +544,12 @@ test('lifecycle events can be cancelled using a promise', t => { start = Date.now(), pause = function(ms) { return new Promise(function(resolve, reject) { - setTimeout(resolve, ms); + setTimeout(function() { resolve('resolved') }, ms); }); }, cancel = function(ms) { return new Promise(function(resolve, reject) { - setTimeout(function() { - reject(); - done(); - }, ms); + setTimeout(function() { reject('rejected'); }, ms); }); }, fsm = new StateMachine({ @@ -572,7 +571,7 @@ test('lifecycle events can be cancelled using a promise', t => { } }); - function done() { + function done(answer) { var duration = Date.now() - start; t.is(fsm.state, 'none'); t.is(duration > 300, true); @@ -583,10 +582,12 @@ test('lifecycle events can be cancelled using a promise', t => { { event: 'onLeaveNone', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] }, { event: 'onTransition', transition: 'step', from: 'none', to: 'complete', current: 'none', args: [ 'additional', 'arguments' ] } ]); + t.is(answer, 'rejected'); resolveTest(); } fsm.step('additional', 'arguments') + .then(done) }) }) From 186130856cbbd777f2646bdfaaefcf6fcef85a26 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 10 Jun 2017 14:24:53 -0700 Subject: [PATCH 80/87] Issue #106 - a rejected async lifecycle event should throw an exception --- src/jsm.js | 3 ++- test/lifecycle.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/jsm.js b/src/jsm.js index b6b56f4..f83c8de 100644 --- a/src/jsm.js +++ b/src/jsm.js @@ -108,6 +108,7 @@ mixin(JSM.prototype, { beginTransit: function() { this.pending = true; }, endTransit: function(result) { this.pending = false; return result; }, + failTransit: function(result) { this.pending = false; throw result; }, doTransit: function(lifecycle) { this.state = lifecycle.to; }, observe: function(args) { @@ -153,7 +154,7 @@ mixin(JSM.prototype, { result = observer[event].apply(observer, args); if (result && typeof result.then === 'function') { return result.then(this.observeEvents.bind(this, events, args, event)) - .catch(this.endTransit.bind(this)) + .catch(this.failTransit.bind(this)) } else if (result === false) { return this.endTransit(false); diff --git a/test/lifecycle.js b/test/lifecycle.js index e793a57..347ebf4 100644 --- a/test/lifecycle.js +++ b/test/lifecycle.js @@ -587,7 +587,8 @@ test('lifecycle events can be cancelled using a promise', t => { } fsm.step('additional', 'arguments') - .then(done) + .then(function() { done('promise was rejected so this should never happen'); }) + .catch(done) }) }) From e09614a51f2a64724bb151da6462bcb34fdb9883 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 10 Jun 2017 14:42:55 -0700 Subject: [PATCH 81/87] first 3.x release (3.0.1) --- README.md | 2 +- RELEASE_NOTES.md | 9 ++ dist/state-machine-history.js | 80 +++++++++----- dist/state-machine-history.min.js | 2 +- dist/state-machine-visualize.js | 40 +++---- dist/state-machine-visualize.min.js | 2 +- dist/state-machine.js | 157 ++++++++++++++++------------ dist/state-machine.min.js | 2 +- lib/history.js | 80 +++++++++----- lib/state-machine.js | 157 ++++++++++++++++------------ lib/visualize.js | 40 +++---- package-lock.json | 2 +- package.json | 2 +- src/app.js | 2 +- test/basics.js | 2 +- 15 files changed, 343 insertions(+), 236 deletions(-) diff --git a/README.md b/README.md index b94ec4b..bd46ea3 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ In a browser: Using npm: ```shell - npm install --save-dev javascript-state-machine@3.0.0-rc.1 # note explicit pre-release version + npm install --save-dev javascript-state-machine ``` In Node.js: diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 5d64d17..6ada814 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,12 @@ +Version 3.0.1 (June 10th 2017) +------------------------------ + + * First 3.x release - see 3.0.0-rc.1 release notes below + + * fix issue #109 - rejection from async lifecycle method does not reject transitions promise + * fix issue #106 - async transition: forward resolved value + * fix issue #107 - lifecycle event name breaks for all uppercase + Version 3.0.0-rc.1 (January 10 2017) ------------------------------------ diff --git a/dist/state-machine-history.js b/dist/state-machine-history.js index d604994..19f7c4f 100644 --- a/dist/state-machine-history.js +++ b/dist/state-machine-history.js @@ -11,41 +11,41 @@ return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; - +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { - +/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) +/******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; - +/******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; - +/******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); - +/******/ /******/ // Flag the module as loaded /******/ module.l = true; - +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } - - +/******/ +/******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; - +/******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; - +/******/ /******/ // identity function for calling harmony imports with the correct context /******/ __webpack_require__.i = function(value) { return value; }; - +/******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { @@ -56,7 +56,7 @@ return /******/ (function(modules) { // webpackBootstrap /******/ }); /******/ } /******/ }; - +/******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? @@ -65,36 +65,60 @@ return /******/ (function(modules) { // webpackBootstrap /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; - +/******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; - +/******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; - +/******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 1); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = function(label) { - var n, word, words = label.split(/[_-]/), result = words[0]; +//------------------------------------------------------------------------------------------------- + +function camelize(label) { + + if (label.length === 0) + return label; + + var n, result, word, words = label.split(/[_-]/); + + // single word with first character already lowercase, return untouched + if ((words.length === 1) && (words[0][0].toLowerCase() === words[0][0])) + return label; + + result = words[0].toLowerCase(); for(n = 1 ; n < words.length ; n++) { - result = result + words[n].charAt(0).toUpperCase() + words[n].substring(1); + result = result + words[n].charAt(0).toUpperCase() + words[n].substring(1).toLowerCase(); } + return result; } +//------------------------------------------------------------------------------------------------- + +camelize.prepended = function(prepend, label) { + label = camelize(label); + return prepend + label[0].toUpperCase() + label.substring(1); +} + +//------------------------------------------------------------------------------------------------- + +module.exports = camelize; + -/***/ }, +/***/ }), /* 1 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -109,11 +133,11 @@ module.exports = function(options) { options = options || {}; var past = camelize(options.name || options.past || 'history'), future = camelize( options.future || 'future'), - clear = camelize('clear-' + past), - back = camelize(past + '-back'), - forward = camelize(past + '-forward'), - canBack = camelize('can-' + back), - canForward = camelize('can-' + forward), + clear = camelize.prepended('clear', past), + back = camelize.prepended(past, 'back'), + forward = camelize.prepended(past, 'forward'), + canBack = camelize.prepended('can', back), + canForward = camelize.prepended('can', forward), max = options.max; var plugin = { @@ -182,6 +206,6 @@ module.exports = function(options) { options = options || {}; } -/***/ } +/***/ }) /******/ ]); }); \ No newline at end of file diff --git a/dist/state-machine-history.min.js b/dist/state-machine-history.min.js index c186024..9c7fb3f 100644 --- a/dist/state-machine-history.min.js +++ b/dist/state-machine-history.min.js @@ -1 +1 @@ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("StateMachineHistory",[],e):"object"==typeof exports?exports.StateMachineHistory=e():t.StateMachineHistory=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,r){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:r})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=1)}([function(t,e,n){"use strict";t.exports=function(t){var e,n=t.split(/[_-]/),r=n[0];for(e=1;ef&&t[e].shift(),r.transition!==i&&r.transition!==s&&(t[n].length=0))},methods:{},properties:{}};return a.methods[o]=function(){this[e].length=0,this[n].length=0},a.properties[u]={get:function(){return this[e].length>1}},a.properties[c]={get:function(){return this[n].length>0}},a.methods[i]=function(){if(!this[u])throw Error("no history");var t=this[e].pop(),r=this[e].pop();this[n].push(t),this._fsm.transit(i,t,r,[])},a.methods[s]=function(){if(!this[c])throw Error("no history");var t=this.state,e=this[n].pop();this._fsm.transit(s,t,e,[])},a}}])}); \ No newline at end of file +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("StateMachineHistory",[],e):"object"==typeof exports?exports.StateMachineHistory=e():t.StateMachineHistory=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,r){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:r})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=1)}([function(t,e,n){"use strict";function r(t){if(0===t.length)return t;var e,n,r=t.split(/[_-]/);if(1===r.length&&r[0][0].toLowerCase()===r[0][0])return t;for(n=r[0].toLowerCase(),e=1;ec&&t[e].shift(),r.transition!==i&&r.transition!==s&&(t[n].length=0))},methods:{},properties:{}};return f.methods[o]=function(){this[e].length=0,this[n].length=0},f.properties[u]={get:function(){return this[e].length>1}},f.properties[p]={get:function(){return this[n].length>0}},f.methods[i]=function(){if(!this[u])throw Error("no history");var t=this[e].pop(),r=this[e].pop();this[n].push(t),this._fsm.transit(i,t,r,[])},f.methods[s]=function(){if(!this[p])throw Error("no history");var t=this.state,e=this[n].pop();this._fsm.transit(s,t,e,[])},f}}])}); \ No newline at end of file diff --git a/dist/state-machine-visualize.js b/dist/state-machine-visualize.js index 6cfdd7e..9c18e13 100644 --- a/dist/state-machine-visualize.js +++ b/dist/state-machine-visualize.js @@ -11,41 +11,41 @@ return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; - +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { - +/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) +/******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; - +/******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; - +/******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); - +/******/ /******/ // Flag the module as loaded /******/ module.l = true; - +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } - - +/******/ +/******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; - +/******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; - +/******/ /******/ // identity function for calling harmony imports with the correct context /******/ __webpack_require__.i = function(value) { return value; }; - +/******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { @@ -56,7 +56,7 @@ return /******/ (function(modules) { // webpackBootstrap /******/ }); /******/ } /******/ }; - +/******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? @@ -65,20 +65,20 @@ return /******/ (function(modules) { // webpackBootstrap /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; - +/******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; - +/******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; - +/******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 1); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -96,9 +96,9 @@ module.exports = function(target, sources) { } -/***/ }, +/***/ }), /* 1 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -264,6 +264,6 @@ module.exports = visualize; //------------------------------------------------------------------------------------------------- -/***/ } +/***/ }) /******/ ]); }); \ No newline at end of file diff --git a/dist/state-machine-visualize.min.js b/dist/state-machine-visualize.min.js index 6e5e296..e517d3b 100644 --- a/dist/state-machine-visualize.min.js +++ b/dist/state-machine-visualize.min.js @@ -1 +1 @@ -!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define("StateMachineVisualize",[],n):"object"==typeof exports?exports.StateMachineVisualize=n():t.StateMachineVisualize=n()}(this,function(){return function(t){function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var e={};return n.m=t,n.c=e,n.i=function(t){return t},n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="",n(n.s=1)}([function(t,n,e){"use strict";t.exports=function(t,n){var e,r,o;for(e=1;e0&&(u.states=s),a&&a.length>0&&(u.transitions=a),u}function i(t){return" "+t+" "}function s(t){return'"'+t+'"'}function a(t){t=t||{};var n,e,r=t.name||"fsm",o=t.states||[],i=t.transitions||[],u=t.rankdir,f=[];for(f.push("digraph "+s(r)+" {"),u&&f.push(" rankdir="+u+";"),n=0,e=o.length;n "+s(t.to)+a.edge.attr(t)+";"},a.edge.attr=function(t){var n,e,r,o=Object.keys(t).sort(),i=[];for(n=0,e=o.length;n0?" [ "+i.join(" ; ")+" ]":""},r.dotcfg=o,r.dotify=a,t.exports=r}])}); \ No newline at end of file +!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define("StateMachineVisualize",[],n):"object"==typeof exports?exports.StateMachineVisualize=n():t.StateMachineVisualize=n()}(this,function(){return function(t){function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var e={};return n.m=t,n.c=e,n.i=function(t){return t},n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="",n(n.s=1)}([function(t,n,e){"use strict";t.exports=function(t,n){var e,r,o;for(e=1;e0&&(u.states=s),a&&a.length>0&&(u.transitions=a),u}function i(t){return" "+t+" "}function s(t){return'"'+t+'"'}function a(t){t=t||{};var n,e,r=t.name||"fsm",o=t.states||[],i=t.transitions||[],u=t.rankdir,f=[];for(f.push("digraph "+s(r)+" {"),u&&f.push(" rankdir="+u+";"),n=0,e=o.length;n "+s(t.to)+a.edge.attr(t)+";"},a.edge.attr=function(t){var n,e,r,o=Object.keys(t).sort(),i=[];for(n=0,e=o.length;n0?" [ "+i.join(" ; ")+" ]":""},r.dotcfg=o,r.dotify=a,t.exports=r}])}); \ No newline at end of file diff --git a/dist/state-machine.js b/dist/state-machine.js index 152edfa..b8b9e37 100644 --- a/dist/state-machine.js +++ b/dist/state-machine.js @@ -11,41 +11,41 @@ return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; - +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { - +/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) +/******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; - +/******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; - +/******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); - +/******/ /******/ // Flag the module as loaded /******/ module.l = true; - +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } - - +/******/ +/******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; - +/******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; - +/******/ /******/ // identity function for calling harmony imports with the correct context /******/ __webpack_require__.i = function(value) { return value; }; - +/******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { @@ -56,7 +56,7 @@ return /******/ (function(modules) { // webpackBootstrap /******/ }); /******/ } /******/ }; - +/******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? @@ -65,20 +65,20 @@ return /******/ (function(modules) { // webpackBootstrap /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; - +/******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; - +/******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; - +/******/ /******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 6); +/******/ return __webpack_require__(__webpack_require__.s = 5); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -96,9 +96,9 @@ module.exports = function(target, sources) { } -/***/ }, +/***/ }), /* 1 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -143,25 +143,49 @@ module.exports = { //------------------------------------------------------------------------------------------------- -/***/ }, +/***/ }), /* 2 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = function(label) { - var n, word, words = label.split(/[_-]/), result = words[0]; +//------------------------------------------------------------------------------------------------- + +function camelize(label) { + + if (label.length === 0) + return label; + + var n, result, word, words = label.split(/[_-]/); + + // single word with first character already lowercase, return untouched + if ((words.length === 1) && (words[0][0].toLowerCase() === words[0][0])) + return label; + + result = words[0].toLowerCase(); for(n = 1 ; n < words.length ; n++) { - result = result + words[n].charAt(0).toUpperCase() + words[n].substring(1); + result = result + words[n].charAt(0).toUpperCase() + words[n].substring(1).toLowerCase(); } + return result; } +//------------------------------------------------------------------------------------------------- + +camelize.prepended = function(prepend, label) { + label = camelize(label); + return prepend + label[0].toUpperCase() + label.substring(1); +} + +//------------------------------------------------------------------------------------------------- + +module.exports = camelize; -/***/ }, + +/***/ }), /* 3 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -208,9 +232,9 @@ mixin(Config.prototype, { }, addStateLifecycleNames: function(name) { - this.lifecycle.onEnter[name] = camelize('on-enter-' + name); - this.lifecycle.onLeave[name] = camelize('on-leave-' + name); - this.lifecycle.on[name] = camelize('on-' + name); + this.lifecycle.onEnter[name] = camelize.prepended('onEnter', name); + this.lifecycle.onLeave[name] = camelize.prepended('onLeave', name); + this.lifecycle.on[name] = camelize.prepended('on', name); }, addTransition: function(name) { @@ -221,9 +245,9 @@ mixin(Config.prototype, { }, addTransitionLifecycleNames: function(name) { - this.lifecycle.onBefore[name] = camelize('on-before-' + name); - this.lifecycle.onAfter[name] = camelize('on-after-' + name); - this.lifecycle.on[name] = camelize('on-' + name); + this.lifecycle.onBefore[name] = camelize.prepended('onBefore', name); + this.lifecycle.onAfter[name] = camelize.prepended('onAfter', name); + this.lifecycle.on[name] = camelize.prepended('on', name); }, mapTransition: function(transition) { @@ -240,11 +264,11 @@ mixin(Config.prototype, { configureLifecycle: function() { return { - onBefore: { transition: camelize('on-before-transition') }, - onAfter: { transition: camelize('on-after-transition') }, - onEnter: { state: camelize('on-enter-state') }, - onLeave: { state: camelize('on-leave-state') }, - on: { transition: camelize('on-transition') } + onBefore: { transition: 'onBeforeTransition' }, + onAfter: { transition: 'onAfterTransition' }, + onEnter: { state: 'onEnterState' }, + onLeave: { state: 'onLeaveState' }, + on: { transition: 'onTransition' } }; }, @@ -327,13 +351,13 @@ module.exports = Config; //------------------------------------------------------------------------------------------------- -/***/ }, +/***/ }), /* 4 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { var mixin = __webpack_require__(0), - Exception = __webpack_require__(5), + Exception = __webpack_require__(6), plugin = __webpack_require__(1), UNOBSERVED = [ null, [] ]; @@ -441,6 +465,7 @@ mixin(JSM.prototype, { beginTransit: function() { this.pending = true; }, endTransit: function(result) { this.pending = false; return result; }, + failTransit: function(result) { this.pending = false; throw result; }, doTransit: function(lifecycle) { this.state = lifecycle.to; }, observe: function(args) { @@ -464,9 +489,9 @@ mixin(JSM.prototype, { return [ event, result, true ] }, - observeEvents: function(events, args, previousEvent) { + observeEvents: function(events, args, previousEvent, previousResult) { if (events.length === 0) { - return this.endTransit(true); + return this.endTransit(previousResult === undefined ? true : previousResult); } var event = events[0][0], @@ -479,20 +504,20 @@ mixin(JSM.prototype, { if (observers.length === 0) { events.shift(); - return this.observeEvents(events, args, event); + return this.observeEvents(events, args, event, previousResult); } else { var observer = observers.shift(), result = observer[event].apply(observer, args); if (result && typeof result.then === 'function') { return result.then(this.observeEvents.bind(this, events, args, event)) - .catch(this.endTransit.bind(this)) + .catch(this.failTransit.bind(this)) } else if (result === false) { return this.endTransit(false); } else { - return this.observeEvents(events, args, event); + return this.observeEvents(events, args, event, result); } } }, @@ -514,25 +539,9 @@ module.exports = JSM; //------------------------------------------------------------------------------------------------- -/***/ }, +/***/ }), /* 5 */ -/***/ function(module, exports, __webpack_require__) { - -"use strict"; - - -module.exports = function(message, transition, from, to, current) { - this.message = message; - this.transition = transition; - this.from = from; - this.to = to; - this.current = current; -} - - -/***/ }, -/* 6 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -623,7 +632,7 @@ function build(target, config) { //----------------------------------------------------------------------------------------------- -StateMachine.version = '3.0.0-rc.1'; +StateMachine.version = '3.0.1'; StateMachine.factory = factory; StateMachine.apply = apply; StateMachine.defaults = { @@ -639,6 +648,22 @@ StateMachine.defaults = { module.exports = StateMachine; -/***/ } +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = function(message, transition, from, to, current) { + this.message = message; + this.transition = transition; + this.from = from; + this.to = to; + this.current = current; +} + + +/***/ }) /******/ ]); }); \ No newline at end of file diff --git a/dist/state-machine.min.js b/dist/state-machine.min.js index 38f2d60..b9439bc 100644 --- a/dist/state-machine.min.js +++ b/dist/state-machine.min.js @@ -1 +1 @@ -!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define("StateMachine",[],n):"object"==typeof exports?exports.StateMachine=n():t.StateMachine=n()}(this,function(){return function(t){function n(e){if(i[e])return i[e].exports;var s=i[e]={i:e,l:!1,exports:{}};return t[e].call(s.exports,s,s.exports,n),s.l=!0,s.exports}var i={};return n.m=t,n.c=i,n.i=function(t){return t},n.d=function(t,i,e){n.o(t,i)||Object.defineProperty(t,i,{configurable:!1,enumerable:!0,get:e})},n.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(i,"a",i),i},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="",n(n.s=6)}([function(t,n,i){"use strict";t.exports=function(t,n){var i,e,s;for(i=1;i=0:this.state===t},isPending:function(){return this.pending},can:function(t){return!this.isPending()&&!!this.seek(t)},cannot:function(t){return!this.can(t)},allStates:function(){return this.config.allStates()},allTransitions:function(){return this.config.allTransitions()},transitions:function(){return this.config.transitionsFor(this.state)},seek:function(t,n){var i=this.config.defaults.wildcard,e=this.config.transitionFor(this.state,t),s=e&&e.to;return"function"==typeof s?s.apply(this.context,n):s===i?this.state:s},fire:function(t,n){return this.transit(t,this.state,this.seek(t,n),n)},transit:function(t,n,i,e){var s=this.config.lifecycle,r=this.config.options.observeUnchangedState||n!==i;return i?this.isPending()?this.context.onPendingTransition(t,n,i):(this.config.addState(i),this.beginTransit(),e.unshift({transition:t,from:n,to:i,fsm:this.context}),this.observeEvents([this.observersForEvent(s.onBefore.transition),this.observersForEvent(s.onBefore[t]),r?this.observersForEvent(s.onLeave.state):a,r?this.observersForEvent(s.onLeave[n]):a,this.observersForEvent(s.on.transition),r?["doTransit",[this]]:a,r?this.observersForEvent(s.onEnter.state):a,r?this.observersForEvent(s.onEnter[i]):a,r?this.observersForEvent(s.on[i]):a,this.observersForEvent(s.onAfter.transition),this.observersForEvent(s.onAfter[t]),this.observersForEvent(s.on[t])],e)):this.context.onInvalidTransition(t,n,i)},beginTransit:function(){this.pending=!0},endTransit:function(t){return this.pending=!1,t},doTransit:function(t){this.state=t.to},observe:function(t){if(2===t.length){var n={};n[t[0]]=t[1],this.observers.push(n)}else this.observers.push(t[0])},observersForEvent:function(t){for(var n,i=0,e=this.observers.length,s=[];i=0:this.state===t},isPending:function(){return this.pending},can:function(t){return!this.isPending()&&!!this.seek(t)},cannot:function(t){return!this.can(t)},allStates:function(){return this.config.allStates()},allTransitions:function(){return this.config.allTransitions()},transitions:function(){return this.config.transitionsFor(this.state)},seek:function(t,n){var i=this.config.defaults.wildcard,e=this.config.transitionFor(this.state,t),s=e&&e.to;return"function"==typeof s?s.apply(this.context,n):s===i?this.state:s},fire:function(t,n){return this.transit(t,this.state,this.seek(t,n),n)},transit:function(t,n,i,e){var s=this.config.lifecycle,r=this.config.options.observeUnchangedState||n!==i;return i?this.isPending()?this.context.onPendingTransition(t,n,i):(this.config.addState(i),this.beginTransit(),e.unshift({transition:t,from:n,to:i,fsm:this.context}),this.observeEvents([this.observersForEvent(s.onBefore.transition),this.observersForEvent(s.onBefore[t]),r?this.observersForEvent(s.onLeave.state):a,r?this.observersForEvent(s.onLeave[n]):a,this.observersForEvent(s.on.transition),r?["doTransit",[this]]:a,r?this.observersForEvent(s.onEnter.state):a,r?this.observersForEvent(s.onEnter[i]):a,r?this.observersForEvent(s.on[i]):a,this.observersForEvent(s.onAfter.transition),this.observersForEvent(s.onAfter[t]),this.observersForEvent(s.on[t])],e)):this.context.onInvalidTransition(t,n,i)},beginTransit:function(){this.pending=!0},endTransit:function(t){return this.pending=!1,t},failTransit:function(t){throw this.pending=!1,t},doTransit:function(t){this.state=t.to},observe:function(t){if(2===t.length){var n={};n[t[0]]=t[1],this.observers.push(n)}else this.observers.push(t[0])},observersForEvent:function(t){for(var n,i=0,e=this.observers.length,s=[];i { - t.is(StateMachine.version, '3.0.0-rc.1'); + t.is(StateMachine.version, '3.0.1'); }); //------------------------------------------------------------------------------------------------- From 93258af7b8fb0a4cd6fff0b478abadfb28d8492a Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sat, 10 Jun 2017 15:58:16 -0700 Subject: [PATCH 82/87] added link to plasso purchase page for commercial license --- docs/commercial-license.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/commercial-license.md b/docs/commercial-license.md index af8b6cd..3dbf474 100644 --- a/docs/commercial-license.md +++ b/docs/commercial-license.md @@ -16,9 +16,10 @@ licensed versions. The commercial license allows for commercial use and provides ## PURCHASE NOW -A commercial license purchasing page will be available soon. +A commercial license can be [purchased here](https://plasso.com/s/TrSu29woZU). You will receive +your license details via email within 48 hours of purchase. -Please email [jake@codeincomplete.com](mailto:jake@codeincomplete.com) for more details. +Please contact [jake@codeincomplete.com](mailto:jake@codeincomplete.com) if you have any questions. ## Commercial License Summary From e05b94f5c3357407c56de98d923dadc8e871f2f1 Mon Sep 17 00:00:00 2001 From: Conor Gilsenan Date: Tue, 24 Oct 2017 13:02:17 -0400 Subject: [PATCH 83/87] Typo vaporise/vaporize The demo errors because the state name is misspelled. --- docs/states-and-transitions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/states-and-transitions.md b/docs/states-and-transitions.md index 3e28413..0581379 100644 --- a/docs/states-and-transitions.md +++ b/docs/states-and-transitions.md @@ -29,7 +29,7 @@ A state machine consists of a set of **states**, e.g: fsm.state; // 'solid' fsm.melt(); fsm.state; // 'liquid' - fsm.vaporise(); + fsm.vaporize(); fsm.state; // 'gas' ``` From 7ae0a4e02e30a2627b357e8e44fbf0c6a871bcb7 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Mon, 2 Apr 2018 09:33:15 -0700 Subject: [PATCH 84/87] Update commercial-license.md --- docs/commercial-license.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/commercial-license.md b/docs/commercial-license.md index 3dbf474..0b4a3cb 100644 --- a/docs/commercial-license.md +++ b/docs/commercial-license.md @@ -16,8 +16,7 @@ licensed versions. The commercial license allows for commercial use and provides ## PURCHASE NOW -A commercial license can be [purchased here](https://plasso.com/s/TrSu29woZU). You will receive -your license details via email within 48 hours of purchase. +A commercial license ~~can be purchased here~~ is no longer available. Please contact [jake@codeincomplete.com](mailto:jake@codeincomplete.com) if you have any questions. From bfee5d404e49e23e22a969375e4e030c0baea291 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Thu, 12 Jul 2018 07:28:44 -0700 Subject: [PATCH 85/87] remove commercial license option and go back to MIT license --- LICENSE | 24 +- README.md | 5 +- RELEASE_NOTES.md | 5 + docs/commercial-license.md | 180 --- package-lock.json | 2914 +++++++++++++++++++++++++++++++----- package.json | 4 +- 6 files changed, 2543 insertions(+), 589 deletions(-) delete mode 100644 docs/commercial-license.md diff --git a/LICENSE b/LICENSE index f4b0142..b01dd40 100644 --- a/LICENSE +++ b/LICENSE @@ -1,10 +1,20 @@ -Copyright (c) Jake Gordon +Copyright (c) 2012, 2013, 2014, 2015, 2016, 2017, 2018, Jake Gordon and contributors -"javascript-state-machine" is dual-licensed: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - * "javascript-state-machine" is available as an Open Source project licensed under the terms - of the LGPLv3 license. Please see - for license text. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. - * "javascript-state-machine" is also available under a commercial license (with support). Please - read docs/commercial-license.md and contact jake@codeincomplete.com for more details. diff --git a/README.md b/README.md index bd46ea3..5cba155 100644 --- a/README.md +++ b/README.md @@ -133,16 +133,13 @@ Read more about You can [Contribute](docs/contributing.md) to this project with issues or pull requests. -You might also want to support this project by purchasing a [commercial license](docs/commercial-license.md). - # Release Notes See [RELEASE NOTES](RELEASE_NOTES.md) file. # License -Dual-licensed under the [LGPL](http://www.gnu.org/licenses/lgpl-3.0.html) for the open source community and also available with support -under a [commercial license](docs/commercial-license.md). +See [MIT LICENSE](https://github.com/jakesgordon/javascript-state-machine/blob/master/LICENSE) file. # Contact diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6ada814..0527d19 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,8 @@ +Version 3.1.0 (July 12th 2018) +------------------------------ + + * Changed back to MIT license + Version 3.0.1 (June 10th 2017) ------------------------------ diff --git a/docs/commercial-license.md b/docs/commercial-license.md deleted file mode 100644 index 0b4a3cb..0000000 --- a/docs/commercial-license.md +++ /dev/null @@ -1,180 +0,0 @@ -# Javascript State Machine Commercial License - -All `javascript-state-machine` features are available in both the open source and the commercially -licensed versions. The commercial license allows for commercial use and provides priority support. - -| | Open Source | | Commercial License | -|--------------------------|---------------|---|-----------------------------| -| All Existing v2 Features | ✔ | | ✔ | -| Promise-based Async | ✔ | | ✔ | -| Observable Transitions | ✔ | | ✔ | -| Conditional Transitions | ✔ | | ✔ | -| State History | ✔ | | ✔ | -| Support | Github Issues | | **Priority Email (1 year)** | -| License | LGPL | | **Commercial** | -| Price | Free | | **$149** | - -## PURCHASE NOW - -A commercial license ~~can be purchased here~~ is no longer available. - -Please contact [jake@codeincomplete.com](mailto:jake@codeincomplete.com) if you have any questions. - -## Commercial License Summary - - * License does not expire - * Commercial use allowed - * Can be used for unlimited projects - * Can modify source-code but cannot distribute modifications (derivative works) - * Email support provided for 1 year (from time of purchase) - -## Commercial License Example - - - Javascript State Machine - Terms and Conditions. - - 1. Preamble. - - This Agreement, signed on [DATE] (hereinafter: Effective Date) governs the relationship - between [COMPANY], a Business Entity, (hereinafter: Licensee) and - Jake Gordon, a private person (hereinafter: Licensor). This Agreement sets the terms, - rights, restrictions and obligations on using Javascript State Machine - (hereinafter: The Software) created and owned by Licensor, as detailed herein - - 2. License Grant. - - Licensor hereby grants Licensee a Personal, Non-assignable & non-transferable, Perpetual, - Commercial, Royalty free, Including the rights to create but not distribute derivative - works, Non-exclusive license, all with accordance with the terms set forth and other legal - restrictions set forth in 3rd party software used while running Software. - - 2.1. Limited: Licensee may use Software for the purpose of: - - 2.1.1. Running Software on Licensee’s Website[s] and Server[s]; - 2.1.2. Allowing 3rd Parties to run Software on Licensee’s Website[s] and Server[s]; - 2.1.3. Publishing Software’s output to Licensee and 3rd Parties; - 2.1.4. Distribute verbatim copies of Software’s output (including compiled binaries); - 2.1.5. Modify Software to suit Licensee’s needs and specifications. - - 2.2. This license is granted perpetually, as long as you do not materially breach it. - 2.3. Binary Restricted: Licensee may sublicense Software as a part of a larger work - containing more than Software, distributed solely in Object or Binary form under a - personal, non-sublicensable, limited license. Such redistribution shall be limited - to unlimited codebases. - 2.4. Non Assignable & Non-Transferable: Licensee may not assign or transfer his rights - and duties under this license. - 2.5. Commercial, Royalty Free: Licensee may use Software for any purpose, including - paid-services, without any royalties - 2.6. Including the Right to Create Derivative Works: Licensee may create derivative works - based on Software, including amending Software’s source code, modifying it, integrating - it into a larger work or removing portions of Software, as long as no distribution of - the derivative works is made - 2.7. With support & maintenance: Licensor shall provide Licensee support and maintenance as follows - - - 1 year (from time of purchase) email support with 48hr response time - - 3. Term & Termination: The Term of this license shall be until terminated. Licensor may - terminate this Agreement, including Licensee’s license in the case where Licensee: - - 3.1. became insolvent or otherwise entered into any liquidation process; or - 3.2. exported The Software to any jurisdiction where licensor may not enforce his - rights under this agreements in; or - 3.3. Licensee was in breach of any of this license's terms and conditions and such - breach was not cured, immediately upon notification; or - 3.4. Licensee in breach of any of the terms of clause 2 to this license; or - 3.5. Licensee otherwise entered into any arrangement which caused Licensor to be - unable to enforce his rights under this License. - - 4. Payment: In consideration of the License granted under clause 2, Licensee shall pay - Licensor a fee, via Credit-Card, PayPal or any other mean which Licensor may deem - adequate. Failure to perform payment shall construe as material breach of this Agreement. - - 5. Upgrades, Updates and Fixes: Licensor may provide Licensee, from time to time, with - Upgrades, Updates or Fixes, as detailed herein and according to his sole discretion. - Licensee hereby warrants to keep The Software up-to-date and install all relevant - updates and fixes, and may, at his sole discretion, purchase upgrades, according to - the rates set by Licensor. Licensor shall provide any update or Fix free of charge; - however, nothing in this Agreement shall require Licensor to provide Updates or Fixes. - - 5.1. Upgrades: for the purpose of this license, an Upgrade shall be a material amendment - in The Software, which contains new features and or major performance improvements - and shall be marked as a new version number. For example, should Licensee purchase - The Software under version 1.X.X, an upgrade shall commence under number 2.0.0. - 5.2. Updates: for the purpose of this license, an update shall be a minor amendment in The - Software, which may contain new features or minor improvements and shall be marked as - a new sub-version number. For example, should Licensee purchase The Software under - version 1.1.X, an upgrade shall commence under number 1.2.0. - 5.3. Fix: for the purpose of this license, a fix shall be a minor amendment in The - Software, intended to remove bugs or alter minor features which impair the The - Software's functionality. A fix shall be marked as a new sub-sub-version number. - For example, should Licensee purchase Software under version 1.1.1, an upgrade - shall commence under number 1.1.2. - - 6. Support: Software is provided with limited support, as detailed in the Software’s SLA - detailed under the License Grant. Licensor shall provide support via electronic mail - and on regular business days and hours. - - 6.1. Bug Notification: Licensee may provide Licensor of details regarding any bug, - defect or failure in The Software promptly and with no delay from such event; - Licensee shall comply with Licensor's request for information regarding bugs, - defects or failures and furnish him with information, screenshots and try to - reproduce such bugs, defects or failures. - 6.2. Feature Request: Licensee may request additional features in Software, provided, - however, that (i) Licensee shall waive any claim or right in such feature should - feature be developed by Licensor; (ii) Licensee shall be prohibited from developing - the feature, or disclose such feature request, or feature, to any 3rd party directly - competing with Licensor or any 3rd party which may be, following the development - of such feature, in direct competition with Licensor; (iii) Licensee warrants that - feature does not infringe any 3rd party patent, trademark, trade-secret or any - other intellectual property right; and (iv) Licensee developed, envisioned or - created the feature solely by himself. - - 7. Liability: To the extent permitted under Law, The Software is provided under an - AS-IS basis. Licensor shall never, and without any limit, be liable for any damage, - cost, expense or any other payment incurred by Licensee as a result of Software’s - actions, failure, bugs and/or any other interaction between The Software and - Licensee’s end-equipment, computers, other software or any 3rd party, end-equipment, - computer or services. Moreover, Licensor shall never be liable for any defect in - source code written by Licensee when relying on The Software or using The Software’s - source code. - - 8. Warranty: - 8.1. Intellectual Property: Licensor hereby warrants that The Software does not violate - or infringe any 3rd party claims in regards to intellectual property, patents and/or - trademarks and that to the best of its knowledge no legal action has been taken - against it for any infringement or violation of any 3rd party intellectual - property rights. - 8.2. No-Warranty: The Software is provided without any warranty; Licensor hereby - disclaims any warranty that The Software shall be error free, without defects - or code which may cause damage to Licensee’s computers or to Licensee, and - that Software shall be functional. Licensee shall be solely liable to any - damage, defect or loss incurred as a result of operating software and undertake - the risks contained in running The Software on License’s Server[s] and Website[s]. - 8.3. Prior Inspection: Licensee hereby states that he inspected The Software thoroughly - and found it satisfactory and adequate to his needs, that it does not interfere - with his regular operation and that it does meet the standards and scope of his - computer systems and architecture. Licensee found that The Software interacts with - his development, website and server environment and that it does not infringe any of - End User License Agreement of any software Licensee may use in performing his - services. Licensee hereby waives any claims regarding The Software's - incompatibility, performance, results and features, and warrants that he - inspected the The Software. - - 9. No Refunds: Licensee warrants that he inspected The Software according to - clause 7(c) and that it is adequate to his needs. Accordingly, as The Software - is intangible goods, Licensee shall not be, ever, entitled to any refund, rebate, - compensation or restitution for any reason whatsoever, even if The Software - contains material flaws. - - 10. Indemnification: Licensee hereby warrants to hold Licensor harmless and indemnify - Licensor for any lawsuit brought against it in regards to Licensee’s use of The - Software in means that violate, breach or otherwise circumvent this license, - Licensor's intellectual property rights or Licensor's title in The Software. - Licensor shall promptly notify Licensee in case of such legal action and request - Licensee’s consent prior to any settlement in relation to such lawsuit or claim. - - 11. Governing Law, Jurisdiction: Licensee hereby agrees not to initiate class-action - lawsuits against Licensor in relation to this license and to compensate Licensor - for any legal fees, cost or attorney fees should any claim brought by Licensee - against Licensor be denied, in part or in full. - diff --git a/package-lock.json b/package-lock.json index 511a15c..b860621 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,8 @@ { "name": "javascript-state-machine", - "version": "3.0.1", + "version": "3.1.0", "lockfileVersion": 1, + "requires": true, "dependencies": { "acorn": { "version": "5.0.3", @@ -14,6 +15,9 @@ "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", "dev": true, + "requires": { + "acorn": "4.0.13" + }, "dependencies": { "acorn": { "version": "4.0.13", @@ -27,7 +31,11 @@ "version": "4.11.8", "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true + "dev": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } }, "ajv-keywords": { "version": "1.5.1", @@ -39,13 +47,21 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true + "dev": true, + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + } }, "ansi-align": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-1.1.0.tgz", "integrity": "sha1-LwwWWIKXOa3V67FeawxuNCPwFro=", - "dev": true + "dev": true, + "requires": { + "string-width": "1.0.2" + } }, "ansi-regex": { "version": "2.1.1", @@ -63,13 +79,20 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz", "integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc=", - "dev": true + "dev": true, + "requires": { + "arrify": "1.0.1", + "micromatch": "2.3.11" + } }, "arr-diff": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true + "dev": true, + "requires": { + "arr-flatten": "1.0.3" + } }, "arr-exclude": { "version": "1.0.0", @@ -99,7 +122,10 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } }, "array-uniq": { "version": "1.0.3", @@ -123,19 +149,30 @@ "version": "4.9.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz", "integrity": "sha1-SLokC0WpKA6UdImQull9IWYX/UA=", - "dev": true + "dev": true, + "requires": { + "bn.js": "4.11.6", + "inherits": "2.0.3", + "minimalistic-assert": "1.0.0" + } }, "assert": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", - "dev": true + "dev": true, + "requires": { + "util": "0.10.3" + } }, "async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/async/-/async-2.4.1.tgz", "integrity": "sha1-YqVrJ5yYoR0JhwlqAcw+6463u9c=", - "dev": true + "dev": true, + "requires": { + "lodash": "4.17.4" + } }, "async-each": { "version": "1.0.1", @@ -153,139 +190,355 @@ "version": "0.17.0", "resolved": "https://registry.npmjs.org/ava/-/ava-0.17.0.tgz", "integrity": "sha1-NZ4qiWFoAe8Dkpw88QqdT45FHQI=", - "dev": true + "dev": true, + "requires": { + "arr-flatten": "1.0.3", + "array-union": "1.0.2", + "array-uniq": "1.0.3", + "arrify": "1.0.1", + "auto-bind": "0.1.0", + "ava-files": "0.2.0", + "ava-init": "0.1.6", + "babel-code-frame": "6.22.0", + "babel-core": "6.25.0", + "babel-plugin-ava-throws-helper": "0.1.0", + "babel-plugin-detective": "2.0.0", + "babel-plugin-espower": "2.3.2", + "babel-plugin-transform-runtime": "6.23.0", + "babel-preset-es2015": "6.24.1", + "babel-preset-es2015-node4": "2.1.1", + "babel-preset-stage-2": "6.24.1", + "babel-runtime": "6.23.0", + "bluebird": "3.5.0", + "caching-transform": "1.0.1", + "chalk": "1.1.3", + "chokidar": "1.7.0", + "clean-yaml-object": "0.1.0", + "cli-cursor": "1.0.2", + "cli-spinners": "0.1.2", + "cli-truncate": "0.2.1", + "co-with-promise": "4.6.0", + "common-path-prefix": "1.0.0", + "convert-source-map": "1.5.0", + "core-assert": "0.2.1", + "currently-unhandled": "0.4.1", + "debug": "2.6.8", + "empower-core": "0.6.2", + "figures": "1.7.0", + "find-cache-dir": "0.1.1", + "fn-name": "2.0.1", + "get-port": "2.1.0", + "has-flag": "2.0.0", + "ignore-by-default": "1.0.1", + "is-ci": "1.0.10", + "is-generator-fn": "1.0.0", + "is-obj": "1.0.1", + "is-observable": "0.2.0", + "is-promise": "2.1.0", + "last-line-stream": "1.0.0", + "lodash.debounce": "4.0.8", + "lodash.difference": "4.5.0", + "lodash.isequal": "4.5.0", + "loud-rejection": "1.6.0", + "matcher": "0.1.2", + "max-timeout": "1.0.0", + "md5-hex": "1.3.0", + "meow": "3.7.0", + "ms": "0.7.3", + "object-assign": "4.1.1", + "observable-to-promise": "0.4.0", + "option-chain": "0.1.1", + "package-hash": "1.2.0", + "pkg-conf": "1.1.3", + "plur": "2.1.2", + "power-assert-context-formatter": "1.1.1", + "power-assert-renderer-assertion": "1.1.1", + "power-assert-renderer-succinct": "1.1.1", + "pretty-ms": "2.1.0", + "repeating": "2.0.1", + "require-precompiled": "0.1.0", + "resolve-cwd": "1.0.0", + "semver": "5.3.0", + "set-immediate-shim": "1.0.1", + "source-map-support": "0.4.15", + "stack-utils": "0.4.0", + "strip-ansi": "3.0.1", + "strip-bom": "2.0.0", + "time-require": "0.1.2", + "unique-temp-dir": "1.0.0", + "update-notifier": "1.0.3" + } }, "ava-files": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/ava-files/-/ava-files-0.2.0.tgz", "integrity": "sha1-x7i24uDOpjtXpuJ+DbFFx8Gc/iA=", - "dev": true + "dev": true, + "requires": { + "auto-bind": "0.1.0", + "bluebird": "3.5.0", + "globby": "6.1.0", + "ignore-by-default": "1.0.1", + "lodash.flatten": "4.4.0", + "multimatch": "2.1.0", + "slash": "1.0.0" + } }, "ava-init": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ava-init/-/ava-init-0.1.6.tgz", "integrity": "sha1-7xntCyS2vzWdrW+63xoF2DY5XJE=", - "dev": true + "dev": true, + "requires": { + "arr-exclude": "1.0.0", + "cross-spawn": "4.0.2", + "pinkie-promise": "2.0.1", + "read-pkg-up": "1.0.1", + "the-argv": "1.0.0", + "write-pkg": "1.0.0" + } }, "babel-code-frame": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", - "dev": true + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.1" + } }, "babel-core": { "version": "6.25.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.25.0.tgz", "integrity": "sha1-fdQrBGPHQunVKW3rPsZ6kyLa1yk=", - "dev": true + "dev": true, + "requires": { + "babel-code-frame": "6.22.0", + "babel-generator": "6.25.0", + "babel-helpers": "6.24.1", + "babel-messages": "6.23.0", + "babel-register": "6.24.1", + "babel-runtime": "6.23.0", + "babel-template": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0", + "babylon": "6.17.3", + "convert-source-map": "1.5.0", + "debug": "2.6.8", + "json5": "0.5.1", + "lodash": "4.17.4", + "minimatch": "3.0.4", + "path-is-absolute": "1.0.1", + "private": "0.1.7", + "slash": "1.0.0", + "source-map": "0.5.6" + } }, "babel-generator": { "version": "6.25.0", "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.25.0.tgz", "integrity": "sha1-M6GvcNXyiQrrRlpKd5PB32qeqfw=", - "dev": true + "dev": true, + "requires": { + "babel-messages": "6.23.0", + "babel-runtime": "6.23.0", + "babel-types": "6.25.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.4", + "source-map": "0.5.6", + "trim-right": "1.0.1" + } }, "babel-helper-bindify-decorators": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz", "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" + } }, "babel-helper-builder-binary-assignment-operator-visitor": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", - "dev": true + "dev": true, + "requires": { + "babel-helper-explode-assignable-expression": "6.24.1", + "babel-runtime": "6.23.0", + "babel-types": "6.25.0" + } }, "babel-helper-call-delegate": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", - "dev": true + "dev": true, + "requires": { + "babel-helper-hoist-variables": "6.24.1", + "babel-runtime": "6.23.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" + } }, "babel-helper-define-map": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.24.1.tgz", "integrity": "sha1-epdH8ljYlH0y1RX2qhx70CIEoIA=", - "dev": true + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.23.0", + "babel-types": "6.25.0", + "lodash": "4.17.4" + } }, "babel-helper-explode-assignable-expression": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" + } }, "babel-helper-explode-class": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz", "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=", - "dev": true + "dev": true, + "requires": { + "babel-helper-bindify-decorators": "6.24.1", + "babel-runtime": "6.23.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" + } }, "babel-helper-function-name": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", - "dev": true + "dev": true, + "requires": { + "babel-helper-get-function-arity": "6.24.1", + "babel-runtime": "6.23.0", + "babel-template": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" + } }, "babel-helper-get-function-arity": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "babel-types": "6.25.0" + } }, "babel-helper-hoist-variables": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "babel-types": "6.25.0" + } }, "babel-helper-optimise-call-expression": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "babel-types": "6.25.0" + } }, "babel-helper-regex": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz", "integrity": "sha1-024i+rEAjXnYhkjjIRaGgShFbOg=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "babel-types": "6.25.0", + "lodash": "4.17.4" + } }, "babel-helper-remap-async-to-generator": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", - "dev": true + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.23.0", + "babel-template": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" + } }, "babel-helper-replace-supers": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", - "dev": true + "dev": true, + "requires": { + "babel-helper-optimise-call-expression": "6.24.1", + "babel-messages": "6.23.0", + "babel-runtime": "6.23.0", + "babel-template": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" + } }, "babel-helpers": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "babel-template": "6.25.0" + } }, "babel-messages": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0" + } }, "babel-plugin-ava-throws-helper": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-ava-throws-helper/-/babel-plugin-ava-throws-helper-0.1.0.tgz", "integrity": "sha1-lREHcIoSIIAmv4ykzvGKh7ybDP4=", - "dev": true + "dev": true, + "requires": { + "babel-template": "6.25.0", + "babel-types": "6.25.0" + } }, "babel-plugin-check-es2015-constants": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0" + } }, "babel-plugin-detective": { "version": "2.0.0", @@ -297,7 +550,16 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/babel-plugin-espower/-/babel-plugin-espower-2.3.2.tgz", "integrity": "sha1-VRa4/NsmyfDh2BYHSfbkxl5xJx4=", - "dev": true + "dev": true, + "requires": { + "babel-generator": "6.25.0", + "babylon": "6.17.3", + "call-matcher": "1.0.1", + "core-js": "2.4.1", + "espower-location-detector": "1.0.0", + "espurify": "1.7.0", + "estraverse": "4.2.0" + } }, "babel-plugin-syntax-async-functions": { "version": "6.13.0", @@ -351,241 +613,471 @@ "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz", "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", - "dev": true + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "6.24.1", + "babel-plugin-syntax-async-generators": "6.13.0", + "babel-runtime": "6.23.0" + } }, "babel-plugin-transform-async-to-generator": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", - "dev": true + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "6.24.1", + "babel-plugin-syntax-async-functions": "6.13.0", + "babel-runtime": "6.23.0" + } }, "babel-plugin-transform-class-properties": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", - "dev": true + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-plugin-syntax-class-properties": "6.13.0", + "babel-runtime": "6.23.0", + "babel-template": "6.25.0" + } }, "babel-plugin-transform-decorators": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz", "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=", - "dev": true + "dev": true, + "requires": { + "babel-helper-explode-class": "6.24.1", + "babel-plugin-syntax-decorators": "6.13.0", + "babel-runtime": "6.23.0", + "babel-template": "6.25.0", + "babel-types": "6.25.0" + } }, "babel-plugin-transform-es2015-arrow-functions": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0" + } }, "babel-plugin-transform-es2015-block-scoped-functions": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0" + } }, "babel-plugin-transform-es2015-block-scoping": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz", "integrity": "sha1-dsKV3DpHQbFmWt/TFnIV3P8ypXY=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "babel-template": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0", + "lodash": "4.17.4" + } }, "babel-plugin-transform-es2015-classes": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", - "dev": true + "dev": true, + "requires": { + "babel-helper-define-map": "6.24.1", + "babel-helper-function-name": "6.24.1", + "babel-helper-optimise-call-expression": "6.24.1", + "babel-helper-replace-supers": "6.24.1", + "babel-messages": "6.23.0", + "babel-runtime": "6.23.0", + "babel-template": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" + } }, "babel-plugin-transform-es2015-computed-properties": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "babel-template": "6.25.0" + } }, "babel-plugin-transform-es2015-destructuring": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0" + } }, "babel-plugin-transform-es2015-duplicate-keys": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "babel-types": "6.25.0" + } }, "babel-plugin-transform-es2015-for-of": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0" + } }, "babel-plugin-transform-es2015-function-name": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", - "dev": true + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.23.0", + "babel-types": "6.25.0" + } }, "babel-plugin-transform-es2015-literals": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0" + } }, "babel-plugin-transform-es2015-modules-amd": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", - "dev": true + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-commonjs": "6.24.1", + "babel-runtime": "6.23.0", + "babel-template": "6.25.0" + } }, "babel-plugin-transform-es2015-modules-commonjs": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz", "integrity": "sha1-0+MQtA72ZKNmIiAAl8bUQCmPK/4=", - "dev": true + "dev": true, + "requires": { + "babel-plugin-transform-strict-mode": "6.24.1", + "babel-runtime": "6.23.0", + "babel-template": "6.25.0", + "babel-types": "6.25.0" + } }, "babel-plugin-transform-es2015-modules-systemjs": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", - "dev": true + "dev": true, + "requires": { + "babel-helper-hoist-variables": "6.24.1", + "babel-runtime": "6.23.0", + "babel-template": "6.25.0" + } }, "babel-plugin-transform-es2015-modules-umd": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", - "dev": true + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-amd": "6.24.1", + "babel-runtime": "6.23.0", + "babel-template": "6.25.0" + } }, "babel-plugin-transform-es2015-object-super": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", - "dev": true + "dev": true, + "requires": { + "babel-helper-replace-supers": "6.24.1", + "babel-runtime": "6.23.0" + } }, "babel-plugin-transform-es2015-parameters": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", - "dev": true + "dev": true, + "requires": { + "babel-helper-call-delegate": "6.24.1", + "babel-helper-get-function-arity": "6.24.1", + "babel-runtime": "6.23.0", + "babel-template": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" + } }, "babel-plugin-transform-es2015-shorthand-properties": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "babel-types": "6.25.0" + } }, "babel-plugin-transform-es2015-spread": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0" + } }, "babel-plugin-transform-es2015-sticky-regex": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", - "dev": true + "dev": true, + "requires": { + "babel-helper-regex": "6.24.1", + "babel-runtime": "6.23.0", + "babel-types": "6.25.0" + } }, "babel-plugin-transform-es2015-template-literals": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0" + } }, "babel-plugin-transform-es2015-typeof-symbol": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0" + } }, "babel-plugin-transform-es2015-unicode-regex": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", - "dev": true + "dev": true, + "requires": { + "babel-helper-regex": "6.24.1", + "babel-runtime": "6.23.0", + "regexpu-core": "2.0.0" + } }, "babel-plugin-transform-exponentiation-operator": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", - "dev": true + "dev": true, + "requires": { + "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", + "babel-plugin-syntax-exponentiation-operator": "6.13.0", + "babel-runtime": "6.23.0" + } }, "babel-plugin-transform-object-rest-spread": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz", "integrity": "sha1-h11ryb52HFiirj/u5dxIldjH+SE=", - "dev": true + "dev": true, + "requires": { + "babel-plugin-syntax-object-rest-spread": "6.13.0", + "babel-runtime": "6.23.0" + } }, "babel-plugin-transform-regenerator": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz", "integrity": "sha1-uNowWtQ8PJm0hI5P5AN7dw0jxBg=", - "dev": true + "dev": true, + "requires": { + "regenerator-transform": "0.9.11" + } }, "babel-plugin-transform-runtime": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz", "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0" + } }, "babel-plugin-transform-strict-mode": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "babel-types": "6.25.0" + } }, "babel-preset-es2015": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", - "dev": true + "dev": true, + "requires": { + "babel-plugin-check-es2015-constants": "6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoping": "6.24.1", + "babel-plugin-transform-es2015-classes": "6.24.1", + "babel-plugin-transform-es2015-computed-properties": "6.24.1", + "babel-plugin-transform-es2015-destructuring": "6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", + "babel-plugin-transform-es2015-for-of": "6.23.0", + "babel-plugin-transform-es2015-function-name": "6.24.1", + "babel-plugin-transform-es2015-literals": "6.22.0", + "babel-plugin-transform-es2015-modules-amd": "6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "6.24.1", + "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", + "babel-plugin-transform-es2015-modules-umd": "6.24.1", + "babel-plugin-transform-es2015-object-super": "6.24.1", + "babel-plugin-transform-es2015-parameters": "6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", + "babel-plugin-transform-es2015-spread": "6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "6.24.1", + "babel-plugin-transform-es2015-template-literals": "6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "6.24.1", + "babel-plugin-transform-regenerator": "6.24.1" + } }, "babel-preset-es2015-node4": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/babel-preset-es2015-node4/-/babel-preset-es2015-node4-2.1.1.tgz", "integrity": "sha1-4x8pCFm1hhnIz6JB0bC8kA+UHNs=", - "dev": true + "dev": true, + "requires": { + "babel-plugin-transform-es2015-destructuring": "6.23.0", + "babel-plugin-transform-es2015-function-name": "6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "6.24.1", + "babel-plugin-transform-es2015-parameters": "6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", + "babel-plugin-transform-es2015-spread": "6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "6.24.1", + "babel-plugin-transform-es2015-unicode-regex": "6.24.1" + } }, "babel-preset-stage-2": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz", "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=", - "dev": true + "dev": true, + "requires": { + "babel-plugin-syntax-dynamic-import": "6.18.0", + "babel-plugin-transform-class-properties": "6.24.1", + "babel-plugin-transform-decorators": "6.24.1", + "babel-preset-stage-3": "6.24.1" + } }, "babel-preset-stage-3": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz", "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", - "dev": true + "dev": true, + "requires": { + "babel-plugin-syntax-trailing-function-commas": "6.22.0", + "babel-plugin-transform-async-generator-functions": "6.24.1", + "babel-plugin-transform-async-to-generator": "6.24.1", + "babel-plugin-transform-exponentiation-operator": "6.24.1", + "babel-plugin-transform-object-rest-spread": "6.23.0" + } }, "babel-register": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.24.1.tgz", "integrity": "sha1-fhDhOi9xBlvfrVoXh7pFvKbe118=", - "dev": true + "dev": true, + "requires": { + "babel-core": "6.25.0", + "babel-runtime": "6.23.0", + "core-js": "2.4.1", + "home-or-tmp": "2.0.0", + "lodash": "4.17.4", + "mkdirp": "0.5.1", + "source-map-support": "0.4.15" + } }, "babel-runtime": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=", - "dev": true + "dev": true, + "requires": { + "core-js": "2.4.1", + "regenerator-runtime": "0.10.5" + } }, "babel-template": { "version": "6.25.0", "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz", "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0", + "babylon": "6.17.3", + "lodash": "4.17.4" + } }, "babel-traverse": { "version": "6.25.0", "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE=", - "dev": true + "dev": true, + "requires": { + "babel-code-frame": "6.22.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.23.0", + "babel-types": "6.25.0", + "babylon": "6.17.3", + "debug": "2.6.8", + "globals": "9.18.0", + "invariant": "2.2.2", + "lodash": "4.17.4" + } }, "babel-types": { "version": "6.25.0", "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz", "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "esutils": "2.0.2", + "lodash": "4.17.4", + "to-fast-properties": "1.0.3" + } }, "babylon": { "version": "6.17.3", @@ -633,19 +1125,39 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-0.6.0.tgz", "integrity": "sha1-g2TUJIrDT/DvGy8r9JpsYM4NgbY=", - "dev": true + "dev": true, + "requires": { + "ansi-align": "1.1.0", + "camelcase": "2.1.1", + "chalk": "1.1.3", + "cli-boxes": "1.0.0", + "filled-array": "1.1.0", + "object-assign": "4.1.1", + "repeating": "2.0.1", + "string-width": "1.0.2", + "widest-line": "1.0.0" + } }, "brace-expansion": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=", - "dev": true + "dev": true, + "requires": { + "balanced-match": "0.4.2", + "concat-map": "0.0.1" + } }, "braces": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } }, "brorand": { "version": "1.1.0", @@ -657,37 +1169,70 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.0.6.tgz", "integrity": "sha1-Xncl297x/Vkw1OurSFZ85FHEigo=", - "dev": true + "dev": true, + "requires": { + "buffer-xor": "1.0.3", + "cipher-base": "1.0.3", + "create-hash": "1.1.3", + "evp_bytestokey": "1.0.0", + "inherits": "2.0.3" + } }, "browserify-cipher": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", - "dev": true + "dev": true, + "requires": { + "browserify-aes": "1.0.6", + "browserify-des": "1.0.0", + "evp_bytestokey": "1.0.0" + } }, "browserify-des": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", - "dev": true + "dev": true, + "requires": { + "cipher-base": "1.0.3", + "des.js": "1.0.0", + "inherits": "2.0.3" + } }, "browserify-rsa": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "dev": true + "dev": true, + "requires": { + "bn.js": "4.11.6", + "randombytes": "2.0.5" + } }, "browserify-sign": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "dev": true + "dev": true, + "requires": { + "bn.js": "4.11.6", + "browserify-rsa": "4.0.1", + "create-hash": "1.1.3", + "create-hmac": "1.1.6", + "elliptic": "6.4.0", + "inherits": "2.0.3", + "parse-asn1": "5.1.0" + } }, "browserify-zlib": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", - "dev": true + "dev": true, + "requires": { + "pako": "0.2.9" + } }, "buf-compare": { "version": "1.0.1", @@ -699,7 +1244,12 @@ "version": "4.9.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", - "dev": true + "dev": true, + "requires": { + "base64-js": "1.2.0", + "ieee754": "1.1.8", + "isarray": "1.0.0" + } }, "buffer-xor": { "version": "1.0.3", @@ -723,13 +1273,24 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-1.0.1.tgz", "integrity": "sha1-bb2y8g+Nj7znnz6U6dF0Lc31wKE=", - "dev": true + "dev": true, + "requires": { + "md5-hex": "1.3.0", + "mkdirp": "0.5.1", + "write-file-atomic": "1.3.4" + } }, "call-matcher": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-matcher/-/call-matcher-1.0.1.tgz", "integrity": "sha1-UTTQd5hPcSpU2tPL9i3ijc5BbKg=", - "dev": true + "dev": true, + "requires": { + "core-js": "2.4.1", + "deep-equal": "1.0.1", + "espurify": "1.7.0", + "estraverse": "4.2.0" + } }, "call-signature": { "version": "0.0.2", @@ -741,7 +1302,11 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", - "dev": true + "dev": true, + "requires": { + "no-case": "2.3.1", + "upper-case": "1.1.3" + } }, "camelcase": { "version": "2.1.1", @@ -753,7 +1318,11 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true + "dev": true, + "requires": { + "camelcase": "2.1.1", + "map-obj": "1.0.1" + } }, "capture-stack-trace": { "version": "1.0.0", @@ -765,19 +1334,41 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true + "dev": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } }, "chokidar": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", - "dev": true + "dev": true, + "requires": { + "anymatch": "1.3.0", + "async-each": "1.0.1", + "fsevents": "1.1.1", + "glob-parent": "2.0.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0" + } }, "ci-info": { "version": "1.0.0", @@ -789,7 +1380,10 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.3.tgz", "integrity": "sha1-7qvxlEGc6QDaMBjCB9IS8qbfCgc=", - "dev": true + "dev": true, + "requires": { + "inherits": "2.0.3" + } }, "clean-yaml-object": { "version": "0.1.0", @@ -807,7 +1401,10 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true + "dev": true, + "requires": { + "restore-cursor": "1.0.1" + } }, "cli-spinners": { "version": "0.1.2", @@ -819,13 +1416,22 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", - "dev": true + "dev": true, + "requires": { + "slice-ansi": "0.0.4", + "string-width": "1.0.2" + } }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true + "dev": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + } }, "co": { "version": "4.6.0", @@ -838,6 +1444,9 @@ "resolved": "https://registry.npmjs.org/co-with-promise/-/co-with-promise-4.6.0.tgz", "integrity": "sha1-QT59tvWJOmC5Qs9JLEvsk9tBWrc=", "dev": true, + "requires": { + "pinkie-promise": "1.0.0" + }, "dependencies": { "pinkie": { "version": "1.0.0", @@ -849,7 +1458,10 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz", "integrity": "sha1-0dpn9UglY7t89X8oauKCLs+/NnA=", - "dev": true + "dev": true, + "requires": { + "pinkie": "1.0.0" + } } } }, @@ -881,13 +1493,27 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/configstore/-/configstore-2.1.0.tgz", "integrity": "sha1-c3o6cDbpiGECqmCZ5HuzOrGroaE=", - "dev": true + "dev": true, + "requires": { + "dot-prop": "3.0.0", + "graceful-fs": "4.1.11", + "mkdirp": "0.5.1", + "object-assign": "4.1.1", + "os-tmpdir": "1.0.2", + "osenv": "0.1.4", + "uuid": "2.0.3", + "write-file-atomic": "1.3.4", + "xdg-basedir": "2.0.0" + } }, "console-browserify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "dev": true + "dev": true, + "requires": { + "date-now": "0.1.4" + } }, "constants-browserify": { "version": "1.0.0", @@ -905,7 +1531,11 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/core-assert/-/core-assert-0.2.1.tgz", "integrity": "sha1-+F4s+b/tKPdzzIs/pcW2m9wC/j8=", - "dev": true + "dev": true, + "requires": { + "buf-compare": "1.0.1", + "is-error": "2.2.1" + } }, "core-js": { "version": "2.4.1", @@ -923,43 +1553,83 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", - "dev": true + "dev": true, + "requires": { + "bn.js": "4.11.6", + "elliptic": "6.4.0" + } }, "create-error-class": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "dev": true + "dev": true, + "requires": { + "capture-stack-trace": "1.0.0" + } }, "create-hash": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", - "dev": true + "dev": true, + "requires": { + "cipher-base": "1.0.3", + "inherits": "2.0.3", + "ripemd160": "2.0.1", + "sha.js": "2.4.8" + } }, "create-hmac": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", - "dev": true + "dev": true, + "requires": { + "cipher-base": "1.0.3", + "create-hash": "1.1.3", + "inherits": "2.0.3", + "ripemd160": "2.0.1", + "safe-buffer": "5.0.1", + "sha.js": "2.4.8" + } }, "cross-spawn": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", - "dev": true + "dev": true, + "requires": { + "lru-cache": "4.1.0", + "which": "1.2.14" + } }, "crypto-browserify": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.0.tgz", "integrity": "sha1-NlKgkGq5sqfgw85mpAjpV6JIVSI=", - "dev": true + "dev": true, + "requires": { + "browserify-cipher": "1.0.0", + "browserify-sign": "4.0.4", + "create-ecdh": "4.0.0", + "create-hash": "1.1.3", + "create-hmac": "1.1.6", + "diffie-hellman": "5.0.2", + "inherits": "2.0.3", + "pbkdf2": "3.0.12", + "public-encrypt": "4.0.0", + "randombytes": "2.0.5" + } }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true + "dev": true, + "requires": { + "array-find-index": "1.0.2" + } }, "date-now": { "version": "0.1.4", @@ -978,6 +1648,9 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", "dev": true, + "requires": { + "ms": "2.0.0" + }, "dependencies": { "ms": { "version": "2.0.0", @@ -1009,19 +1682,31 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", - "dev": true + "dev": true, + "requires": { + "inherits": "2.0.3", + "minimalistic-assert": "1.0.0" + } }, "detect-indent": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true + "dev": true, + "requires": { + "repeating": "2.0.1" + } }, "diffie-hellman": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", - "dev": true + "dev": true, + "requires": { + "bn.js": "4.11.6", + "miller-rabin": "4.0.0", + "randombytes": "2.0.5" + } }, "domain-browser": { "version": "1.1.7", @@ -1033,13 +1718,19 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", - "dev": true + "dev": true, + "requires": { + "is-obj": "1.0.1" + } }, "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "dev": true + "dev": true, + "requires": { + "readable-stream": "2.2.11" + } }, "eastasianwidth": { "version": "0.1.1", @@ -1051,7 +1742,16 @@ "version": "6.4.0", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", - "dev": true + "dev": true, + "requires": { + "bn.js": "4.11.6", + "brorand": "1.1.0", + "hash.js": "1.0.3", + "hmac-drbg": "1.0.1", + "inherits": "2.0.3", + "minimalistic-assert": "1.0.0", + "minimalistic-crypto-utils": "1.0.1" + } }, "emojis-list": { "version": "2.1.0", @@ -1063,25 +1763,41 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/empower-core/-/empower-core-0.6.2.tgz", "integrity": "sha1-Wt71ZgiOMfuoC6CjbfR9cJQWkUQ=", - "dev": true + "dev": true, + "requires": { + "call-signature": "0.0.2", + "core-js": "2.4.1" + } }, "enhanced-resolve": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.1.0.tgz", "integrity": "sha1-n0tib1dyRe3PSyrYPYbhf09CHew=", - "dev": true + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "memory-fs": "0.4.1", + "object-assign": "4.1.1", + "tapable": "0.2.6" + } }, "errno": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz", "integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=", - "dev": true + "dev": true, + "requires": { + "prr": "0.0.0" + } }, "error-ex": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "dev": true + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } }, "escape-string-regexp": { "version": "1.0.5", @@ -1093,13 +1809,22 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/espower-location-detector/-/espower-location-detector-1.0.0.tgz", "integrity": "sha1-oXt+zFnTDheeK+9z+0E3cEyzMbU=", - "dev": true + "dev": true, + "requires": { + "is-url": "1.2.2", + "path-is-absolute": "1.0.1", + "source-map": "0.5.6", + "xtend": "4.0.1" + } }, "espurify": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/espurify/-/espurify-1.7.0.tgz", "integrity": "sha1-HFz2y8zDLm9jk4C9T5kfq5up0iY=", - "dev": true + "dev": true, + "requires": { + "core-js": "2.4.1" + } }, "estraverse": { "version": "4.2.0", @@ -1123,7 +1848,10 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.0.tgz", "integrity": "sha1-SXtmrZ/vZc18CKYYCCS6FHa2blM=", - "dev": true + "dev": true, + "requires": { + "create-hash": "1.1.3" + } }, "exit-hook": { "version": "1.1.1", @@ -1135,25 +1863,38 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } }, "expand-range": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true + "dev": true, + "requires": { + "fill-range": "2.2.3" + } }, "extglob": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } }, "figures": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } }, "filename-regex": { "version": "2.0.1", @@ -1165,7 +1906,14 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", - "dev": true + "dev": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } }, "filled-array": { "version": "1.1.0", @@ -1177,13 +1925,22 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", - "dev": true + "dev": true, + "requires": { + "commondir": "1.0.1", + "mkdirp": "0.5.1", + "pkg-dir": "1.0.0" + } }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } }, "fn-name": { "version": "2.0.1", @@ -1201,13 +1958,23 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true + "dev": true, + "requires": { + "for-in": "1.0.2" + } }, "fs-sync": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/fs-sync/-/fs-sync-1.0.4.tgz", "integrity": "sha1-L5Tq3jGGLsCp8zocJUbfsaPz0a4=", - "dev": true + "dev": true, + "requires": { + "glob": "7.1.2", + "iconv-lite": "0.4.17", + "lodash": "4.17.4", + "mkdirp": "0.5.1", + "rimraf": "2.6.1" + } }, "fs.realpath": { "version": "1.0.0", @@ -1221,6 +1988,10 @@ "integrity": "sha1-8Z/Sj0Pur3YWgOUZogPE0LPTGv8=", "dev": true, "optional": true, + "requires": { + "nan": "2.6.2", + "node-pre-gyp": "0.6.33" + }, "dependencies": { "abbrev": { "version": "1.1.0", @@ -1249,7 +2020,11 @@ "version": "1.1.2", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.2.2" + } }, "asn1": { "version": "0.2.3", @@ -1290,22 +2065,35 @@ "version": "1.0.1", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } }, "block-stream": { "version": "0.0.9", "bundled": true, - "dev": true + "dev": true, + "requires": { + "inherits": "2.0.3" + } }, "boom": { "version": "2.10.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "hoek": "2.16.3" + } }, "brace-expansion": { "version": "1.1.6", "bundled": true, - "dev": true + "dev": true, + "requires": { + "balanced-match": "0.4.2", + "concat-map": "0.0.1" + } }, "buffer-shims": { "version": "1.0.0", @@ -1322,7 +2110,14 @@ "version": "1.1.3", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } }, "code-point-at": { "version": "1.1.0", @@ -1332,13 +2127,19 @@ "combined-stream": { "version": "1.0.5", "bundled": true, - "dev": true + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } }, "commander": { "version": "2.9.0", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "graceful-readlink": "1.0.1" + } }, "concat-map": { "version": "0.0.1", @@ -1359,13 +2160,19 @@ "version": "2.0.5", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "boom": "2.10.1" + } }, "dashdash": { "version": "1.14.1", "bundled": true, "dev": true, "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, "dependencies": { "assert-plus": { "version": "1.0.0", @@ -1379,7 +2186,10 @@ "version": "2.2.0", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "ms": "0.7.1" + } }, "deep-extend": { "version": "0.4.1", @@ -1402,7 +2212,10 @@ "version": "0.1.1", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "jsbn": "0.1.1" + } }, "escape-string-regexp": { "version": "1.0.5", @@ -1431,7 +2244,12 @@ "version": "2.1.2", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.14" + } }, "fs.realpath": { "version": "1.0.0", @@ -1441,19 +2259,40 @@ "fstream": { "version": "1.0.10", "bundled": true, - "dev": true + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.5.4" + } }, "fstream-ignore": { "version": "1.0.5", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "fstream": "1.0.10", + "inherits": "2.0.3", + "minimatch": "3.0.3" + } }, "gauge": { "version": "2.7.3", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "aproba": "1.1.1", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.0" + } }, "generate-function": { "version": "2.0.0", @@ -1465,13 +2304,19 @@ "version": "1.2.0", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "is-property": "1.0.2" + } }, "getpass": { "version": "0.1.6", "bundled": true, "dev": true, "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, "dependencies": { "assert-plus": { "version": "1.0.0", @@ -1484,7 +2329,15 @@ "glob": { "version": "7.1.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.3", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } }, "graceful-fs": { "version": "4.1.11", @@ -1501,13 +2354,22 @@ "version": "2.0.6", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "chalk": "1.1.3", + "commander": "2.9.0", + "is-my-json-valid": "2.15.0", + "pinkie-promise": "2.0.1" + } }, "has-ansi": { "version": "2.0.0", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "ansi-regex": "2.1.1" + } }, "has-unicode": { "version": "2.0.1", @@ -1519,7 +2381,13 @@ "version": "3.1.3", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } }, "hoek": { "version": "2.16.3", @@ -1530,12 +2398,21 @@ "version": "1.1.1", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.3.1", + "sshpk": "1.10.2" + } }, "inflight": { "version": "1.0.6", "bundled": true, - "dev": true + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } }, "inherits": { "version": "2.0.3", @@ -1551,13 +2428,22 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } }, "is-my-json-valid": { "version": "2.15.0", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.1" + } }, "is-property": { "version": "1.0.2", @@ -1586,7 +2472,10 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "jsbn": "0.1.1" + } }, "jsbn": { "version": "0.1.1", @@ -1616,7 +2505,12 @@ "version": "1.3.1", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "extsprintf": "1.0.2", + "json-schema": "0.2.3", + "verror": "1.3.6" + } }, "mime-db": { "version": "1.26.0", @@ -1626,12 +2520,18 @@ "mime-types": { "version": "2.1.14", "bundled": true, - "dev": true + "dev": true, + "requires": { + "mime-db": "1.26.0" + } }, "minimatch": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "requires": { + "brace-expansion": "1.1.6" + } }, "minimist": { "version": "0.0.8", @@ -1641,7 +2541,10 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "minimist": "0.0.8" + } }, "ms": { "version": "0.7.1", @@ -1653,19 +2556,39 @@ "version": "0.6.33", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "npmlog": "4.0.2", + "rc": "1.1.7", + "request": "2.79.0", + "rimraf": "2.5.4", + "semver": "5.3.0", + "tar": "2.2.1", + "tar-pack": "3.3.0" + } }, "nopt": { "version": "3.0.6", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "abbrev": "1.1.0" + } }, "npmlog": { "version": "4.0.2", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "are-we-there-yet": "1.1.2", + "console-control-strings": "1.1.0", + "gauge": "2.7.3", + "set-blocking": "2.0.0" + } }, "number-is-nan": { "version": "1.0.1", @@ -1687,7 +2610,10 @@ "once": { "version": "1.4.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "wrappy": "1.0.2" + } }, "path-is-absolute": { "version": "1.0.1", @@ -1704,7 +2630,10 @@ "version": "2.0.1", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "pinkie": "2.0.4" + } }, "process-nextick-args": { "version": "1.0.7", @@ -1728,6 +2657,12 @@ "bundled": true, "dev": true, "optional": true, + "requires": { + "deep-extend": "0.4.1", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, "dependencies": { "minimist": { "version": "1.2.0", @@ -1741,18 +2676,52 @@ "version": "2.2.2", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } }, "request": { "version": "2.79.0", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.11.0", + "combined-stream": "1.0.5", + "extend": "3.0.0", + "forever-agent": "0.6.1", + "form-data": "2.1.2", + "har-validator": "2.0.6", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.14", + "oauth-sign": "0.8.2", + "qs": "6.3.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.4.3", + "uuid": "3.0.1" + } }, "rimraf": { "version": "2.5.4", "bundled": true, - "dev": true + "dev": true, + "requires": { + "glob": "7.1.1" + } }, "semver": { "version": "5.3.0", @@ -1776,13 +2745,27 @@ "version": "1.0.9", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "hoek": "2.16.3" + } }, "sshpk": { "version": "1.10.2", "bundled": true, "dev": true, "optional": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.6", + "jodid25519": "1.0.2", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, "dependencies": { "assert-plus": { "version": "1.0.0", @@ -1792,14 +2775,19 @@ } } }, - "string_decoder": { - "version": "0.10.31", - "bundled": true, - "dev": true - }, "string-width": { "version": "1.0.2", "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true, "dev": true }, "stringstream": { @@ -1811,7 +2799,10 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } }, "strip-json-comments": { "version": "2.0.1", @@ -1828,25 +2819,52 @@ "tar": { "version": "2.2.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.10", + "inherits": "2.0.3" + } }, "tar-pack": { "version": "3.3.0", "bundled": true, "dev": true, "optional": true, + "requires": { + "debug": "2.2.0", + "fstream": "1.0.10", + "fstream-ignore": "1.0.5", + "once": "1.3.3", + "readable-stream": "2.1.5", + "rimraf": "2.5.4", + "tar": "2.2.1", + "uid-number": "0.0.6" + }, "dependencies": { "once": { "version": "1.3.3", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "wrappy": "1.0.2" + } }, "readable-stream": { "version": "2.1.5", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } } } }, @@ -1854,7 +2872,10 @@ "version": "2.3.2", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "punycode": "1.4.1" + } }, "tunnel-agent": { "version": "0.4.3", @@ -1889,13 +2910,19 @@ "version": "1.3.6", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "extsprintf": "1.0.2" + } }, "wide-align": { "version": "1.1.0", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "string-width": "1.0.2" + } }, "wrappy": { "version": "1.0.2", @@ -1920,7 +2947,10 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/get-port/-/get-port-2.1.0.tgz", "integrity": "sha1-h4P53OvR7qSVozThpqJR54iHqxo=", - "dev": true + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } }, "get-stdin": { "version": "4.0.1", @@ -1932,19 +2962,34 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } }, "glob-base": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true + "dev": true, + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + } }, "glob-parent": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true + "dev": true, + "requires": { + "is-glob": "2.0.1" + } }, "globals": { "version": "9.18.0", @@ -1956,13 +3001,37 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true + "dev": true, + "requires": { + "array-union": "1.0.2", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } }, "got": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz", "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=", - "dev": true + "dev": true, + "requires": { + "create-error-class": "3.0.2", + "duplexer2": "0.1.4", + "is-redirect": "1.0.0", + "is-retry-allowed": "1.1.0", + "is-stream": "1.1.0", + "lowercase-keys": "1.0.0", + "node-status-codes": "1.0.0", + "object-assign": "4.1.1", + "parse-json": "2.2.0", + "pinkie-promise": "2.0.1", + "read-all-stream": "3.1.0", + "readable-stream": "2.2.11", + "timed-out": "3.1.3", + "unzip-response": "1.0.2", + "url-parse-lax": "1.0.0" + } }, "graceful-fs": { "version": "4.1.11", @@ -1974,7 +3043,10 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } }, "has-color": { "version": "0.1.7", @@ -1992,25 +3064,40 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", - "dev": true + "dev": true, + "requires": { + "inherits": "2.0.3" + } }, "hash.js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz", "integrity": "sha1-EzL/ABVsCg/92CNgE9B7d6BFFXM=", - "dev": true + "dev": true, + "requires": { + "inherits": "2.0.3" + } }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true + "dev": true, + "requires": { + "hash.js": "1.0.3", + "minimalistic-assert": "1.0.0", + "minimalistic-crypto-utils": "1.0.1" + } }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "dev": true + "dev": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } }, "hosted-git-info": { "version": "2.4.2", @@ -2052,7 +3139,10 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true + "dev": true, + "requires": { + "repeating": "2.0.1" + } }, "indexof": { "version": "0.0.1", @@ -2064,7 +3154,11 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } }, "inherits": { "version": "2.0.3", @@ -2088,7 +3182,10 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", - "dev": true + "dev": true, + "requires": { + "loose-envify": "1.3.1" + } }, "invert-kv": { "version": "1.0.0", @@ -2112,7 +3209,10 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true + "dev": true, + "requires": { + "binary-extensions": "1.8.0" + } }, "is-buffer": { "version": "1.1.5", @@ -2124,13 +3224,19 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } }, "is-ci": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.0.10.tgz", "integrity": "sha1-9zkzayYyNlBhqdSCcM1WrjNpMY4=", - "dev": true + "dev": true, + "requires": { + "ci-info": "1.0.0" + } }, "is-dotfile": { "version": "1.0.3", @@ -2142,7 +3248,10 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true + "dev": true, + "requires": { + "is-primitive": "2.0.0" + } }, "is-error": { "version": "2.2.1", @@ -2166,13 +3275,19 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } }, "is-generator-fn": { "version": "1.0.0", @@ -2184,7 +3299,10 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } }, "is-npm": { "version": "1.0.0", @@ -2196,7 +3314,10 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true + "dev": true, + "requires": { + "kind-of": "3.2.2" + } }, "is-obj": { "version": "1.0.1", @@ -2208,7 +3329,10 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-0.2.0.tgz", "integrity": "sha1-s2ExHYPG5dcmyr9eJQsCNxBvWuI=", - "dev": true + "dev": true, + "requires": { + "symbol-observable": "0.2.4" + } }, "is-plain-obj": { "version": "1.1.0", @@ -2280,7 +3404,10 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true + "dev": true, + "requires": { + "isarray": "1.0.0" + } }, "js-tokens": { "version": "3.0.1", @@ -2304,7 +3431,10 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true + "dev": true, + "requires": { + "jsonify": "0.0.0" + } }, "json5": { "version": "0.5.1", @@ -2322,19 +3452,28 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true + "dev": true, + "requires": { + "is-buffer": "1.1.5" + } }, "last-line-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/last-line-stream/-/last-line-stream-1.0.0.tgz", "integrity": "sha1-0bZNafhv8kry0EiDos7uFFIKVgA=", - "dev": true + "dev": true, + "requires": { + "through2": "2.0.3" + } }, "latest-version": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-2.0.0.tgz", "integrity": "sha1-VvjWE5YghHuAF/jx9NeOIRMkFos=", - "dev": true + "dev": true, + "requires": { + "package-json": "2.4.0" + } }, "lazy-cache": { "version": "1.0.4", @@ -2352,13 +3491,23 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true + "dev": true, + "requires": { + "invert-kv": "1.0.0" + } }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } }, "loader-runner": { "version": "2.3.0", @@ -2370,7 +3519,13 @@ "version": "0.2.17", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", - "dev": true + "dev": true, + "requires": { + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1", + "object-assign": "4.1.1" + } }, "lodash": { "version": "4.17.4", @@ -2412,13 +3567,20 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", - "dev": true + "dev": true, + "requires": { + "js-tokens": "3.0.1" + } }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true + "dev": true, + "requires": { + "currently-unhandled": "0.4.1", + "signal-exit": "3.0.2" + } }, "lower-case": { "version": "1.1.4", @@ -2436,7 +3598,11 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.0.tgz", "integrity": "sha512-aHGs865JXz6bkB4AHL+3AhyvTFKL3iZamKVWjIUKnXOXyasJvqPK8WAjOnAQKQZVpeXDVz19u1DD0r/12bWAdQ==", - "dev": true + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } }, "map-obj": { "version": "1.0.1", @@ -2448,7 +3614,10 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/matcher/-/matcher-0.1.2.tgz", "integrity": "sha1-7yDL3mTCTFDMYa9bg+4LG4/wAQE=", - "dev": true + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5" + } }, "max-timeout": { "version": "1.0.0", @@ -2460,7 +3629,10 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-1.3.0.tgz", "integrity": "sha1-0sSv6YPENwZiF5uMrRRSGRNQRsQ=", - "dev": true + "dev": true, + "requires": { + "md5-o-matic": "0.1.1" + } }, "md5-o-matic": { "version": "0.1.1", @@ -2472,13 +3644,29 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true + "dev": true, + "requires": { + "errno": "0.1.4", + "readable-stream": "2.2.11" + } }, "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, + "requires": { + "camelcase-keys": "2.1.0", + "decamelize": "1.2.0", + "loud-rejection": "1.6.0", + "map-obj": "1.0.1", + "minimist": "1.2.0", + "normalize-package-data": "2.3.8", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "redent": "1.0.0", + "trim-newlines": "1.0.0" + }, "dependencies": { "minimist": { "version": "1.2.0", @@ -2492,13 +3680,32 @@ "version": "2.3.11", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.3" + } }, "miller-rabin": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.0.tgz", "integrity": "sha1-SmL7HUKTPAVYOYL0xxb2+55sbT0=", - "dev": true + "dev": true, + "requires": { + "bn.js": "4.11.6", + "brorand": "1.1.0" + } }, "minimalistic-assert": { "version": "1.0.0", @@ -2516,7 +3723,10 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true + "dev": true, + "requires": { + "brace-expansion": "1.1.7" + } }, "minimist": { "version": "0.0.8", @@ -2528,7 +3738,10 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true + "dev": true, + "requires": { + "minimist": "0.0.8" + } }, "ms": { "version": "0.7.3", @@ -2540,7 +3753,13 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", - "dev": true + "dev": true, + "requires": { + "array-differ": "1.0.0", + "array-union": "1.0.2", + "arrify": "1.0.1", + "minimatch": "3.0.4" + } }, "nan": { "version": "2.6.2", @@ -2553,13 +3772,41 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.1.tgz", "integrity": "sha1-euuhxzpSGEJlVUt9wDuvcg34AIE=", - "dev": true + "dev": true, + "requires": { + "lower-case": "1.1.4" + } }, "node-libs-browser": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.0.0.tgz", "integrity": "sha1-o6WeyXAkmFtG6Vg3lkb5bEthZkY=", "dev": true, + "requires": { + "assert": "1.4.1", + "browserify-zlib": "0.1.4", + "buffer": "4.9.1", + "console-browserify": "1.1.0", + "constants-browserify": "1.0.0", + "crypto-browserify": "3.11.0", + "domain-browser": "1.1.7", + "events": "1.1.1", + "https-browserify": "0.0.1", + "os-browserify": "0.2.1", + "path-browserify": "0.0.0", + "process": "0.11.10", + "punycode": "1.4.1", + "querystring-es3": "0.2.1", + "readable-stream": "2.2.11", + "stream-browserify": "2.0.1", + "stream-http": "2.7.1", + "string_decoder": "0.10.31", + "timers-browserify": "2.0.2", + "tty-browserify": "0.0.0", + "url": "0.11.0", + "util": "0.10.3", + "vm-browserify": "0.0.4" + }, "dependencies": { "string_decoder": { "version": "0.10.31", @@ -2579,13 +3826,22 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.8.tgz", "integrity": "sha1-2Bntoqne29H/pWPqQHHZNngilbs=", - "dev": true + "dev": true, + "requires": { + "hosted-git-info": "2.4.2", + "is-builtin-module": "1.0.0", + "semver": "5.3.0", + "validate-npm-package-license": "3.0.1" + } }, "normalize-path": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true + "dev": true, + "requires": { + "remove-trailing-separator": "1.0.2" + } }, "number-is-nan": { "version": "1.0.1", @@ -2598,11 +3854,45 @@ "resolved": "https://registry.npmjs.org/nyc/-/nyc-10.3.2.tgz", "integrity": "sha1-8n9NkfKp2zbCT1dP9cbv/wIz3kY=", "dev": true, + "requires": { + "archy": "1.0.0", + "arrify": "1.0.1", + "caching-transform": "1.0.1", + "convert-source-map": "1.5.0", + "debug-log": "1.0.1", + "default-require-extensions": "1.0.0", + "find-cache-dir": "0.1.1", + "find-up": "1.1.2", + "foreground-child": "1.5.6", + "glob": "7.1.1", + "istanbul-lib-coverage": "1.1.0", + "istanbul-lib-hook": "1.0.6", + "istanbul-lib-instrument": "1.7.1", + "istanbul-lib-report": "1.1.0", + "istanbul-lib-source-maps": "1.2.0", + "istanbul-reports": "1.1.0", + "md5-hex": "1.3.0", + "merge-source-map": "1.0.3", + "micromatch": "2.3.11", + "mkdirp": "0.5.1", + "resolve-from": "2.0.0", + "rimraf": "2.6.1", + "signal-exit": "3.0.2", + "spawn-wrap": "1.2.4", + "test-exclude": "4.1.0", + "yargs": "7.1.0", + "yargs-parser": "5.0.0" + }, "dependencies": { "align-text": { "version": "0.1.4", "bundled": true, - "dev": true + "dev": true, + "requires": { + "kind-of": "3.2.0", + "longest": "1.0.1", + "repeat-string": "1.6.1" + } }, "amdefine": { "version": "1.0.1", @@ -2622,7 +3912,10 @@ "append-transform": { "version": "0.4.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "default-require-extensions": "1.0.0" + } }, "archy": { "version": "1.0.0", @@ -2632,7 +3925,10 @@ "arr-diff": { "version": "2.0.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "arr-flatten": "1.0.3" + } }, "arr-flatten": { "version": "1.0.3", @@ -2657,37 +3953,83 @@ "babel-code-frame": { "version": "6.22.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.1" + } }, "babel-generator": { "version": "6.24.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "babel-messages": "6.23.0", + "babel-runtime": "6.23.0", + "babel-types": "6.24.1", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.4", + "source-map": "0.5.6", + "trim-right": "1.0.1" + } }, "babel-messages": { "version": "6.23.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0" + } }, "babel-runtime": { "version": "6.23.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "core-js": "2.4.1", + "regenerator-runtime": "0.10.5" + } }, "babel-template": { "version": "6.24.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "babel-traverse": "6.24.1", + "babel-types": "6.24.1", + "babylon": "6.17.0", + "lodash": "4.17.4" + } }, "babel-traverse": { "version": "6.24.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "babel-code-frame": "6.22.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.23.0", + "babel-types": "6.24.1", + "babylon": "6.17.0", + "debug": "2.6.6", + "globals": "9.17.0", + "invariant": "2.2.2", + "lodash": "4.17.4" + } }, "babel-types": { "version": "6.24.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "esutils": "2.0.2", + "lodash": "4.17.4", + "to-fast-properties": "1.0.3" + } }, "babylon": { "version": "6.17.0", @@ -2702,12 +4044,21 @@ "brace-expansion": { "version": "1.1.7", "bundled": true, - "dev": true + "dev": true, + "requires": { + "balanced-match": "0.4.2", + "concat-map": "0.0.1" + } }, "braces": { "version": "1.8.5", "bundled": true, - "dev": true + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } }, "builtin-modules": { "version": "1.1.1", @@ -2717,7 +4068,12 @@ "caching-transform": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "md5-hex": "1.3.0", + "mkdirp": "0.5.1", + "write-file-atomic": "1.3.4" + } }, "camelcase": { "version": "1.2.1", @@ -2729,18 +4085,34 @@ "version": "0.1.3", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } }, "chalk": { "version": "1.1.3", "bundled": true, - "dev": true + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } }, "cliui": { "version": "2.1.0", "bundled": true, "dev": true, "optional": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + }, "dependencies": { "wordwrap": { "version": "0.0.2", @@ -2778,12 +4150,19 @@ "cross-spawn": { "version": "4.0.2", "bundled": true, - "dev": true + "dev": true, + "requires": { + "lru-cache": "4.0.2", + "which": "1.2.14" + } }, "debug": { "version": "2.6.6", "bundled": true, - "dev": true + "dev": true, + "requires": { + "ms": "0.7.3" + } }, "debug-log": { "version": "1.0.1", @@ -2798,17 +4177,26 @@ "default-require-extensions": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "strip-bom": "2.0.0" + } }, "detect-indent": { "version": "4.0.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "repeating": "2.0.1" + } }, "error-ex": { "version": "1.3.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } }, "escape-string-regexp": { "version": "1.0.5", @@ -2823,17 +4211,26 @@ "expand-brackets": { "version": "0.1.5", "bundled": true, - "dev": true + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } }, "expand-range": { "version": "1.8.2", "bundled": true, - "dev": true + "dev": true, + "requires": { + "fill-range": "2.2.3" + } }, "extglob": { "version": "0.3.2", "bundled": true, - "dev": true + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } }, "filename-regex": { "version": "2.0.1", @@ -2843,17 +4240,33 @@ "fill-range": { "version": "2.2.3", "bundled": true, - "dev": true + "dev": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.6", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } }, "find-cache-dir": { "version": "0.1.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "commondir": "1.0.1", + "mkdirp": "0.5.1", + "pkg-dir": "1.0.0" + } }, "find-up": { "version": "1.1.2", "bundled": true, - "dev": true + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } }, "for-in": { "version": "1.0.2", @@ -2863,12 +4276,19 @@ "for-own": { "version": "0.1.5", "bundled": true, - "dev": true + "dev": true, + "requires": { + "for-in": "1.0.2" + } }, "foreground-child": { "version": "1.5.6", "bundled": true, - "dev": true + "dev": true, + "requires": { + "cross-spawn": "4.0.2", + "signal-exit": "3.0.2" + } }, "fs.realpath": { "version": "1.0.0", @@ -2883,17 +4303,32 @@ "glob": { "version": "7.1.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.3", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } }, "glob-base": { "version": "0.3.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + } }, "glob-parent": { "version": "2.0.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "is-glob": "2.0.1" + } }, "globals": { "version": "9.17.0", @@ -2909,18 +4344,30 @@ "version": "4.0.8", "bundled": true, "dev": true, + "requires": { + "async": "1.5.2", + "optimist": "0.6.1", + "source-map": "0.4.4", + "uglify-js": "2.8.22" + }, "dependencies": { "source-map": { "version": "0.4.4", "bundled": true, - "dev": true + "dev": true, + "requires": { + "amdefine": "1.0.1" + } } } }, "has-ansi": { "version": "2.0.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } }, "has-flag": { "version": "1.0.0", @@ -2940,7 +4387,11 @@ "inflight": { "version": "1.0.6", "bundled": true, - "dev": true + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } }, "inherits": { "version": "2.0.3", @@ -2950,7 +4401,10 @@ "invariant": { "version": "2.2.2", "bundled": true, - "dev": true + "dev": true, + "requires": { + "loose-envify": "1.3.1" + } }, "invert-kv": { "version": "1.0.0", @@ -2970,7 +4424,10 @@ "is-builtin-module": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } }, "is-dotfile": { "version": "1.0.2", @@ -2980,7 +4437,10 @@ "is-equal-shallow": { "version": "0.1.3", "bundled": true, - "dev": true + "dev": true, + "requires": { + "is-primitive": "2.0.0" + } }, "is-extendable": { "version": "0.1.1", @@ -2995,22 +4455,34 @@ "is-finite": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } }, "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } }, "is-glob": { "version": "2.0.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } }, "is-number": { "version": "2.1.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "kind-of": "3.2.0" + } }, "is-posix-bracket": { "version": "0.1.1", @@ -3040,7 +4512,10 @@ "isobject": { "version": "2.1.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "isarray": "1.0.0" + } }, "istanbul-lib-coverage": { "version": "1.1.0", @@ -3050,34 +4525,65 @@ "istanbul-lib-hook": { "version": "1.0.6", "bundled": true, - "dev": true + "dev": true, + "requires": { + "append-transform": "0.4.0" + } }, "istanbul-lib-instrument": { "version": "1.7.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "babel-generator": "6.24.1", + "babel-template": "6.24.1", + "babel-traverse": "6.24.1", + "babel-types": "6.24.1", + "babylon": "6.17.0", + "istanbul-lib-coverage": "1.1.0", + "semver": "5.3.0" + } }, "istanbul-lib-report": { "version": "1.1.0", "bundled": true, "dev": true, + "requires": { + "istanbul-lib-coverage": "1.1.0", + "mkdirp": "0.5.1", + "path-parse": "1.0.5", + "supports-color": "3.2.3" + }, "dependencies": { "supports-color": { "version": "3.2.3", "bundled": true, - "dev": true + "dev": true, + "requires": { + "has-flag": "1.0.0" + } } } }, "istanbul-lib-source-maps": { "version": "1.2.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "debug": "2.6.6", + "istanbul-lib-coverage": "1.1.0", + "mkdirp": "0.5.1", + "rimraf": "2.6.1", + "source-map": "0.5.6" + } }, "istanbul-reports": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "handlebars": "4.0.8" + } }, "js-tokens": { "version": "3.0.1", @@ -3092,7 +4598,10 @@ "kind-of": { "version": "3.2.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "is-buffer": "1.1.5" + } }, "lazy-cache": { "version": "1.0.4", @@ -3103,12 +4612,22 @@ "lcid": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "invert-kv": "1.0.0" + } }, "load-json-file": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } }, "lodash": { "version": "4.17.4", @@ -3123,17 +4642,27 @@ "loose-envify": { "version": "1.3.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "js-tokens": "3.0.1" + } }, "lru-cache": { "version": "4.0.2", "bundled": true, - "dev": true + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } }, "md5-hex": { "version": "1.3.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "md5-o-matic": "0.1.1" + } }, "md5-o-matic": { "version": "0.1.1", @@ -3143,17 +4672,38 @@ "merge-source-map": { "version": "1.0.3", "bundled": true, - "dev": true + "dev": true, + "requires": { + "source-map": "0.5.6" + } }, "micromatch": { "version": "2.3.11", "bundled": true, - "dev": true + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.0", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.3" + } }, "minimatch": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "requires": { + "brace-expansion": "1.1.7" + } }, "minimist": { "version": "0.0.8", @@ -3163,7 +4713,10 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "minimist": "0.0.8" + } }, "ms": { "version": "0.7.3", @@ -3173,12 +4726,21 @@ "normalize-package-data": { "version": "2.3.8", "bundled": true, - "dev": true + "dev": true, + "requires": { + "hosted-git-info": "2.4.2", + "is-builtin-module": "1.0.0", + "semver": "5.3.0", + "validate-npm-package-license": "3.0.1" + } }, "normalize-path": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "remove-trailing-separator": "1.0.1" + } }, "number-is-nan": { "version": "1.0.1", @@ -3193,17 +4755,28 @@ "object.omit": { "version": "2.0.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } }, "once": { "version": "1.4.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "wrappy": "1.0.2" + } }, "optimist": { "version": "0.6.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "minimist": "0.0.8", + "wordwrap": "0.0.3" + } }, "os-homedir": { "version": "1.0.2", @@ -3213,22 +4786,37 @@ "os-locale": { "version": "1.4.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "lcid": "1.0.0" + } }, "parse-glob": { "version": "3.0.4", "bundled": true, - "dev": true + "dev": true, + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.2", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + } }, "parse-json": { "version": "2.2.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "error-ex": "1.3.1" + } }, "path-exists": { "version": "2.1.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } }, "path-is-absolute": { "version": "1.0.1", @@ -3243,7 +4831,12 @@ "path-type": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } }, "pify": { "version": "2.3.0", @@ -3258,12 +4851,18 @@ "pinkie-promise": { "version": "2.0.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "pinkie": "2.0.4" + } }, "pkg-dir": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "find-up": "1.1.2" + } }, "preserve": { "version": "0.2.0", @@ -3278,17 +4877,30 @@ "randomatic": { "version": "1.1.6", "bundled": true, - "dev": true + "dev": true, + "requires": { + "is-number": "2.1.0", + "kind-of": "3.2.0" + } }, "read-pkg": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.3.8", + "path-type": "1.1.0" + } }, "read-pkg-up": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + } }, "regenerator-runtime": { "version": "0.10.5", @@ -3298,7 +4910,11 @@ "regex-cache": { "version": "0.4.3", "bundled": true, - "dev": true + "dev": true, + "requires": { + "is-equal-shallow": "0.1.3", + "is-primitive": "2.0.0" + } }, "remove-trailing-separator": { "version": "1.0.1", @@ -3318,7 +4934,10 @@ "repeating": { "version": "2.0.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "is-finite": "1.0.2" + } }, "require-directory": { "version": "2.1.1", @@ -3339,12 +4958,18 @@ "version": "0.1.3", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "align-text": "0.1.4" + } }, "rimraf": { "version": "2.6.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "glob": "7.1.1" + } }, "semver": { "version": "5.3.0", @@ -3375,6 +5000,14 @@ "version": "1.2.4", "bundled": true, "dev": true, + "requires": { + "foreground-child": "1.5.6", + "mkdirp": "0.5.1", + "os-homedir": "1.0.2", + "rimraf": "2.6.1", + "signal-exit": "2.1.2", + "which": "1.2.14" + }, "dependencies": { "signal-exit": { "version": "2.1.2", @@ -3386,7 +5019,10 @@ "spdx-correct": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "requires": { + "spdx-license-ids": "1.2.2" + } }, "spdx-expression-parse": { "version": "1.0.4", @@ -3401,17 +5037,28 @@ "string-width": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } }, "strip-ansi": { "version": "3.0.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } }, "strip-bom": { "version": "2.0.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } }, "supports-color": { "version": "2.0.0", @@ -3421,7 +5068,14 @@ "test-exclude": { "version": "4.1.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "arrify": "1.0.1", + "micromatch": "2.3.11", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "require-main-filename": "1.0.1" + } }, "to-fast-properties": { "version": "1.0.3", @@ -3438,12 +5092,23 @@ "bundled": true, "dev": true, "optional": true, + "requires": { + "source-map": "0.5.6", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, "dependencies": { "yargs": { "version": "3.10.0", "bundled": true, "dev": true, - "optional": true + "optional": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } } } }, @@ -3456,12 +5121,19 @@ "validate-npm-package-license": { "version": "3.0.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + } }, "which": { "version": "1.2.14", "bundled": true, - "dev": true + "dev": true, + "requires": { + "isexe": "2.0.0" + } }, "which-module": { "version": "1.0.0", @@ -3482,7 +5154,11 @@ "wrap-ansi": { "version": "2.1.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + } }, "wrappy": { "version": "1.0.2", @@ -3492,7 +5168,12 @@ "write-file-atomic": { "version": "1.3.4", "bundled": true, - "dev": true + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "slide": "1.1.6" + } }, "y18n": { "version": "3.2.1", @@ -3508,6 +5189,21 @@ "version": "7.1.0", "bundled": true, "dev": true, + "requires": { + "camelcase": "3.0.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "os-locale": "1.4.0", + "read-pkg-up": "1.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "1.0.2", + "which-module": "1.0.0", + "y18n": "3.2.1", + "yargs-parser": "5.0.0" + }, "dependencies": { "camelcase": { "version": "3.0.0", @@ -3517,7 +5213,12 @@ "cliui": { "version": "3.2.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + } } } }, @@ -3525,6 +5226,9 @@ "version": "5.0.0", "bundled": true, "dev": true, + "requires": { + "camelcase": "3.0.0" + }, "dependencies": { "camelcase": { "version": "3.0.0", @@ -3545,19 +5249,30 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true + "dev": true, + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } }, "observable-to-promise": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/observable-to-promise/-/observable-to-promise-0.4.0.tgz", "integrity": "sha1-KK/nFkUwjy1B1x9HrT/s4aN35Ss=", - "dev": true + "dev": true, + "requires": { + "is-observable": "0.2.0", + "symbol-observable": "0.2.4" + } }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true + "dev": true, + "requires": { + "wrappy": "1.0.2" + } }, "onetime": { "version": "1.1.0", @@ -3569,7 +5284,10 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/option-chain/-/option-chain-0.1.1.tgz", "integrity": "sha1-6bgR4AbxwPVIAvKClb/Ilw+Nz70=", - "dev": true + "dev": true, + "requires": { + "object-assign": "4.1.1" + } }, "os-browserify": { "version": "0.2.1", @@ -3587,7 +5305,10 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true + "dev": true, + "requires": { + "lcid": "1.0.0" + } }, "os-tmpdir": { "version": "1.0.2", @@ -3599,19 +5320,32 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", - "dev": true + "dev": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } }, "package-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-1.2.0.tgz", "integrity": "sha1-AD5WzVe3NqbtYRTMK4FUJnJ3DkQ=", - "dev": true + "dev": true, + "requires": { + "md5-hex": "1.3.0" + } }, "package-json": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz", "integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=", - "dev": true + "dev": true, + "requires": { + "got": "5.7.1", + "registry-auth-token": "3.3.1", + "registry-url": "3.1.0", + "semver": "5.3.0" + } }, "pako": { "version": "0.2.9", @@ -3623,19 +5357,35 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", - "dev": true + "dev": true, + "requires": { + "asn1.js": "4.9.1", + "browserify-aes": "1.0.6", + "create-hash": "1.1.3", + "evp_bytestokey": "1.0.0", + "pbkdf2": "3.0.12" + } }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true + "dev": true, + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + } }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true + "dev": true, + "requires": { + "error-ex": "1.3.1" + } }, "parse-ms": { "version": "1.0.1", @@ -3647,7 +5397,11 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz", "integrity": "sha1-LVeNNFX2YNpl7KGO+VtODekSdh4=", - "dev": true + "dev": true, + "requires": { + "camel-case": "3.0.0", + "upper-case-first": "1.1.2" + } }, "path-browserify": { "version": "0.0.0", @@ -3659,7 +5413,10 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } }, "path-is-absolute": { "version": "1.0.1", @@ -3671,13 +5428,25 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } }, "pbkdf2": { "version": "3.0.12", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.12.tgz", "integrity": "sha1-vjZ4XFBn6kjYBv+SMojF91C2uKI=", - "dev": true + "dev": true, + "requires": { + "create-hash": "1.1.3", + "create-hmac": "1.1.6", + "ripemd160": "2.0.1", + "safe-buffer": "5.0.1", + "sha.js": "2.4.8" + } }, "pify": { "version": "2.3.0", @@ -3695,43 +5464,70 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true + "dev": true, + "requires": { + "pinkie": "2.0.4" + } }, "pkg-conf": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-1.1.3.tgz", "integrity": "sha1-N45W1v0T6Iv7b0ol33qD+qvduls=", - "dev": true + "dev": true, + "requires": { + "find-up": "1.1.2", + "load-json-file": "1.1.0", + "object-assign": "4.1.1", + "symbol": "0.2.3" + } }, "pkg-dir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", - "dev": true + "dev": true, + "requires": { + "find-up": "1.1.2" + } }, "plur": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz", "integrity": "sha1-dIJFLBoPUI4+NE6uwxLJHCncZVo=", - "dev": true + "dev": true, + "requires": { + "irregular-plurals": "1.2.0" + } }, "power-assert-context-formatter": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/power-assert-context-formatter/-/power-assert-context-formatter-1.1.1.tgz", "integrity": "sha1-7bo1LT7YpgMRTWZyZazOYNaJzN8=", - "dev": true + "dev": true, + "requires": { + "core-js": "2.4.1", + "power-assert-context-traversal": "1.1.1" + } }, "power-assert-context-traversal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/power-assert-context-traversal/-/power-assert-context-traversal-1.1.1.tgz", "integrity": "sha1-iMq8oNE7Y1nwfT0+ivppkmRXftk=", - "dev": true + "dev": true, + "requires": { + "core-js": "2.4.1", + "estraverse": "4.2.0" + } }, "power-assert-renderer-assertion": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/power-assert-renderer-assertion/-/power-assert-renderer-assertion-1.1.1.tgz", "integrity": "sha1-y/wOd+AIao+Wrz8djme57n4ozpg=", - "dev": true + "dev": true, + "requires": { + "power-assert-renderer-base": "1.1.1", + "power-assert-util-string-width": "1.1.1" + } }, "power-assert-renderer-base": { "version": "1.1.1", @@ -3743,19 +5539,32 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/power-assert-renderer-diagram/-/power-assert-renderer-diagram-1.1.2.tgz", "integrity": "sha1-ZV+PcRk1qbbVQbhjJ2VHF8Y3qYY=", - "dev": true + "dev": true, + "requires": { + "core-js": "2.4.1", + "power-assert-renderer-base": "1.1.1", + "power-assert-util-string-width": "1.1.1", + "stringifier": "1.3.0" + } }, "power-assert-renderer-succinct": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/power-assert-renderer-succinct/-/power-assert-renderer-succinct-1.1.1.tgz", "integrity": "sha1-wqRosjgiq9b4Diq6UyI0ewnfR24=", - "dev": true + "dev": true, + "requires": { + "core-js": "2.4.1", + "power-assert-renderer-diagram": "1.1.2" + } }, "power-assert-util-string-width": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/power-assert-util-string-width/-/power-assert-util-string-width-1.1.1.tgz", "integrity": "sha1-vmWet5N/3S5smncmjar2S9W3xZI=", - "dev": true + "dev": true, + "requires": { + "eastasianwidth": "0.1.1" + } }, "prepend-http": { "version": "1.0.4", @@ -3774,6 +5583,11 @@ "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-2.1.0.tgz", "integrity": "sha1-QlfCVt8/sLRR1q/6qwIYhBJpgdw=", "dev": true, + "requires": { + "is-finite": "1.0.2", + "parse-ms": "1.0.1", + "plur": "1.0.0" + }, "dependencies": { "plur": { "version": "1.0.0", @@ -3817,7 +5631,14 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", - "dev": true + "dev": true, + "requires": { + "bn.js": "4.11.6", + "browserify-rsa": "4.0.1", + "create-hash": "1.1.3", + "parse-asn1": "5.1.0", + "randombytes": "2.0.5" + } }, "punycode": { "version": "1.4.1", @@ -3842,18 +5663,28 @@ "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, "dependencies": { "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, + "requires": { + "kind-of": "3.2.2" + }, "dependencies": { "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true + "dev": true, + "requires": { + "is-buffer": "1.1.5" + } } } }, @@ -3861,7 +5692,10 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true + "dev": true, + "requires": { + "is-buffer": "1.1.5" + } } } }, @@ -3870,6 +5704,9 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz", "integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==", "dev": true, + "requires": { + "safe-buffer": "5.1.0" + }, "dependencies": { "safe-buffer": { "version": "5.1.0", @@ -3884,6 +5721,12 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", "dev": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, "dependencies": { "minimist": { "version": "1.2.0", @@ -3897,37 +5740,69 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=", - "dev": true + "dev": true, + "requires": { + "pinkie-promise": "2.0.1", + "readable-stream": "2.2.11" + } }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.3.8", + "path-type": "1.1.0" + } }, "read-pkg-up": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + } }, "readable-stream": { "version": "2.2.11", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.11.tgz", "integrity": "sha512-h+8+r3MKEhkiVrwdKL8aWs1oc1VvBu33ueshOvS26RsZQ3Amhx/oO3TKe4lApSV9ueY6as8EAh7mtuFjdlhg9Q==", - "dev": true + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.0.1", + "string_decoder": "1.0.2", + "util-deprecate": "1.0.2" + } }, "readdirp": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", - "dev": true + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "readable-stream": "2.2.11", + "set-immediate-shim": "1.0.1" + } }, "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true + "dev": true, + "requires": { + "indent-string": "2.1.0", + "strip-indent": "1.0.1" + } }, "regenerate": { "version": "1.3.2", @@ -3945,31 +5820,52 @@ "version": "0.9.11", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.9.11.tgz", "integrity": "sha1-On0GdSDLe3F2dp61/4aGkb7+EoM=", - "dev": true + "dev": true, + "requires": { + "babel-runtime": "6.23.0", + "babel-types": "6.25.0", + "private": "0.1.7" + } }, "regex-cache": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz", "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=", - "dev": true + "dev": true, + "requires": { + "is-equal-shallow": "0.1.3", + "is-primitive": "2.0.0" + } }, "regexpu-core": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", - "dev": true + "dev": true, + "requires": { + "regenerate": "1.3.2", + "regjsgen": "0.2.0", + "regjsparser": "0.1.5" + } }, "registry-auth-token": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz", "integrity": "sha1-+w0yie4Nmtosu1KvXf5mywcNMAY=", - "dev": true + "dev": true, + "requires": { + "rc": "1.2.1", + "safe-buffer": "5.0.1" + } }, "registry-url": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", - "dev": true + "dev": true, + "requires": { + "rc": "1.2.1" + } }, "regjsgen": { "version": "0.2.0", @@ -3982,6 +5878,9 @@ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "dev": true, + "requires": { + "jsesc": "0.5.0" + }, "dependencies": { "jsesc": { "version": "0.5.0", @@ -4013,7 +5912,10 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true + "dev": true, + "requires": { + "is-finite": "1.0.2" + } }, "require-directory": { "version": "2.1.1", @@ -4037,7 +5939,10 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-1.0.0.tgz", "integrity": "sha1-Tq7qQe0EDRcCRX32SkKysH0kb58=", - "dev": true + "dev": true, + "requires": { + "resolve-from": "2.0.0" + } }, "resolve-from": { "version": "2.0.0", @@ -4049,25 +5954,39 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true + "dev": true, + "requires": { + "exit-hook": "1.1.1", + "onetime": "1.1.0" + } }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true + "dev": true, + "requires": { + "align-text": "0.1.4" + } }, "rimraf": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", - "dev": true + "dev": true, + "requires": { + "glob": "7.1.2" + } }, "ripemd160": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", - "dev": true + "dev": true, + "requires": { + "hash-base": "2.0.2", + "inherits": "2.0.3" + } }, "safe-buffer": { "version": "5.0.1", @@ -4085,7 +6004,10 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", - "dev": true + "dev": true, + "requires": { + "semver": "5.3.0" + } }, "set-blocking": { "version": "2.0.0", @@ -4109,7 +6031,10 @@ "version": "2.4.8", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.8.tgz", "integrity": "sha1-NwaMLEdra69ALRSknGf1l5IfY08=", - "dev": true + "dev": true, + "requires": { + "inherits": "2.0.3" + } }, "signal-exit": { "version": "3.0.2", @@ -4139,7 +6064,10 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true + "dev": true, + "requires": { + "is-plain-obj": "1.1.0" + } }, "source-list-map": { "version": "1.1.2", @@ -4157,13 +6085,19 @@ "version": "0.4.15", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=", - "dev": true + "dev": true, + "requires": { + "source-map": "0.5.6" + } }, "spdx-correct": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", - "dev": true + "dev": true, + "requires": { + "spdx-license-ids": "1.2.2" + } }, "spdx-expression-parse": { "version": "1.0.4", @@ -4187,49 +6121,82 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", - "dev": true + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.2.11" + } }, "stream-http": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.1.tgz", "integrity": "sha1-VGpRdBrVprB+njGwsQRBqRffUoo=", - "dev": true - }, - "string_decoder": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz", - "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk=", - "dev": true + "dev": true, + "requires": { + "builtin-status-codes": "3.0.0", + "inherits": "2.0.3", + "readable-stream": "2.2.11", + "to-arraybuffer": "1.0.1", + "xtend": "4.0.1" + } }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz", + "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk=", + "dev": true, + "requires": { + "safe-buffer": "5.0.1" + } }, "stringifier": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/stringifier/-/stringifier-1.3.0.tgz", "integrity": "sha1-3vGDQvaTPbDy2/yaoCF1tEjBeVk=", - "dev": true + "dev": true, + "requires": { + "core-js": "2.4.1", + "traverse": "0.6.6", + "type-name": "2.0.2" + } }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } }, "strip-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true + "dev": true, + "requires": { + "get-stdin": "4.0.1" + } }, "strip-json-comments": { "version": "2.0.1", @@ -4277,13 +6244,23 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", - "dev": true + "dev": true, + "requires": { + "readable-stream": "2.2.11", + "xtend": "4.0.1" + } }, "time-require": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/time-require/-/time-require-0.1.2.tgz", "integrity": "sha1-+eEss3D8JgXhFARYK6VO9corLZg=", "dev": true, + "requires": { + "chalk": "0.4.0", + "date-time": "0.1.1", + "pretty-ms": "0.2.2", + "text-table": "0.2.0" + }, "dependencies": { "ansi-styles": { "version": "1.0.0", @@ -4295,7 +6272,12 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", - "dev": true + "dev": true, + "requires": { + "ansi-styles": "1.0.0", + "has-color": "0.1.7", + "strip-ansi": "0.1.1" + } }, "parse-ms": { "version": "0.1.2", @@ -4307,7 +6289,10 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-0.2.2.tgz", "integrity": "sha1-2oeaaC/zOjcBEEbxPWJ/Z8c7hPY=", - "dev": true + "dev": true, + "requires": { + "parse-ms": "0.1.2" + } }, "strip-ansi": { "version": "0.1.1", @@ -4327,7 +6312,10 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.2.tgz", "integrity": "sha1-q0iDz1l9zVCvIRNJoA+8pWrIa4Y=", - "dev": true + "dev": true, + "requires": { + "setimmediate": "1.0.5" + } }, "to-arraybuffer": { "version": "1.0.1", @@ -4375,7 +6363,12 @@ "version": "2.8.28", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.28.tgz", "integrity": "sha512-WqKNbmNJKzIdIEQu/U2ytgGBbhCy2PVks94GoetczOAJ/zCgVu2CuO7gguI5KPFGPtUtI1dmPQl6h0D4cPzypA==", - "dev": true + "dev": true, + "requires": { + "source-map": "0.5.6", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + } }, "uglify-to-browserify": { "version": "1.0.2", @@ -4394,7 +6387,12 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unique-temp-dir/-/unique-temp-dir-1.0.0.tgz", "integrity": "sha1-bc6VsmgcoAPuv7MEpBX5y6vMU4U=", - "dev": true + "dev": true, + "requires": { + "mkdirp": "0.5.1", + "os-tmpdir": "1.0.2", + "uid2": "0.0.3" + } }, "unzip-response": { "version": "1.0.2", @@ -4406,7 +6404,17 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-1.0.3.tgz", "integrity": "sha1-j5LFFUgr1oMbfJMBPnD4dVLHz1o=", - "dev": true + "dev": true, + "requires": { + "boxen": "0.6.0", + "chalk": "1.1.3", + "configstore": "2.1.0", + "is-npm": "1.0.0", + "latest-version": "2.0.0", + "lazy-req": "1.1.0", + "semver-diff": "2.1.0", + "xdg-basedir": "2.0.0" + } }, "upper-case": { "version": "1.1.3", @@ -4418,13 +6426,20 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz", "integrity": "sha1-XXm+3P8UQZUY/S7bCgUHybaFkRU=", - "dev": true + "dev": true, + "requires": { + "upper-case": "1.1.3" + } }, "url": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, "dependencies": { "punycode": { "version": "1.3.2", @@ -4438,13 +6453,19 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dev": true + "dev": true, + "requires": { + "prepend-http": "1.0.4" + } }, "util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, + "requires": { + "inherits": "2.0.1" + }, "dependencies": { "inherits": { "version": "2.0.1", @@ -4470,25 +6491,60 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", - "dev": true + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + } }, "vm-browserify": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", - "dev": true + "dev": true, + "requires": { + "indexof": "0.0.1" + } }, "watchpack": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.3.1.tgz", "integrity": "sha1-fYaTkHsozmAT5/NhCqKhrPB9rYc=", - "dev": true + "dev": true, + "requires": { + "async": "2.4.1", + "chokidar": "1.7.0", + "graceful-fs": "4.1.11" + } }, "webpack": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-2.6.1.tgz", "integrity": "sha1-LgRX8KuxrF3zqxBsacZy8jZ4Xwc=", "dev": true, + "requires": { + "acorn": "5.0.3", + "acorn-dynamic-import": "2.0.2", + "ajv": "4.11.8", + "ajv-keywords": "1.5.1", + "async": "2.4.1", + "enhanced-resolve": "3.1.0", + "interpret": "1.0.3", + "json-loader": "0.5.4", + "json5": "0.5.1", + "loader-runner": "2.3.0", + "loader-utils": "0.2.17", + "memory-fs": "0.4.1", + "mkdirp": "0.5.1", + "node-libs-browser": "2.0.0", + "source-map": "0.5.6", + "supports-color": "3.2.3", + "tapable": "0.2.6", + "uglify-js": "2.8.28", + "watchpack": "1.3.1", + "webpack-sources": "0.2.3", + "yargs": "6.6.0" + }, "dependencies": { "camelcase": { "version": "3.0.0", @@ -4500,7 +6556,12 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + } }, "has-flag": { "version": "1.0.0", @@ -4512,13 +6573,31 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true + "dev": true, + "requires": { + "has-flag": "1.0.0" + } }, "yargs": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", - "dev": true + "dev": true, + "requires": { + "camelcase": "3.0.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "os-locale": "1.4.0", + "read-pkg-up": "1.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "1.0.2", + "which-module": "1.0.0", + "y18n": "3.2.1", + "yargs-parser": "4.2.1" + } } } }, @@ -4526,13 +6605,20 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-0.2.3.tgz", "integrity": "sha1-F8Yr+vE8cH+dAsR54Nzd6DgGl/s=", - "dev": true + "dev": true, + "requires": { + "source-list-map": "1.1.2", + "source-map": "0.5.6" + } }, "which": { "version": "1.2.14", "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", - "dev": true + "dev": true, + "requires": { + "isexe": "2.0.0" + } }, "which-module": { "version": "1.0.0", @@ -4544,7 +6630,10 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-1.0.0.tgz", "integrity": "sha1-DAnIXCqUaD0Nfq+O4JfVZL8OEFw=", - "dev": true + "dev": true, + "requires": { + "string-width": "1.0.2" + } }, "window-size": { "version": "0.1.0", @@ -4562,7 +6651,11 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + } }, "wrappy": { "version": "1.0.2", @@ -4574,25 +6667,45 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", - "dev": true + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "slide": "1.1.6" + } }, "write-json-file": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-1.2.0.tgz", "integrity": "sha1-LV3+lqvDyIkFfJOXGqQAXvtUgTQ=", - "dev": true + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "mkdirp": "0.5.1", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "sort-keys": "1.1.2", + "write-file-atomic": "1.3.4" + } }, "write-pkg": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/write-pkg/-/write-pkg-1.0.0.tgz", "integrity": "sha1-rriqnU14jh2JPfsIVJaLVDqRn1c=", - "dev": true + "dev": true, + "requires": { + "write-json-file": "1.2.0" + } }, "xdg-basedir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz", "integrity": "sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=", - "dev": true + "dev": true, + "requires": { + "os-homedir": "1.0.2" + } }, "xtend": { "version": "4.0.1", @@ -4617,6 +6730,12 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "dev": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + }, "dependencies": { "camelcase": { "version": "1.2.1", @@ -4631,6 +6750,9 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", "dev": true, + "requires": { + "camelcase": "3.0.0" + }, "dependencies": { "camelcase": { "version": "3.0.0", diff --git a/package.json b/package.json index 4c937ed..7eee8a9 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "email": "jake@codeincomplete.com" } ], - "license": "LGPL-3.0", + "license": "MIT", "main": "lib/state-machine.js", "files": [ "lib/**/*.js", @@ -38,7 +38,7 @@ "uglify-js": "^2.7.5", "webpack": "^2.2.0-rc.1" }, - "version": "3.0.1", + "version": "3.1.0", "scripts": { "start": "npm run watch", "build": "npm run bundle && npm run minify", From 0d603577423244228cebcd62e60dbbfff27c6ea3 Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Thu, 12 Jul 2018 07:33:43 -0700 Subject: [PATCH 86/87] update npm version badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5cba155..e9b9cd3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Javascript State Machine -[![NPM version](https://img.shields.io/npm/v/javascript-state-machine.svg?style=flat)](https://www.npmjs.org/package/javascript-state-machine) +[![NPM version](https://badge.fury.io/js/javascript-state-machine.svg)](https://badge.fury.io/js/javascript-state-machine) [![Build Status](https://travis-ci.org/jakesgordon/javascript-state-machine.svg?branch=master)](https://travis-ci.org/jakesgordon/javascript-state-machine) A library for finite state machines. From 2ae84bbbaad13103be43b3e0a24c077002e2301a Mon Sep 17 00:00:00 2001 From: Jake Gordon Date: Sun, 1 Jun 2025 15:01:46 -0700 Subject: [PATCH 87/87] update links s/codeincomplete.com/jakesgordon.com/ --- README.md | 4 ++-- RELEASE_NOTES.md | 8 ++++---- package.json | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e9b9cd3..a2c4add 100644 --- a/README.md +++ b/README.md @@ -144,5 +144,5 @@ See [MIT LICENSE](https://github.com/jakesgordon/javascript-state-machine/blob/m # Contact If you have any ideas, feedback, requests or bug reports, you can reach me at -[jake@codeincomplete.com](mailto:jake@codeincomplete.com), or via -my website: [Code inComplete](http://codeincomplete.com/) +[jakesgordon@gmail.com](mailto:jakesgordon@gmail.com), or via +my website: [jakesgordon.com](https://jakesgordon.com/) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 0527d19..67de268 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -81,7 +81,7 @@ Version 2.2.0 (January 26th 2013) * Added generic state callbacks 'onleavestate' and 'onenterstate' (issue #28) * Fixed 'undefined' event return codes (issue #34) - pull from gentooboontoo (thanks!) * Allow async event transition to be cancelled (issue #22) - * [read more...](http://codeincomplete.com/posts/2013/1/26/javascript_state_machine_v2_2_0/) + * [read more...](https://jakesgordon.com/writing/javascript-state-machine-v2-2-0/) Version 2.1.0 (January 7th 2012) -------------------------------- @@ -104,14 +104,14 @@ Version 2.0.0 (August 19th 2011) * added a generic `onchangestate(event,from,to)` callback to detect all state changes with a single function. * allow callbacks to be declared at creation time (instead of having to attach them afterwards) * renamed 'hooks' => 'callbacks' - * [read more...](http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/) + * [read more...](https://jakesgordon.com/writing/javascript-state-machine-v2/) Version 1.2.0 (June 21st 2011) ------------------------------ * allows the same event to transition to different states, depending on the current state (see 'Multiple...' section in README.md) - * [read more...](http://codeincomplete.com/posts/2011/6/21/javascript_state_machine_v1_2_0/) + * [read more...](https://jakesgordon.com/writing/javascript-state-machine-v1-2-0/) Version 1.0.0 (June 1st 2011) ----------------------------- * initial version - * [read more...](http://codeincomplete.com/posts/2011/6/1/javascript_state_machine/) + * [read more...](https://jakesgordon.com/writing/javascript-state-machine/) diff --git a/package.json b/package.json index 7eee8a9..a143584 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,12 @@ ], "author": { "name": "Jake Gordon", - "email": "jake@codeincomplete.com" + "email": "jakesgordon@gmail.com" }, "maintainers": [ { "name": "Jake Gordon", - "email": "jake@codeincomplete.com" + "email": "jakesgordon@gmail.com" } ], "license": "MIT",