-
-
Notifications
You must be signed in to change notification settings - Fork 354
feat(expo): Add RNSentrySDK APIs support to @sentry/react-native/expo plugin #4633
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: capture-app-start-errors
Are you sure you want to change the base?
Changes from 26 commits
313e844
2e97acc
6eedaae
9ae5475
566550e
770c9f4
f8b37b5
d25db30
adc81a5
8c2cd73
a2b5575
5f4f7c5
0431cc3
62d39cc
235f3ef
369cce7
a53c7f4
5e4a98f
dce74b2
0ffd26c
744993c
5447be9
0b3423f
5c615fd
c356288
8e32556
a20984c
d1db4fa
7c25c2c
1918baf
b2a89f2
3885d70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
|
|
||
| ### Features | ||
|
|
||
| - Add RNSentrySDK APIs support to @sentry/react-native/expo plugin ([#4633](https://github.com/getsentry/sentry-react-native/pull/4633)) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| - User Feedback Widget Beta ([#4435](https://github.com/getsentry/sentry-react-native/pull/4435)) | ||
|
|
||
| To collect user feedback from inside your application call `Sentry.showFeedbackWidget()`. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,26 @@ | ||
| import type { ExpoConfig } from '@expo/config-types'; | ||
| import type { ConfigPlugin } from 'expo/config-plugins'; | ||
| import { withAppBuildGradle, withDangerousMod } from 'expo/config-plugins'; | ||
| import { withAppBuildGradle, withDangerousMod, withMainApplication } from 'expo/config-plugins'; | ||
| import * as path from 'path'; | ||
|
|
||
| import { warnOnce, writeSentryPropertiesTo } from './utils'; | ||
|
|
||
| export const withSentryAndroid: ConfigPlugin<string> = (config, sentryProperties: string) => { | ||
| const cfg = withAppBuildGradle(config, config => { | ||
| export const withSentryAndroid: ConfigPlugin<{ sentryProperties: string; useNativeInit: boolean | undefined }> = ( | ||
| config, | ||
| { sentryProperties, useNativeInit = false }, | ||
| ) => { | ||
| const appBuildGradleCfg = withAppBuildGradle(config, config => { | ||
| if (config.modResults.language === 'groovy') { | ||
| config.modResults.contents = modifyAppBuildGradle(config.modResults.contents); | ||
| } else { | ||
| throw new Error('Cannot configure Sentry in the app gradle because the build.gradle is not groovy'); | ||
| } | ||
| return config; | ||
| }); | ||
| return withDangerousMod(cfg, [ | ||
|
|
||
| const mainApplicationCfg = useNativeInit ? modifyMainApplication(appBuildGradleCfg) : appBuildGradleCfg; | ||
|
|
||
| return withDangerousMod(mainApplicationCfg, [ | ||
| 'android', | ||
| config => { | ||
| writeSentryPropertiesTo(path.resolve(config.modRequest.projectRoot, 'android'), sentryProperties); | ||
|
|
@@ -49,3 +56,57 @@ export function modifyAppBuildGradle(buildGradle: string): string { | |
|
|
||
| return buildGradle.replace(pattern, match => `${applyFrom}\n\n${match}`); | ||
| } | ||
|
|
||
| export function modifyMainApplication(config: ExpoConfig): ExpoConfig { | ||
| return withMainApplication(config, config => { | ||
| if (!config.modResults || !config.modResults.path) { | ||
| warnOnce("Can't add 'RNSentrySDK.init' to Android MainApplication, because the file was not found."); | ||
| return config; | ||
| } | ||
|
|
||
| const fileName = path.basename(config.modResults.path); | ||
|
|
||
| if (config.modResults.contents.includes('RNSentrySDK.init')) { | ||
| warnOnce(`Your '${fileName}' already contains 'RNSentrySDK.init', the native code won't be updated.`); | ||
| return config; | ||
| } | ||
|
|
||
| if (config.modResults.language === 'java') { | ||
| // Add RNSentrySDK.init | ||
| const originalContents = config.modResults.contents; | ||
| config.modResults.contents = config.modResults.contents.replace( | ||
| /(super\.onCreate\(\)[;\n]*)([ \t]*)/, | ||
| `$1\n$2RNSentrySDK.init(this);\n$2`, | ||
krystofwoldrich marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ); | ||
|
Comment on lines
+78
to
+81
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The regex pattern Did we get this right? 👍 / 👎 to inform future reviews. |
||
| if (config.modResults.contents === originalContents) { | ||
| warnOnce(`Failed to insert 'RNSentrySDK.init' in '${fileName}'.`); | ||
| } else if (!config.modResults.contents.includes('import io.sentry.react.RNSentrySDK;')) { | ||
| // Insert import statement after package declaration | ||
| config.modResults.contents = config.modResults.contents.replace( | ||
| /(package .*;\n\n?)/, | ||
| `$1import io.sentry.react.RNSentrySDK;\n`, | ||
| ); | ||
| } | ||
| } else if (config.modResults.language === 'kt') { | ||
| // Add RNSentrySDK.init | ||
| const originalContents = config.modResults.contents; | ||
|
Comment on lines
+82
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When the regex replacement fails and the contents remain unchanged, only a warning is logged but the import statement might still be added in the else block. This could result in an orphaned import statement without the corresponding initialization call. Consider checking if the replacement was successful before adding the import statement. Did we get this right? 👍 / 👎 to inform future reviews. |
||
| config.modResults.contents = config.modResults.contents.replace( | ||
| /(super\.onCreate\(\)[;\n]*)([ \t]*)/, | ||
| `$1\n$2RNSentrySDK.init(this)\n$2`, | ||
| ); | ||
| if (config.modResults.contents === originalContents) { | ||
| warnOnce(`Failed to insert 'RNSentrySDK.init' in '${fileName}'.`); | ||
| } else if (!config.modResults.contents.includes('import io.sentry.react.RNSentrySDK')) { | ||
| // Insert import statement after package declaration | ||
| config.modResults.contents = config.modResults.contents.replace( | ||
| /(package .*\n\n?)/, | ||
| `$1import io.sentry.react.RNSentrySDK\n`, | ||
| ); | ||
| } | ||
| } else { | ||
| warnOnce(`Unrecognized language detected in '${fileName}', the native code won't be updated.`); | ||
| } | ||
|
|
||
| return config; | ||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import type { ExpoConfig } from '@expo/config-types'; | ||
| /* eslint-disable @typescript-eslint/no-unsafe-member-access */ | ||
| import type { ConfigPlugin, XcodeProject } from 'expo/config-plugins'; | ||
| import { withDangerousMod, withXcodeProject } from 'expo/config-plugins'; | ||
| import { withAppDelegate, withDangerousMod, withXcodeProject } from 'expo/config-plugins'; | ||
| import * as path from 'path'; | ||
|
|
||
| import { warnOnce, writeSentryPropertiesTo } from './utils'; | ||
|
|
@@ -12,8 +13,11 @@ const SENTRY_REACT_NATIVE_XCODE_PATH = | |
| const SENTRY_REACT_NATIVE_XCODE_DEBUG_FILES_PATH = | ||
| "`${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; | ||
|
|
||
| export const withSentryIOS: ConfigPlugin<string> = (config, sentryProperties: string) => { | ||
| const cfg = withXcodeProject(config, config => { | ||
| export const withSentryIOS: ConfigPlugin<{ sentryProperties: string; useNativeInit: boolean | undefined }> = ( | ||
| config, | ||
| { sentryProperties, useNativeInit = false }, | ||
| ) => { | ||
| const xcodeProjectCfg = withXcodeProject(config, config => { | ||
| const xcodeProject: XcodeProject = config.modResults; | ||
|
|
||
| const sentryBuildPhase = xcodeProject.pbxItemByComment( | ||
|
|
@@ -36,7 +40,9 @@ export const withSentryIOS: ConfigPlugin<string> = (config, sentryProperties: st | |
| return config; | ||
| }); | ||
|
|
||
| return withDangerousMod(cfg, [ | ||
| const appDelegateCfc = useNativeInit ? modifyAppDelegate(xcodeProjectCfg) : xcodeProjectCfg; | ||
|
|
||
| return withDangerousMod(appDelegateCfc, [ | ||
| 'ios', | ||
| config => { | ||
| writeSentryPropertiesTo(path.resolve(config.modRequest.projectRoot, 'ios'), sentryProperties); | ||
|
|
@@ -79,3 +85,57 @@ export function addSentryWithBundledScriptsToBundleShellScript(script: string): | |
| (match: string) => `/bin/sh ${SENTRY_REACT_NATIVE_XCODE_PATH} ${match}`, | ||
| ); | ||
| } | ||
|
|
||
| export function modifyAppDelegate(config: ExpoConfig): ExpoConfig { | ||
| return withAppDelegate(config, async config => { | ||
| if (!config.modResults || !config.modResults.path) { | ||
| warnOnce("Can't add 'RNSentrySDK.start()' to the iOS AppDelegate, because the file was not found."); | ||
| return config; | ||
| } | ||
|
|
||
| const fileName = path.basename(config.modResults.path); | ||
|
|
||
| if (config.modResults.language === 'swift') { | ||
| if (config.modResults.contents.includes('RNSentrySDK.start()')) { | ||
| warnOnce(`Your '${fileName}' already contains 'RNSentrySDK.start()'.`); | ||
| return config; | ||
| } | ||
| // Add RNSentrySDK.start() at the beginning of application method | ||
| const originalContents = config.modResults.contents; | ||
| config.modResults.contents = config.modResults.contents.replace( | ||
| /(func application\([^)]*\) -> Bool \{)\s*\n(\s*)/s, | ||
| `$1\n$2RNSentrySDK.start()\n$2`, | ||
| ); | ||
|
Comment on lines
+107
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The regex pattern /(func application\([^)]*\)\s*->\s*Bool\s*\{)\s*\n(\s*)/sDid we get this right? 👍 / 👎 to inform future reviews. |
||
| if (config.modResults.contents === originalContents) { | ||
| warnOnce(`Failed to insert 'RNSentrySDK.start()' in '${fileName}'.`); | ||
| } else if (!config.modResults.contents.includes('import RNSentry')) { | ||
| // Insert import statement after UIKit import | ||
| config.modResults.contents = config.modResults.contents.replace(/(import UIKit\n)/, `$1import RNSentry\n`); | ||
| } | ||
|
Comment on lines
+109
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When the regex replacement fails and the contents remain unchanged, only a warning is logged but the import statement might still be added in the else block. This could result in an orphaned import statement without the corresponding initialization call. Consider checking if the replacement was successful before adding the import statement. Did we get this right? 👍 / 👎 to inform future reviews. |
||
| } else if (config.modResults.language === 'objc') { | ||
antonis marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (config.modResults.contents.includes('[RNSentrySDK start]')) { | ||
| warnOnce(`Your '${fileName}' already contains '[RNSentrySDK start]'.`); | ||
| return config; | ||
| } | ||
| // Add [RNSentrySDK start] at the beginning of application:didFinishLaunchingWithOptions method | ||
| const originalContents = config.modResults.contents; | ||
| config.modResults.contents = config.modResults.contents.replace( | ||
| /(- \(BOOL\)application:[\s\S]*?didFinishLaunchingWithOptions:[\s\S]*?\{\n)(\s*)/s, | ||
| `$1$2[RNSentrySDK start];\n$2`, | ||
|
Comment on lines
+123
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The regex pattern /(- \(BOOL\)application:\(UIApplication \*\)\w+\s+didFinishLaunchingWithOptions:\(NSDictionary \*\)\w+\s*\{\n)(\s*)/sDid we get this right? 👍 / 👎 to inform future reviews. |
||
| ); | ||
| if (config.modResults.contents === originalContents) { | ||
| warnOnce(`Failed to insert '[RNSentrySDK start]' in '${fileName}.`); | ||
| } else if (!config.modResults.contents.includes('#import <RNSentry/RNSentry.h>')) { | ||
| // Add import after AppDelegate.h | ||
| config.modResults.contents = config.modResults.contents.replace( | ||
| /(#import "AppDelegate.h"\n)/, | ||
| `$1#import <RNSentry/RNSentry.h>\n`, | ||
| ); | ||
| } | ||
| } else { | ||
| warnOnce(`Unsupported language detected in '${fileName}', the native code won't be updated.`); | ||
krystofwoldrich marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| return config; | ||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,183 @@ | ||||||||||||||||||||||||||||||||||||
| import type { ExpoConfig } from '@expo/config-types'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| import { warnOnce } from '../../plugin/src/utils'; | ||||||||||||||||||||||||||||||||||||
| import { modifyAppDelegate } from '../../plugin/src/withSentryIOS'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Mock dependencies | ||||||||||||||||||||||||||||||||||||
| jest.mock('@expo/config-plugins', () => ({ | ||||||||||||||||||||||||||||||||||||
| ...jest.requireActual('@expo/config-plugins'), | ||||||||||||||||||||||||||||||||||||
| withAppDelegate: jest.fn((config, callback) => callback(config)), | ||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| jest.mock('../../plugin/src/utils', () => ({ | ||||||||||||||||||||||||||||||||||||
| warnOnce: jest.fn(), | ||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| interface MockedExpoConfig extends ExpoConfig { | ||||||||||||||||||||||||||||||||||||
| modResults: { | ||||||||||||||||||||||||||||||||||||
| path: string; | ||||||||||||||||||||||||||||||||||||
| contents: string; | ||||||||||||||||||||||||||||||||||||
| language: 'swift' | 'objc'; | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const objcContents = `#import "AppDelegate.h" | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @implementation AppDelegate | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| self.moduleName = @"main"; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // You can add your custom initial props in the dictionary below. | ||||||||||||||||||||||||||||||||||||
| // They will be passed down to the ViewController used by React Native. | ||||||||||||||||||||||||||||||||||||
| self.initialProps = @{}; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| return [super application:application didFinishLaunchingWithOptions:launchOptions]; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @end | ||||||||||||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const objcExpected = `#import "AppDelegate.h" | ||||||||||||||||||||||||||||||||||||
| #import <RNSentry/RNSentry.h> | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @implementation AppDelegate | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| [RNSentrySDK start]; | ||||||||||||||||||||||||||||||||||||
| self.moduleName = @"main"; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // You can add your custom initial props in the dictionary below. | ||||||||||||||||||||||||||||||||||||
| // They will be passed down to the ViewController used by React Native. | ||||||||||||||||||||||||||||||||||||
| self.initialProps = @{}; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| return [super application:application didFinishLaunchingWithOptions:launchOptions]; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @end | ||||||||||||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const swiftContents = `import React | ||||||||||||||||||||||||||||||||||||
| import React_RCTAppDelegate | ||||||||||||||||||||||||||||||||||||
| import ReactAppDependencyProvider | ||||||||||||||||||||||||||||||||||||
| import UIKit | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @main | ||||||||||||||||||||||||||||||||||||
| class AppDelegate: RCTAppDelegate { | ||||||||||||||||||||||||||||||||||||
| override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { | ||||||||||||||||||||||||||||||||||||
| self.moduleName = "sentry-react-native-sample" | ||||||||||||||||||||||||||||||||||||
| self.dependencyProvider = RCTAppDependencyProvider() | ||||||||||||||||||||||||||||||||||||
| return super.application(application, didFinishLaunchingWithOptions: launchOptions) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| }`; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const swiftExpected = `import React | ||||||||||||||||||||||||||||||||||||
| import React_RCTAppDelegate | ||||||||||||||||||||||||||||||||||||
| import ReactAppDependencyProvider | ||||||||||||||||||||||||||||||||||||
| import UIKit | ||||||||||||||||||||||||||||||||||||
| import RNSentry | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @main | ||||||||||||||||||||||||||||||||||||
| class AppDelegate: RCTAppDelegate { | ||||||||||||||||||||||||||||||||||||
| override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { | ||||||||||||||||||||||||||||||||||||
| RNSentrySDK.start() | ||||||||||||||||||||||||||||||||||||
| self.moduleName = "sentry-react-native-sample" | ||||||||||||||||||||||||||||||||||||
| self.dependencyProvider = RCTAppDependencyProvider() | ||||||||||||||||||||||||||||||||||||
| return super.application(application, didFinishLaunchingWithOptions: launchOptions) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| }`; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| describe('modifyAppDelegate', () => { | ||||||||||||||||||||||||||||||||||||
| let config: MockedExpoConfig; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| beforeEach(() => { | ||||||||||||||||||||||||||||||||||||
| jest.clearAllMocks(); | ||||||||||||||||||||||||||||||||||||
| // Reset to a mocked Swift config after each test | ||||||||||||||||||||||||||||||||||||
| config = createMockConfig(); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| it('should skip modification if modResults or path is missing', async () => { | ||||||||||||||||||||||||||||||||||||
| config.modResults.path = undefined; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const result = await modifyAppDelegate(config); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| expect(warnOnce).toHaveBeenCalledWith( | ||||||||||||||||||||||||||||||||||||
| `Can't add 'RNSentrySDK.start()' to the iOS AppDelegate, because the file was not found.`, | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| expect(result).toBe(config); // No modification | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| it('should warn if RNSentrySDK.start() is already present in a Swift project', async () => { | ||||||||||||||||||||||||||||||||||||
| config.modResults.contents = 'RNSentrySDK.start();'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const result = await modifyAppDelegate(config); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| expect(warnOnce).toHaveBeenCalledWith(`Your 'AppDelegate.swift' already contains 'RNSentrySDK.start()'.`); | ||||||||||||||||||||||||||||||||||||
| expect(result).toBe(config); // No modification | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| it('should warn if [RNSentrySDK start] is already present in an Objective-C project', async () => { | ||||||||||||||||||||||||||||||||||||
| config.modResults.language = 'objc'; | ||||||||||||||||||||||||||||||||||||
| config.modResults.path = 'samples/react-native/ios/AppDelegate.mm'; | ||||||||||||||||||||||||||||||||||||
| config.modResults.contents = '[RNSentrySDK start];'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const result = await modifyAppDelegate(config); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| expect(warnOnce).toHaveBeenCalledWith(`Your 'AppDelegate.mm' already contains '[RNSentrySDK start]'.`); | ||||||||||||||||||||||||||||||||||||
| expect(result).toBe(config); // No modification | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| it('should modify a Swift file by adding the RNSentrySDK import and start', async () => { | ||||||||||||||||||||||||||||||||||||
| const result = (await modifyAppDelegate(config)) as MockedExpoConfig; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| expect(result.modResults.contents).toContain('import RNSentry'); | ||||||||||||||||||||||||||||||||||||
| expect(result.modResults.contents).toContain('RNSentrySDK.start()'); | ||||||||||||||||||||||||||||||||||||
| expect(result.modResults.contents).toBe(swiftExpected); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| it('should modify an Objective-C file by adding the RNSentrySDK import and start', async () => { | ||||||||||||||||||||||||||||||||||||
| config.modResults.language = 'objc'; | ||||||||||||||||||||||||||||||||||||
| config.modResults.contents = objcContents; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const result = (await modifyAppDelegate(config)) as MockedExpoConfig; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| expect(result.modResults.contents).toContain('#import <RNSentry/RNSentry.h>'); | ||||||||||||||||||||||||||||||||||||
| expect(result.modResults.contents).toContain('[RNSentrySDK start];'); | ||||||||||||||||||||||||||||||||||||
| expect(result.modResults.contents).toBe(objcExpected); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| it('should insert import statements only once in an Swift project', async () => { | ||||||||||||||||||||||||||||||||||||
| config.modResults.contents = | ||||||||||||||||||||||||||||||||||||
| 'import UIKit\nimport RNSentrySDK\n\noverride func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const result = (await modifyAppDelegate(config)) as MockedExpoConfig; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const importCount = (result.modResults.contents.match(/import RNSentrySDK/g) || []).length; | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+174
to
+181
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test name and logic are inconsistent. The test checks for 'import RNSentrySDK' but the actual implementation imports 'import RNSentry'. This test should verify the correct import statement. The expected import should be
Suggested change
Did we get this right? 👍 / 👎 to inform future reviews. |
||||||||||||||||||||||||||||||||||||
| expect(importCount).toBe(1); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| it('should insert import statements only once in an Objective-C project', async () => { | ||||||||||||||||||||||||||||||||||||
| config.modResults.language = 'objc'; | ||||||||||||||||||||||||||||||||||||
| config.modResults.contents = | ||||||||||||||||||||||||||||||||||||
| '#import "AppDelegate.h"\n#import <RNSentry/RNSentry.h>\n\n- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const result = (await modifyAppDelegate(config)) as MockedExpoConfig; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const importCount = (result.modResults.contents.match(/#import <RNSentry\/RNSentry.h>/g) || []).length; | ||||||||||||||||||||||||||||||||||||
| expect(importCount).toBe(1); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| function createMockConfig(): MockedExpoConfig { | ||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||
| name: 'test', | ||||||||||||||||||||||||||||||||||||
| slug: 'test', | ||||||||||||||||||||||||||||||||||||
| modResults: { | ||||||||||||||||||||||||||||||||||||
| path: 'samples/react-native/ios/AppDelegate.swift', | ||||||||||||||||||||||||||||||||||||
| contents: swiftContents, | ||||||||||||||||||||||||||||||||||||
| language: 'swift', | ||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit. It would be nice to include an example code snippet and a small summary of what will the flag do.