Both userscripts stop scraping Covers and stop putting one on the wire. A scraped address has no rendering path left now that the backend acquires, stores and serves every Cover from its own origin (ADR-0007), and keeping one would reintroduce third-party URLs into exactly the place #47 came from. - Adapters no longer read og:image / meta[name=image], and comix's img[alt] cover scan and novelfull's metaName helper are deleted. - apiPut strips `cover` off every outgoing body, so a cached row's address (already ours) never travels back either. The server ignores the field regardless. - Both card renderers swap in the existing `.cover.ph` placeholder when the image fails to load, so a failure looks designed rather than broken - the other half of what #47 reported. - The deleted scraping's test cases go with it: the comix cover cases, the img[alt] and meta[name] stub branches, and the stale og:image fixtures. Export lists are unchanged; nothing cover-specific was exported. The cover route itself is already public and uncredentialed, with the immutable cache directive and 404-for-unknown covered by the tests that landed with #59.
4.2 KiB
name, description
| name | description |
|---|---|
| testing-the-userscript | Use when writing, running, or debugging tests for userscript/manga-bookmark.user.js — adding a case to logic.test.js, exporting a function for test, a test that fails with "is not a function"/undefined export, or deciding whether some userscript behaviour is testable at all. |
Testing the userscript
userscript/manga-bookmark.user.js is a browser IIFE, not a module. It is tested
by require()-ing it into Node under a hand-written four-object browser stub in
userscript/test/logic.test.js. The harness covers pure logic only — adapters,
parsers, helpers. UI, network, and storage behaviour are verified on-device.
Commands
node --check userscript/manga-bookmark.user.js # parse check, silent on success
node --test userscript/test/logic.test.js # 14 tests as of 2026-07-28
Run both before every commit that touches the userscript.
Use the file path, not node --test userscript/test/. The directory form
fails MODULE_NOT_FOUND on this machine's Node v22.22.2. Older docs and plans
still write the directory form — substitute the file path; do not try to fix it.
How the harness works
The test file installs four globals before requiring the userscript:
| Global | What it is | Why |
|---|---|---|
localStorage |
Map-backed stub |
loadCache, loadQueue, and the key-migration IIFE touch it at module scope |
location |
{href, hostname, pathname, origin} |
read during boot |
document |
querySelector for meta[property="…"] only, plus a no-op addEventListener |
adapters read og:title (covers are the backend's, never scraped) |
document.body |
left undefined |
this is the whole trick |
document.body === undefined sends the userscript's boot block down its else
branch, where it waits for a DOMContentLoaded that never fires. init(),
buildUI(), and every fetch stay dormant, so nothing else needs stubbing.
The export hook near the end of the userscript is what makes require() work:
if (typeof window === "undefined" && typeof module === "object" && module.exports) {
module.exports = { stripBuildHash, asura, demonic, anchorsFromHTML, statusOf };
}
typeof window === "undefined" is load-bearing: under @grant none the script
shares page globals, so it must not clobber a page's own UMD shim.
Adding a test
- If the function isn't already exported, add it to that
module.exportslist and to the destructuringrequireat the top oflogic.test.js. A test failing withX is not a functionmeans you skipped this step. - Set
metaTags(module-levelletin the test file) for anything that readsog:tags — it is reassigned per test, so set every tag your case needs. - Build locations with the
loc(href)helper;detect()reads onlypathname,origin,href,hostname. - Keep the case pure: inputs in, value out,
asserton the result.
test("stripBuildHash removes a trailing 8-hex suffix", () => {
assert.equal(stripBuildHash("solo-leveling-059befe1"), "solo-leveling");
});
What is NOT testable here
- DOM, layout, Shadow DOM, the panel, the spinner. There is no DOM harness and you must not add one — no jsdom, no happy-dom, no second test file for UI. Verified on-device (Cromite + Violentmonkey) instead.
fetch, sync, the retry queue's network behaviour. Verified against a running backend.- Anything reachable only through
init()/buildUI().
If a change's only meaningful verification is visual or on-device, say so in the report rather than inventing coverage.
Gotchas
- New module-scope code that touches a browser API breaks every test, not
just a new one — the
require()runs it. Keep such work inside functions that onlyinit()calls. If you must add module-scope access, extend the stub. stripBuildHashmust stay in sync withasuraBuildHashinbackend/store.go; changing one without the other silently splits series identity.document.querySelectoronly understandsmeta[property="…"]. Any other selector returnsnull— extend the stub rather than working around it.- The userscript stays GM-free (no
GM_*APIs); a test that needs one is testing something that can't ship.