import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"; import { test, expect, type Locator, type Page } from "@playwright/test"; import { channelStage, generateReport, readJson, resetData, resolvePath, writeSettings, writeSite, } from "./helpers"; // A FORM THAT FAILS KEEPS WHAT WAS TYPED (release 16, slice FK). // // React resets a
when its action's transition commits, success or // failure, and an uncontrolled field goes back to its default. Every action // that can refuse now returns what was submitted and the fields seed from it // (app/lib/formState.ts). One case per form the slice changed: type, submit // something the action refuses, assert the refusal AND that every field still // holds what was typed. // // Fields are found by NAME where a label would be ambiguous: a Field's // accessible name includes its hint, and getByLabel matches substrings. // // THE FOUR OPERATION FORMS, THE SYNC FORM AND THE BACKUP FORM CANNOT BE REFUSED // FROM THE PAGE — their actions only fail when settings.json cannot be written. // `withSettingsUnwritable` makes that happen for real: the settings file is put // aside and a directory takes its path, so the atomic write's rename fails // (EISDIR) and the action returns its error. The file is back before the test // ends, whatever happens. const SETTINGS = resolvePath("test-settings.json"); async function withSettingsUnwritable(fn: () => Promise): Promise { const aside = `${SETTINGS}.fk-aside`; await rename(SETTINGS, aside); await mkdir(SETTINGS); try { await fn(); } finally { await rm(SETTINGS, { recursive: true, force: true }); await rename(aside, SETTINGS); } } // A click before React hydrates submits nothing a client action sees. async function hydrated(form: Locator): Promise { await expect(async () => { const ok = await form.evaluate((el) => Object.keys(el).some((k) => k.startsWith("__reactProps")), ); expect(ok).toBe(true); }).toPass({ timeout: 15_000 }); } const named = (scope: Locator | Page, name: string) => scope.locator(`[name="${name}"]`); // Next's route announcer is a second (empty) role=alert, so filter by text. const alert = (page: Page, text: RegExp) => page.getByRole("alert").filter({ hasText: text }); // A CONTROLLED checkbox, radio or select is read ONCE, as the refusal shows, // not polled. Without forms/Controlled.tsx the reset leaves it showing what it // mounted with only until the next re-render that reaches it — the editor's // auto-refresh, 5 s by default — so a polled assertion passes on code without // the fix. The refusal appears in the commit the reset ran in. const checkedNow = (loc: Locator) => loc.isChecked(); const valueNow = (loc: Locator) => loc.inputValue(); test("SiteForm create: a refused site id keeps every field", async ({ page, }) => { await resetData("one-youtube-channel-with-data"); await page.goto("/sites/new"); const form = page.locator("form").filter({ has: page.getByRole("button", { name: "Create site" }), }); await hydrated(form); await named(form, "siteId").fill("Not A Valid Id!"); await named(form, "siteTitle").fill("Typed Title"); await named(form, "headerTitle").fill("Typedalyzer"); await named(form, "wordmarkLead").fill("Typed"); await named(form, "siteDescription").fill("A typed description"); await named(form, "homeTagline").fill("A typed tagline"); await named(form, "cloudflareProject").fill("typed-project"); await named(form, "archiveMaxMB").fill("12"); // Two checkboxes toggled from their defaults (pwa off, archives on). await named(form, "pwa").check(); await named(form, "archives").uncheck(); // Controlled, posted: the accent radio; controlled, not posted: membership. await form.locator('input[name="accent"][value="brass"]').check(); await form .getByRole("checkbox", { name: /include test youtube channel/i }) .check(); await form.getByRole("button", { name: "Create site" }).click(); await expect(alert(page, /site id must be/i)).toBeVisible(); expect( await checkedNow(form.locator('input[name="accent"][value="brass"]')), ).toBe(true); expect( await checkedNow( form.getByRole("checkbox", { name: /include test youtube channel/i }), ), ).toBe(true); await expect(named(form, "siteId")).toHaveValue("Not A Valid Id!"); await expect(named(form, "siteTitle")).toHaveValue("Typed Title"); await expect(named(form, "headerTitle")).toHaveValue("Typedalyzer"); await expect(named(form, "wordmarkLead")).toHaveValue("Typed"); await expect(named(form, "siteDescription")).toHaveValue( "A typed description", ); await expect(named(form, "homeTagline")).toHaveValue("A typed tagline"); await expect(named(form, "cloudflareProject")).toHaveValue("typed-project"); await expect(named(form, "archiveMaxMB")).toHaveValue("12"); await expect(named(form, "pwa")).toBeChecked(); await expect(named(form, "archives")).not.toBeChecked(); await expect(named(form, "listed")).toBeChecked(); }); test("SiteForm edit: a refused Public URL keeps every field", async ({ page, }) => { await resetData("empty"); await writeSite("alpha", { siteTitle: "Alpha Site" }); await page.goto("/sites/alpha"); const form = page.locator("form").filter({ has: page.getByRole("button", { name: "Save site" }), }); await hydrated(form); await named(form, "siteTitle").fill("Alpha Typed"); await named(form, "headerTitle").fill("Alphalyzer"); await named(form, "wordmarkLead").fill("Alpha"); await named(form, "siteDescription").fill("Typed description"); await named(form, "siteUrl").fill("not a url"); await named(form, "hubUrl").fill("https://hub.example"); await named(form, "duplicates").uncheck(); await named(form, "transcriptDownloads").uncheck(); await form.locator('input[name="accent"][value="violet"]').check(); await form.getByRole("button", { name: "Save site" }).click(); await expect(alert(page, /public url must be/i)).toBeVisible(); expect( await checkedNow(form.locator('input[name="accent"][value="violet"]')), ).toBe(true); await expect(named(form, "siteTitle")).toHaveValue("Alpha Typed"); await expect(named(form, "headerTitle")).toHaveValue("Alphalyzer"); await expect(named(form, "wordmarkLead")).toHaveValue("Alpha"); await expect(named(form, "siteDescription")).toHaveValue( "Typed description", ); await expect(named(form, "siteUrl")).toHaveValue("not a url"); await expect(named(form, "hubUrl")).toHaveValue("https://hub.example"); await expect(named(form, "duplicates")).not.toBeChecked(); await expect(named(form, "transcriptDownloads")).not.toBeChecked(); // Nothing was written. const site = await readJson<{ siteTitle: string }>( "test-transcripts/sites/alpha/site.json", ); expect(site.siteTitle).toBe("Alpha Site"); }); test("SettingsForm: a refused page size keeps every field, and a save after it shows what was stored", async ({ page, }) => { await resetData("empty"); await page.goto("/settings"); const form = page.locator("form").filter({ has: page.getByRole("button", { name: "Save settings" }), }); await hydrated(form); await named(form, "adminTitle").fill("Typed Admin"); await named(form, "maxTranscriptPageBytes").fill("42"); await named(form, "cookiesFromBrowser").fill("firefox:Typed"); await named(form, "cookieMode").selectOption("defer"); await named(form, "sleepBetweenDownloadsSeconds").fill("17"); await named(form, "downloadFormat").selectOption("original"); await named(form, "minFreeDiskGB").fill("3"); await named(form, "skipLiveDownloads").uncheck(); await named(form, "inlineTranscribeOnFallback").check(); await named(form, "archiveStorageBucket").fill("typed-bucket"); await named(form, "reportDebouncePreset").selectOption("lazy"); await named(form, "maxParallelBuilds").fill("7"); await named(form, "dockerImage").fill("typed-image"); // A social link with its controlled "featured" box ticked. await form.getByRole("button", { name: /add link/i }).click(); await form.getByPlaceholder(/label \(e\.g\. github\)/i).fill("GitHub"); const featured = form.getByRole("checkbox", { name: "Keep in header on small screens", }); await featured.check(); await form.getByRole("button", { name: "Save settings" }).click(); await expect(alert(page, /between/)).toBeVisible(); expect(await checkedNow(featured)).toBe(true); await expect(named(form, "adminTitle")).toHaveValue("Typed Admin"); await expect(named(form, "maxTranscriptPageBytes")).toHaveValue("42"); await expect(named(form, "cookiesFromBrowser")).toHaveValue("firefox:Typed"); await expect(named(form, "cookieMode")).toHaveValue("defer"); await expect(named(form, "sleepBetweenDownloadsSeconds")).toHaveValue("17"); await expect(named(form, "downloadFormat")).toHaveValue("original"); await expect(named(form, "minFreeDiskGB")).toHaveValue("3"); await expect(named(form, "skipLiveDownloads")).not.toBeChecked(); await expect(named(form, "inlineTranscribeOnFallback")).toBeChecked(); await expect(named(form, "archiveStorageBucket")).toHaveValue("typed-bucket"); await expect(named(form, "reportDebouncePreset")).toHaveValue("lazy"); await expect(named(form, "maxParallelBuilds")).toHaveValue("7"); await expect(named(form, "dockerImage")).toHaveValue("typed-image"); // THE SUCCESS PATH: fix the one bad field and save. The selects show the // stored choice afterwards (a select's default reached the DOM only at mount // before this slice, so the reset put back the option it mounted with). await form.getByRole("button", { name: /remove/i }).first().click(); await named(form, "maxTranscriptPageBytes").fill("8388608"); await form.getByRole("button", { name: "Save settings" }).click(); await expect( form.getByRole("status").filter({ hasText: "Saved" }), ).toBeVisible(); await expect(async () => { const saved = await readJson<{ cookieMode: string; adminTitle: string }>( "test-settings.json", ); expect(saved.cookieMode).toBe("defer"); expect(saved.adminTitle).toBe("Typed Admin"); }).toPass({ timeout: 10_000 }); await expect(named(form, "cookieMode")).toHaveValue("defer"); await expect(named(form, "reportDebouncePreset")).toHaveValue("lazy"); await expect(named(form, "adminTitle")).toHaveValue("Typed Admin"); }); test("ChannelForm create: a refused slug keeps every field", async ({ page, }) => { await resetData("one-youtube-channel"); await writeSite("alpha", { siteTitle: "Alpha Site" }); await generateReport(page, "new"); await page.goto("/channels/new"); const form = page.locator("form").filter({ has: page.getByRole("button", { name: /create channel/i }), }); await hydrated(form); await named(form, "name").fill("Typed Channel"); await named(form, "slug").fill("test-youtube"); // already exists await named(form, "url").fill("https://www.youtube.com/@typed/videos"); // Create mode's platform select and handling radios are controlled AND // posted: a stale one would be sent by the next submit. await named(form, "platform").selectOption("rumble"); await form.locator('input[name="handling"][value="transcribe"]').check(); const member = form.getByRole("checkbox", { name: "Include on Alpha Site" }); const memberWas = await member.isChecked(); await member.setChecked(!memberWas); await named(form, "fetchPlaylist").uncheck(); await named(form, "prioritizeDownload").check(); await named(form, "audioFormat").selectOption("opus"); await named(form, "downloadFormat").selectOption("original"); await named(form, "keepSourceVideo").check(); await named(form, "audioCheckEnabled").check(); await named(form, "audioCheckIntervalSeconds").fill("45"); await named(form, "keepLatest").fill("5"); await named(form, "extractionMode").selectOption("app"); await named(form, "savedVideosDir").fill("/tmp/typed-dir"); await form.getByText("Advanced", { exact: true }).click(); await named(form, "ytdlpExtraArgs").fill("--typed\n--args"); await named(form, "downloadFilterInclude").fill("typed-include"); await named(form, "downloadFilterIncludeLivestreams").check(); await named(form, "downloadFilterRejectedLivestreams").selectOption( "chat-only", ); await named(form, "cookiesFromBrowser").fill("firefox"); await named(form, "cookieMode").selectOption("always"); await named(form, "sleepBetweenDownloadsSeconds").fill("9"); await form.getByRole("button", { name: /create channel/i }).click(); await expect(form.getByText(/already exists/i)).toBeVisible(); expect(await valueNow(named(form, "platform"))).toBe("rumble"); expect( await checkedNow(form.locator('input[name="handling"][value="transcribe"]')), ).toBe(true); expect(await checkedNow(member)).toBe(!memberWas); await expect(named(form, "name")).toHaveValue("Typed Channel"); await expect(named(form, "slug")).toHaveValue("test-youtube"); await expect(named(form, "url")).toHaveValue( "https://www.youtube.com/@typed/videos", ); await expect(named(form, "fetchPlaylist")).not.toBeChecked(); await expect(named(form, "prioritizeDownload")).toBeChecked(); await expect(named(form, "audioFormat")).toHaveValue("opus"); await expect(named(form, "downloadFormat")).toHaveValue("original"); await expect(named(form, "keepSourceVideo")).toBeChecked(); await expect(named(form, "audioCheckEnabled")).toBeChecked(); await expect(named(form, "audioCheckIntervalSeconds")).toHaveValue("45"); await expect(named(form, "keepLatest")).toHaveValue("5"); await expect(named(form, "extractionMode")).toHaveValue("app"); await expect(named(form, "savedVideosDir")).toHaveValue("/tmp/typed-dir"); await expect(named(form, "ytdlpExtraArgs")).toHaveValue("--typed\n--args"); await expect(named(form, "downloadFilterInclude")).toHaveValue( "typed-include", ); await expect(named(form, "downloadFilterIncludeLivestreams")).toBeChecked(); await expect(named(form, "downloadFilterRejectedLivestreams")).toHaveValue( "chat-only", ); await expect(named(form, "cookiesFromBrowser")).toHaveValue("firefox"); await expect(named(form, "cookieMode")).toHaveValue("always"); await expect(named(form, "sleepBetweenDownloadsSeconds")).toHaveValue("9"); }); // Create mode draws its "On create" boxes per KIND of source: Fetch posts now // for a social account, Fetch playlist now for a video channel. A box of the // kind that was NOT posted must come back at its default (ticked) when the URL // switches kind after a refusal — not read as "absent from values, unticked". test("ChannelForm create: switching the source's kind after a refusal shows the other kind's box at its default", async ({ page, }) => { const BSKY_URL = "https://bsky.app/profile/someone.bsky.social"; const VIDEO_URL = "https://www.youtube.com/@kindswitch/videos"; await resetData("one-youtube-channel"); await generateReport(page, "new"); const form = page.locator("form").filter({ has: page.getByRole("button", { name: /create channel/i }), }); const posts = named(form, "fetchPostsNow"); const playlist = named(form, "fetchPlaylist"); const refuse = async () => { await form.getByRole("button", { name: /create channel/i }).click(); await expect(form.getByText(/already exists/i)).toBeVisible(); }; // A refused VIDEO submit, then a social URL. await page.goto("/channels/new"); await hydrated(form); await named(form, "name").fill("Kind Switch"); await named(form, "slug").fill("test-youtube"); // already exists await named(form, "url").fill(VIDEO_URL); await playlist.uncheck(); await refuse(); await expect(playlist).not.toBeChecked(); await named(form, "url").fill(BSKY_URL); await expect(posts).toBeChecked(); // The reverse: a refused SOCIAL submit, then a video URL. await page.goto("/channels/new"); await hydrated(form); await named(form, "name").fill("Kind Switch"); await named(form, "slug").fill("test-youtube"); await named(form, "url").fill(BSKY_URL); await posts.uncheck(); await refuse(); await expect(posts).not.toBeChecked(); await named(form, "url").fill(VIDEO_URL); await expect(playlist).toBeChecked(); }); test("ChannelForm edit: a refused filter keeps every field", async ({ page, }) => { await resetData("one-youtube-channel"); await generateReport(page, "test-youtube"); await page.goto(channelStage("test-youtube", "configure")); const form = page.locator("form").filter({ has: page.getByRole("button", { name: /save changes/i }), }); await hydrated(form); await named(form, "name").fill("Edited Name"); await named(form, "url").fill("https://www.youtube.com/@edited/videos"); await named(form, "platform").selectOption("rumble"); await form.locator('input[name="handling"][value="transcribe"]').check(); await named(form, "audioFormat").selectOption("m4a"); await named(form, "keepLatest").fill("7"); await named(form, "keepSourceVideo").check(); await form.getByText("Advanced", { exact: true }).click(); await named(form, "ytdlpExtraArgs").fill("--edited"); await named(form, "downloadFilterExclude").fill("(unclosed"); await named(form, "cookieMode").selectOption("defer"); await form.getByRole("button", { name: /save changes/i }).click(); await expect( form.getByText(/not a valid regular expression/i), ).toBeVisible(); await expect(named(form, "name")).toHaveValue("Edited Name"); await expect(named(form, "url")).toHaveValue( "https://www.youtube.com/@edited/videos", ); await expect(named(form, "platform")).toHaveValue("rumble"); await expect( form.locator('input[name="handling"][value="transcribe"]'), ).toBeChecked(); await expect(named(form, "audioFormat")).toHaveValue("m4a"); await expect(named(form, "keepLatest")).toHaveValue("7"); await expect(named(form, "keepSourceVideo")).toBeChecked(); await expect(named(form, "ytdlpExtraArgs")).toHaveValue("--edited"); await expect(named(form, "downloadFilterExclude")).toHaveValue("(unclosed"); await expect(named(form, "cookieMode")).toHaveValue("defer"); const config = await readJson<{ name: string }>( "test-transcripts/channels/test-youtube/config.json", ); expect(config.name).not.toBe("Edited Name"); }); test("RenameChannelForm: a mismatched confirm keeps both boxes", async ({ page, }) => { await resetData("one-youtube-channel"); await generateReport(page, "test-youtube"); await page.goto(channelStage("test-youtube", "danger")); const form = page.locator("form").filter({ has: page.getByRole("button", { name: "Rename channel" }), }); await hydrated(form); await form.getByLabel("new slug").fill("renamed-typed"); await form.getByLabel("confirm current slug").fill("not-the-slug"); await form.getByRole("button", { name: "Rename channel" }).click(); await expect(form.getByText(/exactly to confirm the rename/i)).toBeVisible(); await expect(form.getByLabel("new slug")).toHaveValue("renamed-typed"); await expect(form.getByLabel("confirm current slug")).toHaveValue( "not-the-slug", ); }); test("DeleteChannelForm: a mismatched confirm keeps the box", async ({ page, }) => { await resetData("one-youtube-channel"); await generateReport(page, "test-youtube"); await page.goto(channelStage("test-youtube", "danger")); const form = page.locator("form").filter({ has: page.getByRole("button", { name: "Delete channel" }), }); await hydrated(form); await form.getByLabel("confirm slug to delete").fill("wrong-slug"); await form.getByRole("button", { name: "Delete channel" }).click(); await expect(form.getByText(/exactly to confirm deletion/i)).toBeVisible(); await expect(form.getByLabel("confirm slug to delete")).toHaveValue( "wrong-slug", ); }); test("DeleteVideoDirSection: a mismatched id keeps the box", async ({ page, }) => { await resetData("one-youtube-channel-with-data"); await page.goto("/channels/test-youtube/videos/20240101_test1234567"); const form = page.getByLabel("delete video directory 20240101_test1234567"); await hydrated(form); await form.getByLabel("confirm video id").fill("nope-typed"); await form.getByRole("button", { name: "Delete directory" }).click(); await expect(page.getByLabel("delete directory error")).toContainText( "Type the video id", ); await expect(form.getByLabel("confirm video id")).toHaveValue("nope-typed"); }); test("HealthTimingForm: an out-of-range value keeps the five fields", async ({ page, }) => { await resetData("one-youtube-channel-with-data"); await writeSettings({ adminTitle: "Test Admin", minFreeDiskGB: 0 }); await page.goto("/storage"); const block = page.getByLabel("drive health timing"); const form = block.locator("form"); await hydrated(form); await block.locator("summary").click(); await block.getByLabel("read budget").fill("200"); await block.getByLabel("health check interval").fill("20000"); await block.getByLabel("health check timeout").fill("4000"); await block.getByLabel("clean checks to clear").fill("3"); await block.getByLabel("reads at once per drive").fill("2"); await block.getByLabel("save timing").click(); await expect(block.getByLabel("timing error")).toContainText( "Read budget must be between", ); await expect(block.getByLabel("read budget")).toHaveValue("200"); await expect(block.getByLabel("health check interval")).toHaveValue("20000"); await expect(block.getByLabel("health check timeout")).toHaveValue("4000"); await expect(block.getByLabel("clean checks to clear")).toHaveValue("3"); await expect(block.getByLabel("reads at once per drive")).toHaveValue("2"); }); test("HomepageConfigForm: a refused Public URL keeps every field", async ({ page, }) => { await resetData("empty"); await page.goto("/sites"); const form = page.locator("form").filter({ has: page.getByRole("button", { name: "Save config" }), }); await hydrated(form); await named(form, "siteTitle").fill("Typed Hub"); await named(form, "headerTitle").fill("Typed Header"); await named(form, "siteDescription").fill("Typed hub description"); await named(form, "homeTagline").fill("Typed hub tagline"); await named(form, "siteUrl").fill("not a url"); await named(form, "cloudflareProject").fill("typed-hub"); await named(form, "transcriptDownloads").uncheck(); await form.getByRole("button", { name: "Save config" }).click(); await expect(form.getByText(/public url must be/i)).toBeVisible(); await expect(named(form, "siteTitle")).toHaveValue("Typed Hub"); await expect(named(form, "headerTitle")).toHaveValue("Typed Header"); await expect(named(form, "siteDescription")).toHaveValue( "Typed hub description", ); await expect(named(form, "homeTagline")).toHaveValue("Typed hub tagline"); await expect(named(form, "siteUrl")).toHaveValue("not a url"); await expect(named(form, "cloudflareProject")).toHaveValue("typed-hub"); await expect(named(form, "transcriptDownloads")).not.toBeChecked(); }); test("CutReleaseForm: a bad version keeps it and the commit box", async ({ page, }) => { // The disposable test changelog cut-release.spec.ts drives (never the // repo's own); left with no [Unreleased] heading, as that spec leaves it. const changelog = resolvePath("test-changelog.md"); const before = await readFile(changelog, "utf8").catch(() => null); await writeFile( changelog, "# Changelog\n\n## [Unreleased]\n- a pending bullet\n\n" + "## [9.9.9] - 2024-01-01\n- old\n", ); try { await page.goto("/changelog"); const form = page.locator("form").filter({ has: page.locator("#version-editor"), }); await hydrated(form); await page.locator("#version-editor").fill("not-semver"); await form.getByRole("checkbox", { name: "Commit changelog" }).uncheck(); await form.getByRole("button", { name: /^cut release$/i }).click(); await expect(alert(page, /not a valid semver/i)).toBeVisible(); await expect(page.locator("#version-editor")).toHaveValue("not-semver"); await expect( form.getByRole("checkbox", { name: "Commit changelog" }), ).not.toBeChecked(); // Refused before anything was written. expect(await readFile(changelog, "utf8")).toContain("## [Unreleased]"); } finally { await writeFile( changelog, before ?? "# Changelog\n\n## [9.9.9] - 2024-01-01\n- old released bullet\n", ); } }); test("AttributionSettingsForm: a refused write keeps every field", async ({ page, }) => { await resetData("empty"); await page.goto("/operations/attribution-text"); const form = page.locator('form[data-settings-block="attribution"]'); await hydrated(form); const enabled = await named(form, "attributionEnabled").isChecked(); await named(form, "attributionEnabled").setChecked(!enabled); await named(form, "attributionTextOnly").check(); const app = named(form, "attributionAppId"); await app.selectOption({ index: 1 }); const appValue = await app.inputValue(); await named(form, "attributionModel").fill("typed-model:7b"); await named(form, "attributionPromptVersion").fill("99"); await withSettingsUnwritable(async () => { await form .getByRole("button", { name: "Save attribution settings" }) .click(); await expect(form.getByRole("alert")).toBeVisible(); }); await expect(named(form, "attributionEnabled")).toBeChecked({ checked: !enabled, }); await expect(named(form, "attributionTextOnly")).toBeChecked(); await expect(app).toHaveValue(appValue); await expect(named(form, "attributionModel")).toHaveValue("typed-model:7b"); await expect(named(form, "attributionPromptVersion")).toHaveValue("99"); }); test("DiarizationSettingsForm: a refused write keeps every field", async ({ page, }) => { await resetData("empty"); await page.goto("/operations/diarization"); const form = page.locator('form[data-settings-block="diarization"]'); await hydrated(form); await named(form, "diarizationEnabled").check(); await named(form, "diarizationInlineAfterTranscribe").check(); await named(form, "diarizationEngine").selectOption("sortformer"); await named(form, "diarizationBackend").selectOption("cpu"); await named(form, "diarizationSortformerBin").fill("/opt/typed/diarize"); await named(form, "diarizationPython").fill("/opt/typed/python"); await named(form, "diarizationThreshold").fill("0.42"); await named(form, "diarizationThreads").fill("3"); await named(form, "diarizationMaxAudioHours").fill("2.5"); await withSettingsUnwritable(async () => { await form .getByRole("button", { name: "Save diarization settings" }) .click(); await expect(form.getByRole("alert")).toBeVisible(); }); await expect(named(form, "diarizationEnabled")).toBeChecked(); await expect(named(form, "diarizationInlineAfterTranscribe")).toBeChecked(); await expect(named(form, "diarizationEngine")).toHaveValue("sortformer"); await expect(named(form, "diarizationBackend")).toHaveValue("cpu"); await expect(named(form, "diarizationSortformerBin")).toHaveValue( "/opt/typed/diarize", ); await expect(named(form, "diarizationPython")).toHaveValue( "/opt/typed/python", ); await expect(named(form, "diarizationThreshold")).toHaveValue("0.42"); await expect(named(form, "diarizationThreads")).toHaveValue("3"); await expect(named(form, "diarizationMaxAudioHours")).toHaveValue("2.5"); }); test("DigestSettingsForm: a refused write keeps every field, the section boxes too", async ({ page, }) => { await resetData("empty"); await page.goto("/operations/digest"); const form = page.locator('form[data-settings-block="digest"]'); await hydrated(form); const box = (section: string) => form.locator(`input[name="digestSections"][value="${section}"]`); await box("tags").check(); await box("chapters").uncheck(); await named(form, "digestTimestampMode").selectOption("absolute"); await named(form, "digestPromptVariant").fill("typed-variant"); await named(form, "digestYieldToCpuWorkers").check(); await named(form, "digestRemoteEnabled").check(); await named(form, "digestLongTailSeconds").fill("7200"); await named(form, "digestSpendCapUsd").fill("3.25"); await withSettingsUnwritable(async () => { await form.getByRole("button", { name: "Save digest settings" }).click(); await expect(form.getByRole("alert")).toBeVisible(); expect(await checkedNow(box("tags"))).toBe(true); expect(await checkedNow(box("chapters"))).toBe(false); }); await expect(named(form, "digestTimestampMode")).toHaveValue("absolute"); await expect(named(form, "digestPromptVariant")).toHaveValue("typed-variant"); await expect(named(form, "digestYieldToCpuWorkers")).toBeChecked(); await expect(named(form, "digestRemoteEnabled")).toBeChecked(); await expect(named(form, "digestLongTailSeconds")).toHaveValue("7200"); await expect(named(form, "digestSpendCapUsd")).toHaveValue("3.25"); }); test("LaneSettingsForm: a refused write keeps every field", async ({ page, }) => { await resetData("empty"); await page.goto("/operations/diarization"); const form = page.locator('form[data-settings-block="backfill"]'); await hydrated(form); const run = await named(form, "backfillEnabled").isChecked(); await named(form, "backfillEnabled").setChecked(!run); await named(form, "backfillConcurrency").fill("3"); await named(form, "backfillAllowRedownload").check(); await withSettingsUnwritable(async () => { await form.getByRole("button", { name: "Save lane settings" }).click(); await expect(form.getByRole("alert")).toBeVisible(); }); await expect(named(form, "backfillEnabled")).toBeChecked({ checked: !run }); await expect(named(form, "backfillConcurrency")).toHaveValue("3"); await expect(named(form, "backfillAllowRedownload")).toBeChecked(); }); test("SchedulerSettingsForm: a refused write keeps every field, a cadence select too", async ({ page, }) => { await resetData("empty"); await page.goto("/operations/sync"); const form = page.locator('form[data-settings-block="syncScheduler"]'); await hydrated(form); const on = await named(form, "syncSchedulerEnabled").isChecked(); await named(form, "syncSchedulerEnabled").setChecked(!on); await named(form, "syncSchedulerFullSweepConfirmMaxSuspects").fill("6"); await named(form, "syncSchedulerFullSweepShrinkGuardPercent").fill("33"); await named(form, "syncSchedulerMaxConcurrentSyncs").fill("4"); await named(form, "syncSchedulerQuietHoursStart").fill("22"); await named(form, "syncSchedulerQuietHoursEnd").fill("6"); // A DurationField: a controlled select inside the action form. const sweep = form.getByLabel("Default full sweep interval", { exact: true }); await sweep.selectOption("4320"); await withSettingsUnwritable(async () => { await form.getByRole("button", { name: "Save controls" }).click(); await expect(form.getByRole("alert")).toBeVisible(); expect(await valueNow(sweep)).toBe("4320"); }); await expect(named(form, "syncSchedulerEnabled")).toBeChecked({ checked: !on, }); await expect( named(form, "syncSchedulerFullSweepConfirmMaxSuspects"), ).toHaveValue("6"); await expect( named(form, "syncSchedulerFullSweepShrinkGuardPercent"), ).toHaveValue("33"); await expect(named(form, "syncSchedulerMaxConcurrentSyncs")).toHaveValue("4"); await expect(named(form, "syncSchedulerQuietHoursStart")).toHaveValue("22"); await expect(named(form, "syncSchedulerQuietHoursEnd")).toHaveValue("6"); }); test("SavedVideosControls: a refused write keeps the box and the cadence", async ({ page, }) => { await resetData("empty"); await page.goto("/saved-videos"); const form = page.getByLabel("backup config"); await hydrated(form); await page.getByLabel("backup destination").fill("/tmp/ttb-fk-dest"); await page.getByLabel("scheduled backup enabled").check(); await form.getByLabel("Backup interval amount").fill("3"); await form.getByLabel("Backup interval unit").selectOption("days"); await withSettingsUnwritable(async () => { await form.getByRole("button", { name: "Save backup config" }).click(); await expect(form.getByRole("alert")).toBeVisible(); expect(await valueNow(form.getByLabel("Backup interval unit"))).toBe( "days", ); }); await expect(page.getByLabel("backup destination")).toHaveValue( "/tmp/ttb-fk-dest", ); await expect(page.getByLabel("scheduled backup enabled")).toBeChecked(); await expect(form.getByLabel("Backup interval amount")).toHaveValue("3"); }); test("WorkersConfigForm: a refused list keeps the Enabled boxes as set", async ({ page, }) => { await resetData("empty"); await writeSettings({ workers: ["gpu", "cpu"].map((id, priority) => ({ id, name: id.toUpperCase(), kind: "local", enabled: true, priority, appId: "whisper-cpp", config: {}, })), }); await page.goto("/workers"); const form = page.locator('form[data-settings-block="workers"]'); await hydrated(form); await form.getByLabel("worker 1 enabled").uncheck(); await form.getByLabel("worker 2 enabled").uncheck(); await form.getByRole("button", { name: "Save workers" }).click(); await expect(alert(page, /at least one worker must be enabled/i)).toBeVisible(); // Controlled boxes: before slice FK the reset put them back to ticked, while // the list still said off, until the next re-render. expect(await checkedNow(form.getByLabel("worker 1 enabled"))).toBe(false); expect(await checkedNow(form.getByLabel("worker 2 enabled"))).toBe(false); });