FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
fersLogStore.test.ts
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2// Copyright (c) 2026-present FERS Contributors (see AUTHORS.md).
3
4import {
5 afterAll,
6 beforeAll,
7 beforeEach,
8 describe,
9 expect,
10 test,
11} from 'bun:test';
12import {
13 clampLogDrawerWidth,
14 clampLogMaxLines,
15 DEFAULT_LOG_DRAWER_WIDTH,
16 DEFAULT_LOG_LEVEL,
17 DEFAULT_LOG_MAX_LINES,
18 getEffectiveLogDrawerWidth,
19 MAX_LOG_DRAWER_WIDTH,
20 MAX_LOG_MAX_LINES,
21 MIN_LOG_DRAWER_WIDTH,
22 MIN_LOG_MAX_LINES,
23 readStoredLogDrawerWidth,
24 useFersLogStore,
25} from './fersLogStore';
26
27type StorageMock = {
28 clear: () => void;
29 getItem: (key: string) => string | null;
30 removeItem: (key: string) => void;
31 setItem: (key: string, value: string) => void;
32};
33
34function createStorageMock(): StorageMock {
35 const values = new Map<string, string>();
36
37 return {
38 clear: () => values.clear(),
39 getItem: (key) => values.get(key) ?? null,
40 removeItem: (key) => values.delete(key),
41 setItem: (key, value) => {
42 values.set(key, value);
43 },
44 };
45}
46
47const localStorageMock = createStorageMock();
48const originalLocalStorage = globalThis.localStorage;
49
50describe('fers log store', () => {
51 beforeAll(() => {
52 Object.defineProperty(globalThis, 'localStorage', {
53 value: localStorageMock,
54 configurable: true,
55 });
56 });
57
58 afterAll(() => {
59 Object.defineProperty(globalThis, 'localStorage', {
60 value: originalLocalStorage,
61 configurable: true,
62 });
63 });
64
65 beforeEach(() => {
66 globalThis.localStorage.clear();
67 useFersLogStore.setState({
68 entries: [],
69 droppedCount: 0,
70 maxLines: DEFAULT_LOG_MAX_LINES,
71 logLevel: DEFAULT_LOG_LEVEL,
72 isOpen: false,
73 drawerWidth: DEFAULT_LOG_DRAWER_WIDTH,
74 });
75 });
76
77 test('appends log entries in order', () => {
78 useFersLogStore
79 .getState()
80 .appendLog({ sequence: 1, level: 'INFO', line: 'first' });
81 useFersLogStore
82 .getState()
83 .appendLog({ sequence: 2, level: 'ERROR', line: 'second' });
84
85 expect(useFersLogStore.getState().entries).toEqual([
86 { sequence: 1, level: 'INFO', line: 'first' },
87 { sequence: 2, level: 'ERROR', line: 'second' },
88 ]);
89 });
90
91 test('trims to bounded buffer and records dropped lines', () => {
92 useFersLogStore.setState({ maxLines: 2 });
93
94 for (let sequence = 1; sequence <= 4; sequence += 1) {
95 useFersLogStore.getState().appendLog({
96 sequence,
97 level: 'INFO',
98 line: `line ${sequence}`,
99 });
100 }
101
102 const state = useFersLogStore.getState();
103 expect(state.entries.map((entry) => entry.sequence)).toEqual([3, 4]);
104 expect(state.droppedCount).toBe(2);
105 });
106
107 test('clear resets entries and dropped count', () => {
108 useFersLogStore.setState({
109 entries: [{ sequence: 1, level: 'INFO', line: 'line' }],
110 droppedCount: 4,
111 });
112
113 useFersLogStore.getState().clearLogs();
114
115 expect(useFersLogStore.getState().entries).toEqual([]);
116 expect(useFersLogStore.getState().droppedCount).toBe(0);
117 });
118
119 test('max lines setting clamps and trims immediately', () => {
120 useFersLogStore.setState({
121 entries: [
122 { sequence: 1, level: 'INFO', line: 'one' },
123 { sequence: 2, level: 'INFO', line: 'two' },
124 ],
125 maxLines: 2,
126 });
127
128 useFersLogStore.getState().setMaxLines(1);
129
130 const state = useFersLogStore.getState();
131 expect(state.maxLines).toBe(MIN_LOG_MAX_LINES);
132 expect(state.entries.map((entry) => entry.sequence)).toEqual([1, 2]);
133 });
134
135 test('clampLogMaxLines handles invalid and out-of-range values', () => {
136 expect(clampLogMaxLines(Number.NaN)).toBe(DEFAULT_LOG_MAX_LINES);
137 expect(clampLogMaxLines(1)).toBe(MIN_LOG_MAX_LINES);
138 expect(clampLogMaxLines(MAX_LOG_MAX_LINES + 1)).toBe(MAX_LOG_MAX_LINES);
139 expect(clampLogMaxLines(1234.9)).toBe(1234);
140 });
141
142 test('clampLogDrawerWidth handles invalid and out-of-range values', () => {
143 expect(clampLogDrawerWidth(Number.NaN)).toBe(DEFAULT_LOG_DRAWER_WIDTH);
144 expect(clampLogDrawerWidth(1)).toBe(MIN_LOG_DRAWER_WIDTH);
145 expect(clampLogDrawerWidth(MAX_LOG_DRAWER_WIDTH + 1)).toBe(
146 MAX_LOG_DRAWER_WIDTH
147 );
148 expect(clampLogDrawerWidth(733.6)).toBe(734);
149 });
150
151 test('log level defaults and updates', () => {
152 expect(useFersLogStore.getState().logLevel).toBe(DEFAULT_LOG_LEVEL);
153
154 useFersLogStore.getState().setLogLevel('TRACE');
155 expect(useFersLogStore.getState().logLevel).toBe('TRACE');
156
157 useFersLogStore.getState().setLogLevel('OFF');
158 expect(useFersLogStore.getState().logLevel).toBe('OFF');
159 });
160
161 test('setDrawerWidth updates state and persists width', () => {
162 useFersLogStore.getState().setDrawerWidth(734);
163
164 expect(useFersLogStore.getState().drawerWidth).toBe(734);
165 expect(
166 JSON.parse(
167 globalThis.localStorage.getItem('fers.logViewer.v1') ?? '{}'
168 )
169 ).toEqual({ drawerWidth: 734 });
170 });
171
172 test('readStoredLogDrawerWidth hydrates persisted width', () => {
173 globalThis.localStorage.setItem(
174 'fers.logViewer.v1',
175 JSON.stringify({ drawerWidth: 1440 })
176 );
177
178 expect(readStoredLogDrawerWidth()).toBe(MAX_LOG_DRAWER_WIDTH);
179 });
180
181 test('effective width clamps to viewport without mutating preferred width', () => {
182 useFersLogStore.getState().setDrawerWidth(800);
183
184 const preferredWidth = useFersLogStore.getState().drawerWidth;
185 const effectiveWidth = getEffectiveLogDrawerWidth(preferredWidth, 500);
186
187 expect(preferredWidth).toBe(800);
188 expect(effectiveWidth).toBe(500);
189 });
190});