From 0c031f59b83f6d34c2240a37ae80b220153474a2 Mon Sep 17 00:00:00 2001 From: Hasini Date: Sun, 28 Jun 2026 17:33:49 +0530 Subject: [PATCH 1/4] ci: add pre-push hook for lint and test checks --- .husky/pre-push | 4 ++++ package.json | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100755 .husky/pre-push diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 00000000..0de1b0a4 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,4 @@ +#!/usr/bin/env sh + +echo "Running frontend tests..." +npm run test --prefix FRONTEND -- --run || exit 1 \ No newline at end of file diff --git a/package.json b/package.json index 8fe7c469..b1f29ade 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "build": "npm install && npm install --prefix FRONTEND && npm run build --prefix FRONTEND", "start": "cross-env NODE_ENV=production node BACKEND/server.js", "test:e2e": "npx playwright test", - "test:e2e:ui": "npx playwright test --ui" + "test:e2e:ui": "npx playwright test --ui", + "prepare": "husky" }, "type": "module", "keywords": [], From 06b3400a0be1eb8a4ac8856989cff8938d3a41ac Mon Sep 17 00:00:00 2001 From: Hasini Date: Sun, 28 Jun 2026 18:22:51 +0530 Subject: [PATCH 2/4] test: add coverage reporting and CI integration --- .github/workflows/ci.yml | 4 +++ .gitignore | 1 + FRONTEND/package.json | 3 +- FRONTEND/src/components/ui/QuickViewModal.jsx | 6 ++-- FRONTEND/src/components/ui/ScrollToTop.jsx | 2 +- .../components/ui/tests/ProductCard.test.jsx | 36 +++++++++---------- FRONTEND/vitest.config.js | 13 +++++++ 7 files changed, 43 insertions(+), 22 deletions(-) create mode 100644 FRONTEND/vitest.config.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ff05337..6ab74f85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,10 @@ jobs: NODE_ENV: test JWT_SECRET: testsecret12345678901234567890123 + - name: Run frontend coverage + run: npm run coverage + working-directory: FRONTEND + e2e-tests: name: Playwright E2E Tests runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index d7de12f3..7b4a6acd 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ dist-ssr *.njsproj *.sln *.sw? +coverage/ diff --git a/FRONTEND/package.json b/FRONTEND/package.json index 34e48488..07f6d0bf 100644 --- a/FRONTEND/package.json +++ b/FRONTEND/package.json @@ -8,7 +8,8 @@ "build": "vite build", "lint": "eslint .", "preview": "vite preview", - "test": "vitest" + "test": "vitest", + "coverage": "vitest run --coverage" }, "dependencies": { "@chakra-ui/icons": "^2.0.17", diff --git a/FRONTEND/src/components/ui/QuickViewModal.jsx b/FRONTEND/src/components/ui/QuickViewModal.jsx index 9a91a638..1c75d6c7 100644 --- a/FRONTEND/src/components/ui/QuickViewModal.jsx +++ b/FRONTEND/src/components/ui/QuickViewModal.jsx @@ -17,7 +17,7 @@ import { Link } from "react-router-dom"; import { useCartStore } from "../../store/cart"; import { useCurrencyStore } from "../../store/currency"; import { formatPrice } from "../../utils/currency"; - +import React from "react"; const QuickViewModal = ({ isOpen, onClose, product }) => { const { addToCart } = useCartStore(); const { currency, rates } = useCurrencyStore(); @@ -51,7 +51,9 @@ const QuickViewModal = ({ isOpen, onClose, product }) => { {formatPrice(product.price, currency, rates)} - {product.category && {product.category}} + {product.category && ( + {product.category} + )} {product.brand && ( diff --git a/FRONTEND/src/components/ui/ScrollToTop.jsx b/FRONTEND/src/components/ui/ScrollToTop.jsx index cb2eed3d..d6c97853 100644 --- a/FRONTEND/src/components/ui/ScrollToTop.jsx +++ b/FRONTEND/src/components/ui/ScrollToTop.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { IconButton, useColorModeValue } from "@chakra-ui/react"; import { LuArrowUp } from "react-icons/lu"; diff --git a/FRONTEND/src/components/ui/tests/ProductCard.test.jsx b/FRONTEND/src/components/ui/tests/ProductCard.test.jsx index 79adccf3..64aadc36 100644 --- a/FRONTEND/src/components/ui/tests/ProductCard.test.jsx +++ b/FRONTEND/src/components/ui/tests/ProductCard.test.jsx @@ -1,3 +1,4 @@ +import React from "react"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { describe, it, expect, vi } from "vitest"; @@ -34,8 +35,7 @@ vi.mock("../../../store/cart", () => ({ })); vi.mock("../QuickAddModal", () => ({ - default: ({ isOpen }) => - isOpen ?
Quick Add Modal
: null, + default: ({ isOpen }) => (isOpen ?
Quick Add Modal
: null), })); vi.mock("@chakra-ui/react", async () => { @@ -86,21 +86,21 @@ describe("ProductCard", () => { expect(screen.getByText("Quick Add Modal")).toBeInTheDocument(); }); - it("disables Add to Cart button when product is out of stock", () => { - render( - - ); - - const addToCartButton = screen.getByRole("button", { - name: `Add ${mockProduct.name} to cart`, - }); - - expect(addToCartButton).toBeDisabled(); - expect(addToCartButton).toHaveTextContent("Out of Stock"); + it("disables Add to Cart button when product is out of stock", () => { + render( + + ); + + const addToCartButton = screen.getByRole("button", { + name: `Add ${mockProduct.name} to cart`, }); + + expect(addToCartButton).toBeDisabled(); + expect(addToCartButton).toHaveTextContent("Out of Stock"); }); +}); diff --git a/FRONTEND/vitest.config.js b/FRONTEND/vitest.config.js new file mode 100644 index 00000000..03938019 --- /dev/null +++ b/FRONTEND/vitest.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "jsdom", + globals: true, + setupFiles: "./src/setupTests.js", + coverage: { + provider: "v8", + reporter: ["text", "html"], + }, + }, +}); From 6c4cbee077591b6ddf8939f81d5efe4c3b508a2c Mon Sep 17 00:00:00 2001 From: Hasini Date: Sun, 28 Jun 2026 22:07:35 +0530 Subject: [PATCH 3/4] ci: add automated dependency updates via Dependabot --- .github/dependabot.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..edb27a46 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,26 @@ +version: 2 + +updates: + - package-ecosystem: "npm" + directory: "/FRONTEND" + schedule: + interval: "weekly" + + labels: + - "dependencies" + + - package-ecosystem: "npm" + directory: "/BACKEND" + schedule: + interval: "weekly" + + labels: + - "dependencies" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + + labels: + - "dependencies" \ No newline at end of file From 1657ceb770c281d6304c9af47dc48e396047621d Mon Sep 17 00:00:00 2001 From: Hasini Date: Sun, 28 Jun 2026 22:43:05 +0530 Subject: [PATCH 4/4] chore: add automated accessibility auditing with axe-core --- FRONTEND/package-lock.json | 253 ++++++++++++++++++++++++++++++++++++- FRONTEND/package.json | 3 +- e2e/accessibility.spec.js | 39 ++++++ package-lock.json | 24 ++++ package.json | 1 + 5 files changed, 318 insertions(+), 2 deletions(-) create mode 100644 e2e/accessibility.spec.js diff --git a/FRONTEND/package-lock.json b/FRONTEND/package-lock.json index a2d5f564..4d1dde9b 100644 --- a/FRONTEND/package-lock.json +++ b/FRONTEND/package-lock.json @@ -34,6 +34,7 @@ "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "@vitejs/plugin-react": "^4.3.4", + "@vitest/coverage-v8": "^1.6.1", "eslint": "^9.22.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", @@ -59,7 +60,6 @@ "devDependencies": { "@eslint/js": "^9.22.0", "@playwright/test": "^1.61.0", - "@testing-library/user-event": "^14.6.1", "cross-env": "^7.0.3", "eslint": "^9.22.0", "globals": "^17.6.0", @@ -76,6 +76,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", @@ -385,6 +399,13 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, "node_modules/@chakra-ui/anatomy": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/@chakra-ui/anatomy/-/anatomy-2.3.6.tgz", @@ -1420,6 +1441,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -2195,6 +2226,34 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vitest/coverage-v8": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.1.tgz", + "integrity": "sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.4", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.4", + "istanbul-reports": "^3.1.6", + "magic-string": "^0.30.5", + "magicast": "^0.3.3", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "test-exclude": "^6.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "1.6.1" + } + }, "node_modules/@vitest/expect": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", @@ -3716,6 +3775,13 @@ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", "license": "0BSD" }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3819,6 +3885,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3935,6 +4023,13 @@ "node": ">=18" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/html-parse-stringify": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", @@ -4097,6 +4192,25 @@ "node": ">=8" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -4177,6 +4291,60 @@ "dev": true, "license": "ISC" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4452,6 +4620,47 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4663,6 +4872,16 @@ "node": ">=0.10.0" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/onetime": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", @@ -4782,6 +5001,16 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -5630,6 +5859,21 @@ "dev": true, "license": "MIT" }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -7195,6 +7439,13 @@ "node": ">=0.10.0" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ws": { "version": "8.21.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", diff --git a/FRONTEND/package.json b/FRONTEND/package.json index 07f6d0bf..049e530c 100644 --- a/FRONTEND/package.json +++ b/FRONTEND/package.json @@ -45,7 +45,8 @@ "jsdom": "^24.0.0", "vite": "^6.3.1", "vite-tsconfig-paths": "^5.1.4", - "vitest": "^1.6.0" + "vitest": "^1.6.0", + "@vitest/coverage-v8": "^1.6.1" }, "vitest": { "environment": "jsdom" diff --git a/e2e/accessibility.spec.js b/e2e/accessibility.spec.js new file mode 100644 index 00000000..c794bf18 --- /dev/null +++ b/e2e/accessibility.spec.js @@ -0,0 +1,39 @@ +import { test, expect } from "@playwright/test"; +import AxeBuilder from "@axe-core/playwright"; +import { BASE_URL } from "./helpers.js"; + +test.describe("Accessibility Audits", () => { + test("Homepage should not have serious or critical WCAG violations", async ({ + page, + }) => { + await page.goto(BASE_URL); + + await expect(page.locator("vite-error-overlay")).toHaveCount(0); + + const results = await new AxeBuilder({ page }).analyze(); + + const severeViolations = results.violations.filter( + (violation) => + violation.impact === "serious" || violation.impact === "critical" + ); + + expect(severeViolations).toEqual([]); + }); + + test("404 page should not have serious or critical WCAG violations", async ({ + page, + }) => { + await page.goto(`${BASE_URL}/this-route-does-not-exist`); + + await expect(page.locator("vite-error-overlay")).toHaveCount(0); + + const results = await new AxeBuilder({ page }).analyze(); + + const severeViolations = results.violations.filter( + (violation) => + violation.impact === "serious" || violation.impact === "critical" + ); + + expect(severeViolations).toEqual([]); + }); +}); diff --git a/package-lock.json b/package-lock.json index f4ac1038..905eeb8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "serverless-http": "^4.0.0" }, "devDependencies": { + "@axe-core/playwright": "^4.12.1", "@eslint/js": "^9.22.0", "@playwright/test": "^1.61.0", "cross-env": "^7.0.3", @@ -28,6 +29,19 @@ "prettier": "^3.2.5" } }, + "node_modules/@axe-core/playwright": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.12.1.tgz", + "integrity": "sha512-rMd7xriptqKpP+w5265i4Hdkv2X5kbu6uiBi/B2I7uf3hieRBM3qDCfaKPtxfiYb2mKXfF+yLODJwIx+Jv1GDw==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "axe-core": "~4.12.1" + }, + "peerDependencies": { + "playwright-core": ">= 1.0.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -430,6 +444,16 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/axe-core": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.1.tgz", + "integrity": "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", diff --git a/package.json b/package.json index b1f29ade..c521a815 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "serverless-http": "^4.0.0" }, "devDependencies": { + "@axe-core/playwright": "^4.12.1", "@eslint/js": "^9.22.0", "@playwright/test": "^1.61.0", "cross-env": "^7.0.3",