@jasperoosthoek/react-toolbox 0.9.4 → 0.9.5

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.
Files changed (33) hide show
  1. package/README.md +455 -155
  2. package/change-log.md +8 -1
  3. package/dist/components/forms/FormField.d.ts +1 -0
  4. package/dist/components/forms/FormModal.d.ts +3 -2
  5. package/dist/components/forms/FormProvider.d.ts +3 -0
  6. package/dist/components/forms/fields/FormInput.d.ts +1 -1
  7. package/dist/index.es.js +2188 -0
  8. package/dist/index.es.js.map +1 -0
  9. package/dist/index.umd.js +28 -0
  10. package/dist/index.umd.js.map +1 -0
  11. package/package.json +26 -12
  12. package/src/components/forms/FormField.tsx +3 -0
  13. package/src/components/forms/FormModal.tsx +32 -19
  14. package/src/components/forms/FormProvider.tsx +7 -1
  15. package/src/components/forms/fields/FormBadgesSelection.tsx +3 -2
  16. package/src/components/forms/fields/FormCheckbox.tsx +8 -10
  17. package/src/components/forms/fields/FormDropdown.tsx +4 -4
  18. package/src/components/forms/fields/FormInput.tsx +6 -6
  19. package/src/components/forms/fields/FormSelect.tsx +4 -3
  20. package/src/components/tables/DataTable.tsx +1 -7
  21. package/dist/index.js +0 -3
  22. package/dist/index.js.LICENSE.txt +0 -15
  23. package/src/__tests__/buttons.test.tsx +0 -545
  24. package/src/__tests__/errors.test.tsx +0 -339
  25. package/src/__tests__/forms.test.tsx +0 -3021
  26. package/src/__tests__/hooks.test.tsx +0 -413
  27. package/src/__tests__/indicators.test.tsx +0 -284
  28. package/src/__tests__/localization.test.tsx +0 -462
  29. package/src/__tests__/login.test.tsx +0 -417
  30. package/src/__tests__/setupTests.ts +0 -328
  31. package/src/__tests__/tables.test.tsx +0 -609
  32. package/src/__tests__/timeAndDate.test.tsx +0 -308
  33. package/src/__tests__/utils.test.tsx +0 -422
@@ -1,413 +0,0 @@
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
- });
@@ -1,284 +0,0 @@
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
- });