import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import test from 'node:test'; const root = path.resolve(import.meta.dirname, '../..'); const styles = fs.readFileSync(path.join(root, 'src/web/styles.css'), 'utf8'); const component = fs.readFileSync(path.join(root, 'src/web/components/ClientOverviewPage.jsx'), 'utf8'); const diagnostics = fs.readFileSync(path.join(root, 'src/web/components/ConnectivityDiagnosticsPanel.jsx'), 'utf8'); function rule(selector, source = styles) { const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); return new RegExp(`^${escaped}\\s*\\{([\\s\\S]*?)\\n\\}`, 'm').exec(source)?.[1] || ''; } test('desktop layout keeps the power control on a symmetric center axis', () => { const panel = rule('.client-panel'); const power = rule('.client-power-section'); const form = rule('.client-form'); assert.match(panel, /grid-template-columns:\s*minmax\(0, 1fr\) minmax\(240px, 280px\) minmax\(0, 1fr\)/); assert.match(power, /grid-column:\s*2/); assert.match(form, /grid-column:\s*3/); assert.match(form, /position:\s*static/); assert.match(form, /overflow:\s*visible/); assert.doesNotMatch(form, /\bleft\s*:|translateX|transition:[^;]*(?:left|width|transform)/); assert.match(styles, /\.client-panel\.has-subscription \.client-form \{[\s\S]*height:\s*var\(--client-work-height\)/); assert.match(rule('.client-form-content'), /align-content:\s*start/); assert.match(styles, /--client-power-top:\s*calc\(\(var\(--client-work-height\) - 96px\) \/ 2\)/); assert.match(styles, /\.client-panel\.has-subscription \{[\s\S]*transform:\s*translateY\(-9vh\)/); assert.match(styles, /\.client-panel\.has-subscription \.client-power-section \{[\s\S]*height:\s*var\(--client-work-height\);[\s\S]*padding-top:\s*var\(--client-power-top\)/); assert.match(styles, /\.client-panel\.has-subscription \.client-form-content \{[\s\S]*padding-top:\s*var\(--client-power-top\)/); assert.match(rule('.client-gateway-summary'), /grid-column:\s*1/); assert.match(rule('.client-gateway-summary'), /justify-self:\s*end/); assert.match(component, /client-panel\$\{showPower \? '' : ' is-setup'\}[\s\S]*is-gateway-home/); assert.match(component, /const showPower = isGateway \|\| \(hasSubscription && Boolean\(selectedServerId\)\)/); }); test('page reload plays one stable startup sequence', () => { assert.match(component, /const \[showIntro, setShowIntro\] = useState\(true\)/); assert.match(component, /\$\{showIntro \? ' is-intro' : ''\}/); assert.doesNotMatch(component, /showIntro && !hasSubscription/); assert.match(styles, /\.client-shell\.is-intro \.harbor-brand-content \{[\s\S]*harbor-startup-brand 760ms/); assert.match(styles, /@keyframes harbor-startup-brand[\s\S]*opacity: 0\.35;[\s\S]*filter: blur\(5px\)/); assert.doesNotMatch(/@keyframes harbor-startup-brand \{([\s\S]*?)\n\}/.exec(styles)?.[1] || '', /translate|scale/); assert.match(styles, /\.client-shell\.is-intro \.client-panel,[\s\S]*\.client-secondary-menu,[\s\S]*\.harbor-versions \{[\s\S]*harbor-startup-content 720ms 120ms/); assert.match(styles, /@keyframes harbor-startup-content[\s\S]*opacity: 0;[\s\S]*filter: blur\(6px\)/); assert.match(styles, /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.client-shell\.is-intro \.client-panel,[\s\S]*animation: none/); }); test('server rows scroll without moving the subscription column or showing a scrollbar', () => { const scroll = rule('.client-server-scroll'); const simpleScroll = rule('.client-server-mode-panel.is-simple .client-server-scroll'); const grid = rule('.client-server-grid'); assert.match(scroll, /overflow-y:\s*auto/); assert.match(scroll, /scrollbar-width:\s*none/); assert.match(styles, /\.client-server-scroll::-webkit-scrollbar\s*\{[\s\S]*display:\s*none/); assert.match(simpleScroll, /max-height:\s*none/); assert.match(simpleScroll, /overflow:\s*visible/); assert.match(grid, /width:\s*min\(100%, 220px\)/); assert.match(rule('.client-form-content'), /gap:\s*24px/); assert.match(rule('.client-server-toolbar-title'), /grid-column:\s*2/); assert.match(rule('.client-server-mode-toggle'), /grid-row:\s*2/); assert.match(rule('.client-server-check'), /grid-column:\s*3/); assert.doesNotMatch(rule('.client-servers.is-scalable .client-server-grid'), /width:/); }); test('tablet and mobile regions use normal flow with viewport-safe widths', () => { const responsive = /@media \(max-width: 920px\) \{([\s\S]*?)\n\}\n\n@media \(max-width: 560px\)/.exec(styles)?.[1] || ''; const mobile = /@media \(max-width: 560px\) \{([\s\S]*?)\n\}\n\n@media \(prefers-reduced-motion/.exec(styles)?.[1] || ''; assert.match(responsive, /grid-template-columns:\s*minmax\(0, 1fr\)/); assert.match(responsive, /\.client-power-section,[\s\S]*\.client-form,[\s\S]*grid-column:\s*1/); assert.match(responsive, /\.client-gateway-summary/); assert.match(responsive, /\.client-power-section \{[\s\S]*order:\s*1/); assert.match(responsive, /\.client-gateway-summary \{[\s\S]*order:\s*2/); assert.match(responsive, /width:\s*min\(100%, 380px\)/); assert.match(responsive, /max-height:\s*none/); assert.match(responsive, /overflow-y:\s*visible/); assert.match(responsive, /\.harbor-brand,[\s\S]*position:\s*absolute/); assert.match(responsive, /\.client-inline-error\.is-connection,[\s\S]*position:\s*static/); assert.match(rule('.client-duration-toggle'), /width:\s*min\(290px, 100%\)/); assert.match(mobile, /\.client-server-toolbar \{[\s\S]*grid-template-rows:\s*44px 44px/); assert.match(mobile, /\.client-form-content \{[\s\S]*gap:\s*24px/); assert.match(mobile, /\.client-server-check \{[\s\S]*width:\s*44px/); assert.match(styles, /\.client-instructions\s*\{[\s\S]*width:\s*min\(470px, 100vw\)/); assert.match(styles, /\.client-local-rules\s*\{[\s\S]*width:\s*min\(480px, 100vw\)/); assert.match(styles, /\.client-instructions\.client-diagnostics\s*\{[\s\S]*width:\s*min\(580px, 100vw\)/); assert.match(styles, /\.client-subscription-drawer\s*\{[\s\S]*width:\s*min\(480px, 100vw\)/); assert.match(styles, /\.client-confirmation-dialog\s*\{[\s\S]*width:\s*min\(430px, calc\(100vw - 48px\)\)/); for (const viewport of [320, 390, 768]) { const pagePadding = viewport <= 560 ? 28 : 48; const panelPadding = 12; const available = viewport - pagePadding - panelPadding; assert.ok(Math.min(380, available) <= viewport, `form fits ${viewport}px viewport`); assert.ok(Math.min(290, available) <= viewport, `duration control fits ${viewport}px viewport`); } const desktopOuterColumn = (1100 - 280) / 2; assert.ok(Math.min(360, desktopOuterColumn) <= desktopOuterColumn, 'details fit the 1440px desktop grid'); }); test('secondary menus share one right rail and both drawers open from the right', () => { const disabledRulesLabel = rule('.client-local-rules-toggle:disabled span'); const zIndex = (selector) => Number(/z-index:\s*(\d+)/.exec(rule(selector))?.[1]); assert.match(component, /