From f92c255e4571d54f0bd338fef89cf7f34b6b03d3 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Thu, 2 Jul 2026 22:53:51 -0700 Subject: [PATCH] FN-7457: stabilize plugin runner lifecycle hook tests Replace real timer waits with a bounded microtask flush for plugin runner lifecycle hook tests. - Add a shared flushMicrotasks helper for fire-and-forget hook assertions. - Update task lifecycle hook tests to avoid setTimeout-based settling delays. - Document the test-settling requirement with an FNXC comment. Files changed: .../engine/src/__tests__/plugin-runner.test.ts | 23 ++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) Fusion-Task-Id: FN-7457 Fusion-Task-Lineage: 8a4eb36c-4e36-4c97-a88d-d76b8f410dc8 Co-authored-by: Fusion (runfusion.ai) --- .../src/__tests__/plugin-runner.test.ts | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/engine/src/__tests__/plugin-runner.test.ts b/packages/engine/src/__tests__/plugin-runner.test.ts index f8913404c2..0f11de71a3 100644 --- a/packages/engine/src/__tests__/plugin-runner.test.ts +++ b/packages/engine/src/__tests__/plugin-runner.test.ts @@ -1428,6 +1428,17 @@ describe("PluginRunner", () => { }); describe("task lifecycle hooks", () => { + /* + FNXC:PluginRunnerTests 2026-07-02-17:20: + PluginRunner lifecycle handlers intentionally fire-and-forget their hook dispatch, so tests must wait for the mocked invokeHook promise chain to settle before asserting. + Use a bounded microtask flush instead of real wall-clock sleeps because FN-5048 forbids deterministic test settling through setTimeout delays when no production timer behavior is under test. + */ + const flushMicrotasks = async (turns = 4): Promise => { + for (let turn = 0; turn < turns; turn += 1) { + await Promise.resolve(); + } + }; + it("should invoke onTaskCreated when task:created event fires", async () => { mockPluginLoader.invokeHook = vi.fn(); await pluginRunner.init(); @@ -1443,8 +1454,7 @@ describe("PluginRunner", () => { createdHandler(mockTask); } - // Give async handler time to execute - await new Promise(resolve => setTimeout(resolve, 10)); + await flushMicrotasks(); expect(mockPluginLoader.invokeHook).toHaveBeenCalledWith( "onTaskCreated", @@ -1467,8 +1477,7 @@ describe("PluginRunner", () => { movedHandler({ task: mockTask, from: "todo", to: "in-progress" }); } - // Give async handler time to execute - await new Promise(resolve => setTimeout(resolve, 10)); + await flushMicrotasks(); expect(mockPluginLoader.invokeHook).toHaveBeenCalledWith( "onTaskMoved", @@ -1505,8 +1514,7 @@ describe("PluginRunner", () => { movedHandler({ task: mockTask, from: "in-progress", to: "done" }); } - // Give async handler time to execute - await new Promise(resolve => setTimeout(resolve, 50)); + await flushMicrotasks(); expect(mockPluginLoader.invokeHook).toHaveBeenCalledWith( "onTaskCompleted", @@ -1538,8 +1546,7 @@ describe("PluginRunner", () => { movedHandler({ task: mockTask, from: "todo", to: "in-progress" }); } - // Give async handler time to execute - await new Promise(resolve => setTimeout(resolve, 50)); + await flushMicrotasks(); expect(mockPluginLoader.invokeHook).not.toHaveBeenCalledWith( "onTaskCompleted",