diff --git a/plugins/async-node/core/src/index.test.ts b/plugins/async-node/core/src/index.test.ts index c93af6449..58d8e9c78 100644 --- a/plugins/async-node/core/src/index.test.ts +++ b/plugins/async-node/core/src/index.test.ts @@ -1,5 +1,5 @@ import { expect, test } from "vitest"; -import type { Node, InProgressState } from "@player-ui/player"; +import { Node, InProgressState, ViewInstance } from "@player-ui/player"; import { Player } from "@player-ui/player"; import { waitFor } from "@testing-library/react"; import { AsyncNodePlugin, AsyncNodePluginPlugin } from "./index"; @@ -18,7 +18,7 @@ const basicFRFWithActions = { }, }, { - id: "uhh", + id: "nodeId", async: "true", }, ], @@ -37,6 +37,83 @@ const basicFRFWithActions = { }, }; +const asyncNodeTest = async (resolvedValue: any) => { + const plugin = new AsyncNodePlugin({ + plugins: [new AsyncNodePluginPlugin()], + }); + + let deferredResolve: ((value: any) => void) | undefined; + + plugin.hooks.onAsyncNode.tap("test", async (node: Node.Node) => { + return new Promise((resolve) => { + deferredResolve = resolve; // Promise would be resolved only once + }); + }); + + let updateNumber = 0; + + const player = new Player({ plugins: [plugin] }); + + let viewInstance: ViewInstance | undefined; + + player.hooks.viewController.tap("async-node-test", (vc) => { + vc.hooks.view.tap("async-node-test", (view) => { + viewInstance = view; + view.hooks.onUpdate.tap("async-node-test", () => { + updateNumber++; + }); + }); + }); + + player.start(basicFRFWithActions as any); + + let view = (player.getState() as InProgressState).controllers.view.currentView + ?.lastUpdate; + + expect(view).toBeDefined(); + expect(view?.actions[0].asset.type).toBe("action"); + expect(view?.actions[1]).toBeUndefined(); + + await waitFor(() => { + expect(deferredResolve).toBeDefined(); + }); + + // Consumer responds with null/undefined + if (deferredResolve) { + deferredResolve(resolvedValue); + } + + await waitFor(() => { + expect(updateNumber).toBe(2); + }); + + view = (player.getState() as InProgressState).controllers.view.currentView + ?.lastUpdate; + + expect(view?.actions[0].asset.type).toBe("action"); + expect(view?.actions.length).toBe(1); + + viewInstance.update(); + + await waitFor(() => { + expect(updateNumber).toBe(3); + }); + + view = (player.getState() as InProgressState).controllers.view.currentView + ?.lastUpdate; + + expect(view?.actions[0].asset.type).toBe("action"); + expect(view?.actions.length).toBe(1); +}; + +test("should return current node view when the resolved node is null", async () => { + await asyncNodeTest(null); +}); + +test("should return current node view when the resolved node is undefined", async () => { + await asyncNodeTest(undefined); +}); + test("replaces async nodes with provided node", async () => { const plugin = new AsyncNodePlugin({ plugins: [new AsyncNodePluginPlugin()], diff --git a/plugins/async-node/core/src/index.ts b/plugins/async-node/core/src/index.ts index 3a7cbd24c..dfd4d5734 100644 --- a/plugins/async-node/core/src/index.ts +++ b/plugins/async-node/core/src/index.ts @@ -66,7 +66,7 @@ export class AsyncNodePluginPlugin implements AsyncNodeViewPlugin { name = "AsyncNode"; - private resolvedMapping = new Map(); + private resolvedMapping = new Map(); private currentView: ViewInstance | undefined; @@ -167,10 +167,8 @@ export class AsyncNodePluginPlugin implements AsyncNodeViewPlugin { ? options.parseNode(result) : undefined; - if (parsedNode) { - this.resolvedMapping.set(node.id, parsedNode); - view.updateAsync(); - } + this.resolvedMapping.set(node.id, parsedNode ? parsedNode : node); + view.updateAsync(); }); return node;