Skip to content

Commit c28ebc3

Browse files
committed
chore: add pa11y and adhere to wcag aaa
1 parent 62d22e6 commit c28ebc3

File tree

9 files changed

+1385
-80
lines changed

9 files changed

+1385
-80
lines changed

.github/workflows/ci.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
on:
2+
pull_request:
3+
branches:
4+
- main
5+
6+
permissions:
7+
contents: read
8+
9+
jobs:
10+
lint:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v6
14+
- run: corepack enable
15+
- uses: actions/setup-node@v6
16+
with: &setup-node-config
17+
node-version: 24
18+
registry-url: https://registry.npmjs.org
19+
- run: yarn install
20+
- run: yarn run lint
21+
typecheck:
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v6
25+
- run: corepack enable
26+
- uses: actions/setup-node@v6
27+
with: *setup-node-config
28+
- run: yarn install
29+
- run: yarn run typecheck
30+
a11y:
31+
runs-on: ubuntu-latest
32+
steps:
33+
# https://github.com/pa11y/pa11y/blob/2055ee510163d871e874f6ea0bfe895f84bbac4a/.github/workflows/tests.yml#L37
34+
- run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
35+
- uses: actions/checkout@v6
36+
- run: corepack enable
37+
- uses: actions/setup-node@v6
38+
with: *setup-node-config
39+
- run: yarn install
40+
- run: yarn run build:html
41+
- run: yarn run a11y:ci
42+
build:
43+
runs-on: ubuntu-latest
44+
steps:
45+
- uses: actions/checkout@v6
46+
- run: corepack enable
47+
- uses: actions/setup-node@v6
48+
with: *setup-node-config
49+
- run: yarn install
50+
- run: npx resume export --theme . --resume ./test/fixture.resume.json /tmp/resume.html
51+
- run: npx resume export --theme . --resume ./test/fixture.resume.json /tmp/resume.pdf

.github/workflows/publish.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
name: Node.js Package
2-
31
on:
42
release:
53
types: [created]
@@ -13,11 +11,11 @@ jobs:
1311
publish-npm:
1412
runs-on: ubuntu-latest
1513
steps:
16-
- uses: actions/checkout@v5
14+
- uses: actions/checkout@v6
1715
- run: corepack enable
1816
- uses: actions/setup-node@v6
1917
with:
20-
node-version: 22
18+
node-version: 24
2119
registry-url: https://registry.npmjs.org
2220
- run: yarn install
2321
- run: yarn npm publish --access public

.github/workflows/pulls.yml

Lines changed: 0 additions & 35 deletions
This file was deleted.

assets/preview-dark.png

666 Bytes
Loading

assets/preview-light.png

951 Bytes
Loading

pa11y.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { styleText } from 'node:util';
2+
import pa11y from 'pa11y';
3+
import puppeteer from 'puppeteer';
4+
5+
/**
6+
* @typedef {'light'|'dark'} ColorScheme
7+
*
8+
* @typedef {object} TestCase
9+
* @property {string} name
10+
* @property {string} url
11+
* @property {ColorScheme} colorScheme
12+
*/
13+
14+
const PAGE_URL = 'http://localhost:8080/resume.html';
15+
16+
/** @type {TestCase[]} */
17+
const TEST_CASES = [
18+
{
19+
name: 'Light Mode (default)',
20+
url: PAGE_URL,
21+
colorScheme: 'light'
22+
},
23+
{
24+
name: 'Dark Mode',
25+
url: PAGE_URL,
26+
colorScheme: 'dark'
27+
},
28+
];
29+
30+
/**
31+
* @param {import('puppeteer').Browser} browser
32+
* @param {TestCase} testCase
33+
* @returns {Promise<any>}
34+
*/
35+
const pa11yRunner = async (browser, testCase) => {
36+
const page = await browser.newPage();
37+
await page.emulateMediaFeatures([
38+
{ name: 'prefers-color-scheme', value: testCase.colorScheme }
39+
]);
40+
41+
const results = await pa11y(testCase.url, {
42+
standard: 'WCAG2AAA',
43+
browser,
44+
page,
45+
});
46+
47+
await page.close();
48+
return results;
49+
};
50+
51+
const browser = await puppeteer.launch();
52+
const results = await Promise.all(TEST_CASES.map(async (testCase) => ({
53+
name: testCase.name,
54+
result: await pa11yRunner(browser, testCase),
55+
})));
56+
57+
await browser.close();
58+
59+
let failed = false;
60+
61+
for (const { name, result } of results) {
62+
if (result.issues.length === 0) {
63+
continue;
64+
}
65+
66+
failed = true;
67+
68+
for (const issue of result.issues) {
69+
console.error(
70+
'%s > %s\n%s %s\n%s %s\n%s %s\n',
71+
styleText(['blue', 'bold'], name),
72+
styleText(['red', 'bold'], issue.code),
73+
styleText('yellow', 'Message:'),
74+
issue.message,
75+
styleText('yellow', 'Context:'),
76+
styleText(['gray', 'italic'], issue.context),
77+
styleText('yellow', 'Selector:'),
78+
styleText('gray', issue.selector),
79+
);
80+
}
81+
}
82+
83+
if (failed) {
84+
process.exitCode = 1;
85+
}

package.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"packageManager": "yarn@4.10.3",
33
"name": "@jsonresume/jsonresume-theme-class",
4-
"version": "0.5.1",
4+
"version": "0.6.0",
55
"description": "Class Theme for JSON Resume",
66
"author": "Seth Falco",
77
"license": "MIT",
@@ -28,8 +28,10 @@
2828
"scripts": {
2929
"serve": "resume serve --theme . --resume ./test/fixture.resume.json",
3030
"lint": "eslint .",
31-
"lint:fix": "eslint --fix .",
32-
"typecheck": "tsc --noEmit",
31+
"format": "eslint --fix .",
32+
"typecheck": "tsc",
33+
"a11y": "node ./pa11y.js",
34+
"a11y:ci": "start-server-and-test 'serve . -l 8080' 'http://localhost:8080/resume.html' 'yarn run a11y'",
3335
"generate-preview": "node scripts/generate-preview.js",
3436
"build:pdf": "resume export --theme . --resume ./test/fixture.resume.json resume.pdf",
3537
"build:html": "resume export --theme . --resume ./test/fixture.resume.json resume.html"
@@ -51,8 +53,11 @@
5153
"eslint": "^9.38.0",
5254
"eslint-plugin-import": "^2.32.0",
5355
"globals": "^14.0.0",
56+
"pa11y": "^9.1.0",
5457
"puppeteer": "^18.2.1",
5558
"resume-cli": "^3.1.2",
59+
"serve": "^14.2.5",
60+
"start-server-and-test": "^2.1.3",
5661
"typescript": "^5.9.3"
5762
}
5863
}

src/style.css

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ body {
22
--fg-color: black;
33
--bg-color: #fff;
44
--header-h1-fg-color: #fff;
5-
--header-h2-fg-color: #f3f3f3;
6-
--header-bg-color: #1866c3;
7-
--link-color: #3061a5;
5+
--header-h2-fg-color: #f2f2f2;
6+
--header-bg-color: #13509b;
7+
--note-color: #595959;
8+
--link-color: #2c5999;
89
--separator-color: #e2e2e2;
910

1011
margin: 0;
@@ -21,13 +22,15 @@ body {
2122
--header-h1-fg-color: #ddd;
2223
--header-h2-fg-color: #eee;
2324
--header-bg-color: #1b1b1b;
24-
--link-color: #70a1e5;
25-
--separator-color: #939393
25+
--note-color: #b8b8b8;
26+
--link-color: #96baec;
27+
--separator-color: #939393;
2628
}
2729
}
2830

2931
em {
30-
color: #999;
32+
color: var(--note-color);
33+
font-size: 14px;
3134
}
3235

3336
a {

0 commit comments

Comments
 (0)