feat(userscript): add novel-bookmark.user.js for novelfull and lightnovelworld
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
"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");
|
||||
});
|
||||
Reference in New Issue
Block a user