64 lines
4.8 KiB
JavaScript
64 lines
4.8 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 page = fs.readFileSync(path.join(root, 'src/web/components/ClientOverviewPage.tsx'), 'utf8');
|
|
const feature = fs.readFileSync(path.join(root, 'src/web/features/routing/RoutingFeature.tsx'), 'utf8');
|
|
const boundary = fs.readFileSync(path.join(root, 'src/web/features/routing/index.ts'), 'utf8');
|
|
|
|
test('routing feature is the sole owner at the four existing composition positions', () => {
|
|
assert.match(boundary, /RoutingDiscardDialog,[\s\S]*RoutingPanel,[\s\S]*RoutingPendingStatus,[\s\S]*RoutingToggle,[\s\S]*useRoutingFeature/);
|
|
assert.equal((page.match(/useRoutingFeature\(/g) || []).length, 1);
|
|
assert.equal((page.match(/<RoutingToggle/g) || []).length, 1);
|
|
assert.equal((page.match(/<RoutingPendingStatus/g) || []).length, 1);
|
|
assert.equal((page.match(/<RoutingPanel/g) || []).length, 1);
|
|
assert.equal((page.match(/<RoutingDiscardDialog/g) || []).length, 1);
|
|
assert.doesNotMatch(page, /ROUTE_RULE_OPTIONS|function RuleTypePicker|function LocalRulesPanel|localRulesBaselineRef|setLocalRulesDraft|id="discard-local-rules"|client-local-rules-toggle/);
|
|
assert.doesNotMatch(feature, /from ['"][^'"]*\/api\/|ConnectionPanel|SubscriptionFeature|ServerPicker|DevicesPanel|DiagnosticsPanel/);
|
|
});
|
|
|
|
test('routing controller preserves snapshot drafts, live status and guarded close/save semantics', () => {
|
|
assert.match(feature, /const savedRules = route\?\.localRules \|\| \[\]/);
|
|
assert.match(feature, /baselineRef\.current = localRulesSignature\(savedRules\)/);
|
|
assert.match(feature, /const nextRules = savedRules\.map\(createLocalRuleDraft\)/);
|
|
assert.match(feature, /setRevision\(route\?\.localRulesRevision \|\| 0\)/);
|
|
assert.match(feature, /const currentRules = cancelReorder\(false\)[\s\S]*localRulesSignature\(currentRules\) !== baselineRef\.current/);
|
|
assert.match(feature, /const result = routingSaveState\(await onSave\(values, revision\)\)/);
|
|
assert.match(feature, /if \(!result\) return;[\s\S]*baselineRef\.current = localRulesSignature\(values\);[\s\S]*setRevision\(result\.localRulesRevision\)/);
|
|
assert.match(feature, /if \(!connected \|\| !result\.localRulesPendingRestart\) setIsOpen\(false\)/);
|
|
assert.match(feature, /Number\.isSafeInteger\(localRulesRevision\)[\s\S]*localRulesRevision as number\) < 0[\s\S]*typeof localRulesPendingRestart !== 'boolean'/);
|
|
});
|
|
|
|
test('routing controller owns ordered outbound drafts, capability gating and drag cancellation', () => {
|
|
assert.match(feature, /map\(\(\{ type, value, enabled, outbound \}\) => \(\{ type, value, enabled, outbound \}\)\)/);
|
|
assert.match(feature, /route\?\.rulesContractVersion === ROUTE_RULES_CONTRACT_VERSION/);
|
|
assert.match(feature, /if \(!editable \|\| blocked\) return/);
|
|
assert.match(feature, /sameRule\(rule, savedRules\[index\]\)/);
|
|
assert.match(feature, /sameRule\(rule, activeRules\[index\]\)/);
|
|
assert.match(feature, /beginRuleReorder\(dragRef\.current, ruleKey, 'pointer'\)[\s\S]*setPointerCapture/);
|
|
assert.match(feature, /beginRuleReorder\(dragRef\.current, ruleKey, 'keyboard'\)/);
|
|
assert.match(feature, /event\.detail === 0\) toggleKeyboardReorder/);
|
|
assert.match(feature, /event\.key === 'Tab'[\s\S]*finishReorder\('focus-leave'\)/);
|
|
assert.match(feature, /onBlur=\{\(event\) => feature\.handleReorderBlur/);
|
|
assert.match(feature, /endRuleReorder\(session, 'unmount'\)\.stopAutoScroll/);
|
|
assert.match(feature, /onLostPointerCapture=\{feature\.losePointerReorder\}/);
|
|
assert.match(feature, /keyboardEvent\.preventDefault\(\);[\s\S]*cancelReorder\(\)/);
|
|
assert.match(feature, /stopAutoScroll\(session\)[\s\S]*Перемещение отменено/);
|
|
assert.match(feature, /const slotCenter = \(element: HTMLElement\) => listTop \+ element\.offsetTop \+ element\.offsetHeight \/ 2/);
|
|
assert.match(feature, /const centers = rulesRef\.current\.map[\s\S]*slotCenter\(ruleRow\)/);
|
|
});
|
|
|
|
test('routing lifecycle and Page orchestration keep the existing guards and blocking scopes', () => {
|
|
assert.match(feature, /keyboardEvent\.key !== 'Escape' \|\| keyboardEvent\.defaultPrevented/);
|
|
assert.match(feature, /addEventListener\('beforeunload', warnBeforeUnload\)/);
|
|
assert.match(feature, /matchMedia\('\(prefers-reduced-motion: reduce\)'\)[\s\S]*removing: true/);
|
|
assert.match(feature, /document\.startViewTransition\(update\)/);
|
|
assert.match(feature, /operationBlocked\(operations, 'routeRules'\) \|\| rules\.some/);
|
|
assert.match(page, /current === 'routing' && routingFeature\.dirty[\s\S]*routingFeature\.requestClose\(\)/);
|
|
assert.match(page, /routing:[\s\S]*show: routingFeature\.open,[\s\S]*close: routingFeature\.forceClose/);
|
|
assert.match(page, /routingSlot=\{<RoutingPendingStatus[\s\S]*blocked=\{connectionBlocked\}/);
|
|
assert.match(page, /<RoutingPanel[\s\S]*InlineError[\s\S]*InlineProgress/);
|
|
});
|