Skip to content

Commit a5c7025

Browse files
committed
Allow events that can be fired from any state using a wildcard '*' (or missing) 'from' attribute (github issue jakesgordon#11)
jakesgordon#11
1 parent 69808b3 commit a5c7025

3 files changed

Lines changed: 68 additions & 6 deletions

File tree

state-machine.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ StateMachine = {
22

33
//---------------------------------------------------------------------------
44

5-
VERSION: "2.0.1",
5+
VERSION: "2.1.0",
66

77
//---------------------------------------------------------------------------
88

@@ -12,6 +12,8 @@ StateMachine = {
1212
INVALID_CALLBACK: 300, // caller provided callback function threw an exception
1313
},
1414

15+
WILDCARD: '*',
16+
1517
//---------------------------------------------------------------------------
1618

1719
create: function(cfg, target) {
@@ -23,7 +25,7 @@ StateMachine = {
2325
var map = {};
2426

2527
var add = function(e) {
26-
var from = (e.from instanceof Array) ? e.from : [e.from];
28+
var from = (e.from instanceof Array) ? e.from : (e.from ? [e.from] : [StateMachine.WILDCARD]); // allow 'wildcard' transition if 'from' is not specified
2729
map[e.name] = map[e.name] || {};
2830
for (var n = 0 ; n < from.length ; n++)
2931
map[e.name][from[n]] = e.to || from[n]; // allow no-op transition if 'to' is not specified
@@ -49,7 +51,7 @@ StateMachine = {
4951

5052
fsm.current = 'none';
5153
fsm.is = function(state) { return this.current == state; };
52-
fsm.can = function(event) { return !!map[event][this.current] && !this.transition; };
54+
fsm.can = function(event) { return !this.transition && (map[event].hasOwnProperty(this.current) || map[event].hasOwnProperty(StateMachine.WILDCARD)); }
5355
fsm.cannot = function(event) { return !this.can(event); };
5456
fsm.error = cfg.error || function(name, from, to, args, error, msg) { throw msg; }; // default behavior when something unexpected happens is to throw an exception, but caller can override this behavior if desired (see github issue #3)
5557

@@ -90,7 +92,7 @@ StateMachine = {
9092
return this.error(name, from, to, args, StateMachine.Error.INVALID_TRANSITION, "event " + name + " inappropriate in current state " + this.current);
9193

9294
var from = this.current;
93-
var to = map[from];
95+
var to = map[from] || map[StateMachine.WILDCARD] || from;
9496
var args = Array.prototype.slice.call(arguments); // turn arguments into pure array
9597

9698
if (false === StateMachine.beforeEvent(this, name, from, to, args))

state-machine.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/test_basics.js

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ test("no-op transitions (github issue #5)", function() {
368368
var fsm = StateMachine.create({
369369
initial: 'green',
370370
events: [
371-
{ name: 'noop', from: 'green', }, // NOTE: no 'to' option specified
371+
{ name: 'noop', from: 'green', /* no-op */ },
372372
{ name: 'warn', from: 'green', to: 'yellow' },
373373
{ name: 'panic', from: 'yellow', to: 'red' },
374374
{ name: 'calm', from: 'red', to: 'yellow' },
@@ -388,4 +388,64 @@ test("no-op transitions (github issue #5)", function() {
388388

389389
});
390390

391+
//-----------------------------------------------------------------------------
392+
393+
test("wildcard 'from' allows event from any state (github issue #11)", function() {
394+
395+
var fsm = StateMachine.create({
396+
initial: 'stopped',
397+
events: [
398+
{ name: 'prepare', from: 'stopped', to: 'ready' },
399+
{ name: 'start', from: 'ready', to: 'running' },
400+
{ name: 'resume', from: 'paused', to: 'running' },
401+
{ name: 'pause', from: 'running', to: 'paused' },
402+
{ name: 'stop', from: '*', to: 'stopped' }
403+
]});
404+
405+
equals(fsm.current, 'stopped', "initial state should be stopped");
406+
407+
fsm.prepare(); equals(fsm.current, 'ready', "prepare event should transition from stopped to ready");
408+
fsm.stop(); equals(fsm.current, 'stopped', "stop event should transition from ready to stopped");
409+
410+
fsm.prepare(); equals(fsm.current, 'ready', "prepare event should transition from stopped to ready");
411+
fsm.start(); equals(fsm.current, 'running', "start event should transition from ready to running");
412+
fsm.stop(); equals(fsm.current, 'stopped', "stop event should transition from running to stopped");
413+
414+
fsm.prepare(); equals(fsm.current, 'ready', "prepare event should transition from stopped to ready");
415+
fsm.start(); equals(fsm.current, 'running', "start event should transition from ready to running");
416+
fsm.pause(); equals(fsm.current, 'paused', "pause event should transition from running to paused");
417+
fsm.stop(); equals(fsm.current, 'stopped', "stop event should transition from paused to stopped");
418+
419+
});
420+
421+
//-----------------------------------------------------------------------------
422+
423+
test("missing 'from' allows event from any state (github issue #11) ", function() {
424+
425+
var fsm = StateMachine.create({
426+
initial: 'stopped',
427+
events: [
428+
{ name: 'prepare', from: 'stopped', to: 'ready' },
429+
{ name: 'start', from: 'ready', to: 'running' },
430+
{ name: 'resume', from: 'paused', to: 'running' },
431+
{ name: 'pause', from: 'running', to: 'paused' },
432+
{ name: 'stop', /* any from state */ to: 'stopped' }
433+
]});
434+
435+
equals(fsm.current, 'stopped', "initial state should be stopped");
436+
437+
fsm.prepare(); equals(fsm.current, 'ready', "prepare event should transition from stopped to ready");
438+
fsm.stop(); equals(fsm.current, 'stopped', "stop event should transition from ready to stopped");
439+
440+
fsm.prepare(); equals(fsm.current, 'ready', "prepare event should transition from stopped to ready");
441+
fsm.start(); equals(fsm.current, 'running', "start event should transition from ready to running");
442+
fsm.stop(); equals(fsm.current, 'stopped', "stop event should transition from running to stopped");
443+
444+
fsm.prepare(); equals(fsm.current, 'ready', "prepare event should transition from stopped to ready");
445+
fsm.start(); equals(fsm.current, 'running', "start event should transition from ready to running");
446+
fsm.pause(); equals(fsm.current, 'paused', "pause event should transition from running to paused");
447+
fsm.stop(); equals(fsm.current, 'stopped', "stop event should transition from paused to stopped");
448+
449+
});
450+
391451

0 commit comments

Comments
 (0)