@praxisjs/fsm 1.0.12 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/dist/__tests__/decorators.test.js +38 -51
- package/dist/__tests__/decorators.test.js.map +1 -1
- package/dist/decorators.d.ts +2 -2
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +19 -28
- package/dist/decorators.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/decorators.test.ts +49 -51
- package/src/decorators.ts +21 -49
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @praxisjs/fsm
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- c54d96b: `@StateMachine` converted from a class decorator to a field decorator.
|
|
8
|
+
|
|
9
|
+
Declare the machine as a typed field — no `declare`, no `propertyKey` second argument. The field name is the machine reference used in `@Transition`.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
// before
|
|
13
|
+
@StateMachine({ initial: 'idle', states: { ... } }, 'machine')
|
|
14
|
+
@Component()
|
|
15
|
+
class Foo extends StatefulComponent {
|
|
16
|
+
declare machine: Machine<State, Event>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// after
|
|
20
|
+
@Component()
|
|
21
|
+
class Foo extends StatefulComponent {
|
|
22
|
+
@StateMachine({ initial: 'idle', states: { ... } })
|
|
23
|
+
machine!: Machine<State, Event>
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- Updated dependencies [cfb0de2]
|
|
30
|
+
- @praxisjs/decorators@1.0.2
|
|
31
|
+
|
|
3
32
|
## 1.0.12
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
|
@@ -8,15 +8,27 @@ const TOGGLE_DEF = {
|
|
|
8
8
|
on: { on: { toggle: "off" } },
|
|
9
9
|
},
|
|
10
10
|
};
|
|
11
|
+
// Helper to simulate TC39 field decorator context
|
|
12
|
+
function fieldCtx(name) {
|
|
13
|
+
const initializers = [];
|
|
14
|
+
return {
|
|
15
|
+
ctx: {
|
|
16
|
+
name,
|
|
17
|
+
kind: "field",
|
|
18
|
+
addInitializer(fn) { initializers.push(fn); },
|
|
19
|
+
},
|
|
20
|
+
run(instance) {
|
|
21
|
+
initializers.forEach((fn) => { fn.call(instance); });
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
11
25
|
// Helper to simulate TC39 method decorator context
|
|
12
26
|
function mockMethodContext(name) {
|
|
13
27
|
const initializers = [];
|
|
14
28
|
return {
|
|
15
29
|
name,
|
|
16
30
|
kind: "method",
|
|
17
|
-
addInitializer(fn) {
|
|
18
|
-
initializers.push(fn);
|
|
19
|
-
},
|
|
31
|
+
addInitializer(fn) { initializers.push(fn); },
|
|
20
32
|
runInitializers(instance) {
|
|
21
33
|
initializers.forEach((fn) => { fn.call(instance); });
|
|
22
34
|
},
|
|
@@ -24,36 +36,38 @@ function mockMethodContext(name) {
|
|
|
24
36
|
}
|
|
25
37
|
// ── StateMachine ──────────────────────────────────────────────────────────────
|
|
26
38
|
describe("StateMachine decorator", () => {
|
|
27
|
-
it("
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
39
|
+
it("injects a machine via field decorator", () => {
|
|
40
|
+
const { ctx, run } = fieldCtx("machine");
|
|
41
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx);
|
|
42
|
+
const instance = {};
|
|
43
|
+
run(instance);
|
|
32
44
|
expect(instance.machine).toBeDefined();
|
|
33
45
|
expect(instance.machine.state()).toBe("off");
|
|
34
46
|
});
|
|
35
47
|
it("returns a separate machine per instance", () => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
48
|
+
const { ctx, run } = fieldCtx("machine");
|
|
49
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx);
|
|
50
|
+
const a = {};
|
|
51
|
+
const b = {};
|
|
52
|
+
run(a);
|
|
53
|
+
run(b);
|
|
41
54
|
a.machine.send("toggle");
|
|
42
55
|
expect(a.machine.state()).toBe("on");
|
|
43
56
|
expect(b.machine.state()).toBe("off");
|
|
44
57
|
});
|
|
45
|
-
it("
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
58
|
+
it("supports a custom field name", () => {
|
|
59
|
+
const { ctx, run } = fieldCtx("fsm");
|
|
60
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx);
|
|
61
|
+
const instance = {};
|
|
62
|
+
run(instance);
|
|
50
63
|
expect(instance.fsm).toBeDefined();
|
|
64
|
+
expect(instance.fsm.state()).toBe("off");
|
|
51
65
|
});
|
|
52
|
-
it("returns the same instance on repeated access", () => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
66
|
+
it("returns the same machine instance on repeated access", () => {
|
|
67
|
+
const { ctx, run } = fieldCtx("machine");
|
|
68
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx);
|
|
69
|
+
const instance = {};
|
|
70
|
+
run(instance);
|
|
57
71
|
expect(instance.machine).toBe(instance.machine);
|
|
58
72
|
});
|
|
59
73
|
});
|
|
@@ -91,7 +105,6 @@ describe("Transition decorator", () => {
|
|
|
91
105
|
};
|
|
92
106
|
ctx.runInitializers(instance);
|
|
93
107
|
const method = instance.doTransition;
|
|
94
|
-
// "toggle" is not a valid event from "on" state
|
|
95
108
|
method();
|
|
96
109
|
expect(original).not.toHaveBeenCalled();
|
|
97
110
|
});
|
|
@@ -105,12 +118,11 @@ describe("Transition decorator", () => {
|
|
|
105
118
|
method(42);
|
|
106
119
|
expect(original).toHaveBeenCalledWith(42);
|
|
107
120
|
});
|
|
108
|
-
it("@Transition on method
|
|
121
|
+
it("@Transition on method without @StateMachine — warns and returns undefined", () => {
|
|
109
122
|
const warn = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
110
123
|
const original = vi.fn(() => "ran");
|
|
111
124
|
const ctx = mockMethodContext("doTransition");
|
|
112
125
|
Transition("machine", "toggle")(original, ctx);
|
|
113
|
-
// Instance has no "machine" property at all
|
|
114
126
|
const instance = {};
|
|
115
127
|
ctx.runInitializers(instance);
|
|
116
128
|
const method = instance.doTransition;
|
|
@@ -121,29 +133,4 @@ describe("Transition decorator", () => {
|
|
|
121
133
|
warn.mockRestore();
|
|
122
134
|
});
|
|
123
135
|
});
|
|
124
|
-
// ── StateMachine on subclass ───────────────────────────────────────────────────
|
|
125
|
-
describe("StateMachine decorator on subclass", () => {
|
|
126
|
-
it("machine is accessible on subclass instance", () => {
|
|
127
|
-
class Base {
|
|
128
|
-
}
|
|
129
|
-
class Child extends Base {
|
|
130
|
-
}
|
|
131
|
-
const Wrapped = StateMachine(TOGGLE_DEF)(Child, {});
|
|
132
|
-
const instance = new Wrapped();
|
|
133
|
-
expect(instance.machine).toBeDefined();
|
|
134
|
-
expect(instance.machine.state()).toBe("off");
|
|
135
|
-
});
|
|
136
|
-
it("instances of the decorated subclass are isolated", () => {
|
|
137
|
-
class Base {
|
|
138
|
-
}
|
|
139
|
-
class Child extends Base {
|
|
140
|
-
}
|
|
141
|
-
const Wrapped = StateMachine(TOGGLE_DEF)(Child, {});
|
|
142
|
-
const a = new Wrapped();
|
|
143
|
-
const b = new Wrapped();
|
|
144
|
-
a.machine.send("toggle");
|
|
145
|
-
expect(a.machine.state()).toBe("on");
|
|
146
|
-
expect(b.machine.state()).toBe("off");
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
136
|
//# sourceMappingURL=decorators.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorators.test.js","sourceRoot":"","sources":["../../src/__tests__/decorators.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAElD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"decorators.test.js","sourceRoot":"","sources":["../../src/__tests__/decorators.test.ts"],"names":[],"mappings":"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"}
|
package/dist/decorators.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type MachineDefinition } from "./machine";
|
|
1
|
+
import { type Machine, type MachineDefinition } from "./machine";
|
|
2
2
|
export declare function Transition(machineProp: string, event: string): (value: (...args: unknown[]) => unknown, context: ClassMethodDecoratorContext<any>) => void;
|
|
3
|
-
export declare function StateMachine<S extends string, E extends string>(definition: MachineDefinition<S, E
|
|
3
|
+
export declare function StateMachine<S extends string, E extends string>(definition: MachineDefinition<S, E>): <T extends object>(_value: undefined, context: ClassFieldDecoratorContext<T, Machine<S, E>>) => void;
|
|
4
4
|
//# sourceMappingURL=decorators.d.ts.map
|
package/dist/decorators.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,KAAK,OAAO,EAAE,KAAK,iBAAiB,EAAG,MAAM,WAAW,CAAC;AAEjF,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAiB5B,CAC7B,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAEtC,OAAO,EAAE,2BAA2B,CAAC,GAAG,CAAC,KACtC,IAAI,CACV;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,EAC7D,UAAU,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,IAIlB,CAAC,SAAS,MAAM,EAC/B,QAAQ,SAAS,EACjB,SAAS,0BAA0B,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KACpD,IAAI,CAgBR"}
|
package/dist/decorators.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createMethodDecorator,
|
|
1
|
+
import { createMethodDecorator, } from "@praxisjs/decorators";
|
|
2
2
|
import { createMachine } from "./machine";
|
|
3
3
|
export function Transition(machineProp, event) {
|
|
4
4
|
const decorator = createMethodDecorator({
|
|
@@ -17,33 +17,24 @@ export function Transition(machineProp, event) {
|
|
|
17
17
|
// FSM decorators work on any class, not just StatefulComponent
|
|
18
18
|
return decorator;
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return machines.get(this);
|
|
38
|
-
},
|
|
39
|
-
enumerable: true,
|
|
40
|
-
configurable: true,
|
|
20
|
+
export function StateMachine(definition) {
|
|
21
|
+
const machines = new WeakMap();
|
|
22
|
+
return function (_value, context) {
|
|
23
|
+
context.addInitializer(function () {
|
|
24
|
+
const name = context.name;
|
|
25
|
+
Object.defineProperty(this, name, {
|
|
26
|
+
get() {
|
|
27
|
+
const self = this;
|
|
28
|
+
if (!machines.has(self))
|
|
29
|
+
machines.set(self, createMachine(definition));
|
|
30
|
+
return machines.get(self);
|
|
31
|
+
},
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
33
|
+
set() { },
|
|
34
|
+
enumerable: true,
|
|
35
|
+
configurable: true,
|
|
36
|
+
});
|
|
41
37
|
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
export function StateMachine(definition, propertyKey = "machine") {
|
|
45
|
-
const decorator = createClassDecorator(new StateMachineBehavior(definition, propertyKey));
|
|
46
|
-
// FSM decorators work on any class, not just RootComponent
|
|
47
|
-
return decorator;
|
|
38
|
+
};
|
|
48
39
|
}
|
|
49
40
|
//# sourceMappingURL=decorators.js.map
|
package/dist/decorators.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorators.js","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"decorators.js","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,aAAa,EAAyC,MAAM,WAAW,CAAC;AAEjF,MAAM,UAAU,UAAU,CAAC,WAAmB,EAAE,KAAa;IAC3D,MAAM,SAAS,GAAG,qBAAqB,CAAC;QACtC,IAAI,CAAC,QAAQ,EAAE,QAAQ;YACrB,OAAO,CAAC,GAAG,IAAe,EAAE,EAAE;gBAC5B,MAAM,OAAO,GAAI,QAAoC,CAAC,WAAW,CAEpD,CAAC;gBACd,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,CAAC,IAAI,CAAC,iBAAiB,WAAW,2BAA2B,CAAC,CAAC;oBACtE,OAAO;gBACT,CAAC;gBACD,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;oBACrB,OAAO,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAY,CAAC;YACrD,CAAC,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;IACH,+DAA+D;IAC/D,OAAO,SAIE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,UAAmC;IAEnC,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAyB,CAAC;IAEtD,OAAO,UACL,MAAiB,EACjB,OAAqD;QAErD,OAAO,CAAC,cAAc,CAAC;YACrB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;YAC1B,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE;gBAChC,GAAG;oBACD,MAAM,IAAI,GAAG,IAAc,CAAC;oBAC5B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;wBAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC;oBACvE,OAAO,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAkB,CAAC;gBAC7C,CAAC;gBACD,gEAAgE;gBAChE,GAAG,KAAU,CAAC;gBACd,UAAU,EAAE,IAAI;gBAChB,YAAY,EAAE,IAAI;aACnB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisjs/fsm",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@praxisjs/core": "1.6.0",
|
|
18
|
-
"@praxisjs/
|
|
19
|
-
"@praxisjs/
|
|
18
|
+
"@praxisjs/decorators": "1.0.2",
|
|
19
|
+
"@praxisjs/shared": "0.2.0"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
22
|
"build": "tsc",
|
|
@@ -2,24 +2,38 @@ import { describe, it, expect, vi } from "vitest";
|
|
|
2
2
|
|
|
3
3
|
import { Transition, StateMachine } from "../decorators";
|
|
4
4
|
import { createMachine } from "../machine";
|
|
5
|
+
import type { Machine } from "../machine";
|
|
5
6
|
|
|
6
7
|
const TOGGLE_DEF = {
|
|
7
8
|
initial: "off" as const,
|
|
8
9
|
states: {
|
|
9
10
|
off: { on: { toggle: "on" as const } },
|
|
10
|
-
on:
|
|
11
|
+
on: { on: { toggle: "off" as const } },
|
|
11
12
|
},
|
|
12
13
|
} as const;
|
|
13
14
|
|
|
15
|
+
// Helper to simulate TC39 field decorator context
|
|
16
|
+
function fieldCtx(name: string) {
|
|
17
|
+
const initializers: Array<(this: object) => void> = [];
|
|
18
|
+
return {
|
|
19
|
+
ctx: {
|
|
20
|
+
name,
|
|
21
|
+
kind: "field" as const,
|
|
22
|
+
addInitializer(fn: (this: object) => void) { initializers.push(fn); },
|
|
23
|
+
} as ClassFieldDecoratorContext,
|
|
24
|
+
run(instance: object) {
|
|
25
|
+
initializers.forEach((fn) => { fn.call(instance); });
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
// Helper to simulate TC39 method decorator context
|
|
15
31
|
function mockMethodContext(name: string) {
|
|
16
32
|
const initializers: Array<(this: object) => void> = [];
|
|
17
33
|
return {
|
|
18
34
|
name,
|
|
19
35
|
kind: "method" as const,
|
|
20
|
-
addInitializer(fn: (this: object) => void) {
|
|
21
|
-
initializers.push(fn);
|
|
22
|
-
},
|
|
36
|
+
addInitializer(fn: (this: object) => void) { initializers.push(fn); },
|
|
23
37
|
runInitializers(instance: object) {
|
|
24
38
|
initializers.forEach((fn) => { fn.call(instance); });
|
|
25
39
|
},
|
|
@@ -29,35 +43,45 @@ function mockMethodContext(name: string) {
|
|
|
29
43
|
// ── StateMachine ──────────────────────────────────────────────────────────────
|
|
30
44
|
|
|
31
45
|
describe("StateMachine decorator", () => {
|
|
32
|
-
it("
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
it("injects a machine via field decorator", () => {
|
|
47
|
+
const { ctx, run } = fieldCtx("machine");
|
|
48
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx as ClassFieldDecoratorContext<object, Machine<"off"|"on","toggle">>);
|
|
49
|
+
|
|
50
|
+
const instance: Record<string, unknown> = {};
|
|
51
|
+
run(instance);
|
|
36
52
|
expect(instance.machine).toBeDefined();
|
|
37
|
-
expect((instance.machine as
|
|
53
|
+
expect((instance.machine as Machine<string, string>).state()).toBe("off");
|
|
38
54
|
});
|
|
39
55
|
|
|
40
56
|
it("returns a separate machine per instance", () => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
57
|
+
const { ctx, run } = fieldCtx("machine");
|
|
58
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx as ClassFieldDecoratorContext<object, Machine<"off"|"on","toggle">>);
|
|
59
|
+
|
|
60
|
+
const a: Record<string, unknown> = {};
|
|
61
|
+
const b: Record<string, unknown> = {};
|
|
62
|
+
run(a);
|
|
63
|
+
run(b);
|
|
64
|
+
(a.machine as Machine<string, string>).send("toggle");
|
|
65
|
+
expect((a.machine as Machine<string, string>).state()).toBe("on");
|
|
66
|
+
expect((b.machine as Machine<string, string>).state()).toBe("off");
|
|
48
67
|
});
|
|
49
68
|
|
|
50
|
-
it("
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
69
|
+
it("supports a custom field name", () => {
|
|
70
|
+
const { ctx, run } = fieldCtx("fsm");
|
|
71
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx as ClassFieldDecoratorContext<object, Machine<"off"|"on","toggle">>);
|
|
72
|
+
|
|
73
|
+
const instance: Record<string, unknown> = {};
|
|
74
|
+
run(instance);
|
|
54
75
|
expect(instance.fsm).toBeDefined();
|
|
76
|
+
expect((instance.fsm as Machine<string, string>).state()).toBe("off");
|
|
55
77
|
});
|
|
56
78
|
|
|
57
|
-
it("returns the same instance on repeated access", () => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
79
|
+
it("returns the same machine instance on repeated access", () => {
|
|
80
|
+
const { ctx, run } = fieldCtx("machine");
|
|
81
|
+
StateMachine(TOGGLE_DEF)(undefined, ctx as ClassFieldDecoratorContext<object, Machine<"off"|"on","toggle">>);
|
|
82
|
+
|
|
83
|
+
const instance: Record<string, unknown> = {};
|
|
84
|
+
run(instance);
|
|
61
85
|
expect(instance.machine).toBe(instance.machine);
|
|
62
86
|
});
|
|
63
87
|
});
|
|
@@ -105,7 +129,6 @@ describe("Transition decorator", () => {
|
|
|
105
129
|
ctx.runInitializers(instance);
|
|
106
130
|
const method = (instance as Record<string, () => unknown>).doTransition;
|
|
107
131
|
|
|
108
|
-
// "toggle" is not a valid event from "on" state
|
|
109
132
|
method();
|
|
110
133
|
expect(original).not.toHaveBeenCalled();
|
|
111
134
|
});
|
|
@@ -123,13 +146,12 @@ describe("Transition decorator", () => {
|
|
|
123
146
|
expect(original).toHaveBeenCalledWith(42);
|
|
124
147
|
});
|
|
125
148
|
|
|
126
|
-
it("@Transition on method
|
|
149
|
+
it("@Transition on method without @StateMachine — warns and returns undefined", () => {
|
|
127
150
|
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
128
151
|
const original = vi.fn(() => "ran");
|
|
129
152
|
const ctx = mockMethodContext("doTransition");
|
|
130
153
|
Transition("machine", "toggle")(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
131
154
|
|
|
132
|
-
// Instance has no "machine" property at all
|
|
133
155
|
const instance = {} as Record<string, unknown>;
|
|
134
156
|
ctx.runInitializers(instance);
|
|
135
157
|
const method = (instance as Record<string, () => unknown>).doTransition;
|
|
@@ -141,27 +163,3 @@ describe("Transition decorator", () => {
|
|
|
141
163
|
warn.mockRestore();
|
|
142
164
|
});
|
|
143
165
|
});
|
|
144
|
-
|
|
145
|
-
// ── StateMachine on subclass ───────────────────────────────────────────────────
|
|
146
|
-
|
|
147
|
-
describe("StateMachine decorator on subclass", () => {
|
|
148
|
-
it("machine is accessible on subclass instance", () => {
|
|
149
|
-
class Base {}
|
|
150
|
-
class Child extends Base {}
|
|
151
|
-
const Wrapped = StateMachine(TOGGLE_DEF)(Child, {} as ClassDecoratorContext) as unknown as typeof Child;
|
|
152
|
-
const instance = new Wrapped() as Record<string, unknown>;
|
|
153
|
-
expect(instance.machine).toBeDefined();
|
|
154
|
-
expect((instance.machine as ReturnType<typeof createMachine>).state()).toBe("off");
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("instances of the decorated subclass are isolated", () => {
|
|
158
|
-
class Base {}
|
|
159
|
-
class Child extends Base {}
|
|
160
|
-
const Wrapped = StateMachine(TOGGLE_DEF)(Child, {} as ClassDecoratorContext) as unknown as typeof Child;
|
|
161
|
-
const a = new Wrapped() as Record<string, unknown>;
|
|
162
|
-
const b = new Wrapped() as Record<string, unknown>;
|
|
163
|
-
(a.machine as ReturnType<typeof createMachine>).send("toggle");
|
|
164
|
-
expect((a.machine as ReturnType<typeof createMachine>).state()).toBe("on");
|
|
165
|
-
expect((b.machine as ReturnType<typeof createMachine>).state()).toBe("off");
|
|
166
|
-
});
|
|
167
|
-
});
|
package/src/decorators.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import type { RootComponent } from "@praxisjs/core/internal";
|
|
2
1
|
import {
|
|
3
2
|
createMethodDecorator,
|
|
4
|
-
createClassDecorator,
|
|
5
|
-
ClassBehavior,
|
|
6
|
-
type ClassEnhancement,
|
|
7
3
|
} from "@praxisjs/decorators";
|
|
8
4
|
|
|
9
|
-
import { createMachine, type Machine, type MachineDefinition
|
|
5
|
+
import { createMachine, type Machine, type MachineDefinition } from "./machine";
|
|
10
6
|
|
|
11
7
|
export function Transition(machineProp: string, event: string) {
|
|
12
8
|
const decorator = createMethodDecorator({
|
|
@@ -32,52 +28,28 @@ export function Transition(machineProp: string, event: string) {
|
|
|
32
28
|
) => void;
|
|
33
29
|
}
|
|
34
30
|
|
|
35
|
-
class StateMachineBehavior<
|
|
36
|
-
S extends string,
|
|
37
|
-
E extends string,
|
|
38
|
-
> extends ClassBehavior {
|
|
39
|
-
constructor(
|
|
40
|
-
private readonly definition: MachineDefinition<S, E>,
|
|
41
|
-
private readonly propertyKey: string,
|
|
42
|
-
) {
|
|
43
|
-
super();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
create(_instance: RootComponent): ClassEnhancement {
|
|
47
|
-
return {};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
-
initialize(Enhanced: new (...args: any[]) => unknown): void {
|
|
52
|
-
const machines = new WeakMap<object, Machine<S, E>>();
|
|
53
|
-
const { definition, propertyKey } = this;
|
|
54
|
-
Object.defineProperty(
|
|
55
|
-
(Enhanced as { prototype: object }).prototype,
|
|
56
|
-
propertyKey,
|
|
57
|
-
{
|
|
58
|
-
get(this: object): Machine<S, E> {
|
|
59
|
-
if (!machines.has(this))
|
|
60
|
-
machines.set(this, createMachine(definition));
|
|
61
|
-
return machines.get(this) as Machine<S, E>;
|
|
62
|
-
},
|
|
63
|
-
enumerable: true,
|
|
64
|
-
configurable: true,
|
|
65
|
-
},
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
31
|
export function StateMachine<S extends string, E extends string>(
|
|
71
32
|
definition: MachineDefinition<S, E>,
|
|
72
|
-
propertyKey = "machine",
|
|
73
33
|
) {
|
|
74
|
-
const
|
|
75
|
-
new StateMachineBehavior(definition, propertyKey),
|
|
76
|
-
);
|
|
77
|
-
// FSM decorators work on any class, not just RootComponent
|
|
34
|
+
const machines = new WeakMap<object, Machine<S, E>>();
|
|
78
35
|
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
context:
|
|
82
|
-
)
|
|
36
|
+
return function <T extends object>(
|
|
37
|
+
_value: undefined,
|
|
38
|
+
context: ClassFieldDecoratorContext<T, Machine<S, E>>,
|
|
39
|
+
): void {
|
|
40
|
+
context.addInitializer(function (this: object) {
|
|
41
|
+
const name = context.name;
|
|
42
|
+
Object.defineProperty(this, name, {
|
|
43
|
+
get(): Machine<S, E> {
|
|
44
|
+
const self = this as object;
|
|
45
|
+
if (!machines.has(self)) machines.set(self, createMachine(definition));
|
|
46
|
+
return machines.get(self) as Machine<S, E>;
|
|
47
|
+
},
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
49
|
+
set(): void {},
|
|
50
|
+
enumerable: true,
|
|
51
|
+
configurable: true,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
};
|
|
83
55
|
}
|