@centreon/ui 26.10.8 → 26.10.10

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 (77) hide show
  1. package/package.json +6 -3
  2. package/public/mockServiceWorker.js +31 -8
  3. package/src/Dashboard/Dashboard.browser.test.tsx +107 -0
  4. package/src/Form/CollapsibleGroup.tsx +6 -33
  5. package/src/Form/Form.browser.test.tsx +553 -0
  6. package/src/Form/Form.stories.tsx +102 -1
  7. package/src/Form/Form.tsx +4 -3
  8. package/src/Form/Inputs/ExclusiveCheckboxGroup.browser.test.tsx +304 -0
  9. package/src/Form/Inputs/Grid.browser.test.tsx +186 -0
  10. package/src/Form/Inputs/Grid.tsx +59 -12
  11. package/src/Form/Inputs/PasswordInput.browser.test.tsx +80 -0
  12. package/src/Form/Inputs/SegmentedButtons.browser.test.tsx +197 -0
  13. package/src/Form/Inputs/SegmentedButtons.tsx +38 -29
  14. package/src/Form/Inputs/Text.tsx +13 -1
  15. package/src/Form/Inputs/index.tsx +10 -52
  16. package/src/Form/Inputs/models.ts +6 -0
  17. package/src/Form/Section/FormSection.browser.test.tsx +174 -0
  18. package/src/Form/Section/FormSection.tsx +20 -6
  19. package/src/Form/Section/navigateToSection.ts +26 -8
  20. package/src/Form/Section/useActiveSection.ts +213 -0
  21. package/src/InputField/Number/Number.browser.test.tsx +109 -0
  22. package/src/InputField/Select/Autocomplete/Connected/Multi/MultiConnectedAutocompleteField.browser.test.tsx +250 -0
  23. package/src/InputField/Select/Autocomplete/Connected/index.test.tsx +37 -0
  24. package/src/InputField/Select/Autocomplete/Connected/index.tsx +56 -45
  25. package/src/Listing/Cell/DataCell.tsx +0 -2
  26. package/src/Listing/Listing.browser.test.tsx +476 -0
  27. package/src/Module/Module.browser.test.tsx +145 -0
  28. package/src/Pagination/Pagination.browser.test.tsx +184 -0
  29. package/src/RichTextEditor/RichTextEditor.browser.test.tsx +567 -0
  30. package/src/RichTextEditor/plugins/FloatingLinkEditorPlugin.tsx +3 -1
  31. package/src/TimePeriods/ResolutionTimePeriod.browser.test.tsx +143 -0
  32. package/src/TimePeriods/TimePeriods.browser.test.tsx +468 -0
  33. package/src/Typography/FluidTypography/FluidTypography.browser.test.tsx +54 -0
  34. package/src/api/useMutationQuery/useMutationQuery.browser.test.tsx +154 -0
  35. package/src/components/CollapsibleItem/CollapsibleItem.browser.test.tsx +120 -0
  36. package/src/components/CopyCommand/CopyCommand.browser.test.tsx +287 -0
  37. package/src/components/CrudPage/CrudPage.browser.test.tsx +659 -0
  38. package/src/components/DataTable/DataTable.browser.test.tsx +257 -0
  39. package/src/components/Form/AccessRights/AccessRights.browser.test.tsx +527 -0
  40. package/src/components/Header/PageHeader/PageHeader.stories.tsx +9 -0
  41. package/src/components/Header/PageHeader/PageHeaderMain.tsx +4 -2
  42. package/src/components/Header/PageHeader/PageHeaderTitle.tsx +61 -18
  43. package/src/components/ItemComposition/ItemComposition.browser.test.tsx +172 -0
  44. package/src/components/Layout/PageLayout.browser.test.tsx +114 -0
  45. package/src/components/Modal/ConfirmationModal/ConfirmationModal.browser.test.tsx +237 -0
  46. package/src/components/SortableEntriesList/SortableEntriesList.browser.test.tsx +981 -0
  47. package/src/components/Tabs/Tabs.browser.test.tsx +115 -0
  48. package/src/components/Tabs/Tabs.tsx +7 -1
  49. package/src/utils/useFullscreen/Fullscreen.browser.test.tsx +176 -0
  50. package/src/utils/useLocale/useLocale.browser.test.tsx +49 -0
  51. package/src/utils/useLocaleTimezoneDate/{LocaleTimezoneDate.cypress.spec.tsx → LocaleTimezoneDate.browser.test.tsx} +24 -11
  52. package/src/Dashboard/Dashboard.cypress.spec.tsx +0 -71
  53. package/src/Form/Form.cypress.spec.tsx +0 -306
  54. package/src/Form/Inputs/ExclusiveCheckboxGroup.cypress.spec.tsx +0 -236
  55. package/src/Form/Inputs/SegmentedButtons.cypress.spec.tsx +0 -101
  56. package/src/InputField/Number/Number.cypress.spec.tsx +0 -89
  57. package/src/InputField/Select/Autocomplete/Connected/Multi/MultiConnectedAutocompleteField.cypress.spec.tsx +0 -182
  58. package/src/Listing/Listing.cypress.spec.tsx +0 -304
  59. package/src/Module/Module.cypress.spec.tsx +0 -127
  60. package/src/Pagination/Pagination.cypress.spec.tsx +0 -140
  61. package/src/RichTextEditor/RichTextEditor.cypress.spec.tsx +0 -408
  62. package/src/TimePeriods/ResolutionTimePeriod.cypress.spec.tsx +0 -74
  63. package/src/TimePeriods/TimePeriods.cypress.spec.tsx +0 -488
  64. package/src/Typography/FluidTypography/FluidTypography.cypress.spec.tsx +0 -27
  65. package/src/api/useMutationQuery/useMutationQuery.cypress.spec.tsx +0 -93
  66. package/src/components/CollapsibleItem/CollapsibleItem.cypress.spec.tsx +0 -76
  67. package/src/components/CopyCommand/CopyCommand.cypress.spec.tsx +0 -167
  68. package/src/components/CrudPage/CrudPage.cypress.spec.tsx +0 -561
  69. package/src/components/DataTable/DataTable.cypress.spec.tsx +0 -174
  70. package/src/components/Form/AccessRights/AccessRights.cypress.spec.tsx +0 -376
  71. package/src/components/ItemComposition/ItemComposition.cypress.spec.tsx +0 -112
  72. package/src/components/Layout/PageLayout.cypress.spec.tsx +0 -64
  73. package/src/components/Modal/ConfirmationModal/ConfirmationModal.cypress.spec.tsx +0 -171
  74. package/src/components/SortableEntriesList/SortableEntriesList.cypress.spec.tsx +0 -792
  75. package/src/components/Tabs/Tabs.cypress.spec.tsx +0 -69
  76. package/src/utils/useFullscreen/Fullscreen.cypress.spec.tsx +0 -130
  77. package/src/utils/useLocale/useLocale.cypress.spec.tsx +0 -40
@@ -1,6 +1,7 @@
1
1
  import { CircularProgress, useTheme } from '@mui/material';
2
2
  import type { AutocompleteRenderOptionState } from '@mui/material/Autocomplete';
3
3
 
4
+ import { isCancelledError } from '@tanstack/react-query';
4
5
  import {
5
6
  equals,
6
7
  has,
@@ -307,52 +308,62 @@ const ConnectedAutocompleteField = (
307
308
  );
308
309
 
309
310
  const fetchOptionsAndPrefetchNextOptions = useCallback((): void => {
310
- fetchQuery().then((newOptions) => {
311
- const isError = has('isError', newOptions);
312
-
313
- if (isError) {
314
- return;
315
- }
316
-
317
- const moreOptions = page > 1 ? options : [];
318
-
319
- const { result, limit, total } = getOptionResult(newOptions);
320
-
321
- const formattedList = changeIdValue
322
- ? result.map((item) => ({
323
- ...item,
324
- id: changeIdValue(item)
325
- }))
326
- : result;
327
-
328
- if (!isEmpty(labelKey) && !isNil(labelKey)) {
329
- const list = formattedList.map((item) =>
330
- renameKey({ key: labelKey, newKey: 'name', object: item })
331
- );
332
- setOptions(moreOptions.concat(list as Array<TData>));
333
-
334
- return;
335
- }
336
- setOptions(moreOptions.concat(formattedList));
337
-
338
- setOptions(moreOptions.concat(formattedList as Array<TData>));
339
-
340
- const newMaxPage = Math.ceil(total / limit);
341
-
342
- setMaxPage(newMaxPage);
343
- if (equals(newMaxPage, page)) {
344
- return;
345
- }
346
-
347
- prefetchNextPage({
348
- getPrefetchQueryKey: (newPage: number) => [
349
- `autocomplete-${props.label}`,
350
- newPage,
351
- searchParameter
352
- ],
353
- page
311
+ fetchQuery()
312
+ .then((newOptions) => {
313
+ const isError = has('isError', newOptions);
314
+
315
+ if (isError) {
316
+ return;
317
+ }
318
+
319
+ const moreOptions = page > 1 ? options : [];
320
+
321
+ const { result, limit, total } = getOptionResult(newOptions);
322
+
323
+ const formattedList = changeIdValue
324
+ ? result.map((item) => ({
325
+ ...item,
326
+ id: changeIdValue(item)
327
+ }))
328
+ : result;
329
+
330
+ if (!isEmpty(labelKey) && !isNil(labelKey)) {
331
+ const list = formattedList.map((item) =>
332
+ renameKey({ key: labelKey, newKey: 'name', object: item })
333
+ );
334
+ setOptions(moreOptions.concat(list as Array<TData>));
335
+
336
+ return;
337
+ }
338
+ setOptions(moreOptions.concat(formattedList));
339
+
340
+ setOptions(moreOptions.concat(formattedList as Array<TData>));
341
+
342
+ const newMaxPage = Math.ceil(total / limit);
343
+
344
+ setMaxPage(newMaxPage);
345
+ if (equals(newMaxPage, page)) {
346
+ return;
347
+ }
348
+
349
+ prefetchNextPage({
350
+ getPrefetchQueryKey: (newPage: number) => [
351
+ `autocomplete-${props.label}`,
352
+ newPage,
353
+ searchParameter
354
+ ],
355
+ page
356
+ });
357
+ })
358
+ // The request is cancelled when the field unmounts while it loads: there
359
+ // is nothing left to update, and nothing went wrong.
360
+ .catch((error) => {
361
+ if (isCancelledError(error)) {
362
+ return;
363
+ }
364
+
365
+ throw error;
354
366
  });
355
- });
356
367
  }, [
357
368
  changeIdValue,
358
369
  fetchQuery,
@@ -113,7 +113,6 @@ const DataCell = ({
113
113
  if (!clickable) {
114
114
  return;
115
115
  }
116
- e.preventDefault();
117
116
  e.stopPropagation();
118
117
  }}
119
118
  {...commonCellProps}
@@ -130,7 +129,6 @@ const DataCell = ({
130
129
  if (!clickable) {
131
130
  return;
132
131
  }
133
- e.preventDefault();
134
132
  e.stopPropagation();
135
133
  }}
136
134
  style={{
@@ -0,0 +1,476 @@
1
+ import { Button, Typography } from '@mui/material';
2
+
3
+ import { ListingVariant } from '@centreon/ui-context';
4
+
5
+ import { rs } from '@rstest/core';
6
+ import { render } from '@test-setup/render';
7
+ import { screen, waitFor, within } from '@testing-library/react';
8
+ import userEvent from '@testing-library/user-event';
9
+ import { useState } from 'react';
10
+
11
+ import Listing from '.';
12
+ import {
13
+ type Column,
14
+ ColumnType,
15
+ type ComponentColumnProps,
16
+ type RowRecord
17
+ } from './models';
18
+
19
+ interface NamedRow {
20
+ name: string;
21
+ }
22
+
23
+ const getName = (row: RowRecord): string => (row as unknown as NamedRow).name;
24
+
25
+ const ButtonColumn = ({ row }: ComponentColumnProps): JSX.Element => (
26
+ <Button size="small">Click to reveal details about {getName(row)}</Button>
27
+ );
28
+
29
+ const CheckboxColumn = ({ row }: ComponentColumnProps): JSX.Element => {
30
+ const [checked, setChecked] = useState(false);
31
+
32
+ return (
33
+ <input
34
+ aria-label={`Toggle ${getName(row)}`}
35
+ checked={checked}
36
+ onChange={(event) => setChecked(event.target.checked)}
37
+ type="checkbox"
38
+ />
39
+ );
40
+ };
41
+
42
+ const LargeText = (): JSX.Element => (
43
+ <Typography sx={{ whiteSpace: 'normal' }}>
44
+ This is a large text that fills the content
45
+ </Typography>
46
+ );
47
+
48
+ interface Item {
49
+ active: boolean;
50
+ description: string;
51
+ disableCheckbox: boolean;
52
+ disableRow: boolean;
53
+ id: number;
54
+ name: string;
55
+ selected: boolean;
56
+ subItems?: Array<Item>;
57
+ }
58
+
59
+ const tenElements = new Array(10).fill(0);
60
+
61
+ const generateSubItems = (): Array<Item> => {
62
+ return tenElements.map((_, subIndex) => ({
63
+ active: false,
64
+ description: `Sub item ${subIndex * 10} description`,
65
+ disableCheckbox: false,
66
+ disableRow: false,
67
+ id: subIndex * 10,
68
+ name: `Sub Item ${subIndex * 10}`,
69
+ selected: false
70
+ }));
71
+ };
72
+
73
+ const listingWithSubItems: Array<Item> = tenElements.map((_, index) => ({
74
+ active: false,
75
+ description: `Entity ${index}`,
76
+ disableCheckbox: false,
77
+ disableRow: false,
78
+ id: index,
79
+ name: `E${index}`,
80
+ selected: false,
81
+ subItems: index % 2 === 0 ? generateSubItems() : undefined
82
+ }));
83
+
84
+ const listingWithSubItems3Rows: Array<Item> = Array(3)
85
+ .fill(0)
86
+ .map((_, index) => ({
87
+ active: false,
88
+ description: `Entity ${index}`,
89
+ disableCheckbox: false,
90
+ disableRow: false,
91
+ id: index,
92
+ name: `E${index}`,
93
+ selected: false,
94
+ subItems: index % 2 === 0 ? generateSubItems() : undefined
95
+ }));
96
+
97
+ const defaultColumn: Array<Column> = [
98
+ {
99
+ getFormattedString: getName,
100
+ id: 'name',
101
+ label: 'Name',
102
+ type: ColumnType.string
103
+ },
104
+ {
105
+ getFormattedString: (row: RowRecord): string =>
106
+ (row as unknown as Item).description,
107
+ id: 'description',
108
+ label: 'Description',
109
+ type: ColumnType.string
110
+ },
111
+ {
112
+ Component: LargeText,
113
+ id: '#',
114
+ label: 'Custom',
115
+ type: ColumnType.component,
116
+ width: '100px'
117
+ }
118
+ ];
119
+
120
+ const columnsWithSubItems: Array<Column> = [
121
+ {
122
+ getFormattedString: getName,
123
+ id: 'name',
124
+ label: 'Name',
125
+ type: ColumnType.string
126
+ },
127
+ {
128
+ getFormattedString: (row: RowRecord): string =>
129
+ (row as unknown as Item).description,
130
+ id: 'description',
131
+ label: 'Description',
132
+ type: ColumnType.string
133
+ },
134
+ {
135
+ Component: ButtonColumn,
136
+ displaySubItemsCaret: true,
137
+ id: '#',
138
+ label: 'Custom',
139
+ type: ColumnType.component,
140
+ width: '350px'
141
+ }
142
+ ];
143
+
144
+ const expandedItems = [0, 8];
145
+
146
+ // Rendered in the 1280px-wide rstest viewport.
147
+ const mountListingResponsive = (listingVariant: ListingVariant): void => {
148
+ render(
149
+ <div style={{ height: '100vh' }}>
150
+ <Listing
151
+ columns={defaultColumn}
152
+ currentPage={1}
153
+ isResponsive
154
+ limit={10}
155
+ listingVariant={listingVariant}
156
+ rows={listingWithSubItems}
157
+ totalRows={10}
158
+ />
159
+ </div>
160
+ );
161
+ };
162
+
163
+ interface TestComponentProps {
164
+ canCheckSubItems?: boolean;
165
+ isSmallListing?: boolean;
166
+ }
167
+
168
+ const TestComponent = ({
169
+ isSmallListing = false,
170
+ canCheckSubItems = false
171
+ }: TestComponentProps): JSX.Element => {
172
+ const [selectedRows, setSelectedRows] = useState<Array<Item>>([]);
173
+
174
+ return (
175
+ <Listing
176
+ checkable
177
+ columns={columnsWithSubItems}
178
+ currentPage={1}
179
+ limit={10}
180
+ onSelectRows={setSelectedRows}
181
+ rows={isSmallListing ? listingWithSubItems3Rows : listingWithSubItems}
182
+ selectedRows={selectedRows}
183
+ subItems={{
184
+ canCheckSubItems,
185
+ enable: true,
186
+ getRowProperty: () => 'subItems',
187
+ labelCollapse: 'Collapse',
188
+ labelExpand: 'Expand'
189
+ }}
190
+ totalRows={10}
191
+ />
192
+ );
193
+ };
194
+
195
+ const mountListingForSubItems = ({
196
+ isSmallListing = false,
197
+ canCheckSubItems = false
198
+ }: TestComponentProps): void => {
199
+ render(
200
+ <div style={{ height: '100vh' }}>
201
+ <TestComponent
202
+ canCheckSubItems={canCheckSubItems}
203
+ isSmallListing={isSmallListing}
204
+ />
205
+ </div>
206
+ );
207
+ };
208
+
209
+ // Every row collapsed: the headers, the ten rows in order, an expand caret on
210
+ // the rows with sub items only, and no sub item.
211
+ const expectCollapsedListing = (): void => {
212
+ ['Name', 'Description', 'Custom'].forEach((label) => {
213
+ expect(screen.getByText(label)).toBeVisible();
214
+ });
215
+
216
+ listingWithSubItems.forEach(({ id, name, description, subItems }) => {
217
+ expect(screen.getByText(name)).toBeVisible();
218
+ expect(screen.getByText(description)).toBeVisible();
219
+ expect(
220
+ screen.getByText(`Click to reveal details about ${name}`)
221
+ ).toBeVisible();
222
+
223
+ if (subItems) {
224
+ expect(screen.getByLabelText(`Expand ${id}`)).toBeVisible();
225
+ } else {
226
+ expect(screen.queryByLabelText(`Expand ${id}`)).not.toBeInTheDocument();
227
+ }
228
+ expect(screen.queryByLabelText(`Collapse ${id}`)).not.toBeInTheDocument();
229
+ });
230
+
231
+ expect(screen.queryByText(/^Sub Item/)).not.toBeInTheDocument();
232
+ expect(screen.queryByText(/^Sub item/)).not.toBeInTheDocument();
233
+
234
+ const names = screen
235
+ .getAllByText(/^E\d$/)
236
+ .map((element) => element.textContent);
237
+
238
+ expect(names).toEqual(listingWithSubItems.map(({ name }) => name));
239
+ };
240
+
241
+ // The headers, the rows in order and the large text of the last column on
242
+ // each row.
243
+ const expectResponsiveListing = (): void => {
244
+ ['Name', 'Description', 'Custom'].forEach((label) => {
245
+ expect(screen.getByText(label)).toBeVisible();
246
+ });
247
+ expect(
248
+ screen.getAllByText('This is a large text that fills the content')
249
+ ).toHaveLength(10);
250
+
251
+ const names = screen
252
+ .getAllByText(/^E\d$/)
253
+ .map((element) => element.textContent);
254
+
255
+ expect(names).toEqual(listingWithSubItems.map(({ name }) => name));
256
+ };
257
+
258
+ // The header row keeps the variant's height; the rows grow to fit the large
259
+ // text, which wraps onto several lines. How many lines depends on the
260
+ // platform's text metrics, so the rows are not compared to a fixed height.
261
+ const expectLastColumnOnSeveralLines = async (
262
+ headerHeight: number
263
+ ): Promise<void> => {
264
+ await waitFor(() => {
265
+ const [header, ...rows] = getComputedStyle(getTable())
266
+ .gridTemplateRows.split(' ')
267
+ .map(Number.parseFloat);
268
+
269
+ expect(header).toBe(headerHeight);
270
+ expect(rows).toHaveLength(10);
271
+ rows.forEach((row) => {
272
+ expect(row).toBe(rows[0]);
273
+ expect(row).toBeGreaterThan(headerHeight);
274
+ });
275
+ });
276
+
277
+ screen
278
+ .getAllByText('This is a large text that fills the content')
279
+ .forEach((text) => {
280
+ const lineHeight = Number.parseFloat(getComputedStyle(text).lineHeight);
281
+
282
+ expect(text.getBoundingClientRect().height).toBeGreaterThanOrEqual(
283
+ 2 * lineHeight
284
+ );
285
+ });
286
+ };
287
+
288
+ const getTable = (): HTMLElement => {
289
+ const table = document.querySelector<HTMLElement>('.MuiTable-root');
290
+
291
+ if (!table) {
292
+ throw new Error('No table');
293
+ }
294
+
295
+ return table;
296
+ };
297
+
298
+ describe('Listing', () => {
299
+ beforeEach(() => {
300
+ expect.hasAssertions();
301
+ });
302
+
303
+ describe('Sub items', () => {
304
+ it('expands the row when the corresponding icon is clicked', async () => {
305
+ mountListingForSubItems({});
306
+
307
+ expect(await screen.findByText('E0')).toBeVisible();
308
+
309
+ for (const index of expandedItems) {
310
+ const subItems = generateSubItems();
311
+
312
+ await userEvent.click(screen.getByLabelText(`Expand ${index}`));
313
+
314
+ expect(
315
+ await screen.findByLabelText(`Collapse ${index}`)
316
+ ).toBeInTheDocument();
317
+
318
+ for (const { name, description } of subItems) {
319
+ expect(await screen.findByText(name)).toBeInTheDocument();
320
+ expect(screen.getByText(description)).toBeInTheDocument();
321
+ }
322
+
323
+ await userEvent.click(screen.getByLabelText(`Collapse ${index}`));
324
+ }
325
+
326
+ await waitFor(() => {
327
+ expect(screen.queryByText('Sub Item 0')).not.toBeInTheDocument();
328
+ });
329
+ expectCollapsedListing();
330
+ });
331
+
332
+ it('collapses the row when the corresponding icon is clicked', async () => {
333
+ mountListingForSubItems({});
334
+
335
+ await userEvent.click(await screen.findByLabelText('Expand 0'));
336
+
337
+ expect(await screen.findByText('Sub item 10 description')).toBeVisible();
338
+
339
+ await userEvent.click(screen.getByLabelText('Collapse 0'));
340
+
341
+ await waitFor(() => {
342
+ expect(screen.queryByText(/Sub item 10/)).not.toBeInTheDocument();
343
+ });
344
+
345
+ expectCollapsedListing();
346
+ });
347
+
348
+ it('displays only one row as hovered when mutilple rows are expanded and a sub item row is hovered', async () => {
349
+ mountListingForSubItems({ isSmallListing: true });
350
+
351
+ await userEvent.click(await screen.findByLabelText('Expand 2'));
352
+ await userEvent.click(await screen.findByLabelText('Expand 0'));
353
+
354
+ expect(await screen.findByLabelText('Collapse 0')).toBeVisible();
355
+ expect(await screen.findByLabelText('Collapse 2')).toBeVisible();
356
+
357
+ await userEvent.hover(screen.getAllByText('Sub Item 0')[0]);
358
+
359
+ await waitFor(() => {
360
+ expect(
361
+ document.querySelectorAll('[data-is-hovered="true"]')
362
+ ).toHaveLength(1);
363
+ });
364
+ const hoveredRow = document.querySelector<HTMLElement>(
365
+ '[data-is-hovered="true"]'
366
+ );
367
+
368
+ expect(
369
+ within(hoveredRow as HTMLElement).getByText('Sub Item 0')
370
+ ).toBeInTheDocument();
371
+
372
+ await userEvent.click(screen.getByLabelText('Collapse 0'));
373
+ await userEvent.click(screen.getByLabelText('Collapse 2'));
374
+ });
375
+
376
+ it('selects displayed rows when a row is selected and another row is selected with the shift key', async () => {
377
+ const user = userEvent.setup();
378
+
379
+ mountListingForSubItems({ canCheckSubItems: true, isSmallListing: true });
380
+
381
+ await user.click(await screen.findByLabelText('Expand 2'));
382
+
383
+ expect(await screen.findByLabelText('Collapse 2')).toBeVisible();
384
+
385
+ await user.click(screen.getAllByLabelText('Select row 2')[0]);
386
+ await user.keyboard('{Shift>}');
387
+ await user.click(screen.getByLabelText('Select row 50'));
388
+ await user.keyboard('{/Shift}');
389
+
390
+ await waitFor(() => {
391
+ expect(screen.getByLabelText('Select row 40')).toBeChecked();
392
+ });
393
+ expect(screen.getAllByLabelText('Select row 2')[0]).toBeChecked();
394
+ expect(screen.getByLabelText('Select row 10')).toBeChecked();
395
+ expect(screen.getByLabelText('Select row 20')).toBeChecked();
396
+ expect(screen.getByLabelText('Select row 30')).toBeChecked();
397
+ expect(screen.getByLabelText('Select row 40')).toBeChecked();
398
+
399
+ await user.click(screen.getByLabelText('Collapse 2'));
400
+ });
401
+
402
+ it('selects displayed rows when the corresponding checkbox is clicked', async () => {
403
+ mountListingForSubItems({ canCheckSubItems: true, isSmallListing: true });
404
+
405
+ await userEvent.click(await screen.findByLabelText('Expand 2'));
406
+
407
+ expect(await screen.findByLabelText('Collapse 2')).toBeVisible();
408
+
409
+ await userEvent.click(screen.getAllByLabelText('Select all')[0]);
410
+
411
+ await waitFor(() => {
412
+ expect(screen.getAllByLabelText('Select row 2')[0]).toBeChecked();
413
+ });
414
+ tenElements.forEach((_, index) => {
415
+ if (index === 0) {
416
+ expect(screen.getAllByLabelText('Select row 0')[1]).toBeChecked();
417
+
418
+ return;
419
+ }
420
+ expect(screen.getByLabelText(`Select row ${index * 10}`)).toBeChecked();
421
+ });
422
+ expect(screen.getByLabelText('Select row 1')).toBeChecked();
423
+ expect(screen.getByLabelText('Select row 2')).toBeChecked();
424
+
425
+ await userEvent.click(screen.getByLabelText('Collapse 2'));
426
+ });
427
+ });
428
+
429
+ it('displays the last column on several lines in compact mode when the isResponsive prop is set', async () => {
430
+ mountListingResponsive(ListingVariant.compact);
431
+
432
+ await expectLastColumnOnSeveralLines(30);
433
+
434
+ expectResponsiveListing();
435
+ });
436
+
437
+ it('displays the last column on several lines in extended mode when the isResponsive prop is set', async () => {
438
+ mountListingResponsive(ListingVariant.extended);
439
+
440
+ await expectLastColumnOnSeveralLines(38);
441
+
442
+ expectResponsiveListing();
443
+ });
444
+
445
+ it('toggles a checkbox in a clickable column without clicking the row', async () => {
446
+ const onRowClick = rs.fn();
447
+
448
+ render(
449
+ <div style={{ height: '100vh' }}>
450
+ <Listing
451
+ columns={[
452
+ {
453
+ Component: CheckboxColumn,
454
+ clickable: true,
455
+ id: 'toggle',
456
+ label: 'Toggle',
457
+ type: ColumnType.component
458
+ }
459
+ ]}
460
+ currentPage={1}
461
+ limit={10}
462
+ onRowClick={onRowClick}
463
+ rows={[{ id: 0, name: 'E0' }]}
464
+ totalRows={1}
465
+ />
466
+ </div>
467
+ );
468
+
469
+ await userEvent.click(await screen.findByLabelText('Toggle E0'));
470
+
471
+ await waitFor(() => {
472
+ expect(screen.getByLabelText('Toggle E0')).toBeChecked();
473
+ });
474
+ expect(onRowClick).not.toHaveBeenCalled();
475
+ });
476
+ });