This commit is contained in:
2026-02-17 22:50:57 -08:00
parent ffeef9c136
commit f20d019c11
315 changed files with 9876 additions and 12537 deletions

View File

@@ -1,135 +1,118 @@
import test from "node:test";
import assert from "node:assert/strict";
import { AppReadyRuntimeDeps, runAppReadyRuntime } from "./startup";
import test from 'node:test';
import assert from 'node:assert/strict';
import { AppReadyRuntimeDeps, runAppReadyRuntime } from './startup';
function makeDeps(overrides: Partial<AppReadyRuntimeDeps> = {}) {
const calls: string[] = [];
const deps: AppReadyRuntimeDeps = {
loadSubtitlePosition: () => calls.push("loadSubtitlePosition"),
resolveKeybindings: () => calls.push("resolveKeybindings"),
createMpvClient: () => calls.push("createMpvClient"),
reloadConfig: () => calls.push("reloadConfig"),
loadSubtitlePosition: () => calls.push('loadSubtitlePosition'),
resolveKeybindings: () => calls.push('resolveKeybindings'),
createMpvClient: () => calls.push('createMpvClient'),
reloadConfig: () => calls.push('reloadConfig'),
getResolvedConfig: () => ({
websocket: { enabled: "auto" },
websocket: { enabled: 'auto' },
secondarySub: {},
}),
getConfigWarnings: () => [],
logConfigWarning: () => calls.push("logConfigWarning"),
setLogLevel: (level, source) =>
calls.push(`setLogLevel:${level}:${source}`),
initRuntimeOptionsManager: () => calls.push("initRuntimeOptionsManager"),
logConfigWarning: () => calls.push('logConfigWarning'),
setLogLevel: (level, source) => calls.push(`setLogLevel:${level}:${source}`),
initRuntimeOptionsManager: () => calls.push('initRuntimeOptionsManager'),
setSecondarySubMode: (mode) => calls.push(`setSecondarySubMode:${mode}`),
defaultSecondarySubMode: "hover",
defaultSecondarySubMode: 'hover',
defaultWebsocketPort: 9001,
hasMpvWebsocketPlugin: () => true,
startSubtitleWebsocket: (port) =>
calls.push(`startSubtitleWebsocket:${port}`),
startSubtitleWebsocket: (port) => calls.push(`startSubtitleWebsocket:${port}`),
log: (message) => calls.push(`log:${message}`),
createMecabTokenizerAndCheck: async () => {
calls.push("createMecabTokenizerAndCheck");
calls.push('createMecabTokenizerAndCheck');
},
createSubtitleTimingTracker: () =>
calls.push("createSubtitleTimingTracker"),
createImmersionTracker: () => calls.push("createImmersionTracker"),
createSubtitleTimingTracker: () => calls.push('createSubtitleTimingTracker'),
createImmersionTracker: () => calls.push('createImmersionTracker'),
startJellyfinRemoteSession: async () => {
calls.push("startJellyfinRemoteSession");
calls.push('startJellyfinRemoteSession');
},
loadYomitanExtension: async () => {
calls.push("loadYomitanExtension");
calls.push('loadYomitanExtension');
},
texthookerOnlyMode: false,
shouldAutoInitializeOverlayRuntimeFromConfig: () => true,
initializeOverlayRuntime: () => calls.push("initializeOverlayRuntime"),
handleInitialArgs: () => calls.push("handleInitialArgs"),
initializeOverlayRuntime: () => calls.push('initializeOverlayRuntime'),
handleInitialArgs: () => calls.push('handleInitialArgs'),
...overrides,
};
return { deps, calls };
}
test("runAppReadyRuntime starts websocket in auto mode when plugin missing", async () => {
test('runAppReadyRuntime starts websocket in auto mode when plugin missing', async () => {
const { deps, calls } = makeDeps({
hasMpvWebsocketPlugin: () => false,
});
await runAppReadyRuntime(deps);
assert.ok(calls.includes("startSubtitleWebsocket:9001"));
assert.ok(calls.includes("initializeOverlayRuntime"));
assert.ok(calls.includes("createImmersionTracker"));
assert.ok(calls.includes("startJellyfinRemoteSession"));
assert.ok(
calls.includes("log:Runtime ready: invoking createImmersionTracker."),
);
assert.ok(calls.includes('startSubtitleWebsocket:9001'));
assert.ok(calls.includes('initializeOverlayRuntime'));
assert.ok(calls.includes('createImmersionTracker'));
assert.ok(calls.includes('startJellyfinRemoteSession'));
assert.ok(calls.includes('log:Runtime ready: invoking createImmersionTracker.'));
});
test("runAppReadyRuntime skips Jellyfin remote startup when dependency is not wired", async () => {
test('runAppReadyRuntime skips Jellyfin remote startup when dependency is not wired', async () => {
const { deps, calls } = makeDeps({
startJellyfinRemoteSession: undefined,
});
await runAppReadyRuntime(deps);
assert.equal(calls.includes("startJellyfinRemoteSession"), false);
assert.ok(calls.includes("createMecabTokenizerAndCheck"));
assert.ok(calls.includes("createMpvClient"));
assert.ok(calls.includes("createSubtitleTimingTracker"));
assert.ok(calls.includes("handleInitialArgs"));
assert.equal(calls.includes('startJellyfinRemoteSession'), false);
assert.ok(calls.includes('createMecabTokenizerAndCheck'));
assert.ok(calls.includes('createMpvClient'));
assert.ok(calls.includes('createSubtitleTimingTracker'));
assert.ok(calls.includes('handleInitialArgs'));
assert.ok(
calls.includes("initializeOverlayRuntime") ||
calls.includes(
"log:Overlay runtime deferred: waiting for explicit overlay command.",
),
calls.includes('initializeOverlayRuntime') ||
calls.includes('log:Overlay runtime deferred: waiting for explicit overlay command.'),
);
});
test("runAppReadyRuntime logs when createImmersionTracker dependency is missing", async () => {
test('runAppReadyRuntime logs when createImmersionTracker dependency is missing', async () => {
const { deps, calls } = makeDeps({
createImmersionTracker: undefined,
});
await runAppReadyRuntime(deps);
assert.ok(
calls.includes(
"log:Runtime ready: createImmersionTracker dependency is missing.",
),
);
assert.ok(calls.includes('log:Runtime ready: createImmersionTracker dependency is missing.'));
});
test("runAppReadyRuntime logs and continues when createImmersionTracker throws", async () => {
test('runAppReadyRuntime logs and continues when createImmersionTracker throws', async () => {
const { deps, calls } = makeDeps({
createImmersionTracker: () => {
calls.push("createImmersionTracker");
throw new Error("immersion init failed");
calls.push('createImmersionTracker');
throw new Error('immersion init failed');
},
});
await runAppReadyRuntime(deps);
assert.ok(calls.includes("createImmersionTracker"));
assert.ok(calls.includes('createImmersionTracker'));
assert.ok(
calls.includes(
"log:Runtime ready: createImmersionTracker failed: immersion init failed",
),
calls.includes('log:Runtime ready: createImmersionTracker failed: immersion init failed'),
);
assert.ok(calls.includes("initializeOverlayRuntime"));
assert.ok(calls.includes("handleInitialArgs"));
assert.ok(calls.includes('initializeOverlayRuntime'));
assert.ok(calls.includes('handleInitialArgs'));
});
test("runAppReadyRuntime logs defer message when overlay not auto-started", async () => {
test('runAppReadyRuntime logs defer message when overlay not auto-started', async () => {
const { deps, calls } = makeDeps({
shouldAutoInitializeOverlayRuntimeFromConfig: () => false,
});
await runAppReadyRuntime(deps);
assert.ok(
calls.includes(
"log:Overlay runtime deferred: waiting for explicit overlay command.",
),
);
assert.ok(calls.includes('log:Overlay runtime deferred: waiting for explicit overlay command.'));
});
test("runAppReadyRuntime applies config logging level during app-ready", async () => {
test('runAppReadyRuntime applies config logging level during app-ready', async () => {
const { deps, calls } = makeDeps({
getResolvedConfig: () => ({
websocket: { enabled: "auto" },
websocket: { enabled: 'auto' },
secondarySub: {},
logging: { level: "warn" },
logging: { level: 'warn' },
}),
});
await runAppReadyRuntime(deps);
assert.ok(calls.includes("setLogLevel:warn:config"));
assert.ok(calls.includes('setLogLevel:warn:config'));
});