execbro 1.10.22 → 1.10.24

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.
Files changed (87) hide show
  1. package/README.md +2 -1
  2. package/build/__tests__/unit/componentSource.test.d.ts +2 -0
  3. package/build/__tests__/unit/componentSource.test.d.ts.map +1 -0
  4. package/build/__tests__/unit/componentSource.test.js +67 -0
  5. package/build/__tests__/unit/componentSource.test.js.map +1 -0
  6. package/build/__tests__/unit/componentSourceResolve.test.d.ts +2 -0
  7. package/build/__tests__/unit/componentSourceResolve.test.d.ts.map +1 -0
  8. package/build/__tests__/unit/componentSourceResolve.test.js +91 -0
  9. package/build/__tests__/unit/componentSourceResolve.test.js.map +1 -0
  10. package/build/__tests__/unit/inspectorHarvest.test.d.ts +2 -0
  11. package/build/__tests__/unit/inspectorHarvest.test.d.ts.map +1 -0
  12. package/build/__tests__/unit/inspectorHarvest.test.js +61 -0
  13. package/build/__tests__/unit/inspectorHarvest.test.js.map +1 -0
  14. package/build/__tests__/unit/inspectorSource.test.d.ts +2 -0
  15. package/build/__tests__/unit/inspectorSource.test.d.ts.map +1 -0
  16. package/build/__tests__/unit/inspectorSource.test.js +74 -0
  17. package/build/__tests__/unit/inspectorSource.test.js.map +1 -0
  18. package/build/__tests__/unit/selectionBuffer.test.d.ts +2 -0
  19. package/build/__tests__/unit/selectionBuffer.test.d.ts.map +1 -0
  20. package/build/__tests__/unit/selectionBuffer.test.js +86 -0
  21. package/build/__tests__/unit/selectionBuffer.test.js.map +1 -0
  22. package/build/__tests__/unit/selectionPoller.test.d.ts +2 -0
  23. package/build/__tests__/unit/selectionPoller.test.d.ts.map +1 -0
  24. package/build/__tests__/unit/selectionPoller.test.js +109 -0
  25. package/build/__tests__/unit/selectionPoller.test.js.map +1 -0
  26. package/build/__tests__/unit/symbolicate.test.d.ts +2 -0
  27. package/build/__tests__/unit/symbolicate.test.d.ts.map +1 -0
  28. package/build/__tests__/unit/symbolicate.test.js +154 -0
  29. package/build/__tests__/unit/symbolicate.test.js.map +1 -0
  30. package/build/__tests__/unit/toolsJson.test.d.ts +2 -0
  31. package/build/__tests__/unit/toolsJson.test.d.ts.map +1 -0
  32. package/build/__tests__/unit/toolsJson.test.js +24 -0
  33. package/build/__tests__/unit/toolsJson.test.js.map +1 -0
  34. package/build/core/componentSource.d.ts +62 -0
  35. package/build/core/componentSource.d.ts.map +1 -0
  36. package/build/core/componentSource.js +268 -0
  37. package/build/core/componentSource.js.map +1 -0
  38. package/build/core/connection.d.ts.map +1 -1
  39. package/build/core/connection.js +4 -0
  40. package/build/core/connection.js.map +1 -1
  41. package/build/core/debugGlobals.d.ts.map +1 -1
  42. package/build/core/debugGlobals.js +3 -1
  43. package/build/core/debugGlobals.js.map +1 -1
  44. package/build/core/guides.js +1 -1
  45. package/build/core/index.d.ts +4 -1
  46. package/build/core/index.d.ts.map +1 -1
  47. package/build/core/index.js +4 -1
  48. package/build/core/index.js.map +1 -1
  49. package/build/core/inspector.d.ts +24 -0
  50. package/build/core/inspector.d.ts.map +1 -1
  51. package/build/core/inspector.js +58 -0
  52. package/build/core/inspector.js.map +1 -1
  53. package/build/core/sdkBridge.d.ts +8 -8
  54. package/build/core/sdkBridge.d.ts.map +1 -1
  55. package/build/core/sdkBridge.js +20 -20
  56. package/build/core/sdkBridge.js.map +1 -1
  57. package/build/core/selectionBuffer.d.ts +42 -0
  58. package/build/core/selectionBuffer.d.ts.map +1 -0
  59. package/build/core/selectionBuffer.js +51 -0
  60. package/build/core/selectionBuffer.js.map +1 -0
  61. package/build/core/selectionPoller.d.ts +13 -0
  62. package/build/core/selectionPoller.d.ts.map +1 -0
  63. package/build/core/selectionPoller.js +102 -0
  64. package/build/core/selectionPoller.js.map +1 -0
  65. package/build/core/symbolicate.d.ts +18 -0
  66. package/build/core/symbolicate.d.ts.map +1 -0
  67. package/build/core/symbolicate.js +119 -0
  68. package/build/core/symbolicate.js.map +1 -0
  69. package/build/tools/componentTools.d.ts.map +1 -1
  70. package/build/tools/componentTools.js +78 -9
  71. package/build/tools/componentTools.js.map +1 -1
  72. package/build/tools/connectionTools.d.ts.map +1 -1
  73. package/build/tools/connectionTools.js +3 -1
  74. package/build/tools/connectionTools.js.map +1 -1
  75. package/build/tools/interactionTools.d.ts.map +1 -1
  76. package/build/tools/interactionTools.js +1 -0
  77. package/build/tools/interactionTools.js.map +1 -1
  78. package/build/tools/logTools.d.ts.map +1 -1
  79. package/build/tools/logTools.js +26 -17
  80. package/build/tools/logTools.js.map +1 -1
  81. package/build/tools/networkTools.d.ts.map +1 -1
  82. package/build/tools/networkTools.js +15 -20
  83. package/build/tools/networkTools.js.map +1 -1
  84. package/build/tools/screenshotTools.d.ts.map +1 -1
  85. package/build/tools/screenshotTools.js +3 -0
  86. package/build/tools/screenshotTools.js.map +1 -1
  87. package/package.json +65 -64
package/README.md CHANGED
@@ -34,6 +34,7 @@ Linking your installation here is also **required to unlock [ExecBro Pro](#prici
34
34
  - **JavaScript Execution** - Run code directly in your app (REPL-style) and inspect results
35
35
  - **Global State Debugging** - Discover and inspect Apollo Client, Redux stores, Expo Router, and custom globals. Wire stores and other app internals straight into the agent with the optional [SDK](#install-the-sdk-recommended) for direct, reliable state access
36
36
  - **Bundle Error Detection** - Get Metro bundler errors and compilation issues with file locations
37
+ - **Tap-to-Source** - Point at any element with RN's Element Inspector and get back the absolute source file and line where it is rendered, resolved from the React fiber's `_debugStack` via Metro symbolication. Works on React 19, where `_debugSource` no longer exists. Selections are buffered in the background, so taps you make during a manual inspector session are captured without asking the agent first
37
38
 
38
39
  ### Device Control
39
40
 
@@ -57,7 +58,7 @@ Linking your installation here is also **required to unlock [ExecBro Pro](#prici
57
58
  - **Auto-Discovery** - Scans Metro on ports 8081, 8082, 19000-19002 automatically
58
59
  - **Multi-Device Support** - Connects to all Bridgeless targets simultaneously, with per-device log and network buffers
59
60
  - **Auto-Reconnection** - Exponential backoff (up to 8 attempts) when connection drops
60
- - **Efficient Buffering** - Circular buffers: 500 logs, 200 network requests
61
+ - **Efficient Buffering** - Circular buffers: 500 logs, 200 network requests, 100 inspector selections
61
62
  - **Platform Support** - Expo SDK 54+ (Bridgeless) and React Native 0.70+ (Hermes)
62
63
 
63
64
  ## Setup
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=componentSource.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"componentSource.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/componentSource.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,67 @@
1
+ import { describe, it, expect } from "@jest/globals";
2
+ import { buildSelectionProbeExpression, buildDebugStackHarvestExpression, } from "../../core/componentSource.js";
3
+ describe("buildSelectionProbeExpression", () => {
4
+ const expr = buildSelectionProbeExpression();
5
+ it("looks up the React DevTools hook and iterates renderer ids", () => {
6
+ expect(expr).toContain("__REACT_DEVTOOLS_GLOBAL_HOOK__");
7
+ expect(expr).toContain("getFiberRoots");
8
+ expect(expr).toContain("hook.renderers");
9
+ expect(expr).not.toMatch(/getFiberRoots\(\s*2\s*\)/);
10
+ });
11
+ it("searches for InspectorPanel", () => {
12
+ expect(expr).toContain("InspectorPanel");
13
+ });
14
+ it("compares by object identity against the global stash", () => {
15
+ expect(expr).toContain("__execbro_lastInspected");
16
+ expect(expr).toContain("!==");
17
+ });
18
+ it("does not measure anything (must stay cheap per tick)", () => {
19
+ expect(expr).not.toContain("measureInWindow");
20
+ });
21
+ it("is Hermes-safe: no arrow functions, template literals or optional chaining", () => {
22
+ expect(expr).not.toContain("=>");
23
+ expect(expr).not.toContain("?.");
24
+ expect(expr).not.toContain("`");
25
+ });
26
+ it("is ASCII-only", () => {
27
+ // eslint-disable-next-line no-control-regex
28
+ expect(/^[\x00-\x7F]*$/.test(expr)).toBe(true);
29
+ });
30
+ });
31
+ describe("buildDebugStackHarvestExpression", () => {
32
+ const expr = buildDebugStackHarvestExpression(210, 450);
33
+ it("evaluates to a Promise", () => {
34
+ expect(expr.trim().startsWith("new Promise(")).toBe(true);
35
+ });
36
+ it("interpolates the target coordinates", () => {
37
+ expect(expr).toContain("210");
38
+ expect(expr).toContain("450");
39
+ });
40
+ it("reads _debugStack and falls back to _debugSource", () => {
41
+ expect(expr).toContain("_debugStack");
42
+ expect(expr).toContain("_debugSource");
43
+ });
44
+ it("walks the return chain", () => {
45
+ expect(expr).toContain(".return");
46
+ });
47
+ it("measures host fibers to hit-test", () => {
48
+ expect(expr).toContain("measureInWindow");
49
+ });
50
+ it("handles Fabric publicInstance and string-typed host fibers", () => {
51
+ expect(expr).toContain("publicInstance");
52
+ expect(expr).toMatch(/typeof\s+\w+(\.\w+)*\s*===\s*['"]string['"]/);
53
+ });
54
+ it("defaults the ancestor cap to 30 and honours an override", () => {
55
+ // Real trees bury user components deep behind library wrappers — a tapped
56
+ // SearchBar measured 12 ancestors above the hit fiber, HomeScreen 22.
57
+ expect(buildDebugStackHarvestExpression(1, 2)).toContain("maxAncestors = 30");
58
+ expect(buildDebugStackHarvestExpression(1, 2, 3)).toContain("maxAncestors = 3");
59
+ });
60
+ it("is Hermes-safe and ASCII-only", () => {
61
+ expect(expr).not.toContain("=>");
62
+ expect(expr).not.toContain("?.");
63
+ // eslint-disable-next-line no-control-regex
64
+ expect(/^[\x00-\x7F]*$/.test(expr)).toBe(true);
65
+ });
66
+ });
67
+ //# sourceMappingURL=componentSource.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"componentSource.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/componentSource.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=componentSourceResolve.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"componentSourceResolve.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/componentSourceResolve.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,91 @@
1
+ import { describe, it, expect, beforeEach, jest } from "@jest/globals";
2
+ import { resolveStacksToSource } from "../../core/componentSource.js";
3
+ import { clearSymbolicateCache } from "../../core/symbolicate.js";
4
+ const BUNDLE = "http://192.168.0.102:8081/AppEntry.bundle//&platform=ios&dev=true";
5
+ function stackFor(renderSiteLine) {
6
+ return [
7
+ "Error: react-stack-top-frame",
8
+ ` at anonymous (${BUNDLE}:19648:90)`,
9
+ ` at HomeScreen (${BUNDLE}:${renderSiteLine}:64)`,
10
+ ].join("\n");
11
+ }
12
+ describe("resolveStacksToSource", () => {
13
+ beforeEach(() => {
14
+ clearSymbolicateCache();
15
+ globalThis.fetch = jest.fn();
16
+ });
17
+ it("resolves the render-site frame (index 1) to a user source file", async () => {
18
+ globalThis.fetch.mockResolvedValue({
19
+ ok: true,
20
+ json: async () => ({
21
+ stack: [
22
+ { file: "/x/src/HomeScreen.tsx", lineNumber: 105, column: 10, methodName: "HomeScreen", collapse: false },
23
+ ],
24
+ }),
25
+ });
26
+ const out = await resolveStacksToSource([{ component: "SearchBar", stack: stackFor(342013) }]);
27
+ expect(out.source).toEqual({ file: "/x/src/HomeScreen.tsx", line: 105, column: 10 });
28
+ expect(out.sourceUnavailable).toBeUndefined();
29
+ // Only the render-site frame is sent, not the jsx-runtime frame.
30
+ const sent = JSON.parse(globalThis.fetch.mock.calls[0][1].body);
31
+ expect(sent.stack.length).toBe(1);
32
+ expect(sent.stack[0].lineNumber).toBe(342013);
33
+ });
34
+ it("returns ancestors for each component with a resolved frame", async () => {
35
+ globalThis.fetch.mockResolvedValue({
36
+ ok: true,
37
+ json: async () => ({
38
+ stack: [
39
+ { file: "/x/src/HomeScreen.tsx", lineNumber: 105, column: 10, methodName: "HomeScreen", collapse: false },
40
+ { file: "/x/src/HomeScreen.tsx", lineNumber: 101, column: 8, methodName: "HomeScreen", collapse: false },
41
+ ],
42
+ }),
43
+ });
44
+ const out = await resolveStacksToSource([
45
+ { component: "SearchBar", stack: stackFor(342013) },
46
+ { component: "HomeScreen", stack: stackFor(342005) },
47
+ ]);
48
+ expect(out.ancestors).toEqual([
49
+ { component: "SearchBar", file: "/x/src/HomeScreen.tsx", line: 105 },
50
+ { component: "HomeScreen", file: "/x/src/HomeScreen.tsx", line: 101 },
51
+ ]);
52
+ });
53
+ it("uses _debugSource directly without calling Metro", async () => {
54
+ const out = await resolveStacksToSource([
55
+ { component: "SearchBar", stack: "", file: "/x/src/HomeScreen.tsx", lineNumber: 105, column: 10 },
56
+ ]);
57
+ expect(out.source).toEqual({ file: "/x/src/HomeScreen.tsx", line: 105, column: 10 });
58
+ expect(globalThis.fetch.mock.calls.length).toBe(0);
59
+ });
60
+ it("reports library-only when every frame is collapsed", async () => {
61
+ globalThis.fetch.mockResolvedValue({
62
+ ok: true,
63
+ json: async () => ({
64
+ stack: [
65
+ { file: "/x/node_modules/rn/ScrollView.js", lineNumber: 1942, column: 4, methodName: "Wrapper", collapse: true },
66
+ ],
67
+ }),
68
+ });
69
+ const out = await resolveStacksToSource([{ component: "ScrollView", stack: stackFor(70218) }]);
70
+ expect(out.source).toBeNull();
71
+ expect(out.sourceUnavailable).toBe("library-only");
72
+ });
73
+ it("reports symbolicate-unreachable when Metro is down", async () => {
74
+ globalThis.fetch.mockRejectedValue(new Error("ECONNREFUSED"));
75
+ const out = await resolveStacksToSource([{ component: "SearchBar", stack: stackFor(342013) }]);
76
+ expect(out.source).toBeNull();
77
+ expect(out.sourceUnavailable).toBe("symbolicate-unreachable");
78
+ expect(out.ancestors).toEqual([]);
79
+ });
80
+ it("reports no-debug-stack for an empty stack list", async () => {
81
+ const out = await resolveStacksToSource([]);
82
+ expect(out.source).toBeNull();
83
+ expect(out.sourceUnavailable).toBe("no-debug-stack");
84
+ });
85
+ it("reports no-debug-stack when stacks have no usable frames", async () => {
86
+ const out = await resolveStacksToSource([{ component: "X", stack: "Error: nothing here" }]);
87
+ expect(out.source).toBeNull();
88
+ expect(out.sourceUnavailable).toBe("no-debug-stack");
89
+ });
90
+ });
91
+ //# sourceMappingURL=componentSourceResolve.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"componentSourceResolve.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/componentSourceResolve.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=inspectorHarvest.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspectorHarvest.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/inspectorHarvest.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,61 @@
1
+ import { describe, it, expect, beforeEach, jest } from "@jest/globals";
2
+ const executeInApp = jest.fn();
3
+ jest.unstable_mockModule("../../core/jsExecute.js", () => ({
4
+ executeInApp,
5
+ markConnectionEstablished: jest.fn(),
6
+ }));
7
+ const { harvestStacksAtPoint, harvestStacksAtFrame } = await import("../../core/inspector.js");
8
+ const OK = {
9
+ success: true,
10
+ result: JSON.stringify({ stacks: [{ component: "Text", stack: "Error\n at a (http://h:8081/b:1:1)" }] }),
11
+ };
12
+ describe("harvestStacksAtPoint", () => {
13
+ beforeEach(() => {
14
+ executeInApp.mockReset();
15
+ });
16
+ it("returns the harvested stacks", async () => {
17
+ executeInApp.mockResolvedValueOnce(OK);
18
+ const out = await harvestStacksAtPoint(210, 450);
19
+ expect(out).toEqual([{ component: "Text", stack: "Error\n at a (http://h:8081/b:1:1)" }]);
20
+ });
21
+ it("passes the coordinates into the expression", async () => {
22
+ executeInApp.mockResolvedValueOnce(OK);
23
+ await harvestStacksAtPoint(210, 450);
24
+ const expr = executeInApp.mock.calls[0][0];
25
+ expect(expr).toContain("210");
26
+ expect(expr).toContain("450");
27
+ });
28
+ it("returns an empty list when the evaluate fails", async () => {
29
+ executeInApp.mockResolvedValueOnce({ success: false, error: "disconnected" });
30
+ expect(await harvestStacksAtPoint(1, 2)).toEqual([]);
31
+ });
32
+ it("returns an empty list on unparseable output", async () => {
33
+ executeInApp.mockResolvedValueOnce({ success: true, result: "not json" });
34
+ expect(await harvestStacksAtPoint(1, 2)).toEqual([]);
35
+ });
36
+ it("returns an empty list when the evaluate throws", async () => {
37
+ executeInApp.mockRejectedValueOnce(new Error("boom"));
38
+ expect(await harvestStacksAtPoint(1, 2)).toEqual([]);
39
+ });
40
+ });
41
+ describe("harvestStacksAtFrame", () => {
42
+ beforeEach(() => {
43
+ executeInApp.mockReset();
44
+ });
45
+ it("harvests at the centre of the frame", async () => {
46
+ executeInApp.mockResolvedValueOnce(OK);
47
+ await harvestStacksAtFrame({ left: 100, top: 200, width: 50, height: 20 });
48
+ const expr = executeInApp.mock.calls[0][0];
49
+ expect(expr).toContain("125");
50
+ expect(expr).toContain("210");
51
+ });
52
+ it("returns an empty list without evaluating for a null frame", async () => {
53
+ expect(await harvestStacksAtFrame(null)).toEqual([]);
54
+ expect(executeInApp.mock.calls.length).toBe(0);
55
+ });
56
+ it("returns an empty list without evaluating for an incomplete frame", async () => {
57
+ expect(await harvestStacksAtFrame({ left: 1, top: 2 })).toEqual([]);
58
+ expect(executeInApp.mock.calls.length).toBe(0);
59
+ });
60
+ });
61
+ //# sourceMappingURL=inspectorHarvest.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspectorHarvest.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/inspectorHarvest.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=inspectorSource.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspectorSource.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/inspectorSource.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,74 @@
1
+ import { describe, it, expect, beforeEach, jest } from "@jest/globals";
2
+ import { enrichWithSource, getSelectionHistory } from "../../core/inspector.js";
3
+ import { selectionBuffer } from "../../core/selectionBuffer.js";
4
+ import { clearSymbolicateCache } from "../../core/symbolicate.js";
5
+ const BUNDLE = "http://192.168.0.102:8081/AppEntry.bundle//&platform=ios";
6
+ const STACK = ["Error: top", ` at anonymous (${BUNDLE}:1:1)`, ` at HomeScreen (${BUNDLE}:342013:64)`].join("\n");
7
+ describe("enrichWithSource", () => {
8
+ beforeEach(() => {
9
+ clearSymbolicateCache();
10
+ globalThis.fetch = jest.fn();
11
+ });
12
+ it("adds source and ancestors to the payload", async () => {
13
+ globalThis.fetch.mockResolvedValue({
14
+ ok: true,
15
+ json: async () => ({
16
+ stack: [{ file: "/x/Home.tsx", lineNumber: 105, column: 10, methodName: "HomeScreen", collapse: false }],
17
+ }),
18
+ });
19
+ const out = await enrichWithSource({ element: "Text" }, [{ component: "Text", stack: STACK }]);
20
+ expect(out.element).toBe("Text");
21
+ expect(out.source).toEqual({ file: "/x/Home.tsx", line: 105, column: 10 });
22
+ expect(out.ancestors).toEqual([{ component: "Text", file: "/x/Home.tsx", line: 105 }]);
23
+ });
24
+ it("preserves the payload and attaches a reason when unavailable", async () => {
25
+ const out = await enrichWithSource({ element: "Text", path: "a > b" }, []);
26
+ expect(out.element).toBe("Text");
27
+ expect(out.path).toBe("a > b");
28
+ expect(out.source).toBeUndefined();
29
+ expect(out.sourceUnavailable).toBe("no-debug-stack");
30
+ });
31
+ it("never throws when Metro is unreachable", async () => {
32
+ globalThis.fetch.mockRejectedValue(new Error("ECONNREFUSED"));
33
+ const out = await enrichWithSource({ element: "Text" }, [{ component: "Text", stack: STACK }]);
34
+ expect(out.element).toBe("Text");
35
+ expect(out.sourceUnavailable).toBe("symbolicate-unreachable");
36
+ });
37
+ });
38
+ describe("getSelectionHistory", () => {
39
+ beforeEach(() => {
40
+ clearSymbolicateCache();
41
+ selectionBuffer.clear();
42
+ globalThis.fetch = jest.fn();
43
+ });
44
+ it("returns buffered entries newest first with resolved source", async () => {
45
+ globalThis.fetch.mockResolvedValue({
46
+ ok: true,
47
+ json: async () => ({
48
+ stack: [{ file: "/x/Home.tsx", lineNumber: 105, column: 10, methodName: "HomeScreen", collapse: false }],
49
+ }),
50
+ });
51
+ selectionBuffer.add({
52
+ id: "s1", device: "iPhone Air", timestamp: 1,
53
+ element: "Text", path: "App > Text", hierarchy: ["App", "Text"],
54
+ frame: { left: 1, top: 2, width: 3, height: 4 }, style: null,
55
+ stacks: [{ component: "Text", stack: STACK }],
56
+ });
57
+ const out = await getSelectionHistory({ limit: 5 });
58
+ expect(out.length).toBe(1);
59
+ expect(out[0].element).toBe("Text");
60
+ expect(out[0].source).toEqual({ file: "/x/Home.tsx", line: 105, column: 10 });
61
+ });
62
+ it("returns an empty array when the buffer is empty", async () => {
63
+ expect(await getSelectionHistory({})).toEqual([]);
64
+ });
65
+ it("filters by device", async () => {
66
+ selectionBuffer.add({
67
+ id: "s1", device: "Pixel 7", timestamp: 1, element: "A", path: "A",
68
+ hierarchy: ["A"], frame: null, style: null, stacks: [],
69
+ });
70
+ expect((await getSelectionHistory({ device: "iPhone Air" })).length).toBe(0);
71
+ expect((await getSelectionHistory({ device: "Pixel 7" })).length).toBe(1);
72
+ });
73
+ });
74
+ //# sourceMappingURL=inspectorSource.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspectorSource.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/inspectorSource.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=selectionBuffer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectionBuffer.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/selectionBuffer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,86 @@
1
+ import { describe, it, expect, beforeEach } from "@jest/globals";
2
+ import { SelectionBuffer } from "../../core/selectionBuffer.js";
3
+ let counter = 0;
4
+ function makeEntry(overrides = {}) {
5
+ counter += 1;
6
+ return {
7
+ id: overrides.id ?? `sel-${counter}`,
8
+ device: overrides.device ?? "iPhone Air",
9
+ timestamp: overrides.timestamp ?? 1000 + counter,
10
+ element: overrides.element ?? "Text",
11
+ path: overrides.path ?? "App > HomeScreen > Text",
12
+ hierarchy: overrides.hierarchy ?? ["App", "HomeScreen", "Text"],
13
+ frame: overrides.frame ?? { left: 10, top: 20, width: 30, height: 40 },
14
+ style: overrides.style ?? null,
15
+ stacks: overrides.stacks ?? [],
16
+ };
17
+ }
18
+ describe("SelectionBuffer", () => {
19
+ let buffer;
20
+ beforeEach(() => {
21
+ buffer = new SelectionBuffer(3);
22
+ });
23
+ it("starts empty", () => {
24
+ expect(buffer.size).toBe(0);
25
+ expect(buffer.list()).toEqual([]);
26
+ expect(buffer.latest()).toBeUndefined();
27
+ });
28
+ it("adds an entry and reports it as accepted", () => {
29
+ expect(buffer.add(makeEntry({ id: "a" }))).toBe(true);
30
+ expect(buffer.size).toBe(1);
31
+ });
32
+ it("evicts oldest beyond max size", () => {
33
+ buffer.add(makeEntry({ id: "a", path: "p-a" }));
34
+ buffer.add(makeEntry({ id: "b", path: "p-b" }));
35
+ buffer.add(makeEntry({ id: "c", path: "p-c" }));
36
+ buffer.add(makeEntry({ id: "d", path: "p-d" }));
37
+ expect(buffer.size).toBe(3);
38
+ expect(buffer.list().map((e) => e.id)).toEqual(["d", "c", "b"]);
39
+ });
40
+ it("lists newest first", () => {
41
+ buffer.add(makeEntry({ id: "a", path: "p-a" }));
42
+ buffer.add(makeEntry({ id: "b", path: "p-b" }));
43
+ expect(buffer.list().map((e) => e.id)).toEqual(["b", "a"]);
44
+ });
45
+ it("rejects a duplicate of the most recent entry on the same device", () => {
46
+ buffer.add(makeEntry({ id: "a", path: "same", frame: { left: 1, top: 2, width: 3, height: 4 } }));
47
+ const accepted = buffer.add(makeEntry({ id: "b", path: "same", frame: { left: 1, top: 2, width: 3, height: 4 } }));
48
+ expect(accepted).toBe(false);
49
+ expect(buffer.size).toBe(1);
50
+ });
51
+ it("accepts a repeat of the same element on a different device", () => {
52
+ buffer.add(makeEntry({ id: "a", path: "same", device: "iPhone Air" }));
53
+ const accepted = buffer.add(makeEntry({ id: "b", path: "same", device: "Pixel 7" }));
54
+ expect(accepted).toBe(true);
55
+ expect(buffer.size).toBe(2);
56
+ });
57
+ it("accepts the same path again once another selection intervened", () => {
58
+ buffer.add(makeEntry({ id: "a", path: "one" }));
59
+ buffer.add(makeEntry({ id: "b", path: "two" }));
60
+ expect(buffer.add(makeEntry({ id: "c", path: "one" }))).toBe(true);
61
+ expect(buffer.size).toBe(3);
62
+ });
63
+ it("filters by device", () => {
64
+ buffer.add(makeEntry({ id: "a", device: "iPhone Air", path: "p-a" }));
65
+ buffer.add(makeEntry({ id: "b", device: "Pixel 7", path: "p-b" }));
66
+ expect(buffer.list({ device: "Pixel 7" }).map((e) => e.id)).toEqual(["b"]);
67
+ });
68
+ it("honours limit", () => {
69
+ buffer.add(makeEntry({ id: "a", path: "p-a" }));
70
+ buffer.add(makeEntry({ id: "b", path: "p-b" }));
71
+ expect(buffer.list({ limit: 1 }).map((e) => e.id)).toEqual(["b"]);
72
+ });
73
+ it("latest respects the device filter", () => {
74
+ buffer.add(makeEntry({ id: "a", device: "iPhone Air", path: "p-a" }));
75
+ buffer.add(makeEntry({ id: "b", device: "Pixel 7", path: "p-b" }));
76
+ expect(buffer.latest("iPhone Air").id).toBe("a");
77
+ expect(buffer.latest().id).toBe("b");
78
+ });
79
+ it("clear empties the buffer", () => {
80
+ buffer.add(makeEntry({ id: "a" }));
81
+ buffer.clear();
82
+ expect(buffer.size).toBe(0);
83
+ expect(buffer.latest()).toBeUndefined();
84
+ });
85
+ });
86
+ //# sourceMappingURL=selectionBuffer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectionBuffer.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/selectionBuffer.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=selectionPoller.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectionPoller.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/selectionPoller.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,109 @@
1
+ import { describe, it, expect, beforeEach, afterEach, jest } from "@jest/globals";
2
+ const executeInApp = jest.fn();
3
+ jest.unstable_mockModule("../../core/jsExecute.js", () => ({ executeInApp }));
4
+ const { pollOnce, startSelectionPoller, stopSelectionPoller, isSelectionPollerRunning } = await import("../../core/selectionPoller.js");
5
+ const { selectionBuffer } = await import("../../core/selectionBuffer.js");
6
+ function probeResult(overrides = {}) {
7
+ return {
8
+ success: true,
9
+ result: JSON.stringify({
10
+ active: true,
11
+ selected: true,
12
+ changed: true,
13
+ element: "Text",
14
+ path: "App > HomeScreen > Text",
15
+ hierarchy: ["App", "HomeScreen", "Text"],
16
+ frame: { left: 100, top: 200, width: 50, height: 20 },
17
+ style: { fontSize: 17 },
18
+ ...overrides,
19
+ }),
20
+ };
21
+ }
22
+ function harvestResult() {
23
+ return {
24
+ success: true,
25
+ result: JSON.stringify({
26
+ stacks: [{ component: "Text", stack: "Error\n at anonymous (http://h:8081/b:1:1)" }],
27
+ }),
28
+ };
29
+ }
30
+ describe("selectionPoller", () => {
31
+ beforeEach(() => {
32
+ executeInApp.mockReset();
33
+ selectionBuffer.clear();
34
+ delete process.env.EXECBRO_DISABLE_SELECTION_POLL;
35
+ });
36
+ afterEach(() => {
37
+ stopSelectionPoller("iPhone Air");
38
+ });
39
+ it("buffers an entry when the probe reports a change", async () => {
40
+ executeInApp.mockResolvedValueOnce(probeResult()).mockResolvedValueOnce(harvestResult());
41
+ const buffered = await pollOnce("iPhone Air");
42
+ expect(buffered).toBe(true);
43
+ expect(selectionBuffer.size).toBe(1);
44
+ const entry = selectionBuffer.latest();
45
+ expect(entry.element).toBe("Text");
46
+ expect(entry.device).toBe("iPhone Air");
47
+ expect(entry.stacks.length).toBe(1);
48
+ });
49
+ it("harvests at the centre of the inspected frame", async () => {
50
+ executeInApp.mockResolvedValueOnce(probeResult()).mockResolvedValueOnce(harvestResult());
51
+ await pollOnce("iPhone Air");
52
+ // frame {left:100, top:200, width:50, height:20} -> centre (125, 210)
53
+ const harvestExpr = executeInApp.mock.calls[1][0];
54
+ expect(harvestExpr).toContain("125");
55
+ expect(harvestExpr).toContain("210");
56
+ });
57
+ it("does not harvest when nothing changed", async () => {
58
+ executeInApp.mockResolvedValueOnce({
59
+ success: true,
60
+ result: JSON.stringify({ active: true, selected: true, changed: false }),
61
+ });
62
+ const buffered = await pollOnce("iPhone Air");
63
+ expect(buffered).toBe(false);
64
+ expect(executeInApp.mock.calls.length).toBe(1);
65
+ expect(selectionBuffer.size).toBe(0);
66
+ });
67
+ it("does not harvest when the inspector is not mounted", async () => {
68
+ executeInApp.mockResolvedValueOnce({
69
+ success: true,
70
+ result: JSON.stringify({ active: false, selected: false, changed: false }),
71
+ });
72
+ expect(await pollOnce("iPhone Air")).toBe(false);
73
+ expect(executeInApp.mock.calls.length).toBe(1);
74
+ });
75
+ it("still buffers when the harvest fails, with empty stacks", async () => {
76
+ executeInApp
77
+ .mockResolvedValueOnce(probeResult())
78
+ .mockResolvedValueOnce({ success: false, error: "timeout" });
79
+ expect(await pollOnce("iPhone Air")).toBe(true);
80
+ expect(selectionBuffer.latest().stacks).toEqual([]);
81
+ });
82
+ it("buffers without harvesting when the frame is null", async () => {
83
+ executeInApp.mockResolvedValueOnce(probeResult({ frame: null }));
84
+ expect(await pollOnce("iPhone Air")).toBe(true);
85
+ expect(executeInApp.mock.calls.length).toBe(1);
86
+ expect(selectionBuffer.latest().stacks).toEqual([]);
87
+ });
88
+ it("swallows a failed probe without throwing", async () => {
89
+ executeInApp.mockResolvedValueOnce({ success: false, error: "disconnected" });
90
+ await expect(pollOnce("iPhone Air")).resolves.toBe(false);
91
+ });
92
+ it("swallows unparseable probe output", async () => {
93
+ executeInApp.mockResolvedValueOnce({ success: true, result: "not json" });
94
+ await expect(pollOnce("iPhone Air")).resolves.toBe(false);
95
+ });
96
+ it("does not start when disabled by env var", () => {
97
+ process.env.EXECBRO_DISABLE_SELECTION_POLL = "1";
98
+ startSelectionPoller("iPhone Air");
99
+ expect(isSelectionPollerRunning("iPhone Air")).toBe(false);
100
+ });
101
+ it("start is idempotent and stop halts it", () => {
102
+ startSelectionPoller("iPhone Air");
103
+ startSelectionPoller("iPhone Air");
104
+ expect(isSelectionPollerRunning("iPhone Air")).toBe(true);
105
+ stopSelectionPoller("iPhone Air");
106
+ expect(isSelectionPollerRunning("iPhone Air")).toBe(false);
107
+ });
108
+ });
109
+ //# sourceMappingURL=selectionPoller.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectionPoller.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/selectionPoller.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=symbolicate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbolicate.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/symbolicate.test.ts"],"names":[],"mappings":""}