| 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 { syncMainWindowMaximised, useWindowsMaximisedSync } from "../app-runtime/useNativeWindowController"; |
| 6 | import { useWindowChromeStore } from "../store/windowChrome"; |
| 7 | import { installDesktopHostStub } from "./desktopHostStub"; |
| 8 | |
| 9 | const dom = new JSDOM("<div id='root'></div>", { pretendToBeVisual: true }); |
| 10 | Object.assign(globalThis, { window: dom.window, document: dom.window.document, IS_REACT_ACT_ENVIRONMENT: true }); |
| 11 | globalThis.Event = dom.window.Event; |
| 12 | const root = createRoot(document.getElementById("root")!); |
| 13 | |
| 14 | function deferred<T>() { |
| 15 | let resolve!: (value: T) => void; |
| 16 | const promise = new Promise<T>((yes) => { resolve = yes; }); |
| 17 | return { promise, resolve }; |
| 18 | } |
| 19 | |
| 20 | const bridgeCalls: string[] = []; |
| 21 | let maximisedValue = false; |
| 22 | let maximisedGate: ReturnType<typeof deferred<boolean>> | null = null; |
| 23 | installDesktopHostStub({}, { |
| 24 | window: { |
| 25 | isMaximised: async () => { |
| 26 | bridgeCalls.push("query"); |
| 27 | if (maximisedGate) return maximisedGate.promise; |
| 28 | return maximisedValue; |
| 29 | }, |
| 30 | }, |
| 31 | }); |
| 32 | |
| 33 | function Probe({ enabled }: { enabled: boolean }) { |
| 34 | useWindowsMaximisedSync(enabled); |
| 35 | return null; |
| 36 | } |
| 37 | |
| 38 | const maximised = () => useWindowChromeStore.getState().mainWindowMaximised; |
| 39 | |
| 40 | try { |
| 41 | syncMainWindowMaximised(); |
| 42 | assert.equal(bridgeCalls.length, 0, "sync before any lifecycle is a no-op"); |
| 43 | |
| 44 | maximisedValue = true; |
| 45 | await act(async () => root.render(<Probe enabled={true} />)); |
| 46 | assert.equal(maximised(), true, "the enabling lifecycle syncs the native flag into the store"); |
| 47 | assert.deepEqual(bridgeCalls, ["query"], "the initial sync queries the native host once"); |
| 48 | |
| 49 | maximisedValue = false; |
| 50 | await act(async () => { window.dispatchEvent(new window.Event("resize")); }); |
| 51 | assert.equal(maximised(), false, "a resize event re-syncs the flag"); |
| 52 | assert.equal(bridgeCalls.length, 2, "listener sync queries the bridge again"); |
| 53 | |
| 54 | maximisedValue = true; |
| 55 | await act(async () => { window.dispatchEvent(new window.Event("focus")); }); |
| 56 | assert.equal(maximised(), true, "a focus event re-syncs the flag"); |
| 57 | |
| 58 | const supersededGate = deferred<boolean>(); |
| 59 | maximisedGate = supersededGate; |
| 60 | await act(async () => { syncMainWindowMaximised(); }); |
| 61 | maximisedGate = null; |
| 62 | maximisedValue = false; |
| 63 | await act(async () => { syncMainWindowMaximised(); }); |
| 64 | assert.equal(maximised(), false, "the newer sync lands first"); |
| 65 | await act(async () => { supersededGate.resolve(true); await supersededGate.promise; }); |
| 66 | assert.equal(maximised(), false, "an out-of-order resolution from a superseded sync is discarded"); |
| 67 | |
| 68 | await act(async () => root.render(<Probe enabled={false} />)); |
| 69 | assert.equal(maximised(), false, "disabling the lifecycle resets the flag"); |
| 70 | bridgeCalls.length = 0; |
| 71 | syncMainWindowMaximised(); |
| 72 | assert.equal(bridgeCalls.length, 0, "event-handler sync stays gated while disabled"); |
| 73 | |
| 74 | await act(async () => root.unmount()); |
| 75 | bridgeCalls.length = 0; |
| 76 | syncMainWindowMaximised(); |
| 77 | assert.equal(bridgeCalls.length, 0, "unmounting the host disables the sync gate"); |
| 78 | |
| 79 | console.log("windows maximised sync: lifecycle gating, listener sync, generation fence and store ownership passed"); |
| 80 | } finally { dom.window.close(); } |
| 81 |