77 lines
5.3 KiB
JavaScript
77 lines
5.3 KiB
JavaScript
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 component = fs.readFileSync(path.join(root, 'src/web/components/ClientOverviewPage.jsx'), 'utf8');
|
||
const popup = fs.readFileSync(path.join(root, 'src/web/components/ConfirmationPopup.jsx'), 'utf8');
|
||
const styles = fs.readFileSync(path.join(root, 'src/web/styles.css'), 'utf8');
|
||
|
||
test('rule editor add latency stays constant and dirty exits are guarded', () => {
|
||
const rowRule = /\.client-local-rule \{([\s\S]*?)\n\}/.exec(styles)?.[1] || '';
|
||
assert.doesNotMatch(rowRule, /--rule-index|calc\(/);
|
||
assert.match(component, /addEventListener\('beforeunload', warnBeforeUnload\)/);
|
||
assert.match(component, /requestCloseLocalRules\(\)/);
|
||
assert.match(component, /localRulesPendingRestart/);
|
||
assert.match(component, /activeLocalRules/);
|
||
assert.match(component, /localRulesRevision/);
|
||
assert.match(component, /Не сохранено/);
|
||
assert.match(component, /if \(!runtimeActive\) return \['saved', 'Сохранено'\]/);
|
||
assert.match(component, /Ждёт перезапуска/);
|
||
assert.match(component, /Перезапустить VPN/);
|
||
assert.match(component, /const localRulesPendingRestart = connected && state\?\.route\?\.localRulesPendingRestart === true/);
|
||
assert.match(component, /if \(!connected \|\| !result\.state\.route\.localRulesPendingRestart\) setLocalRulesOpen\(false\)/);
|
||
assert.match(component, /client-deletable-row/);
|
||
assert.match(component, /className="client-delete-strike"[\s\S]*onAnimationEnd/);
|
||
assert.doesNotMatch(component, /client-rule-delete-cross/);
|
||
assert.match(component, /className="client-local-rule-delete"/);
|
||
assert.match(styles, /\.client-deletable-row\.is-removing > \.client-delete-strike[\s\S]*client-delete-strike/);
|
||
assert.match(styles, /\.client-delete-strike \{[\s\S]*z-index: 100[\s\S]*background: transparent/);
|
||
assert.match(styles, /\.client-deletable-row\.is-removing > :not\(\.client-delete-strike\)[\s\S]*z-index: 0/);
|
||
assert.match(styles, /clip-path: polygon\(0 24%, 100% 50%, 0 76%\)/);
|
||
assert.doesNotMatch(styles, /\.client-delete-strike::before/);
|
||
assert.doesNotMatch(styles, /client-local-rule > button:last-child/);
|
||
assert.match(styles, /@keyframes client-delete-strike[\s\S]*scaleX\(0\)[\s\S]*scaleX\(1\)/);
|
||
assert.match(styles, /53%[\s\S]*transform-origin: right center[\s\S]*100%[\s\S]*scaleX\(0\)/);
|
||
assert.match(styles, /@keyframes client-local-rule-leave[\s\S]*translateX\(28px\)/);
|
||
assert.match(styles, /@keyframes client-delete-content-dim[\s\S]*opacity: 0\.58[\s\S]*blur\(1\.4px\)[\s\S]*opacity: 0/);
|
||
assert.match(styles, /\.client-local-rule:has\(\.client-rule-type\.is-open\)[\s\S]*z-index: 5/);
|
||
});
|
||
|
||
test('critical confirmations share one accessible blocking popup', () => {
|
||
assert.match(component, /id="stop-connection"/);
|
||
assert.match(component, /id="discard-local-rules"/);
|
||
assert.match(component, /id="delete-subscription"/);
|
||
assert.doesNotMatch(component, /client-local-rules-discard|client-delete-confirmation/);
|
||
assert.match(popup, /role="alertdialog"/);
|
||
assert.match(popup, /aria-modal="true"/);
|
||
assert.match(popup, /querySelectorAll\(FOCUSABLE\)/);
|
||
assert.match(popup, /element\.inert = true/);
|
||
assert.match(popup, /requestAnimationFrame\(\(\) => cancelRef\.current\?\.focus\(\)\)/);
|
||
assert.match(styles, /\.client-confirmation-popup\.is-open[\s\S]*backdrop-filter: blur\(18px\)/);
|
||
});
|
||
|
||
test('power click and native Enter cannot stop VPN without a separate confirmation', () => {
|
||
assert.match(component, /className="client-power"[\s\S]*type="button"[\s\S]*onClick=\{toggleConnection\}/);
|
||
assert.match(component, /if \(action\?\.type === 'stop'\) \{[\s\S]*setConfirmingStop\(true\);[\s\S]*return;[\s\S]*\}/);
|
||
assert.doesNotMatch(component, /if \(action\?\.type === 'stop'\) return onStop\(\)/);
|
||
assert.match(component, /id="stop-connection"[\s\S]*cancelLabel="Оставить включённым"[\s\S]*confirmLabel="Отключить VPN"[\s\S]*onConfirm=\{stopConnection\}/);
|
||
});
|
||
|
||
test('copy feedback, drawers and Gateway access actions expose complete semantics', () => {
|
||
assert.match(component, /className="client-live-region" role="status" aria-live="polite" aria-atomic="true"/);
|
||
assert.match(component, /Не удалось скопировать/);
|
||
assert.match(component, /Скопировано/);
|
||
assert.doesNotMatch(component, />Error<|>Copied</);
|
||
assert.match(component, /aria-label="Закрыть инструкции"/);
|
||
assert.match(component, /aria-label="Закрыть локальные правила"/);
|
||
assert.match(component, /instructionsCloseRef\.current\?\.focus\(\)/);
|
||
assert.match(component, /localRulesCloseRef\.current\?\.focus\(\)/);
|
||
assert.match(component, /aria-label={`Скопировать \$\{label\}: \$\{kind === 'gateway' \? gatewayAddress : proxyUrls\[kind\]\}`}/);
|
||
assert.doesNotMatch(component, /client-access-tabs|role="tab"|role="tabpanel"/);
|
||
assert.match(styles, /\.client-drawer-close \{[\s\S]*width: 44px;[\s\S]*height: 44px/);
|
||
assert.match(styles, /@media \(max-width: 560px\)[\s\S]*\.client-copy-button \{[\s\S]*min-height: 44px/);
|
||
assert.match(styles, /@media \(max-width: 560px\)[\s\S]*\.client-local-rule-enabled,[\s\S]*\.client-local-rule-delete \{[\s\S]*width: 44px;[\s\S]*height: 44px/);
|
||
});
|