Improve failover controls and preserve manual switching state
Build and Deploy Gateway / build-and-push (push) Failing after 1s
Build and Deploy Gateway / deploy (push) Has been skipped

This commit is contained in:
2026-08-28 19:40:53 +03:00
parent 8f2f418569
commit a84cca0668
30 changed files with 547 additions and 112 deletions
+30 -6
View File
@@ -46,12 +46,32 @@ test('failover settings use Harbor switches, a two-column channel table and plai
assert.doesNotMatch(feature, /<details|<summary|Нет данных|текущий канал вне настроенной пары/);
assert.doesNotMatch(page, /Текущий канал вне резервной пары/);
assert.match(page, /Резерв применится после перезапуска VPN/);
assert.match(feature, /Приостановить автоматическое переключение[\s\S]*Проверить оба канала[\s\S]*Переключить новые подключения на/);
assert.match(feature, /beforeunload/);
assert.match(feature, /client-failover-header-actions[\s\S]*Автоматика[\s\S]*checked=\{!snapshot\.paused\}[\s\S]*Включить автоматическое переключение[\s\S]*Остановить автоматическое переключение[\s\S]*className="client-failover-save"[\s\S]*type="submit"[\s\S]*form="client-failover-form"[\s\S]*Сохранить/);
assert.match(feature, /onChange=\{\(automatic\) => void onPause\(!automatic\)\}/);
assert.match(feature, /checked=\{!snapshot\.paused\}[\s\S]*disabled=\{blocked \|\| !snapshot.enabled\}/);
assert.match(feature, /client-failover-check-action[\s\S]*disabled=\{blocked \|\| checking \|\| !snapshot.enabled \|\| snapshot.activation !== 'active'\}/);
assert.doesNotMatch(feature, /client-failover-actions|client-failover-global-actions|client-failover-discard/);
assert.match(feature, /channel === 'primary' && <div className="client-failover-channel-actions"[\s\S]*client-failover-check-action client-tooltip-anchor[\s\S]*aria-label="Проверить оба канала"[\s\S]*aria-busy=\{checking\}[\s\S]*client-failover-direction-action client-tooltip-anchor[\s\S]*aria-label=\{switchRole \? `Переключить на \$\{switchRole === 'primary' \? 'основной' : 'резервный'\} канал`/);
assert.match(feature, /<Tooltip>Проверить оба канала<\/Tooltip>[\s\S]*<Tooltip>\{switchRole \? `Переключить на/);
assert.match(feature, /M20 11a8 8 0 1 0-2\.3 6\.7M20 5v6h-6/);
assert.match(feature, /M4 12h15M14 7l5 5-5 5/);
assert.match(feature, /normalizeFailoverDraft = \(policy: FailoverPolicy\)[\s\S]*minimumReserveMs: Math\.round\(normalized\.minimumReserveMs \/ 60_000\) \* 60_000/);
assert.match(feature, /before="Оставаться на резервном канале не меньше" after="минут" min=\{1\} max=\{1440\} step=\{1\}[\s\S]*value=\{draft\.minimumReserveMs \/ 60_000\}[\s\S]*minimumReserveMs: Math\.round\(value\) \* 60_000/);
assert.doesNotMatch(feature, /className="client-failover-number-input"[\s\S]*max=\{max\}[\s\S]*step=\{step\}/);
assert.match(feature, /<ConfirmationDialog[\s\S]*id="discard-failover"[\s\S]*Закрыть без сохранения\?[\s\S]*Несохранённые настройки резервного канала будут потеряны\.[\s\S]*Остаться[\s\S]*Закрыть без сохранения/);
assert.match(feature, /discardContinuationFrameRef[\s\S]*cancelAnimationFrame[\s\S]*requestAnimationFrame[\s\S]*continueNavigation[\s\S]*feature\.close\(\)/);
assert.match(page, /if \(!failoverFeature\.beforeCloseRef\.current\(\)\)[\s\S]*pendingTargetRef\.current = \(\) => switchDrawer\(target\)[\s\S]*pendingTargetRef\.current = null/);
assert.match(styles, /\.client-failover-check-action\.is-running svg \{ animation: client-spin 900ms linear infinite; \}/);
assert.match(styles, /\.client-failover-direction-action svg \{[^}]*transition: transform 260ms cubic-bezier\(0\.16, 1, 0\.3, 1\)/);
assert.match(styles, /\.client-failover-direction-action svg\.is-primary \{ transform: rotate\(180deg\); \}/);
assert.match(styles, /@media \(prefers-reduced-motion: reduce\)[\s\S]*client-failover-direction-action svg[\s\S]*transition: none[\s\S]*client-failover-check-action\.is-running svg[\s\S]*animation: none/);
assert.match(styles, /\.client-failover-switch\[aria-checked='true'\][\s\S]*translateX\(22px\)/);
assert.match(styles, /\.client-failover-header \{[^}]*position:\s*sticky[^}]*top:\s*0[^}]*background:/);
assert.match(styles, /\.client-failover-heading \{[^}]*justify-content:\s*space-between[^}]*}/);
assert.doesNotMatch(styles, /\.client-failover-heading \{[^}]*padding-right/);
assert.match(styles, /\.client-failover-channels \{[^}]*grid-template-columns:\s*repeat\(2, minmax\(0, 1fr\)\)/);
assert.match(styles, /\.client-failover-channels \{[^}]*grid-template-columns:\s*minmax\(0, 1fr\) 52px minmax\(0, 1fr\)/);
assert.match(styles, /\.client-failover-channel-actions \{[^}]*grid-column:\s*2[^}]*grid-row:\s*1/);
assert.match(styles, /\.client-failover-check-action, \.client-failover-direction-action \{[^}]*width:\s*44px[^}]*height:\s*44px/);
assert.match(styles, /@media \(max-width: 560px\)[\s\S]*\.client-failover-channels \{ grid-template-columns:\s*minmax\(0, 1fr\)/);
assert.match(styles, /\.client-failover-picker-list[\s\S]*opacity:\s*0[\s\S]*\.client-failover-picker\.is-open[\s\S]*opacity:\s*1/);
assert.match(styles, /failover-service-out 300ms[\s\S]*@media \(prefers-reduced-motion: reduce\)/);
@@ -67,13 +87,17 @@ test('failover settings use Harbor switches, a two-column channel table and plai
assert.doesNotMatch(styles, /client-failover-advanced|failover-advanced-in|failover-advanced-out/);
});
test('runtime status keeps fixed geometry and explains active traffic without motion dependence', () => {
test('runtime status sits beside the channel controls and explains active traffic without motion dependence', () => {
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(feature, /client-failover-direction-action[\s\S]*disabled=\{blocked \|\| !snapshot.enabled \|\| snapshot.activation !== 'active' \|\| !switchRole\}/);
assert.match(feature, /client-failover-channels[\s\S]*client-failover-runtime[\s\S]*Что проверять/);
assert.match(feature, /Сейчас используется[\s\S]*Основной канал[\s\S]*Резервный канал[\s\S]*Новые подключения:/);
assert.match(styles, /\.client-failover-runtime\.is-primary strong \{[^}]*var\(--harbor-connect\)/);
assert.match(styles, /\.client-failover-runtime\.is-reserve strong \{[^}]*var\(--harbor-gateway\)/);
assert.doesNotMatch(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/);
});