Track outbound device traffic deltas
Build and Deploy Gateway / build-and-push (push) Successful in 22s
Build and Deploy Gateway / deploy (push) Successful in 7s

This commit is contained in:
2026-08-13 14:12:12 +03:00
parent 0290784526
commit 0ea2f9d548
12 changed files with 69 additions and 49 deletions
+5 -2
View File
@@ -114,7 +114,9 @@ test('Gateway device inventory uses the existing accessible responsive drawer',
assert.match(chart, /hovered\.proxy - hovered\.directIpv4 > 0n && hovered\.directIpv4 > 0n && <>[\s\S]*через sing-box[\s\S]*мимо sing-box · IPv4/);
assert.match(chart, /client-device-traffic-legend[\s\S]*valueKey === 'gateway'[\s\S]*>VPN<\/span>[\s\S]*valueKey === 'proxy'[\s\S]*>Direct<\/span>[\s\S]*valueKey === 'unknown'[\s\S]*>Другое<\/span>/);
assert.doesNotMatch(chart, /Direct ≈|>\?<\/span>/);
assert.match(panel, /positiveByteDelta\(previous\.gateway, gateway\)[\s\S]*positiveByteDelta\(previous\.proxy, proxy\)/);
assert.match(panel, /positiveByteDelta\(previous\.gateway, gateway\)[\s\S]*positiveByteDelta\(previous\.proxy, proxy\)[\s\S]*positiveByteDelta\(previous\.vpn, vpn\)[\s\S]*positiveByteDelta\(previous\.direct, direct\)[\s\S]*positiveByteDelta\(previous\.unknown, unknown\)/);
assert.match(panel, /trafficView === 'outbound' \? trafficDelta\.outboundTotal : trafficDelta\.total/);
assert.match(panel, /<b>VPN<\/b><TrafficValue[^>]*delta=\{trafficDelta\.vpn\}[\s\S]*<b>Direct<\/b><TrafficValue[^>]*delta=\{trafficDelta\.direct\}[\s\S]*<b>Другое<\/b><TrafficValue[^>]*delta=\{trafficDelta\.unknown\}/);
assert.match(panel, /setTimeout\(\(\) => setTrafficDeltas\(\{\}\), TRAFFIC_DELTA_MS\)/);
assert.doesNotMatch(panel, /client-device-traffic client-tooltip-anchor|<Tooltip>\{trafficLabel\}<\/Tooltip>/);
assert.match(panel, /source\?\.traffic\?\.proxy\?\.error/);
@@ -200,10 +202,11 @@ test('Gateway device inventory uses the existing accessible responsive drawer',
assert.match(styles, /\.client-device-traffic-plot svg \{[\s\S]*height: 100%;[\s\S]*overflow: hidden/);
assert.match(styles, /\.client-devices-sort:hover \.client-devices-sort-icon[\s\S]*rotate\(360deg\)/);
assert.match(styles, /\.client-devices-refresh-ring circle[\s\S]*client-devices-refresh-progress 15s/);
assert.match(panel, /client-devices-reset-wrap client-tooltip-anchor[\s\S]*aria-label="Сбросить вход и выход всех устройств"[\s\S]*<Tooltip>Сбросить вход и выход всех устройств<\/Tooltip>/);
assert.match(panel, /client-devices-reset-wrap client-tooltip-anchor[\s\S]*aria-label="Обнулить трафик устройств"[\s\S]*<Tooltip>Обнулить трафик устройств<\/Tooltip>/);
assert.doesNotMatch(panel, /className="client-devices-reset"[\s\S]{0,320}<span>/);
assert.match(styles, /\.client-devices-kicker \{[^}]*flex-wrap: nowrap/);
assert.match(styles, /\.client-devices-reset-wrap \{[^}]*margin-left: auto/);
assert.match(styles, /\.client-devices-reset-wrap > \.client-tooltip \{[^}]*white-space: nowrap/);
assert.match(styles, /\.client-devices-reset \{[\s\S]*oklch\(0\.68 0\.15 28\)[\s\S]*\.client-devices-reset:hover:not\(:disabled\) svg[\s\S]*rotate\(-360deg\)/);
assert.match(styles, /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.client-device-policy svg[\s\S]*\.client-device-alias-trigger[\s\S]*\.client-device-identity-details[\s\S]*\.client-device-identity-copy[\s\S]*\.client-device-traffic-value > span[\s\S]*\.client-device-traffic-breakdown[\s\S]*\.client-device-traffic-lines[\s\S]*\.client-text-morph-value[\s\S]*transition: none;[\s\S]*animation: none/);
assert.match(styles, /\.client-drawer \{[\s\S]*z-index: 50;[\s\S]*box-shadow:/);
+1
View File
@@ -29,6 +29,7 @@ test('server picker has one public feature owner without legacy shims', () => {
assert.match(overview, /const selectedServerId = desiredProfile\?\.desiredServerId \|\| ''/);
assert.match(overview, /function selectServer\(profile: ProfileSnapshot, serverId: string\)[\s\S]*if \(connected && !gatewayDirect\)[\s\S]*onApply\(profile\.id, serverId\)[\s\S]*onSelectProfileServer\(profile\.id, serverId\)/);
assert.match(overview, /selectedServerId=\{pickerState\.selectedServerId\}/);
assert.doesNotMatch([picker, overview, styles].join('\n'), /client-server-prompt|prompt=\{|prompt: boolean/);
});
const fixtures = (count) => Array.from({ length: count }, (_, index) => ({
+15 -15
View File
@@ -37,26 +37,26 @@ const expectedImports = [
const sha256 = (value) => crypto.createHash('sha256').update(value).digest('hex');
const acceptedLedger = {
counts: {
cascadeEdges: 823,
cascadeEdges: 822,
customProperties: 103,
declarations: 3306,
declarations: 3299,
important: 0,
keyframes: 56,
media: 13,
rules: 950,
variableReferences: 812,
rules: 949,
variableReferences: 808,
},
hashes: {
cascadeEdges: '52014b4f05da82ed3e1404ac7e81e2e7dd0960431828eb22fcdcd99ea4009683',
cascadeEdges: '03bede7036ed12ad1ccb5ee41188834934f8d9acd2d02296c5945dfc195bac71',
customProperties: 'ef70fb1d9b61b177f1939f811babe1116bee631de26f5eac0aa0d0009ca5a1fb',
declarations: '568631ecf7ea61652c5fbb5f6d947e8a08fff1a179a72fb2dd6439e0e9cac950',
declarations: 'fc35daf13ffc5a0754f4e6f4982a5ff0c09c8664dfd86fd144e12c02fb006794',
duplicateKeyframes: '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945',
duplicateSelectors: '257eff4727dab9ce25f4e1f9320e89bf2270eb29feae921b96601f103ad7f036',
duplicateSelectors: '0fa66e370695261a2a5c6a189752c07d518d80d68d3e79a3ba4bb34407893c0e',
keyframes: 'd4378751f36eccc87923c12540315462055032a5d34978a0f45ecada0a5cc1ce',
ruleDeclarationSequences: 'a53ac1a62e6c88fd923971802e9ec5238c8d5d285b6fbecce1d54bbd38d91e2c',
selectors: '190b90a75c4dfedc9ea66e37046fbceba70b0adbbdf89872db8f83ce93119114',
variableReferences: 'd3a740a583156df0b42ce0f52687617a5b5507b394251070c63d355560921f03',
witnesses: 'ae24f63221c956797fcd18706d3f318f15d75ffded0297fdbeb81487e6c7875e',
ruleDeclarationSequences: 'af072317748091c47aaf33de5d124249367ca37e5037dd58eab869ca0fc26130',
selectors: '84116a78a2c71322a4e5ea45a7cdec788ca1d0d02c46767eb1c642b7e0755754',
variableReferences: '664c093241c4076ca05a9dda06ae88d4e7024c926c4636109790201df8212df4',
witnesses: 'b31a875d6be7980f6bb8eb46dac2d9665f6fe9afaa7775a4f994a0156109734d',
},
};
@@ -209,7 +209,7 @@ test('client typography uses the shared semantic scale outside the token owner',
test('accepted stylesheet has pinned declaration, selector, keyframe, variable, and cascade ledgers', () => {
const witnesses = readStyleWitnesses(root);
assert.equal(witnesses.length, 808);
assert.equal(witnesses.length, 805);
assert.equal(witnesses.filter((witness) => witness.unknown || witness.ancestorUnknown).length, 0);
const ledger = createStyleLedger(readStyleSource(root), { witnesses });
assert.deepEqual(ledger.counts, acceptedLedger.counts);
@@ -405,8 +405,8 @@ test('main owns one public stylesheet and the regrouped production CSS is determ
assert.equal((main.match(/import ['"][^'"]+\.css['"]/g) || []).length, 1);
const assets = fs.readdirSync(path.join(root, 'dist/assets')).filter((file) => file.endsWith('.css'));
assert.deepEqual(assets, ['index-B8MBAMBM.css']);
assert.deepEqual(assets, ['index-9maKdwRg.css']);
const built = fs.readFileSync(path.join(root, 'dist/assets', assets[0]));
assert.equal(built.byteLength, 127884);
assert.equal(sha256(built), 'ec9b242f2a01e75970ec6dee43a037764835c9032a1ea6cfa97df3c7735f76ba');
assert.equal(built.byteLength, 127604);
assert.equal(sha256(built), '0ba84e80e387ee62c33379b3674b1849ba65857cf45dac1257f4d87605008d04');
});
@@ -108,6 +108,8 @@ test('profiles render as flat accordion groups with scoped controls', () => {
assert.match(feature, /feature\.profiles\.map\(\(profile\) => <ProfileGroup/);
assert.match(feature, /aria-expanded=\{expanded\}/);
assert.match(feature, /profile\.subscription\.status === 'stale'[\s\S]*profile\.subscription\.fetchedAt/);
assert.match(feature, /\{!localStatus && renderServerPicker\(profile/);
assert.doesNotMatch(feature, /Выберите сервер этой подписки|client-profile-server-hint/);
assert.match(feature, /feature\.operations\.profileRefresh\?\.target === profile\.id/);
assert.match(feature, /\{visibleServer && <button[\s\S]*client-profile-selected-server[\s\S]*aria-expanded=\{expanded\}/);
assert.match(feature, /anchorServerId: visibleServer\?\.id \|\| ''/);