@jasperoosthoek/react-toolbox 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/change-log.md +330 -309
- package/dist/components/buttons/ConfirmButton.d.ts +2 -2
- package/dist/components/buttons/DeleteConfirmButton.d.ts +2 -2
- package/dist/components/buttons/IconButtons.d.ts +40 -41
- package/dist/components/errors/Errors.d.ts +1 -2
- package/dist/components/forms/FormField.d.ts +22 -0
- package/dist/components/forms/FormFields.d.ts +1 -56
- package/dist/components/forms/FormModal.d.ts +7 -34
- package/dist/components/forms/FormModalProvider.d.ts +19 -26
- package/dist/components/forms/FormProvider.d.ts +66 -0
- package/dist/components/forms/fields/FormBadgesSelection.d.ts +26 -0
- package/dist/components/forms/fields/FormCheckbox.d.ts +7 -0
- package/dist/components/forms/fields/FormDropdown.d.ts +19 -0
- package/dist/components/forms/fields/FormInput.d.ts +17 -0
- package/dist/components/forms/fields/FormSelect.d.ts +12 -0
- package/dist/components/forms/fields/index.d.ts +5 -0
- package/dist/components/indicators/CheckIndicator.d.ts +1 -2
- package/dist/components/indicators/LoadingIndicator.d.ts +4 -4
- package/dist/components/login/LoginPage.d.ts +1 -1
- package/dist/components/tables/DataTable.d.ts +2 -2
- package/dist/components/tables/DragAndDropList.d.ts +2 -2
- package/dist/components/tables/SearchBox.d.ts +2 -2
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -2
- package/dist/index.js.LICENSE.txt +0 -4
- package/dist/localization/LocalizationContext.d.ts +1 -1
- package/dist/utils/hooks.d.ts +1 -1
- package/dist/utils/timeAndDate.d.ts +5 -2
- package/dist/utils/utils.d.ts +3 -3
- package/package.json +10 -11
- package/src/__tests__/buttons.test.tsx +545 -0
- package/src/__tests__/errors.test.tsx +339 -0
- package/src/__tests__/forms.test.tsx +3021 -0
- package/src/__tests__/hooks.test.tsx +413 -0
- package/src/__tests__/indicators.test.tsx +284 -0
- package/src/__tests__/localization.test.tsx +462 -0
- package/src/__tests__/login.test.tsx +417 -0
- package/src/__tests__/setupTests.ts +328 -0
- package/src/__tests__/tables.test.tsx +609 -0
- package/src/__tests__/timeAndDate.test.tsx +308 -0
- package/src/__tests__/utils.test.tsx +422 -0
- package/src/components/forms/FormField.tsx +92 -0
- package/src/components/forms/FormFields.tsx +3 -423
- package/src/components/forms/FormModal.tsx +168 -243
- package/src/components/forms/FormModalProvider.tsx +141 -95
- package/src/components/forms/FormProvider.tsx +218 -0
- package/src/components/forms/fields/FormBadgesSelection.tsx +108 -0
- package/src/components/forms/fields/FormCheckbox.tsx +76 -0
- package/src/components/forms/fields/FormDropdown.tsx +123 -0
- package/src/components/forms/fields/FormInput.tsx +114 -0
- package/src/components/forms/fields/FormSelect.tsx +47 -0
- package/src/components/forms/fields/index.ts +6 -0
- package/src/index.ts +32 -28
- package/src/localization/LocalizationContext.tsx +156 -131
- package/src/localization/localization.ts +131 -131
- package/src/utils/hooks.ts +108 -94
- package/src/utils/timeAndDate.ts +33 -4
- package/src/utils/utils.ts +74 -66
- package/dist/components/forms/CreateEditModal.d.ts +0 -41
- package/dist/components/forms/CreateEditModalProvider.d.ts +0 -41
- package/dist/components/forms/FormFields.test.d.ts +0 -4
- package/dist/login/Login.d.ts +0 -70
- package/src/components/forms/FormFields.test.tsx +0 -107
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { renderHook, act } from '@testing-library/react';
|
|
2
|
+
import {
|
|
3
|
+
usePrevious,
|
|
4
|
+
useDebouncedEffect,
|
|
5
|
+
useForceUpdate,
|
|
6
|
+
useSetState,
|
|
7
|
+
useInterval,
|
|
8
|
+
useLocalStorage,
|
|
9
|
+
} from '../utils/hooks';
|
|
10
|
+
|
|
11
|
+
// Mock localStorage for testing
|
|
12
|
+
const mockStorage = {
|
|
13
|
+
store: {} as Record<string, string>,
|
|
14
|
+
getItem: jest.fn((key: string) => mockStorage.store[key] || null),
|
|
15
|
+
setItem: jest.fn((key: string, value: string) => {
|
|
16
|
+
mockStorage.store[key] = value;
|
|
17
|
+
}),
|
|
18
|
+
removeItem: jest.fn((key: string) => {
|
|
19
|
+
delete mockStorage.store[key];
|
|
20
|
+
}),
|
|
21
|
+
clear: jest.fn(() => {
|
|
22
|
+
mockStorage.store = {};
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Override the global localStorage
|
|
27
|
+
Object.defineProperty(window, 'localStorage', {
|
|
28
|
+
value: mockStorage,
|
|
29
|
+
writable: true
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Also set global.localStorage for Node environments
|
|
33
|
+
global.localStorage = mockStorage as any;
|
|
34
|
+
|
|
35
|
+
const localStorageMock = mockStorage;
|
|
36
|
+
|
|
37
|
+
describe('Utils - Hooks Tests', () => {
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
jest.clearAllTimers();
|
|
40
|
+
jest.useFakeTimers();
|
|
41
|
+
// Reset localStorage mock
|
|
42
|
+
localStorageMock.store = {};
|
|
43
|
+
localStorageMock.getItem.mockClear();
|
|
44
|
+
localStorageMock.setItem.mockClear();
|
|
45
|
+
localStorageMock.removeItem.mockClear();
|
|
46
|
+
localStorageMock.clear.mockClear();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
jest.runOnlyPendingTimers();
|
|
51
|
+
jest.useRealTimers();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('usePrevious', () => {
|
|
55
|
+
it('should return undefined on first render', () => {
|
|
56
|
+
const { result } = renderHook(({ value }) => usePrevious(value), {
|
|
57
|
+
initialProps: { value: 'initial' }
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(result.current).toBeUndefined();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should return previous value on subsequent renders', () => {
|
|
64
|
+
const { result, rerender } = renderHook(({ value }) => usePrevious(value), {
|
|
65
|
+
initialProps: { value: 'initial' }
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(result.current).toBeUndefined();
|
|
69
|
+
|
|
70
|
+
rerender({ value: 'updated' });
|
|
71
|
+
expect(result.current).toBe('initial');
|
|
72
|
+
|
|
73
|
+
rerender({ value: 'final' });
|
|
74
|
+
expect(result.current).toBe('updated');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should work with different data types', () => {
|
|
78
|
+
const { result, rerender } = renderHook(({ value }) => usePrevious(value), {
|
|
79
|
+
initialProps: { value: 0 }
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
rerender({ value: 1 });
|
|
83
|
+
expect(result.current).toBe(0);
|
|
84
|
+
|
|
85
|
+
rerender({ value: { key: 'value' } });
|
|
86
|
+
expect(result.current).toBe(1);
|
|
87
|
+
|
|
88
|
+
rerender({ value: [1, 2, 3] });
|
|
89
|
+
expect(result.current).toEqual({ key: 'value' });
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('useDebouncedEffect', () => {
|
|
94
|
+
it('should execute effect after delay', () => {
|
|
95
|
+
const effect = jest.fn();
|
|
96
|
+
const delay = 500;
|
|
97
|
+
|
|
98
|
+
renderHook(() => useDebouncedEffect(effect, [], delay));
|
|
99
|
+
|
|
100
|
+
expect(effect).not.toHaveBeenCalled();
|
|
101
|
+
|
|
102
|
+
act(() => {
|
|
103
|
+
jest.advanceTimersByTime(delay - 1);
|
|
104
|
+
});
|
|
105
|
+
expect(effect).not.toHaveBeenCalled();
|
|
106
|
+
|
|
107
|
+
act(() => {
|
|
108
|
+
jest.advanceTimersByTime(1);
|
|
109
|
+
});
|
|
110
|
+
expect(effect).toHaveBeenCalledTimes(1);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should cancel previous timeout when deps change', () => {
|
|
114
|
+
const effect = jest.fn();
|
|
115
|
+
const delay = 500;
|
|
116
|
+
|
|
117
|
+
const { rerender } = renderHook(({ deps }) => useDebouncedEffect(effect, deps, delay), {
|
|
118
|
+
initialProps: { deps: [1] }
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
act(() => {
|
|
122
|
+
jest.advanceTimersByTime(250);
|
|
123
|
+
});
|
|
124
|
+
expect(effect).not.toHaveBeenCalled();
|
|
125
|
+
|
|
126
|
+
rerender({ deps: [2] });
|
|
127
|
+
|
|
128
|
+
act(() => {
|
|
129
|
+
jest.advanceTimersByTime(250);
|
|
130
|
+
});
|
|
131
|
+
expect(effect).not.toHaveBeenCalled();
|
|
132
|
+
|
|
133
|
+
act(() => {
|
|
134
|
+
jest.advanceTimersByTime(250);
|
|
135
|
+
});
|
|
136
|
+
expect(effect).toHaveBeenCalledTimes(1);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should handle empty deps array', () => {
|
|
140
|
+
const effect = jest.fn();
|
|
141
|
+
const delay = 100;
|
|
142
|
+
|
|
143
|
+
renderHook(() => useDebouncedEffect(effect, [], delay));
|
|
144
|
+
|
|
145
|
+
act(() => {
|
|
146
|
+
jest.advanceTimersByTime(delay);
|
|
147
|
+
});
|
|
148
|
+
expect(effect).toHaveBeenCalledTimes(1);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('useForceUpdate', () => {
|
|
153
|
+
it('should force component re-render', () => {
|
|
154
|
+
let renderCount = 0;
|
|
155
|
+
const { result } = renderHook(() => {
|
|
156
|
+
renderCount++;
|
|
157
|
+
return useForceUpdate();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expect(renderCount).toBe(1);
|
|
161
|
+
|
|
162
|
+
act(() => {
|
|
163
|
+
result.current();
|
|
164
|
+
});
|
|
165
|
+
expect(renderCount).toBe(2);
|
|
166
|
+
|
|
167
|
+
act(() => {
|
|
168
|
+
result.current();
|
|
169
|
+
});
|
|
170
|
+
expect(renderCount).toBe(3);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should return a stable function reference', () => {
|
|
174
|
+
const { result, rerender } = renderHook(() => useForceUpdate());
|
|
175
|
+
const firstUpdate = result.current;
|
|
176
|
+
|
|
177
|
+
rerender();
|
|
178
|
+
const secondUpdate = result.current;
|
|
179
|
+
|
|
180
|
+
expect(firstUpdate).toBe(secondUpdate);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('useSetState', () => {
|
|
185
|
+
it('should initialize with initial state', () => {
|
|
186
|
+
const initialState = { count: 0, name: 'test' };
|
|
187
|
+
const { result } = renderHook(() => useSetState(initialState));
|
|
188
|
+
|
|
189
|
+
expect(result.current[0]).toEqual(initialState);
|
|
190
|
+
expect(typeof result.current[1]).toBe('function');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should update state partially', () => {
|
|
194
|
+
const initialState = { count: 0, name: 'test' };
|
|
195
|
+
const { result } = renderHook(() => useSetState(initialState));
|
|
196
|
+
|
|
197
|
+
act(() => {
|
|
198
|
+
result.current[1]({ count: 5 });
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(result.current[0]).toEqual({ count: 5, name: 'test' });
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should merge multiple partial updates', () => {
|
|
205
|
+
const initialState = { a: 1, b: 2, c: 3 };
|
|
206
|
+
const { result } = renderHook(() => useSetState(initialState));
|
|
207
|
+
|
|
208
|
+
act(() => {
|
|
209
|
+
result.current[1]({ a: 10 });
|
|
210
|
+
});
|
|
211
|
+
expect(result.current[0]).toEqual({ a: 10, b: 2, c: 3 });
|
|
212
|
+
|
|
213
|
+
act(() => {
|
|
214
|
+
result.current[1]({ b: 20, c: 30 });
|
|
215
|
+
});
|
|
216
|
+
expect(result.current[0]).toEqual({ a: 10, b: 20, c: 30 });
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should handle callback execution', async () => {
|
|
220
|
+
const initialState = { count: 0 };
|
|
221
|
+
const callback = jest.fn();
|
|
222
|
+
const { result } = renderHook(() => useSetState(initialState));
|
|
223
|
+
|
|
224
|
+
await act(async () => {
|
|
225
|
+
result.current[1]({ count: 1 }, callback);
|
|
226
|
+
// Wait for the promise to resolve
|
|
227
|
+
await Promise.resolve();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expect(callback).toHaveBeenCalled();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('useInterval', () => {
|
|
235
|
+
it('should execute function at specified intervals', () => {
|
|
236
|
+
const func = jest.fn();
|
|
237
|
+
const interval = 1000;
|
|
238
|
+
|
|
239
|
+
renderHook(() => useInterval(func, interval));
|
|
240
|
+
|
|
241
|
+
expect(func).not.toHaveBeenCalled();
|
|
242
|
+
|
|
243
|
+
act(() => {
|
|
244
|
+
jest.advanceTimersByTime(interval);
|
|
245
|
+
});
|
|
246
|
+
expect(func).toHaveBeenCalledTimes(1);
|
|
247
|
+
|
|
248
|
+
act(() => {
|
|
249
|
+
jest.advanceTimersByTime(interval);
|
|
250
|
+
});
|
|
251
|
+
expect(func).toHaveBeenCalledTimes(2);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should throw error for non-function first argument', () => {
|
|
255
|
+
expect(() => {
|
|
256
|
+
renderHook(() => useInterval('not a function' as any, 1000));
|
|
257
|
+
}).toThrow('First argument of useInterval should be a function');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should throw error for invalid interval values', () => {
|
|
261
|
+
const func = jest.fn();
|
|
262
|
+
|
|
263
|
+
expect(() => {
|
|
264
|
+
renderHook(() => useInterval(func, 0));
|
|
265
|
+
}).toThrow('Second argument of useInterval should be a positive number');
|
|
266
|
+
|
|
267
|
+
expect(() => {
|
|
268
|
+
renderHook(() => useInterval(func, -1));
|
|
269
|
+
}).toThrow('Second argument of useInterval should be a positive number');
|
|
270
|
+
|
|
271
|
+
expect(() => {
|
|
272
|
+
renderHook(() => useInterval(func, Infinity));
|
|
273
|
+
}).toThrow('Second argument of useInterval should be a positive number');
|
|
274
|
+
|
|
275
|
+
expect(() => {
|
|
276
|
+
renderHook(() => useInterval(func, 'invalid' as any));
|
|
277
|
+
}).toThrow('Second argument of useInterval should be a positive number');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should clear interval on unmount', () => {
|
|
281
|
+
const func = jest.fn();
|
|
282
|
+
const clearIntervalSpy = jest.spyOn(global, 'clearInterval');
|
|
283
|
+
|
|
284
|
+
const { unmount } = renderHook(() => useInterval(func, 1000));
|
|
285
|
+
|
|
286
|
+
unmount();
|
|
287
|
+
|
|
288
|
+
expect(clearIntervalSpy).toHaveBeenCalled();
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe('useLocalStorage', () => {
|
|
293
|
+
it('should initialize with initial value when localStorage is empty', () => {
|
|
294
|
+
const key = 'testKey1';
|
|
295
|
+
const initialValue = 'initial';
|
|
296
|
+
|
|
297
|
+
const { result } = renderHook(() => useLocalStorage(key, initialValue));
|
|
298
|
+
|
|
299
|
+
expect(result.current[0]).toBe(initialValue);
|
|
300
|
+
expect(localStorageMock.setItem).toHaveBeenCalledWith(key, JSON.stringify(initialValue));
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('should initialize with localStorage value when available', () => {
|
|
304
|
+
const key = 'testKey2';
|
|
305
|
+
const initialValue = 'initial';
|
|
306
|
+
const storedValue = 'stored';
|
|
307
|
+
|
|
308
|
+
// Set up localStorage to return stored value
|
|
309
|
+
localStorageMock.store[key] = JSON.stringify(storedValue);
|
|
310
|
+
|
|
311
|
+
const { result } = renderHook(() => useLocalStorage(key, initialValue));
|
|
312
|
+
|
|
313
|
+
expect(result.current[0]).toBe(storedValue);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('should update localStorage when state changes', () => {
|
|
317
|
+
const key = 'testKey3';
|
|
318
|
+
const initialValue = 'initial';
|
|
319
|
+
|
|
320
|
+
const { result } = renderHook(() => useLocalStorage(key, initialValue));
|
|
321
|
+
|
|
322
|
+
act(() => {
|
|
323
|
+
result.current[1]('updated');
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
expect(result.current[0]).toBe('updated');
|
|
327
|
+
expect(localStorageMock.setItem).toHaveBeenCalledWith(key, JSON.stringify('updated'));
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('should handle complex objects', () => {
|
|
331
|
+
const key = 'testKey4';
|
|
332
|
+
const initialValue = { count: 0, items: [] };
|
|
333
|
+
|
|
334
|
+
const { result } = renderHook(() => useLocalStorage(key, initialValue));
|
|
335
|
+
|
|
336
|
+
const newValue = { count: 5, items: ['a', 'b'] };
|
|
337
|
+
act(() => {
|
|
338
|
+
result.current[1](newValue);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
expect(result.current[0]).toEqual(newValue);
|
|
342
|
+
expect(localStorageMock.setItem).toHaveBeenCalledWith(key, JSON.stringify(newValue));
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should respond to storage events', () => {
|
|
346
|
+
const key = 'testKey5';
|
|
347
|
+
const initialValue = 'initial';
|
|
348
|
+
|
|
349
|
+
const { result } = renderHook(() => useLocalStorage(key, initialValue));
|
|
350
|
+
|
|
351
|
+
const newValue = 'fromEvent';
|
|
352
|
+
const storageEvent = new StorageEvent('storage', {
|
|
353
|
+
key,
|
|
354
|
+
newValue: JSON.stringify(newValue),
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
act(() => {
|
|
358
|
+
window.dispatchEvent(storageEvent);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
expect(result.current[0]).toBe(newValue);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should ignore storage events for different keys', () => {
|
|
365
|
+
const key = 'testKey6';
|
|
366
|
+
const initialValue = 'initial';
|
|
367
|
+
|
|
368
|
+
const { result } = renderHook(() => useLocalStorage(key, initialValue));
|
|
369
|
+
|
|
370
|
+
const storageEvent = new StorageEvent('storage', {
|
|
371
|
+
key: 'differentKey',
|
|
372
|
+
newValue: JSON.stringify('different'),
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
act(() => {
|
|
376
|
+
window.dispatchEvent(storageEvent);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
expect(result.current[0]).toBe(initialValue);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('should dispatch storage events when setting value', () => {
|
|
383
|
+
const key = 'testKey7';
|
|
384
|
+
const initialValue = 'initial';
|
|
385
|
+
const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent');
|
|
386
|
+
|
|
387
|
+
const { result } = renderHook(() => useLocalStorage(key, initialValue));
|
|
388
|
+
|
|
389
|
+
act(() => {
|
|
390
|
+
result.current[1]('updated');
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
expect(dispatchEventSpy).toHaveBeenCalledWith(
|
|
394
|
+
expect.objectContaining({
|
|
395
|
+
type: 'storage',
|
|
396
|
+
key,
|
|
397
|
+
newValue: JSON.stringify('updated'),
|
|
398
|
+
})
|
|
399
|
+
);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
describe('Hook Export Verification', () => {
|
|
404
|
+
it('should export all hooks as functions', () => {
|
|
405
|
+
expect(typeof usePrevious).toBe('function');
|
|
406
|
+
expect(typeof useDebouncedEffect).toBe('function');
|
|
407
|
+
expect(typeof useForceUpdate).toBe('function');
|
|
408
|
+
expect(typeof useSetState).toBe('function');
|
|
409
|
+
expect(typeof useInterval).toBe('function');
|
|
410
|
+
expect(typeof useLocalStorage).toBe('function');
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
});
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import { LocalizationProvider } from '../localization/LocalizationContext';
|
|
4
|
+
import {
|
|
5
|
+
CheckIndicator,
|
|
6
|
+
LoadingIndicator,
|
|
7
|
+
SmallSpinner,
|
|
8
|
+
BigSpinner,
|
|
9
|
+
} from '../components/indicators/LoadingIndicator';
|
|
10
|
+
import { CheckIndicator as CheckIndicatorComponent } from '../components/indicators/CheckIndicator';
|
|
11
|
+
|
|
12
|
+
// Test wrapper with localization context
|
|
13
|
+
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
|
14
|
+
<LocalizationProvider>
|
|
15
|
+
{children}
|
|
16
|
+
</LocalizationProvider>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
describe('Indicator Components Tests', () => {
|
|
20
|
+
describe('CheckIndicator', () => {
|
|
21
|
+
it('should render CheckIndicator without crashing', () => {
|
|
22
|
+
expect(() => {
|
|
23
|
+
render(<CheckIndicatorComponent checked={true} />);
|
|
24
|
+
}).not.toThrow();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should render CheckIndicator in checked state', () => {
|
|
28
|
+
expect(() => {
|
|
29
|
+
render(<CheckIndicatorComponent checked={true} />);
|
|
30
|
+
}).not.toThrow();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should render CheckIndicator in unchecked state', () => {
|
|
34
|
+
expect(() => {
|
|
35
|
+
render(<CheckIndicatorComponent checked={false} />);
|
|
36
|
+
}).not.toThrow();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should accept custom className', () => {
|
|
40
|
+
expect(() => {
|
|
41
|
+
render(
|
|
42
|
+
<CheckIndicatorComponent
|
|
43
|
+
checked={true}
|
|
44
|
+
className="custom-class"
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}).not.toThrow();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should handle boolean checked prop correctly', () => {
|
|
51
|
+
const { rerender } = render(
|
|
52
|
+
<CheckIndicatorComponent checked={true} />
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(() => {
|
|
56
|
+
rerender(<CheckIndicatorComponent checked={false} />);
|
|
57
|
+
}).not.toThrow();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should be a valid React component', () => {
|
|
61
|
+
expect(typeof CheckIndicatorComponent).toBe('function');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('LoadingIndicator', () => {
|
|
66
|
+
it('should render LoadingIndicator without crashing', () => {
|
|
67
|
+
expect(() => {
|
|
68
|
+
render(
|
|
69
|
+
<TestWrapper>
|
|
70
|
+
<LoadingIndicator />
|
|
71
|
+
</TestWrapper>
|
|
72
|
+
);
|
|
73
|
+
}).not.toThrow();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should accept custom style props', () => {
|
|
77
|
+
expect(() => {
|
|
78
|
+
render(
|
|
79
|
+
<TestWrapper>
|
|
80
|
+
<LoadingIndicator style={{ color: 'blue', fontSize: '16px' }} />
|
|
81
|
+
</TestWrapper>
|
|
82
|
+
);
|
|
83
|
+
}).not.toThrow();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should render with empty style object', () => {
|
|
87
|
+
expect(() => {
|
|
88
|
+
render(
|
|
89
|
+
<TestWrapper>
|
|
90
|
+
<LoadingIndicator style={{}} />
|
|
91
|
+
</TestWrapper>
|
|
92
|
+
);
|
|
93
|
+
}).not.toThrow();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should be a valid React component', () => {
|
|
97
|
+
expect(typeof LoadingIndicator).toBe('function');
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('SmallSpinner', () => {
|
|
102
|
+
it('should render SmallSpinner without crashing', () => {
|
|
103
|
+
expect(() => {
|
|
104
|
+
render(<SmallSpinner />);
|
|
105
|
+
}).not.toThrow();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should render with custom style', () => {
|
|
109
|
+
expect(() => {
|
|
110
|
+
render(
|
|
111
|
+
<SmallSpinner
|
|
112
|
+
style={{ margin: '10px' }}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}).not.toThrow();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should render with custom className', () => {
|
|
119
|
+
expect(() => {
|
|
120
|
+
render(
|
|
121
|
+
<SmallSpinner
|
|
122
|
+
className="custom-spinner-class"
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
}).not.toThrow();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should render with custom component', () => {
|
|
129
|
+
const CustomComponent = (props: any) => (
|
|
130
|
+
<div {...props}>{props.children}</div>
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
expect(() => {
|
|
134
|
+
render(
|
|
135
|
+
<SmallSpinner
|
|
136
|
+
component={CustomComponent}
|
|
137
|
+
style={{ padding: '5px' }}
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
}).not.toThrow();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should render with string component', () => {
|
|
144
|
+
expect(() => {
|
|
145
|
+
render(
|
|
146
|
+
<SmallSpinner
|
|
147
|
+
component="div"
|
|
148
|
+
className="test-class"
|
|
149
|
+
/>
|
|
150
|
+
);
|
|
151
|
+
}).not.toThrow();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should handle component prop variations', () => {
|
|
155
|
+
expect(() => {
|
|
156
|
+
render(<SmallSpinner component={undefined} />);
|
|
157
|
+
}).not.toThrow();
|
|
158
|
+
|
|
159
|
+
expect(() => {
|
|
160
|
+
render(<SmallSpinner component={null} />);
|
|
161
|
+
}).not.toThrow();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should be a valid React component', () => {
|
|
165
|
+
expect(typeof SmallSpinner).toBe('function');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('BigSpinner', () => {
|
|
170
|
+
it('should render BigSpinner without crashing', () => {
|
|
171
|
+
expect(() => {
|
|
172
|
+
render(<BigSpinner />);
|
|
173
|
+
}).not.toThrow();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should render consistently', () => {
|
|
177
|
+
const { rerender } = render(<BigSpinner />);
|
|
178
|
+
|
|
179
|
+
expect(() => {
|
|
180
|
+
rerender(<BigSpinner />);
|
|
181
|
+
}).not.toThrow();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should be a valid React component', () => {
|
|
185
|
+
expect(typeof BigSpinner).toBe('function');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('Component Props and Variations', () => {
|
|
190
|
+
it('should handle CheckIndicator prop combinations', () => {
|
|
191
|
+
const propCombinations = [
|
|
192
|
+
{ checked: true },
|
|
193
|
+
{ checked: false },
|
|
194
|
+
{ checked: true, className: 'test' },
|
|
195
|
+
{ checked: false, className: 'custom-check' },
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
propCombinations.forEach((props) => {
|
|
199
|
+
expect(() => {
|
|
200
|
+
render(<CheckIndicatorComponent {...props} />);
|
|
201
|
+
}).not.toThrow();
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should handle SmallSpinner prop combinations', () => {
|
|
206
|
+
const propCombinations = [
|
|
207
|
+
{},
|
|
208
|
+
{ style: { color: 'red' } },
|
|
209
|
+
{ className: 'test-spinner' },
|
|
210
|
+
{ style: { margin: '5px' }, className: 'combined' },
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
propCombinations.forEach((props) => {
|
|
214
|
+
expect(() => {
|
|
215
|
+
render(<SmallSpinner {...props} />);
|
|
216
|
+
}).not.toThrow();
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should handle LoadingIndicator with various styles', () => {
|
|
221
|
+
const styleVariations = [
|
|
222
|
+
{},
|
|
223
|
+
{ color: 'blue' },
|
|
224
|
+
{ fontSize: '14px', color: 'green' },
|
|
225
|
+
{ margin: '10px', padding: '5px' },
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
styleVariations.forEach((style) => {
|
|
229
|
+
expect(() => {
|
|
230
|
+
render(
|
|
231
|
+
<TestWrapper>
|
|
232
|
+
<LoadingIndicator style={style} />
|
|
233
|
+
</TestWrapper>
|
|
234
|
+
);
|
|
235
|
+
}).not.toThrow();
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('Component Export Verification', () => {
|
|
241
|
+
it('should export all indicator components as functions', () => {
|
|
242
|
+
expect(typeof CheckIndicatorComponent).toBe('function');
|
|
243
|
+
expect(typeof LoadingIndicator).toBe('function');
|
|
244
|
+
expect(typeof SmallSpinner).toBe('function');
|
|
245
|
+
expect(typeof BigSpinner).toBe('function');
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe('Component Rendering Consistency', () => {
|
|
250
|
+
it('should render multiple indicators together', () => {
|
|
251
|
+
expect(() => {
|
|
252
|
+
render(
|
|
253
|
+
<TestWrapper>
|
|
254
|
+
<div>
|
|
255
|
+
<CheckIndicatorComponent checked={true} />
|
|
256
|
+
<LoadingIndicator />
|
|
257
|
+
<SmallSpinner />
|
|
258
|
+
<BigSpinner />
|
|
259
|
+
</div>
|
|
260
|
+
</TestWrapper>
|
|
261
|
+
);
|
|
262
|
+
}).not.toThrow();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should handle conditional rendering', () => {
|
|
266
|
+
const ConditionalComponent = ({ showSpinner }: { showSpinner: boolean }) => (
|
|
267
|
+
<TestWrapper>
|
|
268
|
+
<div>
|
|
269
|
+
<CheckIndicatorComponent checked={true} />
|
|
270
|
+
{showSpinner && <SmallSpinner />}
|
|
271
|
+
</div>
|
|
272
|
+
</TestWrapper>
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
expect(() => {
|
|
276
|
+
render(<ConditionalComponent showSpinner={true} />);
|
|
277
|
+
}).not.toThrow();
|
|
278
|
+
|
|
279
|
+
expect(() => {
|
|
280
|
+
render(<ConditionalComponent showSpinner={false} />);
|
|
281
|
+
}).not.toThrow();
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
});
|