Files
SubMiner/src/main/runtime/composers/mpv-runtime-composer.test.ts

222 lines
7.5 KiB
TypeScript

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: () => {},
syncOverlayMpvSubtitleSuppression: () => {},
maybeRunAnilistPostWatchUpdate: async () => {},
logSubtitleTimingError: () => {},
broadcastToOverlayWindows: () => {},
onSubtitleChange: () => {},
refreshDiscordPresence: () => {},
updateCurrentMediaPath: () => {},
restoreMpvSubVisibility: () => {},
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'));
});