@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.
- package/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +27 -0
- package/README.md +2 -0
- package/dist/index.js +221 -164
- package/dist/react.js +131 -124
- package/dist/types/packages/components/pie-modal/src/defs.d.ts +16 -1
- package/dist/types/packages/components/pie-modal/src/defs.d.ts.map +1 -1
- package/dist/types/packages/components/pie-modal/src/index.d.ts +25 -11
- package/dist/types/packages/components/pie-modal/src/index.d.ts.map +1 -1
- package/dist/types/packages/components/pie-modal/src/react.d.ts +6 -1
- package/dist/types/packages/components/pie-modal/src/react.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/defs.ts +20 -1
- package/src/index.ts +110 -41
- package/src/modal.scss +183 -19
- package/test/component/pie-modal.spec.ts +236 -56
- package/test/visual/pie-modal.spec.ts +50 -0
|
@@ -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 {
|
|
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 ({
|
|
19
|
-
await mount(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
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
|
-
|
|
92
|
+
[ON_MODAL_CLOSE_EVENT]: (event: Event) => events.push(event),
|
|
75
93
|
},
|
|
76
94
|
},
|
|
77
95
|
);
|
|
78
96
|
|
|
79
|
-
await page.
|
|
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(
|
|
118
|
+
expect(modal).not.toBeVisible();
|
|
83
119
|
});
|
|
84
120
|
});
|
|
85
121
|
|
|
86
|
-
test.describe('by clicking the
|
|
87
|
-
test('should dispatch event `pie-modal-
|
|
122
|
+
test.describe('by clicking the back button', () => {
|
|
123
|
+
test('should dispatch event `pie-modal-back`', async ({ mount, page }) => {
|
|
88
124
|
// Arrange
|
|
89
|
-
const
|
|
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
|
-
|
|
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.
|
|
162
|
+
await page.click(modalSelector, { position: { x: -10, y: -10 } }); // Click outside dialog
|
|
104
163
|
|
|
105
164
|
// Assert
|
|
106
|
-
expect(
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
241
|
-
|
|
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
|
|
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.
|
|
356
|
+
await page.click('body');
|
|
277
357
|
|
|
278
|
-
const element = await page.locator(
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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(
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
|
325
|
-
await
|
|
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(
|
|
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('
|
|
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
|
-
|
|
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
|
|
485
|
+
await component.locator(backButtonSelector).click();
|
|
370
486
|
|
|
371
487
|
// Assert
|
|
372
|
-
await expect(component
|
|
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
|
+
});
|