const assert = require("node:assert/strict"); const fs = require("node:fs"); const path = require("node:path"); const root = path.join(__dirname, ".."); const read = relative => fs.readFileSync(path.join(root, relative), "utf8"); const preferences = read("src/admin/web/app/preferences.js"); const icons = read("src/admin/web/app/icons.js"); const core = read("src/admin/web/app/core.js"); const html = read("src/admin/web/index.html"); const css = read("src/admin/web/css/10-base.css"); const overviewCss = read("src/admin/web/css/30-overview.css"); const integrations = read("src/admin/web/integrations.js"); const metricsPartial = read("src/admin/web/partials/overview-metrics.html"); const pl = JSON.parse(read("langs/pl_pl.json")); const openapi = JSON.parse(read("src/admin/openapi.json")); assert.match(html, /id="ui-interface-icons"[^>]*type="checkbox"/); assert.match(preferences, /interface_icons:\s*false/); assert.match(preferences, /applyAdminInterfaceIcons\(value\.interface_icons\)/); assert.match(preferences, /saveAppearancePreference\(\{ interface_icons: next \}/); assert.match(preferences, /interface:\s*\{[^}]*interface_icons:\s*false/); assert.match(core, /rustProxyIcons\?\.refresh\?\.\(\)/); assert.match(icons, /dataset\.interfaceIcons !== "true"/); assert.match(icons, /aria-hidden", "true"/); assert.match(icons, /data\.panel|dataset\.panel/); assert.match(icons, /MutationObserver/); assert.match(icons, /if \(target\.dataset\.httpSettingsTab\) return "network"/); assert.match(icons, /#web-service-preset-apply.*return "wand"/); assert.match(icons, /vhost-quick-toggle\.is-enable.*return "toggleOn"/); assert.match(icons, /vhost-quick-toggle\.is-disable.*return "toggleOff"/); assert.equal((html.match(/class="outline about-repository" data-ui-icon-name="none"/g) || []).length, 4); assert.match(metricsPartial, /backend-latency-stat[\s\S]*backend-health-origins[\s\S]*backend-dashboard-latency-detail/); assert.equal(JSON.parse(read("langs/en_US.json")).translations.backendWidgetProbeCount, "Health Origins"); assert.match(icons, /#tunnels-open.*return null/); assert.match(icons, /#logs-menu > summary/); assert.match(integrations, /listener-h2-settings-link.*uiIconName = "settings"/); assert.match(integrations, /listener-h3-settings-link.*uiIconName = "settings"/); assert.match(metricsPartial, /stat-corner-badge[^>]+data-ui-icon-name="none"[^>]+id="tunnels-open"/); assert.match(overviewCss, /\.stat-corner-badge/); assert.equal(pl.translations.interfaceIconsHelp, "Dodaje semantyczne ikony SVG do nawigacji, akcji, zakładek i okien dialogowych."); assert.match(css, /html\[data-interface-icons="true"\]/); assert.equal(openapi.components.schemas.AdminPreferences.properties.interface_icons.default, false); assert.equal(openapi.components.schemas.AdminPreferences.properties.interface_icons.type, "boolean"); for (const icon of ["plus", "save", "check", "trash", "edit", "refresh", "settings", "search", "key", "certificate", "shield", "network", "database", "wand", "grid", "activity", "book", "toggleOn", "toggleOff"]) { assert.ok(new RegExp(`\\b${icon}:`).test(icons), `missing semantic SVG icon definition: ${icon}`); } // 0.36.2 semantic review: verbs/explicit ids must win over colliding keywords. const explicitIcons = { "overview-layout-unlock": "unlock", "overview-inline-save": "lock", "overview-layout-open": "layers", "docker-settings-open": "settings", "login-submit": "login", "revoke-sessions": "logout", "about-open": "info" }; for (const [id, icon] of Object.entries(explicitIcons)) assert.match(icons, new RegExp(`"${id}": "${icon}"`), `${id} must use ${icon}`); assert.match(icons, /\(\^\|\[\^p\]\)reset/, "preset buttons must not match the reset rule"); assert.match(icons, /UI_GLYPH_LABEL/); assert.match(icons, /raw === key/, "English labels equal to their key must still count as translated"); for (const icon of ["unlock", "layers", "expand", "collapse", "ban", "eraser", "hash", "info", "plug", "transfer", "mapPin", "compare"]) { assert.ok(new RegExp(`\\b${icon}:`).test(icons), `missing semantic SVG icon definition: ${icon}`); } const logo = read("src/admin/web/app/logo-animation.js"); const router = read("src/admin/router.rs"); const shellCss = read("src/admin/web/css/20-shell.css"); assert.match(html, /id="ui-logo-animation"[^>]*type="checkbox"/); assert.match(router, /web\/app\/logo-animation\.js/); assert.match(preferences, /logo_animation:\s*false/); assert.match(preferences, /applyAdminLogoAnimation\(value\.logo_animation\)/); assert.match(preferences, /saveAppearancePreference\(\{ logo_animation: next \}/); assert.match(logo, /prefers-reduced-motion: reduce/); assert.match(logo, /document\.hidden/); assert.match(shellCss, /prefers-reduced-motion: reduce[\s\S]*data-logo-effect/); assert.equal(openapi.components.schemas.AdminPreferences.properties.logo_animation.default, false); assert.ok(pl.translations.logoAnimation && pl.translations.logoAnimationHelp); console.log("Panel SVG icon preference contract: OK");