Lumi/scripts/verify-webui.js
2026-06-15 23:58:24 +02:00

124 lines
4.3 KiB
JavaScript

const assert = require("assert");
const fs = require("fs");
const path = require("path");
const ejs = require("ejs");
const root = path.join(__dirname, "..");
function listFiles(directory, extension, output = []) {
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
const target = path.join(directory, entry.name);
if (entry.isDirectory()) listFiles(target, extension, output);
else if (target.endsWith(extension)) output.push(target);
}
return output;
}
function verifyViews() {
const viewRoots = [path.join(root, "src", "web", "views"), path.join(root, "plugins")];
const files = viewRoots.flatMap((directory) => listFiles(directory, ".ejs"));
for (const file of files) {
ejs.compile(fs.readFileSync(file, "utf8"), { filename: file });
}
return files.length;
}
function verifyThemeService() {
const sandbox = fs.mkdtempSync(path.join(root, ".tmp-lumi-theme-test-"));
const serviceDir = path.join(sandbox, "src", "services");
let database = null;
fs.mkdirSync(serviceDir, { recursive: true });
for (const file of ["config.js", "db.js", "settings.js", "themes.js"]) {
fs.copyFileSync(
path.join(root, "src", "services", file),
path.join(serviceDir, file)
);
}
try {
database = require(path.join(serviceDir, "db.js"));
database.migrate();
require(path.join(serviceDir, "settings.js")).ensureDefaults();
const themes = require(path.join(serviceDir, "themes.js"));
assert.strictEqual(themes.BUILTIN_THEMES.length, 6);
for (const theme of themes.BUILTIN_THEMES) {
assert.deepStrictEqual(themes.validateThemeValues(theme), []);
}
const library = themes.listThemes();
assert(library.every((theme) => theme.builtin));
assert.throws(
() => themes.saveCustomTheme("builtin:lumi-default", library[0]),
/read-only/
);
const copy = themes.duplicateTheme("builtin:midnight", "Verification Theme");
assert.strictEqual(copy.builtin, false);
themes.setActiveTheme(copy.id);
assert.strictEqual(themes.getActiveTheme().id, copy.id);
const invalid = JSON.parse(JSON.stringify(copy));
invalid.light.text = "not-a-color";
assert.throws(() => themes.saveCustomTheme(copy.id, invalid), /hex color/);
const renamed = themes.renameCustomTheme(copy.id, "Verified Theme");
assert.strictEqual(renamed.name, "Verified Theme");
const repaired = themes.normalizeThemeValues({
...renamed,
light: { ...renamed.light, text: "#ffffff", surface: "#ffffff" }
});
assert.notStrictEqual(repaired.light.text, repaired.light.surface);
const themeView = path.join(root, "src", "web", "views", "admin-theme.ejs");
const rendered = ejs.render(fs.readFileSync(themeView, "utf8"), {
title: "Theming",
siteTitle: "Lumi Bot",
assetVersion: "verify",
theme: renamed,
activeTheme: renamed,
themes: themes.listThemes(),
editingTheme: renamed,
botAvatar: null,
navSections: [],
user: { username: "Admin" },
userAvatar: null,
userInitial: "A",
platformLogins: [],
flash: null,
softError: null
}, { filename: themeView });
assert(rendered.includes("data-theme-editor"));
assert(rendered.includes("Built-in · read-only"));
const statusView = path.join(root, "plugins", "moderation", "views", "status.ejs");
const statusRendered = ejs.render(fs.readFileSync(statusView, "utf8"), {
title: "Access restricted",
assetVersion: "verify",
theme: renamed,
sanction: {
action_type: "timeout",
status: "active",
created_at: Date.now(),
expires_at: Date.now() + 60000,
reason_short: "Verification",
reason_detail: "Standalone themed view verification.",
created_by_name: "Admin"
}
}, { filename: statusView });
assert(statusRendered.includes("/lumi-components.css"));
assert(statusRendered.includes('data-theme-id="custom:'));
themes.deleteCustomTheme(copy.id);
assert.strictEqual(themes.getActiveTheme().id, themes.DEFAULT_THEME_ID);
} finally {
database?.db.close();
fs.rmSync(sandbox, { recursive: true, force: true });
}
}
const viewCount = verifyViews();
verifyThemeService();
console.log(`WebUI verification passed: ${viewCount} EJS views and theme CRUD.`);