mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-11-13 16:46:08 -08:00
# Conflicts: # launcher/main.test.ts # src/cli/help.test.ts # src/core/services/__tests__/stats-server.test.ts # src/core/services/cli-command.test.ts # src/core/services/tokenizer/yomitan-parser-runtime.test.ts # src/main/overlay-runtime.test.ts # src/main/runtime/character-dictionary-auto-sync.test.ts # src/main/runtime/cli-command-context-deps.test.ts # src/main/runtime/cli-command-context-factory.test.ts # src/main/runtime/cli-command-context-main-deps.test.ts # src/main/runtime/composers/cli-startup-composer.test.ts # src/main/runtime/stats-server-runtime.test.ts # src/main/runtime/tray-main-actions.test.ts # src/main/runtime/tray-main-deps.test.ts # src/main/runtime/tray-runtime-handlers.test.ts # src/main/runtime/tray-runtime.test.ts # src/main/runtime/yomitan-anki-server-sync.test.ts # src/preload.ts # src/renderer/handlers/mouse.test.ts # src/renderer/yomitan-popup.test.ts
196 lines
5.8 KiB
TypeScript
196 lines
5.8 KiB
TypeScript
import type { WindowGeometry } from '../types';
|
|
import {
|
|
OVERLAY_WINDOW_CONTENT_READY_FLAG,
|
|
OVERLAY_WINDOW_DOCUMENT_LOADED_FLAG,
|
|
} from '../core/services/overlay-window-flags';
|
|
import {
|
|
createOverlayModalRuntimeService,
|
|
type OverlayModalRuntimeOptions,
|
|
type OverlayWindowResolver,
|
|
} from './overlay-runtime';
|
|
|
|
export const MODAL_URL = 'file:///overlay/index.html?layer=modal';
|
|
export const MODAL_GEOMETRY: WindowGeometry = { x: 0, y: 0, width: 400, height: 300 };
|
|
|
|
export interface FakeOverlayWindowInit {
|
|
// Prefix for show/hide entries pushed to `log`, e.g. `modal:show`.
|
|
name?: string;
|
|
log?: string[];
|
|
visible?: boolean;
|
|
focused?: boolean;
|
|
webContentsFocused?: boolean;
|
|
ignoreMouseEvents?: boolean;
|
|
loading?: boolean;
|
|
url?: string;
|
|
contentReady?: boolean;
|
|
documentLoaded?: boolean;
|
|
}
|
|
|
|
// A still-loading window whose document has not been committed yet.
|
|
export const LOADING_BLANK_WINDOW: FakeOverlayWindowInit = {
|
|
loading: true,
|
|
url: '',
|
|
contentReady: false,
|
|
};
|
|
|
|
/**
|
|
* BrowserWindow stand-in covering the surface overlay-runtime touches. Defaults to a hidden,
|
|
* fully loaded, content-ready modal window.
|
|
*/
|
|
export class FakeOverlayWindow {
|
|
destroyed = false;
|
|
visible = false;
|
|
focused = false;
|
|
webContentsFocused = false;
|
|
ignoreMouseEvents = false;
|
|
forwardedIgnoreMouseEvents = false;
|
|
loading = false;
|
|
url = MODAL_URL;
|
|
alwaysOnTopCalls: string[] = [];
|
|
showCount = 0;
|
|
hideCount = 0;
|
|
sent: unknown[][] = [];
|
|
[OVERLAY_WINDOW_CONTENT_READY_FLAG] = true;
|
|
[OVERLAY_WINDOW_DOCUMENT_LOADED_FLAG] = true;
|
|
|
|
private readonly name: string;
|
|
private readonly log: string[] | undefined;
|
|
private loadListeners: Array<() => void> = [];
|
|
private stopLoadingListeners: Array<() => void> = [];
|
|
private readyToShowListeners: Array<() => void> = [];
|
|
|
|
readonly webContents = {
|
|
isLoading: (): boolean => this.loading,
|
|
getURL: (): string => this.url,
|
|
send: (channel: string, ...payload: unknown[]): void => {
|
|
this.sent.push([channel, ...payload]);
|
|
},
|
|
isFocused: (): boolean => this.webContentsFocused,
|
|
focus: (): void => {
|
|
this.webContentsFocused = true;
|
|
},
|
|
once: (event: 'did-finish-load' | 'did-stop-loading', listener: () => void): void => {
|
|
(event === 'did-stop-loading' ? this.stopLoadingListeners : this.loadListeners).push(
|
|
listener,
|
|
);
|
|
},
|
|
};
|
|
|
|
constructor(init: FakeOverlayWindowInit = {}) {
|
|
const { name = 'window', log, contentReady = true, documentLoaded = true, ...fields } = init;
|
|
this.name = name;
|
|
this.log = log;
|
|
Object.assign(this, fields);
|
|
this[OVERLAY_WINDOW_CONTENT_READY_FLAG] = contentReady;
|
|
this[OVERLAY_WINDOW_DOCUMENT_LOADED_FLAG] = documentLoaded;
|
|
}
|
|
|
|
isDestroyed(): boolean {
|
|
return this.destroyed;
|
|
}
|
|
|
|
isVisible(): boolean {
|
|
return this.visible;
|
|
}
|
|
|
|
isFocused(): boolean {
|
|
return this.focused;
|
|
}
|
|
|
|
setIgnoreMouseEvents(ignore: boolean, options?: { forward?: boolean }): void {
|
|
this.ignoreMouseEvents = ignore;
|
|
this.forwardedIgnoreMouseEvents = options?.forward === true;
|
|
}
|
|
|
|
setAlwaysOnTop(flag: boolean, level?: string, relativeLevel?: number): void {
|
|
this.alwaysOnTopCalls.push(`top:${flag}:${level ?? ''}:${relativeLevel ?? ''}`);
|
|
}
|
|
|
|
moveTop(): void {}
|
|
|
|
show(): void {
|
|
this.log?.push(`${this.name}:show`);
|
|
this.visible = true;
|
|
this.showCount += 1;
|
|
}
|
|
|
|
showInactive(): void {
|
|
this.log?.push(`${this.name}:show-inactive`);
|
|
this.visible = true;
|
|
this.showCount += 1;
|
|
}
|
|
|
|
hide(): void {
|
|
this.log?.push(`${this.name}:hide`);
|
|
this.visible = false;
|
|
this.hideCount += 1;
|
|
}
|
|
|
|
destroy(): void {
|
|
this.destroyed = true;
|
|
this.visible = false;
|
|
}
|
|
|
|
focus(): void {
|
|
this.focused = true;
|
|
}
|
|
|
|
once(_event: 'ready-to-show', listener: () => void): void {
|
|
this.readyToShowListeners.push(listener);
|
|
}
|
|
|
|
// Commits the modal document like Electron does: isLoading() stays true inside
|
|
// did-finish-load and only settles before did-stop-loading.
|
|
finishLoad(): void {
|
|
this.url = MODAL_URL;
|
|
this[OVERLAY_WINDOW_DOCUMENT_LOADED_FLAG] = true;
|
|
for (const listener of this.loadListeners.splice(0)) listener();
|
|
this.loading = false;
|
|
for (const listener of this.stopLoadingListeners.splice(0)) listener();
|
|
}
|
|
|
|
// Marks renderer content ready and fires the queued ready-to-show listeners.
|
|
emitReadyToShow(): void {
|
|
this[OVERLAY_WINDOW_CONTENT_READY_FLAG] = true;
|
|
for (const listener of this.readyToShowListeners.splice(0)) listener();
|
|
}
|
|
}
|
|
|
|
export interface OverlayRuntimeHarnessInit {
|
|
main?: FakeOverlayWindow | null;
|
|
modal?: FakeOverlayWindow | null;
|
|
// Called by the runtime's createModalWindow; its result becomes the current modal window.
|
|
createModal?: () => FakeOverlayWindow | null;
|
|
// Defaults to linux so behavior does not depend on the host running the tests.
|
|
platform?: NodeJS.Platform;
|
|
options?: Omit<OverlayModalRuntimeOptions, 'platform'>;
|
|
setModalWindowBounds?: OverlayWindowResolver['setModalWindowBounds'];
|
|
}
|
|
|
|
/** Builds the modal runtime over fake windows and records resolver traffic. */
|
|
export function createHarness(init: OverlayRuntimeHarnessInit = {}) {
|
|
const harness = {
|
|
modal: init.modal ?? null,
|
|
createCalls: 0,
|
|
boundsCalls: [] as WindowGeometry[],
|
|
};
|
|
const runtime = createOverlayModalRuntimeService(
|
|
{
|
|
getMainWindow: () => (init.main ?? null) as never,
|
|
getModalWindow: () => harness.modal as never,
|
|
createModalWindow: () => {
|
|
harness.createCalls += 1;
|
|
harness.modal = init.createModal?.() ?? null;
|
|
return harness.modal as never;
|
|
},
|
|
getModalGeometry: () => MODAL_GEOMETRY,
|
|
setModalWindowBounds: (geometry) => {
|
|
harness.boundsCalls.push(geometry);
|
|
return init.setModalWindowBounds?.(geometry);
|
|
},
|
|
},
|
|
{ ...init.options, platform: init.platform ?? 'linux' },
|
|
);
|
|
return Object.assign(harness, { runtime });
|
|
}
|