Skip to content

Commit

Permalink
test(react-native): move react navigation scenarios to dynamic test f…
Browse files Browse the repository at this point in the history
…ixture
  • Loading branch information
yousif-bugsnag committed Sep 18, 2024
1 parent c67b83b commit 6cbe7c9
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 78 deletions.
192 changes: 126 additions & 66 deletions scripts/generate-react-native-fixture.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ if (!process.env.REGISTRY_URL) {

const notifierVersion = process.env.NOTIFIER_VERSION || common.determineVersion()

const rnVersion = process.env.RN_VERSION
const reactNativeVersion = process.env.RN_VERSION
const ROOT_DIR = resolve(__dirname, '../')

let fixturePath = 'test/react-native/features/fixtures/generated/'
Expand All @@ -25,12 +25,21 @@ if (process.env.RCT_NEW_ARCH_ENABLED === '1') {
fixturePath += 'old-arch/'
}

const fixtureDir = resolve(ROOT_DIR, fixturePath, rnVersion)
const fixtureDir = resolve(ROOT_DIR, fixturePath, reactNativeVersion)

const replacementFilesDir = resolve(ROOT_DIR, 'test/react-native/features/fixtures/app/dynamic/')

const DEPENDENCIES = [
'[email protected]'
'[email protected]',
`@bugsnag/react-native@${notifierVersion}`
]

const REACT_NAVIGATION_DEPENDENCIES = [
`@bugsnag/plugin-react-navigation@${notifierVersion}`,
'@react-navigation/native',
'@react-navigation/native-stack',
'react-native-screens',
'react-native-safe-area-context'
]

if (!process.env.SKIP_GENERATE_FIXTURE) {
Expand All @@ -40,72 +49,12 @@ if (!process.env.SKIP_GENERATE_FIXTURE) {
}

// create the test fixture
const RNInitArgs = [`react-native@${process.env.RN_VERSION}`, 'init', 'reactnative', '--directory', fixtureDir, '--version', rnVersion, '--npm', '--skip-install']
const RNInitArgs = [`react-native@${process.env.RN_VERSION}`, 'init', 'reactnative', '--directory', fixtureDir, '--version', reactNativeVersion, '--npm', '--skip-install']
execFileSync('npx', RNInitArgs, { stdio: 'inherit' })

// replace the App.js/App.tsx file with our own App.js file
fs.readdirSync(resolve(fixtureDir))
.filter((file) => /App\.[tj]sx?$/.test(file))
.map((file) => fs.unlinkSync(resolve(fixtureDir, file)))

fs.copyFileSync(
resolve(replacementFilesDir, 'App.js'),
resolve(fixtureDir, 'App.js')
)

// replace the AndroidManifest.xml file with our own
fs.copyFileSync(
resolve(replacementFilesDir, 'android/AndroidManifest.xml'),
resolve(fixtureDir, 'android/app/src/main/AndroidManifest.xml')
)

// replace the Info.plist file with our own
fs.copyFileSync(
resolve(replacementFilesDir, 'ios/Info.plist'),
resolve(fixtureDir, 'ios/reactnative/Info.plist')
)

// copy the exportOptions.plist file
fs.copyFileSync(
resolve(replacementFilesDir, 'ios/exportOptions.plist'),
resolve(fixtureDir, 'exportOptions.plist')
)

// update pbxproj
let pbxProjContents = fs.readFileSync(`${fixtureDir}/ios/reactnative.xcodeproj/project.pbxproj`, 'utf8')
pbxProjContents = pbxProjContents.replaceAll('org.reactjs.native.example', 'com.bugsnag.fixtures')
replaceGeneratedFixtureFiles()

fs.writeFileSync(`${fixtureDir}/ios/reactnative.xcodeproj/project.pbxproj`, pbxProjContents)

// update Podfile
let podfileContents = fs.readFileSync(`${fixtureDir}/ios/Podfile`, 'utf8')

// use static frameworks (this fixes an issue with react-native-file-access on 0.75)
if (parseFloat(rnVersion) >= 0.75) {
podfileContents = podfileContents.replace(/target 'reactnative' do/, 'use_frameworks! :linkage => :static\ntarget \'reactnative\' do')
}

// disable Flipper
if (podfileContents.includes('use_flipper!')) {
podfileContents = podfileContents.replace(/use_flipper!/, '# use_flipper!')
} else if (podfileContents.includes(':flipper_configuration')) {
podfileContents = podfileContents.replace(/:flipper_configuration/, '# :flipper_configuration')
}

fs.writeFileSync(`${fixtureDir}/ios/Podfile`, podfileContents)

const fixtureDependencyArgs = DEPENDENCIES.join(' ')

// install test fixture dependencies and local packages
execSync(`npm install --save ${fixtureDependencyArgs}`, { cwd: fixtureDir, stdio: 'inherit' })

// install @bugsnag/react-native from the registry
execSync(`npm install --save @bugsnag/react-native@${notifierVersion} --registry ${process.env.REGISTRY_URL}`, { cwd: fixtureDir, stdio: 'inherit' })

// install the scenario launcher package
const scenarioLauncherPackage = `${ROOT_DIR}/test/react-native/features/fixtures/scenario-launcher`
execSync(`npm pack ${scenarioLauncherPackage} --pack-destination ${fixtureDir}`, { cwd: ROOT_DIR, stdio: 'inherit' })
execSync('npm install --save bugsnag-react-native-scenarios-1.0.0.tgz', { cwd: fixtureDir, stdio: 'inherit' })
installFixtureDependencies()
}

if (process.env.BUILD_ANDROID === 'true' || process.env.BUILD_ANDROID === '1') {
Expand Down Expand Up @@ -163,3 +112,114 @@ if (process.env.BUILD_IOS === 'true' || process.env.BUILD_IOS === '1') {

execFileSync('xcrun', exportArgs, { cwd: fixtureDir, stdio: 'inherit' })
}

function installFixtureDependencies() {
if (!process.env.RCT_NEW_ARCH_ENABLED) {
DEPENDENCIES.push(...REACT_NAVIGATION_DEPENDENCIES)
}

const fixtureDependencyArgs = DEPENDENCIES.join(' ')

// install test fixture dependencies
execSync(`npm install --save ${fixtureDependencyArgs} --registry ${process.env.REGISTRY_URL}`, { cwd: fixtureDir, stdio: 'inherit' })

// install the scenario launcher package
const scenarioLauncherPackage = `${ROOT_DIR}/test/react-native/features/fixtures/scenario-launcher`
execSync(`npm pack ${scenarioLauncherPackage} --pack-destination ${fixtureDir}`, { cwd: ROOT_DIR, stdio: 'inherit' })
execSync('npm install --save bugsnag-react-native-scenarios-1.0.0.tgz', { cwd: fixtureDir, stdio: 'inherit' })
}

/** Replace native files generated by react-native cli with pre-configured files */
function replaceGeneratedFixtureFiles() {
// replace the App.js/App.tsx file with our own App.js file
fs.readdirSync(resolve(fixtureDir))
.filter((file) => /App\.[tj]sx?$/.test(file))
.map((file) => fs.unlinkSync(resolve(fixtureDir, file)))

fs.copyFileSync(
resolve(replacementFilesDir, 'App.js'),
resolve(fixtureDir, 'App.js')
)

// replace the AndroidManifest.xml file with our own
fs.copyFileSync(
resolve(replacementFilesDir, 'android/AndroidManifest.xml'),
resolve(fixtureDir, 'android/app/src/main/AndroidManifest.xml')
)

// replace the Info.plist file with our own
fs.copyFileSync(
resolve(replacementFilesDir, 'ios/Info.plist'),
resolve(fixtureDir, 'ios/reactnative/Info.plist')
)

// copy the exportOptions.plist file
fs.copyFileSync(
resolve(replacementFilesDir, 'ios/exportOptions.plist'),
resolve(fixtureDir, 'exportOptions.plist')
)

// update pbxproj
let pbxProjContents = fs.readFileSync(`${fixtureDir}/ios/reactnative.xcodeproj/project.pbxproj`, 'utf8')
pbxProjContents = pbxProjContents.replaceAll('org.reactjs.native.example', 'com.bugsnag.fixtures')

fs.writeFileSync(`${fixtureDir}/ios/reactnative.xcodeproj/project.pbxproj`, pbxProjContents)

// update Podfile
let podfileContents = fs.readFileSync(`${fixtureDir}/ios/Podfile`, 'utf8')

// use static frameworks (this fixes an issue with react-native-file-access on 0.75)
if (parseFloat(reactNativeVersion) >= 0.75) {
podfileContents = podfileContents.replace(/target 'reactnative' do/, 'use_frameworks! :linkage => :static\ntarget \'reactnative\' do')
}

// disable Flipper
if (podfileContents.includes('use_flipper!')) {
podfileContents = podfileContents.replace(/use_flipper!/, '# use_flipper!')
} else if (podfileContents.includes(':flipper_configuration')) {
podfileContents = podfileContents.replace(/:flipper_configuration/, '# :flipper_configuration')
}

fs.writeFileSync(`${fixtureDir}/ios/Podfile`, podfileContents)

// react navigation setup
const fileExtension = parseFloat(reactNativeVersion) < 0.73 ? 'java' : 'kt'
let mainActivityPattern, mainActivityReplacement
if (fileExtension === 'java') {
mainActivityPattern = 'public class MainActivity extends ReactActivity {'
mainActivityReplacement = `
import android.os.Bundle;
public class MainActivity extends ReactActivity {
/**
* Required for react-navigation/native implementation
* https://reactnavigation.org/docs/getting-started/#installing-dependencies-into-a-bare-react-native-project
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
}
`
} else if (fileExtension === 'kt') {
mainActivityPattern = 'class MainActivity : ReactActivity() {'
mainActivityReplacement = `
import android.os.Bundle
class MainActivity : ReactActivity() {
/**
* Required for react-navigation/native implementation
* https://reactnavigation.org/docs/getting-started/#installing-dependencies-into-a-bare-react-native-project
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(null)
}
`
}

const mainActivityPath = `${fixtureDir}/android/app/src/main/java/com/reactnative/MainActivity.${fileExtension}`
let mainActivityContents = fs.readFileSync(mainActivityPath, 'utf8')
mainActivityContents = mainActivityContents.replace(mainActivityPattern, mainActivityReplacement)
fs.writeFileSync(mainActivityPath, mainActivityContents)
}
18 changes: 11 additions & 7 deletions test/react-native/features/fixtures/app/dynamic/App.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import React, { useEffect } from 'react'
import React, { useEffect, useState } from 'react'
import { SafeAreaView, StyleSheet, View, Text } from 'react-native'
import { launchScenario } from '@bugsnag/react-native-scenarios'

const App = () => {
const [scenario, setScenario] = useState(null)

useEffect(() => {
launchScenario()
launchScenario(setScenario)
}, [])

return (
<SafeAreaView style={styles.container}>
<View>
<Text>React Native Test App</Text>
</View>
</SafeAreaView>
scenario !== null ? scenario.view() : (
<SafeAreaView style={styles.container}>
<View>
<Text>React Native Test App</Text>
</View>
</SafeAreaView>
)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ async function runScenario (scenarioName, apiKey, notifyEndpoint, sessionEndpoin
// create the scenario and allow it to modify the configuration
const scenario = new Scenarios[scenarioName](nativeConfig, jsConfig, scenarioData)

console.error(`[Bugsnag ScenarioLauncher] with config: ${JSON.stringify(nativeConfig)} (native) and ${JSON.stringify(jsConfig)} (js)`)

// clear persistent data
console.error('[Bugsnag ScenarioLauncher] clearing persistent data')
NativeInterface.clearPersistentData()
Expand All @@ -36,7 +34,10 @@ async function runScenario (scenarioName, apiKey, notifyEndpoint, sessionEndpoin

// run the scenario
console.error('launching scenario')
setTimeout(() => scenario.run(), 1)
setTimeout(() => {
scenario.run()
if (typeof scenario.view === 'function') setScenario(scenario)
}, 1)
}

async function startBugsnag (scenarioName, apiKey, notifyEndpoint, sessionEndpoint, scenarioData) {
Expand Down Expand Up @@ -67,7 +68,7 @@ async function startBugsnag (scenarioName, apiKey, notifyEndpoint, sessionEndpoi
Bugsnag.start(jsConfig)
}

export async function launchScenario () {
export async function launchScenario (setScenario) {
const command = await getCurrentCommand()

switch (command.action) {
Expand All @@ -78,7 +79,8 @@ export async function launchScenario () {
command.api_key,
command.notify,
command.sessions,
command.scenario_data
command.scenario_data,
setScenario
)

case 'start-bugsnag':
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Scenario from './Scenario'
import Bugsnag from '@bugsnag/react-native'
import BugsnagPluginReactNavigation from '@bugsnag/plugin-react-navigation'
import * as React from 'react'
import { View, Text } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'

const delay = ms => new Promise(resolve => setTimeout(resolve, ms))

export class ReactNavigationBreadcrumbsDisabledScenario extends Scenario {
constructor (configuration, jsConfig) {
super()
configuration.enabledBreadcrumbTypes = ['process', 'request', 'log']
jsConfig.plugins = [new BugsnagPluginReactNavigation()]
}

view () {
const BugsnagNavigationContainer = Bugsnag.getPlugin('reactNavigation').createNavigationContainer(NavigationContainer)
const Stack = createNativeStackNavigator()
return (
<BugsnagNavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={ HomeScreen }
/>
<Stack.Screen
name="Details"
component={ DetailsScreen }
/>
</Stack.Navigator>
</BugsnagNavigationContainer>
)
}

run () {
}
}

function HomeScreen ({ navigation }) {
React.useEffect(() => {
(async () => {
await delay(100)
Bugsnag.notify(new Error('HomeNavigationError'))
await delay(250)
navigation.navigate('Details')
})()
}, [])

return (
<View style={ { flex: 1, alignItems: 'center', justifyContent: 'center' } }>
<Text>Home Screen</Text>
</View>
)
}

function DetailsScreen ({ navigation }) {
React.useEffect(() => {
(async () => {
await delay(100)
Bugsnag.notify(new Error('DetailsNavigationError'))
await delay(250)
throw new Error('DetailsNavigationUnhandledError')
})()
}, [])

return (
<View style={ { flex: 1, alignItems: 'center', justifyContent: 'center' } }>
<Text>Details Screen</Text>
</View>
)
}
Loading

0 comments on commit 6cbe7c9

Please sign in to comment.