Restore the novel script's lost module-scope constants (#74)

novel-bookmark.user.js was split out of manga-bookmark.user.js and lost
four constants: CACHE_KEY, LASTCHECKED_KEY, LATEST_CHECK_THROTTLE_MS and
LATEST_CHECK_BATCH. Every use sits behind a try/catch or a
fire-and-forget promise, so the ReferenceErrors were swallowed:
backgroundRefreshLatest() threw before its first fetch, and the local
cache silently read empty and never persisted.

The issue named the two throttle constants; the cache keys are the same
lost lines and are fixed here too.

Add a static guard over both scripts, since no behavioural test can see
a swallowed ReferenceError.
This commit is contained in:
2026-08-10 18:09:58 +07:00
parent 7c7d597019
commit 688c24d44c
2 changed files with 32 additions and 0 deletions
+7
View File
@@ -25,6 +25,13 @@
// This script owns the novel library; the manga script is a separate install
// with its own prefix, so the two never share a cache, a queue or a panel.
const STORE_PREFIX = "bmgr:novel:";
const CACHE_KEY = STORE_PREFIX + "cache";
// Per-device record of when each series was last checked for new chapters.
// Deliberately not synced: each device does its own checking.
const LASTCHECKED_KEY = STORE_PREFIX + "lastchecked";
const LATEST_CHECK_THROTTLE_MS = 4 * 60 * 60 * 1000;
const LATEST_CHECK_BATCH = 1; // series fetched per navigation // series fetched per navigation
// Which library this script's rows belong to. The manga script is a separate
// install that declares "manga"; the backend keeps whichever it is told.
+25
View File
@@ -165,3 +165,28 @@ test("kindOf defaults a missing kind to manga", () => {
test("kindOf passes through novel", () => {
assert.equal(kindOf({ kind: "novel" }), "novel");
});
// ============================================================
// Source guard
//
// Issue #74: the novel script was split off the manga one and lost four
// module-scope constants. The reads sit inside try/catch or a fire-and-forget
// promise, so the ReferenceError never surfaced — nothing but a static check
// catches this class.
// ============================================================
test("every SCREAMING_CASE constant the script uses is declared in it", () => {
const fs = require("node:fs");
for (const f of ["novel-bookmark.user.js", "manga-bookmark.user.js"]) {
const src = fs.readFileSync(require.resolve("../" + f), "utf8")
// comments and strings carry prose and SVG path data in the same shape
.replace(/\/\/[^\n]*|\/\*[\s\S]*?\*\/|"[^"\n]*"|'[^'\n]*'|`[\s\S]*?`/g, " ");
const declared = new Set(
[...src.matchAll(/\b(?:const|let|var|function)\s+([A-Z][A-Z0-9_]{2,})\b/g)].map((m) => m[1]),
);
for (const name of new Set(src.match(/\b[A-Z][A-Z0-9_]{2,}\b/g) || [])) {
if (name.startsWith("GM_") || name in globalThis) continue;
assert.ok(declared.has(name), `${f} uses ${name} but never declares it`);
}
}
});