Skip to content

Commit fd614cc

Browse files
Merge pull request #678 from cwoolum/main
[WIP] Popover E2E tests 🚧
2 parents 3524202 + 4cfb772 commit fd614cc

File tree

3 files changed

+354
-2
lines changed

3 files changed

+354
-2
lines changed

apps/component-tests/src/routes/[kit]/[component]/[example]/index.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,13 @@ import { component$ } from '@builder.io/qwik';
22
import { ShowcaseTest } from '../../../../components/showcase-test/showcase-test';
33

44
export default component$(() => {
5-
return <ShowcaseTest />;
5+
// Need to center the content in the screen
6+
// so that tests like popover placement can
7+
// be placed on top and not get overridden by
8+
// lack of space
9+
return (
10+
<div class="flex h-screen items-center justify-center">
11+
<ShowcaseTest />
12+
</div>
13+
);
614
});

packages/kit-headless/src/components/popover/popover.driver.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,25 @@ export function createTestDriver<T extends DriverLocator>(rootLocator: T) {
1111
return rootLocator.locator('[popovertarget]');
1212
};
1313

14+
const getAllPopovers = () => {
15+
return getPopover().all();
16+
};
17+
18+
const getAllTriggers = () => {
19+
return getTrigger().all();
20+
};
21+
22+
const getProgrammaticButtonTrigger = () => {
23+
return rootLocator.locator('button');
24+
};
25+
1426
return {
1527
...rootLocator,
1628
locator: rootLocator,
1729
getPopover,
30+
getAllPopovers,
1831
getTrigger,
32+
getAllTriggers,
33+
getProgrammaticButtonTrigger,
1934
};
2035
}

packages/kit-headless/src/components/popover/popover.test.ts

Lines changed: 330 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ test('@Visual diff', async ({ page }) => {
2121
});
2222

2323
test.describe('Mouse Behavior', () => {
24-
test(`GIVEN a closed popover
24+
test(`GIVEN a closed hero popover
2525
WHEN clicking on the trigger
2626
THEN the popover should be opened `, async ({ page }) => {
2727
const { driver: d } = await setup(page, 'hero');
@@ -31,4 +31,333 @@ test.describe('Mouse Behavior', () => {
3131

3232
await expect(d.getPopover()).toBeVisible();
3333
});
34+
35+
test(`GIVEN an open hero popover
36+
WHEN clicking elsewhere on the page
37+
THEN the popover should close`, async ({ page }) => {
38+
const { driver: d } = await setup(page, 'hero');
39+
40+
await expect(d.getPopover()).toBeHidden();
41+
await d.getTrigger().click();
42+
43+
// If I use `toBeVisible` here, the test fails that the `toBeHidden` check below????
44+
await expect(d.getPopover()).toHaveCSS('opacity', '1');
45+
46+
await page.mouse.click(0, 0);
47+
48+
await expect(d.getPopover()).toBeHidden();
49+
});
50+
51+
test(`GIVEN an open auto popover
52+
WHEN clicking the first trigger on the page and then clicking the second trigger
53+
THEN the first popover should close and the second one appear`, async ({ page }) => {
54+
const { driver: d } = await setup(page, 'auto');
55+
//ask shai: is it good to use nth here???
56+
const [firstPopOver, secondPopOver] = await d.getAllPopovers();
57+
const [firstPopoverTrigger, secondPopoverTrigger] = await d.getAllTriggers();
58+
59+
await expect(firstPopOver).toBeHidden();
60+
await expect(secondPopOver).toBeHidden();
61+
62+
await firstPopoverTrigger.click({ position: { x: 1, y: 1 } });
63+
await expect(firstPopOver).toBeVisible();
64+
65+
await secondPopoverTrigger.click({ position: { x: 1, y: 1 } });
66+
await expect(secondPopOver).toBeVisible();
67+
68+
await expect(firstPopOver).toBeHidden();
69+
});
70+
71+
test(`GIVEN a pair of manual popovers
72+
WHEN clicking the first trigger on the page and then clicking the second trigger
73+
THEN then both popovers should be opened`, async ({ page }) => {
74+
const { driver: d } = await setup(page, 'manual');
75+
76+
//ask shai: is it good to use nth here???
77+
const [firstPopOver, secondPopOver] = await d.getAllPopovers();
78+
const [firstPopoverTrigger, secondPopoverTrigger] = await d.getAllTriggers();
79+
80+
await expect(firstPopOver).toBeHidden();
81+
await expect(secondPopOver).toBeHidden();
82+
83+
await firstPopoverTrigger.click({ position: { x: 1, y: 1 } });
84+
await secondPopoverTrigger.click({ position: { x: 1, y: 1 } });
85+
86+
await expect(firstPopOver).toBeVisible();
87+
await expect(secondPopOver).toBeVisible();
88+
});
89+
90+
test(`GIVEN a pair of manual opened popovers
91+
WHEN clicking the first trigger on the page and then clicking the second trigger
92+
THEN then both popovers should be closed`, async ({ page }) => {
93+
const { driver: d } = await setup(page, 'manual');
94+
95+
const [firstPopOver, secondPopOver] = await d.getAllPopovers();
96+
const [firstPopoverTrigger, secondPopoverTrigger] = await d.getAllTriggers();
97+
98+
// Arrange
99+
await firstPopoverTrigger.click({ position: { x: 1, y: 1 } });
100+
await secondPopoverTrigger.click({ position: { x: 1, y: 1 } });
101+
102+
await expect(firstPopOver).toBeVisible();
103+
await expect(secondPopOver).toBeVisible();
104+
105+
// Need to be explicit about where we're clicking. By default
106+
// the click action tries to click the center of the element
107+
// but in this case, the popover is covering it.
108+
await firstPopoverTrigger.click({ position: { x: 1, y: 1 } });
109+
await secondPopoverTrigger.click({ position: { x: 1, y: 1 } });
110+
111+
// Assert
112+
await expect(firstPopOver).toBeHidden();
113+
await expect(secondPopOver).toBeHidden();
114+
});
115+
116+
test(`GIVEN a popover with placement set to top
117+
WHEN opening the popover
118+
THEN the popover should appear to the right of the trigger`, async ({ page }) => {
119+
const { driver: d } = await setup(page, 'placement');
120+
121+
const popover = d.getPopover();
122+
const trigger = d.getTrigger();
123+
124+
await trigger.hover();
125+
126+
await expect(popover).toBeVisible();
127+
128+
const popoverBoundingBox = await popover.boundingBox();
129+
const triggerBoundingBox = await trigger.boundingBox();
130+
131+
expect(popoverBoundingBox?.x).toBeGreaterThan(
132+
(triggerBoundingBox?.x ?? Number.MAX_VALUE) +
133+
(triggerBoundingBox?.width ?? Number.MAX_VALUE),
134+
);
135+
});
136+
137+
test(`GIVEN a popover with a gutter configured
138+
WHEN opening the popover
139+
THEN the popover should be spaced 40px from the popover`, async ({ page }) => {
140+
const { driver: d } = await setup(page, 'gutter');
141+
142+
const popover = d.getPopover();
143+
const trigger = d.getTrigger();
144+
145+
await trigger.click();
146+
147+
await expect(popover).toBeVisible();
148+
149+
const popoverBoundingBox = await popover.boundingBox();
150+
const triggerBoundingBox = await trigger.boundingBox();
151+
152+
expect(
153+
(triggerBoundingBox?.y ?? 0) -
154+
((popoverBoundingBox?.y ?? 0) + (popoverBoundingBox?.height ?? 0)),
155+
).toBe(40);
156+
});
157+
158+
// test(`GIVEN a combobox with a flip configured
159+
// WHEN scrolling the page
160+
// THEN the popover flip to the opposite end once space runs out`, async ({ page }) => {
161+
// const { driver: d } = await setup(page, 'flip');
162+
163+
// const popover = d.getPopover();
164+
// const trigger = d.getTrigger();
165+
166+
// // Introduce artificial spacing
167+
// await trigger.evaluate((element) => (element.style.top = '800px'));
168+
169+
// await trigger.click();
170+
171+
// await expect(popover).toBeVisible();
172+
173+
// const popoverBoundingBox = await popover.boundingBox();
174+
// const triggerBoundingBox = await trigger.boundingBox();
175+
176+
// console.log(triggerBoundingBox, popoverBoundingBox);
177+
178+
// const triggerBottomAbsolutePosition =
179+
// (triggerBoundingBox?.y ?? 0) + (triggerBoundingBox?.height ?? 0);
180+
181+
// expect((popoverBoundingBox?.y ?? 0) - triggerBottomAbsolutePosition).toBe(24);
182+
// });
183+
});
184+
185+
test.describe('Keyboard Behavior', () => {
186+
for (const key of ['Enter', 'Space']) {
187+
test(`GIVEN a closed hero popover
188+
WHEN focusing on the button and pressing the '${key}' key
189+
THEN the popover should open`, async ({ page }) => {
190+
const { driver: d } = await setup(page, 'hero');
191+
await expect(d.getPopover()).toBeHidden();
192+
193+
await d.getTrigger().press(key);
194+
195+
await expect(d.getPopover()).toBeVisible();
196+
});
197+
198+
test(`GIVEN a open hero popover
199+
WHEN focusing on the button and pressing the '${key}' key
200+
THEN the popover should close`, async ({ page }) => {
201+
const { driver: d } = await setup(page, 'hero');
202+
203+
// Open the popover
204+
await d.getTrigger().press(key);
205+
206+
await expect(d.getPopover()).toBeVisible();
207+
208+
// Close the popover
209+
await d.getTrigger().press(key);
210+
211+
await expect(d.getPopover()).toBeHidden();
212+
});
213+
}
214+
215+
test(`GIVEN a open hero popover
216+
WHEN focusing on the button and pressing the 'Escape' key
217+
THEN the popover should close and the trigger be focused`, async ({ page }) => {
218+
const { driver: d } = await setup(page, 'hero');
219+
220+
// Open the popover
221+
await d.getTrigger().press('Enter');
222+
223+
await expect(d.getPopover()).toBeVisible();
224+
225+
// Close the popover
226+
page.keyboard.press('Escape');
227+
228+
await expect(d.getPopover()).toBeHidden();
229+
await expect(d.getTrigger()).toBeFocused();
230+
});
231+
232+
test(`GIVEN a programmatic popover with a programmatic trigger
233+
WHEN focusing on the button and pressing the 'o' key
234+
THEN the popover should open`, async ({ page }) => {
235+
const { driver: d } = await setup(page, 'programmatic');
236+
237+
await expect(d.getPopover()).toBeHidden();
238+
239+
// Using `page` here because driver is scoped to the popover
240+
await page.getByRole('button', { name: "Focus me and press the 'o'" }).focus();
241+
await page.keyboard.type('o');
242+
243+
await expect(d.getPopover()).toBeVisible();
244+
});
245+
test(`GIVEN an open auto popover
246+
WHEN the first trigger open and the focus changes to the second popover
247+
THEN the first popover should close and the second one appear`, async ({ page }) => {
248+
const { driver: d } = await setup(page, 'auto');
249+
250+
const [firstPopOver, secondPopOver] = await d.getAllPopovers();
251+
const [firstPopoverTrigger, secondPopoverTrigger] = await d.getAllTriggers();
252+
253+
await expect(firstPopOver).toBeHidden();
254+
await expect(secondPopOver).toBeHidden();
255+
256+
await firstPopoverTrigger.press('Enter');
257+
await expect(firstPopOver).toBeVisible();
258+
await firstPopoverTrigger.press('Tab');
259+
await expect(secondPopoverTrigger).toBeFocused();
260+
261+
await secondPopoverTrigger.press('Enter');
262+
await expect(secondPopOver).toBeVisible();
263+
264+
await expect(firstPopOver).toBeHidden();
265+
});
266+
267+
test(`GIVEN a pair of manual popovers
268+
WHEN clicking the first trigger on the page and then clicking the second trigger
269+
THEN then both popovers should be opened`, async ({ page }) => {
270+
const { driver: d } = await setup(page, 'manual');
271+
272+
const [firstPopOver, secondPopOver] = await d.getAllPopovers();
273+
const [firstPopoverTrigger, secondPopoverTrigger] = await d.getAllTriggers();
274+
275+
await expect(firstPopOver).toBeHidden();
276+
await expect(secondPopOver).toBeHidden();
277+
278+
await firstPopoverTrigger.press('Enter');
279+
280+
await secondPopoverTrigger.press('Enter');
281+
282+
await expect(firstPopOver).toBeVisible();
283+
await expect(secondPopOver).toBeVisible();
284+
});
285+
286+
test(`GIVEN a pair of manual opened popovers
287+
WHEN activating the first trigger on the page and then activating the second trigger
288+
THEN then both popovers should be closed`, async ({ page }) => {
289+
const { driver: d } = await setup(page, 'manual');
290+
291+
const [firstPopOver, secondPopOver] = await d.getAllPopovers();
292+
const [firstPopoverTrigger, secondPopoverTrigger] = await d.getAllTriggers();
293+
294+
// Arrange
295+
await firstPopoverTrigger.press('Enter');
296+
297+
await secondPopoverTrigger.press('Enter');
298+
299+
await expect(firstPopOver).toBeVisible();
300+
await expect(secondPopOver).toBeVisible();
301+
302+
// Act
303+
await secondPopoverTrigger.press('Enter');
304+
await expect(secondPopOver).toBeHidden();
305+
306+
// Assert
307+
await firstPopoverTrigger.press('Enter');
308+
await expect(firstPopOver).toBeHidden();
309+
});
310+
311+
test(`GIVEN a programmatic popover
312+
WHEN focusing the button on the page and then typing 'o'
313+
THEN the popover should open`, async ({ page }) => {
314+
const { driver: d } = await setup(page, 'programmatic');
315+
316+
const popover = d.getPopover();
317+
const programmaticButtonTrigger = d.getProgrammaticButtonTrigger();
318+
319+
await expect(popover).toBeHidden();
320+
321+
await programmaticButtonTrigger.press('o');
322+
323+
await expect(popover).toBeVisible();
324+
});
325+
326+
test(`GIVEN an open programmatic popover
327+
WHEN focusing the button on the page and then typing 'o'
328+
THEN the popover should close`, async ({ page }) => {
329+
const { driver: d } = await setup(page, 'programmatic');
330+
331+
const popover = d.getPopover();
332+
const programmaticButtonTrigger = d.getProgrammaticButtonTrigger();
333+
334+
await programmaticButtonTrigger.press('o');
335+
336+
await expect(popover).toBeVisible();
337+
338+
await programmaticButtonTrigger.press('o');
339+
340+
await expect(popover).toBeHidden();
341+
});
342+
343+
test(`GIVEN a popover with placement set to top
344+
WHEN opening the popover using the keyboard
345+
THEN the popover should appear to the right of the trigger`, async ({ page }) => {
346+
const { driver: d } = await setup(page, 'placement');
347+
348+
const popover = d.getPopover();
349+
const trigger = d.getTrigger();
350+
351+
await trigger.press('Enter');
352+
353+
await expect(popover).toBeVisible();
354+
355+
const popoverBoundingBox = await popover.boundingBox();
356+
const triggerBoundingBox = await trigger.boundingBox();
357+
358+
expect(popoverBoundingBox?.x).toBeGreaterThan(
359+
(triggerBoundingBox?.x ?? Number.MAX_VALUE) +
360+
(triggerBoundingBox?.width ?? Number.MAX_VALUE),
361+
);
362+
});
34363
});

0 commit comments

Comments
 (0)