Skip to main content

CODITECT Pattern Library: Test-Driven Development Specification

Version: 1.0
Purpose: Automated testing framework for design patterns

Testing Philosophy

Test-Driven Design: Every pattern has:

  1. Visual Tests - Appearance matches specification
  2. Functional Tests - Interactions work correctly
  3. Accessibility Tests - WCAG 2.1 AA compliance
  4. Integration Tests - Patterns compose properly

Test Suite Structure

tests/
├── unit/
│ ├── atoms/
│ │ ├── button.test.js
│ │ ├── input.test.js
│ │ └── ...
│ ├── molecules/
│ └── organisms/
├── integration/
│ ├── dashboard.test.js
│ ├── detail-view.test.js
│ └── ...
├── visual/
│ ├── snapshots/
│ └── regression.test.js
└── accessibility/
└── a11y.test.js

ATOM TESTS

Button Test Suite

import { test, expect } from '@playwright/test';
import { checkA11y, injectAxe } from 'axe-playwright';

describe('Button Atom', () => {
describe('Visual Requirements', () => {
test('primary button has correct styles', async ({ page }) => {
await page.goto('/components/atoms/button');
const btn = page.locator('[data-testid="btn-primary"]');

// Height
await expect(btn).toHaveCSS('height', '40px');

// Border radius
await expect(btn).toHaveCSS('border-radius', '6px');

// Font weight
await expect(btn).toHaveCSS('font-weight', '500');

// Background color
await expect(btn).toHaveCSS('background-color', 'rgb(59, 130, 246)');
});

test('button has minimum touch target size', async ({ page }) => {
await page.goto('/components/atoms/button');
const btn = page.locator('[data-testid="btn-primary"]');
const box = await btn.boundingBox();

expect(box.height).toBeGreaterThanOrEqual(44);
expect(box.width).toBeGreaterThanOrEqual(44);
});

test('visual regression: button states', async ({ page }) => {
await page.goto('/components/atoms/button');
const btn = page.locator('[data-testid="btn-primary"]');

// Default state
await expect(btn).toHaveScreenshot('button-default.png');

// Hover state
await btn.hover();
await expect(btn).toHaveScreenshot('button-hover.png');

// Focus state
await btn.focus();
await expect(btn).toHaveScreenshot('button-focus.png');

// Disabled state
await page.goto('/components/atoms/button?state=disabled');
const btnDisabled = page.locator('[data-testid="btn-disabled"]');
await expect(btnDisabled).toHaveScreenshot('button-disabled.png');
});
});

describe('Functional Requirements', () => {
test('button is clickable', async ({ page }) => {
await page.goto('/components/atoms/button');
const btn = page.locator('[data-testid="btn-primary"]');

let clicked = false;
await page.exposeFunction('handleClick', () => { clicked = true; });
await page.evaluate(() => {
document.querySelector('[data-testid="btn-primary"]')
.addEventListener('click', () => window.handleClick());
});

await btn.click();
expect(clicked).toBe(true);
});

test('disabled button is not clickable', async ({ page }) => {
await page.goto('/components/atoms/button?state=disabled');
const btn = page.locator('[data-testid="btn-disabled"]');

let clicked = false;
await page.exposeFunction('handleClick', () => { clicked = true; });
await page.evaluate(() => {
document.querySelector('[data-testid="btn-disabled"]')
.addEventListener('click', () => window.handleClick());
});

await btn.click({ force: true });
expect(clicked).toBe(false);
});

test('button shows loading state', async ({ page }) => {
await page.goto('/components/atoms/button?state=loading');
const btn = page.locator('[data-testid="btn-loading"]');

// Spinner present
const spinner = btn.locator('.spinner');
await expect(spinner).toBeVisible();

// Button disabled
await expect(btn).toBeDisabled();
});
});

describe('Accessibility Requirements', () => {
test('button is keyboard accessible', async ({ page }) => {
await page.goto('/components/atoms/button');
const btn = page.locator('[data-testid="btn-primary"]');

// Focus with Tab
await page.keyboard.press('Tab');
await expect(btn).toBeFocused();

// Activate with Enter
let activated = false;
await page.exposeFunction('handleActivate', () => { activated = true; });
await page.evaluate(() => {
document.querySelector('[data-testid="btn-primary"]')
.addEventListener('click', () => window.handleActivate());
});

await page.keyboard.press('Enter');
expect(activated).toBe(true);
});

test('button has visible focus indicator', async ({ page }) => {
await page.goto('/components/atoms/button');
const btn = page.locator('[data-testid="btn-primary"]');

await btn.focus();

// Check for outline or box-shadow
const outline = await btn.evaluate(el =>
getComputedStyle(el).outline
);
const boxShadow = await btn.evaluate(el =>
getComputedStyle(el).boxShadow
);

expect(outline !== 'none' || boxShadow !== 'none').toBe(true);
});

test('icon button has aria-label', async ({ page }) => {
await page.goto('/components/atoms/button');
const btn = page.locator('[data-testid="btn-icon"]');

const ariaLabel = await btn.getAttribute('aria-label');
expect(ariaLabel).toBeTruthy();
expect(ariaLabel.length).toBeGreaterThan(0);
});

test('button passes accessibility audit', async ({ page }) => {
await page.goto('/components/atoms/button');
await injectAxe(page);

const results = await checkA11y(page, '[data-testid="btn-primary"]', {
detailedReport: true
});

expect(results.violations).toHaveLength(0);
});

test('button meets color contrast requirements', async ({ page }) => {
await page.goto('/components/atoms/button');
const btn = page.locator('[data-testid="btn-primary"]');

const bgColor = await btn.evaluate(el =>
getComputedStyle(el).backgroundColor
);
const color = await btn.evaluate(el =>
getComputedStyle(el).color
);

const contrast = calculateContrast(bgColor, color);
expect(contrast).toBeGreaterThanOrEqual(4.5);
});
});

describe('Responsive Behavior', () => {
test('button adapts to viewport size', async ({ page }) => {
await page.goto('/components/atoms/button');
const btn = page.locator('[data-testid="btn-primary"]');

// Desktop
await page.setViewportSize({ width: 1440, height: 900 });
const desktopWidth = await btn.evaluate(el => el.offsetWidth);

// Mobile
await page.setViewportSize({ width: 375, height: 667 });
const mobileWidth = await btn.evaluate(el => el.offsetWidth);

// Button doesn't break layout
expect(mobileWidth).toBeLessThanOrEqual(375);
expect(mobileWidth).toBeGreaterThan(0);
});
});
});

// Helper function
function calculateContrast(rgb1, rgb2) {
const l1 = relativeLuminance(rgb1);
const l2 = relativeLuminance(rgb2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}

function relativeLuminance(rgb) {
const [r, g, b] = rgb.match(/\d+/g).map(n => {
const val = n / 255;
return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
});
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}

MOLECULE TESTS

Status Indicator Test Suite

describe('Status Indicator Molecule', () => {
describe('Composition Tests', () => {
test('contains required atoms', async ({ page }) => {
await page.goto('/components/molecules/status-indicator');
const indicator = page.locator('[data-testid="status-indicator"]');

// Contains status dot
const dot = indicator.locator('.status-dot');
await expect(dot).toBeVisible();

// Contains label
const label = indicator.locator('.status-label');
await expect(label).toBeVisible();
});

test('atoms are properly aligned', async ({ page }) => {
await page.goto('/components/molecules/status-indicator');
const indicator = page.locator('[data-testid="status-indicator"]');

const display = await indicator.evaluate(el =>
getComputedStyle(el).display
);
expect(display).toBe('inline-flex');

const alignItems = await indicator.evaluate(el =>
getComputedStyle(el).alignItems
);
expect(alignItems).toBe('center');
});

test('has correct spacing between atoms', async ({ page }) => {
await page.goto('/components/molecules/status-indicator');
const indicator = page.locator('[data-testid="status-indicator"]');

const gap = await indicator.evaluate(el =>
getComputedStyle(el).gap
);
expect(gap).toBe('8px');
});
});

describe('Semantic Consistency', () => {
test('color matches semantic meaning', async ({ page }) => {
await page.goto('/components/molecules/status-indicator');

// Success = green
const success = page.locator('[data-testid="status-success"]');
const successDot = success.locator('.status-dot');
const successBg = await successDot.evaluate(el =>
getComputedStyle(el).backgroundColor
);
expect(successBg).toBe('rgb(16, 185, 129)'); // --green-500

// Warning = yellow
const warning = page.locator('[data-testid="status-warning"]');
const warningDot = warning.locator('.status-dot');
const warningBg = await warningDot.evaluate(el =>
getComputedStyle(el).backgroundColor
);
expect(warningBg).toBe('rgb(245, 158, 11)'); // --yellow-500
});
});

describe('Accessibility', () => {
test('screenreader reads both dot and text', async ({ page }) => {
await page.goto('/components/molecules/status-indicator');
const indicator = page.locator('[data-testid="status-indicator"]');

const text = await indicator.textContent();
expect(text).toContain('Active');

// Dot has role
const dot = indicator.locator('.status-dot');
const role = await dot.getAttribute('role');
expect(role).toBe('img');
});
});
});

ORGANISM TESTS

Card Test Suite

describe('Card Organism', () => {
describe('Structure Tests', () => {
test('card has semantic HTML', async ({ page }) => {
await page.goto('/components/organisms/card');
const card = page.locator('[data-testid="card"]');

// Should be article or section
const tagName = await card.evaluate(el => el.tagName.toLowerCase());
expect(['article', 'section']).toContain(tagName);
});

test('card has proper heading hierarchy', async ({ page }) => {
await page.goto('/components/organisms/card');
const card = page.locator('[data-testid="card"]');

// Has h3 (assuming card in h2 section)
const heading = card.locator('h3');
await expect(heading).toBeVisible();
});
});

describe('Interaction Tests', () => {
test('clickable card responds to click', async ({ page }) => {
await page.goto('/components/organisms/card?clickable=true');
const card = page.locator('[data-testid="card-clickable"]');

let clicked = false;
await page.exposeFunction('handleCardClick', () => { clicked = true; });
await page.evaluate(() => {
document.querySelector('[data-testid="card-clickable"]')
.addEventListener('click', () => window.handleCardClick());
});

await card.click();
expect(clicked).toBe(true);
});

test('card shows hover state', async ({ page }) => {
await page.goto('/components/organisms/card');
const card = page.locator('[data-testid="card"]');

// Get initial shadow
const initialShadow = await card.evaluate(el =>
getComputedStyle(el).boxShadow
);

// Hover
await card.hover();

// Get hover shadow
const hoverShadow = await card.evaluate(el =>
getComputedStyle(el).boxShadow
);

// Shadow should change
expect(hoverShadow).not.toBe(initialShadow);
});
});

describe('Content Tests', () => {
test('card renders all sections', async ({ page }) => {
await page.goto('/components/organisms/card');
const card = page.locator('[data-testid="card"]');

// Header
const header = card.locator('.card-header');
await expect(header).toBeVisible();

// Content
const content = card.locator('.card-content');
await expect(content).toBeVisible();

// Footer
const footer = card.locator('.card-footer');
await expect(footer).toBeVisible();
});

test('card molecules are properly composed', async ({ page }) => {
await page.goto('/components/organisms/card');
const card = page.locator('[data-testid="card"]');

// Status indicator molecule
const statusIndicator = card.locator('.status-indicator');
await expect(statusIndicator).toBeVisible();

// Progress indicator molecule
const progressIndicator = card.locator('.progress-indicator');
await expect(progressIndicator).toBeVisible();
});
});
});

INTEGRATION TESTS

Dashboard Page Test Suite

describe('Dashboard Integration', () => {
test('page loads with all organisms', async ({ page }) => {
await page.goto('/dashboard');

// Navbar present
const navbar = page.locator('[data-testid="navbar"]');
await expect(navbar).toBeVisible();

// Page header present
const header = page.locator('.page-header');
await expect(header).toBeVisible();

// Card grid present
const grid = page.locator('.card-grid');
await expect(grid).toBeVisible();

// Cards present
const cards = page.locator('.card');
const count = await cards.count();
expect(count).toBeGreaterThan(0);
});

test('user can navigate from dashboard to detail', async ({ page }) => {
await page.goto('/dashboard');

// Click first card
const firstCard = page.locator('.card').first();
await firstCard.click();

// Should navigate to detail view
await page.waitForURL(/.*\/projects\/\d+/);

// Detail view loaded
const detailView = page.locator('.detail-layout');
await expect(detailView).toBeVisible();
});

test('patterns compose correctly', async ({ page }) => {
await page.goto('/dashboard');

// Card contains proper molecule hierarchy
const card = page.locator('.card').first();

// Status indicator in card header
const statusInHeader = card.locator('.card-header .status-indicator');
await expect(statusInHeader).toBeVisible();

// Progress indicator in card footer
const progressInFooter = card.locator('.card-footer .progress-indicator');
await expect(progressInFooter).toBeVisible();
});

test('page is responsive', async ({ page }) => {
await page.goto('/dashboard');

// Desktop: 4 column grid
await page.setViewportSize({ width: 1440, height: 900 });
const desktopGrid = page.locator('.card-grid');
const desktopColumns = await desktopGrid.evaluate(el =>
getComputedStyle(el).gridTemplateColumns.split(' ').length
);
expect(desktopColumns).toBeGreaterThanOrEqual(3);

// Mobile: 1 column
await page.setViewportSize({ width: 375, height: 667 });
const mobileColumns = await desktopGrid.evaluate(el =>
getComputedStyle(el).gridTemplateColumns.split(' ').length
);
expect(mobileColumns).toBe(1);
});

test('loading states work', async ({ page }) => {
// Mock slow API
await page.route('**/api/projects', route => {
setTimeout(() => route.fulfill({ body: '[]' }), 2000);
});

await page.goto('/dashboard');

// Skeleton cards visible
const skeletons = page.locator('.skeleton-card');
await expect(skeletons.first()).toBeVisible();

// Wait for loading
await page.waitForTimeout(2500);

// Real content visible
const cards = page.locator('.card');
await expect(cards.first()).toBeVisible();
});
});

ACCESSIBILITY TEST SUITE

Comprehensive A11y Tests

import { injectAxe, checkA11y } from 'axe-playwright';

describe('Accessibility Compliance', () => {
test('entire dashboard passes WCAG 2.1 AA', async ({ page }) => {
await page.goto('/dashboard');
await injectAxe(page);

const results = await checkA11y(page, null, {
detailedReport: true,
detailedReportOptions: { html: true }
});

expect(results.violations).toHaveLength(0);
});

test('keyboard navigation works throughout page', async ({ page }) => {
await page.goto('/dashboard');

// Tab through all focusable elements
const focusable = await page.locator(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const count = await focusable.count();

for (let i = 0; i < count; i++) {
await page.keyboard.press('Tab');
const focused = await page.evaluate(() => document.activeElement.tagName);
expect(focused).toBeTruthy();
}
});

test('screen reader landmarks present', async ({ page }) => {
await page.goto('/dashboard');

// Main navigation
const nav = page.locator('nav[role="navigation"]');
await expect(nav).toBeVisible();

// Main content
const main = page.locator('main');
await expect(main).toBeVisible();

// Landmarks have labels
const navLabel = await nav.getAttribute('aria-label');
expect(navLabel).toBeTruthy();
});

test('heading hierarchy is correct', async ({ page }) => {
await page.goto('/dashboard');

// Get all headings
const headings = await page.locator('h1, h2, h3, h4, h5, h6').all();
const levels = await Promise.all(
headings.map(h => h.evaluate(el => parseInt(el.tagName[1])))
);

// Should start with h1
expect(levels[0]).toBe(1);

// No skipped levels
for (let i = 1; i < levels.length; i++) {
const diff = levels[i] - levels[i - 1];
expect(diff).toBeLessThanOrEqual(1);
}
});

test('images have alt text', async ({ page }) => {
await page.goto('/dashboard');

const images = await page.locator('img').all();
for (const img of images) {
const alt = await img.getAttribute('alt');
expect(alt).not.toBeNull();
}
});

test('form fields have labels', async ({ page }) => {
await page.goto('/form');

const inputs = await page.locator('input, select, textarea').all();
for (const input of inputs) {
const id = await input.getAttribute('id');
const ariaLabel = await input.getAttribute('aria-label');
const ariaLabelledby = await input.getAttribute('aria-labelledby');

if (id) {
const label = page.locator(`label[for="${id}"]`);
const hasLabel = await label.count() > 0;
expect(hasLabel || ariaLabel || ariaLabelledby).toBe(true);
} else {
expect(ariaLabel || ariaLabelledby).toBeTruthy();
}
}
});

test('color is not the only visual means', async ({ page }) => {
await page.goto('/dashboard');

// Status indicators have both color AND text/icon
const statusIndicators = await page.locator('.status-indicator').all();
for (const indicator of statusIndicators) {
const text = await indicator.textContent();
expect(text.trim().length).toBeGreaterThan(0);
}
});
});

VISUAL REGRESSION TEST SUITE

Automated Screenshot Comparison

import { test, expect } from '@playwright/test';

describe('Visual Regression', () => {
const viewports = [
{ name: 'mobile', width: 375, height: 667 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'desktop', width: 1440, height: 900 }
];

for (const viewport of viewports) {
test(`dashboard ${viewport.name}`, async ({ page }) => {
await page.setViewportSize(viewport);
await page.goto('/dashboard');
await page.waitForLoadState('networkidle');

await expect(page).toHaveScreenshot(`dashboard-${viewport.name}.png`, {
fullPage: true,
maxDiffPixelRatio: 0.01
});
});
}

test('component states', async ({ page }) => {
await page.goto('/components');

// Button states
const btnPrimary = page.locator('[data-testid="btn-primary"]');
await expect(btnPrimary).toHaveScreenshot('btn-primary-default.png');

await btnPrimary.hover();
await expect(btnPrimary).toHaveScreenshot('btn-primary-hover.png');

await btnPrimary.focus();
await expect(btnPrimary).toHaveScreenshot('btn-primary-focus.png');

// Input states
const input = page.locator('[data-testid="input-text"]');
await expect(input).toHaveScreenshot('input-default.png');

await input.focus();
await expect(input).toHaveScreenshot('input-focus.png');

await input.fill('Test value');
await expect(input).toHaveScreenshot('input-filled.png');
});

test('dark theme', async ({ page }) => {
await page.goto('/dashboard');
await page.evaluate(() => {
document.documentElement.setAttribute('data-theme', 'dark');
});

await expect(page).toHaveScreenshot('dashboard-dark.png', {
fullPage: true
});
});
});

PERFORMANCE TEST SUITE

Loading and Rendering Performance

describe('Performance', () => {
test('dashboard loads within 2 seconds', async ({ page }) => {
const startTime = Date.now();
await page.goto('/dashboard');
await page.waitForLoadState('networkidle');
const loadTime = Date.now() - startTime;

expect(loadTime).toBeLessThan(2000);
});

test('First Contentful Paint < 1s', async ({ page }) => {
await page.goto('/dashboard');

const fcp = await page.evaluate(() => {
const paint = performance.getEntriesByType('paint')
.find(entry => entry.name === 'first-contentful-paint');
return paint ? paint.startTime : 0;
});

expect(fcp).toBeLessThan(1000);
});

test('Cumulative Layout Shift < 0.1', async ({ page }) => {
await page.goto('/dashboard');
await page.waitForLoadState('networkidle');

const cls = await page.evaluate(() => {
let cls = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
cls += entry.value;
}
}
}).observe({ type: 'layout-shift', buffered: true });
return cls;
});

expect(cls).toBeLessThan(0.1);
});

test('card grid renders without jank', async ({ page }) => {
await page.goto('/dashboard');

const startTime = Date.now();
await page.waitForSelector('.card-grid .card:nth-child(10)');
const renderTime = Date.now() - startTime;

expect(renderTime).toBeLessThan(500);
});
});

TEST AUTOMATION SETUP

GitHub Actions Workflow

name: Design Pattern Tests

on:
push:
branches: [main, develop]
pull_request:
branches: [main]

jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'

- name: Install dependencies
run: npm ci

- name: Run unit tests
run: npm run test:unit

- name: Upload coverage
uses: codecov/codecov-action@v3

accessibility-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3

- name: Install dependencies
run: npm ci

- name: Install Playwright
run: npx playwright install --with-deps

- name: Run a11y tests
run: npm run test:a11y

- name: Upload report
if: always()
uses: actions/upload-artifact@v3
with:
name: a11y-report
path: test-results/

visual-regression:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3

- name: Install dependencies
run: npm ci

- name: Install Playwright
run: npx playwright install --with-deps

- name: Run visual tests
run: npm run test:visual

- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v3
with:
name: screenshot-diffs
path: test-results/

Package.json Scripts

{
"H.P.004-SCRIPTS": {
"test": "npm run test:unit && npm run test:a11y && npm run test:visual",
"test:unit": "jest --coverage",
"test:a11y": "playwright test tests/accessibility/",
"test:visual": "playwright test tests/visual/",
"test:integration": "playwright test tests/integration/",
"test:watch": "jest --watch",
"test:update-snapshots": "playwright test --update-snapshots"
}
}

CONTINUOUS QUALITY MONITORING

Metrics Dashboard

// Generate test metrics
{
"coverage": {
"statements": 95.2,
"branches": 92.8,
"functions": 94.1,
"lines": 95.5
},
"accessibility": {
"violations": 0,
"warnings": 3,
"passes": 127
},
"visual": {
"snapshots": 156,
"updated": 0,
"failed": 0
},
"performance": {
"avgLoadTime": 1247,
"fcp": 823,
"cls": 0.042
}
}

Quality Gates

// Enforce minimum standards
const qualityGates = {
coverage: {
statements: 90,
branches: 85,
functions: 90,
lines: 90
},
accessibility: {
violations: 0,
wcagLevel: 'AA'
},
performance: {
loadTime: 2000,
fcp: 1000,
cls: 0.1
},
visual: {
maxDiffPixelRatio: 0.01
}
};

// Check before merge
function passesQualityGates(metrics) {
return (
metrics.coverage.statements >= qualityGates.coverage.statements &&
metrics.accessibility.violations === 0 &&
metrics.performance.loadTime <= qualityGates.performance.loadTime
);
}

This testing specification ensures every pattern in the library is production-ready, accessible, and maintainable. Run tests before every commit to maintain quality.