mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
refactor: extract additional main dependency builders
This commit is contained in:
@@ -6,7 +6,7 @@ Read first. Keep concise.
|
||||
| ------------ | -------------- | ---------------------------------------------------- | --------- | ------------------------------------- | ---------------------- |
|
||||
| `codex-main` | `planner-exec` | `Fix frequency/N+1 regression in plugin --start flow` | `in_progress` | `docs/subagents/agents/codex-main.md` | `2026-02-19T19:36:46Z` |
|
||||
| `codex-config-validation-20260219T172015Z-iiyf` | `codex-config-validation` | `Find root cause of config validation error for ~/.config/SubMiner/config.jsonc` | `completed` | `docs/subagents/agents/codex-config-validation-20260219T172015Z-iiyf.md` | `2026-02-19T17:26:17Z` |
|
||||
| `codex-task85-20260219T233711Z-46hc` | `codex-task85` | `Resume TASK-85 maintainability refactor from latest handoff point` | `in_progress` | `docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md` | `2026-02-20T08:00:02Z` |
|
||||
-02-20T08:44:42Z` |
|
||||
| `codex-anilist-deeplink-20260219T233926Z` | `anilist-deeplink` | `Fix external subminer:// AniList callback handling from browser` | `done` | `docs/subagents/agents/codex-anilist-deeplink-20260219T233926Z.md` | `2026-02-19T23:59:21Z` |
|
||||
| `codex-texthooker-highlights-20260220T002354Z-927c` | `codex-texthooker-highlights` | `Add optional texthooker highlight toggles for known/n+1/frequency/JLPT` | `completed` | `docs/subagents/agents/codex-texthooker-highlights-20260220T002354Z-927c.md` | `2026-02-20T00:30:49Z` |
|
||||
| `codex-texthooker-ui-playwright-20260220T003827Z-k3p9` | `codex-texthooker-ui-playwright` | `Run Playwright MCP smoke/regression checks for texthooker-ui changes` | `completed` | `docs/subagents/agents/codex-texthooker-ui-playwright-20260220T003827Z-k3p9.md` | `2026-02-20T00:42:09Z` |
|
||||
|
||||
@@ -9,6 +9,22 @@
|
||||
|
||||
## Current Work (newest first)
|
||||
|
||||
- [2026-02-20T08:34:16Z] progress: extracted field-grouping resolver deps assembly into `src/main/runtime/field-grouping-resolver-main-deps.ts` and rewired `getFieldGroupingResolver`/`setFieldGroupingResolver` handler construction in `src/main.ts`.
|
||||
- [2026-02-20T08:34:16Z] progress: extracted Yomitan extension loader deps assembly into `src/main/runtime/yomitan-extension-loader-main-deps.ts` and rewired `loadYomitanExtension`/`ensureYomitanExtensionLoaded` handler construction in `src/main.ts`.
|
||||
- [2026-02-20T08:34:16Z] progress: added parity tests in `src/main/runtime/field-grouping-resolver-main-deps.test.ts` and `src/main/runtime/yomitan-extension-loader-main-deps.test.ts`; `src/main.ts` now 2993 LOC.
|
||||
- [2026-02-20T08:34:16Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/yomitan-extension-loader-main-deps.test.js dist/main/runtime/yomitan-extension-loader.test.js dist/main/runtime/field-grouping-resolver-main-deps.test.js dist/main/runtime/anilist-token-refresh-main-deps.test.js` pass (7/7).
|
||||
- [2026-02-20T08:31:52Z] progress: extracted AniList media-state deps assembly into `src/main/runtime/anilist-media-state-main-deps.ts` and rewired media-key/media-guess state handlers in `src/main.ts`.
|
||||
- [2026-02-20T08:31:52Z] progress: extracted subtitle-position deps assembly into `src/main/runtime/subtitle-position-main-deps.ts` and rewired load/save subtitle position handlers in `src/main.ts`.
|
||||
- [2026-02-20T08:31:52Z] progress: extracted AniList token-refresh deps assembly into `src/main/runtime/anilist-token-refresh-main-deps.ts` and rewired `refreshAnilistClientSecretState` handler construction in `src/main.ts`.
|
||||
- [2026-02-20T08:31:52Z] progress: added parity tests in `src/main/runtime/anilist-media-state-main-deps.test.ts`, `src/main/runtime/subtitle-position-main-deps.test.ts`, and `src/main/runtime/anilist-token-refresh-main-deps.test.ts`; `src/main.ts` now 2968 LOC.
|
||||
- [2026-02-20T08:31:52Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/anilist-token-refresh-main-deps.test.js dist/main/runtime/anilist-token-refresh.test.js dist/main/runtime/anilist-media-state-main-deps.test.js dist/main/runtime/subtitle-position-main-deps.test.js dist/main/runtime/jellyfin-subtitle-preload-main-deps.test.js` pass (12/12).
|
||||
- [2026-02-20T08:12:54Z] progress: extracted Jellyfin subtitle-preload deps assembly into `src/main/runtime/jellyfin-subtitle-preload-main-deps.ts` and rewired `preloadJellyfinExternalSubtitles` construction in `src/main.ts`.
|
||||
- [2026-02-20T08:12:54Z] progress: added parity tests in `src/main/runtime/jellyfin-subtitle-preload-main-deps.test.ts`; `src/main.ts` now 2926 LOC.
|
||||
- [2026-02-20T08:12:54Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/jellyfin-subtitle-preload-main-deps.test.js dist/main/runtime/jellyfin-subtitle-preload.test.js dist/main/runtime/jellyfin-remote-session-main-deps.test.js` pass (6/6).
|
||||
- [2026-02-20T08:11:29Z] progress: committed checkpoint `a85b6c2` (`refactor: extract additional main runtime dependency builders`) and continued with Jellyfin remote-session deps extraction.
|
||||
- [2026-02-20T08:11:29Z] progress: extracted start/stop Jellyfin remote-session deps assembly into `src/main/runtime/jellyfin-remote-session-main-deps.ts` and rewired those constructor sites in `src/main.ts`.
|
||||
- [2026-02-20T08:11:29Z] progress: added parity tests in `src/main/runtime/jellyfin-remote-session-main-deps.test.ts`; `src/main.ts` now 2921 LOC.
|
||||
- [2026-02-20T08:11:29Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/jellyfin-remote-session-main-deps.test.js dist/main/runtime/jellyfin-remote-session-lifecycle.test.js dist/main/runtime/jellyfin-command-dispatch-main-deps.test.js` pass (5/5).
|
||||
- [2026-02-20T08:00:02Z] progress: extracted Jellyfin command-dispatch deps assembly into `src/main/runtime/jellyfin-command-dispatch-main-deps.ts` (`createBuildRunJellyfinCommandMainDepsHandler`) and rewired `runJellyfinCommand` construction in `src/main.ts`.
|
||||
- [2026-02-20T08:00:02Z] progress: added parity tests in `src/main/runtime/jellyfin-command-dispatch-main-deps.test.ts`; `src/main.ts` now 2909 LOC.
|
||||
- [2026-02-20T08:00:02Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/jellyfin-command-dispatch-main-deps.test.js dist/main/runtime/jellyfin-command-dispatch.test.js dist/main/runtime/jellyfin-cli-main-deps.test.js` pass (8/8).
|
||||
@@ -353,3 +369,24 @@
|
||||
- [2026-02-20T07:27:15Z] progress: extracted overlay-shortcuts runtime deps assembly into `src/main/runtime/overlay-shortcuts-runtime-main-deps.ts` and rewired `createOverlayShortcutsRuntimeService` setup in `src/main.ts` through the builder.
|
||||
- [2026-02-20T07:27:15Z] progress: `src/main.ts` currently 2750 LOC after this slice.
|
||||
- [2026-02-20T07:27:15Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/overlay-shortcuts-runtime-main-deps.test.js dist/main/overlay-shortcuts-runtime.test.js dist/main/runtime/overlay-shortcuts-lifecycle.test.js` pass (5/5).
|
||||
- [2026-02-20T08:39:19Z] progress: extracted setup-window dependency assembly from `src/main.ts` into `src/main/runtime/anilist-setup-window-main-deps.ts` and `src/main/runtime/jellyfin-setup-window-main-deps.ts`; rewired `openAnilistSetupWindow` + `openJellyfinSetupWindow` to builder-backed handlers.
|
||||
- [2026-02-20T08:39:19Z] progress: added builder mapping tests in `src/main/runtime/anilist-setup-window-main-deps.test.ts` and `src/main/runtime/jellyfin-setup-window-main-deps.test.ts`.
|
||||
- [2026-02-20T08:39:19Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/anilist-setup-window-main-deps.test.js dist/main/runtime/jellyfin-setup-window-main-deps.test.js dist/main/runtime/anilist-setup-window.test.js dist/main/runtime/jellyfin-setup-window.test.js` pass.
|
||||
- [2026-02-20T08:44:42Z] progress: extracted AniList media-guess/post-watch dependency assemblies into `src/main/runtime/anilist-media-guess-main-deps.ts` + `src/main/runtime/anilist-post-watch-main-deps.ts`; rewired `main.ts` (`maybeProbeAnilistDuration`, `ensureAnilistMediaGuess`, `processNextAnilistRetryUpdate`, `maybeRunAnilistPostWatchUpdate`) to builder-backed setup.
|
||||
- [2026-02-20T08:44:42Z] progress: extracted Anki/mining action dependency assemblies into `src/main/runtime/anki-actions-main-deps.ts` + `src/main/runtime/mining-actions-main-deps.ts`; rewired corresponding handler creation block in `main.ts`.
|
||||
- [2026-02-20T08:44:42Z] progress: added mapping tests for all new builders (`anilist-media-guess-main-deps`, `anilist-post-watch-main-deps`, `anki-actions-main-deps`, `mining-actions-main-deps`).
|
||||
- [2026-02-20T08:44:42Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/anilist-media-guess-main-deps.test.js dist/main/runtime/anilist-post-watch-main-deps.test.js dist/main/runtime/anilist-media-guess.test.js dist/main/runtime/anilist-post-watch.test.js dist/main/runtime/anki-actions-main-deps.test.js dist/main/runtime/mining-actions-main-deps.test.js dist/main/runtime/anki-actions.test.js dist/main/runtime/mining-actions.test.js` pass.
|
||||
- [2026-02-20T08:52:08Z] progress: extracted overlay visibility/main action and IPC-bridge dependency assembly from `main.ts` into new builders: `overlay-visibility-actions-main-deps.ts`, `overlay-main-actions-main-deps.ts`, `ipc-bridge-actions-main-deps.ts`.
|
||||
- [2026-02-20T08:52:08Z] progress: rewired `main.ts` handler setup for `set/toggle overlay`, `appendClipboardVideoToQueue`, `handleMpvCommandFromIpc`, and `runSubsyncManualFromIpc` to builder-backed assembly.
|
||||
- [2026-02-20T08:52:08Z] progress: added builder mapping tests: `overlay-visibility-actions-main-deps.test.ts`, `overlay-main-actions-main-deps.test.ts`, `ipc-bridge-actions-main-deps.test.ts`.
|
||||
- [2026-02-20T08:52:08Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/ipc-bridge-actions-main-deps.test.js dist/main/runtime/overlay-visibility-actions-main-deps.test.js dist/main/runtime/overlay-main-actions-main-deps.test.js dist/main/runtime/ipc-bridge-actions.test.js dist/main/runtime/overlay-visibility-actions.test.js dist/main/runtime/overlay-main-actions.test.js` pass.
|
||||
- [2026-02-20T08:54:17Z] progress: extracted numeric shortcut and overlay-shortcuts lifecycle dependency assembly into `numeric-shortcut-session-main-deps.ts` and `overlay-shortcuts-lifecycle-main-deps.ts`; rewired corresponding `main.ts` setup blocks.
|
||||
- [2026-02-20T08:54:17Z] progress: extracted overlay-window-layout dependency assembly into `overlay-window-layout-main-deps.ts`; rewired visible/invisible bounds + window level/order setup in `main.ts`.
|
||||
- [2026-02-20T08:54:17Z] progress: added mapping tests for all new builders (`numeric-shortcut-session-main-deps`, `overlay-shortcuts-lifecycle-main-deps`, `overlay-window-layout-main-deps`).
|
||||
- [2026-02-20T08:54:17Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/numeric-shortcut-session-main-deps.test.js dist/main/runtime/overlay-shortcuts-lifecycle-main-deps.test.js dist/main/runtime/overlay-window-layout-main-deps.test.js dist/main/runtime/numeric-shortcut-session-handlers.test.js dist/main/runtime/overlay-shortcuts-lifecycle.test.js dist/main/runtime/overlay-window-layout.test.js` pass.
|
||||
- [2026-02-20T08:55:21Z] progress: extracted startup warmup dependency assembly into `src/main/runtime/startup-warmups-main-deps.ts` (`launchBackgroundWarmupTask`, `startBackgroundWarmups`) and rewired main setup to builder-backed constants.
|
||||
- [2026-02-20T08:55:21Z] progress: added `src/main/runtime/startup-warmups-main-deps.test.ts` mapping coverage.
|
||||
- [2026-02-20T08:55:21Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/startup-warmups-main-deps.test.js dist/main/runtime/startup-warmups.test.js` pass.
|
||||
- [2026-02-20T08:56:46Z] progress: extracted MPV IPC command dependency assembly into `src/main/runtime/ipc-mpv-command-main-deps.ts` and rewired `main.ts` IPC bridge setup to compose through `buildMpvCommandFromIpcRuntimeMainDepsHandler`.
|
||||
- [2026-02-20T08:56:46Z] progress: added `src/main/runtime/ipc-mpv-command-main-deps.test.ts` mapping coverage.
|
||||
- [2026-02-20T08:56:46Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/ipc-mpv-command-main-deps.test.js dist/main/runtime/ipc-bridge-actions-main-deps.test.js dist/main/runtime/ipc-bridge-actions.test.js` pass.
|
||||
|
||||
619
src/main.ts
619
src/main.ts
File diff suppressed because it is too large
Load Diff
78
src/main/runtime/anilist-media-guess-main-deps.test.ts
Normal file
78
src/main/runtime/anilist-media-guess-main-deps.test.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildEnsureAnilistMediaGuessMainDepsHandler,
|
||||
createBuildMaybeProbeAnilistDurationMainDepsHandler,
|
||||
} from './anilist-media-guess-main-deps';
|
||||
|
||||
test('maybe probe anilist duration main deps builder maps callbacks', async () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildMaybeProbeAnilistDurationMainDepsHandler({
|
||||
getState: () => ({
|
||||
mediaKey: 'm',
|
||||
mediaDurationSec: null,
|
||||
mediaGuess: null,
|
||||
mediaGuessPromise: null,
|
||||
lastDurationProbeAtMs: 0,
|
||||
}),
|
||||
setState: () => calls.push('set-state'),
|
||||
durationRetryIntervalMs: 1000,
|
||||
now: () => 42,
|
||||
requestMpvDuration: async () => 3600,
|
||||
logWarn: (message) => calls.push(`warn:${message}`),
|
||||
})();
|
||||
|
||||
assert.equal(deps.durationRetryIntervalMs, 1000);
|
||||
assert.equal(deps.now(), 42);
|
||||
assert.equal(await deps.requestMpvDuration(), 3600);
|
||||
deps.setState({
|
||||
mediaKey: 'm',
|
||||
mediaDurationSec: 100,
|
||||
mediaGuess: null,
|
||||
mediaGuessPromise: null,
|
||||
lastDurationProbeAtMs: 0,
|
||||
});
|
||||
deps.logWarn('oops', null);
|
||||
assert.deepEqual(calls, ['set-state', 'warn:oops']);
|
||||
});
|
||||
|
||||
test('ensure anilist media guess main deps builder maps callbacks', async () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildEnsureAnilistMediaGuessMainDepsHandler({
|
||||
getState: () => ({
|
||||
mediaKey: 'm',
|
||||
mediaDurationSec: null,
|
||||
mediaGuess: null,
|
||||
mediaGuessPromise: null,
|
||||
lastDurationProbeAtMs: 0,
|
||||
}),
|
||||
setState: () => calls.push('set-state'),
|
||||
resolveMediaPathForJimaku: (path) => {
|
||||
calls.push('resolve');
|
||||
return path;
|
||||
},
|
||||
getCurrentMediaPath: () => '/tmp/video.mkv',
|
||||
getCurrentMediaTitle: () => 'title',
|
||||
guessAnilistMediaInfo: async () => {
|
||||
calls.push('guess');
|
||||
return { title: 'title', episode: 1, source: 'fallback' };
|
||||
},
|
||||
})();
|
||||
|
||||
assert.equal(deps.getCurrentMediaPath(), '/tmp/video.mkv');
|
||||
assert.equal(deps.getCurrentMediaTitle(), 'title');
|
||||
assert.equal(deps.resolveMediaPathForJimaku('/tmp/video.mkv'), '/tmp/video.mkv');
|
||||
assert.deepEqual(await deps.guessAnilistMediaInfo('/tmp/video.mkv', 'title'), {
|
||||
title: 'title',
|
||||
episode: 1,
|
||||
source: 'fallback',
|
||||
});
|
||||
deps.setState({
|
||||
mediaKey: 'm',
|
||||
mediaDurationSec: null,
|
||||
mediaGuess: null,
|
||||
mediaGuessPromise: null,
|
||||
lastDurationProbeAtMs: 0,
|
||||
});
|
||||
assert.deepEqual(calls, ['resolve', 'guess', 'set-state']);
|
||||
});
|
||||
31
src/main/runtime/anilist-media-guess-main-deps.ts
Normal file
31
src/main/runtime/anilist-media-guess-main-deps.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type {
|
||||
createEnsureAnilistMediaGuessHandler,
|
||||
createMaybeProbeAnilistDurationHandler,
|
||||
} from './anilist-media-guess';
|
||||
|
||||
type MaybeProbeAnilistDurationMainDeps = Parameters<typeof createMaybeProbeAnilistDurationHandler>[0];
|
||||
type EnsureAnilistMediaGuessMainDeps = Parameters<typeof createEnsureAnilistMediaGuessHandler>[0];
|
||||
|
||||
export function createBuildMaybeProbeAnilistDurationMainDepsHandler(
|
||||
deps: MaybeProbeAnilistDurationMainDeps,
|
||||
) {
|
||||
return (): MaybeProbeAnilistDurationMainDeps => ({
|
||||
getState: () => deps.getState(),
|
||||
setState: (state) => deps.setState(state),
|
||||
durationRetryIntervalMs: deps.durationRetryIntervalMs,
|
||||
now: () => deps.now(),
|
||||
requestMpvDuration: () => deps.requestMpvDuration(),
|
||||
logWarn: (message: string, error: unknown) => deps.logWarn(message, error),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildEnsureAnilistMediaGuessMainDepsHandler(deps: EnsureAnilistMediaGuessMainDeps) {
|
||||
return (): EnsureAnilistMediaGuessMainDeps => ({
|
||||
getState: () => deps.getState(),
|
||||
setState: (state) => deps.setState(state),
|
||||
resolveMediaPathForJimaku: (currentMediaPath) => deps.resolveMediaPathForJimaku(currentMediaPath),
|
||||
getCurrentMediaPath: () => deps.getCurrentMediaPath(),
|
||||
getCurrentMediaTitle: () => deps.getCurrentMediaTitle(),
|
||||
guessAnilistMediaInfo: (mediaPath, mediaTitle) => deps.guessAnilistMediaInfo(mediaPath, mediaTitle),
|
||||
});
|
||||
}
|
||||
72
src/main/runtime/anilist-media-state-main-deps.test.ts
Normal file
72
src/main/runtime/anilist-media-state-main-deps.test.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildGetAnilistMediaGuessRuntimeStateMainDepsHandler,
|
||||
createBuildGetCurrentAnilistMediaKeyMainDepsHandler,
|
||||
createBuildResetAnilistMediaGuessStateMainDepsHandler,
|
||||
createBuildResetAnilistMediaTrackingMainDepsHandler,
|
||||
createBuildSetAnilistMediaGuessRuntimeStateMainDepsHandler,
|
||||
} from './anilist-media-state-main-deps';
|
||||
|
||||
test('get current anilist media key main deps builder maps callbacks', () => {
|
||||
const deps = createBuildGetCurrentAnilistMediaKeyMainDepsHandler({
|
||||
getCurrentMediaPath: () => '/tmp/video.mkv',
|
||||
})();
|
||||
assert.equal(deps.getCurrentMediaPath(), '/tmp/video.mkv');
|
||||
});
|
||||
|
||||
test('reset anilist media tracking main deps builder maps callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildResetAnilistMediaTrackingMainDepsHandler({
|
||||
setMediaKey: () => calls.push('key'),
|
||||
setMediaDurationSec: () => calls.push('duration'),
|
||||
setMediaGuess: () => calls.push('guess'),
|
||||
setMediaGuessPromise: () => calls.push('promise'),
|
||||
setLastDurationProbeAtMs: () => calls.push('probe'),
|
||||
})();
|
||||
deps.setMediaKey(null);
|
||||
deps.setMediaDurationSec(null);
|
||||
deps.setMediaGuess(null);
|
||||
deps.setMediaGuessPromise(null);
|
||||
deps.setLastDurationProbeAtMs(0);
|
||||
assert.deepEqual(calls, ['key', 'duration', 'guess', 'promise', 'probe']);
|
||||
});
|
||||
|
||||
test('get/set anilist media guess runtime state main deps builders map callbacks', () => {
|
||||
const getter = createBuildGetAnilistMediaGuessRuntimeStateMainDepsHandler({
|
||||
getMediaKey: () => '/tmp/video.mkv',
|
||||
getMediaDurationSec: () => 24,
|
||||
getMediaGuess: () => ({ title: 'X' }) as never,
|
||||
getMediaGuessPromise: () => Promise.resolve(null) as never,
|
||||
getLastDurationProbeAtMs: () => 123,
|
||||
})();
|
||||
assert.equal(getter.getMediaKey(), '/tmp/video.mkv');
|
||||
assert.equal(getter.getMediaDurationSec(), 24);
|
||||
assert.equal(getter.getLastDurationProbeAtMs(), 123);
|
||||
|
||||
const calls: string[] = [];
|
||||
const setter = createBuildSetAnilistMediaGuessRuntimeStateMainDepsHandler({
|
||||
setMediaKey: () => calls.push('key'),
|
||||
setMediaDurationSec: () => calls.push('duration'),
|
||||
setMediaGuess: () => calls.push('guess'),
|
||||
setMediaGuessPromise: () => calls.push('promise'),
|
||||
setLastDurationProbeAtMs: () => calls.push('probe'),
|
||||
})();
|
||||
setter.setMediaKey(null);
|
||||
setter.setMediaDurationSec(null);
|
||||
setter.setMediaGuess(null);
|
||||
setter.setMediaGuessPromise(null);
|
||||
setter.setLastDurationProbeAtMs(0);
|
||||
assert.deepEqual(calls, ['key', 'duration', 'guess', 'promise', 'probe']);
|
||||
});
|
||||
|
||||
test('reset anilist media guess state main deps builder maps callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildResetAnilistMediaGuessStateMainDepsHandler({
|
||||
setMediaGuess: () => calls.push('guess'),
|
||||
setMediaGuessPromise: () => calls.push('promise'),
|
||||
})();
|
||||
deps.setMediaGuess(null);
|
||||
deps.setMediaGuessPromise(null);
|
||||
assert.deepEqual(calls, ['guess', 'promise']);
|
||||
});
|
||||
72
src/main/runtime/anilist-media-state-main-deps.ts
Normal file
72
src/main/runtime/anilist-media-state-main-deps.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type {
|
||||
createGetAnilistMediaGuessRuntimeStateHandler,
|
||||
createGetCurrentAnilistMediaKeyHandler,
|
||||
createResetAnilistMediaGuessStateHandler,
|
||||
createResetAnilistMediaTrackingHandler,
|
||||
createSetAnilistMediaGuessRuntimeStateHandler,
|
||||
} from './anilist-media-state';
|
||||
|
||||
type GetCurrentAnilistMediaKeyMainDeps = Parameters<typeof createGetCurrentAnilistMediaKeyHandler>[0];
|
||||
type ResetAnilistMediaTrackingMainDeps = Parameters<typeof createResetAnilistMediaTrackingHandler>[0];
|
||||
type GetAnilistMediaGuessRuntimeStateMainDeps = Parameters<
|
||||
typeof createGetAnilistMediaGuessRuntimeStateHandler
|
||||
>[0];
|
||||
type SetAnilistMediaGuessRuntimeStateMainDeps = Parameters<
|
||||
typeof createSetAnilistMediaGuessRuntimeStateHandler
|
||||
>[0];
|
||||
type ResetAnilistMediaGuessStateMainDeps = Parameters<
|
||||
typeof createResetAnilistMediaGuessStateHandler
|
||||
>[0];
|
||||
|
||||
export function createBuildGetCurrentAnilistMediaKeyMainDepsHandler(
|
||||
deps: GetCurrentAnilistMediaKeyMainDeps,
|
||||
) {
|
||||
return (): GetCurrentAnilistMediaKeyMainDeps => ({
|
||||
getCurrentMediaPath: () => deps.getCurrentMediaPath(),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildResetAnilistMediaTrackingMainDepsHandler(
|
||||
deps: ResetAnilistMediaTrackingMainDeps,
|
||||
) {
|
||||
return (): ResetAnilistMediaTrackingMainDeps => ({
|
||||
setMediaKey: (value) => deps.setMediaKey(value),
|
||||
setMediaDurationSec: (value) => deps.setMediaDurationSec(value),
|
||||
setMediaGuess: (value) => deps.setMediaGuess(value),
|
||||
setMediaGuessPromise: (value) => deps.setMediaGuessPromise(value),
|
||||
setLastDurationProbeAtMs: (value: number) => deps.setLastDurationProbeAtMs(value),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildGetAnilistMediaGuessRuntimeStateMainDepsHandler(
|
||||
deps: GetAnilistMediaGuessRuntimeStateMainDeps,
|
||||
) {
|
||||
return (): GetAnilistMediaGuessRuntimeStateMainDeps => ({
|
||||
getMediaKey: () => deps.getMediaKey(),
|
||||
getMediaDurationSec: () => deps.getMediaDurationSec(),
|
||||
getMediaGuess: () => deps.getMediaGuess(),
|
||||
getMediaGuessPromise: () => deps.getMediaGuessPromise(),
|
||||
getLastDurationProbeAtMs: () => deps.getLastDurationProbeAtMs(),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildSetAnilistMediaGuessRuntimeStateMainDepsHandler(
|
||||
deps: SetAnilistMediaGuessRuntimeStateMainDeps,
|
||||
) {
|
||||
return (): SetAnilistMediaGuessRuntimeStateMainDeps => ({
|
||||
setMediaKey: (value) => deps.setMediaKey(value),
|
||||
setMediaDurationSec: (value) => deps.setMediaDurationSec(value),
|
||||
setMediaGuess: (value) => deps.setMediaGuess(value),
|
||||
setMediaGuessPromise: (value) => deps.setMediaGuessPromise(value),
|
||||
setLastDurationProbeAtMs: (value: number) => deps.setLastDurationProbeAtMs(value),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildResetAnilistMediaGuessStateMainDepsHandler(
|
||||
deps: ResetAnilistMediaGuessStateMainDeps,
|
||||
) {
|
||||
return (): ResetAnilistMediaGuessStateMainDeps => ({
|
||||
setMediaGuess: (value) => deps.setMediaGuess(value),
|
||||
setMediaGuessPromise: (value) => deps.setMediaGuessPromise(value),
|
||||
});
|
||||
}
|
||||
118
src/main/runtime/anilist-post-watch-main-deps.test.ts
Normal file
118
src/main/runtime/anilist-post-watch-main-deps.test.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildMaybeRunAnilistPostWatchUpdateMainDepsHandler,
|
||||
createBuildProcessNextAnilistRetryUpdateMainDepsHandler,
|
||||
} from './anilist-post-watch-main-deps';
|
||||
|
||||
test('process next anilist retry update main deps builder maps callbacks', async () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildProcessNextAnilistRetryUpdateMainDepsHandler({
|
||||
nextReady: () => ({ key: 'k', title: 't', episode: 1 }),
|
||||
refreshRetryQueueState: () => calls.push('refresh'),
|
||||
setLastAttemptAt: () => calls.push('attempt'),
|
||||
setLastError: () => calls.push('error'),
|
||||
refreshAnilistClientSecretState: async () => 'token',
|
||||
updateAnilistPostWatchProgress: async () => ({ status: 'updated', message: 'ok' }),
|
||||
markSuccess: () => calls.push('success'),
|
||||
rememberAttemptedUpdateKey: () => calls.push('remember'),
|
||||
markFailure: () => calls.push('failure'),
|
||||
logInfo: (message) => calls.push(`info:${message}`),
|
||||
now: () => 7,
|
||||
})();
|
||||
|
||||
assert.deepEqual(deps.nextReady(), { key: 'k', title: 't', episode: 1 });
|
||||
deps.refreshRetryQueueState();
|
||||
deps.setLastAttemptAt(1);
|
||||
deps.setLastError('x');
|
||||
assert.equal(await deps.refreshAnilistClientSecretState(), 'token');
|
||||
assert.deepEqual(await deps.updateAnilistPostWatchProgress('token', 't', 1), {
|
||||
status: 'updated',
|
||||
message: 'ok',
|
||||
});
|
||||
deps.markSuccess('k');
|
||||
deps.rememberAttemptedUpdateKey('k');
|
||||
deps.markFailure('k', 'bad');
|
||||
deps.logInfo('hello');
|
||||
assert.equal(deps.now(), 7);
|
||||
assert.deepEqual(calls, [
|
||||
'refresh',
|
||||
'attempt',
|
||||
'error',
|
||||
'success',
|
||||
'remember',
|
||||
'failure',
|
||||
'info:hello',
|
||||
]);
|
||||
});
|
||||
|
||||
test('maybe run anilist post watch update main deps builder maps callbacks', async () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildMaybeRunAnilistPostWatchUpdateMainDepsHandler({
|
||||
getInFlight: () => false,
|
||||
setInFlight: () => calls.push('in-flight'),
|
||||
getResolvedConfig: () => ({}),
|
||||
isAnilistTrackingEnabled: () => true,
|
||||
getCurrentMediaKey: () => 'media',
|
||||
hasMpvClient: () => true,
|
||||
getTrackedMediaKey: () => 'media',
|
||||
resetTrackedMedia: () => calls.push('reset'),
|
||||
getWatchedSeconds: () => 100,
|
||||
maybeProbeAnilistDuration: async () => 120,
|
||||
ensureAnilistMediaGuess: async () => ({ title: 'x', episode: 1 }),
|
||||
hasAttemptedUpdateKey: () => false,
|
||||
processNextAnilistRetryUpdate: async () => ({ ok: true, message: 'ok' }),
|
||||
refreshAnilistClientSecretState: async () => 'token',
|
||||
enqueueRetry: () => calls.push('enqueue'),
|
||||
markRetryFailure: () => calls.push('retry-fail'),
|
||||
markRetrySuccess: () => calls.push('retry-ok'),
|
||||
refreshRetryQueueState: () => calls.push('refresh'),
|
||||
updateAnilistPostWatchProgress: async () => ({ status: 'updated', message: 'done' }),
|
||||
rememberAttemptedUpdateKey: () => calls.push('remember'),
|
||||
showMpvOsd: () => calls.push('osd'),
|
||||
logInfo: (message) => calls.push(`info:${message}`),
|
||||
logWarn: (message) => calls.push(`warn:${message}`),
|
||||
minWatchSeconds: 5,
|
||||
minWatchRatio: 0.5,
|
||||
})();
|
||||
|
||||
assert.equal(deps.getInFlight(), false);
|
||||
deps.setInFlight(true);
|
||||
assert.equal(deps.isAnilistTrackingEnabled(deps.getResolvedConfig()), true);
|
||||
assert.equal(deps.getCurrentMediaKey(), 'media');
|
||||
assert.equal(deps.hasMpvClient(), true);
|
||||
assert.equal(deps.getTrackedMediaKey(), 'media');
|
||||
deps.resetTrackedMedia('media');
|
||||
assert.equal(deps.getWatchedSeconds(), 100);
|
||||
assert.equal(await deps.maybeProbeAnilistDuration('media'), 120);
|
||||
assert.deepEqual(await deps.ensureAnilistMediaGuess('media'), { title: 'x', episode: 1 });
|
||||
assert.equal(deps.hasAttemptedUpdateKey('k'), false);
|
||||
assert.deepEqual(await deps.processNextAnilistRetryUpdate(), { ok: true, message: 'ok' });
|
||||
assert.equal(await deps.refreshAnilistClientSecretState(), 'token');
|
||||
deps.enqueueRetry('k', 't', 1);
|
||||
deps.markRetryFailure('k', 'bad');
|
||||
deps.markRetrySuccess('k');
|
||||
deps.refreshRetryQueueState();
|
||||
assert.deepEqual(await deps.updateAnilistPostWatchProgress('token', 't', 1), {
|
||||
status: 'updated',
|
||||
message: 'done',
|
||||
});
|
||||
deps.rememberAttemptedUpdateKey('k');
|
||||
deps.showMpvOsd('ok');
|
||||
deps.logInfo('x');
|
||||
deps.logWarn('y');
|
||||
assert.equal(deps.minWatchSeconds, 5);
|
||||
assert.equal(deps.minWatchRatio, 0.5);
|
||||
assert.deepEqual(calls, [
|
||||
'in-flight',
|
||||
'reset',
|
||||
'enqueue',
|
||||
'retry-fail',
|
||||
'retry-ok',
|
||||
'refresh',
|
||||
'remember',
|
||||
'osd',
|
||||
'info:x',
|
||||
'warn:y',
|
||||
]);
|
||||
});
|
||||
63
src/main/runtime/anilist-post-watch-main-deps.ts
Normal file
63
src/main/runtime/anilist-post-watch-main-deps.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import type {
|
||||
createMaybeRunAnilistPostWatchUpdateHandler,
|
||||
createProcessNextAnilistRetryUpdateHandler,
|
||||
} from './anilist-post-watch';
|
||||
|
||||
type ProcessNextAnilistRetryUpdateMainDeps = Parameters<
|
||||
typeof createProcessNextAnilistRetryUpdateHandler
|
||||
>[0];
|
||||
type MaybeRunAnilistPostWatchUpdateMainDeps = Parameters<
|
||||
typeof createMaybeRunAnilistPostWatchUpdateHandler
|
||||
>[0];
|
||||
|
||||
export function createBuildProcessNextAnilistRetryUpdateMainDepsHandler(
|
||||
deps: ProcessNextAnilistRetryUpdateMainDeps,
|
||||
) {
|
||||
return (): ProcessNextAnilistRetryUpdateMainDeps => ({
|
||||
nextReady: () => deps.nextReady(),
|
||||
refreshRetryQueueState: () => deps.refreshRetryQueueState(),
|
||||
setLastAttemptAt: (value: number) => deps.setLastAttemptAt(value),
|
||||
setLastError: (value: string | null) => deps.setLastError(value),
|
||||
refreshAnilistClientSecretState: () => deps.refreshAnilistClientSecretState(),
|
||||
updateAnilistPostWatchProgress: (accessToken: string, title: string, episode: number) =>
|
||||
deps.updateAnilistPostWatchProgress(accessToken, title, episode),
|
||||
markSuccess: (key: string) => deps.markSuccess(key),
|
||||
rememberAttemptedUpdateKey: (key: string) => deps.rememberAttemptedUpdateKey(key),
|
||||
markFailure: (key: string, message: string) => deps.markFailure(key, message),
|
||||
logInfo: (message: string) => deps.logInfo(message),
|
||||
now: () => deps.now(),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildMaybeRunAnilistPostWatchUpdateMainDepsHandler(
|
||||
deps: MaybeRunAnilistPostWatchUpdateMainDeps,
|
||||
) {
|
||||
return (): MaybeRunAnilistPostWatchUpdateMainDeps => ({
|
||||
getInFlight: () => deps.getInFlight(),
|
||||
setInFlight: (value: boolean) => deps.setInFlight(value),
|
||||
getResolvedConfig: () => deps.getResolvedConfig(),
|
||||
isAnilistTrackingEnabled: (config) => deps.isAnilistTrackingEnabled(config),
|
||||
getCurrentMediaKey: () => deps.getCurrentMediaKey(),
|
||||
hasMpvClient: () => deps.hasMpvClient(),
|
||||
getTrackedMediaKey: () => deps.getTrackedMediaKey(),
|
||||
resetTrackedMedia: (mediaKey) => deps.resetTrackedMedia(mediaKey),
|
||||
getWatchedSeconds: () => deps.getWatchedSeconds(),
|
||||
maybeProbeAnilistDuration: (mediaKey: string) => deps.maybeProbeAnilistDuration(mediaKey),
|
||||
ensureAnilistMediaGuess: (mediaKey: string) => deps.ensureAnilistMediaGuess(mediaKey),
|
||||
hasAttemptedUpdateKey: (key: string) => deps.hasAttemptedUpdateKey(key),
|
||||
processNextAnilistRetryUpdate: () => deps.processNextAnilistRetryUpdate(),
|
||||
refreshAnilistClientSecretState: () => deps.refreshAnilistClientSecretState(),
|
||||
enqueueRetry: (key: string, title: string, episode: number) => deps.enqueueRetry(key, title, episode),
|
||||
markRetryFailure: (key: string, message: string) => deps.markRetryFailure(key, message),
|
||||
markRetrySuccess: (key: string) => deps.markRetrySuccess(key),
|
||||
refreshRetryQueueState: () => deps.refreshRetryQueueState(),
|
||||
updateAnilistPostWatchProgress: (accessToken: string, title: string, episode: number) =>
|
||||
deps.updateAnilistPostWatchProgress(accessToken, title, episode),
|
||||
rememberAttemptedUpdateKey: (key: string) => deps.rememberAttemptedUpdateKey(key),
|
||||
showMpvOsd: (message: string) => deps.showMpvOsd(message),
|
||||
logInfo: (message: string) => deps.logInfo(message),
|
||||
logWarn: (message: string) => deps.logWarn(message),
|
||||
minWatchSeconds: deps.minWatchSeconds,
|
||||
minWatchRatio: deps.minWatchRatio,
|
||||
});
|
||||
}
|
||||
52
src/main/runtime/anilist-setup-window-main-deps.test.ts
Normal file
52
src/main/runtime/anilist-setup-window-main-deps.test.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createBuildOpenAnilistSetupWindowMainDepsHandler } from './anilist-setup-window-main-deps';
|
||||
|
||||
test('open anilist setup window main deps builder maps callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildOpenAnilistSetupWindowMainDepsHandler({
|
||||
maybeFocusExistingSetupWindow: () => false,
|
||||
createSetupWindow: () => ({}) as never,
|
||||
buildAuthorizeUrl: () => 'https://anilist.co/auth',
|
||||
consumeCallbackUrl: () => true,
|
||||
openSetupInBrowser: (url) => calls.push(`browser:${url}`),
|
||||
loadManualTokenEntry: () => calls.push('manual'),
|
||||
redirectUri: 'subminer://anilist-auth',
|
||||
developerSettingsUrl: 'https://anilist.co/settings/developer',
|
||||
isAllowedExternalUrl: () => true,
|
||||
isAllowedNavigationUrl: () => true,
|
||||
logWarn: (message) => calls.push(`warn:${message}`),
|
||||
logError: (message) => calls.push(`error:${message}`),
|
||||
clearSetupWindow: () => calls.push('clear'),
|
||||
setSetupPageOpened: (opened) => calls.push(`opened:${String(opened)}`),
|
||||
setSetupWindow: () => calls.push('window'),
|
||||
openExternal: (url) => calls.push(`external:${url}`),
|
||||
})();
|
||||
|
||||
assert.equal(deps.maybeFocusExistingSetupWindow(), false);
|
||||
assert.equal(deps.buildAuthorizeUrl(), 'https://anilist.co/auth');
|
||||
assert.equal(deps.consumeCallbackUrl('subminer://anilist-setup?access_token=x'), true);
|
||||
assert.equal(deps.redirectUri, 'subminer://anilist-auth');
|
||||
assert.equal(deps.developerSettingsUrl, 'https://anilist.co/settings/developer');
|
||||
assert.equal(deps.isAllowedExternalUrl('https://anilist.co'), true);
|
||||
assert.equal(deps.isAllowedNavigationUrl('https://anilist.co/oauth'), true);
|
||||
deps.openSetupInBrowser('https://anilist.co/auth');
|
||||
deps.loadManualTokenEntry({} as never, 'https://anilist.co/auth');
|
||||
deps.logWarn('warn');
|
||||
deps.logError('error', null);
|
||||
deps.clearSetupWindow();
|
||||
deps.setSetupPageOpened(true);
|
||||
deps.setSetupWindow({} as never);
|
||||
deps.openExternal('https://anilist.co');
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
'browser:https://anilist.co/auth',
|
||||
'manual',
|
||||
'warn:warn',
|
||||
'error:error',
|
||||
'clear',
|
||||
'opened:true',
|
||||
'window',
|
||||
'external:https://anilist.co',
|
||||
]);
|
||||
});
|
||||
27
src/main/runtime/anilist-setup-window-main-deps.ts
Normal file
27
src/main/runtime/anilist-setup-window-main-deps.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { createOpenAnilistSetupWindowHandler } from './anilist-setup-window';
|
||||
|
||||
type OpenAnilistSetupWindowMainDeps = Parameters<typeof createOpenAnilistSetupWindowHandler>[0];
|
||||
|
||||
export function createBuildOpenAnilistSetupWindowMainDepsHandler(
|
||||
deps: OpenAnilistSetupWindowMainDeps,
|
||||
) {
|
||||
return (): OpenAnilistSetupWindowMainDeps => ({
|
||||
maybeFocusExistingSetupWindow: () => deps.maybeFocusExistingSetupWindow(),
|
||||
createSetupWindow: () => deps.createSetupWindow(),
|
||||
buildAuthorizeUrl: () => deps.buildAuthorizeUrl(),
|
||||
consumeCallbackUrl: (rawUrl: string) => deps.consumeCallbackUrl(rawUrl),
|
||||
openSetupInBrowser: (authorizeUrl: string) => deps.openSetupInBrowser(authorizeUrl),
|
||||
loadManualTokenEntry: (setupWindow, authorizeUrl: string) =>
|
||||
deps.loadManualTokenEntry(setupWindow, authorizeUrl),
|
||||
redirectUri: deps.redirectUri,
|
||||
developerSettingsUrl: deps.developerSettingsUrl,
|
||||
isAllowedExternalUrl: (url: string) => deps.isAllowedExternalUrl(url),
|
||||
isAllowedNavigationUrl: (url: string) => deps.isAllowedNavigationUrl(url),
|
||||
logWarn: (message: string, details?: unknown) => deps.logWarn(message, details),
|
||||
logError: (message: string, details: unknown) => deps.logError(message, details),
|
||||
clearSetupWindow: () => deps.clearSetupWindow(),
|
||||
setSetupPageOpened: (opened: boolean) => deps.setSetupPageOpened(opened),
|
||||
setSetupWindow: (setupWindow) => deps.setSetupWindow(setupWindow),
|
||||
openExternal: (url: string) => deps.openExternal(url),
|
||||
});
|
||||
}
|
||||
34
src/main/runtime/anilist-token-refresh-main-deps.test.ts
Normal file
34
src/main/runtime/anilist-token-refresh-main-deps.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createBuildRefreshAnilistClientSecretStateMainDepsHandler } from './anilist-token-refresh-main-deps';
|
||||
|
||||
test('refresh anilist client secret state main deps builder maps callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
const config = { anilist: { accessToken: 'token' } };
|
||||
const deps = createBuildRefreshAnilistClientSecretStateMainDepsHandler({
|
||||
getResolvedConfig: () => config as never,
|
||||
isAnilistTrackingEnabled: () => true,
|
||||
getCachedAccessToken: () => 'cached',
|
||||
setCachedAccessToken: () => calls.push('set-cache'),
|
||||
saveStoredToken: () => calls.push('save'),
|
||||
loadStoredToken: () => 'stored',
|
||||
setClientSecretState: () => calls.push('set-state'),
|
||||
getAnilistSetupPageOpened: () => false,
|
||||
setAnilistSetupPageOpened: () => calls.push('set-opened'),
|
||||
openAnilistSetupWindow: () => calls.push('open-window'),
|
||||
now: () => 123,
|
||||
})();
|
||||
|
||||
assert.equal(deps.getResolvedConfig(), config);
|
||||
assert.equal(deps.isAnilistTrackingEnabled(config as never), true);
|
||||
assert.equal(deps.getCachedAccessToken(), 'cached');
|
||||
deps.setCachedAccessToken(null);
|
||||
deps.saveStoredToken('x');
|
||||
assert.equal(deps.loadStoredToken(), 'stored');
|
||||
deps.setClientSecretState({} as never);
|
||||
assert.equal(deps.getAnilistSetupPageOpened(), false);
|
||||
deps.setAnilistSetupPageOpened(true);
|
||||
deps.openAnilistSetupWindow();
|
||||
assert.equal(deps.now(), 123);
|
||||
assert.deepEqual(calls, ['set-cache', 'save', 'set-state', 'set-opened', 'open-window']);
|
||||
});
|
||||
23
src/main/runtime/anilist-token-refresh-main-deps.ts
Normal file
23
src/main/runtime/anilist-token-refresh-main-deps.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { createRefreshAnilistClientSecretStateHandler } from './anilist-token-refresh';
|
||||
|
||||
type RefreshAnilistClientSecretStateMainDeps = Parameters<
|
||||
typeof createRefreshAnilistClientSecretStateHandler
|
||||
>[0];
|
||||
|
||||
export function createBuildRefreshAnilistClientSecretStateMainDepsHandler(
|
||||
deps: RefreshAnilistClientSecretStateMainDeps,
|
||||
) {
|
||||
return (): RefreshAnilistClientSecretStateMainDeps => ({
|
||||
getResolvedConfig: () => deps.getResolvedConfig(),
|
||||
isAnilistTrackingEnabled: (config) => deps.isAnilistTrackingEnabled(config),
|
||||
getCachedAccessToken: () => deps.getCachedAccessToken(),
|
||||
setCachedAccessToken: (token) => deps.setCachedAccessToken(token),
|
||||
saveStoredToken: (token: string) => deps.saveStoredToken(token),
|
||||
loadStoredToken: () => deps.loadStoredToken(),
|
||||
setClientSecretState: (state) => deps.setClientSecretState(state),
|
||||
getAnilistSetupPageOpened: () => deps.getAnilistSetupPageOpened(),
|
||||
setAnilistSetupPageOpened: (opened: boolean) => deps.setAnilistSetupPageOpened(opened),
|
||||
openAnilistSetupWindow: () => deps.openAnilistSetupWindow(),
|
||||
now: () => deps.now(),
|
||||
});
|
||||
}
|
||||
90
src/main/runtime/anki-actions-main-deps.test.ts
Normal file
90
src/main/runtime/anki-actions-main-deps.test.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildMarkLastCardAsAudioCardMainDepsHandler,
|
||||
createBuildMineSentenceCardMainDepsHandler,
|
||||
createBuildRefreshKnownWordCacheMainDepsHandler,
|
||||
createBuildTriggerFieldGroupingMainDepsHandler,
|
||||
createBuildUpdateLastCardFromClipboardMainDepsHandler,
|
||||
} from './anki-actions-main-deps';
|
||||
|
||||
test('anki action main deps builders map callbacks', async () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
const update = createBuildUpdateLastCardFromClipboardMainDepsHandler({
|
||||
getAnkiIntegration: () => ({ enabled: true }),
|
||||
readClipboardText: () => 'clip',
|
||||
showMpvOsd: (text) => calls.push(`osd:${text}`),
|
||||
updateLastCardFromClipboardCore: async () => {
|
||||
calls.push('update');
|
||||
},
|
||||
})();
|
||||
assert.deepEqual(update.getAnkiIntegration(), { enabled: true });
|
||||
assert.equal(update.readClipboardText(), 'clip');
|
||||
update.showMpvOsd('x');
|
||||
await update.updateLastCardFromClipboardCore({
|
||||
ankiIntegration: { enabled: true },
|
||||
readClipboardText: () => '',
|
||||
showMpvOsd: () => {},
|
||||
});
|
||||
|
||||
const refresh = createBuildRefreshKnownWordCacheMainDepsHandler({
|
||||
getAnkiIntegration: () => null,
|
||||
missingIntegrationMessage: 'missing',
|
||||
})();
|
||||
assert.equal(refresh.getAnkiIntegration(), null);
|
||||
assert.equal(refresh.missingIntegrationMessage, 'missing');
|
||||
|
||||
const fieldGrouping = createBuildTriggerFieldGroupingMainDepsHandler({
|
||||
getAnkiIntegration: () => ({ enabled: true }),
|
||||
showMpvOsd: (text) => calls.push(`fg:${text}`),
|
||||
triggerFieldGroupingCore: async () => {
|
||||
calls.push('trigger');
|
||||
},
|
||||
})();
|
||||
fieldGrouping.showMpvOsd('fg');
|
||||
await fieldGrouping.triggerFieldGroupingCore({
|
||||
ankiIntegration: { enabled: true },
|
||||
showMpvOsd: () => {},
|
||||
});
|
||||
|
||||
const markAudio = createBuildMarkLastCardAsAudioCardMainDepsHandler({
|
||||
getAnkiIntegration: () => ({ enabled: true }),
|
||||
showMpvOsd: (text) => calls.push(`audio:${text}`),
|
||||
markLastCardAsAudioCardCore: async () => {
|
||||
calls.push('mark');
|
||||
},
|
||||
})();
|
||||
markAudio.showMpvOsd('a');
|
||||
await markAudio.markLastCardAsAudioCardCore({
|
||||
ankiIntegration: { enabled: true },
|
||||
showMpvOsd: () => {},
|
||||
});
|
||||
|
||||
const mine = createBuildMineSentenceCardMainDepsHandler({
|
||||
getAnkiIntegration: () => ({ enabled: true }),
|
||||
getMpvClient: () => ({ connected: true }),
|
||||
showMpvOsd: (text) => calls.push(`mine:${text}`),
|
||||
mineSentenceCardCore: async () => true,
|
||||
recordCardsMined: (count) => calls.push(`cards:${count}`),
|
||||
})();
|
||||
assert.deepEqual(mine.getMpvClient(), { connected: true });
|
||||
mine.showMpvOsd('m');
|
||||
await mine.mineSentenceCardCore({
|
||||
ankiIntegration: { enabled: true },
|
||||
mpvClient: { connected: true },
|
||||
showMpvOsd: () => {},
|
||||
});
|
||||
mine.recordCardsMined(1);
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
'osd:x',
|
||||
'update',
|
||||
'fg:fg',
|
||||
'trigger',
|
||||
'audio:a',
|
||||
'mark',
|
||||
'mine:m',
|
||||
'cards:1',
|
||||
]);
|
||||
});
|
||||
88
src/main/runtime/anki-actions-main-deps.ts
Normal file
88
src/main/runtime/anki-actions-main-deps.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { createRefreshKnownWordCacheHandler } from './anki-actions';
|
||||
|
||||
type RefreshKnownWordCacheMainDeps = Parameters<typeof createRefreshKnownWordCacheHandler>[0];
|
||||
|
||||
export function createBuildUpdateLastCardFromClipboardMainDepsHandler<TAnki>(deps: {
|
||||
getAnkiIntegration: () => TAnki;
|
||||
readClipboardText: () => string;
|
||||
showMpvOsd: (text: string) => void;
|
||||
updateLastCardFromClipboardCore: (options: {
|
||||
ankiIntegration: TAnki;
|
||||
readClipboardText: () => string;
|
||||
showMpvOsd: (text: string) => void;
|
||||
}) => Promise<void>;
|
||||
}) {
|
||||
return () => ({
|
||||
getAnkiIntegration: () => deps.getAnkiIntegration(),
|
||||
readClipboardText: () => deps.readClipboardText(),
|
||||
showMpvOsd: (text: string) => deps.showMpvOsd(text),
|
||||
updateLastCardFromClipboardCore: (options: {
|
||||
ankiIntegration: TAnki;
|
||||
readClipboardText: () => string;
|
||||
showMpvOsd: (text: string) => void;
|
||||
}) => deps.updateLastCardFromClipboardCore(options),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildRefreshKnownWordCacheMainDepsHandler(deps: RefreshKnownWordCacheMainDeps) {
|
||||
return (): RefreshKnownWordCacheMainDeps => ({
|
||||
getAnkiIntegration: () => deps.getAnkiIntegration(),
|
||||
missingIntegrationMessage: deps.missingIntegrationMessage,
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildTriggerFieldGroupingMainDepsHandler<TAnki>(deps: {
|
||||
getAnkiIntegration: () => TAnki;
|
||||
showMpvOsd: (text: string) => void;
|
||||
triggerFieldGroupingCore: (options: {
|
||||
ankiIntegration: TAnki;
|
||||
showMpvOsd: (text: string) => void;
|
||||
}) => Promise<void>;
|
||||
}) {
|
||||
return () => ({
|
||||
getAnkiIntegration: () => deps.getAnkiIntegration(),
|
||||
showMpvOsd: (text: string) => deps.showMpvOsd(text),
|
||||
triggerFieldGroupingCore: (options: { ankiIntegration: TAnki; showMpvOsd: (text: string) => void }) =>
|
||||
deps.triggerFieldGroupingCore(options),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildMarkLastCardAsAudioCardMainDepsHandler<TAnki>(deps: {
|
||||
getAnkiIntegration: () => TAnki;
|
||||
showMpvOsd: (text: string) => void;
|
||||
markLastCardAsAudioCardCore: (options: {
|
||||
ankiIntegration: TAnki;
|
||||
showMpvOsd: (text: string) => void;
|
||||
}) => Promise<void>;
|
||||
}) {
|
||||
return () => ({
|
||||
getAnkiIntegration: () => deps.getAnkiIntegration(),
|
||||
showMpvOsd: (text: string) => deps.showMpvOsd(text),
|
||||
markLastCardAsAudioCardCore: (options: { ankiIntegration: TAnki; showMpvOsd: (text: string) => void }) =>
|
||||
deps.markLastCardAsAudioCardCore(options),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildMineSentenceCardMainDepsHandler<TAnki, TMpv>(deps: {
|
||||
getAnkiIntegration: () => TAnki;
|
||||
getMpvClient: () => TMpv;
|
||||
showMpvOsd: (text: string) => void;
|
||||
mineSentenceCardCore: (options: {
|
||||
ankiIntegration: TAnki;
|
||||
mpvClient: TMpv;
|
||||
showMpvOsd: (text: string) => void;
|
||||
}) => Promise<boolean>;
|
||||
recordCardsMined: (count: number) => void;
|
||||
}) {
|
||||
return () => ({
|
||||
getAnkiIntegration: () => deps.getAnkiIntegration(),
|
||||
getMpvClient: () => deps.getMpvClient(),
|
||||
showMpvOsd: (text: string) => deps.showMpvOsd(text),
|
||||
mineSentenceCardCore: (options: {
|
||||
ankiIntegration: TAnki;
|
||||
mpvClient: TMpv;
|
||||
showMpvOsd: (text: string) => void;
|
||||
}) => deps.mineSentenceCardCore(options),
|
||||
recordCardsMined: (count: number) => deps.recordCardsMined(count),
|
||||
});
|
||||
}
|
||||
33
src/main/runtime/field-grouping-resolver-main-deps.test.ts
Normal file
33
src/main/runtime/field-grouping-resolver-main-deps.test.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildGetFieldGroupingResolverMainDepsHandler,
|
||||
createBuildSetFieldGroupingResolverMainDepsHandler,
|
||||
} from './field-grouping-resolver-main-deps';
|
||||
|
||||
test('get field grouping resolver main deps builder maps callbacks', () => {
|
||||
const resolver = () => undefined;
|
||||
const deps = createBuildGetFieldGroupingResolverMainDepsHandler({
|
||||
getResolver: () => resolver,
|
||||
})();
|
||||
assert.equal(deps.getResolver(), resolver);
|
||||
});
|
||||
|
||||
test('set field grouping resolver main deps builder maps callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
const wrapped = (choice: unknown) => calls.push(String(choice));
|
||||
const deps = createBuildSetFieldGroupingResolverMainDepsHandler({
|
||||
setResolver: (resolver) => {
|
||||
if (resolver) {
|
||||
resolver('x' as never);
|
||||
}
|
||||
},
|
||||
nextSequence: () => 2,
|
||||
getSequence: () => 2,
|
||||
})();
|
||||
|
||||
assert.equal(deps.nextSequence(), 2);
|
||||
assert.equal(deps.getSequence(), 2);
|
||||
deps.setResolver(wrapped as never);
|
||||
assert.deepEqual(calls, ['x']);
|
||||
});
|
||||
25
src/main/runtime/field-grouping-resolver-main-deps.ts
Normal file
25
src/main/runtime/field-grouping-resolver-main-deps.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type {
|
||||
createGetFieldGroupingResolverHandler,
|
||||
createSetFieldGroupingResolverHandler,
|
||||
} from './field-grouping-resolver';
|
||||
|
||||
type GetFieldGroupingResolverMainDeps = Parameters<typeof createGetFieldGroupingResolverHandler>[0];
|
||||
type SetFieldGroupingResolverMainDeps = Parameters<typeof createSetFieldGroupingResolverHandler>[0];
|
||||
|
||||
export function createBuildGetFieldGroupingResolverMainDepsHandler(
|
||||
deps: GetFieldGroupingResolverMainDeps,
|
||||
) {
|
||||
return (): GetFieldGroupingResolverMainDeps => ({
|
||||
getResolver: () => deps.getResolver(),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildSetFieldGroupingResolverMainDepsHandler(
|
||||
deps: SetFieldGroupingResolverMainDeps,
|
||||
) {
|
||||
return (): SetFieldGroupingResolverMainDeps => ({
|
||||
setResolver: (resolver) => deps.setResolver(resolver),
|
||||
nextSequence: () => deps.nextSequence(),
|
||||
getSequence: () => deps.getSequence(),
|
||||
});
|
||||
}
|
||||
37
src/main/runtime/ipc-bridge-actions-main-deps.test.ts
Normal file
37
src/main/runtime/ipc-bridge-actions-main-deps.test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildHandleMpvCommandFromIpcMainDepsHandler,
|
||||
createBuildRunSubsyncManualFromIpcMainDepsHandler,
|
||||
} from './ipc-bridge-actions-main-deps';
|
||||
|
||||
test('ipc bridge action main deps builders map callbacks', async () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
const handleMpv = createBuildHandleMpvCommandFromIpcMainDepsHandler({
|
||||
handleMpvCommandFromIpcRuntime: (command) => calls.push(`mpv:${command.join(':')}`),
|
||||
buildMpvCommandDeps: () => ({
|
||||
triggerSubsyncFromConfig: async () => {},
|
||||
openRuntimeOptionsPalette: () => {},
|
||||
cycleRuntimeOption: () => ({ ok: false as const, error: 'x' }),
|
||||
showMpvOsd: () => {},
|
||||
replayCurrentSubtitle: () => {},
|
||||
playNextSubtitle: () => {},
|
||||
sendMpvCommand: () => {},
|
||||
isMpvConnected: () => true,
|
||||
hasRuntimeOptionsManager: () => true,
|
||||
}),
|
||||
})();
|
||||
handleMpv.handleMpvCommandFromIpcRuntime(['show-text', 'hello'], handleMpv.buildMpvCommandDeps());
|
||||
assert.equal(handleMpv.buildMpvCommandDeps().isMpvConnected(), true);
|
||||
|
||||
const runSubsync = createBuildRunSubsyncManualFromIpcMainDepsHandler({
|
||||
runManualFromIpc: async (request: { id: string }) => {
|
||||
calls.push(`subsync:${request.id}`);
|
||||
return { ok: true as const };
|
||||
},
|
||||
})();
|
||||
assert.deepEqual(await runSubsync.runManualFromIpc({ id: 'job-1' }), { ok: true });
|
||||
|
||||
assert.deepEqual(calls, ['mpv:show-text:hello', 'subsync:job-1']);
|
||||
});
|
||||
21
src/main/runtime/ipc-bridge-actions-main-deps.ts
Normal file
21
src/main/runtime/ipc-bridge-actions-main-deps.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { createHandleMpvCommandFromIpcHandler } from './ipc-bridge-actions';
|
||||
|
||||
type HandleMpvCommandFromIpcMainDeps = Parameters<typeof createHandleMpvCommandFromIpcHandler>[0];
|
||||
|
||||
export function createBuildHandleMpvCommandFromIpcMainDepsHandler(
|
||||
deps: HandleMpvCommandFromIpcMainDeps,
|
||||
) {
|
||||
return (): HandleMpvCommandFromIpcMainDeps => ({
|
||||
handleMpvCommandFromIpcRuntime: (command, options) =>
|
||||
deps.handleMpvCommandFromIpcRuntime(command, options),
|
||||
buildMpvCommandDeps: () => deps.buildMpvCommandDeps(),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildRunSubsyncManualFromIpcMainDepsHandler<TRequest, TResult>(deps: {
|
||||
runManualFromIpc: (request: TRequest) => Promise<TResult>;
|
||||
}) {
|
||||
return () => ({
|
||||
runManualFromIpc: (request: TRequest) => deps.runManualFromIpc(request),
|
||||
});
|
||||
}
|
||||
36
src/main/runtime/ipc-mpv-command-main-deps.test.ts
Normal file
36
src/main/runtime/ipc-mpv-command-main-deps.test.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createBuildMpvCommandFromIpcRuntimeMainDepsHandler } from './ipc-mpv-command-main-deps';
|
||||
|
||||
test('ipc mpv command main deps builder maps callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildMpvCommandFromIpcRuntimeMainDepsHandler({
|
||||
triggerSubsyncFromConfig: () => calls.push('subsync'),
|
||||
openRuntimeOptionsPalette: () => calls.push('palette'),
|
||||
cycleRuntimeOption: () => ({ ok: false as const, error: 'x' }),
|
||||
showMpvOsd: (text) => calls.push(`osd:${text}`),
|
||||
replayCurrentSubtitle: () => calls.push('replay'),
|
||||
playNextSubtitle: () => calls.push('next'),
|
||||
sendMpvCommand: (command) => calls.push(`cmd:${command.join(':')}`),
|
||||
isMpvConnected: () => true,
|
||||
hasRuntimeOptionsManager: () => false,
|
||||
})();
|
||||
|
||||
deps.triggerSubsyncFromConfig();
|
||||
deps.openRuntimeOptionsPalette();
|
||||
assert.deepEqual(deps.cycleRuntimeOption('anki.nPlusOneMatchMode', 1), { ok: false, error: 'x' });
|
||||
deps.showMpvOsd('hello');
|
||||
deps.replayCurrentSubtitle();
|
||||
deps.playNextSubtitle();
|
||||
deps.sendMpvCommand(['show-text', 'ok']);
|
||||
assert.equal(deps.isMpvConnected(), true);
|
||||
assert.equal(deps.hasRuntimeOptionsManager(), false);
|
||||
assert.deepEqual(calls, [
|
||||
'subsync',
|
||||
'palette',
|
||||
'osd:hello',
|
||||
'replay',
|
||||
'next',
|
||||
'cmd:show-text:ok',
|
||||
]);
|
||||
});
|
||||
15
src/main/runtime/ipc-mpv-command-main-deps.ts
Normal file
15
src/main/runtime/ipc-mpv-command-main-deps.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { MpvCommandFromIpcRuntimeDeps } from '../ipc-mpv-command';
|
||||
|
||||
export function createBuildMpvCommandFromIpcRuntimeMainDepsHandler(deps: MpvCommandFromIpcRuntimeDeps) {
|
||||
return (): MpvCommandFromIpcRuntimeDeps => ({
|
||||
triggerSubsyncFromConfig: () => deps.triggerSubsyncFromConfig(),
|
||||
openRuntimeOptionsPalette: () => deps.openRuntimeOptionsPalette(),
|
||||
cycleRuntimeOption: (id, direction) => deps.cycleRuntimeOption(id, direction),
|
||||
showMpvOsd: (text: string) => deps.showMpvOsd(text),
|
||||
replayCurrentSubtitle: () => deps.replayCurrentSubtitle(),
|
||||
playNextSubtitle: () => deps.playNextSubtitle(),
|
||||
sendMpvCommand: (command: (string | number)[]) => deps.sendMpvCommand(command),
|
||||
isMpvConnected: () => deps.isMpvConnected(),
|
||||
hasRuntimeOptionsManager: () => deps.hasRuntimeOptionsManager(),
|
||||
});
|
||||
}
|
||||
58
src/main/runtime/jellyfin-remote-session-main-deps.test.ts
Normal file
58
src/main/runtime/jellyfin-remote-session-main-deps.test.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildStartJellyfinRemoteSessionMainDepsHandler,
|
||||
createBuildStopJellyfinRemoteSessionMainDepsHandler,
|
||||
} from './jellyfin-remote-session-main-deps';
|
||||
|
||||
test('start jellyfin remote session main deps builder maps callbacks', async () => {
|
||||
const calls: string[] = [];
|
||||
const session = { start: () => {}, stop: () => {}, advertiseNow: async () => true };
|
||||
const deps = createBuildStartJellyfinRemoteSessionMainDepsHandler({
|
||||
getJellyfinConfig: () => ({ serverUrl: 'http://localhost' }) as never,
|
||||
getCurrentSession: () => null,
|
||||
setCurrentSession: () => calls.push('set-session'),
|
||||
createRemoteSessionService: () => session as never,
|
||||
defaultDeviceId: 'device',
|
||||
defaultClientName: 'SubMiner',
|
||||
defaultClientVersion: '1.0',
|
||||
handlePlay: async () => {
|
||||
calls.push('play');
|
||||
},
|
||||
handlePlaystate: async () => {
|
||||
calls.push('playstate');
|
||||
},
|
||||
handleGeneralCommand: async () => {
|
||||
calls.push('general');
|
||||
},
|
||||
logInfo: (message) => calls.push(`info:${message}`),
|
||||
logWarn: (message) => calls.push(`warn:${message}`),
|
||||
})();
|
||||
|
||||
assert.deepEqual(deps.getJellyfinConfig(), { serverUrl: 'http://localhost' });
|
||||
assert.equal(deps.defaultDeviceId, 'device');
|
||||
assert.equal(deps.defaultClientName, 'SubMiner');
|
||||
assert.equal(deps.defaultClientVersion, '1.0');
|
||||
assert.equal(deps.createRemoteSessionService({} as never), session);
|
||||
await deps.handlePlay({});
|
||||
await deps.handlePlaystate({});
|
||||
await deps.handleGeneralCommand({});
|
||||
deps.logInfo('connected');
|
||||
deps.logWarn('missing');
|
||||
assert.deepEqual(calls, ['play', 'playstate', 'general', 'info:connected', 'warn:missing']);
|
||||
});
|
||||
|
||||
test('stop jellyfin remote session main deps builder maps callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
const session = { start: () => {}, stop: () => {}, advertiseNow: async () => true };
|
||||
const deps = createBuildStopJellyfinRemoteSessionMainDepsHandler({
|
||||
getCurrentSession: () => session as never,
|
||||
setCurrentSession: () => calls.push('set-null'),
|
||||
clearActivePlayback: () => calls.push('clear'),
|
||||
})();
|
||||
|
||||
assert.equal(deps.getCurrentSession(), session);
|
||||
deps.setCurrentSession(null);
|
||||
deps.clearActivePlayback();
|
||||
assert.deepEqual(calls, ['set-null', 'clear']);
|
||||
});
|
||||
36
src/main/runtime/jellyfin-remote-session-main-deps.ts
Normal file
36
src/main/runtime/jellyfin-remote-session-main-deps.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type {
|
||||
createStartJellyfinRemoteSessionHandler,
|
||||
createStopJellyfinRemoteSessionHandler,
|
||||
} from './jellyfin-remote-session-lifecycle';
|
||||
|
||||
type StartJellyfinRemoteSessionMainDeps = Parameters<typeof createStartJellyfinRemoteSessionHandler>[0];
|
||||
type StopJellyfinRemoteSessionMainDeps = Parameters<typeof createStopJellyfinRemoteSessionHandler>[0];
|
||||
|
||||
export function createBuildStartJellyfinRemoteSessionMainDepsHandler(
|
||||
deps: StartJellyfinRemoteSessionMainDeps,
|
||||
) {
|
||||
return (): StartJellyfinRemoteSessionMainDeps => ({
|
||||
getJellyfinConfig: () => deps.getJellyfinConfig(),
|
||||
getCurrentSession: () => deps.getCurrentSession(),
|
||||
setCurrentSession: (session) => deps.setCurrentSession(session),
|
||||
createRemoteSessionService: (options) => deps.createRemoteSessionService(options),
|
||||
defaultDeviceId: deps.defaultDeviceId,
|
||||
defaultClientName: deps.defaultClientName,
|
||||
defaultClientVersion: deps.defaultClientVersion,
|
||||
handlePlay: (payload) => deps.handlePlay(payload),
|
||||
handlePlaystate: (payload) => deps.handlePlaystate(payload),
|
||||
handleGeneralCommand: (payload) => deps.handleGeneralCommand(payload),
|
||||
logInfo: (message: string) => deps.logInfo(message),
|
||||
logWarn: (message: string, details?: unknown) => deps.logWarn(message, details),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildStopJellyfinRemoteSessionMainDepsHandler(
|
||||
deps: StopJellyfinRemoteSessionMainDeps,
|
||||
) {
|
||||
return (): StopJellyfinRemoteSessionMainDeps => ({
|
||||
getCurrentSession: () => deps.getCurrentSession(),
|
||||
setCurrentSession: (session) => deps.setCurrentSession(session),
|
||||
clearActivePlayback: () => deps.clearActivePlayback(),
|
||||
});
|
||||
}
|
||||
59
src/main/runtime/jellyfin-setup-window-main-deps.test.ts
Normal file
59
src/main/runtime/jellyfin-setup-window-main-deps.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createBuildOpenJellyfinSetupWindowMainDepsHandler } from './jellyfin-setup-window-main-deps';
|
||||
|
||||
test('open jellyfin setup window main deps builder maps callbacks', async () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildOpenJellyfinSetupWindowMainDepsHandler({
|
||||
maybeFocusExistingSetupWindow: () => false,
|
||||
createSetupWindow: () => ({}) as never,
|
||||
getResolvedJellyfinConfig: () => ({ serverUrl: 'http://127.0.0.1:8096', username: 'alice' }),
|
||||
buildSetupFormHtml: () => '<html></html>',
|
||||
parseSubmissionUrl: () => ({ server: 's', username: 'u', password: 'p' }),
|
||||
authenticateWithPassword: async () => ({
|
||||
serverUrl: 'http://127.0.0.1:8096',
|
||||
username: 'alice',
|
||||
accessToken: 'token',
|
||||
userId: 'uid',
|
||||
}),
|
||||
getJellyfinClientInfo: () => ({ clientName: 'SubMiner', clientVersion: '1.0', deviceId: 'dev' }),
|
||||
patchJellyfinConfig: () => calls.push('patch'),
|
||||
logInfo: (message) => calls.push(`info:${message}`),
|
||||
logError: (message) => calls.push(`error:${message}`),
|
||||
showMpvOsd: (message) => calls.push(`osd:${message}`),
|
||||
clearSetupWindow: () => calls.push('clear'),
|
||||
setSetupWindow: () => calls.push('set-window'),
|
||||
encodeURIComponent: (value) => encodeURIComponent(value),
|
||||
})();
|
||||
|
||||
assert.equal(deps.maybeFocusExistingSetupWindow(), false);
|
||||
assert.deepEqual(deps.getResolvedJellyfinConfig(), {
|
||||
serverUrl: 'http://127.0.0.1:8096',
|
||||
username: 'alice',
|
||||
});
|
||||
assert.equal(deps.buildSetupFormHtml('a', 'b'), '<html></html>');
|
||||
assert.deepEqual(deps.parseSubmissionUrl('subminer://jellyfin-setup?x=1'), {
|
||||
server: 's',
|
||||
username: 'u',
|
||||
password: 'p',
|
||||
});
|
||||
assert.deepEqual(await deps.authenticateWithPassword('s', 'u', 'p', deps.getJellyfinClientInfo()), {
|
||||
serverUrl: 'http://127.0.0.1:8096',
|
||||
username: 'alice',
|
||||
accessToken: 'token',
|
||||
userId: 'uid',
|
||||
});
|
||||
deps.patchJellyfinConfig({
|
||||
serverUrl: 'http://127.0.0.1:8096',
|
||||
username: 'alice',
|
||||
accessToken: 'token',
|
||||
userId: 'uid',
|
||||
});
|
||||
deps.logInfo('ok');
|
||||
deps.logError('bad', null);
|
||||
deps.showMpvOsd('toast');
|
||||
deps.clearSetupWindow();
|
||||
deps.setSetupWindow({} as never);
|
||||
assert.equal(deps.encodeURIComponent('a b'), 'a%20b');
|
||||
assert.deepEqual(calls, ['patch', 'info:ok', 'error:bad', 'osd:toast', 'clear', 'set-window']);
|
||||
});
|
||||
26
src/main/runtime/jellyfin-setup-window-main-deps.ts
Normal file
26
src/main/runtime/jellyfin-setup-window-main-deps.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { createOpenJellyfinSetupWindowHandler } from './jellyfin-setup-window';
|
||||
|
||||
type OpenJellyfinSetupWindowMainDeps = Parameters<typeof createOpenJellyfinSetupWindowHandler>[0];
|
||||
|
||||
export function createBuildOpenJellyfinSetupWindowMainDepsHandler(
|
||||
deps: OpenJellyfinSetupWindowMainDeps,
|
||||
) {
|
||||
return (): OpenJellyfinSetupWindowMainDeps => ({
|
||||
maybeFocusExistingSetupWindow: () => deps.maybeFocusExistingSetupWindow(),
|
||||
createSetupWindow: () => deps.createSetupWindow(),
|
||||
getResolvedJellyfinConfig: () => deps.getResolvedJellyfinConfig(),
|
||||
buildSetupFormHtml: (defaultServer: string, defaultUser: string) =>
|
||||
deps.buildSetupFormHtml(defaultServer, defaultUser),
|
||||
parseSubmissionUrl: (rawUrl: string) => deps.parseSubmissionUrl(rawUrl),
|
||||
authenticateWithPassword: (server: string, username: string, password: string, clientInfo) =>
|
||||
deps.authenticateWithPassword(server, username, password, clientInfo),
|
||||
getJellyfinClientInfo: () => deps.getJellyfinClientInfo(),
|
||||
patchJellyfinConfig: (session) => deps.patchJellyfinConfig(session),
|
||||
logInfo: (message: string) => deps.logInfo(message),
|
||||
logError: (message: string, error: unknown) => deps.logError(message, error),
|
||||
showMpvOsd: (message: string) => deps.showMpvOsd(message),
|
||||
clearSetupWindow: () => deps.clearSetupWindow(),
|
||||
setSetupWindow: (window) => deps.setSetupWindow(window),
|
||||
encodeURIComponent: (value: string) => deps.encodeURIComponent(value),
|
||||
});
|
||||
}
|
||||
26
src/main/runtime/jellyfin-subtitle-preload-main-deps.test.ts
Normal file
26
src/main/runtime/jellyfin-subtitle-preload-main-deps.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createBuildPreloadJellyfinExternalSubtitlesMainDepsHandler } from './jellyfin-subtitle-preload-main-deps';
|
||||
|
||||
test('preload jellyfin external subtitles main deps builder maps callbacks', async () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildPreloadJellyfinExternalSubtitlesMainDepsHandler({
|
||||
listJellyfinSubtitleTracks: async () => {
|
||||
calls.push('list');
|
||||
return [];
|
||||
},
|
||||
getMpvClient: () => ({ requestProperty: async () => [] }),
|
||||
sendMpvCommand: () => calls.push('send'),
|
||||
wait: async () => {
|
||||
calls.push('wait');
|
||||
},
|
||||
logDebug: (message) => calls.push(`debug:${message}`),
|
||||
})();
|
||||
|
||||
await deps.listJellyfinSubtitleTracks({} as never, {} as never, 'item');
|
||||
assert.equal(typeof deps.getMpvClient()?.requestProperty, 'function');
|
||||
deps.sendMpvCommand(['set_property', 'sid', 'auto']);
|
||||
await deps.wait(1);
|
||||
deps.logDebug('oops', null);
|
||||
assert.deepEqual(calls, ['list', 'send', 'wait', 'debug:oops']);
|
||||
});
|
||||
18
src/main/runtime/jellyfin-subtitle-preload-main-deps.ts
Normal file
18
src/main/runtime/jellyfin-subtitle-preload-main-deps.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { createPreloadJellyfinExternalSubtitlesHandler } from './jellyfin-subtitle-preload';
|
||||
|
||||
type PreloadJellyfinExternalSubtitlesMainDeps = Parameters<
|
||||
typeof createPreloadJellyfinExternalSubtitlesHandler
|
||||
>[0];
|
||||
|
||||
export function createBuildPreloadJellyfinExternalSubtitlesMainDepsHandler(
|
||||
deps: PreloadJellyfinExternalSubtitlesMainDeps,
|
||||
) {
|
||||
return (): PreloadJellyfinExternalSubtitlesMainDeps => ({
|
||||
listJellyfinSubtitleTracks: (session, clientInfo, itemId) =>
|
||||
deps.listJellyfinSubtitleTracks(session, clientInfo, itemId),
|
||||
getMpvClient: () => deps.getMpvClient(),
|
||||
sendMpvCommand: (command) => deps.sendMpvCommand(command),
|
||||
wait: (ms: number) => deps.wait(ms),
|
||||
logDebug: (message: string, error: unknown) => deps.logDebug(message, error),
|
||||
});
|
||||
}
|
||||
76
src/main/runtime/mining-actions-main-deps.test.ts
Normal file
76
src/main/runtime/mining-actions-main-deps.test.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildCopyCurrentSubtitleMainDepsHandler,
|
||||
createBuildHandleMineSentenceDigitMainDepsHandler,
|
||||
createBuildHandleMultiCopyDigitMainDepsHandler,
|
||||
} from './mining-actions-main-deps';
|
||||
|
||||
test('mining action main deps builders map callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
const multiCopy = createBuildHandleMultiCopyDigitMainDepsHandler({
|
||||
getSubtitleTimingTracker: () => ({ track: true }),
|
||||
writeClipboardText: (text) => calls.push(`clip:${text}`),
|
||||
showMpvOsd: (text) => calls.push(`osd:${text}`),
|
||||
handleMultiCopyDigitCore: () => calls.push('multi-copy'),
|
||||
})();
|
||||
assert.deepEqual(multiCopy.getSubtitleTimingTracker(), { track: true });
|
||||
multiCopy.writeClipboardText('x');
|
||||
multiCopy.showMpvOsd('y');
|
||||
multiCopy.handleMultiCopyDigitCore(2, {
|
||||
subtitleTimingTracker: { track: true },
|
||||
writeClipboardText: () => {},
|
||||
showMpvOsd: () => {},
|
||||
});
|
||||
|
||||
const copyCurrent = createBuildCopyCurrentSubtitleMainDepsHandler({
|
||||
getSubtitleTimingTracker: () => ({ track: true }),
|
||||
writeClipboardText: (text) => calls.push(`copy:${text}`),
|
||||
showMpvOsd: (text) => calls.push(`copy-osd:${text}`),
|
||||
copyCurrentSubtitleCore: () => calls.push('copy-current'),
|
||||
})();
|
||||
assert.deepEqual(copyCurrent.getSubtitleTimingTracker(), { track: true });
|
||||
copyCurrent.writeClipboardText('a');
|
||||
copyCurrent.showMpvOsd('b');
|
||||
copyCurrent.copyCurrentSubtitleCore({
|
||||
subtitleTimingTracker: { track: true },
|
||||
writeClipboardText: () => {},
|
||||
showMpvOsd: () => {},
|
||||
});
|
||||
|
||||
const mineDigit = createBuildHandleMineSentenceDigitMainDepsHandler({
|
||||
getSubtitleTimingTracker: () => ({ track: true }),
|
||||
getAnkiIntegration: () => ({ enabled: true }),
|
||||
getCurrentSecondarySubText: () => 'sub',
|
||||
showMpvOsd: (text) => calls.push(`mine-osd:${text}`),
|
||||
logError: (message) => calls.push(`err:${message}`),
|
||||
onCardsMined: (count) => calls.push(`cards:${count}`),
|
||||
handleMineSentenceDigitCore: () => calls.push('mine-digit'),
|
||||
})();
|
||||
assert.equal(mineDigit.getCurrentSecondarySubText(), 'sub');
|
||||
mineDigit.showMpvOsd('done');
|
||||
mineDigit.logError('bad', null);
|
||||
mineDigit.onCardsMined(2);
|
||||
mineDigit.handleMineSentenceDigitCore(2, {
|
||||
subtitleTimingTracker: { track: true },
|
||||
ankiIntegration: { enabled: true },
|
||||
getCurrentSecondarySubText: () => 'sub',
|
||||
showMpvOsd: () => {},
|
||||
logError: () => {},
|
||||
onCardsMined: () => {},
|
||||
});
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
'clip:x',
|
||||
'osd:y',
|
||||
'multi-copy',
|
||||
'copy:a',
|
||||
'copy-osd:b',
|
||||
'copy-current',
|
||||
'mine-osd:done',
|
||||
'err:bad',
|
||||
'cards:2',
|
||||
'mine-digit',
|
||||
]);
|
||||
});
|
||||
92
src/main/runtime/mining-actions-main-deps.ts
Normal file
92
src/main/runtime/mining-actions-main-deps.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
export function createBuildHandleMultiCopyDigitMainDepsHandler<TSubtitleTimingTracker>(deps: {
|
||||
getSubtitleTimingTracker: () => TSubtitleTimingTracker;
|
||||
writeClipboardText: (text: string) => void;
|
||||
showMpvOsd: (text: string) => void;
|
||||
handleMultiCopyDigitCore: (
|
||||
count: number,
|
||||
options: {
|
||||
subtitleTimingTracker: TSubtitleTimingTracker;
|
||||
writeClipboardText: (text: string) => void;
|
||||
showMpvOsd: (text: string) => void;
|
||||
},
|
||||
) => void;
|
||||
}) {
|
||||
return () => ({
|
||||
getSubtitleTimingTracker: () => deps.getSubtitleTimingTracker(),
|
||||
writeClipboardText: (text: string) => deps.writeClipboardText(text),
|
||||
showMpvOsd: (text: string) => deps.showMpvOsd(text),
|
||||
handleMultiCopyDigitCore: (
|
||||
count: number,
|
||||
options: {
|
||||
subtitleTimingTracker: TSubtitleTimingTracker;
|
||||
writeClipboardText: (text: string) => void;
|
||||
showMpvOsd: (text: string) => void;
|
||||
},
|
||||
) => deps.handleMultiCopyDigitCore(count, options),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildCopyCurrentSubtitleMainDepsHandler<TSubtitleTimingTracker>(deps: {
|
||||
getSubtitleTimingTracker: () => TSubtitleTimingTracker;
|
||||
writeClipboardText: (text: string) => void;
|
||||
showMpvOsd: (text: string) => void;
|
||||
copyCurrentSubtitleCore: (options: {
|
||||
subtitleTimingTracker: TSubtitleTimingTracker;
|
||||
writeClipboardText: (text: string) => void;
|
||||
showMpvOsd: (text: string) => void;
|
||||
}) => void;
|
||||
}) {
|
||||
return () => ({
|
||||
getSubtitleTimingTracker: () => deps.getSubtitleTimingTracker(),
|
||||
writeClipboardText: (text: string) => deps.writeClipboardText(text),
|
||||
showMpvOsd: (text: string) => deps.showMpvOsd(text),
|
||||
copyCurrentSubtitleCore: (options: {
|
||||
subtitleTimingTracker: TSubtitleTimingTracker;
|
||||
writeClipboardText: (text: string) => void;
|
||||
showMpvOsd: (text: string) => void;
|
||||
}) => deps.copyCurrentSubtitleCore(options),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildHandleMineSentenceDigitMainDepsHandler<
|
||||
TSubtitleTimingTracker,
|
||||
TAnkiIntegration,
|
||||
>(deps: {
|
||||
getSubtitleTimingTracker: () => TSubtitleTimingTracker;
|
||||
getAnkiIntegration: () => TAnkiIntegration;
|
||||
getCurrentSecondarySubText: () => string | undefined;
|
||||
showMpvOsd: (text: string) => void;
|
||||
logError: (message: string, err: unknown) => void;
|
||||
onCardsMined: (count: number) => void;
|
||||
handleMineSentenceDigitCore: (
|
||||
count: number,
|
||||
options: {
|
||||
subtitleTimingTracker: TSubtitleTimingTracker;
|
||||
ankiIntegration: TAnkiIntegration;
|
||||
getCurrentSecondarySubText: () => string | undefined;
|
||||
showMpvOsd: (text: string) => void;
|
||||
logError: (message: string, err: unknown) => void;
|
||||
onCardsMined: (count: number) => void;
|
||||
},
|
||||
) => void;
|
||||
}) {
|
||||
return () => ({
|
||||
getSubtitleTimingTracker: () => deps.getSubtitleTimingTracker(),
|
||||
getAnkiIntegration: () => deps.getAnkiIntegration(),
|
||||
getCurrentSecondarySubText: () => deps.getCurrentSecondarySubText(),
|
||||
showMpvOsd: (text: string) => deps.showMpvOsd(text),
|
||||
logError: (message: string, err: unknown) => deps.logError(message, err),
|
||||
onCardsMined: (count: number) => deps.onCardsMined(count),
|
||||
handleMineSentenceDigitCore: (
|
||||
count: number,
|
||||
options: {
|
||||
subtitleTimingTracker: TSubtitleTimingTracker;
|
||||
ankiIntegration: TAnkiIntegration;
|
||||
getCurrentSecondarySubText: () => string | undefined;
|
||||
showMpvOsd: (text: string) => void;
|
||||
logError: (message: string, err: unknown) => void;
|
||||
onCardsMined: (count: number) => void;
|
||||
},
|
||||
) => deps.handleMineSentenceDigitCore(count, options),
|
||||
});
|
||||
}
|
||||
38
src/main/runtime/numeric-shortcut-session-main-deps.test.ts
Normal file
38
src/main/runtime/numeric-shortcut-session-main-deps.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildCancelNumericShortcutSessionMainDepsHandler,
|
||||
createBuildStartNumericShortcutSessionMainDepsHandler,
|
||||
} from './numeric-shortcut-session-main-deps';
|
||||
|
||||
test('numeric shortcut session main deps builders map callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
const session = {
|
||||
start: () => calls.push('start'),
|
||||
cancel: () => calls.push('cancel'),
|
||||
};
|
||||
|
||||
const cancel = createBuildCancelNumericShortcutSessionMainDepsHandler({ session })();
|
||||
cancel.session.cancel();
|
||||
|
||||
const start = createBuildStartNumericShortcutSessionMainDepsHandler({
|
||||
session,
|
||||
onDigit: (digit) => calls.push(`digit:${digit}`),
|
||||
messages: {
|
||||
prompt: 'prompt',
|
||||
timeout: 'timeout',
|
||||
cancelled: 'cancelled',
|
||||
},
|
||||
})();
|
||||
start.session.start({
|
||||
timeoutMs: 100,
|
||||
onDigit: () => {},
|
||||
messages: start.messages,
|
||||
});
|
||||
start.onDigit(4);
|
||||
assert.equal(start.messages.prompt, 'prompt');
|
||||
assert.equal(start.messages.timeout, 'timeout');
|
||||
assert.equal(start.messages.cancelled, 'cancelled');
|
||||
|
||||
assert.deepEqual(calls, ['cancel', 'start', 'digit:4']);
|
||||
});
|
||||
25
src/main/runtime/numeric-shortcut-session-main-deps.ts
Normal file
25
src/main/runtime/numeric-shortcut-session-main-deps.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type {
|
||||
createCancelNumericShortcutSessionHandler,
|
||||
createStartNumericShortcutSessionHandler,
|
||||
} from './numeric-shortcut-session-handlers';
|
||||
|
||||
type CancelNumericShortcutSessionMainDeps = Parameters<typeof createCancelNumericShortcutSessionHandler>[0];
|
||||
type StartNumericShortcutSessionMainDeps = Parameters<typeof createStartNumericShortcutSessionHandler>[0];
|
||||
|
||||
export function createBuildCancelNumericShortcutSessionMainDepsHandler(
|
||||
deps: CancelNumericShortcutSessionMainDeps,
|
||||
) {
|
||||
return (): CancelNumericShortcutSessionMainDeps => ({
|
||||
session: deps.session,
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildStartNumericShortcutSessionMainDepsHandler(
|
||||
deps: StartNumericShortcutSessionMainDeps,
|
||||
) {
|
||||
return (): StartNumericShortcutSessionMainDeps => ({
|
||||
session: deps.session,
|
||||
onDigit: (digit: number) => deps.onDigit(digit),
|
||||
messages: deps.messages,
|
||||
});
|
||||
}
|
||||
57
src/main/runtime/overlay-main-actions-main-deps.test.ts
Normal file
57
src/main/runtime/overlay-main-actions-main-deps.test.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildAppendClipboardVideoToQueueMainDepsHandler,
|
||||
createBuildHandleOverlayModalClosedMainDepsHandler,
|
||||
createBuildSetOverlayVisibleMainDepsHandler,
|
||||
createBuildToggleOverlayMainDepsHandler,
|
||||
} from './overlay-main-actions-main-deps';
|
||||
|
||||
test('overlay main action main deps builders map callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
const setOverlay = createBuildSetOverlayVisibleMainDepsHandler({
|
||||
setVisibleOverlayVisible: (visible) => calls.push(`set:${visible}`),
|
||||
})();
|
||||
setOverlay.setVisibleOverlayVisible(true);
|
||||
|
||||
const toggleOverlay = createBuildToggleOverlayMainDepsHandler({
|
||||
toggleVisibleOverlay: () => calls.push('toggle'),
|
||||
})();
|
||||
toggleOverlay.toggleVisibleOverlay();
|
||||
|
||||
const modalClosed = createBuildHandleOverlayModalClosedMainDepsHandler({
|
||||
handleOverlayModalClosedRuntime: (modal) => calls.push(`modal:${modal}`),
|
||||
})();
|
||||
modalClosed.handleOverlayModalClosedRuntime('runtime-options');
|
||||
|
||||
const append = createBuildAppendClipboardVideoToQueueMainDepsHandler({
|
||||
appendClipboardVideoToQueueRuntime: () => {
|
||||
calls.push('append');
|
||||
return { ok: true, message: 'ok' };
|
||||
},
|
||||
getMpvClient: () => ({ connected: true }),
|
||||
readClipboardText: () => '/tmp/v.mkv',
|
||||
showMpvOsd: (text) => calls.push(`osd:${text}`),
|
||||
sendMpvCommand: (command) => calls.push(`cmd:${command.join(':')}`),
|
||||
})();
|
||||
assert.deepEqual(append.appendClipboardVideoToQueueRuntime({
|
||||
getMpvClient: () => ({ connected: true }),
|
||||
readClipboardText: () => '/tmp/v.mkv',
|
||||
showMpvOsd: () => {},
|
||||
sendMpvCommand: () => {},
|
||||
}), { ok: true, message: 'ok' });
|
||||
assert.equal(append.readClipboardText(), '/tmp/v.mkv');
|
||||
assert.equal(typeof append.getMpvClient(), 'object');
|
||||
append.showMpvOsd('queued');
|
||||
append.sendMpvCommand(['loadfile', '/tmp/v.mkv', 'append']);
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
'set:true',
|
||||
'toggle',
|
||||
'modal:runtime-options',
|
||||
'append',
|
||||
'osd:queued',
|
||||
'cmd:loadfile:/tmp/v.mkv:append',
|
||||
]);
|
||||
});
|
||||
43
src/main/runtime/overlay-main-actions-main-deps.ts
Normal file
43
src/main/runtime/overlay-main-actions-main-deps.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type {
|
||||
createAppendClipboardVideoToQueueHandler,
|
||||
createHandleOverlayModalClosedHandler,
|
||||
createSetOverlayVisibleHandler,
|
||||
createToggleOverlayHandler,
|
||||
} from './overlay-main-actions';
|
||||
|
||||
type SetOverlayVisibleMainDeps = Parameters<typeof createSetOverlayVisibleHandler>[0];
|
||||
type ToggleOverlayMainDeps = Parameters<typeof createToggleOverlayHandler>[0];
|
||||
type HandleOverlayModalClosedMainDeps = Parameters<typeof createHandleOverlayModalClosedHandler>[0];
|
||||
type AppendClipboardVideoToQueueMainDeps = Parameters<typeof createAppendClipboardVideoToQueueHandler>[0];
|
||||
|
||||
export function createBuildSetOverlayVisibleMainDepsHandler(deps: SetOverlayVisibleMainDeps) {
|
||||
return (): SetOverlayVisibleMainDeps => ({
|
||||
setVisibleOverlayVisible: (visible: boolean) => deps.setVisibleOverlayVisible(visible),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildToggleOverlayMainDepsHandler(deps: ToggleOverlayMainDeps) {
|
||||
return (): ToggleOverlayMainDeps => ({
|
||||
toggleVisibleOverlay: () => deps.toggleVisibleOverlay(),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildHandleOverlayModalClosedMainDepsHandler(
|
||||
deps: HandleOverlayModalClosedMainDeps,
|
||||
) {
|
||||
return (): HandleOverlayModalClosedMainDeps => ({
|
||||
handleOverlayModalClosedRuntime: (modal) => deps.handleOverlayModalClosedRuntime(modal),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildAppendClipboardVideoToQueueMainDepsHandler(
|
||||
deps: AppendClipboardVideoToQueueMainDeps,
|
||||
) {
|
||||
return (): AppendClipboardVideoToQueueMainDeps => ({
|
||||
appendClipboardVideoToQueueRuntime: (options) => deps.appendClipboardVideoToQueueRuntime(options),
|
||||
getMpvClient: () => deps.getMpvClient(),
|
||||
readClipboardText: () => deps.readClipboardText(),
|
||||
showMpvOsd: (text: string) => deps.showMpvOsd(text),
|
||||
sendMpvCommand: (command: (string | number)[]) => deps.sendMpvCommand(command),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildRefreshOverlayShortcutsMainDepsHandler,
|
||||
createBuildRegisterOverlayShortcutsMainDepsHandler,
|
||||
createBuildSyncOverlayShortcutsMainDepsHandler,
|
||||
createBuildUnregisterOverlayShortcutsMainDepsHandler,
|
||||
} from './overlay-shortcuts-lifecycle-main-deps';
|
||||
|
||||
test('overlay shortcuts lifecycle main deps builders map runtime instance', () => {
|
||||
const runtime = {
|
||||
registerOverlayShortcuts: () => {},
|
||||
unregisterOverlayShortcuts: () => {},
|
||||
syncOverlayShortcuts: () => {},
|
||||
refreshOverlayShortcuts: () => {},
|
||||
};
|
||||
|
||||
const register = createBuildRegisterOverlayShortcutsMainDepsHandler({
|
||||
overlayShortcutsRuntime: runtime,
|
||||
})();
|
||||
const unregister = createBuildUnregisterOverlayShortcutsMainDepsHandler({
|
||||
overlayShortcutsRuntime: runtime,
|
||||
})();
|
||||
const sync = createBuildSyncOverlayShortcutsMainDepsHandler({
|
||||
overlayShortcutsRuntime: runtime,
|
||||
})();
|
||||
const refresh = createBuildRefreshOverlayShortcutsMainDepsHandler({
|
||||
overlayShortcutsRuntime: runtime,
|
||||
})();
|
||||
|
||||
assert.equal(register.overlayShortcutsRuntime, runtime);
|
||||
assert.equal(unregister.overlayShortcutsRuntime, runtime);
|
||||
assert.equal(sync.overlayShortcutsRuntime, runtime);
|
||||
assert.equal(refresh.overlayShortcutsRuntime, runtime);
|
||||
});
|
||||
41
src/main/runtime/overlay-shortcuts-lifecycle-main-deps.ts
Normal file
41
src/main/runtime/overlay-shortcuts-lifecycle-main-deps.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type {
|
||||
createRefreshOverlayShortcutsHandler,
|
||||
createRegisterOverlayShortcutsHandler,
|
||||
createSyncOverlayShortcutsHandler,
|
||||
createUnregisterOverlayShortcutsHandler,
|
||||
} from './overlay-shortcuts-lifecycle';
|
||||
|
||||
type RegisterOverlayShortcutsMainDeps = Parameters<typeof createRegisterOverlayShortcutsHandler>[0];
|
||||
type UnregisterOverlayShortcutsMainDeps = Parameters<typeof createUnregisterOverlayShortcutsHandler>[0];
|
||||
type SyncOverlayShortcutsMainDeps = Parameters<typeof createSyncOverlayShortcutsHandler>[0];
|
||||
type RefreshOverlayShortcutsMainDeps = Parameters<typeof createRefreshOverlayShortcutsHandler>[0];
|
||||
|
||||
export function createBuildRegisterOverlayShortcutsMainDepsHandler(
|
||||
deps: RegisterOverlayShortcutsMainDeps,
|
||||
) {
|
||||
return (): RegisterOverlayShortcutsMainDeps => ({
|
||||
overlayShortcutsRuntime: deps.overlayShortcutsRuntime,
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildUnregisterOverlayShortcutsMainDepsHandler(
|
||||
deps: UnregisterOverlayShortcutsMainDeps,
|
||||
) {
|
||||
return (): UnregisterOverlayShortcutsMainDeps => ({
|
||||
overlayShortcutsRuntime: deps.overlayShortcutsRuntime,
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildSyncOverlayShortcutsMainDepsHandler(deps: SyncOverlayShortcutsMainDeps) {
|
||||
return (): SyncOverlayShortcutsMainDeps => ({
|
||||
overlayShortcutsRuntime: deps.overlayShortcutsRuntime,
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildRefreshOverlayShortcutsMainDepsHandler(
|
||||
deps: RefreshOverlayShortcutsMainDeps,
|
||||
) {
|
||||
return (): RefreshOverlayShortcutsMainDeps => ({
|
||||
overlayShortcutsRuntime: deps.overlayShortcutsRuntime,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildSetInvisibleOverlayVisibleMainDepsHandler,
|
||||
createBuildSetVisibleOverlayVisibleMainDepsHandler,
|
||||
createBuildToggleInvisibleOverlayMainDepsHandler,
|
||||
createBuildToggleVisibleOverlayMainDepsHandler,
|
||||
} from './overlay-visibility-actions-main-deps';
|
||||
|
||||
test('overlay visibility action main deps builders map callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
const setVisible = createBuildSetVisibleOverlayVisibleMainDepsHandler({
|
||||
setVisibleOverlayVisibleCore: () => calls.push('visible-core'),
|
||||
setVisibleOverlayVisibleState: (visible) => calls.push(`visible-state:${visible}`),
|
||||
updateVisibleOverlayVisibility: () => calls.push('update-visible'),
|
||||
updateInvisibleOverlayVisibility: () => calls.push('update-invisible'),
|
||||
syncInvisibleOverlayMousePassthrough: () => calls.push('sync'),
|
||||
shouldBindVisibleOverlayToMpvSubVisibility: () => true,
|
||||
isMpvConnected: () => true,
|
||||
setMpvSubVisibility: (visible) => calls.push(`mpv:${visible}`),
|
||||
})();
|
||||
setVisible.setVisibleOverlayVisibleCore({
|
||||
visible: true,
|
||||
setVisibleOverlayVisibleState: () => {},
|
||||
updateVisibleOverlayVisibility: () => {},
|
||||
updateInvisibleOverlayVisibility: () => {},
|
||||
syncInvisibleOverlayMousePassthrough: () => {},
|
||||
shouldBindVisibleOverlayToMpvSubVisibility: () => true,
|
||||
isMpvConnected: () => true,
|
||||
setMpvSubVisibility: () => {},
|
||||
});
|
||||
setVisible.setVisibleOverlayVisibleState(true);
|
||||
setVisible.updateVisibleOverlayVisibility();
|
||||
setVisible.updateInvisibleOverlayVisibility();
|
||||
setVisible.syncInvisibleOverlayMousePassthrough();
|
||||
assert.equal(setVisible.shouldBindVisibleOverlayToMpvSubVisibility(), true);
|
||||
assert.equal(setVisible.isMpvConnected(), true);
|
||||
setVisible.setMpvSubVisibility(false);
|
||||
|
||||
const setInvisible = createBuildSetInvisibleOverlayVisibleMainDepsHandler({
|
||||
setInvisibleOverlayVisibleCore: () => calls.push('invisible-core'),
|
||||
setInvisibleOverlayVisibleState: (visible) => calls.push(`invisible-state:${visible}`),
|
||||
updateInvisibleOverlayVisibility: () => calls.push('update-only-invisible'),
|
||||
syncInvisibleOverlayMousePassthrough: () => calls.push('sync-only'),
|
||||
})();
|
||||
setInvisible.setInvisibleOverlayVisibleCore({
|
||||
visible: false,
|
||||
setInvisibleOverlayVisibleState: () => {},
|
||||
updateInvisibleOverlayVisibility: () => {},
|
||||
syncInvisibleOverlayMousePassthrough: () => {},
|
||||
});
|
||||
setInvisible.setInvisibleOverlayVisibleState(false);
|
||||
setInvisible.updateInvisibleOverlayVisibility();
|
||||
setInvisible.syncInvisibleOverlayMousePassthrough();
|
||||
|
||||
const toggleVisible = createBuildToggleVisibleOverlayMainDepsHandler({
|
||||
getVisibleOverlayVisible: () => false,
|
||||
setVisibleOverlayVisible: (visible) => calls.push(`toggle-visible:${visible}`),
|
||||
})();
|
||||
assert.equal(toggleVisible.getVisibleOverlayVisible(), false);
|
||||
toggleVisible.setVisibleOverlayVisible(true);
|
||||
|
||||
const toggleInvisible = createBuildToggleInvisibleOverlayMainDepsHandler({
|
||||
getInvisibleOverlayVisible: () => true,
|
||||
setInvisibleOverlayVisible: (visible) => calls.push(`toggle-invisible:${visible}`),
|
||||
})();
|
||||
assert.equal(toggleInvisible.getInvisibleOverlayVisible(), true);
|
||||
toggleInvisible.setInvisibleOverlayVisible(false);
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
'visible-core',
|
||||
'visible-state:true',
|
||||
'update-visible',
|
||||
'update-invisible',
|
||||
'sync',
|
||||
'mpv:false',
|
||||
'invisible-core',
|
||||
'invisible-state:false',
|
||||
'update-only-invisible',
|
||||
'sync-only',
|
||||
'toggle-visible:true',
|
||||
'toggle-invisible:false',
|
||||
]);
|
||||
});
|
||||
53
src/main/runtime/overlay-visibility-actions-main-deps.ts
Normal file
53
src/main/runtime/overlay-visibility-actions-main-deps.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import type {
|
||||
createSetInvisibleOverlayVisibleHandler,
|
||||
createSetVisibleOverlayVisibleHandler,
|
||||
createToggleInvisibleOverlayHandler,
|
||||
createToggleVisibleOverlayHandler,
|
||||
} from './overlay-visibility-actions';
|
||||
|
||||
type SetVisibleOverlayVisibleMainDeps = Parameters<typeof createSetVisibleOverlayVisibleHandler>[0];
|
||||
type SetInvisibleOverlayVisibleMainDeps = Parameters<typeof createSetInvisibleOverlayVisibleHandler>[0];
|
||||
type ToggleVisibleOverlayMainDeps = Parameters<typeof createToggleVisibleOverlayHandler>[0];
|
||||
type ToggleInvisibleOverlayMainDeps = Parameters<typeof createToggleInvisibleOverlayHandler>[0];
|
||||
|
||||
export function createBuildSetVisibleOverlayVisibleMainDepsHandler(
|
||||
deps: SetVisibleOverlayVisibleMainDeps,
|
||||
) {
|
||||
return (): SetVisibleOverlayVisibleMainDeps => ({
|
||||
setVisibleOverlayVisibleCore: (options) => deps.setVisibleOverlayVisibleCore(options),
|
||||
setVisibleOverlayVisibleState: (visible: boolean) => deps.setVisibleOverlayVisibleState(visible),
|
||||
updateVisibleOverlayVisibility: () => deps.updateVisibleOverlayVisibility(),
|
||||
updateInvisibleOverlayVisibility: () => deps.updateInvisibleOverlayVisibility(),
|
||||
syncInvisibleOverlayMousePassthrough: () => deps.syncInvisibleOverlayMousePassthrough(),
|
||||
shouldBindVisibleOverlayToMpvSubVisibility: () => deps.shouldBindVisibleOverlayToMpvSubVisibility(),
|
||||
isMpvConnected: () => deps.isMpvConnected(),
|
||||
setMpvSubVisibility: (visible: boolean) => deps.setMpvSubVisibility(visible),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildSetInvisibleOverlayVisibleMainDepsHandler(
|
||||
deps: SetInvisibleOverlayVisibleMainDeps,
|
||||
) {
|
||||
return (): SetInvisibleOverlayVisibleMainDeps => ({
|
||||
setInvisibleOverlayVisibleCore: (options) => deps.setInvisibleOverlayVisibleCore(options),
|
||||
setInvisibleOverlayVisibleState: (visible: boolean) => deps.setInvisibleOverlayVisibleState(visible),
|
||||
updateInvisibleOverlayVisibility: () => deps.updateInvisibleOverlayVisibility(),
|
||||
syncInvisibleOverlayMousePassthrough: () => deps.syncInvisibleOverlayMousePassthrough(),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildToggleVisibleOverlayMainDepsHandler(deps: ToggleVisibleOverlayMainDeps) {
|
||||
return (): ToggleVisibleOverlayMainDeps => ({
|
||||
getVisibleOverlayVisible: () => deps.getVisibleOverlayVisible(),
|
||||
setVisibleOverlayVisible: (visible: boolean) => deps.setVisibleOverlayVisible(visible),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildToggleInvisibleOverlayMainDepsHandler(
|
||||
deps: ToggleInvisibleOverlayMainDeps,
|
||||
) {
|
||||
return (): ToggleInvisibleOverlayMainDeps => ({
|
||||
getInvisibleOverlayVisible: () => deps.getInvisibleOverlayVisible(),
|
||||
setInvisibleOverlayVisible: (visible: boolean) => deps.setInvisibleOverlayVisible(visible),
|
||||
});
|
||||
}
|
||||
56
src/main/runtime/overlay-window-layout-main-deps.test.ts
Normal file
56
src/main/runtime/overlay-window-layout-main-deps.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildEnforceOverlayLayerOrderMainDepsHandler,
|
||||
createBuildEnsureOverlayWindowLevelMainDepsHandler,
|
||||
createBuildUpdateInvisibleOverlayBoundsMainDepsHandler,
|
||||
createBuildUpdateVisibleOverlayBoundsMainDepsHandler,
|
||||
} from './overlay-window-layout-main-deps';
|
||||
|
||||
test('overlay window layout main deps builders map callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
const visible = createBuildUpdateVisibleOverlayBoundsMainDepsHandler({
|
||||
setOverlayWindowBounds: (layer) => calls.push(`visible:${layer}`),
|
||||
})();
|
||||
visible.setOverlayWindowBounds('visible', { x: 0, y: 0, width: 1, height: 1 });
|
||||
|
||||
const invisible = createBuildUpdateInvisibleOverlayBoundsMainDepsHandler({
|
||||
setOverlayWindowBounds: (layer) => calls.push(`invisible:${layer}`),
|
||||
})();
|
||||
invisible.setOverlayWindowBounds('invisible', { x: 0, y: 0, width: 1, height: 1 });
|
||||
|
||||
const level = createBuildEnsureOverlayWindowLevelMainDepsHandler({
|
||||
ensureOverlayWindowLevelCore: () => calls.push('ensure'),
|
||||
})();
|
||||
level.ensureOverlayWindowLevelCore({});
|
||||
|
||||
const order = createBuildEnforceOverlayLayerOrderMainDepsHandler({
|
||||
enforceOverlayLayerOrderCore: () => calls.push('order'),
|
||||
getVisibleOverlayVisible: () => true,
|
||||
getInvisibleOverlayVisible: () => false,
|
||||
getMainWindow: () => ({ kind: 'main' }),
|
||||
getInvisibleWindow: () => ({ kind: 'invisible' }),
|
||||
ensureOverlayWindowLevel: () => calls.push('order-level'),
|
||||
})();
|
||||
order.enforceOverlayLayerOrderCore({
|
||||
visibleOverlayVisible: true,
|
||||
invisibleOverlayVisible: false,
|
||||
mainWindow: null,
|
||||
invisibleWindow: null,
|
||||
ensureOverlayWindowLevel: () => {},
|
||||
});
|
||||
assert.equal(order.getVisibleOverlayVisible(), true);
|
||||
assert.equal(order.getInvisibleOverlayVisible(), false);
|
||||
assert.deepEqual(order.getMainWindow(), { kind: 'main' });
|
||||
assert.deepEqual(order.getInvisibleWindow(), { kind: 'invisible' });
|
||||
order.ensureOverlayWindowLevel({});
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
'visible:visible',
|
||||
'invisible:invisible',
|
||||
'ensure',
|
||||
'order',
|
||||
'order-level',
|
||||
]);
|
||||
});
|
||||
48
src/main/runtime/overlay-window-layout-main-deps.ts
Normal file
48
src/main/runtime/overlay-window-layout-main-deps.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type {
|
||||
createEnforceOverlayLayerOrderHandler,
|
||||
createEnsureOverlayWindowLevelHandler,
|
||||
createUpdateInvisibleOverlayBoundsHandler,
|
||||
createUpdateVisibleOverlayBoundsHandler,
|
||||
} from './overlay-window-layout';
|
||||
|
||||
type UpdateVisibleOverlayBoundsMainDeps = Parameters<typeof createUpdateVisibleOverlayBoundsHandler>[0];
|
||||
type UpdateInvisibleOverlayBoundsMainDeps = Parameters<typeof createUpdateInvisibleOverlayBoundsHandler>[0];
|
||||
type EnsureOverlayWindowLevelMainDeps = Parameters<typeof createEnsureOverlayWindowLevelHandler>[0];
|
||||
type EnforceOverlayLayerOrderMainDeps = Parameters<typeof createEnforceOverlayLayerOrderHandler>[0];
|
||||
|
||||
export function createBuildUpdateVisibleOverlayBoundsMainDepsHandler(
|
||||
deps: UpdateVisibleOverlayBoundsMainDeps,
|
||||
) {
|
||||
return (): UpdateVisibleOverlayBoundsMainDeps => ({
|
||||
setOverlayWindowBounds: (layer, geometry) => deps.setOverlayWindowBounds(layer, geometry),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildUpdateInvisibleOverlayBoundsMainDepsHandler(
|
||||
deps: UpdateInvisibleOverlayBoundsMainDeps,
|
||||
) {
|
||||
return (): UpdateInvisibleOverlayBoundsMainDeps => ({
|
||||
setOverlayWindowBounds: (layer, geometry) => deps.setOverlayWindowBounds(layer, geometry),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildEnsureOverlayWindowLevelMainDepsHandler(
|
||||
deps: EnsureOverlayWindowLevelMainDeps,
|
||||
) {
|
||||
return (): EnsureOverlayWindowLevelMainDeps => ({
|
||||
ensureOverlayWindowLevelCore: (window: unknown) => deps.ensureOverlayWindowLevelCore(window),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildEnforceOverlayLayerOrderMainDepsHandler(
|
||||
deps: EnforceOverlayLayerOrderMainDeps,
|
||||
) {
|
||||
return (): EnforceOverlayLayerOrderMainDeps => ({
|
||||
enforceOverlayLayerOrderCore: (params) => deps.enforceOverlayLayerOrderCore(params),
|
||||
getVisibleOverlayVisible: () => deps.getVisibleOverlayVisible(),
|
||||
getInvisibleOverlayVisible: () => deps.getInvisibleOverlayVisible(),
|
||||
getMainWindow: () => deps.getMainWindow(),
|
||||
getInvisibleWindow: () => deps.getInvisibleWindow(),
|
||||
ensureOverlayWindowLevel: (window: unknown) => deps.ensureOverlayWindowLevel(window),
|
||||
});
|
||||
}
|
||||
65
src/main/runtime/startup-warmups-main-deps.test.ts
Normal file
65
src/main/runtime/startup-warmups-main-deps.test.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildLaunchBackgroundWarmupTaskMainDepsHandler,
|
||||
createBuildStartBackgroundWarmupsMainDepsHandler,
|
||||
} from './startup-warmups-main-deps';
|
||||
|
||||
test('startup warmups main deps builders map callbacks', async () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
const launch = createBuildLaunchBackgroundWarmupTaskMainDepsHandler({
|
||||
now: () => 11,
|
||||
logDebug: (message) => calls.push(`debug:${message}`),
|
||||
logWarn: (message) => calls.push(`warn:${message}`),
|
||||
})();
|
||||
assert.equal(launch.now(), 11);
|
||||
launch.logDebug('x');
|
||||
launch.logWarn('y');
|
||||
|
||||
const start = createBuildStartBackgroundWarmupsMainDepsHandler({
|
||||
getStarted: () => false,
|
||||
setStarted: (started) => calls.push(`started:${started}`),
|
||||
isTexthookerOnlyMode: () => false,
|
||||
launchTask: (label, task) => {
|
||||
calls.push(`launch:${label}`);
|
||||
void task();
|
||||
},
|
||||
createMecabTokenizerAndCheck: async () => {
|
||||
calls.push('mecab');
|
||||
},
|
||||
ensureYomitanExtensionLoaded: async () => {
|
||||
calls.push('yomitan');
|
||||
},
|
||||
prewarmSubtitleDictionaries: async () => {
|
||||
calls.push('dict');
|
||||
},
|
||||
shouldAutoConnectJellyfinRemote: () => true,
|
||||
startJellyfinRemoteSession: async () => {
|
||||
calls.push('jellyfin');
|
||||
},
|
||||
})();
|
||||
assert.equal(start.getStarted(), false);
|
||||
start.setStarted(true);
|
||||
assert.equal(start.isTexthookerOnlyMode(), false);
|
||||
start.launchTask('demo', async () => {
|
||||
calls.push('task');
|
||||
});
|
||||
await start.createMecabTokenizerAndCheck();
|
||||
await start.ensureYomitanExtensionLoaded();
|
||||
await start.prewarmSubtitleDictionaries();
|
||||
assert.equal(start.shouldAutoConnectJellyfinRemote(), true);
|
||||
await start.startJellyfinRemoteSession();
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
'debug:x',
|
||||
'warn:y',
|
||||
'started:true',
|
||||
'launch:demo',
|
||||
'task',
|
||||
'mecab',
|
||||
'yomitan',
|
||||
'dict',
|
||||
'jellyfin',
|
||||
]);
|
||||
});
|
||||
31
src/main/runtime/startup-warmups-main-deps.ts
Normal file
31
src/main/runtime/startup-warmups-main-deps.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type {
|
||||
createLaunchBackgroundWarmupTaskHandler,
|
||||
createStartBackgroundWarmupsHandler,
|
||||
} from './startup-warmups';
|
||||
|
||||
type LaunchBackgroundWarmupTaskMainDeps = Parameters<typeof createLaunchBackgroundWarmupTaskHandler>[0];
|
||||
type StartBackgroundWarmupsMainDeps = Parameters<typeof createStartBackgroundWarmupsHandler>[0];
|
||||
|
||||
export function createBuildLaunchBackgroundWarmupTaskMainDepsHandler(
|
||||
deps: LaunchBackgroundWarmupTaskMainDeps,
|
||||
) {
|
||||
return (): LaunchBackgroundWarmupTaskMainDeps => ({
|
||||
now: () => deps.now(),
|
||||
logDebug: (message: string) => deps.logDebug(message),
|
||||
logWarn: (message: string) => deps.logWarn(message),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildStartBackgroundWarmupsMainDepsHandler(deps: StartBackgroundWarmupsMainDeps) {
|
||||
return (): StartBackgroundWarmupsMainDeps => ({
|
||||
getStarted: () => deps.getStarted(),
|
||||
setStarted: (started: boolean) => deps.setStarted(started),
|
||||
isTexthookerOnlyMode: () => deps.isTexthookerOnlyMode(),
|
||||
launchTask: (label: string, task: () => Promise<void>) => deps.launchTask(label, task),
|
||||
createMecabTokenizerAndCheck: () => deps.createMecabTokenizerAndCheck(),
|
||||
ensureYomitanExtensionLoaded: () => deps.ensureYomitanExtensionLoaded(),
|
||||
prewarmSubtitleDictionaries: () => deps.prewarmSubtitleDictionaries(),
|
||||
shouldAutoConnectJellyfinRemote: () => deps.shouldAutoConnectJellyfinRemote(),
|
||||
startJellyfinRemoteSession: () => deps.startJellyfinRemoteSession(),
|
||||
});
|
||||
}
|
||||
30
src/main/runtime/subtitle-position-main-deps.test.ts
Normal file
30
src/main/runtime/subtitle-position-main-deps.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildLoadSubtitlePositionMainDepsHandler,
|
||||
createBuildSaveSubtitlePositionMainDepsHandler,
|
||||
} from './subtitle-position-main-deps';
|
||||
|
||||
test('load subtitle position main deps builder maps callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildLoadSubtitlePositionMainDepsHandler({
|
||||
loadSubtitlePositionCore: () => ({ x: 1, y: 2 } as never),
|
||||
setSubtitlePosition: () => calls.push('set'),
|
||||
})();
|
||||
|
||||
assert.deepEqual(deps.loadSubtitlePositionCore(), { x: 1, y: 2 });
|
||||
deps.setSubtitlePosition({ x: 3, y: 4 } as never);
|
||||
assert.deepEqual(calls, ['set']);
|
||||
});
|
||||
|
||||
test('save subtitle position main deps builder maps callbacks', () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildSaveSubtitlePositionMainDepsHandler({
|
||||
saveSubtitlePositionCore: () => calls.push('persist'),
|
||||
setSubtitlePosition: () => calls.push('set'),
|
||||
})();
|
||||
|
||||
deps.setSubtitlePosition({ x: 1, y: 2 } as never);
|
||||
deps.saveSubtitlePositionCore({ x: 1, y: 2 } as never);
|
||||
assert.deepEqual(calls, ['set', 'persist']);
|
||||
});
|
||||
21
src/main/runtime/subtitle-position-main-deps.ts
Normal file
21
src/main/runtime/subtitle-position-main-deps.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type {
|
||||
createLoadSubtitlePositionHandler,
|
||||
createSaveSubtitlePositionHandler,
|
||||
} from './subtitle-position';
|
||||
|
||||
type LoadSubtitlePositionMainDeps = Parameters<typeof createLoadSubtitlePositionHandler>[0];
|
||||
type SaveSubtitlePositionMainDeps = Parameters<typeof createSaveSubtitlePositionHandler>[0];
|
||||
|
||||
export function createBuildLoadSubtitlePositionMainDepsHandler(deps: LoadSubtitlePositionMainDeps) {
|
||||
return (): LoadSubtitlePositionMainDeps => ({
|
||||
loadSubtitlePositionCore: () => deps.loadSubtitlePositionCore(),
|
||||
setSubtitlePosition: (position) => deps.setSubtitlePosition(position),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildSaveSubtitlePositionMainDepsHandler(deps: SaveSubtitlePositionMainDeps) {
|
||||
return (): SaveSubtitlePositionMainDeps => ({
|
||||
saveSubtitlePositionCore: (position) => deps.saveSubtitlePositionCore(position),
|
||||
setSubtitlePosition: (position) => deps.setSubtitlePosition(position),
|
||||
});
|
||||
}
|
||||
49
src/main/runtime/yomitan-extension-loader-main-deps.test.ts
Normal file
49
src/main/runtime/yomitan-extension-loader-main-deps.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildEnsureYomitanExtensionLoadedMainDepsHandler,
|
||||
createBuildLoadYomitanExtensionMainDepsHandler,
|
||||
} from './yomitan-extension-loader-main-deps';
|
||||
|
||||
test('load yomitan extension main deps builder maps callbacks', async () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildLoadYomitanExtensionMainDepsHandler({
|
||||
loadYomitanExtensionCore: async () => {
|
||||
calls.push('load-core');
|
||||
return null;
|
||||
},
|
||||
userDataPath: '/tmp/subminer',
|
||||
getYomitanParserWindow: () => null,
|
||||
setYomitanParserWindow: () => calls.push('set-window'),
|
||||
setYomitanParserReadyPromise: () => calls.push('set-ready'),
|
||||
setYomitanParserInitPromise: () => calls.push('set-init'),
|
||||
setYomitanExtension: () => calls.push('set-ext'),
|
||||
})();
|
||||
|
||||
assert.equal(deps.userDataPath, '/tmp/subminer');
|
||||
await deps.loadYomitanExtensionCore({} as never);
|
||||
deps.setYomitanParserWindow(null);
|
||||
deps.setYomitanParserReadyPromise(null);
|
||||
deps.setYomitanParserInitPromise(null);
|
||||
deps.setYomitanExtension(null);
|
||||
assert.deepEqual(calls, ['load-core', 'set-window', 'set-ready', 'set-init', 'set-ext']);
|
||||
});
|
||||
|
||||
test('ensure yomitan extension loaded main deps builder maps callbacks', async () => {
|
||||
const calls: string[] = [];
|
||||
const deps = createBuildEnsureYomitanExtensionLoadedMainDepsHandler({
|
||||
getYomitanExtension: () => null,
|
||||
getLoadInFlight: () => null,
|
||||
setLoadInFlight: () => calls.push('set-inflight'),
|
||||
loadYomitanExtension: async () => {
|
||||
calls.push('load');
|
||||
return null;
|
||||
},
|
||||
})();
|
||||
|
||||
assert.equal(deps.getYomitanExtension(), null);
|
||||
assert.equal(deps.getLoadInFlight(), null);
|
||||
deps.setLoadInFlight(null);
|
||||
await deps.loadYomitanExtension();
|
||||
assert.deepEqual(calls, ['set-inflight', 'load']);
|
||||
});
|
||||
34
src/main/runtime/yomitan-extension-loader-main-deps.ts
Normal file
34
src/main/runtime/yomitan-extension-loader-main-deps.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type {
|
||||
createEnsureYomitanExtensionLoadedHandler,
|
||||
createLoadYomitanExtensionHandler,
|
||||
} from './yomitan-extension-loader';
|
||||
|
||||
type LoadYomitanExtensionMainDeps = Parameters<typeof createLoadYomitanExtensionHandler>[0];
|
||||
type EnsureYomitanExtensionLoadedMainDeps = Parameters<
|
||||
typeof createEnsureYomitanExtensionLoadedHandler
|
||||
>[0];
|
||||
|
||||
export function createBuildLoadYomitanExtensionMainDepsHandler(
|
||||
deps: LoadYomitanExtensionMainDeps,
|
||||
) {
|
||||
return (): LoadYomitanExtensionMainDeps => ({
|
||||
loadYomitanExtensionCore: (options) => deps.loadYomitanExtensionCore(options),
|
||||
userDataPath: deps.userDataPath,
|
||||
getYomitanParserWindow: () => deps.getYomitanParserWindow(),
|
||||
setYomitanParserWindow: (window) => deps.setYomitanParserWindow(window),
|
||||
setYomitanParserReadyPromise: (promise) => deps.setYomitanParserReadyPromise(promise),
|
||||
setYomitanParserInitPromise: (promise) => deps.setYomitanParserInitPromise(promise),
|
||||
setYomitanExtension: (extension) => deps.setYomitanExtension(extension),
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildEnsureYomitanExtensionLoadedMainDepsHandler(
|
||||
deps: EnsureYomitanExtensionLoadedMainDeps,
|
||||
) {
|
||||
return (): EnsureYomitanExtensionLoadedMainDeps => ({
|
||||
getYomitanExtension: () => deps.getYomitanExtension(),
|
||||
getLoadInFlight: () => deps.getLoadInFlight(),
|
||||
setLoadInFlight: (promise) => deps.setLoadInFlight(promise),
|
||||
loadYomitanExtension: () => deps.loadYomitanExtension(),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user