@jasperoosthoek/react-toolbox 0.8.1 → 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 -312
- 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 +3 -0
- 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 -29
- 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
|
@@ -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) =>
|
|
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[];
|
package/dist/utils/hooks.d.ts
CHANGED
|
@@ -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
|
|
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: () =>
|
|
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;
|
package/dist/utils/utils.d.ts
CHANGED
|
@@ -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
|
-
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
fetchFn?: typeof fetch;
|
|
11
11
|
};
|
|
12
|
-
export declare const downloadFile: (url: string, filename: string, options
|
|
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.
|
|
3
|
+
"version": "0.9.0",
|
|
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
|
-
"
|
|
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
|
-
"
|
|
57
|
-
"
|
|
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.
|
|
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
|
+
});
|