mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-10-02 02:27:44 +00:00
fix: persist presets and theme preferences
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { loadThemePreference, persistThemePreference } from "./ThemeContext";
|
||||
|
||||
function createStorageMock(initialValues: Record<string, string> = {}): Storage {
|
||||
const store = new Map(Object.entries(initialValues));
|
||||
|
||||
return {
|
||||
get length() {
|
||||
return store.size;
|
||||
},
|
||||
clear() {
|
||||
store.clear();
|
||||
},
|
||||
getItem(key) {
|
||||
return store.get(key) ?? null;
|
||||
},
|
||||
key(index) {
|
||||
return Array.from(store.keys())[index] ?? null;
|
||||
},
|
||||
removeItem(key) {
|
||||
store.delete(key);
|
||||
},
|
||||
setItem(key, value) {
|
||||
store.set(key, value);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function stubElectronSettings(initialValues: Record<string, unknown> = {}) {
|
||||
const store = new Map(Object.entries(initialValues));
|
||||
|
||||
Object.defineProperty(globalThis, "electronAPI", {
|
||||
configurable: true,
|
||||
value: {
|
||||
getAppSetting: (key: string) => (store.has(key) ? store.get(key) : null),
|
||||
setAppSetting: (key: string, value: unknown) => {
|
||||
store.set(key, value);
|
||||
return true;
|
||||
},
|
||||
} as Pick<Window["electronAPI"], "getAppSetting" | "setAppSetting">,
|
||||
});
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
describe("ThemeContext persistence", () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
Reflect.deleteProperty(globalThis, "electronAPI");
|
||||
});
|
||||
|
||||
it("loads the persisted theme preference from Electron app settings", () => {
|
||||
stubElectronSettings({ "recordly.theme": "dark" });
|
||||
|
||||
expect(loadThemePreference()).toBe("dark");
|
||||
});
|
||||
|
||||
it("saves the theme preference to Electron app settings", () => {
|
||||
const settingsStore = stubElectronSettings();
|
||||
|
||||
persistThemePreference("dark");
|
||||
|
||||
expect(settingsStore.get("recordly.theme")).toBe("dark");
|
||||
});
|
||||
|
||||
it("falls back to localStorage when Electron settings are unavailable", () => {
|
||||
vi.stubGlobal(
|
||||
"localStorage",
|
||||
createStorageMock({ "recordly.theme": "light" }),
|
||||
);
|
||||
|
||||
expect(loadThemePreference()).toBe("light");
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
||||
import { loadAppSetting, saveAppSetting } from "../lib/appSettings";
|
||||
|
||||
export type ThemePreference = "light" | "dark" | "system";
|
||||
export type ResolvedTheme = "light" | "dark";
|
||||
@@ -16,7 +17,12 @@ const THEME_STORAGE_KEY = "recordly.theme";
|
||||
|
||||
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
||||
|
||||
function getStoredPreference(): ThemePreference {
|
||||
export function loadThemePreference(): ThemePreference {
|
||||
const persisted = loadAppSetting<unknown>(THEME_STORAGE_KEY);
|
||||
if (persisted === "light" || persisted === "dark" || persisted === "system") {
|
||||
return persisted;
|
||||
}
|
||||
|
||||
try {
|
||||
const stored = globalThis.localStorage?.getItem(THEME_STORAGE_KEY);
|
||||
if (stored === "light" || stored === "dark" || stored === "system") {
|
||||
@@ -28,6 +34,16 @@ function getStoredPreference(): ThemePreference {
|
||||
return "system";
|
||||
}
|
||||
|
||||
export function persistThemePreference(pref: ThemePreference): void {
|
||||
saveAppSetting(THEME_STORAGE_KEY, pref);
|
||||
|
||||
try {
|
||||
globalThis.localStorage?.setItem(THEME_STORAGE_KEY, pref);
|
||||
} catch {
|
||||
// Ignore storage errors
|
||||
}
|
||||
}
|
||||
|
||||
function resolveTheme(pref: ThemePreference): ResolvedTheme {
|
||||
if (pref === "system") {
|
||||
return globalThis.matchMedia?.("(prefers-color-scheme: dark)").matches
|
||||
@@ -49,7 +65,7 @@ function applyThemeToDOM(theme: ResolvedTheme) {
|
||||
|
||||
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const [preference, setPreferenceState] = useState<ThemePreference>(() => {
|
||||
const stored = getStoredPreference();
|
||||
const stored = loadThemePreference();
|
||||
applyThemeToDOM(resolveTheme(stored));
|
||||
return stored;
|
||||
});
|
||||
@@ -63,11 +79,7 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const r = resolveTheme(pref);
|
||||
setResolved(r);
|
||||
applyThemeToDOM(r);
|
||||
try {
|
||||
globalThis.localStorage?.setItem(THEME_STORAGE_KEY, pref);
|
||||
} catch {
|
||||
// Ignore storage errors
|
||||
}
|
||||
persistThemePreference(pref);
|
||||
}, []);
|
||||
|
||||
const toggleTheme = useCallback(() => {
|
||||
|
||||
Reference in New Issue
Block a user