Files
doormilxpress_astryx/tests/lib/preferences.test.js
2026-08-27 13:40:48 +05:30

160 lines
6.0 KiB
JavaScript

import { renderHook, act } from '@testing-library/react';
import { DEFAULT_PREFERENCES, writePreferences, usePreferences, useUpdatePreferences } from '@/lib/preferences';
const STORAGE_KEY = 'doormilePreferences';
/**
* Operator display preferences.
*
* Two properties matter: unknown/corrupt storage must never take a page down,
* and a change must reach EVERY mounted consumer including the one that made it
* — `storage` alone fires only in other tabs, which left the table that
* triggered the change unchanged.
*/
describe('preferences', () => {
describe('defaults', () => {
it('should default to comfortable density and a 25-row page', () => {
expect(DEFAULT_PREFERENCES).toEqual({ compactDensity: false, defaultPageSize: 25 });
});
it('should read the defaults when nothing is stored', () => {
const { result } = renderHook(() => usePreferences());
expect(result.current).toEqual(DEFAULT_PREFERENCES);
});
it('should merge a partial stored record over the defaults', () => {
// A record written by an older build must not leave newer keys undefined.
localStorage.setItem(STORAGE_KEY, JSON.stringify({ compactDensity: true }));
const { result } = renderHook(() => usePreferences());
expect(result.current).toEqual({ compactDensity: true, defaultPageSize: 25 });
});
it('should fall back to the defaults for a corrupt stored record', () => {
localStorage.setItem(STORAGE_KEY, '{not json');
const { result } = renderHook(() => usePreferences());
expect(result.current).toEqual(DEFAULT_PREFERENCES);
});
it('should not hand out the shared defaults object itself', () => {
// A consumer mutating what it reads would silently rewrite the defaults
// for every other consumer.
const { result } = renderHook(() => usePreferences());
expect(result.current).not.toBe(DEFAULT_PREFERENCES);
});
});
describe('writePreferences', () => {
it('should persist a patch merged over what is already stored', () => {
writePreferences({ compactDensity: true });
writePreferences({ defaultPageSize: 100 });
expect(JSON.parse(localStorage.getItem(STORAGE_KEY))).toEqual({
compactDensity: true,
defaultPageSize: 100
});
});
it('should return the merged result to the caller', () => {
expect(writePreferences({ defaultPageSize: 50 })).toEqual({
compactDensity: false,
defaultPageSize: 50
});
});
it('should broadcast the change so the triggering component re-renders too', () => {
const listener = jest.fn();
window.addEventListener('doormile:preferences', listener);
writePreferences({ compactDensity: true });
window.removeEventListener('doormile:preferences', listener);
expect(listener).toHaveBeenCalledTimes(1);
expect(listener.mock.calls[0][0].detail).toEqual({ compactDensity: true, defaultPageSize: 25 });
});
it('should still broadcast when the write itself is refused', () => {
// Private mode / quota: the change applies for this session either way,
// so the UI must not be left showing the old value.
const setItem = jest.spyOn(Storage.prototype, 'setItem').mockImplementation(() => {
throw new Error('QuotaExceededError');
});
const listener = jest.fn();
window.addEventListener('doormile:preferences', listener);
expect(() => writePreferences({ compactDensity: true })).not.toThrow();
expect(listener).toHaveBeenCalledTimes(1);
window.removeEventListener('doormile:preferences', listener);
setItem.mockRestore();
});
it('should accept an empty patch as a no-op that still returns the current values', () => {
expect(writePreferences({})).toEqual(DEFAULT_PREFERENCES);
});
});
describe('usePreferences', () => {
it('should re-render with the new value when a preference changes', () => {
const { result } = renderHook(() => usePreferences());
expect(result.current.compactDensity).toBe(false);
act(() => {
writePreferences({ compactDensity: true });
});
expect(result.current.compactDensity).toBe(true);
});
it('should pick up a change made in another tab', () => {
const { result } = renderHook(() => usePreferences());
act(() => {
localStorage.setItem(STORAGE_KEY, JSON.stringify({ defaultPageSize: 100 }));
window.dispatchEvent(new Event('storage'));
});
expect(result.current.defaultPageSize).toBe(100);
});
it('should keep every mounted consumer in agreement', () => {
const a = renderHook(() => usePreferences());
const b = renderHook(() => usePreferences());
act(() => {
writePreferences({ defaultPageSize: 50 });
});
expect(a.result.current).toEqual(b.result.current);
expect(a.result.current.defaultPageSize).toBe(50);
});
it('should stop listening once unmounted', () => {
const removeSpy = jest.spyOn(window, 'removeEventListener');
const { unmount } = renderHook(() => usePreferences());
unmount();
expect(removeSpy).toHaveBeenCalledWith('doormile:preferences', expect.any(Function));
expect(removeSpy).toHaveBeenCalledWith('storage', expect.any(Function));
});
});
describe('useUpdatePreferences', () => {
it('should write and broadcast through the same path as writePreferences', () => {
const update = renderHook(() => useUpdatePreferences());
const read = renderHook(() => usePreferences());
act(() => {
update.result.current({ compactDensity: true });
});
expect(read.result.current.compactDensity).toBe(true);
expect(JSON.parse(localStorage.getItem(STORAGE_KEY)).compactDensity).toBe(true);
});
it('should keep a stable identity across renders so consumers do not re-run effects', () => {
const { result, rerender } = renderHook(() => useUpdatePreferences());
const first = result.current;
rerender();
expect(result.current).toBe(first);
});
});
});