feat(desktop): add macOS app menu template builder

Adds buildAppMenuTemplate pure function to recovery.ts that produces the
three-submenu macOS app menu (App/Edit/Window), reusing MenuActions and
checkForUpdatesItem from T4. Includes Restart to Install Update item
conditionally on updateState=downloaded. Tests use destructuring to satisfy
noUncheckedIndexedAccess. 26 tests pass, tsc clean.
This commit is contained in:
Jannis Braun
2026-05-03 04:13:19 +02:00
parent 172f53e3a5
commit a9122d1fdf
2 changed files with 100 additions and 0 deletions
+43
View File
@@ -4,6 +4,7 @@ import {
RecoveryStateStore,
extractErrorCode,
buildTrayMenuTemplate,
buildAppMenuTemplate,
type RecoveryState,
} from './recovery';
@@ -211,3 +212,45 @@ describe('buildTrayMenuTemplate', () => {
expect(downloadedItem!.enabled).toBe(true);
});
});
describe('buildAppMenuTemplate', () => {
it('returns top-level menu with App, Edit, Window submenus', () => {
const template = buildAppMenuTemplate('Backspace', defaultState());
const [appMenu, editMenu, windowMenu] = template;
expect(template.length).toBeGreaterThanOrEqual(3);
expect(appMenu!.label).toBe('Backspace');
expect(editMenu!.label).toBe('Edit');
expect(windowMenu!.label).toBe('Window');
});
it('App submenu includes About and Change Instance', () => {
const template = buildAppMenuTemplate('Backspace', defaultState());
const [appMenu] = template;
const appSub = appMenu!.submenu as MenuItemConstructorOptions[];
const labels = appSub.map((i) => i.label).filter(Boolean);
expect(appSub.find((i) => i.role === 'about')).toBeDefined();
expect(labels).toContain('Change Instance');
expect(appSub.find((i) => i.role === 'quit')).toBeDefined();
});
it('App submenu includes Check for Updates with state-correct label', () => {
const template = buildAppMenuTemplate('Backspace', defaultState({ updateState: 'idle' }));
const [appMenu] = template;
const appSub = appMenu!.submenu as MenuItemConstructorOptions[];
const item = appSub.find((i) => i.id === 'check-for-updates');
expect(item).toBeDefined();
expect(item!.label).toBe('Check for Updates…');
});
it('App submenu inserts Restart to Install Update only when downloaded', () => {
const [idleAppMenu] = buildAppMenuTemplate('Backspace', defaultState({ updateState: 'idle' }));
const idleSub = idleAppMenu!.submenu as MenuItemConstructorOptions[];
expect(idleSub.find((i) => i.id === 'restart-to-install')).toBeUndefined();
const [dlAppMenu] = buildAppMenuTemplate('Backspace', defaultState({ updateState: 'downloaded' }));
const dlSub = dlAppMenu!.submenu as MenuItemConstructorOptions[];
const restartItem = dlSub.find((i) => i.id === 'restart-to-install');
expect(restartItem).toBeDefined();
expect(restartItem!.label).toBe('Restart to Install Update');
});
});