mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-28 18:22:42 -08:00
feat(core): add Electron runtime, services, and app composition
This commit is contained in:
219
src/main/runtime/composers/mpv-runtime-composer.test.ts
Normal file
219
src/main/runtime/composers/mpv-runtime-composer.test.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import type { MpvSubtitleRenderMetrics } from '../../../types';
|
||||
import { composeMpvRuntimeHandlers } from './mpv-runtime-composer';
|
||||
|
||||
const BASE_METRICS: MpvSubtitleRenderMetrics = {
|
||||
subPos: 100,
|
||||
subFontSize: 36,
|
||||
subScale: 1,
|
||||
subMarginY: 0,
|
||||
subMarginX: 0,
|
||||
subFont: '',
|
||||
subSpacing: 0,
|
||||
subBold: false,
|
||||
subItalic: false,
|
||||
subBorderSize: 0,
|
||||
subShadowOffset: 0,
|
||||
subAssOverride: 'yes',
|
||||
subScaleByWindow: true,
|
||||
subUseMargins: true,
|
||||
osdHeight: 0,
|
||||
osdDimensions: null,
|
||||
};
|
||||
|
||||
test('composeMpvRuntimeHandlers returns callable handlers and forwards to injected deps', async () => {
|
||||
const calls: string[] = [];
|
||||
let started = false;
|
||||
let metrics = BASE_METRICS;
|
||||
|
||||
class FakeMpvClient {
|
||||
connected = false;
|
||||
|
||||
constructor(
|
||||
public socketPath: string,
|
||||
public options: unknown,
|
||||
) {
|
||||
const autoStartOverlay = (options as { autoStartOverlay: boolean }).autoStartOverlay;
|
||||
calls.push(`create-client:${socketPath}`);
|
||||
calls.push(`auto-start:${String(autoStartOverlay)}`);
|
||||
}
|
||||
|
||||
on(): void {}
|
||||
|
||||
connect(): void {
|
||||
this.connected = true;
|
||||
calls.push('client-connect');
|
||||
}
|
||||
}
|
||||
|
||||
const composed = composeMpvRuntimeHandlers<
|
||||
FakeMpvClient,
|
||||
{ isKnownWord: (text: string) => boolean },
|
||||
{ text: string }
|
||||
>({
|
||||
bindMpvMainEventHandlersMainDeps: {
|
||||
appState: {
|
||||
initialArgs: null,
|
||||
overlayRuntimeInitialized: true,
|
||||
mpvClient: null,
|
||||
immersionTracker: null,
|
||||
subtitleTimingTracker: null,
|
||||
currentSubText: '',
|
||||
currentSubAssText: '',
|
||||
playbackPaused: null,
|
||||
previousSecondarySubVisibility: null,
|
||||
},
|
||||
getQuitOnDisconnectArmed: () => false,
|
||||
scheduleQuitCheck: () => {},
|
||||
quitApp: () => {},
|
||||
reportJellyfinRemoteStopped: () => {},
|
||||
maybeRunAnilistPostWatchUpdate: async () => {},
|
||||
logSubtitleTimingError: () => {},
|
||||
broadcastToOverlayWindows: () => {},
|
||||
onSubtitleChange: () => {},
|
||||
refreshDiscordPresence: () => {},
|
||||
updateCurrentMediaPath: () => {},
|
||||
getCurrentAnilistMediaKey: () => null,
|
||||
resetAnilistMediaTracking: () => {},
|
||||
maybeProbeAnilistDuration: () => {},
|
||||
ensureAnilistMediaGuess: () => {},
|
||||
syncImmersionMediaState: () => {},
|
||||
updateCurrentMediaTitle: () => {},
|
||||
resetAnilistMediaGuessState: () => {},
|
||||
reportJellyfinRemoteProgress: () => {},
|
||||
updateSubtitleRenderMetrics: () => {},
|
||||
},
|
||||
mpvClientRuntimeServiceFactoryMainDeps: {
|
||||
createClient: FakeMpvClient,
|
||||
getSocketPath: () => '/tmp/mpv.sock',
|
||||
getResolvedConfig: () => ({ auto_start_overlay: false }),
|
||||
isAutoStartOverlayEnabled: () => true,
|
||||
setOverlayVisible: () => {},
|
||||
shouldBindVisibleOverlayToMpvSubVisibility: () => true,
|
||||
isVisibleOverlayVisible: () => false,
|
||||
getReconnectTimer: () => null,
|
||||
setReconnectTimer: () => {},
|
||||
},
|
||||
updateMpvSubtitleRenderMetricsMainDeps: {
|
||||
getCurrentMetrics: () => metrics,
|
||||
setCurrentMetrics: (next) => {
|
||||
metrics = next;
|
||||
calls.push('set-metrics');
|
||||
},
|
||||
applyPatch: (current, patch) => {
|
||||
calls.push('apply-metrics-patch');
|
||||
return { next: { ...current, ...patch }, changed: true };
|
||||
},
|
||||
broadcastMetrics: () => {
|
||||
calls.push('broadcast-metrics');
|
||||
},
|
||||
},
|
||||
tokenizer: {
|
||||
buildTokenizerDepsMainDeps: {
|
||||
getYomitanExt: () => null,
|
||||
getYomitanParserWindow: () => null,
|
||||
setYomitanParserWindow: () => {},
|
||||
getYomitanParserReadyPromise: () => null,
|
||||
setYomitanParserReadyPromise: () => {},
|
||||
getYomitanParserInitPromise: () => null,
|
||||
setYomitanParserInitPromise: () => {},
|
||||
isKnownWord: (text) => text === 'known',
|
||||
recordLookup: () => {},
|
||||
getKnownWordMatchMode: () => 'headword',
|
||||
getMinSentenceWordsForNPlusOne: () => 3,
|
||||
getJlptLevel: () => null,
|
||||
getJlptEnabled: () => true,
|
||||
getFrequencyDictionaryEnabled: () => true,
|
||||
getFrequencyRank: () => null,
|
||||
getYomitanGroupDebugEnabled: () => false,
|
||||
getMecabTokenizer: () => null,
|
||||
},
|
||||
createTokenizerRuntimeDeps: (deps) => {
|
||||
calls.push('create-tokenizer-runtime-deps');
|
||||
return { isKnownWord: (text: string) => deps.isKnownWord(text) };
|
||||
},
|
||||
tokenizeSubtitle: async (text, deps) => {
|
||||
calls.push(`tokenize:${text}`);
|
||||
deps.isKnownWord('known');
|
||||
return { text };
|
||||
},
|
||||
createMecabTokenizerAndCheckMainDeps: {
|
||||
getMecabTokenizer: () => ({ id: 'mecab' }),
|
||||
setMecabTokenizer: () => {},
|
||||
createMecabTokenizer: () => ({ id: 'mecab' }),
|
||||
checkAvailability: async () => {
|
||||
calls.push('check-mecab');
|
||||
},
|
||||
},
|
||||
prewarmSubtitleDictionariesMainDeps: {
|
||||
ensureJlptDictionaryLookup: async () => {
|
||||
calls.push('prewarm-jlpt');
|
||||
},
|
||||
ensureFrequencyDictionaryLookup: async () => {
|
||||
calls.push('prewarm-frequency');
|
||||
},
|
||||
},
|
||||
},
|
||||
warmups: {
|
||||
launchBackgroundWarmupTaskMainDeps: {
|
||||
now: () => 100,
|
||||
logDebug: () => {
|
||||
calls.push('warmup-debug');
|
||||
},
|
||||
logWarn: () => {
|
||||
calls.push('warmup-warn');
|
||||
},
|
||||
},
|
||||
startBackgroundWarmupsMainDeps: {
|
||||
getStarted: () => started,
|
||||
setStarted: (next) => {
|
||||
started = next;
|
||||
calls.push(`set-started:${String(next)}`);
|
||||
},
|
||||
isTexthookerOnlyMode: () => false,
|
||||
ensureYomitanExtensionLoaded: async () => {
|
||||
calls.push('warmup-yomitan');
|
||||
},
|
||||
shouldAutoConnectJellyfinRemote: () => false,
|
||||
startJellyfinRemoteSession: async () => {
|
||||
calls.push('warmup-jellyfin');
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(typeof composed.bindMpvClientEventHandlers, 'function');
|
||||
assert.equal(typeof composed.createMpvClientRuntimeService, 'function');
|
||||
assert.equal(typeof composed.updateMpvSubtitleRenderMetrics, 'function');
|
||||
assert.equal(typeof composed.tokenizeSubtitle, 'function');
|
||||
assert.equal(typeof composed.createMecabTokenizerAndCheck, 'function');
|
||||
assert.equal(typeof composed.prewarmSubtitleDictionaries, 'function');
|
||||
assert.equal(typeof composed.launchBackgroundWarmupTask, 'function');
|
||||
assert.equal(typeof composed.startBackgroundWarmups, 'function');
|
||||
|
||||
const client = composed.createMpvClientRuntimeService();
|
||||
assert.equal(client.connected, true);
|
||||
|
||||
composed.updateMpvSubtitleRenderMetrics({ subPos: 90 });
|
||||
const tokenized = await composed.tokenizeSubtitle('subtitle text');
|
||||
await composed.createMecabTokenizerAndCheck();
|
||||
await composed.prewarmSubtitleDictionaries();
|
||||
composed.startBackgroundWarmups();
|
||||
|
||||
assert.deepEqual(tokenized, { text: 'subtitle text' });
|
||||
assert.equal(metrics.subPos, 90);
|
||||
assert.ok(calls.includes('create-client:/tmp/mpv.sock'));
|
||||
assert.ok(calls.includes('auto-start:true'));
|
||||
assert.ok(calls.includes('client-connect'));
|
||||
assert.ok(calls.includes('apply-metrics-patch'));
|
||||
assert.ok(calls.includes('set-metrics'));
|
||||
assert.ok(calls.includes('broadcast-metrics'));
|
||||
assert.ok(calls.includes('create-tokenizer-runtime-deps'));
|
||||
assert.ok(calls.includes('tokenize:subtitle text'));
|
||||
assert.ok(calls.includes('check-mecab'));
|
||||
assert.ok(calls.includes('prewarm-jlpt'));
|
||||
assert.ok(calls.includes('prewarm-frequency'));
|
||||
assert.ok(calls.includes('set-started:true'));
|
||||
assert.ok(calls.includes('warmup-yomitan'));
|
||||
});
|
||||
Reference in New Issue
Block a user