mirror of
https://github.com/expo/sentry-react-native.git
synced 2026-11-14 19:12:28 -10:00
257 lines
8.0 KiB
TypeScript
257 lines
8.0 KiB
TypeScript
import type { Event, EventHint } from '@sentry/types';
|
|
|
|
import type { ReactNativeError } from '../../src/js/integrations/debugsymbolicator';
|
|
import type { ReactNativeContext } from '../../src/js/integrations/reactnativeinfo';
|
|
import { ReactNativeInfo } from '../../src/js/integrations/reactnativeinfo';
|
|
|
|
let mockedIsHermesEnabled: jest.Mock<boolean, []>;
|
|
let mockedIsTurboModuleEnabled: jest.Mock<boolean, []>;
|
|
let mockedIsFabricEnabled: jest.Mock<boolean, []>;
|
|
let mockedGetReactNativeVersion: jest.Mock<string, []>;
|
|
let mockedGetHermesVersion: jest.Mock<string | undefined, []>;
|
|
let mockedIsExpo: jest.Mock<boolean, []>;
|
|
|
|
jest.mock('../../src/js/utils/environment', () => ({
|
|
isHermesEnabled: () => mockedIsHermesEnabled(),
|
|
isTurboModuleEnabled: () => mockedIsTurboModuleEnabled(),
|
|
isFabricEnabled: () => mockedIsFabricEnabled(),
|
|
getReactNativeVersion: () => mockedGetReactNativeVersion(),
|
|
getHermesVersion: () => mockedGetHermesVersion(),
|
|
isExpo: () => mockedIsExpo(),
|
|
}));
|
|
|
|
describe('React Native Info', () => {
|
|
beforeEach(() => {
|
|
mockedIsHermesEnabled = jest.fn().mockReturnValue(true);
|
|
mockedIsTurboModuleEnabled = jest.fn().mockReturnValue(false);
|
|
mockedIsFabricEnabled = jest.fn().mockReturnValue(false);
|
|
mockedGetReactNativeVersion = jest.fn().mockReturnValue('1000.0.0-test');
|
|
mockedGetHermesVersion = jest.fn().mockReturnValue(undefined);
|
|
mockedIsExpo = jest.fn().mockReturnValue(false);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
});
|
|
|
|
it('does not pollute event with undefined fields', async () => {
|
|
const mockEvent: Event = {
|
|
message: 'test',
|
|
};
|
|
const mockedHint: EventHint = {};
|
|
const actualEvent = await executeIntegrationFor(mockEvent, mockedHint);
|
|
|
|
expectMocksToBeCalledOnce();
|
|
expect(actualEvent).toEqual(<Event>{
|
|
message: 'test',
|
|
contexts: {
|
|
react_native_context: <ReactNativeContext>{
|
|
turbo_module: false,
|
|
fabric: false,
|
|
js_engine: 'hermes',
|
|
hermes_debug_info: true,
|
|
react_native_version: '1000.0.0-test',
|
|
expo: false,
|
|
},
|
|
},
|
|
tags: {
|
|
hermes: 'true',
|
|
},
|
|
});
|
|
});
|
|
|
|
it('adds hermes tag and js_engine to context if hermes enabled', async () => {
|
|
mockedIsHermesEnabled = jest.fn().mockReturnValue(true);
|
|
mockedGetHermesVersion = jest.fn().mockReturnValue('for RN 999.0.0');
|
|
const actualEvent = await executeIntegrationFor({}, {});
|
|
|
|
expectMocksToBeCalledOnce();
|
|
expect(actualEvent?.tags?.hermes).toEqual('true');
|
|
expect(actualEvent?.contexts?.react_native_context).toEqual(
|
|
expect.objectContaining({
|
|
js_engine: 'hermes',
|
|
hermes_version: 'for RN 999.0.0',
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('does not override existing hermes tag', async () => {
|
|
mockedIsHermesEnabled = jest.fn().mockReturnValue(true);
|
|
const mockedEvent: Event = {
|
|
tags: {
|
|
hermes: 'test_hermes_tag',
|
|
},
|
|
};
|
|
const actualEvent = await executeIntegrationFor(mockedEvent, {});
|
|
|
|
expectMocksToBeCalledOnce();
|
|
expect(actualEvent?.tags?.hermes).toEqual('test_hermes_tag');
|
|
});
|
|
|
|
it('adds engine from rn error', async () => {
|
|
mockedIsHermesEnabled = jest.fn().mockReturnValue(false);
|
|
const mockedHint: EventHint = {
|
|
originalException: <ReactNativeError>{
|
|
jsEngine: 'test_engine',
|
|
},
|
|
};
|
|
const actualEvent = await executeIntegrationFor({}, mockedHint);
|
|
|
|
expectMocksToBeCalledOnce();
|
|
expect(actualEvent?.tags?.hermes).toEqual(undefined);
|
|
expect((actualEvent?.contexts?.react_native_context as ReactNativeContext | undefined)?.js_engine).toEqual(
|
|
'test_engine',
|
|
);
|
|
});
|
|
|
|
it('adds component stack', async () => {
|
|
const mockedHint: EventHint = {
|
|
originalException: <ReactNativeError>{
|
|
componentStack: 'test_stack',
|
|
},
|
|
};
|
|
const actualEvent = await executeIntegrationFor({}, mockedHint);
|
|
|
|
expectMocksToBeCalledOnce();
|
|
expect((actualEvent?.contexts?.react_native_context as ReactNativeContext | undefined)?.component_stack).toEqual(
|
|
'test_stack',
|
|
);
|
|
});
|
|
|
|
it('marks turbo modules enabled', async () => {
|
|
mockedIsTurboModuleEnabled = jest.fn().mockReturnValue(true);
|
|
const actualEvent = await executeIntegrationFor({}, {});
|
|
|
|
expectMocksToBeCalledOnce();
|
|
expect((actualEvent?.contexts?.react_native_context as ReactNativeContext | undefined)?.turbo_module).toEqual(true);
|
|
});
|
|
|
|
it('marks fabric enabled', async () => {
|
|
mockedIsFabricEnabled = jest.fn().mockReturnValue(true);
|
|
const actualEvent = await executeIntegrationFor({}, {});
|
|
|
|
expectMocksToBeCalledOnce();
|
|
expect((actualEvent?.contexts?.react_native_context as ReactNativeContext | undefined)?.fabric).toEqual(true);
|
|
});
|
|
|
|
it('does not override existing react_native_context', async () => {
|
|
const mockedEvent: Event = {
|
|
contexts: {
|
|
react_native_context: {
|
|
test: 'context',
|
|
},
|
|
},
|
|
};
|
|
const actualEvent = await executeIntegrationFor(mockedEvent, {});
|
|
|
|
expectMocksToBeCalledOnce();
|
|
expect(actualEvent?.contexts?.react_native_context).toEqual({
|
|
test: 'context',
|
|
});
|
|
});
|
|
|
|
it('add hermes_debug_info to react_native_context based on exception frames (hermes bytecode frames present -> no debug info)', async () => {
|
|
mockedIsHermesEnabled = jest.fn().mockReturnValue(true);
|
|
|
|
const mockedEvent: Event = {
|
|
exception: {
|
|
values: [
|
|
{
|
|
stacktrace: {
|
|
frames: [
|
|
{
|
|
platform: 'java',
|
|
lineno: 2,
|
|
},
|
|
{
|
|
lineno: 1,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const actualEvent = await executeIntegrationFor(mockedEvent, {});
|
|
|
|
expectMocksToBeCalledOnce();
|
|
expect(actualEvent?.contexts?.react_native_context?.hermes_debug_info).toEqual(false);
|
|
});
|
|
|
|
it('does not hermes_debug_info to react_native_context based on threads frames (hermes bytecode frames present -> no debug info)', async () => {
|
|
mockedIsHermesEnabled = jest.fn().mockReturnValue(true);
|
|
|
|
const mockedEvent: Event = {
|
|
threads: {
|
|
values: [
|
|
{
|
|
stacktrace: {
|
|
frames: [
|
|
{
|
|
platform: 'java',
|
|
lineno: 2,
|
|
},
|
|
{
|
|
lineno: 1,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const actualEvent = await executeIntegrationFor(mockedEvent, {});
|
|
|
|
expectMocksToBeCalledOnce();
|
|
expect(actualEvent?.contexts?.react_native_context?.hermes_debug_info).toEqual(false);
|
|
});
|
|
|
|
it('adds hermes_debug_info to react_native_context (no hermes bytecode frames found -> debug info present)', async () => {
|
|
mockedIsHermesEnabled = jest.fn().mockReturnValue(true);
|
|
|
|
const mockedEvent: Event = {
|
|
threads: {
|
|
values: [
|
|
{
|
|
stacktrace: {
|
|
frames: [
|
|
{
|
|
platform: 'java',
|
|
lineno: 2,
|
|
},
|
|
{
|
|
lineno: 2,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const actualEvent = await executeIntegrationFor(mockedEvent, {});
|
|
|
|
expectMocksToBeCalledOnce();
|
|
expect(actualEvent?.contexts?.react_native_context?.hermes_debug_info).toEqual(true);
|
|
});
|
|
});
|
|
|
|
function expectMocksToBeCalledOnce() {
|
|
expect(mockedIsHermesEnabled).toBeCalledTimes(1);
|
|
expect(mockedIsTurboModuleEnabled).toBeCalledTimes(1);
|
|
expect(mockedIsFabricEnabled).toBeCalledTimes(1);
|
|
}
|
|
|
|
function executeIntegrationFor(mockedEvent: Event, mockedHint: EventHint): Promise<Event | null> {
|
|
const integration = new ReactNativeInfo();
|
|
return new Promise((resolve, reject) => {
|
|
integration.setupOnce(async eventProcessor => {
|
|
try {
|
|
const processedEvent = await eventProcessor(mockedEvent, mockedHint);
|
|
resolve(processedEvent);
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
});
|
|
});
|
|
}
|