From 0bcfa678cacbee04d73de53a506648cb7fa72392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Tue, 31 Mar 2026 21:03:20 +0200 Subject: [PATCH] fix: prune unlabeled groups from snapshot text output (#329) * fix: prune unlabeled groups from snapshot text output * fix: remove dead snapshot formatter state --- src/utils/__tests__/output.test.ts | 78 ++++++++++++++++++++++++++++++ src/utils/snapshot-lines.ts | 21 ++++---- 2 files changed, 87 insertions(+), 12 deletions(-) diff --git a/src/utils/__tests__/output.test.ts b/src/utils/__tests__/output.test.ts index 3910cc2ee..5266693b1 100644 --- a/src/utils/__tests__/output.test.ts +++ b/src/utils/__tests__/output.test.ts @@ -102,6 +102,84 @@ test('formatSnapshotText summarizes large Android TextView surfaces with preview assert.match(text, /\[truncated\]/); }); +test('formatSnapshotText omits unlabeled group wrappers while preserving labeled groups', () => { + const text = withNoColor(() => + formatSnapshotText({ + nodes: [ + { ref: 'e1', index: 0, depth: 0, type: 'android.widget.FrameLayout' }, + { ref: 'e2', index: 1, depth: 1, parentIndex: 0, type: 'android.widget.LinearLayout' }, + { ref: 'e3', index: 2, depth: 2, parentIndex: 1, type: 'android.view.ViewGroup' }, + { + ref: 'e14', + index: 3, + depth: 3, + parentIndex: 2, + type: 'android.widget.ScrollView', + }, + { + ref: 'e17', + index: 4, + depth: 4, + parentIndex: 3, + type: 'android.view.ViewGroup', + label: 'HomePage', + }, + { + ref: 'e21', + index: 5, + depth: 5, + parentIndex: 4, + type: 'android.view.ViewGroup', + label: 'Home', + }, + { + ref: 'e22', + index: 6, + depth: 5, + parentIndex: 4, + type: 'android.widget.Button', + label: 'Search', + }, + ], + truncated: false, + }), + ); + + assert.doesNotMatch(text, /@e1 \[group\]/); + assert.doesNotMatch(text, /@e2 \[group\]/); + assert.doesNotMatch(text, /@e3 \[group\]/); + assert.match(text, /@e14 \[scroll-area\] \[scrollable\]/); + assert.match(text, / @e17 \[group\] "HomePage"/); + assert.match(text, / @e21 \[group\] "Home"/); + assert.match(text, / @e22 \[button\] "Search"/); +}); + +test('formatSnapshotText compresses visible indentation after hidden wrapper chains', () => { + const text = withNoColor(() => + formatSnapshotText({ + nodes: [ + { ref: 'e1', index: 0, depth: 0, type: 'android.widget.FrameLayout' }, + { ref: 'e2', index: 1, depth: 1, parentIndex: 0, type: 'android.widget.ScrollView' }, + { + ref: 'e3', + index: 2, + depth: 2, + parentIndex: 1, + type: 'android.widget.Button', + label: 'Back', + }, + { ref: 'e4', index: 3, depth: 3, parentIndex: 2, type: 'android.view.ViewGroup' }, + { ref: 'e5', index: 4, depth: 4, parentIndex: 3, type: 'android.widget.ImageView' }, + ], + truncated: false, + }), + ); + + assert.match(text, /^@e2 \[scroll-area\] \[scrollable\]$/m); + assert.match(text, /^ @e3 \[button\] "Back"$/m); + assert.match(text, /^ @e5 \[image\]$/m); +}); + test('formatSnapshotLine keeps snapshot-only metadata off the default formatter path', () => { const line = formatSnapshotLine( { diff --git a/src/utils/snapshot-lines.ts b/src/utils/snapshot-lines.ts index e0450d9a5..f9fead106 100644 --- a/src/utils/snapshot-lines.ts +++ b/src/utils/snapshot-lines.ts @@ -16,28 +16,25 @@ export function buildSnapshotDisplayLines( nodes: SnapshotNode[], options: SnapshotLineFormatOptions = {}, ): SnapshotDisplayLine[] { - const hiddenGroupDepths: number[] = []; + const visibleDepths: number[] = []; const lines: SnapshotDisplayLine[] = []; for (const node of nodes) { const depth = node.depth ?? 0; - while ( - hiddenGroupDepths.length > 0 && - depth <= hiddenGroupDepths[hiddenGroupDepths.length - 1] - ) { - hiddenGroupDepths.pop(); - } const label = node.label?.trim() || node.value?.trim() || node.identifier?.trim() || ''; const type = formatRole(node.type ?? 'Element'); - const isHiddenGroup = type === 'group' && !label; - if (isHiddenGroup) { - hiddenGroupDepths.push(depth); + if (type === 'group' && !label) { + continue; } - const adjustedDepth = isHiddenGroup ? depth : Math.max(0, depth - hiddenGroupDepths.length); + while (visibleDepths.length > 0 && depth <= visibleDepths[visibleDepths.length - 1]!) { + visibleDepths.pop(); + } + const adjustedDepth = visibleDepths.length; + visibleDepths.push(depth); lines.push({ node, depth: adjustedDepth, type, - text: formatSnapshotLine(node, adjustedDepth, isHiddenGroup, type, options), + text: formatSnapshotLine(node, adjustedDepth, false, type, options), }); } return lines;