Skip to content

Commit

Permalink
feat(manager): official support spawn and invoke
Browse files Browse the repository at this point in the history
  • Loading branch information
NullVoxPopuli committed Dec 22, 2021
1 parent 5014d33 commit a34d193
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 8 deletions.
38 changes: 33 additions & 5 deletions ember-statechart-component/src/-private/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,28 @@ import { assert } from '@ember/debug';
import { isDestroyed, isDestroying } from '@ember/destroyable';

import { createStorage, getValue, setValue } from 'ember-tracked-storage-polyfill';
import { Interpreter } from 'xstate';

import { createMapWithInterceptedSet } from './utils';

import type { TrackedStorage } from 'ember-tracked-storage-polyfill';
import type { EventObject, Interpreter, State } from 'xstate';
import type { EventObject, State } from 'xstate';

export const UPDATE_EVENT_NAME = 'ARGS_UPDATE';

const CACHE = new WeakMap<Interpreter<unknown>, TrackedStorage<null>>();

export function reactiveInterpreter(interpreter: Interpreter<unknown>) {
/**
* atm, only interpreters can be reactive
*/
if (!(interpreter instanceof Interpreter)) {
return interpreter;
}

ensureStorage(interpreter);

interpreter.onTransition(async (_state: State<unknown>, event: EventObject) => {
console.log(event.type);
// init always runs, we don't need to dirty
if (event.type === 'xstate.init') return;
// a dirty event already triggered a transition
Expand All @@ -37,12 +46,31 @@ export function reactiveInterpreter(interpreter: Interpreter<unknown>) {
dirtyState(interpreter);
});

let children = new Map();
let fakeChildrenMap = createMapWithInterceptedSet(children, {
set(key: string, value: Interpreter<unknown>) {
children.set(key, reactiveInterpreter(value));
},
});

// children is a public API accessor
interpreter.children = fakeChildrenMap;

/**
* For spawn/invoked things, references are stored on
* - the interpreter.children as a map (id => interpreter / actor ref)
* - the state.children as an object (id => interpreter / actor ref)
*/
return new Proxy(interpreter, {
get(target, key, receiver) {
if (key === '_state') {
let storage = ensureStorage(target);
switch (key) {
case '_state': {
let storage = ensureStorage(target);

getValue(storage);

getValue(storage);
return Reflect.get(target, key, receiver);
}
}

return Reflect.get(target, key, receiver);
Expand Down
56 changes: 56 additions & 0 deletions ember-statechart-component/src/-private/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
type AnyMap = Map<any, any>;
type Fn = (...args: any[]) => any;

/**
* There is something wierd about maps where they seem
* to not be able to be reasonably proxied...
*/
export function createMapWithInterceptedSet(source: AnyMap, overrides: Record<string, Fn>): AnyMap {
class InterceptableMap<K = unknown, V = unknown> implements Map<K, V> {
clear(): void {
source.clear();
}
delete(key: K): boolean {
return source.delete(key);
}
forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, _thisArg?: any): void {
source.forEach(callbackfn);
}
get(key: K): V | undefined {
return source.get(key);
}
has(key: K): boolean {
return source.has(key);
}
set(key: K, value: V): this {
if (!('set' in overrides)) {
throw new Error('set override is missing');
}

overrides.set(key, value);

return this;
}
get size() {
return source.size;
}
entries(): IterableIterator<[K, V]> {
return source.entries();
}
keys(): IterableIterator<K> {
return source.keys();
}
values(): IterableIterator<V> {
return source.values();
}
[Symbol.iterator](): IterableIterator<[K, V]> {
return source[Symbol.iterator]();
}
get [Symbol.toStringTag]() {
return source[Symbol.toStringTag];
}
}

return new InterceptableMap();
}
14 changes: 13 additions & 1 deletion testing/ember-app/ember-cli-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
const EmberApp = require('ember-cli/lib/broccoli/ember-app');

module.exports = function (defaults) {
let app = new EmberApp(defaults, {});
let app = new EmberApp(defaults, {
autoImport: {
watchDependencies: ['ember-statechart-component'],
webpack: {
devtool: 'inline-source-map',
},
},
});

const { maybeEmbroider } = require('@embroider/test-setup');

Expand All @@ -19,5 +26,10 @@ module.exports = function (defaults) {
},
},
],
packagerOptions: {
webpackConfig: {
devtool: 'inline-source-map',
},
},
});
};
21 changes: 19 additions & 2 deletions testing/ember-app/tests/tests/integration/dynamic-machines-test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { click, render } from '@ember/test-helpers';
import { clearRender, click, render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
Expand Down Expand Up @@ -193,6 +193,8 @@ module('Dynamic Machines', function (hooks) {
initial: 'idleChild',
states: {
idleChild: {
entry: () => assert.step('entry: child.idleChild'),
exit: () => assert.step('exit: child.idleChild'),
on: {
UPDATE_CONTEXT: { actions: ['updateContext'] },
},
Expand All @@ -211,9 +213,13 @@ module('Dynamic Machines', function (hooks) {
initial: 'idle',
states: {
idle: {
entry: () => assert.step('entry: parent.idle'),
exit: () => assert.step('exit: parent.idle'),
on: { INVOKE_CHILD: 'withChildMachine' },
},
withChildMachine: {
entry: () => assert.step('entry: parent.withChildMachine'),
exit: () => assert.step('exit: parent.withChildMachine'),
invoke: {
id: 'child-machine',
src: childMachine,
Expand Down Expand Up @@ -243,7 +249,7 @@ module('Dynamic Machines', function (hooks) {
Update Context
</button>
<out>{{state.children.child-machine.machine.context.prop1}}</out>
<out>{{state.children.child-machine.state.context.prop1}}</out>
{{/if}}
</this.parentMachine>
`);
Expand All @@ -255,6 +261,17 @@ module('Dynamic Machines', function (hooks) {
await click('#update-context');

assert.dom('out').containsText('new value');

assert.verifySteps([
'entry: parent.idle',
'exit: parent.idle',
'entry: child.idleChild',
'entry: parent.withChildMachine',
]);

await clearRender();

assert.verifySteps(['exit: parent.withChildMachine', 'exit: child.idleChild']);
});
});
});

0 comments on commit a34d193

Please sign in to comment.