mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-28 06:22:45 -08:00
refactor: normalize remaining main runtime dependency setup
This commit is contained in:
@@ -3,8 +3,10 @@ import test from 'node:test';
|
||||
import type { ReloadConfigStrictResult } from '../../config';
|
||||
import type { ResolvedConfig } from '../../types';
|
||||
import {
|
||||
createBuildConfigHotReloadMessageMainDepsHandler,
|
||||
createBuildConfigHotReloadAppliedMainDepsHandler,
|
||||
createBuildConfigHotReloadRuntimeMainDepsHandler,
|
||||
createBuildWatchConfigPathMainDepsHandler,
|
||||
createWatchConfigPathHandler,
|
||||
} from './config-hot-reload-main-deps';
|
||||
|
||||
@@ -43,6 +45,45 @@ test('watch config path handler filters directory events to config files only',
|
||||
assert.deepEqual(calls, ['change', 'change', 'change']);
|
||||
});
|
||||
|
||||
test('watch config path main deps builder maps filesystem callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildWatchConfigPathMainDepsHandler({
|
||||
fileExists: () => true,
|
||||
dirname: (targetPath) => {
|
||||
calls.push(`dirname:${targetPath}`);
|
||||
return '/tmp';
|
||||
},
|
||||
watchPath: (targetPath, listener) => {
|
||||
calls.push(`watch:${targetPath}`);
|
||||
listener('change', 'config.jsonc');
|
||||
return { close: () => calls.push('close') };
|
||||
},
|
||||
})();
|
||||
|
||||
assert.equal(deps.fileExists('/tmp/config.jsonc'), true);
|
||||
assert.equal(deps.dirname('/tmp/config.jsonc'), '/tmp');
|
||||
const watcher = deps.watchPath('/tmp/config.jsonc', () => calls.push('listener'));
|
||||
watcher.close();
|
||||
assert.deepEqual(calls, [
|
||||
'dirname:/tmp/config.jsonc',
|
||||
'watch:/tmp/config.jsonc',
|
||||
'listener',
|
||||
'close',
|
||||
]);
|
||||
});
|
||||
|
||||
test('config hot reload message main deps builder maps notifications', () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildConfigHotReloadMessageMainDepsHandler({
|
||||
showMpvOsd: (message) => calls.push(`osd:${message}`),
|
||||
showDesktopNotification: (title) => calls.push(`notify:${title}`),
|
||||
})();
|
||||
|
||||
deps.showMpvOsd('updated');
|
||||
deps.showDesktopNotification('SubMiner', { body: 'updated' });
|
||||
assert.deepEqual(calls, ['osd:updated', 'notify:SubMiner']);
|
||||
});
|
||||
|
||||
test('config hot reload applied main deps builder maps callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildConfigHotReloadAppliedMainDepsHandler({
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
} from '../../core/services/config-hot-reload';
|
||||
import type { ReloadConfigStrictResult } from '../../config';
|
||||
import type { ConfigHotReloadPayload, ConfigValidationWarning, ResolvedConfig, SecondarySubMode } from '../../types';
|
||||
import type { createConfigHotReloadMessageHandler } from './config-hot-reload-handlers';
|
||||
|
||||
type ConfigWatchListener = (eventType: string, filename: string | null) => void;
|
||||
|
||||
@@ -32,6 +33,28 @@ export function createWatchConfigPathHandler(deps: {
|
||||
};
|
||||
}
|
||||
|
||||
type WatchConfigPathMainDeps = Parameters<typeof createWatchConfigPathHandler>[0];
|
||||
type ConfigHotReloadMessageMainDeps = Parameters<typeof createConfigHotReloadMessageHandler>[0];
|
||||
|
||||
export function createBuildWatchConfigPathMainDepsHandler(deps: WatchConfigPathMainDeps) {
|
||||
return (): WatchConfigPathMainDeps => ({
|
||||
fileExists: (targetPath: string) => deps.fileExists(targetPath),
|
||||
dirname: (targetPath: string) => deps.dirname(targetPath),
|
||||
watchPath: (targetPath: string, listener: ConfigWatchListener) =>
|
||||
deps.watchPath(targetPath, listener),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildConfigHotReloadMessageMainDepsHandler(
|
||||
deps: ConfigHotReloadMessageMainDeps,
|
||||
) {
|
||||
return (): ConfigHotReloadMessageMainDeps => ({
|
||||
showMpvOsd: (message: string) => deps.showMpvOsd(message),
|
||||
showDesktopNotification: (title: string, options: { body: string }) =>
|
||||
deps.showDesktopNotification(title, options),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildConfigHotReloadAppliedMainDepsHandler(deps: {
|
||||
setKeybindings: (keybindings: ConfigHotReloadPayload['keybindings']) => void;
|
||||
refreshGlobalAndOverlayShortcuts: () => void;
|
||||
|
||||
35
src/main/runtime/immersion-startup-main-deps.test.ts
Normal file
35
src/main/runtime/immersion-startup-main-deps.test.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createBuildImmersionTrackerStartupMainDepsHandler } from './immersion-startup-main-deps';
|
||||
|
||||
test('immersion tracker startup main deps builder maps callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildImmersionTrackerStartupMainDepsHandler({
|
||||
getResolvedConfig: () => ({ immersionTracking: { enabled: true } } as never),
|
||||
getConfiguredDbPath: () => '/tmp/immersion.db',
|
||||
createTrackerService: () => {
|
||||
calls.push('create');
|
||||
return { id: 'tracker' };
|
||||
},
|
||||
setTracker: () => calls.push('set'),
|
||||
getMpvClient: () => ({ connected: true, connect: () => {} }),
|
||||
seedTrackerFromCurrentMedia: () => calls.push('seed'),
|
||||
logInfo: (message) => calls.push(`info:${message}`),
|
||||
logDebug: (message) => calls.push(`debug:${message}`),
|
||||
logWarn: (message) => calls.push(`warn:${message}`),
|
||||
})();
|
||||
|
||||
assert.deepEqual(deps.getResolvedConfig(), { immersionTracking: { enabled: true } });
|
||||
assert.equal(deps.getConfiguredDbPath(), '/tmp/immersion.db');
|
||||
assert.deepEqual(deps.createTrackerService({ dbPath: '/tmp/immersion.db', policy: {} as never }), {
|
||||
id: 'tracker',
|
||||
});
|
||||
deps.setTracker(null);
|
||||
assert.equal(deps.getMpvClient()?.connected, true);
|
||||
deps.seedTrackerFromCurrentMedia();
|
||||
deps.logInfo('i');
|
||||
deps.logDebug('d');
|
||||
deps.logWarn('w', null);
|
||||
|
||||
assert.deepEqual(calls, ['create', 'set', 'seed', 'info:i', 'debug:d', 'warn:w']);
|
||||
});
|
||||
22
src/main/runtime/immersion-startup-main-deps.ts
Normal file
22
src/main/runtime/immersion-startup-main-deps.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type {
|
||||
ImmersionTrackerStartupDeps,
|
||||
createImmersionTrackerStartupHandler,
|
||||
} from './immersion-startup';
|
||||
|
||||
type ImmersionTrackerStartupMainDeps = Parameters<typeof createImmersionTrackerStartupHandler>[0];
|
||||
|
||||
export function createBuildImmersionTrackerStartupMainDepsHandler(
|
||||
deps: ImmersionTrackerStartupMainDeps,
|
||||
) {
|
||||
return (): ImmersionTrackerStartupDeps => ({
|
||||
getResolvedConfig: () => deps.getResolvedConfig(),
|
||||
getConfiguredDbPath: () => deps.getConfiguredDbPath(),
|
||||
createTrackerService: (params) => deps.createTrackerService(params),
|
||||
setTracker: (tracker) => deps.setTracker(tracker),
|
||||
getMpvClient: () => deps.getMpvClient(),
|
||||
seedTrackerFromCurrentMedia: () => deps.seedTrackerFromCurrentMedia(),
|
||||
logInfo: (message: string) => deps.logInfo(message),
|
||||
logDebug: (message: string) => deps.logDebug(message),
|
||||
logWarn: (message: string, details: unknown) => deps.logWarn(message, details),
|
||||
});
|
||||
}
|
||||
56
src/main/runtime/jellyfin-playback-launch-main-deps.test.ts
Normal file
56
src/main/runtime/jellyfin-playback-launch-main-deps.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createBuildPlayJellyfinItemInMpvMainDepsHandler } from './jellyfin-playback-launch-main-deps';
|
||||
|
||||
test('play jellyfin item in mpv main deps builder maps callbacks', async () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildPlayJellyfinItemInMpvMainDepsHandler({
|
||||
ensureMpvConnectedForPlayback: async () => true,
|
||||
getMpvClient: () => ({ connected: true }),
|
||||
resolvePlaybackPlan: async () => ({
|
||||
url: 'u',
|
||||
mode: 'direct',
|
||||
title: 't',
|
||||
startTimeTicks: 0,
|
||||
}),
|
||||
applyJellyfinMpvDefaults: () => calls.push('defaults'),
|
||||
sendMpvCommand: (command) => calls.push(`cmd:${command[0]}`),
|
||||
armQuitOnDisconnect: () => calls.push('arm'),
|
||||
schedule: (_callback, delayMs) => calls.push(`schedule:${delayMs}`),
|
||||
convertTicksToSeconds: (ticks) => ticks / 10_000_000,
|
||||
preloadExternalSubtitles: () => calls.push('preload'),
|
||||
setActivePlayback: () => calls.push('active'),
|
||||
setLastProgressAtMs: () => calls.push('progress'),
|
||||
reportPlaying: () => calls.push('report'),
|
||||
showMpvOsd: (text) => calls.push(`osd:${text}`),
|
||||
})();
|
||||
|
||||
assert.equal(await deps.ensureMpvConnectedForPlayback(), true);
|
||||
assert.equal(typeof deps.getMpvClient(), 'object');
|
||||
assert.deepEqual(
|
||||
await deps.resolvePlaybackPlan({ session: {} as never, clientInfo: {} as never, jellyfinConfig: {}, itemId: 'i' }),
|
||||
{ url: 'u', mode: 'direct', title: 't', startTimeTicks: 0 },
|
||||
);
|
||||
deps.applyJellyfinMpvDefaults({});
|
||||
deps.sendMpvCommand(['show-text', 'x']);
|
||||
deps.armQuitOnDisconnect();
|
||||
deps.schedule(() => {}, 500);
|
||||
assert.equal(deps.convertTicksToSeconds(20_000_000), 2);
|
||||
deps.preloadExternalSubtitles({ session: {} as never, clientInfo: {} as never, itemId: 'i' });
|
||||
deps.setActivePlayback({ itemId: 'i', mediaSourceId: undefined, playMethod: 'DirectPlay' });
|
||||
deps.setLastProgressAtMs(0);
|
||||
deps.reportPlaying({ itemId: 'i', mediaSourceId: undefined, playMethod: 'DirectPlay', eventName: 'start' });
|
||||
deps.showMpvOsd('ok');
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
'defaults',
|
||||
'cmd:show-text',
|
||||
'arm',
|
||||
'schedule:500',
|
||||
'preload',
|
||||
'active',
|
||||
'progress',
|
||||
'report',
|
||||
'osd:ok',
|
||||
]);
|
||||
});
|
||||
21
src/main/runtime/jellyfin-playback-launch-main-deps.ts
Normal file
21
src/main/runtime/jellyfin-playback-launch-main-deps.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { createPlayJellyfinItemInMpvHandler } from './jellyfin-playback-launch';
|
||||
|
||||
type PlayJellyfinItemInMpvMainDeps = Parameters<typeof createPlayJellyfinItemInMpvHandler>[0];
|
||||
|
||||
export function createBuildPlayJellyfinItemInMpvMainDepsHandler(deps: PlayJellyfinItemInMpvMainDeps) {
|
||||
return (): PlayJellyfinItemInMpvMainDeps => ({
|
||||
ensureMpvConnectedForPlayback: () => deps.ensureMpvConnectedForPlayback(),
|
||||
getMpvClient: () => deps.getMpvClient(),
|
||||
resolvePlaybackPlan: (params) => deps.resolvePlaybackPlan(params),
|
||||
applyJellyfinMpvDefaults: (mpvClient) => deps.applyJellyfinMpvDefaults(mpvClient),
|
||||
sendMpvCommand: (command: Array<string | number>) => deps.sendMpvCommand(command),
|
||||
armQuitOnDisconnect: () => deps.armQuitOnDisconnect(),
|
||||
schedule: (callback: () => void, delayMs: number) => deps.schedule(callback, delayMs),
|
||||
convertTicksToSeconds: (ticks: number) => deps.convertTicksToSeconds(ticks),
|
||||
preloadExternalSubtitles: (params) => deps.preloadExternalSubtitles(params),
|
||||
setActivePlayback: (state) => deps.setActivePlayback(state),
|
||||
setLastProgressAtMs: (value: number) => deps.setLastProgressAtMs(value),
|
||||
reportPlaying: (payload) => deps.reportPlaying(payload),
|
||||
showMpvOsd: (text: string) => deps.showMpvOsd(text),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import type { MpvSubtitleRenderMetrics } from '../../types';
|
||||
import { createBuildUpdateMpvSubtitleRenderMetricsMainDepsHandler } from './mpv-subtitle-render-metrics-main-deps';
|
||||
|
||||
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('mpv subtitle render metrics main deps builder maps callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildUpdateMpvSubtitleRenderMetricsMainDepsHandler({
|
||||
getCurrentMetrics: () => BASE_METRICS,
|
||||
setCurrentMetrics: () => calls.push('set'),
|
||||
applyPatch: (current, patch) => {
|
||||
calls.push('apply');
|
||||
return { next: { ...current, ...patch }, changed: true };
|
||||
},
|
||||
broadcastMetrics: () => calls.push('broadcast'),
|
||||
})();
|
||||
|
||||
assert.equal(deps.getCurrentMetrics().subPos, 100);
|
||||
deps.setCurrentMetrics(BASE_METRICS);
|
||||
const patched = deps.applyPatch(BASE_METRICS, { subPos: 90 });
|
||||
deps.broadcastMetrics(BASE_METRICS);
|
||||
|
||||
assert.equal(patched.changed, true);
|
||||
assert.equal(patched.next.subPos, 90);
|
||||
assert.deepEqual(calls, ['set', 'apply', 'broadcast']);
|
||||
});
|
||||
14
src/main/runtime/mpv-subtitle-render-metrics-main-deps.ts
Normal file
14
src/main/runtime/mpv-subtitle-render-metrics-main-deps.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { createUpdateMpvSubtitleRenderMetricsHandler } from './mpv-subtitle-render-metrics';
|
||||
|
||||
type UpdateMpvSubtitleRenderMetricsMainDeps = Parameters<typeof createUpdateMpvSubtitleRenderMetricsHandler>[0];
|
||||
|
||||
export function createBuildUpdateMpvSubtitleRenderMetricsMainDepsHandler(
|
||||
deps: UpdateMpvSubtitleRenderMetricsMainDeps,
|
||||
) {
|
||||
return (): UpdateMpvSubtitleRenderMetricsMainDeps => ({
|
||||
getCurrentMetrics: () => deps.getCurrentMetrics(),
|
||||
setCurrentMetrics: (metrics) => deps.setCurrentMetrics(metrics),
|
||||
applyPatch: (current, patch) => deps.applyPatch(current, patch),
|
||||
broadcastMetrics: (metrics) => deps.broadcastMetrics(metrics),
|
||||
});
|
||||
}
|
||||
32
src/main/runtime/numeric-shortcut-runtime-main-deps.test.ts
Normal file
32
src/main/runtime/numeric-shortcut-runtime-main-deps.test.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createBuildNumericShortcutRuntimeMainDepsHandler } from './numeric-shortcut-runtime-main-deps';
|
||||
|
||||
test('numeric shortcut runtime main deps builder maps callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildNumericShortcutRuntimeMainDepsHandler({
|
||||
globalShortcut: {
|
||||
register: () => true,
|
||||
unregister: () => {
|
||||
calls.push('unregister');
|
||||
},
|
||||
},
|
||||
showMpvOsd: (text) => calls.push(`osd:${text}`),
|
||||
setTimer: (handler) => {
|
||||
calls.push('timer');
|
||||
handler();
|
||||
return 1 as never;
|
||||
},
|
||||
clearTimer: () => {
|
||||
calls.push('clear');
|
||||
},
|
||||
})();
|
||||
|
||||
assert.equal(deps.globalShortcut.register('1', () => {}), true);
|
||||
deps.globalShortcut.unregister('1');
|
||||
deps.showMpvOsd('x');
|
||||
deps.setTimer(() => calls.push('tick'), 1000);
|
||||
deps.clearTimer(1 as never);
|
||||
|
||||
assert.deepEqual(calls, ['unregister', 'osd:x', 'timer', 'tick', 'clear']);
|
||||
});
|
||||
10
src/main/runtime/numeric-shortcut-runtime-main-deps.ts
Normal file
10
src/main/runtime/numeric-shortcut-runtime-main-deps.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { NumericShortcutRuntimeOptions } from '../../core/services/numeric-shortcut';
|
||||
|
||||
export function createBuildNumericShortcutRuntimeMainDepsHandler(deps: NumericShortcutRuntimeOptions) {
|
||||
return (): NumericShortcutRuntimeOptions => ({
|
||||
globalShortcut: deps.globalShortcut,
|
||||
showMpvOsd: (text: string) => deps.showMpvOsd(text),
|
||||
setTimer: (handler: () => void, timeoutMs: number) => deps.setTimer(handler, timeoutMs),
|
||||
clearTimer: (timer: ReturnType<typeof setTimeout>) => deps.clearTimer(timer),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user