新增: - 书库导入导出(library_import/library_export)及测试 - 图片变体生成(image_variants)与响应式图片(responsiveImage) - 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件 - 小组件运行时(widgetRuntime)与静态检查(widgetLint) - 上传缓存中间件(upload_cache)与字体 CSS 提取脚本 其它: - 后端 handlers/services 全量调整 - 前端页面、组件、库函数与配置更新
215 lines
11 KiB
TypeScript
215 lines
11 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import { describe, it } from "node:test";
|
||
import {
|
||
announcementPath,
|
||
commentAnchor,
|
||
entityPath,
|
||
leaderboardPath,
|
||
libraryDocPath,
|
||
libraryDocReadPath,
|
||
libraryPath,
|
||
matchPrettyPath,
|
||
matchStandardPath,
|
||
normalizeUrlStyle,
|
||
pagePath,
|
||
parseEntityPath,
|
||
postPath,
|
||
userPath,
|
||
} from "./urlStyle.ts";
|
||
|
||
describe("normalizeUrlStyle", () => {
|
||
it("合法值与容错", () => {
|
||
assert.equal(normalizeUrlStyle("default"), "default");
|
||
assert.equal(normalizeUrlStyle(" HTML "), "html");
|
||
assert.equal(normalizeUrlStyle("DISCUZ"), "discuz");
|
||
assert.equal(normalizeUrlStyle("pretty"), "default");
|
||
assert.equal(normalizeUrlStyle(undefined), "default");
|
||
assert.equal(normalizeUrlStyle(42), "default");
|
||
});
|
||
});
|
||
|
||
describe("路径构造器", () => {
|
||
it("三风格帖子路径", () => {
|
||
assert.equal(postPath("default", 123), "/post/123");
|
||
assert.equal(postPath("html", 123), "/post-123.html");
|
||
assert.equal(postPath("discuz", 123), "/thread-123.html");
|
||
});
|
||
it("三风格用户路径", () => {
|
||
assert.equal(userPath("default", 5), "/u/5");
|
||
assert.equal(userPath("html", 5), "/user-5.html");
|
||
assert.equal(userPath("discuz", 5), "/space-uid-5.html");
|
||
});
|
||
it("三风格公告路径", () => {
|
||
assert.equal(announcementPath("default", 1), "/announcement/1");
|
||
assert.equal(announcementPath("html", 1), "/announcement-1.html");
|
||
assert.equal(announcementPath("discuz", 1), "/notice-1.html");
|
||
});
|
||
it("非法 id 返回空串", () => {
|
||
assert.equal(postPath("default", 0), "");
|
||
assert.equal(postPath("html", -1), "");
|
||
assert.equal(userPath("discuz", Number.NaN), "");
|
||
});
|
||
it("自建页三风格并编码 slug", () => {
|
||
assert.equal(pagePath("default", "about"), "/p/about");
|
||
assert.equal(pagePath("html", "about"), "/p-about.html");
|
||
assert.equal(pagePath("discuz", "使用指南"), `/page-${encodeURIComponent("使用指南")}.html`);
|
||
assert.equal(pagePath("html", "使用指南"), `/p-${encodeURIComponent("使用指南")}.html`);
|
||
assert.equal(pagePath("default", " "), "/p/");
|
||
});
|
||
it("排行榜三风格", () => {
|
||
assert.equal(leaderboardPath("default"), "/leaderboard");
|
||
assert.equal(leaderboardPath("html"), "/leaderboard.html");
|
||
assert.equal(leaderboardPath("discuz"), "/rank.html");
|
||
});
|
||
it("书库三风格(html/discuz 同模板;章节 = 详情路径 + ?sec=)", () => {
|
||
assert.equal(libraryPath("default"), "/library");
|
||
assert.equal(libraryPath("html"), "/library.html");
|
||
assert.equal(libraryPath("discuz"), "/library.html");
|
||
assert.equal(libraryDocPath("default", "abc"), "/library/abc");
|
||
assert.equal(libraryDocPath("html", "abc"), "/library-abc.html");
|
||
assert.equal(libraryDocPath("discuz", "abc"), "/library-abc.html");
|
||
assert.equal(
|
||
libraryDocPath("html", "使用指南"),
|
||
`/library-${encodeURIComponent("使用指南")}.html`
|
||
);
|
||
assert.equal(libraryDocPath("default", " "), "/library");
|
||
assert.equal(libraryDocPath("html", " "), "/library.html");
|
||
// 阅读页:chapterNo 为书内章序号(1 起),缺省表示第一章
|
||
assert.equal(libraryDocReadPath("default", "abc"), "/library/abc/read");
|
||
assert.equal(libraryDocReadPath("default", "abc", 5), "/library/abc/read?c=5");
|
||
assert.equal(libraryDocReadPath("html", "abc"), "/library-abc.html?sec=1");
|
||
assert.equal(libraryDocReadPath("discuz", "abc", 5), "/library-abc.html?sec=5");
|
||
assert.equal(
|
||
libraryDocReadPath("html", "使用指南", 2),
|
||
`/library-${encodeURIComponent("使用指南")}.html?sec=2`
|
||
);
|
||
});
|
||
it("entityPath 与标准路径", () => {
|
||
assert.equal(entityPath("discuz", "post", 9), "/thread-9.html");
|
||
assert.equal(entityPath("default", "user", 9), "/u/9");
|
||
assert.equal(entityPath("html", "announcement", 9), "/announcement-9.html");
|
||
});
|
||
it("评论锚点使用帖内相对楼层号", () => {
|
||
assert.equal(commentAnchor(1, true, 147), "#comment-1");
|
||
assert.equal(commentAnchor(3, false, 147), "#comment-3-r147");
|
||
// 楼中楼缺评论 id 时退化为楼层锚点
|
||
assert.equal(commentAnchor(3, false), "#comment-3");
|
||
// 楼层无效(评论已不可见)返回空串,由调用方回落纯帖子路径
|
||
assert.equal(commentAnchor(0, true, 147), "");
|
||
assert.equal(commentAnchor(Number.NaN, false, 147), "");
|
||
});
|
||
});
|
||
|
||
describe("matchPrettyPath", () => {
|
||
it("命中各风格模板", () => {
|
||
assert.deepEqual(matchPrettyPath("/post-12.html"), { style: "html", kind: "post", id: 12 });
|
||
assert.deepEqual(matchPrettyPath("/user-3.html"), { style: "html", kind: "user", id: 3 });
|
||
assert.deepEqual(matchPrettyPath("/announcement-7.html"), { style: "html", kind: "announcement", id: 7 });
|
||
assert.deepEqual(matchPrettyPath("/thread-12.html"), { style: "discuz", kind: "post", id: 12 });
|
||
assert.deepEqual(matchPrettyPath("/space-uid-3.html"), { style: "discuz", kind: "user", id: 3 });
|
||
assert.deepEqual(matchPrettyPath("/notice-7.html"), { style: "discuz", kind: "announcement", id: 7 });
|
||
assert.deepEqual(matchPrettyPath("/p-about.html"), { style: "html", kind: "page", slug: "about" });
|
||
assert.deepEqual(matchPrettyPath("/leaderboard.html"), { style: "html", kind: "leaderboard" });
|
||
assert.deepEqual(matchPrettyPath("/rank.html"), { style: "discuz", kind: "leaderboard" });
|
||
});
|
||
it("书库两风格同模板:prefer 优先当前风格,缺省首条兜底", () => {
|
||
assert.equal(matchPrettyPath("/library.html", "html")?.style, "html");
|
||
assert.equal(matchPrettyPath("/library.html", "discuz")?.style, "discuz");
|
||
assert.equal(matchPrettyPath("/library.html")?.style, "html");
|
||
assert.deepEqual(matchPrettyPath("/library-x.html", "discuz"), {
|
||
style: "discuz",
|
||
kind: "libraryDoc",
|
||
slug: "x",
|
||
});
|
||
assert.deepEqual(matchPrettyPath("/library-x.html"), { style: "html", kind: "libraryDoc", slug: "x" });
|
||
});
|
||
it("旧版章节伪静态 -read-{n} 仍可解析(不再生成)", () => {
|
||
assert.deepEqual(matchPrettyPath("/library-x-read-2.html", "discuz"), {
|
||
style: "discuz",
|
||
kind: "libraryRead",
|
||
slug: "x",
|
||
id: 2,
|
||
});
|
||
assert.deepEqual(matchPrettyPath("/library-x-read-2.html"), {
|
||
style: "html",
|
||
kind: "libraryRead",
|
||
slug: "x",
|
||
id: 2,
|
||
});
|
||
// 详情模板不再吞掉 -read-{n};纯详情仍命中 libraryDoc
|
||
assert.deepEqual(matchPrettyPath("/library-x.html"), { style: "html", kind: "libraryDoc", slug: "x" });
|
||
assert.equal(matchPrettyPath("/library-x-read-2.htmlx"), null);
|
||
assert.equal(matchPrettyPath("/library-x-read-2.html/edit"), null);
|
||
});
|
||
it("书库 pretty 排除子路径与伪造路径", () => {
|
||
assert.equal(matchPrettyPath("/library-x.htmlx"), null);
|
||
assert.equal(matchPrettyPath("/library-x.html/edit"), null);
|
||
assert.equal(matchPrettyPath("/library-.html"), null);
|
||
assert.equal(matchPrettyPath("/library.html/x"), null);
|
||
});
|
||
it("page slug 保持 encoded 原样", () => {
|
||
const enc = encodeURIComponent("使用指南");
|
||
assert.deepEqual(matchPrettyPath(`/page-${enc}.html`), { style: "discuz", kind: "page", slug: enc });
|
||
assert.deepEqual(matchPrettyPath(`/p-${enc}.html`), { style: "html", kind: "page", slug: enc });
|
||
});
|
||
it("排除子路径与伪造路径", () => {
|
||
assert.equal(matchPrettyPath("/post/12"), null);
|
||
assert.equal(matchPrettyPath("/post/12/edit"), null);
|
||
assert.equal(matchPrettyPath("/post-abc.html"), null);
|
||
assert.equal(matchPrettyPath("/thread-12.htmlx"), null);
|
||
assert.equal(matchPrettyPath("/thread-12.html/edit"), null);
|
||
assert.equal(matchPrettyPath("/thread--12.html"), null);
|
||
assert.equal(matchPrettyPath("/post.html"), null);
|
||
assert.equal(matchPrettyPath("/p-.html"), null);
|
||
assert.equal(matchPrettyPath("/leaderboardx.html"), null);
|
||
assert.equal(matchPrettyPath("/rank.htmlx"), null);
|
||
assert.equal(matchPrettyPath("/rank"), null);
|
||
});
|
||
});
|
||
|
||
describe("matchStandardPath / parseEntityPath", () => {
|
||
it("标准路径精确匹配", () => {
|
||
assert.deepEqual(matchStandardPath("/post/12"), { kind: "post", id: 12 });
|
||
assert.deepEqual(matchStandardPath("/u/5"), { kind: "user", id: 5 });
|
||
assert.deepEqual(matchStandardPath("/announcement/7"), { kind: "announcement", id: 7 });
|
||
assert.deepEqual(matchStandardPath("/p/about"), { kind: "page", slug: "about" });
|
||
assert.deepEqual(matchStandardPath("/leaderboard"), { kind: "leaderboard" });
|
||
assert.deepEqual(matchStandardPath("/leaderboard/"), { kind: "leaderboard" });
|
||
assert.deepEqual(matchStandardPath("/library"), { kind: "library" });
|
||
assert.deepEqual(matchStandardPath("/library/"), { kind: "library" });
|
||
assert.deepEqual(matchStandardPath("/library/abc"), { kind: "libraryDoc", slug: "abc" });
|
||
assert.equal(matchStandardPath("/post/12/edit"), null);
|
||
assert.equal(matchStandardPath("/post/abc"), null);
|
||
assert.equal(matchStandardPath("/u/5/followers"), null);
|
||
assert.equal(matchStandardPath("/p/"), null);
|
||
assert.equal(matchStandardPath("/p/a/b"), null);
|
||
// 读页单独识别(?c= 走 query),不得被详情模式误伤
|
||
assert.deepEqual(matchStandardPath("/library/abc/read"), { kind: "libraryRead", slug: "abc" });
|
||
assert.equal(matchStandardPath("/library/a/b"), null);
|
||
assert.equal(matchStandardPath("/libraryx"), null);
|
||
});
|
||
it("parseEntityPath 兼容标准与全部模板", () => {
|
||
assert.deepEqual(parseEntityPath("/post/12"), { kind: "post", id: 12 });
|
||
assert.deepEqual(parseEntityPath("/thread-12.html"), { kind: "post", id: 12 });
|
||
assert.deepEqual(parseEntityPath("/post-12.html"), { kind: "post", id: 12 });
|
||
assert.deepEqual(parseEntityPath("/space-uid-5.html"), { kind: "user", id: 5 });
|
||
assert.deepEqual(parseEntityPath("/p/about"), { kind: "page", slug: "about" });
|
||
assert.deepEqual(parseEntityPath("/p-about.html"), { kind: "page", slug: "about" });
|
||
assert.deepEqual(parseEntityPath(`/page-${encodeURIComponent("使用指南")}.html`), {
|
||
kind: "page",
|
||
slug: "使用指南",
|
||
});
|
||
assert.deepEqual(parseEntityPath("/rank.html"), { kind: "leaderboard" });
|
||
assert.equal(parseEntityPath("/chat/5"), null);
|
||
assert.equal(parseEntityPath("/"), null);
|
||
});
|
||
it("parseEntityPath 兼容书库路径并解码 slug", () => {
|
||
const enc = encodeURIComponent("三体");
|
||
assert.deepEqual(parseEntityPath(`/library/${enc}`), { kind: "libraryDoc", slug: "三体" });
|
||
assert.deepEqual(parseEntityPath(`/library-${enc}.html`), { kind: "libraryDoc", slug: "三体" });
|
||
assert.deepEqual(parseEntityPath("/library.html"), { kind: "library" });
|
||
assert.deepEqual(parseEntityPath("/library/abc/read"), { kind: "libraryRead", slug: "abc" });
|
||
assert.deepEqual(parseEntityPath("/library-abc-read-2.html"), { kind: "libraryRead", slug: "abc" });
|
||
});
|
||
});
|