mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-04-01 18:12:06 -07:00
refactor: split main.ts into domain runtimes
This commit is contained in:
215
src/main/dictionary-support-runtime.test.ts
Normal file
215
src/main/dictionary-support-runtime.test.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import { createDictionarySupportRuntime } from './dictionary-support-runtime';
|
||||
|
||||
function createRuntime() {
|
||||
const state = {
|
||||
currentMediaPath: null as string | null,
|
||||
currentMediaTitle: null as string | null,
|
||||
jlptLookupSet: 0,
|
||||
frequencyLookupSet: 0,
|
||||
trackerCalls: [] as Array<{ path: string; title: string | null }>,
|
||||
characterDictionaryConfig: {
|
||||
enabled: false,
|
||||
maxLoaded: 1,
|
||||
profileScope: 'global',
|
||||
},
|
||||
youtubePlaybackActive: false,
|
||||
};
|
||||
|
||||
const runtime = createDictionarySupportRuntime({
|
||||
platform: 'darwin',
|
||||
dirname: '/repo/dist/main',
|
||||
appPath: '/Applications/SubMiner.app/Contents/Resources/app.asar',
|
||||
resourcesPath: '/Applications/SubMiner.app/Contents/Resources',
|
||||
userDataPath: '/Users/a/Library/Application Support/SubMiner',
|
||||
appUserDataPath: '/Users/a/Library/Application Support/SubMiner',
|
||||
homeDir: '/Users/a',
|
||||
cwd: '/repo',
|
||||
subtitlePositionsDir: '/Users/a/Library/Application Support/SubMiner/subtitle-positions',
|
||||
getResolvedConfig: () =>
|
||||
({
|
||||
subtitleStyle: {
|
||||
enableJlpt: false,
|
||||
frequencyDictionary: {
|
||||
enabled: false,
|
||||
sourcePath: '',
|
||||
},
|
||||
},
|
||||
anilist: {
|
||||
characterDictionary: {
|
||||
enabled: false,
|
||||
maxLoaded: 1,
|
||||
profileScope: 'global',
|
||||
collapsibleSections: {
|
||||
description: false,
|
||||
glossary: false,
|
||||
termEntry: false,
|
||||
nameReading: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
ankiConnect: {
|
||||
behavior: {
|
||||
notificationType: 'none',
|
||||
},
|
||||
},
|
||||
}) as never,
|
||||
isJlptEnabled: () => false,
|
||||
isFrequencyDictionaryEnabled: () => false,
|
||||
getFrequencyDictionarySourcePath: () => undefined,
|
||||
setJlptLevelLookup: () => {
|
||||
state.jlptLookupSet += 1;
|
||||
},
|
||||
setFrequencyRankLookup: () => {
|
||||
state.frequencyLookupSet += 1;
|
||||
},
|
||||
logInfo: () => {},
|
||||
logDebug: () => {},
|
||||
logWarn: () => {},
|
||||
isRemoteMediaPath: (mediaPath) => mediaPath.startsWith('remote:'),
|
||||
getCurrentMediaPath: () => state.currentMediaPath,
|
||||
setCurrentMediaPath: (mediaPath) => {
|
||||
state.currentMediaPath = mediaPath;
|
||||
},
|
||||
getCurrentMediaTitle: () => state.currentMediaTitle,
|
||||
setCurrentMediaTitle: (title) => {
|
||||
state.currentMediaTitle = title;
|
||||
},
|
||||
getPendingSubtitlePosition: () => null,
|
||||
loadSubtitlePosition: () => null,
|
||||
clearPendingSubtitlePosition: () => {},
|
||||
setSubtitlePosition: () => {},
|
||||
broadcastSubtitlePosition: () => {},
|
||||
broadcastToOverlayWindows: () => {},
|
||||
getTracker: () =>
|
||||
({
|
||||
handleMediaChange: (path: string, title: string | null) => {
|
||||
state.trackerCalls.push({ path, title });
|
||||
},
|
||||
}) as never,
|
||||
getMpvClient: () => null,
|
||||
defaultImmersionDbPath: '/tmp/immersion.db',
|
||||
guessAnilistMediaInfo: async () => null,
|
||||
getCollapsibleSectionOpenState: () => false,
|
||||
isCharacterDictionaryEnabled: () => state.characterDictionaryConfig.enabled,
|
||||
isYoutubePlaybackActiveNow: () => state.youtubePlaybackActive,
|
||||
waitForYomitanMutationReady: async () => {},
|
||||
getYomitanDictionaryInfo: async () => [],
|
||||
importYomitanDictionary: async () => false,
|
||||
deleteYomitanDictionary: async () => false,
|
||||
upsertYomitanDictionarySettings: async () => false,
|
||||
getCharacterDictionaryConfig: () => state.characterDictionaryConfig as never,
|
||||
notifyCharacterDictionaryAutoSyncStatus: () => {},
|
||||
characterDictionaryAutoSyncCompleteDeps: {
|
||||
hasParserWindow: () => false,
|
||||
clearParserCaches: () => {},
|
||||
invalidateTokenizationCache: () => {},
|
||||
refreshSubtitlePrefetch: () => {},
|
||||
refreshCurrentSubtitle: () => {},
|
||||
logInfo: () => {},
|
||||
},
|
||||
getMainWindow: () => null,
|
||||
getVisibleOverlayVisible: () => false,
|
||||
setVisibleOverlayVisible: () => {},
|
||||
getRestoreVisibleOverlayOnModalClose: () => new Set<string>(),
|
||||
sendToActiveOverlayWindow: () => true,
|
||||
});
|
||||
|
||||
return { runtime, state };
|
||||
}
|
||||
|
||||
test('dictionary support runtime wires field grouping resolver and callback', async () => {
|
||||
const { runtime } = createRuntime();
|
||||
const choice = {
|
||||
keepNoteId: 1,
|
||||
deleteNoteId: 2,
|
||||
deleteDuplicate: false,
|
||||
cancelled: false,
|
||||
};
|
||||
|
||||
const callback = runtime.createFieldGroupingCallback();
|
||||
const pending = callback({} as never);
|
||||
const resolver = runtime.getFieldGroupingResolver();
|
||||
assert.ok(resolver);
|
||||
resolver(choice as never);
|
||||
assert.deepEqual(await pending, choice);
|
||||
assert.equal(typeof runtime.getFieldGroupingResolver(), 'function');
|
||||
|
||||
runtime.setFieldGroupingResolver(null);
|
||||
assert.equal(runtime.getFieldGroupingResolver(), null);
|
||||
});
|
||||
|
||||
test('dictionary support runtime resolves media paths and keeps title in sync', () => {
|
||||
const { runtime, state } = createRuntime();
|
||||
|
||||
runtime.updateCurrentMediaTitle(' Example Title ');
|
||||
runtime.updateCurrentMediaPath('remote://media' as never);
|
||||
assert.equal(state.currentMediaTitle, 'Example Title');
|
||||
assert.equal(runtime.resolveMediaPathForJimaku('remote://media'), 'Example Title');
|
||||
|
||||
runtime.updateCurrentMediaPath('local.mp4' as never);
|
||||
assert.equal(state.currentMediaTitle, null);
|
||||
assert.equal(runtime.resolveMediaPathForJimaku('remote://media'), 'remote://media');
|
||||
});
|
||||
|
||||
test('dictionary support runtime skips disabled lookup and sync paths', async () => {
|
||||
const { runtime, state } = createRuntime();
|
||||
|
||||
await runtime.ensureJlptDictionaryLookup();
|
||||
await runtime.ensureFrequencyDictionaryLookup();
|
||||
runtime.scheduleCharacterDictionarySync();
|
||||
|
||||
assert.equal(state.jlptLookupSet, 0);
|
||||
assert.equal(state.frequencyLookupSet, 0);
|
||||
});
|
||||
|
||||
test('dictionary support runtime syncs immersion media from current state', async () => {
|
||||
const { runtime, state } = createRuntime();
|
||||
|
||||
runtime.updateCurrentMediaTitle(' Example Title ');
|
||||
runtime.updateCurrentMediaPath('remote://media' as never);
|
||||
await runtime.seedImmersionMediaFromCurrentMedia();
|
||||
runtime.syncImmersionMediaState();
|
||||
|
||||
assert.deepEqual(state.trackerCalls, [
|
||||
{ path: 'remote://media', title: 'Example Title' },
|
||||
{ path: 'remote://media', title: 'Example Title' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('dictionary support runtime gates character dictionary auto-sync scheduling', () => {
|
||||
const { runtime, state } = createRuntime();
|
||||
const originalSetTimeout = globalThis.setTimeout;
|
||||
const originalClearTimeout = globalThis.clearTimeout;
|
||||
let timeoutCalls = 0;
|
||||
|
||||
try {
|
||||
globalThis.setTimeout = ((handler: TimerHandler, timeout?: number, ...args: never[]) => {
|
||||
timeoutCalls += 1;
|
||||
return originalSetTimeout(handler, timeout ?? 0, ...args);
|
||||
}) as typeof globalThis.setTimeout;
|
||||
globalThis.clearTimeout = ((handle: number | NodeJS.Timeout | undefined) => {
|
||||
originalClearTimeout(handle);
|
||||
}) as typeof globalThis.clearTimeout;
|
||||
|
||||
runtime.scheduleCharacterDictionarySync();
|
||||
assert.equal(timeoutCalls, 0);
|
||||
|
||||
state.characterDictionaryConfig = {
|
||||
enabled: true,
|
||||
maxLoaded: 1,
|
||||
profileScope: 'global',
|
||||
};
|
||||
runtime.scheduleCharacterDictionarySync();
|
||||
assert.equal(timeoutCalls, 1);
|
||||
|
||||
state.youtubePlaybackActive = true;
|
||||
runtime.scheduleCharacterDictionarySync();
|
||||
assert.equal(timeoutCalls, 1);
|
||||
} finally {
|
||||
globalThis.setTimeout = originalSetTimeout;
|
||||
globalThis.clearTimeout = originalClearTimeout;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user