Skip to content

Commit c68cdfe

Browse files
committed
[dark-mode] Phase 4.3: Add Phase 3 testing documentation
- ImplementationSummary.md: complete test suite overview, bugs found/fixed, WCAG fixes, palette redesign, database migration - QuickReference.md: test commands, file map, fixture helpers, color reference, WCAG thresholds, troubleshooting - TechnicalDeepDive.md: architecture, multi-browser config, WCAG calculation methodology, JSDOM setup, CSS specificity analysis, performance measurement, root cause analyses
1 parent 7993ba1 commit c68cdfe

3 files changed

Lines changed: 789 additions & 0 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Phase 3: Testing — Implementation Summary
2+
3+
**Status:** ✅ COMPLETE
4+
**Completion Date:** March 31, 2026
5+
**Build Status:** ✅ Frontend & Backend Passing
6+
**Test Suite:** 139 Playwright e2e tests + 30 Mocha unit tests — all passing
7+
8+
---
9+
10+
## Overview
11+
12+
Phase 3 implemented comprehensive testing for the dark mode feature across five sub-phases: visual/structural testing (3.1), WCAG accessibility (3.2), performance (3.3), localStorage persistence (3.4), and cross-browser compatibility (3.5). Testing uncovered and fixed multiple bugs, drove a color palette redesign, and prompted a database migration.
13+
14+
## What Was Built
15+
16+
### Test Infrastructure
17+
18+
- **E2E Framework:** Playwright with Chromium, Firefox, and WebKit
19+
- **Unit Framework:** Mocha + Chai with JSDOM for browser simulation
20+
- **Test Location:** `src/test/e2e/` (e2e) and `src/test/modules/` (unit)
21+
- **Config:** `playwright.config.ts` — auto-starts dev server on port 7777
22+
- **Shared Fixtures:** `src/test/e2e/fixtures/setup.ts` — selectors, helpers, color maps
23+
24+
### Test Suite Summary
25+
26+
| Spec File | Tests | Phase | Coverage |
27+
|-----------|-------|-------|----------|
28+
| `theme-toggle.spec.ts` | 6 | 3.1 | Button visibility, ARIA labels, click toggle, keyboard (Enter/Space) |
29+
| `theme-persistence.spec.ts` | 14 | 3.1+3.4 | Save/restore, reload, edge cases (clear, invalid values, rapid toggles) |
30+
| `system-preference.spec.ts` | 4 | 3.1 | `prefers-color-scheme` emulation, saved preference overrides system |
31+
| `components.spec.ts` | 16 | 3.1 | CSS variable values per theme, body/header/text rendered colors |
32+
| `no-fouc.spec.ts` | 4 | 3.1 | data-theme attribute on load, DOM consistent with localStorage |
33+
| `accessibility.spec.ts` | 26 | 3.2 | WCAG AA contrast ratios, keyboard nav, focus visibility, ARIA/semantics |
34+
| `performance.spec.ts` | 12 | 3.3 | Toggle timing <100ms, CLS, FOUC prevention, CSS architecture |
35+
| `browser-compat.spec.ts` | 19×3 | 3.5 | CSS variables, toggle, localStorage, system pref, colors, events (3 browsers) |
36+
37+
**Chromium-only tests:** 82
38+
**Cross-browser tests:** 19 × 3 = 57
39+
**Total Playwright runs:** 139
40+
**Unit tests:** 30 (ThemeManager with JSDOM)
41+
42+
### Bugs Discovered & Fixed
43+
44+
| Bug | Symptom | Root Cause | Fix |
45+
|-----|---------|------------|-----|
46+
| **themeToggle event** | Icons never updated after click | Event listener used wrong event name | Listen to `themeChange` directly |
47+
| **Missing body bg** | Page still white in dark mode | `body`, `header`, `copy-button` had hardcoded `white` | Replaced with `var(--color-bg-main)` |
48+
| **Greeting page** | Dark mode lost after login | `index.twig` missing `main.bundle.js` | Added script tag |
49+
| **Alias race condition** | Page redirect → 500 error | `alias.save()` missing `await` in pages.ts | Added `await` to insert/update |
50+
| **NeDB Node 24 crash** | `util.isDate is not a function` | Node 24 removed `util.is*` functions | Migrated to `@seald-io/nedb` |
51+
| **Invalid localStorage** | `applyTheme(null)` called | `hasSavedPreference()` true but `getSavedPreference()` null | Fixed init() fallback logic |
52+
| **CSS specificity** | System dark overrides explicit light choice | `@media` `:root` applied over `[data-theme="light"]` | Added `:not([data-theme="light"])` guard |
53+
54+
### WCAG AA Contrast Fixes
55+
56+
| Variable | Before | Ratio | After | Ratio | Standard |
57+
|----------|--------|-------|-------|-------|----------|
58+
| `--color-line-gray` | `#3F3F46` | 1.70:1 ✗ | `#71717A` | 3.67:1 ✓ | 3:1 (UI boundary) |
59+
| `--color-code-comment` | `#71717A` | 3.84:1 ✗ | `#909099` | 5.86:1 ✓ | 4.5:1 (text) |
60+
| `--color-checkbox-border` | `#52525B` | 2.29:1 ✗ | `#71717A` | 3.67:1 ✓ | 3:1 (UI boundary) |
61+
| `--color-button-primary` | `#3B82F6` | 3.68:1 ✗ | `#2563EB` | 5.17:1 ✓ | 4.5:1 (text on bg) |
62+
| `--color-button-warning` | `#FB923C` | 2.26:1 ✗ | `#C2410C` | 5.18:1 ✓ | 4.5:1 (text on bg) |
63+
64+
### Color Palette Redesign (3 iterations)
65+
66+
1. **Original (Phase 2):** VS Code flat grays `#1E1E1E` — feedback: "too boring"
67+
2. **Slate attempt:** Tailwind slate `#0F172A` — feedback: "dark blue mode, not dark mode"
68+
3. **Final (zinc):** Tailwind zinc neutrals `#18181B` bg, `#27272A` surface, `#71717A` borders, `#E4E4E7` text
69+
70+
### Database Migration
71+
72+
Replaced unmaintained `nedb` 1.8.0 with `@seald-io/nedb` (maintained fork) to resolve Node 24 compatibility. Uses `createRequire()` pattern for CJS/ESM interop in `src/backend/database/local.ts`.
73+
74+
## Files Created
75+
76+
| File | Purpose |
77+
|------|---------|
78+
| `playwright.config.ts` | Multi-browser config with webServer auto-start |
79+
| `src/test/e2e/fixtures/setup.ts` | Shared selectors, helpers, expected color values |
80+
| `src/test/e2e/dark-mode/theme-toggle.spec.ts` | Toggle button interaction tests |
81+
| `src/test/e2e/dark-mode/theme-persistence.spec.ts` | localStorage persistence + edge cases |
82+
| `src/test/e2e/dark-mode/system-preference.spec.ts` | System preference detection tests |
83+
| `src/test/e2e/dark-mode/components.spec.ts` | Component color verification tests |
84+
| `src/test/e2e/dark-mode/no-fouc.spec.ts` | Flash-of-unstyled-content prevention tests |
85+
| `src/test/e2e/dark-mode/accessibility.spec.ts` | WCAG AA contrast + a11y tests |
86+
| `src/test/e2e/dark-mode/performance.spec.ts` | Toggle timing + layout shift tests |
87+
| `src/test/e2e/dark-mode/browser-compat.spec.ts` | Cross-browser compatibility tests |
88+
| `src/test/modules/themeManager.ts` | 30 Mocha unit tests with JSDOM |
89+
90+
## Files Modified
91+
92+
| File | Changes |
93+
|------|---------|
94+
| `src/frontend/styles/dark-mode.pcss` | WCAG contrast fixes (9 color values), `:not([data-theme="light"])` guard |
95+
| `src/frontend/styles/components/header.pcss` | Replaced hardcoded `background: white` |
96+
| `src/frontend/styles/components/copy-button.pcss` | Replaced hardcoded `background: white` |
97+
| `src/frontend/styles/diff.pcss` | Replaced 4 hardcoded colors with CSS variables |
98+
| `src/frontend/styles/layout.pcss` | Added `background-color: var(--color-bg-main)` to body |
99+
| `src/frontend/js/modules/themeManager.js` | Fixed init() invalid value fallback, removed dead code |
100+
| `src/frontend/js/modules/themeToggle.js` | Fixed event name (`themeChange`) |
101+
| `src/backend/controllers/pages.ts` | Added `await` to alias operations |
102+
| `src/backend/database/local.ts` | Migrated to `@seald-io/nedb` |
103+
| `src/backend/views/pages/index.twig` | Added missing `main.bundle.js` script tag |
104+
| `package.json` | Added `@seald-io/nedb`, `jsdom`, Playwright deps and scripts |
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# Phase 3: Testing — Quick Reference
2+
3+
**Status:** ✅ Complete
4+
**Test Suite:** 139 Playwright + 30 Mocha unit tests
5+
**Build:** ✅ Passing
6+
7+
---
8+
9+
## Quick Navigation
10+
11+
| Section | Purpose |
12+
|---------|---------|
13+
| [Running Tests](#running-tests) | Commands to run all test types |
14+
| [Test File Map](#test-file-map) | Where each test file lives and what it covers |
15+
| [Fixture Helpers](#fixture-helpers) | Shared utilities in setup.ts |
16+
| [Color Reference](#color-reference) | Current dark mode color values (post-WCAG fixes) |
17+
| [WCAG Requirements](#wcag-requirements) | Contrast ratio thresholds |
18+
| [Troubleshooting](#troubleshooting) | Common issues and fixes |
19+
20+
---
21+
22+
## Running Tests
23+
24+
### E2E Tests (Playwright)
25+
26+
```bash
27+
# All tests (Chromium + Firefox + WebKit)
28+
npx playwright test
29+
30+
# With line reporter (CI-friendly)
31+
npx playwright test --reporter=line
32+
33+
# Single spec file
34+
npx playwright test src/test/e2e/dark-mode/accessibility.spec.ts
35+
36+
# Single test by name
37+
npx playwright test -g "primary text on main background"
38+
39+
# Chromium-only (skip cross-browser compat tests)
40+
npx playwright test --project=chromium
41+
42+
# Interactive UI mode
43+
npx playwright test --ui
44+
```
45+
46+
### Unit Tests (Mocha)
47+
48+
```bash
49+
# ThemeManager unit tests
50+
npx ts-mocha src/test/modules/themeManager.ts --timeout 5000
51+
```
52+
53+
### All Tests Together
54+
55+
```bash
56+
# Unit + E2E
57+
npx ts-mocha src/test/modules/themeManager.ts --timeout 5000 && npx playwright test --reporter=line
58+
```
59+
60+
---
61+
62+
## Test File Map
63+
64+
```
65+
src/test/
66+
├── e2e/
67+
│ ├── fixtures/
68+
│ │ └── setup.ts # Shared selectors, helpers, color maps
69+
│ └── dark-mode/
70+
│ ├── theme-toggle.spec.ts # 6 tests — button, click, keyboard
71+
│ ├── theme-persistence.spec.ts # 14 tests — save/restore, edge cases
72+
│ ├── system-preference.spec.ts # 4 tests — prefers-color-scheme
73+
│ ├── components.spec.ts # 16 tests — CSS vars, rendered colors
74+
│ ├── no-fouc.spec.ts # 4 tests — flash prevention
75+
│ ├── accessibility.spec.ts # 26 tests — WCAG AA, keyboard, ARIA
76+
│ ├── performance.spec.ts # 12 tests — timing, CLS, architecture
77+
│ └── browser-compat.spec.ts # 19 tests × 3 browsers
78+
└── modules/
79+
└── themeManager.ts # 30 tests — JSDOM unit tests
80+
```
81+
82+
### Playwright Config Projects
83+
84+
| Project | Browser | Tests Run |
85+
|---------|---------|-----------|
86+
| `chromium` | Desktop Chrome | All specs **except** browser-compat |
87+
| `chromium-compat` | Desktop Chrome | browser-compat.spec.ts only |
88+
| `firefox-compat` | Desktop Firefox | browser-compat.spec.ts only |
89+
| `webkit-compat` | Desktop Safari | browser-compat.spec.ts only |
90+
91+
---
92+
93+
## Fixture Helpers
94+
95+
### setup.ts Exports
96+
97+
```typescript
98+
// CSS selectors for dark mode elements
99+
export const selectors = {
100+
themeToggleButton: 'button.theme-toggle',
101+
sunIcon: 'svg.theme-toggle__icon--light',
102+
moonIcon: 'svg.theme-toggle__icon--dark',
103+
header: 'header.docs-header',
104+
sidebar: 'div.docs-sidebar',
105+
sidebarContent: 'aside.docs-sidebar__content',
106+
pageArticle: 'article.page',
107+
pageContent: 'section.page__content',
108+
authForm: 'form.auth-form',
109+
};
110+
111+
// localStorage key
112+
export const STORAGE_KEY = 'codex-docs-theme';
113+
114+
// Expected CSS variable values per theme
115+
export const themeColors = {
116+
light: {
117+
'--color-bg-main': '#fff',
118+
'--color-text-main': '#060c26',
119+
'--color-bg-light': '#f8f7fa',
120+
'--color-line-gray': '#e8e8eb',
121+
},
122+
dark: {
123+
'--color-bg-main': '#18181b',
124+
'--color-text-main': '#e4e4e7',
125+
'--color-bg-light': '#27272a',
126+
'--color-line-gray': '#71717a',
127+
},
128+
};
129+
130+
// Helper functions
131+
getCSSVariable(page, variable) // Get computed CSS var from :root
132+
getThemeAttribute(page) // Get data-theme attribute value
133+
goToThemedPage(page) // Navigate to /auth (has JS bundle)
134+
```
135+
136+
---
137+
138+
## Color Reference
139+
140+
### Dark Mode Palette (Tailwind Zinc — current values)
141+
142+
| Variable | Value | Usage |
143+
|----------|-------|-------|
144+
| `--color-bg-main` | `#18181B` | Page background |
145+
| `--color-bg-light` | `#27272A` | Surface/card backgrounds |
146+
| `--color-text-main` | `#E4E4E7` | Primary text |
147+
| `--color-text-second` | `#A1A1AA` | Secondary text |
148+
| `--color-line-gray` | `#71717A` | Borders, dividers |
149+
| `--color-button-primary` | `#2563EB` | Primary button bg |
150+
| `--color-button-warning` | `#C2410C` | Warning/delete button bg |
151+
| `--color-code-bg` | `#131316` | Code block background |
152+
| `--color-code-comment` | `#909099` | Code comments |
153+
| `--color-checkbox-border` | `#71717A` | Checkbox border |
154+
155+
---
156+
157+
## WCAG Requirements
158+
159+
### Contrast Ratios (WCAG 2.1 AA)
160+
161+
| Type | Minimum Ratio | Examples |
162+
|------|---------------|----------|
163+
| Normal text (< 18pt) | **4.5:1** | Body text, labels, code comments |
164+
| Large text (≥ 18pt / 14pt bold) | **3:1** | Headings |
165+
| UI component boundaries | **3:1** | Borders, checkboxes, dividers |
166+
| Text on colored buttons | **4.5:1** | White text on button backgrounds |
167+
168+
### How Contrast Tests Work
169+
170+
Tests in `accessibility.spec.ts` extract CSS variable values live from the page, compute luminance using the WCAG formula, and calculate contrast ratios:
171+
172+
```typescript
173+
function hexToLuminance(hex: string): number {
174+
// sRGB → linear RGB → relative luminance
175+
}
176+
177+
function contrastRatio(hex1: string, hex2: string): number {
178+
return (lighter + 0.05) / (darker + 0.05);
179+
}
180+
```
181+
182+
---
183+
184+
## Troubleshooting
185+
186+
### Tests fail with "Target page, context or browser has been closed"
187+
188+
WebKit occasionally drops connections. This is a transient Playwright/WebKit issue. Re-run the tests — it typically passes on retry.
189+
190+
### Tests fail with "Theme toggle button not found"
191+
192+
The greeting page (`/`) with an empty database does NOT include `main.bundle.js`, so ThemeManager won't initialize. Tests use `/auth` which always includes the JS bundle.
193+
194+
### Unit tests fail with "document is not defined"
195+
196+
The JSDOM environment must be set up before importing ThemeManager. The unit test file handles this in `before()` hooks. Run with:
197+
```bash
198+
npx ts-mocha src/test/modules/themeManager.ts --timeout 5000
199+
```
200+
201+
### Cross-browser tests are slow
202+
203+
Firefox and WebKit tests run sequentially (1 worker). This is expected — 57 cross-browser tests add ~60s to the total run. Use `--project=chromium` to skip them during development.
204+
205+
### "Port 7777 already in use"
206+
207+
Kill any existing Node processes:
208+
```bash
209+
Get-Process -Name "node" -ErrorAction SilentlyContinue | Stop-Process -Force
210+
```
211+
212+
### Webpack rebuild required after CSS changes
213+
214+
After modifying any `.pcss` file, rebuild before running tests:
215+
```bash
216+
npx webpack --mode=production
217+
```

0 commit comments

Comments
 (0)