From 7ea724a8d9bbbc4c3780307c25e7f6c2aa70926d Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:04:04 -0700 Subject: [PATCH] Simplify and standardize testing structure --- README.md | 42 +- docs/TESTING_GUIDE.md | 208 +++++ docs/guides/performance.md | 391 --------- docs/guides/testing-framework.md | 810 ------------------ docs/guides/testing-quick-reference.md | 357 -------- docs/guides/testing.md | 258 ------ docs/guides/visual-regression.md | 391 --------- package.json | 2 + stories/Checkbox.stories.js | 59 +- stories/RadioButton.stories.js | 55 +- stories/RadioGroup.stories.js | 68 +- tests/accessibility/ContextMenu.a11y.test.jsx | 396 --------- tests/accessibility/Input.a11y.test.jsx | 286 ------- tests/accessibility/Select.a11y.test.jsx | 306 ------- tests/accessibility/Switch.a11y.test.jsx | 98 --- tests/accessibility/TextArea.a11y.test.jsx | 121 --- tests/accessibility/Toggle.a11y.test.jsx | 112 --- tests/accessibility/ToggleGroup.a11y.test.jsx | 92 -- .../accessibility/unit/Checkbox.a11y.test.jsx | 158 ---- .../unit/RadioButton.a11y.test.jsx | 234 ----- .../unit/RadioGroup.a11y.test.jsx | 316 ------- tests/accessibility/unit/components.test.jsx | 217 ----- tests/components/AskOrganizer.test.tsx | 72 ++ tests/components/Button.test.tsx | 66 ++ tests/components/Checkbox.test.tsx | 29 + tests/components/ContentBanner.test.tsx | 45 + tests/components/ContextMenu.test.tsx | 28 + tests/components/ContextMenuItem.test.tsx | 26 + tests/components/FeatureGrid.test.tsx | 80 ++ tests/components/Footer.test.tsx | 73 ++ tests/components/Header.test.tsx | 22 + tests/components/HeroBanner.test.tsx | 66 ++ tests/components/Input.test.tsx | 30 + tests/components/Logo.test.tsx | 68 ++ tests/components/RadioButton.test.tsx | 30 + tests/components/RadioGroup.test.tsx | 28 + tests/components/RelatedArticles.test.tsx | 81 ++ tests/components/SectionHeader.test.tsx | 24 + tests/components/Select.test.tsx | 35 + tests/components/Switch.test.tsx | 29 + tests/components/TextArea.test.tsx | 31 + tests/components/Toggle.test.tsx | 27 + tests/components/ToggleGroup.test.tsx | 23 + tests/e2e/footer.responsive.spec.js | 136 --- tests/e2e/header.responsive.spec.js | 195 ----- .../integration/BlogCore.integration.test.jsx | 171 ---- .../integration/Checkbox.integration.test.jsx | 249 ------ .../ContentLockup.integration.test.jsx | 156 ---- .../ContextMenu.integration.test.jsx | 384 --------- tests/integration/Input.integration.test.jsx | 426 --------- .../RadioButton.integration.test.jsx | 365 -------- .../RadioGroup.integration.test.jsx | 430 ---------- .../RelatedArticles.integration.test.jsx | 214 ----- tests/integration/Select.integration.test.jsx | 407 --------- tests/integration/Switch.integration.test.jsx | 265 ------ .../integration/TextArea.integration.test.jsx | 280 ------ tests/integration/Toggle.integration.test.jsx | 185 ---- .../ToggleGroup.integration.test.jsx | 219 ----- ...omponent-interactions.integration.test.jsx | 353 -------- tests/integration/layout.integration.test.jsx | 246 ------ .../BlogPage.test.jsx => pages/blog.test.jsx} | 0 .../Page.test.jsx => pages/home.test.jsx} | 0 .../page-flow.test.jsx} | 0 .../user-journey.test.jsx} | 0 tests/storybook/Checkbox.interactions.test.js | 136 --- tests/storybook/Checkbox.storybook.test.js | 234 ----- .../RadioButton.interactions.test.js | 124 --- tests/storybook/RadioButton.storybook.test.js | 177 ---- .../storybook/RadioGroup.interactions.test.js | 183 ---- tests/storybook/RadioGroup.storybook.test.js | 252 ------ tests/unit/AskOrganizer.test.jsx | 298 ------- tests/unit/Button.test.jsx | 160 ---- tests/unit/Checkbox.test.jsx | 166 ---- tests/unit/ContentBanner.test.jsx | 287 ------- tests/unit/ContextMenu.test.jsx | 321 ------- tests/unit/FeatureGrid.test.jsx | 144 ---- tests/unit/Footer.test.jsx | 285 ------ tests/unit/Header.test.jsx | 334 -------- tests/unit/HeroBanner.test.jsx | 141 --- tests/unit/Input.test.jsx | 271 ------ tests/unit/Logo.test.jsx | 128 --- tests/unit/RadioButton.test.jsx | 248 ------ tests/unit/RadioGroup.test.jsx | 240 ------ tests/unit/RelatedArticles.test.jsx | 395 --------- tests/unit/SectionHeader.test.jsx | 198 ----- tests/unit/Select.test.jsx | 401 --------- tests/unit/Switch.test.jsx | 184 ---- tests/unit/TextArea.test.jsx | 203 ----- tests/unit/Toggle.test.jsx | 195 ----- tests/unit/ToggleGroup.test.jsx | 213 ----- tests/utils/componentTestSuite.tsx | 221 +++++ .../content-processing.test.js} | 0 tests/visual/Checkbox.visual.test.js | 83 -- tests/visual/visual-regression.config.js | 215 ----- vitest.config.mjs | 11 +- 95 files changed, 1534 insertions(+), 15485 deletions(-) create mode 100644 docs/TESTING_GUIDE.md delete mode 100644 docs/guides/performance.md delete mode 100644 docs/guides/testing-framework.md delete mode 100644 docs/guides/testing-quick-reference.md delete mode 100644 docs/guides/testing.md delete mode 100644 docs/guides/visual-regression.md delete mode 100644 tests/accessibility/ContextMenu.a11y.test.jsx delete mode 100644 tests/accessibility/Input.a11y.test.jsx delete mode 100644 tests/accessibility/Select.a11y.test.jsx delete mode 100644 tests/accessibility/Switch.a11y.test.jsx delete mode 100644 tests/accessibility/TextArea.a11y.test.jsx delete mode 100644 tests/accessibility/Toggle.a11y.test.jsx delete mode 100644 tests/accessibility/ToggleGroup.a11y.test.jsx delete mode 100644 tests/accessibility/unit/Checkbox.a11y.test.jsx delete mode 100644 tests/accessibility/unit/RadioButton.a11y.test.jsx delete mode 100644 tests/accessibility/unit/RadioGroup.a11y.test.jsx delete mode 100644 tests/accessibility/unit/components.test.jsx create mode 100644 tests/components/AskOrganizer.test.tsx create mode 100644 tests/components/Button.test.tsx create mode 100644 tests/components/Checkbox.test.tsx create mode 100644 tests/components/ContentBanner.test.tsx create mode 100644 tests/components/ContextMenu.test.tsx create mode 100644 tests/components/ContextMenuItem.test.tsx create mode 100644 tests/components/FeatureGrid.test.tsx create mode 100644 tests/components/Footer.test.tsx create mode 100644 tests/components/Header.test.tsx create mode 100644 tests/components/HeroBanner.test.tsx create mode 100644 tests/components/Input.test.tsx create mode 100644 tests/components/Logo.test.tsx create mode 100644 tests/components/RadioButton.test.tsx create mode 100644 tests/components/RadioGroup.test.tsx create mode 100644 tests/components/RelatedArticles.test.tsx create mode 100644 tests/components/SectionHeader.test.tsx create mode 100644 tests/components/Select.test.tsx create mode 100644 tests/components/Switch.test.tsx create mode 100644 tests/components/TextArea.test.tsx create mode 100644 tests/components/Toggle.test.tsx create mode 100644 tests/components/ToggleGroup.test.tsx delete mode 100644 tests/e2e/footer.responsive.spec.js delete mode 100644 tests/e2e/header.responsive.spec.js delete mode 100644 tests/integration/BlogCore.integration.test.jsx delete mode 100644 tests/integration/Checkbox.integration.test.jsx delete mode 100644 tests/integration/ContentLockup.integration.test.jsx delete mode 100644 tests/integration/ContextMenu.integration.test.jsx delete mode 100644 tests/integration/Input.integration.test.jsx delete mode 100644 tests/integration/RadioButton.integration.test.jsx delete mode 100644 tests/integration/RadioGroup.integration.test.jsx delete mode 100644 tests/integration/RelatedArticles.integration.test.jsx delete mode 100644 tests/integration/Select.integration.test.jsx delete mode 100644 tests/integration/Switch.integration.test.jsx delete mode 100644 tests/integration/TextArea.integration.test.jsx delete mode 100644 tests/integration/Toggle.integration.test.jsx delete mode 100644 tests/integration/ToggleGroup.integration.test.jsx delete mode 100644 tests/integration/component-interactions.integration.test.jsx delete mode 100644 tests/integration/layout.integration.test.jsx rename tests/{unit/BlogPage.test.jsx => pages/blog.test.jsx} (100%) rename tests/{unit/Page.test.jsx => pages/home.test.jsx} (100%) rename tests/{integration/page-flow.integration.test.jsx => pages/page-flow.test.jsx} (100%) rename tests/{integration/user-journey.integration.test.jsx => pages/user-journey.test.jsx} (100%) delete mode 100644 tests/storybook/Checkbox.interactions.test.js delete mode 100644 tests/storybook/Checkbox.storybook.test.js delete mode 100644 tests/storybook/RadioButton.interactions.test.js delete mode 100644 tests/storybook/RadioButton.storybook.test.js delete mode 100644 tests/storybook/RadioGroup.interactions.test.js delete mode 100644 tests/storybook/RadioGroup.storybook.test.js delete mode 100644 tests/unit/AskOrganizer.test.jsx delete mode 100644 tests/unit/Button.test.jsx delete mode 100644 tests/unit/Checkbox.test.jsx delete mode 100644 tests/unit/ContentBanner.test.jsx delete mode 100644 tests/unit/ContextMenu.test.jsx delete mode 100644 tests/unit/FeatureGrid.test.jsx delete mode 100644 tests/unit/Footer.test.jsx delete mode 100644 tests/unit/Header.test.jsx delete mode 100644 tests/unit/HeroBanner.test.jsx delete mode 100644 tests/unit/Input.test.jsx delete mode 100644 tests/unit/Logo.test.jsx delete mode 100644 tests/unit/RadioButton.test.jsx delete mode 100644 tests/unit/RadioGroup.test.jsx delete mode 100644 tests/unit/RelatedArticles.test.jsx delete mode 100644 tests/unit/SectionHeader.test.jsx delete mode 100644 tests/unit/Select.test.jsx delete mode 100644 tests/unit/Switch.test.jsx delete mode 100644 tests/unit/TextArea.test.jsx delete mode 100644 tests/unit/Toggle.test.jsx delete mode 100644 tests/unit/ToggleGroup.test.jsx create mode 100644 tests/utils/componentTestSuite.tsx rename tests/{integration/ContentProcessing.integration.test.js => utils/content-processing.test.js} (100%) delete mode 100644 tests/visual/Checkbox.visual.test.js delete mode 100644 tests/visual/visual-regression.config.js diff --git a/README.md b/README.md index fe7d7ad..40be15c 100644 --- a/README.md +++ b/README.md @@ -14,28 +14,22 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the ## πŸ§ͺ Testing Framework -This project includes a comprehensive testing framework with multiple layers of testing: +This project uses a simplified, component‑first testing model: + +- **Component tests (Vitest + RTL)** live in `tests/components/` with a single file per component. +- **E2E tests (Playwright)** cover critical user journeys and visual regression. ### Quick Test Commands ```bash -# Unit tests with coverage +# All component tests with coverage npm test -# E2E tests -npm run e2e +# Component tests only (new structure) +npm run test:component -# Performance tests -npm run lhci - -# Storybook tests -npm run test:sb - -# Performance monitoring -npm run test:performance # Comprehensive performance testing -npm run bundle:analyze # Bundle size analysis -npm run web-vitals:track # Web Vitals tracking -npm run monitor:all # All monitoring tools +# E2E tests only +npm run test:e2e ``` ### Test Coverage @@ -56,7 +50,7 @@ npm run monitor:all # All monitoring tools - **Performance monitoring** - **Code coverage reporting** -πŸ“– **For detailed testing documentation, see [docs/README.md](docs/README.md)** +πŸ“– **For detailed testing documentation, see `docs/TESTING_GUIDE.md` and [docs/README.md](docs/README.md)** ## ⚑ Performance Optimizations @@ -144,10 +138,12 @@ The Storybook configuration automatically detects the environment: ### Testing -- `npm test` - Run unit tests with coverage +- `npm test` - Run all component tests with coverage +- `npm run test:component` - Run tests in `tests/components/` only - `npm run test:watch` - Run tests in watch mode - `npm run test:ui` - Run tests with UI -- `npm run e2e` - Run E2E tests +- `npm run test:e2e` - Run E2E tests only +- `npm run e2e` - Alias for Playwright E2E tests - `npm run e2e:ui` - Run E2E tests with UI - `npm run e2e:serve` - Start dev server and run E2E tests - `npm run lhci` - Run performance tests @@ -187,10 +183,12 @@ community-rule/ β”‚ β”œβ”€β”€ status-runner.sh # Check runner status β”‚ └── stop-runner.sh # Stop Gitea runner β”œβ”€β”€ tests/ # Test files -β”‚ β”œβ”€β”€ unit/ # Unit tests -β”‚ β”œβ”€β”€ integration/ # Integration tests -β”‚ β”œβ”€β”€ e2e/ # E2E tests -β”‚ └── accessibility/ # Accessibility tests +β”‚ β”œβ”€β”€ components/ # Component tests (Vitest + RTL) +β”‚ β”œβ”€β”€ pages/ # Page-level tests +β”‚ β”œβ”€β”€ e2e/ # E2E tests (Playwright) +β”‚ β”œβ”€β”€ utils/ # Test utilities (componentTestSuite, etc.) +β”‚ β”œβ”€β”€ msw/ # MSW server setup +β”‚ └── accessibility/ # E2E accessibility checks β”œβ”€β”€ .storybook/ # Storybook configuration β”œβ”€β”€ .gitea/ # Gitea Actions workflows β”‚ └── workflows/ diff --git a/docs/TESTING_GUIDE.md b/docs/TESTING_GUIDE.md new file mode 100644 index 0000000..9be8be9 --- /dev/null +++ b/docs/TESTING_GUIDE.md @@ -0,0 +1,208 @@ +## Testing Guide + +### Philosophy + +- **Test behaviour, not implementation**: Focus on what the user can see and do, not internal details. +- **Single source of truth per component**: Each component should have **one consolidated test file**. +- **Accessibility is mandatory**: Basic a11y checks run as part of every component suite. +- **E2E is sparse**: Only cover critical user journeys that span pages or systems. + +### Test Structure + +The test directory structure is organized as follows: + +```text +tests/ + components/ # All component-focused tests (Vitest + RTL) + Button.test.tsx + Input.test.tsx + Checkbox.test.tsx + Select.test.tsx + Switch.test.tsx + pages/ # Page-level tests (home, blog, etc.) + home.test.jsx + blog.test.jsx + e2e/ # True end‑to‑end flows + visual regression (Playwright) + homepage.spec.ts + user-journeys.spec.ts + visual-regression.spec.ts + performance.spec.ts + utils/ # Shared test utilities + componentTestSuite.tsx + msw/ # MSW server setup for mocking + server.ts + accessibility/ + e2e/ # E2E accessibility checks (WCAG compliance) + wcag-compliance.spec.ts +``` + +**Component tests** (`tests/components/`) use the standard `componentTestSuite` utility to ensure consistent baseline coverage for all UI components. **Page tests** (`tests/pages/`) cover page-level rendering and flows. **E2E tests** (`tests/e2e/`) focus on critical user journeys, visual regression, and performance. **Accessibility E2E** (`tests/accessibility/e2e/`) provides high-level WCAG compliance checks. + +### Standard Component Test Suite + +Use the shared suite in `tests/utils/componentTestSuite.tsx` to get a consistent baseline: + +```ts +import Component from "../../app/components/Component"; +import { + componentTestSuite, + type ComponentTestSuiteConfig, +} from "../utils/componentTestSuite"; + +type Props = React.ComponentProps; + +const config: ComponentTestSuiteConfig = { + component: Component, + name: "Component", + props: { + /* default props */ + } as Props, + requiredProps: ["label"], + optionalProps: { + disabled: true, + }, + queries: { + getPrimaryElement: (s) => s.getByRole("button"), + }, + variants: { + disabled: { + props: { disabled: true }, + assert: (el) => { + expect(el).toBeDisabled(); + }, + }, + error: { + props: { error: true } as Partial, + assert: (el) => { + expect(el).toHaveClass("border-[var(--color-border-default-utility-negative)]"); + }, + }, + }, + testCases: { + renders: true, + accessibility: true, + keyboardNavigation: true, + disabledState: true, + errorState: true, + }, +}; + +componentTestSuite(config); +``` + +#### What the Standard Suite Covers + +- **Rendering** + - Component renders without throwing using the provided `props`. + - Required props are present and do not break rendering. + - Optional props can be applied without breaking. + +- **Accessibility** + - Runs `axe` against the rendered output. + - Fails on common WCAG 2.1 issues (roles, labels, contrast, etc.). + +- **Keyboard Navigation** + - Ensures the primary element can receive focus. + - Smoke‑tests basic keyboard activation (`Enter`, `Space`) without runtime errors. + +- **Disabled State** + - Uses `variants.disabled` to verify disabled behaviour (e.g., `aria-disabled`, `disabled` attribute, tab index). + +- **Error State** + - Uses `variants.error` to verify error styling/attributes when applicable. + +### When to Add Custom Tests + +Use the standard suite for **baseline coverage**, then add custom `describe` blocks in the same file when: + +- The component has **important variants** (different sizes, modes, label variants). +- There is **non‑trivial interaction** (menus, dropdowns, complex keyboard behaviour). +- You need to exercise **stateful flows** (forms, validation, error messages). + +Example (inside the same `*.test.tsx` file): + +```ts +describe("Input – behaviour specifics", () => { + it("calls onChange when user types", async () => { + // ... + }); +}); +``` + +### Test Commands + +- **All component tests** (Vitest + RTL): + + ```bash + npm test + ``` + +- **Component-only tests** (faster inner loop, focused on `tests/components/`): + + ```bash + npm run test:component + # filter by name: + npm run test:component -- --run tests/components/Button.test.tsx + ``` + +- **E2E tests only** (Playwright): + + ```bash + npm run test:e2e + # or, equivalently: + npm run e2e + ``` + +### What to Test vs. What Not to Test + +- **Do test** + - Public behaviour: visible text, roles, labels, ARIA, keyboard paths. + - State transitions that users rely on (error -> success, disabled -> enabled). + - Critical component interactions (clicks, form submissions, dropdown selection). + - Accessibility invariants (no axe violations, basic keyboard support). + +- **Avoid testing** + - Pure styling details that are likely to change frequently (exact shadow radius, minor spacing). + - Internal implementation details (private helpers, hook internals, memoisation specifics). + - Responsive visibility in JSDOM (use Playwright visual / responsive tests instead). + +### Adding Tests for a New Component (β‰ˆ5 minutes) + +1. **Create the component file** in `app/components/`. +2. **Create a test file** in `tests/components/ComponentName.test.tsx`. +3. **Wire the standard suite** using `componentTestSuite`. +4. **Add 1–3 custom tests** for any unique behaviours. +5. Run: + + ```bash + npm run test:component -- --run tests/components/ComponentName.test.tsx + ``` + +### E2E and Visual Regression + +- Use **Playwright** for: + - Critical user journeys (e.g., create rule, navigate blog, key flows). + - Responsive behaviour and cross‑browser checks. + - Visual regression (`tests/e2e/visual-regression.spec.ts`). + + ```bash + npm run test:e2e + npm run visual:test + ``` + +### Accessibility Testing + +Accessibility is tested at two levels: + +1. **Component-level accessibility** (`tests/components/*.test.tsx`): + - Automatically covered by `componentTestSuite` using `jest-axe` + - Tests roles, labels, ARIA attributes, keyboard navigation + - Runs as part of every component test suite + +2. **Full-page accessibility** (`tests/accessibility/e2e/wcag-compliance.spec.ts`): + - E2E tests using Playwright and `@axe-core/playwright` + - Validates WCAG 2.1 AA compliance across entire pages + - Tests complete user journeys for accessibility barriers + +This two-tier approach ensures both individual components and full page experiences meet accessibility standards. + diff --git a/docs/guides/performance.md b/docs/guides/performance.md deleted file mode 100644 index a92f9df..0000000 --- a/docs/guides/performance.md +++ /dev/null @@ -1,391 +0,0 @@ -# Performance Optimization Guide - -## πŸ“‹ Table of Contents - -- [Overview](#overview) -- [Performance Targets](#performance-targets) -- [Frontend Optimizations](#frontend-optimizations) -- [Performance Monitoring](#performance-monitoring) -- [Bundle Analysis](#bundle-analysis) -- [Web Vitals Tracking](#web-vitals-tracking) -- [Performance Testing](#performance-testing) -- [Troubleshooting](#troubleshooting) -- [Best Practices](#best-practices) - -## 🎯 Overview - -This guide covers the comprehensive performance optimization strategy implemented in Community Rule 3.0 to achieve sub-2-second load times across all platform features. - -### Performance Philosophy - -- **Measure First**: Comprehensive monitoring before optimization -- **Performance Budgets**: Enforce limits to prevent regression -- **Real User Monitoring**: Track actual user experience -- **Continuous Optimization**: Regular monitoring and improvement - -## 🎯 Performance Targets - -### Core Web Vitals - -- **LCP (Largest Contentful Paint)**: < 2.5s (Good) -- **FID (First Input Delay)**: < 100ms (Good) -- **CLS (Cumulative Layout Shift)**: < 0.1 (Good) -- **FCP (First Contentful Paint)**: < 1.8s (Good) -- **TTFB (Time to First Byte)**: < 800ms (Good) - -### Bundle Size Targets - -- **Initial JavaScript Bundle**: < 250KB gzipped (currently 101KB) -- **Total Bundle Size**: < 2MB -- **Individual Component Bundles**: < 50KB -- **Image Assets**: Optimized with WebP/AVIF formats - -### Lighthouse Scores - -- **Performance**: > 90 -- **Accessibility**: > 90 -- **Best Practices**: > 90 -- **SEO**: > 90 - -## ⚑ Frontend Optimizations - -### 1. Code Splitting - -Dynamic imports for non-critical components to reduce initial bundle size: - -```javascript -// Dynamic imports for non-critical components -const NumberedCards = dynamic(() => import("./components/NumberedCards"), { - loading: () =>
Loading...
, -}); - -const LogoWall = dynamic(() => import("./components/LogoWall"), { - loading: () =>
Loading...
, -}); -``` - -### 2. React.memo Optimization - -Applied to all 30+ components to prevent unnecessary re-renders: - -```javascript -import React, { memo } from "react"; - -const MyComponent = memo(({ prop1, prop2 }) => { - return
{/* Component content */}
; -}); - -MyComponent.displayName = "MyComponent"; -export default MyComponent; -``` - -### 3. useMemo and useCallback - -Optimized expensive computations and event handlers: - -```javascript -import React, { memo, useMemo, useCallback } from "react"; - -const OptimizedComponent = memo(({ data, onAction }) => { - // Memoize expensive computations - const processedData = useMemo(() => { - return data.map((item) => expensiveOperation(item)); - }, [data]); - - // Memoize event handlers - const handleClick = useCallback( - (id) => { - onAction(id); - }, - [onAction], - ); - - return
{/* Component content */}
; -}); -``` - -### 4. Image Optimization - -Enhanced `next/image` with lazy loading and blur placeholders: - -```javascript -import Image from "next/image"; - -Description; -``` - -### 5. Font Optimization - -Preloading and fallbacks for all fonts: - -```javascript -import { Inter, Bricolage_Grotesque, Space_Grotesk } from "next/font/google"; - -const inter = Inter({ - subsets: ["latin"], - preload: true, - fallback: ["system-ui", "arial"], -}); - -const bricolageGrotesque = Bricolage_Grotesque({ - subsets: ["latin"], - preload: true, - fallback: ["system-ui", "arial"], -}); -``` - -### 6. Error Boundaries - -Comprehensive error handling to prevent cascade failures: - -```javascript -import React, { Component } from "react"; - -class ErrorBoundary extends Component { - constructor(props) { - super(props); - this.state = { hasError: false, error: null }; - } - - static getDerivedStateFromError(error) { - return { hasError: true }; - } - - componentDidCatch(error, errorInfo) { - console.error("ErrorBoundary caught an error:", error, errorInfo); - } - - render() { - if (this.state.hasError) { - return
Something went wrong.
; - } - return this.props.children; - } -} -``` - -## πŸ“Š Performance Monitoring - -### Available Scripts - -```bash -# Individual monitoring tools -npm run bundle:analyze # Analyze bundle sizes and budgets -npm run performance:monitor # Performance metrics and Lighthouse CI -npm run web-vitals:track # Core Web Vitals tracking - -# Comprehensive testing -npm run test:performance # All performance tests -npm run monitor:all # All monitoring tools -``` - -### Performance Dashboard - -Access the performance monitoring dashboard at `/monitor` to view: - -- Real-time Web Vitals metrics -- Historical performance data -- Bundle analysis results -- Performance budget status -- Optimization recommendations - -## πŸ“¦ Bundle Analysis - -### Bundle Analyzer Script - -The bundle analyzer provides comprehensive analysis of bundle sizes: - -```bash -npm run bundle:analyze -``` - -**Features:** - -- Analyzes static assets, chunks, and pages -- Checks against performance budgets -- Generates optimization recommendations -- Saves results in JSON and Markdown formats - -**Output Files:** - -- `.next/analyze/bundle-analysis.json` - Detailed analysis data -- `.next/analyze/bundle-report.md` - Human-readable report - -### Performance Budgets - -Defined in `performance-budgets.json`: - -```json -{ - "budgets": [ - { - "name": "lcp", - "maxValue": 2500, - "description": "Largest Contentful Paint" - }, - { - "name": "bundle-size", - "maxSizeKB": 250, - "description": "Initial JavaScript bundle size" - } - ] -} -``` - -## πŸ“ˆ Web Vitals Tracking - -### Real-time Monitoring - -The Web Vitals tracking system collects and reports Core Web Vitals: - -```bash -npm run web-vitals:track -``` - -**Features:** - -- Collects LCP, FID, CLS, FCP, TTFB metrics -- Stores historical data (last 100 entries per metric) -- Generates summary reports -- Provides optimization recommendations - -**API Endpoint:** - -- `POST /api/web-vitals` - Receives Web Vitals data -- `GET /api/web-vitals` - Returns aggregated metrics - -### Web Vitals Dashboard - -The dashboard component displays real-time and historical metrics: - -```javascript -import WebVitalsDashboard from "./components/WebVitalsDashboard"; - -; -``` - -## πŸ§ͺ Performance Testing - -### Comprehensive Testing - -Run all performance tests with a single command: - -```bash -npm run test:performance -``` - -**Test Coverage:** - -- Bundle analysis with budget checking -- Performance monitoring with Lighthouse CI -- Web Vitals tracking setup -- Comprehensive reporting - -### Individual Tests - -```bash -# Bundle analysis only -npm run bundle:analyze - -# Performance monitoring only -npm run performance:monitor - -# Web Vitals tracking only -npm run web-vitals:track - -# All monitoring tools -npm run monitor:all -``` - -## πŸ”§ Troubleshooting - -### Common Issues - -#### 1. Bundle Size Exceeds Budget - -```bash -# Check bundle analysis -npm run bundle:analyze - -# Review recommendations in .next/analyze/bundle-report.md -# Consider code splitting or removing unused dependencies -``` - -#### 2. Web Vitals Poor Performance - -```bash -# Check Web Vitals data -npm run web-vitals:track - -# Review dashboard at /monitor -# Optimize images, fonts, or JavaScript -``` - -#### 3. Performance Tests Failing - -```bash -# Run comprehensive performance test -npm run test:performance - -# Check individual components -npm run bundle:analyze -npm run performance:monitor -``` - -### Debug Commands - -```bash -# Debug bundle analysis -npm run bundle:analyze --verbose - -# Debug performance monitoring -npm run performance:monitor --debug - -# Check Web Vitals data -curl http://localhost:3000/api/web-vitals -``` - -## 🎯 Best Practices - -### Development - -1. **Always use React.memo** for components that receive props -2. **Implement useMemo/useCallback** for expensive operations -3. **Use dynamic imports** for non-critical components -4. **Optimize images** with proper sizing and formats -5. **Preload critical fonts** and resources - -### Monitoring - -1. **Run bundle analysis** before major releases -2. **Monitor Web Vitals** in production -3. **Check performance budgets** in CI/CD -4. **Review optimization recommendations** regularly - -### Performance Budgets - -1. **Set realistic budgets** based on user needs -2. **Monitor budget violations** in CI/CD -3. **Optimize when budgets are exceeded** -4. **Update budgets** as requirements change - -## πŸ“š Additional Resources - -- **Next.js Performance**: https://nextjs.org/docs/advanced-features/measuring-performance -- **Web Vitals**: https://web.dev/vitals/ -- **Lighthouse CI**: https://github.com/GoogleChrome/lighthouse-ci -- **React Performance**: https://react.dev/learn/render-and-commit - ---- - -**Last Updated**: December 2024 -**Maintained by**: CommunityRule Development Team diff --git a/docs/guides/testing-framework.md b/docs/guides/testing-framework.md deleted file mode 100644 index 7bc98e8..0000000 --- a/docs/guides/testing-framework.md +++ /dev/null @@ -1,810 +0,0 @@ -# Testing Framework Documentation - -## πŸ“‹ Table of Contents - -- [Overview](#overview) -- [Testing Architecture](#testing-architecture) -- [Quick Start](#quick-start) -- [Test Types & Coverage](#test-types--coverage) -- [Unit & Integration Testing](#unit--integration-testing) -- [E2E Testing](#e2e-testing) -- [Visual Regression Testing](#visual-regression-testing) -- [Accessibility Testing](#accessibility-testing) -- [Performance Testing](#performance-testing) -- [CI/CD Pipeline](#cicd-pipeline) -- [Development Workflow](#development-workflow) -- [Best Practices](#best-practices) -- [Troubleshooting](#troubleshooting) - -## 🎯 Overview - -The CommunityRule platform uses a comprehensive testing framework with multiple layers to ensure code quality, functionality, visual consistency, and accessibility across all browsers and devices. - -### Testing Stack - -- **Unit/Integration**: Vitest + JSDOM + React Testing Library -- **E2E**: Playwright (Chromium, Firefox, WebKit, Mobile) -- **Visual Regression**: Playwright Screenshots -- **Performance**: Lighthouse CI -- **Accessibility**: Axe-core + Playwright -- **CI/CD**: Gitea Actions - -### Current Status - -- βœ… **428 Unit Tests** (94.88% coverage - exceeds 85% target) -- βœ… **92 E2E Tests** across 4 browsers -- βœ… **23 Visual Regression Tests** per browser -- βœ… **Performance Budgets** with Lighthouse CI -- βœ… **WCAG 2.1 AA Compliance** with automated testing -- βœ… **Bundle Analysis** with automated monitoring -- βœ… **Web Vitals Tracking** with real-time metrics -- βœ… **Performance Optimization** with React.memo and code splitting - -## πŸ— Testing Architecture - -### Test Pyramid - -- **Unit Tests**: Fast, focused, high coverage (94.88%) -- **Integration Tests**: Component interactions, data flow -- **E2E Tests**: Critical user journeys, cross-browser compatibility - -### Testing Philosophy - -**JSDOM Limitations**: Unit tests in JSDOM can't truly test responsive behavior since CSS media queries aren't evaluated. Therefore: - -- **Unit/Integration Tests**: Test component structure, accessibility, and configuration -- **E2E Tests**: Test real responsive behavior at actual viewport widths -- **Visual Tests**: Capture visual consistency across breakpoints - -## πŸš€ Quick Start - -### Prerequisites - -```bash -# Install dependencies -npm install - -# Install Playwright browsers -npx playwright install -``` - -### Essential Commands - -```bash -# Unit tests with coverage -npm test - -# E2E tests -npm run e2e - -# Visual regression tests -npm run visual:test - -# Performance tests -npm run lhci - -# Storybook tests -npm run test:sb -``` - -## πŸ§ͺ Test Types & Coverage - -### Test Structure - -``` -tests/ -β”œβ”€β”€ unit/ # Component unit tests -β”‚ β”œβ”€β”€ Button.test.jsx # 12 tests -β”‚ β”œβ”€β”€ Logo.test.jsx # 12 tests -β”‚ β”œβ”€β”€ RuleCard.test.jsx # 18 tests -β”‚ β”œβ”€β”€ SectionHeader.test.jsx # 17 tests -β”‚ β”œβ”€β”€ NumberedCard.test.jsx # 18 tests -β”‚ └── accessibility.test.jsx # 18 tests -β”œβ”€β”€ integration/ # Component integration tests -β”‚ β”œβ”€β”€ component-interactions.integration.test.jsx -β”‚ β”œβ”€β”€ page-flow.integration.test.jsx -β”‚ β”œβ”€β”€ user-journey.integration.test.jsx -β”‚ β”œβ”€β”€ layout.integration.test.jsx -β”‚ └── ContentLockup.integration.test.jsx -└── e2e/ # End-to-end tests - β”œβ”€β”€ homepage.spec.ts # Homepage functionality - β”œβ”€β”€ user-journeys.spec.ts # User workflows - β”œβ”€β”€ header.responsive.spec.js # Responsive header - β”œβ”€β”€ footer.responsive.spec.js # Responsive footer - β”œβ”€β”€ visual-regression.spec.ts # Visual consistency - β”œβ”€β”€ accessibility.spec.ts # Accessibility compliance - └── performance.spec.ts # Performance metrics -``` - -### Coverage Requirements - -- **Statements**: >85% (Current: 94.88%) βœ… -- **Branches**: >80% (Current: 86.93%) βœ… -- **Functions**: >80% (Current: 88.67%) βœ… -- **Lines**: >85% (Current: 94.88%) βœ… - -## 🧩 Unit & Integration Testing - -### Framework - -- **Vitest**: Fast unit test runner -- **JSDOM**: Browser environment simulation -- **React Testing Library**: Component testing utilities -- **MSW**: API mocking - -### Configuration - -```javascript -// vitest.config.js -export default defineConfig({ - plugins: [react({ jsxRuntime: "automatic" })], - test: { - environment: "jsdom", - setupFiles: ["./vitest.setup.js"], - coverage: { - provider: "v8", - thresholds: { lines: 85, functions: 85, statements: 85, branches: 80 }, - }, - }, -}); -``` - -### Writing Unit Tests - -```jsx -// tests/unit/Component.test.jsx -import { render, screen } from "@testing-library/react"; -import { describe, test, expect, afterEach } from "vitest"; -import { cleanup } from "@testing-library/react"; -import Component from "../../app/components/Component"; - -describe("Component", () => { - afterEach(() => cleanup()); - - test("renders correctly", () => { - render(); - expect(screen.getByRole("button")).toBeInTheDocument(); - }); - - test("handles user interactions", async () => { - const user = userEvent.setup(); - render(); - - const button = screen.getByRole("button"); - await user.click(button); - - expect(button).toHaveClass("clicked"); - }); -}); -``` - -### Testing Library Queries (Priority Order) - -1. **`getByRole`**: Most accessible, tests user experience -2. **`getByLabelText`**: For form inputs -3. **`getByText`**: For content -4. **`getByTestId`**: Last resort, avoid when possible - -### Integration Testing - -```jsx -test("components work together", () => { - render( -
-
- -
-
, - ); - - // Test that components complement each other - expect(screen.getByRole("banner")).toBeInTheDocument(); - expect(screen.getByRole("main")).toBeInTheDocument(); - expect(screen.getByRole("contentinfo")).toBeInTheDocument(); -}); -``` - -### Available Scripts - -```bash -npm test # Run all tests with coverage -npm run test:watch # Run tests in watch mode -npm run test:ui # Run tests with UI -``` - -## 🌐 E2E Testing - -### Framework - -- **Playwright**: Cross-browser E2E testing -- **Browsers**: Chromium, Firefox, WebKit, Mobile -- **Accessibility**: Axe-core integration - -### Configuration - -```typescript -// playwright.config.ts -export default defineConfig({ - testDir: "./tests/e2e", - 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"] } }, - ], - use: { - timezoneId: "UTC", - locale: "en-US", - headless: true, - }, -}); -``` - -### Test Categories - -#### 1. Functional Tests - -- Page loading and sections -- Component functionality -- Navigation and interactions -- User workflows - -#### 2. Responsive Tests - -- Layout changes between breakpoints -- Component visibility at different viewports -- Interactive behavior across screen sizes - -#### 3. Accessibility Tests - -- WCAG 2.1 AA compliance -- Screen reader compatibility -- Keyboard navigation -- Color contrast - -### Writing E2E Tests - -```typescript -// tests/e2e/example.spec.ts -import { test, expect } from "@playwright/test"; - -test.describe("Feature", () => { - test.beforeEach(async ({ page }) => { - await page.goto("/"); - }); - - test("should work correctly", async ({ page }) => { - await expect(page).toHaveTitle(/CommunityRule/); - await expect(page.locator("h1")).toBeVisible(); - }); - - test("responsive behavior", async ({ page }) => { - // Test mobile viewport - await page.setViewportSize({ width: 375, height: 667 }); - await expect(page.getByTestId("mobile-nav")).toBeVisible(); - - // Test desktop viewport - await page.setViewportSize({ width: 1280, height: 800 }); - await expect(page.getByTestId("desktop-nav")).toBeVisible(); - }); -}); -``` - -### Available Scripts - -```bash -npm run e2e # Run all E2E tests -npm run e2e:ui # Run E2E tests with UI -npm run e2e:serve # Start dev server and run tests -``` - -## 🎨 Visual Regression Testing - -### Overview - -Visual regression testing ensures UI consistency across browsers and prevents unintended visual changes by comparing screenshots against baseline images. - -### Configuration - -- **Snapshot Template**: `{testDir}/{testFileName}-snapshots/{arg}-{projectName}.png` -- **Deterministic Rendering**: Fixed timezone (UTC), locale (en-US), viewport -- **Tolerance**: 2% pixel difference or 500 pixels maximum -- **Animation Handling**: Disabled during capture - -### Screenshots Generated - -- **Full page screenshots** (mobile, tablet, desktop) -- **Component screenshots** (hero, logo wall, cards, etc.) -- **Interactive states** (hover, focus, loading, error) -- **Special modes** (dark mode, high contrast, reduced motion) - -### Breakpoint Coverage - -- **Mobile**: 375x667 (iPhone) -- **Tablet**: 768x1024 (iPad) -- **Desktop**: 1280x800 (Standard) -- **Large Desktop**: 1920x1080 (Full HD) - -### Managing Visual Changes - -```bash -# Update baselines after intentional changes -npm run visual:update - -# Run visual regression tests -npm run visual:test - -# Run with UI for debugging -npm run visual:ui -``` - -### Snapshot Management - -```bash -# Update snapshots for all projects -PLAYWRIGHT_UPDATE_SNAPSHOTS=1 npx playwright test tests/e2e/visual-regression.spec.ts - -# Update snapshots for specific project -PLAYWRIGHT_UPDATE_SNAPSHOTS=1 npx playwright test tests/e2e/visual-regression.spec.ts --project=chromium - -# View test results -npx playwright show-report -``` - -## β™Ώ Accessibility Testing - -### Framework - -- **Unit Level**: jest-axe with Vitest (`tests/accessibility/unit/`) -- **E2E Level**: Playwright accessibility tests (`tests/accessibility/e2e/`) -- **Standards**: WCAG 2.1 AA compliance - -### Test Organization - -Accessibility tests are organized in a dedicated `tests/accessibility/` folder: - -``` -tests/accessibility/ -β”œβ”€β”€ unit/ # Unit-level accessibility tests -β”‚ └── components.test.jsx # Component accessibility (jest-axe) -└── e2e/ # E2E accessibility tests - └── wcag-compliance.spec.ts # WCAG compliance (Playwright) -``` - -### Unit-Level Accessibility Testing - -```jsx -// tests/accessibility/unit/components.test.jsx -import { axe, toHaveNoViolations } from "jest-axe"; - -test("component has no accessibility violations", async () => { - const { container } = render(); - const results = await axe(container); - expect(results).toHaveNoViolations(); -}); -``` - -### E2E Accessibility Testing - -```typescript -// tests/accessibility/e2e/wcag-compliance.spec.ts -import { test, expect } from "@playwright/test"; - -test("WCAG 2.1 AA compliance - homepage", async ({ page }) => { - await page.goto("/"); - - // Check for proper HTML structure - const html = page.locator("html"); - const lang = await html.getAttribute("lang"); - expect(lang).toBeTruthy(); - - // Check for main heading - const h1 = page.locator("h1").first(); - await expect(h1).toBeVisible(); -}); -``` - -### Running Accessibility Tests - -```bash -# Run all accessibility tests -npm test tests/accessibility/ - -# Run unit accessibility tests only -npm test tests/accessibility/unit/ - -# Run E2E accessibility tests only -npx playwright test tests/accessibility/e2e/ - -# Run specific accessibility test -npx playwright test tests/accessibility/e2e/wcag-compliance.spec.ts -``` - -### Manual Testing Checklist - -- [ ] Screen reader compatibility -- [ ] Keyboard navigation -- [ ] Color contrast (WCAG AA) -- [ ] Focus management -- [ ] ARIA attributes -- [ ] Semantic HTML - -### WCAG 2.1 AA Requirements - -- **Perceivable**: Text alternatives, captions, adaptable content -- **Operable**: Keyboard accessible, timing adjustable, navigation -- **Understandable**: Readable, predictable, input assistance -- **Robust**: Compatible with assistive technologies - -## ⚑ Performance Testing - -### Framework - -- **Lighthouse CI**: Automated performance testing -- **Bundle Analysis**: Real-time bundle size monitoring -- **Web Vitals Tracking**: Core Web Vitals collection and reporting -- **Performance Monitoring**: Comprehensive performance metrics -- **Performance Budgets**: Defined thresholds with automated enforcement - -### Configuration - -```json -// .lighthouserc.json -{ - "ci": { - "collect": { - "url": ["http://localhost:3010"], - "chromeFlags": [ - "--no-sandbox", - "--disable-dev-shm-usage", - "--disable-gpu", - "--headless" - ] - }, - "assert": { - "assertions": { - "categories:performance": ["warn", { "minScore": 0.8 }], - "categories:accessibility": ["error", { "minScore": 0.8 }] - } - } - } -} -``` - -### Performance Metrics - -- **Core Web Vitals**: LCP < 2.5s, FID < 100ms, CLS < 0.1 -- **Performance Score**: >80 -- **Accessibility Score**: >80 -- **Best Practices**: >90 -- **Bundle Size**: <250KB gzipped (currently 101KB) - -### Performance Budgets - -- **First Contentful Paint**: <3000ms -- **Largest Contentful Paint**: <5000ms -- **First Input Delay**: <100ms -- **TTFB**: <700ms -- **Bundle Size**: <250KB gzipped -- **Total Bundle Size**: <2MB - -### Performance Optimizations - -- **βœ… Code Splitting**: Dynamic imports for non-critical components -- **βœ… React.memo**: Applied to all 30+ components -- **βœ… Image Optimization**: Enhanced `next/image` with lazy loading -- **βœ… Font Optimization**: Preloading and fallbacks -- **βœ… Bundle Analysis**: Real-time monitoring with budgets -- **βœ… Error Boundaries**: Comprehensive error handling - -### Available Scripts - -```bash -# Individual monitoring tools -npm run bundle:analyze # Analyze bundle sizes and budgets -npm run performance:monitor # Performance metrics and Lighthouse CI -npm run web-vitals:track # Core Web Vitals tracking - -# Comprehensive testing -npm run test:performance # All performance tests -npm run monitor:all # All monitoring tools - -# Traditional Lighthouse CI -npm run lhci # Run Lighthouse CI -npm run lhci:mobile # Run with mobile preset -npm run lhci:desktop # Run with desktop preset -``` - -### Performance Monitoring Dashboard - -Access the performance monitoring dashboard at `/monitor` to view: - -- Real-time Web Vitals metrics -- Historical performance data -- Bundle analysis results -- Performance budget status -- Optimization recommendations - -## πŸ”„ CI/CD Pipeline - -### Gitea Actions Workflow - -Location: `.gitea/workflows/ci.yaml` - -### Pipeline Jobs - -#### 1. Unit Tests - -- **Node.js versions**: 18, 20 -- **Coverage reporting**: Codecov integration -- **Parallel execution**: Matrix strategy - -#### 2. E2E Tests - -- **Browsers**: Chromium, Firefox, WebKit -- **Parallel execution**: Matrix strategy -- **Artifact upload**: Test results and reports - -#### 3. Visual Regression Tests - -- **Screenshot comparison**: Baseline vs current -- **Cross-browser validation**: All 4 browser projects - -#### 4. Performance Tests - -- **Lighthouse CI**: Performance budgets -- **Core Web Vitals**: Monitoring -- **Accessibility compliance** - -#### 5. Storybook Tests - -- **Component testing**: Automated tests -- **Accessibility validation**: WCAG compliance -- **Build verification**: Storybook compilation - -#### 6. Lint & Format - -- **ESLint**: Code quality -- **Prettier**: Code formatting - -#### 7. Build Verification - -- **Next.js build**: Application compilation -- **Storybook build**: Documentation compilation - -### Triggers - -```yaml -on: - push: - branches: [main, develop] - pull_request: - branches: [main, develop] -``` - -## πŸ›  Development Workflow - -### 1. Feature Development - -```bash -# Create feature branch -git checkout -b feature/new-component - -# Write tests first (TDD) -npm run test:watch - -# Implement feature -# Ensure tests pass - -# Run E2E tests -npm run e2e - -# Commit changes -git add . -git commit -m "feat: add new component with tests" -``` - -### 2. Pull Request Process - -1. **Create PR** β†’ CI pipeline starts automatically -2. **Review CI Results** β†’ All 7 jobs must pass -3. **Check Coverage** β†’ Ensure >85% coverage -4. **Review Visual Changes** β†’ Check screenshot diffs -5. **Merge** β†’ Only if all checks pass - -### 3. Visual Changes - -```bash -# Make visual changes -# Run visual regression tests -npm run visual:test - -# If changes are intentional, update baselines -npm run visual:update - -# Review and commit updated snapshots -git add tests/e2e/visual-regression.spec.ts-snapshots/ -git commit -m "Update visual regression snapshots for [describe changes]" -``` - -### 4. Performance Monitoring - -```bash -# Check performance before deploying -npm run lhci - -# Review performance budgets -# Update .lighthouserc.json if needed -``` - -## πŸ“‹ Best Practices - -### 1. Test-Driven Development - -- Write tests before implementation -- Use descriptive test names -- Test edge cases and error scenarios -- Maintain high test coverage - -### 2. Component Testing - -```jsx -// βœ… Good: Test behavior, not implementation -test("shows error message when form is invalid", () => { - render(
); - fireEvent.click(screen.getByRole("button")); - expect(screen.getByText("Please fill all fields")).toBeInTheDocument(); -}); - -// ❌ Avoid: Testing implementation details -test("calls onSubmit with form data", () => { - const mockSubmit = vi.fn(); - render(); - // Implementation details... -}); -``` - -### 3. E2E Testing - -- Test user workflows, not technical details -- Use semantic selectors (role, text, label) -- Test accessibility features -- Include error scenarios - -### 4. Visual Regression - -- Update baselines only for intentional changes -- Review screenshot diffs carefully -- Test across multiple viewports -- Consider animation states - -### 5. Performance Testing - -- Set realistic performance budgets -- Monitor Core Web Vitals -- Test on different network conditions -- Regular performance audits - -### 6. Responsive Testing - -```javascript -// βœ… Good: Test real viewport sizes -await page.setViewportSize({ width: 640, height: 700 }); - -// βœ… Good: Test visibility at breakpoints -if (bp.name === "xs") { - await expect(page.getByTestId("auth-xs")).toBeVisible(); -} - -// ❌ Avoid: Testing responsive behavior in JSDOM -// JSDOM doesn't evaluate CSS media queries -``` - -## πŸ”§ Troubleshooting - -### Common Issues - -#### 1. Unit Tests Failing - -```bash -# Run tests locally -npm test - -# Check for: -# - Missing imports -# - Incorrect assertions -# - Component changes -# - Test environment issues -``` - -#### 2. E2E Tests Failing - -```bash -# Run locally first -npm run e2e - -# Common issues: -# - Selector changes -# - Component structure changes -# - Network issues -# - Browser compatibility -``` - -#### 3. Visual Regression Failing - -```bash -# Check if changes are intentional -npm run visual:test - -# Update baselines if needed -npm run visual:update - -# Review screenshot diffs in CI artifacts -``` - -#### 4. Performance Tests Failing - -```bash -# Run locally -npm run lhci - -# Check performance budgets in .lighthouserc.json -# Optimize slow components -# Review bundle size -``` - -#### 5. CI Pipeline Issues - -```bash -# Check Gitea Actions logs -# Verify workflow configuration -# Check for missing dependencies -# Review environment variables -``` - -### Debug Commands - -```bash -# Debug unit tests -npm run test:ui - -# Debug E2E tests -npm run e2e:ui - -# Debug with browser dev tools -npx playwright test --debug - -# Run specific test file -npx playwright test tests/e2e/homepage.spec.ts - -# Run tests in headed mode -npx playwright test --headed -``` - -## πŸ“š Additional Resources - -### Documentation - -- [Vitest Documentation](https://vitest.dev/) -- [Playwright Documentation](https://playwright.dev/) -- [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) -- [Lighthouse CI](https://github.com/GoogleChrome/lighthouse-ci) -- [Storybook Testing](https://storybook.js.org/docs/writing-tests/introduction) - -### Tools - -- [Codecov](https://codecov.io/) - Coverage reporting -- [Axe-core](https://github.com/dequelabs/axe-core) - Accessibility testing -- [MSW](https://mswjs.io/) - API mocking - -### Best Practices - -- [Testing Best Practices](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library) -- [E2E Testing Guide](https://playwright.dev/docs/best-practices) -- [Visual Regression Testing](https://storybook.js.org/docs/writing-tests/visual-testing) - ---- - -**Last Updated**: December 2024 -**Framework Version**: Next.js 15 + React 19 + Tailwind 4 + Storybook 9 -**Maintained by**: CommunityRule Development Team diff --git a/docs/guides/testing-quick-reference.md b/docs/guides/testing-quick-reference.md deleted file mode 100644 index e2af098..0000000 --- a/docs/guides/testing-quick-reference.md +++ /dev/null @@ -1,357 +0,0 @@ -# Testing Quick Reference - -## πŸš€ Essential Commands - -### Daily Development - -```bash -# Run all tests with coverage -npm test - -# Watch mode (during development) -npm run test:watch - -# E2E tests -npm run e2e - -# Visual regression tests -npm run visual:test - -# Performance check -npm run lhci - -# Performance monitoring -npm run test:performance # Comprehensive performance testing -npm run bundle:analyze # Bundle size analysis -npm run web-vitals:track # Web Vitals tracking -npm run monitor:all # All monitoring tools - -# Storybook tests -npm run test:sb -``` - -### Test UI & Debugging - -```bash -# Debug unit tests -npm run test:ui - -# Debug E2E tests -npm run e2e:ui - -# Debug with browser -npx playwright test --debug - -# Run tests in headed mode -npx playwright test --headed -``` - -## πŸ“Š Current Test Status - -- **Unit Tests**: 94.88% βœ… (Target: >85%) -- **Integration Tests**: 5 comprehensive test suites βœ… -- **E2E Tests**: 92 tests across 4 browsers βœ… -- **Visual Regression**: 23 tests per browser βœ… -- **Accessibility Tests**: WCAG 2.1 AA compliance βœ… -- **Performance Tests**: Lighthouse CI with budgets βœ… -- **Bundle Analysis**: Real-time monitoring with budgets βœ… -- **Web Vitals Tracking**: Core Web Vitals collection βœ… -- **Performance Optimization**: React.memo + code splitting βœ… - -## πŸ”§ Common Test Commands - -### Unit Testing - -```bash -# Run specific test file -npm test -- --run tests/unit/Component.test.jsx - -# Run tests matching pattern -npm test -- --run Component - -# Run with coverage report -npm test -- --coverage - -# Run in watch mode -npm run test:watch -``` - -### E2E Testing - -```bash -# Run specific test file -npm run e2e -- tests/e2e/homepage.spec.ts - -# Run specific project (browser) -npm run e2e -- --project=chromium - -# Run with headed browser -npm run e2e -- --headed - -# Run with debug mode -npm run e2e -- --debug -``` - -### Visual Regression - -```bash -# Update snapshots for all projects -npm run visual:update - -# Update snapshots for specific project -PLAYWRIGHT_UPDATE_SNAPSHOTS=1 npx playwright test tests/e2e/visual-regression.spec.ts --project=chromium - -# View test results -npx playwright show-report -``` - -### Performance Testing - -```bash -# Run mobile performance test -npm run lhci:mobile - -# Run desktop performance test -npm run lhci:desktop - -# Run with custom budget -npm run performance:budget -``` - -### Accessibility Testing - -```bash -# Run all accessibility tests -npm test tests/accessibility/ - -# Run unit accessibility tests only -npm test tests/accessibility/unit/ - -# Run E2E accessibility tests only -npx playwright test tests/accessibility/e2e/ - -# Run specific accessibility test -npx playwright test tests/accessibility/e2e/wcag-compliance.spec.ts -``` - -## πŸ“± Browser Support - -| Browser | Project Name | Status | -| ----------- | ------------ | --------------- | -| **Chrome** | `chromium` | βœ… Full Support | -| **Firefox** | `firefox` | βœ… Full Support | -| **Safari** | `webkit` | βœ… Full Support | -| **Mobile** | `mobile` | βœ… Full Support | - -## 🎯 Testing Best Practices - -### 1. Test Structure (AAA Pattern) - -```jsx -test("should do something", () => { - // Arrange: Set up test data - const data = { name: "Test" }; - - // Act: Perform the action - const result = processData(data); - - // Assert: Verify the outcome - expect(result).toBe("Processed Test"); -}); -``` - -### 2. Query Priority - -1. **`getByRole`** - Most accessible, tests user experience -2. **`getByLabelText`** - For form inputs -3. **`getByText`** - For content -4. **`getByTestId`** - Last resort, avoid when possible - -### 3. Async Testing - -```jsx -test("async operation", async () => { - const user = userEvent.setup(); - - render(); - const button = screen.getByRole("button"); - - await user.click(button); - await waitFor(() => { - expect(screen.getByText("Success")).toBeInTheDocument(); - }); -}); -``` - -### 4. Responsive Testing - -```javascript -// βœ… Good: Test real viewport sizes -await page.setViewportSize({ width: 640, height: 700 }); - -// βœ… Good: Test visibility at breakpoints -if (bp.name === "xs") { - await expect(page.getByTestId("auth-xs")).toBeVisible(); -} - -// ❌ Avoid: Testing responsive behavior in JSDOM -// JSDOM doesn't evaluate CSS media queries -``` - -## πŸ” Common Issues & Solutions - -### Visual Regression Failures - -```bash -# Regenerate snapshots -npm run visual:update - -# Check for environment differences -# Ensure deterministic rendering in Playwright config -``` - -### E2E Test Failures - -```bash -# Use waitFor instead of waitForTimeout -await page.waitForSelector("button", { state: "visible" }); - -# Use role-based selectors -await page.getByRole("button", { name: "Submit" }).click(); - -# Check for selector changes -# Verify component structure hasn't changed -``` - -### Performance Test Failures - -```bash -# Check Chrome path on macOS -# Ensure arm64 Chrome for Apple Silicon -# Verify performance budgets in .lighthouserc.json -``` - -### Unit Test Failures - -```bash -# Check for missing imports -# Verify component exports -# Ensure test environment setup -# Check for component changes -``` - -## πŸ“ˆ Performance Budgets - -### Lighthouse CI Targets - -- **Performance Score**: >80 -- **Accessibility Score**: >80 -- **Best Practices**: >90 -- **SEO Score**: >90 - -### Core Web Vitals - -- **LCP**: <2.5s -- **FID**: <100ms -- **CLS**: <0.1 - -### Performance Budgets - -- **First Contentful Paint**: <3000ms -- **Largest Contentful Paint**: <5000ms -- **First Input Delay**: <100ms -- **TTFB**: <700ms - -## πŸ”„ CI/CD Pipeline Jobs - -1. **Unit Tests** (Node 18, 20) - Coverage reporting -2. **E2E Tests** (Chromium, Firefox, WebKit) - Cross-browser testing -3. **Visual Regression Tests** - Screenshot comparison -4. **Performance Tests** - Lighthouse CI with budgets -5. **Storybook Tests** - Component testing & accessibility -6. **Lint & Format** - Code quality & formatting -7. **Build Verification** - Next.js & Storybook builds - -## πŸ“ Test File Structure - -``` -tests/ -β”œβ”€β”€ unit/ # Component tests -β”‚ β”œβ”€β”€ Button.test.jsx # 12 tests -β”‚ β”œβ”€β”€ Logo.test.jsx # 12 tests -β”‚ β”œβ”€β”€ RuleCard.test.jsx # 18 tests -β”‚ β”œβ”€β”€ SectionHeader.test.jsx # 17 tests -β”‚ β”œβ”€β”€ NumberedCard.test.jsx # 18 tests -β”‚ └── ... # Other component tests -β”œβ”€β”€ integration/ # Integration tests -β”‚ β”œβ”€β”€ component-interactions.integration.test.jsx -β”‚ β”œβ”€β”€ page-flow.integration.test.jsx -β”‚ β”œβ”€β”€ user-journey.integration.test.jsx -β”‚ β”œβ”€β”€ layout.integration.test.jsx -β”‚ └── ContentLockup.integration.test.jsx -β”œβ”€β”€ accessibility/ # Accessibility-focused tests -β”‚ β”œβ”€β”€ unit/ # Unit-level accessibility (jest-axe) -β”‚ β”‚ └── components.test.jsx # Component accessibility tests -β”‚ └── e2e/ # E2E accessibility (Playwright + axe-core) -β”‚ └── wcag-compliance.spec.ts # WCAG compliance tests -└── e2e/ # General E2E tests - β”œβ”€β”€ homepage.spec.ts # Homepage functionality - β”œβ”€β”€ user-journeys.spec.ts # User workflows - β”œβ”€β”€ header.responsive.spec.js # Responsive header - β”œβ”€β”€ footer.responsive.spec.js # Responsive footer - β”œβ”€β”€ visual-regression.spec.ts # Visual consistency - β”œβ”€β”€ accessibility.spec.ts # General accessibility tests - └── performance.spec.ts # Performance metrics -``` - -## 🎨 Visual Regression Screenshots - -### Generated Screenshots - -- Full page (mobile, tablet, desktop) -- Component sections (hero, logo wall, cards) -- Interactive states (hover, focus, loading) -- Special modes (dark, high contrast, reduced motion) - -### Managing Changes - -```bash -# Intentional changes -npm run visual:update - -# Review changes -git diff tests/e2e/visual-regression.spec.ts-snapshots/ - -# Commit updated snapshots -git add tests/e2e/visual-regression.spec.ts-snapshots/ -git commit -m "Update visual regression snapshots for [describe changes]" -``` - -## πŸ“ˆ Monitoring - -### Test Metrics - -- **Unit Tests**: 305 tests (94.88% coverage) -- **E2E Tests**: 92 tests (4 browsers) -- **Visual Screenshots**: 92 baselines per browser -- **Coverage**: >85% target (exceeded) - -### CI Metrics - -- **Pipeline Jobs**: 7 parallel jobs -- **Execution Time**: Monitor build performance -- **Success Rate**: Track pipeline stability -- **Artifacts**: Test results and screenshots - -## πŸ”— Useful Links - -- **Full Testing Documentation**: [docs/guides/testing-framework.md](./testing-framework.md) -- **Vitest Docs**: https://vitest.dev/ -- **Playwright Docs**: https://playwright.dev/ -- **React Testing Library**: https://testing-library.com/docs/react-testing-library/intro/ -- **Lighthouse CI**: https://github.com/GoogleChrome/lighthouse-ci - ---- - -**Quick Reference Version**: December 2024 -**For detailed guidelines, see [testing-framework.md](./testing-framework.md)** diff --git a/docs/guides/testing.md b/docs/guides/testing.md deleted file mode 100644 index d22c79d..0000000 --- a/docs/guides/testing.md +++ /dev/null @@ -1,258 +0,0 @@ -# Testing Strategy for CommunityRule - -## Overview - -This document outlines our comprehensive testing strategy that properly separates unit testing from responsive behavior testing, following best practices for JSDOM limitations and real browser testing. - -## Current Test Status - -- **236 total tests** across the project -- **227 tests passing** (96.2% success rate) -- **9 tests failing** (performance and interaction tests) -- **15 test files** covering all major components -- **Performance Monitoring**: Comprehensive regression detection and budget enforcement - -## Testing Philosophy - -### The Problem with JSDOM and Responsive Testing - -**Short take: Unit tests in JSDOM can't truly "switch breakpoints."** JSDOM doesn't evaluate CSS media queries, so Tailwind's `hidden sm:block …` won't change visibility when you "resize" the window. - -### Solution: Proper Test Separation - -- **Unit / component tests (Vitest + RTL):** assert **structure and classes**, not responsive visibility. -- **Responsive behavior:** verify with **browser-based tests** (Playwright) or **visual tests** (Chromatic/Storybook) at real viewport widths. - -## Test Categories - -### 1. Unit Tests (Vitest + React Testing Library) - -**Purpose:** Test component structure, accessibility, and configuration data. - -**What to test:** - -- DOM roles/labels exist: `role="banner"`, nav landmark, menu items -- The right **Tailwind classes** are present on wrappers (`block sm:hidden`, `hidden md:block`, etc.) -- Data-driven bits produce the expected count/order (e.g., `navigationItems`, `avatarImages`, `logoConfig`) -- Component configuration and exported data structures - -**Example:** - -```javascript -// tests/unit/Header.structure.test.js -test("logo wrappers include breakpoint classes", () => { - render(
); - const logoWrappers = screen.getAllByTestId("logo-wrapper"); - - // Check first logo variant (xs only) - expect(logoWrappers[0]).toHaveClass("block", "sm:hidden"); - - // Check second logo variant (sm only) - expect(logoWrappers[1]).toHaveClass("hidden", "sm:block", "md:hidden"); -}); -``` - -### 2. Browser-Based Tests (Playwright) - -**Purpose:** Test real responsive behavior at actual viewport widths. - -**What to test:** - -- **Visibility** at real breakpoints -- **Layout changes** between breakpoints -- **Interactive behavior** at different screen sizes -- **Accessibility** across viewports - -**Example:** - -```javascript -// tests/e2e/header.responsive.spec.js -const breakpoints = [ - { name: "xs", width: 360, height: 700 }, - { name: "sm", width: 640, height: 700 }, - { name: "md", width: 768, height: 700 }, - { name: "lg", width: 1024, height: 700 }, - { name: "xl", width: 1280, height: 700 }, -]; - -for (const bp of breakpoints) { - test(`header layout at ${bp.name}`, async ({ page }) => { - await page.setViewportSize({ width: bp.width, height: bp.height }); - await page.goto("/"); - - const nav = page.getByRole("navigation", { name: /main navigation/i }); - await expect(nav).toBeVisible(); - }); -} -``` - -### 3. Visual Tests (Storybook + Chromatic) - -**Purpose:** Visual regression testing and design system validation. - -**What to test:** - -- **Visual diffs** per breakpoint -- **Design consistency** across viewports -- **Component variations** and states - -**Example:** - -```javascript -// stories/Header.responsive.stories.js -export default { - parameters: { - chromatic: { - viewports: [360, 640, 768, 1024, 1280], - delay: 100, - }, - }, -}; -``` - -## Component Improvements - -### Header Component Enhancements - -1. **Added Test IDs** for easier testing: - - ```jsx -
- {renderLogo(config.size, config.showText)} -
- ``` - -2. **Exported Configuration** for testing: - - ```javascript - export const navigationItems = [...]; - export const avatarImages = [...]; - export const logoConfig = [...]; - ``` - -3. **Structured Breakpoint Containers**: - ```jsx -
-
-
- ``` - -## Test File Structure - -``` -tests/ -β”œβ”€β”€ unit/ # Unit tests (Vitest + RTL) -β”‚ β”œβ”€β”€ Header.test.jsx # CONSOLIDATED: Comprehensive Header tests -β”‚ β”œβ”€β”€ Footer.test.jsx -β”‚ β”œβ”€β”€ Layout.test.jsx -β”‚ └── Page.test.jsx -β”œβ”€β”€ integration/ # Integration tests -β”‚ └── ContentLockup.integration.test.jsx -β”œβ”€β”€ e2e/ # Browser tests (Playwright) -β”‚ └── header.responsive.spec.js # NEW: Responsive behavior tests -└── stories/ # Storybook stories - └── Header.responsive.stories.js # NEW: Visual testing -``` - -## Best Practices - -### Unit Testing (JSDOM) - -1. **Test structure, not visibility**: - - ```javascript - // βœ… Good: Test classes exist - expect(element).toHaveClass("block", "sm:hidden"); - - // ❌ Bad: Test visibility (doesn't work in JSDOM) - expect(element).toBeVisible(); - ``` - -2. **Use test IDs for containers**: - - ```javascript - // βœ… Good: Test specific containers - const logoWrapper = screen.getByTestId("logo-wrapper"); - - // ❌ Bad: Query by complex class strings - const logoWrapper = document.querySelector(".block.sm\\:hidden"); - ``` - -3. **Test configuration data**: - ```javascript - // βœ… Good: Test exported configuration - expect(navigationItems).toHaveLength(3); - expect(logoConfig).toHaveLength(5); - ``` - -### Browser Testing (Playwright) - -1. **Test real viewport sizes**: - - ```javascript - await page.setViewportSize({ width: 640, height: 700 }); - ``` - -2. **Test visibility at breakpoints**: - - ```javascript - if (bp.name === "xs") { - await expect(page.getByTestId("auth-xs")).toBeVisible(); - } - ``` - -3. **Test accessibility across viewports**: - - ```javascript - const interactiveElements = [ - page.getByRole("link", { name: /use cases/i }), - page.getByRole("button", { name: /create rule/i }), - ]; - - for (const element of interactiveElements) { - await expect(element).toBeVisible(); - await expect(element).toBeEnabled(); - } - ``` - -## Running Tests - -### Unit Tests - -```bash -npm test # Run all unit tests -npm test tests/unit/ # Run only unit tests -npm test Header.structure # Run specific test file -``` - -### Browser Tests - -```bash -npx playwright test # Run all browser tests -npx playwright test header.responsive.spec.js # Run specific test -``` - -### Visual Tests - -```bash -npm run storybook # Start Storybook -npx chromatic --project-token=xxx # Run visual tests -``` - -## Future Improvements - -1. **Add more Playwright tests** for other components -2. **Set up Chromatic** for visual regression testing -3. **Add performance tests** for responsive behavior -4. **Create component-specific test utilities** -5. **Add accessibility testing** with axe-core - -## Key Takeaways - -1. **JSDOM limitations** require separating structure tests from visibility tests -2. **Test IDs** make testing more reliable and maintainable -3. **Exported configuration** enables better data structure testing -4. **Real browser testing** is essential for responsive behavior -5. **Visual testing** catches design regressions across breakpoints - -This strategy provides comprehensive coverage while respecting the limitations of different testing environments. diff --git a/docs/guides/visual-regression.md b/docs/guides/visual-regression.md deleted file mode 100644 index d788b80..0000000 --- a/docs/guides/visual-regression.md +++ /dev/null @@ -1,391 +0,0 @@ -# Visual Regression Testing Guide - -## Overview - -Visual regression testing ensures UI consistency across browsers and prevents unintended visual changes by comparing screenshots against baseline images. This guide covers the complete workflow for managing visual regression tests. - -## πŸš€ Quick Start - -### First-Time Setup - -```bash -# 1. Generate baseline snapshots for all projects -npm run visual:update - -# 2. Verify snapshots were created -ls tests/e2e/visual-regression.spec.ts-snapshots/ - -# 3. Commit the snapshots -git add tests/e2e/visual-regression.spec.ts-snapshots/ -git commit -m "Add baseline visual regression snapshots" - -# 4. Verify setup works -npm run visual:test -``` - -### Daily Workflow - -```bash -# Run visual regression tests -npm run visual:test - -# Run with UI for debugging -npm run visual:ui - -# Update snapshots after UI changes -npm run visual:update -``` - -## πŸ“ Managing Visual Changes - -### When UI Changes Are Intentional - -1. **Make your UI changes** (design updates, component modifications, etc.) - -2. **Update snapshots to reflect new design:** - - ```bash - npm run visual:update - ``` - -3. **Review changes:** - - ```bash - git diff tests/e2e/visual-regression.spec.ts-snapshots/ - ``` - -4. **Commit updated snapshots:** - ```bash - git add tests/e2e/visual-regression.spec.ts-snapshots/ - git commit -m "Update snapshots for [describe changes]" - ``` - -### When UI Changes Are Unintentional - -1. **Investigate the failure** - Check what changed and why -2. **Fix the regression** - Revert or fix the unintended change -3. **Re-run tests** - Ensure they pass without updating snapshots -4. **Commit the fix** - Don't update snapshots for bug fixes - -## βš™οΈ Configuration - -### Playwright Configuration - -The visual regression tests use these key settings in `playwright.config.ts`: - -```typescript -export default defineConfig({ - expect: { - toHaveScreenshot: { - animations: "disabled", - maxDiffPixelRatio: 0.02, // 2% tolerance - maxDiffPixels: 500, // 500 pixel tolerance - }, - }, - use: { - timezoneId: "UTC", // Consistent timezone - locale: "en-US", // Consistent locale - headless: true, // Headless for CI - }, - snapshotPathTemplate: - "{testDir}/{testFileName}-snapshots/{arg}-{projectName}.png", -}); -``` - -### Deterministic Rendering - -To ensure consistent screenshots across environments: - -- **Fixed timezone**: UTC -- **Fixed locale**: en-US -- **Fixed viewport**: 1280x800 (configurable per test) -- **Disabled animations**: Prevents timing-related differences -- **Browser-specific snapshots**: Separate baselines per browser - -## πŸ“± Breakpoint Coverage - -### Standard Viewports - -| Breakpoint | Width | Height | Description | -| ----------- | ------ | ------ | ---------------- | -| **Mobile** | 375px | 667px | iPhone portrait | -| **Tablet** | 768px | 1024px | iPad portrait | -| **Desktop** | 1280px | 800px | Standard desktop | -| **Large** | 1920px | 1080px | Full HD desktop | - -### Custom Viewports - -```typescript -test("mobile layout", async ({ page }) => { - await page.setViewportSize({ width: 375, height: 667 }); - await expect(page).toHaveScreenshot("mobile-layout.png"); -}); - -test("tablet layout", async ({ page }) => { - await page.setViewportSize({ width: 768, height: 1024 }); - await expect(page).toHaveScreenshot("tablet-layout.png"); -}); -``` - -## 🎨 Screenshot Types - -### Full Page Screenshots - -```typescript -test("homepage full page", async ({ page }) => { - await expect(page).toHaveScreenshot("homepage-full.png", { - fullPage: true, - animations: "disabled", - scale: "css", - }); -}); -``` - -### Component Screenshots - -```typescript -test("hero section", async ({ page }) => { - const hero = page.locator("[data-testid='hero-section']"); - await expect(hero).toHaveScreenshot("hero-section.png"); -}); -``` - -### Interactive States - -```typescript -test("button hover state", async ({ page }) => { - const button = page.getByRole("button", { name: "Submit" }); - - // Normal state - await expect(button).toHaveScreenshot("button-normal.png"); - - // Hover state - await button.hover(); - await expect(button).toHaveScreenshot("button-hover.png"); -}); -``` - -### Special Modes - -```typescript -test("dark mode", async ({ page }) => { - // Enable dark mode - await page.evaluate(() => { - document.documentElement.classList.add("dark"); - }); - - await expect(page).toHaveScreenshot("dark-mode.png"); -}); - -test("high contrast", async ({ page }) => { - // Enable high contrast - await page.evaluate(() => { - document.body.style.filter = "contrast(200%)"; - }); - - await expect(page).toHaveScreenshot("high-contrast.png"); -}); -``` - -## πŸ”„ Snapshot Management - -### Update Commands - -```bash -# Update all snapshots for all projects -npm run visual:update - -# Update snapshots for specific project -PLAYWRIGHT_UPDATE_SNAPSHOTS=1 npx playwright test tests/e2e/visual-regression.spec.ts --project=chromium - -# Update snapshots for specific test -PLAYWRIGHT_UPDATE_SNAPSHOTS=1 npx playwright test tests/e2e/visual-regression.spec.ts --grep="homepage" -``` - -### Snapshot Naming Convention - -Snapshots follow this pattern: - -``` -{testDir}/{testFileName}-snapshots/{arg}-{projectName}.png -``` - -Examples: - -- `tests/e2e/visual-regression.spec.ts-snapshots/homepage-full-chromium.png` -- `tests/e2e/visual-regression.spec.ts-snapshots/hero-section-firefox.png` -- `tests/e2e/visual-regression.spec.ts-snapshots/button-hover-webkit.png` -- `tests/e2e/visual-regression.spec.ts-snapshots/mobile-layout-mobile.png` - -### File Organization - -``` -tests/e2e/visual-regression.spec.ts-snapshots/ -β”œβ”€β”€ homepage-full-chromium.png -β”œβ”€β”€ homepage-full-firefox.png -β”œβ”€β”€ homepage-full-webkit.png -β”œβ”€β”€ homepage-full-mobile.png -β”œβ”€β”€ hero-section-chromium.png -β”œβ”€β”€ hero-section-firefox.png -β”œβ”€β”€ hero-section-webkit.png -β”œβ”€β”€ hero-section-mobile.png -└── ... (92 total screenshots) -``` - -## πŸ› Troubleshooting - -### Common Issues - -#### 1. "Snapshot doesn't exist" errors - -**Cause**: Baseline snapshots haven't been generated or are missing - -**Solution**: - -```bash -# Regenerate all snapshots -npm run visual:update - -# Or regenerate for specific project -PLAYWRIGHT_UPDATE_SNAPSHOTS=1 npx playwright test tests/e2e/visual-regression.spec.ts --project=chromium -``` - -#### 2. Platform differences (macOS vs Linux) - -**Cause**: Different font rendering between platforms - -**Solution**: - -- Use CI-generated snapshots for consistency -- Ensure deterministic rendering settings -- Check font availability across platforms - -#### 3. Minor pixel differences - -**Cause**: Font rendering, anti-aliasing, scaling differences - -**Solution**: - -- Check tolerance settings in `playwright.config.ts` -- Use `scale: "css"` for consistent scaling -- Ensure deterministic CSS properties - -#### 4. Animation-related failures - -**Cause**: Animations not fully disabled - -**Solution**: - -- Ensure `animations: "disabled"` is set in test configuration -- Wait for animations to complete before screenshots -- Use `waitForTimeout` if necessary - -#### 5. Height differences (especially WebKit) - -**Cause**: WebKit may render elements with slightly different heights - -**Solution**: - -- Increase tolerance for height-sensitive tests -- Use `maxDiffPixels: 1000` for specific tests -- Consider using `ignoreSize: false` (default) - -### Debug Commands - -```bash -# Run with UI for visual debugging -npm run visual:ui - -# Run specific test with debugging -npx playwright test tests/e2e/visual-regression.spec.ts --grep="homepage" --debug - -# Run with headed browser -npx playwright test tests/e2e/visual-regression.spec.ts --headed - -# View test results -npx playwright show-report -``` - -### Environment Consistency - -To ensure consistent results: - -1. **Use same Node.js version** across environments -2. **Use same Playwright version** across environments -3. **Use same browser versions** when possible -4. **Set consistent environment variables** (timezone, locale) -5. **Use deterministic CSS** (avoid random values, timestamps) - -## πŸ“Š CI/CD Integration - -### CI Workflow - -Visual regression tests run automatically in the CI pipeline: - -- **Main branch**: Tests run against existing snapshots -- **Feature branches**: Tests run against existing snapshots -- **Artifacts**: Test results and screenshots uploaded for review - -### CI Best Practices - -1. **Don't regenerate snapshots in CI** for feature branches -2. **Use CI-generated snapshots** as the source of truth -3. **Review screenshot diffs** in CI artifacts -4. **Fail fast** on visual regressions - -### Artifact Management - -```yaml -# Example CI artifact configuration -- name: Upload visual regression results - if: always() - uses: actions/upload-artifact@v3 - with: - name: visual-regression-results - path: | - test-results/ - tests/e2e/visual-regression.spec.ts-snapshots/ -``` - -## 🎯 Best Practices - -### 1. Snapshot Management - -- **Update snapshots only for intentional changes** -- **Review all changes** before committing -- **Use descriptive names** for snapshot files -- **Keep snapshots in version control** - -### 2. Test Design - -- **Test critical UI components** first -- **Use consistent viewport sizes** across tests -- **Test responsive breakpoints** systematically -- **Include interactive states** when relevant - -### 3. Performance - -- **Limit snapshot count** to essential components -- **Use appropriate timeouts** for slow operations -- **Parallelize tests** when possible -- **Cache browser installations** in CI - -### 4. Maintenance - -- **Regular cleanup** of outdated snapshots -- **Update snapshots promptly** after UI changes -- **Monitor test execution time** and optimize -- **Review and update tolerance settings** as needed - -## πŸ“š Additional Resources - -- **Main Testing Documentation**: [testing-framework.md](./testing-framework.md) | [testing.md](./testing.md) -- **Playwright Visual Testing**: https://playwright.dev/docs/screenshots -- **Visual Regression Best Practices**: https://storybook.js.org/docs/writing-tests/visual-testing -- **CI/CD Integration**: [testing-quick-reference.md](./testing-quick-reference.md) -- **Performance Guide**: [performance.md](./performance.md) - ---- - -**Last Updated**: December 2024 -**Maintained by**: CommunityRule Development Team diff --git a/package.json b/package.json index 67bdeba..cf985a6 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,10 @@ "test": "vitest run --coverage", "test:watch": "vitest", "test:ui": "vitest --ui", + "test:component": "vitest run tests/components", "test:sb": "storybook dev -p 6006 & wait-on http://localhost:6006 && test-storybook --url http://localhost:6006", "e2e": "playwright test", + "test:e2e": "playwright test", "e2e:ui": "playwright test --ui", "e2e:performance": "playwright test tests/e2e/performance.spec.ts", "lhci": "lhci autorun", diff --git a/stories/Checkbox.stories.js b/stories/Checkbox.stories.js index b22d1af..2425cd6 100644 --- a/stories/Checkbox.stories.js +++ b/stories/Checkbox.stories.js @@ -1,11 +1,58 @@ import React from "react"; import Checkbox from "../app/components/Checkbox"; -import { - DefaultInteraction, - CheckedInteraction, - StandardInteraction, - InverseInteraction, -} from "../tests/storybook/Checkbox.interactions.test"; +import { within, userEvent } from "@storybook/test"; +import { expect } from "@storybook/test"; + +// Interaction functions for Storybook play functions +const DefaultInteraction = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const checkbox = canvas.getByRole("checkbox"); + expect(checkbox).toHaveAttribute("aria-checked", "false"); + await userEvent.click(checkbox); + expect(checkbox).toHaveAttribute("aria-checked", "true"); + await userEvent.click(checkbox); + expect(checkbox).toHaveAttribute("aria-checked", "false"); + }, +}; + +const CheckedInteraction = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const checkbox = canvas.getByRole("checkbox"); + expect(checkbox).toHaveAttribute("aria-checked", "true"); + await userEvent.click(checkbox); + expect(checkbox).toHaveAttribute("aria-checked", "false"); + }, +}; + +const StandardInteraction = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const checkboxes = canvas.getAllByRole("checkbox"); + expect(checkboxes).toHaveLength(2); + expect(checkboxes[0]).toHaveAttribute("aria-checked", "false"); + await userEvent.click(checkboxes[0]); + expect(checkboxes[0]).toHaveAttribute("aria-checked", "true"); + expect(checkboxes[1]).toHaveAttribute("aria-checked", "true"); + await userEvent.click(checkboxes[1]); + expect(checkboxes[1]).toHaveAttribute("aria-checked", "false"); + }, +}; + +const InverseInteraction = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const checkboxes = canvas.getAllByRole("checkbox"); + expect(checkboxes).toHaveLength(2); + expect(checkboxes[0]).toHaveAttribute("aria-checked", "false"); + await userEvent.click(checkboxes[0]); + expect(checkboxes[0]).toHaveAttribute("aria-checked", "true"); + expect(checkboxes[1]).toHaveAttribute("aria-checked", "true"); + await userEvent.click(checkboxes[1]); + expect(checkboxes[1]).toHaveAttribute("aria-checked", "false"); + }, +}; export default { title: "Forms/Checkbox", diff --git a/stories/RadioButton.stories.js b/stories/RadioButton.stories.js index 67ffbc5..0fa9d30 100644 --- a/stories/RadioButton.stories.js +++ b/stories/RadioButton.stories.js @@ -1,11 +1,54 @@ import React from "react"; import RadioButton from "../app/components/RadioButton"; -import { - DefaultInteraction, - CheckedInteraction, - StandardInteraction, - InverseInteraction, -} from "../tests/storybook/RadioButton.interactions.test"; +import { expect } from "@storybook/test"; +import { userEvent, within } from "@storybook/test"; + +// Interaction functions for Storybook play functions +const DefaultInteraction = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const radioButton = canvas.getByRole("radio"); + await expect(radioButton).toHaveAttribute("aria-checked", "false"); + await userEvent.click(radioButton); + await expect(radioButton).toHaveAttribute("aria-checked", "true"); + await userEvent.click(radioButton); + await expect(radioButton).toHaveAttribute("aria-checked", "true"); + }, +}; + +const CheckedInteraction = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const radioButton = canvas.getByRole("radio"); + await expect(radioButton).toHaveAttribute("aria-checked", "true"); + await userEvent.click(radioButton); + await expect(radioButton).toHaveAttribute("aria-checked", "true"); + }, +}; + +const StandardInteraction = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const radioButtons = canvas.getAllByRole("radio"); + await expect(radioButtons[0]).toHaveAttribute("aria-checked", "false"); + await expect(radioButtons[1]).toHaveAttribute("aria-checked", "true"); + await userEvent.click(radioButtons[0]); + await expect(radioButtons[0]).toHaveAttribute("aria-checked", "true"); + await expect(radioButtons[1]).toHaveAttribute("aria-checked", "false"); + }, +}; + +const InverseInteraction = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const radioButtons = canvas.getAllByRole("radio"); + await expect(radioButtons[0]).toHaveAttribute("aria-checked", "false"); + await expect(radioButtons[1]).toHaveAttribute("aria-checked", "true"); + await userEvent.click(radioButtons[0]); + await expect(radioButtons[0]).toHaveAttribute("aria-checked", "true"); + await expect(radioButtons[1]).toHaveAttribute("aria-checked", "false"); + }, +}; const meta = { title: "Forms/RadioButton", diff --git a/stories/RadioGroup.stories.js b/stories/RadioGroup.stories.js index 5aaf488..f0e2f33 100644 --- a/stories/RadioGroup.stories.js +++ b/stories/RadioGroup.stories.js @@ -1,11 +1,67 @@ import React from "react"; import RadioGroup from "../app/components/RadioGroup"; -import { - DefaultInteraction, - StandardInteraction, - InverseInteraction, - InteractiveInteraction, -} from "../tests/storybook/RadioGroup.interactions.test"; +import { expect } from "@storybook/test"; +import { userEvent, within } from "@storybook/test"; + +// Interaction functions for Storybook play functions +const DefaultInteraction = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const radioGroup = canvas.getByRole("radiogroup"); + const radioButtons = canvas.getAllByRole("radio"); + await expect(radioGroup).toBeInTheDocument(); + await expect(radioButtons).toHaveLength(3); + await expect(radioButtons[0]).toHaveAttribute("aria-checked", "true"); + await expect(radioButtons[1]).toHaveAttribute("aria-checked", "false"); + await expect(radioButtons[2]).toHaveAttribute("aria-checked", "false"); + }, +}; + +const StandardInteraction = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const radioGroup = canvas.getByRole("radiogroup"); + const radioButtons = canvas.getAllByRole("radio"); + await expect(radioGroup).toBeInTheDocument(); + await expect(radioButtons[0]).toHaveAttribute("aria-checked", "false"); + await expect(radioButtons[1]).toHaveAttribute("aria-checked", "true"); + await expect(radioButtons[2]).toHaveAttribute("aria-checked", "false"); + await userEvent.click(radioButtons[0]); + await expect(radioButtons[0]).toHaveAttribute("aria-checked", "true"); + await expect(radioButtons[1]).toHaveAttribute("aria-checked", "false"); + await expect(radioButtons[2]).toHaveAttribute("aria-checked", "false"); + }, +}; + +const InverseInteraction = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const radioGroup = canvas.getByRole("radiogroup"); + const radioButtons = canvas.getAllByRole("radio"); + await expect(radioGroup).toBeInTheDocument(); + await expect(radioButtons[0]).toHaveAttribute("aria-checked", "true"); + await expect(radioButtons[1]).toHaveAttribute("aria-checked", "false"); + await expect(radioButtons[2]).toHaveAttribute("aria-checked", "false"); + await userEvent.click(radioButtons[1]); + await expect(radioButtons[0]).toHaveAttribute("aria-checked", "false"); + await expect(radioButtons[1]).toHaveAttribute("aria-checked", "true"); + await expect(radioButtons[2]).toHaveAttribute("aria-checked", "false"); + }, +}; + +const InteractiveInteraction = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const radioGroup = canvas.getByRole("radiogroup"); + const radioButtons = canvas.getAllByRole("radio"); + await expect(radioGroup).toBeInTheDocument(); + await expect(canvas.getByText("Selected: option1")).toBeVisible(); + await userEvent.click(radioButtons[1]); + await expect(canvas.getByText("Selected: option2")).toBeVisible(); + await userEvent.click(radioButtons[2]); + await expect(canvas.getByText("Selected: option3")).toBeVisible(); + }, +}; const meta = { title: "Forms/RadioGroup", diff --git a/tests/accessibility/ContextMenu.a11y.test.jsx b/tests/accessibility/ContextMenu.a11y.test.jsx deleted file mode 100644 index 629074c..0000000 --- a/tests/accessibility/ContextMenu.a11y.test.jsx +++ /dev/null @@ -1,396 +0,0 @@ -import React from "react"; -import { render, screen } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; -import { expect, describe, it, vi } from "vitest"; -import { axe, toHaveNoViolations } from "jest-axe"; -import ContextMenu from "../../app/components/ContextMenu"; -import ContextMenuItem from "../../app/components/ContextMenuItem"; -import ContextMenuSection from "../../app/components/ContextMenuSection"; -import ContextMenuDivider from "../../app/components/ContextMenuDivider"; - -expect.extend(toHaveNoViolations); - -describe("ContextMenu Components Accessibility", () => { - describe("ContextMenu Accessibility", () => { - it("has no accessibility violations", async () => { - const { container } = render( - - Item 1 - Item 2 - , - ); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - it("has proper role and structure", () => { - render( - - Item 1 - Item 2 - , - ); - - const menu = screen.getByRole("menu"); - expect(menu).toBeInTheDocument(); - - const items = screen.getAllByRole("menuitem"); - expect(items).toHaveLength(2); - }); - - it("has proper focus management", async () => { - render( - - Item 1 - Item 2 - , - ); - - const firstItem = screen.getByRole("menuitem", { name: "Item 1" }); - expect(firstItem).toHaveAttribute("tabIndex", "0"); - expect(firstItem).toBeInTheDocument(); - }); - }); - - describe("ContextMenuItem Accessibility", () => { - it("has no accessibility violations", async () => { - const { container } = render( - - Test Item - , - ); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - it("has proper ARIA attributes", () => { - render( - - Test Item - , - ); - - const item = screen.getByRole("menuitem"); - expect(item).not.toHaveAttribute("aria-current"); - }); - - it("updates aria-current when selected", () => { - render( - - - Test Item - - , - ); - - const item = screen.getByRole("menuitem"); - expect(item).toHaveAttribute("aria-current", "true"); - }); - - it("is keyboard accessible", async () => { - const user = userEvent.setup(); - const onClick = vi.fn(); - render( - - Test Item - , - ); - - const item = screen.getByRole("menuitem"); - item.focus(); - - await user.keyboard("{Enter}"); - expect(onClick).toHaveBeenCalled(); - }); - - it("is accessible with Space key", async () => { - const user = userEvent.setup(); - const onClick = vi.fn(); - render( - - Test Item - , - ); - - const item = screen.getByRole("menuitem"); - item.focus(); - - await user.keyboard(" "); - expect(onClick).toHaveBeenCalled(); - }); - - it("has proper focus indicators", () => { - render( - - Test Item - , - ); - - const item = screen.getByRole("menuitem"); - expect(item).toHaveClass( - "hover:!bg-[var(--color-surface-default-secondary)]", - ); - }); - - it("announces selection state to screen readers", () => { - render( - - - Test Item - - , - ); - - const item = screen.getByRole("menuitem"); - expect(item).toHaveAttribute("aria-current", "true"); - }); - }); - - describe("ContextMenuSection Accessibility", () => { - it("has no accessibility violations", async () => { - const { container } = render( - - - Item 1 - - , - ); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - it("has proper heading structure", () => { - render( - - - Item 1 - - , - ); - - const title = screen.getByText("Test Section"); - expect(title).toBeInTheDocument(); - }); - - it("has sufficient color contrast for section title", () => { - render( - - - Item 1 - - , - ); - - const title = screen.getByText("Test Section"); - expect(title).toHaveClass("text-[var(--color-content-default-primary)]"); - }); - }); - - describe("ContextMenuDivider Accessibility", () => { - it("has no accessibility violations", async () => { - const { container } = render(); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - it("has proper semantic structure", () => { - render(); - - const divider = screen.getByRole("separator"); - expect(divider).toBeInTheDocument(); - }); - - it("has sufficient visual contrast", () => { - render(); - - const divider = screen.getByRole("separator"); - expect(divider).toHaveClass( - "border-[var(--color-border-default-tertiary)]", - ); - }); - }); - - describe("Integrated Menu Accessibility", () => { - const TestMenu = () => ( - - - Item 1 - - Item 2 - - - - - - Item 3 - - - - ); - - it("has no accessibility violations when integrated", async () => { - const { container } = render(); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - it("has proper menu structure", () => { - render(); - - const menu = screen.getByRole("menu"); - expect(menu).toBeInTheDocument(); - - const items = screen.getAllByRole("menuitem"); - expect(items).toHaveLength(3); - - expect(screen.getByText("First Section")).toBeInTheDocument(); - expect(screen.getByText("Second Section")).toBeInTheDocument(); - }); - - it("maintains proper focus order", async () => { - render(); - - const items = screen.getAllByRole("menuitem"); - expect(items).toHaveLength(3); - - // Check that all items are focusable - items.forEach((item) => { - expect(item).toHaveAttribute("tabIndex", "0"); - }); - }); - - it("handles keyboard navigation correctly", async () => { - const user = userEvent.setup(); - const onClick = vi.fn(); - render( - - Item 1 - Item 2 - , - ); - - const items = screen.getAllByRole("menuitem"); - items[0].focus(); - - await user.keyboard("{Enter}"); - expect(onClick).toHaveBeenCalled(); - }); - }); - - describe("Color Contrast", () => { - it("has sufficient contrast for menu items", () => { - render( - - Test Item - , - ); - - const item = screen.getByRole("menuitem"); - expect(item).toHaveClass( - "text-[var(--color-content-default-brand-primary)]", - ); - }); - - it("has sufficient contrast for section titles", () => { - render( - - - , - ); - - const title = screen.getByText("Test Section"); - expect(title).toHaveClass("text-[var(--color-content-default-primary)]"); - }); - - it("has sufficient contrast for dividers", () => { - render( - - - , - ); - - const divider = screen.getByRole("separator"); - expect(divider).toHaveClass( - "border-[var(--color-border-default-tertiary)]", - ); - }); - }); - - describe("Screen Reader Support", () => { - it("announces menu structure correctly", () => { - render( - - - Item 1 - - Item 2 - - - , - ); - - const menu = screen.getByRole("menu"); - expect(menu).toBeInTheDocument(); - - const items = screen.getAllByRole("menuitem"); - expect(items[0]).not.toHaveAttribute("aria-current"); - expect(items[1]).toHaveAttribute("aria-current", "true"); - }); - - it("announces selection state changes", async () => { - const { rerender } = render( - - Test Item - , - ); - - const item = screen.getByRole("menuitem"); - expect(item).not.toHaveAttribute("aria-current"); - - rerender( - - Test Item - , - ); - - expect(item).toHaveAttribute("aria-current", "true"); - }); - }); - - describe("WCAG Compliance", () => { - it("meets WCAG 2.1 AA standards", async () => { - const { container } = render( - - - Item 1 - - Item 2 - - - - Item 3 - , - ); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - it("meets WCAG standards in all states", async () => { - const { container } = render( - - - Selected Item - - - Submenu Item - - - Disabled Item - - , - ); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - }); -}); diff --git a/tests/accessibility/Input.a11y.test.jsx b/tests/accessibility/Input.a11y.test.jsx deleted file mode 100644 index 47fb772..0000000 --- a/tests/accessibility/Input.a11y.test.jsx +++ /dev/null @@ -1,286 +0,0 @@ -import React from "react"; -import { render, screen, fireEvent } from "@testing-library/react"; -import { expect, test, describe, vi } from "vitest"; -import { axe, toHaveNoViolations } from "jest-axe"; -import Input from "../../app/components/Input"; - -expect.extend(toHaveNoViolations); - -describe("Input Component Accessibility", () => { - test("has no accessibility violations", async () => { - const { container } = render(); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - test("has no accessibility violations when disabled", async () => { - const { container } = render(); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - test("has no accessibility violations when in error state", async () => { - const { container } = render(); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - test("has no accessibility violations with horizontal label", async () => { - const { container } = render( - , - ); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - test("associates label with input correctly", () => { - render(); - const input = screen.getByLabelText("Test input"); - expect(input).toBeInTheDocument(); - expect(input).toHaveAttribute("type", "text"); - }); - - test("maintains label association with custom ID", () => { - render(); - const input = screen.getByLabelText("Test input"); - expect(input).toHaveAttribute("id", "custom-input"); - }); - - test("supports keyboard navigation", () => { - render(); - const input = screen.getByRole("textbox"); - - // Input should be focusable - input.focus(); - expect(input).toHaveFocus(); - }); - - test("supports keyboard activation", () => { - const handleChange = vi.fn(); - render(); - const input = screen.getByRole("textbox"); - - // Type in the input - fireEvent.change(input, { target: { value: "test" } }); - expect(handleChange).toHaveBeenCalled(); - }); - - test("supports Enter key activation", () => { - const handleChange = vi.fn(); - render(); - const input = screen.getByRole("textbox"); - - // Focus the input first - input.focus(); - expect(input).toHaveFocus(); - - fireEvent.keyDown(input, { key: "Enter" }); - // Input should still be focused and ready for typing - expect(input).toHaveFocus(); - }); - - test("supports Space key activation", () => { - const handleChange = vi.fn(); - render(); - const input = screen.getByRole("textbox"); - - // Focus the input first - input.focus(); - expect(input).toHaveFocus(); - - fireEvent.keyDown(input, { key: " " }); - // Input should still be focused and ready for typing - expect(input).toHaveFocus(); - }); - - test("supports Tab navigation", () => { - render( -
- - -
, - ); - - const firstInput = screen.getByLabelText("First input"); - const secondInput = screen.getByLabelText("Second input"); - - firstInput.focus(); - expect(firstInput).toHaveFocus(); - - // Use userEvent for more realistic tab navigation - fireEvent.keyDown(firstInput, { key: "Tab", code: "Tab" }); - // Note: In a real browser, Tab would move focus, but in tests we need to simulate it - secondInput.focus(); - expect(secondInput).toHaveFocus(); - }); - - test("supports Shift+Tab navigation", () => { - render( -
- - -
, - ); - - const firstInput = screen.getByLabelText("First input"); - const secondInput = screen.getByLabelText("Second input"); - - secondInput.focus(); - expect(secondInput).toHaveFocus(); - - // Use userEvent for more realistic tab navigation - fireEvent.keyDown(secondInput, { key: "Tab", shiftKey: true, code: "Tab" }); - // Note: In a real browser, Shift+Tab would move focus, but in tests we need to simulate it - firstInput.focus(); - expect(firstInput).toHaveFocus(); - }); - - test("handles disabled state accessibility", () => { - render(); - const input = screen.getByRole("textbox"); - - expect(input).toBeDisabled(); - expect(input).toHaveAttribute("disabled"); - }); - - test("handles error state accessibility", () => { - render(); - const input = screen.getByRole("textbox"); - - // Error state should still be accessible - expect(input).toBeInTheDocument(); - expect(input).not.toBeDisabled(); - }); - - test("supports different input types", () => { - const { rerender } = render(); - let input = screen.getByRole("textbox"); - expect(input).toHaveAttribute("type", "email"); - - rerender(); - // Password inputs don't have textbox role, they have textbox role only for text inputs - input = screen.getByLabelText("Password"); - expect(input).toHaveAttribute("type", "password"); - - rerender(); - input = screen.getByRole("spinbutton"); - expect(input).toHaveAttribute("type", "number"); - }); - - test("supports placeholder accessibility", () => { - render(); - const input = screen.getByPlaceholderText("Enter your name"); - expect(input).toBeInTheDocument(); - }); - - test("supports value accessibility", () => { - render(); - const input = screen.getByDisplayValue("test value"); - expect(input).toBeInTheDocument(); - }); - - test("maintains focus management", () => { - const handleFocus = vi.fn(); - const handleBlur = vi.fn(); - - render( - , - ); - - const input = screen.getByRole("textbox"); - - fireEvent.focus(input); - expect(handleFocus).toHaveBeenCalled(); - // Focus the input to ensure it has focus - input.focus(); - expect(input).toHaveFocus(); - - fireEvent.blur(input); - expect(handleBlur).toHaveBeenCalled(); - // Manually blur the input to ensure it loses focus - input.blur(); - expect(input).not.toHaveFocus(); - }); - - test("supports form association", () => { - render( - - - , - ); - - const input = screen.getByRole("textbox"); - expect(input).toHaveAttribute("name", "test-field"); - }); - - test("supports ARIA attributes", () => { - render( - , - ); - - const input = screen.getByRole("textbox"); - expect(input).toHaveAttribute("aria-describedby", "help-text"); - expect(input).toHaveAttribute("aria-required", "true"); - }); - - test("supports custom ARIA labels", () => { - render(); - const input = screen.getByLabelText("Custom input label"); - expect(input).toBeInTheDocument(); - }); - - test("handles multiple inputs without conflicts", () => { - render( -
- - - -
, - ); - - const firstInput = screen.getByLabelText("First input"); - const secondInput = screen.getByLabelText("Second input"); - const thirdInput = screen.getByLabelText("Third input"); - - expect(firstInput).toBeInTheDocument(); - expect(secondInput).toBeInTheDocument(); - expect(thirdInput).toBeInTheDocument(); - - // Each should have unique IDs - expect(firstInput.id).not.toBe(secondInput.id); - expect(secondInput.id).not.toBe(thirdInput.id); - expect(firstInput.id).not.toBe(thirdInput.id); - }); - - test("supports screen reader navigation", () => { - render(); - const input = screen.getByRole("textbox"); - const label = screen.getByText("Test input"); - - // Label should be associated with input - expect(label).toHaveAttribute("for", input.id); - }); - - test("handles dynamic label changes", () => { - const { rerender } = render(); - expect(screen.getByText("Original label")).toBeInTheDocument(); - - rerender(); - expect(screen.getByText("Updated label")).toBeInTheDocument(); - expect(screen.queryByText("Original label")).not.toBeInTheDocument(); - }); - - test("supports controlled input behavior", () => { - const handleChange = vi.fn(); - render(); - - const input = screen.getByDisplayValue("controlled value"); - fireEvent.change(input, { target: { value: "new value" } }); - - expect(handleChange).toHaveBeenCalled(); - }); -}); diff --git a/tests/accessibility/Select.a11y.test.jsx b/tests/accessibility/Select.a11y.test.jsx deleted file mode 100644 index aae5b60..0000000 --- a/tests/accessibility/Select.a11y.test.jsx +++ /dev/null @@ -1,306 +0,0 @@ -import React from "react"; -import { render, screen, waitFor } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; -import { expect, describe, it, vi } from "vitest"; -import { axe, toHaveNoViolations } from "jest-axe"; -import Select from "../../app/components/Select"; - -expect.extend(toHaveNoViolations); - -describe("Select Component Accessibility", () => { - const defaultProps = { - label: "Test Select", - placeholder: "Select an option", - options: [ - { value: "option1", label: "Option 1" }, - { value: "option2", label: "Option 2" }, - { value: "option3", label: "Option 3" }, - ], - }; - - describe("ARIA Attributes", () => { - it("has correct initial ARIA attributes", () => { - render(); - - const selectButton = screen.getByRole("button"); - await user.click(selectButton); - - await waitFor(() => { - expect(selectButton).toHaveAttribute("aria-expanded", "true"); - }); - }); - - it("has proper role for dropdown menu", async () => { - const user = userEvent.setup(); - render(); - - const selectButton = screen.getByRole("button"); - await user.click(selectButton); - - await waitFor(() => { - const options = screen.getAllByRole("option"); - expect(options).toHaveLength(3); - expect(options[0]).toHaveTextContent("Option 1"); - expect(options[1]).toHaveTextContent("Option 2"); - expect(options[2]).toHaveTextContent("Option 3"); - }); - }); - }); - - describe("Keyboard Navigation", () => { - it("opens dropdown with Enter key", async () => { - const user = userEvent.setup(); - render(); - - const selectButton = screen.getByRole("button"); - selectButton.focus(); - await user.keyboard(" "); - - await waitFor(() => { - expect(screen.getByRole("listbox")).toBeInTheDocument(); - }); - }); - - it("closes dropdown with Escape key", async () => { - const user = userEvent.setup(); - render(); - - const selectButton = screen.getByRole("button"); - await user.click(selectButton); - - await waitFor(() => { - expect(screen.getByRole("listbox")).toBeInTheDocument(); - }); - - await user.click(screen.getByText("Option 1")); - - expect(onChange).toHaveBeenCalledWith({ - target: { value: "option1", text: "Option 1" }, - }); - }); - }); - - describe("Screen Reader Support", () => { - it("announces selected option", async () => { - render(); - - const selectButton = screen.getByRole("button"); - expect(selectButton).toHaveTextContent("Select an option"); - }); - - it("has accessible name from label", () => { - render(); - - const selectButton = screen.getByRole("button"); - await user.click(selectButton); - - await waitFor(() => { - expect(selectButton).toHaveFocus(); - }); - }); - - it("returns focus to select button after selection", async () => { - const user = userEvent.setup(); - render(); - - const selectButton = screen.getByRole("button"); - expect(selectButton).toBeDisabled(); - - await user.tab(); - expect(selectButton).not.toHaveFocus(); - }); - - it("has correct ARIA attributes when disabled", () => { - render(); - - const selectButton = screen.getByRole("button"); - expect(selectButton).toHaveClass( - "border-[var(--color-border-default-utility-negative)]", - ); - }); - }); - - describe("WCAG Compliance", () => { - it("meets WCAG 2.1 AA standards", async () => { - const { container } = render(, - ); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - it("meets WCAG standards in error state", async () => { - const { container } = render(); - - const selectButton = screen.getByRole("button"); - await user.click(selectButton); - - await waitFor(() => { - expect(screen.getByRole("listbox")).toBeInTheDocument(); - }); - - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - }); - - describe("Color Contrast", () => { - it("has sufficient color contrast for text", () => { - render(); - - const label = screen.getByText("Test Select"); - expect(label).toHaveClass( - "text-[var(--color-content-default-secondary)]", - ); - }); - }); - - describe("Focus Indicators", () => { - it("has visible focus indicator", () => { - render(); - - const selectButton = screen.getByRole("button"); - // Focus state should be different from hover state - expect(selectButton).toHaveClass( - "focus-visible:border-[var(--color-border-default-utility-info)]", - ); - expect(selectButton).toHaveClass( - "hover:shadow-[0_0_0_2px_var(--color-border-default-tertiary)]", - ); - }); - }); -}); diff --git a/tests/accessibility/Switch.a11y.test.jsx b/tests/accessibility/Switch.a11y.test.jsx deleted file mode 100644 index ad73576..0000000 --- a/tests/accessibility/Switch.a11y.test.jsx +++ /dev/null @@ -1,98 +0,0 @@ -import React from "react"; -import { render, screen, fireEvent } from "@testing-library/react"; -import { describe, it, expect, vi } from "vitest"; -import { axe, toHaveNoViolations } from "jest-axe"; -import Switch from "../../app/components/Switch"; - -expect.extend(toHaveNoViolations); - -describe("Switch Accessibility", () => { - it("has proper ARIA attributes", () => { - render(); - const switchButton = screen.getByRole("switch"); - - expect(switchButton).toHaveAttribute("role", "switch"); - expect(switchButton).toHaveAttribute("aria-checked", "false"); - expect(switchButton).toHaveAttribute("aria-label", "Test Switch"); - }); - - it("has proper ARIA attributes when checked", () => { - render(); - const switchButton = screen.getByRole("switch"); - - expect(switchButton).toHaveAttribute("aria-checked", "true"); - }); - - it("has proper ARIA attributes when focused", () => { - render(); - const switchButton = screen.getByRole("switch"); - - expect(switchButton).toHaveAttribute("aria-checked", "false"); - expect(switchButton).toHaveClass("shadow-[0_0_5px_3px_#3281F8]"); - expect(switchButton).toHaveClass("rounded-full"); - expect(switchButton).toHaveAttribute("aria-label", "Test Switch"); - }); - - it("handles keyboard navigation", () => { - const handleChange = vi.fn(); - render(); - const switchButton = screen.getByRole("switch"); - - // Test Enter key - fireEvent.keyDown(switchButton, { key: "Enter" }); - expect(handleChange).toHaveBeenCalledTimes(1); - - // Test Space key - fireEvent.keyDown(switchButton, { key: " " }); - expect(handleChange).toHaveBeenCalledTimes(2); - }); - - it("handles focus state accessibility", () => { - const handleFocus = vi.fn(); - render(); - const switchButton = screen.getByRole("switch"); - - fireEvent.focus(switchButton); - expect(handleFocus).toHaveBeenCalledTimes(1); - }); - - it("handles checked state accessibility", () => { - const { rerender } = render(); - let switchButton = screen.getByRole("switch"); - expect(switchButton).toHaveAttribute("aria-checked", "false"); - - rerender(); - switchButton = screen.getByRole("switch"); - expect(switchButton).toHaveAttribute("aria-checked", "true"); - }); - - it("has no accessibility violations", async () => { - const { container } = render(); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - it("has no accessibility violations when checked", async () => { - const { container } = render(); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - it("has no accessibility violations when focused", async () => { - const { container } = render(); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - it("has no accessibility violations with text", async () => { - const { container } = render(); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); - - it("has no accessibility violations without text", async () => { - const { container } = render(); - const results = await axe(container); - expect(results).toHaveNoViolations(); - }); -}); diff --git a/tests/accessibility/TextArea.a11y.test.jsx b/tests/accessibility/TextArea.a11y.test.jsx deleted file mode 100644 index 95adda9..0000000 --- a/tests/accessibility/TextArea.a11y.test.jsx +++ /dev/null @@ -1,121 +0,0 @@ -import { expect, test, describe, vi } from "vitest"; -import { render, screen } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; -import { axe, toHaveNoViolations } from "jest-axe"; -import TextArea from "../../app/components/TextArea"; - -expect.extend(toHaveNoViolations); - -describe("TextArea Accessibility", () => { - test("renders without accessibility violations", async () => { - const { container } = render(