@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.
- package/dist/components/barchartv2/Barchart.component.d.ts +0 -2
- package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
- package/dist/components/barchartv2/Barchart.component.js +11 -1
- package/dist/components/barchartv2/utils.d.ts +25 -2
- package/dist/components/barchartv2/utils.d.ts.map +1 -1
- package/dist/components/barchartv2/utils.js +35 -3
- package/dist/components/chartlegend/ChartLegend.d.ts +8 -0
- package/dist/components/chartlegend/ChartLegend.d.ts.map +1 -0
- package/dist/components/chartlegend/ChartLegend.js +65 -0
- package/dist/components/chartlegend/ChartLegendWrapper.d.ts +17 -0
- package/dist/components/chartlegend/ChartLegendWrapper.d.ts.map +1 -0
- package/dist/components/chartlegend/ChartLegendWrapper.js +50 -0
- package/dist/components/date/FormattedDateTime.d.ts +3 -1
- package/dist/components/date/FormattedDateTime.d.ts.map +1 -1
- package/dist/components/date/FormattedDateTime.js +19 -1
- package/dist/components/date/FormattedDateTime.spec.js +12 -0
- package/dist/components/icon/Icon.component.d.ts +5 -5
- package/dist/components/icon/Icon.component.d.ts.map +1 -1
- package/dist/components/icon/Icon.component.js +33 -31
- package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts +33 -0
- package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts.map +1 -0
- package/dist/components/linetimeseriechart/linetimeseriechart.component.js +249 -0
- package/dist/components/selectv2/Selectv2.component.d.ts.map +1 -1
- package/dist/components/selectv2/Selectv2.component.js +11 -6
- package/dist/components/steppers/Stepper.component.d.ts.map +1 -1
- package/dist/components/steppers/Stepper.component.js +9 -8
- package/dist/components/toast/ToastProvider.d.ts.map +1 -1
- package/dist/components/toast/ToastProvider.js +4 -5
- package/dist/components/vegachartv2/SyncedCursorCharts.d.ts.map +1 -1
- package/dist/components/vegachartv2/SyncedCursorCharts.js +3 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/next.d.ts +1 -0
- package/dist/next.d.ts.map +1 -1
- package/dist/next.js +1 -0
- package/dist/style/theme.d.ts +1 -0
- package/dist/style/theme.d.ts.map +1 -1
- package/dist/style/theme.js +28 -0
- package/package.json +2 -2
- package/src/lib/components/accordion/Accordion.test.tsx +7 -15
- package/src/lib/components/barchartv2/Barchart.component.test.tsx +82 -101
- package/src/lib/components/barchartv2/Barchart.component.tsx +14 -2
- package/src/lib/components/barchartv2/utils.test.ts +117 -0
- package/src/lib/components/barchartv2/utils.ts +54 -6
- package/src/lib/components/chartlegend/ChartLegend.tsx +113 -0
- package/src/lib/components/chartlegend/ChartLegendWrapper.tsx +85 -0
- package/src/lib/components/date/FormattedDateTime.spec.tsx +24 -0
- package/src/lib/components/date/FormattedDateTime.tsx +36 -2
- package/src/lib/components/healthselectorv2/HealthSelector.component.test.tsx +3 -3
- package/src/lib/components/icon/Icon.component.tsx +48 -60
- package/src/lib/components/inlineinput/InlineInput.test.tsx +22 -19
- package/src/lib/components/inputlist/InputList.test.tsx +21 -19
- package/src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx +502 -0
- package/src/lib/components/searchinput/SearchInput.test.tsx +3 -7
- package/src/lib/components/selectv2/Selectv2.component.tsx +13 -5
- package/src/lib/components/selectv2/selectv2.test.tsx +62 -57
- package/src/lib/components/steppers/Stepper.component.tsx +10 -8
- package/src/lib/components/tablev2/TableSync.test.tsx +8 -11
- package/src/lib/components/tablev2/Tablev2.test.tsx +36 -37
- package/src/lib/components/toast/ToastProvider.tsx +14 -6
- package/src/lib/components/vegachartv2/SyncedCursorCharts.tsx +5 -7
- package/src/lib/index.ts +1 -0
- package/src/lib/next.ts +1 -0
- package/src/lib/style/theme.ts +29 -0
- package/stories/BarChart/barchart.stories.tsx +292 -125
- package/stories/format.mdx +4 -2
- 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
|
-
|
|
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
|
|
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
|
-
|
|
238
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
///
|
|
125
|
-
|
|
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
|
-
<
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|