@dynatrace/react-native-plugin 2.333.1 → 2.335.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 +47 -178
- package/android/build.gradle +1 -1
- package/files/plugin-runtime.gradle +27 -13
- package/files/plugin.gradle +1 -1
- package/instrumentation/BabelPluginDynatrace.js +1 -0
- package/instrumentation/DynatraceInstrumentation.js +1 -1
- package/instrumentation/jsx/JsxRuntime.js +4 -4
- package/instrumentation/libs/UserInteraction.js +114 -0
- package/instrumentation/libs/community/gesture-handler/Touchables.InstrInfo.js +2 -0
- package/instrumentation/libs/community/gesture-handler/Touchables.js +3 -1
- package/instrumentation/libs/community/gesture-handler/index.js +3 -1
- package/instrumentation/libs/withOnPressMonitoring.js +6 -0
- package/lib/dynatrace-reporter.js +0 -14
- package/lib/dynatrace-transformer.js +10 -13
- package/lib/features/ui-interaction/Config.js +8 -2
- package/lib/features/ui-interaction/Plugin.Fragment.Test.js +170 -0
- package/lib/features/ui-interaction/Plugin.js +226 -882
- package/lib/features/ui-interaction/Run.js +11 -7
- package/lib/features/ui-interaction/Runtime.js +229 -896
- package/lib/features/ui-interaction/TouchMetaResolver.js +492 -0
- package/lib/features/ui-interaction/Types.js +1 -62
- package/package.json +6 -9
- package/react-native-dynatrace.podspec +1 -1
- package/scripts/Android.js +75 -62
- package/scripts/Config.js +11 -1
- package/scripts/core/InstrumentCall.js +1 -2
- package/scripts/core/LineOffsetAnalyzeCall.js +9 -15
- package/scripts/util/ReactOptions.js +0 -21
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RootWrapper = exports.wrapProvider = exports.notifyPress = void 0;
|
|
4
|
+
const React = require("react");
|
|
5
|
+
const react_native_1 = require("react-native");
|
|
6
|
+
const ConfigurationHandler_1 = require("../../lib/core/configuration/ConfigurationHandler");
|
|
7
|
+
const EventPipeline_1 = require("../../lib/next/events/EventPipeline");
|
|
8
|
+
const TOUCH_FLUSH_DELAY = 250;
|
|
9
|
+
let pendingTouch = null;
|
|
10
|
+
const getFiberPath = (fiber) => {
|
|
11
|
+
var _a, _b, _c;
|
|
12
|
+
let anyParentMasked = false;
|
|
13
|
+
let path = [];
|
|
14
|
+
let lastDtName;
|
|
15
|
+
while (fiber != null) {
|
|
16
|
+
const dtInfoName = (_a = fiber.type) === null || _a === void 0 ? void 0 : _a.dtName;
|
|
17
|
+
const dtName = (_b = fiber.memoizedProps) === null || _b === void 0 ? void 0 : _b.dtName;
|
|
18
|
+
const effectiveName = dtName !== null && dtName !== void 0 ? dtName : dtInfoName;
|
|
19
|
+
if (effectiveName && effectiveName !== lastDtName) {
|
|
20
|
+
path = [effectiveName, ...path];
|
|
21
|
+
lastDtName = effectiveName;
|
|
22
|
+
}
|
|
23
|
+
anyParentMasked =
|
|
24
|
+
anyParentMasked || ((_c = fiber.memoizedProps) === null || _c === void 0 ? void 0 : _c.dtMask) == true;
|
|
25
|
+
fiber = fiber.return;
|
|
26
|
+
}
|
|
27
|
+
return { path, anyParentMasked };
|
|
28
|
+
};
|
|
29
|
+
const flatten = (children) => Array.isArray(children) ? children.map(String).join('') : String(children);
|
|
30
|
+
function findRCTTextChild(fiber) {
|
|
31
|
+
let node = fiber.child;
|
|
32
|
+
while (node) {
|
|
33
|
+
if (node.type === 'RCTText') {
|
|
34
|
+
return node;
|
|
35
|
+
}
|
|
36
|
+
const found = findRCTTextChild(node);
|
|
37
|
+
if (found)
|
|
38
|
+
return found;
|
|
39
|
+
node = node.sibling;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const getHostComponentInfo = (event) => {
|
|
44
|
+
var _a, _b;
|
|
45
|
+
let fiber = event._targetInst;
|
|
46
|
+
if (!fiber) {
|
|
47
|
+
console.warn('Assumption violated: _targetInst not found');
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
if (fiber.type === 'RNGestureHandlerButton') {
|
|
51
|
+
fiber = (_a = findRCTTextChild(fiber)) !== null && _a !== void 0 ? _a : fiber;
|
|
52
|
+
}
|
|
53
|
+
if (typeof fiber.type !== 'string') {
|
|
54
|
+
console.warn('Assumption violated: leaf fiber not a host component');
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const { path, anyParentMasked } = getFiberPath(fiber);
|
|
58
|
+
const id = path.join('/');
|
|
59
|
+
const leaf = path[path.length - 1];
|
|
60
|
+
const components = [leaf];
|
|
61
|
+
const { nameOrigin, detectedName } = fiber.type !== 'RCTText'
|
|
62
|
+
? {
|
|
63
|
+
nameOrigin: 'component',
|
|
64
|
+
detectedName: (_b = fiber.memoizedProps.accessibilityLabel) !== null && _b !== void 0 ? _b : leaf,
|
|
65
|
+
}
|
|
66
|
+
: anyParentMasked
|
|
67
|
+
? {
|
|
68
|
+
nameOrigin: 'masked',
|
|
69
|
+
detectedName: '***',
|
|
70
|
+
}
|
|
71
|
+
: {
|
|
72
|
+
nameOrigin: 'text',
|
|
73
|
+
detectedName: flatten(fiber.memoizedProps.children),
|
|
74
|
+
};
|
|
75
|
+
return { id, components, detectedName, nameOrigin };
|
|
76
|
+
};
|
|
77
|
+
const flushTouch = (responderComponentInfo) => {
|
|
78
|
+
if (!pendingTouch)
|
|
79
|
+
return;
|
|
80
|
+
const { touchedComponentInfo, positions, timeoutId } = pendingTouch;
|
|
81
|
+
clearTimeout(timeoutId);
|
|
82
|
+
pendingTouch = null;
|
|
83
|
+
const event = Object.assign({ 'characteristics.has_user_interaction': true, 'ui_element.detected_name': touchedComponentInfo.detectedName, 'ui_element.components': touchedComponentInfo.components, 'ui_element.id': touchedComponentInfo.id, 'interaction.type': 'touch', positions, 'ui_element.name_origin': touchedComponentInfo.nameOrigin }, (responderComponentInfo && {
|
|
84
|
+
'ui_element.responder.detected_name': responderComponentInfo.detectedName,
|
|
85
|
+
'ui_element.responder.components': responderComponentInfo.components,
|
|
86
|
+
'ui_element.responder.id': responderComponentInfo.id,
|
|
87
|
+
'ui_element.responder.name_origin': responderComponentInfo.nameOrigin,
|
|
88
|
+
}));
|
|
89
|
+
EventPipeline_1.EventPipeline.insertEvent(event);
|
|
90
|
+
};
|
|
91
|
+
const onTouch = (e) => {
|
|
92
|
+
if (!ConfigurationHandler_1.ConfigurationHandler.isUserInteractionEnabled())
|
|
93
|
+
return;
|
|
94
|
+
flushTouch();
|
|
95
|
+
const touchedComponentInfo = getHostComponentInfo(e);
|
|
96
|
+
if (!touchedComponentInfo)
|
|
97
|
+
return;
|
|
98
|
+
const { pageX, pageY } = e.nativeEvent;
|
|
99
|
+
const positions = [{ x: Math.trunc(pageX), y: Math.trunc(pageY) }];
|
|
100
|
+
const timeoutId = setTimeout(flushTouch, TOUCH_FLUSH_DELAY);
|
|
101
|
+
pendingTouch = { touchedComponentInfo, positions, timeoutId };
|
|
102
|
+
};
|
|
103
|
+
const notifyPress = (event) => flushTouch(getHostComponentInfo(event));
|
|
104
|
+
exports.notifyPress = notifyPress;
|
|
105
|
+
const wrapProvider = (originalProvider) => () => {
|
|
106
|
+
const Original = originalProvider();
|
|
107
|
+
return function DtRootWrapped(props) {
|
|
108
|
+
return (React.createElement(exports.RootWrapper, null,
|
|
109
|
+
React.createElement(Original, Object.assign({}, props))));
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
exports.wrapProvider = wrapProvider;
|
|
113
|
+
const RootWrapper = ({ children }) => (React.createElement(react_native_1.View, { pointerEvents: "box-none", collapsable: false, style: { flex: 1 }, onTouchStart: onTouch }, children));
|
|
114
|
+
exports.RootWrapper = RootWrapper;
|
|
@@ -21,3 +21,5 @@ exports.instrumentationInfo.push(generateInstrumentation('TouchableOpacity'));
|
|
|
21
21
|
exports.instrumentationInfo.push(generateInstrumentation('TouchableNativeFeedback'));
|
|
22
22
|
exports.instrumentationInfo.push(generateInstrumentation('TouchableWithoutFeedback'));
|
|
23
23
|
exports.instrumentationInfo.push(generateInstrumentation('RectButton'));
|
|
24
|
+
exports.instrumentationInfo.push(generateInstrumentation('BorderlessButton'));
|
|
25
|
+
exports.instrumentationInfo.push(generateInstrumentation('BaseButton'));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RectButton = exports.TouchableWithoutFeedback = exports.TouchableNativeFeedback = exports.TouchableHighlight = exports.TouchableOpacity = void 0;
|
|
3
|
+
exports.BaseButton = exports.BorderlessButton = exports.RectButton = exports.TouchableWithoutFeedback = exports.TouchableNativeFeedback = exports.TouchableHighlight = exports.TouchableOpacity = void 0;
|
|
4
4
|
const GestureHandler = require("react-native-gesture-handler");
|
|
5
5
|
const withOnPressMonitoring_1 = require("../../withOnPressMonitoring");
|
|
6
6
|
exports.TouchableOpacity = (0, withOnPressMonitoring_1.withOnPressMonitoring)(GestureHandler.TouchableOpacity, 'Touchable');
|
|
@@ -8,3 +8,5 @@ exports.TouchableHighlight = (0, withOnPressMonitoring_1.withOnPressMonitoring)(
|
|
|
8
8
|
exports.TouchableNativeFeedback = (0, withOnPressMonitoring_1.withOnPressMonitoring)(GestureHandler.TouchableNativeFeedback, 'Touchable');
|
|
9
9
|
exports.TouchableWithoutFeedback = (0, withOnPressMonitoring_1.withOnPressMonitoring)(GestureHandler.TouchableWithoutFeedback, 'Touchable');
|
|
10
10
|
exports.RectButton = (0, withOnPressMonitoring_1.withOnPressMonitoring)(GestureHandler.RectButton, 'Button');
|
|
11
|
+
exports.BorderlessButton = (0, withOnPressMonitoring_1.withOnPressMonitoring)(GestureHandler.BorderlessButton, 'Button');
|
|
12
|
+
exports.BaseButton = (0, withOnPressMonitoring_1.withOnPressMonitoring)(GestureHandler.BaseButton, 'Button');
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RectButton = exports.TouchableWithoutFeedback = exports.TouchableOpacity = exports.TouchableNativeFeedback = exports.TouchableHighlight = void 0;
|
|
3
|
+
exports.BaseButton = exports.BorderlessButton = exports.RectButton = exports.TouchableWithoutFeedback = exports.TouchableOpacity = exports.TouchableNativeFeedback = exports.TouchableHighlight = void 0;
|
|
4
4
|
var Touchables_1 = require("./Touchables");
|
|
5
5
|
Object.defineProperty(exports, "TouchableHighlight", { enumerable: true, get: function () { return Touchables_1.TouchableHighlight; } });
|
|
6
6
|
Object.defineProperty(exports, "TouchableNativeFeedback", { enumerable: true, get: function () { return Touchables_1.TouchableNativeFeedback; } });
|
|
7
7
|
Object.defineProperty(exports, "TouchableOpacity", { enumerable: true, get: function () { return Touchables_1.TouchableOpacity; } });
|
|
8
8
|
Object.defineProperty(exports, "TouchableWithoutFeedback", { enumerable: true, get: function () { return Touchables_1.TouchableWithoutFeedback; } });
|
|
9
9
|
Object.defineProperty(exports, "RectButton", { enumerable: true, get: function () { return Touchables_1.RectButton; } });
|
|
10
|
+
Object.defineProperty(exports, "BorderlessButton", { enumerable: true, get: function () { return Touchables_1.BorderlessButton; } });
|
|
11
|
+
Object.defineProperty(exports, "BaseButton", { enumerable: true, get: function () { return Touchables_1.BaseButton; } });
|
|
@@ -7,6 +7,7 @@ const ConsoleLogger_1 = require("../../lib/core/logging/ConsoleLogger");
|
|
|
7
7
|
const ConfigurationHandler_1 = require("../../lib/core/configuration/ConfigurationHandler");
|
|
8
8
|
const Dynatrace_1 = require("../../lib/core/Dynatrace");
|
|
9
9
|
const ComponentUtil_1 = require("../jsx/components/ComponentUtil");
|
|
10
|
+
const UserInteraction_1 = require("./UserInteraction");
|
|
10
11
|
const Logger = new ConsoleLogger_1.ConsoleLogger('TouchableHelper');
|
|
11
12
|
function withOnPressMonitoring(Component, fallbackName) {
|
|
12
13
|
return React.forwardRef((props, ref) => {
|
|
@@ -73,6 +74,11 @@ const wrapOnPress = (onPress, touchableName, isButtonLike, buttonLabel) => {
|
|
|
73
74
|
Logger.debug(`registerPress bridge hook failed: ${errorMsg}`);
|
|
74
75
|
}
|
|
75
76
|
}
|
|
77
|
+
if (args[0] &&
|
|
78
|
+
typeof args[0] === 'object' &&
|
|
79
|
+
'nativeEvent' in args[0]) {
|
|
80
|
+
(0, UserInteraction_1.notifyPress)(args[0]);
|
|
81
|
+
}
|
|
76
82
|
if (!ConfigurationHandler_1.ConfigurationHandler.isConfigurationAvailable()) {
|
|
77
83
|
Logger.info('React Native plugin has not been started yet! Touch will not be reported!');
|
|
78
84
|
onPress(...args);
|
|
@@ -9,7 +9,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
const child_process_1 = require("child_process");
|
|
13
12
|
const TerminalReporter = (() => {
|
|
14
13
|
try {
|
|
15
14
|
return require('metro/src/lib/TerminalReporter');
|
|
@@ -24,19 +23,6 @@ const paths = require('../scripts/PathsConstants');
|
|
|
24
23
|
const reporter = new TerminalReporter(new Terminal(process.stdout));
|
|
25
24
|
const updateOrig = TerminalReporter.prototype.update;
|
|
26
25
|
TerminalReporter.prototype.update = function update(event) {
|
|
27
|
-
if ((event === null || event === void 0 ? void 0 : event.type) === 'bundle_build_done') {
|
|
28
|
-
(0, child_process_1.exec)('npx lineOffsetDynatrace env prod', (error, stdout, stderr) => {
|
|
29
|
-
if (error) {
|
|
30
|
-
console.error(`Error: ${error.message}`);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
if (stderr) {
|
|
34
|
-
console.error(`stderr: ${stderr}`);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
console.log(`${stdout}`);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
26
|
return updateOrig === null || updateOrig === void 0 ? void 0 : updateOrig.apply(this, arguments);
|
|
41
27
|
};
|
|
42
28
|
const update = (event) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getUpstreamTransformer = exports.transform = void 0;
|
|
4
|
-
const nodePath = require("path");
|
|
5
|
-
const PathsConstants_1 = require("../scripts/PathsConstants");
|
|
6
4
|
const config = require("../scripts/Config");
|
|
7
5
|
const Logger_1 = require("../scripts/Logger");
|
|
8
|
-
const
|
|
6
|
+
const BabelPluginDynatrace_1 = require("../instrumentation/BabelPluginDynatrace");
|
|
9
7
|
const instrumentor = require("../instrumentation/DynatraceInstrumentation");
|
|
10
|
-
const
|
|
11
|
-
const transform = (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
8
|
+
const Config_1 = require("../scripts/Config");
|
|
9
|
+
const transform = (args) => {
|
|
10
|
+
var _a;
|
|
11
|
+
let src = args.src;
|
|
12
|
+
const { filename, options } = args;
|
|
15
13
|
let reactOptions;
|
|
16
14
|
try {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
reactOptions = config.readConfig(nodePath.join(PathsConstants_1.default.getConfigFilePath()));
|
|
15
|
+
reactOptions = (0, Config_1.readConfigDefault)();
|
|
16
|
+
if (!reactOptions.react.useLegacyJscodeshift) {
|
|
17
|
+
args.plugins = [BabelPluginDynatrace_1.default, ...((_a = args.plugins) !== null && _a !== void 0 ? _a : [])];
|
|
18
|
+
return (0, exports.getUpstreamTransformer)(reactOptions === null || reactOptions === void 0 ? void 0 : reactOptions.react).transform(args);
|
|
22
19
|
}
|
|
23
20
|
src = instrumentor.instrument(src, filename, reactOptions.react);
|
|
24
21
|
}
|
|
@@ -7,12 +7,18 @@ exports.DEFAULT_DT_CONFIG = {
|
|
|
7
7
|
artifactsDir: Types_1.Literals.DefaultArtifactsDir,
|
|
8
8
|
runtimeImport: Types_1.Literals.DefaultRuntimeImport,
|
|
9
9
|
componentNames: {
|
|
10
|
-
segment: Types_1.Literals.DefaultSegmentName,
|
|
11
|
-
tabButton: Types_1.Literals.DefaultTabButtonName,
|
|
12
10
|
root: Types_1.Literals.DefaultRootName,
|
|
13
11
|
},
|
|
14
12
|
ui: {
|
|
15
13
|
skipTags: ['NavigationContainer'],
|
|
14
|
+
pathId: {
|
|
15
|
+
enabled: true,
|
|
16
|
+
propName: 'dtPathID',
|
|
17
|
+
uiRootPropName: 'dtUIRoot',
|
|
18
|
+
injectUiRoot: false,
|
|
19
|
+
skipProviderTags: true,
|
|
20
|
+
blacklist: ['AnalyticsRoot', 'Stack', 'React.Fragment', 'Fragment'],
|
|
21
|
+
},
|
|
16
22
|
},
|
|
17
23
|
};
|
|
18
24
|
const getDTConfig = () => exports.DEFAULT_DT_CONFIG;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const jsc = require("jscodeshift");
|
|
4
|
+
const Plugin_1 = require("./Plugin");
|
|
5
|
+
const j = jsc.withParser('tsx');
|
|
6
|
+
const runPlugin = (src, filename = 'FragmentCase.tsx') => {
|
|
7
|
+
const root = j(src);
|
|
8
|
+
const out = (0, Plugin_1.applyDTPathIdInstrumentation)(root, filename);
|
|
9
|
+
return out.root.toSource({ quote: 'single' });
|
|
10
|
+
};
|
|
11
|
+
describe('UI interaction Fragment blacklist', () => {
|
|
12
|
+
it('does not inject dtUIRoot by default', () => {
|
|
13
|
+
const code = runPlugin(`
|
|
14
|
+
import React from 'react';
|
|
15
|
+
import { View, Text } from 'react-native';
|
|
16
|
+
|
|
17
|
+
export default function Screen() {
|
|
18
|
+
return (
|
|
19
|
+
<View>
|
|
20
|
+
<Text>Hello</Text>
|
|
21
|
+
</View>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
`);
|
|
25
|
+
expect(code).toContain('<View dtPathID=');
|
|
26
|
+
expect(code).not.toContain('dtUIRoot');
|
|
27
|
+
});
|
|
28
|
+
it('does not instrument React.Fragment', () => {
|
|
29
|
+
const code = runPlugin(`
|
|
30
|
+
import React from 'react';
|
|
31
|
+
import { View, Text } from 'react-native';
|
|
32
|
+
|
|
33
|
+
export default function Screen() {
|
|
34
|
+
return (
|
|
35
|
+
<View>
|
|
36
|
+
<React.Fragment>
|
|
37
|
+
<Text>Hello</Text>
|
|
38
|
+
</React.Fragment>
|
|
39
|
+
</View>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
`);
|
|
43
|
+
expect(code).toContain('<React.Fragment>');
|
|
44
|
+
expect(code).not.toContain('<React.Fragment dtPathID=');
|
|
45
|
+
expect(code).toContain('<View dtPathID=');
|
|
46
|
+
expect(code).toContain('<Text dtPathID=');
|
|
47
|
+
});
|
|
48
|
+
it('does not instrument imported Fragment', () => {
|
|
49
|
+
const code = runPlugin(`
|
|
50
|
+
import React, { Fragment } from 'react';
|
|
51
|
+
import { View, Text } from 'react-native';
|
|
52
|
+
|
|
53
|
+
export default function Screen() {
|
|
54
|
+
return (
|
|
55
|
+
<View>
|
|
56
|
+
<Fragment>
|
|
57
|
+
<Text>Hello</Text>
|
|
58
|
+
</Fragment>
|
|
59
|
+
</View>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
`);
|
|
63
|
+
expect(code).toContain('<Fragment>');
|
|
64
|
+
expect(code).not.toContain('<Fragment dtPathID=');
|
|
65
|
+
expect(code).toContain('<View dtPathID=');
|
|
66
|
+
expect(code).toContain('<Text dtPathID=');
|
|
67
|
+
});
|
|
68
|
+
it('does not instrument aliased Fragment imports', () => {
|
|
69
|
+
const code = runPlugin(`
|
|
70
|
+
import React, { Fragment as F } from 'react';
|
|
71
|
+
import { View, Text } from 'react-native';
|
|
72
|
+
|
|
73
|
+
export default function Screen() {
|
|
74
|
+
return (
|
|
75
|
+
<View>
|
|
76
|
+
<F>
|
|
77
|
+
<Text>Hello</Text>
|
|
78
|
+
</F>
|
|
79
|
+
</View>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
`);
|
|
83
|
+
expect(code).toContain('<F>');
|
|
84
|
+
expect(code).not.toContain('<F dtPathID=');
|
|
85
|
+
expect(code).toContain('<View dtPathID=');
|
|
86
|
+
expect(code).toContain('<Text dtPathID=');
|
|
87
|
+
});
|
|
88
|
+
it('does not instrument empty fragment tags', () => {
|
|
89
|
+
const code = runPlugin(`
|
|
90
|
+
import React from 'react';
|
|
91
|
+
import { View, Text } from 'react-native';
|
|
92
|
+
|
|
93
|
+
export default function Screen() {
|
|
94
|
+
return (
|
|
95
|
+
<>
|
|
96
|
+
<View>
|
|
97
|
+
<Text>Hello</Text>
|
|
98
|
+
</View>
|
|
99
|
+
</>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
`);
|
|
103
|
+
expect(code).toContain('<>');
|
|
104
|
+
expect(code).toContain('<View dtPathID=');
|
|
105
|
+
expect(code).toContain('<Text dtPathID=');
|
|
106
|
+
});
|
|
107
|
+
it('removes existing dtPathID from React.Fragment JSX', () => {
|
|
108
|
+
const code = runPlugin(`
|
|
109
|
+
import React from 'react';
|
|
110
|
+
export default function Screen() {
|
|
111
|
+
return <React.Fragment dtPathID='bad'>Hello</React.Fragment>;
|
|
112
|
+
}
|
|
113
|
+
`);
|
|
114
|
+
expect(code).toContain('<React.Fragment>');
|
|
115
|
+
expect(code).not.toContain('dtPathID=');
|
|
116
|
+
});
|
|
117
|
+
it('removes dtPathID from transpiled jsx Fragment call', () => {
|
|
118
|
+
const code = runPlugin(`
|
|
119
|
+
import { Fragment as _Fragment, jsx as _jsx } from 'react/jsx-runtime';
|
|
120
|
+
export default function Screen() {
|
|
121
|
+
return _jsx(_Fragment, { dtPathID: 'bad', children: 'Hello' });
|
|
122
|
+
}
|
|
123
|
+
`);
|
|
124
|
+
expect(code).toContain('_jsx(_Fragment');
|
|
125
|
+
expect(code).not.toContain('dtPathID');
|
|
126
|
+
});
|
|
127
|
+
it('removes dtPathID from transpiled jsxDEV Fragment call', () => {
|
|
128
|
+
const code = runPlugin(`
|
|
129
|
+
import { Fragment as _Fragment, jsxDEV as _jsxDEV } from 'react/jsx-dev-runtime';
|
|
130
|
+
export default function Screen() {
|
|
131
|
+
return _jsxDEV(_Fragment, { dtPathID: 'bad', children: 'Hello' }, void 0, false, void 0, this);
|
|
132
|
+
}
|
|
133
|
+
`);
|
|
134
|
+
expect(code).toContain('_jsxDEV(_Fragment');
|
|
135
|
+
expect(code).not.toContain('dtPathID');
|
|
136
|
+
});
|
|
137
|
+
it('does not instrument Stack elements', () => {
|
|
138
|
+
const code = runPlugin(`
|
|
139
|
+
import React from 'react';
|
|
140
|
+
import { Stack } from 'expo-router';
|
|
141
|
+
import { View, Text } from 'react-native';
|
|
142
|
+
|
|
143
|
+
export default function Screen() {
|
|
144
|
+
return (
|
|
145
|
+
<View>
|
|
146
|
+
<Stack>
|
|
147
|
+
<Text>Hello</Text>
|
|
148
|
+
</Stack>
|
|
149
|
+
</View>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
`);
|
|
153
|
+
expect(code).toContain('<Stack>');
|
|
154
|
+
expect(code).not.toContain('<Stack dtPathID=');
|
|
155
|
+
expect(code).toContain('<View dtPathID=');
|
|
156
|
+
expect(code).toContain('<Text dtPathID=');
|
|
157
|
+
});
|
|
158
|
+
it('skips instrumentation for Expo package files', () => {
|
|
159
|
+
const src = `
|
|
160
|
+
import React from 'react';
|
|
161
|
+
import { View, Text } from 'react-native';
|
|
162
|
+
export default function Screen() {
|
|
163
|
+
return <View><Text>Hello</Text></View>;
|
|
164
|
+
}
|
|
165
|
+
`;
|
|
166
|
+
const code = runPlugin(src, 'node_modules/expo-router/build/qualified-entry.js');
|
|
167
|
+
expect(code).toEqual(j(src).toSource({ quote: 'single' }));
|
|
168
|
+
expect(code).not.toContain('dtPathID');
|
|
169
|
+
});
|
|
170
|
+
});
|