tests/e2e/switch-appearance.spec.ts
Rama main · réplica de lectura del repositorio original.
import {
expect,
fulfillJson,
mockTeamSettings,
TEAM,
TEAM_SETTINGS_PATH,
test,
} from "./helpers/run-checks-fixtures";
import type { Locator } from "@playwright/test";
async function switchAppearance(control: Locator) {
return control.evaluate((element) => {
const canvas = document.createElement("canvas");
canvas.width = 1;
canvas.height = 1;
const context = canvas.getContext("2d")!;
const luminance = (color: string) => {
context.clearRect(0, 0, 1, 1);
context.fillStyle = color;
context.fillRect(0, 0, 1, 1);
const [red, green, blue] = context.getImageData(0, 0, 1, 1).data;
const linear = (channel: number) => {
const value = channel / 255;
return value <= 0.04045
? value / 12.92
: ((value + 0.055) / 1.055) ** 2.4;
};
return (
0.2126 * linear(red) + 0.7152 * linear(green) + 0.0722 * linear(blue)
);
};
const contrast = (first: string, second: string) => {
const a = luminance(first);
const b = luminance(second);
return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
};
const style = getComputedStyle(element);
const thumb = element.querySelector('[data-slot="switch-thumb"]')!;
const secondarySurface = document.createElement("span");
secondarySurface.style.backgroundColor = "var(--secondary)";
element.append(secondarySurface);
const secondaryColor = getComputedStyle(secondarySurface).backgroundColor;
secondarySurface.style.backgroundColor = "var(--switch-on)";
const onColor = getComputedStyle(secondarySurface).backgroundColor;
secondarySurface.remove();
const pageColor = getComputedStyle(document.body).backgroundColor;
secondarySurface.style.backgroundColor = "var(--ring)";
element.append(secondarySurface);
const ringColor = getComputedStyle(secondarySurface).backgroundColor;
secondarySurface.remove();
return {
border: style.borderColor,
background: style.backgroundColor,
secondaryColor,
onColor,
ringColor,
thumbColor: getComputedStyle(thumb).backgroundColor,
outlineContrast: contrast(style.borderColor, pageColor),
thumbPageContrast: contrast(
getComputedStyle(thumb).backgroundColor,
pageColor
),
thumbContrast: contrast(
getComputedStyle(thumb).backgroundColor,
style.backgroundColor
),
thumbX: thumb.getBoundingClientRect().x,
thumbRight: thumb.getBoundingClientRect().right,
padding:
Number.parseFloat(style.paddingRight) +
Number.parseFloat(style.borderRightWidth),
right: element.getBoundingClientRect().right,
};
});
}
for (const theme of ["light", "dark"] as const) {
test(`${theme} switches show a grey thumb on an outlined track when off and a white thumb on green when on`, async ({
page,
}) => {
await mockTeamSettings(page);
await page.route(/\/api\/settings(?:\?.*)?$/, (route) =>
fulfillJson(route, { default_model: null, theme })
);
await page.emulateMedia({ colorScheme: theme, reducedMotion: "reduce" });
await page.addInitScript(
(value) => localStorage.setItem("theme", value),
theme
);
let enabled = false;
await page.route(`**/api/teams/${TEAM.id}/signup-domain`, (route) => {
if (route.request().method() === "PATCH") {
enabled = route.request().postDataJSON().enabled;
}
return fulfillJson(route, {
enabled,
domain: "example.com",
eligibleDomain: "example.com",
viewer: { canManage: true },
});
});
await page.goto(`${TEAM_SETTINGS_PATH}/members`);
await expect(page.locator("html")).toHaveClass(new RegExp(theme));
const control = page.getByRole("switch", {
name: "Allow people from @example.com to join",
});
await expect(control).toBeEnabled();
const off = await switchAppearance(control);
expect(off.border).not.toBe("rgba(0, 0, 0, 0)");
// The off track is deliberately quiet; the thumb carries the 3:1 contrast.
expect(off.thumbPageContrast).toBeGreaterThanOrEqual(3);
expect(off.background).toBe(off.secondaryColor);
expect(off.thumbContrast).toBeGreaterThanOrEqual(3);
await expect(control).toHaveText("");
await control.focus();
await expect(control).toBeFocused();
await page.keyboard.press("Tab");
await page.keyboard.press("Shift+Tab");
await expect(control).toBeFocused();
const focused = await switchAppearance(control);
// Keyboard focus recolors the border with --ring, as before the restyle.
expect(focused.border).toBe(focused.ringColor);
await page.keyboard.press("Space");
await expect(control).toBeChecked();
await expect(control).toHaveText("");
const on = await switchAppearance(control);
expect(on.thumbX).toBeGreaterThan(off.thumbX + 10);
expect(on.onColor).not.toBe("rgba(0, 0, 0, 0)");
expect(on.background).toBe(on.onColor);
expect(on.thumbColor).toBe("rgb(255, 255, 255)");
expect(on.outlineContrast).toBeGreaterThanOrEqual(3);
expect(on.thumbContrast).toBeGreaterThanOrEqual(3);
await control.evaluate((element) => {
element.style.width = "80px";
element.style.height = "32px";
element.style.padding = "4px";
});
const resized = await switchAppearance(control);
expect(resized.right - resized.thumbRight).toBeCloseTo(resized.padding);
await page.setViewportSize({ width: 390, height: 844 });
expect((await switchAppearance(control)).right).toBeLessThanOrEqual(390);
await page.screenshot({
path: test.info().outputPath(`${theme}-switches.png`),
});
});
}