diff --git a/.github/workflows/e2e_android.yml b/.github/workflows/e2e_android.yml new file mode 100644 index 0000000..a084e83 --- /dev/null +++ b/.github/workflows/e2e_android.yml @@ -0,0 +1,232 @@ +name: Testing E2E Android + +on: + workflow_dispatch: + inputs: + clearCaches: + description: "Clear workflow caches where possible" + required: false + type: string + + pull_request: + branches: + - '**' + paths-ignore: + - 'docs/**' + - 'website/**' + - '.spellcheck.dict.txt' + # - '**/*.md' + + push: + branches: + - main + - v14-release + paths-ignore: + - 'docs/**' + - 'website/**' + - '.spellcheck.dict.txt' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + android: + name: Android + runs-on: macos-12 + timeout-minutes: 90 + strategy: + fail-fast: false + matrix: + # Refactor to make these dynamic with a low/high bracket only on schedule, not push + # For now this is just the fastest combo (api/arch/target/snapshot-warm-time) based on testing + api-level: [30] + arch: [x86_64] + target: [google_apis] + first-boot-delay: [600] + # This is useful for benchmarking, do 0, 1, 2, etc (up to 256 max job-per-matrix limit) for averages + iteration: [0] + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + EMULATOR_COMMAND: "-avd TestingAVD -noaudio -gpu swiftshader_indirect -camera-back none -no-snapshot -no-window -no-boot-anim -nojni -memory 2048 -timezone 'Europe/London' -cores 2" + EMULATOR_EXECUTABLE: qemu-system-x86_64-headless + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 50 + + # Set up tool versions + - uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Configure JDK 1.11 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '11' + + # Set path variables needed for caches + - name: Set workflow variables + id: workflow-variables + run: | + echo "metro-cache=$HOME/.metro" >> $GITHUB_OUTPUT + echo "yarn-cache-dir=$(yarn cache dir)" >> $GITHUB_OUTPUT + echo "tempdir=$TMPDIR" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 + name: Yarn Cache + id: yarn-cache + with: + path: ${{ steps.workflow-variables.outputs.yarn-cache-dir }} + key: ${{ runner.os }}-yarn-v1-${{ hashFiles('yarn.lock') }} + restore-keys: ${{ runner.os }}-yarn-v1 + + - name: Yarn Install + uses: nick-invision/retry@v2 + with: + timeout_minutes: 10 + retry_wait_seconds: 60 + max_attempts: 3 + command: DETOX_DISABLE_POSTINSTALL=1 yarn --no-audit --prefer-offline + + - name: Cache pushy Emulator + uses: actions/cache@v3 + with: + path: ~/.cache/pushy/emulators + key: pushy-emulators-v1-${{ github.run_id }} + restore-keys: pushy-emulators-v1 + + - name: Start pushy Emulator + run: yarn tests:emulator:start-ci + + - uses: actions/cache@v3 + name: Gradle Cache + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-v1-${{ hashFiles('**/*.gradle*') }} + restore-keys: ${{ runner.os }}-gradle-v1 + + # This appears to be 'Cache Size: ~1230 MB (1290026823 B)' based on watching action logs + # Repo limit is 10GB; branch caches are independent; branches may read default branch cache. + # We don't want branches to evict main branch snapshot, so save on main, read-only all else + - name: AVD cache + uses: actions/cache@v3 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }}-${{ matrix.arch }}-${{matrix.target}}-v1-${{ github.event.inputs.clearCaches }} + restore-keys: | + avd-${{ matrix.api-level }}-${{ matrix.arch }}-${{matrix.target}}-v1 + + - name: Clear Caches Optionally + if: "${{ github.event.inputs.clearCaches != '' }}" + shell: bash + run: | + du -sk ~/.gradle + du -sk ~/.android + rm -fr ~/.gradle + rm -fr ~/.android + du -sk ~/.gradle || echo ~/.gradle is gone + du -sk ~/.android || echo ~/.android is gone + + - name: Build Android App + uses: nick-invision/retry@v2 + with: + timeout_minutes: 25 + retry_wait_seconds: 60 + max_attempts: 3 + command: yarn build:android-release + + - name: Metro Bundler Cache + uses: actions/cache@v3 + with: + path: ${{ steps.workflow-variables.outputs.metro-cache }} + key: ${{ runner.os }}-metro-v1-${{ github.run_id }} + restore-keys: ${{ runner.os }}-metro-v1 + + - name: Pre-fetch Javascript bundle + # Prebuild the bundle so that's fast when the app starts. + run: | + nohup yarn tests:packager:jet-ci & + printf 'Waiting for packager to come online' + until curl --output /dev/null --silent --head --fail http://localhost:8081/status; do + printf '.' + sleep 2 + done + echo "Packager is online! Preparing javascript bundle..." + curl --output /dev/null --silent --head --fail "http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&inlineSourceMap=true" + echo "...javascript bundle ready." + + - name: AVD Boot and Snapshot Creation + # Only generate a snapshot with a cache miss + # Comment the if out to generate snapshots on branch for performance testing + if: "${{ github.event.inputs.clearCaches != '' || (steps.avd-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main') }}" + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + avd-name: TestingAVD + force-avd-creation: false + target: ${{ matrix.target }} + arch: ${{ matrix.arch }} + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + sdcard-path-or-size: 100M + disable-animations: true + # Give the emulator a little time to run and do first boot stuff before taking snapshot + script: | + $ANDROID_HOME/platform-tools/adb logcat '*:D' -d > adb-snapshot-log.txt + $ANDROID_HOME/platform-tools/adb logcat --clear + echo "Generated AVD snapshot for caching." + + # This step is separate so pure install time may be calculated as a step + - name: Emulator Snapshot After Firstboot Warmup + # Only generate a snapshot for saving with a cache miss + # Switch the if statements via comment if generating snapshots for performance testing + # if: matrix.first-boot-delay != '0' + if: "${{ github.event.inputs.clearCaches != '' || (steps.avd-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main') }}" + env: + FIRST_BOOT_DELAY: ${{ matrix.first-boot-delay }} + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + avd-name: TestingAVD + force-avd-creation: false + target: ${{ matrix.target }} + arch: ${{ matrix.arch }} + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + sdcard-path-or-size: 100M + disable-animations: true + # Give the emulator a little time to run and do first boot stuff before taking snapshot + # The zygote restart makes sure zygote has correct heap size as a workaround for android emulator init bug + script: | + $ANDROID_HOME/platform-tools/adb shell su root "setprop ctl.restart zygote" + sleep $FIRST_BOOT_DELAY + $ANDROID_HOME/platform-tools/adb logcat '*:D' -d > adb-warmup-log.txt + $ANDROID_HOME/platform-tools/adb logcat --clear + echo "First boot warmup completed." + + - name: Test Tapper + # Run this outside the emulator runner so the emulator runner does not wait on it for cleanup + run: | + nohup sh -c "until false; do $ANDROID_HOME/platform-tools/adb shell input tap 100 800; sleep 0.2; done" & + shell: bash + + - name: Detox Tests + uses: reactivecircus/android-emulator-runner@v2 + timeout-minutes: 40 + with: + api-level: ${{ matrix.api-level }} + avd-name: TestingAVD + force-avd-creation: false + target: ${{ matrix.target }} + arch: ${{ matrix.arch }} + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + sdcard-path-or-size: 100M + disable-animations: true + # Detox uses Espresso to choreograph steps in reaction to UI events, so we need to send a stream of taps. + script: | + $ANDROID_HOME/platform-tools/adb devices + nohup sh -c "$ANDROID_HOME/platform-tools/adb logcat '*:D' > adb-log.txt" & + yarn test:android-release diff --git a/.github/workflows/e2e_ios.yml b/.github/workflows/e2e_ios.yml new file mode 100644 index 0000000..f2e8fe1 --- /dev/null +++ b/.github/workflows/e2e_ios.yml @@ -0,0 +1,183 @@ +name: Testing E2E iOS + +on: + pull_request: + branches: + - '**' + paths-ignore: + - 'docs/**' + - 'website/**' + - '.spellcheck.dict.txt' + - '**/*.md' + + push: + branches: + - main + - v14-release + paths-ignore: + - 'docs/**' + - 'website/**' + - '.spellcheck.dict.txt' + # - '**/*.md' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + ios: + name: iOS + runs-on: macos-12 + # TODO matrix across APIs, at least 11 and 15 (lowest to highest) + timeout-minutes: 60 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + steps: + # Set up tool versions + - uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Configure JDK 1.11 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '11' + + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: 'latest-stable' + + - uses: actions/checkout@v3 + with: + fetch-depth: 50 + + # Set path variables needed for caches + - name: Set workflow variables + id: workflow-variables + run: | + echo "metro-cache=$HOME/.metro" >> $GITHUB_OUTPUT + echo "xcode-version=$(xcodebuild -version|tail -1|cut -f3 -d' ')" >> $GITHUB_OUTPUT + echo "yarn-cache-dir=$(yarn cache dir)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 + name: Yarn Cache + id: yarn-cache + with: + path: ${{ steps.workflow-variables.outputs.yarn-cache-dir }} + key: ${{ runner.os }}-yarn-v1-${{ hashFiles('yarn.lock') }} + restore-keys: ${{ runner.os }}-yarn-v1 + + - uses: actions/cache@v3 + name: Detox Framework Cache + id: detox-cache + with: + path: ~/Library/Detox/ios + key: ${{ runner.os }}-detox-framework-cache-${{ steps.workflow-variables.outputs.xcode-version }} + + # Detox is compiled during yarn install, using Xcode, set up cache first + - uses: hendrikmuhs/ccache-action@v1.2 + name: Xcode Compile Cache + with: + key: ${{ runner.os }}-v2 # makes a unique key w/related restore key internally + max-size: 1500M + + - name: Yarn Install + uses: nick-invision/retry@v2 + with: + timeout_minutes: 10 + retry_wait_seconds: 60 + max_attempts: 3 + command: yarn --no-audit --prefer-offline + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3 + + - name: Update Ruby build tools + uses: nick-invision/retry@v2 + with: + timeout_minutes: 2 + retry_wait_seconds: 60 + max_attempts: 3 + command: gem update cocoapods xcodeproj + + - uses: actions/cache@v3 + name: Cache Pods + id: pods-cache + with: + path: tests/ios/Pods + key: ${{ runner.os }}-pods-v2-${{ hashFiles('tests/ios/Podfile.lock') }} + restore-keys: ${{ runner.os }}-pods-v2 + + - name: Pod Install + uses: nick-invision/retry@v2 + with: + timeout_minutes: 10 + retry_wait_seconds: 30 + max_attempts: 3 + command: yarn tests:ios:pod:install + + - name: Cache Firestore Emulator + uses: actions/cache@v3 + with: + path: ~/.cache/pushy/emulators + key: pushy-emulators-v1-${{ github.run_id }} + restore-keys: pushy-emulators-v1 + + - name: Start Firestore Emulator + run: yarn tests:emulator:start-ci + + - name: Install brew utilities + uses: nick-invision/retry@v2 + with: + timeout_minutes: 5 + retry_wait_seconds: 60 + max_attempts: 3 + command: HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew && HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils xcbeautify && applesimutils --list + + - name: Build iOS App + run: | + export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" + export CCACHE_SLOPPINESS=clang_index_store,file_stat_matches,include_file_ctime,include_file_mtime,ivfsoverlay,pch_defines,modules,system_headers,time_macros + export CCACHE_FILECLONE=true + export CCACHE_DEPEND=true + export CCACHE_INODECACHE=true + export CCACHE_LIMIT_MULTIPLE=0.95 + ccache -s + export SKIP_BUNDLING=1 + export RCT_NO_LAUNCH_PACKAGER=1 + set -o pipefail + yarn build:ios-release + ccache -s + shell: bash + + - name: Metro Bundler Cache + uses: actions/cache@v3 + with: + path: ${{ steps.workflow-variables.outputs.metro-cache }} + key: ${{ runner.os }}-metro-v1-${{ github.run_id }} + restore-keys: ${{ runner.os }}-metro-v1 + + - name: Pre-fetch Javascript bundle + run: | + nohup yarn tests:packager:jet-ci & + printf 'Waiting for packager to come online' + until curl --output /dev/null --silent --head --fail http://localhost:8081/status; do + printf '.' + sleep 2 + done + echo "Packager is online! Preparing bundle..." + curl --output /dev/null --silent --head --fail "http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&inlineSourceMap=true" + echo "...javascript bundle ready" + + - name: Create Simulator Log + # With a little delay so the detox test below has time to spawn it, missing the first part of boot is fine + # If you boot the simulator separately from detox, some other race fails and detox testee never sends ready to proxy + continue-on-error: true + run: nohup sh -c "sleep 30 && xcrun simctl spawn booted log stream --level debug --style compact > simulator.log 2>&1 &" + + - name: Detox Test + timeout-minutes: 30 + run: yarn test:ios-release diff --git a/.github/workflows/scripts/adb_all_emulators.sh b/.github/workflows/scripts/adb_all_emulators.sh new file mode 100755 index 0000000..3db5cc9 --- /dev/null +++ b/.github/workflows/scripts/adb_all_emulators.sh @@ -0,0 +1,10 @@ +#!/bin/bash +echo "Running $1 on all running emulators..." +devices=`adb devices` + +for device in $devices; do + if [[ "$device" =~ "emulator-" ]]; then + adb -s $device $1 + fi +done +echo "All Done." \ No newline at end of file diff --git a/.github/workflows/scripts/database.rules b/.github/workflows/scripts/database.rules new file mode 100644 index 0000000..b4ffa70 --- /dev/null +++ b/.github/workflows/scripts/database.rules @@ -0,0 +1,13 @@ +{ + "rules": { + // Database in general is closed. Read/Write to anything but "tests/" will fail. + ".read": false, + ".write": false, + + // ..."tests" node will succeed + "tests": { + ".read": true, + ".write": true, + } + } +} diff --git a/.github/workflows/scripts/firebase.json b/.github/workflows/scripts/firebase.json new file mode 100644 index 0000000..4beb161 --- /dev/null +++ b/.github/workflows/scripts/firebase.json @@ -0,0 +1,39 @@ +{ + "firestore": { + "rules": "firestore.rules", + "indexes": "firestore.indexes.json" + }, + "functions": { + "predeploy": [ + "yarn", + "yarn --prefix \"$RESOURCE_DIR\" build" + ], + "source": "functions" + }, + "database": { + "rules": "database.rules" + }, + "storage": { + "rules": "storage.rules" + }, + "emulators": { + "auth": { + "port": 9099 + }, + "database": { + "port": 9000 + }, + "firestore": { + "port": 8080 + }, + "functions": { + "port": 5001 + }, + "storage": { + "port": 9199 + }, + "ui": { + "enabled": true + } + } +} diff --git a/.github/workflows/scripts/firestore.indexes.json b/.github/workflows/scripts/firestore.indexes.json new file mode 100644 index 0000000..b42ac29 --- /dev/null +++ b/.github/workflows/scripts/firestore.indexes.json @@ -0,0 +1,72 @@ +{ + "indexes": [ + { + "collectionGroup": "firestore", + "queryScope": "COLLECTION", + "fields": [ + { + "fieldPath": "a", + "order": "ASCENDING" + }, + { + "fieldPath": "b", + "order": "ASCENDING" + } + ] + } + ], + "fieldOverrides": [ + { + "collectionGroup": "collectionGroup", + "fieldPath": "value", + "indexes": [ + { + "order": "ASCENDING", + "queryScope": "COLLECTION" + }, + { + "order": "DESCENDING", + "queryScope": "COLLECTION" + }, + { + "arrayConfig": "CONTAINS", + "queryScope": "COLLECTION" + }, + { + "order": "ASCENDING", + "queryScope": "COLLECTION_GROUP" + }, + { + "order": "DESCENDING", + "queryScope": "COLLECTION_GROUP" + } + ] + }, + { + "collectionGroup": "collectionGroup", + "fieldPath": "number", + "indexes": [ + { + "order": "ASCENDING", + "queryScope": "COLLECTION" + }, + { + "order": "DESCENDING", + "queryScope": "COLLECTION" + }, + { + "arrayConfig": "CONTAINS", + "queryScope": "COLLECTION" + }, + { + "order": "ASCENDING", + "queryScope": "COLLECTION_GROUP" + }, + { + "order": "DESCENDING", + "queryScope": "COLLECTION_GROUP" + } + ] + } + ] +} diff --git a/.github/workflows/scripts/firestore.rules b/.github/workflows/scripts/firestore.rules new file mode 100644 index 0000000..84924b7 --- /dev/null +++ b/.github/workflows/scripts/firestore.rules @@ -0,0 +1,17 @@ +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + match /{document=**} { + allow read, write: if false; + } + match /firestore-bundle-tests/{document=**} { + allow read, write: if true; + } + match /firestore/{document=**} { + allow read, write: if true; + } + match /{path=**}/collectionGroup/{documentId} { + allow read, write: if true; + } + } +} \ No newline at end of file diff --git a/.github/workflows/scripts/functions/.gitignore b/.github/workflows/scripts/functions/.gitignore new file mode 100644 index 0000000..47b2615 --- /dev/null +++ b/.github/workflows/scripts/functions/.gitignore @@ -0,0 +1,10 @@ +# Compiled JavaScript files +lib/**/*.js +lib/**/*.js.map + +# TypeScript v1 declaration files +typings/ + +# Node.js dependency directory +node_modules/ +yarn.lock diff --git a/.github/workflows/scripts/functions/package.json b/.github/workflows/scripts/functions/package.json new file mode 100644 index 0000000..f9ff34c --- /dev/null +++ b/.github/workflows/scripts/functions/package.json @@ -0,0 +1,24 @@ +{ + "name": "functions", + "scripts": { + "build": "tsc", + "serve": "npm run build && firebase emulators:start --only functions", + "shell": "npm run build && firebase functions:shell", + "start": "npm run shell", + "deploy": "firebase deploy --only functions", + "logs": "firebase functions:log" + }, + "engines": { + "node": "16" + }, + "main": "lib/index.js", + "dependencies": { + "firebase-admin": "^11.3.0", + "firebase-functions": "^4.2.1" + }, + "devDependencies": { + "firebase-functions-test": "^3.0.0", + "typescript": "^4.9.5" + }, + "private": true +} diff --git a/.github/workflows/scripts/functions/src/exports.ts b/.github/workflows/scripts/functions/src/exports.ts new file mode 100644 index 0000000..b3a1971 --- /dev/null +++ b/.github/workflows/scripts/functions/src/exports.ts @@ -0,0 +1,13 @@ +/* + * + * Testing tools for invertase/react-native-firebase use only. + * + * Copyright (C) 2018-present Invertase Limited + * + * See License file for more information. +*/ + +/* eslint-disable global-require */ +module.exports = { + SAMPLE_DATA: require('./functions/sample-data'), +}; diff --git a/.github/workflows/scripts/functions/src/index.ts b/.github/workflows/scripts/functions/src/index.ts new file mode 100644 index 0000000..3c957c9 --- /dev/null +++ b/.github/workflows/scripts/functions/src/index.ts @@ -0,0 +1,12 @@ +import * as functions from 'firebase-functions'; + +// // Start writing Firebase Functions +// // https://firebase.google.com/docs/functions/typescript +// +export const helloWorld = functions.https.onRequest((request, response) => { + functions.logger.info('Hello logs!', { structuredData: true }); + response.send('{ "data": "Hello from Firebase!" }'); +}); + +export { testFunctionCustomRegion } from './testFunctionCustomRegion'; +export { testFunctionDefaultRegion } from './testFunctionDefaultRegion'; diff --git a/.github/workflows/scripts/functions/src/sample-data.ts b/.github/workflows/scripts/functions/src/sample-data.ts new file mode 100644 index 0000000..d0ed3b0 --- /dev/null +++ b/.github/workflows/scripts/functions/src/sample-data.ts @@ -0,0 +1,80 @@ +/* + * Testing tools for invertase/react-native-firebase use only. + * + * Copyright (C) 2018-present Invertase Limited + * + * See License file for more information. + */ + +const SAMPLE_DATA: { [key: string]: any } = { + number: 1234, + string: 'acde', + boolean: true, + null: null, + object: { + number: 1234, + string: 'acde', + boolean: true, + null: null, + }, + array: [1234, 'acde', true, null], + deepObject: { + array: [1234, 'acde', false, null], + object: { + number: 1234, + string: 'acde', + boolean: true, + null: null, + array: [1234, 'acde', true, null], + }, + number: 1234, + string: 'acde', + boolean: true, + null: null, + }, + deepArray: [ + 1234, + 'acde', + true, + null, + [1234, 'acde', true, null], + { + number: 1234, + string: 'acde', + boolean: true, + null: null, + array: [1234, 'acde', true, null], + }, + ], + deepMap: { + number: 123, + string: 'foo', + booleanTrue: true, + booleanFalse: false, + null: null, + list: ['1', 2, true, false], + map: { + number: 123, + string: 'foo', + booleanTrue: true, + booleanFalse: false, + null: null, + }, + }, + deepList: [ + '1', + 2, + true, + false, + ['1', 2, true, false], + { + number: 123, + string: 'foo', + booleanTrue: true, + booleanFalse: false, + null: null, + }, + ], +}; + +export default SAMPLE_DATA; diff --git a/.github/workflows/scripts/functions/src/testFunctionCustomRegion.ts b/.github/workflows/scripts/functions/src/testFunctionCustomRegion.ts new file mode 100644 index 0000000..fd3a990 --- /dev/null +++ b/.github/workflows/scripts/functions/src/testFunctionCustomRegion.ts @@ -0,0 +1,14 @@ +/* + * + * Testing tools for invertase/react-native-firebase use only. + * + * Copyright (C) 2018-present Invertase Limited + * + * See License file for more information. + */ + +import * as functions from 'firebase-functions'; + +export const testFunctionCustomRegion = functions + .region('europe-west1') + .https.onCall(() => 'europe-west1'); diff --git a/.github/workflows/scripts/functions/src/testFunctionDefaultRegion.ts b/.github/workflows/scripts/functions/src/testFunctionDefaultRegion.ts new file mode 100644 index 0000000..35fab68 --- /dev/null +++ b/.github/workflows/scripts/functions/src/testFunctionDefaultRegion.ts @@ -0,0 +1,70 @@ +/* + * + * Testing tools for invertase/react-native-firebase use only. + * + * Copyright (C) 2018-present Invertase Limited + * + * See License file for more information. + */ + +import * as assert from 'assert'; +import { FirebaseError } from 'firebase-admin'; +import * as functions from 'firebase-functions'; +import SAMPLE_DATA from './sample-data'; + +export const testFunctionDefaultRegion = functions.https.onCall(data => { + console.log(Date.now(), data); + + if (typeof data === 'undefined') { + return 'undefined'; + } + + if (typeof data === 'string') { + return 'string'; + } + + if (typeof data === 'number') { + return 'number'; + } + + if (typeof data === 'boolean') { + return 'boolean'; + } + + if (data === null) { + return 'null'; + } + + if (Array.isArray(data)) { + return 'array'; + } + + const { type, asError, inputData } = data; + if (!Object.hasOwnProperty.call(SAMPLE_DATA, type)) { + throw new functions.https.HttpsError('invalid-argument', 'Invalid test requested.'); + } + + const outputData = SAMPLE_DATA[type]; + + try { + assert.deepEqual(outputData, inputData); + } catch (e) { + console.error(e); + throw new functions.https.HttpsError( + 'invalid-argument', + 'Input and Output types did not match.', + (e as FirebaseError).message, + ); + } + + // all good + if (asError) { + throw new functions.https.HttpsError( + 'cancelled', + 'Response data was requested to be sent as part of an Error payload, so here we are!', + outputData, + ); + } + + return outputData; +}); diff --git a/.github/workflows/scripts/functions/tsconfig.json b/.github/workflows/scripts/functions/tsconfig.json new file mode 100644 index 0000000..3996a75 --- /dev/null +++ b/.github/workflows/scripts/functions/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noImplicitReturns": true, + "noUnusedLocals": true, + "outDir": "lib", + "sourceMap": true, + "skipLibCheck": true, + "strict": true, + "target": "es2017" + }, + "compileOnSave": true, + "include": [ + "src" + ] +} diff --git a/.github/workflows/scripts/start-firebase-emulator.bat b/.github/workflows/scripts/start-firebase-emulator.bat new file mode 100644 index 0000000..8cf71ef --- /dev/null +++ b/.github/workflows/scripts/start-firebase-emulator.bat @@ -0,0 +1,6 @@ +@REM this pushd is likely not needed, but just in case +pushd "%~dp0" +@REM this is just to see what our current directory is. Should be .github/workflow/scripts +echo %cd% +@REM strangely, unless you specify the config file as being right in the current directory, it won't find it, and everything fails +yarn firebase emulators:start --config %cd%\firebase.json --only auth,database,firestore,functions,storage --project react-native-firebase-testing \ No newline at end of file diff --git a/.github/workflows/scripts/start-firebase-emulator.sh b/.github/workflows/scripts/start-firebase-emulator.sh new file mode 100755 index 0000000..2ce2448 --- /dev/null +++ b/.github/workflows/scripts/start-firebase-emulator.sh @@ -0,0 +1,44 @@ +#!/bin/bash +if ! [ -x "$(command -v firebase)" ]; then + echo "❌ Firebase-tools CLI is missing. Run 'npm i -g firebase-tools' or the equivalent" + exit 1 +fi + +EMU_START_COMMAND="firebase emulators:start --only auth,database,firestore,functions,storage --project react-native-firebase-testing" +#EMU_START_COMMAND="sleep 120" +MAX_RETRIES=3 +MAX_CHECKATTEMPTS=60 +CHECKATTEMPTS_WAIT=1 + +# Make sure functions are ready to go +pushd "$(dirname "$0")/functions" && yarn && yarn build && popd + + +RETRIES=1 +while [ $RETRIES -le $MAX_RETRIES ]; do + + if [ "$1" == "--no-daemon" ]; then + echo "Starting Firebase Emulator Suite in foreground." + $EMU_START_COMMAND + exit 0 + else + echo "Starting Firebase Emulator Suite in background." + $EMU_START_COMMAND & + CHECKATTEMPTS=1 + while [ $CHECKATTEMPTS -le $MAX_CHECKATTEMPTS ]; do + sleep $CHECKATTEMPTS_WAIT + if curl --output /dev/null --silent --fail http://localhost:8080; then + echo "Firebase Emulator Suite is online!" + exit 0; + fi + echo "Waiting for Firebase Emulator Suite to come online, check $CHECKATTEMPTS of $MAX_CHECKATTEMPTS..." + ((CHECKATTEMPTS = CHECKATTEMPTS + 1)) + done + fi + + echo "Firebase Emulator Suite did not come online in $MAX_CHECKATTEMPTS checks. Try $RETRIES of $MAX_RETRIES." + ((RETRIES = RETRIES + 1)) + +done +echo "Firebase Emulator Suite did not come online after $MAX_RETRIES attempts." +exit 1 diff --git a/.github/workflows/scripts/storage.rules b/.github/workflows/scripts/storage.rules new file mode 100644 index 0000000..c5a99c3 --- /dev/null +++ b/.github/workflows/scripts/storage.rules @@ -0,0 +1,21 @@ +rules_version = '2'; +service firebase.storage { + match /b/{bucket}/o { + match /{document=**} { + allow read, write: if false; + } + + match /writeOnly.jpeg { + allow read: if false; + allow write: if true; + } + + match /playground/{document=**} { + allow read, write: if true; + } + + match /react-native-firebase-testing/{document=**} { + allow read, write: if true; + } + } +} \ No newline at end of file diff --git a/Example/testHotUpdate/cache/test-butler-app.apk b/Example/testHotUpdate/cache/test-butler-app.apk new file mode 100644 index 0000000..105e06e Binary files /dev/null and b/Example/testHotUpdate/cache/test-butler-app.apk differ diff --git a/Example/testHotUpdate/detox.config.js b/Example/testHotUpdate/detox.config.js new file mode 100644 index 0000000..113131e --- /dev/null +++ b/Example/testHotUpdate/detox.config.js @@ -0,0 +1,111 @@ +/** @type {Detox.DetoxConfig} */ +module.exports = { + logger: { + level: process.env.CI ? 'debug' : undefined, + }, + testRunner: { + args: { + config: 'e2e/jest.config.js', + maxWorkers: process.env.CI ? 2 : undefined, + _: ['e2e'], + }, + }, + artifacts: { + plugins: { + log: process.env.CI ? 'failing' : undefined, + screenshot: process.env.CI ? 'failing' : undefined, + }, + }, + apps: { + 'ios.release': { + type: 'ios.app', + binaryPath: + 'ios/build/Build/Products/Release-iphonesimulator/testHotUpdate.app', + build: + 'export RCT_NO_LAUNCH_PACKAGER=true && xcodebuild -workspace ios/testHotUpdate.xcworkspace -UseNewBuildSystem=NO -scheme testHotUpdate -configuration Release -sdk iphonesimulator -derivedDataPath ios/build -quiet', + }, + 'ios.debug': { + type: 'ios.app', + binaryPath: + 'ios/build/Build/Products/Debug-iphonesimulator/testHotUpdate.app', + build: + 'xcodebuild -workspace ios/testHotUpdate.xcworkspace -UseNewBuildSystem=NO -scheme testHotUpdate -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build', + start: 'scripts/start-rn.sh ios', + }, + 'android.debug': { + type: 'android.apk', + binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk', + build: + 'cd android ; ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug ; cd -', + start: 'scripts/start-rn.sh android', + }, + 'android.release': { + type: 'android.apk', + binaryPath: 'android/app/build/outputs/apk/release/app-release.apk', + build: + 'cd android ; ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release ; cd -', + }, + }, + devices: { + simulator: { + type: 'ios.simulator', + headless: Boolean(process.env.CI), + device: { + type: 'iPhone 14', + }, + }, + emulator: { + type: 'android.emulator', + headless: Boolean(process.env.CI), + gpuMode: process.env.CI ? 'off' : undefined, + device: { + avdName: 'Pixel_3a_API_33_arm64-v8a', + }, + utilBinaryPaths: ['./cache/test-butler-app.apk'], + }, + 'genymotion.emulator.uuid': { + type: 'android.genycloud', + device: { + recipeUUID: 'a50a71d6-da90-4c67-bdfa-5b602b0bbd15', + }, + utilBinaryPaths: ['./cache/test-butler-app.apk'], + }, + 'genymotion.emulator.name': { + type: 'android.genycloud', + device: { + recipeName: 'Pixel_3a_API_33_arm64-v8a', + }, + utilBinaryPaths: ['./cache/test-butler-app.apk'], + }, + }, + configurations: { + 'ios.sim.release': { + device: 'simulator', + app: 'ios.release', + }, + 'ios.sim.debug': { + device: 'simulator', + app: 'ios.debug', + }, + 'ios.manual': { + type: 'ios.manual', + behavior: { + launchApp: 'manual', + }, + artifacts: false, + session: { + autoStart: true, + server: 'ws://localhost:8099', + sessionId: 'com.wix.demo.react.native', + }, + }, + 'android.emu.debug': { + device: 'emulator', + app: 'android.debug', + }, + 'android.emu.release': { + device: 'emulator', + app: 'android.release', + }, + }, +}; diff --git a/Example/testHotUpdate/e2e/NativeModule.test.ts b/Example/testHotUpdate/e2e/NativeModule.test.ts new file mode 100644 index 0000000..47e3abf --- /dev/null +++ b/Example/testHotUpdate/e2e/NativeModule.test.ts @@ -0,0 +1,89 @@ +import {by, device, element, expect} from 'detox'; + +describe('测试Native模块的方法', () => { + beforeAll(async () => { + await device.launchApp(); + }); + + it('setLocalHashInfo', async () => { + await element(by.id('testcase')).longPress(); + await element(by.id('setLocalHashInfo')).tap(); + await element(by.id('submit')).tap(); + await expect(element(by.text('done'))).toBeVisible(); + await element(by.text('OK')).tap(); + }); + + it('getLocalHashInfo', async () => { + await element(by.id('getLocalHashInfo')).tap(); + await element(by.id('submit')).tap(); + await expect(element(by.text('done'))).toBeVisible(); + await element(by.text('OK')).tap(); + }); + + it('setUuid', async () => { + await element(by.id('setUuid')).tap(); + await element(by.id('submit')).tap(); + await expect(element(by.text('done'))).toBeVisible(); + await element(by.text('OK')).tap(); + }); + + it('setBlockUpdate', async () => { + await element(by.id('setBlockUpdate')).tap(); + await element(by.id('submit')).tap(); + await expect(element(by.text('done'))).toBeVisible(); + await element(by.text('OK')).tap(); + }); + + it('reloadUpdate', async () => { + await element(by.id('reloadUpdate')).tap(); + await element(by.id('submit')).tap(); + await expect(element(by.text('刚刚更新失败了,版本被回滚.'))).toBeVisible(); + await element(by.text('OK')).tap(); + }); + + it('setNeedUpdate', async () => { + await element(by.id('testcase')).longPress(); + await element(by.id('setNeedUpdate')).tap(); + await element(by.id('submit')).tap(); + await expect(element(by.text('done'))).toBeVisible(); + await element(by.text('OK')).tap(); + }); + + it('markSuccess', async () => { + await element(by.id('markSuccess')).tap(); + await element(by.id('submit')).tap(); + await expect(element(by.text('done'))).toBeVisible(); + await element(by.text('OK')).tap(); + }); + + it('downloadPatchFromPpk', async () => { + await element(by.id('downloadPatchFromPpk')).tap(); + await element(by.id('submit')).tap(); + await expect(element(by.text('failed to open zip file'))).toBeVisible(); + await element(by.text('OK')).tap(); + }); + + it('downloadPatchFromPackage', async () => { + await element(by.id('downloadPatchFromPackage')).tap(); + await element(by.id('submit')).tap(); + await expect(element(by.text('failed to open zip file'))).toBeVisible(); + await element(by.text('OK')).tap(); + }); + + it('downloadFullUpdate', async () => { + await element(by.id('downloadFullUpdate')).tap(); + await element(by.id('submit')).tap(); + await expect(element(by.text('failed to open zip file'))).toBeVisible(); + await element(by.text('OK')).tap(); + }); + + if (device.getPlatform() === 'android') { + it('downloadAndInstallApk', async () => { + await element(by.id('testcase')).longPress(); + await element(by.id('downloadAndInstallApk')).tap(); + await element(by.id('submit')).tap(); + await expect(element(by.text('failed to open zip file'))).toBeVisible(); + await element(by.text('OK')).tap(); + }); + } +}); diff --git a/Example/testHotUpdate/e2e/globalSetup.ts b/Example/testHotUpdate/e2e/globalSetup.ts new file mode 100644 index 0000000..642d371 --- /dev/null +++ b/Example/testHotUpdate/e2e/globalSetup.ts @@ -0,0 +1,29 @@ +import { execSync } from 'child_process'; + +import { pathExists, ensureDir } from 'fs-extra'; + +import { resolveConfig } from 'detox/internals'; +import { globalSetup } from 'detox/runners/jest'; + +export default async function customGlobalSetup() { + const config = await resolveConfig(); + if (config.device.type === 'android.emulator') { + await downloadTestButlerAPK(); + } + + await globalSetup(); +} + +async function downloadTestButlerAPK() { + const version = '2.2.1'; + const artifactUrl = `https://repo1.maven.org/maven2/com/linkedin/testbutler/test-butler-app/${version}/test-butler-app-${version}.apk`; + const filePath = `cache/test-butler-app.apk`; + + await ensureDir('cache'); + if (!(await pathExists(filePath))) { + console.log(`\nDownloading Test-Butler APK v${version}...`); + execSync(`curl -f -o ${filePath} ${artifactUrl}`); + } +} + +module.exports = customGlobalSetup; diff --git a/Example/testHotUpdate/e2e/jest.config.js b/Example/testHotUpdate/e2e/jest.config.js new file mode 100644 index 0000000..0487102 --- /dev/null +++ b/Example/testHotUpdate/e2e/jest.config.js @@ -0,0 +1,16 @@ +/** @type {import('jest').Config} */ +module.exports = { + maxWorkers: 1, + globalSetup: './globalSetup.ts', + globalTeardown: 'detox/runners/jest/globalTeardown', + testEnvironment: 'detox/runners/jest/testEnvironment', + setupFilesAfterEnv: ['./setup.ts'], + testRunner: 'jest-circus/runner', + testTimeout: 120000, + testMatch: ['**/*.test.ts'], + transform: { + '\\.tsx?$': 'ts-jest', + }, + reporters: ['detox/runners/jest/reporter'], + verbose: true, +}; diff --git a/Example/testHotUpdate/e2e/setup.ts b/Example/testHotUpdate/e2e/setup.ts new file mode 100644 index 0000000..a4e12aa --- /dev/null +++ b/Example/testHotUpdate/e2e/setup.ts @@ -0,0 +1,5 @@ +import { device } from 'detox'; + +beforeAll(async () => { + await device.launchApp(); +}); diff --git a/Example/testHotUpdate/ios/Podfile.lock b/Example/testHotUpdate/ios/Podfile.lock index 173fd62..82458b6 100644 --- a/Example/testHotUpdate/ios/Podfile.lock +++ b/Example/testHotUpdate/ios/Podfile.lock @@ -89,11 +89,6 @@ PODS: - DoubleConversion - fmt (~> 6.2.1) - glog - - RCT-Folly/Fabric (2021.07.22.00): - - boost - - DoubleConversion - - fmt (~> 6.2.1) - - glog - RCT-Folly/Futures (2021.07.22.00): - boost - DoubleConversion @@ -126,10 +121,8 @@ PODS: - RCTRequired - RCTTypeSafety - React-Core - - React-graphics - React-jsi - React-jsiexecutor - - React-rncore - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - React-Core (0.71.1): @@ -278,326 +271,6 @@ PODS: - React-logger (= 0.71.1) - React-perflogger (= 0.71.1) - React-runtimeexecutor (= 0.71.1) - - React-Fabric (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-Fabric/animations (= 0.71.1) - - React-Fabric/attributedstring (= 0.71.1) - - React-Fabric/butter (= 0.71.1) - - React-Fabric/componentregistry (= 0.71.1) - - React-Fabric/componentregistrynative (= 0.71.1) - - React-Fabric/components (= 0.71.1) - - React-Fabric/config (= 0.71.1) - - React-Fabric/core (= 0.71.1) - - React-Fabric/debug_core (= 0.71.1) - - React-Fabric/debug_renderer (= 0.71.1) - - React-Fabric/imagemanager (= 0.71.1) - - React-Fabric/leakchecker (= 0.71.1) - - React-Fabric/mapbuffer (= 0.71.1) - - React-Fabric/mounting (= 0.71.1) - - React-Fabric/runtimescheduler (= 0.71.1) - - React-Fabric/scheduler (= 0.71.1) - - React-Fabric/telemetry (= 0.71.1) - - React-Fabric/templateprocessor (= 0.71.1) - - React-Fabric/textlayoutmanager (= 0.71.1) - - React-Fabric/uimanager (= 0.71.1) - - React-Fabric/utils (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/animations (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/attributedstring (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/butter (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/componentregistry (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/componentregistrynative (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/components (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-Fabric/components/activityindicator (= 0.71.1) - - React-Fabric/components/image (= 0.71.1) - - React-Fabric/components/inputaccessory (= 0.71.1) - - React-Fabric/components/legacyviewmanagerinterop (= 0.71.1) - - React-Fabric/components/modal (= 0.71.1) - - React-Fabric/components/root (= 0.71.1) - - React-Fabric/components/safeareaview (= 0.71.1) - - React-Fabric/components/scrollview (= 0.71.1) - - React-Fabric/components/slider (= 0.71.1) - - React-Fabric/components/text (= 0.71.1) - - React-Fabric/components/textinput (= 0.71.1) - - React-Fabric/components/unimplementedview (= 0.71.1) - - React-Fabric/components/view (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/components/activityindicator (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/components/image (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/components/inputaccessory (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/components/legacyviewmanagerinterop (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/components/modal (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/components/root (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/components/safeareaview (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/components/scrollview (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/components/slider (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/components/text (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/components/textinput (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/components/unimplementedview (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/components/view (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - Yoga - - React-Fabric/config (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/core (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/debug_core (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/debug_renderer (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/imagemanager (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - React-RCTImage (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/leakchecker (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/mapbuffer (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/mounting (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/runtimescheduler (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/scheduler (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/telemetry (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/templateprocessor (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/textlayoutmanager (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-Fabric/uimanager - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/uimanager (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-Fabric/utils (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 0.71.1) - - RCTTypeSafety (= 0.71.1) - - React-graphics (= 0.71.1) - - React-jsi (= 0.71.1) - - React-jsiexecutor (= 0.71.1) - - ReactCommon/turbomodule/core (= 0.71.1) - - React-graphics (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - React-Core/Default (= 0.71.1) - React-hermes (0.71.1): - DoubleConversion - glog @@ -624,34 +297,19 @@ PODS: - React-jsinspector (0.71.1) - React-logger (0.71.1): - glog - - react-native-update (9.1.0): - - RCT-Folly (= 2021.07.22.00) - - RCTRequired - - RCTTypeSafety + - react-native-update (9.0.0): - React - - React-Codegen - React-Core - - react-native-update/HDiffPatch (= 9.1.0) - - react-native-update/RCTPushy (= 9.1.0) - - ReactCommon/turbomodule/core + - react-native-update/HDiffPatch (= 9.0.0) + - react-native-update/RCTPushy (= 9.0.0) - SSZipArchive - - react-native-update/HDiffPatch (9.1.0): - - RCT-Folly (= 2021.07.22.00) - - RCTRequired - - RCTTypeSafety + - react-native-update/HDiffPatch (9.0.0): - React - - React-Codegen - React-Core - - ReactCommon/turbomodule/core - SSZipArchive - - react-native-update/RCTPushy (9.1.0): - - RCT-Folly (= 2021.07.22.00) - - RCTRequired - - RCTTypeSafety + - react-native-update/RCTPushy (9.0.0): - React - - React-Codegen - React-Core - - ReactCommon/turbomodule/core - SSZipArchive - React-perflogger (0.71.1) - React-RCTActionSheet (0.71.1): @@ -668,8 +326,6 @@ PODS: - RCTRequired - RCTTypeSafety - React-Core - - React-graphics - - React-RCTFabric - ReactCommon/turbomodule/core - React-RCTBlob (0.71.1): - RCT-Folly (= 2021.07.22.00) @@ -679,11 +335,6 @@ PODS: - React-jsi (= 0.71.1) - React-RCTNetwork (= 0.71.1) - ReactCommon/turbomodule/core (= 0.71.1) - - React-RCTFabric (0.71.1): - - RCT-Folly/Fabric (= 2021.07.22.00) - - React-Core (= 0.71.1) - - React-Fabric (= 0.71.1) - - React-RCTImage (= 0.71.1) - React-RCTImage (0.71.1): - RCT-Folly (= 2021.07.22.00) - RCTTypeSafety (= 0.71.1) @@ -719,7 +370,6 @@ PODS: - React-Core/RCTVibrationHeaders (= 0.71.1) - React-jsi (= 0.71.1) - ReactCommon/turbomodule/core (= 0.71.1) - - React-rncore (0.71.1) - React-runtimeexecutor (0.71.1): - React-jsi (= 0.71.1) - ReactCommon/turbomodule/bridging (0.71.1): @@ -779,7 +429,6 @@ DEPENDENCIES: - libevent (~> 2.1.12) - OpenSSL-Universal (= 1.1.1100) - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - - RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - React (from `../node_modules/react-native/`) @@ -790,8 +439,6 @@ DEPENDENCIES: - React-Core/RCTWebSocket (from `../node_modules/react-native/`) - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) - - React-Fabric (from `../node_modules/react-native/ReactCommon`) - - React-graphics (from `../node_modules/react-native/ReactCommon/react/renderer/graphics`) - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) @@ -803,14 +450,12 @@ DEPENDENCIES: - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) - React-RCTAppDelegate (from `../node_modules/react-native/Libraries/AppDelegate`) - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) - - React-RCTFabric (from `../node_modules/react-native/React`) - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) - React-RCTText (from `../node_modules/react-native/Libraries/Text`) - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - - React-rncore (from `../node_modules/react-native/ReactCommon`) - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) @@ -865,10 +510,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/React/CoreModules" React-cxxreact: :path: "../node_modules/react-native/ReactCommon/cxxreact" - React-Fabric: - :path: "../node_modules/react-native/ReactCommon" - React-graphics: - :path: "../node_modules/react-native/ReactCommon/react/renderer/graphics" React-hermes: :path: "../node_modules/react-native/ReactCommon/hermes" React-jsi: @@ -891,8 +532,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/Libraries/AppDelegate" React-RCTBlob: :path: "../node_modules/react-native/Libraries/Blob" - React-RCTFabric: - :path: "../node_modules/react-native/React" React-RCTImage: :path: "../node_modules/react-native/Libraries/Image" React-RCTLinking: @@ -905,8 +544,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/Libraries/Text" React-RCTVibration: :path: "../node_modules/react-native/Libraries/Vibration" - React-rncore: - :path: "../node_modules/react-native/ReactCommon" React-runtimeexecutor: :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" ReactCommon: @@ -919,7 +556,7 @@ SPEC CHECKSUMS: CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 FBLazyVector: ad72713385db5289b19f1ead07e8e4aa26dcb01d - FBReactNativeSpec: 06fc2a521838dc240b499699d43467b071c66908 + FBReactNativeSpec: df2602c11e33d310433496e28a48b4b2be652a61 Flipper: 26fc4b7382499f1281eb8cb921e5c3ad6de91fe0 Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30 @@ -939,31 +576,27 @@ SPEC CHECKSUMS: RCTTypeSafety: c276d85975bde3d8448907235c70bf0da257adfd React: e481a67971af1ce9639c9f746b753dd0e84ca108 React-callinvoker: 1051c04a94fa9d243786b86380606bad701a3b31 - React-Codegen: d4bc58865453b6a797405aa99f9fa1da3c9e58c6 + React-Codegen: 14b1e716d361d5ad95e0ce1a338f3fa0733a98b5 React-Core: 698fc3baecb80d511d987475a16d036cec6d287f React-CoreModules: 59245305f41ff0adfeac334acc0594dea4585a7c React-cxxreact: 49accd2954b0f532805dbcd1918fa6962f32f247 - React-Fabric: 30982dc19c7511bedf1751b0a0c21a5b816e2a3e - React-graphics: beabc29b026e7472ced1482557effedd15a09cf1 React-hermes: d068733294581a085e95b6024e8d951b005e26d3 React-jsi: 122b9bce14f4c6c7cb58f28f87912cfe091885fa React-jsiexecutor: 60cf272aababc5212410e4249d17cea14fc36caa React-jsinspector: ff56004b0c974b688a6548c156d5830ad751ae07 React-logger: 60a0b5f8bed667ecf9e24fecca1f30d125de6d75 - react-native-update: b9d44d250953e61f1b42c22fa4d585a6bb5dd4d6 + react-native-update: 2b5ef06bfeaa668614c8deb7ec4d20dcf56f9278 React-perflogger: ec8eef2a8f03ecfa6361c2c5fb9197ef4a29cc85 React-RCTActionSheet: a0c023b86cf4c862fa9c4eb0f6f91fbe878fb2de React-RCTAnimation: 168d53718c74153947c0109f55900faa64d79439 - React-RCTAppDelegate: 5f34addd2f9d8c542c129b562b7f3db0cc599a3d + React-RCTAppDelegate: a8efbab128b34aa07a9491c85a41401210b1bec5 React-RCTBlob: 9bcbfc893bfda9f6b2eb016329d38c0f6366d31a - React-RCTFabric: cec4e89720e8778aa132e5515be1af251d2e9b6a React-RCTImage: 3fcd4570b4b0f1ac2f4b4b6308dba33ce66c5b50 React-RCTLinking: 1edb8e1bb3fc39bf9e13c63d6aaaa3f0c3d18683 React-RCTNetwork: 500a79e0e0f67678077df727fabba87a55c043e1 React-RCTSettings: cc4414eb84ad756d619076c3999fecbf12896d6f React-RCTText: 2a34261f3da6e34f47a62154def657546ebfa5e1 React-RCTVibration: 49d531ec8498e0afa2c9b22c2205784372e3d4f3 - React-rncore: b802bc9f6985c482127b066c869999a09d25edeb React-runtimeexecutor: 311feb67600774723fe10eb8801d3138cae9ad67 ReactCommon: 03be76588338a27a88d103b35c3c44a3fd43d136 SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 diff --git a/Example/testHotUpdate/ios/testHotUpdate.xcodeproj/project.pbxproj b/Example/testHotUpdate/ios/testHotUpdate.xcodeproj/project.pbxproj index 8afff61..1d04798 100644 --- a/Example/testHotUpdate/ios/testHotUpdate.xcodeproj/project.pbxproj +++ b/Example/testHotUpdate/ios/testHotUpdate.xcodeproj/project.pbxproj @@ -211,7 +211,7 @@ }; }; }; - buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "testHotupdate" */; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "testHotUpdate" */; compatibilityVersion = "Xcode 12.0"; developmentRegion = en; hasScannedForEncodings = 0; @@ -689,7 +689,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "testHotupdate" */ = { + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "testHotUpdate" */ = { isa = XCConfigurationList; buildConfigurations = ( 83CBBA201A601CBA00E9B192 /* Debug */, diff --git a/Example/testHotUpdate/ios/testHotUpdate.xcworkspace/contents.xcworkspacedata b/Example/testHotUpdate/ios/testHotUpdate.xcworkspace/contents.xcworkspacedata index f11c0a0..13d1525 100644 --- a/Example/testHotUpdate/ios/testHotUpdate.xcworkspace/contents.xcworkspacedata +++ b/Example/testHotUpdate/ios/testHotUpdate.xcworkspace/contents.xcworkspacedata @@ -7,4 +7,7 @@ + + diff --git a/Example/testHotUpdate/ios/testHotUpdate/Info.plist b/Example/testHotUpdate/ios/testHotUpdate/Info.plist index 508c4ae..133e33c 100644 --- a/Example/testHotUpdate/ios/testHotUpdate/Info.plist +++ b/Example/testHotUpdate/ios/testHotUpdate/Info.plist @@ -24,17 +24,6 @@ $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS - NSAppTransportSecurity - - NSExceptionDomains - - localhost - - NSExceptionAllowsInsecureHTTPLoads - - - - NSLocationWhenInUseUsageDescription UILaunchStoryboardName @@ -51,5 +40,18 @@ UIViewControllerBasedStatusBarAppearance + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSExceptionDomains + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + + diff --git a/Example/testHotUpdate/package.json b/Example/testHotUpdate/package.json index c73fccd..49e37e6 100644 --- a/Example/testHotUpdate/package.json +++ b/Example/testHotUpdate/package.json @@ -25,11 +25,15 @@ "@types/react": "^18.0.24", "@types/react-test-renderer": "^18.0.0", "babel-jest": "^29.2.1", + "detox": "^20.5.0", "eslint": "^8.19.0", + "fs-extra": "^11.1.0", "jest": "^29.2.1", "metro-react-native-babel-preset": "0.73.7", "prettier": "^2.4.1", - "react-test-renderer": "18.2.0" + "react-test-renderer": "18.2.0", + "ts-jest": "^29.0.5", + "typescript": "^4.9.5" }, "jest": { "preset": "react-native" diff --git a/Example/testHotUpdate/src/TestConsole.js b/Example/testHotUpdate/src/TestConsole.js index 6dac3d9..5000c7e 100644 --- a/Example/testHotUpdate/src/TestConsole.js +++ b/Example/testHotUpdate/src/TestConsole.js @@ -1,6 +1,6 @@ /* eslint-disable react-native/no-inline-styles */ /* eslint-disable react/react-in-jsx-scope */ -import {useState} from 'react'; +import {useCallback, useMemo, useState} from 'react'; import { ActivityIndicator, Alert, @@ -10,14 +10,117 @@ import { StyleSheet, SafeAreaView, Text, + ScrollView, View, + TouchableOpacity, } from 'react-native'; import {PushyModule} from 'react-native-update'; - +const Hash = '9D5CE6EBA420717BE7E7D308B11F8207681B066C951D68F3994D19828F342474'; +const UUID = '00000000-0000-0000-0000-000000000000'; +const DownloadUrl = + 'http://cos.pgyer.com/697913e94d7441f20c686e2b0996a1aa.apk?sign=363b035b7ef52c199c268abfacee3712&t=1678603669&response-content-disposition=attachment%3Bfilename%3DtestHotupdate_1.0.apk'; export default function TestConsole({visible}) { const [text, setText] = useState(''); const [running, setRunning] = useState(false); + const [options, setOptions] = useState(); + const NativeTestMethod = useMemo(() => { + return [ + { + name: 'setLocalHashInfo', + invoke: () => { + setText( + `setLocalHashInfo\n${Hash}\n{\"version\":\"1.0.0\",\"size\":\"19M\"}`, + ); + }, + }, + { + name: 'getLocalHashInfo', + invoke: () => { + setText(`getLocalHashInfo\n${Hash}`); + }, + }, + { + name: 'setUuid', + invoke: () => { + setText(`setUuid\n${UUID}`); + }, + }, + { + name: 'setBlockUpdate', + invoke: () => { + setText('setBlockUpdate'); + setOptions({reason: 'application has been block', until: 1673082950}); + }, + }, + { + name: 'reloadUpdate', + invoke: () => { + setText('reloadUpdate'); + setOptions({hash: Hash}); + }, + }, + { + name: 'setNeedUpdate', + invoke: () => { + setText('setNeedUpdate'); + setOptions({hash: Hash}); + }, + }, + { + name: 'markSuccess', + invoke: () => { + setText('markSuccess'); + }, + }, + { + name: 'downloadPatchFromPpk', + invoke: () => { + setText('downloadPatchFromPpk'); + setOptions({updateUrl: DownloadUrl, hash: Hash, originHash: Hash}); + }, + }, + { + name: 'downloadPatchFromPackage', + invoke: () => { + setText('downloadPatchFromPackage'); + setOptions({updateUrl: DownloadUrl, hash: Hash}); + }, + }, + { + name: 'downloadFullUpdate', + invoke: () => { + setText('downloadFullUpdate'); + setOptions({updateUrl: DownloadUrl, hash: Hash}); + }, + }, + { + name: 'downloadAndInstallApk', + invoke: () => { + setText('downloadAndInstallApk'); + setOptions({url: DownloadUrl, target: Hash, hash: Hash}); + }, + }, + ]; + }, []); + + const renderTestView = useCallback(() => { + const views = []; + for (let i = 0; i < NativeTestMethod.length; i++) { + views.push( + { + NativeTestMethod[i].invoke(); + }} + style={{width: 10, height: 10, backgroundColor: 'red'}} + />, + ); + } + return {views}; + }, [NativeTestMethod]); + return ( @@ -42,25 +145,27 @@ export default function TestConsole({visible}) { {running && }