mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-27 16:49:51 -07:00
716 lines
22 KiB
TypeScript
716 lines
22 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
initializeOverlayAnkiIntegration,
|
|
initializeOverlayRuntime,
|
|
startOverlayWindowTracker,
|
|
} from './overlay-runtime-init';
|
|
|
|
test('startOverlayWindowTracker starts tracker for the current mpv socket', () => {
|
|
const calls: string[] = [];
|
|
const tracker = {
|
|
onGeometryChange: null as ((...args: unknown[]) => void) | null,
|
|
onWindowFound: null as ((...args: unknown[]) => void) | null,
|
|
onWindowLost: null as (() => void) | null,
|
|
onWindowFocusChange: null as ((focused: boolean) => void) | null,
|
|
isTargetWindowMinimized: () => false,
|
|
start: () => {
|
|
calls.push('start');
|
|
},
|
|
};
|
|
|
|
const result = startOverlayWindowTracker({
|
|
backendOverride: 'windows',
|
|
getMpvSocketPath: () => '\\\\.\\pipe\\subminer-socket',
|
|
createWindowTracker: (override, socketPath) => {
|
|
calls.push(`create:${override}:${socketPath}`);
|
|
return tracker as never;
|
|
},
|
|
setWindowTracker: (nextTracker) => {
|
|
calls.push(nextTracker === tracker ? 'set-tracker' : 'clear-tracker');
|
|
},
|
|
updateVisibleOverlayBounds: () => {
|
|
calls.push('bounds');
|
|
},
|
|
isVisibleOverlayVisible: () => true,
|
|
updateVisibleOverlayVisibility: () => {
|
|
calls.push('visibility');
|
|
},
|
|
refreshCurrentSubtitle: () => {
|
|
calls.push('refresh-subtitle');
|
|
},
|
|
getOverlayWindows: () => [],
|
|
syncOverlayShortcuts: () => {
|
|
calls.push('sync-shortcuts');
|
|
},
|
|
});
|
|
|
|
assert.equal(result, tracker);
|
|
tracker.onWindowFound?.({ x: 10, y: 20, width: 300, height: 200 });
|
|
tracker.onWindowFocusChange?.(true);
|
|
|
|
assert.deepEqual(calls, [
|
|
'create:windows:\\\\.\\pipe\\subminer-socket',
|
|
'set-tracker',
|
|
'start',
|
|
'bounds',
|
|
'visibility',
|
|
'refresh-subtitle',
|
|
'visibility',
|
|
'sync-shortcuts',
|
|
]);
|
|
});
|
|
|
|
test('initializeOverlayRuntime skips Anki integration when ankiConnect.enabled is false', () => {
|
|
let createdIntegrations = 0;
|
|
let startedIntegrations = 0;
|
|
let setIntegrationCalls = 0;
|
|
|
|
initializeOverlayRuntime({
|
|
backendOverride: null,
|
|
createMainWindow: () => {},
|
|
registerGlobalShortcuts: () => {},
|
|
updateVisibleOverlayBounds: () => {},
|
|
isVisibleOverlayVisible: () => false,
|
|
updateVisibleOverlayVisibility: () => {},
|
|
getOverlayWindows: () => [],
|
|
syncOverlayShortcuts: () => {},
|
|
setWindowTracker: () => {},
|
|
getMpvSocketPath: () => '/tmp/mpv.sock',
|
|
createWindowTracker: () => null,
|
|
getResolvedConfig: () => ({
|
|
ankiConnect: { enabled: false } as never,
|
|
}),
|
|
getSubtitleTimingTracker: () => ({}),
|
|
getMpvClient: () => ({
|
|
send: () => {},
|
|
}),
|
|
getRuntimeOptionsManager: () => ({
|
|
getEffectiveAnkiConnectConfig: (config) => config as never,
|
|
}),
|
|
createAnkiIntegration: () => {
|
|
createdIntegrations += 1;
|
|
return {
|
|
start: () => {
|
|
startedIntegrations += 1;
|
|
},
|
|
};
|
|
},
|
|
setAnkiIntegration: () => {
|
|
setIntegrationCalls += 1;
|
|
},
|
|
showDesktopNotification: () => {},
|
|
createFieldGroupingCallback: () => async () => ({
|
|
keepNoteId: 1,
|
|
deleteNoteId: 2,
|
|
deleteDuplicate: false,
|
|
cancelled: false,
|
|
}),
|
|
getKnownWordCacheStatePath: () => '/tmp/known-words-cache.json',
|
|
});
|
|
|
|
assert.equal(createdIntegrations, 0);
|
|
assert.equal(startedIntegrations, 0);
|
|
assert.equal(setIntegrationCalls, 0);
|
|
});
|
|
|
|
test('initializeOverlayRuntime starts Anki integration when ankiConnect.enabled is true', () => {
|
|
let createdIntegrations = 0;
|
|
let startedIntegrations = 0;
|
|
let setIntegrationCalls = 0;
|
|
|
|
initializeOverlayRuntime({
|
|
backendOverride: null,
|
|
createMainWindow: () => {},
|
|
registerGlobalShortcuts: () => {},
|
|
updateVisibleOverlayBounds: () => {},
|
|
isVisibleOverlayVisible: () => false,
|
|
updateVisibleOverlayVisibility: () => {},
|
|
getOverlayWindows: () => [],
|
|
syncOverlayShortcuts: () => {},
|
|
setWindowTracker: () => {},
|
|
getMpvSocketPath: () => '/tmp/mpv.sock',
|
|
createWindowTracker: () => null,
|
|
getResolvedConfig: () => ({
|
|
ankiConnect: { enabled: true } as never,
|
|
}),
|
|
getSubtitleTimingTracker: () => ({}),
|
|
getMpvClient: () => ({
|
|
send: () => {},
|
|
}),
|
|
getRuntimeOptionsManager: () => ({
|
|
getEffectiveAnkiConnectConfig: (config) => config as never,
|
|
}),
|
|
createAnkiIntegration: (args) => {
|
|
createdIntegrations += 1;
|
|
assert.equal(args.config.enabled, true);
|
|
return {
|
|
start: () => {
|
|
startedIntegrations += 1;
|
|
},
|
|
};
|
|
},
|
|
setAnkiIntegration: () => {
|
|
setIntegrationCalls += 1;
|
|
},
|
|
showDesktopNotification: () => {},
|
|
createFieldGroupingCallback: () => async () => ({
|
|
keepNoteId: 3,
|
|
deleteNoteId: 4,
|
|
deleteDuplicate: false,
|
|
cancelled: false,
|
|
}),
|
|
getKnownWordCacheStatePath: () => '/tmp/known-words-cache.json',
|
|
});
|
|
|
|
assert.equal(createdIntegrations, 1);
|
|
assert.equal(startedIntegrations, 1);
|
|
assert.equal(setIntegrationCalls, 1);
|
|
});
|
|
|
|
test('initializeOverlayAnkiIntegration can initialize Anki transport after overlay runtime already exists', () => {
|
|
let createdIntegrations = 0;
|
|
let startedIntegrations = 0;
|
|
let setIntegrationCalls = 0;
|
|
|
|
initializeOverlayAnkiIntegration({
|
|
getResolvedConfig: () => ({
|
|
ankiConnect: { enabled: true } as never,
|
|
}),
|
|
getSubtitleTimingTracker: () => ({}),
|
|
getMpvClient: () => ({
|
|
send: () => {},
|
|
}),
|
|
getRuntimeOptionsManager: () => ({
|
|
getEffectiveAnkiConnectConfig: (config) => config as never,
|
|
}),
|
|
createAnkiIntegration: (args) => {
|
|
createdIntegrations += 1;
|
|
assert.equal(args.config.enabled, true);
|
|
return {
|
|
start: () => {
|
|
startedIntegrations += 1;
|
|
},
|
|
};
|
|
},
|
|
setAnkiIntegration: () => {
|
|
setIntegrationCalls += 1;
|
|
},
|
|
showDesktopNotification: () => {},
|
|
createFieldGroupingCallback: () => async () => ({
|
|
keepNoteId: 11,
|
|
deleteNoteId: 12,
|
|
deleteDuplicate: false,
|
|
cancelled: false,
|
|
}),
|
|
getKnownWordCacheStatePath: () => '/tmp/known-words-cache.json',
|
|
});
|
|
|
|
assert.equal(createdIntegrations, 1);
|
|
assert.equal(startedIntegrations, 1);
|
|
assert.equal(setIntegrationCalls, 1);
|
|
});
|
|
|
|
test('initializeOverlayAnkiIntegration returns false when integration already exists', () => {
|
|
let createdIntegrations = 0;
|
|
let startedIntegrations = 0;
|
|
let setIntegrationCalls = 0;
|
|
|
|
const result = initializeOverlayAnkiIntegration({
|
|
getResolvedConfig: () => ({
|
|
ankiConnect: { enabled: true } as never,
|
|
}),
|
|
getSubtitleTimingTracker: () => ({}),
|
|
getMpvClient: () => ({
|
|
send: () => {},
|
|
}),
|
|
getRuntimeOptionsManager: () => ({
|
|
getEffectiveAnkiConnectConfig: (config) => config as never,
|
|
}),
|
|
getAnkiIntegration: () => ({}),
|
|
createAnkiIntegration: () => {
|
|
createdIntegrations += 1;
|
|
return {
|
|
start: () => {
|
|
startedIntegrations += 1;
|
|
},
|
|
};
|
|
},
|
|
setAnkiIntegration: () => {
|
|
setIntegrationCalls += 1;
|
|
},
|
|
showDesktopNotification: () => {},
|
|
createFieldGroupingCallback: () => async () => ({
|
|
keepNoteId: 11,
|
|
deleteNoteId: 12,
|
|
deleteDuplicate: false,
|
|
cancelled: false,
|
|
}),
|
|
getKnownWordCacheStatePath: () => '/tmp/known-words-cache.json',
|
|
});
|
|
|
|
assert.equal(result, false);
|
|
assert.equal(createdIntegrations, 0);
|
|
assert.equal(startedIntegrations, 0);
|
|
assert.equal(setIntegrationCalls, 0);
|
|
});
|
|
|
|
test('initializeOverlayAnkiIntegration returns false when ankiConnect is disabled', () => {
|
|
let createdIntegrations = 0;
|
|
let startedIntegrations = 0;
|
|
let setIntegrationCalls = 0;
|
|
|
|
const result = initializeOverlayAnkiIntegration({
|
|
getResolvedConfig: () => ({
|
|
ankiConnect: { enabled: false } as never,
|
|
}),
|
|
getSubtitleTimingTracker: () => ({}),
|
|
getMpvClient: () => ({
|
|
send: () => {},
|
|
}),
|
|
getRuntimeOptionsManager: () => ({
|
|
getEffectiveAnkiConnectConfig: (config) => config as never,
|
|
}),
|
|
createAnkiIntegration: () => {
|
|
createdIntegrations += 1;
|
|
return {
|
|
start: () => {
|
|
startedIntegrations += 1;
|
|
},
|
|
};
|
|
},
|
|
setAnkiIntegration: () => {
|
|
setIntegrationCalls += 1;
|
|
},
|
|
showDesktopNotification: () => {},
|
|
createFieldGroupingCallback: () => async () => ({
|
|
keepNoteId: 11,
|
|
deleteNoteId: 12,
|
|
deleteDuplicate: false,
|
|
cancelled: false,
|
|
}),
|
|
getKnownWordCacheStatePath: () => '/tmp/known-words-cache.json',
|
|
});
|
|
|
|
assert.equal(result, false);
|
|
assert.equal(createdIntegrations, 0);
|
|
assert.equal(startedIntegrations, 0);
|
|
assert.equal(setIntegrationCalls, 0);
|
|
});
|
|
|
|
test('initializeOverlayRuntime can skip starting Anki integration transport', () => {
|
|
let createdIntegrations = 0;
|
|
let startedIntegrations = 0;
|
|
let setIntegrationCalls = 0;
|
|
|
|
initializeOverlayRuntime({
|
|
backendOverride: null,
|
|
createMainWindow: () => {},
|
|
registerGlobalShortcuts: () => {},
|
|
updateVisibleOverlayBounds: () => {},
|
|
isVisibleOverlayVisible: () => false,
|
|
updateVisibleOverlayVisibility: () => {},
|
|
getOverlayWindows: () => [],
|
|
syncOverlayShortcuts: () => {},
|
|
setWindowTracker: () => {},
|
|
getMpvSocketPath: () => '/tmp/mpv.sock',
|
|
createWindowTracker: () => null,
|
|
getResolvedConfig: () => ({
|
|
ankiConnect: { enabled: true } as never,
|
|
}),
|
|
getSubtitleTimingTracker: () => ({}),
|
|
getMpvClient: () => ({
|
|
send: () => {},
|
|
}),
|
|
getRuntimeOptionsManager: () => ({
|
|
getEffectiveAnkiConnectConfig: (config) => config as never,
|
|
}),
|
|
createAnkiIntegration: () => {
|
|
createdIntegrations += 1;
|
|
return {
|
|
start: () => {
|
|
startedIntegrations += 1;
|
|
},
|
|
};
|
|
},
|
|
setAnkiIntegration: () => {
|
|
setIntegrationCalls += 1;
|
|
},
|
|
showDesktopNotification: () => {},
|
|
createFieldGroupingCallback: () => async () => ({
|
|
keepNoteId: 7,
|
|
deleteNoteId: 8,
|
|
deleteDuplicate: false,
|
|
cancelled: false,
|
|
}),
|
|
getKnownWordCacheStatePath: () => '/tmp/known-words-cache.json',
|
|
shouldStartAnkiIntegration: () => false,
|
|
});
|
|
|
|
assert.equal(createdIntegrations, 1);
|
|
assert.equal(startedIntegrations, 0);
|
|
assert.equal(setIntegrationCalls, 1);
|
|
});
|
|
|
|
test('initializeOverlayRuntime merges shared ai config with Anki overrides', () => {
|
|
initializeOverlayRuntime({
|
|
backendOverride: null,
|
|
createMainWindow: () => {},
|
|
registerGlobalShortcuts: () => {},
|
|
updateVisibleOverlayBounds: () => {},
|
|
isVisibleOverlayVisible: () => false,
|
|
updateVisibleOverlayVisibility: () => {},
|
|
getOverlayWindows: () => [],
|
|
syncOverlayShortcuts: () => {},
|
|
setWindowTracker: () => {},
|
|
getMpvSocketPath: () => '/tmp/mpv.sock',
|
|
createWindowTracker: () => null,
|
|
getResolvedConfig: () => ({
|
|
ankiConnect: {
|
|
enabled: true,
|
|
ai: {
|
|
enabled: true,
|
|
model: 'openrouter/anki-model',
|
|
systemPrompt: 'Translate mined sentence text.',
|
|
},
|
|
} as never,
|
|
ai: {
|
|
enabled: true,
|
|
apiKey: 'shared-key',
|
|
baseUrl: 'https://openrouter.ai/api',
|
|
model: 'openrouter/shared-model',
|
|
systemPrompt: 'Legacy shared prompt.',
|
|
requestTimeoutMs: 15000,
|
|
},
|
|
}),
|
|
getSubtitleTimingTracker: () => ({}),
|
|
getMpvClient: () => ({
|
|
send: () => {},
|
|
}),
|
|
getRuntimeOptionsManager: () => ({
|
|
getEffectiveAnkiConnectConfig: (config) => config as never,
|
|
}),
|
|
createAnkiIntegration: (args) => {
|
|
assert.equal(args.aiConfig.apiKey, 'shared-key');
|
|
assert.equal(args.aiConfig.baseUrl, 'https://openrouter.ai/api');
|
|
assert.equal(args.aiConfig.model, 'openrouter/anki-model');
|
|
assert.equal(args.aiConfig.systemPrompt, 'Translate mined sentence text.');
|
|
return {
|
|
start: () => {},
|
|
};
|
|
},
|
|
setAnkiIntegration: () => {},
|
|
showDesktopNotification: () => {},
|
|
createFieldGroupingCallback: () => async () => ({
|
|
keepNoteId: 5,
|
|
deleteNoteId: 6,
|
|
deleteDuplicate: false,
|
|
cancelled: false,
|
|
}),
|
|
getKnownWordCacheStatePath: () => '/tmp/known-words-cache.json',
|
|
});
|
|
});
|
|
|
|
test('initializeOverlayRuntime re-syncs overlay shortcuts when tracker focus changes', () => {
|
|
let syncCalls = 0;
|
|
const tracker = {
|
|
onGeometryChange: null as ((...args: unknown[]) => void) | null,
|
|
onWindowFound: null as ((...args: unknown[]) => void) | null,
|
|
onWindowLost: null as (() => void) | null,
|
|
onWindowFocusChange: null as ((focused: boolean) => void) | null,
|
|
start: () => {},
|
|
};
|
|
|
|
initializeOverlayRuntime({
|
|
backendOverride: null,
|
|
createMainWindow: () => {},
|
|
registerGlobalShortcuts: () => {},
|
|
updateVisibleOverlayBounds: () => {},
|
|
isVisibleOverlayVisible: () => false,
|
|
updateVisibleOverlayVisibility: () => {},
|
|
getOverlayWindows: () => [],
|
|
syncOverlayShortcuts: () => {
|
|
syncCalls += 1;
|
|
},
|
|
setWindowTracker: () => {},
|
|
getMpvSocketPath: () => '/tmp/mpv.sock',
|
|
createWindowTracker: () => tracker as never,
|
|
getResolvedConfig: () => ({
|
|
ankiConnect: { enabled: false } as never,
|
|
}),
|
|
getSubtitleTimingTracker: () => null,
|
|
getMpvClient: () => null,
|
|
getRuntimeOptionsManager: () => null,
|
|
setAnkiIntegration: () => {},
|
|
showDesktopNotification: () => {},
|
|
createFieldGroupingCallback: () => async () => ({
|
|
keepNoteId: 1,
|
|
deleteNoteId: 2,
|
|
deleteDuplicate: false,
|
|
cancelled: false,
|
|
}),
|
|
getKnownWordCacheStatePath: () => '/tmp/known-words-cache.json',
|
|
});
|
|
|
|
assert.equal(typeof tracker.onWindowFocusChange, 'function');
|
|
tracker.onWindowFocusChange?.(true);
|
|
assert.equal(syncCalls, 1);
|
|
});
|
|
|
|
test('initializeOverlayRuntime refreshes visible overlay when tracker focus changes while overlay is shown', () => {
|
|
let visibilityRefreshCalls = 0;
|
|
const tracker = {
|
|
onGeometryChange: null as ((...args: unknown[]) => void) | null,
|
|
onWindowFound: null as ((...args: unknown[]) => void) | null,
|
|
onWindowLost: null as (() => void) | null,
|
|
onWindowFocusChange: null as ((focused: boolean) => void) | null,
|
|
start: () => {},
|
|
};
|
|
|
|
initializeOverlayRuntime({
|
|
backendOverride: null,
|
|
createMainWindow: () => {},
|
|
registerGlobalShortcuts: () => {},
|
|
updateVisibleOverlayBounds: () => {},
|
|
isVisibleOverlayVisible: () => true,
|
|
updateVisibleOverlayVisibility: () => {
|
|
visibilityRefreshCalls += 1;
|
|
},
|
|
getOverlayWindows: () => [],
|
|
syncOverlayShortcuts: () => {},
|
|
setWindowTracker: () => {},
|
|
getMpvSocketPath: () => '/tmp/mpv.sock',
|
|
createWindowTracker: () => tracker as never,
|
|
getResolvedConfig: () => ({
|
|
ankiConnect: { enabled: false } as never,
|
|
}),
|
|
getSubtitleTimingTracker: () => null,
|
|
getMpvClient: () => null,
|
|
getRuntimeOptionsManager: () => null,
|
|
setAnkiIntegration: () => {},
|
|
showDesktopNotification: () => {},
|
|
createFieldGroupingCallback: () => async () => ({
|
|
keepNoteId: 1,
|
|
deleteNoteId: 2,
|
|
deleteDuplicate: false,
|
|
cancelled: false,
|
|
}),
|
|
getKnownWordCacheStatePath: () => '/tmp/known-words-cache.json',
|
|
});
|
|
|
|
tracker.onWindowFocusChange?.(true);
|
|
|
|
assert.equal(visibilityRefreshCalls, 2);
|
|
});
|
|
|
|
test('initializeOverlayRuntime refreshes the current subtitle when tracker finds the target window again', () => {
|
|
let subtitleRefreshCalls = 0;
|
|
const tracker = {
|
|
onGeometryChange: null as ((...args: unknown[]) => void) | null,
|
|
onWindowFound: null as ((...args: unknown[]) => void) | null,
|
|
onWindowLost: null as (() => void) | null,
|
|
onWindowFocusChange: null as ((focused: boolean) => void) | null,
|
|
start: () => {},
|
|
};
|
|
|
|
initializeOverlayRuntime({
|
|
backendOverride: null,
|
|
createMainWindow: () => {},
|
|
registerGlobalShortcuts: () => {},
|
|
updateVisibleOverlayBounds: () => {},
|
|
isVisibleOverlayVisible: () => true,
|
|
updateVisibleOverlayVisibility: () => {},
|
|
refreshCurrentSubtitle: () => {
|
|
subtitleRefreshCalls += 1;
|
|
},
|
|
getOverlayWindows: () => [],
|
|
syncOverlayShortcuts: () => {},
|
|
setWindowTracker: () => {},
|
|
getMpvSocketPath: () => '/tmp/mpv.sock',
|
|
createWindowTracker: () => tracker as never,
|
|
getResolvedConfig: () => ({
|
|
ankiConnect: { enabled: false } as never,
|
|
}),
|
|
getSubtitleTimingTracker: () => null,
|
|
getMpvClient: () => null,
|
|
getRuntimeOptionsManager: () => null,
|
|
setAnkiIntegration: () => {},
|
|
showDesktopNotification: () => {},
|
|
createFieldGroupingCallback: () => async () => ({
|
|
keepNoteId: 1,
|
|
deleteNoteId: 2,
|
|
deleteDuplicate: false,
|
|
cancelled: false,
|
|
}),
|
|
getKnownWordCacheStatePath: () => '/tmp/known-words-cache.json',
|
|
});
|
|
|
|
tracker.onWindowFound?.({ x: 100, y: 200, width: 1280, height: 720 });
|
|
|
|
assert.equal(subtitleRefreshCalls, 1);
|
|
});
|
|
|
|
test('initializeOverlayRuntime hides overlay windows when tracker loses the target window', () => {
|
|
const calls: string[] = [];
|
|
const tracker = {
|
|
onGeometryChange: null as ((...args: unknown[]) => void) | null,
|
|
onWindowFound: null as ((...args: unknown[]) => void) | null,
|
|
onWindowLost: null as (() => void) | null,
|
|
onWindowFocusChange: null as ((focused: boolean) => void) | null,
|
|
isTargetWindowMinimized: () => true,
|
|
start: () => {},
|
|
};
|
|
const overlayWindows = [
|
|
{
|
|
hide: () => calls.push('hide-visible'),
|
|
},
|
|
{
|
|
hide: () => calls.push('hide-modal'),
|
|
},
|
|
];
|
|
|
|
initializeOverlayRuntime({
|
|
backendOverride: null,
|
|
createMainWindow: () => {},
|
|
registerGlobalShortcuts: () => {},
|
|
updateVisibleOverlayBounds: () => {},
|
|
isVisibleOverlayVisible: () => true,
|
|
updateVisibleOverlayVisibility: () => {},
|
|
refreshCurrentSubtitle: () => {},
|
|
getOverlayWindows: () => overlayWindows as never,
|
|
syncOverlayShortcuts: () => {
|
|
calls.push('sync-shortcuts');
|
|
},
|
|
setWindowTracker: () => {},
|
|
getMpvSocketPath: () => '/tmp/mpv.sock',
|
|
createWindowTracker: () => tracker as never,
|
|
getResolvedConfig: () => ({
|
|
ankiConnect: { enabled: false } as never,
|
|
}),
|
|
getSubtitleTimingTracker: () => null,
|
|
getMpvClient: () => null,
|
|
getRuntimeOptionsManager: () => null,
|
|
setAnkiIntegration: () => {},
|
|
showDesktopNotification: () => {},
|
|
createFieldGroupingCallback: () => async () => ({
|
|
keepNoteId: 1,
|
|
deleteNoteId: 2,
|
|
deleteDuplicate: false,
|
|
cancelled: false,
|
|
}),
|
|
getKnownWordCacheStatePath: () => '/tmp/known-words-cache.json',
|
|
});
|
|
|
|
tracker.onWindowLost?.();
|
|
|
|
assert.deepEqual(calls, ['hide-visible', 'hide-modal', 'sync-shortcuts']);
|
|
});
|
|
|
|
test('initializeOverlayRuntime refreshes visible overlay on tracker loss when target is not minimized', () => {
|
|
const calls: string[] = [];
|
|
const tracker = {
|
|
onGeometryChange: null as ((...args: unknown[]) => void) | null,
|
|
onWindowFound: null as ((...args: unknown[]) => void) | null,
|
|
onWindowLost: null as (() => void) | null,
|
|
onWindowFocusChange: null as ((focused: boolean) => void) | null,
|
|
isTargetWindowMinimized: () => false,
|
|
start: () => {},
|
|
};
|
|
const overlayWindows = [
|
|
{
|
|
hide: () => calls.push('hide-visible'),
|
|
},
|
|
];
|
|
|
|
initializeOverlayRuntime({
|
|
backendOverride: null,
|
|
createMainWindow: () => {},
|
|
registerGlobalShortcuts: () => {},
|
|
updateVisibleOverlayBounds: () => {},
|
|
isVisibleOverlayVisible: () => true,
|
|
updateVisibleOverlayVisibility: () => {
|
|
calls.push('update-visible');
|
|
},
|
|
refreshCurrentSubtitle: () => {},
|
|
getOverlayWindows: () => overlayWindows as never,
|
|
syncOverlayShortcuts: () => {
|
|
calls.push('sync-shortcuts');
|
|
},
|
|
setWindowTracker: () => {},
|
|
getMpvSocketPath: () => '/tmp/mpv.sock',
|
|
createWindowTracker: () => tracker as never,
|
|
getResolvedConfig: () => ({
|
|
ankiConnect: { enabled: false } as never,
|
|
}),
|
|
getSubtitleTimingTracker: () => null,
|
|
getMpvClient: () => null,
|
|
getRuntimeOptionsManager: () => null,
|
|
setAnkiIntegration: () => {},
|
|
showDesktopNotification: () => {},
|
|
createFieldGroupingCallback: () => async () => ({
|
|
keepNoteId: 1,
|
|
deleteNoteId: 2,
|
|
deleteDuplicate: false,
|
|
cancelled: false,
|
|
}),
|
|
getKnownWordCacheStatePath: () => '/tmp/known-words-cache.json',
|
|
});
|
|
|
|
calls.length = 0;
|
|
tracker.onWindowLost?.();
|
|
|
|
assert.deepEqual(calls, ['update-visible']);
|
|
});
|
|
|
|
test('initializeOverlayRuntime restores overlay bounds and visibility when tracker finds the target window again', () => {
|
|
const bounds: Array<{ x: number; y: number; width: number; height: number }> = [];
|
|
let visibilityRefreshCalls = 0;
|
|
const tracker = {
|
|
onGeometryChange: null as ((...args: unknown[]) => void) | null,
|
|
onWindowFound: null as ((...args: unknown[]) => void) | null,
|
|
onWindowLost: null as (() => void) | null,
|
|
onWindowFocusChange: null as ((focused: boolean) => void) | null,
|
|
start: () => {},
|
|
};
|
|
|
|
initializeOverlayRuntime({
|
|
backendOverride: null,
|
|
createMainWindow: () => {},
|
|
registerGlobalShortcuts: () => {},
|
|
updateVisibleOverlayBounds: (geometry) => {
|
|
bounds.push(geometry);
|
|
},
|
|
isVisibleOverlayVisible: () => true,
|
|
updateVisibleOverlayVisibility: () => {
|
|
visibilityRefreshCalls += 1;
|
|
},
|
|
refreshCurrentSubtitle: () => {},
|
|
getOverlayWindows: () => [],
|
|
syncOverlayShortcuts: () => {},
|
|
setWindowTracker: () => {},
|
|
getMpvSocketPath: () => '/tmp/mpv.sock',
|
|
createWindowTracker: () => tracker as never,
|
|
getResolvedConfig: () => ({
|
|
ankiConnect: { enabled: false } as never,
|
|
}),
|
|
getSubtitleTimingTracker: () => null,
|
|
getMpvClient: () => null,
|
|
getRuntimeOptionsManager: () => null,
|
|
setAnkiIntegration: () => {},
|
|
showDesktopNotification: () => {},
|
|
createFieldGroupingCallback: () => async () => ({
|
|
keepNoteId: 1,
|
|
deleteNoteId: 2,
|
|
deleteDuplicate: false,
|
|
cancelled: false,
|
|
}),
|
|
getKnownWordCacheStatePath: () => '/tmp/known-words-cache.json',
|
|
});
|
|
|
|
const restoredGeometry = { x: 100, y: 200, width: 1280, height: 720 };
|
|
tracker.onWindowFound?.(restoredGeometry);
|
|
|
|
assert.deepEqual(bounds, [restoredGeometry]);
|
|
assert.equal(visibilityRefreshCalls, 2);
|
|
});
|