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]*открывает эти адреса через оба канала/); 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.doesNotMatch(feature, /client-failover-number-steps|уменьшить на|увеличить на|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/); });