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 page = fs.readFileSync(path.join(root, 'src/web/components/ClientOverviewPage.tsx'), 'utf8'); const feature = fs.readFileSync(path.join(root, 'src/web/features/failover/FailoverFeature.tsx'), 'utf8'); const styles = fs.readFileSync(path.join(root, 'src/web/styles/features/failover.css'), 'utf8'); test('reserve is a Gateway-only drawer immediately after subscriptions', () => { assert.match(page, / { assert.match(feature, /client-failover-heading[\s\S]*

Резервный канал<\/h2>[\s\S]*\{channel === 'primary' \? 'Основной' : 'Резервный'}/); assert.match(feature, /snapshot\.reason === 'checking-channels'[\s\S]*Проверяем \{checkingRole === 'primary' \? 'основной' : 'резервный'} канал/); assert.match(feature, /ArrowDown[\s\S]*Home[\s\S]*End[\s\S]*Escape/); assert.match(feature, /role="listbox"[\s\S]*role="option"/); assert.match(feature, /Что проверять[\s\S]*Harbor проверяет эти сайты через оба канала, чтобы понять, какой канал работает/); assert.match(feature, /before="Ждать ответа не дольше"[\s\S]*min=\{2}[\s\S]*max=\{30}/); assert.match(feature, /Проверять оба канала каждые[\s\S]*Считать основной канал недоступным, если он не отвечает[\s\S]*Вернуться на основной канал после/); assert.match(feature, /Дождаться паузы в передаче данных[\s\S]*Переключить после[\s\S]*без передачи данных[\s\S]*Считать передачу активной при скорости выше/); assert.match(feature, /Когда переключаться на резервный канал[\s\S]*основной канал не отвечает, а резервный канал работает[\s\S]*Повторные сбои основного канала[\s\S]*оставляет новые подключения на резервном канале/); assert.doesNotMatch(feature, /Когда включать резервный|Harbor включит резервный,|через резервный\./); assert.match(feature, /client-failover-number-input[\s\S]*Math\.max\(2, String\(value\)\.length \+ 0\.6\)/); assert.match(feature, /client-failover-number-control[\s\S]*client-failover-number-input[\s\S]*client-failover-number-steps[\s\S]*уменьшить на \$\{step\}[\s\S]*>−<\/button>[\s\S]*увеличить на \$\{step\}[\s\S]*>\+<\/button>/); assert.match(feature, /before="Ждать ответа не дольше"[\s\S]*step=\{1\}/); assert.doesNotMatch(feature, /advancedOpen|advancedClosing|toggleAdvanced|aria-controls="client-failover-advanced-options"/); assert.match(feature, /label="Добавить проверку"/); assert.match(feature, /Новый HTTPS-сервис…/); assert.match(feature, /Убрать проверку:/); assert.match(feature, /client-failover-remove-lid/); assert.match(feature, /finishRemoveCheck/); assert.match(feature, /startViewTransition/); assert.match(feature, /onInput=\{pulseNumber\}/); assert.doesNotMatch(feature, / { assert.match(feature, /Ждём паузу в передаче данных/); assert.match(feature, /Сейчас передаётся [\s\S]*Активных подключений:[\s\S]*transmittingConnections/); assert.match(feature, /activity\?\.blockers\.slice\(0, 2\)\.map/); assert.match(feature, /Ещё \{activity\.blockers\.length - 2\}/); assert.match(feature, /Проверяем основной канал ·[\s\S]*Проверим каналы снова через/); assert.match(feature, /switchRole && snapshot\.enabled && snapshot\.activation === 'active'/); assert.match(styles, /\.client-failover-runtime \{[^}]*min-height:\s*132px/); assert.match(styles, /@media \(prefers-reduced-motion: reduce\)[^{]*\{[^}]*\.client-failover-number-control:hover \.client-failover-number-steps[^}]*transition:\s*none/); });