refactor: extract shortcut and mining runtime deps

This commit is contained in:
2026-02-10 01:32:03 -08:00
parent b177be0831
commit a17c2296d5
6 changed files with 357 additions and 42 deletions

View File

@@ -0,0 +1,65 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
createCopyCurrentSubtitleDepsRuntimeService,
createHandleMineSentenceDigitDepsRuntimeService,
createHandleMultiCopyDigitDepsRuntimeService,
createMarkLastCardAsAudioCardDepsRuntimeService,
createMineSentenceCardDepsRuntimeService,
createTriggerFieldGroupingDepsRuntimeService,
createUpdateLastCardFromClipboardDepsRuntimeService,
} from "./mining-runtime-deps-service";
test("mining runtime deps builders preserve references", () => {
const showMpvOsd = (_text: string) => {};
const writeClipboardText = (_text: string) => {};
const readClipboardText = () => "x";
const logError = (_message: string, _err: unknown) => {};
const subtitleTimingTracker = null;
const ankiIntegration = null;
const mpvClient = null;
const multiCopy = createHandleMultiCopyDigitDepsRuntimeService({
subtitleTimingTracker,
writeClipboardText,
showMpvOsd,
});
const copyCurrent = createCopyCurrentSubtitleDepsRuntimeService({
subtitleTimingTracker,
writeClipboardText,
showMpvOsd,
});
const updateLast = createUpdateLastCardFromClipboardDepsRuntimeService({
ankiIntegration,
readClipboardText,
showMpvOsd,
});
const fieldGrouping = createTriggerFieldGroupingDepsRuntimeService({
ankiIntegration,
showMpvOsd,
});
const markAudio = createMarkLastCardAsAudioCardDepsRuntimeService({
ankiIntegration,
showMpvOsd,
});
const mineCard = createMineSentenceCardDepsRuntimeService({
ankiIntegration,
mpvClient,
showMpvOsd,
});
const mineDigit = createHandleMineSentenceDigitDepsRuntimeService({
subtitleTimingTracker,
ankiIntegration,
getCurrentSecondarySubText: () => undefined,
showMpvOsd,
logError,
});
assert.equal(multiCopy.writeClipboardText, writeClipboardText);
assert.equal(copyCurrent.showMpvOsd, showMpvOsd);
assert.equal(updateLast.readClipboardText, readClipboardText);
assert.equal(fieldGrouping.ankiIntegration, ankiIntegration);
assert.equal(markAudio.showMpvOsd, showMpvOsd);
assert.equal(mineCard.mpvClient, mpvClient);
assert.equal(mineDigit.logError, logError);
});

View File

@@ -0,0 +1,107 @@
import {
copyCurrentSubtitleService,
handleMineSentenceDigitService,
handleMultiCopyDigitService,
markLastCardAsAudioCardService,
mineSentenceCardService,
triggerFieldGroupingService,
updateLastCardFromClipboardService,
} from "./mining-runtime-service";
export function createHandleMultiCopyDigitDepsRuntimeService(
options: {
subtitleTimingTracker: Parameters<typeof handleMultiCopyDigitService>[1]["subtitleTimingTracker"];
writeClipboardText: Parameters<typeof handleMultiCopyDigitService>[1]["writeClipboardText"];
showMpvOsd: Parameters<typeof handleMultiCopyDigitService>[1]["showMpvOsd"];
},
): Parameters<typeof handleMultiCopyDigitService>[1] {
return {
subtitleTimingTracker: options.subtitleTimingTracker,
writeClipboardText: options.writeClipboardText,
showMpvOsd: options.showMpvOsd,
};
}
export function createCopyCurrentSubtitleDepsRuntimeService(
options: {
subtitleTimingTracker: Parameters<typeof copyCurrentSubtitleService>[0]["subtitleTimingTracker"];
writeClipboardText: Parameters<typeof copyCurrentSubtitleService>[0]["writeClipboardText"];
showMpvOsd: Parameters<typeof copyCurrentSubtitleService>[0]["showMpvOsd"];
},
): Parameters<typeof copyCurrentSubtitleService>[0] {
return {
subtitleTimingTracker: options.subtitleTimingTracker,
writeClipboardText: options.writeClipboardText,
showMpvOsd: options.showMpvOsd,
};
}
export function createUpdateLastCardFromClipboardDepsRuntimeService(
options: {
ankiIntegration: Parameters<typeof updateLastCardFromClipboardService>[0]["ankiIntegration"];
readClipboardText: Parameters<typeof updateLastCardFromClipboardService>[0]["readClipboardText"];
showMpvOsd: Parameters<typeof updateLastCardFromClipboardService>[0]["showMpvOsd"];
},
): Parameters<typeof updateLastCardFromClipboardService>[0] {
return {
ankiIntegration: options.ankiIntegration,
readClipboardText: options.readClipboardText,
showMpvOsd: options.showMpvOsd,
};
}
export function createTriggerFieldGroupingDepsRuntimeService(
options: {
ankiIntegration: Parameters<typeof triggerFieldGroupingService>[0]["ankiIntegration"];
showMpvOsd: Parameters<typeof triggerFieldGroupingService>[0]["showMpvOsd"];
},
): Parameters<typeof triggerFieldGroupingService>[0] {
return {
ankiIntegration: options.ankiIntegration,
showMpvOsd: options.showMpvOsd,
};
}
export function createMarkLastCardAsAudioCardDepsRuntimeService(
options: {
ankiIntegration: Parameters<typeof markLastCardAsAudioCardService>[0]["ankiIntegration"];
showMpvOsd: Parameters<typeof markLastCardAsAudioCardService>[0]["showMpvOsd"];
},
): Parameters<typeof markLastCardAsAudioCardService>[0] {
return {
ankiIntegration: options.ankiIntegration,
showMpvOsd: options.showMpvOsd,
};
}
export function createMineSentenceCardDepsRuntimeService(
options: {
ankiIntegration: Parameters<typeof mineSentenceCardService>[0]["ankiIntegration"];
mpvClient: Parameters<typeof mineSentenceCardService>[0]["mpvClient"];
showMpvOsd: Parameters<typeof mineSentenceCardService>[0]["showMpvOsd"];
},
): Parameters<typeof mineSentenceCardService>[0] {
return {
ankiIntegration: options.ankiIntegration,
mpvClient: options.mpvClient,
showMpvOsd: options.showMpvOsd,
};
}
export function createHandleMineSentenceDigitDepsRuntimeService(
options: {
subtitleTimingTracker: Parameters<typeof handleMineSentenceDigitService>[1]["subtitleTimingTracker"];
ankiIntegration: Parameters<typeof handleMineSentenceDigitService>[1]["ankiIntegration"];
getCurrentSecondarySubText: Parameters<typeof handleMineSentenceDigitService>[1]["getCurrentSecondarySubText"];
showMpvOsd: Parameters<typeof handleMineSentenceDigitService>[1]["showMpvOsd"];
logError: Parameters<typeof handleMineSentenceDigitService>[1]["logError"];
},
): Parameters<typeof handleMineSentenceDigitService>[1] {
return {
subtitleTimingTracker: options.subtitleTimingTracker,
ankiIntegration: options.ankiIntegration,
getCurrentSecondarySubText: options.getCurrentSecondarySubText,
showMpvOsd: options.showMpvOsd,
logError: options.logError,
};
}

View File

@@ -0,0 +1,52 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
createOverlayShortcutLifecycleDepsRuntimeService,
createOverlayShortcutRuntimeDepsService,
} from "./overlay-shortcut-runtime-deps-service";
test("createOverlayShortcutRuntimeDepsService returns callable runtime deps", async () => {
const calls: string[] = [];
const deps = createOverlayShortcutRuntimeDepsService({
showMpvOsd: () => calls.push("showMpvOsd"),
openRuntimeOptions: () => calls.push("openRuntimeOptions"),
openJimaku: () => calls.push("openJimaku"),
markAudioCard: async () => {
calls.push("markAudioCard");
},
copySubtitleMultiple: () => calls.push("copySubtitleMultiple"),
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: () => calls.push("mineSentenceMultiple"),
});
deps.copySubtitle();
await deps.mineSentence();
deps.mineSentenceMultiple(2);
assert.deepEqual(calls, ["copySubtitle", "mineSentence", "mineSentenceMultiple"]);
});
test("createOverlayShortcutLifecycleDepsRuntimeService returns lifecycle passthrough", () => {
const deps = createOverlayShortcutLifecycleDepsRuntimeService({
getConfiguredShortcuts: () => ({ actions: [] } as never),
getOverlayHandlers: () => ({} as never),
cancelPendingMultiCopy: () => {},
cancelPendingMineSentenceMultiple: () => {},
});
assert.ok(deps.getConfiguredShortcuts());
assert.ok(deps.getOverlayHandlers());
});

View File

@@ -0,0 +1,59 @@
import {
OverlayShortcutLifecycleDeps,
} from "./overlay-shortcut-lifecycle-service";
import {
OverlayShortcutRuntimeDeps,
} from "./overlay-shortcut-runtime-service";
export interface OverlayShortcutRuntimeDepsOptions {
showMpvOsd: (text: string) => void;
openRuntimeOptions: () => void;
openJimaku: () => void;
markAudioCard: () => Promise<void>;
copySubtitleMultiple: (timeoutMs: number) => void;
copySubtitle: () => void;
toggleSecondarySub: () => void;
updateLastCardFromClipboard: () => Promise<void>;
triggerFieldGrouping: () => Promise<void>;
triggerSubsync: () => Promise<void>;
mineSentence: () => Promise<void>;
mineSentenceMultiple: (timeoutMs: number) => void;
}
export interface OverlayShortcutLifecycleDepsOptions {
getConfiguredShortcuts: OverlayShortcutLifecycleDeps["getConfiguredShortcuts"];
getOverlayHandlers: OverlayShortcutLifecycleDeps["getOverlayHandlers"];
cancelPendingMultiCopy: () => void;
cancelPendingMineSentenceMultiple: () => void;
}
export function createOverlayShortcutRuntimeDepsService(
options: OverlayShortcutRuntimeDepsOptions,
): OverlayShortcutRuntimeDeps {
return {
showMpvOsd: options.showMpvOsd,
openRuntimeOptions: options.openRuntimeOptions,
openJimaku: options.openJimaku,
markAudioCard: options.markAudioCard,
copySubtitleMultiple: options.copySubtitleMultiple,
copySubtitle: options.copySubtitle,
toggleSecondarySub: options.toggleSecondarySub,
updateLastCardFromClipboard: options.updateLastCardFromClipboard,
triggerFieldGrouping: options.triggerFieldGrouping,
triggerSubsync: options.triggerSubsync,
mineSentence: options.mineSentence,
mineSentenceMultiple: options.mineSentenceMultiple,
};
}
export function createOverlayShortcutLifecycleDepsRuntimeService(
options: OverlayShortcutLifecycleDepsOptions,
): OverlayShortcutLifecycleDeps {
return {
getConfiguredShortcuts: options.getConfiguredShortcuts,
getOverlayHandlers: options.getOverlayHandlers,
cancelPendingMultiCopy: options.cancelPendingMultiCopy,
cancelPendingMineSentenceMultiple:
options.cancelPendingMineSentenceMultiple,
};
}