feat(theme): apply saved theme before app startup

This commit is contained in:
t8y2 committed 2026-07-21 15:58:04 +08:00
1 parent 01b5f31740
commit 28a5b22e7e
2 files changed
+137

No files matched your search

+99
View File
@@ -0,0 +1,99 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { describe, expect, it, vi } from "vitest";
const indexHtml = readFileSync(fileURLToPath(new URL("../../../../index.html", import.meta.url)), "utf8");
const startupScript = indexHtml.match(/<script data-dbx-startup-theme>([\s\S]*?)<\/script>/)?.[1];
if (!startupScript) throw new Error("Startup theme script not found");
type StartupThemeOptions = {
mode?: string | null;
prefersDark?: boolean;
storageError?: boolean;
};
function runStartupTheme({ mode = null, prefersDark = false, storageError = false }: StartupThemeOptions = {}) {
const toggle = vi.fn();
const root = { classList: { toggle }, style: { colorScheme: "" } };
const localStorage = {
getItem: vi.fn(() => {
if (storageError) throw new DOMException("Storage unavailable", "SecurityError");
return mode;
}),
};
const matchMedia = vi.fn(() => ({ matches: prefersDark }));
Function("document", "localStorage", "window", startupScript)({ documentElement: root }, localStorage, { matchMedia });
return { matchMedia, root, toggle };
}
describe("startup theme", () => {
it("applies dark mode before the application mounts", () => {
const { matchMedia, root, toggle } = runStartupTheme({ mode: "dark" });
expect(toggle).toHaveBeenCalledWith("dark", true);
expect(root.style.colorScheme).toBe("dark");
expect(matchMedia).not.toHaveBeenCalled();
});
it("keeps explicit light mode light", () => {
const { matchMedia, root, toggle } = runStartupTheme({ mode: "light", prefersDark: true });
expect(toggle).toHaveBeenCalledWith("dark", false);
expect(root.style.colorScheme).toBe("light");
expect(matchMedia).not.toHaveBeenCalled();
});
it.each([
[true, "dark"],
[false, "light"],
])("resolves system mode when prefersDark is %s", (prefersDark, appearance) => {
const { matchMedia, root, toggle } = runStartupTheme({ mode: "system", prefersDark });
expect(toggle).toHaveBeenCalledWith("dark", prefersDark);
expect(root.style.colorScheme).toBe(appearance);
expect(matchMedia).toHaveBeenCalledWith("(prefers-color-scheme: dark)");
});
it("uses the current light default when no preference exists", () => {
const { root, toggle } = runStartupTheme();
expect(toggle).toHaveBeenCalledWith("dark", false);
expect(root.style.colorScheme).toBe("light");
});
it("falls back to light when localStorage is unavailable", () => {
const { root, toggle } = runStartupTheme({ storageError: true, prefersDark: true });
expect(toggle).toHaveBeenCalledWith("dark", false);
expect(root.style.colorScheme).toBe("light");
});
it("falls back to light for an invalid persisted mode", () => {
const { root, toggle } = runStartupTheme({ mode: "invalid", prefersDark: true });
expect(toggle).toHaveBeenCalledWith("dark", false);
expect(root.style.colorScheme).toBe("light");
});
it("falls back to light when system appearance detection is unavailable", () => {
const toggle = vi.fn();
const root = { classList: { toggle }, style: { colorScheme: "" } };
Function("document", "localStorage", "window", startupScript)({ documentElement: root }, { getItem: () => "system" }, {});
expect(toggle).toHaveBeenCalledWith("dark", false);
expect(root.style.colorScheme).toBe("light");
});
it.each([
["soft-dark", true],
["soft-light", false],
])("preserves legacy %s mode", (mode, dark) => {
const { toggle } = runStartupTheme({ mode });
expect(toggle).toHaveBeenCalledWith("dark", dark);
});
});