@xmachines/play-xstate 1.0.0-beta.1
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/.oxfmtrc.json +3 -0
- package/.oxlintrc.json +3 -0
- package/README.md +454 -0
- package/dist/catalog/index.d.ts +12 -0
- package/dist/catalog/index.d.ts.map +1 -0
- package/dist/catalog/index.js +11 -0
- package/dist/catalog/index.js.map +1 -0
- package/dist/catalog/types.d.ts +36 -0
- package/dist/catalog/types.d.ts.map +1 -0
- package/dist/catalog/types.js +2 -0
- package/dist/catalog/types.js.map +1 -0
- package/dist/catalog/validate-binding.d.ts +21 -0
- package/dist/catalog/validate-binding.d.ts.map +1 -0
- package/dist/catalog/validate-binding.js +30 -0
- package/dist/catalog/validate-binding.js.map +1 -0
- package/dist/catalog/validate-props.d.ts +41 -0
- package/dist/catalog/validate-props.d.ts.map +1 -0
- package/dist/catalog/validate-props.js +95 -0
- package/dist/catalog/validate-props.js.map +1 -0
- package/dist/define-player.d.ts +110 -0
- package/dist/define-player.d.ts.map +1 -0
- package/dist/define-player.js +116 -0
- package/dist/define-player.js.map +1 -0
- package/dist/guards/compose.d.ts +136 -0
- package/dist/guards/compose.d.ts.map +1 -0
- package/dist/guards/compose.js +156 -0
- package/dist/guards/compose.js.map +1 -0
- package/dist/guards/helpers.d.ts +60 -0
- package/dist/guards/helpers.d.ts.map +1 -0
- package/dist/guards/helpers.js +91 -0
- package/dist/guards/helpers.js.map +1 -0
- package/dist/guards/index.d.ts +12 -0
- package/dist/guards/index.d.ts.map +1 -0
- package/dist/guards/index.js +11 -0
- package/dist/guards/index.js.map +1 -0
- package/dist/guards/types.d.ts +21 -0
- package/dist/guards/types.d.ts.map +1 -0
- package/dist/guards/types.js +2 -0
- package/dist/guards/types.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/player-actor.d.ts +143 -0
- package/dist/player-actor.d.ts.map +1 -0
- package/dist/player-actor.js +294 -0
- package/dist/player-actor.js.map +1 -0
- package/dist/routing/build-url.d.ts +27 -0
- package/dist/routing/build-url.d.ts.map +1 -0
- package/dist/routing/build-url.js +111 -0
- package/dist/routing/build-url.js.map +1 -0
- package/dist/routing/derive-route.d.ts +111 -0
- package/dist/routing/derive-route.d.ts.map +1 -0
- package/dist/routing/derive-route.js +144 -0
- package/dist/routing/derive-route.js.map +1 -0
- package/dist/routing/format-play-route-transitions.d.ts +31 -0
- package/dist/routing/format-play-route-transitions.d.ts.map +1 -0
- package/dist/routing/format-play-route-transitions.js +70 -0
- package/dist/routing/format-play-route-transitions.js.map +1 -0
- package/dist/routing/index.d.ts +13 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +12 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/routing/types.d.ts +25 -0
- package/dist/routing/types.d.ts.map +1 -0
- package/dist/routing/types.js +2 -0
- package/dist/routing/types.js.map +1 -0
- package/dist/signals/debounce.d.ts +18 -0
- package/dist/signals/debounce.d.ts.map +1 -0
- package/dist/signals/debounce.js +35 -0
- package/dist/signals/debounce.js.map +1 -0
- package/dist/signals/index.d.ts +3 -0
- package/dist/signals/index.d.ts.map +1 -0
- package/dist/signals/index.js +3 -0
- package/dist/signals/index.js.map +1 -0
- package/dist/signals/state-signal.d.ts +33 -0
- package/dist/signals/state-signal.d.ts.map +1 -0
- package/dist/signals/state-signal.js +41 -0
- package/dist/signals/state-signal.js.map +1 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/examples/simple-machine.ts +187 -0
- package/package.json +46 -0
- package/src/catalog/index.ts +12 -0
- package/src/catalog/types.ts +38 -0
- package/src/catalog/validate-binding.ts +35 -0
- package/src/catalog/validate-props.ts +109 -0
- package/src/define-player.ts +121 -0
- package/src/guards/compose.ts +169 -0
- package/src/guards/helpers.ts +104 -0
- package/src/guards/index.ts +12 -0
- package/src/guards/types.ts +23 -0
- package/src/index.ts +40 -0
- package/src/player-actor.ts +346 -0
- package/src/routing/build-url.ts +127 -0
- package/src/routing/derive-route.ts +152 -0
- package/src/routing/format-play-route-transitions.ts +77 -0
- package/src/routing/index.ts +13 -0
- package/src/routing/types.ts +26 -0
- package/src/signals/debounce.ts +38 -0
- package/src/signals/index.ts +2 -0
- package/src/signals/state-signal.ts +45 -0
- package/src/types.ts +47 -0
- package/test/derive-route.test.ts +166 -0
- package/test/devtools-integration.spec.ts +97 -0
- package/test/format-play-route-transitions-query.test.ts +187 -0
- package/test/guards-edge-cases.spec.ts +630 -0
- package/test/player-actor-basic.spec.ts +189 -0
- package/test/player-actor-edge-cases.spec.ts +769 -0
- package/test/routing-edge-cases.spec.ts +340 -0
- package/tsconfig.json +15 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevTools integration validation
|
|
3
|
+
*
|
|
4
|
+
* Per CONTEXT.md: "XState DevTools connects successfully and displays actor state/transitions"
|
|
5
|
+
* Per CONTEXT.md: "Snapshot format: Standard XState snapshots (no Play-specific data)"
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it as test, expect } from "vitest";
|
|
9
|
+
|
|
10
|
+
import { setup } from "xstate";
|
|
11
|
+
import { definePlayer } from "../src/define-player.js";
|
|
12
|
+
|
|
13
|
+
describe("DevTools Integration", () => {
|
|
14
|
+
test("PlayerActor maintains XState snapshot format", () => {
|
|
15
|
+
// Create simple machine
|
|
16
|
+
const machine = setup({
|
|
17
|
+
types: {
|
|
18
|
+
context: {} as { count: number },
|
|
19
|
+
events: {} as { type: "increment" },
|
|
20
|
+
},
|
|
21
|
+
}).createMachine({
|
|
22
|
+
context: { count: 0 },
|
|
23
|
+
on: {
|
|
24
|
+
increment: {
|
|
25
|
+
actions: ({ context }) => {
|
|
26
|
+
context.count++;
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Create player
|
|
33
|
+
const createPlayer = definePlayer({ machine });
|
|
34
|
+
const actor = createPlayer({ count: 0 });
|
|
35
|
+
actor.start();
|
|
36
|
+
|
|
37
|
+
// Get snapshot
|
|
38
|
+
const snapshot = actor.getSnapshot();
|
|
39
|
+
|
|
40
|
+
// Verify snapshot has standard XState properties (no Play pollution)
|
|
41
|
+
expect(snapshot, "Snapshot exists").toBeTruthy();
|
|
42
|
+
expect("value" in snapshot, "Snapshot has 'value' property").toBeTruthy();
|
|
43
|
+
expect("context" in snapshot, "Snapshot has 'context' property").toBeTruthy();
|
|
44
|
+
expect("status" in snapshot, "Snapshot has 'status' property").toBeTruthy();
|
|
45
|
+
|
|
46
|
+
// Verify NO Play-specific properties in snapshot
|
|
47
|
+
expect("_play" in snapshot).toBe(false, "Snapshot should not have '_play' property");
|
|
48
|
+
expect("_signals" in snapshot).toBe(false, "Snapshot should not have '_signals' property");
|
|
49
|
+
|
|
50
|
+
actor.stop();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("PlayerActor signals are accessible separately from snapshots", () => {
|
|
54
|
+
const machine = setup({
|
|
55
|
+
types: {
|
|
56
|
+
context: {} as { userId: string },
|
|
57
|
+
},
|
|
58
|
+
}).createMachine({
|
|
59
|
+
context: { userId: "" },
|
|
60
|
+
initial: "idle",
|
|
61
|
+
states: {
|
|
62
|
+
idle: {
|
|
63
|
+
meta: {
|
|
64
|
+
route: "/home",
|
|
65
|
+
view: { component: "Home" },
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const catalog = {
|
|
72
|
+
Home: {
|
|
73
|
+
schema: {
|
|
74
|
+
parse: () => ({}),
|
|
75
|
+
safeParse: () => ({ success: true, data: {} }),
|
|
76
|
+
} as any, // Mock Zod schema
|
|
77
|
+
component: () => "Home",
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const createPlayer = definePlayer({ machine, catalog });
|
|
82
|
+
const actor = createPlayer({ userId: "user123" });
|
|
83
|
+
actor.start();
|
|
84
|
+
|
|
85
|
+
// Signals accessible via actor properties (not in snapshot)
|
|
86
|
+
expect(actor.state, "Actor has state signal").toBeTruthy();
|
|
87
|
+
expect(actor.currentRoute, "Actor has currentRoute signal").toBeTruthy();
|
|
88
|
+
expect(actor.currentView, "Actor has currentView signal").toBeTruthy();
|
|
89
|
+
|
|
90
|
+
// Signals have get() method
|
|
91
|
+
expect(typeof actor.state.get).toBe("function");
|
|
92
|
+
expect(typeof actor.currentRoute.get).toBe("function");
|
|
93
|
+
expect(typeof actor.currentView.get).toBe("function");
|
|
94
|
+
|
|
95
|
+
actor.stop();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createActor, createMachine, type EventObject } from "xstate";
|
|
3
|
+
import { formatPlayRouteTransitions } from "../src/routing/format-play-route-transitions.js";
|
|
4
|
+
|
|
5
|
+
describe("formatPlayRouteTransitions - queryParams context assignment", () => {
|
|
6
|
+
it("should assign routeParams from event.params to context", () => {
|
|
7
|
+
const machineConfig = {
|
|
8
|
+
id: "testMachine",
|
|
9
|
+
initial: "home",
|
|
10
|
+
context: {
|
|
11
|
+
routeParams: {},
|
|
12
|
+
queryParams: {},
|
|
13
|
+
},
|
|
14
|
+
states: {
|
|
15
|
+
home: {
|
|
16
|
+
id: "home",
|
|
17
|
+
meta: { route: "/" },
|
|
18
|
+
},
|
|
19
|
+
profile: {
|
|
20
|
+
id: "profile",
|
|
21
|
+
meta: { route: "/profile/:userId" },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const machine = createMachine(formatPlayRouteTransitions(machineConfig));
|
|
27
|
+
const actor = createActor(machine);
|
|
28
|
+
actor.start();
|
|
29
|
+
|
|
30
|
+
// Send play.route event with params
|
|
31
|
+
actor.send({
|
|
32
|
+
type: "play.route",
|
|
33
|
+
to: "#profile",
|
|
34
|
+
params: { userId: "123" },
|
|
35
|
+
} as EventObject);
|
|
36
|
+
|
|
37
|
+
// Verify routeParams assigned
|
|
38
|
+
const snapshot = actor.getSnapshot();
|
|
39
|
+
expect(snapshot.context).toMatchObject({
|
|
40
|
+
routeParams: { userId: "123" },
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should assign queryParams from event.query to context", () => {
|
|
45
|
+
const machineConfig = {
|
|
46
|
+
id: "testMachine",
|
|
47
|
+
initial: "home",
|
|
48
|
+
context: {
|
|
49
|
+
routeParams: {},
|
|
50
|
+
queryParams: {},
|
|
51
|
+
},
|
|
52
|
+
states: {
|
|
53
|
+
home: {
|
|
54
|
+
id: "home",
|
|
55
|
+
meta: { route: "/" },
|
|
56
|
+
},
|
|
57
|
+
settings: {
|
|
58
|
+
id: "settings",
|
|
59
|
+
meta: { route: "/settings" },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const machine = createMachine(formatPlayRouteTransitions(machineConfig));
|
|
65
|
+
const actor = createActor(machine);
|
|
66
|
+
actor.start();
|
|
67
|
+
|
|
68
|
+
// Send play.route event with query
|
|
69
|
+
actor.send({
|
|
70
|
+
type: "play.route",
|
|
71
|
+
to: "#settings",
|
|
72
|
+
query: { tab: "security", theme: "dark" },
|
|
73
|
+
} as EventObject);
|
|
74
|
+
|
|
75
|
+
// Verify queryParams assigned
|
|
76
|
+
const snapshot = actor.getSnapshot();
|
|
77
|
+
expect(snapshot.context).toMatchObject({
|
|
78
|
+
queryParams: { tab: "security", theme: "dark" },
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should assign empty object when query is undefined", () => {
|
|
83
|
+
const machineConfig = {
|
|
84
|
+
id: "testMachine",
|
|
85
|
+
initial: "home",
|
|
86
|
+
context: {
|
|
87
|
+
routeParams: {},
|
|
88
|
+
queryParams: {},
|
|
89
|
+
},
|
|
90
|
+
states: {
|
|
91
|
+
home: {
|
|
92
|
+
id: "home",
|
|
93
|
+
meta: { route: "/" },
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const machine = createMachine(formatPlayRouteTransitions(machineConfig));
|
|
99
|
+
const actor = createActor(machine);
|
|
100
|
+
actor.start();
|
|
101
|
+
|
|
102
|
+
// Send play.route event without query
|
|
103
|
+
actor.send({
|
|
104
|
+
type: "play.route",
|
|
105
|
+
to: "#home",
|
|
106
|
+
} as EventObject);
|
|
107
|
+
|
|
108
|
+
// Verify queryParams is empty object
|
|
109
|
+
const snapshot = actor.getSnapshot();
|
|
110
|
+
expect(snapshot.context).toMatchObject({
|
|
111
|
+
queryParams: {},
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should assign both routeParams and queryParams when both present", () => {
|
|
116
|
+
const machineConfig = {
|
|
117
|
+
id: "testMachine",
|
|
118
|
+
initial: "home",
|
|
119
|
+
context: {
|
|
120
|
+
routeParams: {},
|
|
121
|
+
queryParams: {},
|
|
122
|
+
},
|
|
123
|
+
states: {
|
|
124
|
+
home: {
|
|
125
|
+
id: "home",
|
|
126
|
+
meta: { route: "/" },
|
|
127
|
+
},
|
|
128
|
+
profile: {
|
|
129
|
+
id: "profile",
|
|
130
|
+
meta: { route: "/profile/:userId" },
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const machine = createMachine(formatPlayRouteTransitions(machineConfig));
|
|
136
|
+
const actor = createActor(machine);
|
|
137
|
+
actor.start();
|
|
138
|
+
|
|
139
|
+
// Send play.route event with both params and query
|
|
140
|
+
actor.send({
|
|
141
|
+
type: "play.route",
|
|
142
|
+
to: "#profile",
|
|
143
|
+
params: { userId: "456" },
|
|
144
|
+
query: { view: "detailed", sort: "date" },
|
|
145
|
+
} as EventObject);
|
|
146
|
+
|
|
147
|
+
// Verify both assigned correctly
|
|
148
|
+
const snapshot = actor.getSnapshot();
|
|
149
|
+
expect(snapshot.context).toMatchObject({
|
|
150
|
+
routeParams: { userId: "456" },
|
|
151
|
+
queryParams: { view: "detailed", sort: "date" },
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("should preserve context types with queryParams field", () => {
|
|
156
|
+
// Type-level test: Verify context type includes queryParams
|
|
157
|
+
type TestContext = {
|
|
158
|
+
routeParams: Record<string, string>;
|
|
159
|
+
queryParams: Record<string, string>;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const machineConfig = {
|
|
163
|
+
id: "testMachine",
|
|
164
|
+
initial: "home",
|
|
165
|
+
context: {
|
|
166
|
+
routeParams: {},
|
|
167
|
+
queryParams: {},
|
|
168
|
+
} satisfies TestContext,
|
|
169
|
+
states: {
|
|
170
|
+
home: {
|
|
171
|
+
id: "home",
|
|
172
|
+
meta: { route: "/" },
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const machine = createMachine(formatPlayRouteTransitions(machineConfig));
|
|
178
|
+
const actor = createActor(machine);
|
|
179
|
+
actor.start();
|
|
180
|
+
|
|
181
|
+
// Verify context includes queryParams
|
|
182
|
+
const snapshot = actor.getSnapshot();
|
|
183
|
+
|
|
184
|
+
expect(snapshot.context).toHaveProperty("queryParams");
|
|
185
|
+
expect(typeof (snapshot.context as any).queryParams).toBe("object");
|
|
186
|
+
});
|
|
187
|
+
});
|