import { test, expect, type Page } from "@playwright/test"; import { resetData } from "./helpers"; import { RETIRED_BASE } from "../../common/components/themeConfig"; // Regression: refreshing the editor must honor the persisted theme. The // pre-paint sets `data-base` and `.dark` on , but is // server-rendered with a static className that omits `dark`; the mutation was // once lost across the hydration boundary and nothing re-asserted it, so the // page loaded light until a manual toggle. ThemeProvider re-applies the // persisted base on mount. // // And the one-time migration: the retired `ytdlp-tb:theme` / `ytdlp-tb:mode` // keys become a base (archive + light → light) and are deleted; a stored // retired third ground becomes light. const BASE_KEY = "ytdlp-tb:base"; const LEGACY_THEME_KEY = "ytdlp-tb:theme"; const LEGACY_MODE_KEY = "ytdlp-tb:mode"; async function seed(page: Page, values: Record) { // Need an origin before localStorage is writable. await page.goto("/"); await page.evaluate( ({ vals, keys }) => { for (const k of keys) localStorage.removeItem(k); for (const [k, v] of Object.entries(vals)) localStorage.setItem(k, v); }, { vals: values, keys: [BASE_KEY, LEGACY_THEME_KEY, LEGACY_MODE_KEY] }, ); } function storage(page: Page) { return page.evaluate( (keys) => keys.map((k) => localStorage.getItem(k)), [BASE_KEY, LEGACY_THEME_KEY, LEGACY_MODE_KEY], ); } test.beforeEach(async () => { await resetData("empty"); }); test("explicit dark base survives a reload without toggling", async ({ page, }) => { await seed(page, { [BASE_KEY]: "dark" }); await page.reload(); const html = page.locator("html"); await expect(html).toHaveClass(/(^|\s)dark(\s|$)/); await expect(html).toHaveAttribute("data-base", "dark"); }); test("explicit light base loads light", async ({ page }) => { await seed(page, { [BASE_KEY]: "light" }); await page.reload(); const html = page.locator("html"); await expect(html).not.toHaveClass(/(^|\s)dark(\s|$)/); await expect(html).toHaveAttribute("data-base", "light"); }); // The retired third ground: a reader who chose it gets Light, before first // paint, with no other ground on the way, and the stored value becomes // "light" once. Every value `data-base` holds is recorded by a // MutationObserver installed before the page's first script. test.describe("a stored retired base, with the OS dark", () => { test.use({ colorScheme: "dark" }); test("renders Light with no other ground on the way, and is rewritten", async ({ page }) => { await page.addInitScript( ([key, retired]) => { try { localStorage.setItem(key, retired); } catch {} const held: (string | null)[] = []; (window as unknown as { __bases: (string | null)[] }).__bases = held; new MutationObserver((records) => { for (const r of records) if (r.target === document.documentElement) held.push(r.oldValue); }).observe(document, { subtree: true, attributes: true, attributeFilter: ["data-base"], attributeOldValue: true, }); }, [BASE_KEY, RETIRED_BASE], ); await page.goto("/", { waitUntil: "commit" }); await page.waitForFunction(() => document.documentElement?.dataset.themeReady === "1"); await page.waitForLoadState("load"); await page.waitForTimeout(300); const held = await page.evaluate(() => [ ...(window as unknown as { __bases: (string | null)[] }).__bases, document.documentElement.getAttribute("data-base"), ]); expect(held.filter((v) => v !== null && v !== "light"), JSON.stringify(held)).toEqual([]); expect(held.at(-1)).toBe("light"); const html = page.locator("html"); await expect(html).not.toHaveClass(/(^|\s)dark(\s|$)/); expect(await storage(page)).toEqual(["light", null, null]); }); }); test.describe("system base with OS dark", () => { test.use({ colorScheme: "dark" }); test("system resolves to dark on reload", async ({ page }) => { await seed(page, { [BASE_KEY]: "system" }); await page.reload(); const html = page.locator("html"); await expect(html).toHaveClass(/(^|\s)dark(\s|$)/); await expect(html).toHaveAttribute("data-base", "dark"); }); }); test("migration: a stored selenized + dark becomes the dark base; the old keys go", async ({ page, }) => { await seed(page, { [LEGACY_MODE_KEY]: "dark", [LEGACY_THEME_KEY]: "selenized" }); await page.reload(); const html = page.locator("html"); await expect(html).toHaveClass(/(^|\s)dark(\s|$)/); await expect(html).toHaveAttribute("data-base", "dark"); await expect(html).not.toHaveAttribute("data-theme", /.*/); expect(await storage(page)).toEqual(["dark", null, null]); }); test("migration: a stored archive + light (the paper look) becomes light", async ({ page, }) => { await seed(page, { [LEGACY_MODE_KEY]: "light", [LEGACY_THEME_KEY]: "archive" }); await page.reload(); const html = page.locator("html"); await expect(html).toHaveAttribute("data-base", "light"); await expect(html).not.toHaveClass(/(^|\s)dark(\s|$)/); expect(await storage(page)).toEqual(["light", null, null]); });