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:
parent
86e1bbd268
commit
f990b9a9e0
11 changed files with 272 additions and 3 deletions
67
.eslintrc.cjs
Normal file
67
.eslintrc.cjs
Normal 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
4
.gitignore
vendored
|
|
@ -8,3 +8,7 @@ dist/
|
|||
|
||||
*storybook.log
|
||||
storybook-static
|
||||
coverage
|
||||
test-results
|
||||
playwright-report
|
||||
.vitest
|
||||
|
|
|
|||
10
.prettierrc
Normal file
10
.prettierrc
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"useTabs": false,
|
||||
"printWidth": 100,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
20
package.json
20
package.json
|
|
@ -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
21
playwright.a11y.config.ts
Normal 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"] },
|
||||
},
|
||||
],
|
||||
});
|
||||
21
playwright.visual.config.ts
Normal file
21
playwright.visual.config.ts
Normal 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
60
src/test/setup.ts
Normal 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() {}
|
||||
};
|
||||
16
tests/e2e/accessibility.spec.ts
Normal file
16
tests/e2e/accessibility.spec.ts
Normal 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([]);
|
||||
});
|
||||
});
|
||||
15
tests/e2e/visual/pages.spec.ts
Normal file
15
tests/e2e/visual/pages.spec.ts
Normal 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 });
|
||||
});
|
||||
});
|
||||
23
tests/vitest/components/PublicFooter.test.tsx
Normal file
23
tests/vitest/components/PublicFooter.test.tsx
Normal 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
18
vitest.config.ts
Normal 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"],
|
||||
},
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue