Skip to content

Commit 622662b

Browse files
committed
03/01: add problem and solution text
1 parent ee7608f commit 622662b

File tree

24 files changed

+538
-7
lines changed

24 files changed

+538
-7
lines changed

exercises/02.vitest-browser-mode/01.problem.installation-and-setup/src/file-preview.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { test, expect } from 'vitest'
21
import { render, screen } from '@testing-library/react'
32
import { FilePreview } from './file-preview.tsx'
43

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Queries
2+
3+
One of the primary ways to achieve user-driven tests is to access and interact with the UI elements in the same manner user would. Your users don't know HTML or CSS so they don't locate elements on the page by their class names or `test-id` attributes. Instead, they find the things they need by their _roles_ and _names_ (this is true for visually-impared users as well).
4+
5+
The way you access different elements in tests matters a lot. It can vary from giving you a ton of value and implicit accessibility assurance to forcing you to change your tests all the time because they are riddled with implementation details.
6+
7+
<callout-info>When testing, do what users do.</callout-info>
8+
9+
👨‍💼 In this exercise, your task is to write a simple integration test for a new React component called <InlineFile file="./src/discount-code-form.tsx">
10+
`<DiscountForm />`</InlineFile>. And what do you know it—this component is a _form_! This means plenty of elements for you to locate. Use locators like `.getByRole()` and `.getByLabelText()`, and write assertions that make sure all the necessary form elements are present on the page.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Vite App</title>
8+
<link rel="preconnect" href="https://fonts.googleapis.com" />
9+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10+
</head>
11+
<body>
12+
<div id="root"></div>
13+
<script type="module" src="/src/main.tsx"></script>
14+
</body>
15+
</html>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"type": "module",
3+
"name": "exercises_03.best-practices_01.problem.accessibility-selectors",
4+
"scripts": {
5+
"dev": "vite",
6+
"test": "vitest"
7+
},
8+
"dependencies": {
9+
"react": "^19.0.0",
10+
"react-dom": "^19.0.0"
11+
},
12+
"devDependencies": {
13+
"@testing-library/dom": "^10.4.0",
14+
"@testing-library/react": "^16.1.0",
15+
"@types/node": "^22.10.6",
16+
"@types/react": "^19.0.6",
17+
"@types/react-dom": "^19.0.3",
18+
"@vitejs/plugin-react": "^4.3.4",
19+
"@vitest/browser": "^3.0.3",
20+
"autoprefixer": "^10.4.20",
21+
"playwright": "^1.49.1",
22+
"postcss": "^8.4.49",
23+
"tailwindcss": "^3.4.17",
24+
"vite": "^6.0.7",
25+
"vitest": "^3.0.3"
26+
}
27+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default {
2+
plugins: {
3+
tailwindcss: {},
4+
autoprefixer: {},
5+
},
6+
}
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { DiscountCodeForm } from './discount-code-form.js'
2+
3+
export function App() {
4+
return <DiscountCodeForm />
5+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { page } from '@vitest/browser/context'
2+
import { render } from 'vitest-browser-react'
3+
import { DiscountCodeForm } from './discount-code-form.js'
4+
5+
test('renders the discount form', async () => {
6+
render(<DiscountCodeForm />)
7+
8+
// 🐨 Create a variable called `discountInput` and assign it
9+
// the result of locating the element by label text "Discount code".
10+
// 💰 const discountInput = page.getByLabelText(labelText)
11+
//
12+
// 🐨 Write an assertion that the `discountInput` element is visible.
13+
// 💰 expect.element(locator).toBeVisible()
14+
//
15+
// 🐨 Create a new variable called `applyDiscountButton` and assign it
16+
// the result of locating the element by role 'button' and accessible name
17+
// 'Apply discount'.
18+
// 💰 const applyDiscountButton = page.getByRole(role, { name: accessibleName })
19+
//
20+
// 🐨 Finally, write another assertion that the apply discount button is visible.
21+
})
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { useState, type FormEventHandler } from 'react'
2+
3+
export interface Discount {
4+
code: string
5+
amount: number
6+
}
7+
8+
export function DiscountCodeForm() {
9+
const [appliedDiscount, setAppliedDiscount] = useState<Discount>()
10+
11+
const handleSubmit: FormEventHandler<HTMLFormElement> = async (event) => {
12+
event.preventDefault()
13+
14+
const data = new FormData(event.currentTarget)
15+
const code = data.get('discountCode') as string
16+
17+
setAppliedDiscount({
18+
code,
19+
amount: 20,
20+
})
21+
}
22+
23+
return (
24+
<section className="w-96 rounded-lg border bg-white p-10">
25+
{appliedDiscount ? (
26+
<p>
27+
Discount: <strong>{appliedDiscount.code}</strong> (-
28+
{appliedDiscount.amount}%)
29+
</p>
30+
) : (
31+
<form onSubmit={handleSubmit} className="flex flex-col gap-5">
32+
<div>
33+
<label htmlFor="discountCode" className="mb-1 block">
34+
Discount code
35+
</label>
36+
<input
37+
id="discountCode"
38+
name="discountCode"
39+
className="w-full rounded-md border px-2 py-1 focus:ring-4"
40+
placeholder="ABCD1234"
41+
pattern="[A-Z]{4}[0-9]{4}"
42+
autoComplete="off"
43+
required
44+
/>
45+
</div>
46+
47+
<button
48+
type="submit"
49+
className="rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-500 focus:ring-4"
50+
>
51+
Apply discount
52+
</button>
53+
</form>
54+
)}
55+
</section>
56+
)
57+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap');
2+
3+
@tailwind base;
4+
@tailwind components;
5+
@tailwind utilities;
6+
7+
html {
8+
@apply flex size-full items-center justify-center bg-slate-100 p-10;
9+
10+
font-family:
11+
'DM Sans',
12+
system-ui,
13+
-apple-system,
14+
BlinkMacSystemFont,
15+
'Segoe UI',
16+
Roboto,
17+
Oxygen,
18+
Ubuntu,
19+
Cantarell,
20+
'Open Sans',
21+
'Helvetica Neue',
22+
sans-serif;
23+
font-style: normal;
24+
font-optical-sizing: auto;
25+
}

0 commit comments

Comments
 (0)