From 643f8eb958491d1ef1a3c30424e9a9f336ba79f2 Mon Sep 17 00:00:00 2001 From: sudacode Date: Thu, 26 Feb 2026 12:54:50 -0800 Subject: [PATCH] test(main): add overlay modal runtime fallback open-state regression coverage --- src/main/overlay-runtime.test.ts | 84 +++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/src/main/overlay-runtime.test.ts b/src/main/overlay-runtime.test.ts index f89b26e..43b775f 100644 --- a/src/main/overlay-runtime.test.ts +++ b/src/main/overlay-runtime.test.ts @@ -12,6 +12,7 @@ type MockWindow = { hideCount: number; sent: unknown[][]; loading: boolean; + url: string; loadCallbacks: Array<() => void>; }; @@ -19,7 +20,8 @@ function createMockWindow(): MockWindow & { isDestroyed: () => boolean; isVisible: () => boolean; isFocused: () => boolean; - setIgnoreMouseEvents: (ignore: boolean) => void; + getURL: () => string; + setIgnoreMouseEvents: (ignore: boolean, options?: { forward?: boolean }) => void; getShowCount: () => number; getHideCount: () => number; show: () => void; @@ -28,6 +30,7 @@ function createMockWindow(): MockWindow & { webContents: { focused: boolean; isLoading: () => boolean; + getURL: () => string; send: (channel: string, payload?: unknown) => void; isFocused: () => boolean; once: (event: 'did-finish-load', cb: () => void) => void; @@ -44,6 +47,7 @@ function createMockWindow(): MockWindow & { hideCount: 0, sent: [], loading: false, + url: 'file:///overlay/index.html?layer=modal', loadCallbacks: [], }; return { @@ -51,7 +55,8 @@ function createMockWindow(): MockWindow & { isDestroyed: () => state.destroyed, isVisible: () => state.visible, isFocused: () => state.focused, - setIgnoreMouseEvents: (ignore: boolean) => { + getURL: () => state.url, + setIgnoreMouseEvents: (ignore: boolean, _options?: { forward?: boolean }) => { state.ignoreMouseEvents = ignore; }, getShowCount: () => state.showCount, @@ -69,6 +74,7 @@ function createMockWindow(): MockWindow & { }, webContents: { isLoading: () => state.loading, + getURL: () => state.url, send: (channel, payload) => { if (payload === undefined) { state.sent.push([channel]); @@ -93,7 +99,6 @@ test('sendToActiveOverlayWindow targets modal window with full geometry and trac const calls: string[] = []; const runtime = createOverlayModalRuntimeService({ getMainWindow: () => null, - getInvisibleWindow: () => null, getModalWindow: () => window as never, createModalWindow: () => { calls.push('create-modal-window'); @@ -111,6 +116,8 @@ test('sendToActiveOverlayWindow targets modal window with full geometry and trac assert.equal(sent, true); assert.equal(runtime.getRestoreVisibleOverlayOnModalClose().has('runtime-options'), true); assert.deepEqual(calls, ['bounds:10,20,300,200']); + assert.equal(window.getShowCount(), 0); + runtime.notifyOverlayModalOpened('runtime-options'); assert.equal(window.getShowCount(), 1); assert.equal(window.isFocused(), true); assert.deepEqual(window.sent, [['runtime-options:open']]); @@ -121,7 +128,6 @@ test('sendToActiveOverlayWindow creates modal window lazily when absent', () => let modalWindow: ReturnType | null = null; const runtime = createOverlayModalRuntimeService({ getMainWindow: () => null, - getInvisibleWindow: () => null, getModalWindow: () => modalWindow as never, createModalWindow: () => { modalWindow = window; @@ -135,14 +141,47 @@ test('sendToActiveOverlayWindow creates modal window lazily when absent', () => runtime.sendToActiveOverlayWindow('jimaku:open', undefined, { restoreOnModalClose: 'jimaku' }), true, ); + assert.equal(window.getShowCount(), 0); + runtime.notifyOverlayModalOpened('jimaku'); + assert.equal(window.getShowCount(), 1); assert.deepEqual(window.sent, [['jimaku:open']]); }); +test('sendToActiveOverlayWindow waits for blank modal URL before sending open command', () => { + const window = createMockWindow(); + window.url = ''; + window.loading = true; + const runtime = createOverlayModalRuntimeService({ + getMainWindow: () => null, + getModalWindow: () => window as never, + createModalWindow: () => { + throw new Error('modal window should not be created when already present'); + }, + getModalGeometry: () => ({ x: 10, y: 20, width: 300, height: 200 }), + setModalWindowBounds: () => {}, + }); + + const sent = runtime.sendToActiveOverlayWindow('runtime-options:open', undefined, { + restoreOnModalClose: 'runtime-options', + }); + + assert.equal(sent, true); + assert.deepEqual(window.sent, []); + + assert.equal(window.loadCallbacks.length, 1); + window.loading = false; + window.url = 'file:///overlay/index.html?layer=modal'; + window.loadCallbacks[0]!(); + + runtime.notifyOverlayModalOpened('runtime-options'); + assert.deepEqual(window.sent, [['runtime-options:open']]); + assert.equal(window.getShowCount(), 1); +}); + test('handleOverlayModalClosed hides modal window only after all pending modals close', () => { const window = createMockWindow(); const runtime = createOverlayModalRuntimeService({ getMainWindow: () => null, - getInvisibleWindow: () => null, getModalWindow: () => window as never, createModalWindow: () => window as never, getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }), @@ -169,7 +208,6 @@ test('modal runtime notifies callers when modal input state becomes active/inact const runtime = createOverlayModalRuntimeService( { getMainWindow: () => null, - getInvisibleWindow: () => null, getModalWindow: () => window as never, createModalWindow: () => window as never, getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }), @@ -201,7 +239,6 @@ test('handleOverlayModalClosed hides modal window for single kiku modal', () => const window = createMockWindow(); const runtime = createOverlayModalRuntimeService({ getMainWindow: () => null, - getInvisibleWindow: () => null, getModalWindow: () => window as never, createModalWindow: () => window as never, getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }), @@ -216,3 +253,36 @@ test('handleOverlayModalClosed hides modal window for single kiku modal', () => assert.equal(window.getHideCount(), 1); assert.equal(runtime.getRestoreVisibleOverlayOnModalClose().size, 0); }); + +test('modal fallback reveal keeps mouse events ignored until modal confirms open', async () => { + const window = createMockWindow(); + const runtime = createOverlayModalRuntimeService({ + getMainWindow: () => null, + getModalWindow: () => window as never, + createModalWindow: () => { + throw new Error('modal window should not be created when already present'); + }, + getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }), + setModalWindowBounds: () => {}, + }); + + window.loading = true; + window.url = ''; + + const sent = runtime.sendToActiveOverlayWindow('jimaku:open', undefined, { + restoreOnModalClose: 'jimaku', + }); + + assert.equal(sent, true); + assert.equal(window.ignoreMouseEvents, false); + + await new Promise((resolve) => { + setTimeout(resolve, 260); + }); + + assert.equal(window.getShowCount(), 1); + assert.equal(window.ignoreMouseEvents, true); + + runtime.notifyOverlayModalOpened('jimaku'); + assert.equal(window.ignoreMouseEvents, false); +});