Add per-row connectivity diagnostic refresh controls
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-19 12:59:21 +03:00
parent 7cb25d0633
commit df865fbe3d
9 changed files with 131 additions and 23 deletions
+15 -1
View File
@@ -83,7 +83,7 @@ test('unknown target and legacy-full results pass one identity-preserving parser
});
test('serial probes, storage and editor behavior stay panel-owned', () => {
assert.match(panel, /const targets = \[[\s\S]*CONNECTIVITY_NETWORK_SOURCE\.id[\s\S]*CONNECTIVITY_IP_SOURCES\.map[\s\S]*sites\.map/);
assert.match(panel, /const targets = onlyTarget \? \[onlyTarget\] : \[[\s\S]*CONNECTIVITY_NETWORK_SOURCE\.id[\s\S]*CONNECTIVITY_IP_SOURCES\.map[\s\S]*sites\.map/);
assert.match(panel, /for \(const target of targets\) \{[\s\S]*setActiveTarget\(target\)[\s\S]*await runConnectivityDiagnostics\(customServices, target\)[\s\S]*if \(legacyFullResult\) break/);
assert.match(panel, /CUSTOM_SERVICES_KEY = 'harbor-diagnostic-services'/);
assert.match(panel, /HIDDEN_SERVICES_KEY = 'harbor-hidden-diagnostic-services'/);
@@ -94,6 +94,20 @@ test('serial probes, storage and editor behavior stay panel-owned', () => {
assert.match(panel, /requestDetails\(error\)[\s\S]*requestError\.retryable/);
});
test('each visible row reuses the targeted probe without adding another result owner', () => {
assert.match(panel, /function RowRefresh\([\s\S]*aria-label=\{`Проверить: \$\{label\}`\}[\s\S]*aria-busy=\{running\}[\s\S]*disabled=\{disabled\}/);
assert.match(panel, /<Tooltip>Проверить только эту строку<\/Tooltip>/);
assert.match(panel, /async function run\(onlyTarget\?: string\)[\s\S]*const targets = onlyTarget \? \[onlyTarget\] :/);
assert.match(panel, /onRun=\{\(\) => run\(CONNECTIVITY_NETWORK_SOURCE\.id\)\}/);
assert.match(panel, /onRun=\{\(\) => run\(target\)\}/);
assert.match(panel, /onRun=\{\(\) => run\(`site:\$\{site\.id\}`\)\}/);
assert.match(panel, /retryTargetRef\.current = onlyTarget[\s\S]*run\(retryTargetRef\.current\)/);
assert.match(panel, /scope="row" aria-label=\{CONNECTIVITY_NETWORK_SOURCE\.label\}/);
assert.match(panel, /role="rowheader" aria-label=\{site\.label\}/);
assert.equal((panel.match(/className="client-diagnostics-row-name"/g) || []).length, 2);
assert.match(panel, /client-diagnostics-service-name client-diagnostics-row-name/);
});
test('network identity is one stable compact row for Direct and VPN', () => {
assert.match(panel, /data-diagnostic-target=\{CONNECTIVITY_NETWORK_SOURCE\.id\}[\s\S]*<NetworkCell[\s\S]*route="Напрямую, сеть"[\s\S]*<NetworkCell[\s\S]*route="VPN, сеть"/);
assert.match(panel, /path\?\.network\?\.asn[\s\S]*path\?\.network\?\.provider[\s\S]*path\?\.network\?\.city[\s\S]*path\?\.network\?\.country/);
@@ -210,6 +210,9 @@ test('connectivity diagnostics render stable compact tables before the first run
assert.match(diagnostics, /client-deletable-row[\s\S]*className="client-delete-strike"[\s\S]*onAnimationEnd/);
assert.match(diagnostics, /document\.startViewTransition\(update\)/);
assert.match(diagnostics, /client-diagnostics-refresh/);
assert.match(diagnostics, /client-diagnostics-row-refresh/);
assert.match(rule('.client-diagnostics-row-name'), /display:\s*inline-flex[\s\S]*max-width:\s*100%/);
assert.match(styles, /\.client-diagnostics-row-refresh-wrap,[\s\S]*flex:\s*0 0 24px/);
assert.match(diagnostics, /isGateway \? 'Gateway' : 'Connect'/);
assert.doesNotMatch(diagnostics, /Проверить ещё раз|client-diagnostics-empty|client-diagnostics-run/);
assert.match(diagnostics, /\{Boolean\(error\) && <div className="client-diagnostics-feedback"/);
@@ -222,6 +225,7 @@ test('connectivity diagnostics render stable compact tables before the first run
assert.match(diagnostics, /const target = `ip:\$\{source\.id\}`;[\s\S]*activeTarget === target/);
assert.match(diagnostics, /activeTarget === `site:\$\{site\.id\}`/);
assert.match(diagnostics, /data-diagnostic-target=\{target\}/);
assert.match(diagnostics, /const targets = onlyTarget \? \[onlyTarget\] :/);
assert.match(diagnostics, /client-diagnostics-active-marker/);
assert.match(styles, /\.client-diagnostics-active-marker\.is-moving \{[\s\S]*transform 680ms/);
assert.match(diagnostics, />\.{3}<\/span>/);
+12 -12
View File
@@ -37,26 +37,26 @@ const expectedImports = [
const sha256 = (value) => crypto.createHash('sha256').update(value).digest('hex');
const acceptedLedger = {
counts: {
cascadeEdges: 949,
cascadeEdges: 954,
customProperties: 106,
declarations: 3474,
declarations: 3486,
important: 0,
keyframes: 48,
media: 13,
rules: 966,
rules: 970,
variableReferences: 837,
},
hashes: {
cascadeEdges: 'a2289354130ffe83c907b977d21d773799c4435c8016e51d14f153184f834804',
cascadeEdges: 'd53f6a2236717d6bc27d486fc19f0f80a33169fa7f68a6df2a19536fd30be3bb',
customProperties: 'fd6f16069f9fe3526f09d4d574431ddae67150d8e7a8a40e04c9fd2d179ec7ac',
declarations: '6581f90631086ad0ba2ef1b3d1ec86f608e872572af5486d4c7199f06802bcfd',
declarations: '1939d4a9a8b812ea799fbbb22a72befde73aa20159ef6736c9c35a5b3458d32c',
duplicateKeyframes: '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945',
duplicateSelectors: '8982145dba05b33bf1c93b2d54cfbe76305b276ff3c657aa020144016ada5848',
keyframes: 'a0d69c5b3e5f235d3a76ed04bdd64f67a73fafc9c59fe2635ffda603709c0ad9',
ruleDeclarationSequences: 'dc793f9ea84540c87ae3650fa915efed643b941c393e8dd0fd75fcb01c543bb3',
selectors: 'a556fe1d9608d73332db5b3c14cd4cfde42f3346a542d3e2b2ce2af34f556c8c',
ruleDeclarationSequences: '0a7655c97f5deb132f4c05d136e4d615a0abbbb9e86ab83c8155047bc409bfec',
selectors: '0c1374fd3f414faac30f1519e6f0b04f63fc50aa69a05a84d53fbe33c426eccc',
variableReferences: '843a5f71b0fb74691ed623dbc7ebb7ddddc34e5736e1f419f17cf54e22d43073',
witnesses: '10b015073632f8697ac16b5849f28029322d9164060896c366ba4d35dbb3cee5',
witnesses: '83b49149eecdf0a67348412f7cfd49023aad76848b1572d25d6c0e9193e612d5',
},
};
@@ -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, 860);
assert.equal(witnesses.length, 880);
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-DUSJhcRO.css']);
assert.deepEqual(assets, ['index-DDl9J3Se.css']);
const built = fs.readFileSync(path.join(root, 'dist/assets', assets[0]));
assert.equal(built.byteLength, 133278);
assert.equal(sha256(built), 'f3116a2f0f282a10030589057ec0c62800640b7b552180dfe12d4b737a5fc436');
assert.equal(built.byteLength, 133627);
assert.equal(sha256(built), '68bf7b36cf345bfb460b7f6c9d973bde54e260a0c695176d035c1fe02ba0f3a1');
});
+1 -1
View File
@@ -32,7 +32,7 @@ test('all repeated client primitive consumers use the shared owners', () => {
.map(({ source }) => source)
.join('\n');
assert.equal((production.match(/<Tooltip\b/g) || []).length, 13);
assert.equal((production.match(/<Tooltip\b/g) || []).length, 14);
assert.equal((production.match(/<CopyButton\b/g) || []).length, 2);
assert.equal((production.match(/<RailAction\b/g) || []).length, 5);
assert.equal((production.match(/<Drawer\b/g) || []).length, 5);