@player-ui/async-node-plugin 0.8.0--canary.307.9621 → 0.8.0--canary.410.15865

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,549 @@
1
+ import { expect, test, vi } 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
+ let updateContent: any;
48
+
49
+ plugin.hooks.onAsyncNode.tap(
50
+ "test",
51
+ async (node: Node.Async, update: (content: any) => void) => {
52
+ const result = new Promise((resolve) => {
53
+ deferredResolve = resolve; // Promise would be resolved only once
54
+ });
55
+
56
+ updateContent = update;
57
+ // Return the result to follow the same mechanism as before
58
+ return result;
59
+ },
60
+ );
61
+
62
+ let updateNumber = 0;
63
+
64
+ const player = new Player({ plugins: [plugin] });
65
+
66
+ let viewInstance: ViewInstance | undefined;
67
+
68
+ player.hooks.viewController.tap("async-node-test", (vc) => {
69
+ vc.hooks.view.tap("async-node-test", (view) => {
70
+ viewInstance = view;
71
+ view.hooks.onUpdate.tap("async-node-test", () => {
72
+ updateNumber++;
73
+ });
74
+ });
75
+ });
76
+
77
+ player.start(basicFRFWithActions as any);
78
+
79
+ let view = (player.getState() as InProgressState).controllers.view.currentView
80
+ ?.lastUpdate;
81
+
82
+ expect(view).toBeDefined();
83
+ expect(view?.actions[0].asset.type).toBe("action");
84
+ expect(view?.actions[1]).toBeUndefined();
85
+
86
+ await waitFor(() => {
87
+ expect(deferredResolve).toBeDefined();
88
+ });
89
+
90
+ // Consumer responds with null/undefined
91
+ if (deferredResolve) {
92
+ deferredResolve(resolvedValue);
93
+ }
94
+
95
+ await waitFor(() => {
96
+ expect(updateNumber).toBe(1);
97
+ });
98
+
99
+ view = (player.getState() as InProgressState).controllers.view.currentView
100
+ ?.lastUpdate;
101
+
102
+ expect(view?.actions[0].asset.type).toBe("action");
103
+ expect(view?.actions.length).toBe(1);
104
+
105
+ // Consumer responds with null/undefined
106
+ if (deferredResolve) {
107
+ updateContent(resolvedValue);
108
+ }
109
+
110
+ //Even after an update, the view should not change as we are deleting the resolved node if there is no view update
111
+ await waitFor(() => {
112
+ expect(updateNumber).toBe(1);
113
+ });
114
+
115
+ view = (player.getState() as InProgressState).controllers.view.currentView
116
+ ?.lastUpdate;
117
+
118
+ expect(view?.actions[0].asset.type).toBe("action");
119
+ expect(view?.actions.length).toBe(1);
120
+ };
121
+
122
+ test("should return current node view when the resolved node is null", async () => {
123
+ await asyncNodeTest(null);
124
+ });
125
+
126
+ test("should return current node view when the resolved node is undefined", async () => {
127
+ await asyncNodeTest(undefined);
128
+ });
129
+
130
+ test("can handle multiple updates through callback mechanism", async () => {
131
+ const plugin = new AsyncNodePlugin({
132
+ plugins: [new AsyncNodePluginPlugin()],
133
+ });
134
+
135
+ let deferredResolve: ((value: any) => void) | undefined;
136
+
137
+ let updateContent: any;
138
+
139
+ plugin.hooks.onAsyncNode.tap(
140
+ "test",
141
+ async (node: Node.Async, update: (content: any) => void) => {
142
+ const result = new Promise((resolve) => {
143
+ deferredResolve = resolve; // Promise would be resolved only once
144
+ });
145
+
146
+ updateContent = update;
147
+ // Return the result to follow the same mechanism as before
148
+ return result;
149
+ },
150
+ );
151
+
152
+ let updateNumber = 0;
153
+
154
+ const player = new Player({ plugins: [plugin] });
155
+
156
+ player.hooks.viewController.tap("async-node-test", (vc) => {
157
+ vc.hooks.view.tap("async-node-test", (view) => {
158
+ view.hooks.onUpdate.tap("async-node-test", (update) => {
159
+ updateNumber++;
160
+ });
161
+ });
162
+ });
163
+
164
+ player.start(basicFRFWithActions as any);
165
+
166
+ let view = (player.getState() as InProgressState).controllers.view.currentView
167
+ ?.lastUpdate;
168
+
169
+ expect(view).toBeDefined();
170
+ expect(view?.actions[1]).toBeUndefined();
171
+
172
+ await waitFor(() => {
173
+ expect(updateNumber).toBe(1);
174
+ expect(deferredResolve).toBeDefined();
175
+ });
176
+
177
+ if (deferredResolve) {
178
+ deferredResolve({
179
+ asset: {
180
+ id: "next-label-action",
181
+ type: "action",
182
+ value: "dummy value",
183
+ },
184
+ });
185
+ }
186
+
187
+ await waitFor(() => {
188
+ expect(updateNumber).toBe(2);
189
+ });
190
+
191
+ view = (player.getState() as InProgressState).controllers.view.currentView
192
+ ?.lastUpdate;
193
+
194
+ expect(view?.actions[0].asset.type).toBe("action");
195
+ expect(view?.actions[1].asset.type).toBe("action");
196
+ expect(updateNumber).toBe(2);
197
+
198
+ if (deferredResolve) {
199
+ updateContent(null);
200
+ }
201
+
202
+ await waitFor(() => {
203
+ expect(updateNumber).toBe(3);
204
+ });
205
+
206
+ view = (player.getState() as InProgressState).controllers.view.currentView
207
+ ?.lastUpdate;
208
+
209
+ expect(view?.actions[0].asset.type).toBe("action");
210
+ expect(view?.actions[1]).toBeUndefined();
211
+ });
212
+
213
+ test("replaces async nodes with provided node", async () => {
214
+ const plugin = new AsyncNodePlugin({
215
+ plugins: [new AsyncNodePluginPlugin()],
216
+ });
217
+
218
+ let deferredResolve: ((value: any) => void) | undefined;
219
+
220
+ plugin.hooks.onAsyncNode.tap("test", async (node: Node.Async) => {
221
+ return new Promise((resolve) => {
222
+ deferredResolve = resolve;
223
+ });
224
+ });
225
+ let updateNumber = 0;
226
+
227
+ const player = new Player({ plugins: [plugin] });
228
+
229
+ player.hooks.viewController.tap("async-node-test", (vc) => {
230
+ vc.hooks.view.tap("async-node-test", (view) => {
231
+ view.hooks.onUpdate.tap("async-node-test", (update) => {
232
+ updateNumber++;
233
+ });
234
+ });
235
+ });
236
+
237
+ player.start(basicFRFWithActions as any);
238
+
239
+ let view = (player.getState() as InProgressState).controllers.view.currentView
240
+ ?.lastUpdate;
241
+
242
+ expect(view).toBeDefined();
243
+ expect(view?.actions[0].asset.type).toBe("action");
244
+ expect(view?.actions[1]).toBeUndefined();
245
+ expect(updateNumber).toBe(1);
246
+
247
+ await waitFor(() => {
248
+ expect(deferredResolve).toBeDefined();
249
+ });
250
+
251
+ if (deferredResolve) {
252
+ deferredResolve({
253
+ asset: {
254
+ id: "next-label-action",
255
+ type: "action",
256
+ value: "dummy value",
257
+ },
258
+ });
259
+ }
260
+
261
+ await waitFor(() => {
262
+ expect(updateNumber).toBe(2);
263
+ });
264
+
265
+ view = (player.getState() as InProgressState).controllers.view.currentView
266
+ ?.lastUpdate;
267
+
268
+ expect(view?.actions[0].asset.type).toBe("action");
269
+ expect(view?.actions[1].asset.type).toBe("action");
270
+ });
271
+
272
+ test("replaces async nodes with multi node", async () => {
273
+ const plugin = new AsyncNodePlugin({
274
+ plugins: [new AsyncNodePluginPlugin()],
275
+ });
276
+
277
+ let deferredResolve: ((value: any) => void) | undefined;
278
+
279
+ plugin.hooks.onAsyncNode.tap("test", async (node) => {
280
+ return new Promise((resolve) => {
281
+ deferredResolve = resolve;
282
+ });
283
+ });
284
+
285
+ let updateNumber = 0;
286
+
287
+ const player = new Player({ plugins: [plugin] });
288
+
289
+ player.hooks.viewController.tap("async-node-test", (vc) => {
290
+ vc.hooks.view.tap("async-node-test", (view) => {
291
+ view.hooks.onUpdate.tap("async-node-test", (update) => {
292
+ updateNumber++;
293
+ });
294
+ });
295
+ });
296
+
297
+ player.start(basicFRFWithActions as any);
298
+
299
+ let view = (player.getState() as InProgressState).controllers.view.currentView
300
+ ?.lastUpdate;
301
+
302
+ expect(view).toBeDefined();
303
+ expect(view?.actions[1]).toBeUndefined();
304
+ expect(updateNumber).toBe(1);
305
+
306
+ await waitFor(() => {
307
+ expect(deferredResolve).toBeDefined();
308
+ });
309
+
310
+ if (deferredResolve) {
311
+ deferredResolve([
312
+ {
313
+ asset: {
314
+ id: "value-1",
315
+ type: "text",
316
+ value: "1st value in the multinode",
317
+ },
318
+ },
319
+ {
320
+ asset: {
321
+ id: "value-2",
322
+ type: "text",
323
+ value: "2nd value in the multinode",
324
+ },
325
+ },
326
+ ]);
327
+ }
328
+
329
+ await waitFor(() => {
330
+ expect(updateNumber).toBe(2);
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
+ });
340
+
341
+ test("replaces async nodes with chained multiNodes", async () => {
342
+ const plugin = new AsyncNodePlugin({
343
+ plugins: [new AsyncNodePluginPlugin()],
344
+ });
345
+
346
+ let deferredResolve: ((value: any) => void) | undefined;
347
+
348
+ plugin.hooks.onAsyncNode.tap("test", async (node: Node.Async) => {
349
+ return new Promise((resolve) => {
350
+ deferredResolve = resolve;
351
+ });
352
+ });
353
+ let updateNumber = 0;
354
+
355
+ const player = new Player({ plugins: [plugin] });
356
+
357
+ player.hooks.viewController.tap("async-node-test", (vc) => {
358
+ vc.hooks.view.tap("async-node-test", (view) => {
359
+ view.hooks.onUpdate.tap("async-node-test", (update) => {
360
+ updateNumber++;
361
+ });
362
+ });
363
+ });
364
+
365
+ player.start(basicFRFWithActions as any);
366
+
367
+ let view = (player.getState() as InProgressState).controllers.view.currentView
368
+ ?.lastUpdate;
369
+
370
+ expect(view).toBeDefined();
371
+ expect(view?.actions[1]).toBeUndefined();
372
+
373
+ await waitFor(() => {
374
+ expect(updateNumber).toBe(1);
375
+ expect(deferredResolve).toBeDefined();
376
+ });
377
+
378
+ if (deferredResolve) {
379
+ deferredResolve([
380
+ {
381
+ asset: {
382
+ id: "value-1",
383
+ type: "text",
384
+ value: "1st value in the multinode",
385
+ },
386
+ },
387
+ {
388
+ id: "another-async",
389
+ async: true,
390
+ },
391
+ ]);
392
+ }
393
+
394
+ await waitFor(() => {
395
+ expect(updateNumber).toBe(2);
396
+ });
397
+
398
+ view = (player.getState() as InProgressState).controllers.view.currentView
399
+ ?.lastUpdate;
400
+
401
+ expect(view?.actions[0].asset.type).toBe("action");
402
+ expect(view?.actions[1].asset.type).toBe("text");
403
+ expect(view?.actions[2]).toBeUndefined();
404
+ expect(updateNumber).toBe(2);
405
+
406
+ if (deferredResolve) {
407
+ deferredResolve([
408
+ {
409
+ asset: {
410
+ id: "value-2",
411
+ type: "text",
412
+ value: "2nd value in the multinode",
413
+ },
414
+ },
415
+ {
416
+ asset: {
417
+ id: "value-3",
418
+ type: "text",
419
+ value: "3rd value in the multinode",
420
+ },
421
+ },
422
+ ]);
423
+ }
424
+
425
+ await waitFor(() => {
426
+ expect(updateNumber).toBe(3);
427
+ });
428
+
429
+ view = (player.getState() as InProgressState).controllers.view.currentView
430
+ ?.lastUpdate;
431
+
432
+ expect(view?.actions[0].asset.type).toBe("action");
433
+ expect(view?.actions[1].asset.type).toBe("text");
434
+ expect(view?.actions[2].asset.type).toBe("text");
435
+ expect(view?.actions[3].asset.type).toBe("text");
436
+ });
437
+
438
+ test("replaces async nodes with chained multiNodes singular", async () => {
439
+ const plugin = new AsyncNodePlugin({
440
+ plugins: [new AsyncNodePluginPlugin()],
441
+ });
442
+
443
+ let deferredResolve: ((value: any) => void) | undefined;
444
+
445
+ plugin.hooks.onAsyncNode.tap("test", async (node: Node.Async) => {
446
+ return new Promise((resolve) => {
447
+ deferredResolve = resolve;
448
+ });
449
+ });
450
+ let updateNumber = 0;
451
+
452
+ const player = new Player({ plugins: [plugin] });
453
+
454
+ player.hooks.viewController.tap("async-node-test", (vc) => {
455
+ vc.hooks.view.tap("async-node-test", (view) => {
456
+ view.hooks.onUpdate.tap("async-node-test", (update) => {
457
+ updateNumber++;
458
+ });
459
+ });
460
+ });
461
+
462
+ player.start(basicFRFWithActions as any);
463
+
464
+ let view = (player.getState() as InProgressState).controllers.view.currentView
465
+ ?.lastUpdate;
466
+
467
+ expect(view).toBeDefined();
468
+ expect(view?.actions[1]).toBeUndefined();
469
+
470
+ await waitFor(() => {
471
+ expect(updateNumber).toBe(1);
472
+ expect(deferredResolve).toBeDefined();
473
+ });
474
+
475
+ if (deferredResolve) {
476
+ deferredResolve([
477
+ {
478
+ asset: {
479
+ id: "value-1",
480
+ type: "text",
481
+ value: "1st value in the multinode",
482
+ },
483
+ },
484
+ {
485
+ id: "another-async",
486
+ async: true,
487
+ },
488
+ ]);
489
+ }
490
+
491
+ await waitFor(() => {
492
+ expect(updateNumber).toBe(2);
493
+ });
494
+
495
+ view = (player.getState() as InProgressState).controllers.view.currentView
496
+ ?.lastUpdate;
497
+
498
+ expect(view?.actions[0].asset.type).toBe("action");
499
+ expect(view?.actions[1].asset.type).toBe("text");
500
+ expect(view?.actions[2]).toBeUndefined();
501
+
502
+ if (deferredResolve) {
503
+ deferredResolve({
504
+ asset: {
505
+ id: "value-2",
506
+ type: "text",
507
+ value: "2nd value in the multinode",
508
+ },
509
+ });
510
+ }
511
+
512
+ await waitFor(() => {
513
+ expect(updateNumber).toBe(3);
514
+ });
515
+
516
+ view = (player.getState() as InProgressState).controllers.view.currentView
517
+ ?.lastUpdate;
518
+
519
+ expect(view?.actions[0].asset.type).toBe("action");
520
+ expect(view?.actions[1].asset.type).toBe("text");
521
+ expect(view?.actions[2].asset.type).toBe("text");
522
+ });
523
+
524
+ test("should call onAsyncNode hook when async node is encountered", async () => {
525
+ const plugin = new AsyncNodePlugin({
526
+ plugins: [new AsyncNodePluginPlugin()],
527
+ });
528
+
529
+ let localNode: Node.Async;
530
+ plugin.hooks.onAsyncNode.tap("test", async (node: Node.Async) => {
531
+ if (node !== null) {
532
+ // assigns node value to a local variable
533
+ localNode = node;
534
+ }
535
+
536
+ return new Promise((resolve) => {
537
+ resolve("Promise resolved");
538
+ });
539
+ });
540
+
541
+ const player = new Player({ plugins: [plugin] });
542
+
543
+ player.start(basicFRFWithActions as any);
544
+
545
+ await waitFor(() => {
546
+ expect(localNode.id).toStrictEqual("nodeId");
547
+ expect(localNode.type).toStrictEqual("async");
548
+ });
549
+ });