Files
RustProxy/tests/ux_0345_followup.cjs
T
2026-10-21 09:38:21 +02:00

88 lines
5.7 KiB
JavaScript

// Run with: node tests/ux_0345_followup.cjs
// Regression guard for the second 0.34.5 UX pass (without needing Chromium).
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const source = file => fs.readFileSync(path.resolve(__dirname, '..', file), 'utf8');
const editor = source('src/admin/web/vhosts/editor.js');
const html = source('src/admin/web/index.html');
const css = source('src/admin/web/style.css');
const docsJs = source('src/admin/web/docs.js');
const docsCss = source('src/admin/web/docs.css');
const settings = source('src/admin/web/settings.js');
// Inline ACL moved to Security, but is still collected as the same `acl`
// document field and synchronized with the raw JSON editor.
const traffic = editor.indexOf('g = group("vhTrafficPolicy")');
const profile = editor.indexOf('subsection(g, "vhAclProfile")', traffic);
const inline = editor.indexOf('subsection(g, "vhIpAccess")', traffic);
const methods = editor.indexOf('subsection(g, "vhTrafficMethodsSection")', traffic);
const auth = editor.indexOf('g = group("vhTinyauth")', traffic);
assert.ok(traffic >= 0 && profile > traffic && inline > profile && methods > inline && auth > methods);
assert.match(editor.slice(inline, methods), /aclControl = aclEditor\(inlineAclFields, value\.acl/);
assert.match(editor.slice(inline, methods), /read\("acl"\)\.value = JSON\.stringify/);
assert.ok(!editor.slice(auth, editor.indexOf('g = group("vhResponsePolicy")', auth)).includes('aclEditor('));
assert.match(editor, /acl, headers,/);
assert.match(source('src/admin/web/vhosts/core.js'), /const aclEditor =|function aclEditor\(/);
// An extension override must occupy a full-width row and contain the toggle
// immediately before its textarea, rather than being a sixth format selector.
const transform = editor.slice(editor.indexOf('g = group("vhTransform")'), editor.indexOf('g = group("vhHeaders")'));
assert.match(transform, /"vh-wide vh-override-fields"/);
assert.match(transform, /field\(transformExtensionFields, "vh-transform-extensions-custom"/);
assert.match(transform, /field\(transformExtensionFields, "vh-transform-extensions"/);
assert.match(transform, /transformExtensions\.disabled = !customTransformExtensions\.checked/);
const cache = editor.slice(editor.indexOf('g = group("vhCache")'), editor.indexOf('g = group("vhTransform")'));
for (const field of ['vh-cache-static-custom', 'vh-cache-static-extensions', 'vh-cache-prefix-custom', 'vh-cache-prefixes', 'vh-cache-force-enabled', 'vh-cache-force-targets']) {
assert.ok(cache.includes(field), `Vhost cache option ${field} remains present`);
}
assert.match(cache, /bypassPrefixes\.disabled = !customCachePrefixes\.checked/);
assert.match(css, /\.vh-override-fields\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\)/);
// The web service keeps its original checkbox IDs and serialized fields.
const preset = html.slice(html.indexOf('data-i18n="webServicePresetTitle"'), html.indexOf('id="web-service-id"'));
assert.match(preset, /id="web-service-preset"/);
assert.match(preset, /id="web-service-preset-apply"/);
assert.match(preset, /class="vh-wide web-service-preset-toggles"/);
assert.ok(preset.indexOf('id="web-service-preset-apply"') < preset.indexOf('id="web-service-hardening-attacks"'));
assert.ok(preset.indexOf('id="web-service-hardening-attacks"') < preset.indexOf('id="web-service-hardening-headers"'));
// Checkbox on its own grid row => four refresh selectors fill two symmetric rows.
const refresh = html.slice(html.indexOf('id="refresh-preferences"'), html.indexOf('id="appearance-reset"'));
assert.match(refresh, /class="appearance-settings-grid refresh-settings-grid"/);
for (const id of ['refresh-live-enabled', 'refresh-overview-seconds', 'refresh-traffic-seconds', 'refresh-cache-seconds', 'refresh-system-info-seconds']) {
assert.equal(refresh.split(`id="${id}"`).length - 1, 1);
}
assert.match(css, /\.refresh-settings-grid > \.check\s*\{[^}]*grid-column:\s*1 \/ -1/);
// Remove only presentation of the process group, not the stat source or API.
const system = html.slice(html.indexOf('id="settings-page-system-info"'), html.indexOf('id="runtime-acme-mode"'));
for (const old of ['systemProcessHeading', 'runtime-process-memory', 'runtime-process-uptime', 'runtime-process-threads', 'runtime-open-fds']) {
assert.ok(!system.includes(old), `process card must not show ${old}`);
}
assert.match(system, /id="runtime-statistics-source"/);
assert.match(settings, /\["runtime-statistics-source", runtime\.statistics_source\]/);
assert.ok(!settings.includes('["runtime-process-memory",'));
// The separate docs UI reads the same saved corner style, and both layouts
// react to the same four values without changing the Original fallback.
assert.match(docsJs, /localStorage\.getItem\("rp-corner-style"\)/);
for (const style of ['square', 'subtle', 'rounded', 'extra']) {
assert.ok(docsCss.includes(`html[data-corner-style=${style}]`));
assert.ok(css.includes(`html[data-corner-style=${style}]`));
}
assert.match(docsCss, /\.reader-sidebar,\s*\.markdown-reader,/);
assert.match(docsCss, /\.swagger-ui select,[\s\S]*?border-radius: var\(--rp-docs-radius-control/);
assert.match(css, /\.metric-info > summary\s*\{[^}]*var\(--rp-radius-control/);
assert.match(css, /#panel-overview \.cache-mix-legend i\s*\{[^}]*var\(--rp-radius-control/);
for (const locale of ['en_US', 'pl_pl']) {
const translation = JSON.parse(source(`langs/${locale}.json`)).translations;
assert.ok(translation.vhInlineAclHelp?.length > 30);
assert.ok(translation.vhTinyauth && !translation.vhTinyauth.includes('IP'));
}
assert.match(source('CHANGELOG.md'), /## 0\.34\.5 — Administration UX improvements/);
assert.match(source('Cargo.toml'), /version = "0\.34\.5"/);
console.log('0.34.5 UX follow-up layout, source IDs, access scopes and corner preferences: PASS');