@centreon/ui 24.4.1-MON-32982-migrate-playlists-front-code.4 → 24.4.1-MON-25027-refactor-access-rights.3

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 (40) hide show
  1. package/package.json +6 -1
  2. package/public/mockServiceWorker.js +2 -2
  3. package/src/FallbackPage/FallbackPage.tsx +3 -3
  4. package/src/InputField/Select/Autocomplete/Connected/index.tsx +12 -3
  5. package/src/Listing/Cell/DataCell.tsx +1 -15
  6. package/src/Module/LicensedModule/LicenseCheck/index.tsx +10 -25
  7. package/src/Module/LicensedModule/index.tsx +1 -7
  8. package/src/Module/index.tsx +4 -2
  9. package/src/components/Form/AccessRightsV2/AccessRights.cypress.spec.tsx +346 -0
  10. package/src/components/Form/AccessRightsV2/AccessRights.stories.tsx +119 -0
  11. package/src/components/Form/AccessRightsV2/AccessRights.styles.ts +10 -0
  12. package/src/components/Form/AccessRightsV2/AccessRights.tsx +56 -0
  13. package/src/components/Form/AccessRightsV2/Actions/Actions.styles.ts +14 -0
  14. package/src/components/Form/AccessRightsV2/Actions/Actions.tsx +60 -0
  15. package/src/components/Form/AccessRightsV2/Actions/useActions.ts +70 -0
  16. package/src/components/Form/AccessRightsV2/List/Item.tsx +90 -0
  17. package/src/components/Form/AccessRightsV2/List/List.styles.tsx +45 -0
  18. package/src/components/Form/AccessRightsV2/List/List.tsx +52 -0
  19. package/src/components/Form/AccessRightsV2/List/ListItemSkeleton.tsx +18 -0
  20. package/src/components/Form/AccessRightsV2/List/ListSkeleton.tsx +22 -0
  21. package/src/components/Form/AccessRightsV2/List/RemoveAccessRight.tsx +38 -0
  22. package/src/components/Form/AccessRightsV2/List/StateChip.tsx +28 -0
  23. package/src/components/Form/AccessRightsV2/List/useItem.ts +64 -0
  24. package/src/components/Form/AccessRightsV2/Provider.tsx +13 -0
  25. package/src/components/Form/AccessRightsV2/ShareInput/ContactSwitch.tsx +42 -0
  26. package/src/components/Form/AccessRightsV2/ShareInput/ShareInput.styles.ts +9 -0
  27. package/src/components/Form/AccessRightsV2/ShareInput/ShareInput.tsx +77 -0
  28. package/src/components/Form/AccessRightsV2/ShareInput/useShareInput.tsx +109 -0
  29. package/src/components/Form/AccessRightsV2/Stats/Stats.tsx +36 -0
  30. package/src/components/Form/AccessRightsV2/atoms.ts +97 -0
  31. package/src/components/Form/AccessRightsV2/common/RoleSelectField.styles.tsx +7 -0
  32. package/src/components/Form/AccessRightsV2/common/RoleSelectField.tsx +45 -0
  33. package/src/components/Form/AccessRightsV2/models.ts +54 -0
  34. package/src/components/Form/AccessRightsV2/storiesData.ts +221 -0
  35. package/src/components/Form/AccessRightsV2/useAccessRightsInitValues.ts +42 -0
  36. package/src/components/Form/index.ts +1 -0
  37. package/src/components/List/Item/ListItem.styles.ts +6 -0
  38. package/src/components/List/Item/ListItem.tsx +3 -1
  39. package/src/components/List/Item/Text.tsx +5 -3
  40. package/src/Module/Module.cypress.spec.tsx +0 -129
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centreon/ui",
3
- "version": "24.4.1-MON-32982-migrate-playlists-front-code.4",
3
+ "version": "24.4.1-MON-25027-refactor-access-rights.3",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "eslint": "eslint ./src --ext .js,.jsx,.ts,.tsx --max-warnings 0",
@@ -85,6 +85,8 @@
85
85
  "identity-obj-proxy": "^3.0.0",
86
86
  "jest-transform-stub": "^2.0.0",
87
87
  "mochawesome": "^7.1.3",
88
+ "msw": "1.3.2",
89
+ "msw-storybook-addon": "^1.10.0",
88
90
  "puppeteer": "^13.7.0",
89
91
  "react": "^18.2.0",
90
92
  "react-dom": "^18.2.0",
@@ -157,5 +159,8 @@
157
159
  "jest-junit": {
158
160
  "suiteName": "jest tests",
159
161
  "output": "./junit.xml"
162
+ },
163
+ "msw": {
164
+ "workerDirectory": "public"
160
165
  }
161
166
  }
@@ -2,7 +2,7 @@
2
2
  /* tslint:disable */
3
3
 
4
4
  /**
5
- * Mock Service Worker (1.2.1).
5
+ * Mock Service Worker (1.3.2).
6
6
  * @see https://github.com/mswjs/msw
7
7
  * - Please do NOT modify this file.
8
8
  * - Please do NOT serve this file on production.
@@ -300,4 +300,4 @@ function sleep(timeMs) {
300
300
  async function respondWithMock(response) {
301
301
  await sleep(response.delay)
302
302
  return new Response(response.body, response)
303
- }
303
+ }
@@ -17,7 +17,7 @@ const useStyles = makeStyles()((theme) => ({
17
17
  logo: {
18
18
  alignSelf: 'flex-end',
19
19
  height: theme.spacing(11),
20
- width: '239px'
20
+ width: '22rem'
21
21
  },
22
22
  message: {
23
23
  color: theme.palette.text.primary
@@ -66,9 +66,9 @@ export const FallbackPage: FC<FallbackPageProps> = typedMemo(
66
66
 
67
67
  return (
68
68
  <div className={classes.notAuthorizedContainer}>
69
- <div className={classes.logo}>
69
+ <section className={classes.logo}>
70
70
  <CentreonLogo />
71
- </div>
71
+ </section>
72
72
  <section className={classes.messageBlock}>
73
73
  <header>
74
74
  <Typography color="primary" fontWeight="bold" variant="h3">
@@ -33,6 +33,7 @@ import useFetchQuery from '../../../../api/useFetchQuery';
33
33
  export interface ConnectedAutoCompleteFieldProps<TData> {
34
34
  allowUniqOption?: boolean;
35
35
  baseEndpoint?: string;
36
+ changeIdValue: (item: TData) => number | string;
36
37
  conditionField?: keyof SelectEntry;
37
38
  field: string;
38
39
  getEndpoint: ({ search, page }) => string;
@@ -62,6 +63,7 @@ const ConnectedAutocompleteField = (
62
63
  queryKey,
63
64
  allowUniqOption,
64
65
  baseEndpoint,
66
+ changeIdValue,
65
67
  ...props
66
68
  }: ConnectedAutoCompleteFieldProps<TData> &
67
69
  Omit<AutocompleteFieldProps, 'options'>): JSX.Element => {
@@ -238,17 +240,24 @@ const ConnectedAutocompleteField = (
238
240
 
239
241
  const moreOptions = page > 1 ? options : [];
240
242
 
243
+ const formattedList = changeIdValue
244
+ ? newOptions.result.map((item) => ({
245
+ ...item,
246
+ id: changeIdValue(item)
247
+ }))
248
+ : newOptions.result;
249
+
241
250
  if (!isEmpty(labelKey) && !isNil(labelKey)) {
242
- const list = newOptions.result.map((item) =>
251
+ const list = formattedList.map((item) =>
243
252
  renameKey({ key: labelKey, newKey: 'name', object: item })
244
253
  );
245
254
  setOptions(moreOptions.concat(list as Array<TData>));
246
255
 
247
256
  return;
248
257
  }
249
- setOptions(moreOptions.concat(newOptions.result));
258
+ setOptions(moreOptions.concat(formattedList));
250
259
 
251
- setOptions(moreOptions.concat(newOptions.result as Array<TData>));
260
+ setOptions(moreOptions.concat(formattedList as Array<TData>));
252
261
 
253
262
  const total = prop('total', newOptions.meta) || 1;
254
263
  const limit = prop('limit', newOptions.meta) || 1;
@@ -107,21 +107,7 @@ const DataCell = ({
107
107
  const isCellHidden = getHiddenCondition?.(isRowSelected);
108
108
 
109
109
  if (isCellHidden) {
110
- return (
111
- <Cell
112
- className={classes.cell}
113
- isRowHighlighted={isRowHighlighted}
114
- listingVariant={listingVariant}
115
- onClick={(e): void => {
116
- if (!clickable) {
117
- return;
118
- }
119
- e.preventDefault();
120
- e.stopPropagation();
121
- }}
122
- {...commonCellProps}
123
- />
124
- );
110
+ return null;
125
111
  }
126
112
 
127
113
  return (
@@ -1,7 +1,10 @@
1
+ import * as React from 'react';
2
+
3
+ import { isNil } from 'ramda';
1
4
  import { useTranslation } from 'react-i18next';
2
5
 
3
6
  import { FallbackPage } from '../../../FallbackPage/FallbackPage';
4
- import { MenuSkeleton, PageSkeleton, useFetchQuery } from '../../..';
7
+ import { PageSkeleton, useFetchQuery } from '../../..';
5
8
  import { getModuleLicenseCheckEndpoint } from '../api';
6
9
 
7
10
  import { licenseDecoder } from './decoder';
@@ -14,27 +17,17 @@ import {
14
17
 
15
18
  export interface LicenseCheckProps {
16
19
  children: React.ReactElement;
17
- isFederatedComponent?: boolean;
18
20
  moduleName: string;
19
21
  }
20
22
 
21
23
  interface ContentProps {
22
24
  children: React.ReactElement;
23
- isFederatedComponent?: boolean;
24
25
  isValid: boolean;
25
26
  }
26
27
 
27
- const Content = ({
28
- children,
29
- isValid,
30
- isFederatedComponent
31
- }: ContentProps): JSX.Element | null => {
28
+ const Content = ({ children, isValid }: ContentProps): JSX.Element => {
32
29
  const { t } = useTranslation();
33
30
 
34
- if (isFederatedComponent && !isValid) {
35
- return null;
36
- }
37
-
38
31
  return isValid ? (
39
32
  children
40
33
  ) : (
@@ -48,10 +41,9 @@ const Content = ({
48
41
 
49
42
  const LicenseCheck = ({
50
43
  children,
51
- moduleName,
52
- isFederatedComponent
44
+ moduleName
53
45
  }: LicenseCheckProps): JSX.Element | null => {
54
- const { isError, data, isLoading } = useFetchQuery<License>({
46
+ const { isError, data } = useFetchQuery<License>({
55
47
  decoder: licenseDecoder,
56
48
  getEndpoint: () => getModuleLicenseCheckEndpoint(moduleName),
57
49
  getQueryKey: () => ['license', moduleName]
@@ -63,17 +55,10 @@ const LicenseCheck = ({
63
55
 
64
56
  const isValid = data?.success;
65
57
 
66
- const skeleton = isFederatedComponent ? <MenuSkeleton /> : <PageSkeleton />;
67
-
68
- return isLoading ? (
69
- skeleton
58
+ return isNil(isValid) ? (
59
+ <PageSkeleton />
70
60
  ) : (
71
- <Content
72
- isFederatedComponent={isFederatedComponent}
73
- isValid={isValid as boolean}
74
- >
75
- {children}
76
- </Content>
61
+ <Content isValid={isValid as boolean}>{children}</Content>
77
62
  );
78
63
  };
79
64
 
@@ -7,17 +7,11 @@ type Props = ModuleProps & LicenseCheckProps;
7
7
  const LicensedModule = ({
8
8
  moduleName,
9
9
  children,
10
- isFederatedComponent,
11
10
  ...props
12
11
  }: Props): JSX.Element => {
13
12
  return (
14
13
  <Module {...props}>
15
- <LicenseCheck
16
- isFederatedComponent={isFederatedComponent}
17
- moduleName={moduleName}
18
- >
19
- {children}
20
- </LicenseCheck>
14
+ <LicenseCheck moduleName={moduleName}>{children}</LicenseCheck>
21
15
  </Module>
22
16
  );
23
17
  };
@@ -1,3 +1,5 @@
1
+ import * as React from 'react';
2
+
1
3
  import { Provider as JotaiProvider, createStore } from 'jotai';
2
4
 
3
5
  import { createGenerateClassName, StylesProvider } from '@mui/styles';
@@ -7,7 +9,7 @@ import SnackbarProvider from '../Snackbar/SnackbarProvider';
7
9
 
8
10
  export interface ModuleProps {
9
11
  children: React.ReactElement;
10
- maxSnackbars?: number;
12
+ maxSnackbars: number;
11
13
  seedName: string;
12
14
  store: ReturnType<typeof createStore>;
13
15
  }
@@ -15,7 +17,7 @@ export interface ModuleProps {
15
17
  const Module = ({
16
18
  children,
17
19
  seedName,
18
- maxSnackbars = 3,
20
+ maxSnackbars,
19
21
  store
20
22
  }: ModuleProps): JSX.Element => {
21
23
  const generateClassName = createGenerateClassName({
@@ -0,0 +1,346 @@
1
+ import { Method, SnackbarProvider, TestQueryProvider } from '../../..';
2
+
3
+ import AccessRights from './AccessRights';
4
+ import {
5
+ buildResult,
6
+ emptyAccessRights,
7
+ labels,
8
+ removedAccessRights,
9
+ roles,
10
+ simpleAccessRights,
11
+ updatedAccessRights
12
+ } from './storiesData';
13
+
14
+ const initialize = ({
15
+ initialValues = simpleAccessRights,
16
+ loading = false,
17
+ link = 'link'
18
+ }): unknown => {
19
+ const cancel = cy.stub();
20
+ const save = cy.stub();
21
+
22
+ cy.interceptAPIRequest({
23
+ alias: 'getContacts',
24
+ method: Method.GET,
25
+ path: '**/contacts?**',
26
+ response: buildResult(false)
27
+ });
28
+ cy.interceptAPIRequest({
29
+ alias: 'getContactGroups',
30
+ method: Method.GET,
31
+ path: '**/contact-groups?**',
32
+ response: buildResult(true)
33
+ });
34
+
35
+ cy.mount({
36
+ Component: (
37
+ <SnackbarProvider>
38
+ <TestQueryProvider>
39
+ <AccessRights
40
+ cancel={cancel}
41
+ endpoints={{
42
+ contact: '/contacts',
43
+ contactGroup: '/contact-groups'
44
+ }}
45
+ initialValues={initialValues}
46
+ labels={labels}
47
+ link={link}
48
+ loading={loading}
49
+ roles={roles}
50
+ submit={save}
51
+ />
52
+ </TestQueryProvider>
53
+ </SnackbarProvider>
54
+ )
55
+ });
56
+
57
+ return {
58
+ cancel,
59
+ save
60
+ };
61
+ };
62
+
63
+ describe('Access rights', () => {
64
+ it('displays the access rights in loading state', () => {
65
+ initialize({ loading: true });
66
+
67
+ cy.contains('Share dashboard with').should('be.visible');
68
+ cy.contains('Contact').should('be.visible');
69
+ cy.contains('Contact group').should('be.visible');
70
+ cy.findByLabelText('Add a contact').should('be.visible');
71
+ cy.findByTestId('add_role').should('be.disabled');
72
+ cy.findByTestId('add').should('be.disabled');
73
+ cy.findByLabelText('Copy link').should('be.visible');
74
+ cy.findByLabelText('Cancel').should('be.visible');
75
+ cy.findByLabelText('Save').should('be.visible');
76
+
77
+ cy.makeSnapshot();
78
+ });
79
+
80
+ it('displays the access rights without link', () => {
81
+ initialize({ link: null });
82
+
83
+ cy.findByLabelText('Copy link').should('not.exist');
84
+
85
+ cy.makeSnapshot();
86
+ });
87
+
88
+ it('displays the access rights with an empty list', () => {
89
+ initialize({ initialValues: emptyAccessRights });
90
+
91
+ cy.contains('The contact list is empty').should('be.visible');
92
+
93
+ cy.makeSnapshot();
94
+ });
95
+
96
+ it('displays the access rights with an empty list', () => {
97
+ initialize({});
98
+
99
+ simpleAccessRights.forEach(({ name, email, isContactGroup, role }) => {
100
+ cy.contains(name).should('be.visible');
101
+ cy.contains(email).should('be.visible');
102
+ cy.get(`[data-groupChip="${name}"]`).should(
103
+ isContactGroup ? 'be.visible' : 'not.exist'
104
+ );
105
+ cy.findByTestId(`role-${name}`).should('have.value', role);
106
+ cy.findByTestId(`remove-${name}`).should('be.visible');
107
+ });
108
+
109
+ cy.makeSnapshot();
110
+ });
111
+
112
+ it('displays a removed chip when the corresponding icon is clicked', () => {
113
+ initialize({});
114
+
115
+ cy.findByTestId(`remove-Leah McGlynn`).should(
116
+ 'have.attr',
117
+ 'data-removed',
118
+ 'false'
119
+ );
120
+
121
+ cy.findByTestId(`remove-Leah McGlynn`).click();
122
+
123
+ cy.findByTestId(`remove-Leah McGlynn`).should(
124
+ 'have.attr',
125
+ 'data-removed',
126
+ 'true'
127
+ );
128
+ cy.contains(labels.list.removed).should('be.visible');
129
+
130
+ cy.makeSnapshot();
131
+ });
132
+
133
+ it('restores the contact when the contact is removed and the corresponding icon is clicked', () => {
134
+ initialize({});
135
+
136
+ cy.findByTestId(`remove-Leah McGlynn`).click();
137
+
138
+ cy.findByTestId(`remove-Leah McGlynn`).should(
139
+ 'have.attr',
140
+ 'data-removed',
141
+ 'true'
142
+ );
143
+ cy.contains(labels.list.removed).should('be.visible');
144
+
145
+ cy.findByTestId(`remove-Leah McGlynn`).click();
146
+
147
+ cy.findByTestId(`remove-Leah McGlynn`).should(
148
+ 'have.attr',
149
+ 'data-removed',
150
+ 'false'
151
+ );
152
+ cy.contains(labels.list.removed).should('not.exist');
153
+
154
+ cy.makeSnapshot();
155
+ });
156
+
157
+ it('submits the new acces rights list without the removed contact', () => {
158
+ const { save } = initialize({});
159
+
160
+ cy.findByTestId(`remove-Leah McGlynn`).click();
161
+
162
+ cy.findByTestId(`remove-Leah McGlynn`).should(
163
+ 'have.attr',
164
+ 'data-removed',
165
+ 'true'
166
+ );
167
+ cy.contains(labels.list.removed).should('be.visible');
168
+ cy.contains('1 removed').should('be.visible');
169
+
170
+ cy.contains(labels.actions.save)
171
+ .click()
172
+ .then(() => {
173
+ expect(save).to.have.been.calledWith(removedAccessRights);
174
+ });
175
+
176
+ cy.makeSnapshot();
177
+ });
178
+
179
+ it('submits the new acces rights list with the updated contact', () => {
180
+ const { save } = initialize({});
181
+
182
+ cy.findByTestId(`role-Leah McGlynn`).parent().click();
183
+
184
+ cy.get('li[data-value="editor"]').click();
185
+ cy.contains(labels.list.updated).should('be.visible');
186
+ cy.contains('1 updated').should('be.visible');
187
+
188
+ cy.contains(labels.actions.save)
189
+ .click()
190
+ .then(() => {
191
+ expect(save).to.have.been.calledWith(updatedAccessRights);
192
+ });
193
+
194
+ cy.makeSnapshot();
195
+ });
196
+
197
+ it('removes the updated chip when the contact role is updated and its initial role is assigned back', () => {
198
+ initialize({});
199
+
200
+ cy.findByTestId(`role-Leah McGlynn`).parent().click();
201
+
202
+ cy.get('li[data-value="editor"]').click();
203
+ cy.contains(labels.list.updated).should('be.visible');
204
+
205
+ cy.get('li[data-value="viewer"]').click();
206
+ cy.contains(labels.list.updated).should('not.exist');
207
+
208
+ cy.makeSnapshot();
209
+ });
210
+
211
+ it('retrieves contacts when the contact radio is selected', () => {
212
+ initialize({});
213
+
214
+ cy.contains(labels.add.contact).click();
215
+ cy.findByLabelText(labels.add.autocompleteContact).click();
216
+
217
+ cy.waitForRequest('@getContacts');
218
+
219
+ cy.makeSnapshot();
220
+ });
221
+
222
+ it('retrieves contact groups when the contact group radio is selected', () => {
223
+ initialize({});
224
+
225
+ cy.contains(labels.add.contactGroup).click();
226
+ cy.findByLabelText(labels.add.autocompleteContactGroup).click();
227
+
228
+ cy.waitForRequest('@getContactGroups');
229
+
230
+ cy.makeSnapshot();
231
+ });
232
+
233
+ it('adds the contact as viewer when a contact is selected and the corresponding button is clicked', () => {
234
+ initialize({});
235
+
236
+ cy.contains(labels.add.contact).click();
237
+ cy.findByLabelText(labels.add.autocompleteContact).click();
238
+
239
+ cy.waitForRequest('@getContacts');
240
+
241
+ cy.contains('Entity 10').click();
242
+
243
+ cy.findByTestId('add').click();
244
+
245
+ cy.contains('Entity 10').should('be.visible');
246
+
247
+ cy.findByTestId('role-Entity 10').should('have.value', 'viewer');
248
+ cy.contains(labels.list.added).should('be.visible');
249
+
250
+ cy.makeSnapshot();
251
+ });
252
+
253
+ it('adds the contact group as editor when a contact group is selected and the corresponding button is clicked', () => {
254
+ initialize({});
255
+
256
+ cy.contains(labels.add.contactGroup).click();
257
+ cy.findByLabelText(labels.add.autocompleteContactGroup).click();
258
+
259
+ cy.waitForRequest('@getContactGroups');
260
+
261
+ cy.contains('Entity Group 10').click();
262
+ cy.findByTestId('add_role').parent().click();
263
+ cy.get('li[data-value="editor"]').click();
264
+ cy.findByTestId('add').click();
265
+
266
+ cy.contains('Entity Group 10').should('be.visible');
267
+
268
+ cy.findByTestId('role-Entity Group 10').should('have.value', 'editor');
269
+ cy.contains(labels.list.added).should('be.visible');
270
+ cy.contains('1 added').should('be.visible');
271
+
272
+ cy.makeSnapshot();
273
+ });
274
+
275
+ it('displays statistics when a contact is added, a contact is updated and a contact is removed', () => {
276
+ initialize({});
277
+
278
+ cy.contains(labels.add.contactGroup).click();
279
+ cy.findByLabelText(labels.add.autocompleteContactGroup).click();
280
+
281
+ cy.waitForRequest('@getContactGroups');
282
+
283
+ cy.contains('Entity Group 10').click();
284
+ cy.findByTestId('add_role').parent().click();
285
+ cy.get('li[data-value="editor"]').click();
286
+ cy.findByTestId('add').click();
287
+
288
+ cy.contains('Entity Group 10').should('be.visible');
289
+
290
+ cy.findByTestId(`role-Leah McGlynn`).parent().click();
291
+ cy.get('li[data-value="editor"]').click();
292
+
293
+ cy.findByTestId('remove-Jody Blanda').click();
294
+
295
+ cy.contains('1 added | 1 updated | 1 removed').should('be.visible');
296
+
297
+ cy.makeSnapshot();
298
+ });
299
+
300
+ it('clears the autocomplete when a contact is selected and the contact type is changed', () => {
301
+ initialize({});
302
+
303
+ cy.contains(labels.add.contact).click();
304
+ cy.findByLabelText(labels.add.autocompleteContact).click();
305
+
306
+ cy.waitForRequest('@getContacts');
307
+
308
+ cy.contains('Entity 10').click();
309
+ cy.findByLabelText(labels.add.autocompleteContact).should(
310
+ 'have.value',
311
+ 'Entity 10'
312
+ );
313
+
314
+ cy.contains(labels.add.contactGroup).click();
315
+ cy.findByLabelText(labels.add.autocompleteContactGroup).should(
316
+ 'have.value',
317
+ ''
318
+ );
319
+
320
+ cy.makeSnapshot();
321
+ });
322
+
323
+ it('removes the contact from the list when the contact has been to the list and the corresponding button is clicked', () => {
324
+ initialize({});
325
+
326
+ cy.contains(labels.add.contact).click();
327
+ cy.findByLabelText(labels.add.autocompleteContact).click();
328
+
329
+ cy.waitForRequest('@getContacts');
330
+
331
+ cy.contains('Entity 10').click();
332
+
333
+ cy.findByTestId('add').click();
334
+
335
+ cy.contains('Entity 10').should('be.visible');
336
+
337
+ cy.contains(labels.list.added).should('be.visible');
338
+
339
+ cy.findByTestId('remove-Entity 10').click();
340
+
341
+ cy.contains('Entity 10').should('not.exist');
342
+ cy.contains(labels.list.added).should('not.exist');
343
+
344
+ cy.makeSnapshot();
345
+ });
346
+ });