diff --git a/.changes/electron-deferred-startup-wiring.md b/.changes/electron-deferred-startup-wiring.md index 126d72aad..32c1a8953 100644 --- a/.changes/electron-deferred-startup-wiring.md +++ b/.changes/electron-deferred-startup-wiring.md @@ -1,6 +1,7 @@ --- type: perf area: electron +highlight: Faster startup --- The desktop app now opens its window before it prepares the portal, program diff --git a/.changes/settings-search.md b/.changes/settings-search.md index 137d7eff1..f3259ac87 100644 --- a/.changes/settings-search.md +++ b/.changes/settings-search.md @@ -1,6 +1,7 @@ --- type: feature area: settings +screenshot: settings-search highlight: Search your settings --- diff --git a/tools/release/capture-navigation-setup-actions.ts b/tools/release/capture-navigation-setup-actions.ts index cbeb140aa..6bb49bf1b 100644 --- a/tools/release/capture-navigation-setup-actions.ts +++ b/tools/release/capture-navigation-setup-actions.ts @@ -164,6 +164,36 @@ async function openSettingsEpg(page: Page): Promise { await page.waitForTimeout(500); } +/** + * Settings with a term typed into the header search, so the page shows the + * ranked results list and the per-section match counts instead of a section. + * The term lives only in the `q` query param: the next action's navigation + * clears it, and nothing is staged in the settings form. + */ +async function openSettingsSearch( + page: Page, + term: string | null +): Promise { + if (!term) { + throw new Error('open-settings-search needs a term: open-settings-search='); + } + + await openSettings(page); + await page + .locator('app-workspace-shell-header input[type="search"]') + .fill(term, { timeout: 10_000 }); + await page.waitForURL(/\/workspace\/settings\/general\?q=/, { + timeout: 15_000, + }); + const results = page.locator('[data-test-id="settings-search-results"]'); + await results.waitFor({ state: 'visible', timeout: 15_000 }); + await results + .locator('[data-test-id^="settings-search-result-"]') + .first() + .waitFor({ state: 'visible', timeout: 10_000 }); + await settleUi(page); +} + /* ------------------------------------------------------------------ */ /* Remote control (guide shots) */ /* ------------------------------------------------------------------ */ @@ -301,6 +331,7 @@ export const SETUP_ACTIONS: Readonly> = { 'open-add-playlist-stalker': openAddPlaylistStalker, 'open-add-playlist-m3u-url': openAddPlaylistM3uUrl, 'open-settings-epg': openSettingsEpg, + 'open-settings-search': openSettingsSearch, 'open-settings-tmdb': openSettingsTmdb, 'open-settings-remote-control': openSettingsRemoteControl, 'enable-remote-control': enableRemoteControl, diff --git a/tools/release/screenshot-guards.mjs b/tools/release/screenshot-guards.mjs index 9d7641010..a805f8402 100644 --- a/tools/release/screenshot-guards.mjs +++ b/tools/release/screenshot-guards.mjs @@ -70,6 +70,7 @@ export const KNOWN_ACTIONS = [ 'open-m3u-channel-menu', 'open-epg-mapping-dialog', 'open-settings-tmdb', + 'open-settings-search', ]; /** diff --git a/tools/release/screenshot-guards.test.mjs b/tools/release/screenshot-guards.test.mjs index fa61b90b3..f29706bfb 100644 --- a/tools/release/screenshot-guards.test.mjs +++ b/tools/release/screenshot-guards.test.mjs @@ -26,6 +26,7 @@ import { snapshotDatabaseState, stubbedResponseFor, DEFAULT_SHOT_GROUP, + KNOWN_ACTIONS, FICTIONAL_STALKER_MAC, outputDirectoryFor, shotGroup, @@ -81,6 +82,35 @@ describe('manifest validation', () => { assert.deepEqual(validateManifest(validManifest()), []); }); + it('accepts the committed screenshots.manifest.json', () => { + const manifest = JSON.parse( + readFileSync(new URL('./screenshots.manifest.json', import.meta.url), 'utf8') + ); + + assert.deepEqual(validateManifest(manifest), []); + }); + + it('knows exactly the setup actions the capture navigation dispatches', () => { + // The dispatcher is TypeScript and cannot be imported here, so read + // the action tables' keys from source: an action added on one side + // only would fail the manifest check or the capture run. + const dir = new URL('./', import.meta.url); + const dispatched = readdirSync(dir) + .filter((name) => /^capture-navigation-.+-actions\.ts$/.test(name)) + .flatMap((name) => { + const source = readFileSync(new URL(name, dir), 'utf8'); + const table = source.match( + /_ACTIONS: Readonly> = \{([^}]*)\}/ + ); + assert.ok(table, `${name} exports no action table`); + return [...table[1].matchAll(/'([a-z0-9-]+)':/g)].map( + ([, action]) => action + ); + }); + + assert.deepEqual([...dispatched].sort(), [...KNOWN_ACTIONS].sort()); + }); + it('rejects unknown setup actions, bad slugs and duplicates', () => { const manifest = validManifest(); manifest.shots.push( diff --git a/tools/release/screenshots.manifest.json b/tools/release/screenshots.manifest.json index d9dc53582..ec58e8b06 100644 --- a/tools/release/screenshots.manifest.json +++ b/tools/release/screenshots.manifest.json @@ -23,6 +23,13 @@ "open-settings" ] }, + { + "slug": "settings-search", + "title": "Settings search results", + "setup": [ + "open-settings-search=player" + ] + }, { "slug": "xtream-vod-details", "title": "Movie details",