Skip to content

test: cover the static/manifest.json PWA install contract (tests/manifest-contract.test.mjs) - #335

Open
kubestellar-hive[bot] wants to merge 2 commits into
mainfrom
quality/test-manifest-contract
Open

kubestellar-hive[bot] wants to merge 2 commits into
mainfrom
quality/test-manifest-contract

Conversation

@kubestellar-hive

Copy link
Copy Markdown
Contributor

Test Improvement

Adds one new test-only file, tests/manifest-contract.test.mjs (11 tests). It
is 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, no
lockfile, no new dependency. It uses only node:test, node:assert/strict,
node:fs, node:url and node:path. It does not modify
tests/helpers.mjs (claimed by #231) and does not import
docusaurus.config.js (claimed by #275).

The gap

static/manifest.json is the web app manifest linked from every page
(docusaurus.config.js:41-46). It is the single file that decides whether a
browser offers to install the site, and nothing in the repository read it:
grep -rln "manifest" .github/ tests/ scripts/ returned no matches, and it
never appeared in --experimental-test-coverage output because no test
process ever loaded it.

Every failure mode is silent. Docusaurus copies static/ into build/
verbatim with no validation pass, and onBrokenLinks: 'throw' enforces page
routes rather than static assets — so a renamed icon, a zero-byte PNG, a
trailing comma, or a display typo all produce a clean build and a green
deploy. The only symptom is that the install prompt quietly stops appearing.

A case-only rename is the worst variant: /favicons/Favicon.svg resolves
on a case-insensitive developer filesystem and 404s on GitHub Pages, which
serves from a case-sensitive one. existsSync() does not catch it, so the
test walks each path segment and compares against the real readdirSync
entry.

What the 11 tests assert

  1. The manifest parses as a JSON object — reported as a named test failure
    rather than a module-load crash, so the other ten still report.
  2. name, short_name, start_url, display are non-empty strings;
    display is one of the four appmanifest spec values; start_url is
    root-absolute.
  3. icons is a non-empty array — this guards against a regression that empties
    it silently making the assertions below vacuous.
  4. Each icon is fully described: root-absolute src, no .. escape, a
    recognised type, a sizes string.
  5. Each icon resolves under static/ case-exactly, is a file, and is
    non-empty.
  6. Each icon file's extension matches its declared media type.
  7. Raster icon dimensions read from the PNG IHDR chunk match the declared
    sizes (192x192 and 512x512 today).
  8. Vector icons declare sizes: "any" rather than being pinned to one bucket.
  9. Icons of at least 192px and at least 512px survive — Chromium's threshold
    for firing beforeinstallprompt.
  10. background_color and theme_color are #rrggbb.
  11. No two icons claim the same src.

Verification

  • 11/11 pass; full suite 66/66 (was 55/55). No existing test, helper or
    fixture is modified. npx prettier --check clean.
  • Environment: rev 00b44df, node v26.8.2, run locally 2026-09-19.
  • Mutation-checked. Eleven independent mutations of static/manifest.json
    were each applied and reverted; ten failed at least one new test — case-only
    icon path typo, declared size drifting to 256x256, display typo,
    three-digit theme_color, SVG pinned to 32x32, relative start_url, 512
    icon removed, icons emptied, duplicate icon src, PNG mislabelled
    image/svg+xml, and a trailing comma. The one survivor — removing the
    192x192 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 diff empty).

Deliberately out of scope

static/manifest.json hard-codes root-absolute paths, and Docusaurus does not
rewrite files under static/ for baseUrl, so under the BASE_URL=/endusers/
preview mode documented at docusaurus.config.js:26-28 those paths resolve
against 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

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

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>
@kubestellar-hive

Copy link
Copy Markdown
Contributor Author

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 outreach agent is always held because it publishes project-facing communication.

Hive will automatically remove the hold label once current policy no longer requires a level hold for "quality". If this is an outreach PR, a human must review it and remove the label.

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>
@kubestellar-hive

Copy link
Copy Markdown
Contributor Author

Follow-up pushed: a32389b.

The first revision asserted every manifest path was root-absolute. While verifying the baseUrl behaviour described under Deliberately out of scope, I confirmed by build that the correct fix is to make those paths manifest-relative — so the original assertion would have blocked the fix it was pointing at.

a32389b asserts same-origin instead (no scheme, no protocol-relative // prefix) and resolves both path forms under static/ case-exactly. Re-mutation-checked: an off-site https:// icon, an off-site start_url and a protocol-relative icon each fail; applying the recommended relative-path fix keeps all 11 green.

That baseUrl defect is now filed as #337 with the exact replacement text. It is a production change in static/manifest.json and docusaurus.config.js, so it needs a human or a production-capable lane — this PR stays test-only and is unaffected either way.

Suite: 66/66. prettier --check clean.

🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5n31 | SHA: a32389b

— hive: agent=quality backend=copilot model=claude-opus-5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[quality] static/manifest.json is never parsed by any test or CI gate; the PWA install contract is unguarded

0 participants