feat(ui): helper de thème dark/light persisté (TDD)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 05:26:00 +02:00
parent d3bf4a9fd2
commit b9699bfb8f
3 changed files with 51 additions and 1 deletions
+25
View File
@@ -0,0 +1,25 @@
import { describe, it, expect, beforeEach } from "vitest";
import { nextTheme, getInitialTheme } from "./theme.js";
describe("nextTheme", () => {
it("bascule dark <-> light", () => {
expect(nextTheme("dark")).toBe("light");
expect(nextTheme("light")).toBe("dark");
});
});
describe("getInitialTheme", () => {
beforeEach(() => {
// @ts-expect-error - environnement node sans localStorage
delete globalThis.localStorage;
});
it("retombe sur dark sans localStorage", () => {
expect(getInitialTheme()).toBe("dark");
});
it("lit la valeur persistée si présente", () => {
const store: Record<string, string> = { "su-theme": "light" };
// @ts-expect-error - stub minimal
globalThis.localStorage = { getItem: (k: string) => store[k] ?? null };
expect(getInitialTheme()).toBe("light");
});
});
+25
View File
@@ -0,0 +1,25 @@
// client/src/lib/theme.ts
export type Theme = "dark" | "light";
const KEY = "su-theme";
export function nextTheme(t: Theme): Theme {
return t === "dark" ? "light" : "dark";
}
export function getInitialTheme(): Theme {
try {
const v = globalThis.localStorage?.getItem(KEY);
return v === "light" ? "light" : "dark";
} catch {
return "dark";
}
}
export function applyTheme(t: Theme): void {
try {
document.documentElement.dataset.theme = t;
globalThis.localStorage?.setItem(KEY, t);
} catch {
/* localStorage indisponible (mode privé) : on ignore la persistance */
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ import { defineConfig } from "vitest/config";
export default defineConfig({ export default defineConfig({
test: { test: {
globals: true, globals: true,
include: ["server/**/*.test.ts", "shared/**/*.test.ts"], include: ["server/**/*.test.ts", "shared/**/*.test.ts", "client/**/*.test.ts"],
environment: "node", environment: "node",
}, },
resolve: { alias: { "@shared": new URL("./shared", import.meta.url).pathname } }, resolve: { alias: { "@shared": new URL("./shared", import.meta.url).pathname } },