Site: scroll tour with live clips, beat-reactive desk and speaker, road playground, night wave, film on an isometric screen
This commit is contained in:
46 files changed
+2708
-169
No files matched your search
@@ -0,0 +1,111 @@
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
|
||||
const PAGE = "/";
|
||||
const DESK = { light: "rgb(230, 230, 226)", dark: "rgb(58, 58, 58)" };
|
||||
|
||||
let errors: string[] = [];
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
errors = [];
|
||||
page.on("pageerror", (e) => errors.push(String(e)));
|
||||
});
|
||||
|
||||
test.afterEach(() => expect(errors).toEqual([]));
|
||||
|
||||
const open = async (page: Page) => {
|
||||
await page.goto(PAGE);
|
||||
await expect(page.locator(".model .block").first()).toBeAttached();
|
||||
};
|
||||
|
||||
/** The hero desk's top face colour. */
|
||||
const desk = (page: Page) =>
|
||||
page.evaluate(() => {
|
||||
const top = document.querySelector(".model .block .face.top")!;
|
||||
return getComputedStyle(top).fill;
|
||||
});
|
||||
|
||||
const lamp = (page: Page) => page.getByRole("button", { name: "Switch to dark" });
|
||||
|
||||
test("the lamp turns the page dark in a wave", async ({ page }) => {
|
||||
await open(page);
|
||||
expect(await page.evaluate(() => document.documentElement.dataset.theme)).toBe("light");
|
||||
expect(await desk(page)).toBe(DESK.light);
|
||||
|
||||
// Read the nearest and farthest hero blocks mid-wave.
|
||||
const mid = await page.evaluate(async () => {
|
||||
const tops = [...document.querySelectorAll<SVGGElement>(".model .block")];
|
||||
document.querySelector<HTMLButtonElement>(".night")!.click();
|
||||
const wave = (g: SVGGElement) => parseFloat(g.style.getPropertyValue("--wave"));
|
||||
tops.sort((a, b) => wave(a) - wave(b));
|
||||
const [near, far] = [tops[0], tops[tops.length - 1]];
|
||||
const fill = (g: SVGGElement) => getComputedStyle(g.querySelector(".face.top")!).fill;
|
||||
// Sample between the near landing and the far start.
|
||||
const gap = (wave(near) + 0.2 + wave(far)) / 2;
|
||||
await new Promise((r) => setTimeout(r, gap * 1000));
|
||||
return {
|
||||
steps: [wave(near), wave(far)],
|
||||
near: [fill(near), near.style.getPropertyValue("--tone-dark")],
|
||||
far: [fill(far), far.style.getPropertyValue("--tone")],
|
||||
};
|
||||
});
|
||||
expect(mid.steps[0] + 0.25).toBeLessThan(mid.steps[1]);
|
||||
expect(mid.steps[1]).toBeLessThanOrEqual(1);
|
||||
const rgb = (hex: string) => `rgb(${[1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16)).join(", ")})`;
|
||||
expect(mid.near[0]).toBe(rgb(mid.near[1]));
|
||||
expect(mid.far[0]).toBe(rgb(mid.far[1]));
|
||||
|
||||
await expect.poll(() => desk(page), { timeout: 1200 }).toBe(DESK.dark);
|
||||
expect(await page.evaluate(() => document.documentElement.dataset.theme)).toBe("dark");
|
||||
await expect(page.getByRole("button", { name: "Switch to light" })).toBeAttached();
|
||||
await expect(page.locator(".night-sky")).toHaveCount(0);
|
||||
await expect(page.locator("html")).toHaveCSS("background-color", "rgb(37, 37, 37)");
|
||||
});
|
||||
|
||||
test("dark text keeps 4.5:1 against the page", async ({ page }) => {
|
||||
await page.emulateMedia({ colorScheme: "dark" });
|
||||
await open(page);
|
||||
const worst = await page.evaluate(() => {
|
||||
const parse = (c: string) => c.match(/[\d.]+/g)!.slice(0, 3).map(Number);
|
||||
const lum = (c: string) => {
|
||||
const [r, g, b] = parse(c).map((v) => {
|
||||
const s = v / 255;
|
||||
return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
||||
});
|
||||
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
};
|
||||
const page = lum(getComputedStyle(document.documentElement).backgroundColor);
|
||||
const ratios = [...document.querySelectorAll<HTMLElement>("h1, h2, h3, p, a, li, button")]
|
||||
.filter((el) => el.textContent?.trim() && getComputedStyle(el).color !== "rgba(0, 0, 0, 0)")
|
||||
.map((el) => {
|
||||
const text = lum(getComputedStyle(el).color);
|
||||
return { ratio: (Math.max(text, page) + 0.05) / (Math.min(text, page) + 0.05), text: el.textContent };
|
||||
});
|
||||
return ratios.sort((a, b) => a.ratio - b.ratio)[0];
|
||||
});
|
||||
expect(worst.ratio, String(worst.text)).toBeGreaterThanOrEqual(4.5);
|
||||
});
|
||||
|
||||
test("the system scheme sets the first theme; the lamp's choice stays", async ({ page }) => {
|
||||
await page.emulateMedia({ colorScheme: "dark" });
|
||||
await open(page);
|
||||
expect(await page.evaluate(() => document.documentElement.dataset.theme)).toBe("dark");
|
||||
expect(await desk(page)).toBe(DESK.dark);
|
||||
|
||||
await page.emulateMedia({ colorScheme: "light" });
|
||||
await expect.poll(() => page.evaluate(() => document.documentElement.dataset.theme)).toBe("light");
|
||||
|
||||
await page.emulateMedia({ reducedMotion: "reduce" });
|
||||
await lamp(page).click();
|
||||
expect(await desk(page)).toBe(DESK.dark);
|
||||
await page.emulateMedia({ colorScheme: "light" });
|
||||
await page.reload();
|
||||
expect(await page.evaluate(() => document.documentElement.dataset.theme)).toBe("dark");
|
||||
});
|
||||
|
||||
test("reduced motion switches at once", async ({ page }) => {
|
||||
await page.emulateMedia({ reducedMotion: "reduce", colorScheme: "light" });
|
||||
await open(page);
|
||||
await lamp(page).click();
|
||||
expect(await desk(page)).toBe(DESK.dark);
|
||||
await expect(page.locator(".night-sky")).toHaveCount(0);
|
||||
});
|
||||
Reference in new issue
Block a user