@nyby/detox-component-testing 1.5.0 → 1.5.1
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/README.md +5 -49
- package/dist/ComponentHarness.d.ts.map +1 -1
- package/dist/ComponentHarness.js +6 -8
- package/dist/ComponentHarness.js.map +1 -1
- package/dist/configureHarness.d.ts +0 -3
- package/dist/configureHarness.d.ts.map +1 -1
- package/dist/configureHarness.js +0 -12
- package/dist/configureHarness.js.map +1 -1
- package/dist/debug.d.ts +8 -0
- package/dist/debug.d.ts.map +1 -1
- package/dist/debug.js +25 -31
- package/dist/debug.js.map +1 -1
- package/dist/environment.js +4 -34
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/ComponentHarness.tsx +10 -16
- package/src/configureHarness.ts +0 -12
- package/src/debug.ts +29 -32
- package/src/environment.js +4 -34
- package/src/index.ts +0 -1
- package/dist/DebugTree.d.ts +0 -20
- package/dist/DebugTree.d.ts.map +0 -1
- package/dist/DebugTree.js +0 -239
- package/dist/DebugTree.js.map +0 -1
- package/src/DebugTree.tsx +0 -280
package/README.md
CHANGED
|
@@ -40,7 +40,6 @@ configureHarness({
|
|
|
40
40
|
wrapper: ({children}) => (
|
|
41
41
|
<View style={{flex: 1}}>{children}</View>
|
|
42
42
|
),
|
|
43
|
-
debugTree: true,
|
|
44
43
|
});
|
|
45
44
|
|
|
46
45
|
AppRegistry.registerComponent('example', () => ComponentHarness);
|
|
@@ -199,9 +198,9 @@ Register a component for testing. When called with just a component, the name is
|
|
|
199
198
|
|
|
200
199
|
Root component for the test harness. Register as your app's root component in the component test entry point.
|
|
201
200
|
|
|
202
|
-
#### `configureHarness({ wrapper
|
|
201
|
+
#### `configureHarness({ wrapper? })`
|
|
203
202
|
|
|
204
|
-
Set a global wrapper component
|
|
203
|
+
Set a global wrapper component for all mounted components:
|
|
205
204
|
|
|
206
205
|
```js
|
|
207
206
|
import {Provider} from 'react-redux';
|
|
@@ -216,14 +215,11 @@ configureHarness({
|
|
|
216
215
|
}
|
|
217
216
|
return <Provider store={store}>{children}</Provider>;
|
|
218
217
|
},
|
|
219
|
-
debugTree: true,
|
|
220
218
|
});
|
|
221
219
|
```
|
|
222
220
|
|
|
223
221
|
The wrapper receives `launchArgs` — the props passed to `mount()` — so you can configure per-test state.
|
|
224
222
|
|
|
225
|
-
The `debugTree` option enables component tree capture for `debug()` and the custom test environment. Pass `true` for defaults, or an object with `usefulProps`, `skipNames`, and/or `nativeDuplicates` to customize filtering (see [DebugTree](#debugtree) below).
|
|
226
|
-
|
|
227
223
|
### Test-side (import from `@nyby/detox-component-testing/test`)
|
|
228
224
|
|
|
229
225
|
#### `mount(componentName, props?)`
|
|
@@ -244,65 +240,26 @@ Returns an assertion object for a spy:
|
|
|
244
240
|
|
|
245
241
|
#### `debug(label?, outputDir?)`
|
|
246
242
|
|
|
247
|
-
Capture a screenshot
|
|
243
|
+
Capture a screenshot and native view hierarchy for the current screen state. Useful for debugging test failures or inspecting what's on screen at any point in a test.
|
|
248
244
|
|
|
249
245
|
```ts
|
|
250
246
|
import {mount, debug} from '@nyby/detox-component-testing/test';
|
|
251
247
|
|
|
252
248
|
it('renders the event screen', async () => {
|
|
253
249
|
await mount('EventScreen', {eventId: 'event_1'});
|
|
254
|
-
await debug('after-mount'); // writes to artifacts/debug-after-mount.{png,
|
|
250
|
+
await debug('after-mount'); // writes to artifacts/debug-after-mount.{png,xml}
|
|
255
251
|
});
|
|
256
252
|
```
|
|
257
253
|
|
|
258
|
-
Each call writes up to
|
|
254
|
+
Each call writes up to two files to the output directory (defaults to `<cwd>/artifacts`):
|
|
259
255
|
|
|
260
256
|
- `debug-<label>.png` — screenshot
|
|
261
|
-
- `debug-<label>-tree.json` — React component tree (requires `DebugTree` wrapper)
|
|
262
257
|
- `debug-<label>-view.xml` — native view hierarchy
|
|
263
258
|
|
|
264
259
|
If no label is provided, calls are numbered automatically (`1`, `2`, `3`, ...).
|
|
265
260
|
|
|
266
261
|
### Debugging
|
|
267
262
|
|
|
268
|
-
#### `DebugTree`
|
|
269
|
-
|
|
270
|
-
A component that captures the React fiber tree on demand. Enable it via `configureHarness({ debugTree: true })` to get component tree capture in `debug()` and the custom test environment.
|
|
271
|
-
|
|
272
|
-
```js
|
|
273
|
-
import {configureHarness} from '@nyby/detox-component-testing';
|
|
274
|
-
|
|
275
|
-
configureHarness({
|
|
276
|
-
wrapper: ({children}) => (
|
|
277
|
-
<Provider store={store}>{children}</Provider>
|
|
278
|
-
),
|
|
279
|
-
debugTree: true,
|
|
280
|
-
});
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
You can also pass filtering options directly:
|
|
284
|
-
|
|
285
|
-
```js
|
|
286
|
-
configureHarness({
|
|
287
|
-
debugTree: {
|
|
288
|
-
usefulProps: ['testID', 'accessibilityLabel', 'title'],
|
|
289
|
-
skipNames: ['MyInternalWrapper'],
|
|
290
|
-
},
|
|
291
|
-
});
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
`DebugTree` walks the React fiber tree and produces a JSON snapshot of component names, key props (`testID`, `accessibilityLabel`, `variant`, `onPress`, etc.), and text content. The output filters noise — internal wrappers and native duplicates are excluded by default.
|
|
295
|
-
|
|
296
|
-
The `DebugTree` component is also exported for advanced use cases where you need direct control.
|
|
297
|
-
|
|
298
|
-
All filtering lists are configurable:
|
|
299
|
-
|
|
300
|
-
| Prop | Type | Description |
|
|
301
|
-
| ------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
302
|
-
| `usefulProps` | `string[]` | Props to include in output. Defaults to `testID`, `accessibilityLabel`, `title`, `value`, `placeholder`, `disabled`, etc. |
|
|
303
|
-
| `skipNames` | `string[]` | Component names to skip. Defaults to internal wrappers like `StaticContainer`, `PressabilityDebugView`, etc. |
|
|
304
|
-
| `nativeDuplicates` | `string[]` | Native components that duplicate their parent. Defaults to `RCTText`, `RCTView`, etc. |
|
|
305
|
-
|
|
306
263
|
#### Custom test environment
|
|
307
264
|
|
|
308
265
|
A Detox Jest environment that automatically captures debug artifacts when a test fails. Use it instead of the default Detox environment:
|
|
@@ -318,7 +275,6 @@ module.exports = {
|
|
|
318
275
|
On test failure, it captures:
|
|
319
276
|
|
|
320
277
|
- A screenshot
|
|
321
|
-
- The React component tree (if `DebugTree` is in the wrapper)
|
|
322
278
|
- The native view hierarchy
|
|
323
279
|
|
|
324
280
|
All artifacts are written to `<cwd>/artifacts/`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComponentHarness.d.ts","sourceRoot":"","sources":["../src/ComponentHarness.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8E,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ComponentHarness.d.ts","sourceRoot":"","sources":["../src/ComponentHarness.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8E,MAAM,OAAO,CAAC;AAuDnG,wBAAgB,gBAAgB,sBAkD/B"}
|
package/dist/ComponentHarness.js
CHANGED
|
@@ -39,7 +39,6 @@ const react_native_1 = require("react-native");
|
|
|
39
39
|
const react_native_launch_arguments_1 = require("react-native-launch-arguments");
|
|
40
40
|
const ComponentRegistry_1 = require("./ComponentRegistry");
|
|
41
41
|
const configureHarness_1 = require("./configureHarness");
|
|
42
|
-
const DebugTree_1 = require("./DebugTree");
|
|
43
42
|
class RenderErrorBoundary extends react_1.Component {
|
|
44
43
|
constructor() {
|
|
45
44
|
super(...arguments);
|
|
@@ -137,12 +136,11 @@ function ComponentRenderer({ mount }) {
|
|
|
137
136
|
});
|
|
138
137
|
const props = { ...defaultProps, ...(mount.props || {}), ...spyProps };
|
|
139
138
|
const Wrapper = (0, configureHarness_1.getWrapper)();
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return (react_1.default.createElement(Wrapper, { launchArgs: mount.props || {} }, debugTreeConfig ? react_1.default.createElement(DebugTree_1.DebugTree, { ...debugTreeConfig }, content) : content));
|
|
139
|
+
return (react_1.default.createElement(Wrapper, { launchArgs: mount.props || {} },
|
|
140
|
+
react_1.default.createElement(react_native_1.View, { testID: "component-harness-root", style: { flex: 1 } },
|
|
141
|
+
react_1.default.createElement(Component, { ...props }),
|
|
142
|
+
spyNames.map((name) => (react_1.default.createElement(react_native_1.View, { key: name },
|
|
143
|
+
react_1.default.createElement(react_native_1.Text, { testID: `spy-${name}-count` }, String(spyData[name].count)),
|
|
144
|
+
react_1.default.createElement(react_native_1.Text, { testID: `spy-${name}-lastArgs` }, JSON.stringify(spyData[name].lastArgs))))))));
|
|
147
145
|
}
|
|
148
146
|
//# sourceMappingURL=ComponentHarness.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComponentHarness.js","sourceRoot":"","sources":["../src/ComponentHarness.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"ComponentHarness.js","sourceRoot":"","sources":["../src/ComponentHarness.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { ComponentType, ReactNode } from 'react';
|
|
2
|
-
import { DebugTreeProps } from './DebugTree';
|
|
3
2
|
export interface WrapperProps {
|
|
4
3
|
children: ReactNode;
|
|
5
4
|
launchArgs: Record<string, any>;
|
|
6
5
|
}
|
|
7
6
|
export interface HarnessConfig {
|
|
8
7
|
wrapper?: ComponentType<WrapperProps>;
|
|
9
|
-
debugTree?: boolean | Omit<DebugTreeProps, 'children'>;
|
|
10
8
|
}
|
|
11
9
|
export declare function configureHarness(config: HarnessConfig): void;
|
|
12
10
|
export declare function getWrapper(): ComponentType<WrapperProps>;
|
|
13
|
-
export declare function getDebugTreeConfig(): Omit<DebugTreeProps, 'children'> | null;
|
|
14
11
|
//# sourceMappingURL=configureHarness.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configureHarness.d.ts","sourceRoot":"","sources":["../src/configureHarness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAAE,SAAS,EAAC,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"configureHarness.d.ts","sourceRoot":"","sources":["../src/configureHarness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAAE,SAAS,EAAC,MAAM,OAAO,CAAC;AAE/C,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,SAAS,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;CACvC;AAMD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAI5D;AAED,wBAAgB,UAAU,IAAI,aAAa,CAAC,YAAY,CAAC,CAExD"}
|
package/dist/configureHarness.js
CHANGED
|
@@ -2,26 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.configureHarness = configureHarness;
|
|
4
4
|
exports.getWrapper = getWrapper;
|
|
5
|
-
exports.getDebugTreeConfig = getDebugTreeConfig;
|
|
6
5
|
const DefaultWrapper = ({ children }) => children;
|
|
7
6
|
let globalWrapper = null;
|
|
8
|
-
let globalDebugTree = null;
|
|
9
7
|
function configureHarness(config) {
|
|
10
8
|
if (config.wrapper) {
|
|
11
9
|
globalWrapper = config.wrapper;
|
|
12
10
|
}
|
|
13
|
-
if (config.debugTree !== undefined) {
|
|
14
|
-
globalDebugTree = config.debugTree;
|
|
15
|
-
}
|
|
16
11
|
}
|
|
17
12
|
function getWrapper() {
|
|
18
13
|
return globalWrapper || DefaultWrapper;
|
|
19
14
|
}
|
|
20
|
-
function getDebugTreeConfig() {
|
|
21
|
-
if (!globalDebugTree)
|
|
22
|
-
return null;
|
|
23
|
-
if (globalDebugTree === true)
|
|
24
|
-
return {};
|
|
25
|
-
return globalDebugTree;
|
|
26
|
-
}
|
|
27
15
|
//# sourceMappingURL=configureHarness.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configureHarness.js","sourceRoot":"","sources":["../src/configureHarness.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"configureHarness.js","sourceRoot":"","sources":["../src/configureHarness.ts"],"names":[],"mappings":";;AAeA,4CAIC;AAED,gCAEC;AAZD,MAAM,cAAc,GAAG,CAAC,EAAC,QAAQ,EAAe,EAAE,EAAE,CAAC,QAAQ,CAAC;AAE9D,IAAI,aAAa,GAAuC,IAAI,CAAC;AAE7D,SAAgB,gBAAgB,CAAC,MAAqB;IACpD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC;IACjC,CAAC;AACH,CAAC;AAED,SAAgB,UAAU;IACxB,OAAO,aAAa,IAAI,cAAc,CAAC;AACzC,CAAC"}
|
package/dist/debug.d.ts
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture a screenshot and native view hierarchy to the given directory.
|
|
3
|
+
* Used by both the `debug()` helper and the custom test environment.
|
|
4
|
+
*/
|
|
5
|
+
export declare function captureArtifacts(name: string, outputDir: string, deviceRef: {
|
|
6
|
+
takeScreenshot: (n: string) => Promise<string>;
|
|
7
|
+
generateViewHierarchyXml: () => Promise<string>;
|
|
8
|
+
}): Promise<void>;
|
|
1
9
|
export declare function debug(label?: string, outputDir?: string): Promise<void>;
|
|
2
10
|
//# sourceMappingURL=debug.d.ts.map
|
package/dist/debug.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../src/debug.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../src/debug.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE;IAAC,cAAc,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAAC,wBAAwB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;CAAC,iBAiB7G;AAaD,wBAAsB,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,iBAI7D"}
|
package/dist/debug.js
CHANGED
|
@@ -1,49 +1,43 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.captureArtifacts = captureArtifacts;
|
|
3
4
|
exports.debug = debug;
|
|
4
5
|
const fs_1 = require("fs");
|
|
5
6
|
const path_1 = require("path");
|
|
6
7
|
/**
|
|
7
|
-
* Capture a screenshot
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* import { debug } from '@nyby/detox-component-testing/test';
|
|
12
|
-
* await debug(); // artifacts/debug-1.png, debug-1-tree.json, debug-1-view.xml
|
|
13
|
-
* await debug('after-tap'); // artifacts/debug-after-tap.png, etc.
|
|
8
|
+
* Capture a screenshot and native view hierarchy to the given directory.
|
|
9
|
+
* Used by both the `debug()` helper and the custom test environment.
|
|
14
10
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const name = label || String(++counter);
|
|
18
|
-
const dir = outputDir || (0, path_1.join)(process.cwd(), 'artifacts');
|
|
19
|
-
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
20
|
-
const screenshotPath = (0, path_1.join)(dir, `debug-${name}.png`);
|
|
21
|
-
const treePath = (0, path_1.join)(dir, `debug-${name}-tree.json`);
|
|
22
|
-
const viewPath = (0, path_1.join)(dir, `debug-${name}-view.xml`);
|
|
11
|
+
async function captureArtifacts(name, outputDir, deviceRef) {
|
|
12
|
+
(0, fs_1.mkdirSync)(outputDir, { recursive: true });
|
|
23
13
|
// Screenshot via Detox, then move to our artifacts dir
|
|
24
14
|
try {
|
|
25
|
-
const tempPath = await
|
|
15
|
+
const tempPath = await deviceRef.takeScreenshot(`debug-${name}`);
|
|
26
16
|
if (tempPath) {
|
|
27
|
-
(0, fs_1.renameSync)(tempPath,
|
|
17
|
+
(0, fs_1.renameSync)(tempPath, (0, path_1.join)(outputDir, `debug-${name}.png`));
|
|
28
18
|
}
|
|
29
19
|
}
|
|
30
20
|
catch (_a) { }
|
|
31
|
-
// React component tree via DebugTree harness
|
|
32
|
-
try {
|
|
33
|
-
await element(by.id('debug-tree-control')).replaceText('dump');
|
|
34
|
-
await waitFor(element(by.id('debug-tree-output')))
|
|
35
|
-
.toExist()
|
|
36
|
-
.withTimeout(3000);
|
|
37
|
-
const attrs = await element(by.id('debug-tree-output')).getAttributes();
|
|
38
|
-
const tree = attrs.text || attrs.label || '[]';
|
|
39
|
-
(0, fs_1.writeFileSync)(treePath, tree, 'utf8');
|
|
40
|
-
}
|
|
41
|
-
catch (_b) { }
|
|
42
21
|
// Native view hierarchy
|
|
43
22
|
try {
|
|
44
|
-
const xml = await
|
|
45
|
-
(0, fs_1.writeFileSync)(
|
|
23
|
+
const xml = await deviceRef.generateViewHierarchyXml();
|
|
24
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(outputDir, `debug-${name}-view.xml`), xml, 'utf8');
|
|
46
25
|
}
|
|
47
|
-
catch (
|
|
26
|
+
catch (_b) { }
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Capture a screenshot and native view hierarchy.
|
|
30
|
+
* Drop this anywhere in a test to inspect the current screen state.
|
|
31
|
+
*
|
|
32
|
+
* Usage:
|
|
33
|
+
* import { debug } from '@nyby/detox-component-testing/test';
|
|
34
|
+
* await debug(); // artifacts/debug-1.png, debug-1-view.xml
|
|
35
|
+
* await debug('after-tap'); // artifacts/debug-after-tap.png, etc.
|
|
36
|
+
*/
|
|
37
|
+
let counter = 0;
|
|
38
|
+
async function debug(label, outputDir) {
|
|
39
|
+
const name = label || String(++counter);
|
|
40
|
+
const dir = outputDir || (0, path_1.join)(process.cwd(), 'artifacts');
|
|
41
|
+
await captureArtifacts(name, dir, device);
|
|
48
42
|
}
|
|
49
43
|
//# sourceMappingURL=debug.js.map
|
package/dist/debug.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../src/debug.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../src/debug.ts"],"names":[],"mappings":";;AAOA,4CAoBC;AAaD,sBAIC;AA5CD,2BAAwD;AACxD,+BAA0B;AAE1B;;;GAGG;AACI,KAAK,UAAU,gBAAgB,CACpC,IAAY,EACZ,SAAiB,EACjB,SAA4G;IAE5G,IAAA,cAAS,EAAC,SAAS,EAAE,EAAC,SAAS,EAAE,IAAI,EAAC,CAAC,CAAC;IAExC,uDAAuD;IACvD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,cAAc,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;QACjE,IAAI,QAAQ,EAAE,CAAC;YACb,IAAA,eAAU,EAAC,QAAQ,EAAE,IAAA,WAAI,EAAC,SAAS,EAAE,SAAS,IAAI,MAAM,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAAC,WAAM,CAAC,CAAA,CAAC;IAEV,wBAAwB;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,wBAAwB,EAAE,CAAC;QACvD,IAAA,kBAAa,EAAC,IAAA,WAAI,EAAC,SAAS,EAAE,SAAS,IAAI,WAAW,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IACxE,CAAC;IAAC,WAAM,CAAC,CAAA,CAAC;AACZ,CAAC;AAED;;;;;;;;GAQG;AACH,IAAI,OAAO,GAAG,CAAC,CAAC;AAET,KAAK,UAAU,KAAK,CAAC,KAAc,EAAE,SAAkB;IAC5D,MAAM,IAAI,GAAG,KAAK,IAAI,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,SAAS,IAAI,IAAA,WAAI,EAAC,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,CAAC,CAAC;IAC1D,MAAM,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;AAC5C,CAAC"}
|
package/dist/environment.js
CHANGED
|
@@ -1,47 +1,17 @@
|
|
|
1
1
|
const DetoxCircusEnvironment = require('detox/runners/jest/testEnvironment');
|
|
2
|
-
const {mkdirSync, writeFileSync} = require('fs');
|
|
3
2
|
const {join} = require('path');
|
|
3
|
+
const {captureArtifacts} = require('./debug');
|
|
4
4
|
|
|
5
5
|
class CustomDetoxEnvironment extends DetoxCircusEnvironment {
|
|
6
6
|
async handleTestEvent(event, state) {
|
|
7
7
|
await super.handleTestEvent(event, state);
|
|
8
8
|
|
|
9
9
|
if (event.name === 'test_done' && event.test.errors.length > 0) {
|
|
10
|
-
|
|
10
|
+
const safeName = event.test.name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
11
|
+
const outputDir = join(process.cwd(), 'artifacts');
|
|
12
|
+
await captureArtifacts(safeName, outputDir, this.global.device);
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
|
-
|
|
14
|
-
async _dumpDebugInfo(test) {
|
|
15
|
-
const outputDir = join(process.cwd(), 'artifacts');
|
|
16
|
-
const safeName = test.name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
17
|
-
|
|
18
|
-
mkdirSync(outputDir, {recursive: true});
|
|
19
|
-
|
|
20
|
-
// Screenshot (saved by Detox into its own artifacts folder)
|
|
21
|
-
try {
|
|
22
|
-
await this.global.device.takeScreenshot(`debug-${safeName}`);
|
|
23
|
-
} catch (_e) {}
|
|
24
|
-
|
|
25
|
-
// Component tree via DebugTree
|
|
26
|
-
try {
|
|
27
|
-
const {element, by, waitFor} = this.global;
|
|
28
|
-
|
|
29
|
-
await element(by.id('debug-tree-control')).replaceText('dump');
|
|
30
|
-
await waitFor(element(by.id('debug-tree-output')))
|
|
31
|
-
.toExist()
|
|
32
|
-
.withTimeout(3000);
|
|
33
|
-
|
|
34
|
-
const attrs = await element(by.id('debug-tree-output')).getAttributes();
|
|
35
|
-
const tree = attrs.text || attrs.label || '[]';
|
|
36
|
-
writeFileSync(join(outputDir, `componenttree-${safeName}.json`), tree, 'utf8');
|
|
37
|
-
} catch (_e) {}
|
|
38
|
-
|
|
39
|
-
// Native view hierarchy
|
|
40
|
-
try {
|
|
41
|
-
const xml = await this.global.device.generateViewHierarchyXml();
|
|
42
|
-
writeFileSync(join(outputDir, `viewhierarchy-${safeName}.xml`), xml, 'utf8');
|
|
43
|
-
} catch (_e) {}
|
|
44
|
-
}
|
|
45
15
|
}
|
|
46
16
|
|
|
47
17
|
module.exports = CustomDetoxEnvironment;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { registerComponent } from './ComponentRegistry';
|
|
2
2
|
export { ComponentHarness } from './ComponentHarness';
|
|
3
3
|
export { configureHarness, WrapperProps, HarnessConfig } from './configureHarness';
|
|
4
|
-
export { DebugTree, DebugTreeProps } from './DebugTree';
|
|
5
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAC,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAC,MAAM,oBAAoB,CAAC
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAC,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAC,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.configureHarness = exports.ComponentHarness = exports.registerComponent = void 0;
|
|
4
4
|
var ComponentRegistry_1 = require("./ComponentRegistry");
|
|
5
5
|
Object.defineProperty(exports, "registerComponent", { enumerable: true, get: function () { return ComponentRegistry_1.registerComponent; } });
|
|
6
6
|
var ComponentHarness_1 = require("./ComponentHarness");
|
|
7
7
|
Object.defineProperty(exports, "ComponentHarness", { enumerable: true, get: function () { return ComponentHarness_1.ComponentHarness; } });
|
|
8
8
|
var configureHarness_1 = require("./configureHarness");
|
|
9
9
|
Object.defineProperty(exports, "configureHarness", { enumerable: true, get: function () { return configureHarness_1.configureHarness; } });
|
|
10
|
-
var DebugTree_1 = require("./DebugTree");
|
|
11
|
-
Object.defineProperty(exports, "DebugTree", { enumerable: true, get: function () { return DebugTree_1.DebugTree; } });
|
|
12
10
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,yDAAsD;AAA9C,sHAAA,iBAAiB,OAAA;AACzB,uDAAoD;AAA5C,oHAAA,gBAAgB,OAAA;AACxB,uDAAiF;AAAzE,oHAAA,gBAAgB,OAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,yDAAsD;AAA9C,sHAAA,iBAAiB,OAAA;AACzB,uDAAoD;AAA5C,oHAAA,gBAAgB,OAAA;AACxB,uDAAiF;AAAzE,oHAAA,gBAAgB,OAAA"}
|
package/package.json
CHANGED
package/src/ComponentHarness.tsx
CHANGED
|
@@ -2,8 +2,7 @@ import React, {Component as ReactComponent, useState, useRef, useCallback, React
|
|
|
2
2
|
import {View, Text, TextInput, ScrollView} from 'react-native';
|
|
3
3
|
import {LaunchArguments} from 'react-native-launch-arguments';
|
|
4
4
|
import {getComponent} from './ComponentRegistry';
|
|
5
|
-
import {getWrapper
|
|
6
|
-
import {DebugTree} from './DebugTree';
|
|
5
|
+
import {getWrapper} from './configureHarness';
|
|
7
6
|
|
|
8
7
|
interface ErrorBoundaryState {
|
|
9
8
|
error: Error | null;
|
|
@@ -141,23 +140,18 @@ function ComponentRenderer({mount}: {mount: MountPayload}) {
|
|
|
141
140
|
|
|
142
141
|
const props = {...defaultProps, ...(mount.props || {}), ...spyProps};
|
|
143
142
|
const Wrapper = getWrapper();
|
|
144
|
-
const debugTreeConfig = getDebugTreeConfig();
|
|
145
|
-
|
|
146
|
-
const content = (
|
|
147
|
-
<View testID="component-harness-root" style={{flex: 1}}>
|
|
148
|
-
<Component {...props} />
|
|
149
|
-
{spyNames.map((name) => (
|
|
150
|
-
<View key={name}>
|
|
151
|
-
<Text testID={`spy-${name}-count`}>{String(spyData[name].count)}</Text>
|
|
152
|
-
<Text testID={`spy-${name}-lastArgs`}>{JSON.stringify(spyData[name].lastArgs)}</Text>
|
|
153
|
-
</View>
|
|
154
|
-
))}
|
|
155
|
-
</View>
|
|
156
|
-
);
|
|
157
143
|
|
|
158
144
|
return (
|
|
159
145
|
<Wrapper launchArgs={mount.props || {}}>
|
|
160
|
-
|
|
146
|
+
<View testID="component-harness-root" style={{flex: 1}}>
|
|
147
|
+
<Component {...props} />
|
|
148
|
+
{spyNames.map((name) => (
|
|
149
|
+
<View key={name}>
|
|
150
|
+
<Text testID={`spy-${name}-count`}>{String(spyData[name].count)}</Text>
|
|
151
|
+
<Text testID={`spy-${name}-lastArgs`}>{JSON.stringify(spyData[name].lastArgs)}</Text>
|
|
152
|
+
</View>
|
|
153
|
+
))}
|
|
154
|
+
</View>
|
|
161
155
|
</Wrapper>
|
|
162
156
|
);
|
|
163
157
|
}
|
package/src/configureHarness.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {ComponentType, ReactNode} from 'react';
|
|
2
|
-
import {DebugTreeProps} from './DebugTree';
|
|
3
2
|
|
|
4
3
|
export interface WrapperProps {
|
|
5
4
|
children: ReactNode;
|
|
@@ -8,29 +7,18 @@ export interface WrapperProps {
|
|
|
8
7
|
|
|
9
8
|
export interface HarnessConfig {
|
|
10
9
|
wrapper?: ComponentType<WrapperProps>;
|
|
11
|
-
debugTree?: boolean | Omit<DebugTreeProps, 'children'>;
|
|
12
10
|
}
|
|
13
11
|
|
|
14
12
|
const DefaultWrapper = ({children}: WrapperProps) => children;
|
|
15
13
|
|
|
16
14
|
let globalWrapper: ComponentType<WrapperProps> | null = null;
|
|
17
|
-
let globalDebugTree: boolean | Omit<DebugTreeProps, 'children'> | null = null;
|
|
18
15
|
|
|
19
16
|
export function configureHarness(config: HarnessConfig): void {
|
|
20
17
|
if (config.wrapper) {
|
|
21
18
|
globalWrapper = config.wrapper;
|
|
22
19
|
}
|
|
23
|
-
if (config.debugTree !== undefined) {
|
|
24
|
-
globalDebugTree = config.debugTree;
|
|
25
|
-
}
|
|
26
20
|
}
|
|
27
21
|
|
|
28
22
|
export function getWrapper(): ComponentType<WrapperProps> {
|
|
29
23
|
return globalWrapper || DefaultWrapper;
|
|
30
24
|
}
|
|
31
|
-
|
|
32
|
-
export function getDebugTreeConfig(): Omit<DebugTreeProps, 'children'> | null {
|
|
33
|
-
if (!globalDebugTree) return null;
|
|
34
|
-
if (globalDebugTree === true) return {};
|
|
35
|
-
return globalDebugTree;
|
|
36
|
-
}
|
package/src/debug.ts
CHANGED
|
@@ -2,47 +2,44 @@ import {mkdirSync, renameSync, writeFileSync} from 'fs';
|
|
|
2
2
|
import {join} from 'path';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Capture a screenshot
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* import { debug } from '@nyby/detox-component-testing/test';
|
|
10
|
-
* await debug(); // artifacts/debug-1.png, debug-1-tree.json, debug-1-view.xml
|
|
11
|
-
* await debug('after-tap'); // artifacts/debug-after-tap.png, etc.
|
|
5
|
+
* Capture a screenshot and native view hierarchy to the given directory.
|
|
6
|
+
* Used by both the `debug()` helper and the custom test environment.
|
|
12
7
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
mkdirSync(
|
|
19
|
-
|
|
20
|
-
const screenshotPath = join(dir, `debug-${name}.png`);
|
|
21
|
-
const treePath = join(dir, `debug-${name}-tree.json`);
|
|
22
|
-
const viewPath = join(dir, `debug-${name}-view.xml`);
|
|
8
|
+
export async function captureArtifacts(
|
|
9
|
+
name: string,
|
|
10
|
+
outputDir: string,
|
|
11
|
+
deviceRef: {takeScreenshot: (n: string) => Promise<string>; generateViewHierarchyXml: () => Promise<string>},
|
|
12
|
+
) {
|
|
13
|
+
mkdirSync(outputDir, {recursive: true});
|
|
23
14
|
|
|
24
15
|
// Screenshot via Detox, then move to our artifacts dir
|
|
25
16
|
try {
|
|
26
|
-
const tempPath = await
|
|
17
|
+
const tempPath = await deviceRef.takeScreenshot(`debug-${name}`);
|
|
27
18
|
if (tempPath) {
|
|
28
|
-
renameSync(tempPath,
|
|
19
|
+
renameSync(tempPath, join(outputDir, `debug-${name}.png`));
|
|
29
20
|
}
|
|
30
21
|
} catch {}
|
|
31
22
|
|
|
32
|
-
// React component tree via DebugTree harness
|
|
33
|
-
try {
|
|
34
|
-
await element(by.id('debug-tree-control')).replaceText('dump');
|
|
35
|
-
await waitFor(element(by.id('debug-tree-output')))
|
|
36
|
-
.toExist()
|
|
37
|
-
.withTimeout(3000);
|
|
38
|
-
const attrs = await element(by.id('debug-tree-output')).getAttributes();
|
|
39
|
-
const tree = (attrs as any).text || (attrs as any).label || '[]';
|
|
40
|
-
writeFileSync(treePath, tree, 'utf8');
|
|
41
|
-
} catch {}
|
|
42
|
-
|
|
43
23
|
// Native view hierarchy
|
|
44
24
|
try {
|
|
45
|
-
const xml = await
|
|
46
|
-
writeFileSync(
|
|
25
|
+
const xml = await deviceRef.generateViewHierarchyXml();
|
|
26
|
+
writeFileSync(join(outputDir, `debug-${name}-view.xml`), xml, 'utf8');
|
|
47
27
|
} catch {}
|
|
48
28
|
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Capture a screenshot and native view hierarchy.
|
|
32
|
+
* Drop this anywhere in a test to inspect the current screen state.
|
|
33
|
+
*
|
|
34
|
+
* Usage:
|
|
35
|
+
* import { debug } from '@nyby/detox-component-testing/test';
|
|
36
|
+
* await debug(); // artifacts/debug-1.png, debug-1-view.xml
|
|
37
|
+
* await debug('after-tap'); // artifacts/debug-after-tap.png, etc.
|
|
38
|
+
*/
|
|
39
|
+
let counter = 0;
|
|
40
|
+
|
|
41
|
+
export async function debug(label?: string, outputDir?: string) {
|
|
42
|
+
const name = label || String(++counter);
|
|
43
|
+
const dir = outputDir || join(process.cwd(), 'artifacts');
|
|
44
|
+
await captureArtifacts(name, dir, device);
|
|
45
|
+
}
|
package/src/environment.js
CHANGED
|
@@ -1,47 +1,17 @@
|
|
|
1
1
|
const DetoxCircusEnvironment = require('detox/runners/jest/testEnvironment');
|
|
2
|
-
const {mkdirSync, writeFileSync} = require('fs');
|
|
3
2
|
const {join} = require('path');
|
|
3
|
+
const {captureArtifacts} = require('./debug');
|
|
4
4
|
|
|
5
5
|
class CustomDetoxEnvironment extends DetoxCircusEnvironment {
|
|
6
6
|
async handleTestEvent(event, state) {
|
|
7
7
|
await super.handleTestEvent(event, state);
|
|
8
8
|
|
|
9
9
|
if (event.name === 'test_done' && event.test.errors.length > 0) {
|
|
10
|
-
|
|
10
|
+
const safeName = event.test.name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
11
|
+
const outputDir = join(process.cwd(), 'artifacts');
|
|
12
|
+
await captureArtifacts(safeName, outputDir, this.global.device);
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
|
-
|
|
14
|
-
async _dumpDebugInfo(test) {
|
|
15
|
-
const outputDir = join(process.cwd(), 'artifacts');
|
|
16
|
-
const safeName = test.name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
17
|
-
|
|
18
|
-
mkdirSync(outputDir, {recursive: true});
|
|
19
|
-
|
|
20
|
-
// Screenshot (saved by Detox into its own artifacts folder)
|
|
21
|
-
try {
|
|
22
|
-
await this.global.device.takeScreenshot(`debug-${safeName}`);
|
|
23
|
-
} catch (_e) {}
|
|
24
|
-
|
|
25
|
-
// Component tree via DebugTree
|
|
26
|
-
try {
|
|
27
|
-
const {element, by, waitFor} = this.global;
|
|
28
|
-
|
|
29
|
-
await element(by.id('debug-tree-control')).replaceText('dump');
|
|
30
|
-
await waitFor(element(by.id('debug-tree-output')))
|
|
31
|
-
.toExist()
|
|
32
|
-
.withTimeout(3000);
|
|
33
|
-
|
|
34
|
-
const attrs = await element(by.id('debug-tree-output')).getAttributes();
|
|
35
|
-
const tree = attrs.text || attrs.label || '[]';
|
|
36
|
-
writeFileSync(join(outputDir, `componenttree-${safeName}.json`), tree, 'utf8');
|
|
37
|
-
} catch (_e) {}
|
|
38
|
-
|
|
39
|
-
// Native view hierarchy
|
|
40
|
-
try {
|
|
41
|
-
const xml = await this.global.device.generateViewHierarchyXml();
|
|
42
|
-
writeFileSync(join(outputDir, `viewhierarchy-${safeName}.xml`), xml, 'utf8');
|
|
43
|
-
} catch (_e) {}
|
|
44
|
-
}
|
|
45
15
|
}
|
|
46
16
|
|
|
47
17
|
module.exports = CustomDetoxEnvironment;
|
package/src/index.ts
CHANGED
package/dist/DebugTree.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
export declare function captureTree(ref: any, usefulProps?: Set<string>, skipNames?: Set<string>, nativeDuplicates?: Set<string>): string;
|
|
3
|
-
export interface DebugTreeProps {
|
|
4
|
-
children: React.ReactNode;
|
|
5
|
-
/** Props to include in tree output. Defaults to testID, accessibilityLabel, etc. */
|
|
6
|
-
usefulProps?: string[];
|
|
7
|
-
/** Component names to skip (internal wrappers that add noise). */
|
|
8
|
-
skipNames?: string[];
|
|
9
|
-
/** Native component names that duplicate their parent (e.g. RCTText inside Text). */
|
|
10
|
-
nativeDuplicates?: string[];
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Debug component that captures the React component tree on demand.
|
|
14
|
-
* Wrap your test content with this. Trigger via Detox:
|
|
15
|
-
*
|
|
16
|
-
* await element(by.id('debug-tree-control')).replaceText('dump');
|
|
17
|
-
* const attrs = await element(by.id('debug-tree-output')).getAttributes();
|
|
18
|
-
*/
|
|
19
|
-
export declare function DebugTree({ children, usefulProps, skipNames, nativeDuplicates }: DebugTreeProps): React.JSX.Element;
|
|
20
|
-
//# sourceMappingURL=DebugTree.d.ts.map
|
package/dist/DebugTree.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DebugTree.d.ts","sourceRoot":"","sources":["../src/DebugTree.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAiN3D,wBAAgB,WAAW,CACzB,GAAG,EAAE,GAAG,EACR,WAAW,GAAE,GAAG,CAAC,MAAM,CAAwB,EAC/C,SAAS,GAAE,GAAG,CAAC,MAAM,CAAsB,EAC3C,gBAAgB,GAAE,GAAG,CAAC,MAAM,CAA6B,GACxD,MAAM,CAQR;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,oFAAoF;IACpF,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,EAAC,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,gBAAgB,EAAC,EAAE,cAAc,qBAsC7F"}
|
package/dist/DebugTree.js
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.captureTree = captureTree;
|
|
37
|
-
exports.DebugTree = DebugTree;
|
|
38
|
-
const react_1 = __importStar(require("react"));
|
|
39
|
-
const react_native_1 = require("react-native");
|
|
40
|
-
// React fiber tags
|
|
41
|
-
const FunctionComponent = 0;
|
|
42
|
-
const ClassComponent = 1;
|
|
43
|
-
const HostComponent = 5;
|
|
44
|
-
const HostText = 6;
|
|
45
|
-
const ForwardRef = 11;
|
|
46
|
-
const MemoComponent = 14;
|
|
47
|
-
const SimpleMemoComponent = 15;
|
|
48
|
-
const DEFAULT_USEFUL_PROPS = new Set([
|
|
49
|
-
'testID',
|
|
50
|
-
'accessibilityLabel',
|
|
51
|
-
'accessibilityRole',
|
|
52
|
-
'title',
|
|
53
|
-
'name',
|
|
54
|
-
'variant',
|
|
55
|
-
'size',
|
|
56
|
-
'value',
|
|
57
|
-
'placeholder',
|
|
58
|
-
'disabled',
|
|
59
|
-
'selected',
|
|
60
|
-
'checked',
|
|
61
|
-
'visible',
|
|
62
|
-
]);
|
|
63
|
-
const DEFAULT_SKIP_NAMES = new Set([
|
|
64
|
-
'StaticContainer',
|
|
65
|
-
'EnsureSingleNavigator',
|
|
66
|
-
'PreventRemoveProvider',
|
|
67
|
-
'NavigationContent',
|
|
68
|
-
'NavigationStateContext',
|
|
69
|
-
'ScreenStackHeaderConfig',
|
|
70
|
-
'RenderErrorBoundary',
|
|
71
|
-
'DebugTree',
|
|
72
|
-
'PressabilityDebugView',
|
|
73
|
-
]);
|
|
74
|
-
const DEFAULT_NATIVE_DUPLICATES = new Set([
|
|
75
|
-
'RCTText',
|
|
76
|
-
'RCTView',
|
|
77
|
-
'RCTScrollView',
|
|
78
|
-
'RCTCustomScrollView',
|
|
79
|
-
'RCTScrollContentView',
|
|
80
|
-
'RCTSinglelineTextInputView',
|
|
81
|
-
'RCTUITextField',
|
|
82
|
-
]);
|
|
83
|
-
function findFiberFromRef(ref) {
|
|
84
|
-
if (!ref)
|
|
85
|
-
return null;
|
|
86
|
-
// React Native: __internalInstanceHandle is the fiber node
|
|
87
|
-
if (ref.__internalInstanceHandle) {
|
|
88
|
-
return ref.__internalInstanceHandle;
|
|
89
|
-
}
|
|
90
|
-
// React DEV builds
|
|
91
|
-
if (ref._internalFiberInstanceHandleDEV) {
|
|
92
|
-
return ref._internalFiberInstanceHandleDEV;
|
|
93
|
-
}
|
|
94
|
-
// React DOM style: __reactFiber$ key on the node
|
|
95
|
-
for (const key of Object.getOwnPropertyNames(ref)) {
|
|
96
|
-
if (key.startsWith('__reactFiber$')) {
|
|
97
|
-
return ref[key];
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
function getComponentName(fiber) {
|
|
103
|
-
var _a, _b, _c, _d;
|
|
104
|
-
const { type, tag } = fiber;
|
|
105
|
-
if (tag === HostText)
|
|
106
|
-
return null;
|
|
107
|
-
if (tag === HostComponent) {
|
|
108
|
-
return typeof type === 'string' ? type : null;
|
|
109
|
-
}
|
|
110
|
-
if (tag === FunctionComponent || tag === ClassComponent || tag === SimpleMemoComponent) {
|
|
111
|
-
return (type === null || type === void 0 ? void 0 : type.displayName) || (type === null || type === void 0 ? void 0 : type.name) || null;
|
|
112
|
-
}
|
|
113
|
-
if (tag === ForwardRef) {
|
|
114
|
-
return (type === null || type === void 0 ? void 0 : type.displayName) || ((_a = type === null || type === void 0 ? void 0 : type.render) === null || _a === void 0 ? void 0 : _a.displayName) || ((_b = type === null || type === void 0 ? void 0 : type.render) === null || _b === void 0 ? void 0 : _b.name) || null;
|
|
115
|
-
}
|
|
116
|
-
if (tag === MemoComponent) {
|
|
117
|
-
return (type === null || type === void 0 ? void 0 : type.displayName) || ((_c = type === null || type === void 0 ? void 0 : type.type) === null || _c === void 0 ? void 0 : _c.displayName) || ((_d = type === null || type === void 0 ? void 0 : type.type) === null || _d === void 0 ? void 0 : _d.name) || null;
|
|
118
|
-
}
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
function isUserComponent(fiber) {
|
|
122
|
-
const { tag } = fiber;
|
|
123
|
-
return (tag === FunctionComponent ||
|
|
124
|
-
tag === ClassComponent ||
|
|
125
|
-
tag === ForwardRef ||
|
|
126
|
-
tag === MemoComponent ||
|
|
127
|
-
tag === SimpleMemoComponent);
|
|
128
|
-
}
|
|
129
|
-
function extractProps(fiber, usefulProps) {
|
|
130
|
-
const { memoizedProps } = fiber;
|
|
131
|
-
if (!memoizedProps)
|
|
132
|
-
return undefined;
|
|
133
|
-
const result = {};
|
|
134
|
-
let hasProps = false;
|
|
135
|
-
for (const key of Object.keys(memoizedProps)) {
|
|
136
|
-
if (key === 'children' || key === 'style')
|
|
137
|
-
continue;
|
|
138
|
-
if (usefulProps.has(key)) {
|
|
139
|
-
const val = memoizedProps[key];
|
|
140
|
-
if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
|
|
141
|
-
result[key] = val;
|
|
142
|
-
hasProps = true;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
if (memoizedProps.onPress) {
|
|
147
|
-
result.onPress = true;
|
|
148
|
-
hasProps = true;
|
|
149
|
-
}
|
|
150
|
-
return hasProps ? result : undefined;
|
|
151
|
-
}
|
|
152
|
-
function getTextContent(fiber) {
|
|
153
|
-
var _a;
|
|
154
|
-
if (fiber.tag === HostText) {
|
|
155
|
-
return typeof fiber.memoizedProps === 'string' ? fiber.memoizedProps : undefined;
|
|
156
|
-
}
|
|
157
|
-
if (typeof ((_a = fiber.memoizedProps) === null || _a === void 0 ? void 0 : _a.children) === 'string') {
|
|
158
|
-
return fiber.memoizedProps.children;
|
|
159
|
-
}
|
|
160
|
-
return undefined;
|
|
161
|
-
}
|
|
162
|
-
function shouldShow(fiber, skipNames, nativeDuplicates) {
|
|
163
|
-
const name = getComponentName(fiber);
|
|
164
|
-
if (!name)
|
|
165
|
-
return false;
|
|
166
|
-
if (skipNames.has(name))
|
|
167
|
-
return false;
|
|
168
|
-
if (nativeDuplicates.has(name))
|
|
169
|
-
return false;
|
|
170
|
-
if (isUserComponent(fiber))
|
|
171
|
-
return true;
|
|
172
|
-
if (fiber.tag === HostComponent) {
|
|
173
|
-
const props = fiber.memoizedProps;
|
|
174
|
-
if ((props === null || props === void 0 ? void 0 : props.testID) || (props === null || props === void 0 ? void 0 : props.accessibilityLabel))
|
|
175
|
-
return true;
|
|
176
|
-
if (typeof (props === null || props === void 0 ? void 0 : props.children) === 'string')
|
|
177
|
-
return true;
|
|
178
|
-
}
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
function walkFiber(fiber, collect, usefulProps, skipNames, nativeDuplicates) {
|
|
182
|
-
if (!fiber)
|
|
183
|
-
return;
|
|
184
|
-
const name = getComponentName(fiber);
|
|
185
|
-
const show = fiber.tag !== HostText && shouldShow(fiber, skipNames, nativeDuplicates);
|
|
186
|
-
if (show && name) {
|
|
187
|
-
const node = { name };
|
|
188
|
-
const props = extractProps(fiber, usefulProps);
|
|
189
|
-
if (props)
|
|
190
|
-
node.props = props;
|
|
191
|
-
const text = getTextContent(fiber);
|
|
192
|
-
if (text)
|
|
193
|
-
node.text = text;
|
|
194
|
-
const childNodes = [];
|
|
195
|
-
walkFiber(fiber.child, childNodes, usefulProps, skipNames, nativeDuplicates);
|
|
196
|
-
if (childNodes.length > 0)
|
|
197
|
-
node.children = childNodes;
|
|
198
|
-
collect.push(node);
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
walkFiber(fiber.child, collect, usefulProps, skipNames, nativeDuplicates);
|
|
202
|
-
}
|
|
203
|
-
walkFiber(fiber.sibling, collect, usefulProps, skipNames, nativeDuplicates);
|
|
204
|
-
}
|
|
205
|
-
function captureTree(ref, usefulProps = DEFAULT_USEFUL_PROPS, skipNames = DEFAULT_SKIP_NAMES, nativeDuplicates = DEFAULT_NATIVE_DUPLICATES) {
|
|
206
|
-
const fiber = findFiberFromRef(ref);
|
|
207
|
-
if (!fiber)
|
|
208
|
-
return '[]';
|
|
209
|
-
const tree = [];
|
|
210
|
-
walkFiber(fiber.child, tree, usefulProps, skipNames, nativeDuplicates);
|
|
211
|
-
return JSON.stringify(tree);
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Debug component that captures the React component tree on demand.
|
|
215
|
-
* Wrap your test content with this. Trigger via Detox:
|
|
216
|
-
*
|
|
217
|
-
* await element(by.id('debug-tree-control')).replaceText('dump');
|
|
218
|
-
* const attrs = await element(by.id('debug-tree-output')).getAttributes();
|
|
219
|
-
*/
|
|
220
|
-
function DebugTree({ children, usefulProps, skipNames, nativeDuplicates }) {
|
|
221
|
-
const rootRef = (0, react_1.useRef)(null);
|
|
222
|
-
const [tree, setTree] = (0, react_1.useState)('');
|
|
223
|
-
const usefulPropsSet = usefulProps ? new Set(usefulProps) : DEFAULT_USEFUL_PROPS;
|
|
224
|
-
const skipNamesSet = skipNames ? new Set(skipNames) : DEFAULT_SKIP_NAMES;
|
|
225
|
-
const nativeDuplicatesSet = nativeDuplicates
|
|
226
|
-
? new Set(nativeDuplicates)
|
|
227
|
-
: DEFAULT_NATIVE_DUPLICATES;
|
|
228
|
-
const handleCommand = (0, react_1.useCallback)((text) => {
|
|
229
|
-
if (text === 'dump' && rootRef.current) {
|
|
230
|
-
const result = captureTree(rootRef.current, usefulPropsSet, skipNamesSet, nativeDuplicatesSet);
|
|
231
|
-
setTree(result);
|
|
232
|
-
}
|
|
233
|
-
}, [usefulPropsSet, skipNamesSet, nativeDuplicatesSet]);
|
|
234
|
-
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
235
|
-
react_1.default.createElement(react_native_1.TextInput, { testID: "debug-tree-control", onChangeText: handleCommand, style: { height: 1 } }),
|
|
236
|
-
react_1.default.createElement(react_native_1.View, { ref: rootRef, style: { flex: 1 }, collapsable: false }, children),
|
|
237
|
-
tree ? (react_1.default.createElement(react_native_1.Text, { testID: "debug-tree-output", style: { height: 1 } }, tree)) : null));
|
|
238
|
-
}
|
|
239
|
-
//# sourceMappingURL=DebugTree.js.map
|
package/dist/DebugTree.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DebugTree.js","sourceRoot":"","sources":["../src/DebugTree.tsx"],"names":[],"mappings":"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"}
|
package/src/DebugTree.tsx
DELETED
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import React, {useCallback, useRef, useState} from 'react';
|
|
2
|
-
import {Text, TextInput, View} from 'react-native';
|
|
3
|
-
|
|
4
|
-
type Fiber = {
|
|
5
|
-
type: any;
|
|
6
|
-
memoizedProps: Record<string, any>;
|
|
7
|
-
child: Fiber | null;
|
|
8
|
-
sibling: Fiber | null;
|
|
9
|
-
return: Fiber | null;
|
|
10
|
-
tag: number;
|
|
11
|
-
stateNode: any;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
type TreeNode = {
|
|
15
|
-
name: string;
|
|
16
|
-
props?: Record<string, any>;
|
|
17
|
-
text?: string;
|
|
18
|
-
children?: TreeNode[];
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// React fiber tags
|
|
22
|
-
const FunctionComponent = 0;
|
|
23
|
-
const ClassComponent = 1;
|
|
24
|
-
const HostComponent = 5;
|
|
25
|
-
const HostText = 6;
|
|
26
|
-
const ForwardRef = 11;
|
|
27
|
-
const MemoComponent = 14;
|
|
28
|
-
const SimpleMemoComponent = 15;
|
|
29
|
-
|
|
30
|
-
const DEFAULT_USEFUL_PROPS = new Set([
|
|
31
|
-
'testID',
|
|
32
|
-
'accessibilityLabel',
|
|
33
|
-
'accessibilityRole',
|
|
34
|
-
'title',
|
|
35
|
-
'name',
|
|
36
|
-
'variant',
|
|
37
|
-
'size',
|
|
38
|
-
'value',
|
|
39
|
-
'placeholder',
|
|
40
|
-
'disabled',
|
|
41
|
-
'selected',
|
|
42
|
-
'checked',
|
|
43
|
-
'visible',
|
|
44
|
-
]);
|
|
45
|
-
|
|
46
|
-
const DEFAULT_SKIP_NAMES = new Set([
|
|
47
|
-
'StaticContainer',
|
|
48
|
-
'EnsureSingleNavigator',
|
|
49
|
-
'PreventRemoveProvider',
|
|
50
|
-
'NavigationContent',
|
|
51
|
-
'NavigationStateContext',
|
|
52
|
-
'ScreenStackHeaderConfig',
|
|
53
|
-
'RenderErrorBoundary',
|
|
54
|
-
'DebugTree',
|
|
55
|
-
'PressabilityDebugView',
|
|
56
|
-
]);
|
|
57
|
-
|
|
58
|
-
const DEFAULT_NATIVE_DUPLICATES = new Set([
|
|
59
|
-
'RCTText',
|
|
60
|
-
'RCTView',
|
|
61
|
-
'RCTScrollView',
|
|
62
|
-
'RCTCustomScrollView',
|
|
63
|
-
'RCTScrollContentView',
|
|
64
|
-
'RCTSinglelineTextInputView',
|
|
65
|
-
'RCTUITextField',
|
|
66
|
-
]);
|
|
67
|
-
|
|
68
|
-
function findFiberFromRef(ref: any): Fiber | null {
|
|
69
|
-
if (!ref) return null;
|
|
70
|
-
|
|
71
|
-
// React Native: __internalInstanceHandle is the fiber node
|
|
72
|
-
if (ref.__internalInstanceHandle) {
|
|
73
|
-
return ref.__internalInstanceHandle;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// React DEV builds
|
|
77
|
-
if (ref._internalFiberInstanceHandleDEV) {
|
|
78
|
-
return ref._internalFiberInstanceHandleDEV;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// React DOM style: __reactFiber$ key on the node
|
|
82
|
-
for (const key of Object.getOwnPropertyNames(ref)) {
|
|
83
|
-
if (key.startsWith('__reactFiber$')) {
|
|
84
|
-
return ref[key];
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function getComponentName(fiber: Fiber): string | null {
|
|
92
|
-
const {type, tag} = fiber;
|
|
93
|
-
|
|
94
|
-
if (tag === HostText) return null;
|
|
95
|
-
|
|
96
|
-
if (tag === HostComponent) {
|
|
97
|
-
return typeof type === 'string' ? type : null;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (tag === FunctionComponent || tag === ClassComponent || tag === SimpleMemoComponent) {
|
|
101
|
-
return type?.displayName || type?.name || null;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (tag === ForwardRef) {
|
|
105
|
-
return type?.displayName || type?.render?.displayName || type?.render?.name || null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (tag === MemoComponent) {
|
|
109
|
-
return type?.displayName || type?.type?.displayName || type?.type?.name || null;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function isUserComponent(fiber: Fiber): boolean {
|
|
116
|
-
const {tag} = fiber;
|
|
117
|
-
return (
|
|
118
|
-
tag === FunctionComponent ||
|
|
119
|
-
tag === ClassComponent ||
|
|
120
|
-
tag === ForwardRef ||
|
|
121
|
-
tag === MemoComponent ||
|
|
122
|
-
tag === SimpleMemoComponent
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function extractProps(fiber: Fiber, usefulProps: Set<string>): Record<string, any> | undefined {
|
|
127
|
-
const {memoizedProps} = fiber;
|
|
128
|
-
if (!memoizedProps) return undefined;
|
|
129
|
-
|
|
130
|
-
const result: Record<string, any> = {};
|
|
131
|
-
let hasProps = false;
|
|
132
|
-
|
|
133
|
-
for (const key of Object.keys(memoizedProps)) {
|
|
134
|
-
if (key === 'children' || key === 'style') continue;
|
|
135
|
-
if (usefulProps.has(key)) {
|
|
136
|
-
const val = memoizedProps[key];
|
|
137
|
-
if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
|
|
138
|
-
result[key] = val;
|
|
139
|
-
hasProps = true;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (memoizedProps.onPress) {
|
|
145
|
-
result.onPress = true;
|
|
146
|
-
hasProps = true;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return hasProps ? result : undefined;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function getTextContent(fiber: Fiber): string | undefined {
|
|
153
|
-
if (fiber.tag === HostText) {
|
|
154
|
-
return typeof fiber.memoizedProps === 'string' ? fiber.memoizedProps : undefined;
|
|
155
|
-
}
|
|
156
|
-
if (typeof fiber.memoizedProps?.children === 'string') {
|
|
157
|
-
return fiber.memoizedProps.children;
|
|
158
|
-
}
|
|
159
|
-
return undefined;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function shouldShow(fiber: Fiber, skipNames: Set<string>, nativeDuplicates: Set<string>): boolean {
|
|
163
|
-
const name = getComponentName(fiber);
|
|
164
|
-
if (!name) return false;
|
|
165
|
-
if (skipNames.has(name)) return false;
|
|
166
|
-
if (nativeDuplicates.has(name)) return false;
|
|
167
|
-
|
|
168
|
-
if (isUserComponent(fiber)) return true;
|
|
169
|
-
|
|
170
|
-
if (fiber.tag === HostComponent) {
|
|
171
|
-
const props = fiber.memoizedProps;
|
|
172
|
-
if (props?.testID || props?.accessibilityLabel) return true;
|
|
173
|
-
if (typeof props?.children === 'string') return true;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function walkFiber(
|
|
180
|
-
fiber: Fiber | null,
|
|
181
|
-
collect: TreeNode[],
|
|
182
|
-
usefulProps: Set<string>,
|
|
183
|
-
skipNames: Set<string>,
|
|
184
|
-
nativeDuplicates: Set<string>,
|
|
185
|
-
): void {
|
|
186
|
-
if (!fiber) return;
|
|
187
|
-
|
|
188
|
-
const name = getComponentName(fiber);
|
|
189
|
-
const show = fiber.tag !== HostText && shouldShow(fiber, skipNames, nativeDuplicates);
|
|
190
|
-
|
|
191
|
-
if (show && name) {
|
|
192
|
-
const node: TreeNode = {name};
|
|
193
|
-
const props = extractProps(fiber, usefulProps);
|
|
194
|
-
if (props) node.props = props;
|
|
195
|
-
const text = getTextContent(fiber);
|
|
196
|
-
if (text) node.text = text;
|
|
197
|
-
|
|
198
|
-
const childNodes: TreeNode[] = [];
|
|
199
|
-
walkFiber(fiber.child, childNodes, usefulProps, skipNames, nativeDuplicates);
|
|
200
|
-
if (childNodes.length > 0) node.children = childNodes;
|
|
201
|
-
|
|
202
|
-
collect.push(node);
|
|
203
|
-
} else {
|
|
204
|
-
walkFiber(fiber.child, collect, usefulProps, skipNames, nativeDuplicates);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
walkFiber(fiber.sibling, collect, usefulProps, skipNames, nativeDuplicates);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export function captureTree(
|
|
211
|
-
ref: any,
|
|
212
|
-
usefulProps: Set<string> = DEFAULT_USEFUL_PROPS,
|
|
213
|
-
skipNames: Set<string> = DEFAULT_SKIP_NAMES,
|
|
214
|
-
nativeDuplicates: Set<string> = DEFAULT_NATIVE_DUPLICATES,
|
|
215
|
-
): string {
|
|
216
|
-
const fiber = findFiberFromRef(ref);
|
|
217
|
-
if (!fiber) return '[]';
|
|
218
|
-
|
|
219
|
-
const tree: TreeNode[] = [];
|
|
220
|
-
walkFiber(fiber.child, tree, usefulProps, skipNames, nativeDuplicates);
|
|
221
|
-
|
|
222
|
-
return JSON.stringify(tree);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export interface DebugTreeProps {
|
|
226
|
-
children: React.ReactNode;
|
|
227
|
-
/** Props to include in tree output. Defaults to testID, accessibilityLabel, etc. */
|
|
228
|
-
usefulProps?: string[];
|
|
229
|
-
/** Component names to skip (internal wrappers that add noise). */
|
|
230
|
-
skipNames?: string[];
|
|
231
|
-
/** Native component names that duplicate their parent (e.g. RCTText inside Text). */
|
|
232
|
-
nativeDuplicates?: string[];
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Debug component that captures the React component tree on demand.
|
|
237
|
-
* Wrap your test content with this. Trigger via Detox:
|
|
238
|
-
*
|
|
239
|
-
* await element(by.id('debug-tree-control')).replaceText('dump');
|
|
240
|
-
* const attrs = await element(by.id('debug-tree-output')).getAttributes();
|
|
241
|
-
*/
|
|
242
|
-
export function DebugTree({children, usefulProps, skipNames, nativeDuplicates}: DebugTreeProps) {
|
|
243
|
-
const rootRef = useRef<View>(null);
|
|
244
|
-
const [tree, setTree] = useState('');
|
|
245
|
-
|
|
246
|
-
const usefulPropsSet = usefulProps ? new Set(usefulProps) : DEFAULT_USEFUL_PROPS;
|
|
247
|
-
const skipNamesSet = skipNames ? new Set(skipNames) : DEFAULT_SKIP_NAMES;
|
|
248
|
-
const nativeDuplicatesSet = nativeDuplicates
|
|
249
|
-
? new Set(nativeDuplicates)
|
|
250
|
-
: DEFAULT_NATIVE_DUPLICATES;
|
|
251
|
-
|
|
252
|
-
const handleCommand = useCallback(
|
|
253
|
-
(text: string) => {
|
|
254
|
-
if (text === 'dump' && rootRef.current) {
|
|
255
|
-
const result = captureTree(
|
|
256
|
-
rootRef.current,
|
|
257
|
-
usefulPropsSet,
|
|
258
|
-
skipNamesSet,
|
|
259
|
-
nativeDuplicatesSet,
|
|
260
|
-
);
|
|
261
|
-
setTree(result);
|
|
262
|
-
}
|
|
263
|
-
},
|
|
264
|
-
[usefulPropsSet, skipNamesSet, nativeDuplicatesSet],
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
return (
|
|
268
|
-
<>
|
|
269
|
-
<TextInput testID="debug-tree-control" onChangeText={handleCommand} style={{height: 1}} />
|
|
270
|
-
<View ref={rootRef} style={{flex: 1}} collapsable={false}>
|
|
271
|
-
{children}
|
|
272
|
-
</View>
|
|
273
|
-
{tree ? (
|
|
274
|
-
<Text testID="debug-tree-output" style={{height: 1}}>
|
|
275
|
-
{tree}
|
|
276
|
-
</Text>
|
|
277
|
-
) : null}
|
|
278
|
-
</>
|
|
279
|
-
);
|
|
280
|
-
}
|