Initial commit

This commit is contained in:
2026-04-17 23:26:01 +00:00
commit 2ea4ca5d52
409 changed files with 63459 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
# End-to-End Testing with Playwright Test Runner
## Installation
```bash
pnpm add -D @playwright/test @nuxt/test-utils
```
## Configuration
```ts
// playwright.config.ts
import { fileURLToPath } from 'node:url'
import { defineConfig, devices } from '@playwright/test'
import type { ConfigOptions } from '@nuxt/test-utils/playwright'
export default defineConfig<ConfigOptions>({
use: {
nuxt: {
rootDir: fileURLToPath(new URL('.', import.meta.url)),
},
},
// ...
})
```
## Writing Tests
Import `expect` and `test` from `@nuxt/test-utils/playwright` (not from `@playwright/test`):
```ts
// tests/example.test.ts
import { expect, test } from '@nuxt/test-utils/playwright'
test('test', async ({ page, goto }) => {
await goto('/', { waitUntil: 'hydration' })
await expect(page.getByRole('heading')).toHaveText('Welcome to Playwright!')
})
```
The `goto` helper is Nuxt-aware and supports `waitUntil: 'hydration'`.
## Per-File Nuxt Configuration
Override Nuxt config directly in a test file:
```ts
import { fileURLToPath } from 'node:url'
import { expect, test } from '@nuxt/test-utils/playwright'
test.use({
nuxt: {
rootDir: fileURLToPath(new URL('..', import.meta.url)),
},
})
test('test', async ({ page, goto }) => {
await goto('/', { waitUntil: 'hydration' })
await expect(page.getByRole('heading')).toHaveText('Welcome to Playwright!')
})
```

View File

@@ -0,0 +1,109 @@
# End-to-End Testing with Vitest
## Setup
In each `describe` block, call `setup()` before tests to initialize the Nuxt test environment:
```ts
import { describe, test } from 'vitest'
import { $fetch, setup } from '@nuxt/test-utils/e2e'
describe('My test', async () => {
await setup({
// test context options
})
test('my test', () => {
// ...
})
})
```
`setup` performs tasks in `beforeAll`, `beforeEach`, `afterEach`, and `afterAll`.
## Setup Options
### Nuxt Config
- `rootDir`: Path to Nuxt app directory (default: `'.'`)
- `configFile`: Configuration file name (default: `'nuxt.config'`)
### Timings
- `setupTimeout`: Time in ms for setup to complete (default: `120000`, `240000` on Windows)
- `teardownTimeout`: Time in ms for teardown (default: `30000`)
### Features
- `build`: Run a separate build step (default: `true`, `false` if browser/server disabled or host provided)
- `server`: Launch a server for requests (default: `true`, `false` if host provided)
- `port`: Fixed test server port (default: `undefined`, auto-assigned)
- `host`: URL to test against instead of building a new server (useful for testing deployed apps)
- `browser`: Launch a Playwright browser instance (default: `false`)
- `browserOptions`:
- `type`: `'chromium'` | `'firefox'` | `'webkit'`
- `launch`: Playwright launch options
- `runner`: Test runner (`'vitest'` | `'jest'` | `'cucumber'`, default: `'vitest'`)
## API Helpers
### `$fetch(url)`
Get the HTML of a server-rendered page:
```ts
import { $fetch } from '@nuxt/test-utils/e2e'
const html = await $fetch('/')
```
### `fetch(url)`
Get the full response:
```ts
import { fetch } from '@nuxt/test-utils/e2e'
const res = await fetch('/')
const { body, headers } = res
```
### `url(path)`
Get the full URL including the test server port:
```ts
import { url } from '@nuxt/test-utils/e2e'
const pageUrl = url('/page')
// 'http://localhost:6840/page'
```
### `createPage(url)`
Create a Playwright browser page (requires `browser: true` in setup):
```ts
import { createPage } from '@nuxt/test-utils/e2e'
const page = await createPage('/page')
// Full Playwright Page API available
```
## Target Host Example
Test against a running server instead of building:
```ts
import { createPage, setup } from '@nuxt/test-utils/e2e'
import { describe, expect, it } from 'vitest'
describe('login page', async () => {
await setup({
host: 'http://localhost:8787',
})
it('displays the email and password fields', async () => {
const page = await createPage('/login')
expect(await page.getByTestId('email').isVisible()).toBe(true)
expect(await page.getByTestId('password').isVisible()).toBe(true)
})
})
```

View File

@@ -0,0 +1,147 @@
# Installation & Configuration
## Dependencies
```bash
pnpm add -D @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
```
## Nuxt Config
Add the test utils module for Vitest integration in DevTools (optional):
```ts
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@nuxt/test-utils/module',
],
})
```
## Vitest Config (Project-Based Setup)
Use Vitest projects for fine-grained control over which tests run in which environment:
```ts
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import { defineVitestProject } from '@nuxt/test-utils/config'
export default defineConfig({
test: {
projects: [
{
test: {
name: 'unit',
include: ['test/unit/*.{test,spec}.ts'],
environment: 'node',
},
},
{
test: {
name: 'e2e',
include: ['test/e2e/*.{test,spec}.ts'],
environment: 'node',
},
},
await defineVitestProject({
test: {
name: 'nuxt',
include: ['test/nuxt/*.{test,spec}.ts'],
environment: 'nuxt',
},
}),
],
},
})
```
**Important:** Requires `"type": "module"` in `package.json`, or rename to `vitest.config.m{ts,js}`.
## Alternative: Simple Setup
If all tests should run in the Nuxt environment:
```ts
// vitest.config.ts
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
test: {
environment: 'nuxt',
// environmentOptions: {
// nuxt: {
// domEnvironment: 'happy-dom', // 'happy-dom' (default) or 'jsdom'
// overrides: {
// // other Nuxt config
// }
// }
// }
},
})
```
Opt out of the Nuxt environment per test file with a comment:
```ts
// @vitest-environment node
import { test } from 'vitest'
test('my test', () => {
// runs without Nuxt environment
})
```
## Recommended Test Directory Structure
```
test/
├── e2e/ # End-to-end tests (node environment)
│ └── ssr.test.ts
├── nuxt/ # Tests needing Nuxt runtime (nuxt environment)
│ ├── components.test.ts
│ └── composables.test.ts
└── unit/ # Pure unit tests (node environment)
└── utils.test.ts
```
## Running Tests
```bash
# Run all tests
npx vitest
# Run only unit tests
npx vitest --project unit
# Run only Nuxt tests
npx vitest --project nuxt
# Run tests in watch mode
npx vitest --watch
```
## Environment Variables
Use a `.env.test` file for test-specific environment variables.
## TypeScript Support
Test files in `test/nuxt/` and `tests/nuxt/` are automatically included in the Nuxt app TypeScript context, so they recognize `~/`, `@/`, `#imports` aliases and auto-imports.
To add other directories to the Nuxt TypeScript context:
```ts
// nuxt.config.ts
export default defineNuxtConfig({
typescript: {
tsConfig: {
include: [
// relative to generated .nuxt/tsconfig.json
'../test/other-nuxt-context/**/*',
],
},
},
})
```

View File

@@ -0,0 +1,206 @@
# Test Helpers
All helpers are imported from `@nuxt/test-utils/runtime`.
## `mountSuspended`
Mounts any Vue component within the Nuxt environment, allowing async setup and access to plugin injections. Wraps `@vue/test-utils` `mount` under the hood.
```ts
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { SomeComponent } from '#components'
it('can mount some component', async () => {
const component = await mountSuspended(SomeComponent)
expect(component.text()).toMatchInlineSnapshot('"This is an auto-imported component"')
})
```
Mount with a route:
```ts
import { mountSuspended } from '@nuxt/test-utils/runtime'
import App from '~/app.vue'
it('can mount an app', async () => {
const component = await mountSuspended(App, { route: '/test' })
expect(component.html()).toMatchInlineSnapshot(`
"<div>This is an auto-imported component</div>
<div> I am a global component </div>
<div>/</div>
<a href="/test"> Test link </a>"
`)
})
```
**Options:** Accepts all `@vue/test-utils` mount options plus:
- `route`: initial route string, or `false` to skip initial route change (default: `/`)
## `renderSuspended`
Renders a component using `@testing-library/vue` within the Nuxt environment. Requires `@testing-library/vue` as a dependency.
```ts
import { renderSuspended } from '@nuxt/test-utils/runtime'
import { SomeComponent } from '#components'
import { screen } from '@testing-library/vue'
it('can render some component', async () => {
await renderSuspended(SomeComponent)
expect(screen.getByText('This is an auto-imported component')).toBeDefined()
})
```
The component is rendered inside `<div id="test-wrapper"></div>`.
**Options:** Accepts all `@testing-library/vue` render options plus:
- `route`: initial route string, or `false` to skip initial route change (default: `/`)
## `mockNuxtImport`
Mocks Nuxt auto-imported functions. This is a macro that transforms to `vi.mock` (hoisted).
**Can only be used once per mocked import per test file.**
```ts
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport('useState', () => {
return () => {
return { value: 'mocked storage' }
}
})
```
### With type safety and original implementation
```ts
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport<typeof useState>('useState', (original) => {
return (...args) => {
return { ...original('some-key'), value: 'mocked state' }
}
})
// Or pass the function directly
mockNuxtImport(useState, (original) => {
return (...args) => {
return { ...original('some-key'), value: 'mocked state' }
}
})
```
### Different implementations per test
Use `vi.hoisted` to create mutable mocks:
```ts
import { vi } from 'vitest'
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
const { useStateMock } = vi.hoisted(() => {
return {
useStateMock: vi.fn(() => {
return { value: 'mocked storage' }
}),
}
})
mockNuxtImport('useState', () => {
return useStateMock
})
// In a test:
useStateMock.mockImplementation(() => {
return { value: 'something else' }
})
```
### Alternative: per-test mocking with vi.fn
```ts
import { beforeEach, vi } from 'vitest'
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport(useRoute, original => vi.fn(original))
beforeEach(() => {
vi.resetAllMocks()
})
// In a test:
const useRouteOriginal = vi.mocked(useRoute).getMockImplementation()!
vi.mocked(useRoute).mockImplementation(
(...args) => ({ ...useRouteOriginal(...args), path: '/mocked' }),
)
```
## `mockComponent`
Mocks a Nuxt component by PascalCase name or relative path.
```ts
import { mockComponent } from '@nuxt/test-utils/runtime'
// By component name
mockComponent('MyComponent', {
props: {
value: String,
},
setup(props) {
// ...
},
})
// By path with factory function
mockComponent('~/components/my-component.vue', () => {
return defineComponent({
setup(props) {
// ...
},
})
})
// Redirect to a mock SFC
mockComponent('MyComponent', () => import('./MockComponent.vue'))
```
**Note:** Cannot reference local variables in the factory function (hoisted). Import dependencies inside the factory:
```ts
mockComponent('MyComponent', async () => {
const { ref, h } = await import('vue')
return defineComponent({
setup(props) {
const counter = ref(0)
return () => h('div', null, counter.value)
},
})
})
```
## `registerEndpoint`
Creates a mock Nitro endpoint that returns test data.
```ts
import { registerEndpoint } from '@nuxt/test-utils/runtime'
// Simple GET endpoint
registerEndpoint('/test/', () => ({
test: 'test-field',
}))
// With specific HTTP method
registerEndpoint('/test/', {
method: 'POST',
handler: () => ({ test: 'test-field' }),
})
```
Options object properties:
- `handler`: event handler function
- `method`: (optional) HTTP method to match (e.g., 'GET', 'POST')
- `once`: (optional) if true, handler is removed after first matching request

View File

@@ -0,0 +1,33 @@
# Built-In Mocks
`@nuxt/test-utils` provides built-in mocks for browser APIs in the DOM test environment.
## `intersectionObserver`
- **Default:** `true`
- Creates a dummy class without any functionality for the IntersectionObserver API.
## `indexedDB`
- **Default:** `false`
- Uses [`fake-indexeddb`](https://github.com/dumbmatter/fakeIndexedDB) to create a functional mock of the IndexedDB API.
## Configuration
```ts
// vitest.config.ts
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
test: {
environmentOptions: {
nuxt: {
mock: {
intersectionObserver: true,
indexedDb: true,
},
},
},
},
})
```

View File

@@ -0,0 +1,49 @@
# Nuxt Runtime Environment
## Overview
The Nuxt test environment initializes a global Nuxt app before tests run, including plugins and `app.vue`. This gives tests access to auto-imports, composables, and injections.
Tests run in `happy-dom` (default) or `jsdom`. Be careful not to mutate global state, or reset it afterwards.
## Environment Separation
- **`test/unit/`** — Node environment, fast, no Nuxt runtime. For pure logic, utilities, helpers.
- **`test/nuxt/`** — Nuxt environment, has access to auto-imports, composables, plugins. For components and composables that depend on Nuxt.
- **`test/e2e/`** — Node environment, launches a full Nuxt server. For end-to-end testing.
**Important:** `@nuxt/test-utils/runtime` and `@nuxt/test-utils/e2e` cannot be used in the same file — they need different environments.
## Naming Convention for Mixed Setups
If using the simple (non-project) config, separate by file extension:
- `app.nuxt.spec.ts` — runs in Nuxt environment (uses `@nuxt/test-utils/runtime`)
- `app.e2e.spec.ts` — runs in Node environment (uses `@nuxt/test-utils/e2e`)
Or use per-file environment comments:
```ts
// @vitest-environment nuxt
```
## Environment Options
Configure in `vitest.config.ts`:
```ts
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
test: {
environmentOptions: {
nuxt: {
domEnvironment: 'happy-dom', // or 'jsdom'
overrides: {
// Nuxt config overrides for testing
},
},
},
},
})
```