@centreon/ui 26.12.1 → 26.12.3
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/package.json +6 -3
- package/public/mockServiceWorker.js +31 -8
- package/src/Dashboard/Dashboard.browser.test.tsx +107 -0
- package/src/Form/CollapsibleGroup.tsx +6 -33
- package/src/Form/Form.browser.test.tsx +553 -0
- package/src/Form/Inputs/ExclusiveCheckboxGroup.browser.test.tsx +304 -0
- package/src/Form/Inputs/Grid.tsx +1 -2
- package/src/Form/Inputs/SegmentedButtons.browser.test.tsx +113 -0
- package/src/Form/Inputs/index.tsx +10 -52
- package/src/InputField/Number/Number.browser.test.tsx +109 -0
- package/src/InputField/Select/Autocomplete/Connected/Multi/MultiConnectedAutocompleteField.browser.test.tsx +250 -0
- package/src/Listing/Listing.browser.test.tsx +476 -0
- package/src/Module/Module.browser.test.tsx +145 -0
- package/src/Pagination/Pagination.browser.test.tsx +184 -0
- package/src/RichTextEditor/RichTextEditor.browser.test.tsx +567 -0
- package/src/RichTextEditor/plugins/FloatingLinkEditorPlugin.tsx +3 -1
- package/src/TimePeriods/ResolutionTimePeriod.browser.test.tsx +143 -0
- package/src/TimePeriods/TimePeriods.browser.test.tsx +468 -0
- package/src/Typography/FluidTypography/FluidTypography.browser.test.tsx +54 -0
- package/src/api/useMutationQuery/useMutationQuery.browser.test.tsx +154 -0
- package/src/components/CollapsibleItem/CollapsibleItem.browser.test.tsx +120 -0
- package/src/components/CopyCommand/CopyCommand.browser.test.tsx +287 -0
- package/src/components/CrudPage/CrudPage.browser.test.tsx +659 -0
- package/src/components/DataTable/DataTable.browser.test.tsx +257 -0
- package/src/components/Form/AccessRights/AccessRights.browser.test.tsx +527 -0
- package/src/components/ItemComposition/ItemComposition.browser.test.tsx +172 -0
- package/src/components/Layout/PageLayout.browser.test.tsx +114 -0
- package/src/components/Modal/ConfirmationModal/ConfirmationModal.browser.test.tsx +237 -0
- package/src/components/SortableEntriesList/SortableEntriesList.browser.test.tsx +981 -0
- package/src/components/Tabs/Tabs.browser.test.tsx +115 -0
- package/src/utils/useFullscreen/Fullscreen.browser.test.tsx +176 -0
- package/src/utils/useLocale/useLocale.browser.test.tsx +49 -0
- package/src/utils/useLocaleTimezoneDate/{LocaleTimezoneDate.cypress.spec.tsx → LocaleTimezoneDate.browser.test.tsx} +24 -11
- package/src/Dashboard/Dashboard.cypress.spec.tsx +0 -71
- package/src/Form/Form.cypress.spec.tsx +0 -306
- package/src/Form/Inputs/ExclusiveCheckboxGroup.cypress.spec.tsx +0 -236
- package/src/Form/Inputs/SegmentedButtons.cypress.spec.tsx +0 -101
- package/src/InputField/Number/Number.cypress.spec.tsx +0 -89
- package/src/InputField/Select/Autocomplete/Connected/Multi/MultiConnectedAutocompleteField.cypress.spec.tsx +0 -182
- package/src/Listing/Listing.cypress.spec.tsx +0 -349
- package/src/Module/Module.cypress.spec.tsx +0 -127
- package/src/Pagination/Pagination.cypress.spec.tsx +0 -140
- package/src/RichTextEditor/RichTextEditor.cypress.spec.tsx +0 -408
- package/src/TimePeriods/ResolutionTimePeriod.cypress.spec.tsx +0 -74
- package/src/TimePeriods/TimePeriods.cypress.spec.tsx +0 -488
- package/src/Typography/FluidTypography/FluidTypography.cypress.spec.tsx +0 -27
- package/src/api/useMutationQuery/useMutationQuery.cypress.spec.tsx +0 -93
- package/src/components/CollapsibleItem/CollapsibleItem.cypress.spec.tsx +0 -76
- package/src/components/CopyCommand/CopyCommand.cypress.spec.tsx +0 -167
- package/src/components/CrudPage/CrudPage.cypress.spec.tsx +0 -561
- package/src/components/DataTable/DataTable.cypress.spec.tsx +0 -174
- package/src/components/Form/AccessRights/AccessRights.cypress.spec.tsx +0 -376
- package/src/components/ItemComposition/ItemComposition.cypress.spec.tsx +0 -112
- package/src/components/Layout/PageLayout.cypress.spec.tsx +0 -64
- package/src/components/Modal/ConfirmationModal/ConfirmationModal.cypress.spec.tsx +0 -171
- package/src/components/SortableEntriesList/SortableEntriesList.cypress.spec.tsx +0 -792
- package/src/components/Tabs/Tabs.cypress.spec.tsx +0 -69
- package/src/utils/useFullscreen/Fullscreen.cypress.spec.tsx +0 -130
- package/src/utils/useLocale/useLocale.cypress.spec.tsx +0 -40
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { Typography } from '@mui/material';
|
|
2
|
+
|
|
3
|
+
import { rs } from '@rstest/core';
|
|
4
|
+
import { render } from '@test-setup/render';
|
|
5
|
+
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
|
6
|
+
import userEvent from '@testing-library/user-event';
|
|
7
|
+
import { useFormikContext } from 'formik';
|
|
8
|
+
import { object } from 'yup';
|
|
9
|
+
|
|
10
|
+
import { Form } from '../Form';
|
|
11
|
+
import { InputType } from './models';
|
|
12
|
+
|
|
13
|
+
const options = ['Down', 'Unreachable', 'Recovery', 'None'];
|
|
14
|
+
|
|
15
|
+
const ValueDisplay = (): JSX.Element => {
|
|
16
|
+
const { values } = useFormikContext<{
|
|
17
|
+
notificationOptions: Array<string> | null;
|
|
18
|
+
}>();
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Typography data-testid="value">
|
|
22
|
+
{JSON.stringify(values.notificationOptions)}
|
|
23
|
+
</Typography>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
interface InitializeProps {
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
notificationOptions?: Array<string> | null;
|
|
30
|
+
variant?: 'checkboxes' | 'chips';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const initialize = ({
|
|
34
|
+
notificationOptions = [],
|
|
35
|
+
disabled = false,
|
|
36
|
+
variant
|
|
37
|
+
}: InitializeProps = {}): void => {
|
|
38
|
+
render(
|
|
39
|
+
<Form
|
|
40
|
+
initialValues={{ notificationOptions }}
|
|
41
|
+
inputs={[
|
|
42
|
+
{
|
|
43
|
+
dataTestId: 'notification-options',
|
|
44
|
+
exclusiveCheckboxGroup: {
|
|
45
|
+
direction: 'horizontal',
|
|
46
|
+
exclusiveLabel: 'No notifications',
|
|
47
|
+
exclusiveOption: 'None',
|
|
48
|
+
options,
|
|
49
|
+
variant
|
|
50
|
+
},
|
|
51
|
+
fieldName: 'notificationOptions',
|
|
52
|
+
getDisabled: () => disabled,
|
|
53
|
+
group: '',
|
|
54
|
+
label: 'Notification options',
|
|
55
|
+
type: InputType.ExclusiveCheckboxGroup
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
custom: { Component: ValueDisplay },
|
|
59
|
+
fieldName: 'value',
|
|
60
|
+
group: '',
|
|
61
|
+
label: 'value',
|
|
62
|
+
type: InputType.Custom
|
|
63
|
+
}
|
|
64
|
+
]}
|
|
65
|
+
submit={rs.fn()}
|
|
66
|
+
validationSchema={object()}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const toggle = (): Promise<HTMLElement> =>
|
|
72
|
+
screen.findByLabelText('No notifications');
|
|
73
|
+
|
|
74
|
+
const checkbox = (option: string): HTMLElement =>
|
|
75
|
+
screen.getByRole('checkbox', { name: option });
|
|
76
|
+
|
|
77
|
+
const expectValue = async (value: Array<string> | null): Promise<void> => {
|
|
78
|
+
await waitFor(() => {
|
|
79
|
+
expect(screen.getByTestId('value')).toHaveTextContent(
|
|
80
|
+
new RegExp(`^${JSON.stringify(value).replace(/[[\]]/g, '\\$&')}$`)
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
describe('Exclusive checkbox group', () => {
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
expect.hasAssertions();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('displays the toggle and every option except the exclusive one', async () => {
|
|
91
|
+
initialize();
|
|
92
|
+
|
|
93
|
+
expect(await toggle()).not.toBeChecked();
|
|
94
|
+
expect(screen.getByLabelText('Down')).toBeInTheDocument();
|
|
95
|
+
expect(screen.getByLabelText('Unreachable')).toBeInTheDocument();
|
|
96
|
+
expect(screen.getByLabelText('Recovery')).toBeInTheDocument();
|
|
97
|
+
expect(screen.queryByLabelText('None')).not.toBeInTheDocument();
|
|
98
|
+
|
|
99
|
+
// The three options in order, then the toggle,
|
|
100
|
+
// all unticked and usable, and the empty value.
|
|
101
|
+
expect(
|
|
102
|
+
screen
|
|
103
|
+
.getAllByRole('checkbox')
|
|
104
|
+
.map((element) => element.getAttribute('aria-label') ?? element.id)
|
|
105
|
+
).toEqual(['Down', 'Unreachable', 'Recovery', 'No notifications']);
|
|
106
|
+
['Down', 'Unreachable', 'Recovery'].forEach((option) => {
|
|
107
|
+
expect(checkbox(option)).not.toBeChecked();
|
|
108
|
+
expect(checkbox(option)).toBeEnabled();
|
|
109
|
+
});
|
|
110
|
+
expect(await toggle()).toBeEnabled();
|
|
111
|
+
expect(screen.getByText('No notifications')).toBeVisible();
|
|
112
|
+
await expectValue([]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('keeps the inherited state when nothing is selected', async () => {
|
|
116
|
+
initialize();
|
|
117
|
+
|
|
118
|
+
expect(await toggle()).not.toBeChecked();
|
|
119
|
+
expect(screen.getByLabelText('Down')).not.toBeChecked();
|
|
120
|
+
expect(screen.getByLabelText('Down')).toBeEnabled();
|
|
121
|
+
await expectValue([]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('treats a null value as the inherited state', async () => {
|
|
125
|
+
initialize({ notificationOptions: null });
|
|
126
|
+
|
|
127
|
+
expect(await toggle()).not.toBeChecked();
|
|
128
|
+
expect(screen.getByLabelText('Down')).not.toBeChecked();
|
|
129
|
+
expect(screen.getByLabelText('Down')).toBeEnabled();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('displays the exclusive state from the initial value', async () => {
|
|
133
|
+
initialize({ notificationOptions: ['None'] });
|
|
134
|
+
|
|
135
|
+
expect(await toggle()).toBeChecked();
|
|
136
|
+
expect(screen.getByLabelText('Down')).not.toBeChecked();
|
|
137
|
+
expect(screen.getByLabelText('Down')).toBeDisabled();
|
|
138
|
+
await expectValue(['None']);
|
|
139
|
+
|
|
140
|
+
// Every option is unticked and greyed out.
|
|
141
|
+
['Unreachable', 'Recovery'].forEach((option) => {
|
|
142
|
+
expect(checkbox(option)).not.toBeChecked();
|
|
143
|
+
expect(checkbox(option)).toBeDisabled();
|
|
144
|
+
});
|
|
145
|
+
expect(await toggle()).toBeEnabled();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('displays a selection from the initial value', async () => {
|
|
149
|
+
initialize({ notificationOptions: ['Down', 'Recovery'] });
|
|
150
|
+
|
|
151
|
+
expect(await toggle()).not.toBeChecked();
|
|
152
|
+
expect(screen.getByLabelText('Down')).toBeChecked();
|
|
153
|
+
expect(screen.getByLabelText('Unreachable')).not.toBeChecked();
|
|
154
|
+
expect(screen.getByLabelText('Recovery')).toBeChecked();
|
|
155
|
+
await expectValue(['Down', 'Recovery']);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('goes from the inherited state to a selection and back', async () => {
|
|
159
|
+
initialize();
|
|
160
|
+
|
|
161
|
+
await userEvent.click(await screen.findByLabelText('Down'));
|
|
162
|
+
await userEvent.click(screen.getByLabelText('Recovery'));
|
|
163
|
+
await expectValue(['Down', 'Recovery']);
|
|
164
|
+
|
|
165
|
+
await userEvent.click(screen.getByLabelText('Down'));
|
|
166
|
+
await userEvent.click(screen.getByLabelText('Recovery'));
|
|
167
|
+
await expectValue([]);
|
|
168
|
+
expect(await toggle()).not.toBeChecked();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('goes from the inherited state to the exclusive state and back', async () => {
|
|
172
|
+
initialize();
|
|
173
|
+
|
|
174
|
+
await userEvent.click(await toggle());
|
|
175
|
+
await waitFor(async () => {
|
|
176
|
+
expect(await toggle()).toBeChecked();
|
|
177
|
+
});
|
|
178
|
+
await expectValue(['None']);
|
|
179
|
+
|
|
180
|
+
await userEvent.click(await toggle());
|
|
181
|
+
await waitFor(async () => {
|
|
182
|
+
expect(await toggle()).not.toBeChecked();
|
|
183
|
+
});
|
|
184
|
+
await expectValue([]);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('clears the selection when the toggle is turned on and leaves it empty when turned off', async () => {
|
|
188
|
+
initialize({ notificationOptions: ['Down', 'Recovery'] });
|
|
189
|
+
|
|
190
|
+
await userEvent.click(await toggle());
|
|
191
|
+
await expectValue(['None']);
|
|
192
|
+
await waitFor(() => {
|
|
193
|
+
expect(screen.getByLabelText('Down')).not.toBeChecked();
|
|
194
|
+
});
|
|
195
|
+
expect(screen.getByLabelText('Down')).toBeDisabled();
|
|
196
|
+
expect(screen.getByLabelText('Recovery')).not.toBeChecked();
|
|
197
|
+
expect(screen.getByLabelText('Recovery')).toBeDisabled();
|
|
198
|
+
|
|
199
|
+
await userEvent.click(await toggle());
|
|
200
|
+
await expectValue([]);
|
|
201
|
+
await waitFor(() => {
|
|
202
|
+
expect(screen.getByLabelText('Down')).toBeEnabled();
|
|
203
|
+
});
|
|
204
|
+
expect(screen.getByLabelText('Down')).not.toBeChecked();
|
|
205
|
+
|
|
206
|
+
await userEvent.click(screen.getByLabelText('Unreachable'));
|
|
207
|
+
await expectValue(['Unreachable']);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('prevents ticking a box while the toggle is on', async () => {
|
|
211
|
+
initialize({ notificationOptions: ['None'] });
|
|
212
|
+
|
|
213
|
+
// The checkbox is natively disabled, so the click never reaches its
|
|
214
|
+
// handler (`userEvent` would refuse it): it checks the box cannot be ticked.
|
|
215
|
+
fireEvent.click(await screen.findByLabelText('Down'));
|
|
216
|
+
expect(screen.getByLabelText('Down')).not.toBeChecked();
|
|
217
|
+
await expectValue(['None']);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('disables both the toggle and the checkboxes when the input is disabled', async () => {
|
|
221
|
+
initialize({ disabled: true, notificationOptions: ['Down'] });
|
|
222
|
+
|
|
223
|
+
expect(await toggle()).toBeDisabled();
|
|
224
|
+
expect(screen.getByLabelText('Down')).toBeChecked();
|
|
225
|
+
expect(screen.getByLabelText('Down')).toBeDisabled();
|
|
226
|
+
await expectValue(['Down']);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe('Exclusive checkbox group, as chips', () => {
|
|
231
|
+
beforeEach(() => {
|
|
232
|
+
expect.hasAssertions();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const chip = (option: string): Promise<HTMLElement> =>
|
|
236
|
+
screen.findByTestId(`notification-options-${option}`);
|
|
237
|
+
|
|
238
|
+
it('displays the options as chips and the exclusive one as the toggle', async () => {
|
|
239
|
+
initialize({ variant: 'chips' });
|
|
240
|
+
|
|
241
|
+
for (const option of ['Down', 'Unreachable', 'Recovery']) {
|
|
242
|
+
expect(await chip(option)).toHaveAttribute('aria-pressed', 'false');
|
|
243
|
+
}
|
|
244
|
+
expect(
|
|
245
|
+
screen.queryByTestId('notification-options-None')
|
|
246
|
+
).not.toBeInTheDocument();
|
|
247
|
+
expect(await toggle()).not.toBeChecked();
|
|
248
|
+
expect(
|
|
249
|
+
screen.queryByRole('checkbox', { name: 'Down' })
|
|
250
|
+
).not.toBeInTheDocument();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('toggles a chip on and off', async () => {
|
|
254
|
+
initialize({ variant: 'chips' });
|
|
255
|
+
|
|
256
|
+
await userEvent.click(await chip('Down'));
|
|
257
|
+
await userEvent.click(await chip('Recovery'));
|
|
258
|
+
|
|
259
|
+
await expectValue(['Down', 'Recovery']);
|
|
260
|
+
await waitFor(async () => {
|
|
261
|
+
expect(await chip('Down')).toHaveAttribute('aria-pressed', 'true');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
await userEvent.click(await chip('Down'));
|
|
265
|
+
|
|
266
|
+
await expectValue(['Recovery']);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('clears and disables the chips while the toggle is on', async () => {
|
|
270
|
+
initialize({ notificationOptions: ['Down', 'Recovery'], variant: 'chips' });
|
|
271
|
+
|
|
272
|
+
await userEvent.click(await toggle());
|
|
273
|
+
|
|
274
|
+
await expectValue(['None']);
|
|
275
|
+
await waitFor(async () => {
|
|
276
|
+
expect(await chip('Down')).toHaveAttribute('aria-pressed', 'false');
|
|
277
|
+
});
|
|
278
|
+
expect(await chip('Down')).toHaveAttribute('aria-disabled', 'true');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('leaves the chips empty and usable once the toggle is off', async () => {
|
|
282
|
+
initialize({ notificationOptions: ['None'], variant: 'chips' });
|
|
283
|
+
|
|
284
|
+
await userEvent.click(await toggle());
|
|
285
|
+
|
|
286
|
+
await expectValue([]);
|
|
287
|
+
await waitFor(async () => {
|
|
288
|
+
expect(await chip('Unreachable')).not.toHaveAttribute(
|
|
289
|
+
'aria-disabled',
|
|
290
|
+
'true'
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
await userEvent.click(await chip('Unreachable'));
|
|
294
|
+
|
|
295
|
+
await expectValue(['Unreachable']);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('disables the chips and the toggle when the input is disabled', async () => {
|
|
299
|
+
initialize({ disabled: true, variant: 'chips' });
|
|
300
|
+
|
|
301
|
+
expect(await chip('Down')).toHaveAttribute('aria-disabled', 'true');
|
|
302
|
+
expect(await toggle()).toBeDisabled();
|
|
303
|
+
});
|
|
304
|
+
});
|
package/src/Form/Inputs/Grid.tsx
CHANGED
|
@@ -45,8 +45,7 @@ const Grid = ({
|
|
|
45
45
|
<Box key={key as string} sx={{ width: '100%' }}>
|
|
46
46
|
{field.additionalLabel && (
|
|
47
47
|
<Typography
|
|
48
|
-
className={field?.additionalLabelClassName}
|
|
49
|
-
sx={{ color: 'primary.main', marginBottom: 0.5 }}
|
|
48
|
+
className={`mb-1 text-base font-bold text-text-primary ${field?.additionalLabelClassName || ''}`}
|
|
50
49
|
variant="h6"
|
|
51
50
|
>
|
|
52
51
|
{field.additionalLabel}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Typography } from '@mui/material';
|
|
2
|
+
|
|
3
|
+
import { rs } from '@rstest/core';
|
|
4
|
+
import { render } from '@test-setup/render';
|
|
5
|
+
import { screen, waitFor } from '@testing-library/react';
|
|
6
|
+
import userEvent from '@testing-library/user-event';
|
|
7
|
+
import { useFormikContext } from 'formik';
|
|
8
|
+
import { object } from 'yup';
|
|
9
|
+
|
|
10
|
+
import { Form } from '../Form';
|
|
11
|
+
import { InputType } from './models';
|
|
12
|
+
|
|
13
|
+
const options = [
|
|
14
|
+
{ label: 'Yes', value: 'true' },
|
|
15
|
+
{ label: 'No', value: 'false' },
|
|
16
|
+
{ label: 'Default', value: 'use_default' }
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const ValueDisplay = (): JSX.Element => {
|
|
20
|
+
const { values } = useFormikContext<{ enabled: string }>();
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Typography data-testid="value">
|
|
24
|
+
{JSON.stringify(values.enabled)}
|
|
25
|
+
</Typography>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
interface InitializeProps {
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
enabled?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const initialize = ({
|
|
35
|
+
enabled = 'use_default',
|
|
36
|
+
disabled = false
|
|
37
|
+
}: InitializeProps = {}): void => {
|
|
38
|
+
render(
|
|
39
|
+
<Form
|
|
40
|
+
initialValues={{ enabled }}
|
|
41
|
+
inputs={[
|
|
42
|
+
{
|
|
43
|
+
dataTestId: 'enabled',
|
|
44
|
+
fieldName: 'enabled',
|
|
45
|
+
getDisabled: () => disabled,
|
|
46
|
+
group: '',
|
|
47
|
+
label: 'Notification Enabled',
|
|
48
|
+
segmentedButtons: { options },
|
|
49
|
+
type: InputType.SegmentedButtons
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
custom: { Component: ValueDisplay },
|
|
53
|
+
fieldName: 'value',
|
|
54
|
+
group: '',
|
|
55
|
+
label: 'value',
|
|
56
|
+
type: InputType.Custom
|
|
57
|
+
}
|
|
58
|
+
]}
|
|
59
|
+
submit={rs.fn()}
|
|
60
|
+
validationSchema={object()}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const button = (label: string): Promise<HTMLElement> =>
|
|
66
|
+
screen.findByRole('button', { name: label });
|
|
67
|
+
|
|
68
|
+
describe('Segmented buttons', () => {
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
expect.hasAssertions();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('displays every option and marks the current one', async () => {
|
|
74
|
+
initialize();
|
|
75
|
+
|
|
76
|
+
expect(await screen.findByText('Notification Enabled')).toBeVisible();
|
|
77
|
+
expect(await button('Yes')).toHaveAttribute('aria-pressed', 'false');
|
|
78
|
+
expect(await button('No')).toHaveAttribute('aria-pressed', 'false');
|
|
79
|
+
expect(await button('Default')).toHaveAttribute('aria-pressed', 'true');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('stores the value of the clicked option as given', async () => {
|
|
83
|
+
initialize();
|
|
84
|
+
|
|
85
|
+
await userEvent.click(await button('Yes'));
|
|
86
|
+
|
|
87
|
+
// A string, not the boolean the radio would store.
|
|
88
|
+
await waitFor(() => {
|
|
89
|
+
expect(screen.getByTestId('value')).toHaveTextContent(/^"true"$/);
|
|
90
|
+
});
|
|
91
|
+
expect(await button('Yes')).toHaveAttribute('aria-pressed', 'true');
|
|
92
|
+
expect(await button('Default')).toHaveAttribute('aria-pressed', 'false');
|
|
93
|
+
|
|
94
|
+
await userEvent.click(await button('No'));
|
|
95
|
+
|
|
96
|
+
await waitFor(() => {
|
|
97
|
+
expect(screen.getByTestId('value')).toHaveTextContent(/^"false"$/);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('starts on the initial value', async () => {
|
|
102
|
+
initialize({ enabled: 'false' });
|
|
103
|
+
|
|
104
|
+
expect(await button('No')).toHaveAttribute('aria-pressed', 'true');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('does not change when disabled', async () => {
|
|
108
|
+
initialize({ disabled: true });
|
|
109
|
+
|
|
110
|
+
expect(await button('Yes')).toBeDisabled();
|
|
111
|
+
expect(screen.getByTestId('value')).toHaveTextContent(/^"use_default"$/);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -26,7 +26,6 @@ import {
|
|
|
26
26
|
toPairs
|
|
27
27
|
} from 'ramda';
|
|
28
28
|
import { Fragment, useMemo } from 'react';
|
|
29
|
-
import { makeStyles } from 'tss-react/mui';
|
|
30
29
|
|
|
31
30
|
import { GroupDirection } from '..';
|
|
32
31
|
import CollapsibleGroup from '../CollapsibleGroup';
|
|
@@ -104,42 +103,6 @@ export const getInput = cond<
|
|
|
104
103
|
[T, always(TextInput)]
|
|
105
104
|
]);
|
|
106
105
|
|
|
107
|
-
interface StylesProps {
|
|
108
|
-
groupDirection?: GroupDirection;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const useStyles = makeStyles<StylesProps>()((theme, { groupDirection }) => ({
|
|
112
|
-
additionalLabel: {
|
|
113
|
-
marginBottom: theme.spacing(0.5)
|
|
114
|
-
},
|
|
115
|
-
buttons: {
|
|
116
|
-
columnGap: theme.spacing(2),
|
|
117
|
-
display: 'flex',
|
|
118
|
-
flexDirection: 'row',
|
|
119
|
-
justifyContent: 'flex-end'
|
|
120
|
-
},
|
|
121
|
-
divider: {
|
|
122
|
-
margin: equals(groupDirection, GroupDirection.Horizontal)
|
|
123
|
-
? theme.spacing(0, 2)
|
|
124
|
-
: theme.spacing(2, 0)
|
|
125
|
-
},
|
|
126
|
-
groups: {
|
|
127
|
-
display: 'flex',
|
|
128
|
-
flexDirection: equals(groupDirection, GroupDirection.Horizontal)
|
|
129
|
-
? 'row'
|
|
130
|
-
: 'column'
|
|
131
|
-
},
|
|
132
|
-
inputs: {
|
|
133
|
-
display: 'flex',
|
|
134
|
-
flexDirection: 'column',
|
|
135
|
-
margin: theme.spacing(2, 0),
|
|
136
|
-
rowGap: theme.spacing(2)
|
|
137
|
-
},
|
|
138
|
-
inputWrapper: {
|
|
139
|
-
width: '100%'
|
|
140
|
-
}
|
|
141
|
-
}));
|
|
142
|
-
|
|
143
106
|
interface Props {
|
|
144
107
|
areGroupsOpen?: boolean;
|
|
145
108
|
groupDirection?: GroupDirection;
|
|
@@ -161,7 +124,7 @@ const Inputs = ({
|
|
|
161
124
|
areGroupsOpen,
|
|
162
125
|
inputsClassName
|
|
163
126
|
}: Props): JSX.Element => {
|
|
164
|
-
const
|
|
127
|
+
const isHorizontal = equals(groupDirection, GroupDirection.Horizontal);
|
|
165
128
|
const formikContext = useFormikContext<FormikValues>();
|
|
166
129
|
|
|
167
130
|
const groupsName = pluck('name', groups);
|
|
@@ -217,7 +180,7 @@ const Inputs = ({
|
|
|
217
180
|
) as Array<[string | null, Array<InputProps>]>;
|
|
218
181
|
|
|
219
182
|
return (
|
|
220
|
-
<div className={
|
|
183
|
+
<div className={`flex ${isHorizontal ? 'flex-row' : 'flex-col'}`}>
|
|
221
184
|
{normalizedInputsByGroup.map(([groupName, groupedInputs], index) => {
|
|
222
185
|
const hasGroupTitle = not(isNil(groupName));
|
|
223
186
|
|
|
@@ -238,7 +201,9 @@ const Inputs = ({
|
|
|
238
201
|
hasGroupTitle={hasGroupTitle}
|
|
239
202
|
isCollapsible={isCollapsible}
|
|
240
203
|
>
|
|
241
|
-
<div
|
|
204
|
+
<div
|
|
205
|
+
className={`flex flex-col my-4 gap-y-4 ${inputsClassName || ''}`}
|
|
206
|
+
>
|
|
242
207
|
{groupedInputs.map((inputProps) => {
|
|
243
208
|
const key =
|
|
244
209
|
isNotNil(inputProps.label) || isNotEmpty(inputProps.label)
|
|
@@ -257,14 +222,11 @@ const Inputs = ({
|
|
|
257
222
|
const Input = getInput(inputProps.type);
|
|
258
223
|
|
|
259
224
|
return (
|
|
260
|
-
<div className=
|
|
225
|
+
<div className="w-full" key={key as string}>
|
|
261
226
|
{inputProps.additionalLabel && (
|
|
262
227
|
<Typography
|
|
263
|
-
className={
|
|
264
|
-
|
|
265
|
-
inputProps?.additionalLabelClassName
|
|
266
|
-
)}
|
|
267
|
-
variant="body1"
|
|
228
|
+
className={`mb-1 text-base font-bold text-text-primary ${inputProps?.additionalLabelClassName || ''}`}
|
|
229
|
+
variant="h6"
|
|
268
230
|
>
|
|
269
231
|
{inputProps.additionalLabel}
|
|
270
232
|
</Typography>
|
|
@@ -282,13 +244,9 @@ const Inputs = ({
|
|
|
282
244
|
hasGroupTitle &&
|
|
283
245
|
not(equals(lastGroup, groupName as string)) && (
|
|
284
246
|
<Divider
|
|
285
|
-
className={
|
|
247
|
+
className={isHorizontal ? 'my-0 mx-4' : 'my-4 mx-0'}
|
|
286
248
|
flexItem
|
|
287
|
-
orientation={
|
|
288
|
-
equals(groupDirection, GroupDirection.Horizontal)
|
|
289
|
-
? 'vertical'
|
|
290
|
-
: 'horizontal'
|
|
291
|
-
}
|
|
249
|
+
orientation={isHorizontal ? 'vertical' : 'horizontal'}
|
|
292
250
|
/>
|
|
293
251
|
)}
|
|
294
252
|
</Fragment>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { rs } from '@rstest/core';
|
|
2
|
+
import { render } from '@test-setup/render';
|
|
3
|
+
import { waitFor } from '@testing-library/react';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
|
|
6
|
+
import NumberField, { type NumberProps } from './Number';
|
|
7
|
+
|
|
8
|
+
const initialize = (props: NumberProps): void => {
|
|
9
|
+
render(<NumberField {...props} />);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const getInput = (): HTMLInputElement =>
|
|
13
|
+
document.querySelector('input') as HTMLInputElement;
|
|
14
|
+
|
|
15
|
+
const getFieldWidth = (): number =>
|
|
16
|
+
(
|
|
17
|
+
getInput().closest('.MuiTextField-root') as HTMLElement
|
|
18
|
+
).getBoundingClientRect().width;
|
|
19
|
+
|
|
20
|
+
describe('Number field', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
expect.hasAssertions();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('displays the placeholder when the field has no default or fallback values', () => {
|
|
26
|
+
initialize({ dataTestId: 'test', onChange: rs.fn() });
|
|
27
|
+
|
|
28
|
+
expect(getInput()).toHaveValue(null);
|
|
29
|
+
expect(getInput()).toHaveAttribute('placeholder', '0');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('displays the fallback value as placeholder when the field as no default value', () => {
|
|
33
|
+
initialize({ dataTestId: 'test', fallbackValue: 25, onChange: rs.fn() });
|
|
34
|
+
|
|
35
|
+
expect(getInput()).toHaveValue(null);
|
|
36
|
+
expect(getInput()).toHaveAttribute('placeholder', '25');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('displays the default value as placeholder when the field as default value', () => {
|
|
40
|
+
initialize({ dataTestId: 'test', defaultValue: 25, onChange: rs.fn() });
|
|
41
|
+
|
|
42
|
+
expect(getInput()).toHaveValue(25);
|
|
43
|
+
// The value only: no placeholder behind it.
|
|
44
|
+
expect(getInput()).not.toHaveAttribute('placeholder');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('displays the fallback value when the field is cleared out', async () => {
|
|
48
|
+
const onChange = rs.fn();
|
|
49
|
+
|
|
50
|
+
initialize({
|
|
51
|
+
dataTestId: 'test',
|
|
52
|
+
defaultValue: 25,
|
|
53
|
+
fallbackValue: 10,
|
|
54
|
+
onChange
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(getInput()).toHaveValue(25);
|
|
58
|
+
await userEvent.clear(getInput());
|
|
59
|
+
await waitFor(() => {
|
|
60
|
+
expect(getInput()).toHaveValue(null);
|
|
61
|
+
});
|
|
62
|
+
expect(getInput()).toHaveAttribute('placeholder', '10');
|
|
63
|
+
expect(onChange).toHaveBeenLastCalledWith(10);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('displays the field using auto size', async () => {
|
|
67
|
+
initialize({
|
|
68
|
+
autoSize: true,
|
|
69
|
+
dataTestId: 'test',
|
|
70
|
+
defaultValue: 25,
|
|
71
|
+
onChange: rs.fn()
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(getInput()).toHaveValue(25);
|
|
75
|
+
|
|
76
|
+
await userEvent.type(getInput(), '0');
|
|
77
|
+
|
|
78
|
+
await waitFor(() => {
|
|
79
|
+
expect(getInput()).toHaveValue(250);
|
|
80
|
+
});
|
|
81
|
+
// A narrow field sized on its value, where a field without auto size has
|
|
82
|
+
// the 178px default width.
|
|
83
|
+
expect(getFieldWidth()).toBeLessThan(100);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('sets the value to the minimum value configured when a value less than the minimum value is typed in the field', async () => {
|
|
87
|
+
initialize({
|
|
88
|
+
dataTestId: 'test',
|
|
89
|
+
defaultValue: 25,
|
|
90
|
+
onChange: rs.fn(),
|
|
91
|
+
textFieldSlotsAndSlotProps: {
|
|
92
|
+
slotProps: {
|
|
93
|
+
htmlInput: {
|
|
94
|
+
min: 2
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(getInput()).toHaveValue(25);
|
|
101
|
+
|
|
102
|
+
await userEvent.clear(getInput());
|
|
103
|
+
await userEvent.type(getInput(), '-1');
|
|
104
|
+
|
|
105
|
+
await waitFor(() => {
|
|
106
|
+
expect(getInput()).toHaveValue(2);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|