Skip to content

Commit

Permalink
🙋 basic implementation of hierarchical machines (#7)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
kareniel authored and yoshuawuyts committed Jan 30, 2018
1 parent 3839653 commit e35f060
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 95 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ dist/
npm-debug.log*
.DS_Store
.nyc_output
package-lock.json
38 changes: 37 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,56 @@ function Nanostate (initialState, transitions) {

this.transitions = transitions
this.state = initialState
this.submachines = {}
this._submachine = null

Nanobus.call(this)
}

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

Nanostate.prototype.constructor = Nanostate

Nanostate.prototype.emit = function (eventName) {
var nextState = this.transitions[this.state][eventName]
var nextState = this._next(eventName)
assert.ok(nextState, `nanostate.emit: invalid transition ${this.state} -> ${eventName}`)

if (this._submachine && Object.keys(this.transitions).indexOf(nextState) !== -1) {
this._unregister()
}

this.state = nextState
Nanobus.prototype.emit.call(this, eventName)
}

Nanostate.prototype.event = function (eventName, machine) {
this.submachines[eventName] = machine
}

Nanostate.parallel = function (transitions) {
return new Parallelstate(transitions)
}

Nanostate.prototype._unregister = function () {
if (this._submachine) {
this._submachine._unregister()
this._submachine = null
}
}

Nanostate.prototype._next = function (eventName) {
if (this._submachine) {
var nextState = this._submachine._next(eventName)
if (nextState) {
return nextState
}
}

var submachine = this.submachines[eventName]
if (submachine) {
this._submachine = submachine
return submachine.state
}

return this.transitions[this.state][eventName]
}
94 changes: 0 additions & 94 deletions test.js

This file was deleted.

54 changes: 54 additions & 0 deletions test/hierarchical.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
var tape = require('tape')

var nanostate = require('../')
var move = require('./move')

tape('change to substate and back', function (assert) {
var machine = nanostate('green', {
green: { timer: 'yellow' },
yellow: { timer: 'red' },
red: { timer: 'green' }
})

machine.event('powerOutage', nanostate('flashingRed', {
flashingRed: { powerRestored: 'green' }
}))

move(assert, machine, [
['timer', 'yellow'],
['powerOutage', 'flashingRed'],
['powerRestored', 'green']
])

assert.end()
})

tape('move down two levels', function (assert) {
var trafficLights = nanostate('green', {
green: { timer: 'yellow' },
yellow: { timer: 'red' },
red: { timer: 'green' }
})

var powerOutage = nanostate('flashingRed', {
flashingRed: { powerRestored: 'green' }
})

var apocalypse = nanostate('darkness', {
darkness: { worldSaved: 'green' }
})

trafficLights.event('powerOutage', powerOutage)
powerOutage.event('apocalypse', apocalypse)

move(assert, trafficLights, [
['powerOutage', 'flashingRed'],
['apocalypse', 'darkness'],
['worldSaved', 'green']
])

assert.equal(trafficLights._submachine, null, 'first level submachine is unregistered')
assert.equal(powerOutage._submachine, null, 'second level submachine is unregistered')

assert.end()
})
3 changes: 3 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
require('./nanostate')
require('./parallel')
require('./hierarchical')
11 changes: 11 additions & 0 deletions test/move.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = move

// Move the machine a bunch of states.
function move (assert, machine, states) {
states.forEach(function (tuple) {
var initial = machine.state
var expected = tuple[1]
machine.emit(tuple[0])
assert.equal(machine.state, expected, `from ${initial} to ${expected}`)
})
}
29 changes: 29 additions & 0 deletions test/nanostate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
var tape = require('tape')

var nanostate = require('../')
var move = require('./move')

tape('sets an initial state', function (assert) {
var machine = nanostate('green', {
green: { timer: 'yellow' },
yellow: { timer: 'red' },
red: { timer: 'green' }
})
assert.equal(machine.state, 'green')
assert.end()
})

tape('change state', function (assert) {
var machine = nanostate('green', {
green: { timer: 'yellow' },
yellow: { timer: 'red' },
red: { timer: 'green' }
})

move(assert, machine, [
['timer', 'yellow'],
['timer', 'red'],
['timer', 'green']
])
assert.end()
})
61 changes: 61 additions & 0 deletions test/parallel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
var tape = require('tape')

var nanostate = require('../')

tape('create parallel state', (assert) => {
var machine = nanostate.parallel(createParallelTransitions())

machine.emit('bold:toggle')
assert.deepEqual(machine.state, {
bold: 'on', underline: 'off', italics: 'off', list: 'none'
})

assert.end()
})

tape('change states in parallel machine', (assert) => {
var machine = nanostate.parallel(createParallelTransitions())

machine.emit('underline:toggle')
machine.emit('list:numbers')
assert.deepEqual(machine.state, {
bold: 'off', underline: 'on', italics: 'off', list: 'numbers'
})

machine.emit('bold:toggle')
machine.emit('underline:toggle')
machine.emit('italics:toggle')
machine.emit('list:bullets')
assert.deepEqual(machine.state, {
bold: 'on', underline: 'off', italics: 'on', list: 'bullets'
})

machine.emit('list:none')
assert.deepEqual(machine.state, {
bold: 'on', underline: 'off', italics: 'on', list: 'none'
})

assert.end()
})

function createParallelTransitions () {
return {
bold: nanostate('off', {
on: { 'toggle': 'off' },
off: { 'toggle': 'on' }
}),
underline: nanostate('off', {
on: { 'toggle': 'off' },
off: { 'toggle': 'on' }
}),
italics: nanostate('off', {
on: { 'toggle': 'off' },
off: { 'toggle': 'on' }
}),
list: nanostate('none', {
none: { bullets: 'bullets', numbers: 'numbers' },
bullets: { none: 'none', numbers: 'numbers' },
numbers: { bullets: 'bullets', none: 'none' }
})
}
}

0 comments on commit e35f060

Please sign in to comment.