@player-ui/asset-transform-plugin 0.8.0--canary.307.9621 → 0.8.0-next.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.
@@ -0,0 +1,359 @@
1
+ import { test, expect, vitest } from "vitest";
2
+ import type { InProgressState, TransformRegistry } from "@player-ui/player";
3
+ import { waitFor } from "@testing-library/react";
4
+ import { Player } from "@player-ui/player";
5
+ import type { Flow } from "@player-ui/types";
6
+ import { Registry } from "@player-ui/partial-match-registry";
7
+ import {
8
+ AssetTransformPlugin,
9
+ compose,
10
+ composeBefore,
11
+ propertiesToSkipTransform,
12
+ } from "..";
13
+
14
+ const basicContentWithActions: Flow<any> = {
15
+ id: "test-flow",
16
+ views: [
17
+ {
18
+ id: "my-view",
19
+ actions: [
20
+ {
21
+ asset: {
22
+ id: "next-label-action",
23
+ type: "action",
24
+ value: "{{foo.bar}}",
25
+ },
26
+ },
27
+ ],
28
+ },
29
+ ],
30
+ navigation: {
31
+ BEGIN: "FLOW_1",
32
+ FLOW_1: {
33
+ startState: "VIEW_1",
34
+ VIEW_1: {
35
+ state_type: "VIEW",
36
+ ref: "my-view",
37
+ transitions: {},
38
+ },
39
+ },
40
+ },
41
+ };
42
+
43
+ const basicFRFWithActionsAndExpressions = (asset = "action"): Flow<any> => ({
44
+ id: "test-flow",
45
+ views: [
46
+ {
47
+ id: "my-view",
48
+ actions: [
49
+ {
50
+ asset: {
51
+ id: "next-label-action",
52
+ type: asset,
53
+ value: "{{foo.bar}}",
54
+ example: ['{{foo.bar}} = "test"'],
55
+ },
56
+ },
57
+ ],
58
+ },
59
+ ],
60
+ data: {
61
+ foo: {
62
+ bar: "1",
63
+ },
64
+ },
65
+ navigation: {
66
+ BEGIN: "FLOW_1",
67
+ FLOW_1: {
68
+ startState: "VIEW_1",
69
+ VIEW_1: {
70
+ state_type: "VIEW",
71
+ ref: "my-view",
72
+ transitions: {},
73
+ },
74
+ },
75
+ },
76
+ });
77
+
78
+ const otherAction: Flow<any> = {
79
+ ...basicContentWithActions,
80
+ views: [
81
+ {
82
+ id: "my-view",
83
+ actions: [
84
+ {
85
+ asset: {
86
+ id: "next-label-action",
87
+ type: "stateful-action",
88
+ },
89
+ },
90
+ ],
91
+ },
92
+ ],
93
+ };
94
+
95
+ const choice: Flow<any> = {
96
+ id: "some view",
97
+ views: [
98
+ {
99
+ id: "my-view",
100
+ actions: [
101
+ {
102
+ asset: {
103
+ id: "choice",
104
+ type: "choice",
105
+ value: "some value",
106
+ choiceDetail: {
107
+ asset: {
108
+ id: "choiceDetail",
109
+ type: "choiceItem",
110
+ value: "choice detail",
111
+ },
112
+ },
113
+ },
114
+ },
115
+ ],
116
+ },
117
+ ],
118
+ navigation: {
119
+ BEGIN: "FLOW_1",
120
+ FLOW_1: {
121
+ startState: "VIEW_1",
122
+ VIEW_1: {
123
+ state_type: "VIEW",
124
+ ref: "my-view",
125
+ transitions: {},
126
+ },
127
+ },
128
+ },
129
+ };
130
+
131
+ const registry: TransformRegistry = new Registry();
132
+ registry.set({ type: "action" }, (asset, options) => {
133
+ return {
134
+ ...asset,
135
+ run: () => {
136
+ options.data.model.set([["foo.bar", "it worked!"]]);
137
+ },
138
+ };
139
+ });
140
+ registry.set(
141
+ { type: "action-2" },
142
+ compose(
143
+ (asset, options) => {
144
+ return {
145
+ ...asset,
146
+ run: () => {
147
+ options.data.model.set([["foo.bar", "it worked!"]]);
148
+ },
149
+ };
150
+ },
151
+ composeBefore(propertiesToSkipTransform(["example"])),
152
+ ),
153
+ );
154
+
155
+ registry.set({ type: "stateful-action" }, (asset, options, store) => {
156
+ const [count, setCount] = store.useLocalState(1);
157
+ const [label] = store.useLocalState("some text");
158
+
159
+ return {
160
+ ...asset,
161
+ count,
162
+ label,
163
+ increment() {
164
+ setCount(Math.min(count + 1, 2));
165
+ },
166
+ };
167
+ });
168
+
169
+ registry.set(
170
+ { type: "choice" },
171
+ {
172
+ beforeResolve: (asset, options, store) => {
173
+ store.useSharedState("shared")("choice before resolve");
174
+ store.useLocalState(5);
175
+
176
+ return {
177
+ ...asset,
178
+ children: [],
179
+ };
180
+ },
181
+ resolve: (asset, options, store) => {
182
+ const [count, setCount] = store.useLocalState(2);
183
+ const [label, setLabel] = store.useSharedState("shared")("newValue");
184
+
185
+ return {
186
+ ...asset,
187
+ count,
188
+ label,
189
+ after: () => {
190
+ setCount(count + 1);
191
+ setLabel("i can reset this");
192
+ },
193
+ };
194
+ },
195
+ },
196
+ );
197
+
198
+ test("transforms matching assets", () => {
199
+ const player = new Player({ plugins: [new AssetTransformPlugin(registry)] });
200
+ player.start(basicContentWithActions);
201
+
202
+ // Should now add a run function
203
+ const view = (player.getState() as InProgressState).controllers.view
204
+ .currentView?.lastUpdate;
205
+ expect(typeof view?.actions[0].asset.run).toBe("function");
206
+ });
207
+
208
+ test("transforms matching assets and does not skip string resolution", () => {
209
+ const player = new Player({ plugins: [new AssetTransformPlugin(registry)] });
210
+ player.start(basicFRFWithActionsAndExpressions());
211
+
212
+ // Should now add a run function
213
+ const view = (player.getState() as InProgressState).controllers.view
214
+ .currentView?.lastUpdate;
215
+
216
+ expect(typeof view?.actions[0].asset.run).toBe("function");
217
+ expect(view?.actions[0].asset.example).toStrictEqual(['1 = "test"']);
218
+ });
219
+
220
+ test("transforms matching assets and skips string resolution", () => {
221
+ const player = new Player({ plugins: [new AssetTransformPlugin(registry)] });
222
+ player.start(basicFRFWithActionsAndExpressions("action-2"));
223
+
224
+ // Should now add a run function
225
+ const view = (player.getState() as InProgressState).controllers.view
226
+ .currentView?.lastUpdate;
227
+
228
+ expect(typeof view?.actions[0].asset.run).toBe("function");
229
+ expect(view?.actions[0].asset.example).toStrictEqual([
230
+ '{{foo.bar}} = "test"',
231
+ ]);
232
+ });
233
+
234
+ test("uses shorthand version", () => {
235
+ const player = new Player({
236
+ plugins: [
237
+ new AssetTransformPlugin([
238
+ [
239
+ { type: "action" },
240
+ (asset, options) => {
241
+ return {
242
+ ...asset,
243
+ run: () => {
244
+ options.data.model.set([["foo.bar", "it worked!"]]);
245
+ },
246
+ };
247
+ },
248
+ ],
249
+ ]),
250
+ ],
251
+ });
252
+ player.start(basicContentWithActions);
253
+
254
+ // Should now add a run function
255
+ const view = (player.getState() as InProgressState).controllers.view
256
+ .currentView?.lastUpdate;
257
+ expect(typeof view?.actions[0].asset.run).toBe("function");
258
+ });
259
+
260
+ test("transforms matching assets with all transform types", () => {
261
+ const player = new Player({ plugins: [new AssetTransformPlugin(registry)] });
262
+ player.start(choice);
263
+
264
+ // Should now add a run function
265
+ const view = (player.getState() as InProgressState).controllers.view
266
+ .currentView?.lastUpdate;
267
+
268
+ expect(typeof view?.actions[0].asset.choiceDetail).toBe("undefined");
269
+ expect(typeof view?.actions[0].asset.after).toBe("function");
270
+ });
271
+
272
+ test("keeps same transform when data stays the same", async () => {
273
+ const player = new Player({ plugins: [new AssetTransformPlugin(registry)] });
274
+ player.start(basicContentWithActions);
275
+
276
+ // Should now add a run function
277
+ const initialView = (player.getState() as InProgressState).controllers.view
278
+ .currentView?.lastUpdate;
279
+
280
+ expect(initialView?.actions[0].asset.value).toBe(undefined);
281
+ initialView?.actions[0].asset.run();
282
+
283
+ let updatedView = (player.getState() as InProgressState).controllers.view
284
+ .currentView?.lastUpdate;
285
+ await waitFor(() => {
286
+ updatedView = (player.getState() as InProgressState).controllers.view
287
+ .currentView?.lastUpdate;
288
+ expect(updatedView?.actions[0].asset.value).toBe("it worked!");
289
+ });
290
+
291
+ expect(updatedView?.actions[0].asset.value).toBe("it worked!");
292
+
293
+ updatedView?.actions[0].asset.run();
294
+ const nonUpdatedView = (player.getState() as InProgressState).controllers.view
295
+ .currentView?.lastUpdate;
296
+ expect(nonUpdatedView).toBe(updatedView);
297
+ });
298
+
299
+ test("updates when the transform store updates", () => {
300
+ const player = new Player({ plugins: [new AssetTransformPlugin(registry)] });
301
+ player.start(otherAction);
302
+
303
+ // Should now have the count and an increment function
304
+ const initialView = (player.getState() as InProgressState).controllers.view
305
+ .currentView?.lastUpdate;
306
+ expect(initialView?.actions[0].asset.count).toBe(1);
307
+
308
+ expect(initialView?.actions[0].asset.label).toBe("some text");
309
+
310
+ initialView?.actions[0].asset.increment();
311
+
312
+ const updatedView = (player.getState() as InProgressState).controllers.view
313
+ .currentView?.lastUpdate;
314
+ expect(updatedView?.actions[0].asset.count).toBe(2);
315
+ updatedView?.actions[0].asset.increment();
316
+
317
+ const nonUpdatedView = (player.getState() as InProgressState).controllers.view
318
+ .currentView?.lastUpdate;
319
+ expect(nonUpdatedView).toBe(updatedView);
320
+ });
321
+
322
+ test("uses the same store", () => {
323
+ const player = new Player({ plugins: [new AssetTransformPlugin(registry)] });
324
+ player.start(choice);
325
+
326
+ // Should now have the count and an increment function
327
+ const initialView = (player.getState() as InProgressState).controllers.view
328
+ .currentView?.lastUpdate;
329
+ expect(initialView?.actions[0].asset.count).toBe(2);
330
+ expect(initialView?.actions[0].asset.label).toBe("choice before resolve");
331
+ initialView?.actions[0].asset.after();
332
+
333
+ const updatedView = (player.getState() as InProgressState).controllers.view
334
+ .currentView?.lastUpdate;
335
+ expect(updatedView?.actions[0].asset.count).toBe(3);
336
+ expect(updatedView?.actions[0].asset.label).toBe("i can reset this");
337
+ });
338
+
339
+ test("merges registries", () => {
340
+ const actionFn1 = vitest.fn();
341
+ const actionFn2 = vitest.fn();
342
+
343
+ const player = new Player({
344
+ plugins: [
345
+ new AssetTransformPlugin([[{ type: "action" }, actionFn1]]),
346
+ new AssetTransformPlugin([[{ type: "action" }, actionFn2]]),
347
+ ],
348
+ });
349
+
350
+ player.start(basicContentWithActions);
351
+ const { transformRegistry } = (player.getState() as InProgressState)
352
+ .controllers.view;
353
+
354
+ const transform = transformRegistry.get({ type: "action" });
355
+ transform?.resolve?.({}, {} as any, {} as any);
356
+
357
+ expect(actionFn1).not.toHaveBeenCalled();
358
+ expect(actionFn2).toHaveBeenCalled();
359
+ });