Skip to content

Commit ab2ec38

Browse files
committed
test: make hard-coded caps overridable behind seams (#1781 B5)
HarmonyOS snapshot gains a maxNodes seam (mirroring the Android helper and Linux AT-SPI capture options) so the node cap's truncation signal is exercised below the 5,000 default; the durable descriptor JSON node cap gets its boundary test alongside the existing depth one.
1 parent a70cdee commit ab2ec38

3 files changed

Lines changed: 73 additions & 5 deletions

File tree

packages/capture-kit/src/durable-json.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ test('bounded durable JSON rejects cycles and excessive depth', () => {
1212
assert.equal(isBoundedJsonObject(nested), false);
1313
});
1414

15+
test('bounded durable JSON rejects a wide, shallow document past the node cap', () => {
16+
// Depth 2 everywhere, so only the node budget can reject it: the root plus
17+
// one array plus 4,096 empty objects is 4,098 nodes.
18+
const wide = { items: Array.from({ length: 4_096 }, () => ({})) };
19+
assert.equal(isBoundedJsonObject(wide), false);
20+
assert.equal(isBoundedJsonObject({ items: wide.items.slice(0, 4_000) }), true);
21+
});
22+
1523
test('validated durable JSON freezes without recursively revalidating every subtree', () => {
1624
let reads = 0;
1725
const leaf = Object.defineProperty({}, 'value', {

src/platforms/harmonyos/__tests__/snapshot.test.ts

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,35 @@
11
import assert from 'node:assert/strict';
2-
import { test } from 'vitest';
3-
import { parseArkUiBounds, parseHarmonyLayout } from '../snapshot.ts';
2+
import fs from 'node:fs';
3+
import { beforeEach, test, vi } from 'vitest';
4+
5+
const { runHarmonyHdc } = vi.hoisted(() => ({ runHarmonyHdc: vi.fn() }));
6+
7+
vi.mock('../hdc.ts', () => ({ runHarmonyHdc }));
8+
9+
import { parseArkUiBounds, parseHarmonyLayout, snapshotHarmony } from '../snapshot.ts';
10+
11+
const DEVICE = {
12+
platform: 'harmonyos' as const,
13+
id: 'harmony-1',
14+
name: 'HarmonyOS test device',
15+
kind: 'device' as const,
16+
target: 'mobile' as const,
17+
booted: true,
18+
};
19+
20+
beforeEach(() => {
21+
runHarmonyHdc.mockReset();
22+
});
23+
24+
/** Scripts `uitest dumpLayout` + `file recv` so the pulled layout is `layout`. */
25+
function scriptHarmonyLayoutDump(layout: unknown): void {
26+
runHarmonyHdc.mockImplementation(async (_device: unknown, args: string[]) => {
27+
if (args[0] === 'file' && args[1] === 'recv') {
28+
fs.writeFileSync(args[3] as string, JSON.stringify(layout), 'utf8');
29+
}
30+
return { exitCode: 0, stdout: '', stderr: '' };
31+
});
32+
}
433

534
test('parseArkUiBounds converts API 24 layout bounds into a rectangle', () => {
635
assert.deepEqual(parseArkUiBounds('[84,1127][1172,1295]'), {
@@ -15,3 +44,27 @@ test('parseArkUiBounds converts API 24 layout bounds into a rectangle', () => {
1544
test('parseHarmonyLayout rejects non-object uitest documents', () => {
1645
assert.throws(() => parseHarmonyLayout('[]'), /invalid layout JSON/i);
1746
});
47+
48+
test('snapshotHarmony reports truncation once the node cap is hit instead of dropping nodes silently', async () => {
49+
scriptHarmonyLayoutDump({
50+
attributes: { type: 'root', bounds: '[0,0][1080,2340]' },
51+
children: [
52+
{ attributes: { type: 'Button', text: 'first', clickable: 'true' } },
53+
{ attributes: { type: 'Button', text: 'second', clickable: 'true' } },
54+
{ attributes: { type: 'Button', text: 'third', clickable: 'true' } },
55+
],
56+
});
57+
58+
const capped = await snapshotHarmony(DEVICE, { maxNodes: 2 });
59+
60+
assert.equal(capped.truncated, true);
61+
assert.deepEqual(
62+
capped.nodes.map((node) => node.value ?? node.type),
63+
['Application', 'first'],
64+
);
65+
assert.equal(capped.analysis.rawNodeCount, 4);
66+
67+
const uncapped = await snapshotHarmony(DEVICE);
68+
assert.equal(uncapped.truncated, undefined);
69+
assert.equal(uncapped.nodes.length, 4);
70+
});

src/platforms/harmonyos/snapshot.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,16 @@ type ArkUiLayoutNode = {
1515
children?: ArkUiLayoutNode[];
1616
};
1717

18+
/**
19+
* `maxNodes` bounds the emitted tree; nodes past it are dropped and the
20+
* result reports `truncated`. Mirrors the Android helper and Linux AT-SPI
21+
* capture seams so the bound is exercisable below the 5,000 default.
22+
*/
23+
export type HarmonySnapshotOptions = SnapshotOptions & { maxNodes?: number };
24+
1825
export async function snapshotHarmony(
1926
device: DeviceInfo,
20-
options: SnapshotOptions = {},
27+
options: HarmonySnapshotOptions = {},
2128
): Promise<{
2229
nodes: RawSnapshotNode[];
2330
truncated?: boolean;
@@ -75,7 +82,7 @@ export function parseHarmonyLayout(raw: string): ArkUiLayoutNode {
7582

7683
function buildHarmonySnapshot(
7784
root: ArkUiLayoutNode,
78-
options: SnapshotOptions,
85+
options: HarmonySnapshotOptions,
7986
): {
8087
nodes: RawSnapshotNode[];
8188
truncated?: boolean;
@@ -85,7 +92,7 @@ function buildHarmonySnapshot(
8592
let rawNodeCount = 0;
8693
let maxDepth = 0;
8794
let truncated = false;
88-
const maxNodes = MAX_NODES;
95+
const maxNodes = options.maxNodes ?? MAX_NODES;
8996
const walk = (node: ArkUiLayoutNode, depth: number, parentIndex?: number): void => {
9097
rawNodeCount += 1;
9198
maxDepth = Math.max(maxDepth, depth);

0 commit comments

Comments
 (0)