├── .env ├── .github └── workflows │ ├── e2e-testing.yml │ ├── pr-check.yml │ └── unit-testing.yml ├── .gitignore ├── .yarn └── releases │ └── yarn-1.18.0.cjs ├── .yarnrc ├── LICENSE ├── README.md ├── app.json ├── apps ├── rn-cli-app │ ├── .bundle │ │ └── config │ ├── .eslintrc.js │ ├── .maestro │ │ ├── flow.yaml │ │ ├── setup.js │ │ └── subFlows │ │ │ └── goBackHome.yaml │ ├── .prettierrc.js │ ├── .watchmanconfig │ ├── App.tsx │ ├── Gemfile │ ├── Gemfile.lock │ ├── __mocks__ │ │ └── react-native-video.ts │ ├── __tests__ │ │ ├── Counter.test.tsx │ │ ├── CounterUsesCustomHook.test.tsx │ │ ├── EasyButton.test.tsx │ │ ├── FlatList.test.tsx │ │ ├── Home.test.tsx │ │ ├── ListWithFetch.test.tsx │ │ ├── Login.test.tsx │ │ ├── LoginSubmission.test.tsx │ │ ├── Modal.test.tsx │ │ └── Video.test.tsx │ ├── android │ │ ├── app │ │ │ ├── build.gradle │ │ │ ├── debug.keystore │ │ │ ├── proguard-rules.pro │ │ │ └── src │ │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ │ └── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── reactnativetesting │ │ │ │ │ ├── MainActivity.kt │ │ │ │ │ └── MainApplication.kt │ │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── rn_edit_text_material.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ └── values │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ └── settings.gradle │ ├── app.json │ ├── assetsTransformer.js │ ├── babel.config.js │ ├── fastlane │ │ ├── Fastfile │ │ └── Pluginfile │ ├── index.js │ ├── ios │ │ ├── .xcode.env │ │ ├── Podfile │ │ ├── Podfile.lock │ │ ├── Pods │ │ │ ├── Flipper-DoubleConversion │ │ │ │ └── Frameworks │ │ │ │ │ └── double-conversion.xcframework │ │ │ │ │ └── ios-arm64_x86_64-maccatalyst │ │ │ │ │ └── double-conversion.framework │ │ │ │ │ ├── Headers │ │ │ │ │ ├── Resources │ │ │ │ │ └── Versions │ │ │ │ │ └── Current │ │ │ ├── Flipper-Glog │ │ │ │ └── Frameworks │ │ │ │ │ └── glog.xcframework │ │ │ │ │ └── ios-arm64_x86_64-maccatalyst │ │ │ │ │ └── glog.framework │ │ │ │ │ ├── Headers │ │ │ │ │ ├── Modules │ │ │ │ │ ├── Resources │ │ │ │ │ └── Versions │ │ │ │ │ └── Current │ │ │ ├── OpenSSL-Universal │ │ │ │ └── Frameworks │ │ │ │ │ └── OpenSSL.xcframework │ │ │ │ │ ├── ios-arm64_x86_64-maccatalyst │ │ │ │ │ └── OpenSSL.framework │ │ │ │ │ │ ├── Headers │ │ │ │ │ │ ├── Modules │ │ │ │ │ │ ├── Resources │ │ │ │ │ │ └── Versions │ │ │ │ │ │ └── Current │ │ │ │ │ └── macos-arm64_x86_64 │ │ │ │ │ └── OpenSSL.framework │ │ │ │ │ ├── Headers │ │ │ │ │ ├── Modules │ │ │ │ │ ├── Resources │ │ │ │ │ └── Versions │ │ │ │ │ └── Current │ │ │ └── hermes-engine │ │ │ │ └── destroot │ │ │ │ └── Library │ │ │ │ └── Frameworks │ │ │ │ ├── macosx │ │ │ │ └── hermes.framework │ │ │ │ │ ├── Resources │ │ │ │ │ └── Versions │ │ │ │ │ └── Current │ │ │ │ └── universal │ │ │ │ └── hermes.xcframework │ │ │ │ └── ios-arm64_x86_64-maccatalyst │ │ │ │ └── hermes.framework │ │ │ │ ├── Resources │ │ │ │ └── Versions │ │ │ │ └── Current │ │ ├── reactnativetesting.xcodeproj │ │ │ ├── project.pbxproj │ │ │ └── xcshareddata │ │ │ │ └── xcschemes │ │ │ │ └── reactnativetesting.xcscheme │ │ ├── reactnativetesting.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── reactnativetesting │ │ │ ├── AppDelegate.h │ │ │ ├── AppDelegate.mm │ │ │ ├── Images.xcassets │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Info.plist │ │ │ ├── LaunchScreen.storyboard │ │ │ └── main.m │ │ └── reactnativetestingTests │ │ │ ├── Info.plist │ │ │ └── reactnativetestingTests.m │ ├── jest.config.js │ ├── jest.setup.js │ ├── metro.config.js │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── Counter.tsx │ │ │ ├── CounterUsesCustomHook.tsx │ │ │ ├── EasyButton.tsx │ │ │ ├── FlatList.tsx │ │ │ ├── Home.tsx │ │ │ ├── ListWithFetch.tsx │ │ │ ├── Login.tsx │ │ │ ├── LoginSubmission.tsx │ │ │ ├── Modal.tsx │ │ │ └── Video.tsx │ │ ├── hooks │ │ │ └── useCounter.ts │ │ ├── test │ │ │ ├── mocks │ │ │ │ ├── handlers.ts │ │ │ │ ├── mockedApiResponse.json │ │ │ │ └── server.ts │ │ │ └── test-utils.tsx │ │ └── utils │ │ │ └── theme.tsx │ └── tsconfig.json └── rn-expo-app │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── __tests__ │ └── app │ │ └── index.test.tsx │ ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ ├── debug.keystore │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── stevegalili │ │ │ │ └── rnexpoapp │ │ │ │ ├── MainActivity.kt │ │ │ │ └── MainApplication.kt │ │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ └── splashscreen_image.png │ │ │ ├── drawable-mdpi │ │ │ └── splashscreen_image.png │ │ │ ├── drawable-xhdpi │ │ │ └── splashscreen_image.png │ │ │ ├── drawable-xxhdpi │ │ │ └── splashscreen_image.png │ │ │ ├── drawable-xxxhdpi │ │ │ └── splashscreen_image.png │ │ │ ├── drawable │ │ │ ├── rn_edit_text_material.xml │ │ │ └── splashscreen.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── values-night │ │ │ └── colors.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle │ ├── app.json │ ├── app │ ├── _layout.tsx │ ├── blogs │ │ ├── [slug].tsx │ │ ├── _layout.tsx │ │ └── index.tsx │ ├── index.tsx │ └── settings │ │ ├── _layout.tsx │ │ ├── country.tsx │ │ └── index.tsx │ ├── assets │ ├── adaptive-icon.png │ ├── favicon.png │ ├── icon.png │ └── splash.png │ ├── babel.config.js │ ├── ios │ ├── .gitignore │ ├── .xcode.env │ ├── Podfile │ ├── Podfile.lock │ ├── Podfile.properties.json │ ├── rnexpoapp.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── rnexpoapp.xcscheme │ ├── rnexpoapp.xcworkspace │ │ └── contents.xcworkspacedata │ └── rnexpoapp │ │ ├── AppDelegate.h │ │ ├── AppDelegate.mm │ │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── App-Icon-1024x1024@1x.png │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── SplashScreen.imageset │ │ │ ├── Contents.json │ │ │ └── image.png │ │ └── SplashScreenBackground.imageset │ │ │ ├── Contents.json │ │ │ └── image.png │ │ ├── Info.plist │ │ ├── SplashScreen.storyboard │ │ ├── Supporting │ │ └── Expo.plist │ │ ├── main.m │ │ ├── noop-file.swift │ │ ├── rnexpoapp-Bridging-Header.h │ │ └── rnexpoapp.entitlements │ ├── jest.config.js │ ├── jest.setup.js │ ├── metro.config.js │ ├── package.json │ ├── tsconfig.json │ └── yarn ├── package.json ├── sonar-project.properties └── yarn.lock /.env: -------------------------------------------------------------------------------- 1 | EXPO_USE_METRO_WORKSPACE_ROOT=1 2 | -------------------------------------------------------------------------------- /.github/workflows/e2e-testing.yml: -------------------------------------------------------------------------------- 1 | name: E2E Testing 2 | env: 3 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 4 | on: 5 | pull_request: 6 | types: [opened, synchronize, reopened] 7 | concurrency: 8 | group: react-native-workflow-${{ github.ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | build_android: 13 | name: Build & Test Android 14 | timeout-minutes: 15 15 | runs-on: ubuntu-latest 16 | steps: 17 | run: echo "To be implemented" 18 | # gradlew assembleRelease currently fails due to monorepo structure 19 | # - uses: actions/checkout@v3 20 | # 21 | # - name: Set up JDK 17 22 | # uses: actions/setup-java@v3 23 | # with: 24 | # distribution: 'zulu' 25 | # java-version: '17' 26 | # check-latest: true 27 | # 28 | # - name: Setup Node 29 | # uses: actions/setup-node@v3 30 | # with: 31 | # node-version: '18' 32 | # 33 | # - name: Run Yarn Install 34 | # run: yarn install 35 | # 36 | # - name: Build Release APK 37 | # run: cd apps/rn-cli-app/android/ && ./gradlew assembleRelease 38 | # 39 | # - name: Upload to Maestro Cloud 40 | # uses: mobile-dev-inc/action-maestro-cloud@v1.8.0 41 | # with: 42 | # api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }} 43 | # app-file: app/build/outputs/apk/debug/app-debug.apk 44 | 45 | build_ios: 46 | name: Build IOS 47 | timeout-minutes: 60 48 | runs-on: macos-latest 49 | steps: 50 | - name: Archive iOS app 51 | run: echo "To be implemented" 52 | # - uses: actions/checkout@v2 53 | # - uses: maxim-lobanov/setup-xcode@v1.6.0 54 | # with: 55 | # xcode-version: 14.2.0 56 | # - run: cd ios && xcodebuild -project reactnativetesting.xcodeproj -scheme reactnativetesting -destination 'platform=iOS Simulator,name=iPhone 11' 57 | -------------------------------------------------------------------------------- /.github/workflows/pr-check.yml: -------------------------------------------------------------------------------- 1 | name: Check PR title 2 | 3 | on: 4 | pull_request: 5 | types: [opened, edited, synchronize, reopened] 6 | 7 | jobs: 8 | lint: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: aslafy-z/conventional-pr-title-action@v3 12 | env: 13 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 14 | -------------------------------------------------------------------------------- /.github/workflows/unit-testing.yml: -------------------------------------------------------------------------------- 1 | name: Unit Testing 2 | env: 3 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 4 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 5 | on: 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | types: [opened, synchronize, reopened] 11 | 12 | jobs: 13 | install-lint-test-scan: 14 | runs-on: ubuntu-latest 15 | steps: 16 | # Checkout the repository 17 | - uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 20 | # Setup Node environment 21 | - name: Setup Node 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: 18.x 25 | cache: 'yarn' 26 | # Load previous cache 27 | - name: ESLint Cache 28 | uses: actions/cache@v3 29 | with: 30 | path: './.eslintcache' 31 | key: ${{ runner.os }}-eslintcache-${{ github.ref_name }}-${{ hashFiles('.eslintcache') }} 32 | 33 | - name: Install Dependencies 34 | run: yarn install --immutable 35 | # Verify linting 36 | - name: Lint 37 | run: yarn lint 38 | # Run unit tests with coverage 39 | - name: test 40 | run: yarn test:unit:coverage 41 | # Run Code Analysis Scan 42 | - name: SonarCloud Scan 43 | uses: SonarSource/sonarcloud-github-action@master 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | **/ios/.xcode.env.local 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | *.hprof 33 | .cxx/ 34 | *.keystore 35 | !debug.keystore 36 | 37 | # node.js 38 | # 39 | node_modules/ 40 | npm-debug.log 41 | yarn-error.log 42 | 43 | # fastlane 44 | # 45 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 46 | # screenshots whenever they are needed. 47 | # For more information about the recommended setup visit: 48 | # https://docs.fastlane.tools/best-practices/source-control/ 49 | 50 | **/fastlane/report.xml 51 | **/fastlane/Preview.html 52 | **/fastlane/screenshots 53 | **/fastlane/test_output 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | 58 | # Ruby / CocoaPods 59 | **/ios/Pods/ 60 | **/vendor/bundle/ 61 | 62 | # Temporary files created by Metro to check the health of the file watcher 63 | .metro-health-check* 64 | 65 | # testing 66 | **/coverage 67 | 68 | *.env 69 | 70 | # Expo 71 | **/.expo/ 72 | **/dist/ 73 | **/web-build/ 74 | 75 | # @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb 76 | # The following patterns were generated by expo-cli 77 | 78 | **/expo-env.d.ts 79 | # @end expo-cli 80 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | yarn-path ".yarn/releases/yarn-1.18.0.cjs" 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Steve Galili 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | banner 3 |

4 | 5 | ## No More Hacks and Headaches <> Learn How to Test Your Components Before Your Users Will 6 | 7 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vanGalilea_react-native-testing&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vanGalilea_react-native-testing) 8 | [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=vanGalilea_react-native-testing&metric=coverage)](https://sonarcloud.io/summary/new_code?id=vanGalilea_react-native-testing) 9 | [![Unit Testing](https://github.com/vanGalilea/react-native-testing/actions/workflows/unit-testing.yml/badge.svg)](https://github.com/vanGalilea/react-native-testing/actions/workflows/unit-testing.yml) 10 | [![E2E Testing](https://github.com/vanGalilea/react-native-testing/actions/workflows/e2e-testing.yml/badge.svg)](https://github.com/vanGalilea/react-native-testing/actions/workflows/e2e-testing.yml) 11 | 12 | ### Covered Examples 🎞 13 | - 👆 [Clicking buttons and asserting onPress' outcome](https://github.com/vanGalilea/react-native-testing/blob/main/apps/rn-cli-app/__tests__/Counter.test.tsx). 14 | - 📲 [Filling a simple login form and asserting successful submission](https://github.com/vanGalilea/react-native-testing/blob/main/apps/rn-cli-app/__tests__/LoginSubmission.test.tsx). 15 | - 🎣 [Custom hook testing (number of alternatives)](https://github.com/vanGalilea/react-native-testing/blob/main/apps/rn-cli-app/__tests__/CounterUsesCustomHook.test.tsx). 16 | - 📡 [Mocking fetch calls](https://github.com/vanGalilea/react-native-testing/blob/main/apps/rn-cli-app/__tests__/LoginSubmission.test.tsx#L36). 17 | - 🧭 [Navigating through screens with React Navigation](https://github.com/vanGalilea/react-native-testing/blob/main/apps/rn-cli-app/__tests__/LoginSubmission.test.tsx#L13). 18 | - 🚟 [Navigating through screens with Expo Router](https://github.com/vanGalilea/react-native-testing/blob/main/apps/rn-expo-app/__tests__/app/index.test.tsx). 19 | - 🔚 [E2E feel due to real navigation throughout screens](https://github.com/vanGalilea/react-native-testing/blob/main/apps/rn-cli-app/__tests__/Home.test.tsx). 20 | - 📥 [Handling and mocking providers](https://github.com/vanGalilea/react-native-testing/blob/main/apps/rn-cli-app/src/test/test-utils.tsx). 21 | - 📹 [Mocking external lib.'s components](https://github.com/vanGalilea/react-native-testing/blob/main/apps/rn-cli-app/__tests__/Video.test.tsx). 22 | - 🎭 [Mocking and interacting with RN's Modal component](https://github.com/vanGalilea/react-native-testing/blob/main/apps/rn-cli-app/__tests__/Modal.test.tsx). 23 | - 🧾 [Handling with a screen with RN's FlatList component](https://github.com/vanGalilea/react-native-testing/blob/main/apps/rn-cli-app/__tests__/FlatList.test.tsx). 24 | - 📡 [Using MSW to mock api calls and handling loading/errors](https://github.com/vanGalilea/react-native-testing/blob/main/apps/rn-cli-app/__tests__/ListWithFetch.test.tsx). 25 | 26 | ### Tools in use 🛠️ 27 | - [Jest](https://jestjs.io/) 28 | - [React Native Testing Library](https://callstack.github.io/react-native-testing-library/) 29 | - [Maestro](https://maestro.mobile.dev/) 30 | 31 | ### Setup and requirements 📋 32 | - [RN- Setting up the development environment](https://reactnative.dev/docs/environment-setup) 33 | - [Installing Maestro](https://maestro.mobile.dev/getting-started/installing-maestro) 34 | 35 | ## Getting Started 🚀 36 | - Clone the repo ` git clone git@github.com:vanGalilea/react-native-testing.git` 37 | - Run `yarn` to install dependencies 38 | - Explore RN CLI app and/or Expo app's tests and source code that are relevant to your use case. 39 | 40 | 41 | ## RN CLI application 42 | ### How to run the tests 🏃‍♀️ 43 | - Run `cd apps/rn-cli-app` to navigate to the app folder 44 | - Run `yarn test:unit` to run the unit tests 45 | - Run `yarn test:unit:dev` to run the unit tests in dev/watch mode 46 | - Run `yarn test:unit:coverage` to run the tests and generate a coverage report 47 | 48 | Make sure you have built and run the app in dev mode before running the e2e tests. 49 | - Run `yarn test:e2e` to run the e2e tests 50 | - Run `yarn test:e2e:dev` to run the e2e tests in dev/watch mode 51 | - Run `yarn test:e2e:record` to run the e2e tests and record a video of the tests 52 | 53 | ### How to run the app 📱 54 | - Run `cd apps/rn-cli-app` to navigate to the app folder 55 | - Run `npx pod-install` to install iOS dependencies 56 | - Run `yarn start` to start the metro bundler 57 | - Click `i` to run the app on iOS simulator or `a` to run it on Android emulator 58 | 59 | ## Expo application 60 | ### How to run the tests 🏃‍♀️ 61 | - Run `cd apps/rn-expo-app` to navigate to the app folder 62 | - Run `yarn test:unit` to run the unit tests 63 | 64 | ### How to run the app 📱 65 | - Run `cd apps/rn-expo-app` to navigate to the app folder 66 | - Run `yarn start` to start the metro bundler 67 | - Click `i` to run the app on iOS simulator or `a` to run it on Android emulator 68 | 69 | ### Inspiration, resources and further reading 📚 70 | - 📑 A blog by [Steve Galili]([url](https://github.com/vanGalilea)) on ["Where and How to Start Testing Your React Native App"](https://medium.com/@stevegalili/where-and-how-to-start-testing-your-react-native-app-%EF%B8%8F-and-how-to-keep-on-testin-ec3464fb9b41) 71 | - 👏 Inspired by [Kent C. Dodds'](https://testingjavascript.com/) workshop [Test React Components with Jest and React Testing Library](https://github.com/testing-library/react-testing-library). 72 | For more info check [Epic React](https://epicreact.dev/). 73 | - 📕 [React Native Testing Library](https://callstack.github.io/react-native-testing-library/) 74 | - 🧑‍🔬️ [Jest](https://jestjs.io/) 75 | - ️⚛️ [React Native](https://reactnative.dev/) 76 | - 🗺 [React Navigation](https://reactnavigation.org/) 77 | - 🛰 [MSW](https://mswjs.io/) 78 | 79 | ### Impression of the project 📸 80 | https://github.com/vanGalilea/react-native-testing/assets/25864161/cdb6cdc7-7b28-4ecd-819f-52dd3c3d76c8 81 | 82 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "react-native-testing", 4 | "slug": "react-native-testing", 5 | "version": "1.2.0", 6 | "sdkVersion": "49.0.0", 7 | "platforms": [ 8 | "ios", 9 | "android", 10 | "web" 11 | ], 12 | "ios": { 13 | "bundleIdentifier": "com.stevegalili.react-native-testing" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/rn-cli-app/.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /apps/rn-cli-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native', 4 | ignorePatterns: ['.maestro/', 'coverage'], 5 | }; 6 | -------------------------------------------------------------------------------- /apps/rn-cli-app/.maestro/flow.yaml: -------------------------------------------------------------------------------- 1 | # flow.yaml 2 | 3 | appId: com.reactnativetesting 4 | --- 5 | - launchApp 6 | # load all output constants saved in the setup file 7 | - runScript: ./setup.js 8 | # test counter screen flow 9 | - tapOn: ${output.screens.home.counterButton} 10 | - assertVisible: ${output.screens.counter.currentCountIs0} 11 | - tapOn: ${output.screens.counter.incrementButton} 12 | - assertVisible: ${output.screens.counter.currentCountIs1} 13 | - tapOn: ${output.screens.counter.decrementButton} 14 | - assertVisible: ${output.screens.counter.currentCountIs0} 15 | - runFlow: ./subFlows/goBackHome.yaml 16 | # test Login screen flow 17 | - tapOn: ${output.screens.home.loginButton} 18 | - tapOn: ${output.screens.login.usernamePlaceholder} 19 | - inputText: ${output.screens.login.username} 20 | - tapOn: ${output.screens.login.passwordPlaceholder} 21 | - inputText: ${output.screens.login.password} 22 | - tapOn: ${output.screens.login.submitButton} 23 | - assertVisible: ${output.screens.login.submissionInProcessA11yId} 24 | # after login, we should be redirected to home screen 25 | # test EasyButton screen flow 26 | - tapOn: ${output.screens.home.easyButtonButton} 27 | - tapOn: ${output.screens.easyButton.button} 28 | - assertVisible: ${output.screens.easyButton.modalDescription} 29 | - tapOn: ${output.screens.easyButton.modalDismissButton} 30 | - runFlow: ./subFlows/goBackHome.yaml 31 | # test Video screen flow 32 | - tapOn: ${output.screens.home.videoButton} 33 | - assertVisible: ${output.screens.video.playerA11yId} 34 | - tapOn: ${output.screens.video.fullScreenButton} 35 | - tapOn: ${output.screens.video.pauseStartButton} 36 | - tapOn: ${output.screens.video.exitFullScreen} 37 | - tapOn: ${output.screens.video.pauseStartButton} 38 | - runFlow: ./subFlows/goBackHome.yaml 39 | # test Modal screen flow 40 | - tapOn: ${output.screens.home.modalButton} 41 | - tapOn: ${output.screens.modal.showButton} 42 | - assertVisible: ${output.screens.modal.description} 43 | - tapOn: ${output.screens.modal.hideButton} 44 | - assertVisible: ${output.screens.modal.showButton} 45 | - runFlow: ./subFlows/goBackHome.yaml 46 | # flat list screen flow 47 | - tapOn: ${output.screens.home.flatListButton} 48 | - assertVisible: ${output.screens.flatList.firstItemPage1} 49 | - scrollUntilVisible: 50 | element: ${output.screens.flatList.lastItemPage1} 51 | direction: "DOWN" 52 | - scrollUntilVisible: 53 | element: ${output.screens.flatList.fifthItemPage2} 54 | direction: "DOWN" 55 | - runFlow: ./subFlows/goBackHome.yaml 56 | # ListWithFetch screen flow 57 | - tapOn: ${output.screens.home.listWithFetchButton} 58 | - assertVisible: ${output.screens.listWitchFetch.firstItemId} 59 | - scrollUntilVisible: 60 | element: ${output.screens.listWitchFetch.fifteenthItemId} 61 | direction: "DOWN" 62 | - runFlow: ./subFlows/goBackHome.yaml 63 | - assertVisible: ${output.screens.home.title} 64 | -------------------------------------------------------------------------------- /apps/rn-cli-app/.maestro/setup.js: -------------------------------------------------------------------------------- 1 | output.screens = { 2 | navigation: { 3 | goBackHomeButtonTestID: 'go-back-home-button', 4 | }, 5 | home: { 6 | title: 'Go to component...', 7 | counterButton: 'Counter', 8 | loginButton: 'Login', 9 | easyButtonButton: 'EasyButton', 10 | videoButton: 'Video', 11 | modalButton: 'Modal', 12 | flatListButton: 'FlatList', 13 | listWithFetchButton: 'ListWithFetch', 14 | }, 15 | counter: { 16 | currentCountIs0: 'Current count: 0', 17 | currentCountIs1: 'Current count: 1', 18 | incrementButton: 'Increment', 19 | decrementButton: 'Decrement', 20 | }, 21 | login: { 22 | usernamePlaceholder: 'Username', 23 | passwordPlaceholder: 'Password', 24 | username: 'admin', 25 | password: 'admin', 26 | submitButton: 'Submit', 27 | submissionInProcessA11yId: 'submission-in-process', 28 | }, 29 | easyButton: { 30 | button: 'Click me!', 31 | modalDescription: 'You clicked me!', 32 | modalDismissButton: 'OK', 33 | }, 34 | video: { 35 | fullScreenButton: 'Full screen', 36 | exitFullScreen: 'Exit full screen', 37 | pauseStartButton: 'Pause/Start', 38 | playerA11yId: 'video-player', 39 | }, 40 | modal: { 41 | showButton: 'Show modal', 42 | hideButton: 'Hide modal', 43 | description: 'Hello world!', 44 | }, 45 | flatList: { 46 | firstItemPage1: 'Pizza', 47 | lastItemPage1: 'Cheese Cake', 48 | fifthItemPage2: 'Onion Fries', 49 | }, 50 | listWitchFetch: { 51 | firstItemId: '1-user-container', 52 | fifteenthItemId: '15-user-container', 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /apps/rn-cli-app/.maestro/subFlows/goBackHome.yaml: -------------------------------------------------------------------------------- 1 | appId: com.reactnativetesting 2 | --- 3 | ## go back to home screen 4 | - tapOn: 5 | id: ${output.screens.navigation.goBackHomeButtonTestID} 6 | - assertVisible: ${output.screens.home.title} 7 | -------------------------------------------------------------------------------- /apps/rn-cli-app/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSameLine: true, 4 | bracketSpacing: false, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /apps/rn-cli-app/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /apps/rn-cli-app/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {NavigationContainer, useNavigation} from '@react-navigation/native'; 3 | import { 4 | createStackNavigator, 5 | StackNavigationProp, 6 | } from '@react-navigation/stack'; 7 | import Home from './src/components/Home'; 8 | import EasyButton from './src/components/EasyButton'; 9 | import Video from './src/components/Video'; 10 | import FlatList from './src/components/FlatList'; 11 | import Modal from './src/components/Modal'; 12 | import {ThemeProvider} from './src/utils/theme'; 13 | import ListWithFetch from './src/components/ListWithFetch'; 14 | import LoginSubmission from './src/components/LoginSubmission'; 15 | import Counter from './src/components/Counter'; 16 | import {Alert, Pressable, Text} from 'react-native'; 17 | 18 | export type RootStackParamList = { 19 | Home: undefined; 20 | Counter: undefined; 21 | Login: undefined; 22 | EasyButton: undefined; 23 | Video: undefined; 24 | Modal: undefined; 25 | FlatList: undefined; 26 | ListWithFetch: undefined; 27 | }; 28 | export type NavigationProps = StackNavigationProp; 29 | 30 | export const Stack = createStackNavigator(); 31 | 32 | export const SCREENS: Record = { 33 | HOME: 'Home', 34 | COUNTER: 'Counter', 35 | LOGIN: 'Login', 36 | EASYBUTTON: 'EasyButton', 37 | VIDEO: 'Video', 38 | MODAL: 'Modal', 39 | FLATLIST: 'FlatList', 40 | LIST_WITH_FETCH: 'ListWithFetch', 41 | }; 42 | const EasyButtonScreen = () => { 43 | const handleOnPress = () => { 44 | Alert.alert('EasyButton', 'You clicked me!'); 45 | }; 46 | return ; 47 | }; 48 | export default () => { 49 | return ( 50 | <> 51 | 52 | 53 | 58 | 59 | 60 | 64 | 65 | 66 | 67 | 68 | 72 | 73 | 74 | 75 | 76 | ); 77 | }; 78 | 79 | const HeaderLeft = () => { 80 | const navigation = useNavigation(); 81 | if (!navigation.canGoBack()) { 82 | return null; 83 | } 84 | 85 | return ( 86 | navigation.goBack()}> 89 | 90 | 91 | ); 92 | }; 93 | -------------------------------------------------------------------------------- /apps/rn-cli-app/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby ">= 2.6.10" 5 | 6 | gem 'cocoapods', '~> 1.13' 7 | gem 'fastlane' 8 | gem 'activesupport', '>= 6.1.7.3', '< 7.1.0' 9 | 10 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 11 | eval_gemfile(plugins_path) if File.exist?(plugins_path) 12 | -------------------------------------------------------------------------------- /apps/rn-cli-app/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.6) 5 | rexml 6 | activesupport (6.1.7.6) 7 | concurrent-ruby (~> 1.0, >= 1.0.2) 8 | i18n (>= 1.6, < 2) 9 | minitest (>= 5.1) 10 | tzinfo (~> 2.0) 11 | zeitwerk (~> 2.3) 12 | addressable (2.8.6) 13 | public_suffix (>= 2.0.2, < 6.0) 14 | algoliasearch (1.27.5) 15 | httpclient (~> 2.8, >= 2.8.3) 16 | json (>= 1.5.1) 17 | artifactory (3.0.15) 18 | atomos (0.1.3) 19 | aws-eventstream (1.3.0) 20 | aws-partitions (1.876.0) 21 | aws-sdk-core (3.190.1) 22 | aws-eventstream (~> 1, >= 1.3.0) 23 | aws-partitions (~> 1, >= 1.651.0) 24 | aws-sigv4 (~> 1.8) 25 | jmespath (~> 1, >= 1.6.1) 26 | aws-sdk-kms (1.75.0) 27 | aws-sdk-core (~> 3, >= 3.188.0) 28 | aws-sigv4 (~> 1.1) 29 | aws-sdk-s3 (1.142.0) 30 | aws-sdk-core (~> 3, >= 3.189.0) 31 | aws-sdk-kms (~> 1) 32 | aws-sigv4 (~> 1.8) 33 | aws-sigv4 (1.8.0) 34 | aws-eventstream (~> 1, >= 1.0.2) 35 | babosa (1.0.4) 36 | claide (1.1.0) 37 | cocoapods (1.14.3) 38 | addressable (~> 2.8) 39 | claide (>= 1.0.2, < 2.0) 40 | cocoapods-core (= 1.14.3) 41 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 42 | cocoapods-downloader (>= 2.1, < 3.0) 43 | cocoapods-plugins (>= 1.0.0, < 2.0) 44 | cocoapods-search (>= 1.0.0, < 2.0) 45 | cocoapods-trunk (>= 1.6.0, < 2.0) 46 | cocoapods-try (>= 1.1.0, < 2.0) 47 | colored2 (~> 3.1) 48 | escape (~> 0.0.4) 49 | fourflusher (>= 2.3.0, < 3.0) 50 | gh_inspector (~> 1.0) 51 | molinillo (~> 0.8.0) 52 | nap (~> 1.0) 53 | ruby-macho (>= 2.3.0, < 3.0) 54 | xcodeproj (>= 1.23.0, < 2.0) 55 | cocoapods-core (1.14.3) 56 | activesupport (>= 5.0, < 8) 57 | addressable (~> 2.8) 58 | algoliasearch (~> 1.0) 59 | concurrent-ruby (~> 1.1) 60 | fuzzy_match (~> 2.0.4) 61 | nap (~> 1.0) 62 | netrc (~> 0.11) 63 | public_suffix (~> 4.0) 64 | typhoeus (~> 1.0) 65 | cocoapods-deintegrate (1.0.5) 66 | cocoapods-downloader (2.1) 67 | cocoapods-plugins (1.0.0) 68 | nap 69 | cocoapods-search (1.0.1) 70 | cocoapods-trunk (1.6.0) 71 | nap (>= 0.8, < 2.0) 72 | netrc (~> 0.11) 73 | cocoapods-try (1.2.0) 74 | colored (1.2) 75 | colored2 (3.1.2) 76 | commander (4.6.0) 77 | highline (~> 2.0.0) 78 | concurrent-ruby (1.2.2) 79 | declarative (0.0.20) 80 | digest-crc (0.6.5) 81 | rake (>= 12.0.0, < 14.0.0) 82 | domain_name (0.5.20190701) 83 | unf (>= 0.0.5, < 1.0.0) 84 | dotenv (2.8.1) 85 | emoji_regex (3.2.3) 86 | escape (0.0.4) 87 | ethon (0.16.0) 88 | ffi (>= 1.15.0) 89 | excon (0.109.0) 90 | faraday (1.10.3) 91 | faraday-em_http (~> 1.0) 92 | faraday-em_synchrony (~> 1.0) 93 | faraday-excon (~> 1.1) 94 | faraday-httpclient (~> 1.0) 95 | faraday-multipart (~> 1.0) 96 | faraday-net_http (~> 1.0) 97 | faraday-net_http_persistent (~> 1.0) 98 | faraday-patron (~> 1.0) 99 | faraday-rack (~> 1.0) 100 | faraday-retry (~> 1.0) 101 | ruby2_keywords (>= 0.0.4) 102 | faraday-cookie_jar (0.0.7) 103 | faraday (>= 0.8.0) 104 | http-cookie (~> 1.0.0) 105 | faraday-em_http (1.0.0) 106 | faraday-em_synchrony (1.0.0) 107 | faraday-excon (1.1.0) 108 | faraday-httpclient (1.0.1) 109 | faraday-multipart (1.0.4) 110 | multipart-post (~> 2) 111 | faraday-net_http (1.0.1) 112 | faraday-net_http_persistent (1.2.0) 113 | faraday-patron (1.0.0) 114 | faraday-rack (1.0.0) 115 | faraday-retry (1.0.3) 116 | faraday_middleware (1.2.0) 117 | faraday (~> 1.0) 118 | fastimage (2.3.0) 119 | fastlane (2.218.0) 120 | CFPropertyList (>= 2.3, < 4.0.0) 121 | addressable (>= 2.8, < 3.0.0) 122 | artifactory (~> 3.0) 123 | aws-sdk-s3 (~> 1.0) 124 | babosa (>= 1.0.3, < 2.0.0) 125 | bundler (>= 1.12.0, < 3.0.0) 126 | colored 127 | commander (~> 4.6) 128 | dotenv (>= 2.1.1, < 3.0.0) 129 | emoji_regex (>= 0.1, < 4.0) 130 | excon (>= 0.71.0, < 1.0.0) 131 | faraday (~> 1.0) 132 | faraday-cookie_jar (~> 0.0.6) 133 | faraday_middleware (~> 1.0) 134 | fastimage (>= 2.1.0, < 3.0.0) 135 | gh_inspector (>= 1.1.2, < 2.0.0) 136 | google-apis-androidpublisher_v3 (~> 0.3) 137 | google-apis-playcustomapp_v1 (~> 0.1) 138 | google-cloud-storage (~> 1.31) 139 | highline (~> 2.0) 140 | http-cookie (~> 1.0.5) 141 | json (< 3.0.0) 142 | jwt (>= 2.1.0, < 3) 143 | mini_magick (>= 4.9.4, < 5.0.0) 144 | multipart-post (>= 2.0.0, < 3.0.0) 145 | naturally (~> 2.2) 146 | optparse (>= 0.1.1) 147 | plist (>= 3.1.0, < 4.0.0) 148 | rubyzip (>= 2.0.0, < 3.0.0) 149 | security (= 0.1.3) 150 | simctl (~> 1.6.3) 151 | terminal-notifier (>= 2.0.0, < 3.0.0) 152 | terminal-table (~> 3) 153 | tty-screen (>= 0.6.3, < 1.0.0) 154 | tty-spinner (>= 0.8.0, < 1.0.0) 155 | word_wrap (~> 1.0.0) 156 | xcodeproj (>= 1.13.0, < 2.0.0) 157 | xcpretty (~> 0.3.0) 158 | xcpretty-travis-formatter (>= 0.0.3) 159 | fastlane-plugin-stream_actions (0.3.25) 160 | xctest_list (= 1.2.1) 161 | ffi (1.16.3) 162 | fourflusher (2.3.1) 163 | fuzzy_match (2.0.4) 164 | gh_inspector (1.1.3) 165 | google-apis-androidpublisher_v3 (0.54.0) 166 | google-apis-core (>= 0.11.0, < 2.a) 167 | google-apis-core (0.11.2) 168 | addressable (~> 2.5, >= 2.5.1) 169 | googleauth (>= 0.16.2, < 2.a) 170 | httpclient (>= 2.8.1, < 3.a) 171 | mini_mime (~> 1.0) 172 | representable (~> 3.0) 173 | retriable (>= 2.0, < 4.a) 174 | rexml 175 | webrick 176 | google-apis-iamcredentials_v1 (0.17.0) 177 | google-apis-core (>= 0.11.0, < 2.a) 178 | google-apis-playcustomapp_v1 (0.13.0) 179 | google-apis-core (>= 0.11.0, < 2.a) 180 | google-apis-storage_v1 (0.29.0) 181 | google-apis-core (>= 0.11.0, < 2.a) 182 | google-cloud-core (1.6.1) 183 | google-cloud-env (>= 1.0, < 3.a) 184 | google-cloud-errors (~> 1.0) 185 | google-cloud-env (1.6.0) 186 | faraday (>= 0.17.3, < 3.0) 187 | google-cloud-errors (1.3.1) 188 | google-cloud-storage (1.45.0) 189 | addressable (~> 2.8) 190 | digest-crc (~> 0.4) 191 | google-apis-iamcredentials_v1 (~> 0.1) 192 | google-apis-storage_v1 (~> 0.29.0) 193 | google-cloud-core (~> 1.6) 194 | googleauth (>= 0.16.2, < 2.a) 195 | mini_mime (~> 1.0) 196 | googleauth (1.8.1) 197 | faraday (>= 0.17.3, < 3.a) 198 | jwt (>= 1.4, < 3.0) 199 | multi_json (~> 1.11) 200 | os (>= 0.9, < 2.0) 201 | signet (>= 0.16, < 2.a) 202 | highline (2.0.3) 203 | http-cookie (1.0.5) 204 | domain_name (~> 0.5) 205 | httpclient (2.8.3) 206 | i18n (1.14.1) 207 | concurrent-ruby (~> 1.0) 208 | jmespath (1.6.2) 209 | json (2.7.1) 210 | jwt (2.7.1) 211 | mini_magick (4.12.0) 212 | mini_mime (1.1.5) 213 | minitest (5.20.0) 214 | molinillo (0.8.0) 215 | multi_json (1.15.0) 216 | multipart-post (2.3.0) 217 | nanaimo (0.3.0) 218 | nap (1.1.0) 219 | naturally (2.2.1) 220 | netrc (0.11.0) 221 | optparse (0.4.0) 222 | os (1.1.4) 223 | plist (3.7.1) 224 | public_suffix (4.0.7) 225 | rake (13.1.0) 226 | representable (3.2.0) 227 | declarative (< 0.1.0) 228 | trailblazer-option (>= 0.1.1, < 0.2.0) 229 | uber (< 0.2.0) 230 | retriable (3.1.2) 231 | rexml (3.2.6) 232 | rouge (2.0.7) 233 | ruby-macho (2.5.1) 234 | ruby2_keywords (0.0.5) 235 | rubyzip (2.3.2) 236 | security (0.1.3) 237 | signet (0.18.0) 238 | addressable (~> 2.8) 239 | faraday (>= 0.17.5, < 3.a) 240 | jwt (>= 1.5, < 3.0) 241 | multi_json (~> 1.10) 242 | simctl (1.6.10) 243 | CFPropertyList 244 | naturally 245 | terminal-notifier (2.0.0) 246 | terminal-table (3.0.2) 247 | unicode-display_width (>= 1.1.1, < 3) 248 | trailblazer-option (0.1.2) 249 | tty-cursor (0.7.1) 250 | tty-screen (0.8.2) 251 | tty-spinner (0.9.3) 252 | tty-cursor (~> 0.7) 253 | typhoeus (1.4.1) 254 | ethon (>= 0.9.0) 255 | tzinfo (2.0.6) 256 | concurrent-ruby (~> 1.0) 257 | uber (0.1.0) 258 | unf (0.1.4) 259 | unf_ext 260 | unf_ext (0.0.9.1) 261 | unicode-display_width (2.5.0) 262 | webrick (1.8.1) 263 | word_wrap (1.0.0) 264 | xcodeproj (1.23.0) 265 | CFPropertyList (>= 2.3.3, < 4.0) 266 | atomos (~> 0.1.3) 267 | claide (>= 1.0.2, < 2.0) 268 | colored2 (~> 3.1) 269 | nanaimo (~> 0.3.0) 270 | rexml (~> 3.2.4) 271 | xcpretty (0.3.0) 272 | rouge (~> 2.0.7) 273 | xcpretty-travis-formatter (1.0.1) 274 | xcpretty (~> 0.2, >= 0.0.7) 275 | xctest_list (1.2.1) 276 | zeitwerk (2.6.12) 277 | 278 | PLATFORMS 279 | ruby 280 | 281 | DEPENDENCIES 282 | activesupport (>= 6.1.7.3, < 7.1.0) 283 | cocoapods (~> 1.13) 284 | fastlane 285 | fastlane-plugin-stream_actions 286 | 287 | RUBY VERSION 288 | ruby 2.6.10p210 289 | 290 | BUNDLED WITH 291 | 2.3.21 292 | -------------------------------------------------------------------------------- /apps/rn-cli-app/__mocks__/react-native-video.ts: -------------------------------------------------------------------------------- 1 | import {View} from 'react-native'; 2 | 3 | export default View; 4 | -------------------------------------------------------------------------------- /apps/rn-cli-app/__tests__/Counter.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | cleanup, 4 | fireEvent, 5 | render, 6 | screen, 7 | } from '@testing-library/react-native'; 8 | import Counter from '../src/components/Counter'; 9 | 10 | afterEach(cleanup); 11 | 12 | it('renders correctly after in/decrement action', () => { 13 | // Render the Counter component 14 | render(); 15 | const {getByText} = screen; 16 | // Grab in/decrement Pressables for later use 17 | // (this will throw an error if not existing in component tree) 18 | const decrement = getByText(/decrement/i); 19 | const increment = getByText(/increment/i); 20 | 21 | // Initially check that the current count is 0 22 | expect(getByText('Current count: 0')).toBeOnTheScreen(); 23 | 24 | // Press the increment button and check that the current count is 1 25 | fireEvent.press(increment); 26 | expect(getByText('Current count: 1')).toBeOnTheScreen(); 27 | 28 | // Press the decrement button and check that the current count is 0 29 | fireEvent.press(decrement); 30 | expect(getByText('Current count: 0')).toBeOnTheScreen(); 31 | }); 32 | -------------------------------------------------------------------------------- /apps/rn-cli-app/__tests__/CounterUsesCustomHook.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | act, 4 | cleanup, 5 | fireEvent, 6 | render, 7 | screen, 8 | renderHook, 9 | } from '@testing-library/react-native'; 10 | import CounterUsesCustomHook from '../src/components/CounterUsesCustomHook'; 11 | import useCounter, { 12 | IUseCounterProps, 13 | IUseCounterResult, 14 | } from '../src/hooks/useCounter'; 15 | 16 | afterEach(cleanup); 17 | 18 | // There are several approaches for testing hooks: 19 | // - Integration approach: Test the using component 20 | // - Integration approach: With a dummy test component 21 | // - Unit approach: In isolation 22 | // Rule of thumb is, when a hook is being used within only one component, 23 | // you should at least test them together 24 | 25 | describe('Integration approach: Test the using component', () => { 26 | it('exposes the count and increment/decrement funcs. and overall func. works', () => { 27 | // Render the CounterUsesCustomHook component 28 | render(); 29 | const {getByText} = screen; 30 | // Grab in/decrement Pressables for later use 31 | // (this will throw an error if not existing in component tree) 32 | const decrement = getByText(/decrement/i); 33 | const increment = getByText(/increment/i); 34 | 35 | // Initially check that the current count is 0 36 | expect(getByText('Current count: 0')).toBeOnTheScreen(); 37 | 38 | // Press the increment button and check that the current count is 1 39 | fireEvent.press(increment); 40 | expect(getByText('Current count: 1')).toBeOnTheScreen(); 41 | 42 | // Press the decrement button and check that the current count is 0 43 | fireEvent.press(decrement); 44 | expect(getByText('Current count: 0')).toBeOnTheScreen(); 45 | }); 46 | }); 47 | 48 | describe('Integration approach: With a dummy test component', () => { 49 | const renderHookAndSetup = (componentProps: IUseCounterProps = {}) => { 50 | const result: {current: IUseCounterResult | undefined} = { 51 | current: undefined, 52 | }; 53 | const TestComponent = (props: any) => { 54 | result.current = useCounter(props); 55 | return null; 56 | }; 57 | render(); 58 | return result; 59 | }; 60 | it('exposes the count and increment/decrement functions- without component', () => { 61 | const result = renderHookAndSetup(); 62 | expect(result.current?.count).toBe(0); 63 | act(() => result.current?.increment()); 64 | expect(result.current?.count).toBe(1); 65 | act(() => result.current?.decrement()); 66 | expect(result.current?.count).toBe(0); 67 | }); 68 | 69 | it('allows customization of the initial count', () => { 70 | const result = renderHookAndSetup({initialCount: 3}); 71 | expect(result.current?.count).toBe(3); 72 | }); 73 | 74 | it('allows customization of the step', () => { 75 | const result = renderHookAndSetup({step: 2}); 76 | expect(result.current?.count).toBe(0); 77 | act(() => result.current?.increment()); 78 | expect(result.current?.count).toBe(2); 79 | act(() => result.current?.decrement()); 80 | expect(result.current?.count).toBe(0); 81 | }); 82 | }); 83 | 84 | describe('Unit approach: In isolation', () => { 85 | it('exposes the count and increment/decrement functions- hook only', () => { 86 | const {result} = renderHook(useCounter); 87 | expect(result.current.count).toBe(0); 88 | act(() => result.current.increment()); 89 | expect(result.current.count).toBe(1); 90 | act(() => result.current.decrement()); 91 | expect(result.current.count).toBe(0); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /apps/rn-cli-app/__tests__/EasyButton.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import EasyButton from '../src/components/EasyButton'; 4 | import {render} from '../src/test/test-utils'; 5 | import {cleanup, screen} from '@testing-library/react-native'; 6 | 7 | afterEach(cleanup); 8 | 9 | // We will right the following test in a scalable way 10 | // At the moment we have only 2 themes, but we can imagine 11 | // that we might have more themes in the future. 12 | const cases = [ 13 | ['dark', 'black', 'white'], 14 | // ['light', 'white', 'black'], 15 | ]; 16 | 17 | // We will use the jest.each function to run the same test with different 18 | // parameters. This will allow us to write a single test that will run 19 | // for each theme without repeating ourselves. 20 | it.each(cases)( 21 | 'renders with the light styles for the light theme', 22 | (desiredTheme, expectedBackground, expectedColor) => { 23 | render(Click me!, { 24 | theme: desiredTheme, 25 | }); 26 | const innerText = screen.getByText(/click me/i); 27 | const pressable = screen.getByRole('button'); 28 | expect(pressable).toHaveStyle({ 29 | backgroundColor: expectedBackground, 30 | }); 31 | expect(innerText).toHaveStyle({ 32 | color: expectedColor, 33 | }); 34 | }, 35 | ); 36 | -------------------------------------------------------------------------------- /apps/rn-cli-app/__tests__/FlatList.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | act, 4 | cleanup, 5 | render, 6 | screen, 7 | userEvent, 8 | waitForElementToBeRemoved, 9 | } from '@testing-library/react-native'; 10 | import SectionList from '../src/components/FlatList'; 11 | 12 | const SCREEN_SIZE = {width: 240, height: 480}; 13 | const scrollDownEventData = { 14 | y: 300, 15 | contentSize: SCREEN_SIZE, 16 | layoutMeasurement: SCREEN_SIZE, 17 | }; 18 | 19 | afterEach(cleanup); 20 | jest.useFakeTimers(); 21 | it('scrolls to bottom and loads more items', async () => { 22 | // Render the SectionList component 23 | render(); 24 | // First dish is visible 25 | 26 | expect(screen.getByText(/pizza/i)).toBeOnTheScreen(); 27 | // First dish from 2nd page is not visible yet 28 | expect(() => screen.getByText(/the impossible burger/i)).toThrow( 29 | 'Unable to find an element with text: /the impossible burger/i', 30 | ); 31 | // We haven't started loading yet 32 | expect(() => screen.getByText(/loading more dishes/i)).toThrow( 33 | 'Unable to find an element with text: /loading more dishes/i', 34 | ); 35 | // Simulate scrolling to the bottom of the list 36 | 37 | const user = userEvent.setup(); 38 | await user.scrollTo( 39 | screen.getByLabelText('dishes-list'), 40 | scrollDownEventData, 41 | ); 42 | await waitForElementToBeRemoved( 43 | () => screen.getByText(/loading more dishes/i), 44 | { 45 | timeout: 1500, 46 | }, 47 | ); 48 | 49 | expect(await screen.findByText(/the impossible burger/i)).toBeOnTheScreen(); 50 | }); 51 | 52 | it('refreshes when scrolling to the top', async () => { 53 | // Render the SectionList component 54 | render(); 55 | 56 | // First dish is visible 57 | expect(screen.getByText(/pizza/i)).toBeOnTheScreen(); 58 | 59 | // Simulate pull to refresh via refreshControl props, this can not be simulated by fireEvent.scroll 60 | // See discussion https://github.com/callstack/react-native-testing-library/issues/809#issuecomment-984823700 61 | const flatListTestInstance = screen.getByLabelText('dishes-list'); 62 | const {refreshControl} = flatListTestInstance.props; 63 | await act(async () => { 64 | refreshControl.props.onRefresh(); 65 | }); 66 | 67 | // First dish is not visible due to refresh and refreshing indicator is visible 68 | expect(() => screen.getByText(/pizza/i)).toThrow( 69 | 'Unable to find an element with text: /pizza/i', 70 | ); 71 | await waitForElementToBeRemoved(() => screen.getByText(/refreshing/i), { 72 | timeout: 1500, 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /apps/rn-cli-app/__tests__/Home.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | cleanup, 4 | render, 5 | screen, 6 | userEvent, 7 | } from '@testing-library/react-native'; 8 | import App from '../App'; 9 | 10 | afterEach(cleanup); 11 | 12 | //mocking async storage module 13 | const mockedSetItem = jest.fn(); 14 | jest.mock('@react-native-community/async-storage', () => ({ 15 | setItem: mockedSetItem, 16 | })); 17 | jest.useFakeTimers(); 18 | 19 | it('renders/navigates throughout app screens', async () => { 20 | // Render the app from teh root 21 | render(); 22 | 23 | // Check whether we're in the home screen 24 | expect(screen.getByText(/home/i)).toBeOnTheScreen(); 25 | 26 | // Navigate to counter screen by pressing on button 27 | const user = userEvent.setup(); 28 | await user.press(screen.getByText(/counter/i)); 29 | // Check that navigation was succeeded by inspecting corresponding text on the screen 30 | expect(screen.getByText(/current count: 0/i)).toBeOnTheScreen(); 31 | }); 32 | -------------------------------------------------------------------------------- /apps/rn-cli-app/__tests__/ListWithFetch.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | cleanup, 4 | render, 5 | screen, 6 | waitForElementToBeRemoved, 7 | } from '@testing-library/react-native'; 8 | import ListWithFetch from '../src/components/ListWithFetch'; 9 | import {server} from '../src/test/mocks/server'; 10 | import {rest} from 'msw'; 11 | 12 | afterEach(cleanup); 13 | 14 | // In this test suite, we are testing the component that fetches data from the server 15 | // We are using msw to mock the server response 16 | 17 | test('displays images from the server', async () => { 18 | // Render the component 19 | render(); 20 | 21 | // Loader is initially visible 22 | expect(screen.getByLabelText(/loader/i)).toBeOnTheScreen(); 23 | await waitForElementToBeRemoved(() => screen.getByLabelText(/loader/i), { 24 | timeout: 1500, 25 | }); 26 | // Verify that users are fetched and rendered 27 | expect(await screen.findAllByLabelText(/user-container/i)).toHaveLength(10); 28 | 29 | // Verifying that the loader is no longer visible 30 | // There are 2 ways to verify that a component is not in the UI tree 31 | // 1. Use waitForElementToBeRemoved to wait for the element to be removed from the DOM 32 | // 2. Use getBy* methods and expect them to throw an error with a corresponding message 33 | // 3. Use queryBy* methods and expect them to return null (See the next expect statement) 34 | expect(() => screen.getByLabelText(/loader/i)).toThrow( 35 | 'Unable to find an element with accessibility label: /loader/i', 36 | ); 37 | 38 | // Verifying that there are no errors 39 | expect(screen.queryByLabelText(/alert/i)).toBeNull(); 40 | }); 41 | 42 | test('displays error upon error response from server', async () => { 43 | // Simulate an error response from the server 44 | server.resetHandlers( 45 | rest.get('https://dummyjson.com/users', (res, req, ctx) => { 46 | // @ts-ignore 47 | res(ctx.status(500)); 48 | }), 49 | ); 50 | // Render the component 51 | render(); 52 | 53 | // Loader is initially visible 54 | expect(screen.getByLabelText(/loader/i)).toBeOnTheScreen(); 55 | // Verify that the error is rendered 56 | expect(await screen.findByText(/error oopsie/i)).toBeOnTheScreen(); 57 | // Verifying that the loader is no longer visible 58 | expect(screen.queryByLabelText(/loader/i)).toBeNull(); 59 | }); 60 | -------------------------------------------------------------------------------- /apps/rn-cli-app/__tests__/Login.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | cleanup, 4 | fireEvent, 5 | render, 6 | screen, 7 | } from '@testing-library/react-native'; 8 | import Login from '../src/components/Login'; 9 | 10 | afterEach(cleanup); 11 | 12 | it('fills in the form and handleSubmit is called', async () => { 13 | const username = 'hi'; 14 | const password = 'qwerty1234'; 15 | // Create a mock function to pass as onSubmit prop 16 | const handleSubmit = jest.fn(); 17 | // Render the component 18 | render(); 19 | 20 | // Fill in the form and submit it 21 | await fireEvent.changeText( 22 | screen.getByPlaceholderText(/username/i), 23 | username, 24 | ); 25 | await fireEvent.changeText( 26 | screen.getByPlaceholderText(/password/i), 27 | password, 28 | ); 29 | fireEvent.press(screen.getByText(/submit/i)); 30 | 31 | // Verify that handleSubmit was called with the correct arguments and only once 32 | expect(handleSubmit).toHaveBeenCalledWith({password, username}); 33 | expect(handleSubmit).toHaveBeenCalledTimes(1); 34 | }); 35 | -------------------------------------------------------------------------------- /apps/rn-cli-app/__tests__/LoginSubmission.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import { 4 | cleanup, 5 | fireEvent, 6 | render, 7 | screen, 8 | waitFor, 9 | } from '@testing-library/react-native'; 10 | import LoginSubmission from '../src/components/LoginSubmission'; 11 | import AsyncStorage from '@react-native-community/async-storage'; 12 | import {useNavigationMock} from '../src/test/test-utils'; 13 | 14 | jest.mock('@react-native-community/async-storage', () => ({ 15 | setItem: jest.fn(), 16 | })); 17 | jest.mock('@react-navigation/native', () => { 18 | return { 19 | createNavigatorFactory: jest.fn(), 20 | useNavigation: jest.fn(), 21 | }; 22 | }); 23 | jest.mock('@react-navigation/stack', () => ({ 24 | createStackNavigator: jest.fn(), 25 | })); 26 | 27 | afterEach(cleanup); 28 | beforeEach(() => { 29 | useNavigationMock.mockReset(); 30 | }); 31 | 32 | jest.useFakeTimers(); 33 | 34 | it('verifies happy flow of login', async () => { 35 | // Mock navigate function from useNavigation hook, in order to verify that 36 | // it's called with the correct arguments and not to actually navigate 37 | const mockNavigate = jest.fn(); 38 | useNavigationMock.mockImplementation(() => ({navigate: mockNavigate})); 39 | 40 | // Ensuring correct typing of the mock 41 | const fetchMock = global.fetch as jest.MockedFunction; 42 | // We're not going to implement all members of the fetch API, only what's needed 43 | // @ts-ignore 44 | fetchMock.mockResolvedValueOnce({ 45 | json: jest.fn().mockResolvedValue({token: 'fake-token'}), 46 | }); 47 | const username = 'chucknorris'; 48 | const password = 'i need no password'; 49 | 50 | // Render the component 51 | render(); 52 | 53 | // Fill in the form and submit it 54 | fireEvent.changeText(screen.getByPlaceholderText(/username/i), username); 55 | fireEvent.changeText(screen.getByPlaceholderText(/password/i), password); 56 | fireEvent.press(screen.getByText(/submit/i)); 57 | 58 | // Verify that the loading indicator is shown 59 | expect(screen.getByLabelText(/submission-in-process/i)).toBeVisible(); 60 | // Verify that the fetch function was called with the correct arguments 61 | // Can be done in 2 ways: 62 | // 1. Using toHaveBeenCalledWith 63 | expect(fetchMock).toHaveBeenCalledWith( 64 | 'https://e2c168f9-97f3-42e1-8b31-57f4ab52a3bc.mock.pstmn.io/api/login', 65 | { 66 | method: 'POST', 67 | body: JSON.stringify({username, password}), 68 | headers: {'content-type': 'application/json'}, 69 | }, 70 | ); 71 | // 2. Using toMatchInlineSnapshot in combination with mock.calls property 72 | expect(fetchMock.mock.calls).toMatchInlineSnapshot(` 73 | [ 74 | [ 75 | "https://e2c168f9-97f3-42e1-8b31-57f4ab52a3bc.mock.pstmn.io/api/login", 76 | { 77 | "body": "{"username":"chucknorris","password":"i need no password"}", 78 | "headers": { 79 | "content-type": "application/json", 80 | }, 81 | "method": "POST", 82 | }, 83 | ], 84 | ] 85 | `); 86 | // Advance timers by 2.5 seconds to allow the simulated delay after fetch to complete 87 | jest.advanceTimersByTime(2500); 88 | // // Verify that the navigate function was called with the correct arguments and only once 89 | await waitFor(() => expect(mockNavigate).toHaveBeenCalledTimes(1), { 90 | timeout: 2500, 91 | }); 92 | expect(mockNavigate).toHaveBeenCalledWith('Home'); 93 | // Verify that the token was saved to AsyncStorage 94 | expect(AsyncStorage.setItem).toHaveBeenCalledWith('token', 'fake-token'); 95 | }); 96 | -------------------------------------------------------------------------------- /apps/rn-cli-app/__tests__/Modal.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | cleanup, 4 | fireEvent, 5 | render, 6 | screen, 7 | waitFor, 8 | } from '@testing-library/react-native'; 9 | import ModalScreen from '../src/components/Modal'; 10 | 11 | afterEach(cleanup); 12 | 13 | it('renders modal screen correctly', async () => { 14 | // Render component 15 | render(); 16 | 17 | // Check if modal is initially closed 18 | expect(() => screen.getByText(/hello world/i)).toThrow( 19 | 'Unable to find an element with text: /hello world/i', 20 | ); 21 | 22 | // Simulate opening the modal 23 | fireEvent.press(screen.getByText(/show modal/i)); 24 | // Validate that modal is open 25 | await waitFor(() => screen.getByText(/hello world/i)); 26 | 27 | // Simulate closing the modal 28 | fireEvent.press(screen.getByText(/hide modal/i)); 29 | // Validate that modal is closed 30 | expect(() => screen.getByText(/hide modal/i)).toThrow( 31 | 'Unable to find an element with text: /hide modal/i', 32 | ); 33 | }); 34 | -------------------------------------------------------------------------------- /apps/rn-cli-app/__tests__/Video.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import { 4 | cleanup, 5 | fireEvent, 6 | render, 7 | screen, 8 | } from '@testing-library/react-native'; 9 | import App from '../App'; 10 | 11 | // 'react-native-video' is being mocked in /__mocks__/react-native-video.ts 12 | jest.mock('@react-native-community/async-storage', () => ({ 13 | setItem: jest.fn(), 14 | })); 15 | 16 | afterEach(cleanup); 17 | 18 | it('renders/navigates throughout app screens', async () => { 19 | // Render the app from the root 20 | render(); 21 | // Navigate to video screen 22 | fireEvent.press(screen.getByText(/video/i)); 23 | 24 | // Grab video comp., full-screen and pause/start pressables 25 | const videoTestInstance = screen.getByLabelText('video-player'); 26 | const enterFullScreenButton = screen.getByText(/full screen/i); 27 | const pauseStartButton = screen.getByText(/pause\/start/i); 28 | 29 | // We make sure to veify that the video is initially playing and 30 | // presented not in full screen mode 31 | expect(videoTestInstance).toHaveProp('paused', false); 32 | expect(videoTestInstance).toHaveProp('fullscreen', false); 33 | expect(videoTestInstance).toHaveStyle({ 34 | width: 200, 35 | height: 200, 36 | }); 37 | 38 | // Simulate pause video and enter full screen mode 39 | fireEvent.press(enterFullScreenButton); 40 | fireEvent.press(pauseStartButton); 41 | 42 | // Props indeed changed and match the scenario with the style we expect 43 | expect(videoTestInstance).toHaveProp('paused', true); 44 | expect(videoTestInstance).toHaveProp('fullscreen', true); 45 | expect(videoTestInstance).toHaveStyle({ 46 | width: '100%', 47 | height: 200, 48 | zIndex: 5, 49 | }); 50 | 51 | // Play video and assert not paused anymore 52 | fireEvent.press(pauseStartButton); 53 | expect(videoTestInstance).toHaveProp('paused', false); 54 | 55 | // Exit full screen mode and assert by value of prop 56 | fireEvent.press(screen.getByText(/exit full screen/i)); 57 | expect(videoTestInstance).toHaveProp('fullscreen', false); 58 | }); 59 | -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply plugin: "org.jetbrains.kotlin.android" 3 | apply plugin: "com.facebook.react" 4 | 5 | /** 6 | * This is the configuration block to customize your React Native Android app. 7 | * By default you don't need to apply any configuration, just uncomment the lines you need. 8 | */ 9 | react { 10 | /* Folders */ 11 | // The root of your project, i.e. where "package.json" lives. Default is '..' 12 | root = file("../../../../") 13 | // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen 14 | codegenDir = file("../../../../node_modules/@react-native/codegen") 15 | // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js 16 | cliFile = file("../../../../node_modules/react-native/cli.js") 17 | 18 | /* Variants */ 19 | // The list of variants to that are debuggable. For those we're going to 20 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'. 21 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. 22 | // debuggableVariants = ["liteDebug", "prodDebug"] 23 | 24 | /* Bundling */ 25 | // A list containing the node command and its flags. Default is just 'node'. 26 | // nodeExecutableAndArgs = ["node"] 27 | // 28 | // The command to run when bundling. By default is 'bundle' 29 | // bundleCommand = "ram-bundle" 30 | // 31 | // The path to the CLI configuration file. Default is empty. 32 | // bundleConfig = file(../rn-cli.config.js) 33 | // 34 | // The name of the generated asset file containing your JS bundle 35 | // bundleAssetName = "MyApplication.android.bundle" 36 | // 37 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' 38 | entryFile = file("../../index.js") 39 | // 40 | // A list of extra flags to pass to the 'bundle' commands. 41 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle 42 | // extraPackagerArgs = [] 43 | 44 | /* Hermes Commands */ 45 | // The hermes compiler command to run. By default it is 'hermesc' 46 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" 47 | // 48 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" 49 | // hermesFlags = ["-O", "-output-source-map"] 50 | } 51 | 52 | /** 53 | * Set this to true to Run Proguard on Release builds to minify the Java bytecode. 54 | */ 55 | def enableProguardInReleaseBuilds = false 56 | 57 | /** 58 | * The preferred build flavor of JavaScriptCore (JSC) 59 | * 60 | * For example, to use the international variant, you can use: 61 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 62 | * 63 | * The international variant includes ICU i18n library and necessary data 64 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 65 | * give correct results when using with locales other than en-US. Note that 66 | * this variant is about 6MiB larger per architecture than default. 67 | */ 68 | def jscFlavor = 'org.webkit:android-jsc:+' 69 | 70 | android { 71 | ndkVersion rootProject.ext.ndkVersion 72 | buildToolsVersion rootProject.ext.buildToolsVersion 73 | compileSdk rootProject.ext.compileSdkVersion 74 | 75 | namespace "com.reactnativetesting" 76 | defaultConfig { 77 | applicationId "com.reactnativetesting" 78 | minSdkVersion rootProject.ext.minSdkVersion 79 | targetSdkVersion rootProject.ext.targetSdkVersion 80 | versionCode 1 81 | versionName "1.0" 82 | } 83 | signingConfigs { 84 | debug { 85 | storeFile file('debug.keystore') 86 | storePassword 'android' 87 | keyAlias 'androiddebugkey' 88 | keyPassword 'android' 89 | } 90 | release { 91 | // Caution! In production, you need to generate your own keystore file. 92 | // see https://reactnative.dev/docs/signed-apk-android. 93 | storeFile file('release.keystore') 94 | storePassword 'your_key_alias' 95 | keyAlias '!igF-.z6Tw@BscKx' 96 | keyPassword '!igF-.z6Tw@BscKx' 97 | } 98 | } 99 | buildTypes { 100 | debug { 101 | signingConfig signingConfigs.debug 102 | } 103 | release { 104 | // Caution! In production, you need to generate your own keystore file. 105 | // see https://reactnative.dev/docs/signed-apk-android. 106 | signingConfig signingConfigs.debug 107 | minifyEnabled enableProguardInReleaseBuilds 108 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 109 | } 110 | } 111 | } 112 | 113 | dependencies { 114 | // The version of react-native is set by the React Native Gradle Plugin 115 | implementation("com.facebook.react:react-android") 116 | implementation("com.facebook.react:flipper-integration") 117 | if (hermesEnabled.toBoolean()) { 118 | implementation("com.facebook.react:hermes-android") 119 | } else { 120 | implementation jscFlavor 121 | } 122 | } 123 | 124 | apply from: file("../../../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) 125 | -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-cli-app/android/app/debug.keystore -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/main/java/com/reactnativetesting/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.reactnativetesting 2 | 3 | import com.facebook.react.ReactActivity 4 | import com.facebook.react.ReactActivityDelegate 5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled 6 | import com.facebook.react.defaults.DefaultReactActivityDelegate 7 | 8 | class MainActivity : ReactActivity() { 9 | 10 | /** 11 | * Returns the name of the main component registered from JavaScript. This is used to schedule 12 | * rendering of the component. 13 | */ 14 | override fun getMainComponentName(): String = "reactnativetesting" 15 | 16 | /** 17 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] 18 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] 19 | */ 20 | override fun createReactActivityDelegate(): ReactActivityDelegate = 21 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) 22 | } 23 | -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/main/java/com/reactnativetesting/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.reactnativetesting 2 | 3 | import android.app.Application 4 | import com.facebook.react.PackageList 5 | import com.facebook.react.ReactApplication 6 | import com.facebook.react.ReactHost 7 | import com.facebook.react.ReactNativeHost 8 | import com.facebook.react.ReactPackage 9 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load 10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost 11 | import com.facebook.react.defaults.DefaultReactNativeHost 12 | import com.facebook.react.flipper.ReactNativeFlipper 13 | import com.facebook.soloader.SoLoader 14 | 15 | class MainApplication : Application(), ReactApplication { 16 | 17 | override val reactNativeHost: ReactNativeHost = 18 | object : DefaultReactNativeHost(this) { 19 | override fun getPackages(): List { 20 | // Packages that cannot be autolinked yet can be added manually here, for example: 21 | // packages.add(new MyReactNativePackage()); 22 | return PackageList(this).packages 23 | } 24 | 25 | override fun getJSMainModuleName(): String = "index" 26 | 27 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG 28 | 29 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 30 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED 31 | } 32 | 33 | override val reactHost: ReactHost 34 | get() = getDefaultReactHost(this.applicationContext, reactNativeHost) 35 | 36 | override fun onCreate() { 37 | super.onCreate() 38 | SoLoader.init(this, false) 39 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 40 | // If you opted-in for the New Architecture, we load the native entry point for this app. 41 | load() 42 | } 43 | ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 21 | 22 | 23 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-cli-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-cli-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-cli-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-cli-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-cli-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-cli-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-cli-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-cli-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-cli-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-cli-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | reactnativetesting 3 | 4 | -------------------------------------------------------------------------------- /apps/rn-cli-app/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /apps/rn-cli-app/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | buildToolsVersion = "34.0.0" 4 | minSdkVersion = 21 5 | compileSdkVersion = 34 6 | targetSdkVersion = 34 7 | ndkVersion = "25.1.8937393" 8 | kotlinVersion = "1.8.0" 9 | } 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | dependencies { 15 | classpath("com.android.tools.build:gradle") 16 | classpath("com.facebook.react:react-native-gradle-plugin") 17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") 18 | } 19 | } 20 | 21 | apply plugin: "com.facebook.react.rootproject" 22 | -------------------------------------------------------------------------------- /apps/rn-cli-app/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Use this property to specify which architecture you want to build. 28 | # You can also override it from the CLI using 29 | # ./gradlew -PreactNativeArchitectures=x86_64 30 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 31 | 32 | # Use this property to enable support to the new architecture. 33 | # This will allow you to use TurboModules and the Fabric render in 34 | # your application. You should enable this flag either if you want 35 | # to write custom TurboModules/Fabric components OR use libraries that 36 | # are providing them. 37 | newArchEnabled=false 38 | 39 | # Use this property to enable or disable the Hermes JS engine. 40 | # If set to false, you will be using JSC instead. 41 | hermesEnabled=true 42 | -------------------------------------------------------------------------------- /apps/rn-cli-app/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-cli-app/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /apps/rn-cli-app/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /apps/rn-cli-app/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /apps/rn-cli-app/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'reactnativetesting' 2 | apply from: file("../../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | includeBuild('../../../node_modules/@react-native/gradle-plugin') 5 | -------------------------------------------------------------------------------- /apps/rn-cli-app/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactnativetesting", 3 | "displayName": "reactnativetesting" 4 | } -------------------------------------------------------------------------------- /apps/rn-cli-app/assetsTransformer.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | process(src, filename) { 5 | return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /apps/rn-cli-app/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:@react-native/babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /apps/rn-cli-app/fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | skip_docs 2 | metro_port = 8081 3 | build_dir = 'dist' 4 | bundle_id = 'com.reactnativetesting' 5 | 6 | before_all do 7 | if is_ci 8 | setup_ci 9 | ENV['FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT'] = '180' 10 | end 11 | end 12 | 13 | desc 'Installs all Certs and Profiles necessary for appstore' 14 | lane :match_appstore do 15 | match( 16 | type: 'appstore', 17 | app_identifier: [bundle_id], 18 | readonly: is_ci 19 | ) 20 | end 21 | 22 | lane :stop_metro do 23 | sh("lsof -t -i:#{metro_port} | xargs kill -s INT || true") 24 | end 25 | 26 | lane :load_package do 27 | load_json(json_path: 'package.json') 28 | end 29 | 30 | lane :build_android do 31 | gradle(project_dir: 'android', tasks: %w[clean assembleRelease]) 32 | 33 | Dir.chdir('..') do 34 | sh("mkdir -p #{build_dir} && mv -f #{lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]} #{build_dir}/app.apk") 35 | end 36 | end 37 | 38 | lane :test_android do |options| 39 | build_android unless is_ci || options[:skip_install] 40 | wait_android_emu_idle(load_threshold: 1, timeout: 2200) 41 | emulator_status = -> { sh('adb devices').include?('emulator') } 42 | 43 | Dir.chdir('..') do 44 | unless options[:skip_install] 45 | sh("adb uninstall #{bundle_id} >/dev/null || true") 46 | sh("adb install #{build_dir}/app.apk") 47 | end 48 | 49 | sh('adb logcat -c || true') if emulator_status.call 50 | 51 | # Record emulator screen using codec h264 52 | # adb limits the recording to 3 minutes, so we bypass this by redirecting the stream to ffmpeg tool 53 | video_group_pid = Process.spawn('adb shell "while true; do screenrecord --output-format=h264 -; done" | ' \ 54 | 'ffmpeg -y -i - fastlane/video.mp4 > fastlane/recording.log 2>&1 &', pgroup: true) 55 | video_pid = `pgrep -g #{Process.getpgid(video_group_pid)}`.split.map(&:to_i).first 56 | 57 | sh('yarn test:e2e') 58 | ensure 59 | sh("kill -s INT #{video_pid} || true") 60 | sh('adb logcat -d > fastlane/device.log || true') if emulator_status.call 61 | sh('maestro hierarchy > fastlane/hierarchy.json || true') 62 | [ 63 | 'recording.log', 64 | 'hierarchy.json', 65 | 'device.log', 66 | 'video.mp4' 67 | ].each { |f| sh("mv -f fastlane/#{f} ~/.maestro/tests || true") } if is_ci 68 | stop_metro 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /apps/rn-cli-app/fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-stream_actions' 6 | 7 | -------------------------------------------------------------------------------- /apps/rn-cli-app/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import {AppRegistry} from 'react-native'; 6 | import App from './App'; 7 | import {name as appName} from './app.json'; 8 | 9 | AppRegistry.registerComponent(appName, () => App); 10 | -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Resolve react_native_pods.rb with node to allow for hoisting 2 | require Pod::Executable.execute_command('node', ['-p', 3 | 'require.resolve( 4 | "react-native/scripts/react_native_pods.rb", 5 | {paths: [process.argv[1]]}, 6 | )', __dir__]).strip 7 | require_relative '../../../node_modules/@react-native-community/cli-platform-ios/native_modules' 8 | 9 | platform :ios, min_ios_version_supported 10 | prepare_react_native_project! 11 | 12 | # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. 13 | # because `react-native-flipper` depends on (FlipperKit,...) that will be excluded 14 | # 15 | # To fix this you can also exclude `react-native-flipper` using a `react-native.config.js` 16 | # ```js 17 | # module.exports = { 18 | # dependencies: { 19 | # ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}), 20 | # ``` 21 | flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled 22 | 23 | linkage = ENV['USE_FRAMEWORKS'] 24 | if linkage != nil 25 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green 26 | use_frameworks! :linkage => linkage.to_sym 27 | end 28 | 29 | target 'reactnativetesting' do 30 | config = use_native_modules! 31 | 32 | use_react_native!( 33 | :path => config[:reactNativePath], 34 | # Enables Flipper. 35 | # 36 | # Note that if you have use_frameworks! enabled, Flipper will not work and 37 | # you should disable the next line. 38 | :flipper_configuration => flipper_config, 39 | # An absolute path to your application root. 40 | :app_path => "#{Pod::Config.instance.installation_root}/.." 41 | ) 42 | 43 | target 'reactnativetestingTests' do 44 | inherit! :complete 45 | # Pods for testing 46 | end 47 | 48 | post_install do |installer| 49 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 50 | react_native_post_install( 51 | installer, 52 | config[:reactNativePath], 53 | :mac_catalyst_enabled => false 54 | ) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/Flipper-DoubleConversion/Frameworks/double-conversion.xcframework/ios-arm64_x86_64-maccatalyst/double-conversion.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/Flipper-DoubleConversion/Frameworks/double-conversion.xcframework/ios-arm64_x86_64-maccatalyst/double-conversion.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/Flipper-DoubleConversion/Frameworks/double-conversion.xcframework/ios-arm64_x86_64-maccatalyst/double-conversion.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/Flipper-Glog/Frameworks/glog.xcframework/ios-arm64_x86_64-maccatalyst/glog.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/Flipper-Glog/Frameworks/glog.xcframework/ios-arm64_x86_64-maccatalyst/glog.framework/Modules: -------------------------------------------------------------------------------- 1 | Versions/Current/Modules -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/Flipper-Glog/Frameworks/glog.xcframework/ios-arm64_x86_64-maccatalyst/glog.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/Flipper-Glog/Frameworks/glog.xcframework/ios-arm64_x86_64-maccatalyst/glog.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework/ios-arm64_x86_64-maccatalyst/OpenSSL.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework/ios-arm64_x86_64-maccatalyst/OpenSSL.framework/Modules: -------------------------------------------------------------------------------- 1 | Versions/Current/Modules -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework/ios-arm64_x86_64-maccatalyst/OpenSSL.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework/ios-arm64_x86_64-maccatalyst/OpenSSL.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework/macos-arm64_x86_64/OpenSSL.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework/macos-arm64_x86_64/OpenSSL.framework/Modules: -------------------------------------------------------------------------------- 1 | Versions/Current/Modules -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework/macos-arm64_x86_64/OpenSSL.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework/macos-arm64_x86_64/OpenSSL.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/hermes-engine/destroot/Library/Frameworks/macosx/hermes.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/hermes-engine/destroot/Library/Frameworks/macosx/hermes.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | 0 -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/hermes-engine/destroot/Library/Frameworks/universal/hermes.xcframework/ios-arm64_x86_64-maccatalyst/hermes.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/Pods/hermes-engine/destroot/Library/Frameworks/universal/hermes.xcframework/ios-arm64_x86_64-maccatalyst/hermes.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | 0 -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/reactnativetesting.xcodeproj/xcshareddata/xcschemes/reactnativetesting.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/reactnativetesting.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/reactnativetesting.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/reactnativetesting/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : RCTAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/reactnativetesting/AppDelegate.mm: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | 5 | @implementation AppDelegate 6 | 7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 8 | { 9 | self.moduleName = @"reactnativetesting"; 10 | // You can add your custom initial props in the dictionary below. 11 | // They will be passed down to the ViewController used by React Native. 12 | self.initialProps = @{}; 13 | 14 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 15 | } 16 | 17 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 18 | { 19 | return [self getBundleURL]; 20 | } 21 | 22 | - (NSURL *)getBundleURL 23 | { 24 | #if DEBUG 25 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; 26 | #else 27 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 28 | #endif 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/reactnativetesting/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "scale" : "1x", 46 | "size" : "1024x1024" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/reactnativetesting/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/reactnativetesting/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | reactnativetesting 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(CURRENT_PROJECT_VERSION) 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | NSAllowsLocalNetworking 32 | 33 | NSExceptionDomains 34 | 35 | localhost 36 | 37 | NSExceptionAllowsInsecureHTTPLoads 38 | 39 | 40 | 41 | 42 | NSLocationWhenInUseUsageDescription 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UIViewControllerBasedStatusBarAppearance 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/reactnativetesting/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/reactnativetesting/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | @autoreleasepool { 8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/reactnativetestingTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | NSAllowsArbitraryLoads 25 | 26 | NSAllowsLocalNetworking 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /apps/rn-cli-app/ios/reactnativetestingTests/reactnativetestingTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import 6 | 7 | #define TIMEOUT_SECONDS 600 8 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 9 | 10 | @interface reactnativetestingTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation reactnativetestingTests 15 | 16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test 17 | { 18 | if (test(view)) { 19 | return YES; 20 | } 21 | for (UIView *subview in [view subviews]) { 22 | if ([self findSubviewInView:subview matching:test]) { 23 | return YES; 24 | } 25 | } 26 | return NO; 27 | } 28 | 29 | - (void)testRendersWelcomeScreen 30 | { 31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 33 | BOOL foundElement = NO; 34 | 35 | __block NSString *redboxError = nil; 36 | #ifdef DEBUG 37 | RCTSetLogFunction( 38 | ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 39 | if (level >= RCTLogLevelError) { 40 | redboxError = message; 41 | } 42 | }); 43 | #endif 44 | 45 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 46 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 47 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 48 | 49 | foundElement = [self findSubviewInView:vc.view 50 | matching:^BOOL(UIView *view) { 51 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 52 | return YES; 53 | } 54 | return NO; 55 | }]; 56 | } 57 | 58 | #ifdef DEBUG 59 | RCTSetLogFunction(RCTDefaultLogFunction); 60 | #endif 61 | 62 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 63 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /apps/rn-cli-app/jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | preset: 'react-native', 5 | setupFilesAfterEnv: ['./jest.setup.js'], 6 | // This is needed to mock the react-native-gesture-handler 7 | setupFiles: ['../../node_modules/react-native-gesture-handler/jestSetup.js'], 8 | clearMocks: true, 9 | moduleDirectories: ['../../node_modules', path.join(__dirname, 'src')], 10 | // This is needed to be able to render images in tests and transform them to mocks 11 | moduleNameMapper: { 12 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 13 | '/assetsTransformer.js', 14 | '\\.(css|less)$': '/assetsTransformer.js', 15 | }, 16 | transform: {}, 17 | }; 18 | -------------------------------------------------------------------------------- /apps/rn-cli-app/jest.setup.js: -------------------------------------------------------------------------------- 1 | import {server} from './src/test/mocks/server'; 2 | import '@testing-library/react-native/extend-expect'; 3 | import {jest, beforeAll, beforeEach, afterEach, afterAll} from '@jest/globals'; 4 | 5 | // increasing jest timeout to 10 seconds due to slow ci env 6 | jest.setTimeout(10000); 7 | // surpressing warning resulted by useLinking due to usage of NavigationContainer 8 | jest.mock('@react-navigation/native/lib/commonjs/useLinking.native', () => ({ 9 | default: () => ({getInitialState: {then: () => null}}), 10 | __esModule: true, 11 | })); 12 | 13 | // surpressing Animated: `useNativeDriver` is not supported warning 14 | jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper'); 15 | 16 | //establish api mocking before all tests 17 | beforeAll(() => server.listen()); 18 | 19 | beforeEach(() => { 20 | global.fetch = jest.fn((...args) => { 21 | console.warn('global.fetch needs to be mocked in tests', ...args); 22 | throw new Error('global.fetch needs to be mocked in tests'); 23 | }); 24 | }); 25 | 26 | //clean up after the tests are finished 27 | afterAll(() => server.close()); 28 | 29 | afterEach(() => { 30 | global.fetch.mockRestore(); 31 | //reset any requests handlers that we may add during the tests, 32 | //so they don't affect other tests. 33 | server.resetHandlers(); 34 | }); 35 | -------------------------------------------------------------------------------- /apps/rn-cli-app/metro.config.js: -------------------------------------------------------------------------------- 1 | const {getDefaultConfig} = require('@react-native/metro-config'); 2 | const path = require('path'); 3 | 4 | // Find the project and workspace directories 5 | const projectRoot = __dirname; 6 | // This can be replaced with `find-yarn-workspace-root` 7 | const workspaceRoot = path.resolve(projectRoot, '../..'); 8 | 9 | const config = getDefaultConfig(projectRoot); 10 | 11 | // 1. Watch all files within the monorepo 12 | config.watchFolders = [workspaceRoot]; 13 | // 2. Let Metro know where to resolve packages and in what order 14 | config.resolver.nodeModulesPaths = [ 15 | path.resolve(projectRoot, 'node_modules'), 16 | path.resolve(workspaceRoot, 'node_modules'), 17 | ]; 18 | // 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths` 19 | config.resolver.disableHierarchicalLookup = true; 20 | 21 | module.exports = config; 22 | -------------------------------------------------------------------------------- /apps/rn-cli-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rn-cli-app", 3 | "version": "1.1.0", 4 | "private": true, 5 | "scripts": { 6 | "android": "npx react-native run-android", 7 | "ios": "npx react-native run-ios", 8 | "lint": "eslint .", 9 | "start": "npx react-native start", 10 | "test:unit": "jest", 11 | "test:unit:dev": "jest --watch", 12 | "test:unit:coverage": "jest --coverage", 13 | "test:e2e": "maestro test .maestro/flow.yaml", 14 | "test:e2e:dev": "maestro test -c .maestro/flow.yaml", 15 | "test:e2e:record": "maestro record .maestro/flow.yaml" 16 | }, 17 | "dependencies": { 18 | "@react-native-community/async-storage": "^1.12.1", 19 | "@react-native-community/masked-view": "^0.1.11", 20 | "@react-navigation/native": "^6.1.7", 21 | "@react-navigation/stack": "^6.3.17", 22 | "react": "18.2.0", 23 | "react-native": "0.73.1", 24 | "react-native-gesture-handler": "^2.14.0", 25 | "react-native-safe-area-context": "^4.8.2", 26 | "react-native-screens": "^3.29.0", 27 | "react-native-video": "6.0.0-alpha.7" 28 | }, 29 | "devDependencies": { 30 | "@babel/core": "^7.20.0", 31 | "@babel/preset-env": "^7.20.0", 32 | "@babel/runtime": "^7.20.0", 33 | "@react-native/babel-preset": "^0.73.18", 34 | "@react-native/eslint-config": "^0.73.1", 35 | "@react-native/metro-config": "^0.73.2", 36 | "@react-native/typescript-config": "^0.73.1", 37 | "@testing-library/react-native": "^12.4.3", 38 | "@types/jest": "^29.5.4", 39 | "@types/react": "^18.2.6", 40 | "@types/react-native-video": "^5.0.15", 41 | "@types/react-test-renderer": "^18.0.0", 42 | "axios": "^1.6.0", 43 | "babel-jest": "^29.6.3", 44 | "eslint": "^8.19.0", 45 | "jest": "^29.7.0", 46 | "msw": "^1.3.0", 47 | "prettier": "^2.8.8", 48 | "react-test-renderer": "18.2.0", 49 | "typescript": "5.3.3" 50 | }, 51 | "engines": { 52 | "node": ">=18" 53 | }, 54 | "installConfig": { 55 | "hoistingLimits": "workspaces" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /apps/rn-cli-app/src/components/Counter.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {Pressable, StyleSheet, Text, View} from 'react-native'; 3 | import {Colors} from 'react-native/Libraries/NewAppScreen'; 4 | 5 | export default () => { 6 | const [count, setCount] = useState(0); 7 | const increment = () => setCount(c => c + 1); 8 | const decrement = () => setCount(c => c - 1); 9 | 10 | return ( 11 | 12 | 13 | Current count: {count} 14 | 15 | Decrement 16 | 17 | 18 | Increment 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | const styles = StyleSheet.create({ 26 | body: { 27 | backgroundColor: Colors.white, 28 | }, 29 | sectionContainer: { 30 | padding: 24, 31 | alignItems: 'center', 32 | }, 33 | sectionTitle: { 34 | fontSize: 24, 35 | fontWeight: '600', 36 | color: Colors.black, 37 | }, 38 | button: { 39 | borderRadius: 12, 40 | padding: 6, 41 | margin: 6, 42 | backgroundColor: '#9e9ef8', 43 | justifyContent: 'center', 44 | alignItems: 'center', 45 | }, 46 | }); 47 | -------------------------------------------------------------------------------- /apps/rn-cli-app/src/components/CounterUsesCustomHook.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, Text, Pressable, View} from 'react-native'; 3 | import {Colors} from 'react-native/Libraries/NewAppScreen'; 4 | import useCounter from '../hooks/useCounter'; 5 | 6 | export default () => { 7 | const {count, increment, decrement} = useCounter(); 8 | 9 | return ( 10 | 11 | 12 | Current count: {count} 13 | 14 | Decrement 15 | 16 | 17 | Increment 18 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | const styles = StyleSheet.create({ 25 | body: { 26 | backgroundColor: Colors.white, 27 | }, 28 | sectionContainer: { 29 | padding: 24, 30 | alignItems: 'center', 31 | }, 32 | sectionTitle: { 33 | fontSize: 24, 34 | fontWeight: '600', 35 | color: Colors.black, 36 | }, 37 | button: { 38 | borderRadius: 12, 39 | padding: 6, 40 | margin: 6, 41 | backgroundColor: '#9e9ef8', 42 | justifyContent: 'center', 43 | alignItems: 'center', 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /apps/rn-cli-app/src/components/EasyButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Pressable, StyleSheet, Text} from 'react-native'; 3 | import {useTheme} from '../utils/theme'; 4 | 5 | export default (props: any) => { 6 | const {theme} = useTheme(); 7 | const {backgroundColor, color} = styles[theme]; 8 | 9 | return ( 10 | 14 | {props.children || 'Click me!'} 15 | 16 | ); 17 | }; 18 | 19 | const styles = StyleSheet.create({ 20 | pressable: { 21 | padding: 8, 22 | }, 23 | dark: { 24 | backgroundColor: 'black', 25 | color: 'white', 26 | }, 27 | light: { 28 | color: 'black', 29 | backgroundColor: 'white', 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /apps/rn-cli-app/src/components/FlatList.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {FlatList, SafeAreaView, StyleSheet, Text, View} from 'react-native'; 3 | 4 | const DATA = [ 5 | 'Pizza', 6 | 'Burger', 7 | 'Risotto', 8 | 'French Fries', 9 | 'Onion Rings', 10 | 'Fried Shrimps', 11 | 'Water', 12 | 'Coke', 13 | 'Beer', 14 | 'Cheese Cake', 15 | ]; 16 | 17 | const EXTRA_DATA = [ 18 | 'Pancakes', 19 | 'The Impossible Burger', 20 | 'Fanta', 21 | 'French Omelette', 22 | 'Onion Fries', 23 | 'Nep Shrimps', 24 | 'Soda', 25 | 'Cheesy Mushroom', 26 | ]; 27 | 28 | const NETWORK_DELAY = 1000; 29 | 30 | const Item = ({title}: {title: string}) => ( 31 | 32 | {title} 33 | 34 | ); 35 | 36 | export default () => { 37 | const [refreshing, setRefreshing] = useState(false); 38 | const [data, setData] = useState(DATA); 39 | const [loadingMore, setLoadingMore] = useState(false); 40 | const onRefresh = () => { 41 | setRefreshing(true); 42 | setData([]); 43 | setTimeout(() => { 44 | setRefreshing(false); 45 | setData(DATA); 46 | }, NETWORK_DELAY); 47 | }; 48 | 49 | const onEndReached = () => { 50 | if (data.length > 15) { 51 | return null; 52 | } 53 | setLoadingMore(true); 54 | setTimeout(() => { 55 | setData([...data, ...EXTRA_DATA]); 56 | setLoadingMore(false); 57 | }, NETWORK_DELAY); 58 | }; 59 | 60 | return ( 61 | 62 | item + index} 66 | renderItem={({item}) => } 67 | onEndReachedThreshold={0.2} 68 | onEndReached={onEndReached} 69 | onRefresh={onRefresh} 70 | progressViewOffset={100} 71 | refreshing={refreshing} 72 | /> 73 | {refreshing && Refreshing...} 74 | 75 | 76 | ); 77 | }; 78 | 79 | const LoadingMore = ({isEnabled}: {isEnabled: boolean}) => { 80 | if (!isEnabled) { 81 | return null; 82 | } 83 | 84 | return ( 85 | 86 | Loading More Dishes... 87 | 88 | ); 89 | }; 90 | 91 | const styles = StyleSheet.create({ 92 | container: { 93 | flex: 1, 94 | marginHorizontal: 16, 95 | }, 96 | item: { 97 | backgroundColor: '#f9c2ff', 98 | padding: 20, 99 | marginVertical: 8, 100 | }, 101 | header: { 102 | fontSize: 32, 103 | backgroundColor: '#fff', 104 | }, 105 | title: { 106 | fontSize: 24, 107 | }, 108 | loadingMoreContainer: {paddingVertical: 12}, 109 | }); 110 | -------------------------------------------------------------------------------- /apps/rn-cli-app/src/components/Home.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Pressable, 4 | SafeAreaView, 5 | ScrollView, 6 | StatusBar, 7 | StyleSheet, 8 | Text, 9 | View, 10 | } from 'react-native'; 11 | import {Colors} from 'react-native/Libraries/NewAppScreen'; 12 | import {NavigationProps, SCREENS} from '../../App'; 13 | import {useNavigation} from '@react-navigation/native'; 14 | 15 | export default () => { 16 | const {navigate} = useNavigation(); 17 | 18 | return ( 19 | 20 | 21 | 22 | 25 | 26 | Go to component... 27 | 28 | {Object.keys(SCREENS).map((key, i) => { 29 | const screenName = SCREENS[key]; 30 | if (screenName === SCREENS.HOME) { 31 | return null; 32 | } 33 | 34 | return ( 35 | navigate(screenName)}> 39 | {screenName} 40 | 41 | ); 42 | })} 43 | 44 | 45 | 46 | 47 | 48 | ); 49 | }; 50 | 51 | const styles = StyleSheet.create({ 52 | flex1: { 53 | flex: 1, 54 | }, 55 | body: { 56 | backgroundColor: Colors.white, 57 | ...StyleSheet.absoluteFillObject, 58 | }, 59 | innerScrollView: { 60 | flex: 1, 61 | alignItems: 'center', 62 | justifyContent: 'center', 63 | padding: 16, 64 | }, 65 | sectionContainer: { 66 | padding: 24, 67 | alignItems: 'center', 68 | }, 69 | sectionTitle: { 70 | fontSize: 24, 71 | fontWeight: '600', 72 | color: Colors.black, 73 | }, 74 | button: { 75 | borderRadius: 8, 76 | padding: 6, 77 | margin: 6, 78 | backgroundColor: '#9ef8d4', 79 | justifyContent: 'center', 80 | alignItems: 'center', 81 | }, 82 | }); 83 | -------------------------------------------------------------------------------- /apps/rn-cli-app/src/components/ListWithFetch.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback, useEffect, useState} from 'react'; 2 | import { 3 | ActivityIndicator, 4 | FlatList, 5 | Image, 6 | StyleSheet, 7 | Text, 8 | View, 9 | } from 'react-native'; 10 | import axios from 'axios'; 11 | 12 | const AVATAR_SIZE = 68; 13 | 14 | export interface IUser { 15 | firstName: string; 16 | lastName: string; 17 | email: string; 18 | id: string; 19 | image: string; 20 | birthDate: string; 21 | } 22 | 23 | export default () => { 24 | const [usersData, setUsersData] = useState([]); 25 | const [loading, setLoading] = useState(false); 26 | const [hasError, setHasError] = useState(false); 27 | useEffect(() => { 28 | const fetchData = async () => { 29 | setLoading(true); 30 | try { 31 | const response = await axios.get('https://dummyjson.com/users'); 32 | setUsersData(response.data.users); 33 | } catch (e) { 34 | setHasError(true); 35 | } finally { 36 | setLoading(false); 37 | } 38 | }; 39 | 40 | fetchData(); 41 | }, []); 42 | 43 | const handleRenderItem = useCallback( 44 | ({ 45 | item: {firstName, lastName, email, image, id, birthDate}, 46 | }: { 47 | item: IUser; 48 | }) => ( 49 | 52 | 53 | 54 | 55 | 56 | 57 | {firstName} {lastName} 58 | 59 | {email} 60 | {birthDate} 61 | 62 | 63 | ), 64 | [], 65 | ); 66 | 67 | return ( 68 | 69 | The Funky Users DB 70 | {loading && ( 71 | 76 | )} 77 | {hasError && ( 78 | 79 | Error oopsie! 80 | 81 | )} 82 | item.id} 86 | /> 87 | 88 | ); 89 | }; 90 | 91 | const styles = StyleSheet.create({ 92 | errorContainer: {backgroundColor: '#C63939', padding: 16, borderRadius: 6}, 93 | userContainer: { 94 | alignItems: 'center', 95 | flexDirection: 'row', 96 | padding: 16, 97 | marginBottom: 8, 98 | flex: 1, 99 | }, 100 | avatarWrapper: { 101 | backgroundColor: 'rgba(88,186,224,0.65)', 102 | padding: 16, 103 | borderRadius: AVATAR_SIZE, 104 | }, 105 | userInfoContainer: {flex: 1, marginLeft: 16}, 106 | image: {height: AVATAR_SIZE, width: AVATAR_SIZE}, 107 | }); 108 | -------------------------------------------------------------------------------- /apps/rn-cli-app/src/components/Login.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback, useState} from 'react'; 2 | import {Pressable, StyleSheet, Text, TextInput, View} from 'react-native'; 3 | 4 | export default ({ 5 | onSubmit, 6 | }: { 7 | onSubmit(data: {username: string; password: string}): void; 8 | }) => { 9 | const [username, setUsername] = useState(''); 10 | const [password, setPassword] = useState(''); 11 | const handleSubmit = useCallback(() => { 12 | setUsername(''); 13 | setPassword(''); 14 | onSubmit({username, password}); 15 | }, [onSubmit, password, username]); 16 | 17 | return ( 18 | 19 | 20 | setUsername(text)} 23 | value={username} 24 | /> 25 | setPassword(text)} 29 | value={password} 30 | /> 31 | 32 | Submit 33 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | const styles = StyleSheet.create({ 40 | body: { 41 | backgroundColor: '#fff', 42 | }, 43 | sectionContainer: { 44 | padding: 24, 45 | alignItems: 'center', 46 | }, 47 | button: { 48 | borderRadius: 12, 49 | padding: 6, 50 | margin: 6, 51 | backgroundColor: '#9e9ef8', 52 | justifyContent: 'center', 53 | alignItems: 'center', 54 | }, 55 | }); 56 | -------------------------------------------------------------------------------- /apps/rn-cli-app/src/components/LoginSubmission.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useReducer, useState} from 'react'; 2 | import {ActivityIndicator, Text, View} from 'react-native'; 3 | import Login from './Login'; 4 | import {useNavigation} from '@react-navigation/native'; 5 | import AsyncStorage from '@react-native-community/async-storage'; 6 | import {NavigationProps} from '../../App'; 7 | 8 | const ENDPOINT_URL = 9 | 'https://e2c168f9-97f3-42e1-8b31-57f4ab52a3bc.mock.pstmn.io/api/login'; 10 | // @ts-ignore 11 | const formSubmissionReducer = (state, action) => { 12 | switch (action.type) { 13 | case 'START': { 14 | return {status: 'pending', responseData: null, errorMessage: null}; 15 | } 16 | case 'RESOLVE': { 17 | return { 18 | status: 'resolved', 19 | responseData: action.responseData, 20 | errorMessage: null, 21 | }; 22 | } 23 | case 'REJECT': { 24 | return { 25 | status: 'rejected', 26 | responseData: null, 27 | errorMessage: action.error.message, 28 | }; 29 | } 30 | default: 31 | throw new Error(`Unsupported type: ${action.type}`); 32 | } 33 | }; 34 | 35 | // @ts-ignore 36 | const useFormSubmission = ({endpoint, data}) => { 37 | const [state, dispatch] = useReducer(formSubmissionReducer, { 38 | status: 'idle', 39 | responseData: null, 40 | errorMessage: null, 41 | }); 42 | 43 | const fetchBody = data ? JSON.stringify(data) : null; 44 | 45 | useEffect(() => { 46 | const fetchData = async () => { 47 | if (fetchBody) { 48 | dispatch({type: 'START'}); 49 | try { 50 | const response = await fetch(endpoint, { 51 | method: 'POST', 52 | body: fetchBody, 53 | headers: { 54 | 'content-type': 'application/json', 55 | }, 56 | }); 57 | const responseData = await response.json(); 58 | // add a delay to simulate network latency 59 | setTimeout(() => dispatch({type: 'RESOLVE', responseData}), 2000); 60 | } catch (error) { 61 | dispatch({type: 'REJECT', error}); 62 | } 63 | } 64 | }; 65 | fetchData(); 66 | }, [fetchBody, endpoint]); 67 | 68 | return state; 69 | }; 70 | 71 | const Spinner = () => { 72 | return ( 73 | 74 | 75 | 76 | ); 77 | }; 78 | 79 | export default () => { 80 | const {navigate} = useNavigation(); 81 | const [formData, setFormData] = useState< 82 | | { 83 | username: string; 84 | password: string; 85 | } 86 | | undefined 87 | >(); 88 | const {status, responseData, errorMessage} = useFormSubmission({ 89 | endpoint: ENDPOINT_URL, 90 | data: formData, 91 | }); 92 | const token = responseData?.token; 93 | 94 | useEffect(() => { 95 | if (status === 'resolved') { 96 | navigate('Home'); 97 | } 98 | const setAndNavigate = async () => { 99 | if (!token) { 100 | return; 101 | } 102 | 103 | await AsyncStorage.setItem('token', token); 104 | }; 105 | setAndNavigate(); 106 | }, [token, navigate, status]); 107 | 108 | return ( 109 | <> 110 | setFormData(data)} /> 111 | {status === 'pending' ? : null} 112 | {errorMessage} 113 | 114 | ); 115 | }; 116 | -------------------------------------------------------------------------------- /apps/rn-cli-app/src/components/Modal.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {Modal, Pressable, StyleSheet, Text, View} from 'react-native'; 3 | 4 | export default () => { 5 | const [modalVisible, setModalVisible] = useState(false); 6 | return ( 7 | 8 | setModalVisible(false)}> 13 | 14 | 15 | Hello World! 16 | { 19 | setModalVisible(!modalVisible); 20 | }}> 21 | Hide Modal 22 | 23 | 24 | 25 | 26 | 27 | { 30 | setModalVisible(true); 31 | }}> 32 | Show Modal 33 | 34 | 35 | ); 36 | }; 37 | 38 | const styles = StyleSheet.create({ 39 | centeredView: { 40 | flex: 1, 41 | justifyContent: 'center', 42 | alignItems: 'center', 43 | marginTop: 22, 44 | }, 45 | modalView: { 46 | margin: 20, 47 | backgroundColor: 'white', 48 | borderRadius: 20, 49 | padding: 35, 50 | alignItems: 'center', 51 | shadowColor: '#000', 52 | shadowOffset: { 53 | width: 0, 54 | height: 2, 55 | }, 56 | shadowOpacity: 0.25, 57 | shadowRadius: 3.84, 58 | elevation: 5, 59 | }, 60 | openButton: { 61 | borderRadius: 8, 62 | padding: 6, 63 | margin: 6, 64 | backgroundColor: '#9ef8d4', 65 | justifyContent: 'center', 66 | alignItems: 'center', 67 | }, 68 | textStyle: { 69 | color: 'white', 70 | fontWeight: 'bold', 71 | textAlign: 'center', 72 | }, 73 | modalText: { 74 | marginBottom: 15, 75 | textAlign: 'center', 76 | }, 77 | specialBGColor: { 78 | backgroundColor: '#2196F3', 79 | }, 80 | }); 81 | -------------------------------------------------------------------------------- /apps/rn-cli-app/src/components/Video.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback, useEffect, useState} from 'react'; 2 | import {Pressable, StatusBar, StyleSheet, Text, View} from 'react-native'; 3 | import {Colors} from 'react-native/Libraries/NewAppScreen'; 4 | import Video from 'react-native-video'; 5 | import {useNavigation} from '@react-navigation/native'; 6 | import {NavigationProps} from '../../App'; 7 | 8 | const SOME_VIDEO = 9 | 'https://d192a4z5wljn2.cloudfront.net/ervNPPeH/hoookedup/5938/TvtX4P6AYH.mp4'; 10 | 11 | export default () => { 12 | const {setOptions} = useNavigation(); 13 | const [isPlaying, setIsPlaying] = useState(true); 14 | const [isFullScreen, setIsFullScreen] = useState(false); 15 | 16 | useEffect(() => { 17 | setOptions({headerShown: !isFullScreen}); 18 | }, [isFullScreen, setOptions]); 19 | 20 | const showFullScreen = () => setIsFullScreen(true); 21 | const exitFullScreen = () => setIsFullScreen(false); 22 | 23 | const togglePause = useCallback(() => setIsPlaying(!isPlaying), [isPlaying]); 24 | 25 | const resetMediaState = useCallback(() => { 26 | setIsPlaying(false); 27 | setIsFullScreen(false); 28 | }, []); 29 | 30 | useEffect(() => { 31 | return resetMediaState; 32 | }, [resetMediaState]); 33 | 34 | return ( 35 | 36 | 37 | 38 | Full screen 39 | 40 | 41 | Pause/Start 42 | 43 | 44 | <> 45 | 67 | ); 68 | }; 69 | 70 | const styles = StyleSheet.create({ 71 | body: { 72 | backgroundColor: Colors.white, 73 | alignItems: 'center', 74 | flex: 1, 75 | }, 76 | sectionContainer: { 77 | padding: 24, 78 | alignItems: 'center', 79 | }, 80 | sectionTitle: { 81 | fontSize: 24, 82 | fontWeight: '600', 83 | color: Colors.black, 84 | }, 85 | button: { 86 | borderRadius: 12, 87 | padding: 6, 88 | margin: 6, 89 | backgroundColor: '#9e9ef8', 90 | justifyContent: 'center', 91 | alignItems: 'center', 92 | }, 93 | video: { 94 | width: 200, 95 | height: 200, 96 | }, 97 | videoFullScreen: { 98 | width: '100%', 99 | height: 200, 100 | zIndex: 5, 101 | }, 102 | fullScreenBG: { 103 | backgroundColor: Colors.black, 104 | ...StyleSheet.absoluteFillObject, 105 | }, 106 | }); 107 | -------------------------------------------------------------------------------- /apps/rn-cli-app/src/hooks/useCounter.ts: -------------------------------------------------------------------------------- 1 | import {useState} from 'react'; 2 | 3 | export interface IUseCounterProps { 4 | initialCount?: number; 5 | step?: number; 6 | } 7 | 8 | export interface IUseCounterResult { 9 | count: number; 10 | increment(): void; 11 | decrement(): void; 12 | } 13 | export default ({ 14 | initialCount = 0, 15 | step = 1, 16 | }: IUseCounterProps = {}): IUseCounterResult => { 17 | const [count, setCount] = useState(initialCount); 18 | const increment = () => setCount(c => c + step); 19 | const decrement = () => setCount(c => c - step); 20 | return {count, increment, decrement}; 21 | }; 22 | -------------------------------------------------------------------------------- /apps/rn-cli-app/src/test/mocks/handlers.ts: -------------------------------------------------------------------------------- 1 | import {rest} from 'msw'; 2 | import mockedApiResponse from './mockedApiResponse.json'; 3 | export const handlers = [ 4 | rest.get('https://dummyjson.com/users', (req, res, ctx) => { 5 | return res(ctx.json(mockedApiResponse)); 6 | }), 7 | ]; 8 | -------------------------------------------------------------------------------- /apps/rn-cli-app/src/test/mocks/server.ts: -------------------------------------------------------------------------------- 1 | import {setupServer} from 'msw/node'; 2 | import {handlers} from './handlers'; 3 | 4 | export const server = setupServer(...handlers); 5 | -------------------------------------------------------------------------------- /apps/rn-cli-app/src/test/test-utils.tsx: -------------------------------------------------------------------------------- 1 | import React, {ComponentType} from 'react'; 2 | import {render as rtlRender} from '@testing-library/react-native'; 3 | import type {ThemeType} from '../utils/theme'; 4 | import {ThemeProvider} from '../utils/theme'; 5 | import {useNavigation} from '@react-navigation/native'; 6 | 7 | const render = (ui: any, {theme = 'light', ...options} = {}) => { 8 | // @ts-ignore 9 | const Wrapper = ({children}): ComponentType => ( 10 | {children} 11 | ); 12 | // @ts-ignore 13 | return rtlRender(ui, {wrapper: Wrapper, ...options}); 14 | }; 15 | 16 | export * from '@testing-library/react-native'; 17 | // override React Testing Library's render with our own 18 | export {render}; 19 | 20 | export const useNavigationMock = useNavigation as jest.MockedFunction< 21 | typeof useNavigation 22 | >; 23 | -------------------------------------------------------------------------------- /apps/rn-cli-app/src/utils/theme.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | createContext, 3 | Dispatch, 4 | SetStateAction, 5 | useContext, 6 | useState, 7 | } from 'react'; 8 | 9 | export type ThemeType = 'dark' | 'light'; 10 | type ThemeContextType = { 11 | theme: ThemeType; 12 | setTheme: Dispatch>; 13 | }; 14 | const ThemeContext = createContext(null); 15 | 16 | const useTheme = () => { 17 | const context = useContext(ThemeContext); 18 | if (!context) { 19 | throw new Error('useTheme should be used within a ThemeProvider'); 20 | } 21 | return context; 22 | }; 23 | 24 | const ThemeProvider = ({ 25 | initialTheme = 'light', 26 | ...props 27 | }: { 28 | initialTheme: ThemeType; 29 | children: React.ReactNode; 30 | }): JSX.Element => { 31 | const [theme, setTheme] = useState(initialTheme); 32 | return ; 33 | }; 34 | 35 | export {useTheme, ThemeProvider}; 36 | -------------------------------------------------------------------------------- /apps/rn-cli-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@react-native/typescript-config/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /apps/rn-expo-app/.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | /.expo 3 | 4 | node_modules 5 | -------------------------------------------------------------------------------- /apps/rn-expo-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['universe/native'], 4 | env: { 5 | node: true, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /apps/rn-expo-app/.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | 11 | # Native 12 | *.orig.* 13 | *.jks 14 | *.p8 15 | *.p12 16 | *.key 17 | *.mobileprovision 18 | 19 | # Metro 20 | .metro-health-check* 21 | 22 | # debug 23 | npm-debug.* 24 | yarn-debug.* 25 | yarn-error.* 26 | 27 | # macOS 28 | .DS_Store 29 | *.pem 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | 37 | -------------------------------------------------------------------------------- /apps/rn-expo-app/__tests__/app/index.test.tsx: -------------------------------------------------------------------------------- 1 | import {renderRouter, userEvent, screen} from 'expo-router/testing-library'; 2 | 3 | jest.useFakeTimers(); 4 | 5 | // Temporary fix for https://github.com/expo/expo/issues/25494 6 | jest.mock('react-native-reanimated', () => null, { 7 | virtual: true, 8 | }); 9 | jest.mock('@testing-library/jest-native/extend-expect', () => null, { 10 | virtual: true, 11 | }); 12 | 13 | describe('Smoke test expo router navigation through the app', () => { 14 | it('displays home screen initially, and navigates to a specific blo screen through blogs screen', async () => { 15 | renderRouter('./app'); 16 | 17 | // validating that we're on the home screen 18 | // via the path 19 | expect(screen).toHavePathname('/'); 20 | // via the title text 21 | expect(screen.getByText(/rn media group/i)).toBeVisible(); 22 | 23 | const user = userEvent.setup(); 24 | // navigating to the blogs screen via tab bar 25 | await user.press(screen.getByText(/blogs/i)); 26 | 27 | // validating that we're in the blogs screen 28 | expect(screen).toHavePathname('/blogs'); 29 | expect(screen.getByText(/blogs page!/i)).toBeVisible(); 30 | 31 | // navigating to the blog post screen via a blog post card 32 | await user.press(screen.getByText(/blog 123/i)); 33 | 34 | // validating that we're in the blog post screen 35 | expect(screen).toHavePathnameWithParams('/blogs/123'); 36 | expect(screen.getByText(/blog post: 123/i)).toBeVisible(); 37 | }); 38 | 39 | it('displays home screen initially, and navigates to country screen through settings screen', async () => { 40 | renderRouter('./app'); 41 | 42 | // validating that we're on the home screen 43 | // via the path 44 | expect(screen).toHavePathname('/'); 45 | // via the title text 46 | expect(screen.getByText(/rn media group/i)).toBeVisible(); 47 | 48 | const user = userEvent.setup(); 49 | // navigating to the blogs screen via tab bar 50 | await user.press(screen.getByText(/settings/i)); 51 | 52 | // validating that we're in the blogs screen 53 | expect(screen).toHavePathname('/settings'); 54 | expect(screen.getByText(/settings page!/i)).toBeVisible(); 55 | 56 | // navigating to the country screen via a button 57 | await user.press(screen.getByText(/country/i)); 58 | 59 | // validating that we're in the country screen 60 | expect(screen).toHavePathname('/settings/country'); 61 | expect(screen.getByText(/country page!/i)).toBeVisible(); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Android/IntelliJ 6 | # 7 | build/ 8 | .idea 9 | .gradle 10 | local.properties 11 | *.iml 12 | *.hprof 13 | 14 | # Bundle artifacts 15 | *.jsbundle 16 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply plugin: "org.jetbrains.kotlin.android" 3 | apply plugin: "com.facebook.react" 4 | 5 | def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() 6 | 7 | /** 8 | * This is the configuration block to customize your React Native Android app. 9 | * By default you don't need to apply any configuration, just uncomment the lines you need. 10 | */ 11 | react { 12 | entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim()) 13 | reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() 14 | hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc" 15 | codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() 16 | 17 | // Use Expo CLI to bundle the app, this ensures the Metro config 18 | // works correctly with Expo projects. 19 | cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim()) 20 | bundleCommand = "export:embed" 21 | 22 | /* Folders */ 23 | // The root of your project, i.e. where "package.json" lives. Default is '..' 24 | // root = file("../") 25 | // The folder where the react-native NPM package is. Default is ../node_modules/react-native 26 | // reactNativeDir = file("../node_modules/react-native") 27 | // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen 28 | // codegenDir = file("../node_modules/@react-native/codegen") 29 | 30 | /* Variants */ 31 | // The list of variants to that are debuggable. For those we're going to 32 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'. 33 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. 34 | // debuggableVariants = ["liteDebug", "prodDebug"] 35 | 36 | /* Bundling */ 37 | // A list containing the node command and its flags. Default is just 'node'. 38 | // nodeExecutableAndArgs = ["node"] 39 | 40 | // 41 | // The path to the CLI configuration file. Default is empty. 42 | // bundleConfig = file(../rn-cli.config.js) 43 | // 44 | // The name of the generated asset file containing your JS bundle 45 | // bundleAssetName = "MyApplication.android.bundle" 46 | // 47 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' 48 | // entryFile = file("../js/MyApplication.android.js") 49 | // 50 | // A list of extra flags to pass to the 'bundle' commands. 51 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle 52 | // extraPackagerArgs = [] 53 | 54 | /* Hermes Commands */ 55 | // The hermes compiler command to run. By default it is 'hermesc' 56 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" 57 | // 58 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" 59 | // hermesFlags = ["-O", "-output-source-map"] 60 | } 61 | 62 | /** 63 | * Set this to true to Run Proguard on Release builds to minify the Java bytecode. 64 | */ 65 | def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean() 66 | 67 | /** 68 | * The preferred build flavor of JavaScriptCore (JSC) 69 | * 70 | * For example, to use the international variant, you can use: 71 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 72 | * 73 | * The international variant includes ICU i18n library and necessary data 74 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 75 | * give correct results when using with locales other than en-US. Note that 76 | * this variant is about 6MiB larger per architecture than default. 77 | */ 78 | def jscFlavor = 'org.webkit:android-jsc:+' 79 | 80 | android { 81 | ndkVersion rootProject.ext.ndkVersion 82 | 83 | buildToolsVersion rootProject.ext.buildToolsVersion 84 | compileSdk rootProject.ext.compileSdkVersion 85 | 86 | namespace 'com.stevegalili.rnexpoapp' 87 | defaultConfig { 88 | applicationId 'com.stevegalili.rnexpoapp' 89 | minSdkVersion rootProject.ext.minSdkVersion 90 | targetSdkVersion rootProject.ext.targetSdkVersion 91 | versionCode 1 92 | versionName "1.0.0" 93 | 94 | buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString()) 95 | } 96 | signingConfigs { 97 | debug { 98 | storeFile file('debug.keystore') 99 | storePassword 'android' 100 | keyAlias 'androiddebugkey' 101 | keyPassword 'android' 102 | } 103 | } 104 | buildTypes { 105 | debug { 106 | signingConfig signingConfigs.debug 107 | } 108 | release { 109 | // Caution! In production, you need to generate your own keystore file. 110 | // see https://reactnative.dev/docs/signed-apk-android. 111 | signingConfig signingConfigs.debug 112 | shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false) 113 | minifyEnabled enableProguardInReleaseBuilds 114 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 115 | } 116 | } 117 | } 118 | 119 | // Apply static values from `gradle.properties` to the `android.packagingOptions` 120 | // Accepts values in comma delimited lists, example: 121 | // android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini 122 | ["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop -> 123 | // Split option: 'foo,bar' -> ['foo', 'bar'] 124 | def options = (findProperty("android.packagingOptions.$prop") ?: "").split(","); 125 | // Trim all elements in place. 126 | for (i in 0.. 0) { 131 | println "android.packagingOptions.$prop += $options ($options.length)" 132 | // Ex: android.packagingOptions.pickFirsts += '**/SCCS/**' 133 | options.each { 134 | android.packagingOptions[prop] += it 135 | } 136 | } 137 | } 138 | 139 | dependencies { 140 | // The version of react-native is set by the React Native Gradle Plugin 141 | implementation("com.facebook.react:react-android") 142 | 143 | def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; 144 | def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; 145 | def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true"; 146 | 147 | if (isGifEnabled) { 148 | // For animated gif support 149 | implementation("com.facebook.fresco:animated-gif:${reactAndroidLibs.versions.fresco.get()}") 150 | } 151 | 152 | if (isWebpEnabled) { 153 | // For webp support 154 | implementation("com.facebook.fresco:webpsupport:${reactAndroidLibs.versions.fresco.get()}") 155 | if (isWebpAnimatedEnabled) { 156 | // Animated webp support 157 | implementation("com.facebook.fresco:animated-webp:${reactAndroidLibs.versions.fresco.get()}") 158 | } 159 | } 160 | 161 | implementation("com.facebook.react:flipper-integration") 162 | 163 | if (hermesEnabled.toBoolean()) { 164 | implementation("com.facebook.react:hermes-android") 165 | } else { 166 | implementation jscFlavor 167 | } 168 | } 169 | 170 | apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); 171 | applyNativeModulesAppBuildGradle(project) 172 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/debug.keystore -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # react-native-reanimated 11 | -keep class com.swmansion.reanimated.** { *; } 12 | -keep class com.facebook.react.turbomodule.** { *; } 13 | 14 | # Add any project specific keep options here: 15 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/java/com/stevegalili/rnexpoapp/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.stevegalili.rnexpoapp 2 | 3 | import android.os.Build 4 | import android.os.Bundle 5 | 6 | import com.facebook.react.ReactActivity 7 | import com.facebook.react.ReactActivityDelegate 8 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled 9 | import com.facebook.react.defaults.DefaultReactActivityDelegate 10 | 11 | import expo.modules.ReactActivityDelegateWrapper 12 | 13 | class MainActivity : ReactActivity() { 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | // Set the theme to AppTheme BEFORE onCreate to support 16 | // coloring the background, status bar, and navigation bar. 17 | // This is required for expo-splash-screen. 18 | setTheme(R.style.AppTheme); 19 | super.onCreate(null) 20 | } 21 | 22 | /** 23 | * Returns the name of the main component registered from JavaScript. This is used to schedule 24 | * rendering of the component. 25 | */ 26 | override fun getMainComponentName(): String = "main" 27 | 28 | /** 29 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] 30 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] 31 | */ 32 | override fun createReactActivityDelegate(): ReactActivityDelegate { 33 | return ReactActivityDelegateWrapper( 34 | this, 35 | BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, 36 | object : DefaultReactActivityDelegate( 37 | this, 38 | mainComponentName, 39 | fabricEnabled 40 | ){}) 41 | } 42 | 43 | /** 44 | * Align the back button behavior with Android S 45 | * where moving root activities to background instead of finishing activities. 46 | * @see onBackPressed 47 | */ 48 | override fun invokeDefaultOnBackPressed() { 49 | if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { 50 | if (!moveTaskToBack(false)) { 51 | // For non-root activities, use the default implementation to finish them. 52 | super.invokeDefaultOnBackPressed() 53 | } 54 | return 55 | } 56 | 57 | // Use the default back button implementation on Android S 58 | // because it's doing more than [Activity.moveTaskToBack] in fact. 59 | super.invokeDefaultOnBackPressed() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/java/com/stevegalili/rnexpoapp/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.stevegalili.rnexpoapp 2 | 3 | import android.app.Application 4 | import android.content.res.Configuration 5 | import androidx.annotation.NonNull 6 | 7 | import com.facebook.react.PackageList 8 | import com.facebook.react.ReactApplication 9 | import com.facebook.react.ReactNativeHost 10 | import com.facebook.react.ReactPackage 11 | import com.facebook.react.ReactHost 12 | import com.facebook.react.config.ReactFeatureFlags 13 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load 14 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost 15 | import com.facebook.react.defaults.DefaultReactNativeHost 16 | import com.facebook.react.flipper.ReactNativeFlipper 17 | import com.facebook.soloader.SoLoader 18 | 19 | import expo.modules.ApplicationLifecycleDispatcher 20 | import expo.modules.ReactNativeHostWrapper 21 | 22 | class MainApplication : Application(), ReactApplication { 23 | 24 | override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper( 25 | this, 26 | object : DefaultReactNativeHost(this) { 27 | override fun getPackages(): List { 28 | // Packages that cannot be autolinked yet can be added manually here, for example: 29 | // packages.add(new MyReactNativePackage()); 30 | return PackageList(this).packages 31 | } 32 | 33 | override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry" 34 | 35 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG 36 | 37 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 38 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED 39 | } 40 | ) 41 | 42 | override val reactHost: ReactHost 43 | get() = getDefaultReactHost(this.applicationContext, reactNativeHost) 44 | 45 | override fun onCreate() { 46 | super.onCreate() 47 | SoLoader.init(this, false) 48 | if (!BuildConfig.REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS) { 49 | ReactFeatureFlags.unstable_useRuntimeSchedulerAlways = false 50 | } 51 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 52 | // If you opted-in for the New Architecture, we load the native entry point for this app. 53 | load() 54 | } 55 | if (BuildConfig.DEBUG) { 56 | ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager) 57 | } 58 | ApplicationLifecycleDispatcher.onApplicationCreate(this) 59 | } 60 | 61 | override fun onConfigurationChanged(newConfig: Configuration) { 62 | super.onConfigurationChanged(newConfig) 63 | ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/drawable-hdpi/splashscreen_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/drawable-hdpi/splashscreen_image.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/drawable-mdpi/splashscreen_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/drawable-mdpi/splashscreen_image.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/drawable-xhdpi/splashscreen_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/drawable-xhdpi/splashscreen_image.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/drawable-xxhdpi/splashscreen_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/drawable-xxhdpi/splashscreen_image.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/drawable-xxxhdpi/splashscreen_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/drawable-xxxhdpi/splashscreen_image.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 21 | 22 | 23 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/drawable/splashscreen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff 3 | #ffffff 4 | #023c69 5 | #ffffff 6 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | rn-expo-app 3 | contain 4 | false 5 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 14 | 17 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = findProperty('android.buildToolsVersion') ?: '34.0.0' 6 | minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '23') 7 | compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '34') 8 | targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '34') 9 | kotlinVersion = findProperty('android.kotlinVersion') ?: '1.8.10' 10 | 11 | ndkVersion = "25.1.8937393" 12 | reactNativeVersion = "0.73.2" // https://github.com/expo/expo/issues/18129 13 | } 14 | repositories { 15 | google() 16 | mavenCentral() 17 | } 18 | dependencies { 19 | classpath('com.android.tools.build:gradle') 20 | classpath('com.facebook.react:react-native-gradle-plugin') 21 | } 22 | } 23 | 24 | apply plugin: "com.facebook.react.rootproject" 25 | 26 | allprojects { 27 | repositories { 28 | maven { 29 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 30 | url(new File(['node', '--print', "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), '../android')) 31 | } 32 | maven { 33 | // Android JSC is installed from npm 34 | url(new File(['node', '--print', "require.resolve('jsc-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), '../dist')) 35 | } 36 | 37 | google() 38 | mavenCentral() 39 | maven { url 'https://www.jitpack.io' } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | 25 | # Automatically convert third-party libraries to use AndroidX 26 | android.enableJetifier=true 27 | 28 | # Use this property to specify which architecture you want to build. 29 | # You can also override it from the CLI using 30 | # ./gradlew -PreactNativeArchitectures=x86_64 31 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 32 | 33 | # Use this property to enable support to the new architecture. 34 | # This will allow you to use TurboModules and the Fabric render in 35 | # your application. You should enable this flag either if you want 36 | # to write custom TurboModules/Fabric components OR use libraries that 37 | # are providing them. 38 | newArchEnabled=false 39 | 40 | # Use this property to enable or disable the Hermes JS engine. 41 | # If set to false, you will be using JSC instead. 42 | hermesEnabled=true 43 | 44 | # Enable GIF support in React Native images (~200 B increase) 45 | expo.gif.enabled=true 46 | # Enable webp support in React Native images (~85 KB increase) 47 | expo.webp.enabled=true 48 | # Enable animated webp support (~3.4 MB increase) 49 | # Disabled by default because iOS doesn't support animated webp 50 | expo.webp.animated=false 51 | 52 | # Enable network inspector 53 | EX_DEV_CLIENT_NETWORK_INSPECTOR=true 54 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /apps/rn-expo-app/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if %ERRORLEVEL% equ 0 goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if %ERRORLEVEL% equ 0 goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega 92 | -------------------------------------------------------------------------------- /apps/rn-expo-app/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'rn-expo-app' 2 | 3 | dependencyResolutionManagement { 4 | versionCatalogs { 5 | reactAndroidLibs { 6 | from(files(new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../gradle/libs.versions.toml"))) 7 | } 8 | } 9 | } 10 | 11 | apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle"); 12 | useExpoModules() 13 | 14 | apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); 15 | applyNativeModulesSettingsGradle(settings) 16 | 17 | include ':app' 18 | includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile()) 19 | -------------------------------------------------------------------------------- /apps/rn-expo-app/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "rn-expo-app", 4 | "slug": "rn-expo-app", 5 | "scheme": "rn-expo-app", 6 | "version": "1.0.0", 7 | "orientation": "portrait", 8 | "icon": "./assets/icon.png", 9 | "userInterfaceStyle": "light", 10 | "splash": { 11 | "image": "./assets/splash.png", 12 | "resizeMode": "contain", 13 | "backgroundColor": "#ffffff" 14 | }, 15 | "assetBundlePatterns": [ 16 | "**/*" 17 | ], 18 | "ios": { 19 | "supportsTablet": true, 20 | "bundleIdentifier": "com.stevegalili.rnexpoapp" 21 | }, 22 | "android": { 23 | "adaptiveIcon": { 24 | "foregroundImage": "./assets/adaptive-icon.png", 25 | "backgroundColor": "#ffffff" 26 | }, 27 | "package": "com.stevegalili.rnexpoapp" 28 | }, 29 | "web": { 30 | "favicon": "./assets/favicon.png" 31 | }, 32 | "plugins": [ 33 | "expo-router" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /apps/rn-expo-app/app/_layout.tsx: -------------------------------------------------------------------------------- 1 | import {Tabs} from 'expo-router'; 2 | 3 | export default function Layout() { 4 | return ( 5 | 6 | 13 | 20 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /apps/rn-expo-app/app/blogs/[slug].tsx: -------------------------------------------------------------------------------- 1 | import {useLocalSearchParams} from 'expo-router'; 2 | import {Text} from 'react-native'; 3 | 4 | export default function Page() { 5 | const {slug} = useLocalSearchParams(); 6 | 7 | return Blog post: {slug}; 8 | } 9 | -------------------------------------------------------------------------------- /apps/rn-expo-app/app/blogs/_layout.tsx: -------------------------------------------------------------------------------- 1 | import {Stack} from 'expo-router'; 2 | 3 | export default function Layout() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /apps/rn-expo-app/app/blogs/index.tsx: -------------------------------------------------------------------------------- 1 | import {Link} from 'expo-router'; 2 | import {Text} from 'react-native'; 3 | 4 | export default function Page() { 5 | return ( 6 | <> 7 | Blogs page! 8 | Blog 123 9 | Blog 345 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /apps/rn-expo-app/app/index.tsx: -------------------------------------------------------------------------------- 1 | import {Text} from 'react-native'; 2 | 3 | export default function Page() { 4 | return ( 5 | <> 6 | RN Media Group 7 | Trending 8 | For you 9 | Following 10 | Listen 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /apps/rn-expo-app/app/settings/_layout.tsx: -------------------------------------------------------------------------------- 1 | import {Stack} from 'expo-router'; 2 | 3 | export default function Layout() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /apps/rn-expo-app/app/settings/country.tsx: -------------------------------------------------------------------------------- 1 | import {Text} from 'react-native'; 2 | 3 | export default function Page() { 4 | return ( 5 | <> 6 | Country page! 7 | 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /apps/rn-expo-app/app/settings/index.tsx: -------------------------------------------------------------------------------- 1 | import {Link} from 'expo-router'; 2 | import {Text} from 'react-native'; 3 | 4 | export default function Page() { 5 | return ( 6 | <> 7 | Settings page! 8 | Country 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /apps/rn-expo-app/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/assets/adaptive-icon.png -------------------------------------------------------------------------------- /apps/rn-expo-app/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/assets/favicon.png -------------------------------------------------------------------------------- /apps/rn-expo-app/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/assets/icon.png -------------------------------------------------------------------------------- /apps/rn-expo-app/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/assets/splash.png -------------------------------------------------------------------------------- /apps/rn-expo-app/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | .xcode.env.local 25 | 26 | # Bundle artifacts 27 | *.jsbundle 28 | 29 | # CocoaPods 30 | /Pods/ 31 | -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/Podfile: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") 2 | require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods") 3 | 4 | require 'json' 5 | podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {} 6 | 7 | ENV['RCT_NEW_ARCH_ENABLED'] = podfile_properties['newArchEnabled'] == 'true' ? '1' : '0' 8 | ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] = podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR'] 9 | 10 | platform :ios, podfile_properties['ios.deploymentTarget'] || '13.4' 11 | install! 'cocoapods', 12 | :deterministic_uuids => false 13 | 14 | prepare_react_native_project! 15 | 16 | # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. 17 | # because `react-native-flipper` depends on (FlipperKit,...), which will be excluded. To fix this, 18 | # you can also exclude `react-native-flipper` in `react-native.config.js` 19 | # 20 | # ```js 21 | # module.exports = { 22 | # dependencies: { 23 | # ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}), 24 | # } 25 | # } 26 | # ``` 27 | flipper_config = FlipperConfiguration.disabled 28 | if ENV['NO_FLIPPER'] == '1' then 29 | # Explicitly disabled through environment variables 30 | flipper_config = FlipperConfiguration.disabled 31 | elsif podfile_properties.key?('ios.flipper') then 32 | # Configure Flipper in Podfile.properties.json 33 | if podfile_properties['ios.flipper'] == 'true' then 34 | flipper_config = FlipperConfiguration.enabled(["Debug", "Release"]) 35 | elsif podfile_properties['ios.flipper'] != 'false' then 36 | flipper_config = FlipperConfiguration.enabled(["Debug", "Release"], { 'Flipper' => podfile_properties['ios.flipper'] }) 37 | end 38 | end 39 | 40 | target 'rnexpoapp' do 41 | use_expo_modules! 42 | config = use_native_modules! 43 | 44 | use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks'] 45 | use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS'] 46 | 47 | use_react_native!( 48 | :path => config[:reactNativePath], 49 | :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes', 50 | # An absolute path to your application root. 51 | :app_path => "#{Pod::Config.instance.installation_root}/..", 52 | # Note that if you have use_frameworks! enabled, Flipper will not work if enabled 53 | :flipper_configuration => flipper_config 54 | ) 55 | 56 | post_install do |installer| 57 | react_native_post_install( 58 | installer, 59 | config[:reactNativePath], 60 | :mac_catalyst_enabled => false 61 | ) 62 | 63 | # This is necessary for Xcode 14, because it signs resource bundles by default 64 | # when building for devices. 65 | installer.target_installation_results.pod_target_installation_results 66 | .each do |pod_name, target_installation_result| 67 | target_installation_result.resource_bundle_targets.each do |resource_bundle_target| 68 | resource_bundle_target.build_configurations.each do |config| 69 | config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO' 70 | end 71 | end 72 | end 73 | end 74 | 75 | post_integrate do |installer| 76 | begin 77 | expo_patch_react_imports!(installer) 78 | rescue => e 79 | Pod::UI.warn e 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/Podfile.properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo.jsEngine": "hermes", 3 | "EX_DEV_CLIENT_NETWORK_INSPECTOR": "true" 4 | } 5 | -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp.xcodeproj/xcshareddata/xcschemes/rnexpoapp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | @interface AppDelegate : EXAppDelegateWrapper 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp/AppDelegate.mm: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | #import 5 | 6 | @implementation AppDelegate 7 | 8 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 9 | { 10 | self.moduleName = @"main"; 11 | 12 | // You can add your custom initial props in the dictionary below. 13 | // They will be passed down to the ViewController used by React Native. 14 | self.initialProps = @{}; 15 | 16 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 17 | } 18 | 19 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 20 | { 21 | return [self getBundleURL]; 22 | } 23 | 24 | - (NSURL *)getBundleURL 25 | { 26 | #if DEBUG 27 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"]; 28 | #else 29 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 30 | #endif 31 | } 32 | 33 | // Linking API 34 | - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { 35 | return [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options]; 36 | } 37 | 38 | // Universal Links 39 | - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler { 40 | BOOL result = [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; 41 | return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result; 42 | } 43 | 44 | // Explicitly define remote notification delegates to ensure compatibility with some third-party libraries 45 | - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken 46 | { 47 | return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; 48 | } 49 | 50 | // Explicitly define remote notification delegates to ensure compatibility with some third-party libraries 51 | - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error 52 | { 53 | return [super application:application didFailToRegisterForRemoteNotificationsWithError:error]; 54 | } 55 | 56 | // Explicitly define remote notification delegates to ensure compatibility with some third-party libraries 57 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler 58 | { 59 | return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/ios/rnexpoapp/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "App-Icon-1024x1024@1x.png", 5 | "idiom": "universal", 6 | "platform": "ios", 7 | "size": "1024x1024" 8 | } 9 | ], 10 | "info": { 11 | "version": 1, 12 | "author": "expo" 13 | } 14 | } -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "expo" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp/Images.xcassets/SplashScreen.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "universal", 5 | "filename": "image.png", 6 | "scale": "1x" 7 | }, 8 | { 9 | "idiom": "universal", 10 | "scale": "2x" 11 | }, 12 | { 13 | "idiom": "universal", 14 | "scale": "3x" 15 | } 16 | ], 17 | "info": { 18 | "version": 1, 19 | "author": "expo" 20 | } 21 | } -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp/Images.xcassets/SplashScreen.imageset/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/ios/rnexpoapp/Images.xcassets/SplashScreen.imageset/image.png -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp/Images.xcassets/SplashScreenBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "universal", 5 | "filename": "image.png", 6 | "scale": "1x" 7 | }, 8 | { 9 | "idiom": "universal", 10 | "scale": "2x" 11 | }, 12 | { 13 | "idiom": "universal", 14 | "scale": "3x" 15 | } 16 | ], 17 | "info": { 18 | "version": 1, 19 | "author": "expo" 20 | } 21 | } -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp/Images.xcassets/SplashScreenBackground.imageset/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/ios/rnexpoapp/Images.xcassets/SplashScreenBackground.imageset/image.png -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | rn-expo-app 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 21 | CFBundleShortVersionString 22 | 1.0.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleURLTypes 26 | 27 | 28 | CFBundleURLSchemes 29 | 30 | com.stevegalili.rnexpoapp 31 | 32 | 33 | 34 | CFBundleVersion 35 | 1 36 | LSRequiresIPhoneOS 37 | 38 | NSAppTransportSecurity 39 | 40 | NSAllowsArbitraryLoads 41 | 42 | NSAllowsLocalNetworking 43 | 44 | 45 | UILaunchStoryboardName 46 | SplashScreen 47 | UIRequiredDeviceCapabilities 48 | 49 | armv7 50 | 51 | UIRequiresFullScreen 52 | 53 | UIStatusBarStyle 54 | UIStatusBarStyleDefault 55 | UISupportedInterfaceOrientations 56 | 57 | UIInterfaceOrientationPortrait 58 | UIInterfaceOrientationPortraitUpsideDown 59 | 60 | UISupportedInterfaceOrientations~ipad 61 | 62 | UIInterfaceOrientationPortrait 63 | UIInterfaceOrientationPortraitUpsideDown 64 | UIInterfaceOrientationLandscapeLeft 65 | UIInterfaceOrientationLandscapeRight 66 | 67 | UIUserInterfaceStyle 68 | Light 69 | UIViewControllerBasedStatusBarAppearance 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp/SplashScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp/Supporting/Expo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | EXUpdatesCheckOnLaunch 6 | ALWAYS 7 | EXUpdatesEnabled 8 | 9 | EXUpdatesLaunchWaitMs 10 | 0 11 | EXUpdatesSDKVersion 12 | 50.0.0 13 | 14 | -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp/noop-file.swift: -------------------------------------------------------------------------------- 1 | // 2 | // @generated 3 | // A blank Swift file must be created for native modules with Swift files to work correctly. 4 | // 5 | -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp/rnexpoapp-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | -------------------------------------------------------------------------------- /apps/rn-expo-app/ios/rnexpoapp/rnexpoapp.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | -------------------------------------------------------------------------------- /apps/rn-expo-app/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'jest-expo', 3 | setupFilesAfterEnv: ['./jest.setup.js'], 4 | transformIgnorePatterns: [ 5 | 'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)', 6 | ], 7 | }; 8 | -------------------------------------------------------------------------------- /apps/rn-expo-app/jest.setup.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/react-native/extend-expect'; 2 | -------------------------------------------------------------------------------- /apps/rn-expo-app/metro.config.js: -------------------------------------------------------------------------------- 1 | const {getDefaultConfig} = require('expo/metro-config'); 2 | const path = require('path'); 3 | 4 | // Find the project and workspace directories 5 | const projectRoot = __dirname; 6 | // This can be replaced with `find-yarn-workspace-root` 7 | const workspaceRoot = path.resolve(projectRoot, '../..'); 8 | 9 | const config = getDefaultConfig(projectRoot); 10 | 11 | // 1. Watch all files within the monorepo 12 | config.watchFolders = [workspaceRoot]; 13 | // 2. Let Metro know where to resolve packages and in what order 14 | config.resolver.nodeModulesPaths = [ 15 | path.resolve(projectRoot, 'node_modules'), 16 | path.resolve(workspaceRoot, 'node_modules'), 17 | ]; 18 | // 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths` 19 | config.resolver.disableHierarchicalLookup = true; 20 | 21 | module.exports = config; 22 | -------------------------------------------------------------------------------- /apps/rn-expo-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rn-expo-app", 3 | "version": "1.0.0", 4 | "main": "expo-router/entry", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo run:android", 8 | "ios": "expo run:ios", 9 | "web": "expo start --web", 10 | "test:unit": "jest", 11 | "test:unit:coverage": "jest --coverage", 12 | "ts:check": "tsc", 13 | "lint": "eslint ." 14 | }, 15 | "dependencies": { 16 | "@expo/config": "^8.5.4", 17 | "@expo/metro-config": "^0.17.3", 18 | "expo": "~50.0.3", 19 | "expo-constants": "~15.4.5", 20 | "expo-linking": "~6.2.2", 21 | "expo-router": "~3.4.6", 22 | "expo-status-bar": "~1.11.1", 23 | "react": "18.2.0", 24 | "react-native": "0.73.1", 25 | "react-native-safe-area-context": "4.8.2", 26 | "react-native-screens": "~3.29.0", 27 | "react-native-vector-icons": "^10.0.3" 28 | }, 29 | "devDependencies": { 30 | "@babel/core": "^7.23.9", 31 | "@testing-library/react-native": "^12.4.3", 32 | "@types/jest": "^29.5.11", 33 | "@types/react": "~18.2.45", 34 | "eslint": "^8.56.0", 35 | "eslint-config-universe": "^12.0.0", 36 | "jest": "^29.7.0", 37 | "jest-expo": "~50.0.1", 38 | "prettier": "^3.2.4", 39 | "react-test-renderer": "^18.2.0", 40 | "typescript": "^5.1.3" 41 | }, 42 | "private": true, 43 | "installConfig": { 44 | "hoistingLimits": "workspaces" 45 | }, 46 | "license": "MIT" 47 | } 48 | -------------------------------------------------------------------------------- /apps/rn-expo-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/rn-expo-app/yarn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanGalilea/react-native-testing/579281f602d8c3b4c71c612782e6f42d7aa94a23/apps/rn-expo-app/yarn -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.2.0", 3 | "private": true, 4 | "name": "react-native-testing", 5 | "workspaces": [ 6 | "apps/*" 7 | ], 8 | "repository": "https://github.com/vanGalilea/react-native-testing.git", 9 | "author": "vanGalilea ", 10 | "license": "MIT", 11 | "scripts": { 12 | "test:unit:coverage": "yarn workspaces run test:unit:coverage", 13 | "lint": "yarn workspaces run lint" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.organization=vangalilea 2 | sonar.projectKey=vanGalilea_react-native-testing 3 | sonar.javascript.lcov.reportPaths=**/coverage/lcov.info 4 | sonar.sources=apps/rn-cli-app/src/, apps/rn-expo-app/app 5 | sonar.test.exclusions=**/__tests__/** 6 | sonar.coverage.exclusions=**/__tests__/**, **/__mocks__/** 7 | sonar.coverage.inclusions=**/src/**/**.ts|tsx, **/app/**/**.ts|tsx 8 | sonar.exclusions=**/android/**, **/ios/** 9 | sonar.verbose=true 10 | sonar.host.url=https://sonarcloud.io 11 | --------------------------------------------------------------------------------