test: cover the static/manifest.json PWA install contract (tests/manifest-contract.test.mjs) - #335
kubestellar-hive[bot] wants to merge 2 commits into
Conversation
Adds tests/manifest-contract.test.mjs, the first test to parse static/manifest.json. The file is linked from every page as the web app manifest and decides whether a browser offers to install the site, but nothing in the repository read it: no test loaded it and no CI step referenced it. Asserts the manifest parses, declares the install fields the appmanifest spec requires, and that every icon resolves under static/ case-exactly, is non-empty, matches its declared media type, and matches its declared pixel dimensions as read from the PNG IHDR chunk. Test-only. No production code and no new dependency. Closes #333 Signed-off-by: kubestellar-hive[bot] <kubestellar-hive@hive.kubestellar.io>
|
Important Held for human review by the hive's ACMM level gate. This PR was opened by the "quality" agent while Hive policy required a human checkpoint for that agent. Non-outreach agents are held at ACMM L3–L5; the Hive will automatically remove the |
The first revision required every manifest path to be root-absolute. That would have blocked the correct fix for the baseUrl defect noted in the PR body: per the appmanifest spec, member URLs resolve against the manifest's own URL, so switching to manifest-relative paths makes the manifest work under any baseUrl without build machinery. Asserts same-origin instead: no scheme and no protocol-relative prefix. Both path forms still resolve under static/ case-exactly. Signed-off-by: kubestellar-hive[bot] <kubestellar-hive@hive.kubestellar.io>
|
Follow-up pushed: The first revision asserted every manifest path was root-absolute. While verifying the
That Suite: 66/66.
|
Test Improvement
Adds one new test-only file,
tests/manifest-contract.test.mjs(11 tests). Itis the first test in the repository to parse
static/manifest.json.Files claimed by this PR:
tests/manifest-contract.test.mjs(new).Nothing else — no production code, no workflow, no
package.json, nolockfile, no new dependency. It uses only
node:test,node:assert/strict,node:fs,node:urlandnode:path. It does not modifytests/helpers.mjs(claimed by #231) and does not importdocusaurus.config.js(claimed by #275).The gap
static/manifest.jsonis the web app manifest linked from every page(
docusaurus.config.js:41-46). It is the single file that decides whether abrowser offers to install the site, and nothing in the repository read it:
grep -rln "manifest" .github/ tests/ scripts/returned no matches, and itnever appeared in
--experimental-test-coverageoutput because no testprocess ever loaded it.
Every failure mode is silent. Docusaurus copies
static/intobuild/verbatim with no validation pass, and
onBrokenLinks: 'throw'enforces pageroutes rather than static assets — so a renamed icon, a zero-byte PNG, a
trailing comma, or a
displaytypo all produce a clean build and a greendeploy. The only symptom is that the install prompt quietly stops appearing.
A case-only rename is the worst variant:
/favicons/Favicon.svgresolveson a case-insensitive developer filesystem and 404s on GitHub Pages, which
serves from a case-sensitive one.
existsSync()does not catch it, so thetest walks each path segment and compares against the real
readdirSyncentry.
What the 11 tests assert
rather than a module-load crash, so the other ten still report.
name,short_name,start_url,displayare non-empty strings;displayis one of the fourappmanifestspec values;start_urlisroot-absolute.
iconsis a non-empty array — this guards against a regression that emptiesit silently making the assertions below vacuous.
src, no..escape, arecognised
type, asizesstring.static/case-exactly, is a file, and isnon-empty.
sizes(192x192 and 512x512 today).sizes: "any"rather than being pinned to one bucket.for firing
beforeinstallprompt.background_colorandtheme_colorare#rrggbb.src.Verification
fixture is modified.
npx prettier --checkclean.00b44df, node v26.8.2, run locally 2026-09-19.static/manifest.jsonwere each applied and reverted; ten failed at least one new test — case-only
icon path typo, declared size drifting to
256x256,displaytypo,three-digit
theme_color, SVG pinned to32x32, relativestart_url, 512icon removed,
iconsemptied, duplicate iconsrc, PNG mislabelledimage/svg+xml, and a trailing comma. The one survivor — removing the192x192 icon — is correct: the 512x512 icon satisfies both Chromium
thresholds on its own, so treating that as a failure would be wrong. The
manifest was restored clean afterwards (
git diffempty).Deliberately out of scope
static/manifest.jsonhard-codes root-absolute paths, and Docusaurus does notrewrite files under
static/forbaseUrl, so under theBASE_URL=/endusers/preview mode documented at
docusaurus.config.js:26-28those paths resolveagainst the origin root instead of the site root. That is a production defect,
not a test gap; asserting it here would land red, so it is filed separately
rather than weakened to fit this PR.
Disjointness
headTagshref/manifest.jsonresolves to a file. Itnever opens that file. This PR claims the manifest's contents.
docs/,blog/,src/anddata/*.jsonfor/imgreferences.
static/manifest.jsonis not in its scan set, and/favicons/is not
/img/.static/images,static/socialand
static/favicons; the file it would delete,static/favicons/favicon.ico,is not a manifest icon, so these assertions stay green after it lands.
src/css/custom.cssurl()refs andstatic/fonts/.static/manifest.json.Related Issue
Closes #333 — merging this lands the only deliverable that issue describes, so
nothing is left for it to track.
Filed by quality agent (hold-gated mode). Human review required.
— hive: agent=quality backend=copilot model=claude-opus-5