@pie-lib/config-ui 12.0.0-beta.5 → 12.1.0-next.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/CHANGELOG.json +8 -1653
- package/CHANGELOG.md +345 -4
- package/LICENSE.md +5 -0
- package/NEXT.CHANGELOG.json +1 -0
- package/lib/__tests__/alert-dialog.test.js +262 -0
- package/lib/__tests__/checkbox.test.js +227 -0
- package/lib/__tests__/choice-utils.test.js +14 -0
- package/lib/__tests__/form-section.test.js +252 -0
- package/lib/__tests__/help.test.js +270 -0
- package/lib/__tests__/input.test.js +268 -0
- package/lib/__tests__/langs.test.js +541 -0
- package/lib/__tests__/number-text-field-custom.test.js +362 -0
- package/lib/__tests__/number-text-field.test.js +421 -0
- package/lib/__tests__/radio-with-label.test.js +233 -0
- package/lib/__tests__/settings-panel.test.js +184 -0
- package/lib/__tests__/settings.test.js +653 -0
- package/lib/__tests__/tabs.test.js +211 -0
- package/lib/__tests__/two-choice.test.js +124 -0
- package/lib/__tests__/with-stateful-model.test.js +221 -0
- package/lib/alert-dialog.js +41 -11
- package/lib/alert-dialog.js.map +1 -1
- package/lib/checkbox.js +59 -49
- package/lib/checkbox.js.map +1 -1
- package/lib/choice-configuration/__tests__/feedback-menu.test.js +287 -0
- package/lib/choice-configuration/__tests__/index.test.js +253 -0
- package/lib/choice-configuration/feedback-menu.js +25 -27
- package/lib/choice-configuration/feedback-menu.js.map +1 -1
- package/lib/choice-configuration/index.js +183 -186
- package/lib/choice-configuration/index.js.map +1 -1
- package/lib/choice-utils.js +6 -8
- package/lib/choice-utils.js.map +1 -1
- package/lib/feedback-config/__tests__/feedback-config.test.js +201 -0
- package/lib/feedback-config/__tests__/feedback-selector.test.js +177 -0
- package/lib/feedback-config/feedback-selector.js +70 -74
- package/lib/feedback-config/feedback-selector.js.map +1 -1
- package/lib/feedback-config/group.js +23 -26
- package/lib/feedback-config/group.js.map +1 -1
- package/lib/feedback-config/index.js +42 -45
- package/lib/feedback-config/index.js.map +1 -1
- package/lib/form-section.js +32 -26
- package/lib/form-section.js.map +1 -1
- package/lib/help.js +38 -48
- package/lib/help.js.map +1 -1
- package/lib/index.js +2 -3
- package/lib/index.js.map +1 -1
- package/lib/input.js +13 -18
- package/lib/input.js.map +1 -1
- package/lib/inputs.js +59 -68
- package/lib/inputs.js.map +1 -1
- package/lib/langs.js +57 -71
- package/lib/langs.js.map +1 -1
- package/lib/layout/__tests__/config.layout.test.js +70 -0
- package/lib/layout/__tests__/layout-content.test.js +6 -0
- package/lib/layout/config-layout.js +79 -48
- package/lib/layout/config-layout.js.map +1 -1
- package/lib/layout/index.js +1 -1
- package/lib/layout/index.js.map +1 -1
- package/lib/layout/layout-contents.js +59 -61
- package/lib/layout/layout-contents.js.map +1 -1
- package/lib/layout/settings-box.js +26 -34
- package/lib/layout/settings-box.js.map +1 -1
- package/lib/mui-box/index.js +42 -51
- package/lib/mui-box/index.js.map +1 -1
- package/lib/number-text-field-custom.js +152 -90
- package/lib/number-text-field-custom.js.map +1 -1
- package/lib/number-text-field.js +75 -64
- package/lib/number-text-field.js.map +1 -1
- package/lib/radio-with-label.js +31 -17
- package/lib/radio-with-label.js.map +1 -1
- package/lib/settings/display-size.js +17 -21
- package/lib/settings/display-size.js.map +1 -1
- package/lib/settings/index.js +14 -20
- package/lib/settings/index.js.map +1 -1
- package/lib/settings/panel.js +141 -142
- package/lib/settings/panel.js.map +1 -1
- package/lib/settings/settings-radio-label.js +30 -17
- package/lib/settings/settings-radio-label.js.map +1 -1
- package/lib/settings/toggle.js +40 -26
- package/lib/settings/toggle.js.map +1 -1
- package/lib/tabs/index.js +19 -31
- package/lib/tabs/index.js.map +1 -1
- package/lib/tags-input/__tests__/index.test.js +183 -0
- package/lib/tags-input/index.js +50 -62
- package/lib/tags-input/index.js.map +1 -1
- package/lib/two-choice.js +34 -44
- package/lib/two-choice.js.map +1 -1
- package/lib/with-stateful-model.js +9 -13
- package/lib/with-stateful-model.js.map +1 -1
- package/package.json +14 -11
- package/src/__tests__/alert-dialog.test.jsx +283 -0
- package/src/__tests__/checkbox.test.jsx +249 -0
- package/src/__tests__/choice-utils.test.js +12 -0
- package/src/__tests__/form-section.test.jsx +334 -0
- package/src/__tests__/help.test.jsx +184 -0
- package/src/__tests__/input.test.jsx +192 -0
- package/src/__tests__/langs.test.jsx +457 -0
- package/src/__tests__/number-text-field-custom.test.jsx +438 -0
- package/src/__tests__/number-text-field.test.jsx +341 -0
- package/src/__tests__/radio-with-label.test.jsx +259 -0
- package/src/__tests__/settings-panel.test.js +187 -0
- package/src/__tests__/settings.test.jsx +515 -0
- package/src/__tests__/tabs.test.jsx +193 -0
- package/src/__tests__/two-choice.test.js +110 -0
- package/src/__tests__/with-stateful-model.test.jsx +145 -0
- package/src/alert-dialog.jsx +30 -8
- package/src/checkbox.jsx +43 -37
- package/src/choice-configuration/__tests__/feedback-menu.test.jsx +163 -0
- package/src/choice-configuration/__tests__/index.test.jsx +234 -0
- package/src/choice-configuration/feedback-menu.jsx +6 -6
- package/src/choice-configuration/index.jsx +208 -199
- package/src/feedback-config/__tests__/feedback-config.test.jsx +141 -0
- package/src/feedback-config/__tests__/feedback-selector.test.jsx +97 -0
- package/src/feedback-config/feedback-selector.jsx +50 -55
- package/src/feedback-config/group.jsx +21 -22
- package/src/feedback-config/index.jsx +27 -29
- package/src/form-section.jsx +26 -18
- package/src/help.jsx +20 -28
- package/src/input.jsx +1 -1
- package/src/inputs.jsx +35 -44
- package/src/langs.jsx +41 -46
- package/src/layout/__tests__/config.layout.test.jsx +59 -0
- package/src/layout/__tests__/layout-content.test.jsx +3 -0
- package/src/layout/config-layout.jsx +53 -23
- package/src/layout/layout-contents.jsx +38 -40
- package/src/layout/settings-box.jsx +16 -19
- package/src/mui-box/index.jsx +35 -43
- package/src/number-text-field-custom.jsx +117 -65
- package/src/number-text-field.jsx +51 -34
- package/src/radio-with-label.jsx +26 -10
- package/src/settings/display-size.jsx +12 -11
- package/src/settings/index.js +2 -1
- package/src/settings/panel.jsx +101 -92
- package/src/settings/settings-radio-label.jsx +26 -10
- package/src/settings/toggle.jsx +37 -18
- package/src/tabs/index.jsx +8 -8
- package/src/tags-input/__tests__/index.test.jsx +113 -0
- package/src/tags-input/index.jsx +35 -38
- package/src/two-choice.jsx +15 -19
- package/README.md +0 -12
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { NumberTextField } from '../number-text-field';
|
|
5
|
+
|
|
6
|
+
describe('NumberTextField', () => {
|
|
7
|
+
const defaultProps = {
|
|
8
|
+
value: 1,
|
|
9
|
+
min: 1,
|
|
10
|
+
max: 10,
|
|
11
|
+
classes: {},
|
|
12
|
+
onChange: jest.fn(),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
jest.clearAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('rendering', () => {
|
|
20
|
+
it('renders TextField with correct value', () => {
|
|
21
|
+
render(<NumberTextField {...defaultProps} />);
|
|
22
|
+
const input = screen.getByRole('spinbutton');
|
|
23
|
+
expect(input).toHaveValue(1);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('renders with custom label', () => {
|
|
27
|
+
render(<NumberTextField {...defaultProps} label="Custom Label" />);
|
|
28
|
+
expect(screen.getByLabelText('Custom Label')).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('renders with suffix', () => {
|
|
32
|
+
const { container } = render(
|
|
33
|
+
<NumberTextField {...defaultProps} suffix="px" />,
|
|
34
|
+
);
|
|
35
|
+
expect(screen.getByText('px')).toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('renders without suffix when not provided', () => {
|
|
39
|
+
const { container } = render(
|
|
40
|
+
<NumberTextField {...defaultProps} />,
|
|
41
|
+
);
|
|
42
|
+
const input = screen.getByRole('spinbutton');
|
|
43
|
+
expect(input).toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('renders as disabled when disabled prop is true', () => {
|
|
47
|
+
render(<NumberTextField {...defaultProps} disabled={true} />);
|
|
48
|
+
const input = screen.getByRole('spinbutton');
|
|
49
|
+
expect(input).toBeDisabled();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('renders as enabled when disabled prop is false', () => {
|
|
53
|
+
render(<NumberTextField {...defaultProps} disabled={false} />);
|
|
54
|
+
const input = screen.getByRole('spinbutton');
|
|
55
|
+
expect(input).not.toBeDisabled();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('renders with custom variant', () => {
|
|
59
|
+
const { container } = render(
|
|
60
|
+
<NumberTextField {...defaultProps} variant="outlined" />,
|
|
61
|
+
);
|
|
62
|
+
expect(container.querySelector('input')).toBeInTheDocument();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('renders with disableUnderline variant', () => {
|
|
66
|
+
const { container } = render(
|
|
67
|
+
<NumberTextField {...defaultProps} disableUnderline={true} />,
|
|
68
|
+
);
|
|
69
|
+
expect(container.querySelector('input')).toBeInTheDocument();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('renders with custom className', () => {
|
|
73
|
+
const { container } = render(
|
|
74
|
+
<NumberTextField {...defaultProps} className="custom-class" />,
|
|
75
|
+
);
|
|
76
|
+
expect(container.querySelector('.custom-class')).toBeInTheDocument();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('renders with custom inputClassName', () => {
|
|
80
|
+
const { container } = render(
|
|
81
|
+
<NumberTextField {...defaultProps} inputClassName="input-class" />,
|
|
82
|
+
);
|
|
83
|
+
expect(container.querySelector('.input-class')).toBeInTheDocument();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('renders with ShrinkLabel InputLabelProps', () => {
|
|
87
|
+
render(<NumberTextField {...defaultProps} label="Shrink Label" />);
|
|
88
|
+
expect(screen.getByLabelText('Shrink Label')).toBeInTheDocument();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('renders with margin normal', () => {
|
|
92
|
+
const { container } = render(
|
|
93
|
+
<NumberTextField {...defaultProps} />,
|
|
94
|
+
);
|
|
95
|
+
expect(container.querySelector('input')).toBeInTheDocument();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('onChange callback', () => {
|
|
100
|
+
it('calls onChange with clamped value on blur', async () => {
|
|
101
|
+
const user = userEvent.setup();
|
|
102
|
+
const onChange = jest.fn();
|
|
103
|
+
render(<NumberTextField {...defaultProps} onChange={onChange} />);
|
|
104
|
+
|
|
105
|
+
const input = screen.getByRole('spinbutton');
|
|
106
|
+
await user.clear(input);
|
|
107
|
+
await user.type(input, '15');
|
|
108
|
+
fireEvent.blur(input);
|
|
109
|
+
|
|
110
|
+
await waitFor(() => {
|
|
111
|
+
expect(onChange).toHaveBeenCalledWith(expect.anything(), 10);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('calls onChange on Enter key press', async () => {
|
|
116
|
+
const user = userEvent.setup();
|
|
117
|
+
const onChange = jest.fn();
|
|
118
|
+
render(<NumberTextField {...defaultProps} onChange={onChange} />);
|
|
119
|
+
|
|
120
|
+
const input = screen.getByRole('spinbutton');
|
|
121
|
+
await user.clear(input);
|
|
122
|
+
await user.type(input, '5');
|
|
123
|
+
await user.keyboard('{Enter}');
|
|
124
|
+
|
|
125
|
+
expect(input).toHaveValue(5);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('calls onChange during typing (unvalidated)', async () => {
|
|
129
|
+
const user = userEvent.setup();
|
|
130
|
+
const onChange = jest.fn();
|
|
131
|
+
render(<NumberTextField {...defaultProps} onChange={onChange} />);
|
|
132
|
+
|
|
133
|
+
const input = screen.getByRole('spinbutton');
|
|
134
|
+
await user.clear(input);
|
|
135
|
+
await user.type(input, '7');
|
|
136
|
+
|
|
137
|
+
// onChange should be called at least once during typing
|
|
138
|
+
expect(input).toHaveValue(7);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('does not call onChange when value does not change on blur', async () => {
|
|
142
|
+
const user = userEvent.setup();
|
|
143
|
+
const onChange = jest.fn();
|
|
144
|
+
render(<NumberTextField {...defaultProps} value={5} onChange={onChange} />);
|
|
145
|
+
|
|
146
|
+
const input = screen.getByRole('spinbutton');
|
|
147
|
+
fireEvent.blur(input);
|
|
148
|
+
|
|
149
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('range constraints', () => {
|
|
154
|
+
it('handles only min constraint', async () => {
|
|
155
|
+
const user = userEvent.setup();
|
|
156
|
+
const { container } = render(
|
|
157
|
+
<NumberTextField
|
|
158
|
+
value={5}
|
|
159
|
+
min={3}
|
|
160
|
+
onChange={jest.fn()}
|
|
161
|
+
/>,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const input = screen.getByRole('spinbutton');
|
|
165
|
+
expect(input).toHaveValue(5);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('handles only max constraint', async () => {
|
|
169
|
+
const user = userEvent.setup();
|
|
170
|
+
const { container } = render(
|
|
171
|
+
<NumberTextField
|
|
172
|
+
value={5}
|
|
173
|
+
max={10}
|
|
174
|
+
onChange={jest.fn()}
|
|
175
|
+
/>,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const input = screen.getByRole('spinbutton');
|
|
179
|
+
expect(input).toHaveValue(5);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('handles no constraints', () => {
|
|
183
|
+
const { container } = render(
|
|
184
|
+
<NumberTextField
|
|
185
|
+
value={100}
|
|
186
|
+
onChange={jest.fn()}
|
|
187
|
+
/>,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const input = screen.getByRole('spinbutton');
|
|
191
|
+
expect(input).toHaveValue(100);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('keyboard interactions', () => {
|
|
196
|
+
it('handles Enter key to blur', async () => {
|
|
197
|
+
const user = userEvent.setup();
|
|
198
|
+
const onChange = jest.fn();
|
|
199
|
+
render(<NumberTextField {...defaultProps} onChange={onChange} />);
|
|
200
|
+
|
|
201
|
+
const input = screen.getByRole('spinbutton');
|
|
202
|
+
input.focus();
|
|
203
|
+
await user.type(input, '{Enter}');
|
|
204
|
+
|
|
205
|
+
expect(input).not.toHaveFocus();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('allows typing numbers', async () => {
|
|
209
|
+
const user = userEvent.setup();
|
|
210
|
+
render(<NumberTextField {...defaultProps} />);
|
|
211
|
+
|
|
212
|
+
const input = screen.getByRole('spinbutton');
|
|
213
|
+
await user.clear(input);
|
|
214
|
+
await user.type(input, '42');
|
|
215
|
+
|
|
216
|
+
expect(input).toHaveValue(42);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('allows negative sign', async () => {
|
|
220
|
+
const user = userEvent.setup();
|
|
221
|
+
render(<NumberTextField {...defaultProps} min={-100} value={0} />);
|
|
222
|
+
|
|
223
|
+
const input = screen.getByRole('spinbutton');
|
|
224
|
+
await user.clear(input);
|
|
225
|
+
await user.type(input, '-5');
|
|
226
|
+
|
|
227
|
+
expect(input).toHaveValue(-5);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('props updates', () => {
|
|
232
|
+
it('updates value when prop changes', () => {
|
|
233
|
+
const { rerender } = render(
|
|
234
|
+
<NumberTextField {...defaultProps} value={5} />,
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
let input = screen.getByRole('spinbutton');
|
|
238
|
+
expect(input).toHaveValue(5);
|
|
239
|
+
|
|
240
|
+
rerender(<NumberTextField {...defaultProps} value={8} />);
|
|
241
|
+
|
|
242
|
+
input = screen.getByRole('spinbutton');
|
|
243
|
+
expect(input).toHaveValue(8);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('updates constraints when min/max props change', async () => {
|
|
247
|
+
const user = userEvent.setup();
|
|
248
|
+
const onChange = jest.fn();
|
|
249
|
+
const { rerender } = render(
|
|
250
|
+
<NumberTextField
|
|
251
|
+
value={5}
|
|
252
|
+
min={1}
|
|
253
|
+
max={10}
|
|
254
|
+
onChange={onChange}
|
|
255
|
+
/>,
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
rerender(
|
|
259
|
+
<NumberTextField
|
|
260
|
+
value={5}
|
|
261
|
+
min={1}
|
|
262
|
+
max={8}
|
|
263
|
+
onChange={onChange}
|
|
264
|
+
/>,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
const input = screen.getByRole('spinbutton');
|
|
268
|
+
await user.clear(input);
|
|
269
|
+
await user.type(input, '15');
|
|
270
|
+
fireEvent.blur(input);
|
|
271
|
+
|
|
272
|
+
await waitFor(() => {
|
|
273
|
+
expect(input).toHaveValue(8); // Clamped to new max
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('re-clamps value when constraints become more restrictive', () => {
|
|
278
|
+
const onChange = jest.fn();
|
|
279
|
+
const { rerender } = render(
|
|
280
|
+
<NumberTextField
|
|
281
|
+
value={8}
|
|
282
|
+
min={1}
|
|
283
|
+
max={10}
|
|
284
|
+
onChange={onChange}
|
|
285
|
+
/>,
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
rerender(
|
|
289
|
+
<NumberTextField
|
|
290
|
+
value={8}
|
|
291
|
+
min={1}
|
|
292
|
+
max={5}
|
|
293
|
+
onChange={onChange}
|
|
294
|
+
/>,
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
// Component should re-clamp the value
|
|
298
|
+
expect(screen.getByRole('spinbutton')).toBeInTheDocument();
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe('fallback number logic', () => {
|
|
303
|
+
it('defaults to 0 when no min or max is provided', () => {
|
|
304
|
+
render(
|
|
305
|
+
<NumberTextField
|
|
306
|
+
value={undefined}
|
|
307
|
+
onChange={jest.fn()}
|
|
308
|
+
/>,
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const input = screen.getByRole('spinbutton');
|
|
312
|
+
expect(input).toHaveValue(0);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('defaults to max when only max is provided', () => {
|
|
316
|
+
render(
|
|
317
|
+
<NumberTextField
|
|
318
|
+
value={undefined}
|
|
319
|
+
max={15}
|
|
320
|
+
onChange={jest.fn()}
|
|
321
|
+
/>,
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
const input = screen.getByRole('spinbutton');
|
|
325
|
+
expect(input).toHaveValue(15);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('defaults to min when only min is provided', () => {
|
|
329
|
+
render(
|
|
330
|
+
<NumberTextField
|
|
331
|
+
value={undefined}
|
|
332
|
+
min={5}
|
|
333
|
+
onChange={jest.fn()}
|
|
334
|
+
/>,
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
const input = screen.getByRole('spinbutton');
|
|
338
|
+
expect(input).toHaveValue(5);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
});
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import RadioWithLabel from '../radio-with-label';
|
|
5
|
+
|
|
6
|
+
describe('RadioWithLabel Component', () => {
|
|
7
|
+
const onChange = jest.fn();
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
onChange.mockClear();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('Rendering', () => {
|
|
14
|
+
it('should render radio button with label', () => {
|
|
15
|
+
render(
|
|
16
|
+
<RadioWithLabel
|
|
17
|
+
label="Option 1"
|
|
18
|
+
value="option1"
|
|
19
|
+
checked={false}
|
|
20
|
+
onChange={onChange}
|
|
21
|
+
/>,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
expect(screen.getByText('Option 1')).toBeInTheDocument();
|
|
25
|
+
expect(screen.getByRole('radio')).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should render checked radio button', () => {
|
|
29
|
+
render(
|
|
30
|
+
<RadioWithLabel
|
|
31
|
+
label="Selected"
|
|
32
|
+
value="selected"
|
|
33
|
+
checked={true}
|
|
34
|
+
onChange={onChange}
|
|
35
|
+
/>,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
expect(screen.getByRole('radio')).toBeChecked();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should render unchecked radio button', () => {
|
|
42
|
+
render(
|
|
43
|
+
<RadioWithLabel
|
|
44
|
+
label="Unselected"
|
|
45
|
+
value="unselected"
|
|
46
|
+
checked={false}
|
|
47
|
+
onChange={onChange}
|
|
48
|
+
/>,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(screen.getByRole('radio')).not.toBeChecked();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('User interaction', () => {
|
|
56
|
+
it('should call onChange when radio button is clicked', async () => {
|
|
57
|
+
const user = userEvent.setup();
|
|
58
|
+
render(
|
|
59
|
+
<RadioWithLabel
|
|
60
|
+
label="Option"
|
|
61
|
+
value="option"
|
|
62
|
+
checked={false}
|
|
63
|
+
onChange={onChange}
|
|
64
|
+
/>,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const radio = screen.getByRole('radio');
|
|
68
|
+
await user.click(radio);
|
|
69
|
+
|
|
70
|
+
expect(onChange).toHaveBeenCalled();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should call onChange when label is clicked', async () => {
|
|
74
|
+
const user = userEvent.setup();
|
|
75
|
+
render(
|
|
76
|
+
<RadioWithLabel
|
|
77
|
+
label="Click me"
|
|
78
|
+
value="test"
|
|
79
|
+
checked={false}
|
|
80
|
+
onChange={onChange}
|
|
81
|
+
/>,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const label = screen.getByText('Click me');
|
|
85
|
+
await user.click(label);
|
|
86
|
+
|
|
87
|
+
expect(onChange).toHaveBeenCalled();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('Props', () => {
|
|
92
|
+
it('should accept different value types', () => {
|
|
93
|
+
const { container: stringContainer } = render(
|
|
94
|
+
<RadioWithLabel
|
|
95
|
+
label="String value"
|
|
96
|
+
value="string"
|
|
97
|
+
checked={false}
|
|
98
|
+
onChange={onChange}
|
|
99
|
+
/>,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const { container: numberContainer } = render(
|
|
103
|
+
<RadioWithLabel
|
|
104
|
+
label="Number value"
|
|
105
|
+
value={123}
|
|
106
|
+
checked={false}
|
|
107
|
+
onChange={onChange}
|
|
108
|
+
/>,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
expect(stringContainer.querySelector('input[type="radio"]')).toBeInTheDocument();
|
|
112
|
+
expect(numberContainer.querySelector('input[type="radio"]')).toBeInTheDocument();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should display different labels', () => {
|
|
116
|
+
render(
|
|
117
|
+
<>
|
|
118
|
+
<RadioWithLabel
|
|
119
|
+
label="Label A"
|
|
120
|
+
value="a"
|
|
121
|
+
checked={false}
|
|
122
|
+
onChange={onChange}
|
|
123
|
+
/>
|
|
124
|
+
<RadioWithLabel
|
|
125
|
+
label="Label B"
|
|
126
|
+
value="b"
|
|
127
|
+
checked={false}
|
|
128
|
+
onChange={onChange}
|
|
129
|
+
/>
|
|
130
|
+
</>,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
expect(screen.getByText('Label A')).toBeInTheDocument();
|
|
134
|
+
expect(screen.getByText('Label B')).toBeInTheDocument();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('Radio button group behavior', () => {
|
|
139
|
+
it('should allow multiple radio buttons to be rendered', () => {
|
|
140
|
+
render(
|
|
141
|
+
<>
|
|
142
|
+
<RadioWithLabel
|
|
143
|
+
label="Option 1"
|
|
144
|
+
value="opt1"
|
|
145
|
+
checked={true}
|
|
146
|
+
onChange={onChange}
|
|
147
|
+
/>
|
|
148
|
+
<RadioWithLabel
|
|
149
|
+
label="Option 2"
|
|
150
|
+
value="opt2"
|
|
151
|
+
checked={false}
|
|
152
|
+
onChange={onChange}
|
|
153
|
+
/>
|
|
154
|
+
<RadioWithLabel
|
|
155
|
+
label="Option 3"
|
|
156
|
+
value="opt3"
|
|
157
|
+
checked={false}
|
|
158
|
+
onChange={onChange}
|
|
159
|
+
/>
|
|
160
|
+
</>,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const radios = screen.getAllByRole('radio');
|
|
164
|
+
expect(radios).toHaveLength(3);
|
|
165
|
+
expect(radios[0]).toBeChecked();
|
|
166
|
+
expect(radios[1]).not.toBeChecked();
|
|
167
|
+
expect(radios[2]).not.toBeChecked();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should handle independent onChange callbacks for multiple radios', async () => {
|
|
171
|
+
const user = userEvent.setup();
|
|
172
|
+
const onChange1 = jest.fn();
|
|
173
|
+
const onChange2 = jest.fn();
|
|
174
|
+
const onChange3 = jest.fn();
|
|
175
|
+
|
|
176
|
+
render(
|
|
177
|
+
<>
|
|
178
|
+
<RadioWithLabel
|
|
179
|
+
label="Option 1"
|
|
180
|
+
value="opt1"
|
|
181
|
+
checked={true}
|
|
182
|
+
onChange={onChange1}
|
|
183
|
+
/>
|
|
184
|
+
<RadioWithLabel
|
|
185
|
+
label="Option 2"
|
|
186
|
+
value="opt2"
|
|
187
|
+
checked={false}
|
|
188
|
+
onChange={onChange2}
|
|
189
|
+
/>
|
|
190
|
+
<RadioWithLabel
|
|
191
|
+
label="Option 3"
|
|
192
|
+
value="opt3"
|
|
193
|
+
checked={false}
|
|
194
|
+
onChange={onChange3}
|
|
195
|
+
/>
|
|
196
|
+
</>,
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const radios = screen.getAllByRole('radio');
|
|
200
|
+
|
|
201
|
+
await user.click(radios[1]);
|
|
202
|
+
expect(onChange2).toHaveBeenCalled();
|
|
203
|
+
expect(onChange1).not.toHaveBeenCalled();
|
|
204
|
+
expect(onChange3).not.toHaveBeenCalled();
|
|
205
|
+
|
|
206
|
+
onChange2.mockClear();
|
|
207
|
+
|
|
208
|
+
await user.click(radios[2]);
|
|
209
|
+
expect(onChange3).toHaveBeenCalled();
|
|
210
|
+
expect(onChange1).not.toHaveBeenCalled();
|
|
211
|
+
expect(onChange2).not.toHaveBeenCalled();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('Styling', () => {
|
|
216
|
+
it('should render with correct styling classes', () => {
|
|
217
|
+
const { container } = render(
|
|
218
|
+
<RadioWithLabel
|
|
219
|
+
label="Styled"
|
|
220
|
+
value="styled"
|
|
221
|
+
checked={false}
|
|
222
|
+
onChange={onChange}
|
|
223
|
+
/>,
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
expect(container.querySelector('[class*="MuiFormControlLabel"]')).toBeInTheDocument();
|
|
227
|
+
expect(container.querySelector('[class*="MuiRadio"]')).toBeInTheDocument();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('Edge cases', () => {
|
|
232
|
+
it('should handle empty string label', () => {
|
|
233
|
+
render(
|
|
234
|
+
<RadioWithLabel
|
|
235
|
+
label=""
|
|
236
|
+
value="empty"
|
|
237
|
+
checked={false}
|
|
238
|
+
onChange={onChange}
|
|
239
|
+
/>,
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
expect(screen.getByRole('radio')).toBeInTheDocument();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should handle long labels', () => {
|
|
246
|
+
const longLabel = 'This is a very long label that should wrap properly and not break the layout';
|
|
247
|
+
render(
|
|
248
|
+
<RadioWithLabel
|
|
249
|
+
label={longLabel}
|
|
250
|
+
value="long"
|
|
251
|
+
checked={false}
|
|
252
|
+
onChange={onChange}
|
|
253
|
+
/>,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
expect(screen.getByText(longLabel)).toBeInTheDocument();
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
});
|