refactor: extract tray runtime handler wiring

This commit is contained in:
2026-02-20 02:14:40 -08:00
parent b6b81a72f5
commit e8db67e621
5 changed files with 167 additions and 31 deletions

View File

@@ -0,0 +1,96 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { createTrayRuntimeHandlers } from './tray-runtime-handlers';
test('tray runtime handlers compose resolve/menu/ensure/destroy handlers', () => {
let tray: { destroyed: boolean } | null = null;
let overlayInitialized = false;
let visibleOverlay = false;
const calls: string[] = [];
const runtime = createTrayRuntimeHandlers({
resolveTrayIconPathDeps: {
resolveTrayIconPathRuntime: () => '/tmp/SubMiner.png',
platform: 'darwin',
resourcesPath: '/resources',
appPath: '/app',
dirname: '/dirname',
joinPath: (...parts) => parts.join('/'),
fileExists: () => true,
},
buildTrayMenuTemplateDeps: {
buildTrayMenuTemplateRuntime: () => [{ label: 'Open Overlay' }],
initializeOverlayRuntime: () => {
overlayInitialized = true;
},
isOverlayRuntimeInitialized: () => overlayInitialized,
setVisibleOverlayVisible: (visible) => {
visibleOverlay = visible;
},
openYomitanSettings: () => {},
openRuntimeOptionsPalette: () => {},
openJellyfinSetupWindow: () => {},
openAnilistSetupWindow: () => {},
quitApp: () => {},
},
ensureTrayDeps: {
getTray: () => tray as never,
setTray: (next) => {
tray = next as { destroyed: boolean } | null;
},
createImageFromPath: () => ({
isEmpty: () => false,
resize: () => ({
isEmpty: () => false,
resize: () => ({} as never),
setTemplateImage: () => {},
}),
setTemplateImage: () => {},
}),
createEmptyImage: () => ({
isEmpty: () => true,
resize: () => ({} as never),
setTemplateImage: () => {},
}),
createTray: () => ({
setContextMenu: () => calls.push('setContextMenu'),
setToolTip: () => calls.push('setToolTip'),
on: (_event: 'click', handler: () => void) => {
calls.push('on-click');
handler();
},
destroy: () => {
calls.push('destroy');
if (tray) tray.destroyed = true;
},
}),
trayTooltip: 'SubMiner',
platform: 'darwin',
logWarn: (message) => calls.push(`warn:${message}`),
initializeOverlayRuntime: () => {
overlayInitialized = true;
},
isOverlayRuntimeInitialized: () => overlayInitialized,
setVisibleOverlayVisible: (visible) => {
visibleOverlay = visible;
},
},
destroyTrayDeps: {
getTray: () => tray as never,
setTray: (next) => {
tray = next as { destroyed: boolean } | null;
},
},
buildMenuFromTemplate: (template) => ({ template }),
});
assert.equal(runtime.resolveTrayIconPath(), '/tmp/SubMiner.png');
assert.deepEqual(runtime.buildTrayMenu(), { template: [{ label: 'Open Overlay' }] });
runtime.ensureTray();
assert.equal(overlayInitialized, true);
assert.equal(visibleOverlay, true);
assert.ok(tray);
runtime.destroyTray();
assert.equal(tray, null);
assert.deepEqual(calls, ['setToolTip', 'setContextMenu', 'on-click', 'destroy']);
});

View File

@@ -0,0 +1,48 @@
import { createDestroyTrayHandler, createEnsureTrayHandler } from './tray-lifecycle';
import { createBuildDestroyTrayMainDepsHandler, createBuildEnsureTrayMainDepsHandler } from './app-runtime-main-deps';
import { createBuildTrayMenuTemplateHandler, createResolveTrayIconPathHandler } from './tray-main-actions';
import {
createBuildResolveTrayIconPathMainDepsHandler,
createBuildTrayMenuTemplateMainDepsHandler,
} from './tray-main-deps';
type ResolveTrayIconPathMainDeps = Parameters<typeof createBuildResolveTrayIconPathMainDepsHandler>[0];
type BuildTrayMenuTemplateMainDeps<TMenuItem> = Parameters<
typeof createBuildTrayMenuTemplateMainDepsHandler<TMenuItem>
>[0];
type EnsureTrayMainDeps = Parameters<typeof createBuildEnsureTrayMainDepsHandler>[0];
type DestroyTrayMainDeps = Parameters<typeof createBuildDestroyTrayMainDepsHandler>[0];
export function createTrayRuntimeHandlers<TMenuItem, TMenu>(deps: {
resolveTrayIconPathDeps: ResolveTrayIconPathMainDeps;
buildTrayMenuTemplateDeps: BuildTrayMenuTemplateMainDeps<TMenuItem>;
ensureTrayDeps: Omit<EnsureTrayMainDeps, 'buildTrayMenu' | 'resolveTrayIconPath'>;
destroyTrayDeps: DestroyTrayMainDeps;
buildMenuFromTemplate: (template: TMenuItem[]) => TMenu;
}) {
const resolveTrayIconPath = createResolveTrayIconPathHandler(
createBuildResolveTrayIconPathMainDepsHandler(deps.resolveTrayIconPathDeps)(),
);
const buildTrayMenuTemplate = createBuildTrayMenuTemplateHandler(
createBuildTrayMenuTemplateMainDepsHandler(deps.buildTrayMenuTemplateDeps)(),
);
const buildTrayMenu = () => deps.buildMenuFromTemplate(buildTrayMenuTemplate());
const ensureTray = createEnsureTrayHandler(
createBuildEnsureTrayMainDepsHandler({
...deps.ensureTrayDeps,
buildTrayMenu: () => buildTrayMenu(),
resolveTrayIconPath: () => resolveTrayIconPath(),
})(),
);
const destroyTray = createDestroyTrayHandler(
createBuildDestroyTrayMainDepsHandler(deps.destroyTrayDeps)(),
);
return {
resolveTrayIconPath,
buildTrayMenu,
ensureTray,
destroyTray,
};
}