@justeattakeaway/pie-modal 0.11.0 → 0.13.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,5 +1,6 @@
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,
@@ -7,24 +8,40 @@ import {
7
8
  import { 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 modalSelector = '[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(modalSelector);
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);
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(modalSelector);
113
+
114
+ // Act
115
+ await page.click(closeButtonSelector);
80
116
 
81
117
  // Assert
82
- expect(messages).toHaveLength(1);
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
+
102
161
  // Act
103
- await page.locator('#dialog').click();
162
+ await page.click(modalSelector, { position: { x: -10, y: -10 } }); // Click outside dialog
104
163
 
105
164
  // Assert
106
- expect(messages).toHaveLength(1);
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(modalSelector);
178
+
179
+ // Act
180
+ await modal.click({ position: { x: -10, y: -10 } }); // Click outside dialog
181
+
182
+ // Assert
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(modalSelector);
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(modalSelector);
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(modalSelector);
344
423
 
345
424
  const styles = await element.evaluate((modal) => {
346
425
  const computedStyles = window.getComputedStyle(modal);
@@ -350,26 +429,127 @@ 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(modalSelector);
447
+
448
+ // Assert
449
+ await expect(modal).toBeVisible();
450
+ });
451
+ });
452
+ });
453
+
454
+ test.describe('`hasBackButton` prop', () => {
455
+ test.describe('when `true`', () => {
456
+ test('should make the modal contain a back button', async ({ mount }) => {
357
457
  // Arrange
358
458
  const component = await mount(
359
459
  PieModal,
360
460
  {
361
461
  props: {
362
462
  isOpen: true,
363
- isDismissible: false,
463
+ hasBackButton: true,
464
+ },
465
+ },
466
+ );
467
+
468
+ // Act & Assert
469
+ await expect(component.locator(backButtonSelector)).toBeVisible();
470
+ });
471
+
472
+ test('should close the modal when the back button is clicked', async ({ mount }) => {
473
+ // Arrange
474
+ const component = await mount(
475
+ PieModal,
476
+ {
477
+ props: {
478
+ isOpen: true,
479
+ hasBackButton: true,
364
480
  },
365
481
  },
366
482
  );
367
483
 
368
484
  // Act
369
- await page.keyboard.press('Escape');
485
+ await component.locator(backButtonSelector).click();
370
486
 
371
487
  // Assert
372
- await expect(component.locator('dialog')).toBeVisible();
488
+ await expect(component).not.toBeVisible();
489
+ });
490
+ });
491
+
492
+ test.describe('when `hasBackButton` is `false`', () => {
493
+ test('should make the modal NOT contain a back button', async ({ mount }) => {
494
+ // Arrange
495
+ const component = await mount(
496
+ PieModal,
497
+ {
498
+ props: {
499
+ isOpen: true,
500
+ hasBackButton: false,
501
+ },
502
+ },
503
+ );
504
+
505
+ // Act & Assert
506
+ await expect(component.locator(backButtonSelector)).not.toBeVisible();
507
+ });
508
+ });
509
+ });
510
+
511
+ test.describe('actions', () => {
512
+ ['leading', 'supporting'].forEach((actionName) => {
513
+ test.describe(`${actionName} action, when clicked`, () => {
514
+ const buttonSelector = `[data-test-id="modal-${actionName}-action"]`;
515
+
516
+ test('should close the modal', async ({ page, mount }) => {
517
+ // Arrange
518
+ await mount(PieModal, {
519
+ props: {
520
+ heading: 'Modal Header',
521
+ isOpen: true,
522
+ },
523
+ });
524
+
525
+ const modal = await page.locator(modalSelector);
526
+
527
+ // Act
528
+ await page.click(buttonSelector);
529
+
530
+ // Assert
531
+ expect(modal).not.toBeVisible();
532
+ });
533
+
534
+ test('should submit the correct return value', async ({ page, mount }) => {
535
+ // Arrange
536
+ await mount(PieModal, {
537
+ props: {
538
+ heading: 'Modal Header',
539
+ isOpen: true,
540
+ },
541
+ });
542
+
543
+ // Act
544
+ await page.click(buttonSelector);
545
+ const returnValue = await page.$eval(
546
+ modalSelector,
547
+ (dialog : HTMLDialogElement) => dialog.returnValue,
548
+ );
549
+
550
+ // Assert
551
+ expect(returnValue).toBe(actionName);
552
+ });
373
553
  });
374
554
  });
375
555
  });
@@ -151,3 +151,53 @@ test.describe('`isDismissible`', () => {
151
151
  });
152
152
  });
153
153
  });
154
+
155
+ const directions = ['ltr', 'rtl', 'auto'] as const;
156
+
157
+ test.describe('`hasBackButton`', () => {
158
+ directions.forEach((dir) => {
159
+ test.describe('when true', () => {
160
+ test(`should display a back button within the modal and dir is ${dir}`, async ({ mount, page }) => {
161
+ await mount(PieModal, {
162
+ props: {
163
+ heading: 'This is a modal heading',
164
+ hasBackButton: true,
165
+ isOpen: true,
166
+ dir,
167
+ },
168
+ });
169
+
170
+ await percySnapshot(page, `Modal with back button displayed - hasBackButton: ${true} - dir: ${dir}`);
171
+ });
172
+ });
173
+
174
+ test.describe('when false', () => {
175
+ test(`should NOT display a back button and dir is ${dir}`, async ({ mount, page }) => {
176
+ await mount(PieModal, {
177
+ props: {
178
+ heading: 'This is a modal heading',
179
+ hasBackButton: false,
180
+ isOpen: true,
181
+ dir,
182
+ },
183
+ });
184
+
185
+ await percySnapshot(page, `Modal without back button - hasBackButton: ${false} - dir: ${dir}`);
186
+ });
187
+ });
188
+ });
189
+ });
190
+
191
+ test('Should display loading spinner when `isLoading` is true', async ({ mount, page }) => {
192
+ await mount(PieModal, {
193
+ props: {
194
+ heading: 'This is a modal heading',
195
+ hasBackButton: true,
196
+ isDismissible: true,
197
+ isOpen: true,
198
+ isLoading: true,
199
+ },
200
+ });
201
+
202
+ await percySnapshot(page, `Modal displays loading spinner - isLoading: ${true}`);
203
+ });