@scality/core-ui 0.162.0 → 0.163.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.
Files changed (68) hide show
  1. package/dist/components/barchartv2/Barchart.component.d.ts +0 -2
  2. package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
  3. package/dist/components/barchartv2/Barchart.component.js +11 -1
  4. package/dist/components/barchartv2/utils.d.ts +25 -2
  5. package/dist/components/barchartv2/utils.d.ts.map +1 -1
  6. package/dist/components/barchartv2/utils.js +35 -3
  7. package/dist/components/chartlegend/ChartLegend.d.ts +8 -0
  8. package/dist/components/chartlegend/ChartLegend.d.ts.map +1 -0
  9. package/dist/components/chartlegend/ChartLegend.js +65 -0
  10. package/dist/components/chartlegend/ChartLegendWrapper.d.ts +17 -0
  11. package/dist/components/chartlegend/ChartLegendWrapper.d.ts.map +1 -0
  12. package/dist/components/chartlegend/ChartLegendWrapper.js +50 -0
  13. package/dist/components/date/FormattedDateTime.d.ts +3 -1
  14. package/dist/components/date/FormattedDateTime.d.ts.map +1 -1
  15. package/dist/components/date/FormattedDateTime.js +19 -1
  16. package/dist/components/date/FormattedDateTime.spec.js +12 -0
  17. package/dist/components/icon/Icon.component.d.ts +5 -5
  18. package/dist/components/icon/Icon.component.d.ts.map +1 -1
  19. package/dist/components/icon/Icon.component.js +33 -31
  20. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts +33 -0
  21. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts.map +1 -0
  22. package/dist/components/linetimeseriechart/linetimeseriechart.component.js +249 -0
  23. package/dist/components/selectv2/Selectv2.component.d.ts.map +1 -1
  24. package/dist/components/selectv2/Selectv2.component.js +11 -6
  25. package/dist/components/steppers/Stepper.component.d.ts.map +1 -1
  26. package/dist/components/steppers/Stepper.component.js +9 -8
  27. package/dist/components/toast/ToastProvider.d.ts.map +1 -1
  28. package/dist/components/toast/ToastProvider.js +4 -5
  29. package/dist/components/vegachartv2/SyncedCursorCharts.d.ts.map +1 -1
  30. package/dist/components/vegachartv2/SyncedCursorCharts.js +3 -5
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1 -0
  34. package/dist/next.d.ts +1 -0
  35. package/dist/next.d.ts.map +1 -1
  36. package/dist/next.js +1 -0
  37. package/dist/style/theme.d.ts +1 -0
  38. package/dist/style/theme.d.ts.map +1 -1
  39. package/dist/style/theme.js +28 -0
  40. package/package.json +2 -2
  41. package/src/lib/components/accordion/Accordion.test.tsx +7 -15
  42. package/src/lib/components/barchartv2/Barchart.component.test.tsx +82 -101
  43. package/src/lib/components/barchartv2/Barchart.component.tsx +14 -2
  44. package/src/lib/components/barchartv2/utils.test.ts +117 -0
  45. package/src/lib/components/barchartv2/utils.ts +54 -6
  46. package/src/lib/components/chartlegend/ChartLegend.tsx +113 -0
  47. package/src/lib/components/chartlegend/ChartLegendWrapper.tsx +85 -0
  48. package/src/lib/components/date/FormattedDateTime.spec.tsx +24 -0
  49. package/src/lib/components/date/FormattedDateTime.tsx +36 -2
  50. package/src/lib/components/healthselectorv2/HealthSelector.component.test.tsx +3 -3
  51. package/src/lib/components/icon/Icon.component.tsx +48 -60
  52. package/src/lib/components/inlineinput/InlineInput.test.tsx +22 -19
  53. package/src/lib/components/inputlist/InputList.test.tsx +21 -19
  54. package/src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx +502 -0
  55. package/src/lib/components/searchinput/SearchInput.test.tsx +3 -7
  56. package/src/lib/components/selectv2/Selectv2.component.tsx +13 -5
  57. package/src/lib/components/selectv2/selectv2.test.tsx +62 -57
  58. package/src/lib/components/steppers/Stepper.component.tsx +10 -8
  59. package/src/lib/components/tablev2/TableSync.test.tsx +8 -11
  60. package/src/lib/components/tablev2/Tablev2.test.tsx +36 -37
  61. package/src/lib/components/toast/ToastProvider.tsx +14 -6
  62. package/src/lib/components/vegachartv2/SyncedCursorCharts.tsx +5 -7
  63. package/src/lib/index.ts +1 -0
  64. package/src/lib/next.ts +1 -0
  65. package/src/lib/style/theme.ts +29 -0
  66. package/stories/BarChart/barchart.stories.tsx +292 -125
  67. package/stories/format.mdx +4 -2
  68. package/stories/linetimeseriechart.stories.tsx +485 -0
@@ -0,0 +1,85 @@
1
+ import { createContext, useContext, useState, ReactNode, useMemo, useCallback } from 'react';
2
+ import { ChartColors } from '../../style/theme';
3
+
4
+ export type ChartLegendState = {
5
+ selectedResources: string[];
6
+ addSelectedResource: (resource: string) => void;
7
+ removeSelectedResource: (resource: string) => void;
8
+ isSelected: (resource: string) => boolean;
9
+ getColor: (resource: string) => string | undefined;
10
+ listResources: () => string[];
11
+ };
12
+
13
+ const ChartLegendContext = createContext<ChartLegendState | null>(null);
14
+
15
+ export type ChartLegendWrapperProps = {
16
+ children: ReactNode;
17
+ colorSet: Record<string, ChartColors | string>;
18
+ };
19
+
20
+ export const ChartLegendWrapper = ({
21
+ children,
22
+ colorSet,
23
+ }: ChartLegendWrapperProps) => {
24
+ const [selectedResources, setSelectedResources] = useState<string[]>([]);
25
+
26
+ const addSelectedResource = useCallback((resource: string) => {
27
+ setSelectedResources((prev) =>
28
+ prev.includes(resource) ? prev : [...prev, resource],
29
+ );
30
+ }, []);
31
+
32
+ const removeSelectedResource = useCallback((resource: string) => {
33
+ setSelectedResources((prev) => prev.filter((r) => r !== resource));
34
+ }, []);
35
+
36
+ const isSelected = useCallback((resource: string) => {
37
+ return selectedResources.includes(resource);
38
+ }, [selectedResources]);
39
+
40
+ const getColor = useCallback((resource: string) => {
41
+ const color = colorSet[resource];
42
+ if (!color) {
43
+ console.warn(
44
+ `ChartLegendWrapper: No color defined for resource "${resource}"`,
45
+ );
46
+ return undefined;
47
+ }
48
+ return color;
49
+ }, [colorSet]);
50
+
51
+ const listResources = useCallback(() => {
52
+ return Object.keys(colorSet);
53
+ }, [colorSet]);
54
+
55
+ const chartLegendState = useMemo(() => ({
56
+ selectedResources,
57
+ addSelectedResource,
58
+ removeSelectedResource,
59
+ isSelected,
60
+ getColor,
61
+ listResources,
62
+ }), [
63
+ selectedResources,
64
+ addSelectedResource,
65
+ removeSelectedResource,
66
+ isSelected,
67
+ getColor,
68
+ listResources,
69
+ ]);
70
+
71
+ return (
72
+ <ChartLegendContext.Provider value={chartLegendState}>
73
+ {children}
74
+ </ChartLegendContext.Provider>
75
+ );
76
+ };
77
+
78
+ // Hook for accessing legend state in custom components
79
+ export const useChartLegend = () => {
80
+ const context = useContext(ChartLegendContext);
81
+ if (!context) {
82
+ throw new Error('useChartLegend must be used within a ChartLegendWrapper');
83
+ }
84
+ return context;
85
+ };
@@ -214,4 +214,28 @@ describe('FormatttedDateTime', () => {
214
214
  //V
215
215
  expect(screen.getByText('2022-12-12 11:57:26')).toBeInTheDocument();
216
216
  });
217
+
218
+ it('should display the date in the expected format of the xaxis tick in the chart', () => {
219
+ //S
220
+ render(
221
+ <FormattedDateTime
222
+ format="day-month-abbreviated-hour-minute"
223
+ value={new Date('2022-10-06T18:33:00Z')}
224
+ />,
225
+ );
226
+ //V
227
+ expect(screen.getByText('6 Oct 18:33')).toBeInTheDocument();
228
+ });
229
+
230
+ it('should display the date in the expected format of date in the chart', () => {
231
+ //S
232
+ render(
233
+ <FormattedDateTime
234
+ format="day-month-abbreviated-hour-minute-second"
235
+ value={new Date('2022-10-06T18:33:00Z')}
236
+ />,
237
+ );
238
+ //V
239
+ expect(screen.getByText('6 Oct 18:33:00')).toBeInTheDocument();
240
+ });
217
241
  });
@@ -27,6 +27,26 @@ export const TIME_FORMATER = Intl.DateTimeFormat('en-GB', {
27
27
  minute: '2-digit',
28
28
  });
29
29
 
30
+ export const DAY_MONTH_ABBREVIATED_HOUR_MINUTE_SECOND = Intl.DateTimeFormat(
31
+ 'en-GB',
32
+ {
33
+ day: 'numeric',
34
+ month: 'short',
35
+ hour: '2-digit',
36
+ minute: '2-digit',
37
+ second: '2-digit',
38
+ hour12: false,
39
+ },
40
+ );
41
+
42
+ export const DAY_MONTH_ABBREVIATED_HOUR_MINUTE = Intl.DateTimeFormat('en-GB', {
43
+ day: 'numeric',
44
+ month: 'short',
45
+ hour: '2-digit',
46
+ minute: '2-digit',
47
+ hour12: false,
48
+ });
49
+
30
50
  type FormattedDateTimeProps = {
31
51
  format:
32
52
  | 'date'
@@ -34,7 +54,9 @@ type FormattedDateTimeProps = {
34
54
  | 'date-time-second'
35
55
  | 'time'
36
56
  | 'time-second'
37
- | 'relative';
57
+ | 'relative'
58
+ | 'day-month-abbreviated-hour-minute'
59
+ | 'day-month-abbreviated-hour-minute-second';
38
60
  value: Date;
39
61
  };
40
62
 
@@ -149,7 +171,19 @@ export const FormattedDateTime = ({
149
171
  few seconds ago
150
172
  </Tooltip>
151
173
  );
152
- //TO FINISH
174
+ case 'day-month-abbreviated-hour-minute':
175
+ return (
176
+ <>{DAY_MONTH_ABBREVIATED_HOUR_MINUTE.format(value).replace(',', '')}</>
177
+ );
178
+ case 'day-month-abbreviated-hour-minute-second':
179
+ return (
180
+ <>
181
+ {DAY_MONTH_ABBREVIATED_HOUR_MINUTE_SECOND.format(value).replace(
182
+ ',',
183
+ '',
184
+ )}
185
+ </>
186
+ );
153
187
  default:
154
188
  return <></>;
155
189
  }
@@ -3,18 +3,18 @@ import {
3
3
  optionsDefaultConfiguration,
4
4
  } from './HealthSelector.component';
5
5
  import React from 'react';
6
- import { render, screen } from '@testing-library/react';
6
+ import { render, screen, waitFor } from '@testing-library/react';
7
7
  import userEvent from '@testing-library/user-event';
8
- import { QueryClient, QueryClientProvider } from 'react-query';
9
8
  import { getWrapper } from '../../testUtils';
10
9
  describe('HealthSelector', () => {
11
- it('should display correctly without any props and select first option', () => {
10
+ it('should display correctly without any props and select first option', async () => {
12
11
  const { Wrapper } = getWrapper();
13
12
  const { getByText } = render(
14
13
  <Wrapper>
15
14
  <HealthSelector id="health" onChange={() => {}} />
16
15
  </Wrapper>,
17
16
  );
17
+ await waitFor(() => screen.findByRole('img', { hidden: true }));
18
18
  const input = screen.getByRole('textbox');
19
19
 
20
20
  // open the menu
@@ -7,12 +7,14 @@ import {
7
7
  useEffect,
8
8
  useState,
9
9
  } from 'react';
10
- import { useQuery } from 'react-query';
11
10
  import styled, { css } from 'styled-components';
12
11
  import { CoreUITheme } from '../../style/theme';
13
12
  import { Loader } from '../loader/Loader.component';
14
13
  import { RemoteGroup, RemoteUser } from './CustomsIcons';
15
14
 
15
+ // Module-level cache for imported icons
16
+ const iconCache: Record<string, any> = {};
17
+
16
18
  export const iconTable = {
17
19
  Account: 'fas faWallet',
18
20
  Backend: 'fas faNetworkWired',
@@ -141,10 +143,10 @@ export const iconTable = {
141
143
  };
142
144
 
143
145
  export const customIcons = {
144
- 'Remote-user': ({ ariaLabel, color, size }) => (
146
+ 'Remote-user': ({ 'aria-label': ariaLabel, color, size }) => (
145
147
  <RemoteUser ariaLabel={ariaLabel} color={color} size={size} />
146
148
  ),
147
- 'Remote-group': ({ ariaLabel, color, size }) => (
149
+ 'Remote-group': ({ 'aria-label': ariaLabel, color, size }) => (
148
150
  <RemoteGroup ariaLabel={ariaLabel} color={color} size={size} />
149
151
  ),
150
152
  };
@@ -169,7 +171,7 @@ type Props = {
169
171
  ariaLabel?: string;
170
172
  withWrapper?: boolean;
171
173
  style?: CSSProperties;
172
- onClick?: (event: MouseEvent) => void;
174
+ onClick?: (event: React.MouseEvent) => void;
173
175
  title?: string;
174
176
  };
175
177
 
@@ -226,7 +228,7 @@ export const IconWrapper = styled.div<{ size: SizeProp }>`
226
228
  function NonWrappedIcon({
227
229
  name,
228
230
  size = '1x',
229
- color = undefined,
231
+ color,
230
232
  ariaLabel = '',
231
233
  title,
232
234
  ...rest
@@ -234,63 +236,49 @@ function NonWrappedIcon({
234
236
  const iconInfo = iconTable[name] || customIcons[name];
235
237
  if (!iconInfo) throw new Error(`${name}: is not a valid icon.`);
236
238
 
237
- const { data, status } = useQuery({
238
- queryKey: ['icon', name],
239
- queryFn: async () => {
240
- if (customIcons[name]) {
241
- return {
242
- default: customIcons[name],
243
- };
244
- }
245
- const [iconType, iconClass] = iconInfo.split(' ');
246
- try {
247
- const fontAwesomeType =
248
- iconType === 'far'
249
- ? 'free-regular-svg-icons'
250
- : 'free-solid-svg-icons';
251
- const icon = await import(
252
- `@fortawesome/${fontAwesomeType}/${iconClass}.js`
253
- );
254
- return {
255
- default: ({ name, color, size, ariaLabel, ...rest }) => (
256
- <IconStyled
257
- color={color}
258
- icon={icon[iconClass]}
259
- size={size}
260
- aria-label={`${name} ${ariaLabel}`}
261
- {...rest}
262
- />
263
- ),
264
- };
265
- } catch {
266
- return {
267
- default: ({ name, ariaLabel }) => (
268
- <Loader size="base" aria-label={`${name} ${ariaLabel}`} />
269
- ),
270
- };
271
- }
272
- },
273
- });
239
+ // Loaded fortawesome icon if not a custom icon
240
+ const [icon, setIcon] = useState();
274
241
 
275
- return (
276
- <>
277
- {(status === 'loading' || status === 'error') && (
278
- <DelayedFallback aria-label={`${name} ${ariaLabel}`}>
279
- <Loader size="base" />
280
- </DelayedFallback>
281
- )}
242
+ useEffect(() => {
243
+ if (customIcons[name]) {
244
+ return;
245
+ }
246
+
247
+ const [iconType, iconClass] = iconInfo.split(' ');
248
+ const fontAwesomeType = iconType === 'far' ? 'free-regular-svg-icons' : 'free-solid-svg-icons';
249
+ const cacheKey = `${fontAwesomeType}/${iconClass}`;
250
+ if (iconCache[cacheKey]) {
251
+ setIcon(iconCache[cacheKey]);
252
+ return () => setIcon(undefined);
253
+ }
282
254
 
283
- {status === 'success' && (
284
- <data.default
285
- name={name}
286
- color={color}
287
- size={size}
288
- ariaLabel={ariaLabel}
289
- title={title}
290
- {...rest}
291
- />
292
- )}
293
- </>
255
+ // Handle FontAwesome icons with dynamic import
256
+ import(`@fortawesome/${fontAwesomeType}/${iconClass}.js`)
257
+ .then((module) => {
258
+ setIcon(module[iconClass]);
259
+ iconCache[cacheKey] = module[iconClass];
260
+ });
261
+ return () => setIcon(undefined);
262
+ }, [name, iconInfo]);
263
+
264
+ if (!icon && !customIcons[name]) {
265
+ return (
266
+ <DelayedFallback aria-label={`${name} ${ariaLabel}`}>
267
+ <Loader size="base" />
268
+ </DelayedFallback>
269
+ );
270
+ }
271
+
272
+ const IconComponent = customIcons[name] ?? IconStyled;
273
+ return (
274
+ <IconComponent
275
+ color={color}
276
+ icon={icon}
277
+ size={size}
278
+ title={title}
279
+ aria-label={`${name} ${ariaLabel}`}
280
+ {...rest}
281
+ />
294
282
  );
295
283
  }
296
284
 
@@ -7,6 +7,7 @@ import {
7
7
  } from 'react-query';
8
8
  import { ToastProvider } from '../toast/ToastProvider';
9
9
  import {
10
+ act,
10
11
  render,
11
12
  screen,
12
13
  waitFor,
@@ -68,17 +69,18 @@ describe('InlineInput', () => {
68
69
  </ChangeMutationProvider>,
69
70
  { wrapper: Wrapper },
70
71
  );
72
+ await waitFor(() => screen.findByRole('img', { hidden: true }));
71
73
 
72
74
  //E
73
75
  /// First focus the edit button
74
76
  await userEvent.tab();
75
77
  /// Then press enter to edit the input
76
- await userEvent.keyboard('{enter}');
78
+ await act(() => userEvent.keyboard('{enter}'));
77
79
  /// Then type a new value
78
- await userEvent.type(document.activeElement, 'new value');
80
+ await act(() => userEvent.type(document.activeElement, 'new value'));
79
81
  /// Then press enter to confirm the new value
80
- await userEvent.keyboard('{enter}');
81
- await waitForElementToBeRemoved(() => screen.getByRole('textbox'));
82
+ await act(() => userEvent.keyboard('{enter}'));
83
+ expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
82
84
 
83
85
  //V
84
86
  expect(mock).toHaveBeenCalledWith('testnew value');
@@ -105,24 +107,23 @@ describe('InlineInput', () => {
105
107
  </ChangeMutationProvider>,
106
108
  { wrapper: Wrapper },
107
109
  );
110
+ await waitFor(() => screen.findByRole('img', { hidden: true }));
108
111
 
109
112
  //E
110
113
  /// First focus the edit button
111
114
  await userEvent.tab();
112
115
  /// Then press enter to edit the input
113
- await userEvent.keyboard('{enter}');
116
+ await act(() => userEvent.keyboard('{enter}'));
114
117
  /// Then type a new value
115
- await userEvent.type(document.activeElement, 'new value');
118
+ await act(() => userEvent.type(document.activeElement, 'new value'));
116
119
  /// Then press enter to confirm the new value
117
- await userEvent.keyboard('{enter}');
120
+ await act(() => userEvent.keyboard('{enter}'));
118
121
  /// Expect the confirmation modal to be opened
119
- await waitFor(() =>
120
- expect(selectors.confirmationModal()).toBeInTheDocument(),
121
- );
122
+ expect(selectors.confirmationModal()).toBeInTheDocument()
122
123
  /// Click the confirm button
123
- await userEvent.click(screen.getByRole('button', { name: /confirm/i }));
124
- /// Wait for modal to be closed
125
- await waitForElementToBeRemoved(() => selectors.confirmationModal());
124
+ await act(() => userEvent.click(screen.getByRole('button', { name: /confirm/i })));
125
+ /// modal should be closed
126
+ expect(screen.queryByRole('dialog', { name: /Confirm/i })).not.toBeInTheDocument();
126
127
 
127
128
  //V
128
129
  expect(mock).toHaveBeenCalledWith('testnew value');
@@ -147,16 +148,17 @@ describe('InlineInput', () => {
147
148
  </ChangeMutationProvider>,
148
149
  { wrapper: Wrapper },
149
150
  );
151
+ await waitFor(() => screen.findByRole('img', { hidden: true }));
150
152
 
151
153
  //E
152
154
  /// First focus the edit button
153
155
  await userEvent.tab();
154
156
  /// Then press enter to edit the input
155
- await userEvent.keyboard('{enter}');
157
+ await act(() => userEvent.keyboard('{enter}'));
156
158
  /// Then type a new value
157
- await userEvent.type(document.activeElement, 'new value');
159
+ await act(() => userEvent.type(document.activeElement, 'new value'));
158
160
  /// Then press escape to cancel the new value
159
- await userEvent.keyboard('{esc}');
161
+ await act(() => userEvent.keyboard('{esc}'));
160
162
 
161
163
  //V
162
164
  expect(mock).not.toHaveBeenCalled();
@@ -181,16 +183,17 @@ describe('InlineInput', () => {
181
183
  </ChangeMutationProvider>,
182
184
  { wrapper: Wrapper },
183
185
  );
186
+ await waitFor(() => screen.findByRole('img', { hidden: true }));
184
187
 
185
188
  //E
186
189
  /// First focus the edit button
187
190
  await userEvent.tab();
188
191
  /// Then press enter to edit the input
189
- await userEvent.keyboard('{enter}');
192
+ await act(() => userEvent.keyboard('{enter}'));
190
193
  /// Then type a new value
191
- await userEvent.type(document.activeElement, 'new value');
194
+ await act(() => userEvent.type(document.activeElement, 'new value'));
192
195
  /// Then press enter to confirm the new value
193
- await userEvent.keyboard('{enter}');
196
+ await act(() => userEvent.keyboard('{enter}'));
194
197
  /// Expect the confirmation modal to be opened
195
198
  await waitFor(() =>
196
199
  expect(selectors.confirmationModal()).toBeInTheDocument(),
@@ -1,24 +1,21 @@
1
1
  import React from 'react';
2
- import { render, screen, fireEvent } from '@testing-library/react';
2
+ import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
3
3
  import { InputList, InputListProps } from './InputList.component';
4
4
  import { FormSection } from '../form/Form.component';
5
- import { QueryClient, QueryClientProvider } from 'react-query';
6
5
 
7
6
  describe('InputList', () => {
8
7
  const onChangeMock = jest.fn();
9
8
 
10
9
  const renderInputList = (props: InputListProps<string[]>) => {
11
10
  render(
12
- <QueryClientProvider client={new QueryClient()}>
13
- <FormSection>
14
- <InputList
15
- placeholder="Input list Test"
16
- onChange={onChangeMock}
17
- value={props.value}
18
- name="inputListTest"
19
- />
20
- </FormSection>
21
- </QueryClientProvider>,
11
+ <FormSection>
12
+ <InputList
13
+ placeholder="Input list Test"
14
+ onChange={onChangeMock}
15
+ value={props.value}
16
+ name="inputListTest"
17
+ />
18
+ </FormSection>
22
19
  );
23
20
  };
24
21
 
@@ -26,20 +23,22 @@ describe('InputList', () => {
26
23
  onChangeMock.mockClear();
27
24
  });
28
25
 
29
- it('should render an empty input list', () => {
26
+ it('should render an empty input list', async () => {
30
27
  renderInputList({
31
28
  value: [''],
32
29
  });
30
+ await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
33
31
 
34
32
  expect(screen.getByLabelText('inputListTest0')).toHaveValue('');
35
33
  });
36
34
 
37
- it('should render an input list with initial values', () => {
35
+ it('should render an input list with initial values', async () => {
38
36
  const initialValues = ['Value 1', 'Value 2', 'Value 3'];
39
37
 
40
38
  renderInputList({
41
39
  value: initialValues,
42
40
  });
41
+ await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
43
42
 
44
43
  const inputElements = screen.getAllByRole('textbox');
45
44
 
@@ -50,44 +49,47 @@ describe('InputList', () => {
50
49
  });
51
50
  });
52
51
 
53
- it('should add a new input when clicking the add button', () => {
52
+ it('should add a new input when clicking the add button', async () => {
54
53
  const initialValues = ['Value 1', 'Value 2'];
55
54
 
56
55
  renderInputList({
57
56
  value: initialValues,
58
57
  });
58
+ await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
59
59
 
60
60
  const addButton = screen.getByLabelText('Add1');
61
61
 
62
- fireEvent.click(addButton);
62
+ await act(() => fireEvent.click(addButton));
63
63
 
64
64
  expect(onChangeMock).toHaveBeenCalledWith({
65
65
  target: { value: [...initialValues, ''] },
66
66
  });
67
67
  });
68
68
 
69
- it('should delete an input when clicking the delete button', () => {
69
+ it('should delete an input when clicking the delete button', async () => {
70
70
  const initialValues = ['Value 1', 'Value 2', 'Value 3'];
71
71
 
72
72
  renderInputList({
73
73
  value: initialValues,
74
74
  });
75
+ await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
75
76
 
76
77
  const deleteButton = screen.getByLabelText('Remove1');
77
78
 
78
- fireEvent.click(deleteButton);
79
+ await act(() => fireEvent.click(deleteButton));
79
80
 
80
81
  expect(onChangeMock).toHaveBeenCalledWith({
81
82
  target: { value: ['Value 1', 'Value 3'] },
82
83
  });
83
84
  });
84
85
 
85
- it('should update the value of an input', () => {
86
+ it('should update the value of an input', async () => {
86
87
  const initialValues = ['Value 1', 'Value 2', 'Value 3'];
87
88
 
88
89
  renderInputList({
89
90
  value: initialValues,
90
91
  });
92
+ await waitFor(() => screen.queryAllByRole('img', { hidden: true }));
91
93
 
92
94
  const inputElements = screen.getAllByRole('textbox');
93
95