| 1 | import assert from "node:assert/strict"; |
| 2 | import React, { act } from "react"; |
| 3 | import { createRoot } from "react-dom/client"; |
| 4 | import { JSDOM } from "jsdom"; |
| 5 | import { useAutomationNavigation } from "../app-runtime/useAutomationNavigation"; |
| 6 | import { useAppNavigationStore as navigation } from "../store/appNavigation"; |
| 7 | import type { DesktopNavigationIntent } from "../app-runtime/desktopNavigationOwner"; |
| 8 | |
| 9 | const dom = new JSDOM("<div id='root'></div>"); |
| 10 | Object.assign(globalThis, { window: dom.window, document: dom.window.document, IS_REACT_ACT_ENVIRONMENT: true }); |
| 11 | const root = createRoot(document.getElementById("root")!); |
| 12 | let seq = 0; |
| 13 | const queued: { request: DesktopNavigationIntent; intent: number; resolve(): void }[] = []; |
| 14 | let commands!: ReturnType<typeof useAutomationNavigation>; |
| 15 | function Probe() { |
| 16 | commands = useAutomationNavigation({ noteIntent: () => ++seq, |
| 17 | enqueue: (request, intent) => new Promise<void>(resolve => queued.push({ request, intent, resolve })) }); |
| 18 | return null; |
| 19 | } |
| 20 | try { |
| 21 | await act(async () => root.render(<Probe />)); |
| 22 | navigation.getState().openPage({ kind: "automation" }); |
| 23 | const a = commands.openAutomationTopic("project", "fixture", "A"); |
| 24 | assert.deepEqual(queued[0].request, { kind: "topic", scope: "project", workspaceRoot: "fixture", topicId: "A" }); |
| 25 | assert.equal(navigation.getState().page.kind, "automation", "keep the management page until the target is accepted"); |
| 26 | commands.topicAccepted(queued[0].intent); |
| 27 | assert.equal(navigation.getState().page.kind, "workspace"); |
| 28 | assert.equal(navigation.getState().automationReturn, true); |
| 29 | navigation.getState().openPage({ kind: "automation" }); |
| 30 | const b = commands.openAutomationTopic("project", "fixture", "B"); |
| 31 | queued[0].resolve(); await a; |
| 32 | commands.topicAccepted(queued[1].intent); |
| 33 | assert.equal(navigation.getState().page.kind, "workspace", "old finally cannot retire the newer link"); |
| 34 | queued[1].resolve(); await b; |
| 35 | navigation.getState().openPage({ kind: "automation" }); |
| 36 | const c = commands.openAutomationTopic("project", "fixture", "C"); |
| 37 | const original = queued[2].intent; |
| 38 | navigation.getState().openPage({ kind: "settings", tab: "general" }); |
| 39 | navigation.getState().openPage({ kind: "automation" }); |
| 40 | commands.topicAccepted(original); |
| 41 | assert.equal(navigation.getState().page.kind, "automation", "ABA page replacement cannot regain navigation rights"); |
| 42 | assert.ok(seq > original, "page replacement revokes the controller's navigation intent"); |
| 43 | queued[2].resolve(); await c; |
| 44 | const d = commands.openAutomationTopic("project", "fixture", "D"); |
| 45 | const last = queued[3].intent; |
| 46 | queued[3].resolve(); await d; |
| 47 | commands.topicAccepted(last); |
| 48 | assert.equal(navigation.getState().page.kind, "automation", "an unaccepted terminal request leaves no live link"); |
| 49 | await act(async () => root.unmount()); |
| 50 | const before = seq; |
| 51 | commands.openAutomationTopic("project", "fixture", "disposed"); |
| 52 | navigation.getState().returnToWorkspace(); |
| 53 | assert.equal(seq, before, "unmount releases both command and page subscription"); |
| 54 | console.log("automation navigation: accepted-target return, page ABA, exact finally and disposal passed"); |
| 55 | } finally { dom.window.close(); } |
| 56 |