decap-cms-widget-list 2.11.0-beta.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.
@@ -0,0 +1,792 @@
1
+ import React from 'react';
2
+ import { fireEvent, render } from '@testing-library/react';
3
+ import { fromJS } from 'immutable';
4
+
5
+ import ListControl from '../ListControl';
6
+
7
+ jest.mock('decap-cms-widget-object', () => {
8
+ const React = require('react');
9
+
10
+ class MockObjectControl extends React.Component {
11
+ render() {
12
+ return <mock-object-control {...this.props}>{this.props.children}</mock-object-control>;
13
+ }
14
+ }
15
+
16
+ return {
17
+ controlComponent: MockObjectControl,
18
+ };
19
+ });
20
+ jest.mock('decap-cms-ui-default', () => {
21
+ const actual = jest.requireActual('decap-cms-ui-default');
22
+
23
+ function ListItemTopBar(props) {
24
+ return (
25
+ <mock-list-item-top-bar {...props} onClick={props.onCollapseToggle}>
26
+ <button onClick={props.onRemove}>Remove</button>
27
+ {props.children}
28
+ </mock-list-item-top-bar>
29
+ );
30
+ }
31
+
32
+ return {
33
+ ...actual,
34
+ ListItemTopBar,
35
+ };
36
+ });
37
+ jest.mock('uuid/v4');
38
+
39
+ describe('ListControl', () => {
40
+ const props = {
41
+ onChange: jest.fn(),
42
+ onChangeObject: jest.fn(),
43
+ onValidateObject: jest.fn(),
44
+ validate: jest.fn(),
45
+ mediaPaths: fromJS({}),
46
+ getAsset: jest.fn(),
47
+ onOpenMediaLibrary: jest.fn(),
48
+ onAddAsset: jest.fn(),
49
+ onRemoveInsertedMedia: jest.fn(),
50
+ classNameWrapper: 'classNameWrapper',
51
+ setActiveStyle: jest.fn(),
52
+ setInactiveStyle: jest.fn(),
53
+ editorControl: jest.fn(),
54
+ resolveWidget: jest.fn(),
55
+ clearFieldErrors: jest.fn(),
56
+ fieldsErrors: fromJS({}),
57
+ entry: fromJS({
58
+ path: 'posts/index.md',
59
+ }),
60
+ forID: 'forID',
61
+ t: key => key,
62
+ };
63
+
64
+ beforeEach(() => {
65
+ jest.clearAllMocks();
66
+ const uuid = require('uuid/v4');
67
+ let id = 0;
68
+ uuid.mockImplementation(() => {
69
+ return id++;
70
+ });
71
+ });
72
+ it('should render empty list', () => {
73
+ const field = fromJS({ name: 'list', label: 'List' });
74
+ const { asFragment } = render(<ListControl {...props} field={field} />);
75
+
76
+ expect(asFragment()).toMatchSnapshot();
77
+ });
78
+
79
+ it('should render list with string array', () => {
80
+ const field = fromJS({ name: 'list', label: 'List' });
81
+ const { asFragment } = render(
82
+ <ListControl {...props} field={field} value={fromJS(['item 1', 'item 2'])} />,
83
+ );
84
+
85
+ expect(asFragment()).toMatchSnapshot();
86
+ });
87
+
88
+ it('should render list with nested object', () => {
89
+ const field = fromJS({
90
+ name: 'list',
91
+ label: 'List',
92
+ field: {
93
+ name: 'object',
94
+ widget: 'object',
95
+ label: 'Object',
96
+ fields: [{ name: 'title', widget: 'string', label: 'Title' }],
97
+ },
98
+ });
99
+ const { asFragment, getByTestId } = render(
100
+ <ListControl
101
+ {...props}
102
+ field={field}
103
+ value={fromJS([{ object: { title: 'item 1' } }, { object: { title: 'item 2' } }])}
104
+ />,
105
+ );
106
+
107
+ expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'true');
108
+ expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'true');
109
+
110
+ expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'true');
111
+ expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'true');
112
+
113
+ expect(asFragment()).toMatchSnapshot();
114
+ });
115
+
116
+ it('should render list with nested object with collapse = false', () => {
117
+ const field = fromJS({
118
+ name: 'list',
119
+ label: 'List',
120
+ collapsed: false,
121
+ field: {
122
+ name: 'object',
123
+ widget: 'object',
124
+ label: 'Object',
125
+ fields: [{ name: 'title', widget: 'string', label: 'Title' }],
126
+ },
127
+ });
128
+ const { asFragment, getByTestId } = render(
129
+ <ListControl
130
+ {...props}
131
+ field={field}
132
+ value={fromJS([{ object: { title: 'item 1' } }, { object: { title: 'item 2' } }])}
133
+ />,
134
+ );
135
+
136
+ expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'false');
137
+ expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'false');
138
+
139
+ expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'false');
140
+ expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'false');
141
+
142
+ expect(asFragment()).toMatchSnapshot();
143
+ });
144
+
145
+ it('should collapse all items on top bar collapse click', () => {
146
+ const field = fromJS({
147
+ name: 'list',
148
+ label: 'List',
149
+ collapsed: false,
150
+ field: {
151
+ name: 'object',
152
+ widget: 'object',
153
+ label: 'Object',
154
+ fields: [{ name: 'title', widget: 'string', label: 'Title' }],
155
+ },
156
+ });
157
+ const { getByTestId } = render(
158
+ <ListControl
159
+ {...props}
160
+ field={field}
161
+ value={fromJS([{ object: { title: 'item 1' } }, { object: { title: 'item 2' } }])}
162
+ />,
163
+ );
164
+
165
+ expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'false');
166
+ expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'false');
167
+
168
+ expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'false');
169
+ expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'false');
170
+
171
+ fireEvent.click(getByTestId('expand-button'));
172
+
173
+ expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'true');
174
+ expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'true');
175
+
176
+ expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'true');
177
+ expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'true');
178
+ });
179
+
180
+ it('should collapse a single item on collapse item click', () => {
181
+ const field = fromJS({
182
+ name: 'list',
183
+ label: 'List',
184
+ collapsed: false,
185
+ field: {
186
+ name: 'object',
187
+ widget: 'object',
188
+ label: 'Object',
189
+ fields: [{ name: 'title', widget: 'string', label: 'Title' }],
190
+ },
191
+ });
192
+ const { getByTestId } = render(
193
+ <ListControl
194
+ {...props}
195
+ field={field}
196
+ value={fromJS([{ object: { title: 'item 1' } }, { object: { title: 'item 2' } }])}
197
+ />,
198
+ );
199
+
200
+ expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'false');
201
+ expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'false');
202
+
203
+ expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'false');
204
+ expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'false');
205
+
206
+ fireEvent.click(getByTestId('styled-list-item-top-bar-0'));
207
+
208
+ expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'true');
209
+ expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'false');
210
+
211
+ expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'true');
212
+ expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'false');
213
+ });
214
+
215
+ it('should expand all items on top bar expand click', () => {
216
+ const field = fromJS({
217
+ name: 'list',
218
+ label: 'List',
219
+ collapsed: true,
220
+ field: {
221
+ name: 'object',
222
+ widget: 'object',
223
+ label: 'Object',
224
+ fields: [{ name: 'title', widget: 'string', label: 'Title' }],
225
+ },
226
+ });
227
+ const { getByTestId } = render(
228
+ <ListControl
229
+ {...props}
230
+ field={field}
231
+ value={fromJS([{ object: { title: 'item 1' } }, { object: { title: 'item 2' } }])}
232
+ />,
233
+ );
234
+
235
+ expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'true');
236
+ expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'true');
237
+
238
+ expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'true');
239
+ expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'true');
240
+
241
+ fireEvent.click(getByTestId('expand-button'));
242
+
243
+ expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'false');
244
+ expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'false');
245
+
246
+ expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'false');
247
+ expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'false');
248
+ });
249
+
250
+ it('should expand a single item on expand item click', () => {
251
+ const field = fromJS({
252
+ name: 'list',
253
+ label: 'List',
254
+ collapsed: true,
255
+ field: {
256
+ name: 'object',
257
+ widget: 'object',
258
+ label: 'Object',
259
+ fields: [{ name: 'title', widget: 'string', label: 'Title' }],
260
+ },
261
+ });
262
+ const { getByTestId } = render(
263
+ <ListControl
264
+ {...props}
265
+ field={field}
266
+ value={fromJS([{ object: { title: 'item 1' } }, { object: { title: 'item 2' } }])}
267
+ />,
268
+ );
269
+
270
+ expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'true');
271
+ expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'true');
272
+
273
+ expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'true');
274
+ expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'true');
275
+
276
+ fireEvent.click(getByTestId('styled-list-item-top-bar-0'));
277
+
278
+ expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'false');
279
+ expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'true');
280
+
281
+ expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'false');
282
+ expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'true');
283
+ });
284
+
285
+ it('should use widget name when no summary or label are configured for mixed types', () => {
286
+ const field = fromJS({
287
+ name: 'list',
288
+ label: 'List',
289
+ collapsed: true,
290
+ types: [
291
+ {
292
+ name: 'type_1_object',
293
+ widget: 'object',
294
+ fields: [
295
+ { label: 'First Name', name: 'first_name', widget: 'string' },
296
+ { label: 'Last Name', name: 'last_name', widget: 'string' },
297
+ ],
298
+ },
299
+ ],
300
+ });
301
+
302
+ const { getAllByText } = render(
303
+ <ListControl
304
+ {...props}
305
+ field={field}
306
+ value={fromJS([{ first_name: 'hello', last_name: 'world', type: 'type_1_object' }])}
307
+ />,
308
+ );
309
+ expect(getAllByText('type_1_object')[1]).toBeInTheDocument();
310
+ });
311
+
312
+ it('should use label when no summary is configured for mixed types', () => {
313
+ const field = fromJS({
314
+ name: 'list',
315
+ label: 'List',
316
+ collapsed: true,
317
+ types: [
318
+ {
319
+ label: 'Type 1 Object',
320
+ name: 'type_1_object',
321
+ widget: 'object',
322
+ fields: [
323
+ { label: 'First Name', name: 'first_name', widget: 'string' },
324
+ { label: 'Last Name', name: 'last_name', widget: 'string' },
325
+ ],
326
+ },
327
+ ],
328
+ });
329
+
330
+ const { getAllByText } = render(
331
+ <ListControl
332
+ {...props}
333
+ field={field}
334
+ value={fromJS([{ first_name: 'hello', last_name: 'world', type: 'type_1_object' }])}
335
+ />,
336
+ );
337
+ expect(getAllByText('Type 1 Object')[1]).toBeInTheDocument();
338
+ });
339
+
340
+ it('should use summary when configured for mixed types', () => {
341
+ const field = fromJS({
342
+ name: 'list',
343
+ label: 'List',
344
+ collapsed: true,
345
+ types: [
346
+ {
347
+ label: 'Type 1 Object',
348
+ name: 'type_1_object',
349
+ summary: '{{first_name}} - {{last_name}} - {{filename}}.{{extension}}',
350
+ widget: 'object',
351
+ fields: [
352
+ { label: 'First Name', name: 'first_name', widget: 'string' },
353
+ { label: 'Last Name', name: 'last_name', widget: 'string' },
354
+ ],
355
+ },
356
+ ],
357
+ });
358
+
359
+ const { getByText } = render(
360
+ <ListControl
361
+ {...props}
362
+ field={field}
363
+ value={fromJS([{ first_name: 'hello', last_name: 'world', type: 'type_1_object' }])}
364
+ />,
365
+ );
366
+ expect(getByText('hello - world - index.md')).toBeInTheDocument();
367
+ });
368
+
369
+ it('should use widget name when no summary or label are configured for a single field', () => {
370
+ const field = fromJS({
371
+ name: 'list',
372
+ label: 'List',
373
+ collapsed: true,
374
+ field: { name: 'name', widget: 'string' },
375
+ });
376
+
377
+ const { getByText } = render(<ListControl {...props} field={field} value={fromJS(['Name'])} />);
378
+ expect(getByText('name')).toBeInTheDocument();
379
+ });
380
+
381
+ it('should use label when no summary is configured for a single field', () => {
382
+ const field = fromJS({
383
+ name: 'list',
384
+ label: 'List',
385
+ collapsed: true,
386
+ field: { name: 'name', widget: 'string', label: 'Name' },
387
+ });
388
+
389
+ const { getByText } = render(<ListControl {...props} field={field} value={fromJS(['Name'])} />);
390
+ expect(getByText('Name')).toBeInTheDocument();
391
+ });
392
+
393
+ it('should use summary when configured for a single field', () => {
394
+ const field = fromJS({
395
+ name: 'list',
396
+ label: 'List',
397
+ collapsed: true,
398
+ summary: 'Name - {{fields.name}}',
399
+ field: { name: 'name', widget: 'string', label: 'Name' },
400
+ });
401
+
402
+ const { getByText } = render(<ListControl {...props} field={field} value={fromJS(['Name'])} />);
403
+ expect(getByText('Name - Name')).toBeInTheDocument();
404
+ });
405
+
406
+ it('should use first field value when no summary or label are configured for multiple fields', () => {
407
+ const field = fromJS({
408
+ name: 'list',
409
+ label: 'List',
410
+ collapsed: true,
411
+ fields: [
412
+ { name: 'first_name', widget: 'string', label: 'First Name' },
413
+ { name: 'last_name', widget: 'string', label: 'Last Name' },
414
+ ],
415
+ });
416
+
417
+ const { getByText } = render(
418
+ <ListControl
419
+ {...props}
420
+ field={field}
421
+ value={fromJS([{ first_name: 'hello', last_name: 'world' }])}
422
+ />,
423
+ );
424
+ expect(getByText('hello')).toBeInTheDocument();
425
+ });
426
+
427
+ it('should show `No <field>` when value is missing from first field for multiple fields', () => {
428
+ const field = fromJS({
429
+ name: 'list',
430
+ label: 'List',
431
+ collapsed: true,
432
+ fields: [
433
+ { name: 'first_name', widget: 'string', label: 'First Name' },
434
+ { name: 'last_name', widget: 'string', label: 'Last Name' },
435
+ ],
436
+ });
437
+
438
+ const { getByText } = render(
439
+ <ListControl {...props} field={field} value={fromJS([{ last_name: 'world' }])} />,
440
+ );
441
+ expect(getByText('No first_name')).toBeInTheDocument();
442
+ });
443
+
444
+ it('should use summary when configured for multiple fields', () => {
445
+ const field = fromJS({
446
+ name: 'list',
447
+ label: 'List',
448
+ collapsed: true,
449
+ summary: '{{first_name}} - {{last_name}} - {{filename}}.{{extension}}',
450
+ fields: [
451
+ { name: 'first_name', widget: 'string', label: 'First Name' },
452
+ { name: 'last_name', widget: 'string', label: 'Last Name' },
453
+ ],
454
+ });
455
+
456
+ const { getByText } = render(
457
+ <ListControl
458
+ {...props}
459
+ field={field}
460
+ value={fromJS([{ first_name: 'hello', last_name: 'world' }])}
461
+ />,
462
+ );
463
+ expect(getByText('hello - world - index.md')).toBeInTheDocument();
464
+ });
465
+
466
+ it('should render list with fields with default collapse ("true") and minimize_collapsed ("false")', () => {
467
+ const field = fromJS({
468
+ name: 'list',
469
+ label: 'List',
470
+ fields: [{ label: 'String', name: 'string', widget: 'string' }],
471
+ });
472
+ const { asFragment, getByTestId } = render(
473
+ <ListControl
474
+ {...props}
475
+ field={field}
476
+ value={fromJS([{ string: 'item 1' }, { string: 'item 2' }])}
477
+ />,
478
+ );
479
+
480
+ expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'true');
481
+ expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'true');
482
+
483
+ expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'true');
484
+ expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'true');
485
+
486
+ expect(asFragment()).toMatchSnapshot();
487
+ });
488
+
489
+ it('should render list with fields with collapse = "false" and default minimize_collapsed ("false")', () => {
490
+ const field = fromJS({
491
+ name: 'list',
492
+ label: 'List',
493
+ collapsed: false,
494
+ fields: [{ label: 'String', name: 'string', widget: 'string' }],
495
+ });
496
+ const { asFragment, getByTestId } = render(
497
+ <ListControl
498
+ {...props}
499
+ field={field}
500
+ value={fromJS([{ string: 'item 1' }, { string: 'item 2' }])}
501
+ />,
502
+ );
503
+
504
+ expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'false');
505
+ expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'false');
506
+
507
+ expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'false');
508
+ expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'false');
509
+
510
+ expect(asFragment()).toMatchSnapshot();
511
+ });
512
+
513
+ it('should render list with fields with default collapse ("true") and minimize_collapsed = "true"', () => {
514
+ const field = fromJS({
515
+ name: 'list',
516
+ label: 'List',
517
+ minimize_collapsed: true,
518
+ fields: [{ label: 'String', name: 'string', widget: 'string' }],
519
+ });
520
+ const { asFragment, getByTestId, queryByTestId } = render(
521
+ <ListControl
522
+ {...props}
523
+ field={field}
524
+ value={fromJS([{ string: 'item 1' }, { string: 'item 2' }])}
525
+ />,
526
+ );
527
+
528
+ expect(queryByTestId('styled-list-item-top-bar-0')).toBeNull();
529
+ expect(queryByTestId('styled-list-item-top-bar-1')).toBeNull();
530
+
531
+ expect(queryByTestId('object-control-0')).toBeNull();
532
+ expect(queryByTestId('object-control-1')).toBeNull();
533
+
534
+ expect(asFragment()).toMatchSnapshot();
535
+
536
+ fireEvent.click(getByTestId('expand-button'));
537
+
538
+ expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'true');
539
+ expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'true');
540
+
541
+ expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'true');
542
+ expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'true');
543
+ });
544
+
545
+ it('should render list with fields with collapse = "false" and default minimize_collapsed = "true"', () => {
546
+ const field = fromJS({
547
+ name: 'list',
548
+ label: 'List',
549
+ collapsed: false,
550
+ minimize_collapsed: true,
551
+ fields: [{ label: 'String', name: 'string', widget: 'string' }],
552
+ });
553
+ const { asFragment, getByTestId, queryByTestId } = render(
554
+ <ListControl
555
+ {...props}
556
+ field={field}
557
+ value={fromJS([{ string: 'item 1' }, { string: 'item 2' }])}
558
+ />,
559
+ );
560
+
561
+ expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'false');
562
+ expect(getByTestId('styled-list-item-top-bar-1')).toHaveAttribute('collapsed', 'false');
563
+
564
+ expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'false');
565
+ expect(getByTestId('object-control-1')).toHaveAttribute('collapsed', 'false');
566
+
567
+ expect(asFragment()).toMatchSnapshot();
568
+
569
+ fireEvent.click(getByTestId('expand-button'));
570
+
571
+ expect(queryByTestId('styled-list-item-top-bar-0')).toBeNull();
572
+ expect(queryByTestId('styled-list-item-top-bar-1')).toBeNull();
573
+
574
+ expect(queryByTestId('object-control-0')).toBeNull();
575
+ expect(queryByTestId('object-control-1')).toBeNull();
576
+ });
577
+
578
+ it('should add to list when add button is clicked', () => {
579
+ const field = fromJS({
580
+ name: 'list',
581
+ label: 'List',
582
+ fields: [{ label: 'String', name: 'string', widget: 'string' }],
583
+ });
584
+ const { asFragment, getByText, queryByTestId, rerender, getByTestId } = render(
585
+ <ListControl {...props} field={field} value={fromJS([])} />,
586
+ );
587
+
588
+ expect(queryByTestId('object-control-0')).toBeNull();
589
+
590
+ fireEvent.click(getByText('editor.editorWidgets.list.add'));
591
+
592
+ expect(props.onChange).toHaveBeenCalledTimes(1);
593
+ expect(props.onChange).toHaveBeenCalledWith(fromJS([{}]));
594
+
595
+ rerender(<ListControl {...props} field={field} value={fromJS([{}])} />);
596
+
597
+ expect(getByTestId('styled-list-item-top-bar-0')).toHaveAttribute('collapsed', 'false');
598
+ expect(getByTestId('object-control-0')).toHaveAttribute('collapsed', 'false');
599
+
600
+ expect(asFragment()).toMatchSnapshot();
601
+ });
602
+
603
+ it('should remove from list when remove button is clicked', () => {
604
+ const field = fromJS({
605
+ name: 'list',
606
+ label: 'List',
607
+ collapsed: false,
608
+ minimize_collapsed: true,
609
+ fields: [{ label: 'String', name: 'string', widget: 'string' }],
610
+ });
611
+ const { asFragment, getAllByText, rerender } = render(
612
+ <ListControl
613
+ {...props}
614
+ field={field}
615
+ value={fromJS([{ string: 'item 1' }, { string: 'item 2' }])}
616
+ />,
617
+ );
618
+
619
+ expect(asFragment()).toMatchSnapshot();
620
+
621
+ let mock;
622
+ try {
623
+ mock = jest.spyOn(console, 'error').mockImplementation(() => undefined);
624
+
625
+ const items = getAllByText('Remove');
626
+ fireEvent.click(items[0]);
627
+
628
+ expect(props.onChange).toHaveBeenCalledTimes(1);
629
+ expect(props.onChange).toHaveBeenCalledWith(fromJS([{ string: 'item 2' }]), undefined);
630
+
631
+ rerender(<ListControl {...props} field={field} value={fromJS([{ string: 'item 2' }])} />);
632
+
633
+ expect(asFragment()).toMatchSnapshot();
634
+ } finally {
635
+ mock.mockRestore();
636
+ }
637
+ });
638
+
639
+ it('should give validation error if below min elements', () => {
640
+ const field = fromJS({
641
+ name: 'list',
642
+ label: 'List',
643
+ collapsed: false,
644
+ minimize_collapsed: true,
645
+ required: true,
646
+ min: 2,
647
+ max: 3,
648
+ fields: [{ label: 'String', name: 'string', widget: 'string' }],
649
+ });
650
+ const listControl = new ListControl({
651
+ ...props,
652
+ field,
653
+ value: fromJS([{ string: 'item 1' }]),
654
+ });
655
+
656
+ listControl.validate();
657
+ expect(props.onValidateObject).toHaveBeenCalledWith('forID', [
658
+ {
659
+ message: 'editor.editorControlPane.widget.rangeCount',
660
+ type: 'RANGE',
661
+ },
662
+ ]);
663
+ });
664
+
665
+ it('should give min validation error if below min elements', () => {
666
+ const field = fromJS({
667
+ name: 'list',
668
+ label: 'List',
669
+ collapsed: false,
670
+ minimize_collapsed: true,
671
+ required: true,
672
+ min: 2,
673
+ fields: [{ label: 'String', name: 'string', widget: 'string' }],
674
+ });
675
+ const listControl = new ListControl({
676
+ ...props,
677
+ field,
678
+ value: fromJS([{ string: 'item 1' }]),
679
+ });
680
+
681
+ listControl.validate();
682
+ expect(props.onValidateObject).toHaveBeenCalledWith('forID', [
683
+ {
684
+ message: 'editor.editorControlPane.widget.rangeMin',
685
+ type: 'RANGE',
686
+ },
687
+ ]);
688
+ });
689
+
690
+ it('should give validation error if above max elements', () => {
691
+ const field = fromJS({
692
+ name: 'list',
693
+ label: 'List',
694
+ collapsed: false,
695
+ minimize_collapsed: true,
696
+ required: true,
697
+ min: 2,
698
+ max: 3,
699
+ fields: [{ label: 'String', name: 'string', widget: 'string' }],
700
+ });
701
+ const listControl = new ListControl({
702
+ ...props,
703
+ field,
704
+ value: fromJS([
705
+ { string: 'item 1' },
706
+ { string: 'item 2' },
707
+ { string: 'item 3' },
708
+ { string: 'item 4' },
709
+ ]),
710
+ });
711
+
712
+ listControl.validate();
713
+ expect(props.onValidateObject).toHaveBeenCalledWith('forID', [
714
+ {
715
+ message: 'editor.editorControlPane.widget.rangeCount',
716
+ type: 'RANGE',
717
+ },
718
+ ]);
719
+ });
720
+
721
+ it('should give max validation error if above max elements', () => {
722
+ const field = fromJS({
723
+ name: 'list',
724
+ label: 'List',
725
+ collapsed: false,
726
+ minimize_collapsed: true,
727
+ required: true,
728
+ max: 3,
729
+ fields: [{ label: 'String', name: 'string', widget: 'string' }],
730
+ });
731
+ const listControl = new ListControl({
732
+ ...props,
733
+ field,
734
+ value: fromJS([
735
+ { string: 'item 1' },
736
+ { string: 'item 2' },
737
+ { string: 'item 3' },
738
+ { string: 'item 4' },
739
+ ]),
740
+ });
741
+
742
+ listControl.validate();
743
+ expect(props.onValidateObject).toHaveBeenCalledWith('forID', [
744
+ {
745
+ message: 'editor.editorControlPane.widget.rangeMax',
746
+ type: 'RANGE',
747
+ },
748
+ ]);
749
+ });
750
+
751
+ it('should give no validation error if between min and max elements', () => {
752
+ const field = fromJS({
753
+ name: 'list',
754
+ label: 'List',
755
+ collapsed: false,
756
+ minimize_collapsed: true,
757
+ required: true,
758
+ min: 2,
759
+ max: 3,
760
+ fields: [{ label: 'String', name: 'string', widget: 'string' }],
761
+ });
762
+ const listControl = new ListControl({
763
+ ...props,
764
+ field,
765
+ value: fromJS([{ string: 'item 1' }, { string: 'item 2' }, { string: 'item 3' }]),
766
+ });
767
+
768
+ listControl.validate();
769
+ expect(props.onValidateObject).toHaveBeenCalledWith('forID', []);
770
+ });
771
+
772
+ it('should give no validation error if no elements and optional', () => {
773
+ const field = fromJS({
774
+ name: 'list',
775
+ label: 'List',
776
+ collapsed: false,
777
+ minimize_collapsed: true,
778
+ required: false,
779
+ min: 2,
780
+ max: 3,
781
+ fields: [{ label: 'String', name: 'string', widget: 'string' }],
782
+ });
783
+ const listControl = new ListControl({
784
+ ...props,
785
+ field,
786
+ value: fromJS([]),
787
+ });
788
+
789
+ listControl.validate();
790
+ expect(props.onValidateObject).toHaveBeenCalledWith('forID', []);
791
+ });
792
+ });