This commit is contained in:
Mateusz Gruszczyński committed 2026-10-21 09:38:21 +02:00
1 parent 3776edf4fc
commit 7cfc3a6da1
35 files changed
+1377 -875

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
// Run with: node tests/css_usage_audit.cjs
// Ensure the review archive stays unreferenced and critical live styles survive.
'use strict';
const fs = require('node:fs');
const path = require('node:path');
const assert = require('node:assert/strict');
const root = path.resolve(__dirname, '..');
const read = file => fs.readFileSync(path.join(root, file), 'utf8');
const active = read('src/admin/web/style.css');
const unused = read('src/admin/web/unused.css');
const docs = read('src/admin/web/docs.css');
const archived = [
'auth-ip-card', 'certificate-dialog', 'config-columns', 'overview-grid',
'overview-inline-hidden-toggle', 'overview-layout-body', 'overview-layout-filters',
'overview-layout-grid', 'overview-layout-hidden-list', 'overview-layout-section-heading',
'settings-grid', 'stream-dashboard-card', 'table-container', 'transport-grid', 'www-service-heading',
];
for (const name of archived) {
const cssClass = new RegExp(`\\.${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![-\\w])`);
assert.ok(!cssClass.test(active), `${name} must not remain in the active stylesheet`);
assert.ok(cssClass.test(unused), `${name} must be reviewable in unused.css`);
}
for (const name of ['vh-setting-section', 'dashboard-overview-grid', 'vh-override-fields',
'refresh-settings-grid', 'docker-backend-ip-settings', 'acl-profile-layout']) {
assert.ok(active.includes(`.${name}`), `active class ${name} was removed`);
}
for (const file of [
'src/admin/web/index.html', 'src/admin/web/login.html', 'src/admin/web/docs.html',
'src/admin/panel_assets.rs', 'src/admin/documentation.rs',
]) assert.ok(!read(file).includes('unused.css'), `${file} must not load or serve unused.css`);
assert.ok(!/@import[^;]*unused\.css/.test(active));
assert.match(unused, /NOT INCLUDED by the admin panel/);
assert.match(docs, /\.swagger-ui \.opblock/);
for (const style of ['square', 'subtle', 'rounded', 'extra']) {
assert.ok(active.includes(`html[data-corner-style=${style}]`));
assert.ok(docs.includes(`html[data-corner-style=${style}]`));
}
for (const name of ['app-startup-spin', 'app-startup-reveal']) {
assert.ok(active.includes(`@keyframes ${name}`));
assert.ok(!unused.includes(`@keyframes ${name}`));
}
assert.ok(!active.includes('--backdrop-soft'));
assert.ok(!active.includes('var(--panel-strong)'));
assert.ok(!active.includes('var(--border)'));
assert.match(read('CHANGELOG.md'), /CSS maintenance:/);
console.log('CSS used/unused segregation, asset isolation and corner-style regression: PASS');
+70
View File
@@ -0,0 +1,70 @@
// Run with: node tests/ux_0345.cjs
// Prevent editor grouping regressions without changing saved input names or IDs.
"use strict";
const fs = require("node:fs");
const path = require("node:path");
const assert = require("node:assert/strict");
const source = file => fs.readFileSync(path.resolve(__dirname, "..", file), "utf8");
const html = source("src/admin/web/index.html");
const streams = source("src/admin/web/streams.js");
const css = source("src/admin/web/style.css");
const php = html.split('data-web-service-page="php" hidden>')[1].split('data-web-service-page="security"')[0];
const delivery = html.split('data-web-service-page="delivery" hidden>')[1].split('data-web-service-page="php"')[0];
function assertGroups(page, groups) {
let prev = -1;
for (const [key, ids] of groups) {
const heading = page.indexOf(`data-i18n="${key}"`);
assert.ok(heading > prev, `section ${key} must exist in order`);
const nextHeading = groups[groups.findIndex(item => item[0] === key) + 1];
const end = nextHeading ? page.indexOf(`data-i18n="${nextHeading[0]}"`) : page.length;
for (const id of ids) {
const pos = page.indexOf(`id="${id}"`);
assert.ok(pos > heading && pos < end, `${id} must belong to ${key}`);
assert.equal(page.split(`id="${id}"`).length - 1, 1, `${id} must occur once`);
}
prev = heading;
}
}
assertGroups(delivery, [
["webServiceStaticDeliverySection", ["web-service-sendfile", "web-service-download-extensions"]],
["webServiceDynamicCompressionSection", ["web-service-compression", "web-service-compression-options", "web-service-compression-zstd", "web-service-compression-br", "web-service-compression-gzip", "web-service-compression-deflate"]],
["webServiceSidecarsSection", ["web-service-precompressed", "web-service-precompressed-br", "web-service-precompressed-gzip"]]
]);
assertGroups(php, [
["webServicePhpSourceSection", ["web-service-php-profile", "web-service-php-profiles", "web-service-php-enabled", "web-service-php-endpoint", "web-service-php-extensions"]],
["webServicePhpRequestSection", ["web-service-php-pass-authorization", "web-service-php-path-info"]],
["webServicePhpLimitsSection", ["web-service-php-connect-timeout", "web-service-php-request-timeout", "web-service-php-concurrency", "web-service-php-body-limit", "web-service-php-header-limit", "web-service-php-stderr-limit"]],
["webServicePhpOverridesSection", ["web-service-php-custom-params", "web-service-php-values", "web-service-php-admin-values", "web-service-php-help"]]
]);
for (const name of ["redirect", "notfound"]) {
const form = html.split(`id="${name}-form"`)[1].split("</form>")[0];
const identity = form.indexOf('data-i18n="hostIdentitySection"');
const cert = form.indexOf(`id="${name}-certificate"`);
const listener = form.indexOf(`id="${name}-listeners"`);
assert.ok(identity >= 0 && cert > identity && listener > cert, `${name}: separate identity and frontend certificate/listeners`);
assert.equal(form.split(`id="${name}-certificate"`).length - 1, 1);
}
for (const needle of [
'backendSection("streamBackendConnectionSection")',
'backendSection("streamBackendHealthSection")',
'healthMode.addEventListener("change", syncProbeFields)',
'probeFields.hidden = inactive;',
'input.disabled = inactive;',
'field(probeFields, "backend-health-timeout"',
'field(connectionFields, "backend-address"',
'get(row, "backend-health-mode").value',
'get(row, "backend-address").value.trim()'
]) assert.ok(streams.includes(needle), `Streams behavior must retain: ${needle}`);
assert.match(css, /\.vh-setting-section\s*\{[^}]*border-radius:\s*var\(--rp-radius-surface/);
assert.match(css, /\.stream-health-probe-fields\[hidden\][\s\S]*?display:\s*none !important/);
for (const locale of ["en_US", "pl_pl"]) {
const pack = JSON.parse(source(`langs/${locale}.json`)).translations;
for (const key of ["webServiceStaticDeliverySection", "webServiceDynamicCompressionSection", "webServiceSidecarsSection", "webServicePhpSourceSection", "webServicePhpRequestSection", "webServicePhpLimitsSection", "webServicePhpOverridesSection", "hostIdentitySection", "streamBackendConnectionSection", "streamBackendHealthSection"]) {
assert.ok(pack[key]?.trim(), `${locale} missing ${key}`);
}
}
assert.match(source("Cargo.toml"), /version = "0\.34\.5"/);
assert.equal(JSON.parse(source("src/admin/openapi.json")).info.version, "0.34.5");
assert.match(source("CHANGELOG.md"), /## 0\.34\.5 — Administration UX improvements/);
console.log("0.34.5 UI layout, inactive probe controls, localization and version: PASS");
+87
View File
@@ -0,0 +1,87 @@
// 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');
+59
View File
@@ -0,0 +1,59 @@
// Run with: node tests/vhost_ui_layout.cjs
// Regression guard for purely presentational Vhost grouping and corner settings.
"use strict";
const fs = require("node:fs");
const path = require("node:path");
const assert = require("node:assert/strict");
const root = path.resolve(__dirname, "..");
const source = file => fs.readFileSync(path.join(root, file), "utf8");
const core = source("src/admin/web/vhosts/core.js");
const editor = source("src/admin/web/vhosts/editor.js");
const rates = source("src/admin/web/vhosts/rates.js");
const docker = source("src/admin/web/docker.js");
const css = source("src/admin/web/style.css");
const keys = [
"vhGeneralDetailsSection", "vhWebsocketOptionsSection", "vhBalancerAlgorithmSection",
"vhBalancerRetrySection", "vhBalancerKeepaliveSection", "vhTrafficMethodsSection",
"vhTrafficRateSection", "vhTrafficCountrySection", "vhTrafficCountryRatesSection",
"vhSecurityHstsSection", "vhSecurityHeadersSection", "vhUaBlockSection",
"vhLocationIpAclSection", "vhSslSettings",
];
for (const locale of ["en_US", "pl_pl"]) {
const pack = JSON.parse(source(`langs/${locale}.json`));
for (const key of keys) {
assert.ok(pack.translations[key]?.trim(), `${locale}: ${key} must be translated`);
const occurrences = source(`langs/${locale}.json`).split("\n").filter(line => line.trimStart().startsWith(`"${key}":`));
assert.equal(occurrences.length, 1, `${locale}: ${key} must occur exactly once`);
}
}
assert.match(core, /const subsection = \(parent, key\) =>/);
assert.match(core, /"vh-wide vh-setting-section"/);
assert.match(core, /vhSslSettings: "ssl"/);
assert.match(core, /subsection\(accessBody, "vhAclProfile"\)/);
assert.match(core, /subsection\(accessBody, "vhTrafficMethodsSection"\)/);
assert.match(core, /subsection\(accessBody, "vhTrafficCountrySection"\)/);
assert.match(core, /subsection\(accessBody, "vhLocationIpAclSection"\)/);
for (const key of keys.filter(k => k !== "vhLocationIpAclSection" && k !== "vhSslSettings")) {
if (key === "vhTrafficCountryRatesSection" || key === "vhGeneralDetailsSection" || key.startsWith("vhBalancer") || key.startsWith("vhSecurity") || key === "vhUaBlockSection" || key.includes("Traffic") || key === "vhWebsocketOptionsSection") {
assert.ok(editor.includes(`subsection(g, "${key}")`), `missing editor subgroup ${key}`);
}
}
assert.match(editor, /g = group\("vhSsl"\);[\s\S]*?g = group\("vhSslSettings"\)/);
assert.match(editor, /"allow-certificate-mismatch"/);
assert.match(editor, /"tls-policy-custom"/);
assert.match(editor, /"ua-block-enabled"/);
assert.match(editor, /"ua-block-patterns"/);
assert.match(rates, /showHeading = true/);
assert.match(editor, /userAgentRateEditor\(uaRateFields, rate\.user_agent_limits \|\| \[\], false\)/);
assert.match(docker, /backendIpSettings\.append\(labeled\("dockerBackendIp", target\), certificateBar\)/);
assert.match(css, /\.docker-backend-ip-settings\s*\{[^}]*gap:\s*12px;/);
for (const selector of [".vh-setting-section", ".cache-memory-sizing", ".appearance-chart-preview", ".draft-dock"]) {
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const block = css.match(new RegExp(escaped + "\\s*\\{([^}]+)\\}"));
assert.ok(block, `missing CSS: ${selector}`);
assert.match(block[1], /border-radius:\s*var\(--rp-radius-surface/, `${selector} must use system corner setting`);
}
assert.match(css, /\.vh-form \.vh-grid>\.vh-check:not\(\.vh-enable-control\)/);
assert.match(css, /\.form-actions \.check/);
assert.match(css, /\.docker-service-certificate-bar>\.check\s*\{[^}]*margin:\s*0\s*!important/);
console.log("Vhost UI grouping, certificate controls, checkbox alignment and corner preset: PASS");