@centreon/ui 26.10.7 → 26.10.9

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 (64) 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/Form.browser.test.tsx +445 -0
  5. package/src/Form/Inputs/ConnectedAutocomplete.tsx +12 -4
  6. package/src/Form/Inputs/ExclusiveCheckboxGroup.browser.test.tsx +304 -0
  7. package/src/Form/Inputs/ExclusiveCheckboxGroup.tsx +60 -15
  8. package/src/Form/Inputs/LoadingSkeleton.tsx +4 -0
  9. package/src/Form/Inputs/SegmentedButtons.browser.test.tsx +113 -0
  10. package/src/Form/Inputs/SegmentedButtons.tsx +71 -0
  11. package/src/Form/Inputs/index.tsx +5 -0
  12. package/src/Form/Inputs/models.ts +16 -1
  13. package/src/InputField/Number/Number.browser.test.tsx +109 -0
  14. package/src/InputField/Select/Autocomplete/Connected/Multi/MultiConnectedAutocompleteField.browser.test.tsx +250 -0
  15. package/src/InputField/Select/Autocomplete/Connected/index.test.tsx +37 -0
  16. package/src/InputField/Select/Autocomplete/Connected/index.tsx +59 -45
  17. package/src/Listing/Cell/DataCell.tsx +0 -2
  18. package/src/Listing/Listing.browser.test.tsx +476 -0
  19. package/src/Module/Module.browser.test.tsx +145 -0
  20. package/src/Pagination/Pagination.browser.test.tsx +184 -0
  21. package/src/RichTextEditor/RichTextEditor.browser.test.tsx +567 -0
  22. package/src/RichTextEditor/plugins/FloatingLinkEditorPlugin.tsx +3 -1
  23. package/src/TimePeriods/ResolutionTimePeriod.browser.test.tsx +143 -0
  24. package/src/TimePeriods/TimePeriods.browser.test.tsx +468 -0
  25. package/src/Typography/FluidTypography/FluidTypography.browser.test.tsx +54 -0
  26. package/src/api/useMutationQuery/useMutationQuery.browser.test.tsx +154 -0
  27. package/src/components/CollapsibleItem/CollapsibleItem.browser.test.tsx +120 -0
  28. package/src/components/CopyCommand/CopyCommand.browser.test.tsx +287 -0
  29. package/src/components/CrudPage/CrudPage.browser.test.tsx +659 -0
  30. package/src/components/DataTable/DataTable.browser.test.tsx +257 -0
  31. package/src/components/Form/AccessRights/AccessRights.browser.test.tsx +527 -0
  32. package/src/components/ItemComposition/ItemComposition.browser.test.tsx +172 -0
  33. package/src/components/Layout/PageLayout.browser.test.tsx +114 -0
  34. package/src/components/Modal/ConfirmationModal/ConfirmationModal.browser.test.tsx +237 -0
  35. package/src/components/SortableEntriesList/SortableEntriesList.browser.test.tsx +981 -0
  36. package/src/components/Tabs/Tabs.browser.test.tsx +115 -0
  37. package/src/utils/useFullscreen/Fullscreen.browser.test.tsx +176 -0
  38. package/src/utils/useLocale/useLocale.browser.test.tsx +49 -0
  39. package/src/utils/useLocaleTimezoneDate/{LocaleTimezoneDate.cypress.spec.tsx → LocaleTimezoneDate.browser.test.tsx} +24 -11
  40. package/src/Dashboard/Dashboard.cypress.spec.tsx +0 -71
  41. package/src/Form/Form.cypress.spec.tsx +0 -306
  42. package/src/Form/Inputs/ExclusiveCheckboxGroup.cypress.spec.tsx +0 -174
  43. package/src/InputField/Number/Number.cypress.spec.tsx +0 -89
  44. package/src/InputField/Select/Autocomplete/Connected/Multi/MultiConnectedAutocompleteField.cypress.spec.tsx +0 -182
  45. package/src/Listing/Listing.cypress.spec.tsx +0 -304
  46. package/src/Module/Module.cypress.spec.tsx +0 -127
  47. package/src/Pagination/Pagination.cypress.spec.tsx +0 -140
  48. package/src/RichTextEditor/RichTextEditor.cypress.spec.tsx +0 -408
  49. package/src/TimePeriods/ResolutionTimePeriod.cypress.spec.tsx +0 -74
  50. package/src/TimePeriods/TimePeriods.cypress.spec.tsx +0 -488
  51. package/src/Typography/FluidTypography/FluidTypography.cypress.spec.tsx +0 -27
  52. package/src/api/useMutationQuery/useMutationQuery.cypress.spec.tsx +0 -93
  53. package/src/components/CollapsibleItem/CollapsibleItem.cypress.spec.tsx +0 -76
  54. package/src/components/CopyCommand/CopyCommand.cypress.spec.tsx +0 -167
  55. package/src/components/CrudPage/CrudPage.cypress.spec.tsx +0 -561
  56. package/src/components/DataTable/DataTable.cypress.spec.tsx +0 -174
  57. package/src/components/Form/AccessRights/AccessRights.cypress.spec.tsx +0 -376
  58. package/src/components/ItemComposition/ItemComposition.cypress.spec.tsx +0 -112
  59. package/src/components/Layout/PageLayout.cypress.spec.tsx +0 -64
  60. package/src/components/Modal/ConfirmationModal/ConfirmationModal.cypress.spec.tsx +0 -171
  61. package/src/components/SortableEntriesList/SortableEntriesList.cypress.spec.tsx +0 -792
  62. package/src/components/Tabs/Tabs.cypress.spec.tsx +0 -69
  63. package/src/utils/useFullscreen/Fullscreen.cypress.spec.tsx +0 -130
  64. package/src/utils/useLocale/useLocale.cypress.spec.tsx +0 -40
@@ -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
+ });
@@ -0,0 +1,250 @@
1
+ import {
2
+ Method,
3
+ MultiConnectedAutocompleteField,
4
+ type SelectEntry,
5
+ TestQueryProvider
6
+ } from '@centreon/ui';
7
+
8
+ import { interceptAPIRequest, waitForRequest } from '@test-setup/api';
9
+ import { render } from '@test-setup/render';
10
+ import { screen, waitFor, within } from '@testing-library/react';
11
+ import userEvent from '@testing-library/user-event';
12
+ import i18next from 'i18next';
13
+ import { useState } from 'react';
14
+ import { initReactI18next } from 'react-i18next';
15
+
16
+ import { labelSelectAll, labelUnSelectAll } from '../../../../translatedLabels';
17
+ import { baseEndpoint, getEndpoint, label, placeholder } from './utils';
18
+
19
+ const optionOne = 'My Option 1';
20
+
21
+ // The option list and search result the endpoint mocks return.
22
+ const listOptions = {
23
+ meta: { limit: 5, page: 1, total: 5 },
24
+ result: [
25
+ { id: 0, name: 'My Option 1' },
26
+ { id: 1, name: 'My Option 2' },
27
+ { id: 2, name: 'My Option 3' },
28
+ { id: 3, name: 'My Option 4' },
29
+ { id: 4, name: 'My Option 5' }
30
+ ]
31
+ };
32
+
33
+ const searchedOption = {
34
+ meta: { limit: 1, page: 1, total: 1 },
35
+ result: [{ id: 0, name: 'My Option 1' }]
36
+ };
37
+
38
+ const optionNames = listOptions.result.map(({ name }) => name);
39
+
40
+ // Passed down to the multi autocomplete, though the connected field's type
41
+ // does not declare it.
42
+ const multiAutocompleteProps: Record<string, unknown> = {
43
+ disableSelectAll: false
44
+ };
45
+
46
+ const Component = (): JSX.Element => {
47
+ const [values, setValues] = useState<Array<SelectEntry>>([]);
48
+
49
+ return (
50
+ <TestQueryProvider>
51
+ <div style={{ paddingTop: 20 }}>
52
+ <MultiConnectedAutocompleteField
53
+ {...multiAutocompleteProps}
54
+ field="host.name"
55
+ getEndpoint={getEndpoint}
56
+ label={label}
57
+ onChange={(_, item) => setValues(item as Array<SelectEntry>)}
58
+ placeholder={placeholder}
59
+ value={values}
60
+ />
61
+ </div>
62
+ </TestQueryProvider>
63
+ );
64
+ };
65
+
66
+ const getInput = (): HTMLInputElement =>
67
+ document.querySelector(
68
+ '[data-testid="Multi Connected Autocomplete"]'
69
+ ) as HTMLInputElement;
70
+
71
+ // The last one: a closing list can still be in the page.
72
+ const findListOptions = async (): Promise<HTMLElement> =>
73
+ (await screen.findAllByRole('presentation')).at(-1) as HTMLElement;
74
+
75
+ const getOptionTexts = (listOptionsElement: HTMLElement): Array<string> =>
76
+ within(listOptionsElement)
77
+ .queryAllByText(/^My Option \d$/)
78
+ .map((option) => option.textContent ?? '');
79
+
80
+ const getOptionCheckboxes = (listOptionsElement: HTMLElement) =>
81
+ within(listOptionsElement).getAllByRole('checkbox');
82
+
83
+ describe('Multi connected autocomplete', () => {
84
+ beforeEach(() => {
85
+ expect.hasAssertions();
86
+ });
87
+
88
+ beforeEach(() => {
89
+ i18next.use(initReactI18next).init({
90
+ lng: 'en',
91
+ resources: {}
92
+ });
93
+
94
+ interceptAPIRequest({
95
+ alias: 'getListOptions',
96
+ method: Method.GET,
97
+ path: `${baseEndpoint}**`,
98
+ response: listOptions
99
+ });
100
+
101
+ interceptAPIRequest({
102
+ alias: 'getSearchedOption',
103
+ method: Method.GET,
104
+ path: `${baseEndpoint}**`,
105
+ query: {
106
+ name: 'search',
107
+ value: JSON.stringify({
108
+ $and: [{ $and: [{ 'host.name': { $lk: `%${optionOne}%` } }] }]
109
+ })
110
+ },
111
+ response: searchedOption
112
+ });
113
+
114
+ render(<Component />);
115
+ });
116
+
117
+ it('displays and hides the list when the user double-clicks on the input', async () => {
118
+ expect((await screen.findAllByText(label))[0]).toBeVisible();
119
+
120
+ const input = getInput();
121
+
122
+ await userEvent.click(input);
123
+
124
+ await waitForRequest('@getListOptions');
125
+
126
+ expect(input).toHaveAttribute('placeholder', placeholder);
127
+
128
+ const listOptionsElement = await findListOptions();
129
+
130
+ expect(listOptionsElement).toBeVisible();
131
+
132
+ // The list re-renders its options: compared by text, not by element.
133
+ await waitFor(() => {
134
+ expect(getOptionTexts(listOptionsElement)).toEqual(optionNames);
135
+ });
136
+
137
+ // The count of options, the select all button and the five options
138
+ // unchecked.
139
+ expect(
140
+ within(listOptionsElement).getByText('5 element(s) found')
141
+ ).toBeVisible();
142
+ expect(within(listOptionsElement).getByText(labelSelectAll)).toBeVisible();
143
+ getOptionCheckboxes(listOptionsElement).forEach((checkbox) => {
144
+ expect(checkbox).not.toBeChecked();
145
+ });
146
+
147
+ await userEvent.click(input);
148
+
149
+ await waitFor(() => {
150
+ expect(listOptionsElement).not.toBeInTheDocument();
151
+ });
152
+
153
+ // No option list, and nothing selected.
154
+ expect(screen.queryByRole('presentation')).not.toBeInTheDocument();
155
+ expect(screen.queryByTestId('CancelIcon')).not.toBeInTheDocument();
156
+ });
157
+
158
+ it('displays all options on the list when the user searches for and selects an option', async () => {
159
+ const input = getInput();
160
+
161
+ await userEvent.click(input);
162
+
163
+ await waitForRequest('@getListOptions');
164
+
165
+ const listOptionsElement = await findListOptions();
166
+
167
+ expect(listOptionsElement).toBeVisible();
168
+
169
+ await userEvent.type(input, optionOne);
170
+
171
+ await waitForRequest('@getSearchedOption');
172
+
173
+ await waitFor(() => {
174
+ expect(listOptionsElement.querySelectorAll('li')).toHaveLength(6);
175
+ });
176
+
177
+ await userEvent.click(document.querySelectorAll('[type="checkbox"]')[0]);
178
+
179
+ const inputContainer = input.parentElement as HTMLElement;
180
+
181
+ expect(await within(inputContainer).findByText(optionOne)).toBeVisible();
182
+ expect(within(inputContainer).getByTestId('CancelIcon')).toBeVisible();
183
+
184
+ await waitForRequest('@getListOptions');
185
+
186
+ await waitFor(() => {
187
+ expect(listOptionsElement.querySelectorAll('li')).toHaveLength(
188
+ listOptions.result.length + 1
189
+ );
190
+ });
191
+
192
+ // The list re-renders its options: compared by text, not by element.
193
+ await waitFor(() => {
194
+ expect(getOptionTexts(listOptionsElement)).toEqual(optionNames);
195
+ });
196
+ });
197
+
198
+ it('checks all options when Select all button is clicked', async () => {
199
+ const input = getInput();
200
+
201
+ await userEvent.click(input);
202
+
203
+ expect(await screen.findByText('5 element(s) found')).toBeInTheDocument();
204
+
205
+ await waitForRequest('@getListOptions');
206
+
207
+ await userEvent.click(await screen.findByText(labelSelectAll));
208
+
209
+ expect(await screen.findByText(labelUnSelectAll)).toBeVisible();
210
+
211
+ await waitFor(() => {
212
+ expect(screen.getAllByTestId('CancelIcon')).toHaveLength(5);
213
+ });
214
+
215
+ // A chip per option in the field, in order, and every option checked.
216
+ expect(
217
+ within(input.parentElement as HTMLElement)
218
+ .getAllByText(/^My Option \d$/)
219
+ .map((chip) => chip.textContent)
220
+ ).toEqual(optionNames);
221
+ getOptionCheckboxes(await findListOptions()).forEach((checkbox) => {
222
+ expect(checkbox).toBeChecked();
223
+ });
224
+ });
225
+
226
+ it('unchecks all options when unSelect all button is clicked', async () => {
227
+ const input = getInput();
228
+
229
+ await userEvent.click(input);
230
+
231
+ expect(await screen.findByText('5 element(s) found')).toBeInTheDocument();
232
+
233
+ await waitForRequest('@getListOptions');
234
+
235
+ await userEvent.click(await screen.findByText(labelSelectAll));
236
+ await userEvent.click(await screen.findByText(labelUnSelectAll));
237
+
238
+ expect(await screen.findByText(labelSelectAll)).toBeVisible();
239
+
240
+ await waitFor(() => {
241
+ expect(screen.queryAllByTestId('CancelIcon')).toHaveLength(0);
242
+ });
243
+
244
+ // The field is empty and every option unchecked.
245
+ expect(input).toHaveAttribute('placeholder', placeholder);
246
+ getOptionCheckboxes(await findListOptions()).forEach((checkbox) => {
247
+ expect(checkbox).not.toBeChecked();
248
+ });
249
+ });
250
+ });
@@ -8,6 +8,8 @@ import {
8
8
  resetMocks,
9
9
  waitFor
10
10
  } from '../../../../../test/testRenderer';
11
+ import fetchMock from 'jest-fetch-mock';
12
+
11
13
  import buildListingEndpoint from '../../../../api/buildListingEndpoint';
12
14
  import type {
13
15
  BuildListingEndpointParameters,
@@ -127,4 +129,39 @@ describe(SingleConnectedAutocompleteField, () => {
127
129
  );
128
130
  });
129
131
  });
132
+
133
+ it('leaves no request rejected unhandled when it is closed while its options load', async () => {
134
+ const unhandledRejections: Array<unknown> = [];
135
+ const onUnhandledRejection = (reason: unknown): void => {
136
+ unhandledRejections.push(reason);
137
+ };
138
+
139
+ process.on('unhandledRejection', onUnhandledRejection);
140
+
141
+ try {
142
+ // Options that never arrive: the request is still running at unmount,
143
+ // which cancels it.
144
+ fetchMock.mockResponse(() => new Promise(() => undefined));
145
+
146
+ const { getByLabelText, unmount } =
147
+ renderSingleConnectedAutocompleteField();
148
+
149
+ fireEvent.click(getByLabelText('Open'));
150
+
151
+ await waitFor(() => {
152
+ expect(getFetchCall(0)).toEqual(`${baseEndpoint}?page=1`);
153
+ });
154
+
155
+ unmount();
156
+
157
+ // Unhandled rejections are reported once the microtask queue drains.
158
+ await new Promise((resolve) => {
159
+ setTimeout(resolve, 50);
160
+ });
161
+
162
+ expect(unhandledRejections).toEqual([]);
163
+ } finally {
164
+ process.off('unhandledRejection', onUnhandledRejection);
165
+ }
166
+ });
130
167
  });
@@ -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,
@@ -126,8 +127,11 @@ const ConnectedAutocompleteField = (
126
127
  search: searchParameter
127
128
  });
128
129
  },
130
+ // Two fields can carry the same label and read different APIs, and
131
+ // would otherwise share each other's options.
129
132
  getQueryKey: () => [
130
133
  `autocomplete-${queryKey || props.label}`,
134
+ baseEndpoint,
131
135
  page,
132
136
  searchParameter
133
137
  ],
@@ -304,52 +308,62 @@ const ConnectedAutocompleteField = (
304
308
  );
305
309
 
306
310
  const fetchOptionsAndPrefetchNextOptions = useCallback((): void => {
307
- fetchQuery().then((newOptions) => {
308
- const isError = has('isError', newOptions);
309
-
310
- if (isError) {
311
- return;
312
- }
313
-
314
- const moreOptions = page > 1 ? options : [];
315
-
316
- const { result, limit, total } = getOptionResult(newOptions);
317
-
318
- const formattedList = changeIdValue
319
- ? result.map((item) => ({
320
- ...item,
321
- id: changeIdValue(item)
322
- }))
323
- : result;
324
-
325
- if (!isEmpty(labelKey) && !isNil(labelKey)) {
326
- const list = formattedList.map((item) =>
327
- renameKey({ key: labelKey, newKey: 'name', object: item })
328
- );
329
- setOptions(moreOptions.concat(list as Array<TData>));
330
-
331
- return;
332
- }
333
- setOptions(moreOptions.concat(formattedList));
334
-
335
- setOptions(moreOptions.concat(formattedList as Array<TData>));
336
-
337
- const newMaxPage = Math.ceil(total / limit);
338
-
339
- setMaxPage(newMaxPage);
340
- if (equals(newMaxPage, page)) {
341
- return;
342
- }
343
-
344
- prefetchNextPage({
345
- getPrefetchQueryKey: (newPage: number) => [
346
- `autocomplete-${props.label}`,
347
- newPage,
348
- searchParameter
349
- ],
350
- 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;
351
366
  });
352
- });
353
367
  }, [
354
368
  changeIdValue,
355
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={{