Files
Crystal/tests/preferences.test.ts
T
samyandClaude Opus 5.5 f289615bd2
Checks / web (push) Failing after 18s
Private ROM checks / private-rom (push) Successful in 14m40s
Add text size and keyboard settings (#38)
Settings sets the text size (100, 125 or 150 %) and the key of each
button, kept in this browser's storage and validated on load. Every font
size scales with --text-scale, and the dialogue box scrolls instead of
growing past the screen. The game screen's label, the Play message and
Help's control list name the current keys.

Screen reader announcements for every native screen and touch control
remapping remain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 20:42:59 +02:00

81 lines
2.7 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import {
buttonKeys,
defaultPreferences,
keyMap,
keyName,
loadPreferences,
rebind,
savePreferences,
} from "../src/ui/preferences";
function storage(initial: Record<string, string> = {}) {
const values = new Map(Object.entries(initial));
return {
getItem: (key: string) => values.get(key) ?? null,
setItem: (key: string, value: string) => void values.set(key, value),
values,
};
}
describe("interface preferences", () => {
afterEach(() => vi.unstubAllGlobals());
it("defaults to the original keys and standard text", () => {
const preferences = defaultPreferences();
expect(preferences.textScale).toBe(1);
const map = keyMap(preferences);
expect(map.get("KeyX")).toBe("a");
expect(map.get("KeyZ")).toBe("b");
expect(map.get("Enter")).toBe("start");
expect(map.get("ShiftRight")).toBe("select");
expect(buttonKeys(preferences, "select")).toBe("Shift");
expect(buttonKeys(preferences, "up")).toBe("↑");
});
it("names keys readably", () => {
expect(keyName("KeyQ")).toBe("Q");
expect(keyName("Digit4")).toBe("4");
expect(keyName("ArrowLeft")).toBe("←");
expect(keyName("Semicolon")).toBe("Semicolon");
});
it("moves a key to its new button and refuses reserved keys", () => {
let preferences = rebind(defaultPreferences(), "a", "KeyZ");
expect(preferences.keys.a).toEqual(["KeyZ"]);
expect(preferences.keys.b).toEqual([]);
expect(buttonKeys(preferences, "b")).toBe("None");
preferences = rebind(preferences, "start", "Tab");
expect(preferences.keys.start).toEqual(["Enter"]);
});
it("round-trips through storage and rejects invalid entries", () => {
const store = storage();
vi.stubGlobal("localStorage", store);
const saved = { ...rebind(defaultPreferences(), "start", "KeyQ"), textScale: 1.5 as const };
savePreferences(saved);
expect(loadPreferences()).toEqual(saved);
// A duplicated key or an unknown scale falls back to the defaults.
store.values.set(
"crystal-preferences-v1",
JSON.stringify({ textScale: 3, keys: { ...saved.keys, b: ["KeyQ"] } }),
);
expect(loadPreferences()).toEqual(defaultPreferences());
store.values.set("crystal-preferences-v1", "{not json");
expect(loadPreferences()).toEqual(defaultPreferences());
});
it("falls back to the defaults without storage", () => {
vi.stubGlobal("localStorage", {
getItem: () => {
throw new Error("blocked");
},
setItem: () => {
throw new Error("blocked");
},
});
expect(loadPreferences()).toEqual(defaultPreferences());
expect(() => savePreferences(defaultPreferences())).not.toThrow();
});
});