evOo-local/apps/mobile/lib/appearance.test.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

95 lines
3.3 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const store = new Map<string, string>();
const schemeListeners = new Set<(event: { colorScheme: "light" | "dark" | null }) => void>();
let colorScheme: "light" | "dark" | null = "dark";
vi.mock("expo-secure-store", () => ({
getItemAsync: vi.fn(async (key: string) => store.get(key) ?? null),
setItemAsync: vi.fn(async (key: string, value: string) => {
store.set(key, value);
}),
}));
vi.mock("react-native", () => ({
Appearance: {
getColorScheme: () => colorScheme,
addChangeListener: (listener: (event: { colorScheme: "light" | "dark" | null }) => void) => {
schemeListeners.add(listener);
return {
remove() {
schemeListeners.delete(listener);
},
};
},
},
}));
describe("mobile appearance", () => {
beforeEach(() => {
store.clear();
schemeListeners.clear();
colorScheme = "dark";
vi.resetModules();
});
it("defaults to system and resolves light or dark from the scheme", async () => {
const { getCachedAppearancePreference, resolveMobileAppearance, setAppearancePreference } =
await import("./appearance");
expect(getCachedAppearancePreference()).toBe("system");
expect(resolveMobileAppearance("system", "light")).toBe("light");
expect(resolveMobileAppearance("system", "dark")).toBe("dark");
await setAppearancePreference("light");
expect(getCachedAppearancePreference()).toBe("light");
expect(resolveMobileAppearance("light", "dark")).toBe("light");
await setAppearancePreference("system");
});
it("notifies mounted navigation when the saved preference loads", async () => {
const { UI_APPEARANCE_STORAGE_KEY, lightTokens } = await import("@rakazo/ui-tokens");
const { loadAppearancePreference, mobileTokens, subscribeAppearance } = await import(
"./appearance"
);
store.set(UI_APPEARANCE_STORAGE_KEY, "light");
const listener = vi.fn();
subscribeAppearance(listener);
await loadAppearancePreference();
expect(listener).toHaveBeenCalledOnce();
expect(mobileTokens()).toEqual(lightTokens);
});
it("notifies subscribers when the OS scheme flips under System preference", async () => {
const { resolveMobileAppearance, subscribeAppearance } = await import("./appearance");
const listener = vi.fn();
subscribeAppearance(listener);
expect(resolveMobileAppearance()).toBe("dark");
colorScheme = "light";
for (const notify of schemeListeners) notify({ colorScheme: "light" });
expect(listener).toHaveBeenCalledTimes(1);
expect(resolveMobileAppearance()).toBe("light");
});
it("flips user bubble tokens when appearance switches with an existing preference", async () => {
const { mobileTokens, setAppearancePreference, subscribeAppearance } = await import(
"./appearance"
);
const listener = vi.fn();
subscribeAppearance(listener);
await setAppearancePreference("dark");
const darkBubble = mobileTokens().secondary;
const darkInk = mobileTokens().secondaryForeground;
listener.mockClear();
await setAppearancePreference("light");
expect(listener).toHaveBeenCalledOnce();
expect(mobileTokens().secondary).not.toBe(darkBubble);
expect(mobileTokens().secondaryForeground).not.toBe(darkInk);
expect(mobileTokens().secondary).not.toBe(mobileTokens().primary);
});
});