@jasperoosthoek/react-toolbox 0.8.1 → 0.9.1

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 (63) hide show
  1. package/change-log.md +330 -312
  2. package/dist/components/buttons/ConfirmButton.d.ts +2 -2
  3. package/dist/components/buttons/DeleteConfirmButton.d.ts +2 -2
  4. package/dist/components/buttons/IconButtons.d.ts +40 -41
  5. package/dist/components/errors/Errors.d.ts +1 -2
  6. package/dist/components/forms/FormField.d.ts +22 -0
  7. package/dist/components/forms/FormFields.d.ts +1 -56
  8. package/dist/components/forms/FormModal.d.ts +7 -34
  9. package/dist/components/forms/FormModalProvider.d.ts +25 -15
  10. package/dist/components/forms/FormProvider.d.ts +66 -0
  11. package/dist/components/forms/fields/FormBadgesSelection.d.ts +26 -0
  12. package/dist/components/forms/fields/FormCheckbox.d.ts +7 -0
  13. package/dist/components/forms/fields/FormDropdown.d.ts +19 -0
  14. package/dist/components/forms/fields/FormInput.d.ts +17 -0
  15. package/dist/components/forms/fields/FormSelect.d.ts +12 -0
  16. package/dist/components/forms/fields/index.d.ts +5 -0
  17. package/dist/components/indicators/CheckIndicator.d.ts +1 -2
  18. package/dist/components/indicators/LoadingIndicator.d.ts +4 -4
  19. package/dist/components/login/LoginPage.d.ts +1 -1
  20. package/dist/components/tables/DataTable.d.ts +2 -2
  21. package/dist/components/tables/DragAndDropList.d.ts +2 -2
  22. package/dist/components/tables/SearchBox.d.ts +2 -2
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.js +2 -2
  25. package/dist/index.js.LICENSE.txt +0 -4
  26. package/dist/localization/LocalizationContext.d.ts +1 -1
  27. package/dist/utils/hooks.d.ts +1 -1
  28. package/dist/utils/timeAndDate.d.ts +5 -2
  29. package/dist/utils/utils.d.ts +3 -3
  30. package/package.json +10 -11
  31. package/src/__tests__/buttons.test.tsx +545 -0
  32. package/src/__tests__/errors.test.tsx +339 -0
  33. package/src/__tests__/forms.test.tsx +3021 -0
  34. package/src/__tests__/hooks.test.tsx +413 -0
  35. package/src/__tests__/indicators.test.tsx +284 -0
  36. package/src/__tests__/localization.test.tsx +462 -0
  37. package/src/__tests__/login.test.tsx +417 -0
  38. package/src/__tests__/setupTests.ts +328 -0
  39. package/src/__tests__/tables.test.tsx +609 -0
  40. package/src/__tests__/timeAndDate.test.tsx +308 -0
  41. package/src/__tests__/utils.test.tsx +422 -0
  42. package/src/components/forms/FormField.tsx +92 -0
  43. package/src/components/forms/FormFields.tsx +3 -423
  44. package/src/components/forms/FormModal.tsx +168 -243
  45. package/src/components/forms/FormModalProvider.tsx +164 -85
  46. package/src/components/forms/FormProvider.tsx +218 -0
  47. package/src/components/forms/fields/FormBadgesSelection.tsx +108 -0
  48. package/src/components/forms/fields/FormCheckbox.tsx +76 -0
  49. package/src/components/forms/fields/FormDropdown.tsx +123 -0
  50. package/src/components/forms/fields/FormInput.tsx +114 -0
  51. package/src/components/forms/fields/FormSelect.tsx +47 -0
  52. package/src/components/forms/fields/index.ts +6 -0
  53. package/src/index.ts +32 -29
  54. package/src/localization/LocalizationContext.tsx +156 -131
  55. package/src/localization/localization.ts +131 -131
  56. package/src/utils/hooks.ts +108 -94
  57. package/src/utils/timeAndDate.ts +33 -4
  58. package/src/utils/utils.ts +74 -66
  59. package/dist/components/forms/CreateEditModal.d.ts +0 -41
  60. package/dist/components/forms/CreateEditModalProvider.d.ts +0 -41
  61. package/dist/components/forms/FormFields.test.d.ts +0 -4
  62. package/dist/login/Login.d.ts +0 -70
  63. package/src/components/forms/FormFields.test.tsx +0 -107
@@ -13,7 +13,3 @@
13
13
  * This source code is licensed under the MIT license found in the
14
14
  * LICENSE file in the root directory of this source tree.
15
15
  */
16
-
17
- //! moment.js
18
-
19
- //! moment.js locale configuration
@@ -21,7 +21,7 @@ export interface LocalizationProviderProps extends RestProps {
21
21
  children?: any;
22
22
  [prop: string]: any;
23
23
  }
24
- export declare const LocalizationProvider: ({ lang: initialLanguage, localization: additionalLocalizationInitial, languages: languagesOverride, children, ...restProps }: LocalizationProviderProps) => React.JSX.Element;
24
+ export declare const LocalizationProvider: ({ lang: initialLanguage, localization: additionalLocalizationInitial, languages: languagesOverride, children, ...restProps }: LocalizationProviderProps) => import("react/jsx-runtime").JSX.Element;
25
25
  export declare const useLocalization: () => {
26
26
  lang: string;
27
27
  languages: string[];
@@ -1,6 +1,6 @@
1
1
  export declare const usePrevious: <T>(value: T) => T | undefined;
2
2
  export declare const useDebouncedEffect: (effect: () => void, deps: any[], delay: number) => void;
3
3
  export declare const useForceUpdate: () => () => void;
4
- export declare const useSetState: <T>(initialState: T) => [T, (subState: Partial<T>) => void];
4
+ export declare const useSetState: <T>(initialState: T) => [T, (subState: Partial<T>, callback?: () => void) => void];
5
5
  export declare const useInterval: (func: () => void, value: number) => void;
6
6
  export declare const useLocalStorage: <T>(key: string, initialValue: T) => [T, (value: T) => void];
@@ -1,3 +1,6 @@
1
- import moment from 'moment';
2
1
  export declare const getTimestamp: () => number;
3
- export declare const getToday: () => moment.Moment;
2
+ export declare const getToday: () => Date;
3
+ export declare const formatDate: (date: Date | string, pattern?: string) => string;
4
+ export declare const formatDateTime: (date: Date | string, pattern?: string) => string;
5
+ export declare const toUtc: (date: Date | string, timezone?: string) => Date;
6
+ export declare const fromUtc: (date: Date | string, timezone: string) => Date;
@@ -1,4 +1,3 @@
1
- import { AxiosInstance } from 'axios';
2
1
  export declare const isEmpty: (value: unknown) => boolean;
3
2
  export declare const snakeToCamelCase: (str: string) => string;
4
3
  export declare const camelToSnakeCase: (str: string) => string;
@@ -7,6 +6,7 @@ export declare const arrayToObject: <T extends any[]>(array: T, byKey: string) =
7
6
  export declare const roundFixed: (str: string | number, decimals?: number) => string;
8
7
  export declare const round: (str: string | number, decimals?: number) => number;
9
8
  export type DownloadFileOptions = {
10
- axios: AxiosInstance;
9
+ headers?: Record<string, string>;
10
+ fetchFn?: typeof fetch;
11
11
  };
12
- export declare const downloadFile: (url: string, filename: string, options: DownloadFileOptions) => Promise<void>;
12
+ export declare const downloadFile: (url: string, filename: string, options?: DownloadFileOptions) => Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jasperoosthoek/react-toolbox",
3
- "version": "0.8.1",
3
+ "version": "0.9.1",
4
4
  "author": "jasperoosthoek",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -26,6 +26,7 @@
26
26
  "@types/jest": "^29.5.2",
27
27
  "@types/lodash": "^4.14.191",
28
28
  "@types/node": "^18.11.19",
29
+ "babel-jest": "^29.7.0",
29
30
  "babel-loader": "^8.2.5",
30
31
  "bootstrap": "^5.1.3",
31
32
  "css-loader": "^6.7.1",
@@ -43,21 +44,23 @@
43
44
  "main": "dist/index.js",
44
45
  "scripts": {
45
46
  "build": "webpack",
46
- "prepublishOnly": "npm run build",
47
- "test": "jest"
47
+ "test": "jest",
48
+ "test:watch": "jest --watch",
49
+ "test:coverage": "jest --coverage",
50
+ "prepublishOnly": "npm run test && npm run build"
48
51
  },
49
52
  "bugs": {
50
53
  "url": "https://github.com/jasperoosthoek/react-toolbox/issues"
51
54
  },
52
55
  "homepage": "https://github.com/jasperoosthoek/react-toolbox#readme",
53
56
  "peerDependencies": {
54
- "axios": "^1.4.0",
55
57
  "bootstrap": "^5.1.3",
56
- "moment": "^2.29.4",
57
- "react": "^19.1.0",
58
+ "date-fns": "^4.1.0",
59
+ "date-fns-tz": "^3.2.0",
60
+ "react": "^19.0.0",
58
61
  "react-bootstrap": "^2.10.9",
59
62
  "react-dnd": "^16.0.1",
60
- "react-dom": "^19.1.0",
63
+ "react-dom": "^19.0.0",
61
64
  "react-icons": "^5.4.0",
62
65
  "react-localization": "^2.0.5"
63
66
  },
@@ -66,10 +69,6 @@
66
69
  "ui",
67
70
  "components"
68
71
  ],
69
- "jest": {
70
- "preset": "ts-jest",
71
- "testEnvironment": "node"
72
- },
73
72
  "browser": {
74
73
  "fs": false,
75
74
  "os": false,
@@ -0,0 +1,545 @@
1
+ import React from 'react';
2
+ import { render, fireEvent, waitFor } from '@testing-library/react';
3
+ import { Button } from 'react-bootstrap';
4
+ import { LocalizationProvider } from '../localization/LocalizationContext';
5
+ import ConfirmButton from '../components/buttons/ConfirmButton';
6
+ import DeleteConfirmButton from '../components/buttons/DeleteConfirmButton';
7
+ import {
8
+ IconButton,
9
+ CheckButton,
10
+ CopyButton,
11
+ CloseButton,
12
+ CreateButton,
13
+ DeleteButton,
14
+ EditButton,
15
+ SaveButton,
16
+ UploadTextButton,
17
+ makeIconButton,
18
+ } from '../components/buttons/IconButtons';
19
+ import { AiOutlineHome } from 'react-icons/ai';
20
+
21
+ // Mock the SmallSpinner component
22
+ jest.mock('../components/indicators/LoadingIndicator', () => ({
23
+ SmallSpinner: () => <div data-testid="spinner">Loading...</div>,
24
+ }));
25
+
26
+ // Test wrapper with localization context
27
+ const TestWrapper = ({ children }: { children: React.ReactNode }) => (
28
+ <LocalizationProvider>
29
+ {children}
30
+ </LocalizationProvider>
31
+ );
32
+
33
+ describe('Button Components Tests', () => {
34
+ describe('ConfirmButton', () => {
35
+ const defaultProps = {
36
+ onConfirm: jest.fn(),
37
+ buttonComponent: Button,
38
+ children: 'Click me',
39
+ };
40
+
41
+ const renderWithLocalization = (ui: React.ReactElement) => {
42
+ return render(
43
+ <TestWrapper>
44
+ {ui}
45
+ </TestWrapper>
46
+ );
47
+ };
48
+
49
+ beforeEach(() => {
50
+ jest.clearAllMocks();
51
+ });
52
+
53
+ describe('Basic Functionality', () => {
54
+ it('should render the button component', () => {
55
+ const { getByRole } = renderWithLocalization(
56
+ <ConfirmButton {...defaultProps} />
57
+ );
58
+
59
+ expect(getByRole('button')).toBeInTheDocument();
60
+ expect(getByRole('button')).toHaveTextContent('Click me');
61
+ });
62
+
63
+ it('should not show modal initially', () => {
64
+ const { queryByRole } = renderWithLocalization(
65
+ <ConfirmButton {...defaultProps} />
66
+ );
67
+
68
+ expect(queryByRole('dialog')).not.toBeInTheDocument();
69
+ });
70
+
71
+ it('should show modal when button is clicked', () => {
72
+ const { getByRole } = renderWithLocalization(
73
+ <ConfirmButton {...defaultProps} />
74
+ );
75
+
76
+ fireEvent.click(getByRole('button'));
77
+
78
+ expect(getByRole('dialog')).toBeInTheDocument();
79
+ });
80
+
81
+ it('should close modal when cancel button is clicked', () => {
82
+ const { getByRole, getByText, queryByRole } = renderWithLocalization(
83
+ <ConfirmButton {...defaultProps} />
84
+ );
85
+
86
+ fireEvent.click(getByRole('button'));
87
+ expect(getByRole('dialog')).toBeInTheDocument();
88
+
89
+ fireEvent.click(getByText('Cancel'));
90
+ expect(queryByRole('dialog')).not.toBeInTheDocument();
91
+ });
92
+
93
+ it('should close modal when close button is clicked', () => {
94
+ const { getByRole, queryByRole } = renderWithLocalization(
95
+ <ConfirmButton {...defaultProps} />
96
+ );
97
+
98
+ fireEvent.click(getByRole('button'));
99
+ expect(getByRole('dialog')).toBeInTheDocument();
100
+
101
+ const closeButton = getByRole('button', { name: /close/i });
102
+ fireEvent.click(closeButton);
103
+ expect(queryByRole('dialog')).not.toBeInTheDocument();
104
+ });
105
+ });
106
+
107
+ describe('Confirm Action', () => {
108
+ it('should call onConfirm when confirm button is clicked', async () => {
109
+ const onConfirm = jest.fn();
110
+ const { getByRole, getByText } = renderWithLocalization(
111
+ <ConfirmButton {...defaultProps} onConfirm={onConfirm} />
112
+ );
113
+
114
+ fireEvent.click(getByRole('button'));
115
+ fireEvent.click(getByText('OK'));
116
+
117
+ await waitFor(() => {
118
+ expect(onConfirm).toHaveBeenCalledWith(expect.any(Function));
119
+ });
120
+ });
121
+
122
+ it('should close modal after onConfirm when closeUsingCallback is false', async () => {
123
+ const onConfirm = jest.fn();
124
+ const { getByRole, getByText, queryByRole } = renderWithLocalization(
125
+ <ConfirmButton
126
+ {...defaultProps}
127
+ onConfirm={onConfirm}
128
+ closeUsingCallback={false}
129
+ />
130
+ );
131
+
132
+ fireEvent.click(getByRole('button'));
133
+ fireEvent.click(getByText('OK'));
134
+
135
+ await waitFor(() => {
136
+ expect(queryByRole('dialog')).not.toBeInTheDocument();
137
+ });
138
+ });
139
+
140
+ it('should not auto-close modal when closeUsingCallback is true', async () => {
141
+ const onConfirm = jest.fn();
142
+ const { getByRole, getByText } = renderWithLocalization(
143
+ <ConfirmButton
144
+ {...defaultProps}
145
+ onConfirm={onConfirm}
146
+ closeUsingCallback={true}
147
+ />
148
+ );
149
+
150
+ fireEvent.click(getByRole('button'));
151
+ fireEvent.click(getByText('OK'));
152
+
153
+ await waitFor(() => {
154
+ expect(onConfirm).toHaveBeenCalled();
155
+ });
156
+
157
+ // Modal should still be open since closeUsingCallback is true
158
+ expect(getByRole('dialog')).toBeInTheDocument();
159
+ });
160
+
161
+ it('should close modal using callback when closeUsingCallback is true', async () => {
162
+ const onConfirm = jest.fn((closeModal) => {
163
+ closeModal();
164
+ });
165
+ const { getByRole, getByText, queryByRole } = renderWithLocalization(
166
+ <ConfirmButton
167
+ {...defaultProps}
168
+ onConfirm={onConfirm}
169
+ closeUsingCallback={true}
170
+ />
171
+ );
172
+
173
+ fireEvent.click(getByRole('button'));
174
+ fireEvent.click(getByText('OK'));
175
+
176
+ await waitFor(() => {
177
+ expect(queryByRole('dialog')).not.toBeInTheDocument();
178
+ });
179
+ });
180
+ });
181
+
182
+ describe('Custom Text Props', () => {
183
+ it('should display custom modal title', () => {
184
+ const { getByRole, getByText } = renderWithLocalization(
185
+ <ConfirmButton
186
+ {...defaultProps}
187
+ modalTitle="Custom Title"
188
+ />
189
+ );
190
+
191
+ fireEvent.click(getByRole('button'));
192
+ expect(getByText('Custom Title')).toBeInTheDocument();
193
+ });
194
+
195
+ it('should display custom modal body', () => {
196
+ const { getByRole, getByText } = renderWithLocalization(
197
+ <ConfirmButton
198
+ {...defaultProps}
199
+ modalBody="Custom body message"
200
+ />
201
+ );
202
+
203
+ fireEvent.click(getByRole('button'));
204
+ expect(getByText('Custom body message')).toBeInTheDocument();
205
+ });
206
+
207
+ it('should display default "Are you sure?" when no modalBody provided', () => {
208
+ const { getByRole, getByText } = renderWithLocalization(
209
+ <ConfirmButton {...defaultProps} />
210
+ );
211
+
212
+ fireEvent.click(getByRole('button'));
213
+ expect(getByText('Are you sure?')).toBeInTheDocument();
214
+ });
215
+
216
+ it('should display custom confirm text', () => {
217
+ const { getByRole, getByText } = renderWithLocalization(
218
+ <ConfirmButton
219
+ {...defaultProps}
220
+ confirmText="Yes, Delete"
221
+ />
222
+ );
223
+
224
+ fireEvent.click(getByRole('button'));
225
+ expect(getByText('Yes, Delete')).toBeInTheDocument();
226
+ });
227
+
228
+ it('should display custom cancel text', () => {
229
+ const { getByRole, getByText } = renderWithLocalization(
230
+ <ConfirmButton
231
+ {...defaultProps}
232
+ cancelText="No, Keep"
233
+ />
234
+ );
235
+
236
+ fireEvent.click(getByRole('button'));
237
+ expect(getByText('No, Keep')).toBeInTheDocument();
238
+ });
239
+ });
240
+
241
+ describe('Loading State', () => {
242
+ it('should show spinner when loading is true', () => {
243
+ const { getByRole, getByTestId } = renderWithLocalization(
244
+ <ConfirmButton
245
+ {...defaultProps}
246
+ loading={true}
247
+ />
248
+ );
249
+
250
+ fireEvent.click(getByRole('button'));
251
+ expect(getByTestId('spinner')).toBeInTheDocument();
252
+ });
253
+
254
+ it('should not show spinner when loading is false', () => {
255
+ const { getByRole, queryByTestId } = renderWithLocalization(
256
+ <ConfirmButton
257
+ {...defaultProps}
258
+ loading={false}
259
+ />
260
+ );
261
+
262
+ fireEvent.click(getByRole('button'));
263
+ expect(queryByTestId('spinner')).not.toBeInTheDocument();
264
+ });
265
+ });
266
+
267
+ describe('ReactElement Props', () => {
268
+ it('should render ReactElement as modal title', () => {
269
+ const CustomTitle = <span data-testid="custom-title">Custom React Title</span>;
270
+ const { getByRole, getByTestId } = renderWithLocalization(
271
+ <ConfirmButton
272
+ {...defaultProps}
273
+ modalTitle={CustomTitle}
274
+ />
275
+ );
276
+
277
+ fireEvent.click(getByRole('button'));
278
+ expect(getByTestId('custom-title')).toBeInTheDocument();
279
+ });
280
+
281
+ it('should render ReactElement as modal body', () => {
282
+ const CustomBody = <div data-testid="custom-body">Custom React Body</div>;
283
+ const { getByRole, getByTestId } = renderWithLocalization(
284
+ <ConfirmButton
285
+ {...defaultProps}
286
+ modalBody={CustomBody}
287
+ />
288
+ );
289
+
290
+ fireEvent.click(getByRole('button'));
291
+ expect(getByTestId('custom-body')).toBeInTheDocument();
292
+ });
293
+ });
294
+
295
+ describe('Event Handling', () => {
296
+ it('should stop propagation on modal click', () => {
297
+ const { getByRole } = renderWithLocalization(
298
+ <ConfirmButton {...defaultProps} />
299
+ );
300
+
301
+ fireEvent.click(getByRole('button'));
302
+ const modal = getByRole('dialog');
303
+
304
+ const clickEvent = new MouseEvent('click', { bubbles: true });
305
+ const stopPropagationSpy = jest.spyOn(clickEvent, 'stopPropagation');
306
+
307
+ fireEvent(modal, clickEvent);
308
+ expect(stopPropagationSpy).toHaveBeenCalled();
309
+ });
310
+
311
+ it('should handle async onConfirm function', async () => {
312
+ const onConfirm = jest.fn(async () => {
313
+ await new Promise(resolve => setTimeout(resolve, 10));
314
+ });
315
+
316
+ const { getByRole, getByText } = renderWithLocalization(
317
+ <ConfirmButton {...defaultProps} onConfirm={onConfirm} />
318
+ );
319
+
320
+ fireEvent.click(getByRole('button'));
321
+ fireEvent.click(getByText('OK'));
322
+
323
+ await waitFor(() => {
324
+ expect(onConfirm).toHaveBeenCalled();
325
+ });
326
+ });
327
+ });
328
+
329
+ describe('Button Props Passthrough', () => {
330
+ it('should pass additional props to button component', () => {
331
+ const { getByRole } = renderWithLocalization(
332
+ <ConfirmButton
333
+ {...defaultProps}
334
+ variant="danger"
335
+ size="lg"
336
+ disabled={true}
337
+ />
338
+ );
339
+
340
+ const button = getByRole('button');
341
+ expect(button).toHaveClass('btn-danger');
342
+ expect(button).toHaveClass('btn-lg');
343
+ expect(button).toBeDisabled();
344
+ });
345
+ });
346
+ });
347
+
348
+ describe('DeleteConfirmButton', () => {
349
+ const mockOnDelete = jest.fn();
350
+
351
+ afterEach(() => {
352
+ jest.clearAllMocks();
353
+ });
354
+
355
+ it('should render DeleteConfirmButton without crashing', () => {
356
+ expect(() => {
357
+ render(
358
+ <TestWrapper>
359
+ <DeleteConfirmButton
360
+ onDelete={mockOnDelete}
361
+ >
362
+ Delete
363
+ </DeleteConfirmButton>
364
+ </TestWrapper>
365
+ );
366
+ }).not.toThrow();
367
+ });
368
+
369
+ it('should accept custom modal props', () => {
370
+ expect(() => {
371
+ render(
372
+ <TestWrapper>
373
+ <DeleteConfirmButton
374
+ onDelete={mockOnDelete}
375
+ modalTitle="Delete Item"
376
+ modalBody="Are you sure you want to delete this?"
377
+ confirmText="Delete"
378
+ loading={true}
379
+ >
380
+ Delete Item
381
+ </DeleteConfirmButton>
382
+ </TestWrapper>
383
+ );
384
+ }).not.toThrow();
385
+ });
386
+
387
+ it('should be a valid React component', () => {
388
+ expect(typeof DeleteConfirmButton).toBe('function');
389
+ });
390
+ });
391
+
392
+ describe('IconButton', () => {
393
+ it('should render IconButton without crashing', () => {
394
+ expect(() => {
395
+ render(
396
+ <IconButton icon={AiOutlineHome}>
397
+ Home
398
+ </IconButton>
399
+ );
400
+ }).not.toThrow();
401
+ });
402
+
403
+ it('should handle loading state', () => {
404
+ expect(() => {
405
+ render(
406
+ <IconButton
407
+ icon={AiOutlineHome}
408
+ loading={true}
409
+ iconSize="16px"
410
+ >
411
+ Loading
412
+ </IconButton>
413
+ );
414
+ }).not.toThrow();
415
+ });
416
+
417
+ it('should handle click events', () => {
418
+ const mockClick = jest.fn();
419
+ const { getByRole } = render(
420
+ <IconButton
421
+ icon={AiOutlineHome}
422
+ onClick={mockClick}
423
+ >
424
+ Click Me
425
+ </IconButton>
426
+ );
427
+
428
+ const button = getByRole('button');
429
+ fireEvent.click(button);
430
+
431
+ expect(mockClick).toHaveBeenCalled();
432
+ });
433
+
434
+ it('should be a valid React component', () => {
435
+ expect(typeof IconButton).toBe('function');
436
+ });
437
+ });
438
+
439
+ describe('Predefined Icon Buttons', () => {
440
+ const iconButtons = [
441
+ { component: CheckButton, name: 'CheckButton' },
442
+ { component: CopyButton, name: 'CopyButton' },
443
+ { component: CloseButton, name: 'CloseButton' },
444
+ { component: CreateButton, name: 'CreateButton' },
445
+ { component: DeleteButton, name: 'DeleteButton' },
446
+ { component: EditButton, name: 'EditButton' },
447
+ { component: SaveButton, name: 'SaveButton' },
448
+ ];
449
+
450
+ iconButtons.forEach(({ component: ButtonComponent, name }) => {
451
+ it(`should render ${name} without crashing`, () => {
452
+ expect(() => {
453
+ render(<ButtonComponent>Test</ButtonComponent>);
454
+ }).not.toThrow();
455
+ });
456
+
457
+ it(`${name} should be a valid React component`, () => {
458
+ expect(typeof ButtonComponent).toBe('function');
459
+ });
460
+
461
+ it(`${name} should handle props correctly`, () => {
462
+ expect(() => {
463
+ render(
464
+ <ButtonComponent
465
+ loading={true}
466
+ variant="primary"
467
+ size="lg"
468
+ disabled={true}
469
+ >
470
+ Test Button
471
+ </ButtonComponent>
472
+ );
473
+ }).not.toThrow();
474
+ });
475
+ });
476
+ });
477
+
478
+ describe('UploadTextButton', () => {
479
+ const mockOnLoadFile = jest.fn();
480
+
481
+ afterEach(() => {
482
+ jest.clearAllMocks();
483
+ });
484
+
485
+ it('should render UploadTextButton without crashing', () => {
486
+ expect(() => {
487
+ render(
488
+ <UploadTextButton onLoadFile={mockOnLoadFile}>
489
+ Upload File
490
+ </UploadTextButton>
491
+ );
492
+ }).not.toThrow();
493
+ });
494
+
495
+ it('should accept file type restrictions', () => {
496
+ expect(() => {
497
+ render(
498
+ <UploadTextButton
499
+ onLoadFile={mockOnLoadFile}
500
+ accept=".txt,.json"
501
+ >
502
+ Upload Text File
503
+ </UploadTextButton>
504
+ );
505
+ }).not.toThrow();
506
+ });
507
+
508
+ it('should be a valid React component', () => {
509
+ expect(typeof UploadTextButton).toBe('function');
510
+ });
511
+ });
512
+
513
+ describe('makeIconButton factory', () => {
514
+ it('should create valid icon button components', () => {
515
+ const CustomIconButton = makeIconButton(AiOutlineHome);
516
+
517
+ expect(typeof CustomIconButton).toBe('function');
518
+
519
+ expect(() => {
520
+ render(<CustomIconButton>Custom Button</CustomIconButton>);
521
+ }).not.toThrow();
522
+ });
523
+
524
+ it('should be a valid function', () => {
525
+ expect(typeof makeIconButton).toBe('function');
526
+ });
527
+ });
528
+
529
+ describe('Button Props Handling', () => {
530
+ it('should handle various ButtonProps correctly', () => {
531
+ const commonProps = {
532
+ variant: 'primary' as const,
533
+ size: 'lg' as const,
534
+ disabled: true,
535
+ className: 'test-class',
536
+ loading: true,
537
+ iconSize: '20px',
538
+ };
539
+
540
+ expect(() => {
541
+ render(<CheckButton {...commonProps}>Check</CheckButton>);
542
+ }).not.toThrow();
543
+ });
544
+ });
545
+ });