mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-01 06:22:44 -08:00
282 lines
8.3 KiB
TypeScript
282 lines
8.3 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { ConfiguredShortcuts } from '../utils/shortcut-config';
|
|
import {
|
|
createOverlayShortcutRuntimeHandlers,
|
|
OverlayShortcutRuntimeDeps,
|
|
runOverlayShortcutLocalFallback,
|
|
} from './overlay-shortcut-handler';
|
|
|
|
function makeShortcuts(overrides: Partial<ConfiguredShortcuts> = {}): ConfiguredShortcuts {
|
|
return {
|
|
toggleVisibleOverlayGlobal: null,
|
|
copySubtitle: null,
|
|
copySubtitleMultiple: null,
|
|
updateLastCardFromClipboard: null,
|
|
triggerFieldGrouping: null,
|
|
triggerSubsync: null,
|
|
mineSentence: null,
|
|
mineSentenceMultiple: null,
|
|
multiCopyTimeoutMs: 2500,
|
|
toggleSecondarySub: null,
|
|
markAudioCard: null,
|
|
openRuntimeOptions: null,
|
|
openJimaku: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function createDeps(overrides: Partial<OverlayShortcutRuntimeDeps> = {}) {
|
|
const calls: string[] = [];
|
|
const osd: string[] = [];
|
|
const deps: OverlayShortcutRuntimeDeps = {
|
|
showMpvOsd: (text) => {
|
|
osd.push(text);
|
|
},
|
|
openRuntimeOptions: () => {
|
|
calls.push('openRuntimeOptions');
|
|
},
|
|
openJimaku: () => {
|
|
calls.push('openJimaku');
|
|
},
|
|
markAudioCard: async () => {
|
|
calls.push('markAudioCard');
|
|
},
|
|
copySubtitleMultiple: (timeoutMs) => {
|
|
calls.push(`copySubtitleMultiple:${timeoutMs}`);
|
|
},
|
|
copySubtitle: () => {
|
|
calls.push('copySubtitle');
|
|
},
|
|
toggleSecondarySub: () => {
|
|
calls.push('toggleSecondarySub');
|
|
},
|
|
updateLastCardFromClipboard: async () => {
|
|
calls.push('updateLastCardFromClipboard');
|
|
},
|
|
triggerFieldGrouping: async () => {
|
|
calls.push('triggerFieldGrouping');
|
|
},
|
|
triggerSubsync: async () => {
|
|
calls.push('triggerSubsync');
|
|
},
|
|
mineSentence: async () => {
|
|
calls.push('mineSentence');
|
|
},
|
|
mineSentenceMultiple: (timeoutMs) => {
|
|
calls.push(`mineSentenceMultiple:${timeoutMs}`);
|
|
},
|
|
...overrides,
|
|
};
|
|
|
|
return { deps, calls, osd };
|
|
}
|
|
|
|
test('createOverlayShortcutRuntimeHandlers dispatches sync and async handlers', async () => {
|
|
const { deps, calls } = createDeps();
|
|
const { overlayHandlers, fallbackHandlers } = createOverlayShortcutRuntimeHandlers(deps);
|
|
|
|
overlayHandlers.copySubtitle();
|
|
overlayHandlers.copySubtitleMultiple(1111);
|
|
overlayHandlers.toggleSecondarySub();
|
|
overlayHandlers.openRuntimeOptions();
|
|
overlayHandlers.openJimaku();
|
|
overlayHandlers.mineSentenceMultiple(2222);
|
|
overlayHandlers.updateLastCardFromClipboard();
|
|
fallbackHandlers.mineSentence();
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
|
|
assert.deepEqual(calls, [
|
|
'copySubtitle',
|
|
'copySubtitleMultiple:1111',
|
|
'toggleSecondarySub',
|
|
'openRuntimeOptions',
|
|
'openJimaku',
|
|
'mineSentenceMultiple:2222',
|
|
'updateLastCardFromClipboard',
|
|
'mineSentence',
|
|
]);
|
|
});
|
|
|
|
test('createOverlayShortcutRuntimeHandlers reports async failures via OSD', async () => {
|
|
const logs: unknown[][] = [];
|
|
const originalError = console.error;
|
|
console.error = (...args: unknown[]) => {
|
|
logs.push(args);
|
|
};
|
|
|
|
try {
|
|
const { deps, osd } = createDeps({
|
|
markAudioCard: async () => {
|
|
throw new Error('audio boom');
|
|
},
|
|
});
|
|
const { overlayHandlers } = createOverlayShortcutRuntimeHandlers(deps);
|
|
|
|
overlayHandlers.markAudioCard();
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
|
|
assert.equal(logs.length, 1);
|
|
assert.equal(typeof logs[0]?.[0], 'string');
|
|
assert.ok(String(logs[0]?.[0]).includes('markLastCardAsAudioCard failed:'));
|
|
assert.ok(String(logs[0]?.[0]).includes('audio boom'));
|
|
assert.ok(osd.some((entry) => entry.includes('Audio card failed: audio boom')));
|
|
} finally {
|
|
console.error = originalError;
|
|
}
|
|
});
|
|
|
|
test('runOverlayShortcutLocalFallback dispatches matching actions with timeout', () => {
|
|
const handled: string[] = [];
|
|
const matched: Array<{ accelerator: string; allowWhenRegistered: boolean }> = [];
|
|
const shortcuts = makeShortcuts({
|
|
copySubtitleMultiple: 'Ctrl+M',
|
|
multiCopyTimeoutMs: 4321,
|
|
});
|
|
|
|
const result = runOverlayShortcutLocalFallback(
|
|
{} as Electron.Input,
|
|
shortcuts,
|
|
(_input, accelerator, allowWhenRegistered) => {
|
|
matched.push({
|
|
accelerator,
|
|
allowWhenRegistered: allowWhenRegistered === true,
|
|
});
|
|
return accelerator === 'Ctrl+M';
|
|
},
|
|
{
|
|
openRuntimeOptions: () => handled.push('openRuntimeOptions'),
|
|
openJimaku: () => handled.push('openJimaku'),
|
|
markAudioCard: () => handled.push('markAudioCard'),
|
|
copySubtitleMultiple: (timeoutMs) => handled.push(`copySubtitleMultiple:${timeoutMs}`),
|
|
copySubtitle: () => handled.push('copySubtitle'),
|
|
toggleSecondarySub: () => handled.push('toggleSecondarySub'),
|
|
updateLastCardFromClipboard: () => handled.push('updateLastCardFromClipboard'),
|
|
triggerFieldGrouping: () => handled.push('triggerFieldGrouping'),
|
|
triggerSubsync: () => handled.push('triggerSubsync'),
|
|
mineSentence: () => handled.push('mineSentence'),
|
|
mineSentenceMultiple: (timeoutMs) => handled.push(`mineSentenceMultiple:${timeoutMs}`),
|
|
},
|
|
);
|
|
|
|
assert.equal(result, true);
|
|
assert.deepEqual(handled, ['copySubtitleMultiple:4321']);
|
|
assert.deepEqual(matched, [{ accelerator: 'Ctrl+M', allowWhenRegistered: false }]);
|
|
});
|
|
|
|
test('runOverlayShortcutLocalFallback passes allowWhenRegistered for secondary-sub toggle', () => {
|
|
const matched: Array<{ accelerator: string; allowWhenRegistered: boolean }> = [];
|
|
const shortcuts = makeShortcuts({
|
|
toggleSecondarySub: 'Ctrl+2',
|
|
});
|
|
|
|
const result = runOverlayShortcutLocalFallback(
|
|
{} as Electron.Input,
|
|
shortcuts,
|
|
(_input, accelerator, allowWhenRegistered) => {
|
|
matched.push({
|
|
accelerator,
|
|
allowWhenRegistered: allowWhenRegistered === true,
|
|
});
|
|
return accelerator === 'Ctrl+2';
|
|
},
|
|
{
|
|
openRuntimeOptions: () => {},
|
|
openJimaku: () => {},
|
|
markAudioCard: () => {},
|
|
copySubtitleMultiple: () => {},
|
|
copySubtitle: () => {},
|
|
toggleSecondarySub: () => {},
|
|
updateLastCardFromClipboard: () => {},
|
|
triggerFieldGrouping: () => {},
|
|
triggerSubsync: () => {},
|
|
mineSentence: () => {},
|
|
mineSentenceMultiple: () => {},
|
|
},
|
|
);
|
|
|
|
assert.equal(result, true);
|
|
assert.deepEqual(matched, [{ accelerator: 'Ctrl+2', allowWhenRegistered: true }]);
|
|
});
|
|
|
|
test('runOverlayShortcutLocalFallback allows registered-global jimaku shortcut', () => {
|
|
const matched: Array<{ accelerator: string; allowWhenRegistered: boolean }> = [];
|
|
const shortcuts = makeShortcuts({
|
|
openJimaku: 'Ctrl+J',
|
|
});
|
|
|
|
const result = runOverlayShortcutLocalFallback(
|
|
{} as Electron.Input,
|
|
shortcuts,
|
|
(_input, accelerator, allowWhenRegistered) => {
|
|
matched.push({
|
|
accelerator,
|
|
allowWhenRegistered: allowWhenRegistered === true,
|
|
});
|
|
return accelerator === 'Ctrl+J';
|
|
},
|
|
{
|
|
openRuntimeOptions: () => {},
|
|
openJimaku: () => {},
|
|
markAudioCard: () => {},
|
|
copySubtitleMultiple: () => {},
|
|
copySubtitle: () => {},
|
|
toggleSecondarySub: () => {},
|
|
updateLastCardFromClipboard: () => {},
|
|
triggerFieldGrouping: () => {},
|
|
triggerSubsync: () => {},
|
|
mineSentence: () => {},
|
|
mineSentenceMultiple: () => {},
|
|
},
|
|
);
|
|
|
|
assert.equal(result, true);
|
|
assert.deepEqual(matched, [{ accelerator: 'Ctrl+J', allowWhenRegistered: true }]);
|
|
});
|
|
|
|
test('runOverlayShortcutLocalFallback returns false when no action matches', () => {
|
|
const shortcuts = makeShortcuts({
|
|
copySubtitle: 'Ctrl+C',
|
|
});
|
|
let called = false;
|
|
|
|
const result = runOverlayShortcutLocalFallback({} as Electron.Input, shortcuts, () => false, {
|
|
openRuntimeOptions: () => {
|
|
called = true;
|
|
},
|
|
openJimaku: () => {
|
|
called = true;
|
|
},
|
|
markAudioCard: () => {
|
|
called = true;
|
|
},
|
|
copySubtitleMultiple: () => {
|
|
called = true;
|
|
},
|
|
copySubtitle: () => {
|
|
called = true;
|
|
},
|
|
toggleSecondarySub: () => {
|
|
called = true;
|
|
},
|
|
updateLastCardFromClipboard: () => {
|
|
called = true;
|
|
},
|
|
triggerFieldGrouping: () => {
|
|
called = true;
|
|
},
|
|
triggerSubsync: () => {
|
|
called = true;
|
|
},
|
|
mineSentence: () => {
|
|
called = true;
|
|
},
|
|
mineSentenceMultiple: () => {
|
|
called = true;
|
|
},
|
|
});
|
|
|
|
assert.equal(result, false);
|
|
assert.equal(called, false);
|
|
});
|