import { expect, test } from "@playwright/test"; import { installRoutes } from "./helpers"; // Regression: when a profile is active and the user commits filter edits // that diverge from that profile, commitSearch must drop the profile // pointer. Otherwise hydration on reload prefers the (unchanged) profile // snapshot over the just-committed `working` snapshot and the user sees // their commit silently revert. // // The Save-as profile UI is only rendered when there are multiple channels // to choose between, but the underlying behavior is about // commitSearch + hydration. We pre-seed localStorage directly via // page.evaluate (not addInitScript — that would re-fire on reload and // clobber the commit we're trying to test) so the test isn't coupled to // fixture channel counts. const STORAGE_KEY = "ytdlp-tb:export-filters"; test.describe("filter persistence — diverging commit clears active profile", () => { test.beforeEach(async ({ page }) => { await installRoutes(page); }); test("commit-with-divergence survives refresh", async ({ page }) => { // First navigation: just to give us a window to write localStorage on. // Hydration here runs against empty storage; we throw it away. await page.goto("/"); await page.evaluate( ({ key, value }) => { window.localStorage.setItem(key, value); }, { key: STORAGE_KEY, value: JSON.stringify({ v: 1, // Profile "p1": Livestreams off (nol=true), Deleted on. working: { channels: { included: [], excluded: [] }, nol: true }, profiles: { p1: { channels: { included: [], excluded: [] }, nol: true }, }, activeProfileName: "p1", }), }, ); // Reload so hydration picks up the pre-seeded state. await page.reload(); const livestreams = page.getByRole("checkbox", { name: "Livestreams" }); const deleted = page.getByRole("checkbox", { name: "Deleted" }); await expect(livestreams).not.toBeChecked(); await expect(deleted).toBeChecked(); // Diverge from the profile: also uncheck Deleted, then commit. await deleted.uncheck(); await page.getByPlaceholder("Search transcripts...").press("Enter"); // The commit should clear activeProfileName and persist the new // divergent state into `working`. Availability now persists as the `av` // kept-set rather than the legacy `nd` exclusion boolean, so "Deleted // unchecked" means deleted is absent from av. await expect .poll( async () => await page.evaluate((key) => { const raw = window.localStorage.getItem(key); if (!raw) return null; const parsed = JSON.parse(raw); return { activeProfileName: parsed.activeProfileName, deletedKept: (parsed.working?.av ?? []).includes("deleted"), workingNol: parsed.working?.nol ?? false, }; }, STORAGE_KEY), { timeout: 5_000 }, ) .toEqual({ activeProfileName: null, deletedKept: false, workingNol: true, }); // Reload. Without the fix, hydration would re-read profile p1 (Deleted // still on) and silently revert the commit. await page.reload(); await expect( page.getByRole("checkbox", { name: "Livestreams" }), ).not.toBeChecked(); await expect( page.getByRole("checkbox", { name: "Deleted" }), ).not.toBeChecked(); }); });