"use strict"; const test = require("node:test"); const assert = require("node:assert"); // ============================================================ // Minimal browser stub. Same shape as logic.test.js, plus a meta[name=...] // branch: novelfull ships no og: tags, so its cover comes from name="image". // document.body stays UNDEFINED so the boot block waits for a DOMContentLoaded // that never fires and no network call is ever made. // ============================================================ const store = new Map(); globalThis.localStorage = { getItem: (k) => (store.has(k) ? store.get(k) : null), setItem: (k, v) => store.set(k, String(v)), removeItem: (k) => store.delete(k), }; globalThis.location = { href: "about:blank", hostname: "", pathname: "/", origin: "" }; let metaTags = {}; let namedMetas = {}; let elements = {}; globalThis.document = { querySelector(sel) { let m = sel.match(/^meta\[property="([^"]+)"\]$/); if (m) { const v = metaTags[m[1]]; return v == null ? null : { getAttribute: () => v }; } m = sel.match(/^meta\[name="([^"]+)"\]$/); if (m) { const v = namedMetas[m[1]]; return v == null ? null : { getAttribute: () => v }; } const text = elements[sel]; return text == null ? null : { textContent: text }; }, querySelectorAll() { return []; }, addEventListener() {}, body: undefined, }; const { novelfull, lightnovelworld, kindOf, maxChapter, } = require("../novel-bookmark.user.js"); function loc(href) { const u = new URL(href); return { pathname: u.pathname, origin: u.origin, href: u.href, hostname: u.hostname }; } function reset() { metaTags = {}; namedMetas = {}; elements = {}; } // ============================================================ // novelfull adapter // ============================================================ test("novelfull.detect reads a series page", () => { reset(); namedMetas = { image: "https://novelfull.com/uploads/thumbs/ri.jpg" }; elements = { "h3.title": "Reverend Insanity" }; const p = novelfull.detect(loc("https://novelfull.com/reverend-insanity.html")); assert.equal(p.type, "series"); assert.equal(p.site, "novelfull"); assert.equal(p.seriesId, "reverend-insanity"); assert.equal(p.title, "Reverend Insanity"); assert.equal(p.cover, "https://novelfull.com/uploads/thumbs/ri.jpg"); assert.equal(p.seriesUrl, "https://novelfull.com/reverend-insanity.html"); assert.equal(p.chapterNum, null); }); test("novelfull.detect reads a chapter page and points seriesUrl at the series", () => { reset(); namedMetas = { image: "https://novelfull.com/uploads/thumbs/ri.jpg" }; elements = { "a.truyen-title": "Reverend Insanity" }; const url = "https://novelfull.com/reverend-insanity/chapter-2334-fang-yuan.html"; const p = novelfull.detect(loc(url)); assert.equal(p.type, "chapter"); assert.equal(p.seriesId, "reverend-insanity"); assert.equal(p.chapterNum, 2334); assert.equal(p.chapterLabel, "Chapter 2334"); assert.equal(p.chapterUrl, url); assert.equal(p.seriesUrl, "https://novelfull.com/reverend-insanity.html"); assert.equal(p.title, "Reverend Insanity"); }); test("novelfull.detect returns other for non-series paths", () => { reset(); assert.equal(novelfull.detect(loc("https://novelfull.com/")).type, "other"); assert.equal(novelfull.detect(loc("https://novelfull.com/genre/Fantasy")).type, "other"); }); test("novelfull.latestChapterFromAnchors takes the max and ignores other series", () => { const best = novelfull.latestChapterFromAnchors( [ { href: "/reverend-insanity/chapter-2334-fang-yuan.html", text: "Chapter 2334" }, { href: "/reverend-insanity/chapter-1.html", text: "Chapter 1" }, { href: "/reverend-insanity/chapter-2.html", text: "Chapter 2" }, { href: "/release-that-witch/chapter-9999.html", text: "Chapter 9999" }, ], "reverend-insanity" ); assert.deepEqual(best, { num: 2334, label: "Chapter 2334" }); }); // ============================================================ // lightnovelworld adapter // ============================================================ test("lightnovelworld.detect reads a series page", () => { reset(); metaTags = { "og:image": "https://lightnovelworld.net/wp-content/uploads/awe.webp" }; elements = { "h1.entry-title": "A Will Eternal" }; const p = lightnovelworld.detect(loc("https://lightnovelworld.net/novel/a-will-eternal/")); assert.equal(p.type, "series"); assert.equal(p.site, "lightnovelworld"); assert.equal(p.seriesId, "a-will-eternal"); assert.equal(p.title, "A Will Eternal"); assert.equal(p.cover, "https://lightnovelworld.net/wp-content/uploads/awe.webp"); }); test("lightnovelworld.detect strips the chapter suffix off the heading", () => { reset(); elements = { "h1.entry-title": "A Will Eternal Chapter 1298" }; const url = "https://lightnovelworld.net/a-will-eternal-chapter-1298/"; const p = lightnovelworld.detect(loc(url)); assert.equal(p.type, "chapter"); assert.equal(p.seriesId, "a-will-eternal"); assert.equal(p.chapterNum, 1298); assert.equal(p.chapterLabel, "Chapter 1298"); assert.equal(p.title, "A Will Eternal"); assert.equal(p.seriesUrl, "https://lightnovelworld.net/novel/a-will-eternal/"); // Chapter pages have no cover; the merge in bookmarkCurrent keeps the stored one. assert.equal(p.cover, ""); }); test("lightnovelworld.detect returns other for non-series paths", () => { reset(); assert.equal(lightnovelworld.detect(loc("https://lightnovelworld.net/")).type, "other"); assert.equal(lightnovelworld.detect(loc("https://lightnovelworld.net/az-lists/")).type, "other"); }); test("lightnovelworld.latestChapterFromAnchors takes the max and ignores other series", () => { const best = lightnovelworld.latestChapterFromAnchors( [ { href: "https://lightnovelworld.net/a-will-eternal-chapter-1/", text: "Chapter 1" }, { href: "https://lightnovelworld.net/a-will-eternal-chapter-1317/", text: "Chapter 1317" }, { href: "https://lightnovelworld.net/a-will-eternal-chapter-1298/", text: "Chapter 1298" }, { href: "https://lightnovelworld.net/overgeared-chapter-9999/", text: "Chapter 9999" }, ], "a-will-eternal" ); assert.deepEqual(best, { num: 1317, label: "Chapter 1317" }); }); test("latestChapterFromAnchors returns null when nothing matches", () => { assert.equal(novelfull.latestChapterFromAnchors([{ href: "/about", text: "About" }], "x"), null); assert.equal(maxChapter([], /chapter-([0-9.]+)/), null); }); // ============================================================ // kindOf // ============================================================ test("kindOf defaults a missing kind to manga", () => { assert.equal(kindOf({}), "manga"); }); test("kindOf passes through novel", () => { assert.equal(kindOf({ kind: "novel" }), "novel"); });