Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,95 @@ it('M send a RUM Action event W interceptOnPress { no accessibilityLabel, no ele
expect(DdRum.addAction.mock.calls[0][1]).toBe(UNKNOWN_TARGET_NAME);
});

it('M send a RUM Action event W interceptOnPress { event nested in props wrapper (react-native-ui-lib pattern) } ', async () => {
// GIVEN
const fakeAccessibilityLabel = 'wrapped_target';
const fakeEvent = {
_targetInst: {
memoizedProps: {
accessibilityLabel: fakeAccessibilityLabel
}
}
};
const fakeArguments = {
onPress: jest.fn(),
someOtherProp: 'value',
event: fakeEvent
};

// WHEN
testedEventsInterceptor.interceptOnPress(fakeArguments);

// THEN
expect(DdRum.addAction.mock.calls.length).toBe(1);
expect(DdRum.addAction.mock.calls[0][0]).toBe(RumActionType.TAP);
expect(DdRum.addAction.mock.calls[0][1]).toBe(fakeAccessibilityLabel);
expect(DdRum.addAction.mock.calls[0][4]).toBe(fakeEvent);
});

it('M send a RUM Action event W interceptOnPress { event nested in props wrapper with dd-action-name } ', async () => {
// GIVEN
const fakeDdActionLabel = 'WrappedDdActionLabel';
const fakeEvent = {
_targetInst: {
memoizedProps: {
'dd-action-name': fakeDdActionLabel
}
}
};
const fakeArguments = {
onPress: jest.fn(),
event: fakeEvent
};

// WHEN
testedEventsInterceptor.interceptOnPress(fakeArguments);

// THEN
expect(DdRum.addAction.mock.calls.length).toBe(1);
expect(DdRum.addAction.mock.calls[0][0]).toBe(RumActionType.TAP);
expect(DdRum.addAction.mock.calls[0][1]).toBe(fakeDdActionLabel);
expect(DdRum.addAction.mock.calls[0][4]).toBe(fakeEvent);
});

it('M do nothing W interceptOnPress { props wrapper without nested event (incubator pattern) } ', async () => {
// GIVEN - react-native-ui-lib Incubator passes props without event
const fakeArguments = {
onPress: jest.fn(),
someOtherProp: 'value'
};

// WHEN
testedEventsInterceptor.interceptOnPress(fakeArguments);

// THEN
expect(DdRum.addAction.mock.calls.length).toBe(0);
expect(InternalLog.log.mock.calls.length).toBe(1);
expect(InternalLog.log.mock.calls[0][0]).toBe(
DdEventsInterceptor.ACTION_EVENT_DROPPED_DEBUG_MESSAGE
);
expect(InternalLog.log.mock.calls[0][1]).toBe(SdkVerbosity.DEBUG);
});

it('M do nothing W interceptOnPress { props wrapper with event that has no _targetInst } ', async () => {
// GIVEN - event property exists but is not a valid native event
const fakeArguments = {
onPress: jest.fn(),
event: { nativeEvent: { pageX: 0, pageY: 0 } }
};

// WHEN
testedEventsInterceptor.interceptOnPress(fakeArguments);

// THEN
expect(DdRum.addAction.mock.calls.length).toBe(0);
expect(InternalLog.log.mock.calls.length).toBe(1);
expect(InternalLog.log.mock.calls[0][0]).toBe(
DdEventsInterceptor.ACTION_EVENT_DROPPED_DEBUG_MESSAGE
);
expect(InternalLog.log.mock.calls[0][1]).toBe(SdkVerbosity.DEBUG);
});

it('M do nothing W interceptOnPress { invalid arguments - empty object } ', async () => {
// GIVEN
const fakeArguments = {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,15 @@ export class DdEventsInterceptor implements EventsInterceptor {
}

interceptOnPress(...args: any[]): void {
if (args.length > 0 && args[0] && args[0]._targetInst) {
const event = this.resolveNativeEvent(args);
if (event) {
const currentTime = Date.now();
const timestampDifference = Math.abs(
Date.now() - this.debouncingStartedTimestamp
);
if (timestampDifference > DEBOUNCE_EVENT_THRESHOLD_IN_MS) {
const targetNode = args[0]._targetInst;
this.handleTargetEvent(targetNode, args[0]);
const targetNode = event._targetInst;
this.handleTargetEvent(targetNode, event);
// we add an approximated 1 millisecond for the execution time of the `handleTargetEvent` function
this.debouncingStartedTimestamp =
currentTime + HANDLE_EVENT_APP_EXECUTION_TIME_IN_MS;
Expand All @@ -68,6 +69,27 @@ export class DdEventsInterceptor implements EventsInterceptor {
}
}

/**
* Resolves the native GestureResponderEvent from the onPress arguments.
*
* Some third-party libraries (e.g. react-native-ui-lib) wrap the native
* event inside a props object: `onPress({...componentProps, event})`.
* This method checks for `_targetInst` on `args[0]` first (standard RN),
* then falls back to `args[0].event` for the wrapped pattern.
*/
private resolveNativeEvent(args: any[]): any | null {
if (args.length === 0 || !args[0]) {
return null;
}
if (args[0]._targetInst) {
return args[0];
}
if (args[0].event && args[0].event._targetInst) {
return args[0].event;
}
return null;
}

private handleTargetEvent(targetNode: any, event: unknown) {
if (targetNode) {
const resolvedTargetName = this.resolveTargetName(targetNode);
Expand Down
Loading