@centreon/ui 23.10.1-sync-dev-23-10-x-develop-oss.0 → 23.10.1-sync-release-34346.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 (94) hide show
  1. package/package.json +6 -1
  2. package/public/mockServiceWorker.js +2 -2
  3. package/src/Form/Inputs/ConnectedAutocomplete.tsx +3 -1
  4. package/src/Form/Inputs/models.ts +1 -0
  5. package/src/Graph/Gauge/Gauge.stories.tsx +15 -2
  6. package/src/Graph/HeatMap/HeatMap.stories.tsx +105 -0
  7. package/src/Graph/HeatMap/HeatMap.styles.tsx +29 -0
  8. package/src/Graph/HeatMap/HeatMap.tsx +12 -0
  9. package/src/Graph/HeatMap/HeatMapData.json +137 -0
  10. package/src/Graph/HeatMap/ResponsiveHeatMap.tsx +98 -0
  11. package/src/Graph/HeatMap/index.ts +1 -0
  12. package/src/Graph/HeatMap/model.ts +33 -0
  13. package/src/Graph/LineChart/BasicComponents/Lines/RegularLines/index.tsx +1 -1
  14. package/src/Graph/LineChart/BasicComponents/useFilterLines.ts +2 -4
  15. package/src/Graph/LineChart/Legend/InteractiveValue.tsx +3 -3
  16. package/src/Graph/LineChart/Legend/Legend.styles.ts +78 -32
  17. package/src/Graph/LineChart/Legend/LegendContent.tsx +12 -6
  18. package/src/Graph/LineChart/Legend/LegendHeader.tsx +69 -21
  19. package/src/Graph/LineChart/Legend/index.tsx +51 -23
  20. package/src/Graph/LineChart/Legend/models.ts +5 -0
  21. package/src/Graph/LineChart/Legend/useLegend.ts +9 -10
  22. package/src/Graph/LineChart/LineChart.tsx +17 -3
  23. package/src/Graph/index.ts +1 -0
  24. package/src/InputField/Select/Autocomplete/Connected/Multi/index.test.tsx +1 -0
  25. package/src/InputField/Select/Autocomplete/Connected/index.test.tsx +1 -0
  26. package/src/InputField/Select/Autocomplete/Connected/index.tsx +15 -3
  27. package/src/InputField/Text/index.tsx +6 -1
  28. package/src/Module/LicensedModule/LicenseCheck/index.tsx +24 -9
  29. package/src/Module/LicensedModule/index.tsx +7 -1
  30. package/src/RichTextEditor/RichTextEditor.cypress.spec.tsx +100 -52
  31. package/src/RichTextEditor/RichTextEditor.tsx +9 -3
  32. package/src/RichTextEditor/plugins/ToolbarPlugin/AlignPicker.tsx +21 -20
  33. package/src/RichTextEditor/plugins/ToolbarPlugin/BlockButtons.tsx +35 -77
  34. package/src/RichTextEditor/plugins/ToolbarPlugin/FormatButtons.tsx +29 -83
  35. package/src/RichTextEditor/plugins/ToolbarPlugin/LinkButton.tsx +106 -0
  36. package/src/RichTextEditor/plugins/ToolbarPlugin/ListButton.tsx +155 -0
  37. package/src/RichTextEditor/plugins/ToolbarPlugin/MacrosButton.tsx +23 -39
  38. package/src/RichTextEditor/plugins/ToolbarPlugin/ToolbarPlugin.styles.ts +24 -14
  39. package/src/RichTextEditor/plugins/ToolbarPlugin/UndoRedoButtons.tsx +6 -0
  40. package/src/RichTextEditor/plugins/ToolbarPlugin/index.tsx +5 -10
  41. package/src/RichTextEditor/translatedLabels.ts +3 -0
  42. package/src/ThemeProvider/palettes.ts +4 -4
  43. package/src/Typography/EllipsisTypography.tsx +18 -0
  44. package/src/Typography/Subtitle.tsx +55 -0
  45. package/src/api/buildListingEndpoint/models.ts +2 -1
  46. package/src/api/customFetch.ts +12 -3
  47. package/src/api/useFetchQuery/index.ts +22 -5
  48. package/src/api/useGraphQuery/index.ts +4 -1
  49. package/src/api/useMutationQuery/index.ts +12 -2
  50. package/src/components/Form/AccessRightsV2/AccessRights.cypress.spec.tsx +350 -0
  51. package/src/components/Form/AccessRightsV2/AccessRights.stories.tsx +119 -0
  52. package/src/components/Form/AccessRightsV2/AccessRights.styles.ts +10 -0
  53. package/src/components/Form/AccessRightsV2/AccessRights.tsx +60 -0
  54. package/src/components/Form/AccessRightsV2/Actions/Actions.styles.ts +14 -0
  55. package/src/components/Form/AccessRightsV2/Actions/Actions.tsx +81 -0
  56. package/src/components/Form/AccessRightsV2/Actions/useActions.ts +79 -0
  57. package/src/components/Form/AccessRightsV2/List/Item.tsx +90 -0
  58. package/src/components/Form/AccessRightsV2/List/List.styles.tsx +45 -0
  59. package/src/components/Form/AccessRightsV2/List/List.tsx +52 -0
  60. package/src/components/Form/AccessRightsV2/List/ListItemSkeleton.tsx +18 -0
  61. package/src/components/Form/AccessRightsV2/List/ListSkeleton.tsx +22 -0
  62. package/src/components/Form/AccessRightsV2/List/RemoveAccessRight.tsx +38 -0
  63. package/src/components/Form/AccessRightsV2/List/StateChip.tsx +28 -0
  64. package/src/components/Form/AccessRightsV2/List/useItem.ts +64 -0
  65. package/src/components/Form/AccessRightsV2/Provider.tsx +15 -0
  66. package/src/components/Form/AccessRightsV2/ShareInput/ContactSwitch.tsx +42 -0
  67. package/src/components/Form/AccessRightsV2/ShareInput/ShareInput.styles.ts +9 -0
  68. package/src/components/Form/AccessRightsV2/ShareInput/ShareInput.tsx +77 -0
  69. package/src/components/Form/AccessRightsV2/ShareInput/useShareInput.tsx +109 -0
  70. package/src/components/Form/AccessRightsV2/Stats/Stats.tsx +36 -0
  71. package/src/components/Form/AccessRightsV2/atoms.ts +97 -0
  72. package/src/components/Form/AccessRightsV2/common/RoleSelectField.styles.tsx +7 -0
  73. package/src/components/Form/AccessRightsV2/common/RoleSelectField.tsx +45 -0
  74. package/src/components/Form/AccessRightsV2/models.ts +54 -0
  75. package/src/components/Form/AccessRightsV2/storiesData.ts +221 -0
  76. package/src/components/Form/AccessRightsV2/useAccessRightsInitValues.ts +51 -0
  77. package/src/components/Form/index.ts +1 -0
  78. package/src/components/Header/PageHeader/PageHeader.styles.ts +28 -1
  79. package/src/components/Header/PageHeader/PageHeaderMessage.tsx +32 -0
  80. package/src/components/Header/PageHeader/index.ts +2 -0
  81. package/src/components/ItemComposition/Item.tsx +2 -2
  82. package/src/components/ItemComposition/ItemComposition.styles.ts +3 -0
  83. package/src/components/ItemComposition/ItemComposition.tsx +4 -2
  84. package/src/components/List/Item/ListItem.styles.ts +6 -0
  85. package/src/components/List/Item/ListItem.tsx +3 -1
  86. package/src/components/List/Item/Text.tsx +5 -3
  87. package/src/components/Menu/Button/MenuButton.tsx +4 -2
  88. package/src/components/Menu/MenuItem.tsx +5 -3
  89. package/src/components/Menu/MenuItems.tsx +4 -3
  90. package/src/components/Modal/Modal.styles.ts +4 -2
  91. package/src/index.ts +5 -0
  92. package/src/utils/useInfiniteScrollListing.ts +22 -7
  93. package/src/utils/useLocaleDateTimeFormat/index.ts +3 -0
  94. package/src/Graph/LineChart/Legend/Marker.tsx +0 -43
@@ -0,0 +1,18 @@
1
+ import { Box, Typography, TypographyProps } from '@mui/material';
2
+
3
+ const EllipsisTypography = (props: TypographyProps): JSX.Element => {
4
+ return (
5
+ <Box sx={{ width: '100%' }}>
6
+ <Typography
7
+ sx={{
8
+ overflow: 'hidden',
9
+ textOverflow: 'ellipsis',
10
+ whiteSpace: 'nowrap'
11
+ }}
12
+ {...props}
13
+ />
14
+ </Box>
15
+ );
16
+ };
17
+
18
+ export default EllipsisTypography;
@@ -0,0 +1,55 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ import { type, equals } from 'ramda';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ import { Typography } from '@mui/material';
7
+ import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
8
+
9
+ import { Tooltip } from '../components';
10
+
11
+ interface Props {
12
+ children: ReactNode;
13
+ secondaryLabel?: string | Array<string>;
14
+ }
15
+
16
+ const Subtitle = ({ children, secondaryLabel }: Props): JSX.Element => {
17
+ const { t } = useTranslation();
18
+
19
+ const containsSeveralSecondaryLabels = equals(type(secondaryLabel), 'Array');
20
+
21
+ return (
22
+ <Typography variant="subtitle1">
23
+ <strong>{children}</strong>
24
+ {secondaryLabel && (
25
+ <Tooltip
26
+ aria-label={
27
+ containsSeveralSecondaryLabels
28
+ ? secondaryLabel[0]
29
+ : (secondaryLabel as string)
30
+ }
31
+ followCursor={false}
32
+ label={
33
+ containsSeveralSecondaryLabels ? (
34
+ <>
35
+ {secondaryLabel.map((label) => (
36
+ <p key={label}>{t(label)}</p>
37
+ ))}
38
+ </>
39
+ ) : (
40
+ t(secondaryLabel)
41
+ )
42
+ }
43
+ placement="right"
44
+ >
45
+ <HelpOutlineIcon
46
+ color="primary"
47
+ sx={{ ml: 1, verticalAlign: 'middle' }}
48
+ />
49
+ </Tooltip>
50
+ )}
51
+ </Typography>
52
+ );
53
+ };
54
+
55
+ export default Subtitle;
@@ -52,7 +52,8 @@ export type Operator =
52
52
  | '$lk'
53
53
  | '$nk'
54
54
  | '$in'
55
- | '$ni';
55
+ | '$ni'
56
+ | '$rg';
56
57
 
57
58
  export type ConditionValue = {
58
59
  [value in Operator]?: string | Array<string>;
@@ -1,4 +1,4 @@
1
- import { equals } from 'ramda';
1
+ import { equals, isNil, startsWith } from 'ramda';
2
2
  import { JsonDecoder } from 'ts.data.json';
3
3
 
4
4
  import { Method } from './useMutationQuery';
@@ -22,6 +22,7 @@ export interface CatchErrorProps {
22
22
  }
23
23
 
24
24
  interface CustomFetchProps<T> {
25
+ baseEndpoint?: string;
25
26
  catchError?: (props: CatchErrorProps) => void;
26
27
  decoder?: JsonDecoder.Decoder<T>;
27
28
  defaultFailureMessage?: string;
@@ -42,10 +43,18 @@ export const customFetch = <T>({
42
43
  defaultFailureMessage = 'Something went wrong',
43
44
  isMutation = false,
44
45
  payload,
45
- method = 'GET'
46
+ method = 'GET',
47
+ baseEndpoint = './api/latest'
46
48
  }: CustomFetchProps<T>): Promise<T | ResponseError> => {
47
49
  const defaultOptions = { headers, method, signal };
48
50
 
51
+ const formattedEndpoint =
52
+ !isNil(baseEndpoint) &&
53
+ !startsWith(baseEndpoint, endpoint) &&
54
+ !startsWith('./api/internal.php', endpoint)
55
+ ? `${baseEndpoint}${endpoint}`
56
+ : endpoint;
57
+
49
58
  const options = isMutation
50
59
  ? {
51
60
  ...defaultOptions,
@@ -53,7 +62,7 @@ export const customFetch = <T>({
53
62
  }
54
63
  : defaultOptions;
55
64
 
56
- return fetch(endpoint, options)
65
+ return fetch(formattedEndpoint, options)
57
66
  .then((response) => {
58
67
  if (equals(response.status, 204)) {
59
68
  return {
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo } from 'react';
1
+ import { useEffect, useMemo, useRef } from 'react';
2
2
 
3
3
  import 'ulog';
4
4
  import {
@@ -10,16 +10,18 @@ import {
10
10
  } from '@tanstack/react-query';
11
11
  import { JsonDecoder } from 'ts.data.json';
12
12
  import anylogger from 'anylogger';
13
- import { has, includes, not, omit } from 'ramda';
13
+ import { has, includes, isNil, not, omit } from 'ramda';
14
14
 
15
15
  import { CatchErrorProps, customFetch, ResponseError } from '../customFetch';
16
16
  import useSnackbar from '../../Snackbar/useSnackbar';
17
17
  import { useDeepCompare } from '../../utils';
18
18
 
19
19
  export interface UseFetchQueryProps<T> {
20
+ baseEndpoint?: string;
20
21
  catchError?: (props: CatchErrorProps) => void;
21
22
  decoder?: JsonDecoder.Decoder<T>;
22
23
  defaultFailureMessage?: string;
24
+ doNotCancelCallsOnUnmount?: boolean;
23
25
  fetchHeaders?: HeadersInit;
24
26
  getEndpoint: (params?: PrefetchEndpointParams) => string;
25
27
  getQueryKey: () => QueryKey;
@@ -54,14 +56,19 @@ const useFetchQuery = <T extends object>({
54
56
  fetchHeaders,
55
57
  isPaginated,
56
58
  queryOptions,
57
- httpCodesBypassErrorSnackbar = []
59
+ httpCodesBypassErrorSnackbar = [],
60
+ baseEndpoint,
61
+ doNotCancelCallsOnUnmount = false
58
62
  }: UseFetchQueryProps<T>): UseFetchQueryState<T> => {
63
+ const dataRef = useRef<T | undefined>(undefined);
64
+
59
65
  const { showErrorMessage } = useSnackbar();
60
66
 
61
67
  const queryData = useQuery<T | ResponseError, Error>(
62
68
  getQueryKey(),
63
69
  ({ signal }): Promise<T | ResponseError> =>
64
70
  customFetch<T>({
71
+ baseEndpoint,
65
72
  catchError,
66
73
  decoder,
67
74
  defaultFailureMessage,
@@ -96,6 +103,7 @@ const useFetchQuery = <T extends object>({
96
103
  queryKey,
97
104
  ({ signal }): Promise<T | ResponseError> =>
98
105
  customFetch<T>({
106
+ baseEndpoint,
99
107
  catchError,
100
108
  decoder,
101
109
  defaultFailureMessage,
@@ -137,6 +145,7 @@ const useFetchQuery = <T extends object>({
137
145
  getQueryKey(),
138
146
  ({ signal }): Promise<T | ResponseError> =>
139
147
  customFetch<T>({
148
+ baseEndpoint,
140
149
  catchError,
141
150
  decoder,
142
151
  defaultFailureMessage,
@@ -153,10 +162,18 @@ const useFetchQuery = <T extends object>({
153
162
  [queryData.data]
154
163
  );
155
164
 
165
+ if (!isNil(data)) {
166
+ dataRef.current = data;
167
+ }
168
+
156
169
  const errorData = queryData.data as ResponseError | undefined;
157
170
 
158
171
  useEffect(() => {
159
172
  return (): void => {
173
+ if (doNotCancelCallsOnUnmount) {
174
+ return;
175
+ }
176
+
160
177
  queryClient.cancelQueries(getQueryKey());
161
178
  };
162
179
  }, []);
@@ -166,8 +183,8 @@ const useFetchQuery = <T extends object>({
166
183
  }, useDeepCompare([queryData.data]));
167
184
 
168
185
  return {
169
- ...omit(['data'], queryData),
170
- data,
186
+ ...omit(['data', 'error'], queryData),
187
+ data: dataRef.current,
171
188
  error: errorData?.isError ? omit(['isError'], errorData) : null,
172
189
  fetchQuery,
173
190
  prefetchNextPage,
@@ -92,6 +92,9 @@ const useGraphQuery = ({
92
92
  : getStartEndFromTimePeriod(timePeriodToUse as number);
93
93
 
94
94
  const definedMetrics = metrics.filter((metric) => metric);
95
+ const formattedDefinedMetrics = definedMetrics.map((metric) =>
96
+ encodeURIComponent(metric)
97
+ );
95
98
 
96
99
  const {
97
100
  data: graphData,
@@ -115,7 +118,7 @@ const useGraphQuery = ({
115
118
 
116
119
  return `${endpoint}&start=${startAndEnd.start}&end=${
117
120
  startAndEnd.end
118
- }&metric_names=[${definedMetrics.join(',')}]`;
121
+ }&metric_names=[${formattedDefinedMetrics.join(',')}]`;
119
122
  },
120
123
  getQueryKey: () => [
121
124
  'graph',
@@ -23,6 +23,7 @@ export enum Method {
23
23
  }
24
24
 
25
25
  export type UseMutationQueryProps<T, TMeta> = {
26
+ baseEndpoint?: string;
26
27
  catchError?: (props: CatchErrorProps) => void;
27
28
  decoder?: JsonDecoder.Decoder<T>;
28
29
  defaultFailureMessage?: string;
@@ -49,7 +50,8 @@ const useMutationQuery = <T extends object, TMeta>({
49
50
  method,
50
51
  onMutate,
51
52
  onError,
52
- onSuccess
53
+ onSuccess,
54
+ baseEndpoint
53
55
  }: UseMutationQueryProps<T, TMeta>): UseMutationQueryState<T> => {
54
56
  const { showErrorMessage } = useSnackbar();
55
57
 
@@ -62,6 +64,7 @@ const useMutationQuery = <T extends object, TMeta>({
62
64
  const { _meta, ...payload } = _payload || {};
63
65
 
64
66
  return customFetch<T>({
67
+ baseEndpoint,
65
68
  catchError,
66
69
  decoder,
67
70
  defaultFailureMessage,
@@ -78,7 +81,14 @@ const useMutationQuery = <T extends object, TMeta>({
78
81
  {
79
82
  onError,
80
83
  onMutate,
81
- onSuccess
84
+ onSuccess: (data, variables, context) => {
85
+ if (data?.isError) {
86
+ onError?.(data, variables, context);
87
+
88
+ return;
89
+ }
90
+ onSuccess?.(data, variables, context);
91
+ }
82
92
  }
83
93
  );
84
94
 
@@ -0,0 +1,350 @@
1
+ import { Provider, createStore } from 'jotai';
2
+
3
+ import { Method, SnackbarProvider, TestQueryProvider } from '../../..';
4
+
5
+ import { AccessRights } from './AccessRights';
6
+ import {
7
+ buildResult,
8
+ emptyAccessRights,
9
+ labels,
10
+ removedAccessRights,
11
+ roles,
12
+ simpleAccessRights,
13
+ updatedAccessRights
14
+ } from './storiesData';
15
+
16
+ const initialize = ({
17
+ initialValues = simpleAccessRights,
18
+ loading = false,
19
+ link = 'link'
20
+ }): unknown => {
21
+ const cancel = cy.stub();
22
+ const save = cy.stub();
23
+
24
+ cy.interceptAPIRequest({
25
+ alias: 'getContacts',
26
+ method: Method.GET,
27
+ path: '**/contacts?**',
28
+ response: buildResult(false)
29
+ });
30
+ cy.interceptAPIRequest({
31
+ alias: 'getContactGroups',
32
+ method: Method.GET,
33
+ path: '**/contact-groups?**',
34
+ response: buildResult(true)
35
+ });
36
+
37
+ cy.mount({
38
+ Component: (
39
+ <SnackbarProvider>
40
+ <TestQueryProvider>
41
+ <Provider store={createStore()}>
42
+ <AccessRights
43
+ cancel={cancel}
44
+ endpoints={{
45
+ contact: '/contacts',
46
+ contactGroup: '/contact-groups'
47
+ }}
48
+ initialValues={initialValues}
49
+ labels={labels}
50
+ link={link}
51
+ loading={loading}
52
+ roles={roles}
53
+ submit={save}
54
+ />
55
+ </Provider>
56
+ </TestQueryProvider>
57
+ </SnackbarProvider>
58
+ )
59
+ });
60
+
61
+ return {
62
+ cancel,
63
+ save
64
+ };
65
+ };
66
+
67
+ describe('Access rights', () => {
68
+ it('displays the access rights in loading state', () => {
69
+ initialize({ loading: true });
70
+
71
+ cy.contains('Share dashboard with').should('be.visible');
72
+ cy.contains('Contact').should('be.visible');
73
+ cy.contains('Contact group').should('be.visible');
74
+ cy.findByLabelText('Add a contact').should('be.visible');
75
+ cy.findByTestId('add_role').should('be.disabled');
76
+ cy.findByTestId('add').should('be.disabled');
77
+ cy.findByLabelText('Copy link').should('be.visible');
78
+ cy.findByLabelText('Cancel').should('be.visible');
79
+ cy.findByLabelText('Save').should('be.visible');
80
+
81
+ cy.makeSnapshot();
82
+ });
83
+
84
+ it('displays the access rights without link', () => {
85
+ initialize({ link: null });
86
+
87
+ cy.findByLabelText('Copy link').should('not.exist');
88
+
89
+ cy.makeSnapshot();
90
+ });
91
+
92
+ it('displays the access rights with an empty list', () => {
93
+ initialize({ initialValues: emptyAccessRights });
94
+
95
+ cy.contains('The contact list is empty').should('be.visible');
96
+
97
+ cy.makeSnapshot();
98
+ });
99
+
100
+ it('displays the access rights with an empty list', () => {
101
+ initialize({});
102
+
103
+ simpleAccessRights.forEach(({ name, email, isContactGroup, role }) => {
104
+ cy.contains(name).should('be.visible');
105
+ cy.contains(email).should('be.visible');
106
+ cy.get(`[data-groupChip="${name}"]`).should(
107
+ isContactGroup ? 'be.visible' : 'not.exist'
108
+ );
109
+ cy.findByTestId(`role-${name}`).should('have.value', role);
110
+ cy.findByTestId(`remove-${name}`).should('be.visible');
111
+ });
112
+
113
+ cy.makeSnapshot();
114
+ });
115
+
116
+ it('displays a removed chip when the corresponding icon is clicked', () => {
117
+ initialize({});
118
+
119
+ cy.findByTestId(`remove-Leah McGlynn`).should(
120
+ 'have.attr',
121
+ 'data-removed',
122
+ 'false'
123
+ );
124
+
125
+ cy.findByTestId(`remove-Leah McGlynn`).click();
126
+
127
+ cy.findByTestId(`remove-Leah McGlynn`).should(
128
+ 'have.attr',
129
+ 'data-removed',
130
+ 'true'
131
+ );
132
+ cy.contains(labels.list.removed).should('be.visible');
133
+
134
+ cy.makeSnapshot();
135
+ });
136
+
137
+ it('restores the contact when the contact is removed and the corresponding icon is clicked', () => {
138
+ initialize({});
139
+
140
+ cy.findByTestId(`remove-Leah McGlynn`).click();
141
+
142
+ cy.findByTestId(`remove-Leah McGlynn`).should(
143
+ 'have.attr',
144
+ 'data-removed',
145
+ 'true'
146
+ );
147
+ cy.contains(labels.list.removed).should('be.visible');
148
+
149
+ cy.findByTestId(`remove-Leah McGlynn`).click();
150
+
151
+ cy.findByTestId(`remove-Leah McGlynn`).should(
152
+ 'have.attr',
153
+ 'data-removed',
154
+ 'false'
155
+ );
156
+ cy.contains(labels.list.removed).should('not.exist');
157
+
158
+ cy.makeSnapshot();
159
+ });
160
+
161
+ it('submits the new acces rights list without the removed contact', () => {
162
+ const { save } = initialize({});
163
+
164
+ cy.findByTestId(`remove-Leah McGlynn`).click();
165
+
166
+ cy.findByTestId(`remove-Leah McGlynn`).should(
167
+ 'have.attr',
168
+ 'data-removed',
169
+ 'true'
170
+ );
171
+ cy.contains(labels.list.removed).should('be.visible');
172
+ cy.contains('1 removed').should('be.visible');
173
+
174
+ cy.contains(labels.actions.save)
175
+ .click()
176
+ .then(() => {
177
+ expect(save).to.have.been.calledWith(removedAccessRights);
178
+ });
179
+
180
+ cy.makeSnapshot();
181
+ });
182
+
183
+ it('submits the new acces rights list with the updated contact', () => {
184
+ const { save } = initialize({});
185
+
186
+ cy.findByTestId(`role-Leah McGlynn`).parent().click();
187
+
188
+ cy.get('li[data-value="editor"]').click();
189
+ cy.contains(labels.list.updated).should('be.visible');
190
+ cy.contains('1 updated').should('be.visible');
191
+
192
+ cy.contains(labels.actions.save)
193
+ .click()
194
+ .then(() => {
195
+ expect(save).to.have.been.calledWith(updatedAccessRights);
196
+ });
197
+
198
+ cy.makeSnapshot();
199
+ });
200
+
201
+ it('removes the updated chip when the contact role is updated and its initial role is assigned back', () => {
202
+ initialize({});
203
+
204
+ cy.findByTestId(`role-Leah McGlynn`).parent().click();
205
+
206
+ cy.get('li[data-value="editor"]').click();
207
+ cy.contains(labels.list.updated).should('be.visible');
208
+
209
+ cy.get('li[data-value="viewer"]').click();
210
+ cy.contains(labels.list.updated).should('not.exist');
211
+
212
+ cy.makeSnapshot();
213
+ });
214
+
215
+ it('retrieves contacts when the contact radio is selected', () => {
216
+ initialize({});
217
+
218
+ cy.contains(labels.add.contact).click();
219
+ cy.findByLabelText(labels.add.autocompleteContact).click();
220
+
221
+ cy.waitForRequest('@getContacts');
222
+
223
+ cy.makeSnapshot();
224
+ });
225
+
226
+ it('retrieves contact groups when the contact group radio is selected', () => {
227
+ initialize({});
228
+
229
+ cy.contains(labels.add.contactGroup).click();
230
+ cy.findByLabelText(labels.add.autocompleteContactGroup).click();
231
+
232
+ cy.waitForRequest('@getContactGroups');
233
+
234
+ cy.makeSnapshot();
235
+ });
236
+
237
+ it('adds the contact as viewer when a contact is selected and the corresponding button is clicked', () => {
238
+ initialize({});
239
+
240
+ cy.contains(labels.add.contact).click();
241
+ cy.findByLabelText(labels.add.autocompleteContact).click();
242
+
243
+ cy.waitForRequest('@getContacts');
244
+
245
+ cy.contains('Entity 10').click();
246
+
247
+ cy.findByTestId('add').click();
248
+
249
+ cy.contains('Entity 10').should('be.visible');
250
+
251
+ cy.findByTestId('role-Entity 10').should('have.value', 'viewer');
252
+ cy.contains(labels.list.added).should('be.visible');
253
+
254
+ cy.makeSnapshot();
255
+ });
256
+
257
+ it('adds the contact group as editor when a contact group is selected and the corresponding button is clicked', () => {
258
+ initialize({});
259
+
260
+ cy.contains(labels.add.contactGroup).click();
261
+ cy.findByLabelText(labels.add.autocompleteContactGroup).click();
262
+
263
+ cy.waitForRequest('@getContactGroups');
264
+
265
+ cy.contains('Entity Group 10').click();
266
+ cy.findByTestId('add_role').parent().click();
267
+ cy.get('li[data-value="editor"]').click();
268
+ cy.findByTestId('add').click();
269
+
270
+ cy.contains('Entity Group 10').should('be.visible');
271
+
272
+ cy.findByTestId('role-Entity Group 10').should('have.value', 'editor');
273
+ cy.contains(labels.list.added).should('be.visible');
274
+ cy.contains('1 added').should('be.visible');
275
+
276
+ cy.makeSnapshot();
277
+ });
278
+
279
+ it('displays statistics when a contact is added, a contact is updated and a contact is removed', () => {
280
+ initialize({});
281
+
282
+ cy.contains(labels.add.contactGroup).click();
283
+ cy.findByLabelText(labels.add.autocompleteContactGroup).click();
284
+
285
+ cy.waitForRequest('@getContactGroups');
286
+
287
+ cy.contains('Entity Group 10').click();
288
+ cy.findByTestId('add_role').parent().click();
289
+ cy.get('li[data-value="editor"]').click();
290
+ cy.findByTestId('add').click();
291
+
292
+ cy.contains('Entity Group 10').should('be.visible');
293
+
294
+ cy.findByTestId(`role-Leah McGlynn`).parent().click();
295
+ cy.get('li[data-value="editor"]').click();
296
+
297
+ cy.findByTestId('remove-Jody Blanda').click();
298
+
299
+ cy.contains('1 added | 1 updated | 1 removed').should('be.visible');
300
+
301
+ cy.makeSnapshot();
302
+ });
303
+
304
+ it('clears the autocomplete when a contact is selected and the contact type is changed', () => {
305
+ initialize({});
306
+
307
+ cy.contains(labels.add.contact).click();
308
+ cy.findByLabelText(labels.add.autocompleteContact).click();
309
+
310
+ cy.waitForRequest('@getContacts');
311
+
312
+ cy.contains('Entity 10').click();
313
+ cy.findByLabelText(labels.add.autocompleteContact).should(
314
+ 'have.value',
315
+ 'Entity 10'
316
+ );
317
+
318
+ cy.contains(labels.add.contactGroup).click();
319
+ cy.findByLabelText(labels.add.autocompleteContactGroup).should(
320
+ 'have.value',
321
+ ''
322
+ );
323
+
324
+ cy.makeSnapshot();
325
+ });
326
+
327
+ it('removes the contact from the list when the contact has been to the list and the corresponding button is clicked', () => {
328
+ initialize({});
329
+
330
+ cy.contains(labels.add.contact).click();
331
+ cy.findByLabelText(labels.add.autocompleteContact).click();
332
+
333
+ cy.waitForRequest('@getContacts');
334
+
335
+ cy.contains('Entity 10').click();
336
+
337
+ cy.findByTestId('add').click();
338
+
339
+ cy.contains('Entity 10').should('be.visible');
340
+
341
+ cy.contains(labels.list.added).should('be.visible');
342
+
343
+ cy.findByTestId('remove-Entity 10').click();
344
+
345
+ cy.contains('Entity 10').should('not.exist');
346
+ cy.contains(labels.list.added).should('not.exist');
347
+
348
+ cy.makeSnapshot();
349
+ });
350
+ });