UNPKG

@sentry/wizard

Version:

Sentry wizard helping you to configure your project

397 lines (354 loc) 11.4 kB
// @ts-ignore - magicast is ESM and TS complains about that. It works though import { generateCode, type ProxifiedModule, parseModule } from 'magicast'; import * as recast from 'recast'; import x = recast.types; import t = x.namedTypes; import { addSentrySerializerRequireToMetroConfig, addSentrySerializerToMetroConfig, getMetroConfigObject, patchMetroWithSentryConfigInMemory, removeSentryRequire, removeSentrySerializerFromMetroConfig, } from '../../src/react-native/metro'; describe('patch metro config - sentry serializer', () => { describe('patchMetroWithSentryConfigInMemory', () => { it('patches react native 0.72 default metro config', async () => { const mod = parseModule(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); /** * Metro configuration * https://reactnative.dev/docs/metro * * @type {import('metro-config').MetroConfig} */ const config = {}; module.exports = mergeConfig(getDefaultConfig(__dirname), config);`); const result = await patchMetroWithSentryConfigInMemory(mod, async () => { /* noop */ }); expect(result).toBe(true); expect(generateCode(mod.$ast).code) .toBe(`const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); const { withSentryConfig } = require("@sentry/react-native/metro"); /** * Metro configuration * https://reactnative.dev/docs/metro * * @type {import('metro-config').MetroConfig} */ const config = {}; module.exports = withSentryConfig(mergeConfig(getDefaultConfig(__dirname), config));`); }); it('patches react native 0.65 default metro config', async () => { const mod = parseModule(`/** * Metro configuration for React Native * https://github.com/facebook/react-native * * @format */ module.exports = { transformer: { getTransformOptions: async () => ({ transform: { experimentalImportSupport: false, inlineRequires: true, }, }), }, };`); const result = await patchMetroWithSentryConfigInMemory(mod, async () => { /* noop */ }); expect(result).toBe(true); expect(generateCode(mod.$ast).code).toBe(`const { withSentryConfig } = require("@sentry/react-native/metro"); /** * Metro configuration for React Native * https://github.com/facebook/react-native * * @format */ module.exports = withSentryConfig({ transformer: { getTransformOptions: async () => ({ transform: { experimentalImportSupport: false, inlineRequires: true, }, }), }, });`); }); it('patches react native metro config exported variable', async () => { const mod = parseModule(`const testConfig = {}; module.exports = testConfig;`); const result = await patchMetroWithSentryConfigInMemory(mod, async () => { /* noop */ }); expect(result).toBe(true); expect(generateCode(mod.$ast).code).toBe(`const { withSentryConfig } = require("@sentry/react-native/metro"); const testConfig = {}; module.exports = withSentryConfig(testConfig);`); }); it('does not patch react native metro config exported as factory function', async () => { const mod = parseModule(`module.exports = () => ({});`); const result = await patchMetroWithSentryConfigInMemory(mod, async () => { /* noop */ }); expect(result).toBe(false); expect(generateCode(mod.$ast).code).toBe(`module.exports = () => ({});`); }); }); describe('addSentrySerializerToMetroConfig', () => { it('add to empty config', () => { const mod = parseModule(`module.exports = { other: 'config' }`); const configObject = getModuleExportsObject(mod); const result = addSentrySerializerToMetroConfig(configObject); expect(result).toBe(true); expect(generateCode(mod.$ast).code).toBe(`module.exports = { other: 'config', serializer: { customSerializer: createSentryMetroSerializer() } }`); }); it('add to existing serializer config', () => { const mod = parseModule(`module.exports = { other: 'config', serializer: { other: 'config' } }`); const configObject = getModuleExportsObject(mod); const result = addSentrySerializerToMetroConfig(configObject); expect(result).toBe(true); expect(generateCode(mod.$ast).code).toBe(`module.exports = { other: 'config', serializer: { other: 'config', customSerializer: createSentryMetroSerializer() } }`); }); it('not add to existing customSerializer config', () => { const mod = parseModule(`module.exports = { other: 'config', serializer: { other: 'config', customSerializer: 'existing-serializer' } }`); const configObject = getModuleExportsObject(mod); const result = addSentrySerializerToMetroConfig(configObject); expect(result).toBe(false); expect(generateCode(mod.$ast).code).toBe(`module.exports = { other: 'config', serializer: { other: 'config', customSerializer: 'existing-serializer' } }`); }); }); describe('addSentrySerializerImportToMetroConfig', () => { it('add import', () => { const mod = parseModule(`const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); module.exports = { other: 'config' }`); const result = addSentrySerializerRequireToMetroConfig( mod.$ast as t.Program, ); expect(result).toBe(true); expect(generateCode(mod.$ast).code) .toBe(`const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); const { createSentryMetroSerializer } = require("@sentry/react-native/dist/js/tools/sentryMetroSerializer"); module.exports = { other: 'config' }`); }); }); describe('getMetroConfigObject', () => { it('get config object from variable called config', () => { const mod = parseModule(`var config = { some: 'config' };`); const configObject = getMetroConfigObject(mod.$ast as t.Program); expect( ((configObject?.properties[0] as t.ObjectProperty).key as t.Identifier) .name, ).toBe('some'); expect( ( (configObject?.properties[0] as t.ObjectProperty) .value as t.StringLiteral ).value, ).toBe('config'); }); it('get config object from const called config', () => { const mod = parseModule(`const config = { some: 'config' };`); const configObject = getMetroConfigObject(mod.$ast as t.Program); expect( ((configObject?.properties[0] as t.ObjectProperty).key as t.Identifier) .name, ).toBe('some'); expect( ( (configObject?.properties[0] as t.ObjectProperty) .value as t.StringLiteral ).value, ).toBe('config'); }); it('get config oject from let called config', () => { const mod = parseModule(`let config = { some: 'config' };`); const configObject = getMetroConfigObject(mod.$ast as t.Program); expect( ((configObject?.properties[0] as t.ObjectProperty).key as t.Identifier) .name, ).toBe('some'); expect( ( (configObject?.properties[0] as t.ObjectProperty) .value as t.StringLiteral ).value, ).toBe('config'); }); it('get config object from module.exports', () => { const mod = parseModule(`module.exports = { some: 'config' };`); const configObject = getMetroConfigObject(mod.$ast as t.Program); expect( ((configObject?.properties[0] as t.ObjectProperty).key as t.Identifier) .name, ).toBe('some'); expect( ( (configObject?.properties[0] as t.ObjectProperty) .value as t.StringLiteral ).value, ).toBe('config'); }); }); describe('remove @sentry require', () => { it('nothing to remove', () => { const mod = parseModule(`let config = { some: 'config' };`); const result = removeSentryRequire(mod.$ast as t.Program); expect(result).toBe(false); expect(generateCode(mod.$ast).code).toBe( `let config = { some: 'config' };`, ); }); it('remove metro serializer import', () => { const mod = parseModule(`const { createSentryMetroSerializer, } = require('@sentry/react-native/dist/js/tools/sentryMetroSerializer'); let config = { some: 'config' };`); const result = removeSentryRequire(mod.$ast as t.Program); expect(result).toBe(true); expect(generateCode(mod.$ast).code).toBe( `let config = { some: 'config' };`, ); }); it('remove all sentry imports', () => { const mod = parseModule(`const { createSentryMetroSerializer, } = require('@sentry/react-native/dist/js/tools/sentryMetroSerializer'); var Sentry = require('@sentry/react-native'); let SentryIntegrations = require('@sentry/integrations'); let config = { some: 'config' };`); const result = removeSentryRequire(mod.$ast as t.Program); expect(result).toBe(true); expect(generateCode(mod.$ast).code).toBe( `let config = { some: 'config' };`, ); }); }); describe('remove sentryMetroSerializer', () => { it('no custom serializer to remove', () => { const mod = parseModule(`let config = { some: 'config' };`); const result = removeSentrySerializerFromMetroConfig( mod.$ast as t.Program, ); expect(result).toBe(false); expect(generateCode(mod.$ast).code).toBe( `let config = { some: 'config' };`, ); }); it('no Sentry custom serializer to remove', () => { const mod = parseModule(`let config = { serializer: { customSerializer: 'existing-serializer', other: 'config', }, other: 'config', };`); const result = removeSentrySerializerFromMetroConfig( mod.$ast as t.Program, ); expect(result).toBe(false); expect(generateCode(mod.$ast).code).toBe(`let config = { serializer: { customSerializer: 'existing-serializer', other: 'config', }, other: 'config', };`); }); it('Sentry serializer to remove', () => { const mod = parseModule(`let config = { serializer: { customSerializer: createSentryMetroSerializer(), other: 'config', }, other: 'config', };`); const result = removeSentrySerializerFromMetroConfig( mod.$ast as t.Program, ); expect(result).toBe(true); expect(generateCode(mod.$ast).code).toBe(`let config = { serializer: { other: 'config' }, other: 'config', };`); }); it('Sentry serializer to remove with wrapped serializer', () => { const mod = parseModule(`let config = { serializer: { customSerializer: createSentryMetroSerializer(wrappedSerializer()), other: 'config', }, other: 'config', };`); const result = removeSentrySerializerFromMetroConfig( mod.$ast as t.Program, ); expect(result).toBe(true); expect(generateCode(mod.$ast).code).toBe(`let config = { serializer: { customSerializer: wrappedSerializer(), other: 'config', }, other: 'config', };`); }); }); }); function getModuleExportsObject( mod: ProxifiedModule, index = 0, ): t.ObjectExpression { return ( ((mod.$ast as t.Program).body[index] as t.ExpressionStatement) .expression as t.AssignmentExpression ).right as t.ObjectExpression; }