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
5 changes: 5 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
"require": "./lib/commonjs/index.js",
"types": "./lib/typescript/index.d.ts"
},
"./internal": {
"import": "./lib/module/internal.js",
"require": "./lib/commonjs/internal.js",
"types": "./lib/typescript/internal.d.ts"
Comment thread
cdn34dd marked this conversation as resolved.
},
"./metro": {
"import": "./lib/module/metro.js",
"require": "./lib/commonjs/metro.js",
Expand Down
42 changes: 23 additions & 19 deletions packages/core/src/__tests__/DdSdkReactNative.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { AttributesSingleton } from '../sdk/AttributesSingleton/AttributesSingle
import { DdSdkWrapper } from '../sdk/DdSdkInternal';
import { GlobalState } from '../sdk/GlobalState/GlobalState';
import { UserInfoSingleton } from '../sdk/UserInfoSingleton/UserInfoSingleton';
import NativeDdSdkSpec from '../specs/NativeDdSdk';
import { getNativeDdSdk } from '../specs/NativeDdSdk';
import type { LogEvent } from '../types';
import { ErrorSource } from '../types';
import { version as sdkVersion } from '../version';
Expand Down Expand Up @@ -1318,8 +1318,8 @@ describe('DdSdkReactNative', () => {
await DdSdkReactNative.addAttribute(key, value);

// THEN
expect(NativeDdSdkSpec.addAttribute).toHaveBeenCalledTimes(1);
expect(NativeDdSdkSpec.addAttribute).toHaveBeenCalledWith(key, {
expect(getNativeDdSdk()?.addAttribute).toHaveBeenCalledTimes(1);
expect(getNativeDdSdk()?.addAttribute).toHaveBeenCalledWith(key, {
value
});
expect(AttributesSingleton.getInstance().getAttribute(key)).toEqual(
Expand All @@ -1339,8 +1339,8 @@ describe('DdSdkReactNative', () => {
await DdSdkReactNative.removeAttribute(key);

// THEN
expect(NativeDdSdkSpec.removeAttribute).toHaveBeenCalledTimes(1);
expect(NativeDdSdkSpec.removeAttribute).toHaveBeenCalledWith(key);
expect(getNativeDdSdk()?.removeAttribute).toHaveBeenCalledTimes(1);
expect(getNativeDdSdk()?.removeAttribute).toHaveBeenCalledWith(key);
expect(AttributesSingleton.getInstance().getAttribute(key)).toEqual(
undefined
);
Expand All @@ -1357,8 +1357,8 @@ describe('DdSdkReactNative', () => {
await DdSdkReactNative.addAttributes(attributes);

// THEN
expect(NativeDdSdkSpec.addAttributes).toHaveBeenCalledTimes(1);
expect(NativeDdSdkSpec.addAttributes).toHaveBeenCalledWith(
expect(getNativeDdSdk()?.addAttributes).toHaveBeenCalledTimes(1);
expect(getNativeDdSdk()?.addAttributes).toHaveBeenCalledWith(
attributes
);
expect(AttributesSingleton.getInstance().getAttributes()).toEqual({
Expand All @@ -1377,8 +1377,8 @@ describe('DdSdkReactNative', () => {
await DdSdkReactNative.removeAttributes(['foo', 'baz']);

// THEN
expect(NativeDdSdkSpec.removeAttributes).toHaveBeenCalledTimes(1);
expect(NativeDdSdkSpec.removeAttributes).toHaveBeenCalledWith([
expect(getNativeDdSdk()?.removeAttributes).toHaveBeenCalledTimes(1);
expect(getNativeDdSdk()?.removeAttributes).toHaveBeenCalledWith([
'foo',
'baz'
]);
Expand All @@ -1404,8 +1404,10 @@ describe('DdSdkReactNative', () => {
await DdSdkReactNative.setUserInfo(userInfo);

// THEN
expect(NativeDdSdkSpec.setUserInfo).toHaveBeenCalledTimes(1);
expect(NativeDdSdkSpec.setUserInfo).toHaveBeenCalledWith(userInfo);
expect(getNativeDdSdk()?.setUserInfo).toHaveBeenCalledTimes(1);
expect(getNativeDdSdk()?.setUserInfo).toHaveBeenCalledWith(
userInfo
);
expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual(
userInfo
);
Expand All @@ -1425,8 +1427,8 @@ describe('DdSdkReactNative', () => {
await DdSdkReactNative.addUserExtraInfo(extraInfo);

// THEN
expect(NativeDdSdkSpec.addUserExtraInfo).toHaveBeenCalledTimes(1);
expect(NativeDdSdkSpec.addUserExtraInfo).toHaveBeenCalledWith(
expect(getNativeDdSdk()?.addUserExtraInfo).toHaveBeenCalledTimes(1);
expect(getNativeDdSdk()?.addUserExtraInfo).toHaveBeenCalledWith(
extraInfo
);
expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual({
Expand All @@ -1446,7 +1448,7 @@ describe('DdSdkReactNative', () => {
await DdSdkReactNative.addUserExtraInfo(extraInfo);

// THEN
expect(NativeDdSdkSpec.addUserExtraInfo).toHaveBeenCalledWith(
expect(getNativeDdSdk()?.addUserExtraInfo).toHaveBeenCalledWith(
extraInfo
);
expect(UserInfoSingleton.getInstance().getUserInfo()).toEqual({
Expand Down Expand Up @@ -1475,8 +1477,8 @@ describe('DdSdkReactNative', () => {
await DdSdkReactNative.clearUserInfo();

// THEN
expect(NativeDdSdkSpec.clearUserInfo).toHaveBeenCalledTimes(1);
expect(NativeDdSdkSpec.setUserInfo).toHaveBeenCalled();
expect(getNativeDdSdk()?.clearUserInfo).toHaveBeenCalledTimes(1);
expect(getNativeDdSdk()?.setUserInfo).toHaveBeenCalled();
expect(
UserInfoSingleton.getInstance().getUserInfo()
).toBeUndefined();
Expand All @@ -1493,8 +1495,10 @@ describe('DdSdkReactNative', () => {
DdSdkReactNative.setTrackingConsent(consent);

// THEN
expect(NativeDdSdkSpec.setTrackingConsent).toHaveBeenCalledTimes(1);
expect(NativeDdSdkSpec.setTrackingConsent).toHaveBeenCalledWith(
expect(getNativeDdSdk()?.setTrackingConsent).toHaveBeenCalledTimes(
1
);
expect(getNativeDdSdk()?.setTrackingConsent).toHaveBeenCalledWith(
consent
);
});
Expand All @@ -1506,7 +1510,7 @@ describe('DdSdkReactNative', () => {
DdSdkReactNative.clearAllData();

// THEN
expect(NativeDdSdkSpec.clearAllData).toHaveBeenCalledTimes(1);
expect(getNativeDdSdk()?.clearAllData).toHaveBeenCalledTimes(1);
});
});

Expand Down
26 changes: 26 additions & 0 deletions packages/core/src/__tests__/internalEntry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
describe('./internal entry', () => {
it('exports the internals react-native-vega needs', () => {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const internal = require('../internal');
const expected = [
'addDefaultValuesToAutoInstrumentationConfiguration',
'buildConfigurationFromPartialConfiguration',
'DdSdkNativeConfiguration',
'RUM_DEFAULTS',
'adaptLongTaskThreshold',
'version',
'AccountInfoSingleton',
'AttributesSingleton',
'UserInfoSingleton',
'GlobalState',
'BufferSingleton',
'DatadogProviderState',
'DdRumResourceTracking'
];
for (const name of expected) {
expect(internal[name]).toBeDefined();
}
});

// Import safety of this entry point is covered in wrapperImportSafety.test.ts.
});
4 changes: 1 addition & 3 deletions packages/core/src/__tests__/mock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ import { DdLogs, DdRum, DdTrace, DdSdkReactNative } from '../index';
*/

const privateProperties = {
DdTrace: ['nativeTrace'],
DdTrace: [],
DdLogs: [
'nativeLogs',
'logEventMapper',
'printLogDroppedSdkNotInitialized',
'printLogDroppedByMapper',
Expand All @@ -33,7 +32,6 @@ const privateProperties = {
'logWithError'
],
DdRum: [
'nativeRum',
'errorEventMapper',
'resourceEventMapper',
'actionEventMapper',
Expand Down
58 changes: 58 additions & 0 deletions packages/core/src/__tests__/nativeSpecGetters.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

describe('native spec getters', () => {
afterEach(() => {
jest.restoreAllMocks();
});

it.each([
['../specs/NativeDdSdk', 'getNativeDdSdk', 'DdSdk'],
['../specs/NativeDdRum', 'getNativeDdRum', 'DdRum'],
['../specs/NativeDdLogs', 'getNativeDdLogs', 'DdLogs'],
['../specs/NativeDdTrace', 'getNativeDdTrace', 'DdTrace'],
['../specs/NativeDdFlags', 'getNativeDdFlags', 'DdFlags']
])(
'%s retries a missing module and caches it once found',
(modulePath, getterName, registryName) => {
jest.isolateModules(() => {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const { TurboModuleRegistry } = require('react-native');
const nativeModule = { name: registryName };
const getSpy = jest
.spyOn(TurboModuleRegistry, 'get')
.mockReturnValueOnce(null)
.mockReturnValue(nativeModule);
// eslint-disable-next-line global-require, import/no-dynamic-require, @typescript-eslint/no-var-requires
const getter = require(modulePath)[getterName];

expect(getter()).toBeNull();
expect(getter()).toBe(nativeModule);
expect(getter()).toBe(nativeModule);
expect(getSpy).toHaveBeenCalledTimes(2);
expect(getSpy).toHaveBeenCalledWith(registryName);
});
}
);

it('DdSdkWrapper picks up a module that was missing on first use', () => {
jest.isolateModules(() => {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const { TurboModuleRegistry } = require('react-native');
const nativeModule = { clearAllData: jest.fn() };
jest.spyOn(TurboModuleRegistry, 'get')
.mockReturnValueOnce(null)
.mockReturnValue(nativeModule);
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const { DdSdkWrapper } = require('../sdk/DdSdkInternal');
const sdk = new DdSdkWrapper();

expect(() => sdk.clearAllData()).toThrow();
sdk.clearAllData();
expect(nativeModule.clearAllData).toHaveBeenCalledTimes(1);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import React from 'react';
import type { DdNativeRumType } from '../../../nativeModulesTypes';
import { DdRumUserInteractionTracking } from '../../../rum/instrumentation/interactionTracking/DdRumUserInteractionTracking';
import { BufferSingleton } from '../../../sdk/DatadogProvider/Buffer/BufferSingleton';
import NativeDdSdkSpec from '../../../specs/NativeDdSdk';
import { getNativeDdSdk } from '../../../specs/NativeDdSdk';

const styles = StyleSheet.create({
button: {
Expand Down Expand Up @@ -467,7 +467,7 @@ describe('startTracking', () => {
jest.setMock('react/jsx-runtime', {});
DdRumUserInteractionTracking.startTracking({});
expect(DdRumUserInteractionTracking['isTracking']).toBe(true);
expect(NativeDdSdkSpec.telemetryDebug).toBeCalledWith(
expect(getNativeDdSdk()?.telemetryDebug).toBeCalledWith(
'React jsx runtime does not export new jsx transform'
);
});
Expand All @@ -477,7 +477,7 @@ describe('startTracking', () => {

DdRumUserInteractionTracking.startTracking({});
expect(DdRumUserInteractionTracking['isTracking']).toBe(true);
expect(NativeDdSdkSpec.telemetryDebug).toBeCalledWith(
expect(getNativeDdSdk()?.telemetryDebug).toBeCalledWith(
'React version does not support new jsx transform'
);
});
Expand Down
129 changes: 129 additions & 0 deletions packages/core/src/__tests__/wrapperImportSafety.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

/**
* The spy must be installed on the `react-native` instance from inside
* `jest.isolateModules`: lookups made by isolated modules go through that
* registry, not the one imported at the top of a test file.
*/
const requireCountingLookups = (modulePath: string) => {
let lookups: string[] = [];
let mod: any;
jest.isolateModules(() => {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const { TurboModuleRegistry } = require('react-native');
const getSpy = jest.spyOn(TurboModuleRegistry, 'get');
// eslint-disable-next-line global-require, import/no-dynamic-require
mod = require(modulePath);
lookups = getSpy.mock.calls.map(call => call[0] as string);
});
return { mod, lookups };
};

describe('SDK wrappers are import-safe', () => {
afterEach(() => {
jest.restoreAllMocks();
// Singletons live on globalThis, which isolateModules does not reset.
const g = globalThis as Record<PropertyKey, unknown>;
for (const symbol of Object.getOwnPropertySymbols(g)) {
if (symbol.description?.startsWith('com.datadog.reactnative')) {
delete g[symbol];
}
}
});

it.each([
'../specs/NativeDdSdk',
'../specs/NativeDdRum',
'../specs/NativeDdLogs',
'../specs/NativeDdTrace',
'../specs/NativeDdFlags',
'../sdk/DdSdkInternal',
'../rum/DdRum',
'../logs/DdLogs',
'../trace/DdTrace',
'../flags/DdFlags'
])('importing %s performs no native lookup', modulePath => {
expect(requireCountingLookups(modulePath).lookups).toEqual([]);
});

it('DdSdkWrapper only resolves the native module when a method is used', () => {
jest.isolateModules(() => {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const { TurboModuleRegistry } = require('react-native');
const getSpy = jest.spyOn(TurboModuleRegistry, 'get');
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const { DdSdkWrapper } = require('../sdk/DdSdkInternal');

const sdk = new DdSdkWrapper();
expect(getSpy).not.toHaveBeenCalled();

sdk.getConstants();
expect(getSpy).toHaveBeenCalledWith('DdSdk');
});
});

it('importing the public barrel performs no native lookup', () => {
expect(requireCountingLookups('../index').lookups).toEqual([]);
});

it('importing the internal entry performs no native lookup', () => {
expect(requireCountingLookups('../internal').lookups).toEqual([]);
});
});

describe('SDK wrappers do not rely on Proxy', () => {
// Older Android JSC and Hermes versions supported by the SDK lack Proxy.
it.each(['../sdk/DdSdkInternal', '../index', '../internal'])(
'importing %s works without Proxy',
modulePath => {
const g = globalThis as Record<string, unknown>;
const originalProxy = g.Proxy;
try {
delete g.Proxy;
expect(() => requireCountingLookups(modulePath)).not.toThrow();
} finally {
g.Proxy = originalProxy;
}
}
);

it('DdSdkWrapper forwards every method to the native module', () => {
jest.isolateModules(() => {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const { TurboModuleRegistry } = require('react-native');
const nativeModule: Record<string, jest.Mock> = {};
jest.spyOn(TurboModuleRegistry, 'get').mockReturnValue(
nativeModule
);
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const { DdSdkWrapper } = require('../sdk/DdSdkInternal');
const sdk = new DdSdkWrapper();
const methods = Object.getOwnPropertyNames(
DdSdkWrapper.prototype
).filter(
name =>
name !== 'constructor' &&
!name.startsWith('_') &&
typeof Object.getOwnPropertyDescriptor(
DdSdkWrapper.prototype,
name
)?.value === 'function'
);
for (const name of methods) {
nativeModule[name] = jest.fn().mockReturnValue(name);
}

for (const name of methods) {
const arg =
name === 'initialize' ? { attributeEncoders: [] } : 'arg';
expect(sdk[name](arg)).toBe(name);
expect(nativeModule[name]).toHaveBeenCalledTimes(1);
}
expect(methods.length).toBeGreaterThan(15);
});
});
});
Loading
Loading