mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-30 06:12:06 -07:00
test: add behavioral assertions to composer tests
Upgrade 8 composer test files from shape-only typeof checks to behavioral assertions that invoke returned handlers and verify injected dependencies are actually called, following the mpv-runtime-composer pattern.
This commit is contained in:
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
|
||||
@@ -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, '');
|
||||
});
|
||||
|
||||
@@ -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'));
|
||||
});
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user