anvilsign in

collin/mahjong

master / src / game / prefs.test.ts
1import { beforeEach, describe, expect, it } from 'vitest';
2import { DEFAULT_PREFS, loadPrefs, savePrefs } from './prefs';
3
4const KEY = 'taiwanese-mahjong/prefs';
5
6/** Just enough localStorage to exercise the load/save path under node. */
7function stubStorage() {
8 const map = new Map<string, string>();
9 Object.defineProperty(globalThis, 'localStorage', {
10 configurable: true,
11 value: {
12 getItem: (k: string) => map.get(k) ?? null,
13 setItem: (k: string, v: string) => void map.set(k, v),
14 removeItem: (k: string) => void map.delete(k),
15 },
16 });
17 return map;
18}
19
20describe('preferences', () => {
21 let store: Map<string, string>;
22 beforeEach(() => {
23 store = stubStorage();
24 });
25
26 it('round-trips what was saved', () => {
27 const mine = { ...DEFAULT_PREFS, sound: false, voice: false, volume: 0.2, names: ['a', 'b', 'c', 'd'] };
28 savePrefs(mine);
29 expect(loadPrefs()).toEqual(mine);
30 });
31
32 it('starts a stored blob from before the voice pack on the new audio defaults', () => {
33 // What an older build wrote: no audioGeneration, and voice off.
34 store.set(
35 KEY,
36 JSON.stringify({
37 sound: false,
38 voice: false,
39 volume: 0.9,
40 names: ['甲', '乙', '丙', '丁'],
41 rules: { ...DEFAULT_PREFS.rules, base: 5 },
42 }),
43 );
44 const p = loadPrefs();
45 expect(p.sound).toBe(DEFAULT_PREFS.sound);
46 expect(p.voice).toBe(DEFAULT_PREFS.voice);
47 // ...but what somebody actually chose is theirs, and survives.
48 expect(p.names).toEqual(['甲', '乙', '丙', '丁']);
49 expect(p.rules.base).toBe(5);
50 expect(p.volume).toBe(0.9);
51 });
52
53 it('falls back to defaults on junk', () => {
54 store.set(KEY, '{not json');
55 expect(loadPrefs()).toEqual(DEFAULT_PREFS);
56 });
57});