Skip to content
Source
Sign inSign up

nativeTheme.test.ts

45 lines1.6 KB

1import { describe, expect, it } from "vite-plus/test";
2import { getMobileThemeVariables, MOBILE_THEME_OPTIONS } from "./mobileTheme";
3import { createNativeThemeColors } from "./nativeTheme";
4​
5function luminance(hex: string) {
6 const channels = [1, 3, 5].map((offset) => {
7 const value = Number.parseInt(hex.slice(offset, offset + 2), 16) / 255;
8 return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
9 });
10 return channels[0]! * 0.2126 + channels[1]! * 0.7152 + channels[2]! * 0.0722;
12​
13function contrast(a: string, b: string) {
14 const values = [luminance(a), luminance(b)].sort((x, y) => y - x);
15 return (values[0]! + 0.05) / (values[1]! + 0.05);
17​
18const cases = MOBILE_THEME_OPTIONS.flatMap((theme) => {
19 if (theme.id === "material-you") return [];
20 const themeId = theme.id;
21 return (["light", "dark"] as const).map((appearance) => ({
22 label: theme.label,
23 themeId,
24 appearance,
25 }));
26});
27​
28describe("native theme colors", () => {
29 it.each(cases)(
30 "$label $appearance keeps native labels legible on every container",
31 ({ themeId, appearance }) => {
32 const colors = createNativeThemeColors(getMobileThemeVariables(themeId, appearance));
33 for (const surface of [
34 colors.surface,
35 colors.surfaceContainer,
36 colors.surfaceContainerHigh,
37 colors.surfaceContainerHighest,
38 ]) {
39 expect(contrast(colors.onSurface, surface)).toBeGreaterThanOrEqual(4.5);
40 }
41 expect(contrast(colors.onPrimary, colors.primary)).toBeGreaterThanOrEqual(4.5);
42 for (const color of Object.values(colors)) expect(color).toMatch(/^#[0-9a-f]{6}$/i);
43 },
44 );
45});