feat: add auto update support

This commit is contained in:
2026-05-15 01:47:56 -07:00
parent d1ec678d7a
commit 094bcce0dc
101 changed files with 4978 additions and 163 deletions
+157 -1
View File
@@ -2,7 +2,12 @@ import assert from 'node:assert/strict';
import test from 'node:test';
import { SPECIAL_COMMANDS } from '../../config/definitions';
import { describeSessionHelpCommand, formatSessionHelpKeybinding } from './session-help.js';
import { createRendererState } from '../state.js';
import {
createSessionHelpModal,
describeSessionHelpCommand,
formatSessionHelpKeybinding,
} from './session-help.js';
test('session help describes sub-seek commands as subtitle-line navigation', () => {
assert.equal(describeSessionHelpCommand(['sub-seek', 1]), 'Jump to next subtitle');
@@ -24,3 +29,154 @@ test('session help formats bracket keybindings as physical keys', () => {
assert.equal(formatSessionHelpKeybinding('Shift+BracketRight'), 'Shift + ]');
assert.equal(formatSessionHelpKeybinding('Shift+BracketLeft'), 'Shift + [');
});
function createClassList(initialTokens: string[] = []) {
const tokens = new Set(initialTokens);
return {
add: (...entries: string[]) => {
for (const entry of entries) tokens.add(entry);
},
remove: (...entries: string[]) => {
for (const entry of entries) tokens.delete(entry);
},
contains: (entry: string) => tokens.has(entry),
};
}
function createElementStub() {
return {
value: '',
textContent: '',
innerHTML: '',
classList: createClassList(['hidden']),
setAttribute: () => {},
addEventListener: () => {},
removeEventListener: () => {},
querySelectorAll: () => [],
focus: () => {},
select: () => {},
};
}
test('modal-layer session help does not focus hidden main overlay and still closes', async () => {
const globals = globalThis as typeof globalThis & {
window?: unknown;
document?: unknown;
HTMLElement?: unknown;
Element?: unknown;
};
const previousWindow = globals.window;
const previousDocument = globals.document;
const previousHTMLElement = globals.HTMLElement;
const previousElement = globals.Element;
const focusMainWindowCalls: number[] = [];
const notifications: string[] = [];
try {
class TestElement {}
Object.defineProperty(globalThis, 'HTMLElement', {
configurable: true,
writable: true,
value: TestElement,
});
Object.defineProperty(globalThis, 'Element', {
configurable: true,
writable: true,
value: TestElement,
});
Object.defineProperty(globalThis, 'window', {
configurable: true,
writable: true,
value: {
electronAPI: {
focusMainWindow: async () => {
focusMainWindowCalls.push(1);
},
setIgnoreMouseEvents: () => {},
notifyOverlayModalClosed: (modal: string) => {
notifications.push(modal);
},
getKeybindings: async () => {
throw new Error('mpv unavailable');
},
getSubtitleStyle: async () => ({}),
getConfiguredShortcuts: async () => ({}),
},
focus: () => {},
addEventListener: () => {},
removeEventListener: () => {},
setTimeout: (callback: () => void) => setTimeout(callback, 0),
},
});
Object.defineProperty(globalThis, 'document', {
configurable: true,
writable: true,
value: {
activeElement: null,
addEventListener: () => {},
removeEventListener: () => {},
},
});
const state = createRendererState();
const modal = createSessionHelpModal(
{
state,
platform: {
overlayLayer: 'modal',
isModalLayer: true,
isLinuxPlatform: false,
isMacOSPlatform: false,
isWindowsPlatform: true,
shouldToggleMouseIgnore: false,
},
dom: {
overlay: createElementStub(),
sessionHelpModal: createElementStub(),
sessionHelpFilter: createElementStub(),
sessionHelpContent: createElementStub(),
sessionHelpClose: createElementStub(),
sessionHelpShortcut: createElementStub(),
sessionHelpWarning: createElementStub(),
sessionHelpStatus: createElementStub(),
},
} as never,
{
modalStateReader: { isAnyModalOpen: () => false },
syncSettingsModalSubtitleSuppression: () => {},
},
);
modal.openSessionHelpModal({
bindingKey: 'KeyH',
fallbackUsed: false,
fallbackUnavailable: false,
});
modal.closeSessionHelpModal();
await new Promise((resolve) => setTimeout(resolve, 0));
assert.deepEqual(focusMainWindowCalls, []);
assert.deepEqual(notifications, ['session-help']);
} finally {
Object.defineProperty(globalThis, 'window', {
configurable: true,
writable: true,
value: previousWindow,
});
Object.defineProperty(globalThis, 'document', {
configurable: true,
writable: true,
value: previousDocument,
});
Object.defineProperty(globalThis, 'HTMLElement', {
configurable: true,
writable: true,
value: previousHTMLElement,
});
Object.defineProperty(globalThis, 'Element', {
configurable: true,
writable: true,
value: previousElement,
});
}
});
+6 -2
View File
@@ -450,7 +450,9 @@ export function createSessionHelpModal(
}
function focusFallbackTarget(): boolean {
void window.electronAPI.focusMainWindow();
if (!ctx.platform.isModalLayer) {
void window.electronAPI.focusMainWindow();
}
const items = getItems();
const firstItem = items.find((item) => item.offsetParent !== null);
if (firstItem) {
@@ -526,7 +528,9 @@ export function createSessionHelpModal(
}
function requestOverlayFocus(): void {
void window.electronAPI.focusMainWindow();
if (!ctx.platform.isModalLayer) {
void window.electronAPI.focusMainWindow();
}
}
function addPointerFocusListener(): void {