agent-react-devtools 0.1.0 → 0.1.1-canary-20260210005551
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +180 -0
- package/package.json +8 -2
- package/src/__tests__/cli-parser.test.ts +0 -76
- package/src/__tests__/component-tree.test.ts +0 -229
- package/src/__tests__/formatters.test.ts +0 -189
- package/src/__tests__/profiler.test.ts +0 -264
- package/src/cli.ts +0 -315
- package/src/component-tree.ts +0 -495
- package/src/daemon-client.ts +0 -144
- package/src/daemon.ts +0 -275
- package/src/devtools-bridge.ts +0 -391
- package/src/formatters.ts +0 -270
- package/src/profiler.ts +0 -356
- package/src/types.ts +0 -126
- package/tsconfig.json +0 -9
- package/tsup.config.ts +0 -17
- package/vitest.config.ts +0 -7
package/CHANGELOG.md
CHANGED
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# agent-react-devtools
|
|
2
|
+
|
|
3
|
+
Give your AI agent eyes into your React app. Inspect component trees, read props and state, and profile rendering performance — all from the command line. Inspired by Vercel's [agent-browser](https://github.com/vercel-labs/agent-browser) and Callstack's [agent-device](https://github.com/callstackincubator/agent-device).
|
|
4
|
+
|
|
5
|
+
The project is in early development and considered experimental. Pull requests are welcome!
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Walk the full component tree with props, state, and hooks
|
|
10
|
+
- Search for components by display name
|
|
11
|
+
- Profile renders: find slow components, excessive re-renders, and commit timelines
|
|
12
|
+
- Persistent background daemon that survives across CLI calls
|
|
13
|
+
- Token-efficient output built for LLM consumption
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install -g agent-react-devtools
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or run it directly:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npx agent-react-devtools start
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
agent-react-devtools start
|
|
31
|
+
agent-react-devtools status
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Daemon: running (port 8097)
|
|
36
|
+
Apps: 1 connected, 24 components
|
|
37
|
+
Uptime: 12s
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Browse the component tree:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
agent-react-devtools get tree --depth 3
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
@c1 [fn] "App"
|
|
48
|
+
├─ @c2 [fn] "Header"
|
|
49
|
+
│ ├─ @c3 [fn] "Nav"
|
|
50
|
+
│ └─ @c4 [fn] "SearchBar"
|
|
51
|
+
├─ @c5 [fn] "TodoList"
|
|
52
|
+
│ ├─ @c6 [fn] "TodoItem" key="1"
|
|
53
|
+
│ ├─ @c7 [fn] "TodoItem" key="2"
|
|
54
|
+
│ └─ @c8 [fn] "TodoItem" key="3"
|
|
55
|
+
└─ @c9 [fn] "Footer"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Inspect a component's props, state, and hooks:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
agent-react-devtools get component @c6
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
@c6 [fn] "TodoItem" key="1"
|
|
66
|
+
props:
|
|
67
|
+
id: 1
|
|
68
|
+
text: "Buy groceries"
|
|
69
|
+
done: false
|
|
70
|
+
onToggle: ƒ
|
|
71
|
+
hooks:
|
|
72
|
+
State: false
|
|
73
|
+
Callback: ƒ
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Find components by name:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
agent-react-devtools find TodoItem
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
@c6 [fn] "TodoItem" key="1"
|
|
84
|
+
@c7 [fn] "TodoItem" key="2"
|
|
85
|
+
@c8 [fn] "TodoItem" key="3"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Profile rendering performance:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
agent-react-devtools profile start
|
|
92
|
+
# ... interact with the app ...
|
|
93
|
+
agent-react-devtools profile stop
|
|
94
|
+
agent-react-devtools profile slow
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
Slowest (by avg render time):
|
|
99
|
+
TodoList avg:4.2ms max:8.1ms renders:6 cause:props
|
|
100
|
+
SearchBar avg:2.1ms max:3.4ms renders:12 cause:hooks
|
|
101
|
+
Header avg:0.8ms max:1.2ms renders:3 cause:parent
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Commands
|
|
105
|
+
|
|
106
|
+
### Daemon
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
agent-react-devtools start [--port 8097] # Start daemon
|
|
110
|
+
agent-react-devtools stop # Stop daemon
|
|
111
|
+
agent-react-devtools status # Connection status
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Components
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
agent-react-devtools get tree [--depth N] # Component hierarchy
|
|
118
|
+
agent-react-devtools get component <@c1 | id> # Props, state, hooks
|
|
119
|
+
agent-react-devtools find <name> [--exact] # Search by display name
|
|
120
|
+
agent-react-devtools count # Component count by type
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Components are labeled `@c1`, `@c2`, etc. You can use these labels or numeric IDs interchangeably.
|
|
124
|
+
|
|
125
|
+
### Profiling
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
agent-react-devtools profile start [name] # Begin a profiling session
|
|
129
|
+
agent-react-devtools profile stop # Stop and collect data
|
|
130
|
+
agent-react-devtools profile report <@c1 | id> # Render report for a component
|
|
131
|
+
agent-react-devtools profile slow [--limit N] # Slowest components by avg duration
|
|
132
|
+
agent-react-devtools profile rerenders [--limit N] # Most re-rendered components
|
|
133
|
+
agent-react-devtools profile timeline [--limit N] # Commit timeline
|
|
134
|
+
agent-react-devtools profile commit <N | #N> [--limit N] # Single commit detail
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Connecting Your App
|
|
138
|
+
|
|
139
|
+
Install `react-devtools-core` in your app and connect before React renders (e.g. in `src/main.tsx`):
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { initialize, connectToDevTools } from 'react-devtools-core';
|
|
143
|
+
import { createRoot } from 'react-dom/client';
|
|
144
|
+
import App from './App';
|
|
145
|
+
|
|
146
|
+
initialize();
|
|
147
|
+
connectToDevTools({ port: 8097 });
|
|
148
|
+
|
|
149
|
+
createRoot(document.getElementById('root')!).render(<App />);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Using with AI Agents
|
|
153
|
+
|
|
154
|
+
Add something like this to your project's `CLAUDE.md` (or equivalent agent instructions):
|
|
155
|
+
|
|
156
|
+
```markdown
|
|
157
|
+
## React Debugging
|
|
158
|
+
|
|
159
|
+
This project uses agent-react-devtools to inspect the running React app.
|
|
160
|
+
|
|
161
|
+
- `agent-react-devtools start` — start the daemon
|
|
162
|
+
- `agent-react-devtools status` — check if the app is connected
|
|
163
|
+
- `agent-react-devtools get tree` — see the component hierarchy
|
|
164
|
+
- `agent-react-devtools get component @c1` — inspect a specific component
|
|
165
|
+
- `agent-react-devtools find <Name>` — search for components
|
|
166
|
+
- `agent-react-devtools profile start` / `profile stop` / `profile slow` — diagnose render performance
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
bun install # Install dependencies
|
|
173
|
+
bun run build # Build
|
|
174
|
+
bun run test # Run tests
|
|
175
|
+
bun run typecheck # Type check
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-react-devtools",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1-canary-20260210005551",
|
|
4
4
|
"description": "CLI tool for AI agents to inspect React component trees and profile performance",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"agent-react-devtools": "./dist/cli.js"
|
|
8
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"CHANGELOG.md"
|
|
13
|
+
],
|
|
9
14
|
"scripts": {
|
|
10
15
|
"build": "tsup",
|
|
11
16
|
"dev": "tsup --watch",
|
|
12
17
|
"test": "vitest run",
|
|
13
18
|
"test:watch": "vitest",
|
|
14
19
|
"typecheck": "tsc --noEmit",
|
|
15
|
-
"lint": "tsc --noEmit"
|
|
20
|
+
"lint": "tsc --noEmit",
|
|
21
|
+
"prepublishOnly": "cp ../../README.md ."
|
|
16
22
|
},
|
|
17
23
|
"repository": {
|
|
18
24
|
"type": "git",
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
|
|
3
|
-
// Inline the parseArgs function for testing (it's not exported from cli.ts)
|
|
4
|
-
function parseArgs(argv: string[]): {
|
|
5
|
-
command: string[];
|
|
6
|
-
flags: Record<string, string | boolean>;
|
|
7
|
-
} {
|
|
8
|
-
const command: string[] = [];
|
|
9
|
-
const flags: Record<string, string | boolean> = {};
|
|
10
|
-
|
|
11
|
-
for (let i = 0; i < argv.length; i++) {
|
|
12
|
-
const arg = argv[i];
|
|
13
|
-
if (arg.startsWith('--')) {
|
|
14
|
-
const key = arg.slice(2);
|
|
15
|
-
const eqIdx = key.indexOf('=');
|
|
16
|
-
if (eqIdx !== -1) {
|
|
17
|
-
flags[key.slice(0, eqIdx)] = key.slice(eqIdx + 1);
|
|
18
|
-
} else {
|
|
19
|
-
const next = argv[i + 1];
|
|
20
|
-
if (next && !next.startsWith('--')) {
|
|
21
|
-
flags[key] = next;
|
|
22
|
-
i++;
|
|
23
|
-
} else {
|
|
24
|
-
flags[key] = true;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
} else {
|
|
28
|
-
command.push(arg);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return { command, flags };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
describe('CLI argument parser', () => {
|
|
35
|
-
it('should parse simple commands', () => {
|
|
36
|
-
const { command, flags } = parseArgs(['start']);
|
|
37
|
-
expect(command).toEqual(['start']);
|
|
38
|
-
expect(flags).toEqual({});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should parse commands with subcommands', () => {
|
|
42
|
-
const { command } = parseArgs(['get', 'tree']);
|
|
43
|
-
expect(command).toEqual(['get', 'tree']);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should parse --flag=value', () => {
|
|
47
|
-
const { flags } = parseArgs(['start', '--port=8098']);
|
|
48
|
-
expect(flags['port']).toBe('8098');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should parse --flag value', () => {
|
|
52
|
-
const { flags } = parseArgs(['start', '--port', '8098']);
|
|
53
|
-
expect(flags['port']).toBe('8098');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('should parse boolean flags', () => {
|
|
57
|
-
const { flags } = parseArgs(['find', 'User', '--exact']);
|
|
58
|
-
expect(flags['exact']).toBe(true);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should parse mixed args', () => {
|
|
62
|
-
const { command, flags } = parseArgs([
|
|
63
|
-
'profile',
|
|
64
|
-
'slow',
|
|
65
|
-
'--limit',
|
|
66
|
-
'5',
|
|
67
|
-
]);
|
|
68
|
-
expect(command).toEqual(['profile', 'slow']);
|
|
69
|
-
expect(flags['limit']).toBe('5');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should parse component id as positional arg', () => {
|
|
73
|
-
const { command } = parseArgs(['get', 'component', '42']);
|
|
74
|
-
expect(command).toEqual(['get', 'component', '42']);
|
|
75
|
-
});
|
|
76
|
-
});
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { ComponentTree } from '../component-tree.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Operations encoding reference (protocol v2):
|
|
6
|
-
* [rendererID, rootFiberID, stringTableSize, ...stringTable, ...ops]
|
|
7
|
-
*
|
|
8
|
-
* String table: for each string, [length, ...charCodes]. String ID 0 = null.
|
|
9
|
-
*
|
|
10
|
-
* TREE_OPERATION_ADD (1):
|
|
11
|
-
* 1, id, elementType, parentId, ownerID, displayNameStringID, keyStringID
|
|
12
|
-
*
|
|
13
|
-
* Element types (from react-devtools-shared/src/frontend/types.js):
|
|
14
|
-
* CLASS=1, FUNCTION=5, FORWARD_REF=6, HOST=7, MEMO=8, PROFILER=10, ROOT=11, SUSPENSE=12
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/** Build a string table and return [tableData, stringIdMap] */
|
|
18
|
-
function buildStringTable(strings: string[]): [number[], Map<string, number>] {
|
|
19
|
-
const idMap = new Map<string, number>();
|
|
20
|
-
const data: number[] = [];
|
|
21
|
-
for (const s of strings) {
|
|
22
|
-
const id = idMap.size + 1; // 0 is reserved for null
|
|
23
|
-
if (!idMap.has(s)) {
|
|
24
|
-
idMap.set(s, id);
|
|
25
|
-
data.push(s.length, ...Array.from(s).map((c) => c.charCodeAt(0)));
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return [data, idMap];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Build a complete operations array with string table */
|
|
32
|
-
function buildOps(
|
|
33
|
-
rendererID: number,
|
|
34
|
-
rootID: number,
|
|
35
|
-
strings: string[],
|
|
36
|
-
opsFn: (strId: (s: string) => number) => number[],
|
|
37
|
-
): number[] {
|
|
38
|
-
const [tableData, idMap] = buildStringTable(strings);
|
|
39
|
-
const strId = (s: string) => idMap.get(s) || 0;
|
|
40
|
-
const ops = opsFn(strId);
|
|
41
|
-
return [rendererID, rootID, tableData.length, ...tableData, ...ops];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function addOp(
|
|
45
|
-
id: number,
|
|
46
|
-
elementType: number,
|
|
47
|
-
parentId: number,
|
|
48
|
-
displayNameStrId: number,
|
|
49
|
-
keyStrId: number = 0,
|
|
50
|
-
): number[] {
|
|
51
|
-
return [1, id, elementType, parentId, 0, displayNameStrId, keyStrId];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function removeOp(...ids: number[]): number[] {
|
|
55
|
-
return [2, ids.length, ...ids];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function reorderOp(id: number, children: number[]): number[] {
|
|
59
|
-
return [3, id, children.length, ...children];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
describe('ComponentTree', () => {
|
|
63
|
-
let tree: ComponentTree;
|
|
64
|
-
|
|
65
|
-
beforeEach(() => {
|
|
66
|
-
tree = new ComponentTree();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should add nodes from operations', () => {
|
|
70
|
-
const ops = buildOps(1, 100, ['App', 'Header', 'Footer'], (s) => [
|
|
71
|
-
...addOp(1, 5, 0, s('App')), // Function component at root
|
|
72
|
-
...addOp(2, 8, 1, s('Header')), // Memo child
|
|
73
|
-
...addOp(3, 5, 1, s('Footer')), // Function child
|
|
74
|
-
]);
|
|
75
|
-
|
|
76
|
-
tree.applyOperations(ops);
|
|
77
|
-
|
|
78
|
-
expect(tree.getComponentCount()).toBe(3);
|
|
79
|
-
|
|
80
|
-
const node1 = tree.getNode(1);
|
|
81
|
-
expect(node1).toBeDefined();
|
|
82
|
-
expect(node1!.displayName).toBe('App');
|
|
83
|
-
expect(node1!.type).toBe('function');
|
|
84
|
-
expect(node1!.children).toEqual([2, 3]);
|
|
85
|
-
|
|
86
|
-
const node2 = tree.getNode(2);
|
|
87
|
-
expect(node2!.displayName).toBe('Header');
|
|
88
|
-
expect(node2!.type).toBe('memo');
|
|
89
|
-
expect(node2!.parentId).toBe(1);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should handle keys', () => {
|
|
93
|
-
const ops = buildOps(1, 100, ['List', 'Item', 'item-1', 'item-2'], (s) => [
|
|
94
|
-
...addOp(1, 5, 0, s('List')),
|
|
95
|
-
...addOp(2, 5, 1, s('Item'), s('item-1')),
|
|
96
|
-
...addOp(3, 5, 1, s('Item'), s('item-2')),
|
|
97
|
-
]);
|
|
98
|
-
|
|
99
|
-
tree.applyOperations(ops);
|
|
100
|
-
|
|
101
|
-
expect(tree.getNode(2)!.key).toBe('item-1');
|
|
102
|
-
expect(tree.getNode(3)!.key).toBe('item-2');
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('should remove nodes', () => {
|
|
106
|
-
const addOps = buildOps(1, 100, ['App', 'Child'], (s) => [
|
|
107
|
-
...addOp(1, 5, 0, s('App')),
|
|
108
|
-
...addOp(2, 5, 1, s('Child')),
|
|
109
|
-
]);
|
|
110
|
-
tree.applyOperations(addOps);
|
|
111
|
-
expect(tree.getComponentCount()).toBe(2);
|
|
112
|
-
|
|
113
|
-
// Remove ops still need a string table (can be empty)
|
|
114
|
-
const rmOps = [1, 100, 0, ...removeOp(2)];
|
|
115
|
-
tree.applyOperations(rmOps);
|
|
116
|
-
expect(tree.getComponentCount()).toBe(1);
|
|
117
|
-
expect(tree.getNode(1)!.children).toEqual([]);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should reorder children', () => {
|
|
121
|
-
const ops = buildOps(1, 100, ['App', 'A', 'B', 'C'], (s) => [
|
|
122
|
-
...addOp(1, 5, 0, s('App')),
|
|
123
|
-
...addOp(2, 5, 1, s('A')),
|
|
124
|
-
...addOp(3, 5, 1, s('B')),
|
|
125
|
-
...addOp(4, 5, 1, s('C')),
|
|
126
|
-
]);
|
|
127
|
-
tree.applyOperations(ops);
|
|
128
|
-
expect(tree.getNode(1)!.children).toEqual([2, 3, 4]);
|
|
129
|
-
|
|
130
|
-
const reorderOps = [1, 100, 0, ...reorderOp(1, [4, 2, 3])];
|
|
131
|
-
tree.applyOperations(reorderOps);
|
|
132
|
-
expect(tree.getNode(1)!.children).toEqual([4, 2, 3]);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('should find by name (partial match)', () => {
|
|
136
|
-
const ops = buildOps(1, 100, ['App', 'UserProfile', 'UserCard', 'Footer'], (s) => [
|
|
137
|
-
...addOp(1, 5, 0, s('App')),
|
|
138
|
-
...addOp(2, 5, 1, s('UserProfile')),
|
|
139
|
-
...addOp(3, 5, 1, s('UserCard')),
|
|
140
|
-
...addOp(4, 5, 1, s('Footer')),
|
|
141
|
-
]);
|
|
142
|
-
tree.applyOperations(ops);
|
|
143
|
-
|
|
144
|
-
const results = tree.findByName('user');
|
|
145
|
-
expect(results).toHaveLength(2);
|
|
146
|
-
expect(results.map((r) => r.displayName).sort()).toEqual([
|
|
147
|
-
'UserCard',
|
|
148
|
-
'UserProfile',
|
|
149
|
-
]);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('should find by name (exact match)', () => {
|
|
153
|
-
const ops = buildOps(1, 100, ['App', 'UserProfile', 'UserCard'], (s) => [
|
|
154
|
-
...addOp(1, 5, 0, s('App')),
|
|
155
|
-
...addOp(2, 5, 1, s('UserProfile')),
|
|
156
|
-
...addOp(3, 5, 1, s('UserCard')),
|
|
157
|
-
]);
|
|
158
|
-
tree.applyOperations(ops);
|
|
159
|
-
|
|
160
|
-
const results = tree.findByName('UserProfile', true);
|
|
161
|
-
expect(results).toHaveLength(1);
|
|
162
|
-
expect(results[0].displayName).toBe('UserProfile');
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('should get count by type', () => {
|
|
166
|
-
const ops = buildOps(1, 100, ['App', 'MemoComp', 'FuncComp', 'div'], (s) => [
|
|
167
|
-
...addOp(1, 5, 0, s('App')), // function
|
|
168
|
-
...addOp(2, 8, 1, s('MemoComp')), // memo
|
|
169
|
-
...addOp(3, 5, 1, s('FuncComp')), // function
|
|
170
|
-
...addOp(4, 7, 1, s('div')), // host
|
|
171
|
-
]);
|
|
172
|
-
tree.applyOperations(ops);
|
|
173
|
-
|
|
174
|
-
const counts = tree.getCountByType();
|
|
175
|
-
expect(counts['function']).toBe(2);
|
|
176
|
-
expect(counts['memo']).toBe(1);
|
|
177
|
-
expect(counts['host']).toBe(1);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('should get tree with depth limit', () => {
|
|
181
|
-
const ops = buildOps(1, 100, ['App', 'Level1', 'Level2', 'Level3'], (s) => [
|
|
182
|
-
...addOp(1, 5, 0, s('App')),
|
|
183
|
-
...addOp(2, 5, 1, s('Level1')),
|
|
184
|
-
...addOp(3, 5, 2, s('Level2')),
|
|
185
|
-
...addOp(4, 5, 3, s('Level3')),
|
|
186
|
-
]);
|
|
187
|
-
tree.applyOperations(ops);
|
|
188
|
-
|
|
189
|
-
const fullTree = tree.getTree();
|
|
190
|
-
expect(fullTree).toHaveLength(4);
|
|
191
|
-
|
|
192
|
-
const shallow = tree.getTree(1);
|
|
193
|
-
expect(shallow).toHaveLength(2);
|
|
194
|
-
expect(shallow.map((n) => n.displayName)).toEqual(['App', 'Level1']);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('should handle empty operations', () => {
|
|
198
|
-
tree.applyOperations([]);
|
|
199
|
-
expect(tree.getComponentCount()).toBe(0);
|
|
200
|
-
|
|
201
|
-
tree.applyOperations([1]);
|
|
202
|
-
expect(tree.getComponentCount()).toBe(0);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it('should handle all element types', () => {
|
|
206
|
-
const ops = buildOps(
|
|
207
|
-
1, 100,
|
|
208
|
-
['ClassComp', 'FuncComp', 'ForwardRefComp', 'HostComp', 'MemoComp', 'ProfilerComp', 'SuspenseComp'],
|
|
209
|
-
(s) => [
|
|
210
|
-
...addOp(1, 1, 0, s('ClassComp')), // class = 1
|
|
211
|
-
...addOp(2, 5, 1, s('FuncComp')), // function = 5
|
|
212
|
-
...addOp(3, 6, 1, s('ForwardRefComp')), // forwardRef = 6
|
|
213
|
-
...addOp(4, 7, 1, s('HostComp')), // host = 7
|
|
214
|
-
...addOp(5, 8, 1, s('MemoComp')), // memo = 8
|
|
215
|
-
...addOp(6, 10, 1, s('ProfilerComp')), // profiler = 10
|
|
216
|
-
...addOp(7, 12, 1, s('SuspenseComp')), // suspense = 12
|
|
217
|
-
],
|
|
218
|
-
);
|
|
219
|
-
tree.applyOperations(ops);
|
|
220
|
-
|
|
221
|
-
expect(tree.getNode(1)!.type).toBe('class');
|
|
222
|
-
expect(tree.getNode(2)!.type).toBe('function');
|
|
223
|
-
expect(tree.getNode(3)!.type).toBe('forwardRef');
|
|
224
|
-
expect(tree.getNode(4)!.type).toBe('host');
|
|
225
|
-
expect(tree.getNode(5)!.type).toBe('memo');
|
|
226
|
-
expect(tree.getNode(6)!.type).toBe('profiler');
|
|
227
|
-
expect(tree.getNode(7)!.type).toBe('suspense');
|
|
228
|
-
});
|
|
229
|
-
});
|