Files
jiang13-bbs/frontend/lib/widgetLint.test.ts
freefire ff2ab286fb feat: 书库导入导出/图片变体/书籍搜索/小组件运行时等
新增:
- 书库导入导出(library_import/library_export)及测试
- 图片变体生成(image_variants)与响应式图片(responsiveImage)
- 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件
- 小组件运行时(widgetRuntime)与静态检查(widgetLint)
- 上传缓存中间件(upload_cache)与字体 CSS 提取脚本

其它:
- 后端 handlers/services 全量调整
- 前端页面、组件、库函数与配置更新
2026-10-01 03:06:05 +08:00

219 lines
7.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { lintWidgetCode, type WidgetLintIssue } from "./widgetLint.ts";
const codes = (issues: WidgetLintIssue[]) => issues.map((i) => i.code);
describe("lintWidgetCode - JS 全局查找", () => {
it("document 上的六种查找 API 全部报 error", () => {
const js = [
'document.getElementById("a");',
'document.querySelector(".a");',
'document.querySelectorAll(".a");',
'document.getElementsByClassName("a");',
'document.getElementsByTagName("div");',
'document.getElementsByName("a");',
].join("\n");
const issues = lintWidgetCode({ js });
assert.equal(issues.length, 6);
assert.ok(issues.every((i) => i.level === "error" && i.code === "no-global-dom-lookup"));
assert.deepEqual(
issues.map((i) => i.line),
[1, 2, 3, 4, 5, 6]
);
});
it("window.document 与 ownerDocument 上的同款查找同样命中", () => {
const issues = lintWidgetCode({
js: 'window.document.querySelector(".x");\nroot.ownerDocument.getElementById("y");',
});
assert.equal(issues.length, 2);
assert.ok(issues.every((i) => i.code === "no-global-dom-lookup"));
});
it("从 root 出发的查找不报错", () => {
const issues = lintWidgetCode({
js: [
'root.querySelector(".a");',
'root.querySelectorAll(".a");',
'root.getElementById("a");',
'el.getElementsByClassName("a");',
].join("\n"),
});
assert.equal(issues.length, 0);
});
it("注释、字符串、正则字面量中的关键词不误报", () => {
const js = `
// document.getElementById("x")
/* document.querySelector(".y") */
const s1 = "document.getElementById('z')";
const s2 = 'document.querySelector(".z")';
const re = /document\\.getElementById/;
const tpl = \`document.getElementsByName("t")\`;
root.querySelector(".real");
`;
const issues = lintWidgetCode({ js });
assert.deepEqual(codes(issues), []);
});
it("行列号按原文定位", () => {
const js = "\n\n document.getElementById('x');";
const [issue] = lintWidgetCode({ js });
assert.equal(issue.line, 3);
assert.equal(issue.column, 3);
});
});
describe("lintWidgetCode - setInterval", () => {
it("只有 setInterval、全文无 clearInterval 时报 warn", () => {
const issues = lintWidgetCode({
js: "const t = setInterval(fn, 100);",
});
assert.equal(issues.length, 1);
assert.equal(issues[0].level, "warn");
assert.equal(issues[0].code, "warn-set-interval");
assert.equal(issues[0].line, 1);
});
it("setInterval 与 clearInterval 配对出现时不报", () => {
assert.deepEqual(
lintWidgetCode({ js: "const t = setInterval(fn, 100);\nclearInterval(t);" }),
[]
);
});
it("safeInterval 封装(cleanup 内清理)不报", () => {
const js = [
"function safeInterval(fn, ms) {",
" var id = setInterval(fn, ms);",
" registerCleanup(function () { clearInterval(id); });",
" return id;",
"}",
"safeInterval(render, 1000);",
].join("\n");
assert.deepEqual(lintWidgetCode({ js }), []);
});
it("clearInterval 只写在注释或字符串里不算配对,仍报 warn", () => {
const issues = lintWidgetCode({
js: 'const t = setInterval(f, 100);\n// clearInterval(t);\nconst s = "clearInterval(t)";',
});
assert.equal(issues.length, 1);
assert.equal(issues[0].code, "warn-set-interval");
assert.equal(issues[0].line, 1);
});
it("window.setInterval 同样命中;注释里的 setInterval 不报", () => {
const issues = lintWidgetCode({
js: "window.setInterval(f, 50);\n// setInterval(f, 50);",
});
assert.equal(issues.length, 1);
assert.equal(issues[0].code, "warn-set-interval");
});
it("多个无清理的 setInterval 逐处提醒", () => {
const issues = lintWidgetCode({
js: "setInterval(a, 10);\nsetInterval(b, 20);",
});
assert.deepEqual(
issues.map((i) => i.line),
[1, 2]
);
});
});
describe("lintWidgetCode - CSS position:fixed", () => {
it("position: fixed 报 error,兼容空格与 !important", () => {
const css = ".a { position: fixed; }\n.b { position:fixed !important; }\n.c { position: absolute; }";
const issues = lintWidgetCode({ css });
assert.equal(issues.length, 2);
assert.ok(issues.every((i) => i.code === "no-fixed-position" && i.level === "error"));
assert.deepEqual(
issues.map((i) => i.line),
[1, 2]
);
});
it("@media 内层规则同样检查", () => {
const issues = lintWidgetCode({ css: "@media (min-width: 100px) { .m { position: fixed; } }" });
assert.equal(issues.length, 1);
assert.equal(issues[0].code, "no-fixed-position");
});
it("注释与 content 字符串不造成误报", () => {
const css = `/* position: fixed */\n.a::after { content: "position: fixed"; color: red; }`;
assert.deepEqual(codes(lintWidgetCode({ css })), []);
});
});
describe("lintWidgetCode - CSS 全局选择器", () => {
it("body / html / * 开头的规则报 error", () => {
const issues = lintWidgetCode({
css: "body { margin: 0; }\nhtml.dark .a { color: #000; }\n* { box-sizing: border-box; }",
});
assert.equal(issues.length, 3);
assert.ok(issues.every((i) => i.code === "no-global-selector" && i.level === "error"));
});
it("选择器列表逐个判定,.body 类名与后代通配不误伤", () => {
const issues = lintWidgetCode({
css: ".body, body .card, .a *, html, *.x {}",
});
// 命中:body .card、html、*.x;.body 与 .a * 不命中
assert.equal(issues.length, 3);
});
it("@media 内层选择器同样检查", () => {
const issues = lintWidgetCode({ css: "@media (min-width: 1px) { body { color: red; } }" });
assert.equal(issues.length, 1);
assert.equal(issues[0].code, "no-global-selector");
});
});
describe("lintWidgetCode - transform: scale", () => {
it("transform 含 scale() 报 warn,支持厂商前缀与嵌套", () => {
const issues = lintWidgetCode({
css: ".a { transform: scale(1.2); }\n@media (min-width: 1px) { .b { -webkit-transform: scale(0.8); } }\n.c { transform: translateX(2px); }",
});
assert.equal(issues.length, 2);
assert.ok(issues.every((i) => i.code === "warn-scale-transform" && i.level === "warn"));
});
it("@keyframes 里的 scale 不报", () => {
const css = "@keyframes spin { from { transform: scale(0.9); } to { transform: scale(1); } }";
assert.deepEqual(codes(lintWidgetCode({ css })), []);
});
});
describe("lintWidgetCode - 综合", () => {
it("空输入与干净代码返回空列表", () => {
assert.deepEqual(lintWidgetCode({}), []);
assert.deepEqual(
lintWidgetCode({
html: '<div class="box"></div>',
css: ".box { color: red; transform: rotate(45deg); }",
js: 'root.querySelector(".box").textContent = "hi";',
}),
[]
);
});
it("css 问题排在 js 问题之前,error 与 warn 各自分级", () => {
const issues = lintWidgetCode({
css: "body { position: fixed; }\n.a { transform: scale(2); }",
js: "setInterval(f, 10);",
});
assert.deepEqual(codes(issues), [
"no-global-selector",
"no-fixed-position",
"warn-scale-transform",
"warn-set-interval",
]);
assert.deepEqual(
issues.map((i) => i.level),
["error", "error", "warn", "warn"]
);
});
});