From 688c24d44ca1f87412c029f1db9e2d2d57889920 Mon Sep 17 00:00:00 2001 From: Sulthan Zaki Date: Mon, 10 Aug 2026 18:09:58 +0700 Subject: [PATCH] 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. --- userscript/novel-bookmark.user.js | 7 +++++++ userscript/test/novel-logic.test.js | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/userscript/novel-bookmark.user.js b/userscript/novel-bookmark.user.js index 7a89952..cca85f8 100644 --- a/userscript/novel-bookmark.user.js +++ b/userscript/novel-bookmark.user.js @@ -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. diff --git a/userscript/test/novel-logic.test.js b/userscript/test/novel-logic.test.js index 62e689e..926e8c7 100644 --- a/userscript/test/novel-logic.test.js +++ b/userscript/test/novel-logic.test.js @@ -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`); + } + } +});