Skip to main content
Vuetify0 is now in beta!
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

Testing

This guide covers testing patterns for v0 consumers — from environment setup through asserting errors, warnings, and locale-safe output.

Setup

v0 tests run on Vitest↗︎ with happy-dom↗︎ (or jsdom). Install the required packages:

bash
pnpm add -D vitest @vue/test-utils happy-dom

Configure Vitest to use happy-dom:

vitest.config.ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'happy-dom',
  },
})

Mocking Vue DI

Many v0 composables use provide / inject internally. Mock them at the top of any test file that exercises these composables:

ts
import { vi } from 'vitest'

vi.mock('vue', () => ({
  provide: vi.fn(),
  inject: vi.fn(),
}))

When the composable also calls hasInjectionContext() to guard against use outside a component, extend the mock to keep that gate truthy:

ts
vi.mock('vue', async () => {
  const actual = await vi.importActual('vue')
  return {
    ...actual,
    provide: vi.fn(),
    inject: vi.fn(),
    hasInjectionContext: vi.fn(() => true),
  }
})

Mocking plugins

Use global.plugins in @vue/test-utils mount options to install v0 plugins. This covers Stack, Theme, and any other plugin-based contexts:

ts
import { mount } from '@vue/test-utils'
import { createStackPlugin, createThemePlugin } from '@vuetify/v0'
import MyComponent from './MyComponent.vue'

const wrapper = mount(MyComponent, {
  global: {
    plugins: [createStackPlugin(), createThemePlugin()],
  },
})

Fake timers

Composables that schedule work via setTimeout or setInterval (such as useTimer, useDelay, and usePopover) require fake timers. Enable them in beforeEach and restore in afterEach:

ts
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
import { useTimer } from '@vuetify/v0'

beforeEach(() => {
  vi.useFakeTimers()
})

afterEach(() => {
  vi.restoreAllMocks()
})

it('should fire handler after duration', () => {
  const handler = vi.fn()
  const timer = useTimer(handler, { duration: 1000 })
  timer.start()
  vi.advanceTimersByTime(1000)
  expect(handler).toHaveBeenCalledTimes(1)
})

Asserting v0 errors

v0 throws structured errors that carry a typed code discriminant. Use isV0Error(err, code) to narrow the caught value and assert the payload shape:

ts
import { expect, it } from 'vitest'
import { isV0Error, useDate, V0Error } from '@vuetify/v0'

it('should throw V0_PLUGIN_MISSING when the date plugin is absent', () => {
  let caught: unknown
  try {
    useDate()
  } catch (err) {
    caught = err
  }

  expect(caught).toBeInstanceOf(V0Error)
  expect(isV0Error(caught, 'V0_PLUGIN_MISSING')).toBe(true)
  if (isV0Error(caught, 'V0_PLUGIN_MISSING')) {
    // TypeScript now knows caught.plugin exists
    expect(caught.plugin).toBe('createDatePlugin')
  }
})
Note

Plugin-backed composables differ on whether they throw. useDate() throws V0_PLUGIN_MISSING when no provider is installed, but composables that ship a fallback — such as useTheme() — return synthesized defaults instead of throwing. Assert against a composable that actually throws.

Error codes

CodeThrown when
V0_CONTEXT_MISSINGinject() returns nothing — required provider not installed
V0_PLUGIN_MISSINGA plugin that the composable depends on was not registered
V0_PALETTE_INVALID_SEEDAn invalid seed color was passed to a palette factory
V0_PALETTE_UNKNOWN_VARIANTAn unrecognised variant name was given to the Material palette
V0_ADAPTER_INSTANCE_MISSINGThe adapter composable was called without a mounted adapter instance

Asserting warnings

When a test intentionally triggers a v0 warning (for example, passing an invalid prop or registering a duplicate), capture it with vi.spyOn and assert it was called. The using form auto-restores the spy when the block exits:

ts
import { expect, it, vi } from 'vitest'

it('should warn on duplicate registration', () => {
  using spy = vi.spyOn(console, 'warn').mockImplementation(() => {})

  // ... code that triggers the warning ...

  expect(spy).toHaveBeenCalledTimes(1)
  expect(spy).toHaveBeenCalledWith(expect.stringContaining('duplicate'))
})
Note

The using keyword relies on TC39 Explicit Resource Management and requires target: "esnext" in tsconfig.json. If your setup does not support it, use const spy = vi.spyOn(...) and call spy.mockRestore() manually after the assertion.

Never silently swallow warnings — always assert that the spy was called.

SSR-only tests

vi.mock is hoisted and applies file-wide, so mixing client and SSR branches in the same file causes the mock to leak. Split SSR-only tests into a sibling *.ssr.test.ts file and hoist the IN_BROWSER mock there:

index.ssr.test.ts
// Must be the first statement — vi.mock is hoisted
vi.mock('#v0/constants/globals', () => ({
  IN_BROWSER: false,
}))

import { expect, it } from 'vitest'
import { useDate } from './index'

it('should throw when called outside a component in SSR', () => {
  expect(() => useDate()).toThrow('[v0] useDate() must be called inside a Vue component')
})

The corresponding client tests live in index.test.ts and are unaffected by the SSR mock.

Locale-safe assertions

v0 formats user-visible strings through the locale system. Pin assertions to structure, not English text, so tests pass under any locale:

ts
// Right — locale-safe
expect(wrapper.find('[aria-label]').element.getAttribute('aria-label')).toBeDefined()

// Wrong — breaks under non-English locales
expect(wrapper.find('[aria-label]').element.getAttribute('aria-label')).toBe('Close dialog')
Was this page helpful?

Ctrl+/