Merge branch 'codex/task-057-059'
Build and Deploy Gateway / build-and-push (push) Failing after 17s
Build and Deploy Gateway / deploy (push) Has been skipped

# Conflicts:
#	src/shared/versions.ts
#	test/web/style-boundaries.test.js
This commit is contained in:
2026-08-31 05:20:57 +03:00
62 changed files with 10975 additions and 220 deletions
+1
View File
@@ -131,6 +131,7 @@ test('typed endpoint facade preserves exact request contracts and raw payload id
expectedRevision: 10,
}),
}],
[() => api.traffic.live(), '/api/traffic/live', {}],
[() => api.singbox.stop(), '/api/singbox/stop', { method: 'POST' }],
[() => api.singbox.restart(), '/api/singbox/restart', { method: 'POST' }],
[() => api.servers.ping('profile-1', ['one', 'two']), '/api/profiles/profile-1/servers/ping', {
+348
View File
@@ -0,0 +1,348 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import test from 'node:test';
import { assertLiveTrafficSnapshot } from '../../.test-dist/src/shared/liveTraffic.js';
import {
groupTrafficConnections,
reconcileTrafficGroups,
trafficGroupMatches,
} from '../../.test-dist/src/web/features/traffic/trafficRows.js';
const root = path.resolve(import.meta.dirname, '../..');
const source = (file) => fs.readFileSync(path.join(root, file), 'utf8');
const app = source('src/web/App.tsx');
const api = source('src/web/api/harborClient.ts');
const page = source('src/web/components/ClientOverviewPage.tsx');
const feature = source('src/web/features/traffic/TrafficFeature.tsx');
const rowModel = source('src/web/features/traffic/trafficRows.ts');
const boundary = source('src/web/features/traffic/index.ts');
const styles = source('src/web/styles/features/traffic.css');
const primitives = source('src/web/styles/primitives.css');
const validSnapshot = {
apiVersion: 1,
epoch: 'epoch-1',
sequence: 1,
observedAt: '2026-08-31T10:00:00.000Z',
capabilities: { lifecycle: true, deviceAttribution: false, applicationAttribution: false },
source: {
transport: 'native',
state: 'live',
completeness: 'lifecycle',
singBoxVersion: '1.14.0-rc.5',
singBoxApiVersion: 1,
error: null,
unattributedUploadBytes: '0',
unattributedDownloadBytes: '0',
},
summary: {
active: 1,
recent: 0,
visible: 1,
recognized: 1,
unresolved: 0,
unresolvedOrigin: 0,
truncated: false,
},
connections: [{
id: 'connection-1',
startedAt: '2026-08-31T09:59:59.000Z',
closedAt: null,
inbound: { tag: 'mixed-in', type: 'mixed' },
network: 'tcp',
protocol: 'tls',
source: { ip: '127.0.0.1', port: 53000 },
destination: { domain: 'example.com', ip: '203.0.113.1', port: 443, provenance: 'sing-box' },
origin: { kind: 'this-mac', id: null, label: 'Этот Mac', provenance: 'client-runtime' },
route: {
kind: 'vpn',
scope: 'local-sing-box',
outbound: 'proxy',
outboundType: 'selector',
chain: ['proxy'],
rule: 'default',
},
traffic: {
uploadBytes: '10',
downloadBytes: '20',
uploadBytesPerSecond: '1',
downloadBytesPerSecond: '2',
},
}],
};
function trafficConnection(id, overrides = {}) {
const base = validSnapshot.connections[0];
return {
...base,
...overrides,
id,
inbound: { ...base.inbound, ...overrides.inbound },
source: { ...base.source, ...overrides.source },
destination: { ...base.destination, ...overrides.destination },
origin: { ...base.origin, ...overrides.origin },
route: { ...base.route, ...overrides.route },
traffic: { ...base.traffic, ...overrides.traffic },
};
}
test('live traffic runtime boundary accepts the DTO and rejects inconsistent or malformed snapshots', () => {
assert.equal(assertLiveTrafficSnapshot(validSnapshot), validSnapshot);
assert.throws(() => assertLiveTrafficSnapshot({
...validSnapshot,
summary: { ...validSnapshot.summary, visible: 0 },
}), /Inconsistent traffic summary/);
assert.throws(() => assertLiveTrafficSnapshot({
...validSnapshot,
connections: [{
...validSnapshot.connections[0],
route: { ...validSnapshot.connections[0].route, outbound: 42 },
}],
}), /Invalid traffic connection/);
assert.throws(() => assertLiveTrafficSnapshot({
...validSnapshot,
summary: { ...validSnapshot.summary, active: 0, recent: 1, recognized: 0 },
connections: [{
...validSnapshot.connections[0],
closedAt: '2026-08-31T10:00:00.000Z',
}],
}), /Invalid closed traffic rate/);
assert.throws(() => assertLiveTrafficSnapshot({
...validSnapshot,
summary: { ...validSnapshot.summary, active: 2, visible: 2, recognized: 2 },
connections: [validSnapshot.connections[0], validSnapshot.connections[0]],
}), /Invalid traffic connection identity/);
assert.throws(() => assertLiveTrafficSnapshot({
...validSnapshot,
connections: [{ ...validSnapshot.connections[0], closedAt: '2026-08-31 10:00:00Z' }],
}), /Invalid traffic connection identity/);
assert.throws(() => assertLiveTrafficSnapshot({
...validSnapshot,
connections: [{
...validSnapshot.connections[0],
traffic: { ...validSnapshot.connections[0].traffic, uploadBytes: 10 },
}],
}), /Invalid traffic byte value/);
});
test('Mac and Gateway traffic drawers use one feature boundary and the cached read-only endpoint', () => {
assert.match(boundary, /TrafficPanel,[\s\S]*TrafficToggle,[\s\S]*useTrafficFeature/);
assert.match(api, /traffic: \{[\s\S]*live: \(\) => request\('\/api\/traffic\/live'\)/);
assert.match(app, /loadLiveTraffic: api\.traffic\.live/);
assert.match(page, /useTrafficFeature\(\{[\s\S]*enabled: true,[\s\S]*isGateway,[\s\S]*loadLiveTraffic: actions\.loadLiveTraffic/);
assert.match(feature, /interface TrafficFeatureOptions \{[\s\S]*isGateway: boolean/);
assert.match(feature, /return \{[\s\S]*isGateway,[\s\S]*isOpen/);
const rail = page.slice(page.indexOf('<nav'), page.indexOf('</nav>'));
const devices = rail.indexOf('<DevicesToggle');
const traffic = rail.indexOf('<TrafficToggle');
const diagnostics = rail.indexOf('<DiagnosticsToggle');
assert.ok(devices >= 0 && traffic > devices && diagnostics > traffic);
assert.equal((rail.match(/<TrafficToggle/g) || []).length, 1);
assert.doesNotMatch(rail, /!isGateway\s*&&\s*<TrafficToggle/);
assert.match(page, /\{hasSubscription && <TrafficPanel feature=\{trafficFeature\} \/>}/);
assert.doesNotMatch(page, /!isGateway && hasSubscription && <TrafficPanel/);
assert.doesNotMatch(page, /if \(isGateway\) trafficFeature\.close\(\)/);
assert.match(page, /DRAWER_ORDER = \['subscription', 'failover', 'instructions', 'devices', 'traffic', 'diagnostics', 'routing', 'journal'\]/);
assert.doesNotMatch(feature, /from ['"][^'"]*\/api(?:\/|\.js)/);
});
test('traffic polling runs every second only while the drawer is open and unpaused', () => {
assert.match(feature, /const POLL_MS = 1_000/);
assert.match(feature, /if \(!enabled \|\| !isOpen \|\| paused\) return undefined/);
assert.match(feature, /assertLiveTrafficSnapshot\(await loadLiveTraffic\(\)\)/);
assert.match(feature, /setSnapshot\(next\)[\s\S]*setRequestState\('ready'\)/);
assert.match(feature, /catch \{[\s\S]*setRequestState\('error'\)/);
assert.match(feature, /timer = setTimeout\(poll, POLL_MS\)/);
assert.match(feature, /cancelled = true[\s\S]*clearTimeout\(timer\)/);
assert.match(feature, /aria-pressed=\{feature\.paused\}[\s\S]*Продолжить[\s\S]*Пауза/);
assert.doesNotMatch(feature, /setInterval|WebSocket|EventSource/);
});
test('traffic drawer exposes the requested truthful states and accessible controls', () => {
for (const copy of [
'VPN остановлен',
'Активных соединений пока нет',
'Инспектор трафика временно недоступен',
'Эта версия sing-box не поддерживает инспектор трафика',
'Инспектор трафика выключен в настройках Harbor Connect.',
'Инспектор трафика выключен в настройках Harbor Gateway.',
'Показан последний полученный снимок',
'Только трафик через Harbor Connect. Приложения macOS недоступны внутри Docker.',
'Только соединения, прошедшие через sing-box Gateway. Трафик, обходящий sing-box напрямую, здесь не виден.',
]) assert.match(feature, new RegExp(copy.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
assert.match(feature, /\{feature\.isGateway \? 'GATEWAY' : 'MAC'\} · \{snapshot\?\.summary\.active \|\| 0\} АКТИВНЫХ/);
assert.match(feature, /feature\.isGateway[\s\S]*Инспектор трафика выключен в настройках Harbor Gateway\.[\s\S]*Инспектор трафика выключен в настройках Harbor Connect\./);
assert.match(feature, /feature\.isGateway[\s\S]*Только соединения, прошедшие через sing-box Gateway\. Трафик, обходящий sing-box напрямую, здесь не виден\.[\s\S]*Только трафик через Harbor Connect\. Приложения macOS недоступны внутри Docker\./);
assert.match(feature, /type="search"[\s\S]*aria-label="Найти домен, сервис или IP"/);
assert.match(feature, /role="group" aria-label="Фильтр по маршруту"/);
assert.match(feature, /role="group" aria-label="Фильтр по качеству распознавания"/);
assert.match(feature, /aria-pressed=\{routeFilter === value\}/);
assert.match(feature, /aria-pressed=\{qualityFilter === value\}/);
assert.match(feature, /const \[expandedId, setExpandedId\] = useState\(''\)/);
assert.match(feature, /aria-expanded=\{expanded\}[\s\S]*aria-controls=\{detailsId\}/);
assert.match(feature, /Источник[\s\S]*Назначение[\s\S]*Правило[\s\S]*Цепочка/);
assert.doesNotMatch(feature, /closeConnection|reroute|history|sessionStorage/);
});
test('traffic retention is local, bounded to approved choices and uses the frozen server observation clock', () => {
assert.match(feature, /const RETENTION_STORAGE_KEY = 'harbor:traffic-retention-seconds'/);
assert.match(feature, /const RETENTION_OPTIONS = \[5, 10, 30\] as const/);
assert.match(feature, /Number\(localStorage\.getItem\(RETENTION_STORAGE_KEY\)\)/);
assert.match(feature, /RETENTION_OPTIONS\.includes\(value as RetentionSeconds\)[\s\S]*: 10/);
assert.match(feature, /localStorage\.setItem\(RETENTION_STORAGE_KEY, String\(seconds\)\)/);
assert.match(feature, /Показывать завершённые/);
assert.match(feature, /aria-label="Время показа завершённых соединений"/);
assert.match(feature, /snapshot\?\.observedAt \? Date\.parse\(snapshot\.observedAt\) : Number\.NaN/);
assert.match(feature, /connection\.closedAt === null \|\| !Number\.isFinite\(snapshotTime\)/);
assert.match(feature, /snapshotTime - Date\.parse\(connection\.closedAt\) < retentionSeconds \* 1_000/);
assert.match(feature, /groupTrafficConnections\(retainedConnections\)/);
assert.match(feature, /trafficGroups\.filter\([\s\S]*trafficGroupMatches/);
assert.match(feature, /group\.connections\.length === 1[\s\S]*`Завершено · \$\{group\.protocol\}`/);
assert.match(feature, /Соединения сгруппированы по назначению, протоколу и маршруту\./);
});
test('traffic groups combine compatible UUIDs with exact byte sums and whole-group search', () => {
const groups = groupTrafficConnections([
trafficConnection('active', {
destination: { domain: 'yandex.ru', ip: '203.0.113.1' },
traffic: {
uploadBytes: '10',
downloadBytes: '20',
uploadBytesPerSecond: '1',
downloadBytesPerSecond: '2',
},
}),
trafficConnection('closed-a', {
closedAt: '2026-08-31T09:59:59.500Z',
destination: { domain: 'yandex.ru', ip: '203.0.113.2' },
traffic: {
uploadBytes: '30',
downloadBytes: '40',
uploadBytesPerSecond: '0',
downloadBytesPerSecond: '0',
},
}),
trafficConnection('closed-b', {
closedAt: '2026-08-31T09:59:59.700Z',
destination: { domain: 'yandex.ru', ip: '203.0.113.2' },
traffic: {
uploadBytes: '9007199254740993',
downloadBytes: '9007199254740995',
uploadBytesPerSecond: '0',
downloadBytesPerSecond: '0',
},
}),
]);
assert.equal(groups.length, 1);
assert.equal(groups[0].label, 'yandex.ru');
assert.equal(groups[0].activeCount, 1);
assert.equal(groups[0].recentCount, 2);
assert.deepEqual(groups[0].connections.map(({ id }) => id), ['active', 'closed-a', 'closed-b']);
assert.deepEqual(groups[0].destinationIps, ['203.0.113.1', '203.0.113.2']);
assert.deepEqual(groups[0].traffic, {
uploadBytes: '9007199254741033',
downloadBytes: '9007199254741055',
uploadBytesPerSecond: '1',
downloadBytesPerSecond: '2',
});
assert.equal(trafficGroupMatches(groups[0], '203.0.113.2', 'all', 'all'), true);
assert.equal(trafficGroupMatches(groups[0], 'missing.test', 'all', 'all'), false);
assert.equal(trafficGroupMatches(groups[0], '', 'vpn', 'recognized'), true);
assert.equal(trafficGroupMatches(groups[0], '', 'direct', 'all'), false);
});
test('traffic grouping keeps incompatible and unknown destinations separate', () => {
const base = trafficConnection('base', { destination: { domain: 'example.com', ip: '203.0.113.1' } });
const same = trafficConnection('same', { destination: { domain: 'Example.COM', ip: '203.0.113.2' } });
assert.equal(groupTrafficConnections([base, same]).length, 1);
const variants = [
['domain', { destination: { domain: 'www.example.com', ip: '203.0.113.1' } }],
['port', { destination: { domain: 'example.com', ip: '203.0.113.1', port: 80 } }],
['network', { network: 'udp' }],
['protocol', { protocol: 'http' }],
['origin', { origin: { kind: 'device', id: 'device-1', label: 'iPhone', provenance: 'source-ip' } }],
['route kind', { route: { kind: 'other' } }],
['route outbound', { route: { outbound: 'other-proxy' } }],
['route outbound type', { route: { outboundType: 'vless' } }],
['route chain', { route: { chain: ['proxy', 'hop'] } }],
['route rule', { route: { rule: 'other-rule' } }],
];
for (const [name, overrides] of variants) {
assert.equal(groupTrafficConnections([base, trafficConnection(String(name), overrides)]).length, 2, name);
}
assert.equal(groupTrafficConnections([
trafficConnection('ip-a', { destination: { domain: null, ip: '203.0.113.1' } }),
trafficConnection('ip-b', { destination: { domain: null, ip: '203.0.113.2' } }),
]).length, 2);
assert.equal(groupTrafficConnections([
trafficConnection('unknown-a', { destination: { domain: null, ip: null, provenance: 'unknown' } }),
trafficConnection('unknown-b', { destination: { domain: null, ip: null, provenance: 'unknown' } }),
]).length, 2);
const deviceOrigin = { kind: 'device', id: 'device-1', label: 'iPhone', provenance: 'source-ip' };
const [deviceGroup] = groupTrafficConnections([
trafficConnection('device', { origin: deviceOrigin }),
]);
assert.deepEqual(deviceGroup.origin, deviceOrigin);
assert.match(feature, /const source = onlyConnection[\s\S]*group\.origin\.label[\s\S]*onlyConnection\.source\.ip[\s\S]*group\.connections\.length/);
});
test('traffic groups stay mounted and inert through exit while the same group cancels removal', () => {
const group = (id, count = 1) => ({ id, connections: Array.from({ length: count }) });
let rows = reconcileTrafficGroups([], [group('a'), group('b'), group('c')], false);
rows = reconcileTrafficGroups(rows, [group('b', 2)], false);
assert.deepEqual(rows.map((row) => [row.group.id, row.group.connections.length, row.exiting]), [
['a', 1, true],
['b', 2, false],
['c', 1, true],
]);
rows = reconcileTrafficGroups(rows, [group('a'), group('b', 3), group('c')], false);
assert.deepEqual(rows.map((row) => [row.group.id, row.group.connections.length, row.exiting]), [
['a', 1, false],
['b', 3, false],
['c', 1, false],
]);
assert.deepEqual(reconcileTrafficGroups(rows, [], true), []);
assert.match(rowModel, /const desiredIds = new Set\(desired\.map\(\(group\) => group\.id\)\)/);
assert.match(feature, /groupTrafficConnections\(retainedConnections\)/);
assert.match(feature, /trafficGroupMatches\(group, query, routeFilter, qualityFilter\)/);
assert.match(feature, /reconcileTrafficGroups\(current, groups, immediate\)/);
assert.match(feature, /setExpandedId\(\(current\) => desiredIds\.has\(current\) \? current : ''\)/);
assert.match(feature, /inert=\{exiting \|\| undefined\}/);
assert.match(feature, /event\.target === event\.currentTarget[\s\S]*event\.animationName === 'client-traffic-connection-out'/);
assert.match(feature, /row\.group\.id !== id \|\| !row\.exiting/);
assert.match(feature, /matchMedia\('\(prefers-reduced-motion: reduce\)'\)[\s\S]*media\.addEventListener\('change', update\)[\s\S]*media\.removeEventListener\('change', update\)/);
assert.match(feature, /const immediate = reducedMotion[\s\S]*\['disabled', 'incompatible', 'stopped'\]\.includes/);
assert.match(feature, /aria-label="Группы активных и недавно завершённых соединений"/);
assert.match(feature, /group\.connections\.length > 1[\s\S]*×\$\{group\.connections\.length\}/);
assert.match(feature, /group\.activeCount > 0 && <strong>/);
assert.match(feature, /displayedGroups\.map\(\(row\) => <TrafficGroupRow[\s\S]*key=\{row\.group\.id\}/);
assert.match(feature, /<b>Активных распознано<\/b>/);
assert.match(feature, /<b>Активных требует внимания<\/b>/);
});
test('traffic styling preserves the shared drawer geometry and minimal motion', () => {
assert.match(styles, /\.client-traffic-toggle svg \{[\s\S]*width: 24px;[\s\S]*height: 24px/);
assert.match(primitives, /\.client-drawer \{[\s\S]*width: min\(580px, 100vw\)/);
assert.match(styles, /@media \(max-width: 768px\) \{[\s\S]*\.client-traffic \{[\s\S]*width: 100vw/);
assert.doesNotMatch(styles, /overflow-y:\s*(?:auto|scroll)/);
assert.match(styles, /\.client-traffic-meta button \{[\s\S]*width: 96px/);
assert.match(styles, /\.client-traffic-details \{[\s\S]*animation: client-traffic-details-in 180ms/);
assert.match(styles, /\.client-traffic-connection \{[\s\S]*animation: client-traffic-connection-in 420ms/);
assert.match(styles, /\.client-traffic-connection\.is-exiting \{[\s\S]*animation: client-traffic-connection-out 240ms/);
const detailMotion = /@keyframes client-traffic-details-in \{([\s\S]*?)\n\}/.exec(styles)?.[1] || '';
assert.match(detailMotion, /opacity:/);
assert.match(detailMotion, /translateY/);
assert.doesNotMatch(detailMotion, /height|width|margin|padding|scale|filter/);
for (const name of ['client-traffic-connection-in', 'client-traffic-connection-out']) {
const rowMotion = new RegExp(`@keyframes ${name} \\{([\\s\\S]*?)\\n\\}`).exec(styles)?.[1] || '';
assert.match(rowMotion, /opacity:/);
assert.match(rowMotion, /translateY/);
assert.doesNotMatch(rowMotion, /height|width|margin|padding|scale|filter/);
}
assert.match(styles, /@media \(prefers-reduced-motion: reduce\) \{[\s\S]*\.client-traffic-connection,[\s\S]*\.client-traffic-details \{[\s\S]*animation: none/);
});
+3 -3
View File
@@ -128,7 +128,7 @@ test('secondary menus share one right rail and switch equal drawers as a vertica
assert.match(component, /<SubscriptionToggle[\s\S]*<InstructionsToggle[\s\S]*<DevicesToggle/);
assert.match(subscription, /controls="client-subscription-drawer"/);
assert.match(component, /<InstructionsToggle[\s\S]*<RoutingToggle/);
assert.match(component, /<InstructionsToggle[\s\S]*<DevicesToggle[\s\S]*<DiagnosticsToggle[\s\S]*<RoutingToggle/);
assert.match(component, /<InstructionsToggle[\s\S]*<DevicesToggle[\s\S]*<TrafficToggle[\s\S]*<DiagnosticsToggle[\s\S]*<RoutingToggle/);
assert.match(diagnosticsFeature, /client-diagnostics-toggle/);
assert.match(component, /<ConnectivityDiagnosticsPanel/);
assert.doesNotMatch(component, /diagnosticsToggleRef|diagnosticsPanelRef|diagnosticsCloseRef/);
@@ -142,7 +142,7 @@ test('secondary menus share one right rail and switch equal drawers as a vertica
assert.match(styles, /\.client-local-rules-toggle:disabled:hover span\s*\{[\s\S]*opacity:\s*1/);
assert.match(subscription, /client-rail-subscription-back[\s\S]*client-rail-subscription-front[\s\S]*client-rail-subscription-lines/);
assert.match(instructions, /client-rail-book-page-position is-left[\s\S]*client-rail-book-page is-left[\s\S]*client-rail-book-page-position is-right[\s\S]*client-rail-book-page is-right[\s\S]*client-rail-book-spine/);
assert.match(component, /<InstructionsToggle[\s\S]*<DevicesToggle[\s\S]*<DiagnosticsToggle[\s\S]*<RoutingToggle/);
assert.match(component, /<InstructionsToggle[\s\S]*<DevicesToggle[\s\S]*<TrafficToggle[\s\S]*<DiagnosticsToggle[\s\S]*<RoutingToggle/);
assert.match(diagnosticsFeature, /client-rail-diagnostics-trace[\s\S]*client-rail-diagnostics-position[\s\S]*client-rail-diagnostics-glass/);
assert.match(devices, /client-rail-device-monitor[\s\S]*M5 19h5\.5M7 15v4[\s\S]*client-rail-device-phone[\s\S]*client-rail-device-link[\s\S]*M19 16v3h-5\.5/);
assert.match(routing, /client-rail-rule-track[\s\S]*client-rail-rule-knob-position is-top[\s\S]*client-rail-rule-knob is-top[\s\S]*client-rail-rule-knob-position is-bottom[\s\S]*client-rail-rule-knob is-bottom/);
@@ -167,7 +167,7 @@ test('secondary menus share one right rail and switch equal drawers as a vertica
assert.match(instructions, /<Drawer[\s\S]*className="client-instructions"/);
assert.match(routing, /<Drawer[\s\S]*className="client-local-rules"/);
assert.match(subscription, /<Drawer[\s\S]*className="client-subscription-drawer"/);
assert.match(component, /const DRAWER_ORDER = \['subscription', 'failover', 'instructions', 'devices', 'diagnostics', 'routing', 'journal'\]/);
assert.match(component, /const DRAWER_ORDER = \['subscription', 'failover', 'instructions', 'devices', 'traffic', 'diagnostics', 'routing', 'journal'\]/);
assert.match(component, /function switchDrawer\(target: DrawerKey\)[\s\S]*from\.inert = true[\s\S]*translateY\(\$\{direction \* 100\}%\)[\s\S]*translateY\(\$\{-direction \* 100\}%\)/);
assert.match(component, /const \[drawerSwitchTarget, setDrawerSwitchTarget\] = useState<DrawerKey \| null>\(null\)/);
assert.match(component, /const activeRailDrawer = drawerSwitchTarget && drawerControls\[drawerSwitchTarget\]\.isOpen[\s\S]*drawerControls\[drawer\]\.isOpen/);
+19 -18
View File
@@ -33,36 +33,37 @@ const expectedImports = [
'./features/diagnostics.css',
'./features/failover.css',
'./features/activity-journal.css',
'./features/traffic.css',
'./layout.css',
'./themes.css',
];
const sha256 = (value) => crypto.createHash('sha256').update(value).digest('hex');
const acceptedLedger = {
counts: {
cascadeEdges: 1120,
cascadeEdges: 1126,
customProperties: 115,
declarations: 4455,
declarations: 4676,
important: 0,
keyframes: 52,
media: 19,
rules: 1193,
variableReferences: 1138,
keyframes: 55,
media: 22,
rules: 1262,
variableReferences: 1214,
},
hashes: {
cascadeEdges: 'd8eeaf20637638f97dd826ae7486469367fb7f5c7e2d32a6fb1d64647e8b52e0',
cascadeEdges: '1f3c75839bd37bb312b9aed2987ed61571e5148f8b23c8288424cb193c7a8dda',
customProperties: '81d1e70737ae5a8e4b20d4c1be1b24685975a404863444da4a8821be1f0d5097',
declarations: '89b645d94c44f0dcfea1a7c5f653a8124f0c87057a9898437419ddce813271d2',
declarations: '616fc7cb72a9db509f6b41e1808118c2e2be7da6ce4c078dfa2eb4a963f0dbb1',
duplicateKeyframes: '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945',
duplicateSelectors: '8982145dba05b33bf1c93b2d54cfbe76305b276ff3c657aa020144016ada5848',
keyframes: '405688c9a452aa9d54e9c50dd30abb13143fdb5910f63f4474ecefcc800311e6',
ruleDeclarationSequences: '12a5a2deec8a8521f49551d9a29c1950a01bce225f1b387def47304fcfe1c960',
selectors: 'fa7cc612da0fc1e3804a884032e9ab99e0bb0776b29a9e40d44a2891de198067',
variableReferences: '1cbaf891cd2df7c91003d8879a205075211678ad40a817b746c4dde54830ad6d',
witnesses: 'bae0329346060aeeef91dee449ccee7187e68501c0b5b082026312e7c21a4798',
keyframes: 'b9b0bf3fad92e69b93ec0c24f01da15e78bbe89a095597d64ce4f7ef5e272fdd',
ruleDeclarationSequences: '3a65866ea25506c6fd79b68bfbbdeddd495f6957dbd99dec6a814b816ba4cca2',
selectors: '56e1c67b35230649a3c69b2aea67c1929580700f1b4357ee05dfb11012d72655',
variableReferences: '71722a81fa16b3586ae9ee5890f79176729dfdf7fe58c4d4994755f8059aa034',
witnesses: '2272ed9b07e02edd232c33a971c4cc5171f6a612ccaa36418933f0fd6bb465c1',
},
};
test('public stylesheet exposes exactly fourteen flat semantic owners', () => {
test('public stylesheet exposes exactly fifteen flat semantic owners', () => {
const imports = Array.from(index.matchAll(/^@import ['"](\.\/[^'"]+\.css)['"];$/gm), ([, file]) => file);
assert.deepEqual(imports, expectedImports);
assert.equal(index, `${expectedImports.map((file) => `@import '${file}';`).join('\n')}\n`);
@@ -211,7 +212,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, 1198);
assert.equal(witnesses.length, 1292);
assert.equal(witnesses.filter((witness) => witness.unknown || witness.ancestorUnknown).length, 0);
const ledger = createStyleLedger(readStyleSource(root), { witnesses });
assert.deepEqual(ledger.counts, acceptedLedger.counts);
@@ -408,8 +409,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-C2yOT86L.css']);
assert.deepEqual(assets, ['index-tJmxnB8a.css']);
const built = fs.readFileSync(path.join(root, 'dist/assets', assets[0]));
assert.equal(built.byteLength, 169625);
assert.equal(sha256(built), 'a9bb2c83ab62798e762f4338be1869a6d29366903f83f90d4abcaa2855e44d8e');
assert.equal(built.byteLength, 176543);
assert.equal(sha256(built), '38a283204c656164c38c17aa03f41c931544e05b5732248807d911b2645bf970');
});
+2 -2
View File
@@ -34,7 +34,7 @@ test('all repeated client primitive consumers use the shared owners', () => {
assert.equal((production.match(/<Tooltip\b/g) || []).length, 16);
assert.equal((production.match(/<CopyButton\b/g) || []).length, 2);
assert.equal((production.match(/<RailAction\b/g) || []).length, 7);
assert.equal((production.match(/<Drawer\b/g) || []).length, 7);
assert.equal((production.match(/<RailAction\b/g) || []).length, 8);
assert.equal((production.match(/<Drawer\b/g) || []).length, 8);
assert.doesNotMatch(production, /className="client-tooltip"|className="client-copy-label"|className="client-drawer-close"/);
});