Skip to content

Commit 08731e5

Browse files
authored
Merge pull request #1460 from Joel234-png/fix/scaffolding-eslint-prettier-env-tests-scripts
Add ESLint/Prettier config, TTS env test coverage, and missing a11y:manual script (#1305, #1306, #1307, #1308)
2 parents 4626cec + 8518486 commit 08731e5

6 files changed

Lines changed: 154 additions & 0 deletions

File tree

frontend/.prettierignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.next/
2+
coverage/
3+
playwright-report/
4+
test-results/
5+
node_modules/
6+
public/
7+
src/lib/api/schema.d.ts
8+
next-env.d.ts

frontend/.prettierrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"semi": true,
3+
"singleQuote": true,
4+
"trailingComma": "es5",
5+
"printWidth": 100,
6+
"tabWidth": 2
7+
}

frontend/eslint.config.mjs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { FlatCompat } from '@eslint/eslintrc';
2+
import path from 'node:path';
3+
import { fileURLToPath } from 'node:url';
4+
5+
const __filename = fileURLToPath(import.meta.url);
6+
const __dirname = path.dirname(__filename);
7+
8+
const compat = new FlatCompat({
9+
baseDirectory: __dirname,
10+
});
11+
12+
const eslintConfig = [
13+
...compat.extends('next/core-web-vitals', 'next/typescript', 'prettier'),
14+
{
15+
ignores: ['.next/**', 'coverage/**', 'playwright-report/**', 'test-results/**'],
16+
},
17+
];
18+
19+
export default eslintConfig;

frontend/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
"build": "npm run generate-client && next build",
1010
"start": "next start",
1111
"lint": "next lint",
12+
"format": "prettier --write .",
13+
"format:check": "prettier --check .",
1214
"test": "jest",
1315
"test:watch": "jest --watch",
1416
"test:coverage": "jest --coverage",
@@ -41,6 +43,7 @@
4143
},
4244
"devDependencies": {
4345
"@axe-core/react": "^4.12.1",
46+
"@eslint/eslintrc": "^3.2.0",
4447
"@next/bundle-analyzer": "16.2.11",
4548
"@playwright/test": "^1.61.1",
4649
"@testing-library/dom": "^10.4.1",
@@ -53,12 +56,16 @@
5356
"@types/react-dom": "^19.2.3",
5457
"axe-core": "^4.12.1",
5558
"bundlewatch": "^0.4.2",
59+
"eslint": "^9.20.0",
60+
"eslint-config-next": "16.2.11",
61+
"eslint-config-prettier": "^9.1.0",
5662
"jest": "^30.4.2",
5763
"jest-axe": "^10.0.0",
5864
"jest-environment-jsdom": "^30.4.1",
5965
"lighthouse": "^13.4.1",
6066
"openapi-typescript": "^7.0.0",
6167
"pa11y": "^9.1.1",
68+
"prettier": "^3.4.2",
6269
"typescript": "^7.0.2"
6370
},
6471
"engines": {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Manual Accessibility Testing Checklist
3+
*
4+
* Automated tools (axe-core, Lighthouse, jest-axe) catch most WCAG 2.1 AA
5+
* violations, but several categories require a human to actually try the
6+
* page. This prints that checklist so `npm run a11y:manual` has something
7+
* to run — it previously pointed at a script that didn't exist in this repo.
8+
*/
9+
10+
const CHECKLIST = [
11+
{
12+
category: 'Keyboard navigation',
13+
items: [
14+
'Tab through the entire page — every interactive element is reachable and in a logical order',
15+
'Focus is always visible (no invisible focus outlines)',
16+
'No keyboard trap: focus can always move forward and backward out of any widget',
17+
'Modals/dialogs trap focus while open and return it to the trigger on close',
18+
'Skip-to-content link is the first focusable element and actually works',
19+
],
20+
},
21+
{
22+
category: 'Screen reader (VoiceOver / NVDA / JAWS)',
23+
items: [
24+
'Page landmarks (banner, main, navigation, contentinfo) are announced correctly',
25+
'Headings form a logical, non-skipping hierarchy',
26+
'Form fields announce their label, required state, and any validation error',
27+
'Live regions (toasts, status updates) are announced without moving focus',
28+
'Images convey their alt text (or are correctly marked decorative)',
29+
],
30+
},
31+
{
32+
category: 'Color & contrast',
33+
items: [
34+
'Text meets 4.5:1 contrast (3:1 for large text) in both light and dark mode',
35+
'Information is never conveyed by color alone (e.g. status badges also use text/icons)',
36+
'Focus indicators meet 3:1 contrast against their background',
37+
],
38+
},
39+
{
40+
category: 'Zoom & reflow',
41+
items: [
42+
'Page is usable at 200% browser zoom with no horizontal scroll or clipped content',
43+
'Page is usable at 400% zoom in a 1280px viewport (WCAG 1.4.10 reflow)',
44+
],
45+
},
46+
{
47+
category: 'Motion & animation',
48+
items: [
49+
'prefers-reduced-motion is respected for any animated transitions',
50+
'No content flashes more than 3 times per second',
51+
],
52+
},
53+
];
54+
55+
function printChecklist() {
56+
console.log('Manual Accessibility Testing Checklist (WCAG 2.1 AA)\n');
57+
for (const { category, items } of CHECKLIST) {
58+
console.log(category);
59+
for (const item of items) {
60+
console.log(` [ ] ${item}`);
61+
}
62+
console.log('');
63+
}
64+
console.log('Run alongside `npm run test:a11y`, `npm run lighthouse`, and `npm run axe`.');
65+
}
66+
67+
printChecklist();

frontend/src/lib/__tests__/env.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
describe('env validation', () => {
22
const original = process.env.NEXT_PUBLIC_API_URL;
3+
const originalTts = process.env.NEXT_PUBLIC_TTS_API_URL;
34

45
afterEach(() => {
56
process.env.NEXT_PUBLIC_API_URL = original;
7+
process.env.NEXT_PUBLIC_TTS_API_URL = originalTts;
68
jest.resetModules();
79
});
810

@@ -33,4 +35,48 @@ describe('env validation', () => {
3335
});
3436
}).toThrow(/must be a valid URL/);
3537
});
38+
39+
// Issue #1307: TTS integration (#116) is now in scope, but no test
40+
// exercised NEXT_PUBLIC_TTS_API_URL's validation branch at all.
41+
it('parses a valid NEXT_PUBLIC_TTS_API_URL', () => {
42+
process.env.NEXT_PUBLIC_API_URL = 'http://localhost:3001';
43+
process.env.NEXT_PUBLIC_TTS_API_URL = 'http://localhost:3002';
44+
let mod: typeof import('../env');
45+
jest.isolateModules(() => {
46+
mod = require('../env');
47+
});
48+
expect(mod!.getEnvConfig().NEXT_PUBLIC_TTS_API_URL).toBe('http://localhost:3002');
49+
});
50+
51+
it('allows NEXT_PUBLIC_TTS_API_URL to be unset — TTS features are optional', () => {
52+
process.env.NEXT_PUBLIC_API_URL = 'http://localhost:3001';
53+
delete process.env.NEXT_PUBLIC_TTS_API_URL;
54+
let mod: typeof import('../env');
55+
expect(() => {
56+
jest.isolateModules(() => {
57+
mod = require('../env');
58+
});
59+
}).not.toThrow();
60+
expect(mod!.getEnvConfig().NEXT_PUBLIC_TTS_API_URL).toBeUndefined();
61+
});
62+
63+
it('allows NEXT_PUBLIC_TTS_API_URL to be an empty string', () => {
64+
process.env.NEXT_PUBLIC_API_URL = 'http://localhost:3001';
65+
process.env.NEXT_PUBLIC_TTS_API_URL = '';
66+
expect(() => {
67+
jest.isolateModules(() => {
68+
require('../env');
69+
});
70+
}).not.toThrow();
71+
});
72+
73+
it('throws when NEXT_PUBLIC_TTS_API_URL is set but not a valid URL', () => {
74+
process.env.NEXT_PUBLIC_API_URL = 'http://localhost:3001';
75+
process.env.NEXT_PUBLIC_TTS_API_URL = 'not-a-url';
76+
expect(() => {
77+
jest.isolateModules(() => {
78+
require('../env');
79+
});
80+
}).toThrow(/NEXT_PUBLIC_TTS_API_URL.*must be a valid URL/s);
81+
});
3682
});

0 commit comments

Comments
 (0)