Skip to content

Commit 182f62e

Browse files
Merge pull request #618 from thejackshelton/feat/select
test(select): initial select tests
2 parents e07573b + f86fa5c commit 182f62e

File tree

19 files changed

+317
-364
lines changed

19 files changed

+317
-364
lines changed

.changeset/few-numbers-cross.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik-ui/headless': patch
3+
---
4+
5+
Added a new contributing guide in the docs! /docs/headless/contributing

apps/website/e2e/example.spec.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

apps/website/playwright.config.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const baseURL = process.env['BASE_URL'] || 'http://localhost:5173';
1616
* See https://playwright.dev/docs/test-configuration.
1717
*/
1818
export default defineConfig({
19-
...nxE2EPreset(__filename, { testDir: './e2e' }),
19+
...nxE2EPreset(__filename, { testDir: './src' }),
2020
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
2121
use: {
2222
baseURL,
@@ -37,15 +37,15 @@ export default defineConfig({
3737
use: { ...devices['Desktop Chrome'] },
3838
},
3939

40-
{
41-
name: 'firefox',
42-
use: { ...devices['Desktop Firefox'] },
43-
},
40+
// {
41+
// name: 'firefox',
42+
// use: { ...devices['Desktop Firefox'] },
43+
// },
4444

45-
{
46-
name: 'webkit',
47-
use: { ...devices['Desktop Safari'] },
48-
},
45+
// {
46+
// name: 'webkit',
47+
// use: { ...devices['Desktop Safari'] },
48+
// },
4949

5050
// Uncomment for mobile browsers support
5151
/* {

apps/website/src/routes/docs/headless/contributing/index.mdx

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,99 @@ Tests ensure we can sleep sound at night and know that our component behavior is
104104
- get the test passing by adding said feature!
105105
- enjoy life when refactoring 🏝️
106106

107-
We currently use [playwright](https://playwright.dev/docs/intro) for testing.
107+
We strongly recommend TDD development for the headless library, and we are currently in the process of a playwright integration.
108108

109-
> We also currently have a few cypress tests that we are migrating over to playwright.
109+
Currently, the component testing integration for Qwik & Playwright is in development, and we are using e2e tests for the time being. That said, most tests should be very easy to migrate later on.
110110

111-
Shai also has a testing course that Qwik UI contributors get access to for the price of **FREE**! Don't hesitate to reach out.
111+
#### Getting started w/ testing
112+
113+
Here's an example way of getting a testid of the `Hero` select docs example in `index.mdx`, without affecting any visible markup.
114+
115+
```tsx
116+
<div data-testid="select-hero-test">
117+
<Showcase name="hero" />
118+
</div>
119+
```
120+
121+
Then, we get our testDriver, you can think of it as reusable code we use throughout the test. For example, in the Select component we constantly grab the listbox, trigger, etc.
122+
123+
```tsx
124+
import { Locator, Page } from '@playwright/test';
125+
126+
export type DriverLocator = Locator | Page;
127+
128+
export function selectTestDriver<T extends DriverLocator>(locator: T) {
129+
const getRoot = () => {
130+
return locator.getByRole('combobox');
131+
};
132+
133+
return {
134+
...locator,
135+
locator,
136+
getRoot,
137+
getListbox() {
138+
return getRoot().getByRole('listbox');
139+
},
140+
getTrigger() {
141+
return getRoot().getByRole('button');
142+
},
143+
// get all options
144+
getOptions() {
145+
return getRoot().getByRole('option').all();
146+
},
147+
};
148+
}
149+
```
150+
151+
Now we can write some tests:
152+
153+
```tsx
154+
import { test, expect } from '@playwright/test';
155+
import { selectTestDriver } from './select.driver';
156+
157+
test.beforeEach(async ({ page }) => {
158+
await page.goto('/docs/headless/select');
159+
});
160+
161+
test.describe('critical functionality', () => {
162+
test(`GIVEN a basic select
163+
WHEN clicking on the trigger
164+
THEN open up the listbox
165+
AND aria-expanded should be true`, async ({ page }) => {
166+
const testDriver = selectTestDriver(page.getByTestId('select-hero-test'));
167+
168+
const { getListbox, getTrigger } = testDriver;
169+
170+
await getTrigger().click();
171+
172+
await expect(getListbox()).toBeVisible();
173+
await expect(getTrigger()).toHaveAttribute('aria-expanded', 'true');
174+
});
175+
176+
test(`GIVEN a basic select
177+
WHEN focusing the trigger and hitting enter
178+
THEN open up the listbox
179+
AND aria-expanded should be true`, async ({ page }) => {
180+
const testDriver = selectTestDriver(page.getByTestId('select-hero-test'));
181+
182+
const { getListbox, getTrigger } = testDriver;
183+
184+
await getTrigger().focus();
185+
await page.keyboard.press('Enter');
186+
187+
await expect(getListbox()).toBeVisible();
188+
await expect(getTrigger()).toHaveAttribute('aria-expanded', 'true');
189+
});
190+
});
191+
```
192+
193+
To run the tests, use the `pnpm playwright` (only in my branch) command, or the `nx e2e website` longform. You can also do `--ui` to open the UI mode (which is pretty awesome!)
194+
195+
> This example is in `spec.select.tsx` in the `src/components/select` folder of the website.
196+
197+
Once we've added a failing test with what we expect, we can now go ahead and implement the feature for that!
198+
199+
Heads up, I (Jack) am also relatively new to playwright myself 😅. I'm guessing there is a way to not need the testDriver to be consumed on each test. Feel free to experiment!
112200

113201
## Docs
114202

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,20 @@
1-
import { $, component$, useSignal, useTask$ } from '@builder.io/qwik';
1+
import { component$, useSignal } from '@builder.io/qwik';
22
import { Select, SelectListbox, SelectOption, SelectTrigger } from '@qwik-ui/headless';
33

44
export default component$(() => {
5-
const mockUsers = ['Tim', 'Ryan', 'Jim'];
6-
const moreUsers = ['Carla', 'Rachel', 'Monica', 'Jessie', 'Abby'];
7-
8-
const usersSig = useSignal<string[]>([]);
9-
10-
useTask$(async () => {
11-
usersSig.value = mockUsers;
12-
});
13-
14-
const handleClick$ = $(() => {
15-
usersSig.value = [...usersSig.value, ...moreUsers];
16-
});
5+
const usersSig = useSignal<string[]>(['Tim', 'Ryan', 'Jim', 'Jessie', 'Abby']);
176

187
return (
19-
<div>
20-
<Select>
21-
<SelectTrigger>Trigger</SelectTrigger>
22-
<SelectListbox style={{ padding: '0px', margin: '0px', listStyle: 'none' }}>
23-
<SelectOption disabled>My option</SelectOption>
24-
{usersSig.value.map((user) => (
25-
<SelectOption key={user}>{user}</SelectOption>
26-
))}
27-
</SelectListbox>
28-
</Select>
29-
{/* somehow this adds more js on page load? / wakes up the framework? */}
30-
<button onClick$={handleClick$}>Add more!</button>
31-
</div>
8+
<Select class="relative min-w-40">
9+
<SelectTrigger class="w-full border-2 border-dashed border-red-400" />
10+
<SelectListbox class="absolute w-full border-2 border-dashed border-green-400 bg-slate-900 p-2">
11+
<SelectOption class="border-2 border-dashed border-blue-400">
12+
first option!
13+
</SelectOption>
14+
{usersSig.value.map((user) => (
15+
<SelectOption key={user}>{user}</SelectOption>
16+
))}
17+
</SelectListbox>
18+
</Select>
3219
);
3320
});

apps/website/src/routes/docs/headless/select/index.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import { statusByComponent } from '~/_state/component-statuses';
1010

1111
This element is used to create a drop-down list, it's often used in a form, to collect user input.
1212

13-
<Showcase name="hero" />
13+
<div data-testid="select-hero-test">
14+
<Showcase name="hero" />
15+
</div>
1416

1517
## Building blocks
1618

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Locator, Page } from '@playwright/test';
2+
3+
export type DriverLocator = Locator | Page;
4+
5+
export function selectTestDriver<T extends DriverLocator>(locator: T) {
6+
const getRoot = () => {
7+
return locator.getByRole('combobox');
8+
};
9+
10+
return {
11+
...locator,
12+
locator,
13+
getRoot,
14+
getListbox() {
15+
return getRoot().getByRole('listbox');
16+
},
17+
getTrigger() {
18+
return getRoot().getByRole('button');
19+
},
20+
// get all options
21+
getOptions() {
22+
return getRoot().getByRole('option').all();
23+
},
24+
};
25+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { test, expect } from '@playwright/test';
2+
import { selectTestDriver } from './select.driver';
3+
4+
test.beforeEach(async ({ page }) => {
5+
await page.goto('/docs/headless/select');
6+
});
7+
8+
test.describe('critical functionality', () => {
9+
test(`GIVEN a basic select
10+
WHEN clicking on the trigger
11+
THEN open up the listbox
12+
AND aria-expanded should be true`, async ({ page }) => {
13+
const testDriver = selectTestDriver(page.getByTestId('select-hero-test'));
14+
15+
const { getListbox, getTrigger } = testDriver;
16+
17+
await getTrigger().click();
18+
19+
await expect(getListbox()).toBeVisible();
20+
await expect(getTrigger()).toHaveAttribute('aria-expanded', 'true');
21+
});
22+
23+
test(`GIVEN a basic select
24+
WHEN focusing the trigger and hitting enter
25+
THEN open up the listbox
26+
AND aria-expanded should be true`, async ({ page }) => {
27+
const testDriver = selectTestDriver(page.getByTestId('select-hero-test'));
28+
29+
const { getListbox, getTrigger } = testDriver;
30+
31+
await getTrigger().focus();
32+
await page.keyboard.press('Enter');
33+
34+
await expect(getListbox()).toBeVisible();
35+
await expect(getTrigger()).toHaveAttribute('aria-expanded', 'true');
36+
});
37+
});

apps/website/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@
2626
"path": "./tsconfig.spec.json"
2727
}
2828
],
29-
"include": ["src"]
29+
"include": ["src", "src/routes/docs/headless/select/select.spec.ts"]
3030
}

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"changeset": "changeset",
1616
"changeset.version": "changeset version && pnpm install --no-frozen-lockfile && git add --all",
1717
"dev": "nx serve website",
18+
"playwright": "nx e2e website",
1819
"format.fix": "pretty-quick --staged",
1920
"link.dist": "cd dist/packages/kit-headless && pnpm link --global",
2021
"lint": "nx affected:lint",
@@ -61,7 +62,7 @@
6162
"@nx/vite": "17.1.3",
6263
"@nx/workspace": "17.1.3",
6364
"@oddbird/popover-polyfill": "0.3.7",
64-
"@playwright/test": "^1.36.0",
65+
"@playwright/test": "^1.41.2",
6566
"@storybook/addon-a11y": "7.6.0",
6667
"@storybook/addon-coverage": "^0.0.9",
6768
"@storybook/addon-essentials": "7.6.0",
@@ -104,7 +105,7 @@
104105
"eslint": "^8.48.0",
105106
"eslint-config-prettier": "9.0.0",
106107
"eslint-plugin-cypress": "^2.14.0",
107-
"eslint-plugin-playwright": "^0.15.3",
108+
"eslint-plugin-playwright": "^1.3.0",
108109
"eslint-plugin-qwik": "^1.4.0",
109110
"eslint-plugin-storybook": "^0.6.13",
110111
"focus-trap": "7.5.3",

0 commit comments

Comments
 (0)