diff --git a/src/main/runtime/composers/anilist-setup-composer.test.ts b/src/main/runtime/composers/anilist-setup-composer.test.ts index df22dd9..3e81cac 100644 --- a/src/main/runtime/composers/anilist-setup-composer.test.ts +++ b/src/main/runtime/composers/anilist-setup-composer.test.ts @@ -3,11 +3,14 @@ import assert from 'node:assert/strict'; import { composeAnilistSetupHandlers } from './anilist-setup-composer'; test('composeAnilistSetupHandlers returns callable setup handlers', () => { + const calls: string[] = []; const composed = composeAnilistSetupHandlers({ notifyDeps: { hasMpvClient: () => false, showMpvOsd: () => {}, - showDesktopNotification: () => {}, + showDesktopNotification: (title, opts) => { + calls.push(`notify:${opts.body}`); + }, logInfo: () => {}, }, consumeTokenDeps: { @@ -37,4 +40,16 @@ test('composeAnilistSetupHandlers returns callable setup handlers', () => { assert.equal(typeof composed.consumeAnilistSetupTokenFromUrl, 'function'); assert.equal(typeof composed.handleAnilistSetupProtocolUrl, 'function'); assert.equal(typeof composed.registerSubminerProtocolClient, 'function'); + + // notifyAnilistSetup forwards to showDesktopNotification when no MPV client + composed.notifyAnilistSetup('Setup complete'); + assert.deepEqual(calls, ['notify:Setup complete']); + + // handleAnilistSetupProtocolUrl returns false for non-subminer URLs + const handled = composed.handleAnilistSetupProtocolUrl('https://other.example.com/'); + assert.equal(handled, false); + + // handleAnilistSetupProtocolUrl returns true for subminer:// URLs + const handledProtocol = composed.handleAnilistSetupProtocolUrl('subminer://anilist-setup?code=abc'); + assert.equal(handledProtocol, true); }); diff --git a/src/main/runtime/composers/app-ready-composer.test.ts b/src/main/runtime/composers/app-ready-composer.test.ts index 11b8641..d4e98eb 100644 --- a/src/main/runtime/composers/app-ready-composer.test.ts +++ b/src/main/runtime/composers/app-ready-composer.test.ts @@ -3,9 +3,13 @@ import test from 'node:test'; import { composeAppReadyRuntime } from './app-ready-composer'; test('composeAppReadyRuntime returns reload/critical/app-ready handlers', () => { + const calls: string[] = []; const composed = composeAppReadyRuntime({ reloadConfigMainDeps: { - reloadConfigStrict: () => ({ ok: true, path: '/tmp/config.jsonc', warnings: [] }), + reloadConfigStrict: () => { + calls.push('reloadConfigStrict'); + return { ok: true, path: '/tmp/config.jsonc', warnings: [] }; + }, logInfo: () => {}, logWarning: () => {}, showDesktopNotification: () => {}, @@ -79,4 +83,8 @@ test('composeAppReadyRuntime returns reload/critical/app-ready handlers', () => assert.equal(typeof composed.reloadConfig, 'function'); assert.equal(typeof composed.criticalConfigError, 'function'); assert.equal(typeof composed.appReadyRuntimeRunner, 'function'); + + // reloadConfig invokes the injected reloadConfigStrict dep + composed.reloadConfig(); + assert.deepEqual(calls, ['reloadConfigStrict']); }); diff --git a/src/main/runtime/composers/cli-startup-composer.test.ts b/src/main/runtime/composers/cli-startup-composer.test.ts index d81b3f6..071befc 100644 --- a/src/main/runtime/composers/cli-startup-composer.test.ts +++ b/src/main/runtime/composers/cli-startup-composer.test.ts @@ -1,8 +1,10 @@ import assert from 'node:assert/strict'; import test from 'node:test'; +import type { CliArgs } from '../../../cli/args'; import { composeCliStartupHandlers } from './cli-startup-composer'; test('composeCliStartupHandlers returns callable CLI startup handlers', () => { + const calls: string[] = []; const handlers = composeCliStartupHandlers({ cliCommandContextMainDeps: { appState: {} as never, @@ -57,7 +59,9 @@ test('composeCliStartupHandlers returns callable CLI startup handlers', () => { startBackgroundWarmups: () => {}, logInfo: () => {}, }, - handleCliCommandRuntimeServiceWithContext: () => {}, + handleCliCommandRuntimeServiceWithContext: (args, _source, _ctx) => { + calls.push(`handleCommand:${(args as { command?: string }).command ?? 'unknown'}`); + }, }, initialArgsRuntimeHandlerMainDeps: { getInitialArgs: () => null, @@ -80,4 +84,8 @@ test('composeCliStartupHandlers returns callable CLI startup handlers', () => { assert.equal(typeof handlers.createCliCommandContext, 'function'); assert.equal(typeof handlers.handleCliCommand, 'function'); assert.equal(typeof handlers.handleInitialArgs, 'function'); + + // handleCliCommand routes to the injected handleCliCommandRuntimeServiceWithContext dep + handlers.handleCliCommand({ command: 'start' } as unknown as CliArgs); + assert.deepEqual(calls, ['handleCommand:start']); }); diff --git a/src/main/runtime/composers/jellyfin-remote-composer.test.ts b/src/main/runtime/composers/jellyfin-remote-composer.test.ts index e3fa0f8..ec6525f 100644 --- a/src/main/runtime/composers/jellyfin-remote-composer.test.ts +++ b/src/main/runtime/composers/jellyfin-remote-composer.test.ts @@ -2,8 +2,11 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import { composeJellyfinRemoteHandlers } from './jellyfin-remote-composer'; -test('composeJellyfinRemoteHandlers returns callable jellyfin remote handlers', () => { +test('composeJellyfinRemoteHandlers returns callable jellyfin remote handlers', async () => { let lastProgressAt = 0; + let activePlayback: unknown = { itemId: 'item-1', mediaSourceId: 'src-1', playMethod: 'DirectPlay', audioStreamIndex: null, subtitleStreamIndex: null }; + const calls: string[] = []; + const composed = composeJellyfinRemoteHandlers({ getConfiguredSession: () => null, getClientInfo: () => @@ -14,8 +17,11 @@ test('composeJellyfinRemoteHandlers returns callable jellyfin remote handlers', getMpvClient: () => null, sendMpvCommand: () => {}, jellyfinTicksToSeconds: () => 0, - getActivePlayback: () => null, - clearActivePlayback: () => {}, + getActivePlayback: () => activePlayback as never, + clearActivePlayback: () => { + activePlayback = null; + calls.push('clearActivePlayback'); + }, getSession: () => null, getNow: () => 0, getLastProgressAtMs: () => lastProgressAt, @@ -32,4 +38,9 @@ test('composeJellyfinRemoteHandlers returns callable jellyfin remote handlers', assert.equal(typeof composed.handleJellyfinRemotePlay, 'function'); assert.equal(typeof composed.handleJellyfinRemotePlaystate, 'function'); assert.equal(typeof composed.handleJellyfinRemoteGeneralCommand, 'function'); + + // reportJellyfinRemoteStopped clears active playback when there is no connected session + await composed.reportJellyfinRemoteStopped(); + assert.equal(activePlayback, null); + assert.deepEqual(calls, ['clearActivePlayback']); }); diff --git a/src/main/runtime/composers/jellyfin-runtime-composer.test.ts b/src/main/runtime/composers/jellyfin-runtime-composer.test.ts index ed68a1d..53b7486 100644 --- a/src/main/runtime/composers/jellyfin-runtime-composer.test.ts +++ b/src/main/runtime/composers/jellyfin-runtime-composer.test.ts @@ -190,4 +190,9 @@ test('composeJellyfinRuntimeHandlers returns callable jellyfin runtime handlers' assert.equal(typeof composed.stopJellyfinRemoteSession, 'function'); assert.equal(typeof composed.runJellyfinCommand, 'function'); assert.equal(typeof composed.openJellyfinSetupWindow, 'function'); + + // getResolvedJellyfinConfig forwards to the injected getResolvedConfig dep + const jellyfinConfig = composed.getResolvedJellyfinConfig(); + assert.equal(jellyfinConfig.enabled, false); + assert.equal(jellyfinConfig.serverUrl, ''); }); diff --git a/src/main/runtime/composers/overlay-visibility-runtime-composer.test.ts b/src/main/runtime/composers/overlay-visibility-runtime-composer.test.ts index 17c485d..59f7e67 100644 --- a/src/main/runtime/composers/overlay-visibility-runtime-composer.test.ts +++ b/src/main/runtime/composers/overlay-visibility-runtime-composer.test.ts @@ -3,15 +3,20 @@ import test from 'node:test'; import { composeOverlayVisibilityRuntime } from './overlay-visibility-runtime-composer'; test('composeOverlayVisibilityRuntime returns overlay visibility handlers', () => { + const calls: string[] = []; const composed = composeOverlayVisibilityRuntime({ overlayVisibilityRuntime: { - updateVisibleOverlayVisibility: () => {}, + updateVisibleOverlayVisibility: () => { + calls.push('updateVisibleOverlayVisibility'); + }, }, restorePreviousSecondarySubVisibilityMainDeps: { getMpvClient: () => null, }, broadcastRuntimeOptionsChangedMainDeps: { - broadcastRuntimeOptionsChangedRuntime: () => {}, + broadcastRuntimeOptionsChangedRuntime: () => { + calls.push('broadcastRuntimeOptionsChangedRuntime'); + }, getRuntimeOptionsState: () => [], broadcastToOverlayWindows: () => {}, }, @@ -24,7 +29,9 @@ test('composeOverlayVisibilityRuntime returns overlay visibility handlers', () = setCurrentEnabled: () => {}, }, openRuntimeOptionsPaletteMainDeps: { - openRuntimeOptionsPaletteRuntime: () => {}, + openRuntimeOptionsPaletteRuntime: () => { + calls.push('openRuntimeOptionsPaletteRuntime'); + }, }, }); @@ -34,4 +41,16 @@ test('composeOverlayVisibilityRuntime returns overlay visibility handlers', () = assert.equal(typeof composed.sendToActiveOverlayWindow, 'function'); assert.equal(typeof composed.setOverlayDebugVisualizationEnabled, 'function'); assert.equal(typeof composed.openRuntimeOptionsPalette, 'function'); + + // updateVisibleOverlayVisibility passes through to the injected runtime dep + composed.updateVisibleOverlayVisibility(); + assert.deepEqual(calls, ['updateVisibleOverlayVisibility']); + + // openRuntimeOptionsPalette forwards to the injected runtime dep + composed.openRuntimeOptionsPalette(); + assert.deepEqual(calls, ['updateVisibleOverlayVisibility', 'openRuntimeOptionsPaletteRuntime']); + + // broadcastRuntimeOptionsChanged forwards to the injected runtime dep + composed.broadcastRuntimeOptionsChanged(); + assert.ok(calls.includes('broadcastRuntimeOptionsChangedRuntime')); }); diff --git a/src/main/runtime/composers/shortcuts-runtime-composer.test.ts b/src/main/runtime/composers/shortcuts-runtime-composer.test.ts index a93b547..ad87e36 100644 --- a/src/main/runtime/composers/shortcuts-runtime-composer.test.ts +++ b/src/main/runtime/composers/shortcuts-runtime-composer.test.ts @@ -3,6 +3,7 @@ import test from 'node:test'; import { composeShortcutRuntimes } from './shortcuts-runtime-composer'; test('composeShortcutRuntimes returns callable shortcut runtime handlers', () => { + const calls: string[] = []; const composed = composeShortcutRuntimes({ globalShortcuts: { getConfiguredShortcutsMainDeps: { @@ -39,9 +40,13 @@ test('composeShortcutRuntimes returns callable shortcut runtime handlers', () => }, overlayShortcutsRuntimeMainDeps: { overlayShortcutsRuntime: { - registerOverlayShortcuts: () => {}, + registerOverlayShortcuts: () => { + calls.push('registerOverlayShortcuts'); + }, unregisterOverlayShortcuts: () => {}, - syncOverlayShortcuts: () => {}, + syncOverlayShortcuts: () => { + calls.push('syncOverlayShortcuts'); + }, refreshOverlayShortcuts: () => {}, }, }, @@ -58,4 +63,12 @@ test('composeShortcutRuntimes returns callable shortcut runtime handlers', () => assert.equal(typeof composed.unregisterOverlayShortcuts, 'function'); assert.equal(typeof composed.syncOverlayShortcuts, 'function'); assert.equal(typeof composed.refreshOverlayShortcuts, 'function'); + + // registerOverlayShortcuts forwards to the injected overlayShortcutsRuntime dep + composed.registerOverlayShortcuts(); + assert.deepEqual(calls, ['registerOverlayShortcuts']); + + // syncOverlayShortcuts forwards to the injected overlayShortcutsRuntime dep + composed.syncOverlayShortcuts(); + assert.deepEqual(calls, ['registerOverlayShortcuts', 'syncOverlayShortcuts']); }); diff --git a/src/main/runtime/composers/startup-lifecycle-composer.test.ts b/src/main/runtime/composers/startup-lifecycle-composer.test.ts index 60eb4a7..0c2cf22 100644 --- a/src/main/runtime/composers/startup-lifecycle-composer.test.ts +++ b/src/main/runtime/composers/startup-lifecycle-composer.test.ts @@ -3,6 +3,7 @@ import test from 'node:test'; import { composeStartupLifecycleHandlers } from './startup-lifecycle-composer'; test('composeStartupLifecycleHandlers returns callable startup lifecycle handlers', () => { + const calls: string[] = []; const composed = composeStartupLifecycleHandlers({ registerProtocolUrlHandlersMainDeps: { registerOpenUrl: () => {}, @@ -51,7 +52,9 @@ test('composeStartupLifecycleHandlers returns callable startup lifecycle handler getAllWindowCount: () => 0, }, restoreWindowsOnActivateMainDeps: { - createMainWindow: () => {}, + createMainWindow: () => { + calls.push('createMainWindow'); + }, updateVisibleOverlayVisibility: () => {}, syncOverlayMpvSubtitleSuppression: () => {}, }, @@ -61,4 +64,11 @@ test('composeStartupLifecycleHandlers returns callable startup lifecycle handler assert.equal(typeof composed.onWillQuitCleanup, 'function'); assert.equal(typeof composed.shouldRestoreWindowsOnActivate, 'function'); assert.equal(typeof composed.restoreWindowsOnActivate, 'function'); + + // shouldRestoreWindowsOnActivate returns false when overlay runtime is not initialized + assert.equal(composed.shouldRestoreWindowsOnActivate(), false); + + // restoreWindowsOnActivate invokes the injected createMainWindow dep + composed.restoreWindowsOnActivate(); + assert.deepEqual(calls, ['createMainWindow']); });