182 lines
6.3 KiB
JavaScript
182 lines
6.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);
|
|
assert.strictEqual(copy.typography.bodyFont, "lumi");
|
|
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/);
|
|
invalid.light.text = copy.light.text;
|
|
invalid.typography.bodyFont = "external-css";
|
|
assert.throws(() => themes.saveCustomTheme(copy.id, invalid), /font preset/);
|
|
|
|
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,
|
|
editingBaseTheme: themes.getThemeById(renamed.baseThemeId),
|
|
fontStacks: themes.FONT_STACKS,
|
|
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("data-theme-font"));
|
|
assert(rendered.includes("data-theme-popout"));
|
|
assert(rendered.includes("data-lumi-state-button"));
|
|
assert(rendered.includes("Built-in · read-only"));
|
|
|
|
const homeView = path.join(root, "src", "web", "views", "home.ejs");
|
|
const homeRendered = ejs.render(fs.readFileSync(homeView, "utf8"), {
|
|
title: "Home",
|
|
siteTitle: "Lumi Bot",
|
|
assetVersion: "verify",
|
|
theme: renamed,
|
|
botAvatar: null,
|
|
navSections: [],
|
|
user: { username: "Admin" },
|
|
userAvatar: null,
|
|
userInitial: "A",
|
|
platformLogins: [],
|
|
flash: null,
|
|
softError: null,
|
|
homepageLinks: [{
|
|
label: "Twitch",
|
|
description: "Watch live",
|
|
url: "https://example.com",
|
|
fallback_icon: "T"
|
|
}],
|
|
homepageHero: {
|
|
type: "static_image",
|
|
title: "Featured",
|
|
description: "Featured content",
|
|
image_url: "https://example.com/hero.png",
|
|
source_url: "https://example.com"
|
|
}
|
|
}, { filename: homeView });
|
|
assert(homeRendered.includes("homepage-link-button"));
|
|
assert(homeRendered.includes("homepage-dynamic-hero"));
|
|
|
|
const loginView = path.join(root, "src", "web", "views", "localhost-login.ejs");
|
|
const loginRendered = ejs.render(fs.readFileSync(loginView, "utf8"), {
|
|
title: "Localhost Login",
|
|
username: "admin",
|
|
siteTitle: "Lumi Bot",
|
|
assetVersion: "verify",
|
|
theme: renamed,
|
|
botAvatar: null,
|
|
navSections: [],
|
|
user: null,
|
|
userAvatar: null,
|
|
userInitial: "",
|
|
platformLogins: [{ id: "localhost", label: "Localhost Login", configured: true, loginPath: "/auth/localhost" }],
|
|
flash: null,
|
|
softError: null
|
|
}, { filename: loginView });
|
|
assert(loginRendered.includes("admin / admin"));
|
|
|
|
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.`);
|