Skip to content

Commit 08f88ca

Browse files
author
Lukas Geiger
committed
test: add theme coverage and CI
1 parent cc3c269 commit 08f88ca

4 files changed

Lines changed: 133 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
workflow_dispatch:
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
test:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
20+
- name: Set up Node.js
21+
uses: actions/setup-node@v4
22+
with:
23+
node-version: 22
24+
25+
- name: Run tests
26+
run: npm test

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## [Unreleased]
4+
5+
### Added
6+
- Added automated light/dark theme coverage for popup and options CSS variables.
7+
- Added a read-only GitHub Actions CI workflow for the Node test suite.
8+
9+
### Verified
10+
- `npm test` now covers 25 dependency-free Node tests, including theme coverage.
11+
312
## [1.1.2] — 2026-04-30
413

514
### Added

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
![Manifest V3](https://img.shields.io/badge/Manifest-V3-blue)
44
![License: MIT](https://img.shields.io/badge/License-MIT-green)
55
![Privacy](https://img.shields.io/badge/Privacy-No%20Tracking-brightgreen)
6+
![CI](https://github.com/file-bricks/RSS-BOOK/actions/workflows/ci.yml/badge.svg)
67

78
## Screenshot
89

@@ -58,12 +59,14 @@ Store listing is planned after the remaining browser and screenshot checks.
5859

5960
## Development
6061

61-
RSS-BOOK has no build step. The repository includes dependency-free Node tests:
62+
RSS-BOOK has no build step. The repository includes 25 dependency-free Node tests for parser behavior, OPML, storage, bookmark cleanup, feed discovery, folder export, store assets, and light/dark theme CSS coverage:
6263

6364
```bash
64-
node --test tests/*.test.mjs
65+
npm test
6566
```
6667

68+
GitHub Actions runs the same suite on pushes to `main` and pull requests.
69+
6770
## Permissions
6871

6972
| Permission | Why |

tests/theme.test.mjs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { test } from "node:test";
2+
import assert from "node:assert/strict";
3+
import fs from "node:fs";
4+
import path from "node:path";
5+
import { fileURLToPath } from "node:url";
6+
7+
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8+
9+
function readHtml(relativePath) {
10+
return fs.readFileSync(path.join(rootDir, relativePath), "utf8");
11+
}
12+
13+
function extractStyle(html, relativePath) {
14+
const match = html.match(/<style>([\s\S]*?)<\/style>/i);
15+
assert.ok(match, `${relativePath} should include inline CSS`);
16+
return match[1];
17+
}
18+
19+
function extractVariableSet(block) {
20+
const variables = new Map();
21+
for (const match of block.matchAll(/--([a-z0-9-]+)\s*:\s*([^;]+);/gi)) {
22+
variables.set(match[1], match[2].trim());
23+
}
24+
return variables;
25+
}
26+
27+
function extractThemeBlocks(css, relativePath) {
28+
const lightMatch = css.match(/:root\s*\{([\s\S]*?)\}/);
29+
assert.ok(lightMatch, `${relativePath} should define light :root variables`);
30+
31+
const darkMatch = css.match(/@media\s*\(prefers-color-scheme:\s*dark\)\s*\{\s*:root\s*\{([\s\S]*?)\}\s*\}/);
32+
assert.ok(darkMatch, `${relativePath} should define a dark prefers-color-scheme override`);
33+
34+
return {
35+
light: extractVariableSet(lightMatch[1]),
36+
dark: extractVariableSet(darkMatch[1])
37+
};
38+
}
39+
40+
function extractUsedVariables(css) {
41+
return new Set(Array.from(css.matchAll(/var\(--([a-z0-9-]+)\)/gi), (match) => match[1]));
42+
}
43+
44+
function assertThemeCoverage(relativePath, pageSpecificVariables = []) {
45+
const html = readHtml(relativePath);
46+
const css = extractStyle(html, relativePath);
47+
const { light, dark } = extractThemeBlocks(css, relativePath);
48+
49+
const requiredVariables = [
50+
"bg",
51+
"text",
52+
"text-muted",
53+
"border",
54+
"btn-bg",
55+
"btn-hover",
56+
"btn-border",
57+
"accent",
58+
"accent-hover",
59+
"error",
60+
"success",
61+
...pageSpecificVariables
62+
];
63+
64+
for (const variable of requiredVariables) {
65+
assert.ok(light.has(variable), `${relativePath} light theme should define --${variable}`);
66+
assert.ok(dark.has(variable), `${relativePath} dark theme should define --${variable}`);
67+
}
68+
69+
for (const variable of extractUsedVariables(css)) {
70+
assert.ok(light.has(variable), `${relativePath} uses --${variable} in CSS but does not define it for light mode`);
71+
assert.ok(dark.has(variable), `${relativePath} uses --${variable} in CSS but does not define it for dark mode`);
72+
}
73+
74+
assert.notEqual(light.get("bg"), dark.get("bg"), `${relativePath} dark mode should override --bg`);
75+
assert.notEqual(light.get("text"), dark.get("text"), `${relativePath} dark mode should override --text`);
76+
assert.notEqual(light.get("border"), dark.get("border"), `${relativePath} dark mode should override --border`);
77+
assert.match(css, /body\s*\{[^}]*color:\s*var\(--text\)[^}]*background:\s*var\(--bg\)/s);
78+
}
79+
80+
test("popup page has complete light and dark theme variables", () => {
81+
assertThemeCoverage("ui/popup.html", ["discover-bg", "discover-border"]);
82+
});
83+
84+
test("options page has complete light and dark theme variables", () => {
85+
assertThemeCoverage("ui/options.html", [
86+
"input-border",
87+
"input-bg",
88+
"card-bg",
89+
"section-bg",
90+
"info-bg",
91+
"info-border"
92+
]);
93+
});

0 commit comments

Comments
 (0)