@justeattakeaway/pie-modal 0.12.0 → 0.14.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,30 +1,47 @@
1
1
  import { test, expect } from '@sand4rt/experimental-ct-web';
2
2
  import { type Page } from '@playwright/test';
3
+ import { PieButton } from '@justeattakeaway/pie-button';
3
4
  import { PieIconButton } from '@justeattakeaway/pie-icon-button';
4
5
  import {
5
6
  WebComponentTestWrapper,
6
7
  } from '@justeattakeaway/pie-webc-testing/src/helpers/components/web-component-test-wrapper/WebComponentTestWrapper.ts';
7
- import { renderTestPieModal } from '../helpers/index.ts';
8
+ import { createScrollablePageHTML, renderTestPieModal } from '../helpers/index.ts';
8
9
 
9
10
  import { PieModal } from '@/index';
10
- import { headingLevels } from '@/defs';
11
+ import {
12
+ ON_MODAL_BACK_EVENT,
13
+ ON_MODAL_CLOSE_EVENT,
14
+ headingLevels,
15
+ } from '@/defs';
11
16
 
17
+ const componentSelector = '[data-test-id="pie-modal"]';
18
+ const backButtonSelector = '[data-test-id="modal-back-button"]';
12
19
  const closeButtonSelector = '[data-test-id="modal-close-button"]';
13
20
 
14
- // Mount any components that are used inside of pie-modal so that
21
+ // Mount then unmount any components that are used inside of pie-modal so that
15
22
  // they have been registered with the browser before the tests run.
16
23
  // There is likely a nicer way to do this but this will temporarily
17
24
  // unblock tests.
18
- test.beforeEach(async ({ page, mount }) => {
19
- await mount(
20
- PieIconButton,
21
- {},
22
- );
23
-
24
- // Removing the element so it's not present in the tests (but is still registered in the DOM)
25
- await page.evaluate(() => {
26
- const element : Element | null = document.querySelector('pie-icon-button');
27
- element?.remove();
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();
28
45
  });
29
46
  });
30
47
 
@@ -62,7 +79,8 @@ test.describe('When modal is closed', () => {
62
79
  test.describe('by clicking the close button', () => {
63
80
  test('should dispatch event `pie-modal-close`', async ({ mount, page }) => {
64
81
  // Arrange
65
- const messages: string[] = [];
82
+ const events : Array<Event> = [];
83
+
66
84
  await mount(
67
85
  PieModal,
68
86
  {
@@ -71,39 +89,98 @@ test.describe('When modal is closed', () => {
71
89
  isDismissible: true,
72
90
  },
73
91
  on: {
74
- click: (event: string) => messages.push(event),
92
+ [ON_MODAL_CLOSE_EVENT]: (event: Event) => events.push(event),
75
93
  },
76
94
  },
77
95
  );
78
96
 
79
- await page.locator(closeButtonSelector).click();
97
+ await page.click(closeButtonSelector);
80
98
 
81
99
  // Assert
82
- expect(messages).toHaveLength(1);
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();
83
119
  });
84
120
  });
85
121
 
86
- test.describe('by clicking the backdrop', () => {
87
- test('should dispatch event `pie-modal-close`', async ({ mount, page }) => {
122
+ test.describe('by clicking the back button', () => {
123
+ test('should dispatch event `pie-modal-back`', async ({ mount, page }) => {
88
124
  // Arrange
89
- const messages: string[] = [];
125
+ const events: Event[] = [];
90
126
  await mount(
91
127
  PieModal,
92
128
  {
93
129
  props: {
94
130
  isOpen: true,
131
+ hasBackButton: true,
95
132
  },
96
133
  on: {
97
- click: (event: string) => messages.push(event),
134
+ [ON_MODAL_BACK_EVENT]: (event: Event) => events.push(event),
98
135
  },
99
136
  },
100
137
  );
101
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
+
102
179
  // Act
103
- await page.locator('#dialog').click();
180
+ await modal.click({ position: { x: -10, y: -10 } }); // Click outside dialog
104
181
 
105
182
  // Assert
106
- expect(messages).toHaveLength(1);
183
+ expect(modal).not.toBeVisible();
107
184
  });
108
185
  });
109
186
 
@@ -130,7 +207,7 @@ test.describe('When modal is closed', () => {
130
207
  });
131
208
 
132
209
  // Act
133
- await page.locator(closeButtonSelector).click();
210
+ await page.click(closeButtonSelector);
134
211
 
135
212
  const focusedElement = await page.locator(':focus');
136
213
  const focusedElementId = await focusedElement.getAttribute('id');
@@ -160,7 +237,7 @@ test.describe('When modal is closed', () => {
160
237
  });
161
238
 
162
239
  // Act
163
- await page.locator(closeButtonSelector).click();
240
+ await page.click(closeButtonSelector);
164
241
 
165
242
  const focusedElement = await page.locator(':focus');
166
243
  const focusedElementId = await focusedElement.getAttribute('id');
@@ -174,7 +251,7 @@ test.describe('When modal is closed', () => {
174
251
  [{
175
252
  mechanism: 'close button',
176
253
  modalCloseFunction: async (page : Page) => {
177
- await page.locator(closeButtonSelector).click();
254
+ await page.click(closeButtonSelector);
178
255
  },
179
256
  }, {
180
257
  mechanism: 'Esc key',
@@ -208,7 +285,7 @@ test.describe('When modal is closed', () => {
208
285
  });
209
286
 
210
287
  // Act
211
- await page.locator('[data-test-id="open-modal"]').click();
288
+ await page.click('[data-test-id="open-modal"]');
212
289
  await modalCloseFunction(page);
213
290
 
214
291
  const focusedElement = await page.locator(':focus');
@@ -237,11 +314,14 @@ test.describe('`isDismissible` prop', () => {
237
314
  },
238
315
  );
239
316
 
240
- // Act & Assert
241
- await expect(component.locator(closeButtonSelector)).toBeVisible();
317
+ // Act
318
+ const closeButton = component.locator(closeButtonSelector);
319
+
320
+ // Assert
321
+ await expect(closeButton).toBeVisible();
242
322
  });
243
323
 
244
- test('should close the modal when the close button is clicked', async ({ mount }) => {
324
+ test('should close the modal when the close button is clicked', async ({ mount, page }) => {
245
325
  // Arrange
246
326
  const component = await mount(
247
327
  PieModal,
@@ -254,7 +334,7 @@ test.describe('`isDismissible` prop', () => {
254
334
  );
255
335
 
256
336
  // Act
257
- await component.locator('[data-test-id="modal-close-button"]').click();
337
+ await page.click('[data-test-id="modal-close-button"]');
258
338
 
259
339
  // Assert
260
340
  await expect(component).not.toBeVisible();
@@ -273,9 +353,9 @@ test.describe('`isDismissible` prop', () => {
273
353
  );
274
354
 
275
355
  // Act
276
- await page.locator('body').click();
356
+ await page.click('body');
277
357
 
278
- const element = await page.locator('#dialog');
358
+ const element = await page.locator(componentSelector);
279
359
 
280
360
  const styles = await element.evaluate((modal) => {
281
361
  const computedStyles = window.getComputedStyle(modal);
@@ -290,39 +370,38 @@ test.describe('`isDismissible` prop', () => {
290
370
 
291
371
  test('should close the modal when the Escape key is pressed', async ({ mount, page }) => {
292
372
  // Arrange
293
- const component = await mount(
294
- PieModal,
295
- {
296
- props: {
297
- isOpen: true,
298
- isDismissible: false,
299
- },
373
+ await mount(PieModal, {
374
+ props: {
375
+ isOpen: true,
376
+ isDismissible: true,
300
377
  },
301
- );
378
+ });
379
+
380
+ const modal = await page.locator(componentSelector);
302
381
 
303
382
  // Act
304
383
  await page.keyboard.press('Escape');
305
384
 
306
385
  // Assert
307
- await expect(component).not.toBeVisible();
386
+ await expect(modal).not.toBeVisible();
308
387
  });
309
388
  });
310
389
 
311
390
  test.describe('when `isDismissible` is `false`', () => {
312
391
  test('should make the modal NOT contain a close button', async ({ mount }) => {
313
392
  // Arrange
314
- const component = await mount(
315
- PieModal,
316
- {
317
- props: {
318
- isOpen: true,
319
- isDismissible: false,
320
- },
393
+ const component = await mount(PieModal, {
394
+ props: {
395
+ isOpen: true,
396
+ isDismissible: false,
321
397
  },
322
- );
398
+ });
323
399
 
324
- // Act & Assert
325
- await expect(component.locator(closeButtonSelector)).not.toBeVisible();
400
+ // Act
401
+ const closeButton = await component.locator(closeButtonSelector);
402
+
403
+ // Assert
404
+ await expect(closeButton).not.toBeVisible();
326
405
  });
327
406
 
328
407
  test('should NOT close the modal when the backdrop is clicked', async ({ mount, page }) => {
@@ -340,7 +419,7 @@ test.describe('`isDismissible` prop', () => {
340
419
  // Act
341
420
  await page.locator('body').click();
342
421
 
343
- const element = await page.locator('#dialog');
422
+ const element = await page.locator(componentSelector);
344
423
 
345
424
  const styles = await element.evaluate((modal) => {
346
425
  const computedStyles = window.getComputedStyle(modal);
@@ -350,26 +429,225 @@ test.describe('`isDismissible` prop', () => {
350
429
  });
351
430
 
352
431
  // Assert
353
- expect(styles.display).toBe('block');
432
+ expect(styles.display).toBe('flex');
354
433
  });
355
434
 
356
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 }) => {
357
545
  // Arrange
358
546
  const component = await mount(
359
547
  PieModal,
360
548
  {
361
549
  props: {
362
550
  isOpen: true,
363
- isDismissible: false,
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,
364
568
  },
365
569
  },
366
570
  );
367
571
 
368
572
  // Act
369
- await page.keyboard.press('Escape');
573
+ await component.locator(backButtonSelector).click();
370
574
 
371
575
  // Assert
372
- await expect(component.locator('dialog')).toBeVisible();
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
+ },
616
+ });
617
+
618
+ const modal = await page.locator(componentSelector);
619
+
620
+ // Act
621
+ await page.click(buttonSelector);
622
+
623
+ // Assert
624
+ expect(modal).not.toBeVisible();
625
+ });
626
+
627
+ test('should submit the correct return value', async ({ page, mount }) => {
628
+ // Arrange
629
+ await mount(PieModal, {
630
+ props: {
631
+ heading: 'Modal Header',
632
+ isOpen: true,
633
+ leadingAction: {
634
+ text: 'Confirm',
635
+ variant: 'primary',
636
+ ariaLabel: 'Descriptive message',
637
+ },
638
+ },
639
+ });
640
+
641
+ // Act
642
+ await page.click(buttonSelector);
643
+ const returnValue = await page.$eval(
644
+ componentSelector,
645
+ (dialog : HTMLDialogElement) => dialog.returnValue,
646
+ );
647
+
648
+ // Assert
649
+ expect(returnValue).toBe(actionName);
650
+ });
373
651
  });
374
652
  });
375
653
  });
@@ -22,8 +22,10 @@ export const renderTestPieModal = ({
22
22
  // Creates some test page markup to test scroll locking
23
23
  export const createScrollablePageHTML = () => `<div>
24
24
  <h1>Test Page</h1>
25
+ <p>Top of page copy</p>
25
26
  <p> Test copy </p>
26
27
  <ol>
27
28
  ${'<li>List item</li>'.repeat(200)}
29
+ <li>Bottom of page copy</li>
28
30
  </ol>
29
31
  </div>`;