Skip to content

Commit d1264fe

Browse files
authored
V2 Switch Test (#300)
* update switch test to vitest * chore: remove unused switch driver and test files * Update pnpm-lock.yaml
1 parent 04ed4ba commit d1264fe

File tree

4 files changed

+678
-837
lines changed

4 files changed

+678
-837
lines changed
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
import { $, type PropsOf, component$, useComputed$, useSignal } from "@qwik.dev/core";
2+
import { page, userEvent } from "@vitest/browser/context";
3+
import { expect, test } from "vitest";
4+
import { render } from "vitest-browser-qwik";
5+
import { Switch } from "..";
6+
7+
// Top-level locator constants using data-testid
8+
const Root = page.getByTestId("root");
9+
const Trigger = page.getByTestId("trigger");
10+
const Thumb = page.getByTestId("thumb");
11+
const Label = page.getByTestId("label");
12+
const Description = page.getByTestId("description");
13+
const HiddenInput = page.getByTestId("hidden-input");
14+
const Errors = page.getByTestId("error");
15+
const SubmitButton = page.getByTestId("submit-button");
16+
17+
const Basic = component$((props: PropsOf<typeof Switch.Root>) => {
18+
return (
19+
<Switch.Root {...props} data-testid="root">
20+
<Switch.Label data-testid="label">Enable notifications</Switch.Label>
21+
<Switch.Trigger data-testid="trigger">
22+
<Switch.Thumb data-testid="thumb" />
23+
</Switch.Trigger>
24+
</Switch.Root>
25+
);
26+
});
27+
28+
const WithDescription = component$((props: PropsOf<typeof Switch.Root>) => {
29+
return (
30+
<Switch.Root {...props} data-testid="root">
31+
<Switch.Label data-testid="label">Enable notifications</Switch.Label>
32+
<Switch.Trigger data-testid="trigger">
33+
<Switch.Thumb data-testid="thumb" />
34+
</Switch.Trigger>
35+
<Switch.Description data-testid="description">
36+
(Receive notifications about important updates)
37+
</Switch.Description>
38+
</Switch.Root>
39+
);
40+
});
41+
42+
const BasicForm = component$((props: PropsOf<typeof Switch.Root>) => {
43+
return (
44+
<form
45+
preventdefault:submit
46+
noValidate
47+
style={{ display: "flex", flexDirection: "column", gap: "8px" }}
48+
>
49+
<Switch.Root {...props} name="notifications" value="enabled" data-testid="root">
50+
<Switch.Label data-testid="label">Enable notifications</Switch.Label>
51+
<Switch.Trigger data-testid="trigger">
52+
<Switch.Thumb data-testid="thumb" />
53+
</Switch.Trigger>
54+
<Switch.HiddenInput data-testid="hidden-input" />
55+
</Switch.Root>
56+
<button type="submit" data-testid="submit-button">
57+
Submit
58+
</button>
59+
</form>
60+
);
61+
});
62+
63+
const FormWithValidation = component$((props: PropsOf<typeof Switch.Root>) => {
64+
const isChecked = useSignal(false);
65+
const isSubmitAttempt = useSignal(false);
66+
const isError = useComputed$(() => !isChecked.value && isSubmitAttempt.value);
67+
68+
const handleSubmit$ = $((e: SubmitEvent) => {
69+
const form = e.target as HTMLFormElement;
70+
if (!isChecked.value) {
71+
isSubmitAttempt.value = true;
72+
return;
73+
}
74+
isSubmitAttempt.value = false;
75+
});
76+
77+
return (
78+
<form
79+
preventdefault:submit
80+
noValidate
81+
onSubmit$={handleSubmit$}
82+
style={{ display: "flex", flexDirection: "column", gap: "8px" }}
83+
>
84+
<Switch.Root
85+
{...props}
86+
required
87+
name="notifications"
88+
value="enabled"
89+
bind:checked={isChecked}
90+
hasError={isError.value}
91+
data-testid="root"
92+
>
93+
<Switch.Label data-testid="label">Enable notifications</Switch.Label>
94+
<Switch.Trigger data-testid="trigger">
95+
<Switch.Thumb data-testid="thumb" />
96+
</Switch.Trigger>
97+
<Switch.HiddenInput data-testid="hidden-input" />
98+
{isError.value && (
99+
<Switch.Error data-testid="error">This field is required</Switch.Error>
100+
)}
101+
</Switch.Root>
102+
<button type="submit" data-testid="submit-button">
103+
Submit
104+
</button>
105+
</form>
106+
);
107+
});
108+
109+
test("should have correct ARIA attributes when rendered", async () => {
110+
render(<Basic />);
111+
112+
await expect.element(Root).toHaveAttribute("role", "switch");
113+
await expect.element(Root).toHaveAttribute("aria-checked", "false");
114+
});
115+
116+
test("should toggle state when clicking trigger", async () => {
117+
render(<Basic />);
118+
119+
await userEvent.click(Trigger);
120+
await expect.element(Root).toHaveAttribute("aria-checked", "true");
121+
122+
await userEvent.click(Trigger);
123+
await expect.element(Root).toHaveAttribute("aria-checked", "false");
124+
});
125+
126+
test("should toggle state with space key", async () => {
127+
render(<Basic />);
128+
129+
await expect.element(Trigger).toBeVisible();
130+
((await Trigger.element()) as HTMLButtonElement).focus();
131+
132+
await userEvent.keyboard("{Space}");
133+
await expect.element(Root).toHaveAttribute("aria-checked", "true");
134+
135+
await userEvent.keyboard("{Space}");
136+
await expect.element(Root).toHaveAttribute("aria-checked", "false");
137+
});
138+
139+
test("should toggle state with enter key", async () => {
140+
render(<Basic />);
141+
142+
await expect.element(Trigger).toBeVisible();
143+
((await Trigger.element()) as HTMLButtonElement).focus();
144+
145+
await userEvent.keyboard("{Enter}");
146+
await expect.element(Root).toHaveAttribute("aria-checked", "true");
147+
148+
await userEvent.keyboard("{Enter}");
149+
await expect.element(Root).toHaveAttribute("aria-checked", "false");
150+
});
151+
152+
test("should be disabled when disabled prop is true", async () => {
153+
render(<Basic disabled />);
154+
155+
await expect.element(Root).toHaveAttribute("aria-disabled", "true");
156+
await expect.element(Trigger).toBeDisabled();
157+
await expect.element(Root).toHaveAttribute("aria-checked", "false");
158+
});
159+
160+
test("should not toggle when disabled and clicked", async () => {
161+
render(<Basic disabled />);
162+
163+
await expect.element(Root).toHaveAttribute("aria-checked", "false");
164+
// Disabled elements cannot be clicked in userEvent, so we just verify the state remains
165+
await expect.element(Trigger).toBeDisabled();
166+
await expect.element(Root).toHaveAttribute("aria-checked", "false");
167+
});
168+
169+
test("should not toggle when disabled and using keyboard", async () => {
170+
render(<Basic disabled />);
171+
172+
await expect.element(Trigger).toBeVisible();
173+
((await Trigger.element()) as HTMLButtonElement).focus();
174+
175+
await userEvent.keyboard("{Space}");
176+
await expect.element(Root).toHaveAttribute("aria-checked", "false");
177+
178+
await userEvent.keyboard("{Enter}");
179+
await expect.element(Root).toHaveAttribute("aria-checked", "false");
180+
});
181+
182+
test("should toggle when label is clicked", async () => {
183+
render(<Basic />);
184+
185+
await userEvent.click(Label);
186+
await expect.element(Root).toHaveAttribute("aria-checked", "true");
187+
188+
await userEvent.click(Label);
189+
await expect.element(Root).toHaveAttribute("aria-checked", "false");
190+
});
191+
192+
test("should have hidden input with correct attributes in form", async () => {
193+
render(<BasicForm />);
194+
195+
await expect.element(HiddenInput).toHaveAttribute("name", "notifications");
196+
await expect.element(HiddenInput).toHaveAttribute("value", "enabled");
197+
});
198+
199+
test("should show error when required and submitted unchecked", async () => {
200+
render(<FormWithValidation />);
201+
202+
await userEvent.click(SubmitButton);
203+
204+
await expect.element(Root).toHaveAttribute("data-error");
205+
await expect.element(Errors).toBeVisible();
206+
await expect.element(Errors).toHaveTextContent("This field is required");
207+
});
208+
209+
test("should clear error when switch is checked", async () => {
210+
render(<FormWithValidation />);
211+
212+
await userEvent.click(SubmitButton);
213+
await expect.element(Errors).toBeVisible();
214+
215+
await userEvent.click(Trigger);
216+
await expect.element(Errors).not.toBeInTheDocument();
217+
});
218+
219+
test("should connect error message with aria-errormessage", async () => {
220+
render(<FormWithValidation />);
221+
222+
await userEvent.click(SubmitButton);
223+
224+
const errorElements = await Errors.elements();
225+
const errorId = errorElements[0]?.id;
226+
227+
expect(errorId).toBeTruthy();
228+
await expect.element(Root).toHaveAttribute("aria-errormessage", errorId as string);
229+
});
230+
231+
test("should display description when provided", async () => {
232+
render(<WithDescription />);
233+
234+
await expect.element(Description).toBeVisible();
235+
await expect
236+
.element(Description)
237+
.toHaveTextContent("(Receive notifications about important updates)");
238+
});
239+
240+
test("should set data-checked attribute when checked", async () => {
241+
render(<Basic />);
242+
243+
await userEvent.click(Trigger);
244+
await expect.element(Root).toHaveAttribute("data-checked");
245+
});
246+
247+
test("should set data-disabled attribute when disabled", async () => {
248+
render(<Basic disabled />);
249+
250+
await expect.element(Root).toHaveAttribute("data-disabled");
251+
});
252+
253+
test("should be checked when checked prop is true", async () => {
254+
render(<Basic checked />);
255+
256+
await expect.element(Root).toHaveAttribute("aria-checked", "true");
257+
await expect.element(Root).toHaveAttribute("data-checked");
258+
});
259+
260+
test("should set aria-required when required prop is true", async () => {
261+
render(<Basic required />);
262+
263+
await expect.element(Root).toHaveAttribute("aria-required", "true");
264+
});

libs/components/src/switch/switch.driver.ts

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

0 commit comments

Comments
 (0)