import { expect, test, type Locator, type Page } from "@playwright/test"; import { ICON_PALETTES, markSvg } from "../../common/lib/brand"; // The project site wears the PARENT mark (plans/brand-and-themes.md, slice // S1): bone on slate in its icons and its header, and the wordmark split on the // subject — "Archi" heavy, "lyzer" light — no longer tracked uppercase. test("the header is the parent mark + Archi|lyzer, still named 'Archilyzer home'", async ({ page, }) => { await page.goto("/"); const home = page.getByRole("link", { name: "Archilyzer home", exact: true }); await expect(home).toBeVisible(); await expect(home.locator("[data-wordmark-lead]")).toHaveText("Archi"); await expect(home.locator("[data-wordmark-suffix]")).toHaveText("lyzer"); await expect(home.locator("[data-wordmark]")).not.toHaveCSS("text-transform", "uppercase"); const mark = home.locator("svg[data-brand-mark]"); await expect(mark).toHaveAttribute("aria-hidden", "true"); const fills = await mark.evaluate((svg) => ["ground", "dim", "lit"].map( (t) => getComputedStyle(svg.querySelector(`[data-tone="${t}"]`) as Element).fill, ), ); expect(fills).toEqual(["rgb(21, 27, 32)", "rgb(88, 105, 119)", "rgb(231, 237, 241)"]); }); test("the icon set is rendered from the parent mark", async ({ request }) => { const svg = await request.get("/icons/icon.svg"); expect(svg.headers()["content-type"]).toBe("image/svg+xml"); expect(await svg.text()).toBe(markSvg(ICON_PALETTES.archilyzer)); // The OG image names this one. const png = await request.get("/icons/icon-512.png"); expect(png.headers()["content-type"]).toBe("image/png"); const b = await png.body(); expect([b.readUInt32BE(16), b.readUInt32BE(20)]).toEqual([512, 512]); const ico = await request.get("/favicon.ico"); expect([...(await ico.body()).subarray(0, 4)]).toEqual([0, 0, 1, 0]); }); // The ring on dark (release 10, slice MR): on the dark base the tile has no // edge, so every mark's tile gets a 1px ring outside it in its palette's dim, // following the ground's corner (rx 112 of 512). A box-shadow, so the mark's // box is the same on every base; light draws none. const BASE_KEY = "ytdlp-tb:base"; async function onBase(page: Page, base: "light" | "dark") { await page.evaluate(([k, b]) => localStorage.setItem(k, b), [BASE_KEY, base]); await page.reload({ waitUntil: "commit" }); await page.waitForFunction(() => document.documentElement?.dataset.themeReady === "1"); expect(await page.evaluate(() => document.documentElement.classList.contains("dark"))).toBe( base === "dark", ); } // The mark's visible box-shadow layers (Tailwind's composite lists four // transparent ones before the ring), its outline, its corner, and its laid-out // size. async function markRing(mark: Locator) { return mark.evaluate((svg) => { const cs = getComputedStyle(svg); const r = svg.getBoundingClientRect(); const layers = cs.boxShadow === "none" ? [] : cs.boxShadow.split(/,(?![^(]*\))/); return { ring: layers.map((l) => l.trim()).filter((l) => !l.startsWith("rgba(0, 0, 0, 0)")), outline: cs.outlineStyle, radius: cs.borderTopLeftRadius, size: [r.width, r.height], }; }); } test("on dark the header mark has a 1px ring in the slate's dim; on light none", async ({ page, }) => { await page.goto("/"); const mark = page .getByRole("link", { name: "Archilyzer home", exact: true }) .locator("svg[data-brand-mark]"); await onBase(page, "dark"); expect(await markRing(mark)).toEqual({ ring: ["rgb(88, 105, 119) 0px 0px 0px 1px"], // ICON_PALETTES.archilyzer.dim, #586977 outline: "none", radius: "21.875%", size: [28, 28], }); for (const base of ["light"] as const) { await onBase(page, base); expect(await markRing(mark), base).toEqual({ ring: [], outline: "none", radius: "21.875%", size: [28, 28], }); } }); // Forced colours (Windows High Contrast; release 11): the reader's Canvas // replaces the page on every base and may be as dark as the tile, and the ring // is only drawn on dark (an engine that forces box-shadow off drops even that). // So there the tile gets a 1px outline in the system CanvasText, on every base, // outside it and following the same corner; the mark's box is unchanged. // Playwright's emulation applies the forced palette itself (a white Canvas). // (Outside forced colours the test above holds: no outline.) test.describe("under forced colours", () => { test("the header mark's tile is outlined in CanvasText on every base", async ({ page }) => { await page.emulateMedia({ forcedColors: "active" }); await page.goto("/"); const mark = page .getByRole("link", { name: "Archilyzer home", exact: true }) .locator("svg[data-brand-mark]"); for (const base of ["dark", "light"] as const) { await onBase(page, base); expect(await page.evaluate(() => matchMedia("(forced-colors: active)").matches)).toBe(true); const got = await mark.evaluate((svg) => { const cs = getComputedStyle(svg); const probe = document.createElement("span"); probe.style.color = "CanvasText"; document.body.append(probe); const canvasText = getComputedStyle(probe).color; probe.remove(); const r = svg.getBoundingClientRect(); return { outline: `${cs.outlineStyle} ${cs.outlineWidth}`, isCanvasText: cs.outlineColor === canvasText, offset: cs.outlineOffset, radius: cs.borderTopLeftRadius, size: [r.width, r.height], }; }); expect(got, base).toEqual({ outline: "solid 1px", isCanvasText: true, offset: "0px", radius: "21.875%", size: [28, 28], }); } }); });