初始化奇妙小屏幕控制器项目

This commit is contained in:
2026-09-08 22:56:52 +08:00
commit 8d368de3b5
491 changed files with 67678 additions and 0 deletions
@@ -0,0 +1,115 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import path from "node:path";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const staticDir = path.join(root, "app", "static");
async function source(relativePath) {
return readFile(path.join(staticDir, relativePath), "utf8");
}
test("unavailable controls never masquerade as indefinite loading", async () => {
const [style, index, interaction, app] = await Promise.all([
source("style.css"),
source("index.html"),
source("interaction-feedback.js"),
source("app.js"),
]);
assert.doesNotMatch(style, /cursor\s*:\s*wait/);
assert.doesNotMatch(index, /<button[^>]*\sdisabled(?:\s|>|=)/i);
assert.doesNotMatch(index, /<input[^>]*type="checkbox"[^>]*\sdisabled(?:\s|>|=)/i);
assert.match(index, /id="control-unavailable-tooltip"[^>]*role="tooltip"/);
assert.match(index, /id="control-feedback-toast"[^>]*role="status"[^>]*aria-live="polite"/);
assert.match(index, /请先新建或打开一个动图,再把当前画板添加为帧。/);
assert.match(interaction, /setAttribute\("aria-disabled", "true"\)/);
assert.match(interaction, /setAttribute\("aria-busy", "true"\)/);
assert.match(interaction, /data-unavailable-reason/);
assert.match(app, /import "\.\/interaction-feedback\.js"/);
});
test("action controls do not bypass the shared state module", async () => {
const files = [
"app.js",
"color-picker.js",
"scene-board.js",
"views/animations.js",
"views/device.js",
"views/settings.js",
"views/templates.js",
"views/text.js",
];
const directAssignments = [];
for (const relativePath of files) {
const text = await source(relativePath);
text.split(/\r?\n/).forEach((line, index) => {
if (/\.disabled\s*=/.test(line)
&& !/fields\.disabled\s*=/.test(line)
&& !/calibrationReference\.disabled\s*=/.test(line)) {
directAssignments.push(`${relativePath}:${index + 1}:${line.trim()}`);
}
});
}
assert.deepEqual(directAssignments, []);
});
test("HTTP requests time out with a stable user-facing error", async () => {
const originalWindow = globalThis.window;
const originalFetch = globalThis.fetch;
globalThis.window = { setTimeout, clearTimeout };
globalThis.fetch = (_path, options) => new Promise((_resolve, reject) => {
options.signal.addEventListener("abort", () => reject(new DOMException("aborted", "AbortError")), { once: true });
});
try {
const { apiJson, DEFAULT_API_TIMEOUT_MS } = await import("../app/static/core.js");
assert.equal(DEFAULT_API_TIMEOUT_MS, 15000);
await assert.rejects(
() => apiJson("/never-returns", { timeoutMs: 5 }),
(error) => error.code === "REQUEST_TIMEOUT"
&& error.message === "请求超时,请检查核桃派网络或服务状态后重试",
);
} finally {
globalThis.window = originalWindow;
globalThis.fetch = originalFetch;
}
});
test("demo library actions stay black, explain restrictions, and preserve copy", async () => {
const [templates, style] = await Promise.all([
source("views/templates.js"),
source("style.css"),
]);
assert.match(templates, /演示案例不得编辑,请复制/);
assert.match(templates, /include_demo=true/);
assert.match(templates, /demo_order/);
assert.match(templates, /api\/animations\/\$\{animation\.id\}\/copy/);
assert.match(templates, /restrictDemoAction\(edit\)/);
assert.match(templates, /restrictDemoAction\(rename\)/);
assert.match(templates, /restrictDemoAction\(remove\)/);
assert.doesNotMatch(templates, /restrictDemoAction\(copy\)/);
assert.match(style, /\.demo-restricted-action\[aria-disabled="true"\][\s\S]*background:\s*#050505/);
});
test("sortable cards protect controls and keep demo cards fixed", async () => {
const [sortable, animations, templates, style] = await Promise.all([
source("sortable-card.js"),
source("views/animations.js"),
source("views/templates.js"),
source("style.css"),
]);
assert.match(sortable, /DRAG_START_GUARD_PX = 8/);
assert.match(sortable, /button, input, select, textarea/);
assert.match(sortable, /pointerdown/);
assert.doesNotMatch(sortable, /pointerdown[\s\S]{0,500}preventDefault\(\)/);
assert.match(animations, /protectCardDragStart\(card\)/);
assert.match(templates, /protectCardDragStart\(card, \{ locked: true \}\)/);
assert.match(templates, /template-card-order-actions/);
assert.match(templates, /libraryDragType/);
assert.match(style, /\.library-demo-fixed/);
assert.match(style, /\.library-sortable-card\.library-drop-target/);
});