Files
RustProxy/tools/checks/appearance_ux_0323_ui_test.mjs
T
2026-10-13 21:29:57 +02:00

97 lines
6.5 KiB
JavaScript

// Regression contract: Appearance list is badge-free, corners are account-scoped,
// and Settings opens Appearance/last active tab unless a valid URL tab wins.
import assert from 'node:assert/strict';
import fs from 'node:fs';
import vm from 'node:vm';
const prefs = fs.readFileSync('src/admin/web/app/preferences.js', 'utf8');
const settings = fs.readFileSync('src/admin/web/settings.js', 'utf8');
const css = fs.readFileSync('src/admin/web/style.css', 'utf8');
const html = fs.readFileSync('src/admin/web/index.html', 'utf8');
const login = fs.readFileSync('src/admin/web/login.js', 'utf8');
const rust = fs.readFileSync('src/admin/preferences.rs', 'utf8');
const tests = fs.readFileSync('src/admin/preferences_tests.rs', 'utf8');
const schema = JSON.parse(fs.readFileSync('src/admin/openapi.json', 'utf8'));
const en = JSON.parse(fs.readFileSync('langs/en_US.json', 'utf8')).translations;
const pl = JSON.parse(fs.readFileSync('langs/pl_pl.json', 'utf8')).translations;
const styles = ['default', 'square', 'subtle', 'rounded', 'extra'];
const appearance = prefs.slice(prefs.indexOf('function renderDashboardWidgetSettings()'), prefs.indexOf('function refreshChartStylePalette'));
assert(appearance.includes('dashboard-widget-setting-actions'));
assert(!appearance.includes('dashboardWidgetTypeBadge('), 'Appearance rows must not render type badges');
assert(prefs.includes('function dashboardWidgetTypeBadge('), 'visual editors retain type labels');
assert(html.includes('id="ui-corner-style"'));
assert(html.includes('id="dashboard-widget-list"'));
const contract = schema.components.schemas.AdminPreferences.properties.corner_style;
assert.deepEqual(contract.enum, styles);
assert.equal(contract.default, 'default');
assert(rust.includes('default_admin_corner_style'));
assert(rust.includes('"default" | "square" | "subtle" | "rounded" | "extra"'));
assert(tests.includes('fn corner_style_defaults_and_rejects_unknown_presets()'));
for (const key of ['cornerStyle', 'cornerStyleHelp', 'cornerStyleDefault', 'cornerStyleSquare', 'cornerStyleSubtle', 'cornerStyleRounded', 'cornerStyleExtra']) {
assert(en[key] && pl[key], `missing translation ${key}`);
}
assert(!/html:is\(\[data-theme="ultra_dark"\], \[data-theme="cream"\]\) :is\(\.card, \.stat, dialog\) \{/.test(css), 'theme may not override corner geometry');
assert(css.includes('--logo-accent: #f69b74;'));
assert(css.includes('--logo-secondary: #cfe0ac;'));
assert(css.includes('var(--logo-accent, var(--accent))'));
assert(login.includes('rp-corner-style'), 'cached corners must work on login too');
const decl = prefs.match(/^const ADMIN_CORNER_STYLES = .*;$/m)?.[0];
const normalize = prefs.match(/^function normalizeAdminCornerStyle\(value\) \{[^\n]*\}$/m)?.[0];
const body = prefs.split('function applyAdminCornerStyle(value, cache = true) {')[1]?.split('function applyAdminTableDensity(')[0];
assert(decl && normalize && body);
const storage = new Map();
const root = { dataset: {} }, input = { value: '' };
const ctx = { document: { documentElement: root }, $: id => id === 'ui-corner-style' ? input : null,
localStorage: { setItem: (key, value) => storage.set(key, value) } };
vm.createContext(ctx);
vm.runInContext(`${decl}\n${normalize}\nfunction applyAdminCornerStyle(value, cache = true) {${body}`, ctx);
for (const style of styles) {
assert.equal(vm.runInContext(`applyAdminCornerStyle('${style}')`, ctx), style);
assert.equal(root.dataset.cornerStyle, style);
assert.equal(storage.get('rp-corner-style'), style);
assert.equal(input.value, style);
}
vm.runInContext("applyAdminCornerStyle('invalid')", ctx);
assert.equal(root.dataset.cornerStyle, 'default');
assert(prefs.includes('corner_style: "default"'), 'old account defaults preserve original geometry');
assert(prefs.includes('corner_style: next'), 'choice persisted via API');
assert(prefs.includes('applyAdminCornerStyle(previous)'), 'failed update rolls back');
const setup = settings.slice(settings.indexOf(' const buttons ='), settings.indexOf(' const policyTabs ='));
assert(setup.length > 1000);
function boot(path, cached, query = '') {
const cache = new Map(cached ? [['rp-settings-last-tab', cached]] : []);
const names = ['appearance', 'listeners', 'cache', 'security', 'manual-config', 'logging'];
const buttons = names.map(name => ({ dataset: { settingsTab: name }, hidden: false,
textContent: name, addEventListener() {}, setAttribute() {}, focus() {} }));
const pages = names.map(name => ({ dataset: { settingsPage: name }, hidden: true }));
const url = new URL(`https://dashboard.example${path}${query}`);
const location = { href: url.href, pathname: url.pathname, search: url.search, hash: '', protocol: url.protocol };
const context = { location, URL, URLSearchParams, CSS: { escape: value => value },
localStorage: { getItem: key => cache.get(key) || null, setItem: (key, value) => cache.set(key, value) },
document: { querySelectorAll: selector => selector === '[data-settings-tab]' ? buttons : pages,
querySelector: () => null }, $: () => null,
history: { state: {}, replaceState(_state, _title, href) { location.href = 'https://dashboard.example' + href; } },
window: { rustProxyRefresh: { setSettingsTab() {} } }, t: key => key,
};
const vmctx = vm.createContext(context);
const result = vm.runInContext(`(() => { ${setup}\nreturn { current: () => active, select }; })()`, vmctx);
return { ...result, cache };
}
assert.equal(boot('/settings').current(), 'appearance', 'first Settings visit opens Appearance');
assert.equal(boot('/settings', 'cache').current(), 'cache', 'last Settings tab is restored');
assert.equal(boot('/settings', 'unknown').current(), 'appearance', 'invalid cached tab is ignored');
assert.equal(boot('/settings', 'cache', '?tab=security').current(), 'security', 'URL tab takes precedence');
assert.equal(boot('/configuration', 'cache').current(), 'manual-config', 'legacy /configuration keeps intent');
assert.equal(boot('/logging', 'cache').current(), 'logging', 'legacy /logging keeps intent');
const active = boot('/settings');
active.select('cache');
assert.equal(active.cache.get('rp-settings-last-tab'), 'cache');
active.select('security');
assert.equal(active.cache.get('rp-settings-last-tab'), 'security');
active.select('appearance', false, false);
assert.equal(active.cache.get('rp-settings-last-tab'), 'security', 'temporary selection does not override remembered tab');
console.log('PASS: badge-free Appearance, 5 global corner presets, logo colors, account/API defaults, and last Settings tab navigation');