π Vue
Run Vue component tests with a real DOM environment: zero config, automatic Single File Component handling, and optional render metrics.
PluginHistory
| Version | Changes |
|---|---|
| v1.1.0 | |
| v1.0.0 |
What is @pokujs/vue?β
@pokujs/vue is a Poku plugin that lets you render and assert on Vue components directly inside Poku tests. It automatically:
- Sets up a DOM environment (happy-dom or jsdom) per test process.
- Injects the Single File Component loader so
.vuefiles just work. - Exposes familiar Testing Library helpers (
render,screen,fireEvent,cleanup,renderHook). - Optionally collects render duration metrics across your entire test suite.
Installβ
npm i -D @pokujs/vue
Install at least one DOM adapter:
| |
Enabling the Pluginβ
Add vueTestingPlugin to your Poku configuration file:
// poku.config.js
import { vueTestingPlugin } from '@pokujs/vue/plugin';
import { defineConfig } from 'poku';
export default defineConfig({
plugins: [
vueTestingPlugin({
dom: 'happy-dom',
}),
],
});
Writing Testsβ
// tests/counter-button.test.ts
import { afterEach, assert, test } from 'poku';
import { cleanup, fireEvent, render, screen } from '@pokujs/vue';
import CounterButton from './CounterButton.vue';
afterEach(cleanup);
test('renders and updates an SFC component', async () => {
render(CounterButton, {
props: { initialCount: 1 },
});
assert.strictEqual(
screen.getByRole('heading', { name: 'Count: 1' }).textContent,
'Count: 1'
);
await fireEvent.click(screen.getByRole('button', { name: 'Increment' }));
assert.strictEqual(
screen.getByRole('heading', { name: 'Count: 2' }).textContent,
'Count: 2'
);
});
<!-- tests/CounterButton.vue -->
<script setup lang="ts">
import { ref } from 'vue';
const props = withDefaults(
defineProps<{
initialCount?: number;
}>(),
{ initialCount: 0 }
);
const count = ref(props.initialCount);
const increment = () => {
count.value += 1;
};
</script>
<template>
<section>
<h1>Count: {{ count }}</h1>
<button type="button" @click="increment">Increment</button>
</section>
</template>
No extra tsconfig flags, no manual loader setup. The plugin injects everything at test-process start.
DOM Adaptersβ
happy-dom (default)β
Fast, lightweight DOM implementation. Recommended for most component tests.
vueTestingPlugin({ dom: 'happy-dom' });
Requires happy-dom and @happy-dom/global-registrator as dev dependencies.
jsdomβ
Broader browser API compatibility. Use it when you depend on APIs that happy-dom does not yet implement.
vueTestingPlugin({ dom: 'jsdom' });
Requires jsdom as a dev dependency.
Custom DOM setupβ
Point to your own module that sets up window, document, and any other globals:
vueTestingPlugin({
dom: { setupModule: './tests/setup/custom.ts' },
});
The module is imported before each test process runs.
Compatibilityβ
Runtime Γ DOM Adapterβ
| Node.js β₯ 20 | Bun β₯ 1 | Deno β₯ 2 | |
|---|---|---|---|
| happy-dom | β | β | β |
| jsdom | β | β | β οΈ |
| custom setup | β | β | β |
jsdom under Deno may be unstable depending on Deno's npm compatibility layer for the current jsdom version. For Deno projects, prefer happy-dom.
Peer Versionsβ
| Package | Required |
|---|---|
| Vue | β₯ 3.4 |
| Poku | β₯ 4.1.0 |
Plugin Optionsβ
vueTestingPlugin({
/**
* DOM adapter to use.
* - 'happy-dom': fast, recommended for most tests (default)
* - 'jsdom': broader browser API coverage
* - { setupModule }: path to a custom DOM setup module
*/
dom: 'happy-dom',
/** Base URL assigned to the DOM environment. Defaults to 'http://localhost:3000/' */
domUrl: 'http://localhost:3000/',
/**
* Render metrics. Disabled by default.
* Pass `true` for defaults, or an object for fine-grained control.
*/
metrics: {
enabled: true,
topN: 5,
minDurationMs: 0,
reporter(summary) {
console.log(summary.topSlowest);
},
},
});
Testing Helpersβ
All helpers are exported from @pokujs/vue:
import { cleanup, fireEvent, render, renderHook, screen } from '@pokujs/vue';
render(component, options?)β
Renders a Vue component (a Single File Component or a render function) into a managed container appended to document.body. Returns DOM queries bound to the container plus rerender and unmount helpers.
const { getByText, rerender, unmount } = render(CounterButton, {
props: { initialCount: 0 },
});
Options:
| Option | Type | Description |
|---|---|---|
props | ComponentProps | Props passed to the rendered component. |
container | HTMLElement | Custom container element. Created automatically if omitted. |
baseElement | HTMLElement | Element queries are bound to. Defaults to document.body. |
wrapper | WrapperComponent | Wrap the rendered component (e.g. context providers). |
screenβ
Queries bound to document.body. All Testing Library queries are available.
screen.getByRole('button', { name: /submit/i });
screen.queryAllByText('Error');
fireEventβ
All Testing Library fireEvent methods, wrapped so Vue's reactivity flushes before the promise resolves. Each call is asynchronous (await it so the DOM reflects the update).
await fireEvent.click(screen.getByRole('button'));
await fireEvent.update(screen.getByRole('textbox'), 'hello');
cleanup()β
Unmounts all rendered components and removes owned containers. Call it in afterEach to keep tests isolated:
import { cleanup } from '@pokujs/vue';
import { afterEach } from 'poku';
afterEach(cleanup);
renderHook(composable, options?)β
Renders a Vue composable in an isolated harness and exposes result.current.
const { result, rerender } = renderHook(
({ initial }: { initial: boolean }) => useToggle(initial),
{ initialProps: { initial: true } }
);
assert.strictEqual(result.current.enabled.value, true);
await rerender({ initial: false });
result.current stays live after destructuring, so you keep reading the latest value across rerender calls.
Render Metricsβ
The plugin can record how long each render() call takes and report the slowest components at the end of the suite.
Quick enableβ
vueTestingPlugin({
dom: 'happy-dom',
metrics: true,
});
Default output at suite end:
[poku-vue-testing] Slowest component renders
- MyHeavyComponent in tests/heavy.test.ts: 24.31ms
- AnotherComponent in tests/list.test.ts: 11.07ms
Fine-grained controlβ
vueTestingPlugin({
dom: 'happy-dom',
metrics: {
enabled: true,
topN: 10, // show top 10 slowest renders
minDurationMs: 5, // ignore anything faster than 5 ms
reporter(summary) {
// custom reporting, e.g. write to a file, send to CI metrics
console.table(summary.topSlowest);
},
},
});
VueMetricsSummary shape:
type VueMetricsSummary = {
totalCaptured: number; // total renders recorded
totalReported: number; // renders that passed the filters
topSlowest: {
file: string;
componentName: string;
durationMs: number;
}[];
};
Using with isolation: 'none'β
When Poku runs tests in-process (isolation: 'none'), the plugin:
- Registers the Single File Component loader in-process (Node.js only).
- Imports the DOM setup module directly into the current process.
- Skips IPC wiring since all tests share the same process.
// poku.config.js
export default defineConfig({
isolation: 'none',
plugins: [vueTestingPlugin({ dom: 'happy-dom' })],
});
Custom DOM Setupβ
If you need full control over DOM setup (for example, to configure custom globals or mock browser APIs), provide a module path:
// tests/setup/custom.ts
import { GlobalRegistrator } from '@happy-dom/global-registrator';
GlobalRegistrator.register({ url: 'http://localhost:3000/' });
// poku.config.js
vueTestingPlugin({
dom: { setupModule: './tests/setup/custom.ts' },
});
Context Providers (Wrapper)β
Wrap rendered components in a provider using the wrapper option. This is useful for components that depend on provide/inject, themes, or global state:
import { render, screen } from '@pokujs/vue';
import ThemeLabel from './ThemeLabel.vue';
import ThemeWrapper from './ThemeWrapper.vue';
render(ThemeLabel, { wrapper: ThemeWrapper });
assert.strictEqual(screen.getByText('Theme: dark').textContent, 'Theme: dark');