@digigov/form 2.0.9 → 2.0.11

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 (33) hide show
  1. package/CHANGELOG.md +19 -2
  2. package/FieldArray/BaseFieldArray.d.ts +3 -2
  3. package/FieldArray/BaseFieldArray.js +26 -4
  4. package/FieldArray/DeleteConfirmationModal.d.ts +8 -0
  5. package/FieldArray/DeleteConfirmationModal.js +29 -0
  6. package/FieldArray/FormDialog/ArrayDisplay/ArrayItemDisplay.js +3 -1
  7. package/FieldArray/FormDialog/ArrayDisplay/index.d.ts +2 -1
  8. package/FieldArray/FormDialog/ArrayDisplay/index.js +39 -6
  9. package/FieldArray/FormDialog/index.d.ts +1 -0
  10. package/FieldArray/FormDialog/index.js +4 -3
  11. package/FieldArray/index.js +2 -0
  12. package/index.js +1 -1
  13. package/lazy.d.ts +4 -34
  14. package/lazy.js +3 -0
  15. package/package.json +4 -4
  16. package/registry.d.ts +3 -2
  17. package/registry.js +6 -4
  18. package/src/FieldArray/BaseFieldArray.tsx +34 -3
  19. package/src/FieldArray/DeleteConfirmationModal.tsx +57 -0
  20. package/src/FieldArray/FieldArray.stories.js +1 -0
  21. package/src/FieldArray/FormDialog/ArrayDisplay/ArrayDisplay.stories.js +1 -0
  22. package/src/FieldArray/FormDialog/ArrayDisplay/ArrayItemDisplay.tsx +3 -1
  23. package/src/FieldArray/FormDialog/ArrayDisplay/__stories__/WithDeleteConfirmation.tsx +94 -0
  24. package/src/FieldArray/FormDialog/ArrayDisplay/index.test.tsx +30 -26
  25. package/src/FieldArray/FormDialog/ArrayDisplay/index.tsx +70 -36
  26. package/src/FieldArray/FormDialog/index.tsx +5 -1
  27. package/src/FieldArray/__stories__/WithDeleteConfirmation.tsx +190 -0
  28. package/src/FieldArray/__tests__/nested-fieldset-multiplicity.spec.tsx +210 -219
  29. package/src/FieldArray/index.spec.tsx +42 -0
  30. package/src/FieldArray/index.test.tsx +27 -23
  31. package/src/FieldArray/index.tsx +2 -1
  32. package/src/lazy.ts +1 -0
  33. package/src/registry.ts +6 -4
@@ -272,12 +272,8 @@ describe('nested_fieldset_multiplicity case from dilosi', () => {
272
272
  it(`should open the nested_multiplicity_${fieldsetType} dialog on nested "add" button click`, async () => {
273
273
  render(<TestForm />);
274
274
  openMainDialog();
275
- const nestedFieldset = getNestedFieldset(fieldsetType);
276
- const nestedFieldsetAddButton = within(nestedFieldset!).getByRole(
277
- 'button',
278
- { name: 'Προσθήκη' }
279
- );
280
- fireEvent.click(nestedFieldsetAddButton);
275
+ const nestedAddButton = getNestedAddButton(fieldsetType);
276
+ fireEvent.click(nestedAddButton);
281
277
  expect(screen.getAllByRole('dialog').length).toBe(2);
282
278
  const inputField = screen.getByRole('textbox');
283
279
  expect(inputField).toBeInTheDocument();
@@ -306,18 +302,12 @@ describe('nested_fieldset_multiplicity case from dilosi', () => {
306
302
  it('should validate nested_multiplicity_required string field', async () => {
307
303
  render(<TestForm />);
308
304
  openMainDialog();
309
- const nestedFieldset = getNestedFieldset('required');
310
- const nestedFieldsetAddButton = within(nestedFieldset).getByRole('button', {
311
- name: 'Προσθήκη',
312
- });
313
- fireEvent.click(nestedFieldsetAddButton);
305
+ const nestedAddButton = getNestedAddButton('required');
306
+ fireEvent.click(nestedAddButton);
314
307
  expect(screen.getAllByRole('dialog').length).toBe(2);
315
308
  const inputField = screen.getByRole('textbox');
316
309
  expect(inputField).toHaveProperty('name', `nested_multiplicity_required`);
317
- const submitButton = screen.getAllByRole('button', {
318
- name: 'Αποθήκευση',
319
- })[1];
320
- fireEvent.click(submitButton);
310
+ clickNestedSaveButton();
321
311
  await waitFor(() =>
322
312
  expect(
323
313
  screen.getByText('Το πεδίο είναι υποχρεωτικό.')
@@ -328,49 +318,17 @@ describe('nested_fieldset_multiplicity case from dilosi', () => {
328
318
  it('should submit nested_multiplicity_required item added by nested form', async () => {
329
319
  render(<TestForm />);
330
320
  openMainDialog();
331
- const nestedFieldset = getNestedFieldset('required');
332
- const nestedFieldsetAddButton = within(nestedFieldset).getByRole('button', {
333
- name: 'Προσθήκη',
334
- });
335
- fireEvent.click(nestedFieldsetAddButton);
336
- expect(screen.getAllByRole('dialog').length).toBe(2);
337
- const nestedFieldsetInput = screen.getByRole('textbox');
338
- expect(nestedFieldsetInput).toBeInTheDocument();
339
- expect(nestedFieldsetInput).toHaveProperty(
340
- 'name',
341
- `nested_multiplicity_required`
342
- );
343
- fireEvent.change(nestedFieldsetInput, {
344
- target: { value: 'Test Value' },
345
- });
346
- expect(nestedFieldsetInput).toHaveValue('Test Value');
347
- // There are two "Αποθήκευση" buttons, one for the nested dialog and one for the main dialog
348
- const submitButton = screen.getAllByRole('button', {
349
- name: 'Αποθήκευση',
350
- })[1];
351
- fireEvent.click(submitButton);
352
- await waitFor(() => {
353
- // Only one dialog should remain open (the main one)
354
- expect(screen.getAllByRole('dialog').length).toBe(1);
355
- });
321
+
322
+ const nestedAddButton = getNestedAddButton('required');
323
+ await addNestedItem(nestedAddButton, 'Test Value');
356
324
 
357
325
  expect(screen.getByText('Test Value')).toBeInTheDocument();
358
326
  expect(submitHandler).not.toHaveBeenCalled();
359
327
 
360
- const mainDialogSubmitButton = screen.getByRole('button', {
361
- name: 'Αποθήκευση',
362
- });
363
- fireEvent.click(mainDialogSubmitButton);
364
- await waitFor(() => {
365
- expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
366
- });
328
+ await saveMainDialog();
367
329
  expect(screen.getByText('Test Value')).toBeInTheDocument();
368
- const continueButton = screen.getByRole('button', { name: 'Συνέχεια' });
369
- expect(continueButton).toBeInTheDocument();
370
- fireEvent.click(continueButton);
371
- await waitFor(() => {
372
- expect(submitHandler).toHaveBeenCalledTimes(1);
373
- });
330
+
331
+ await submitForm(submitHandler);
374
332
  expect(submitHandler).toHaveBeenCalledWith({
375
333
  nested_fieldset_multiplicity: expect.arrayContaining([
376
334
  {
@@ -386,108 +344,25 @@ describe('nested_fieldset_multiplicity case from dilosi', () => {
386
344
  openMainDialog();
387
345
 
388
346
  // Add multiple nested_multiplicity_required items
389
- const requiredNestedFieldset = getNestedFieldset('required');
390
- const requiredNestedFieldsetAddButton = within(
391
- requiredNestedFieldset
392
- ).getByRole('button', {
393
- name: 'Προσθήκη',
394
- });
395
- fireEvent.click(requiredNestedFieldsetAddButton);
396
- expect(screen.getAllByRole('dialog').length).toBe(2);
397
- const requiredNestedFieldsetInput = screen.getByRole('textbox');
398
- expect(requiredNestedFieldsetInput).toBeInTheDocument();
399
- expect(requiredNestedFieldsetInput).toHaveProperty(
400
- 'name',
401
- `nested_multiplicity_required`
402
- );
403
- fireEvent.change(requiredNestedFieldsetInput, {
404
- target: { value: 'Required Test Value 1' },
405
- });
406
- expect(requiredNestedFieldsetInput).toHaveValue('Required Test Value 1');
407
- // There are two "Αποθήκευση" buttons, one for the nested dialog and one for the main dialog
408
- const requiredSubmitButton = screen.getAllByRole('button', {
409
- name: 'Αποθήκευση',
410
- })[1];
411
- fireEvent.click(requiredSubmitButton);
412
- await waitFor(() => {
413
- // Only one dialog should remain open (the main one)
414
- expect(screen.getAllByRole('dialog').length).toBe(1);
415
- });
416
-
347
+ const requiredAddButton = getNestedAddButton('required');
348
+ await addNestedItem(requiredAddButton, 'Required Test Value 1');
417
349
  expect(screen.getByText('Required Test Value 1')).toBeInTheDocument();
418
350
  expect(submitHandler).not.toHaveBeenCalled();
419
- // Add a second required item
420
- fireEvent.click(requiredNestedFieldsetAddButton);
421
- expect(screen.getAllByRole('dialog').length).toBe(2);
422
- const secondRequiredNestedFieldsetInput = screen.getByRole('textbox');
423
- expect(secondRequiredNestedFieldsetInput).toBeInTheDocument();
424
- expect(secondRequiredNestedFieldsetInput).toHaveProperty(
425
- 'name',
426
- `nested_multiplicity_required`
427
- );
428
- fireEvent.change(secondRequiredNestedFieldsetInput, {
429
- target: { value: 'Required Test Value 2' },
430
- });
431
- expect(secondRequiredNestedFieldsetInput).toHaveValue(
432
- 'Required Test Value 2'
433
- );
434
- const secondRequiredSubmitButton = screen.getAllByRole('button', {
435
- name: 'Αποθήκευση',
436
- })[1];
437
- fireEvent.click(secondRequiredSubmitButton);
438
- await waitFor(() => {
439
- // Only one dialog should remain open (the main one)
440
- expect(screen.getAllByRole('dialog').length).toBe(1);
441
- });
442
351
 
352
+ await addNestedItem(requiredAddButton, 'Required Test Value 2');
443
353
  expect(screen.getByText('Required Test Value 1')).toBeInTheDocument();
444
354
  expect(submitHandler).not.toHaveBeenCalled();
445
355
 
446
356
  // Add nested_multiplicity_optional item
447
- const optionalNestedFieldset = getNestedFieldset('optional');
448
- const optionalNestedFieldsetAddButton = within(
449
- optionalNestedFieldset
450
- ).getByRole('button', { name: 'Προσθήκη' });
451
- fireEvent.click(optionalNestedFieldsetAddButton);
452
- expect(screen.getAllByRole('dialog').length).toBe(2);
453
- const optionalNestedFieldsetInput = screen.getByRole('textbox');
454
- expect(optionalNestedFieldsetInput).toBeInTheDocument();
455
- expect(optionalNestedFieldsetInput).toHaveProperty(
456
- 'name',
457
- `nested_multiplicity_optional`
458
- );
459
- fireEvent.change(optionalNestedFieldsetInput, {
460
- target: { value: 'Optional Test Value' },
461
- });
462
- expect(optionalNestedFieldsetInput).toHaveValue('Optional Test Value');
463
-
464
- // There are two "Αποθήκευση" buttons, one for the nested dialog and one for the main dialog
465
- const optionalSubmitButton = screen.getAllByRole('button', {
466
- name: 'Αποθήκευση',
467
- })[1];
468
- fireEvent.click(optionalSubmitButton);
469
- await waitFor(() => {
470
- // Only one dialog should remain open (the main one)
471
- expect(screen.getAllByRole('dialog').length).toBe(1);
472
- });
473
-
357
+ const optionalAddButton = getNestedAddButton('optional');
358
+ await addNestedItem(optionalAddButton, 'Optional Test Value');
474
359
  expect(screen.getByText('Optional Test Value')).toBeInTheDocument();
475
360
  expect(submitHandler).not.toHaveBeenCalled();
476
361
 
477
- const mainDialogSubmitButton = screen.getByRole('button', {
478
- name: 'Αποθήκευση',
479
- });
480
- fireEvent.click(mainDialogSubmitButton);
481
- await waitFor(() => {
482
- expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
483
- });
362
+ await saveMainDialog();
484
363
  expect(screen.getByText('Optional Test Value')).toBeInTheDocument();
485
- const continueButton = screen.getByRole('button', { name: 'Συνέχεια' });
486
- expect(continueButton).toBeInTheDocument();
487
- fireEvent.click(continueButton);
488
- await waitFor(() => {
489
- expect(submitHandler).toHaveBeenCalledTimes(1);
490
- });
364
+
365
+ await submitForm(submitHandler);
491
366
  expect(submitHandler).toHaveBeenCalledWith({
492
367
  nested_fieldset_multiplicity: expect.arrayContaining([
493
368
  {
@@ -501,96 +376,144 @@ describe('nested_fieldset_multiplicity case from dilosi', () => {
501
376
  });
502
377
  });
503
378
 
504
- it('should submit multiple fieldset items added to main form', async () => {
379
+ it('should have independent nested_multiplicity_required max limits per fieldset item', async () => {
505
380
  render(<TestForm />);
381
+
382
+ // --- First nested_fieldset_multiplicity item ---
506
383
  openMainDialog();
507
384
 
508
- // Add first item
509
- const nestedFieldset = getNestedFieldset('required');
510
- const nestedFieldsetAddButton = within(nestedFieldset).getByRole('button', {
511
- name: 'Προσθήκη',
512
- });
513
- fireEvent.click(nestedFieldsetAddButton);
514
- expect(screen.getAllByRole('dialog').length).toBe(2);
515
- const nestedFieldsetInput = screen.getByRole('textbox');
516
- expect(nestedFieldsetInput).toBeInTheDocument();
517
- expect(nestedFieldsetInput).toHaveProperty(
518
- 'name',
519
- `nested_multiplicity_required`
520
- );
521
- fireEvent.change(nestedFieldsetInput, {
522
- target: { value: 'Test Value 1' },
523
- });
524
- expect(nestedFieldsetInput).toHaveValue('Test Value 1');
525
- // There are two "Αποθήκευση" buttons, one for the nested dialog and one for the main dialog
526
- const submitButton = screen.getAllByRole('button', {
527
- name: 'Αποθήκευση',
528
- })[1];
529
- fireEvent.click(submitButton);
530
- await waitFor(() => {
531
- // Only one dialog should remain open (the main one)
532
- expect(screen.getAllByRole('dialog').length).toBe(1);
385
+ const nestedAddButton = getNestedAddButton('required');
386
+
387
+ // Fill all 10 (max) nested_multiplicity_required items
388
+ await addNestedItems(nestedAddButton, 'Item 1', 10);
389
+
390
+ // The "add" button for nested_multiplicity_required should now be disabled (max reached)
391
+ expect(nestedAddButton).toBeDisabled();
392
+
393
+ // Submit first fieldset item
394
+ await saveMainDialog();
395
+
396
+ // --- Second nested_fieldset_multiplicity item ---
397
+ openMainDialog();
398
+
399
+ const nestedAddButton2 = getNestedAddButton('required', true);
400
+
401
+ // The "add" button should NOT be disabled — it's a fresh fieldset item
402
+ expect(nestedAddButton2).not.toBeDisabled();
403
+
404
+ // Add a nested_multiplicity_required item to verify it works
405
+ await addNestedItems(nestedAddButton2, 'Item 2', 1);
406
+ expect(screen.getByText('Item 2-1')).toBeInTheDocument();
407
+ });
408
+
409
+ it('should not validate nested_multiplicity_optional when empty (min 0)', async () => {
410
+ render(<TestForm />);
411
+ openMainDialog();
412
+
413
+ // Add only a required item — leave optional empty
414
+ const requiredAddButton = getNestedAddButton('required');
415
+ await addNestedItem(requiredAddButton, 'Required Value');
416
+
417
+ // Save main dialog — should succeed without optional validation error
418
+ await saveMainDialog();
419
+
420
+ await submitForm(submitHandler);
421
+ expect(submitHandler).toHaveBeenCalledWith({
422
+ nested_fieldset_multiplicity: expect.arrayContaining([
423
+ {
424
+ nested_multiplicity_required: ['Required Value'],
425
+ nested_multiplicity_optional: [],
426
+ },
427
+ ]),
533
428
  });
429
+ });
430
+
431
+ it('should disable nested_multiplicity_optional add button when max (10) is reached', async () => {
432
+ render(<TestForm />);
433
+ openMainDialog();
434
+
435
+ // Must add at least 1 required item to allow saving later
436
+ const requiredAddButton = getNestedAddButton('required');
437
+ await addNestedItem(requiredAddButton, 'Required Value');
438
+
439
+ const optionalAddButton = getNestedAddButton('optional');
440
+
441
+ // Fill all 10 (max) optional items
442
+ await addNestedItems(optionalAddButton, 'Opt', 10);
443
+
444
+ // The "add" button for nested_multiplicity_optional should now be disabled
445
+ expect(optionalAddButton).toBeDisabled();
446
+ });
447
+
448
+ it('should have independent nested_multiplicity_optional max limits per fieldset item', async () => {
449
+ render(<TestForm />);
450
+
451
+ // --- First nested_fieldset_multiplicity item ---
452
+ openMainDialog();
453
+
454
+ const requiredAddButton = getNestedAddButton('required');
455
+ await addNestedItem(requiredAddButton, 'Req 1');
456
+
457
+ const optionalAddButton = getNestedAddButton('optional');
458
+ await addNestedItems(optionalAddButton, 'Opt 1', 10);
459
+
460
+ // Max reached for optional in first fieldset item
461
+ expect(optionalAddButton).toBeDisabled();
462
+
463
+ await saveMainDialog();
464
+
465
+ // --- Second nested_fieldset_multiplicity item ---
466
+ openMainDialog();
467
+
468
+ const requiredAddButton2 = getNestedAddButton('required', true);
469
+ await addNestedItem(requiredAddButton2, 'Req 2');
470
+
471
+ const optionalAddButton2 = getNestedAddButton('optional', true);
472
+
473
+ // The "add" button should NOT be disabled — it's a fresh fieldset item
474
+ expect(optionalAddButton2).not.toBeDisabled();
475
+
476
+ await addNestedItem(optionalAddButton2, 'Opt 2-1');
477
+ expect(screen.getByText('Opt 2-1')).toBeInTheDocument();
478
+ });
479
+
480
+ it('should disable outer add button when max (5) fieldset items are reached', async () => {
481
+ render(<TestForm />);
482
+
483
+ // Add 5 fieldset items (the max)
484
+ for (let i = 1; i <= 5; i++) {
485
+ await addFieldsetItem(`Value ${i}`);
486
+ }
487
+
488
+ // The outer "Προσθήκη" button should now be disabled
489
+ const outerAddButton = screen.getByRole('button', { name: 'Προσθήκη' });
490
+ expect(outerAddButton).toBeDisabled();
491
+ });
492
+
493
+ it('should submit multiple fieldset items added to main form', async () => {
494
+ render(<TestForm />);
495
+
496
+ // Add first item
497
+ openMainDialog();
498
+ const nestedAddButton = getNestedAddButton('required');
499
+ await addNestedItem(nestedAddButton, 'Test Value 1');
534
500
  expect(screen.getByText('Test Value 1')).toBeInTheDocument();
535
501
  expect(submitHandler).not.toHaveBeenCalled();
536
502
 
537
- // Submit first item
538
- const mainDialogSubmitButton = screen.getByRole('button', {
539
- name: 'Αποθήκευση',
540
- });
541
- fireEvent.click(mainDialogSubmitButton);
542
- await waitFor(() => {
543
- expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
544
- });
503
+ await saveMainDialog();
545
504
  expect(screen.getByText('Test Value 1')).toBeInTheDocument();
546
505
 
547
506
  // Add second item
548
507
  openMainDialog();
549
- const secondNestedFieldset = getNestedFieldset('required', true);
550
- const secondNestedFieldsetAddButton = within(
551
- secondNestedFieldset
552
- ).getByRole('button', {
553
- name: 'Προσθήκη',
554
- });
555
- fireEvent.click(secondNestedFieldsetAddButton);
556
- expect(screen.getAllByRole('dialog').length).toBe(2);
557
- const secondNestedFieldsetInput = screen.getByRole('textbox');
558
- expect(secondNestedFieldsetInput).toBeInTheDocument();
559
- expect(secondNestedFieldsetInput).toHaveProperty(
560
- 'name',
561
- `nested_multiplicity_required`
562
- );
563
- fireEvent.change(secondNestedFieldsetInput, {
564
- target: { value: 'Test Value 2' },
565
- });
566
- expect(secondNestedFieldsetInput).toHaveValue('Test Value 2');
567
- // There are two "Αποθήκευση" buttons, one for the nested dialog and one for the main dialog
568
- const secondSubmitButton = screen.getAllByRole('button', {
569
- name: 'Αποθήκευση',
570
- })[1];
571
- fireEvent.click(secondSubmitButton);
572
- await waitFor(() => {
573
- // Only one dialog should remain open (the main one)
574
- expect(screen.getAllByRole('dialog').length).toBe(1);
575
- });
508
+ const secondNestedAddButton = getNestedAddButton('required', true);
509
+ await addNestedItem(secondNestedAddButton, 'Test Value 2');
576
510
  expect(screen.getByText('Test Value 2')).toBeInTheDocument();
577
511
  expect(submitHandler).not.toHaveBeenCalled();
578
- // Submit second item
579
- const secondMainDialogSubmitButton = screen.getByRole('button', {
580
- name: 'Αποθήκευση',
581
- });
582
- fireEvent.click(secondMainDialogSubmitButton);
583
- await waitFor(() => {
584
- expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
585
- });
512
+
513
+ await saveMainDialog();
586
514
  expect(screen.getByText('Test Value 2')).toBeInTheDocument();
587
515
 
588
- const continueButton = screen.getByRole('button', { name: 'Συνέχεια' });
589
- expect(continueButton).toBeInTheDocument();
590
- fireEvent.click(continueButton);
591
- await waitFor(() => {
592
- expect(submitHandler).toHaveBeenCalledTimes(1);
593
- });
516
+ await submitForm(submitHandler);
594
517
  expect(submitHandler).toHaveBeenCalledWith({
595
518
  nested_fieldset_multiplicity: expect.arrayContaining([
596
519
  {
@@ -606,6 +529,8 @@ describe('nested_fieldset_multiplicity case from dilosi', () => {
606
529
  });
607
530
  });
608
531
 
532
+ // --- Helper functions ---
533
+
609
534
  function openMainDialog() {
610
535
  const addButton = screen.getByRole('button', { name: 'Προσθήκη' });
611
536
  fireEvent.click(addButton);
@@ -625,3 +550,69 @@ function getNestedFieldset(
625
550
  expect(nestedFieldset).toBeInTheDocument();
626
551
  return nestedFieldset!;
627
552
  }
553
+
554
+ function getNestedAddButton(
555
+ type: 'required' | 'optional',
556
+ hasSubmittedOnce = false
557
+ ) {
558
+ const fieldset = getNestedFieldset(type, hasSubmittedOnce);
559
+ return within(fieldset).getByRole('button', { name: 'Προσθήκη' });
560
+ }
561
+
562
+ function clickNestedSaveButton() {
563
+ const saveBtn = screen.getAllByRole('button', { name: 'Αποθήκευση' })[1];
564
+ fireEvent.click(saveBtn);
565
+ }
566
+
567
+ async function addNestedItem(addButton: HTMLElement, value: string) {
568
+ fireEvent.click(addButton);
569
+ expect(screen.getAllByRole('dialog').length).toBe(2);
570
+ const input = screen.getByRole('textbox');
571
+ fireEvent.change(input, { target: { value } });
572
+ clickNestedSaveButton();
573
+ await waitFor(() => {
574
+ expect(screen.getAllByRole('dialog').length).toBe(1);
575
+ });
576
+ }
577
+
578
+ async function addNestedItems(
579
+ addButton: HTMLElement,
580
+ prefix: string,
581
+ count: number
582
+ ) {
583
+ for (let i = 1; i <= count; i++) {
584
+ await addNestedItem(addButton, `${prefix}-${i}`);
585
+ }
586
+ }
587
+
588
+ async function saveMainDialog() {
589
+ const saveBtn = screen.getByRole('button', { name: 'Αποθήκευση' });
590
+ fireEvent.click(saveBtn);
591
+ await waitFor(() => {
592
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
593
+ });
594
+ }
595
+
596
+ async function addFieldsetItem(requiredValue: string) {
597
+ openMainDialog();
598
+ // After the first submission, there are duplicate fieldset labels in the DOM.
599
+ // Always pick the last matching fieldset (the one inside the open dialog).
600
+ const requiredFieldset = screen
601
+ .getAllByText(/"required" μέχρι 10/)
602
+ .at(-1)!
603
+ .closest('fieldset')!;
604
+ const nestedAddButton = within(requiredFieldset).getByRole('button', {
605
+ name: 'Προσθήκη',
606
+ });
607
+ await addNestedItem(nestedAddButton, requiredValue);
608
+ await saveMainDialog();
609
+ }
610
+
611
+ async function submitForm(submitHandler: ReturnType<typeof vi.fn>) {
612
+ const continueButton = screen.getByRole('button', { name: 'Συνέχεια' });
613
+ expect(continueButton).toBeInTheDocument();
614
+ fireEvent.click(continueButton);
615
+ await waitFor(() => {
616
+ expect(submitHandler).toHaveBeenCalledTimes(1);
617
+ });
618
+ }
@@ -351,5 +351,47 @@ describe('FieldArray', () => {
351
351
 
352
352
  expect(screen.getByText('Προσθήκη συνυπογράφοντα')).toHaveFocus();
353
353
  });
354
+
355
+ it('should show delete confirmation modal and delete item on confirm', async () => {
356
+ render(
357
+ <TestForm
358
+ fieldObject={{
359
+ ...fieldSpec,
360
+ extra: {
361
+ ...fieldSpec.extra,
362
+ deleteConfirmation: true,
363
+ },
364
+ }}
365
+ />
366
+ );
367
+
368
+ const addButton = await screen.findByText('Προσθήκη συνυπογράφοντα');
369
+ expect(addButton).toBeInTheDocument();
370
+ fireEvent.click(addButton);
371
+
372
+ const nameInput = screen.getByLabelText('Όνομα');
373
+ fireEvent.change(nameInput, {
374
+ target: { value: 'John' },
375
+ });
376
+ fireEvent.click(screen.getByText('Αποθήκευση'));
377
+
378
+ await waitFor(() =>
379
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
380
+ );
381
+ await waitFor(() => expect(screen.getByText('John')).toBeInTheDocument());
382
+
383
+ fireEvent.click(screen.getByText('Διαγραφή'));
384
+
385
+ await waitFor(() =>
386
+ expect(screen.getByRole('dialog')).toBeInTheDocument()
387
+ );
388
+ expect(screen.getByText('Επιβεβαίωση διαγραφής')).toBeInTheDocument();
389
+ fireEvent.click(screen.getAllByText('Διαγραφή')[1]);
390
+ await waitFor(() =>
391
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
392
+ );
393
+
394
+ expect(screen.queryByText('John')).not.toBeInTheDocument();
395
+ });
354
396
  });
355
397
  });
@@ -1,40 +1,44 @@
1
1
  import React from 'react';
2
2
  import { test, expect } from '@playwright/experimental-ct-react';
3
- import TestVariant from '@digigov/ui/utils/TestVariant'
3
+ import TestVariant from '@digigov/ui/utils/TestVariant';
4
4
  import { CardsWithError } from '@digigov/form/FieldArray/__stories__/CardsWithError';
5
5
  import { Default } from '@digigov/form/FieldArray/__stories__/Default';
6
6
  import { TableWithError } from '@digigov/form/FieldArray/__stories__/TableWithError';
7
7
  import { WithExactLength } from '@digigov/form/FieldArray/__stories__/WithExactLength';
8
8
  import { WithModal } from '@digigov/form/FieldArray/__stories__/WithModal';
9
+ import { WithDeleteConfirmation } from '@digigov/form/FieldArray/__stories__/WithDeleteConfirmation';
9
10
 
10
11
  test('renders the All FieldArray variants', async ({ mount, page }) => {
11
12
  await mount(
12
-
13
- <div>
14
- <TestVariant title="CardsWithError">
15
- <CardsWithError />
16
- </TestVariant>
17
- <TestVariant title="Default">
18
- <Default />
19
- </TestVariant>
20
- <TestVariant title="TableWithError">
21
- <TableWithError />
22
- </TestVariant>
23
- <TestVariant title="WithExactLength">
24
- <WithExactLength />
25
- </TestVariant>
26
- <TestVariant title="WithModal">
27
- <WithModal />
28
- </TestVariant>
29
- </div>
30
- )
13
+ <div>
14
+ <TestVariant title="CardsWithError">
15
+ <CardsWithError />
16
+ </TestVariant>
17
+ <TestVariant title="Default">
18
+ <Default />
19
+ </TestVariant>
20
+ <TestVariant title="TableWithError">
21
+ <TableWithError />
22
+ </TestVariant>
23
+ <TestVariant title="WithExactLength">
24
+ <WithExactLength />
25
+ </TestVariant>
26
+ <TestVariant title="WithModal">
27
+ <WithModal />
28
+ </TestVariant>
29
+ <TestVariant title="WithDeleteConfirmation">
30
+ <WithDeleteConfirmation />
31
+ </TestVariant>
32
+ </div>
33
+ );
31
34
  await page.evaluate(() => document.fonts.ready);
32
35
 
33
36
  // Move the mouse to the top-left corner to avoid random hover issues
34
37
  await page.mouse.move(0, 0);
35
38
 
36
-
37
- const screenshot = await page.screenshot({ fullPage: true, animations: 'disabled' });
39
+ const screenshot = await page.screenshot({
40
+ fullPage: true,
41
+ animations: 'disabled',
42
+ });
38
43
  expect(screenshot).toMatchSnapshot();
39
44
  });
40
-
@@ -40,7 +40,6 @@ export const FieldArray: React.FC<FieldArrayProps> = ({
40
40
  }) => {
41
41
  /** The ref of the add-button */
42
42
  const buttonRef = useRef<HTMLButtonElement | null>(null);
43
-
44
43
  // Register the button to be focusable
45
44
  useEffect(() => {
46
45
  let unregister: (name: string) => void = () => {};
@@ -90,6 +89,7 @@ export const FieldArray: React.FC<FieldArrayProps> = ({
90
89
  reset={reset}
91
90
  resetField={resetField}
92
91
  sortable={customField.extra.sortable}
92
+ deleteConfirmation={customField.extra?.deleteConfirmation}
93
93
  {...customField}
94
94
  />
95
95
  ) : (
@@ -102,6 +102,7 @@ export const FieldArray: React.FC<FieldArrayProps> = ({
102
102
  error={error}
103
103
  getValues={getValues}
104
104
  Field={Field}
105
+ deleteConfirmation={customField.extra?.deleteConfirmation}
105
106
  {...customField}
106
107
  />
107
108
  )}
package/src/lazy.ts CHANGED
@@ -10,6 +10,7 @@ export default {
10
10
  'FieldBaseContainer': lazy(() => import('@digigov/form/Field/FieldBaseContainer').then((module) => ({ default: module['FieldBaseContainer'] }))),
11
11
  'FieldConditional': lazy(() => import('@digigov/form/Field/FieldConditional').then((module) => ({ default: module['FieldConditional'] }))),
12
12
  'BaseFieldArray': lazy(() => import('@digigov/form/FieldArray/BaseFieldArray').then((module) => ({ default: module['BaseFieldArray'] }))),
13
+ 'DeleteConfirmationModal': lazy(() => import('@digigov/form/FieldArray/DeleteConfirmationModal').then((module) => ({ default: module['DeleteConfirmationModal'] }))),
13
14
  'FieldArray': lazy(() => import('@digigov/form/FieldArray').then((module) => ({ default: module['FieldArray'] }))),
14
15
  'FieldObject': lazy(() => import('@digigov/form/FieldObject').then((module) => ({ default: module['FieldObject'] }))),
15
16
  'FieldsetWithContext': lazy(() => import('@digigov/form/Fieldset/FieldsetWithContext').then((module) => ({ default: module['FieldsetWithContext'] }))),