Permalink
Browse files

Allow no-op transition event if 'to' parameter is not specified (gith…

…ub issue #5)

  #5
  • Loading branch information...
1 parent 25b9d49 commit 69808b33f2588a32913c2c932bf072c57d71d7cd @jakesgordon committed Jan 7, 2012
Showing with 73 additions and 3 deletions.
  1. +1 −1 state-machine.js
  2. +1 −1 state-machine.min.js
  3. +43 −1 test/test_advanced.js
  4. +28 −0 test/test_basics.js
View
2 state-machine.js
@@ -26,7 +26,7 @@ StateMachine = {
var from = (e.from instanceof Array) ? e.from : [e.from];
map[e.name] = map[e.name] || {};
for (var n = 0 ; n < from.length ; n++)
- map[e.name][from[n]] = e.to;
+ map[e.name][from[n]] = e.to || from[n]; // allow no-op transition if 'to' is not specified
};
if (initial) {
View
2 state-machine.min.js
@@ -1 +1 @@
-StateMachine={VERSION:"2.0.1",Error:{INVALID_TRANSITION:100,PENDING_TRANSITION:200,INVALID_CALLBACK:300,},create:function(e,f){var h=(typeof e.initial=="string")?{state:e.initial}:e.initial;var d=f||e.target||{};var j=e.events||[];var g=e.callbacks||{};var b={};var i=function(k){var m=(k.from instanceof Array)?k.from:[k.from];b[k.name]=b[k.name]||{};for(var l=0;l<m.length;l++){b[k.name][m[l]]=k.to}};if(h){h.event=h.event||"startup";i({name:h.event,from:"none",to:h.state})}for(var c=0;c<j.length;c++){i(j[c])}for(var a in b){if(b.hasOwnProperty(a)){d[a]=StateMachine.buildEvent(a,b[a])}}for(var a in g){if(g.hasOwnProperty(a)){d[a]=g[a]}}d.current="none";d.is=function(k){return this.current==k};d.can=function(k){return !!b[k][this.current]&&!this.transition};d.cannot=function(k){return !this.can(k)};d.error=e.error||function(m,p,o,l,k,n){throw n};if(h&&!h.defer){d[h.event]()}return d},doCallback:function(f,c,b,h,g,a){if(c){try{return c.apply(f,[b,h,g].concat(a))}catch(d){return f.error(b,h,g,a,StateMachine.Error.INVALID_CALLBACK,"an exception occurred in a caller-provided callback function")}}},beforeEvent:function(c,b,e,d,a){return StateMachine.doCallback(c,c["onbefore"+b],b,e,d,a)},afterEvent:function(c,b,e,d,a){return StateMachine.doCallback(c,c["onafter"+b]||c["on"+b],b,e,d,a)},leaveState:function(c,b,e,d,a){return StateMachine.doCallback(c,c["onleave"+e],b,e,d,a)},enterState:function(c,b,e,d,a){return StateMachine.doCallback(c,c["onenter"+d]||c["on"+d],b,e,d,a)},changeState:function(c,b,e,d,a){return StateMachine.doCallback(c,c.onchangestate,b,e,d,a)},buildEvent:function(a,b){return function(){if(this.transition){return this.error(a,f,e,c,StateMachine.Error.PENDING_TRANSITION,"event "+a+" inappropriate because previous transition did not complete")}if(this.cannot(a)){return this.error(a,f,e,c,StateMachine.Error.INVALID_TRANSITION,"event "+a+" inappropriate in current state "+this.current)}var f=this.current;var e=b[f];var c=Array.prototype.slice.call(arguments);if(false===StateMachine.beforeEvent(this,a,f,e,c)){return}if(f!==e){var d=this;this.transition=function(){d.transition=null;d.current=e;StateMachine.enterState(d,a,f,e,c);StateMachine.changeState(d,a,f,e,c);StateMachine.afterEvent(d,a,f,e,c)};if(false!==StateMachine.leaveState(this,a,f,e,c)){if(this.transition){this.transition()}}return}StateMachine.afterEvent(this,a,f,e,c)}}};
+StateMachine={VERSION:"2.0.1",Error:{INVALID_TRANSITION:100,PENDING_TRANSITION:200,INVALID_CALLBACK:300,},create:function(e,f){var h=(typeof e.initial=="string")?{state:e.initial}:e.initial;var d=f||e.target||{};var j=e.events||[];var g=e.callbacks||{};var b={};var i=function(k){var m=(k.from instanceof Array)?k.from:[k.from];b[k.name]=b[k.name]||{};for(var l=0;l<m.length;l++){b[k.name][m[l]]=k.to||m[l]}};if(h){h.event=h.event||"startup";i({name:h.event,from:"none",to:h.state})}for(var c=0;c<j.length;c++){i(j[c])}for(var a in b){if(b.hasOwnProperty(a)){d[a]=StateMachine.buildEvent(a,b[a])}}for(var a in g){if(g.hasOwnProperty(a)){d[a]=g[a]}}d.current="none";d.is=function(k){return this.current==k};d.can=function(k){return !!b[k][this.current]&&!this.transition};d.cannot=function(k){return !this.can(k)};d.error=e.error||function(m,p,o,l,k,n){throw n};if(h&&!h.defer){d[h.event]()}return d},doCallback:function(f,c,b,h,g,a){if(c){try{return c.apply(f,[b,h,g].concat(a))}catch(d){return f.error(b,h,g,a,StateMachine.Error.INVALID_CALLBACK,"an exception occurred in a caller-provided callback function")}}},beforeEvent:function(c,b,e,d,a){return StateMachine.doCallback(c,c["onbefore"+b],b,e,d,a)},afterEvent:function(c,b,e,d,a){return StateMachine.doCallback(c,c["onafter"+b]||c["on"+b],b,e,d,a)},leaveState:function(c,b,e,d,a){return StateMachine.doCallback(c,c["onleave"+e],b,e,d,a)},enterState:function(c,b,e,d,a){return StateMachine.doCallback(c,c["onenter"+d]||c["on"+d],b,e,d,a)},changeState:function(c,b,e,d,a){return StateMachine.doCallback(c,c.onchangestate,b,e,d,a)},buildEvent:function(a,b){return function(){if(this.transition){return this.error(a,f,e,c,StateMachine.Error.PENDING_TRANSITION,"event "+a+" inappropriate because previous transition did not complete")}if(this.cannot(a)){return this.error(a,f,e,c,StateMachine.Error.INVALID_TRANSITION,"event "+a+" inappropriate in current state "+this.current)}var f=this.current;var e=b[f];var c=Array.prototype.slice.call(arguments);if(false===StateMachine.beforeEvent(this,a,f,e,c)){return}if(f!==e){var d=this;this.transition=function(){d.transition=null;d.current=e;StateMachine.enterState(d,a,f,e,c);StateMachine.changeState(d,a,f,e,c);StateMachine.afterEvent(d,a,f,e,c)};if(false!==StateMachine.leaveState(this,a,f,e,c)){if(this.transition){this.transition()}}return}StateMachine.afterEvent(this,a,f,e,c)}}};
View
44 test/test_advanced.js
@@ -18,7 +18,7 @@ test("multiple 'from' states for the same event", function() {
equals(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 NOT be able to panic 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")
@@ -66,6 +66,48 @@ test("multiple 'to' states for the same event", function() {
//-----------------------------------------------------------------------------
+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' },
+ ]});
+
+ equals(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(); equals(fsm.current, 'green', "noop event should not transition");
+ fsm.warn(); equals(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(); equals(fsm.current, 'yellow', "noop event should not transition");
+ fsm.panic(); equals(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 = [];
View
28 test/test_basics.js
@@ -361,3 +361,31 @@ test("callback arguments are correct", function() {
});
+//-----------------------------------------------------------------------------
+
+test("no-op transitions (github issue #5)", function() {
+
+ var fsm = StateMachine.create({
+ initial: 'green',
+ events: [
+ { name: 'noop', from: 'green', }, // NOTE: no 'to' option specified
+ { 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");
+
+ 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");
+
+ 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")
+
+});
+
+

0 comments on commit 69808b3

Please sign in to comment.