Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

## Unreleased

### Features

- Use `accessibilityLabel`, `aria-label`, and `testID` as fallback labels for touch breadcrumbs when `sentry-label` is not set ([#6103](https://github.com/getsentry/sentry-react-native/pull/6103))

### Fixes

- Fix the issue with uploading iOS Debug Symbols in EAS Build when using pnpm ([#6076](https://github.com/getsentry/sentry-react-native/issues/6076))
Expand Down
37 changes: 28 additions & 9 deletions packages/core/src/js/touchevents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ const SENTRY_SPAN_ATTRIBUTES_PROP_KEY = 'sentry-span-attributes';
const SENTRY_COMPONENT_PROP_KEY = 'data-sentry-component';
const SENTRY_ELEMENT_PROP_KEY = 'data-sentry-element';
const SENTRY_FILE_PROP_KEY = 'data-sentry-source-file';
const ACCESSIBILITY_LABEL_PROP_KEY = 'accessibilityLabel';
const ARIA_LABEL_PROP_KEY = 'aria-label';
const TEST_ID_PROP_KEY = 'testID';

interface ElementInstance {
elementType?: {
Expand Down Expand Up @@ -364,15 +367,31 @@ function getFileName(props: Record<string, unknown>): string | undefined {
}

function getLabelValue(props: Record<string, unknown>, labelKey: string | undefined): string | undefined {
return typeof props[SENTRY_LABEL_PROP_KEY] === 'string' && props[SENTRY_LABEL_PROP_KEY].length > 0
? props[SENTRY_LABEL_PROP_KEY]
: // For some reason type narrowing doesn't work as expected with indexing when checking it all in one go in
// the "check-label" if sentence, so we have to assign it to a variable here first
// oxlint-disable-next-line typescript-eslint(no-unnecessary-type-assertion)
typeof labelKey === 'string' && typeof props[labelKey] == 'string' && (props[labelKey] as string).length > 0
? // oxlint-disable-next-line typescript-eslint(no-unnecessary-type-assertion)
(props[labelKey] as string)
: undefined;
if (typeof props[SENTRY_LABEL_PROP_KEY] === 'string' && props[SENTRY_LABEL_PROP_KEY].length > 0) {
return props[SENTRY_LABEL_PROP_KEY];
}

// For some reason type narrowing doesn't work as expected with indexing when checking it all in one go in
// the "check-label" if sentence, so we have to assign it to a variable here first
// oxlint-disable-next-line typescript-eslint(no-unnecessary-type-assertion)
if (typeof labelKey === 'string' && typeof props[labelKey] == 'string' && (props[labelKey] as string).length > 0) {
// oxlint-disable-next-line typescript-eslint(no-unnecessary-type-assertion)
return props[labelKey] as string;
}

if (typeof props[ACCESSIBILITY_LABEL_PROP_KEY] === 'string' && props[ACCESSIBILITY_LABEL_PROP_KEY].length > 0) {
return props[ACCESSIBILITY_LABEL_PROP_KEY];
}

if (typeof props[ARIA_LABEL_PROP_KEY] === 'string' && props[ARIA_LABEL_PROP_KEY].length > 0) {
return props[ARIA_LABEL_PROP_KEY];
}

if (typeof props[TEST_ID_PROP_KEY] === 'string' && props[TEST_ID_PROP_KEY].length > 0) {
return props[TEST_ID_PROP_KEY];
}

return undefined;
}

function getSpanAttributes(currentInst: ElementInstance): Record<string, SpanAttributeValue> | undefined {
Expand Down
213 changes: 213 additions & 0 deletions packages/core/test/touchevents.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,219 @@ describe('TouchEventBoundary._onTouchStart', () => {
});
});

it('accessibilityLabel is used as label fallback when sentry-label is not set', () => {
const { defaultProps } = TouchEventBoundary;
const boundary = new TouchEventBoundary(defaultProps);

const event = {
_targetInst: {
elementType: {
displayName: 'Button',
},
memoizedProps: {
accessibilityLabel: 'Save workout',
},
},
};

// @ts-expect-error Calling private member
boundary._onTouchStart(event);

expect(addBreadcrumb).toHaveBeenCalledWith({
category: defaultProps.breadcrumbCategory,
data: {
path: [{ name: 'Button', label: 'Save workout' }],
},
level: 'info' as SeverityLevel,
message: 'Touch event within element: Save workout',
type: defaultProps.breadcrumbType,
});
});

it('testID is used as label fallback when sentry-label and accessibilityLabel are not set', () => {
const { defaultProps } = TouchEventBoundary;
const boundary = new TouchEventBoundary(defaultProps);

const event = {
_targetInst: {
elementType: {
displayName: 'Button',
},
memoizedProps: {
testID: 'save-workout-button',
},
},
};

// @ts-expect-error Calling private member
boundary._onTouchStart(event);

expect(addBreadcrumb).toHaveBeenCalledWith({
category: defaultProps.breadcrumbCategory,
data: {
path: [{ name: 'Button', label: 'save-workout-button' }],
},
level: 'info' as SeverityLevel,
message: 'Touch event within element: save-workout-button',
type: defaultProps.breadcrumbType,
});
});

it('sentry-label takes priority over accessibilityLabel and testID', () => {
const { defaultProps } = TouchEventBoundary;
const boundary = new TouchEventBoundary(defaultProps);

const event = {
_targetInst: {
elementType: {
displayName: 'Button',
},
memoizedProps: {
'sentry-label': 'explicit-label',
accessibilityLabel: 'Save workout',
testID: 'save-workout-button',
},
},
};

// @ts-expect-error Calling private member
boundary._onTouchStart(event);

expect(addBreadcrumb).toHaveBeenCalledWith({
category: defaultProps.breadcrumbCategory,
data: {
path: [{ name: 'Button', label: 'explicit-label' }],
},
level: 'info' as SeverityLevel,
message: 'Touch event within element: explicit-label',
type: defaultProps.breadcrumbType,
});
});

it('custom labelName takes priority over accessibilityLabel', () => {
const { defaultProps } = TouchEventBoundary;
const boundary = new TouchEventBoundary({
...defaultProps,
labelName: 'custom-label-key',
});

const event = {
_targetInst: {
elementType: {
displayName: 'Button',
},
memoizedProps: {
'custom-label-key': 'Custom label',
accessibilityLabel: 'Save workout',
testID: 'save-workout-button',
},
},
};

// @ts-expect-error Calling private member
boundary._onTouchStart(event);

expect(addBreadcrumb).toHaveBeenCalledWith({
category: defaultProps.breadcrumbCategory,
data: {
path: [{ name: 'Button', label: 'Custom label' }],
},
level: 'info' as SeverityLevel,
message: 'Touch event within element: Custom label',
type: defaultProps.breadcrumbType,
});
});

it('aria-label is used as fallback after accessibilityLabel', () => {
const { defaultProps } = TouchEventBoundary;
const boundary = new TouchEventBoundary(defaultProps);

const event = {
_targetInst: {
elementType: {
displayName: 'Button',
},
memoizedProps: {
'aria-label': 'Close dialog',
},
},
};

// @ts-expect-error Calling private member
boundary._onTouchStart(event);

expect(addBreadcrumb).toHaveBeenCalledWith({
category: defaultProps.breadcrumbCategory,
data: {
path: [{ name: 'Button', label: 'Close dialog' }],
},
level: 'info' as SeverityLevel,
message: 'Touch event within element: Close dialog',
type: defaultProps.breadcrumbType,
});
});

it('accessibilityLabel takes priority over aria-label and testID', () => {
const { defaultProps } = TouchEventBoundary;
const boundary = new TouchEventBoundary(defaultProps);

const event = {
_targetInst: {
elementType: {
displayName: 'Button',
},
memoizedProps: {
accessibilityLabel: 'Save workout',
'aria-label': 'Close dialog',
testID: 'save-workout-button',
},
},
};

// @ts-expect-error Calling private member
boundary._onTouchStart(event);

expect(addBreadcrumb).toHaveBeenCalledWith({
category: defaultProps.breadcrumbCategory,
data: {
path: [{ name: 'Button', label: 'Save workout' }],
},
level: 'info' as SeverityLevel,
message: 'Touch event within element: Save workout',
type: defaultProps.breadcrumbType,
});
});

it('accessibilityLabel takes priority over testID', () => {
const { defaultProps } = TouchEventBoundary;
const boundary = new TouchEventBoundary(defaultProps);

const event = {
_targetInst: {
elementType: {
displayName: 'Button',
},
memoizedProps: {
accessibilityLabel: 'Save workout',
testID: 'save-workout-button',
},
},
};

// @ts-expect-error Calling private member
boundary._onTouchStart(event);

expect(addBreadcrumb).toHaveBeenCalledWith({
category: defaultProps.breadcrumbCategory,
data: {
path: [{ name: 'Button', label: 'Save workout' }],
},
level: 'info' as SeverityLevel,
message: 'Touch event within element: Save workout',
type: defaultProps.breadcrumbType,
});
});

it('ignoreNames', () => {
const { defaultProps } = TouchEventBoundary;
const boundary = new TouchEventBoundary({
Expand Down
Loading