Skip to content

Commit e35f060

Browse files
karenielyoshuawuyts
authored andcommitted
🙋 basic implementation of hierarchical machines (#7)
* basic implementation of hierarchical machines - move tests to test folder - split test file in parts - add test for hierarchical based on spec - add _next private method to return the next state - implement `event` method - move `move` test helper to it's own file - add test that moves a machine to a substate two levels down - add `_unregister` that unregisters submachines recursively - define prototype constructor so class is called a Nanostate, not a Nanobus - ignore package-lock * fmt test fn
1 parent 3839653 commit e35f060

8 files changed

+196
-95
lines changed

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ dist/
55
npm-debug.log*
66
.DS_Store
77
.nyc_output
8+
package-lock.json

‎index.js

+37-1
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,56 @@ function Nanostate (initialState, transitions) {
1111

1212
this.transitions = transitions
1313
this.state = initialState
14+
this.submachines = {}
15+
this._submachine = null
1416

1517
Nanobus.call(this)
1618
}
1719

1820
Nanostate.prototype = Object.create(Nanobus.prototype)
1921

22+
Nanostate.prototype.constructor = Nanostate
23+
2024
Nanostate.prototype.emit = function (eventName) {
21-
var nextState = this.transitions[this.state][eventName]
25+
var nextState = this._next(eventName)
2226
assert.ok(nextState, `nanostate.emit: invalid transition ${this.state} -> ${eventName}`)
2327

28+
if (this._submachine && Object.keys(this.transitions).indexOf(nextState) !== -1) {
29+
this._unregister()
30+
}
31+
2432
this.state = nextState
2533
Nanobus.prototype.emit.call(this, eventName)
2634
}
2735

36+
Nanostate.prototype.event = function (eventName, machine) {
37+
this.submachines[eventName] = machine
38+
}
39+
2840
Nanostate.parallel = function (transitions) {
2941
return new Parallelstate(transitions)
3042
}
43+
44+
Nanostate.prototype._unregister = function () {
45+
if (this._submachine) {
46+
this._submachine._unregister()
47+
this._submachine = null
48+
}
49+
}
50+
51+
Nanostate.prototype._next = function (eventName) {
52+
if (this._submachine) {
53+
var nextState = this._submachine._next(eventName)
54+
if (nextState) {
55+
return nextState
56+
}
57+
}
58+
59+
var submachine = this.submachines[eventName]
60+
if (submachine) {
61+
this._submachine = submachine
62+
return submachine.state
63+
}
64+
65+
return this.transitions[this.state][eventName]
66+
}

‎test.js

-94
This file was deleted.

‎test/hierarchical.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
var tape = require('tape')
2+
3+
var nanostate = require('../')
4+
var move = require('./move')
5+
6+
tape('change to substate and back', function (assert) {
7+
var machine = nanostate('green', {
8+
green: { timer: 'yellow' },
9+
yellow: { timer: 'red' },
10+
red: { timer: 'green' }
11+
})
12+
13+
machine.event('powerOutage', nanostate('flashingRed', {
14+
flashingRed: { powerRestored: 'green' }
15+
}))
16+
17+
move(assert, machine, [
18+
['timer', 'yellow'],
19+
['powerOutage', 'flashingRed'],
20+
['powerRestored', 'green']
21+
])
22+
23+
assert.end()
24+
})
25+
26+
tape('move down two levels', function (assert) {
27+
var trafficLights = nanostate('green', {
28+
green: { timer: 'yellow' },
29+
yellow: { timer: 'red' },
30+
red: { timer: 'green' }
31+
})
32+
33+
var powerOutage = nanostate('flashingRed', {
34+
flashingRed: { powerRestored: 'green' }
35+
})
36+
37+
var apocalypse = nanostate('darkness', {
38+
darkness: { worldSaved: 'green' }
39+
})
40+
41+
trafficLights.event('powerOutage', powerOutage)
42+
powerOutage.event('apocalypse', apocalypse)
43+
44+
move(assert, trafficLights, [
45+
['powerOutage', 'flashingRed'],
46+
['apocalypse', 'darkness'],
47+
['worldSaved', 'green']
48+
])
49+
50+
assert.equal(trafficLights._submachine, null, 'first level submachine is unregistered')
51+
assert.equal(powerOutage._submachine, null, 'second level submachine is unregistered')
52+
53+
assert.end()
54+
})

‎test/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
require('./nanostate')
2+
require('./parallel')
3+
require('./hierarchical')

‎test/move.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module.exports = move
2+
3+
// Move the machine a bunch of states.
4+
function move (assert, machine, states) {
5+
states.forEach(function (tuple) {
6+
var initial = machine.state
7+
var expected = tuple[1]
8+
machine.emit(tuple[0])
9+
assert.equal(machine.state, expected, `from ${initial} to ${expected}`)
10+
})
11+
}

‎test/nanostate.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
var tape = require('tape')
2+
3+
var nanostate = require('../')
4+
var move = require('./move')
5+
6+
tape('sets an initial state', function (assert) {
7+
var machine = nanostate('green', {
8+
green: { timer: 'yellow' },
9+
yellow: { timer: 'red' },
10+
red: { timer: 'green' }
11+
})
12+
assert.equal(machine.state, 'green')
13+
assert.end()
14+
})
15+
16+
tape('change state', function (assert) {
17+
var machine = nanostate('green', {
18+
green: { timer: 'yellow' },
19+
yellow: { timer: 'red' },
20+
red: { timer: 'green' }
21+
})
22+
23+
move(assert, machine, [
24+
['timer', 'yellow'],
25+
['timer', 'red'],
26+
['timer', 'green']
27+
])
28+
assert.end()
29+
})

‎test/parallel.js

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
var tape = require('tape')
2+
3+
var nanostate = require('../')
4+
5+
tape('create parallel state', (assert) => {
6+
var machine = nanostate.parallel(createParallelTransitions())
7+
8+
machine.emit('bold:toggle')
9+
assert.deepEqual(machine.state, {
10+
bold: 'on', underline: 'off', italics: 'off', list: 'none'
11+
})
12+
13+
assert.end()
14+
})
15+
16+
tape('change states in parallel machine', (assert) => {
17+
var machine = nanostate.parallel(createParallelTransitions())
18+
19+
machine.emit('underline:toggle')
20+
machine.emit('list:numbers')
21+
assert.deepEqual(machine.state, {
22+
bold: 'off', underline: 'on', italics: 'off', list: 'numbers'
23+
})
24+
25+
machine.emit('bold:toggle')
26+
machine.emit('underline:toggle')
27+
machine.emit('italics:toggle')
28+
machine.emit('list:bullets')
29+
assert.deepEqual(machine.state, {
30+
bold: 'on', underline: 'off', italics: 'on', list: 'bullets'
31+
})
32+
33+
machine.emit('list:none')
34+
assert.deepEqual(machine.state, {
35+
bold: 'on', underline: 'off', italics: 'on', list: 'none'
36+
})
37+
38+
assert.end()
39+
})
40+
41+
function createParallelTransitions () {
42+
return {
43+
bold: nanostate('off', {
44+
on: { 'toggle': 'off' },
45+
off: { 'toggle': 'on' }
46+
}),
47+
underline: nanostate('off', {
48+
on: { 'toggle': 'off' },
49+
off: { 'toggle': 'on' }
50+
}),
51+
italics: nanostate('off', {
52+
on: { 'toggle': 'off' },
53+
off: { 'toggle': 'on' }
54+
}),
55+
list: nanostate('none', {
56+
none: { bullets: 'bullets', numbers: 'numbers' },
57+
bullets: { none: 'none', numbers: 'numbers' },
58+
numbers: { bullets: 'bullets', none: 'none' }
59+
})
60+
}
61+
}

0 commit comments

Comments
 (0)