Skip to content

Break out invoke types #109

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
uses: ianwalter/[email protected]
- name: npm install, build, and test
run: |
yarn
npm ci
npm run server &
npm run bundlesize
npm test
Expand Down
2 changes: 1 addition & 1 deletion bundlesize.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"files": [
{
"path": "./machine.min.js",
"maxSize": "1.347 kB"
"maxSize": "1.39 kB"
}
]
}
51 changes: 30 additions & 21 deletions machine.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export let transition = makeTransition.bind(transitionType);
export let immediate = makeTransition.bind(immediateType, null);

function enterImmediate(machine, service, event) {
return transitionTo(service, event, this.immediates) || machine;
return transitionTo(service, machine, event, this.immediates) || machine;
}

function transitionsToMap(transitions) {
Expand All @@ -81,33 +81,42 @@ export function state(...args) {
return create(stateType, desc);
}

let invokeType = {
let invokePromiseType = {
enter(machine, service, event) {
this.fn.call(service, service.context, event)
.then(data => service.send({ type: 'done', data }))
.catch(error => service.send({ type: 'error', error }));
return machine;
}
};
const machineToThen = machine => function(ctx, ev) {
return {
then: resolve => {
this.child = interpret(machine, s => {
this.onChange(s);
if(this.child == s && s.machine.state.value.final) {
delete this.child;
resolve(s.context);
}
}, ctx, ev);
return { catch: identity };
let invokeMachineType = {
enter(machine, service, event) {
service.child = interpret(this.machine, s => {
service.onChange(s);
if(service.child == s && s.machine.state.value.final) {
delete service.child;
service.send({ type: 'done', data: s.context });
}
}, service.context, event);
if(service.child.machine.state.value.final) {
let data = service.child.context;
delete service.child;
return transitionTo(service, machine, { type: 'done', data }, this.transitions.get('done'));
}
};
return machine;
}
};
export function invoke(fn, ...transitions) {
return create(invokeType, {
fn: valueEnumerable(machine.isPrototypeOf(fn) ? machineToThen(fn) : fn),
transitions: valueEnumerable(transitionsToMap(transitions))
});
let t = valueEnumerable(transitionsToMap(transitions));
return machine.isPrototypeOf(fn) ?
create(invokeMachineType, {
machine: valueEnumerable(fn),
transitions: t
}) :
create(invokePromiseType, {
fn: valueEnumerable(machine.isPrototypeOf(fn) ? machineToThen(fn) : fn),
transitions: t
});
}

let machine = {
Expand All @@ -133,8 +142,8 @@ export function createMachine(current, states, contextFn = empty) {
});
}

function transitionTo(service, fromEvent, candidates) {
let { machine, context } = service;
function transitionTo(service, machine, fromEvent, candidates) {
let { context } = service;
for(let { to, guards, reducers } of candidates) {
if(guards(context, fromEvent)) {
service.context = reducers.call(service, context, fromEvent);
Expand All @@ -157,7 +166,7 @@ function send(service, event) {
let { value: state } = machine.state;

if(state.transitions.has(eventName)) {
return transitionTo(service, event, state.transitions.get(eventName)) || machine;
return transitionTo(service, machine, event, state.transitions.get(eventName)) || machine;
}
return machine;
}
Expand Down
16 changes: 16 additions & 0 deletions test/test-immediate.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,20 @@ QUnit.module('Immediate', hooks => {
service.send('next');
assert.equal(service.machine.current, 'three');
});

QUnit.test('Can immediately transitions past 2 states', assert => {
let machine = createMachine({
one: state(
immediate('two')
),
two: state(
immediate('three')
),
three: state()
});

let service = interpret(machine, () => {});
assert.equal(service.machine.current, 'three', 'transitioned to 3');
assert.ok(service.machine.state.value.final, 'in the final state');
});
});
35 changes: 32 additions & 3 deletions test/test-invoke.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ QUnit.module('Invoke', hooks => {
QUnit.test('Can invoke a child machine', async assert => {
assert.expect(4);
let one = createMachine({
one: state(
transition('go', 'two')
nestedOne: state(
transition('go', 'nestedTwo')
),
two: final()
nestedTwo: final()
});
let two = createMachine({
one: state(
Expand Down Expand Up @@ -246,4 +246,33 @@ QUnit.module('Invoke', hooks => {
service.child.child.child.send('DONE') // machine four
assert.equal(c, 8, 'there were 6 transitions');
});

QUnit.test('Invoking a machine that immediately finishes', async assert => {
assert.expect(1);

const child = createMachine({
nestedOne: state(
immediate('nestedTwo')
),
nestedTwo: final()
});

const parent = createMachine({
one: state(
transition('next', 'two')
),
two: invoke(child,
transition('done', 'three')
),
three: final()
});

let service = interpret(parent, s => {
// TODO not totally sure if this is correct, but I think it should
// hit this only once and be equal to three
assert.equal(s.machine.current, 'three');
});

service.send('next');
});
});
14 changes: 11 additions & 3 deletions test/test-state.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMachine, interpret, invoke, state, transition } from '../machine.js';
import { createMachine, interpret, invoke, reduce, state, transition } from '../machine.js';

QUnit.module('States', hooks => {
QUnit.test('Basic state change', assert => {
Expand Down Expand Up @@ -60,10 +60,18 @@ QUnit.module('States', hooks => {
start: state(
transition('next', 'next')
),
next: invoke(child)
next: invoke(child,
transition('done', 'end',
reduce((ctx, ev) => ({
...ctx,
...ev.data
}))
)
),
end: state()
});
let service = interpret(parent, () => {});
service.send({ type: 'next', count: 14 });
assert.equal(service.child.context.count, 14, 'event sent through');
assert.equal(service.context.count, 14, 'event sent through');
});
});