97 lines
6.5 KiB
JavaScript
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');
|