feat: comprehensive testing infrastructure (without workflow pending token scope)

- Add vitest, Playwright, ESLint, Prettier configs
- Add unit tests and e2e accessibility/visual tests
- Add MSW mocks and test setup
- Update scripts and .gitignore
- Install required dev dependencies

Note: GitHub Actions workflow will be added after token scope is granted.
This commit is contained in:
Ashwin Kumar 2026-04-08 02:43:29 +02:00
parent 86e1bbd268
commit f990b9a9e0
11 changed files with 272 additions and 3 deletions

67
.eslintrc.cjs Normal file
View file

@ -0,0 +1,67 @@
/* eslint-env node */
require("@typescript-eslint/eslint-recommended");
require("@typescript-eslint/parser");
require("eslint-plugin-solid");
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
plugins: ["solid"],
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:solid/recommended",
],
overrides: [
{
files: ["**/*.tsx"],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
settings: {
solid: {
noImportReact: true,
},
},
rules: {
"solid/hyperscript": "off",
"solid/jsx-no-undef": "error",
"solid/jsx-pseudo-element": "error",
"solid/jsx-single-root-elem": "error",
"solid/no-dynamic-mount": "error",
"solid/no-hyphen-in-props": "error",
"solid/no-leaked-event-handlers": "error",
"solid/no-react-unknown-property": "error",
"solid/no-unknown-property": "error",
"solid/no-useless-fragment": "error",
"solid/prefer-classlist": "error",
"solid/prefer-destructuring": "warn",
"solid/prefer-innerhtml": "warn",
},
},
],
rules: {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
"@typescript-eslint/no-explicit-any": "warn",
"arrow-body-style": ["warn", "as-needed"],
curly: ["error", "multi-line"],
"no-console": "off",
"no-debugger": "warn",
"no-unused-vars": "off",
"prefer-const": "error",
},
env: {
browser: true,
},
ignorePatterns: [
"dist",
".output",
"node_modules",
"coverage",
"test-results",
"playwright-report",
],
};

4
.gitignore vendored
View file

@ -8,3 +8,7 @@ dist/
*storybook.log
storybook-static
coverage
test-results
playwright-report
.vitest

10
.prettierrc Normal file
View file

@ -0,0 +1,10 @@
{
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false,
"printWidth": 100,
"arrowParens": "always",
"endOfLine": "lf"
}

View file

@ -7,6 +7,10 @@
"start": "vinxi start",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:e2e": "playwright test",
"test:accessibility": "playwright test --config=playwright.a11y.config.ts",
"test:visual": "playwright test --config=playwright.visual.config.ts",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
@ -20,24 +24,34 @@
"vinxi": "^0.5.7"
},
"devDependencies": {
"@axe-core/playwright": "^1.8.0",
"@chromatic-com/storybook": "^5.1.0",
"@playwright/test": "^1.58.2",
"@solidjs/testing-library": "^0.8.0",
"@storybook/addon-a11y": "^10.3.3",
"@storybook/addon-docs": "^10.3.3",
"@storybook/addon-vitest": "^10.3.3",
"@storybook/cli": "^10.3.3",
"@tailwindcss/vite": "^4.2.2",
"@testing-library/jest-dom": "^6.6.3",
"@vitest/browser": "^3.2.4",
"@vitest/coverage-v8": "^3.2.4",
"eslint": "^10.1.0",
"jsdom": "^25.0.1",
"loki": "^0.35.1",
"msw": "^2.7.3",
"@mswjs/data": "^0.16.2",
"pixelmatch": "^7.1.0",
"playwright": "^1.58.2",
"pngjs": "^7.0.0",
"storybook": "^10.3.3",
"storybook-solidjs-vite": "^10.0.11",
"tailwindcss": "^4.2.2",
"visbug": "^0.1.14",
"vitest": "^3.2.4"
"vitest": "^4.1.1",
"vitest-plugin-solid": "^0.2.0",
"@typescript-eslint/parser": "^7.0.0",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"eslint-plugin-solid": "^1.8.0",
"prettier": "^3.0.0"
},
"engines": {
"node": ">=20"

21
playwright.a11y.config.ts Normal file
View file

@ -0,0 +1,21 @@
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests/e2e",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 4 : undefined,
reporter: "html",
use: {
baseURL: "http://localhost:5173",
trace: "on-first-retry",
screenshot: "on",
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
});

View file

@ -0,0 +1,21 @@
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests/e2e/visual",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 4 : undefined,
reporter: "list",
use: {
baseURL: "http://localhost:5173",
screenshot: "only-on-failure",
trace: "on-first-retry",
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
});

60
src/test/setup.ts Normal file
View file

@ -0,0 +1,60 @@
import "@testing-library/jest-dom";
import { beforeAll, afterEach, afterAll } from "vitest";
import { setupServer } from "msw/node";
import { rest } from "msw";
// Mock API responses
const server = setupServer(
rest.get("/api/users/public", (req, res, ctx) => {
return res.once(
200,
ctx.json([{ id: "1", name: "Public User", email: "user@example.com" }]),
);
}),
rest.get("/api/jobs", (req, res, ctx) => {
return res.once(
200,
ctx.json({
jobs: [{ id: "1", title: "Developer", status: "OPEN" }],
}),
);
}),
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// Mock window.matchMedia
Object.defineProperty(window, "matchMedia", {
writable: true,
value: vi.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});
// Mock IntersectionObserver
global.IntersectionObserver = class IntersectionObserver {
constructor() {}
disconnect() {}
observe() {}
takeRecords() {
return [];
}
trigger() {}
};
// Mock ResizeObserver
global.ResizeObserver = class ResizeObserver {
constructor() {}
observe() {}
unobserve() {}
disconnect() {}
};

View file

@ -0,0 +1,16 @@
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
test.describe("Public Frontend Accessibility", () => {
test("homepage should have no accessibility violations", async ({ page }) => {
await page.goto("/");
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
test("jobs listing page should be accessible", async ({ page }) => {
await page.goto("/jobs");
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
});

View file

@ -0,0 +1,15 @@
import { test, expect } from "@playwright/test";
test.describe("Visual Regression - Public Pages", () => {
test("homepage should match baseline", async ({ page }) => {
await page.goto("/");
await expect(page.locator("main")).toBeVisible({ timeout: 10000 });
await expect(page).toHaveScreenshot({ maxDiffPixelRatio: 0.1 });
});
test("jobs page should match baseline", async ({ page }) => {
await page.goto("/jobs");
await expect(page.locator("main")).toBeVisible({ timeout: 10000 });
await expect(page).toHaveScreenshot({ maxDiffPixelRatio: 0.1 });
});
});

View file

@ -0,0 +1,23 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@solidjs/testing-library";
import PublicFooter from "~/components/PublicFooter";
// Mock any external dependencies if needed
describe("PublicFooter", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders footer with copyright text", () => {
render(() => PublicFooter());
expect(screen.getByText(/© \d{4} NXTGAUGE/)).toBeInTheDocument();
});
it("renders links", () => {
render(() => PublicFooter());
expect(screen.getByRole("link", { name: /terms/i })).toBeInTheDocument();
expect(screen.getByRole("link", { name: /privacy/i })).toBeInTheDocument();
expect(screen.getByRole("link", { name: /contact/i })).toBeInTheDocument();
});
});

18
vitest.config.ts Normal file
View file

@ -0,0 +1,18 @@
import { defineConfig } from "vitest/config";
import solid from "vitest-plugin-solid";
export default defineConfig({
plugins: [solid()],
test: {
globals: true,
environment: "jsdom",
setupFiles: ["./src/test/setup.ts"],
include: ["src/**/*.{test,spec}.{js,mjs,ts,tsx}"],
coverage: {
provider: "v8",
reporter: ["text", "json", "html", "lcov"],
reportsDirectory: "./test-results/vitest-coverage",
exclude: ["node_modules", "dist", ".output", "**/*.d.ts"],
},
},
});