mirror of
https://github.com/hanzoai/shadcn.git
synced 2026-10-30 18:22:59 +00:00
Major documentation reorganization and improvements: ## Documentation Cleanup - Deleted 13 old status/report MD files from root - Moved old reports to docs/archive/ for reference - Removed USAGE.md, WHITE_LABEL.md, TESTING_GUIDE.md - Now only 9 essential MD files at root (LLM.md, README.md, etc.) ## New Comprehensive Documentation Created in app/content/docs/ (visible on ui.hanzo.ai): ### Frameworks (/docs/frameworks/) - Multi-framework overview and comparison - React guide (8KB) - hooks, Next.js, TypeScript - Vue guide (4.6KB) - Composition API, Nuxt ### Packages (/docs/packages/) - Complete package documentation (6.3KB) - @hanzo/ui, @hanzo/auth, @hanzo/commerce, @hanzo/brand ### Testing (/docs/testing/) - Comprehensive testing guide (7.6KB) - Unit, E2E, visual regression, accessibility - Vitest, Playwright, CI/CD examples ### White-Label (/docs/white-label/) - Fork and rebrand guide (6.4KB) - Publishing, deployment, syncing with upstream ## Navigation & UX Improvements - Updated sidebar navigation with 4 new sections - Added comprehensive footer with quick links - 4-column footer: Getting Started, Frameworks, Resources, Community - Version number in footer (v5.0.0) ## Technical Fixes - Fixed build:registry workspace references (www,v4 → app) - Removed deprecated prettier import order options - Updated site config with version and Discord link ## Updated Documentation - README.md - Concise, feature-rich overview - LLM.md - Documentation structure rules and recent updates - All 175 MDX files accessible and building correctly Deployment ready for ui.hanzo.ai
344 lines
7.4 KiB
Plaintext
344 lines
7.4 KiB
Plaintext
---
|
|
title: Testing Guide
|
|
description: How to test @hanzo/ui components and applications
|
|
---
|
|
|
|
# Testing Guide
|
|
|
|
Comprehensive guide to testing applications built with @hanzo/ui.
|
|
|
|
## Prerequisites
|
|
|
|
- Node.js 18+
|
|
- pnpm (recommended) or npm
|
|
- Playwright for E2E tests (optional)
|
|
|
|
## Unit Testing
|
|
|
|
### Setup with Vitest
|
|
|
|
```bash
|
|
npm install -D vitest @testing-library/react @testing-library/user-event happy-dom
|
|
```
|
|
|
|
```ts title="vitest.config.ts"
|
|
import react from "@vitejs/plugin-react"
|
|
import { defineConfig } from "vitest/config"
|
|
|
|
export default defineConfig({
|
|
plugins: [react()],
|
|
test: {
|
|
environment: "happy-dom",
|
|
setupFiles: ["./vitest.setup.ts"],
|
|
},
|
|
})
|
|
```
|
|
|
|
```ts title="vitest.setup.ts"
|
|
import "@testing-library/jest-dom"
|
|
```
|
|
|
|
### Testing Components
|
|
|
|
```tsx title="button.test.tsx"
|
|
import { Button } from "@hanzo/ui"
|
|
import { render, screen } from "@testing-library/react"
|
|
import userEvent from "@testing-library/user-event"
|
|
|
|
describe("Button", () => {
|
|
it("renders correctly", () => {
|
|
render(<Button>Click me</Button>)
|
|
expect(screen.getByRole("button")).toHaveTextContent("Click me")
|
|
})
|
|
|
|
it("handles click events", async () => {
|
|
const handleClick = vi.fn()
|
|
render(<Button onClick={handleClick}>Click me</Button>)
|
|
|
|
await userEvent.click(screen.getByRole("button"))
|
|
expect(handleClick).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it("applies variants correctly", () => {
|
|
render(<Button variant="destructive">Delete</Button>)
|
|
expect(screen.getByRole("button")).toHaveClass("bg-destructive")
|
|
})
|
|
})
|
|
```
|
|
|
|
### Testing Forms
|
|
|
|
```tsx title="form.test.tsx"
|
|
import { Button, Form, Input } from "@hanzo/ui"
|
|
import { render, screen, waitFor } from "@testing-library/react"
|
|
import userEvent from "@testing-library/user-event"
|
|
import { useForm } from "react-hook-form"
|
|
|
|
function LoginForm({ onSubmit }) {
|
|
const form = useForm()
|
|
|
|
return (
|
|
<Form {...form}>
|
|
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
<Input {...form.register("email")} type="email" />
|
|
<Button type="submit">Submit</Button>
|
|
</form>
|
|
</Form>
|
|
)
|
|
}
|
|
|
|
describe("LoginForm", () => {
|
|
it("submits form data", async () => {
|
|
const handleSubmit = vi.fn()
|
|
render(<LoginForm onSubmit={handleSubmit} />)
|
|
|
|
await userEvent.type(screen.getByRole("textbox"), "[email protected]")
|
|
await userEvent.click(screen.getByRole("button"))
|
|
|
|
await waitFor(() => {
|
|
expect(handleSubmit).toHaveBeenCalledWith(
|
|
{
|
|
email: "[email protected]",
|
|
},
|
|
expect.anything()
|
|
)
|
|
})
|
|
})
|
|
})
|
|
```
|
|
|
|
## End-to-End Testing
|
|
|
|
### Setup with Playwright
|
|
|
|
```bash
|
|
npm install -D @playwright/test
|
|
npx playwright install
|
|
```
|
|
|
|
```ts title="playwright.config.ts"
|
|
import { defineConfig, devices } from "@playwright/test"
|
|
|
|
export default defineConfig({
|
|
testDir: "./tests/e2e",
|
|
fullyParallel: true,
|
|
use: {
|
|
baseURL: "http://localhost:3003",
|
|
trace: "on-first-retry",
|
|
},
|
|
projects: [
|
|
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
|
|
{ name: "firefox", use: { ...devices["Desktop Firefox"] } },
|
|
{ name: "webkit", use: { ...devices["Desktop Safari"] } },
|
|
],
|
|
webServer: {
|
|
command: "npm run dev",
|
|
url: "http://localhost:3003",
|
|
reuseExistingServer: !process.env.CI,
|
|
},
|
|
})
|
|
```
|
|
|
|
### E2E Test Examples
|
|
|
|
```ts title="tests/e2e/navigation.spec.ts"
|
|
import { expect, test } from "@playwright/test"
|
|
|
|
test.describe("Navigation", () => {
|
|
test("homepage loads correctly", async ({ page }) => {
|
|
await page.goto("/")
|
|
await expect(page).toHaveTitle(/Hanzo UI/)
|
|
await expect(page.locator("h1")).toBeVisible()
|
|
})
|
|
|
|
test("component page navigates", async ({ page }) => {
|
|
await page.goto("/docs/components/button")
|
|
await expect(page.locator("h1")).toContainText("Button")
|
|
|
|
// Test component preview
|
|
const preview = page.locator("[data-rehype-pretty-code-fragment]")
|
|
await expect(preview).toBeVisible()
|
|
})
|
|
})
|
|
```
|
|
|
|
### Testing Interactive Components
|
|
|
|
```ts title="tests/e2e/dialog.spec.ts"
|
|
import { expect, test } from "@playwright/test"
|
|
|
|
test("dialog opens and closes", async ({ page }) => {
|
|
await page.goto("/docs/components/dialog")
|
|
|
|
// Click trigger button
|
|
await page.click("text=Open Dialog")
|
|
|
|
// Check dialog is visible
|
|
await expect(page.locator('[role="dialog"]')).toBeVisible()
|
|
|
|
// Close dialog
|
|
await page.click('[aria-label="Close"]')
|
|
|
|
// Check dialog is hidden
|
|
await expect(page.locator('[role="dialog"]')).not.toBeVisible()
|
|
})
|
|
```
|
|
|
|
## Visual Regression Testing
|
|
|
|
```ts title="tests/visual/components.spec.ts"
|
|
import { expect, test } from "@playwright/test"
|
|
|
|
test.describe("Visual Regression", () => {
|
|
test("button variants", async ({ page }) => {
|
|
await page.goto("/docs/components/button")
|
|
|
|
const button = page.locator('[data-variant="default"]')
|
|
await expect(button).toHaveScreenshot("button-default.png")
|
|
})
|
|
|
|
test("dark mode theme", async ({ page }) => {
|
|
await page.goto("/")
|
|
await page.click("[data-theme-toggle]")
|
|
|
|
await expect(page).toHaveScreenshot("dark-mode.png", {
|
|
fullPage: true,
|
|
})
|
|
})
|
|
})
|
|
```
|
|
|
|
## Accessibility Testing
|
|
|
|
```tsx title="button.test.tsx"
|
|
import { Button } from "@hanzo/ui"
|
|
import { render } from "@testing-library/react"
|
|
import { axe, toHaveNoViolations } from "jest-axe"
|
|
|
|
expect.extend(toHaveNoViolations)
|
|
|
|
test("Button has no accessibility violations", async () => {
|
|
const { container } = render(<Button>Click me</Button>)
|
|
const results = await axe(container)
|
|
expect(results).toHaveNoViolations()
|
|
})
|
|
```
|
|
|
|
## Running Tests
|
|
|
|
### Development
|
|
|
|
```bash
|
|
# Run all tests
|
|
pnpm test
|
|
|
|
# Run in watch mode
|
|
pnpm test:watch
|
|
|
|
# Run with coverage
|
|
pnpm test:coverage
|
|
|
|
# Run specific file
|
|
pnpm test button.test.tsx
|
|
```
|
|
|
|
### End-to-End
|
|
|
|
```bash
|
|
# Run E2E tests
|
|
pnpm test:e2e
|
|
|
|
# Run in headed mode
|
|
pnpm test:e2e --headed
|
|
|
|
# Run in UI mode
|
|
pnpm test:e2e --ui
|
|
|
|
# Generate report
|
|
pnpm test:e2e
|
|
npx playwright show-report
|
|
```
|
|
|
|
### CI/CD
|
|
|
|
```yaml
|
|
name: Tests
|
|
on: [push, pull_request]
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v3
|
|
- uses: pnpm/action-setup@v2
|
|
- uses: actions/setup-node@v3
|
|
with:
|
|
node-version: 18
|
|
cache: "pnpm"
|
|
- run: pnpm install
|
|
- run: pnpm test
|
|
- run: pnpm test:e2e
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Test User Behavior, Not Implementation**
|
|
|
|
```tsx
|
|
// Good
|
|
await userEvent.click(screen.getByRole("button"))
|
|
|
|
// Bad
|
|
fireEvent.click(getByTestId("submit-btn"))
|
|
```
|
|
|
|
2. **Use Semantic Queries**
|
|
|
|
```tsx
|
|
screen.getByRole("button", { name: /submit/i })
|
|
screen.getByLabelText("Email")
|
|
```
|
|
|
|
3. **Test Accessibility**
|
|
- Always include axe tests
|
|
- Test keyboard navigation
|
|
- Verify ARIA attributes
|
|
|
|
4. **Mock External Dependencies**
|
|
|
|
```tsx
|
|
vi.mock("@/api", () => ({
|
|
fetchData: vi.fn(() => Promise.resolve(mockData)),
|
|
}))
|
|
```
|
|
|
|
5. **Use Data Attributes for Test IDs**
|
|
```tsx
|
|
<Button data-testid="submit-btn">Submit</Button>
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Tests Fail Intermittently
|
|
|
|
- Use `waitFor` for async operations
|
|
- Increase timeout for slow operations
|
|
- Check for race conditions
|
|
|
|
### Snapshot Tests Break Often
|
|
|
|
- Use visual regression instead
|
|
- Keep snapshots small and focused
|
|
- Update snapshots carefully
|
|
|
|
### Playwright Can't Find Elements
|
|
|
|
- Wait for navigation: `await page.waitForURL()`
|
|
- Wait for selectors: `await page.waitForSelector()`
|
|
- Use more specific selectors
|
|
|
|
## Next Steps
|
|
|
|
- [View test examples](https://github.com/hanzoai/ui/tree/main/tests)
|
|
- [Learn about CI/CD](/docs/deployment)
|
|
- [Accessibility guide](/docs/accessibility)
|