@player-ui/async-node-plugin 0.8.0--canary.307.9645 → 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,453 @@
1
+ import { expect, test } from "vitest";
2
+ import { Node, InProgressState, ViewInstance } from "@player-ui/player";
3
+ import { Player } from "@player-ui/player";
4
+ import { waitFor } from "@testing-library/react";
5
+ import { AsyncNodePlugin, AsyncNodePluginPlugin } from "./index";
6
+
7
+ const basicFRFWithActions = {
8
+ id: "test-flow",
9
+ views: [
10
+ {
11
+ id: "my-view",
12
+ actions: [
13
+ {
14
+ asset: {
15
+ id: "action-0",
16
+ type: "action",
17
+ value: "{{foo.bar}}",
18
+ },
19
+ },
20
+ {
21
+ id: "nodeId",
22
+ async: "true",
23
+ },
24
+ ],
25
+ },
26
+ ],
27
+ navigation: {
28
+ BEGIN: "FLOW_1",
29
+ FLOW_1: {
30
+ startState: "VIEW_1",
31
+ VIEW_1: {
32
+ state_type: "VIEW",
33
+ ref: "my-view",
34
+ transitions: {},
35
+ },
36
+ },
37
+ },
38
+ };
39
+
40
+ const asyncNodeTest = async (resolvedValue: any) => {
41
+ const plugin = new AsyncNodePlugin({
42
+ plugins: [new AsyncNodePluginPlugin()],
43
+ });
44
+
45
+ let deferredResolve: ((value: any) => void) | undefined;
46
+
47
+ plugin.hooks.onAsyncNode.tap("test", async (node: Node.Async) => {
48
+ return new Promise((resolve) => {
49
+ deferredResolve = resolve; // Promise would be resolved only once
50
+ });
51
+ });
52
+
53
+ let updateNumber = 0;
54
+
55
+ const player = new Player({ plugins: [plugin] });
56
+
57
+ let viewInstance: ViewInstance | undefined;
58
+
59
+ player.hooks.viewController.tap("async-node-test", (vc) => {
60
+ vc.hooks.view.tap("async-node-test", (view) => {
61
+ viewInstance = view;
62
+ view.hooks.onUpdate.tap("async-node-test", () => {
63
+ updateNumber++;
64
+ });
65
+ });
66
+ });
67
+
68
+ player.start(basicFRFWithActions as any);
69
+
70
+ let view = (player.getState() as InProgressState).controllers.view.currentView
71
+ ?.lastUpdate;
72
+
73
+ expect(view).toBeDefined();
74
+ expect(view?.actions[0].asset.type).toBe("action");
75
+ expect(view?.actions[1]).toBeUndefined();
76
+
77
+ await waitFor(() => {
78
+ expect(deferredResolve).toBeDefined();
79
+ });
80
+
81
+ // Consumer responds with null/undefined
82
+ if (deferredResolve) {
83
+ deferredResolve(resolvedValue);
84
+ }
85
+
86
+ await waitFor(() => {
87
+ expect(updateNumber).toBe(2);
88
+ });
89
+
90
+ view = (player.getState() as InProgressState).controllers.view.currentView
91
+ ?.lastUpdate;
92
+
93
+ expect(view?.actions[0].asset.type).toBe("action");
94
+ expect(view?.actions.length).toBe(1);
95
+
96
+ viewInstance?.update();
97
+
98
+ await waitFor(() => {
99
+ expect(updateNumber).toBe(3);
100
+ });
101
+
102
+ view = (player.getState() as InProgressState).controllers.view.currentView
103
+ ?.lastUpdate;
104
+
105
+ expect(view?.actions[0].asset.type).toBe("action");
106
+ expect(view?.actions.length).toBe(1);
107
+ };
108
+
109
+ test("should return current node view when the resolved node is null", async () => {
110
+ await asyncNodeTest(null);
111
+ });
112
+
113
+ test("should return current node view when the resolved node is undefined", async () => {
114
+ await asyncNodeTest(undefined);
115
+ });
116
+
117
+ test("replaces async nodes with provided node", async () => {
118
+ const plugin = new AsyncNodePlugin({
119
+ plugins: [new AsyncNodePluginPlugin()],
120
+ });
121
+
122
+ let deferredResolve: ((value: any) => void) | undefined;
123
+
124
+ plugin.hooks.onAsyncNode.tap("test", async (node: Node.Async) => {
125
+ return new Promise((resolve) => {
126
+ deferredResolve = resolve;
127
+ });
128
+ });
129
+ let updateNumber = 0;
130
+
131
+ const player = new Player({ plugins: [plugin] });
132
+
133
+ player.hooks.viewController.tap("async-node-test", (vc) => {
134
+ vc.hooks.view.tap("async-node-test", (view) => {
135
+ view.hooks.onUpdate.tap("async-node-test", (update) => {
136
+ updateNumber++;
137
+ });
138
+ });
139
+ });
140
+
141
+ player.start(basicFRFWithActions as any);
142
+
143
+ let view = (player.getState() as InProgressState).controllers.view.currentView
144
+ ?.lastUpdate;
145
+
146
+ expect(view).toBeDefined();
147
+ expect(view?.actions[0].asset.type).toBe("action");
148
+ expect(view?.actions[1]).toBeUndefined();
149
+ expect(updateNumber).toBe(1);
150
+
151
+ await waitFor(() => {
152
+ expect(deferredResolve).toBeDefined();
153
+ });
154
+
155
+ if (deferredResolve) {
156
+ deferredResolve({
157
+ asset: {
158
+ id: "next-label-action",
159
+ type: "action",
160
+ value: "dummy value",
161
+ },
162
+ });
163
+ }
164
+
165
+ await waitFor(() => {
166
+ expect(updateNumber).toBe(2);
167
+ });
168
+
169
+ view = (player.getState() as InProgressState).controllers.view.currentView
170
+ ?.lastUpdate;
171
+
172
+ expect(view?.actions[0].asset.type).toBe("action");
173
+ expect(view?.actions[1].asset.type).toBe("action");
174
+ });
175
+
176
+ test("replaces async nodes with multi node", async () => {
177
+ const plugin = new AsyncNodePlugin({
178
+ plugins: [new AsyncNodePluginPlugin()],
179
+ });
180
+
181
+ let deferredResolve: ((value: any) => void) | undefined;
182
+
183
+ plugin.hooks.onAsyncNode.tap("test", async (node) => {
184
+ return new Promise((resolve) => {
185
+ deferredResolve = resolve;
186
+ });
187
+ });
188
+
189
+ let updateNumber = 0;
190
+
191
+ const player = new Player({ plugins: [plugin] });
192
+
193
+ player.hooks.viewController.tap("async-node-test", (vc) => {
194
+ vc.hooks.view.tap("async-node-test", (view) => {
195
+ view.hooks.onUpdate.tap("async-node-test", (update) => {
196
+ updateNumber++;
197
+ });
198
+ });
199
+ });
200
+
201
+ player.start(basicFRFWithActions as any);
202
+
203
+ let view = (player.getState() as InProgressState).controllers.view.currentView
204
+ ?.lastUpdate;
205
+
206
+ expect(view).toBeDefined();
207
+ expect(view?.actions[1]).toBeUndefined();
208
+ expect(updateNumber).toBe(1);
209
+
210
+ await waitFor(() => {
211
+ expect(deferredResolve).toBeDefined();
212
+ });
213
+
214
+ if (deferredResolve) {
215
+ deferredResolve([
216
+ {
217
+ asset: {
218
+ id: "value-1",
219
+ type: "text",
220
+ value: "1st value in the multinode",
221
+ },
222
+ },
223
+ {
224
+ asset: {
225
+ id: "value-2",
226
+ type: "text",
227
+ value: "2nd value in the multinode",
228
+ },
229
+ },
230
+ ]);
231
+ }
232
+
233
+ await waitFor(() => {
234
+ expect(updateNumber).toBe(2);
235
+ });
236
+
237
+ view = (player.getState() as InProgressState).controllers.view.currentView
238
+ ?.lastUpdate;
239
+
240
+ expect(view?.actions[0].asset.type).toBe("action");
241
+ expect(view?.actions[1].asset.type).toBe("text");
242
+ expect(view?.actions[2].asset.type).toBe("text");
243
+ });
244
+
245
+ test("replaces async nodes with chained multiNodes", async () => {
246
+ const plugin = new AsyncNodePlugin({
247
+ plugins: [new AsyncNodePluginPlugin()],
248
+ });
249
+
250
+ let deferredResolve: ((value: any) => void) | undefined;
251
+
252
+ plugin.hooks.onAsyncNode.tap("test", async (node: Node.Async) => {
253
+ return new Promise((resolve) => {
254
+ deferredResolve = resolve;
255
+ });
256
+ });
257
+ let updateNumber = 0;
258
+
259
+ const player = new Player({ plugins: [plugin] });
260
+
261
+ player.hooks.viewController.tap("async-node-test", (vc) => {
262
+ vc.hooks.view.tap("async-node-test", (view) => {
263
+ view.hooks.onUpdate.tap("async-node-test", (update) => {
264
+ updateNumber++;
265
+ });
266
+ });
267
+ });
268
+
269
+ player.start(basicFRFWithActions as any);
270
+
271
+ let view = (player.getState() as InProgressState).controllers.view.currentView
272
+ ?.lastUpdate;
273
+
274
+ expect(view).toBeDefined();
275
+ expect(view?.actions[1]).toBeUndefined();
276
+
277
+ await waitFor(() => {
278
+ expect(updateNumber).toBe(1);
279
+ expect(deferredResolve).toBeDefined();
280
+ });
281
+
282
+ if (deferredResolve) {
283
+ deferredResolve([
284
+ {
285
+ asset: {
286
+ id: "value-1",
287
+ type: "text",
288
+ value: "1st value in the multinode",
289
+ },
290
+ },
291
+ {
292
+ id: "another-async",
293
+ async: true,
294
+ },
295
+ ]);
296
+ }
297
+
298
+ await waitFor(() => {
299
+ expect(updateNumber).toBe(2);
300
+ });
301
+
302
+ view = (player.getState() as InProgressState).controllers.view.currentView
303
+ ?.lastUpdate;
304
+
305
+ expect(view?.actions[0].asset.type).toBe("action");
306
+ expect(view?.actions[1].asset.type).toBe("text");
307
+ expect(view?.actions[2]).toBeUndefined();
308
+ expect(updateNumber).toBe(2);
309
+
310
+ if (deferredResolve) {
311
+ deferredResolve([
312
+ {
313
+ asset: {
314
+ id: "value-2",
315
+ type: "text",
316
+ value: "2nd value in the multinode",
317
+ },
318
+ },
319
+ {
320
+ asset: {
321
+ id: "value-3",
322
+ type: "text",
323
+ value: "3rd value in the multinode",
324
+ },
325
+ },
326
+ ]);
327
+ }
328
+
329
+ await waitFor(() => {
330
+ expect(updateNumber).toBe(3);
331
+ });
332
+
333
+ view = (player.getState() as InProgressState).controllers.view.currentView
334
+ ?.lastUpdate;
335
+
336
+ expect(view?.actions[0].asset.type).toBe("action");
337
+ expect(view?.actions[1].asset.type).toBe("text");
338
+ expect(view?.actions[2].asset.type).toBe("text");
339
+ expect(view?.actions[3].asset.type).toBe("text");
340
+ });
341
+
342
+ test("replaces async nodes with chained multiNodes singular", async () => {
343
+ const plugin = new AsyncNodePlugin({
344
+ plugins: [new AsyncNodePluginPlugin()],
345
+ });
346
+
347
+ let deferredResolve: ((value: any) => void) | undefined;
348
+
349
+ plugin.hooks.onAsyncNode.tap("test", async (node: Node.Async) => {
350
+ return new Promise((resolve) => {
351
+ deferredResolve = resolve;
352
+ });
353
+ });
354
+ let updateNumber = 0;
355
+
356
+ const player = new Player({ plugins: [plugin] });
357
+
358
+ player.hooks.viewController.tap("async-node-test", (vc) => {
359
+ vc.hooks.view.tap("async-node-test", (view) => {
360
+ view.hooks.onUpdate.tap("async-node-test", (update) => {
361
+ updateNumber++;
362
+ });
363
+ });
364
+ });
365
+
366
+ player.start(basicFRFWithActions as any);
367
+
368
+ let view = (player.getState() as InProgressState).controllers.view.currentView
369
+ ?.lastUpdate;
370
+
371
+ expect(view).toBeDefined();
372
+ expect(view?.actions[1]).toBeUndefined();
373
+
374
+ await waitFor(() => {
375
+ expect(updateNumber).toBe(1);
376
+ expect(deferredResolve).toBeDefined();
377
+ });
378
+
379
+ if (deferredResolve) {
380
+ deferredResolve([
381
+ {
382
+ asset: {
383
+ id: "value-1",
384
+ type: "text",
385
+ value: "1st value in the multinode",
386
+ },
387
+ },
388
+ {
389
+ id: "another-async",
390
+ async: true,
391
+ },
392
+ ]);
393
+ }
394
+
395
+ await waitFor(() => {
396
+ expect(updateNumber).toBe(2);
397
+ });
398
+
399
+ view = (player.getState() as InProgressState).controllers.view.currentView
400
+ ?.lastUpdate;
401
+
402
+ expect(view?.actions[0].asset.type).toBe("action");
403
+ expect(view?.actions[1].asset.type).toBe("text");
404
+ expect(view?.actions[2]).toBeUndefined();
405
+
406
+ if (deferredResolve) {
407
+ deferredResolve({
408
+ asset: {
409
+ id: "value-2",
410
+ type: "text",
411
+ value: "2nd value in the multinode",
412
+ },
413
+ });
414
+ }
415
+
416
+ await waitFor(() => {
417
+ expect(updateNumber).toBe(3);
418
+ });
419
+
420
+ view = (player.getState() as InProgressState).controllers.view.currentView
421
+ ?.lastUpdate;
422
+
423
+ expect(view?.actions[0].asset.type).toBe("action");
424
+ expect(view?.actions[1].asset.type).toBe("text");
425
+ expect(view?.actions[2].asset.type).toBe("text");
426
+ });
427
+
428
+ test("should call onAsyncNode hook when async node is encountered", async () => {
429
+ const plugin = new AsyncNodePlugin({
430
+ plugins: [new AsyncNodePluginPlugin()],
431
+ });
432
+
433
+ let localNode: Node.Async;
434
+ plugin.hooks.onAsyncNode.tap("test", async (node: Node.Async) => {
435
+ if (node !== null) {
436
+ // assigns node value to a local variable
437
+ localNode = node;
438
+ }
439
+
440
+ return new Promise((resolve) => {
441
+ resolve("Promise resolved");
442
+ });
443
+ });
444
+
445
+ const player = new Player({ plugins: [plugin] });
446
+
447
+ player.start(basicFRFWithActions as any);
448
+
449
+ await waitFor(() => {
450
+ expect(localNode.id).toStrictEqual("nodeId");
451
+ expect(localNode.type).toStrictEqual("async");
452
+ });
453
+ });