Initial testing framework
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with: { node-version: 20 }
|
||||
- run: npm ci
|
||||
- run: npx playwright install --with-deps
|
||||
- name: Unit & integration tests (Vitest)
|
||||
run: npm test
|
||||
- name: Storybook a11y (axe via test-runner)
|
||||
run: npm run test:sb
|
||||
- name: E2E (Playwright)
|
||||
run: npm run e2e:serve
|
||||
- name: Performance (Lighthouse CI)
|
||||
run: npm run lhci
|
||||
@@ -34,6 +34,9 @@ const preview = {
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
a11y: { element: '#storybook-root', manual: false },
|
||||
viewport: { defaultViewport: 'responsive' },
|
||||
chromatic: { viewports: [360, 768, 1024, 1440] } // breakpoints
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"ci": {
|
||||
"collect": {
|
||||
"startServerCommand": "npm run preview",
|
||||
"url": ["http://localhost:3000/"],
|
||||
"numberOfRuns": 2
|
||||
},
|
||||
"assert": {
|
||||
"assertions": {
|
||||
"categories:performance": ["error", {"minScore": 0.9}],
|
||||
"categories:accessibility": ["warn", {"minScore": 0.95}],
|
||||
"first-contentful-paint": ["warn", {"maxNumericValue": 2000}],
|
||||
"interactive": ["warn", {"maxNumericValue": 3000}]
|
||||
}
|
||||
},
|
||||
"upload": { "target": "temporary-public-storage" }
|
||||
}
|
||||
}
|
||||
Generated
+8879
-29
File diff suppressed because it is too large
Load Diff
+23
-2
@@ -11,7 +11,16 @@
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"storybook:local": "cp .storybook/main.local.js .storybook/main.js && cp .storybook/preview.local.js .storybook/preview.js && storybook dev -p 6006",
|
||||
"storybook:restore": "cp .storybook/main.github.js .storybook/main.js && cp .storybook/preview.github.js .storybook/preview.js",
|
||||
"build-storybook": "storybook build"
|
||||
"build-storybook": "storybook build",
|
||||
"test": "vitest run --coverage",
|
||||
"test:watch": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:sb": "storybook dev -p 6006 & wait-on http://localhost:6006 && test-storybook",
|
||||
"e2e": "playwright test",
|
||||
"e2e:ui": "playwright test --ui",
|
||||
"lhci": "lhci autorun",
|
||||
"preview": "next build && next start -p 3000",
|
||||
"e2e:serve": "start-server-and-test preview http://localhost:3000 e2e"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "15.2.4",
|
||||
@@ -19,6 +28,7 @@
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "^4.10.2",
|
||||
"@chromatic-com/storybook": "^4.1.0",
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@storybook/addon-a11y": "^9.1.2",
|
||||
@@ -28,17 +38,28 @@
|
||||
"@storybook/addon-viewport": "^9.0.8",
|
||||
"@storybook/addon-vitest": "^9.1.2",
|
||||
"@storybook/nextjs-vite": "^9.1.2",
|
||||
"@storybook/test": "^8.6.14",
|
||||
"@storybook/test-runner": "^0.23.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@vitejs/plugin-react": "^5.0.2",
|
||||
"@vitest/browser": "^3.2.4",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.2.0",
|
||||
"eslint-plugin-storybook": "^9.1.2",
|
||||
"jsdom": "^26.1.0",
|
||||
"lighthouse-ci": "^1.13.1",
|
||||
"msw": "^2.10.5",
|
||||
"playwright": "^1.54.2",
|
||||
"postcss": "^8.5.6",
|
||||
"start-server-and-test": "^2.0.13",
|
||||
"storybook": "^9.1.2",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^3.2.4",
|
||||
"wait-on": "^8.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: 'tests/e2e',
|
||||
timeout: 60_000,
|
||||
expect: { timeout: 10_000 },
|
||||
fullyParallel: true,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
reporter: [['list'], ['html', { open: 'never' }]],
|
||||
use: {
|
||||
baseURL: 'http://localhost:3000',
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
video: 'retain-on-failure'
|
||||
},
|
||||
webServer: {
|
||||
command: 'npm run preview',
|
||||
url: 'http://localhost:3000',
|
||||
reuseExistingServer: !process.env.CI
|
||||
},
|
||||
projects: [
|
||||
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
||||
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
|
||||
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
|
||||
{ name: 'mobile', use: { ...devices['iPhone 13'] } }
|
||||
]
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { injectAxe, checkA11y } from '@axe-core/playwright';
|
||||
export async function runA11y(page, options = {}) {
|
||||
await injectAxe(page);
|
||||
await checkA11y(page, undefined, { detailedReport: true, detailedReportOptions: { html: true }, ...options });
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { runA11y } from './axe';
|
||||
|
||||
test('home loads, nav works, basic a11y passes', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Check that the page loads
|
||||
await expect(page).toHaveTitle(/Community Rule/);
|
||||
|
||||
// Check for main navigation elements
|
||||
await expect(page.getByRole('banner')).toBeVisible();
|
||||
|
||||
// Check for main content
|
||||
await expect(page.getByRole('main')).toBeVisible();
|
||||
|
||||
// Check for footer
|
||||
await expect(page.getByRole('contentinfo')).toBeVisible();
|
||||
});
|
||||
|
||||
test('keyboard navigation works', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Tab through the page
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
// Check that focus is visible
|
||||
await expect(page.locator(':focus')).toBeVisible();
|
||||
|
||||
// Continue tabbing to test navigation
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await page.keyboard.press('Tab');
|
||||
await expect(page.locator(':focus')).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('accessibility standards are met', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Run automated a11y checks
|
||||
await runA11y(page, {
|
||||
axeOptions: {
|
||||
runOnly: ['wcag2a', 'wcag2aa']
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('responsive design works', async ({ page }) => {
|
||||
// Test mobile viewport
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await page.goto('/');
|
||||
|
||||
// Check that content is visible on mobile
|
||||
await expect(page.getByRole('main')).toBeVisible();
|
||||
|
||||
// Test tablet viewport
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
await expect(page.getByRole('main')).toBeVisible();
|
||||
|
||||
// Test desktop viewport
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await expect(page.getByRole('main')).toBeVisible();
|
||||
});
|
||||
|
||||
test('images have alt text', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Get all images
|
||||
const images = page.locator('img');
|
||||
const imageCount = await images.count();
|
||||
|
||||
// Check that all images have alt attributes
|
||||
for (let i = 0; i < imageCount; i++) {
|
||||
const image = images.nth(i);
|
||||
const alt = await image.getAttribute('alt');
|
||||
expect(alt).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
test('links are accessible', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Get all links
|
||||
const links = page.locator('a');
|
||||
const linkCount = await links.count();
|
||||
|
||||
// Check that all links have either text content or aria-label
|
||||
for (let i = 0; i < linkCount; i++) {
|
||||
const link = links.nth(i);
|
||||
const text = await link.textContent();
|
||||
const ariaLabel = await link.getAttribute('aria-label');
|
||||
|
||||
// Link should have either text content or aria-label
|
||||
expect(text?.trim() || ariaLabel).toBeTruthy();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,156 @@
|
||||
import { render, screen, cleanup } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { vi, describe, test, expect, afterEach } from "vitest";
|
||||
import ContentLockup from "../../app/components/ContentLockup";
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
describe("ContentLockup Integration", () => {
|
||||
test("renders hero variant with all content", () => {
|
||||
render(
|
||||
<ContentLockup
|
||||
variant="hero"
|
||||
title="Welcome"
|
||||
subtitle="Get Started"
|
||||
description="This is a description"
|
||||
ctaText="Get Started"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Welcome" })
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Get Started" })
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText("This is a description")).toBeInTheDocument();
|
||||
expect(screen.getAllByRole("button", { name: "Get Started" })).toHaveLength(
|
||||
3
|
||||
);
|
||||
});
|
||||
|
||||
test("renders feature variant with link", () => {
|
||||
render(
|
||||
<ContentLockup
|
||||
variant="feature"
|
||||
title="Feature Title"
|
||||
subtitle="Feature Subtitle"
|
||||
description="Feature description"
|
||||
linkText="Learn More"
|
||||
linkHref="/learn"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Feature Title" })
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("link", { name: "Learn More" })
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole("link", { name: "Learn More" })).toHaveAttribute(
|
||||
"href",
|
||||
"/learn"
|
||||
);
|
||||
});
|
||||
|
||||
test("renders ask variant with simplified structure", () => {
|
||||
render(
|
||||
<ContentLockup
|
||||
variant="ask"
|
||||
title="Ask Question"
|
||||
subtitle="Ask subtitle"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Ask Question" })
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Ask subtitle" })
|
||||
).toBeInTheDocument();
|
||||
// Ask variant should not have description or CTA
|
||||
expect(screen.queryByRole("button")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("handles left alignment", () => {
|
||||
render(
|
||||
<ContentLockup
|
||||
variant="ask"
|
||||
title="Left Aligned"
|
||||
subtitle="Subtitle"
|
||||
alignment="left"
|
||||
/>
|
||||
);
|
||||
|
||||
const container = screen
|
||||
.getByRole("heading", { name: "Left Aligned" })
|
||||
.closest("div");
|
||||
expect(container).toHaveClass("justify-start");
|
||||
});
|
||||
|
||||
test("renders responsive buttons correctly", () => {
|
||||
render(
|
||||
<ContentLockup variant="hero" title="Responsive" ctaText="Click Me" />
|
||||
);
|
||||
|
||||
// Should render all three button variants for different breakpoints
|
||||
const buttons = screen.getAllByRole("button", { name: "Click Me" });
|
||||
expect(buttons).toHaveLength(3);
|
||||
});
|
||||
|
||||
test("applies custom button className", () => {
|
||||
render(
|
||||
<ContentLockup
|
||||
variant="hero"
|
||||
title="Custom Button"
|
||||
ctaText="Custom"
|
||||
buttonClassName="custom-button-class"
|
||||
/>
|
||||
);
|
||||
|
||||
const buttons = screen.getAllByRole("button", { name: "Custom" });
|
||||
// The large button (md breakpoint) should have the custom class
|
||||
expect(buttons[1]).toHaveClass("custom-button-class");
|
||||
});
|
||||
|
||||
test("handles missing optional props gracefully", () => {
|
||||
render(<ContentLockup variant="hero" title="Minimal" />);
|
||||
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Minimal" })
|
||||
).toBeInTheDocument();
|
||||
// Should not crash without subtitle, description, or CTA
|
||||
expect(screen.queryByRole("button")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("renders decorative shape for hero variant", () => {
|
||||
render(<ContentLockup variant="hero" title="Hero with Shape" />);
|
||||
|
||||
const shape = screen.getByAltText("Decorative shapes");
|
||||
expect(shape).toBeInTheDocument();
|
||||
expect(shape).toHaveAttribute("src", "assets/Shapes_1.svg");
|
||||
});
|
||||
|
||||
test("does not render shape for non-hero variants", () => {
|
||||
render(<ContentLockup variant="feature" title="Feature without Shape" />);
|
||||
|
||||
expect(screen.queryByAltText("Decorative shapes")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("link has proper accessibility attributes", () => {
|
||||
render(
|
||||
<ContentLockup
|
||||
variant="feature"
|
||||
title="Accessible"
|
||||
linkText="Accessible Link"
|
||||
linkHref="/accessible"
|
||||
/>
|
||||
);
|
||||
|
||||
const link = screen.getByRole("link", { name: "Accessible Link" });
|
||||
expect(link).toHaveAttribute("href", "/accessible");
|
||||
expect(link).toHaveClass("focus:outline-none", "focus:ring-2");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
import { setupServer } from 'msw/node';
|
||||
export const server = setupServer(); // add handlers per test as needed
|
||||
@@ -0,0 +1,112 @@
|
||||
import { render, screen, cleanup } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { vi, describe, test, expect, afterEach } from "vitest";
|
||||
import Button from "../../app/components/Button";
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
describe("Button Component", () => {
|
||||
test("renders button with children", () => {
|
||||
render(<Button>Click me</Button>);
|
||||
const button = screen.getByRole("button", { name: "Click me" });
|
||||
expect(button).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("handles click events", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onClick = vi.fn();
|
||||
render(<Button onClick={onClick}>Click me</Button>);
|
||||
|
||||
const button = screen.getByRole("button", { name: "Click me" });
|
||||
await user.click(button);
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("renders as link when href is provided", () => {
|
||||
render(<Button href="/test">Link Button</Button>);
|
||||
const link = screen.getByRole("link", { name: "Link Button" });
|
||||
expect(link).toBeInTheDocument();
|
||||
expect(link).toHaveAttribute("href", "/test");
|
||||
});
|
||||
|
||||
test("applies different variants", () => {
|
||||
const { rerender } = render(<Button variant="default">Default</Button>);
|
||||
let button = screen.getByRole("button", { name: "Default" });
|
||||
expect(button.className).toContain(
|
||||
"bg-[var(--color-surface-inverse-primary)]"
|
||||
);
|
||||
|
||||
rerender(<Button variant="secondary">Secondary</Button>);
|
||||
button = screen.getByRole("button", { name: "Secondary" });
|
||||
expect(button.className).toContain("bg-transparent");
|
||||
});
|
||||
|
||||
test("applies different sizes", () => {
|
||||
const { rerender } = render(<Button size="xsmall">Small</Button>);
|
||||
let button = screen.getByRole("button", { name: "Small" });
|
||||
expect(button.className).toContain("px-[var(--spacing-scale-006)]");
|
||||
|
||||
rerender(<Button size="large">Large</Button>);
|
||||
button = screen.getByRole("button", { name: "Large" });
|
||||
expect(button.className).toContain("px-[var(--spacing-scale-012)]");
|
||||
});
|
||||
|
||||
test("handles disabled state", () => {
|
||||
render(<Button disabled>Disabled</Button>);
|
||||
const button = screen.getByRole("button", { name: "Disabled" });
|
||||
expect(button).toBeDisabled();
|
||||
expect(button).toHaveAttribute("aria-disabled", "true");
|
||||
expect(button).toHaveAttribute("tabindex", "-1");
|
||||
});
|
||||
|
||||
test("applies custom className", () => {
|
||||
render(<Button className="custom-class">Custom</Button>);
|
||||
const button = screen.getByRole("button", { name: "Custom" });
|
||||
expect(button.className).toContain("custom-class");
|
||||
});
|
||||
|
||||
test("applies aria-label for accessibility", () => {
|
||||
render(<Button aria-label="Custom label">Button</Button>);
|
||||
const button = screen.getByRole("button", { name: "Custom label" });
|
||||
expect(button).toHaveAttribute("aria-label", "Custom label");
|
||||
});
|
||||
|
||||
test("renders with design tokens", () => {
|
||||
render(<Button>Token Test</Button>);
|
||||
const button = screen.getByRole("button", { name: "Token Test" });
|
||||
|
||||
// Check that design tokens are applied
|
||||
expect(button.className).toContain(
|
||||
"rounded-[var(--radius-measures-radius-full)]"
|
||||
);
|
||||
expect(button.className).toContain(
|
||||
"bg-[var(--color-surface-inverse-primary)]"
|
||||
);
|
||||
});
|
||||
|
||||
test("handles keyboard navigation", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onClick = vi.fn();
|
||||
render(<Button onClick={onClick}>Keyboard</Button>);
|
||||
|
||||
const button = screen.getByRole("button", { name: "Keyboard" });
|
||||
button.focus();
|
||||
expect(button).toHaveFocus();
|
||||
|
||||
await user.keyboard("{Enter}");
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("does not render as link when disabled and href provided", () => {
|
||||
render(
|
||||
<Button href="/test" disabled>
|
||||
Disabled Link
|
||||
</Button>
|
||||
);
|
||||
const button = screen.getByRole("button", { name: "Disabled Link" });
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import React from "react";
|
||||
|
||||
function Thing() {
|
||||
return <div>ok</div>;
|
||||
}
|
||||
|
||||
describe("jsx in .js", () => {
|
||||
it("parses", () => {
|
||||
expect(Thing).toBeTypeOf("function");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import { describe, test, expect } from 'vitest';
|
||||
|
||||
describe('Simple Test', () => {
|
||||
test('should work', () => {
|
||||
expect(1 + 1).toBe(2);
|
||||
});
|
||||
});
|
||||
+27
-27
@@ -1,35 +1,35 @@
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { defineConfig } from "vitest/config";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
|
||||
|
||||
const dirname =
|
||||
typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
|
||||
export default defineConfig({
|
||||
test: {
|
||||
projects: [
|
||||
{
|
||||
extends: true,
|
||||
plugins: [
|
||||
// The plugin will run tests for the stories defined in your Storybook config
|
||||
// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
|
||||
storybookTest({ configDir: path.join(dirname, '.storybook') }),
|
||||
// Enables React transform
|
||||
react({ jsxRuntime: "automatic" }),
|
||||
],
|
||||
|
||||
// Key part: make .js be parsed as JSX *before* import-analysis
|
||||
esbuild: {
|
||||
jsx: "automatic",
|
||||
loader: "jsx", // default loader
|
||||
include: /(?:^|\/)(app|components|pages|src|tests)\/.*\.[jt]sx?$/, // match your folders
|
||||
exclude: [/node_modules/],
|
||||
},
|
||||
|
||||
test: {
|
||||
name: 'storybook',
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: true,
|
||||
provider: 'playwright',
|
||||
instances: [{ browser: 'chromium' }]
|
||||
},
|
||||
setupFiles: ['.storybook/vitest.setup.js'],
|
||||
},
|
||||
},
|
||||
environment: "jsdom",
|
||||
setupFiles: ["./vitest.setup.ts"], // match your actual filename
|
||||
include: [
|
||||
"tests/unit/**/*.test.{js,jsx,ts,tsx}",
|
||||
"tests/integration/**/*.test.{js,jsx,ts,tsx}",
|
||||
],
|
||||
css: true,
|
||||
transformMode: { web: [/\.[jt]sx?$/] }, // ensure web transform for JSX
|
||||
coverage: {
|
||||
provider: "v8",
|
||||
reporter: ["text", "lcov"],
|
||||
thresholds: { lines: 85, functions: 85, statements: 85, branches: 80 },
|
||||
},
|
||||
pool: "threads",
|
||||
testTimeout: 10000,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { afterAll, afterEach, beforeAll } from "vitest";
|
||||
import { server } from "./tests/msw/server";
|
||||
// expose Tailwind tokens to JSDOM (for design token checks)
|
||||
import "./app/tailwind.css";
|
||||
|
||||
// MSW for API integration tests (mock fetch)
|
||||
beforeAll(() => server.listen({ onUnhandledRequest: "bypass" }));
|
||||
afterEach(() => server.resetHandlers());
|
||||
afterAll(() => server.close());
|
||||
Reference in New Issue
Block a user