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.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]*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\)[\s\S]*transition:\s*none/); });