Framework Integration
TWD is designed to work with any Vite-based application. Currently, react, vue, angular and solid.js are supported, and the library can be adapted to work with other build tools and frameworks.
React
TWD works seamlessly with any Vite-based React application. We recommend using the twd() Vite plugin — it auto-loads the sidebar and discovers test files in dev with no entry-file changes. Manual setup is available for projects that need full control.
View React Examples - Multiple React examples available in the repository.
Recommended: Vite Plugin
Add the twd() plugin to your vite.config.ts:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { twd } from 'twd-js/vite-plugin';
export default defineConfig({
plugins: [
react(),
twd({
testFilePattern: '/**/*.twd.test.{ts,tsx}',
open: true,
position: 'left',
serviceWorker: true, // Enable request mocking (default: true)
serviceWorkerUrl: '/mock-sw.js', // Custom service worker path (default: '/mock-sw.js')
}),
],
});The plugin only runs in vite dev (apply: 'serve') — it's a no-op in production builds.
twd() Plugin Options
The twd() plugin accepts the following options:
testFilePattern(string, optional) - Glob pattern for discovering test files. Default:'/**/*.twd.test.ts'open(boolean, optional) - Whether the sidebar is open by default. Default:trueposition("left" | "right", optional) - Sidebar position. Default:"left"serviceWorker(boolean, optional) - Whether to initialize request mocking. Default:trueserviceWorkerUrl(string, optional) - Custom path to the service worker file. Default:'/mock-sw.js'theme(Partial<TWDTheme>, optional) - Custom theme configuration. See Theming for details.search(boolean, optional) - Whether to show the search/filter input in the sidebar. Default:false
Examples:
// Minimal setup — uses all defaults
twd();
// Custom sidebar configuration
twd({ open: false, position: 'right' });
// Custom test file pattern
twd({ testFilePattern: '/**/*.spec.{ts,tsx}' });
// Disable request mocking
twd({ serviceWorker: false });
// Enable test filtering in the sidebar
twd({ search: true });
// All options together
twd({
testFilePattern: '/**/*.twd.test.{ts,tsx}',
open: true,
position: 'right',
serviceWorker: true,
serviceWorkerUrl: '/my-mock-sw.js',
theme: { primary: '#2563eb', background: '#ffffff' },
});Theming
Learn more about customizing the TWD sidebar appearance in the Theming guide.
Alternative: Manual Bundled Setup
If you need full control, you can call initTWD directly in your entry file instead of using the plugin. This is the same code the plugin runs internally:
// src/main.tsx
if (import.meta.env.DEV) {
const { initTWD } = await import('twd-js/bundled');
const tests = import.meta.glob('./**/*.twd.test.ts');
initTWD(tests, {
open: true,
position: 'left',
serviceWorker: true,
serviceWorkerUrl: '/mock-sw.js',
});
}initTWD accepts the same options as the plugin (minus testFilePattern, which is handled by import.meta.glob here). Use this approach when you need conditional init, custom test discovery, or any logic the plugin doesn't expose.
Alternative: Standard Setup (React Only)
The standard setup gives full control over the React root and request-mocking lifecycle. React-only.
// src/main.tsx
if (import.meta.env.DEV) {
const testModules = import.meta.glob("./**/*.twd.test.ts");
const { initTests, twd, TWDSidebar } = await import('twd-js');
// You need to pass the test modules, the sidebar component, and createRoot function
initTests(testModules, <TWDSidebar open={true} position="left" />, createRoot);
// Initialize request mocking (optional)
twd.initRequestMocking().catch(console.error);
}TIP
For Vue, Solid.js, and other Vite-based frameworks, use the twd() plugin (recommended) or the manual bundled setup. The standard setup above is React-only.
Testing shadcn Components
If you're using shadcn/ui components in your React application, we've created a comprehensive guide with TWD patterns specifically for testing shadcn components:
shadcn Testing Guide - Patterns and best practices for testing shadcn/ui components with TWD.
Vue
For Vue applications, use the twd() Vite plugin. The plugin is framework-agnostic and the bundled version it uses internally ships React separately, so it doesn't conflict with your Vue runtime.
Vue Example Repository - Complete working example with advanced scenarios.
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { twd } from 'twd-js/vite-plugin';
export default defineConfig({
plugins: [
vue(),
twd({
testFilePattern: '/**/*.twd.test.ts',
open: true,
position: 'left',
}),
],
});Your src/main.ts stays untouched — no initTWD import needed:
// src/main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');Alternative: Manual Bundled Setup (Vue)
If you can't use the Vite plugin, fall back to manual initTWD in src/main.ts:
import { createApp } from 'vue';
import App from './App.vue';
if (import.meta.env.DEV) {
const { initTWD } = await import('twd-js/bundled');
const tests = import.meta.glob('./**/*.twd.test.ts');
initTWD(tests, { open: true, position: 'left' });
}
createApp(App).mount('#app');Solid
For Solid.js applications, use the twd() Vite plugin. The bundled version handles its React runtime internally and doesn't conflict with your Solid runtime.
Solid Example Repository - Complete Solid.js integration example.
// vite.config.ts
import { defineConfig } from 'vite';
import solid from 'vite-plugin-solid';
import { twd } from 'twd-js/vite-plugin';
export default defineConfig({
plugins: [
solid(),
twd({
testFilePattern: '/**/*.twd.test.ts',
open: true,
position: 'left',
}),
],
});Your src/main.tsx stays untouched:
// src/main.tsx
/* @refresh reload */
import { render } from 'solid-js/web';
import App from './App';
const root = document.getElementById('root');
if (!(root instanceof HTMLElement)) {
throw new Error('Root element not found.');
}
render(() => <App />, root);Notes for Solid
- This setup works with Solid + Vite applications.
- Solid Start compatibility has not been tested yet, but may work with similar configuration.
Angular
Angular CLI uses esbuild, not vanilla Vite, so the twd() Vite plugin doesn't apply. Angular projects use the manual bundled setup with initTWD. You'll typically need to build the tests object explicitly since Angular's build tooling doesn't support import.meta.glob the same way.
Angular Example Repository - Working Angular integration example.
// src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { App } from './app/app';
import { isDevMode } from '@angular/core';
if (isDevMode()) {
const { initTWD } = await import('twd-js/bundled');
// Define your test files manually or use a compatible glob importer
const tests = {
'./twd-tests/helloWorld.twd.test.ts': () => import('./twd-tests/helloWorld.twd.test'),
'./twd-tests/todoList.twd.test.ts': () => import('./twd-tests/todoList.twd.test'),
};
// Initialize TWD - request mocking is automatically initialized by default
initTWD(tests, { open: true, position: 'left' });
}
bootstrapApplication(App, appConfig)
.catch((err) => console.error(err));Create React App (CRA)
Create React App uses Webpack instead of Vite, so you'll need to use Webpack's require.context to load test files. Here's how to set it up:
// src/index.tsx (or your main entry file)
if (process.env.NODE_ENV === "development") {
// Use Webpack's context feature to load all test files
const context = require.context("./", true, /\.twd\.test\.ts$/);
// Build a Vite-like object of async importers
const testModules = {};
context.keys().forEach((key) => {
testModules[key] = async () => {
// Webpack requires modules synchronously, so wrap in Promise.resolve
return Promise.resolve(context(key));
};
});
const { initTests, twd, TWDSidebar } = await import('twd-js');
// You need to pass the test modules, the sidebar component, and createRoot function
initTests(testModules, <TWDSidebar open={true} position="left" />, createRoot);
// Optionally initialize request mocking
twd.initRequestMocking()
.then(() => {
console.log("Request mocking initialized");
})
.catch((err) => {
console.error("Error initializing request mocking:", err);
});
}Notes for CRA
- The test files will be loaded using Webpack's module system
- This approach also works for other Webpack-based React setups
Astro
Astro uses Vite under the hood, so you can register the twd() plugin via Astro's vite.plugins config block.
Astro Example - Astro + React integration example.
// astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import { twd } from 'twd-js/vite-plugin';
export default defineConfig({
integrations: [react()],
vite: {
plugins: [
twd({
testFilePattern: '/**/*.twd.test.{ts,tsx}',
open: true,
position: 'left',
}),
],
},
});The plugin handles test discovery, sidebar mounting, and HMR full-reload — no per-page component or useEffect boilerplate required.
React Router (Framework Mode)
TWD works with React Router Framework mode applications, including SSR mode with explicit loaders and actions. Using createRoutesStub, you can mock loaders and actions to validate your route behavior at the frontend boundary.
INFO
TWD focuses on client-side UI behavior. Server-side loaders and actions can be mocked with createRoutesStub and tested as pure functions separately.
Recommended: Testing Routes with createRoutesStub
The best way to test React Router Framework Mode applications is using React Router's createRoutesStub API. This approach allows you to:
- Mock routes with loaders and actions
- Create different scenarios per test
- Test frontend behavior realistically
- Test loaders and actions separately as pure functions
This separation of concerns makes testing straightforward, explicit, and predictable.
React Router + TWD Example - Complete working example with this approach.
Setup
- Add a dedicated testing route to your route configuration:
import { type RouteConfig, index, route } from "@react-router/dev/routes";
export default [
index("routes/home.tsx"),
route("todos", "routes/todolist.tsx"),
route("testing", "routes/testing-page.tsx"), // Add this route
] satisfies RouteConfig;- Create the testing page:
// app/routes/testing-page.tsx
let twdInitialized = false;
export async function clientLoader() {
if (import.meta.env.DEV) {
const testModules = import.meta.glob("../**/*.twd.test.{ts,tsx}");
if (!twdInitialized) {
const { initTWD } = await import('twd-js/bundled');
initTWD(testModules, {
serviceWorker: false, // Disable service worker if not needed
});
twdInitialized = true;
}
return {};
} else {
return {};
}
}
export default function TestPage() {
return (
<div data-testid="testing-page">
<h1 style={{ opacity: 0.5, fontFamily: 'system-ui, sans-serif' }}>TWD Test Page</h1>
<p style={{ opacity: 0.5, fontFamily: 'system-ui, sans-serif' }}>
This page is used as a mounting point for TWD tests.
</p>
</div>
);
}- Create a test helper to set up the React root:
// test-utils/setup-react-root.tsx
import { createRoot } from "react-dom/client";
import { twd, screenDomGlobal } from "twd-js";
let root: ReturnType<typeof createRoot> | undefined;
export async function setupReactRoot() {
if (root) {
root.unmount();
root = undefined;
}
// Navigate to the empty test page
await twd.visit('/testing');
// Get the container from the test page
const container = await screenDomGlobal.findByTestId('testing-page');
root = createRoot(container);
return root;
}Example Test
import { createRoutesStub } from "@react-router/dev/routes";
import { useLoaderData, useParams, useMatches } from "react-router";
import { createRoot } from "react-dom/client";
import { setupReactRoot } from "../test-utils/setup-react-root";
import { twd, screenDom } from "twd-js";
import { describe, it, beforeEach } from "twd-js/runner";
import Home from "../routes/home";
describe("Home Page Test", () => {
let root: ReturnType<typeof createRoot> | undefined;
beforeEach(async () => {
root = await setupReactRoot();
});
it("should render home page with loader data", async () => {
// Create a route stub with mocked loader
const Stub = createRoutesStub([
{
path: "/",
Component: () => {
const loaderData = useLoaderData();
const params = useParams();
const matches = useMatches() as any;
return <Home loaderData={loaderData} params={params} matches={matches} />;
},
loader() {
return { title: "Home Page test" };
},
},
]);
// Render the stub
root!.render(<Stub />);
await twd.wait(300);
// Assert the rendered content
const h1 = await screenDom.findByRole('heading', { level: 1 });
twd.should(h1, 'have.text', 'Home Page test');
});
});This approach validates React Router routes at the frontend boundary — loaders and actions are mocked, so you're testing how your UI responds to data, not whether the backend returns it correctly.
Framework Support Philosophy
TWD is designed for deterministic frontend boundary validation. It focuses on frameworks that provide:
- Explicit execution - Clear control over when and how components render
- Deterministic behavior - Predictable rendering and state management
- Fast feedback loops - Quick test execution and hot module replacement
TWD works with SPAs and frameworks where the UI renders client-side and external dependencies can be explicitly isolated. This includes SSR frameworks like React Router where loaders and actions are explicit and testable at the boundary. Server-component-first architectures like Next.js App Router, where rendering, data loading, and infrastructure are implicitly mixed, blur the ownership boundary and are not compatible with TWD's validation model.
TWD officially supports:
- React (SPA) - Standard Vite-based React applications
- React Router (Framework Mode) - Including SSR mode, with explicit loaders and
createRoutesStub - Vue, Angular, Solid.js - Other SPA frameworks
- Astro - When used with client-driven components
Other Frameworks
We're actively working on adding more framework recipes and integrations. If you're using a framework not listed here:
- Check if it's Vite-based - If so, the standard Vite setup should work
- Check if it uses Webpack - Adapt the CRA setup above
- Browse our examples directory - See working examples for multiple frameworks
- Share your setup - We'd love to hear about your integration! Open an issue or start a discussion
Framework Support Roadmap
We plan to add official support and documentation for:
- Svelte - Framework support in development
Getting Help
If you're having trouble integrating TWD with your framework:
- 📖 Check the Getting Started Guide for the standard setup
- 📚 Review the Installation Tutorial for step-by-step instructions
- 🐛 Report issues if you encounter problems
- 💬 Join discussions to share your setup or ask questions