@justeattakeaway/pie-modal 0.17.0 → 0.18.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.
@@ -1,816 +0,0 @@
1
- import { test, expect } from '@sand4rt/experimental-ct-web';
2
- import { type Page } from '@playwright/test';
3
- import { PieButton } from '@justeattakeaway/pie-button';
4
- import { PieIconButton } from '@justeattakeaway/pie-icon-button';
5
- import {
6
- WebComponentTestWrapper,
7
- } from '@justeattakeaway/pie-webc-testing/src/helpers/components/web-component-test-wrapper/WebComponentTestWrapper.ts';
8
- import { createScrollablePageHTML, renderTestPieModal } from '../helpers/index.ts';
9
-
10
- import { PieModal } from '@/index';
11
- import {
12
- ON_MODAL_BACK_EVENT,
13
- ON_MODAL_CLOSE_EVENT,
14
- headingLevels,
15
- } from '@/defs';
16
-
17
- const componentSelector = '[data-test-id="pie-modal"]';
18
- const backButtonSelector = '[data-test-id="modal-back-button"]';
19
- const closeButtonSelector = '[data-test-id="modal-close-button"]';
20
-
21
- // Mount then unmount any components that are used inside of pie-modal so that
22
- // they have been registered with the browser before the tests run.
23
- // There is likely a nicer way to do this but this will temporarily
24
- // unblock tests.
25
- test.beforeEach(async ({ mount }) => {
26
- await (await mount(PieButton)).unmount();
27
- await (await mount(PieIconButton)).unmount();
28
- });
29
-
30
- test.describe('modal', () => {
31
- test('should be visible when opened', async ({ mount, page }) => {
32
- // Arrange
33
- await mount(PieModal, {
34
- props: {
35
- heading: 'Modal heading',
36
- isOpen: true,
37
- },
38
- });
39
-
40
- // Act
41
- const modal = page.locator(componentSelector);
42
-
43
- // Assert
44
- expect(modal).toBeVisible();
45
- });
46
- });
47
-
48
- headingLevels.forEach((headingLevel) => test(`should render the correct heading tag based on the value of headingLevel: ${headingLevel}`, async ({ mount }) => {
49
- // Arrange
50
- const props = {
51
- heading: 'Modal Header',
52
- headingLevel,
53
- };
54
-
55
- // Act
56
- const component = await mount(PieModal, { props });
57
-
58
- // Assert
59
- await expect(component.locator(`${props.headingLevel}.c-modal-heading`)).toContainText(props.heading);
60
- }));
61
-
62
- ['span', 'section'].forEach((headingLevel) => test(`should render the fallback heading level 'h2' if invalid headingLevel: ${headingLevel} is passed`, async ({ mount }) => {
63
- // Arrange
64
- const props = {
65
- heading: 'Modal Header',
66
- headingLevel,
67
- };
68
-
69
- // Act
70
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
71
- // @ts-ignore // Added this as we want to deliberately test with invalid headingLevel (which is an invalid type based on ModalProps)
72
- const component = await mount(PieModal, { props });
73
-
74
- // Assert
75
- await expect(component.locator('h2.c-modal-heading')).toContainText(props.heading);
76
- }));
77
-
78
- test.describe('When modal is closed', () => {
79
- test.describe('by clicking the close button', () => {
80
- test('should dispatch event `pie-modal-close`', async ({ mount, page }) => {
81
- // Arrange
82
- const events : Array<Event> = [];
83
-
84
- await mount(
85
- PieModal,
86
- {
87
- props: {
88
- isOpen: true,
89
- isDismissible: true,
90
- },
91
- on: {
92
- [ON_MODAL_CLOSE_EVENT]: (event: Event) => events.push(event),
93
- },
94
- },
95
- );
96
-
97
- await page.click(closeButtonSelector);
98
-
99
- // Assert
100
- expect(events).toHaveLength(1);
101
- });
102
-
103
- test('should close the modal', async ({ mount, page }) => {
104
- // Arrange
105
- await mount(PieModal, {
106
- props: {
107
- isOpen: true,
108
- isDismissible: true,
109
- },
110
- });
111
-
112
- const modal = page.locator(componentSelector);
113
-
114
- // Act
115
- await page.click(closeButtonSelector);
116
-
117
- // Assert
118
- expect(modal).not.toBeVisible();
119
- });
120
- });
121
-
122
- test.describe('by clicking the back button', () => {
123
- test('should dispatch event `pie-modal-back`', async ({ mount, page }) => {
124
- // Arrange
125
- const events: Event[] = [];
126
- await mount(
127
- PieModal,
128
- {
129
- props: {
130
- isOpen: true,
131
- hasBackButton: true,
132
- },
133
- on: {
134
- [ON_MODAL_BACK_EVENT]: (event: Event) => events.push(event),
135
- },
136
- },
137
- );
138
-
139
- await page.locator(backButtonSelector).click();
140
-
141
- // Assert
142
- expect(events).toHaveLength(1);
143
- });
144
- });
145
-
146
- test.describe('by clicking the backdrop', () => {
147
- test('should dispatch event `pie-modal-close`', async ({ mount, page }) => {
148
- // Arrange
149
- const events : Array<Event> = [];
150
-
151
- await mount(PieModal, {
152
- props: {
153
- isOpen: true,
154
- isDismissible: true,
155
- },
156
- on: {
157
- [ON_MODAL_CLOSE_EVENT]: (event: Event) => events.push(event),
158
- },
159
- });
160
-
161
- // Act
162
- await page.click(componentSelector, { position: { x: -10, y: -10 } }); // Click outside dialog
163
-
164
- // Assert
165
- expect(events).toHaveLength(1); // TODO - Event object is null for this test
166
- });
167
-
168
- test('should close the modal', async ({ mount, page }) => {
169
- // Arrange
170
- await mount(PieModal, {
171
- props: {
172
- isOpen: true,
173
- isDismissible: true,
174
- },
175
- });
176
-
177
- const modal = await page.locator(componentSelector);
178
-
179
- // Act
180
- await modal.click({ position: { x: -10, y: -10 } }); // Click outside dialog
181
-
182
- // Assert
183
- expect(modal).not.toBeVisible();
184
- });
185
- });
186
-
187
- test.describe('`returnFocusAfterCloseSelector` prop', () => {
188
- test.describe('when given', () => {
189
- test('should return focus to specified element', async ({ mount, page }) => {
190
- // Arrange
191
- const component = renderTestPieModal({
192
- returnFocusAfterCloseSelector: '#focus-me',
193
- });
194
-
195
- await mount(WebComponentTestWrapper, {
196
- props: {
197
- pageMode: true,
198
- },
199
- slots: {
200
- component,
201
- pageMarkup: `<div>
202
- <button id="default"></button>
203
- <button id="focus-me"></button>
204
- <button id="not-me"></button>
205
- </div>`,
206
- },
207
- });
208
-
209
- // Act
210
- await page.click(closeButtonSelector);
211
-
212
- const focusedElement = await page.locator(':focus');
213
- const focusedElementId = await focusedElement.getAttribute('id');
214
-
215
- // Assert
216
- expect(focusedElementId).toBe('focus-me');
217
- });
218
-
219
- test('should return focus to first matching element', async ({ page, mount }) => {
220
- // Arrange
221
- const component = renderTestPieModal({
222
- returnFocusAfterCloseSelector: '[data-test-id="focus-me"]',
223
- });
224
-
225
- await mount(WebComponentTestWrapper, {
226
- props: {
227
- pageMode: true,
228
- },
229
- slots: {
230
- component,
231
- pageMarkup: `<div>
232
- <button id="default"></button>
233
- <button data-test-id="focus-me" id="actual-focus"></button>
234
- <button data-test-id="focus-me"></button>
235
- </div>`,
236
- },
237
- });
238
-
239
- // Act
240
- await page.click(closeButtonSelector);
241
-
242
- const focusedElement = await page.locator(':focus');
243
- const focusedElementId = await focusedElement.getAttribute('id');
244
-
245
- // Assert
246
- expect(focusedElementId).toBe('actual-focus');
247
- });
248
- });
249
-
250
- test.describe('when not given', () => {
251
- [{
252
- mechanism: 'close button',
253
- modalCloseFunction: async (page : Page) => {
254
- await page.click(closeButtonSelector);
255
- },
256
- }, {
257
- mechanism: 'Esc key',
258
- modalCloseFunction: async (page : Page) => {
259
- await page.keyboard.press('Escape');
260
- },
261
- }].forEach(({ mechanism, modalCloseFunction }) => {
262
- test.describe(`and closed by the ${mechanism}`, () => {
263
- test('should return focus to the element that opens the modal', async ({ page, mount }) => {
264
- // Arrange
265
- const component = renderTestPieModal({ isOpen: false });
266
-
267
- await mount(WebComponentTestWrapper, {
268
- props: {
269
- pageMode: true,
270
- },
271
- slots: {
272
- component,
273
- pageMarkup: `<div>
274
- <button id="not-me"></button>
275
- <button data-test-id="open-modal" id="default"></button>
276
- </div>`,
277
- },
278
- });
279
-
280
- await page.evaluate(() => {
281
- // Set up a button which opens the modal when clicked
282
- document.querySelector('[data-test-id="open-modal"]')?.addEventListener('click', () => {
283
- document.querySelector('pie-modal')?.setAttribute('isOpen', 'true');
284
- });
285
- });
286
-
287
- // Act
288
- await page.click('[data-test-id="open-modal"]');
289
- await modalCloseFunction(page);
290
-
291
- const focusedElement = await page.locator(':focus');
292
- const focusedElementId = await focusedElement.getAttribute('id');
293
-
294
- // Assert
295
- expect(focusedElementId).toBe('default');
296
- });
297
- });
298
- });
299
- });
300
- });
301
- });
302
-
303
- test.describe('`isDismissible` prop', () => {
304
- test.describe('when `true`', () => {
305
- test('should make the modal contain a close button', async ({ mount }) => {
306
- // Arrange
307
- const component = await mount(
308
- PieModal,
309
- {
310
- props: {
311
- isOpen: true,
312
- isDismissible: true,
313
- },
314
- },
315
- );
316
-
317
- // Act
318
- const closeButton = component.locator(closeButtonSelector);
319
-
320
- // Assert
321
- await expect(closeButton).toBeVisible();
322
- });
323
-
324
- test('should close the modal when the close button is clicked', async ({ mount, page }) => {
325
- // Arrange
326
- const component = await mount(
327
- PieModal,
328
- {
329
- props: {
330
- isOpen: true,
331
- isDismissible: true,
332
- },
333
- },
334
- );
335
-
336
- // Act
337
- await page.click('[data-test-id="modal-close-button"]');
338
-
339
- // Assert
340
- await expect(component).not.toBeVisible();
341
- });
342
-
343
- test('should close the modal when the backdrop is clicked', async ({ mount, page }) => {
344
- // Arrange
345
- await mount(
346
- PieModal,
347
- {
348
- props: {
349
- isOpen: true,
350
- isDismissible: true,
351
- },
352
- },
353
- );
354
-
355
- // Act
356
- await page.click('body');
357
-
358
- const element = await page.locator(componentSelector);
359
-
360
- const styles = await element.evaluate((modal) => {
361
- const computedStyles = window.getComputedStyle(modal);
362
- return {
363
- display: computedStyles.getPropertyValue('display'),
364
- };
365
- });
366
-
367
- // Assert
368
- expect(styles.display).toBe('none');
369
- });
370
-
371
- test('should close the modal when the Escape key is pressed', async ({ mount, page }) => {
372
- // Arrange
373
- await mount(PieModal, {
374
- props: {
375
- isOpen: true,
376
- isDismissible: true,
377
- },
378
- });
379
-
380
- const modal = await page.locator(componentSelector);
381
-
382
- // Act
383
- await page.keyboard.press('Escape');
384
-
385
- // Assert
386
- await expect(modal).not.toBeVisible();
387
- });
388
- });
389
-
390
- test.describe('when `isDismissible` is `false`', () => {
391
- test('should make the modal NOT contain a close button', async ({ mount }) => {
392
- // Arrange
393
- const component = await mount(PieModal, {
394
- props: {
395
- isOpen: true,
396
- isDismissible: false,
397
- },
398
- });
399
-
400
- // Act
401
- const closeButton = await component.locator(closeButtonSelector);
402
-
403
- // Assert
404
- await expect(closeButton).not.toBeVisible();
405
- });
406
-
407
- test('should NOT close the modal when the backdrop is clicked', async ({ mount, page }) => {
408
- // Arrange
409
- await mount(
410
- PieModal,
411
- {
412
- props: {
413
- isOpen: true,
414
- isDismissible: false,
415
- },
416
- },
417
- );
418
-
419
- // Act
420
- await page.locator('body').click();
421
-
422
- const element = await page.locator(componentSelector);
423
-
424
- const styles = await element.evaluate((modal) => {
425
- const computedStyles = window.getComputedStyle(modal);
426
- return {
427
- display: computedStyles.getPropertyValue('display'),
428
- };
429
- });
430
-
431
- // Assert
432
- expect(styles.display).toBe('flex');
433
- });
434
-
435
- test('should NOT close the modal when the Escape key is pressed', async ({ mount, page }) => {
436
- // Arrange
437
- await mount(PieModal, {
438
- props: {
439
- isOpen: true,
440
- isDismissible: false,
441
- },
442
- });
443
-
444
- // Act
445
- await page.keyboard.press('Escape');
446
- const modal = await page.locator(componentSelector);
447
-
448
- // Assert
449
- await expect(modal).toBeVisible();
450
- });
451
- });
452
- });
453
-
454
- test.describe('isOpen prop', () => {
455
- test('should not render open when isOpen = false', async ({ mount, page }) => {
456
- // Arrange
457
- await mount(PieModal, {
458
- props: {
459
- isOpen: false,
460
- },
461
- });
462
-
463
- // Assert
464
- await expect(page.locator(componentSelector)).not.toBeVisible();
465
- });
466
-
467
- test('should render open when isOpen = true', async ({ mount, page }) => {
468
- // Arrange
469
- await mount(PieModal, {
470
- props: {
471
- isOpen: true,
472
- },
473
- });
474
-
475
- // Assert
476
- await expect(page.locator(componentSelector)).toBeVisible();
477
- });
478
- });
479
-
480
- test.describe('scrolling logic', () => {
481
- test('Should not be able to scroll when isOpen = true', async ({ page, mount }) => {
482
- // Arrange
483
- const modalComponent = renderTestPieModal();
484
-
485
- await mount(
486
- WebComponentTestWrapper,
487
- {
488
- props: {
489
- pageMode: true,
490
- },
491
- slots: {
492
- component: modalComponent,
493
- pageMarkup: createScrollablePageHTML(),
494
- },
495
- },
496
- );
497
-
498
- // Act
499
- // Scroll 800 pixels down the page
500
- await page.mouse.wheel(0, 5000);
501
-
502
- // The mouse.wheel function causes scrolling, but doesn't wait for the scroll to finish before returning.
503
- await page.waitForTimeout(3000);
504
-
505
- // Assert
506
- await expect.soft(page.getByText('Top of page copy')).toBeInViewport();
507
- await expect(page.getByText('Bottom of page copy')).not.toBeInViewport();
508
- });
509
-
510
- test('Should scroll to the bottom when Pie Modal is closed', async ({ page, mount }) => {
511
- // Arrange
512
- const modalComponent = renderTestPieModal();
513
-
514
- await mount(
515
- WebComponentTestWrapper,
516
- {
517
- props: {
518
- pageMode: true,
519
- },
520
- slots: {
521
- component: modalComponent,
522
- pageMarkup: createScrollablePageHTML(),
523
- },
524
- },
525
- );
526
-
527
- // Act
528
- await page.locator('[data-test-id="modal-close-button"]').click();
529
-
530
- // Scroll 800 pixels down the page
531
- await page.mouse.wheel(0, 5000);
532
-
533
- // The mouse.wheel function causes scrolling, but doesn't wait for the scroll to finish before returning.
534
- await page.waitForTimeout(3000);
535
-
536
- // Assert
537
- await expect.soft(page.getByText('Top of page copy')).not.toBeInViewport();
538
- await expect(page.getByText('Bottom of page copy')).toBeInViewport();
539
- });
540
- });
541
-
542
- test.describe('`hasBackButton` prop', () => {
543
- test.describe('when `true`', () => {
544
- test('should make the modal contain a back button', async ({ mount }) => {
545
- // Arrange
546
- const component = await mount(
547
- PieModal,
548
- {
549
- props: {
550
- isOpen: true,
551
- hasBackButton: true,
552
- },
553
- },
554
- );
555
-
556
- // Act & Assert
557
- await expect(component.locator(backButtonSelector)).toBeVisible();
558
- });
559
-
560
- test('should close the modal when the back button is clicked', async ({ mount }) => {
561
- // Arrange
562
- const component = await mount(
563
- PieModal,
564
- {
565
- props: {
566
- isOpen: true,
567
- hasBackButton: true,
568
- },
569
- },
570
- );
571
-
572
- // Act
573
- await component.locator(backButtonSelector).click();
574
-
575
- // Assert
576
- await expect(component).not.toBeVisible();
577
- });
578
- });
579
-
580
- test.describe('when `hasBackButton` is `false`', () => {
581
- test('should make the modal NOT contain a back button', async ({ mount }) => {
582
- // Arrange
583
- const component = await mount(
584
- PieModal,
585
- {
586
- props: {
587
- isOpen: true,
588
- hasBackButton: false,
589
- },
590
- },
591
- );
592
-
593
- // Act & Assert
594
- await expect(component.locator(backButtonSelector)).not.toBeVisible();
595
- });
596
- });
597
- });
598
-
599
- test.describe('actions', () => {
600
- ['leading', 'supporting'].forEach((actionName) => {
601
- test.describe(`${actionName} action, when clicked`, () => {
602
- const buttonSelector = `[data-test-id="modal-${actionName}-action"]`;
603
-
604
- test('should close the modal', async ({ page, mount }) => {
605
- // Arrange
606
- await mount(PieModal, {
607
- props: {
608
- heading: 'Modal Header',
609
- isOpen: true,
610
- leadingAction: {
611
- text: 'Confirm',
612
- variant: 'primary',
613
- ariaLabel: 'Descriptive message',
614
- },
615
- supportingAction: {
616
- text: 'Cancel',
617
- variant: 'ghost',
618
- ariaLabel: 'Descriptive message',
619
- },
620
- },
621
- });
622
-
623
- const modal = await page.locator(componentSelector);
624
-
625
- // Act
626
- await page.click(buttonSelector);
627
-
628
- // Assert
629
- expect(modal).not.toBeVisible();
630
- });
631
-
632
- test('should submit the correct return value', async ({ page, mount }) => {
633
- // Arrange
634
- await mount(PieModal, {
635
- props: {
636
- heading: 'Modal Header',
637
- isOpen: true,
638
- leadingAction: {
639
- text: 'Confirm',
640
- variant: 'primary',
641
- ariaLabel: 'Descriptive message',
642
- },
643
- supportingAction: {
644
- text: 'Cancel',
645
- variant: 'ghost',
646
- ariaLabel: 'Descriptive message',
647
- },
648
- },
649
- });
650
-
651
- // Act
652
- await page.click(buttonSelector);
653
- const returnValue = await page.$eval(
654
- componentSelector,
655
- (dialog : HTMLDialogElement) => dialog.returnValue,
656
- );
657
-
658
- // Assert
659
- expect(returnValue).toBe(actionName);
660
- });
661
- });
662
- });
663
- });
664
-
665
- test.describe('Props: `aria`', () => {
666
- test.describe('when aria exist', () => {
667
- test('should render component elements with the correct aria-labels', async ({ mount }) => {
668
- // Arrange
669
- const component = await mount(PieModal, {
670
- props: {
671
- isOpen: true,
672
- isDismissible: true,
673
- isLoading: true,
674
- hasBackButton: true,
675
- aria: {
676
- close: 'Close label info',
677
- back: 'Back label info',
678
- loading: 'Loading label info',
679
- },
680
- },
681
- });
682
-
683
- // Act
684
- // Close button
685
- const closeButton = await component.locator(closeButtonSelector);
686
- const ariaCloseLabel = await closeButton.getAttribute('aria-label');
687
-
688
- // Back button
689
- const backButton = await component.locator(backButtonSelector);
690
- const ariaBackLabel = await backButton.getAttribute('aria-label');
691
-
692
- // Assert
693
- await expect(ariaCloseLabel).toBe('Close label info');
694
- await expect(ariaBackLabel).toBe('Back label info');
695
- });
696
-
697
- test.describe('when modal `isloading` is true', () => {
698
- test('should render component with the correct aria values: `aria-label` & `aria-busy`', async ({ mount }) => {
699
- // Arrange
700
- const component = await mount(PieModal, {
701
- props: {
702
- isOpen: true,
703
- isLoading: true,
704
- aria: {
705
- loading: 'Loading label info',
706
- },
707
- },
708
- });
709
-
710
- // Loading state
711
- const pieModalComponent = await component.locator(componentSelector);
712
- const ariaLoadingLabel = await pieModalComponent.getAttribute('aria-label');
713
- const ariaLoadingBusy = await pieModalComponent.getAttribute('aria-busy');
714
-
715
- // Assert
716
- await expect(ariaLoadingLabel).toBe('Loading label info');
717
- await expect(ariaLoadingBusy).toBe('true');
718
- });
719
- });
720
-
721
- test.describe('when modal `isLoading` is dynamically changing from `isLoading: true` to `isLoading: false`', () => {
722
- test('should dynamically add, remove, and update `arial-label` & `aria-busy` labels', async ({ mount }) => {
723
- // Arrange
724
- const component = await mount(PieModal, {
725
- props: {
726
- isOpen: true,
727
- isLoading: true,
728
- aria: {
729
- loading: 'Loading label info',
730
- },
731
- },
732
- });
733
-
734
- const pieModalComponent = await component.locator(componentSelector);
735
- let ariaLoadingLabel = await pieModalComponent.getAttribute('aria-label');
736
- let ariaLoadingBusy = await pieModalComponent.getAttribute('aria-busy');
737
-
738
- // Assert: When `isLoading: true`
739
- await expect(ariaLoadingLabel).toBe('Loading label info');
740
- await expect(ariaLoadingBusy).toBe('true');
741
-
742
- await component.update({ props: { isLoading: false } });
743
-
744
- ariaLoadingLabel = await pieModalComponent.getAttribute('aria-label');
745
- ariaLoadingBusy = await pieModalComponent.getAttribute('aria-busy');
746
-
747
- // Assert: When `isLoading: false`
748
- await expect(ariaLoadingLabel).toBeNull();
749
- await expect(ariaLoadingBusy).toBe('false');
750
- });
751
- });
752
- });
753
-
754
- test.describe('when aria does not exist', () => {
755
- test('should not render the aria-labels', async ({ mount }) => {
756
- // Arrange
757
- const component = await mount(PieModal, {
758
- props: {
759
- isOpen: true,
760
- isDismissible: true,
761
- hasBackButton: true,
762
- },
763
- });
764
-
765
- // Act
766
- // Close button
767
- const closeButton = await component.locator(closeButtonSelector);
768
- const ariaCloseLabel = await closeButton.getAttribute('aria-label');
769
-
770
- // Back button
771
- const backButton = await component.locator(backButtonSelector);
772
- const ariaBackLabel = await backButton.getAttribute('aria-label');
773
-
774
- // Assert
775
- await expect(ariaCloseLabel).toBe(null);
776
- await expect(ariaBackLabel).toBe(null);
777
- });
778
- });
779
-
780
- test.describe('when modal `isloading` is false', () => {
781
- test('should not render aria-label', async ({ mount }) => {
782
- // Arrange
783
- const component = await mount(PieModal, {
784
- props: {
785
- isOpen: true,
786
- isLoading: false,
787
- },
788
- });
789
-
790
- // Loading state
791
- const pieModalComponent = await component.locator(componentSelector);
792
- const ariaLoadingLabel = await pieModalComponent.getAttribute('aria-label');
793
-
794
- // Assert
795
- await expect(ariaLoadingLabel).toBe(null);
796
- });
797
-
798
- test('should set `aria-busy` to `false`', async ({ mount }) => {
799
- // Arrange
800
- const component = await mount(PieModal, {
801
- props: {
802
- isOpen: true,
803
- isLoading: false,
804
- },
805
- });
806
-
807
- // Loading state
808
- const pieModalComponent = await component.locator(componentSelector);
809
- const ariaLoadingBusy = await pieModalComponent.getAttribute('aria-busy');
810
-
811
- // Assert
812
- await expect(ariaLoadingBusy).toBe('false');
813
- });
814
- });
815
- });
816
-