@centreon/ui 26.11.5 → 26.11.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/Form/Form.stories.tsx +55 -0
- package/src/Form/Inputs/CheckboxGroup.tsx +1 -5
- package/src/Form/Inputs/ExclusiveCheckboxGroup.cypress.spec.tsx +174 -0
- package/src/Form/Inputs/ExclusiveCheckboxGroup.tsx +96 -0
- package/src/Form/Inputs/index.tsx +5 -0
- package/src/Form/Inputs/models.ts +9 -1
- package/src/components/SortableEntriesList/Row.tsx +117 -0
- package/src/components/SortableEntriesList/SortableEntriesList.cypress.spec.tsx +506 -0
- package/src/components/SortableEntriesList/SortableEntriesList.stories.tsx +252 -0
- package/src/components/SortableEntriesList/SortableEntriesList.tsx +164 -0
- package/src/components/SortableEntriesList/index.ts +3 -0
- package/src/components/SortableEntriesList/models.ts +20 -0
- package/src/components/SortableEntriesList/translatedLabels.ts +12 -0
- package/src/components/SortableEntriesList/useSortableEntries.ts +140 -0
- package/src/components/index.ts +1 -0
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import EditIcon from '@mui/icons-material/Edit';
|
|
2
|
+
import VisibilityIcon from '@mui/icons-material/Visibility';
|
|
3
|
+
|
|
4
|
+
import { useState } from 'react';
|
|
5
|
+
|
|
6
|
+
import { TestQueryProvider } from '../..';
|
|
7
|
+
import buildListingEndpoint from '../../api/buildListingEndpoint';
|
|
8
|
+
import type { SelectEntry } from '../../InputField/Select';
|
|
9
|
+
import SingleConnectedAutocompleteField from '../../InputField/Select/Autocomplete/Connected/Single';
|
|
10
|
+
import TextField from '../../InputField/Text';
|
|
11
|
+
import type { RenderRowParams, RowParams, SortableRowAction } from './models';
|
|
12
|
+
import { type Props, SortableEntriesList } from './SortableEntriesList';
|
|
13
|
+
|
|
14
|
+
type Stub = ReturnType<typeof cy.stub>;
|
|
15
|
+
|
|
16
|
+
const ControlledList = <T,>({
|
|
17
|
+
initialValues,
|
|
18
|
+
onChangeSpy,
|
|
19
|
+
...props
|
|
20
|
+
}: Omit<Props<T>, 'values' | 'onChange'> & {
|
|
21
|
+
initialValues: Array<T>;
|
|
22
|
+
onChangeSpy: Stub;
|
|
23
|
+
}): JSX.Element => {
|
|
24
|
+
const [values, setValues] = useState(initialValues);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<SortableEntriesList<T>
|
|
28
|
+
{...props}
|
|
29
|
+
onChange={(nextValues): void => {
|
|
30
|
+
onChangeSpy(nextValues);
|
|
31
|
+
setValues(nextValues);
|
|
32
|
+
}}
|
|
33
|
+
values={values}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const getLastValues = <T,>(onChange: Stub): Array<T> =>
|
|
39
|
+
onChange.lastCall.args[0];
|
|
40
|
+
|
|
41
|
+
const getRows = (): Cypress.Chainable => cy.findAllByRole('listitem');
|
|
42
|
+
|
|
43
|
+
const endpoint = '/configuration/hosts/templates';
|
|
44
|
+
|
|
45
|
+
const options: Array<SelectEntry> = [
|
|
46
|
+
{ id: 1, name: 'generic-active-host' },
|
|
47
|
+
{ id: 2, name: 'generic-passive-host' },
|
|
48
|
+
{ id: 3, name: 'linux-server-standard' }
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
type TemplateRow = SelectEntry | null;
|
|
52
|
+
|
|
53
|
+
const renderTemplateRow = ({
|
|
54
|
+
setValue,
|
|
55
|
+
value,
|
|
56
|
+
values
|
|
57
|
+
}: RenderRowParams<TemplateRow>): JSX.Element => {
|
|
58
|
+
const selectedElsewhere = values
|
|
59
|
+
.map((template) => template?.id)
|
|
60
|
+
.filter((id) => id !== value?.id);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<SingleConnectedAutocompleteField
|
|
64
|
+
baseEndpoint=""
|
|
65
|
+
field="name"
|
|
66
|
+
fullWidth
|
|
67
|
+
getEndpoint={(parameters): string =>
|
|
68
|
+
buildListingEndpoint({ baseEndpoint: endpoint, parameters })
|
|
69
|
+
}
|
|
70
|
+
getOptionDisabled={(option: SelectEntry): boolean =>
|
|
71
|
+
selectedElsewhere.includes(option.id)
|
|
72
|
+
}
|
|
73
|
+
label="Template"
|
|
74
|
+
onChange={(_event, template): void => setValue(template as TemplateRow)}
|
|
75
|
+
value={value}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const initializeTemplates = ({
|
|
81
|
+
draggable = true,
|
|
82
|
+
templates = [options[0], options[1]]
|
|
83
|
+
}: {
|
|
84
|
+
draggable?: boolean;
|
|
85
|
+
templates?: Array<TemplateRow>;
|
|
86
|
+
} = {}): { onChange: Stub; onEdit: Stub } => {
|
|
87
|
+
const onChange = cy.stub();
|
|
88
|
+
const onEdit = cy.stub();
|
|
89
|
+
|
|
90
|
+
cy.intercept('GET', `**${endpoint}**`, {
|
|
91
|
+
body: {
|
|
92
|
+
meta: { limit: 10, page: 1, total: options.length },
|
|
93
|
+
result: options
|
|
94
|
+
},
|
|
95
|
+
statusCode: 200
|
|
96
|
+
}).as('getTemplates');
|
|
97
|
+
|
|
98
|
+
cy.mount({
|
|
99
|
+
Component: (
|
|
100
|
+
<TestQueryProvider>
|
|
101
|
+
<div className="p-4">
|
|
102
|
+
<ControlledList<TemplateRow>
|
|
103
|
+
actions={({
|
|
104
|
+
index,
|
|
105
|
+
value
|
|
106
|
+
}: RowParams<TemplateRow>): Array<SortableRowAction> => [
|
|
107
|
+
{
|
|
108
|
+
icon: <EditIcon />,
|
|
109
|
+
id: 'edit-template',
|
|
110
|
+
label: 'Edit template',
|
|
111
|
+
onClick: () => onEdit(value, index)
|
|
112
|
+
}
|
|
113
|
+
]}
|
|
114
|
+
createValue={(): TemplateRow => null}
|
|
115
|
+
draggable={draggable}
|
|
116
|
+
initialValues={templates}
|
|
117
|
+
label="Templates"
|
|
118
|
+
onChangeSpy={onChange}
|
|
119
|
+
renderRow={renderTemplateRow}
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
</TestQueryProvider>
|
|
123
|
+
)
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return { onChange, onEdit };
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
describe('SortableEntriesList - single input rows', () => {
|
|
130
|
+
it('displays one row per value with its value, its actions, a delete button and a trailing drag handle', () => {
|
|
131
|
+
initializeTemplates();
|
|
132
|
+
|
|
133
|
+
cy.findByRole('list', { name: 'Templates' }).should('exist');
|
|
134
|
+
getRows().should('have.length', 2);
|
|
135
|
+
cy.get('[data-testid="Template"]')
|
|
136
|
+
.eq(0)
|
|
137
|
+
.should('have.value', options[0].name);
|
|
138
|
+
cy.get('[data-testid="Template"]')
|
|
139
|
+
.eq(1)
|
|
140
|
+
.should('have.value', options[1].name);
|
|
141
|
+
cy.findAllByTestId('edit-template').should('have.length', 2);
|
|
142
|
+
cy.findAllByTestId('delete-row').should('have.length', 2);
|
|
143
|
+
|
|
144
|
+
getRows()
|
|
145
|
+
.eq(0)
|
|
146
|
+
.find('button')
|
|
147
|
+
.last()
|
|
148
|
+
.should('have.attr', 'data-testid', 'drag-handle');
|
|
149
|
+
|
|
150
|
+
cy.makeSnapshot();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('reports the plain values, without any internal id, when an option is selected', () => {
|
|
154
|
+
const { onChange } = initializeTemplates();
|
|
155
|
+
|
|
156
|
+
cy.get('[data-testid="Template"]').eq(1).click();
|
|
157
|
+
cy.wait('@getTemplates');
|
|
158
|
+
cy.findByRole('option', { name: options[2].name }).click();
|
|
159
|
+
|
|
160
|
+
cy.get('[data-testid="Template"]')
|
|
161
|
+
.eq(1)
|
|
162
|
+
.should('have.value', options[2].name);
|
|
163
|
+
cy.wrap(onChange).should(() => {
|
|
164
|
+
expect(getLastValues(onChange)).to.deep.equal([options[0], options[2]]);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('lets a row disable the options already selected in sibling rows', () => {
|
|
169
|
+
initializeTemplates();
|
|
170
|
+
|
|
171
|
+
cy.get('[data-testid="Template"]').eq(1).click();
|
|
172
|
+
cy.wait('@getTemplates');
|
|
173
|
+
|
|
174
|
+
cy.findByRole('option', { name: options[0].name }).should(
|
|
175
|
+
'have.attr',
|
|
176
|
+
'aria-disabled',
|
|
177
|
+
'true'
|
|
178
|
+
);
|
|
179
|
+
cy.findByRole('option', { name: options[2].name }).should(
|
|
180
|
+
'not.have.attr',
|
|
181
|
+
'aria-disabled',
|
|
182
|
+
'true'
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('appends a row built with createValue and focuses its first input', () => {
|
|
187
|
+
const { onChange } = initializeTemplates();
|
|
188
|
+
|
|
189
|
+
cy.findByTestId('Add').click();
|
|
190
|
+
|
|
191
|
+
getRows().should('have.length', 3);
|
|
192
|
+
cy.get('[data-testid="Template"]').eq(2).should('have.value', '');
|
|
193
|
+
cy.focused().should('have.attr', 'data-testid', 'Template');
|
|
194
|
+
cy.focused()
|
|
195
|
+
.closest('li')
|
|
196
|
+
.should(($row) => {
|
|
197
|
+
expect($row.index()).to.equal(2);
|
|
198
|
+
});
|
|
199
|
+
cy.wrap(onChange).should(() => {
|
|
200
|
+
expect(getLastValues(onChange)).to.deep.equal([
|
|
201
|
+
options[0],
|
|
202
|
+
options[1],
|
|
203
|
+
null
|
|
204
|
+
]);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('removes a row and moves the focus to the delete button of the row taking its place', () => {
|
|
209
|
+
const { onChange } = initializeTemplates({
|
|
210
|
+
templates: [options[0], options[1], options[2]]
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
cy.findAllByTestId('delete-row').eq(0).click();
|
|
214
|
+
|
|
215
|
+
getRows().should('have.length', 2);
|
|
216
|
+
cy.wrap(onChange).should(() => {
|
|
217
|
+
expect(getLastValues(onChange)).to.deep.equal([options[1], options[2]]);
|
|
218
|
+
});
|
|
219
|
+
cy.focused()
|
|
220
|
+
.should('have.attr', 'data-testid', 'delete-row')
|
|
221
|
+
.closest('li')
|
|
222
|
+
.find('[data-testid="Template"]')
|
|
223
|
+
.should('have.value', options[1].name);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('moves the focus to the previous row when the last row is removed, then to the add button when the list becomes empty', () => {
|
|
227
|
+
initializeTemplates();
|
|
228
|
+
|
|
229
|
+
cy.findAllByTestId('delete-row').eq(1).click();
|
|
230
|
+
cy.focused()
|
|
231
|
+
.closest('li')
|
|
232
|
+
.find('[data-testid="Template"]')
|
|
233
|
+
.should('have.value', options[0].name);
|
|
234
|
+
|
|
235
|
+
cy.findAllByTestId('delete-row').eq(0).click();
|
|
236
|
+
getRows().should('not.exist');
|
|
237
|
+
cy.focused().should('have.attr', 'data-testid', 'Add');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('reorders the rows with the keyboard through the drag handle', () => {
|
|
241
|
+
const { onChange } = initializeTemplates();
|
|
242
|
+
|
|
243
|
+
cy.moveSortableElement({
|
|
244
|
+
direction: 'down',
|
|
245
|
+
element: cy.findAllByTestId('drag-handle').eq(0)
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
cy.get('[data-testid="Template"]')
|
|
249
|
+
.eq(0)
|
|
250
|
+
.should('have.value', options[1].name);
|
|
251
|
+
cy.get('[data-testid="Template"]')
|
|
252
|
+
.eq(1)
|
|
253
|
+
.should('have.value', options[0].name);
|
|
254
|
+
cy.wrap(onChange).should(() => {
|
|
255
|
+
expect(getLastValues(onChange)).to.deep.equal([options[1], options[0]]);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('passes the up-to-date row value and index to the actions after a reorder', () => {
|
|
260
|
+
const { onEdit } = initializeTemplates();
|
|
261
|
+
|
|
262
|
+
cy.moveSortableElement({
|
|
263
|
+
direction: 'down',
|
|
264
|
+
element: cy.findAllByTestId('drag-handle').eq(0)
|
|
265
|
+
});
|
|
266
|
+
cy.get('[data-testid="Template"]')
|
|
267
|
+
.eq(0)
|
|
268
|
+
.should('have.value', options[1].name);
|
|
269
|
+
|
|
270
|
+
cy.findAllByTestId('edit-template').eq(1).click();
|
|
271
|
+
cy.wrap(onEdit).should('have.been.calledWith', options[0], 1);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('hides the drag handle when the list is not draggable', () => {
|
|
275
|
+
initializeTemplates({ draggable: false });
|
|
276
|
+
|
|
277
|
+
cy.findAllByTestId('delete-row').should('have.length', 2);
|
|
278
|
+
cy.findByTestId('drag-handle').should('not.exist');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('lets the inputs take the remaining width and keeps the row buttons at a 32px pitch', () => {
|
|
282
|
+
initializeTemplates();
|
|
283
|
+
|
|
284
|
+
cy.get('[data-row-fields]')
|
|
285
|
+
.eq(0)
|
|
286
|
+
.next()
|
|
287
|
+
.find('button')
|
|
288
|
+
.should('have.length', 3)
|
|
289
|
+
.then(($buttons) => {
|
|
290
|
+
const lefts = $buttons
|
|
291
|
+
.toArray()
|
|
292
|
+
.map((button) => button.getBoundingClientRect().left);
|
|
293
|
+
|
|
294
|
+
lefts.slice(1).forEach((left, index) => {
|
|
295
|
+
expect(left - lefts[index]).to.equal(32);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
cy.get('[data-row-fields]')
|
|
300
|
+
.eq(0)
|
|
301
|
+
.invoke('outerWidth')
|
|
302
|
+
.should('be.greaterThan', 500);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
interface MacroRow {
|
|
307
|
+
isPassword: boolean;
|
|
308
|
+
name: string;
|
|
309
|
+
origin: 'direct' | 'fromTpl';
|
|
310
|
+
value: string;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const macros: Array<MacroRow> = [
|
|
314
|
+
{ isPassword: false, name: 'MACRO_A', origin: 'direct', value: 'a' },
|
|
315
|
+
{ isPassword: true, name: 'MACRO_B', origin: 'fromTpl', value: 'b' }
|
|
316
|
+
];
|
|
317
|
+
|
|
318
|
+
const emptyMacro: MacroRow = {
|
|
319
|
+
isPassword: false,
|
|
320
|
+
name: '',
|
|
321
|
+
origin: 'direct',
|
|
322
|
+
value: ''
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const renderMacroRow = ({
|
|
326
|
+
setField,
|
|
327
|
+
value: macro
|
|
328
|
+
}: RenderRowParams<MacroRow>): JSX.Element => (
|
|
329
|
+
<div className="flex gap-2">
|
|
330
|
+
<TextField
|
|
331
|
+
dataTestId="macro-name"
|
|
332
|
+
label="Name"
|
|
333
|
+
onChange={(event): void => setField('name', event.target.value)}
|
|
334
|
+
value={macro.name}
|
|
335
|
+
/>
|
|
336
|
+
<TextField
|
|
337
|
+
dataTestId="macro-value"
|
|
338
|
+
label="Value"
|
|
339
|
+
onChange={(event): void => setField('value', event.target.value)}
|
|
340
|
+
type={macro.isPassword ? 'password' : 'text'}
|
|
341
|
+
value={macro.value}
|
|
342
|
+
/>
|
|
343
|
+
</div>
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
const MacrosList = ({
|
|
347
|
+
copyOnChange,
|
|
348
|
+
onChangeSpy
|
|
349
|
+
}: {
|
|
350
|
+
copyOnChange: boolean;
|
|
351
|
+
onChangeSpy: Stub;
|
|
352
|
+
}): JSX.Element => {
|
|
353
|
+
const [values, setValues] = useState(macros);
|
|
354
|
+
|
|
355
|
+
const change = (nextValues: Array<MacroRow>): void => {
|
|
356
|
+
onChangeSpy(nextValues);
|
|
357
|
+
// Simulates a parent that stores a copy (new array and objects), like a
|
|
358
|
+
// form library that clones its state.
|
|
359
|
+
setValues(
|
|
360
|
+
copyOnChange ? nextValues.map((macro) => ({ ...macro })) : nextValues
|
|
361
|
+
);
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
return (
|
|
365
|
+
<>
|
|
366
|
+
<button
|
|
367
|
+
data-testid="reset"
|
|
368
|
+
onClick={(): void => setValues([emptyMacro])}
|
|
369
|
+
type="button"
|
|
370
|
+
>
|
|
371
|
+
Reset
|
|
372
|
+
</button>
|
|
373
|
+
<SortableEntriesList<MacroRow>
|
|
374
|
+
actions={({ index, value }) => [
|
|
375
|
+
{
|
|
376
|
+
icon: <VisibilityIcon />,
|
|
377
|
+
id: 'toggle-password',
|
|
378
|
+
label: 'Toggle password',
|
|
379
|
+
onClick: () =>
|
|
380
|
+
change(
|
|
381
|
+
values.map((macro, macroIndex) =>
|
|
382
|
+
macroIndex === index
|
|
383
|
+
? { ...macro, isPassword: !value.isPassword }
|
|
384
|
+
: macro
|
|
385
|
+
)
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
]}
|
|
389
|
+
createValue={(): MacroRow => emptyMacro}
|
|
390
|
+
getRowClassName={({ value }) => `origin-${value.origin}`}
|
|
391
|
+
onChange={change}
|
|
392
|
+
renderRow={renderMacroRow}
|
|
393
|
+
values={values}
|
|
394
|
+
/>
|
|
395
|
+
</>
|
|
396
|
+
);
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
const initializeMacros = ({
|
|
400
|
+
copyOnChange = false
|
|
401
|
+
}: {
|
|
402
|
+
copyOnChange?: boolean;
|
|
403
|
+
} = {}): { onChange: Stub } => {
|
|
404
|
+
const onChange = cy.stub();
|
|
405
|
+
|
|
406
|
+
cy.mount({
|
|
407
|
+
Component: <MacrosList copyOnChange={copyOnChange} onChangeSpy={onChange} />
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
return { onChange };
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
describe('SortableEntriesList - multiple input rows', () => {
|
|
414
|
+
it('renders every input of every row', () => {
|
|
415
|
+
initializeMacros();
|
|
416
|
+
|
|
417
|
+
cy.get('input[data-testid="macro-name"]').should('have.length', 2);
|
|
418
|
+
cy.get('input[data-testid="macro-value"]').should('have.length', 2);
|
|
419
|
+
cy.get('input[data-testid="macro-value"]')
|
|
420
|
+
.eq(1)
|
|
421
|
+
.should('have.attr', 'type', 'password');
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('keeps the focus in the edited input while typing and reports a structured value', () => {
|
|
425
|
+
const { onChange } = initializeMacros();
|
|
426
|
+
|
|
427
|
+
cy.get('input[data-testid="macro-name"]').eq(0).clear();
|
|
428
|
+
cy.get('input[data-testid="macro-name"]').eq(0).type('HOST_IP');
|
|
429
|
+
cy.focused().should('have.value', 'HOST_IP');
|
|
430
|
+
|
|
431
|
+
cy.get('input[data-testid="macro-value"]').eq(0).clear();
|
|
432
|
+
cy.get('input[data-testid="macro-value"]').eq(0).type('10.0.0.1');
|
|
433
|
+
cy.focused().should('have.value', '10.0.0.1');
|
|
434
|
+
|
|
435
|
+
cy.wrap(onChange).should(() => {
|
|
436
|
+
expect(getLastValues(onChange)).to.deep.equal([
|
|
437
|
+
{
|
|
438
|
+
isPassword: false,
|
|
439
|
+
name: 'HOST_IP',
|
|
440
|
+
origin: 'direct',
|
|
441
|
+
value: '10.0.0.1'
|
|
442
|
+
},
|
|
443
|
+
macros[1]
|
|
444
|
+
]);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('keeps the focus when a new row is typed into right after being added', () => {
|
|
449
|
+
const { onChange } = initializeMacros();
|
|
450
|
+
|
|
451
|
+
cy.findByTestId('Add').click();
|
|
452
|
+
cy.focused().should('have.attr', 'data-testid', 'macro-name').type('NEW');
|
|
453
|
+
cy.get('input[data-testid="macro-value"]').eq(2).type('value');
|
|
454
|
+
|
|
455
|
+
cy.wrap(onChange).should(() => {
|
|
456
|
+
expect(getLastValues<MacroRow>(onChange)[2]).to.deep.equal({
|
|
457
|
+
...emptyMacro,
|
|
458
|
+
name: 'NEW',
|
|
459
|
+
value: 'value'
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('keeps the rows and the focus stable when the parent stores a copy of the values', () => {
|
|
465
|
+
const { onChange } = initializeMacros({ copyOnChange: true });
|
|
466
|
+
|
|
467
|
+
cy.get('input[data-testid="macro-name"]').eq(1).clear();
|
|
468
|
+
cy.get('input[data-testid="macro-name"]').eq(1).type('RENAMED');
|
|
469
|
+
cy.focused().should('have.value', 'RENAMED');
|
|
470
|
+
|
|
471
|
+
cy.findByTestId('Add').click();
|
|
472
|
+
cy.focused().should('have.attr', 'data-testid', 'macro-name').type('NEW');
|
|
473
|
+
|
|
474
|
+
cy.wrap(onChange).should(() => {
|
|
475
|
+
expect(
|
|
476
|
+
getLastValues<MacroRow>(onChange).map(({ name }) => name)
|
|
477
|
+
).to.deep.equal(['MACRO_A', 'RENAMED', 'NEW']);
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it('renders the new values when the parent replaces them', () => {
|
|
482
|
+
initializeMacros();
|
|
483
|
+
|
|
484
|
+
cy.findByTestId('reset').click();
|
|
485
|
+
|
|
486
|
+
getRows().should('have.length', 1);
|
|
487
|
+
cy.get('input[data-testid="macro-name"]').should('have.value', '');
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('lets a row action change its own row', () => {
|
|
491
|
+
initializeMacros();
|
|
492
|
+
|
|
493
|
+
cy.findAllByTestId('toggle-password').eq(0).click();
|
|
494
|
+
|
|
495
|
+
cy.get('input[data-testid="macro-value"]')
|
|
496
|
+
.eq(0)
|
|
497
|
+
.should('have.attr', 'type', 'password');
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('applies the class returned by getRowClassName to each row', () => {
|
|
501
|
+
initializeMacros();
|
|
502
|
+
|
|
503
|
+
getRows().eq(0).should('have.class', 'origin-direct');
|
|
504
|
+
getRows().eq(1).should('have.class', 'origin-fromTpl');
|
|
505
|
+
});
|
|
506
|
+
});
|