diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml new file mode 100644 index 0000000000..e71792358f --- /dev/null +++ b/.github/workflows/e2e-test.yml @@ -0,0 +1,339 @@ +name: E2E Tests + +on: + pull_request: + + workflow_dispatch: + inputs: + ios: + description: "Run iOS e2e tests" + type: boolean + default: false + android: + description: "Run Android e2e tests" + type: boolean + default: true + mock_server_branch: + description: "stream-chat-test-mock-server branch" + type: string + default: "main" + +permissions: + contents: read + pull-requests: read + checks: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + MOCK_SERVER_BRANCH: ${{ inputs.mock_server_branch || 'main' }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_PR_NUM: ${{ github.event.pull_request.number }} + GITHUB_EVENT_ACTION: ${{ github.event.action }} + GITHUB_EVENT_BEFORE: ${{ github.event.before }} + GITHUB_EVENT_AFTER: ${{ github.event.after }} + # node_modules of the e2e app and the workspaces it depends on (nmHoistingLimits: workspaces + # keeps them apart); `yarn workspaces focus` installs nothing else. + NODE_MODULES_PATHS: | + node_modules + configs/*/node_modules + package/node_modules + package/native-package/node_modules + examples/E2ETestApp/node_modules + .yarn/install-state.gz + +jobs: + check: + name: Check E2E Required + runs-on: ubuntu-latest + timeout-minutes: 10 + outputs: + required: ${{ steps.check.outputs.required }} + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/ruby-cache + - name: Check if e2e tests are required + id: check + working-directory: examples/SampleApp + run: bundle exec fastlane e2e_check_required + + ios: + needs: check + if: needs.check.outputs.required == 'true' && github.event_name == 'workflow_dispatch' && inputs.ios + runs-on: macos-15 + timeout-minutes: 60 + env: + DETOX_IOS_DEVICE: "iPhone 17 Pro" + IOS_VERSION: "26.2" + XCODE_VERSION: "26.2" + APP_PATH: examples/E2ETestApp/ios/build/detox/Build/Products/Release-iphonesimulator/E2ETestApp.app + MOCK_SERVER_BUNDLE_PATH: ${{ github.workspace }}/../mock-server-gems + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: 24.x + cache: "yarn" + cache-dependency-path: "yarn.lock" + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: ${{ env.XCODE_VERSION }} + # A fresh simulator takes minutes to boot on CI; boot it now so that overlaps with setup. + - name: Boot simulator + run: | + runtime="com.apple.CoreSimulator.SimRuntime.iOS-${IOS_VERSION//./-}" + udid=$(xcrun simctl list devices available -j | + jq -r --arg rt "$runtime" --arg name "$DETOX_IOS_DEVICE" '.devices[$rt][]? | select(.name == $name) | .udid' | head -1) + if [ -z "$udid" ]; then + echo "::warning::No $DETOX_IOS_DEVICE on iOS $IOS_VERSION; Detox will pick and boot a simulator itself" + exit 0 + fi + echo "DETOX_IOS_UDID=$udid" >> "$GITHUB_ENV" + nohup xcrun simctl boot "$udid" > /dev/null 2>&1 & + - uses: ./.github/actions/ruby-cache + + # --- JS dependencies --- + - name: Restore node_modules + id: node-modules + uses: actions/cache/restore@v5 + with: + path: ${{ env.NODE_MODULES_PATHS }} + key: ${{ runner.os }}-e2e-node-modules-${{ hashFiles('yarn.lock') }} + # Only the e2e app's dependency tree, not every example app. + - name: Install dependencies + run: yarn workspaces focus e2etestapp + - name: Save node_modules + if: steps.node-modules.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: ${{ env.NODE_MODULES_PATHS }} + key: ${{ steps.node-modules.outputs.cache-primary-key }} + + # --- App binary: rebuilt only when the app or SDK sources change, not for test-only changes --- + - name: Restore app + id: app + uses: actions/cache/restore@v5 + with: + path: ${{ env.APP_PATH }} + key: e2e-ios-app-xcode${{ env.XCODE_VERSION }}-${{ hashFiles('yarn.lock', 'package/package.json', 'package/src/**', 'package/shared-native/**', 'package/native-package/package.json', 'package/native-package/src/**', 'package/native-package/ios/**', 'package/native-package/*.podspec', 'examples/SampleApp/Gemfile.lock', 'examples/E2ETestApp/*.js', '!examples/E2ETestApp/.detoxrc.js', 'examples/E2ETestApp/*.json', 'examples/E2ETestApp/*.tsx', 'examples/E2ETestApp/src/**', 'examples/E2ETestApp/ios/**', '!examples/E2ETestApp/ios/build/**', '!examples/E2ETestApp/ios/Pods/**') }} + - name: Set up ccache + if: steps.app.outputs.cache-hit != 'true' + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: e2e-ios-xcode${{ env.XCODE_VERSION }} + max-size: 1500M + - name: Point the RN ccache wrappers at ccache + if: steps.app.outputs.cache-hit != 'true' + # RN's ccache-clang.sh runs `$CCACHE_BINARY clang`, but Xcode doesn't export that build + # setting to the compiler, so without this every compile silently skips ccache. + run: echo "CCACHE_BINARY=$(which ccache)" >> "$GITHUB_ENV" + - name: Cache pods + if: steps.app.outputs.cache-hit != 'true' + uses: actions/cache@v5 + with: + path: examples/E2ETestApp/ios/Pods + key: ${{ runner.os }}-e2e-pods-${{ hashFiles('examples/E2ETestApp/ios/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-e2e-pods- + - name: Install pods + if: steps.app.outputs.cache-hit != 'true' + working-directory: examples/SampleApp + run: USE_CCACHE=1 bundle exec fastlane e2e_pod_install + - name: Build app + if: steps.app.outputs.cache-hit != 'true' + working-directory: examples/E2ETestApp + run: yarn e2e:build -c ios.sim.release + - name: Save app + if: steps.app.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: ${{ env.APP_PATH }} + key: ${{ steps.app.outputs.cache-primary-key }} + + # --- Test tooling --- + - name: Install applesimutils + # Homebrew refuses formulae from non-official taps until they are trusted. + run: brew tap wix/brew && brew trust --formula wix/brew/applesimutils && brew install wix/brew/applesimutils + - name: Restore Detox framework + id: detox-framework + uses: actions/cache/restore@v5 + with: + path: ~/Library/Detox + key: ${{ runner.os }}-detox-framework-xcode${{ env.XCODE_VERSION }}-${{ hashFiles('yarn.lock') }} + # Yarn's `enableScripts: false` skips Detox's postinstall, which would build this. + - name: Build Detox framework + if: steps.detox-framework.outputs.cache-hit != 'true' + working-directory: examples/E2ETestApp + run: yarn detox build-framework-cache + - name: Save Detox framework + if: steps.detox-framework.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: ~/Library/Detox + key: ${{ steps.detox-framework.outputs.cache-primary-key }} + - name: Resolve mock server revision + id: mock-server + run: echo "sha=$(git ls-remote https://github.com/GetStream/stream-chat-test-mock-server "refs/heads/$MOCK_SERVER_BRANCH" | cut -f1)" >> "$GITHUB_OUTPUT" + - name: Cache mock server gems + uses: actions/cache@v5 + with: + path: ${{ env.MOCK_SERVER_BUNDLE_PATH }} + key: ${{ runner.os }}-mock-server-gems-${{ steps.mock-server.outputs.sha }} + restore-keys: | + ${{ runner.os }}-mock-server-gems- + + - name: Wait for the simulator + if: env.DETOX_IOS_UDID != '' + timeout-minutes: 15 + # `-b` boots it if the background boot never started; it errors (405) when the device is + # already booted, and then the plain call returns at once. + run: xcrun simctl bootstatus "$DETOX_IOS_UDID" -b || xcrun simctl bootstatus "$DETOX_IOS_UDID" + - name: Run e2e tests + working-directory: examples/SampleApp + run: bundle exec fastlane test_e2e platform:ios skip_build:true mock_server_branch:"$MOCK_SERVER_BRANCH" + - uses: actions/upload-artifact@v6 + if: failure() + with: + name: e2e-ios-logs + path: | + examples/E2ETestApp/stream-chat-test-mock-server/logs + examples/E2ETestApp/artifacts + + android: + needs: check + if: needs.check.outputs.required == 'true' && (github.event_name != 'workflow_dispatch' || inputs.android) + runs-on: ubuntu-latest + timeout-minutes: 60 + env: + # Same as the native Android SDK e2e jobs, which run stably on these runners + ANDROID_API_LEVEL: "36" + ANDROID_EMULATOR_OPTIONS: -no-snapshot-save -no-window -no-audio -no-boot-anim -gpu swiftshader_indirect -camera-back none -camera-front none + DETOX_ADB_NAME: "emulator-5554" + APK_PATHS: | + examples/E2ETestApp/android/app/build/outputs/apk/release/app-release.apk + examples/E2ETestApp/android/app/build/outputs/apk/androidTest/release/app-release-androidTest.apk + MOCK_SERVER_BUNDLE_PATH: ${{ github.workspace }}/../mock-server-gems + # The emulator is x86_64: skip compiling native code for the other ABIs. + ORG_GRADLE_PROJECT_reactNativeArchitectures: x86_64 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: 24.x + cache: "yarn" + cache-dependency-path: "yarn.lock" + - uses: actions/setup-java@v5 + with: + distribution: "zulu" + java-version: "21" + # Caches Gradle's dependencies, wrapper and build cache. It only writes on the default branch by + # default, and this workflow doesn't run there, so let PR runs write: their reruns reuse it. + - uses: gradle/actions/setup-gradle@v4 + with: + cache-read-only: false + - uses: ./.github/actions/ruby-cache + + # --- JS dependencies --- + - name: Restore node_modules + id: node-modules + uses: actions/cache/restore@v5 + with: + path: ${{ env.NODE_MODULES_PATHS }} + key: ${{ runner.os }}-e2e-node-modules-${{ hashFiles('yarn.lock') }} + # Only the e2e app's dependency tree, not every example app. + - name: Install dependencies + run: yarn workspaces focus e2etestapp + - name: Save node_modules + if: steps.node-modules.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: ${{ env.NODE_MODULES_PATHS }} + key: ${{ steps.node-modules.outputs.cache-primary-key }} + + # --- App binaries: rebuilt only when the app or SDK sources change, not for test-only changes --- + - name: Restore APKs + id: apk + uses: actions/cache/restore@v5 + with: + path: ${{ env.APK_PATHS }} + key: e2e-android-apk-${{ hashFiles('yarn.lock', 'package/package.json', 'package/src/**', 'package/shared-native/**', 'package/native-package/package.json', 'package/native-package/src/**', 'package/native-package/android/**', '!package/native-package/android/build/**', 'examples/E2ETestApp/*.js', '!examples/E2ETestApp/.detoxrc.js', 'examples/E2ETestApp/*.json', 'examples/E2ETestApp/*.tsx', 'examples/E2ETestApp/src/**', 'examples/E2ETestApp/android/**', '!examples/E2ETestApp/android/**/build/**', '!examples/E2ETestApp/android/.gradle/**') }} + # Built before the emulator boots so the emulator doesn't idle through the Gradle build. + - name: Build app + if: steps.apk.outputs.cache-hit != 'true' + working-directory: examples/E2ETestApp + run: yarn e2e:build -c android.att.release + - name: Save APKs + if: steps.apk.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: ${{ env.APK_PATHS }} + key: ${{ steps.apk.outputs.cache-primary-key }} + # Frees the daemons' memory for the emulator, which otherwise can get killed mid-run. + - name: Stop Gradle daemons + if: steps.apk.outputs.cache-hit != 'true' + working-directory: examples/E2ETestApp/android + run: ./gradlew --stop + + # --- Emulator: same setup as the native Android SDK's e2e jobs (AOSP image, no Play services) --- + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + # --- Test tooling --- + - name: Resolve mock server revision + id: mock-server + run: echo "sha=$(git ls-remote https://github.com/GetStream/stream-chat-test-mock-server "refs/heads/$MOCK_SERVER_BRANCH" | cut -f1)" >> "$GITHUB_OUTPUT" + - name: Cache mock server gems + uses: actions/cache@v5 + with: + path: ${{ env.MOCK_SERVER_BUNDLE_PATH }} + key: ${{ runner.os }}-mock-server-gems-${{ steps.mock-server.outputs.sha }} + restore-keys: | + ${{ runner.os }}-mock-server-gems- + + - name: Run e2e tests (Android emulator) + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ env.ANDROID_API_LEVEL }} + arch: x86_64 + profile: pixel + emulator-options: ${{ env.ANDROID_EMULATOR_OPTIONS }} + disable-animations: true + working-directory: examples/SampleApp + script: bundle exec fastlane test_e2e platform:android device:att skip_build:true mock_server_branch:"$MOCK_SERVER_BRANCH" + # The emulator has vanished mid-run without any output; this shows whether the kernel killed it. + - name: Diagnose emulator + if: failure() + run: | + sudo dmesg -T | grep -i -E 'out of memory|oom-kill|killed process' || echo "No OOM kills in dmesg" + free -m + df -h + ls -la /tmp/android-runner 2>/dev/null || true + - uses: actions/upload-artifact@v6 + if: failure() + with: + name: e2e-android-logs + path: | + examples/E2ETestApp/stream-chat-test-mock-server/logs + examples/E2ETestApp/artifacts + /tmp/android-runner + + result: + name: Test E2E Result + runs-on: ubuntu-latest + if: always() + needs: + - check + - android + steps: + - name: Check E2E results + if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') + run: | + echo "$NEEDS" + exit 1 + env: + NEEDS: ${{ toJson(needs) }} diff --git a/.prettierignore b/.prettierignore index c4f36f8ee4..55d7ebb190 100644 --- a/.prettierignore +++ b/.prettierignore @@ -24,6 +24,10 @@ examples/SampleApp/patches/ .yarn/ yarn.lock +# Detox e2e: mock server checkout cloned by `fastlane start_mock_server`, test artifacts +examples/E2ETestApp/stream-chat-test-mock-server/ +examples/E2ETestApp/artifacts/ + # Generated SDK assets package/src/components/docs/ package/src/theme/generated/ diff --git a/AGENTS.md b/AGENTS.md index be4dd9dce5..fa8cbbd6b9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ Targets iOS and Android. - **Languages:** TypeScript + React Native - **Runtime:** Node 24 (`.nvmrc` is `v24`; root `engines.node` is `>=20.19.4`; CI runs `24.x`) - **Package manager:** Yarn 4.15.0 (Berry). The binary lives at `.yarn/releases/yarn-4.15.0.cjs` and is activated via `yarnPath` in `.yarnrc.yml`. Any globally installed `yarn` (even the Homebrew classic 1.x) acts only as a launcher — no Corepack required. -- **Workspaces:** single root `yarn.lock`; workspaces are `configs/typescript-config`, `package`, `package/native-package`, `package/expo-package`, `examples/SampleApp`, `examples/ExpoMessaging`. **No Lerna.** +- **Workspaces:** single root `yarn.lock`; workspaces are `configs/typescript-config`, `package`, `package/native-package`, `package/expo-package`, `examples/SampleApp`, `examples/E2ETestApp`, `examples/ExpoMessaging`. **No Lerna.** - **Testing:** Jest with the `@react-native/jest-preset` + `@testing-library/react-native`. - **Build:** `react-native-builder-bob` → CommonJS (`lib/commonjs`), ESM (`lib/module`), types (`lib/typescript`) - **Lint/format:** ESLint 9 flat config + Prettier, strict (`--max-warnings 0`) @@ -41,7 +41,7 @@ Respect repo-specific rules. Do not suppress lint rules broadly; justify and sco - `native-package/` — bare RN wrapper (`stream-chat-react-native`) - `expo-package/` — Expo wrapper (`stream-chat-expo`) - `shared-native/{ios,android}` — native source shared by both wrappers; synced into them, never edited in place -- `examples/` — `SampleApp` (full-featured), `ExpoMessaging` +- `examples/` — `SampleApp` (full-featured), `E2ETestApp` (minimal app for the Detox e2e suite), `ExpoMessaging` - `configs/typescript-config/` — shared `tsconfig` presets - `ai-docs/` — agent-facing deep dives (see [References](#references)) - `perf/` — on-device performance toolkit (see [Accessibility, RTL & performance](#accessibility-rtl--performance)) @@ -244,6 +244,8 @@ Native pieces live in `package/shared-native/`: the Android-only `PerfText` Fabr To run one test file, prefer `cd package && TZ=UTC npx jest path/to/file.test.tsx`. The `testRegex` array in `jest.config.js` also accepts a temporary path — revert it before committing. +**E2E:** Detox suite in `examples/E2ETestApp/e2e/` against the shared `stream-chat-test-mock-server`, with robots and page objects mirroring the native iOS/Android suites. Run it with `bundle exec fastlane test_e2e platform:ios|android` from `examples/SampleApp`; see `examples/E2ETestApp/e2e/README.md`. Elements it needs get a `testID` in the SDK (kebab-case, additive). + **Mock builders** (`package/src/mock-builders/`): - `api/initiateClientWithChannels` — creates a test client + channels in one call (fastest path) @@ -369,7 +371,7 @@ Follow `PULL_REQUEST_TEMPLATE.md`. Keep PRs small and focused. ### CI expectations -`.github/workflows/check-pr.yml` (Node 24): `yarn install --immutable` → `yarn build` → `yarn lint` → `yarn typecheck` → `yarn test:coverage`. Other workflows: `changelog-preview`, `lint-pr-title`, `release`, `sample-distribution`, `sdk-size-metrics`. +`.github/workflows/check-pr.yml` (Node 24): `yarn install --immutable` → `yarn build` → `yarn lint` → `yarn typecheck` → `yarn test:coverage`. Other workflows: `changelog-preview`, `e2e-test` (Detox on `examples/E2ETestApp`, gated by `is_check_required`; require its `Test E2E Result` check), `lint-pr-title`, `release`, `sample-distribution`, `sdk-size-metrics`. Failing or flaky tests: fix them, or quarantine with a justification comment and a follow-up. diff --git a/eslint.config.mjs b/eslint.config.mjs index 97425a89b6..468d782eab 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -44,6 +44,10 @@ export default tsEslint.config( '**/android/', '**/fastlane/', + // Detox e2e: mock server checkout cloned by `fastlane start_mock_server`, test artifacts + 'examples/E2ETestApp/stream-chat-test-mock-server/', + 'examples/E2ETestApp/artifacts/', + // Generated SDK assets 'package/src/components/docs/', 'package/src/theme/generated/', diff --git a/examples/E2ETestApp/.detoxrc.js b/examples/E2ETestApp/.detoxrc.js new file mode 100644 index 0000000000..2e090e80c7 --- /dev/null +++ b/examples/E2ETestApp/.detoxrc.js @@ -0,0 +1,67 @@ +/** @type {Detox.DetoxConfig} */ +module.exports = { + testRunner: { + args: { + $0: 'jest', + config: 'e2e/jest.config.js', + }, + jest: { + setupTimeout: 300000, + }, + }, + apps: { + 'ios.debug': { + type: 'ios.app', + binaryPath: 'ios/build/detox/Build/Products/Debug-iphonesimulator/E2ETestApp.app', + build: + 'xcodebuild -workspace ios/E2ETestApp.xcworkspace -scheme E2ETestApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build/detox', + }, + 'ios.release': { + type: 'ios.app', + binaryPath: 'ios/build/detox/Build/Products/Release-iphonesimulator/E2ETestApp.app', + build: + 'xcodebuild -workspace ios/E2ETestApp.xcworkspace -scheme E2ETestApp -configuration Release -sdk iphonesimulator -derivedDataPath ios/build/detox', + }, + 'android.debug': { + type: 'android.apk', + binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk', + testBinaryPath: 'android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk', + build: + 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..', + reversePorts: [8081], + }, + 'android.release': { + type: 'android.apk', + binaryPath: 'android/app/build/outputs/apk/release/app-release.apk', + testBinaryPath: + 'android/app/build/outputs/apk/androidTest/release/app-release-androidTest.apk', + build: + 'cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ..', + }, + }, + devices: { + simulator: { + type: 'ios.simulator', + // CI pins the simulator it boots early (DETOX_IOS_UDID), so Detox reuses it. + device: process.env.DETOX_IOS_UDID + ? { id: process.env.DETOX_IOS_UDID } + : { type: process.env.DETOX_IOS_DEVICE || 'iPhone 16 Pro' }, + }, + emulator: { + type: 'android.emulator', + device: { avdName: process.env.DETOX_AVD_NAME || 'Pixel_API_37' }, + }, + attached: { + type: 'android.attached', + device: { adbName: process.env.DETOX_ADB_NAME || '.*' }, + }, + }, + configurations: { + 'ios.sim.debug': { app: 'ios.debug', device: 'simulator' }, + 'ios.sim.release': { app: 'ios.release', device: 'simulator' }, + 'android.emu.debug': { app: 'android.debug', device: 'emulator' }, + 'android.emu.release': { app: 'android.release', device: 'emulator' }, + 'android.att.debug': { app: 'android.debug', device: 'attached' }, + 'android.att.release': { app: 'android.release', device: 'attached' }, + }, +}; diff --git a/examples/E2ETestApp/.gitignore b/examples/E2ETestApp/.gitignore new file mode 100644 index 0000000000..9be6361de6 --- /dev/null +++ b/examples/E2ETestApp/.gitignore @@ -0,0 +1,79 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +**/.xcode.env.local + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml +*.hprof +.cxx/ +*.keystore +!debug.keystore +.kotlin/ + +# node.js +# +node_modules/ +npm-debug.log +yarn-error.log + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ + +**/fastlane/report.xml +**/fastlane/Preview.html +**/fastlane/screenshots +**/fastlane/test_output + +# Bundle artifact +*.jsbundle + +# Ruby / CocoaPods +**/Pods/ +/vendor/bundle/ + +# Temporary files created by Metro to check the health of the file watcher +.metro-health-check* + +# testing +/coverage + +# Yarn +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Detox e2e: mock server checkout cloned by `fastlane start_mock_server`, test artifacts +/stream-chat-test-mock-server/ +/artifacts/ diff --git a/examples/E2ETestApp/.watchmanconfig b/examples/E2ETestApp/.watchmanconfig new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/examples/E2ETestApp/.watchmanconfig @@ -0,0 +1 @@ +{} diff --git a/examples/E2ETestApp/App.tsx b/examples/E2ETestApp/App.tsx new file mode 100644 index 0000000000..653961dc5f --- /dev/null +++ b/examples/E2ETestApp/App.tsx @@ -0,0 +1,133 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { BackHandler, Pressable, StyleSheet, Text, View } from 'react-native'; +import { GestureHandlerRootView } from 'react-native-gesture-handler'; +import { SafeAreaProvider, SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { Channel as ChannelType, ChannelFilters, ChannelSort, StreamChat } from 'stream-chat'; +import { + Channel, + ChannelList, + Chat, + MessageComposer, + MessageList, + OverlayProvider, +} from 'stream-chat-react-native'; + +import { apiKey, appUser, appUserToken, mockServerUrl } from './src/config'; + +// Same opt-in as the SampleApp, so the e2e suite sees the same accessibility tree. +const accessibility = { enabled: true }; +const sort: ChannelSort = { last_message_at: -1 }; + +/** Lists the mock server's app user, like the SampleApp's user selector. */ +const LoginScreen = ({ onLogin }: { onLogin: () => void }) => ( + + E2E Test App + {mockServerUrl ? ( + + {appUser.name} + + ) : ( + Launch with the MOCK_SERVER_URL argument (see e2e/README.md). + )} + +); + +const ChannelScreen = ({ channel, onBack }: { channel: ChannelType; onBack: () => void }) => { + const { top } = useSafeAreaInsets(); + + useEffect(() => { + const subscription = BackHandler.addEventListener('hardwareBackPress', () => { + onBack(); + return true; + }); + return () => subscription.remove(); + }, [onBack]); + + // Laid out like the SampleApp's channel screen: the header lives inside , which sizes + // itself to the screen and handles the bottom inset and keyboard for the composer. + return ( + + + + + ‹ Back + + + {channel.id} + + + + + + + ); +}; + +const ChatScreens = ({ client }: { client: StreamChat }) => { + const [channel, setChannel] = useState(); + const filters = useMemo( + () => ({ members: { $in: [client.userID ?? appUser.id] } }), + [client.userID], + ); + const closeChannel = useCallback(() => setChannel(undefined), []); + + return ( + + {/* The list stays mounted under the channel, as in a navigation stack. */} + + + + {channel ? : null} + + ); +}; + +const App = () => { + const [client, setClient] = useState(); + + const login = useCallback(async () => { + const chatClient = StreamChat.getInstance(apiKey, { baseURL: mockServerUrl, timeout: 6000 }); + await chatClient.connectUser(appUser, appUserToken); + setClient(chatClient); + }, []); + + return ( + + + + {client ? : } + + + + ); +}; + +const styles = StyleSheet.create({ + back: { fontSize: 17, paddingRight: 12 }, + container: { backgroundColor: '#FFFFFF', flex: 1 }, + flex: { flex: 1 }, + header: { + alignItems: 'center', + flexDirection: 'row', + paddingHorizontal: 16, + paddingVertical: 12, + }, + headerTitle: { flex: 1, fontSize: 17, fontWeight: '600' }, + hint: { padding: 16 }, + title: { fontSize: 22, fontWeight: '700', padding: 16 }, + userName: { fontSize: 17 }, + userRow: { borderBottomColor: '#E5E5E5', borderBottomWidth: 1, padding: 16 }, +}); + +export default App; diff --git a/examples/E2ETestApp/android/app/build.gradle b/examples/E2ETestApp/android/app/build.gradle new file mode 100644 index 0000000000..879f64489c --- /dev/null +++ b/examples/E2ETestApp/android/app/build.gradle @@ -0,0 +1,124 @@ +apply plugin: "com.android.application" +apply plugin: "org.jetbrains.kotlin.android" +apply plugin: "com.facebook.react" + +/** + * This is the configuration block to customize your React Native Android app. + * By default you don't need to apply any configuration, just uncomment the lines you need. + */ +react { + /* Folders */ + // The root of your project, i.e. where "package.json" lives. Default is '../..' + // root = file("../../") + // The folder where the react-native NPM package is. Default is ../../node_modules/react-native + // reactNativeDir = file("../../node_modules/react-native") + // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen + // codegenDir = file("../../node_modules/@react-native/codegen") + // The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js + // cliFile = file("../../node_modules/react-native/cli.js") + + /* Variants */ + // The list of variants to that are debuggable. For those we're going to + // skip the bundling of the JS bundle and the assets. Default is "debug", "debugOptimized". + // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. + // debuggableVariants = ["liteDebug", "liteDebugOptimized", "prodDebug", "prodDebugOptimized"] + + /* Bundling */ + // A list containing the node command and its flags. Default is just 'node'. + // nodeExecutableAndArgs = ["node"] + // + // The command to run when bundling. By default is 'bundle' + // bundleCommand = "ram-bundle" + // + // The path to the CLI configuration file. Default is empty. + // bundleConfig = file(../rn-cli.config.js) + // + // The name of the generated asset file containing your JS bundle + // bundleAssetName = "MyApplication.android.bundle" + // + // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' + // entryFile = file("../js/MyApplication.android.js") + // + // A list of extra flags to pass to the 'bundle' commands. + // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle + // extraPackagerArgs = [] + + /* Hermes Commands */ + // The hermes compiler command to run. By default it is 'hermesc' + // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" + // + // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" + // hermesFlags = ["-O", "-output-source-map"] + + /* Autolinking */ + autolinkLibrariesWithApp() +} + +/** + * Set this to true to Run Proguard on Release builds to minify the Java bytecode. + */ +def enableProguardInReleaseBuilds = false + +/** + * The preferred build flavor of JavaScriptCore (JSC) + * + * For example, to use the international variant, you can use: + * `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+` + * + * The international variant includes ICU i18n library and necessary data + * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that + * give correct results when using with locales other than en-US. Note that + * this variant is about 6MiB larger per architecture than default. + */ +def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' + +android { + ndkVersion rootProject.ext.ndkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + compileSdk rootProject.ext.compileSdkVersion + + namespace "io.getstream.reactnative.e2etestapp" + defaultConfig { + applicationId "io.getstream.reactnative.e2etestapp" + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "1.0" + // Detox: builds the instrumentation APK against the variant under test + testBuildType System.getProperty('testBuildType', 'debug') + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + } + signingConfigs { + debug { + storeFile file('debug.keystore') + storePassword 'android' + keyAlias 'androiddebugkey' + keyPassword 'android' + } + } + buildTypes { + debug { + signingConfig signingConfigs.debug + } + release { + // Caution! In production, you need to generate your own keystore file. + // see https://reactnative.dev/docs/signed-apk-android. + signingConfig signingConfigs.debug + minifyEnabled enableProguardInReleaseBuilds + proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" + } + } +} + +dependencies { + // The version of react-native is set by the React Native Gradle Plugin + implementation("com.facebook.react:react-android") + + if (hermesEnabled.toBoolean()) { + implementation("com.facebook.react:hermes-android") + } else { + implementation jscFlavor + } + + androidTestImplementation('com.wix:detox:+') +} diff --git a/examples/E2ETestApp/android/app/debug.keystore b/examples/E2ETestApp/android/app/debug.keystore new file mode 100644 index 0000000000..364e105ed3 Binary files /dev/null and b/examples/E2ETestApp/android/app/debug.keystore differ diff --git a/examples/E2ETestApp/android/app/proguard-rules.pro b/examples/E2ETestApp/android/app/proguard-rules.pro new file mode 100644 index 0000000000..11b025724a --- /dev/null +++ b/examples/E2ETestApp/android/app/proguard-rules.pro @@ -0,0 +1,10 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: diff --git a/examples/E2ETestApp/android/app/src/androidTest/java/io/getstream/reactnative/e2etestapp/DetoxTest.kt b/examples/E2ETestApp/android/app/src/androidTest/java/io/getstream/reactnative/e2etestapp/DetoxTest.kt new file mode 100644 index 0000000000..18e8533735 --- /dev/null +++ b/examples/E2ETestApp/android/app/src/androidTest/java/io/getstream/reactnative/e2etestapp/DetoxTest.kt @@ -0,0 +1,27 @@ +package io.getstream.reactnative.e2etestapp + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import androidx.test.rule.ActivityTestRule +import com.wix.detox.Detox +import com.wix.detox.config.DetoxConfig +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@LargeTest +class DetoxTest { + @get:Rule + val activityRule = ActivityTestRule(MainActivity::class.java, false, false) + + @Test + fun runDetoxTests() { + val detoxConfig = DetoxConfig().apply { + idlePolicyConfig.masterTimeoutSec = 90 + idlePolicyConfig.idleResourceTimeoutSec = 60 + rnContextLoadTimeoutSec = if (BuildConfig.DEBUG) 180 else 60 + } + Detox.runTests(activityRule, detoxConfig) + } +} diff --git a/examples/E2ETestApp/android/app/src/main/AndroidManifest.xml b/examples/E2ETestApp/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..d5ac9c6e7c --- /dev/null +++ b/examples/E2ETestApp/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/examples/E2ETestApp/android/app/src/main/java/io/getstream/reactnative/e2etestapp/MainActivity.kt b/examples/E2ETestApp/android/app/src/main/java/io/getstream/reactnative/e2etestapp/MainActivity.kt new file mode 100644 index 0000000000..b59b475ece --- /dev/null +++ b/examples/E2ETestApp/android/app/src/main/java/io/getstream/reactnative/e2etestapp/MainActivity.kt @@ -0,0 +1,22 @@ +package io.getstream.reactnative.e2etestapp + +import com.facebook.react.ReactActivity +import com.facebook.react.ReactActivityDelegate +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled +import com.facebook.react.defaults.DefaultReactActivityDelegate + +class MainActivity : ReactActivity() { + + /** + * Returns the name of the main component registered from JavaScript. This is used to schedule + * rendering of the component. + */ + override fun getMainComponentName(): String = "E2ETestApp" + + /** + * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] + * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] + */ + override fun createReactActivityDelegate(): ReactActivityDelegate = + DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) +} diff --git a/examples/E2ETestApp/android/app/src/main/java/io/getstream/reactnative/e2etestapp/MainApplication.kt b/examples/E2ETestApp/android/app/src/main/java/io/getstream/reactnative/e2etestapp/MainApplication.kt new file mode 100644 index 0000000000..dd573a42aa --- /dev/null +++ b/examples/E2ETestApp/android/app/src/main/java/io/getstream/reactnative/e2etestapp/MainApplication.kt @@ -0,0 +1,27 @@ +package io.getstream.reactnative.e2etestapp + +import android.app.Application +import com.facebook.react.PackageList +import com.facebook.react.ReactApplication +import com.facebook.react.ReactHost +import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative +import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost + +class MainApplication : Application(), ReactApplication { + + override val reactHost: ReactHost by lazy { + getDefaultReactHost( + context = applicationContext, + packageList = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + // add(MyReactNativePackage()) + }, + ) + } + + override fun onCreate() { + super.onCreate() + loadReactNative(this) + } +} diff --git a/examples/E2ETestApp/android/app/src/main/res/drawable/rn_edit_text_material.xml b/examples/E2ETestApp/android/app/src/main/res/drawable/rn_edit_text_material.xml new file mode 100644 index 0000000000..5c25e728ea --- /dev/null +++ b/examples/E2ETestApp/android/app/src/main/res/drawable/rn_edit_text_material.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + diff --git a/examples/E2ETestApp/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/E2ETestApp/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..a2f5908281 Binary files /dev/null and b/examples/E2ETestApp/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/examples/E2ETestApp/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/examples/E2ETestApp/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000..1b52399808 Binary files /dev/null and b/examples/E2ETestApp/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/examples/E2ETestApp/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/E2ETestApp/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ff10afd6e1 Binary files /dev/null and b/examples/E2ETestApp/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/examples/E2ETestApp/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/examples/E2ETestApp/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000..115a4c768a Binary files /dev/null and b/examples/E2ETestApp/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/examples/E2ETestApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/E2ETestApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..dcd3cd8083 Binary files /dev/null and b/examples/E2ETestApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/examples/E2ETestApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/examples/E2ETestApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..459ca609d3 Binary files /dev/null and b/examples/E2ETestApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/examples/E2ETestApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/E2ETestApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..8ca12fe024 Binary files /dev/null and b/examples/E2ETestApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/examples/E2ETestApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/examples/E2ETestApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..8e19b410a1 Binary files /dev/null and b/examples/E2ETestApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/examples/E2ETestApp/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/examples/E2ETestApp/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..b824ebdd48 Binary files /dev/null and b/examples/E2ETestApp/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/examples/E2ETestApp/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/examples/E2ETestApp/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..4c19a13c23 Binary files /dev/null and b/examples/E2ETestApp/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/examples/E2ETestApp/android/app/src/main/res/values/strings.xml b/examples/E2ETestApp/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000000..a0ea08d876 --- /dev/null +++ b/examples/E2ETestApp/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + E2ETestApp + diff --git a/examples/E2ETestApp/android/app/src/main/res/values/styles.xml b/examples/E2ETestApp/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000000..7ba83a2ad5 --- /dev/null +++ b/examples/E2ETestApp/android/app/src/main/res/values/styles.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/examples/E2ETestApp/android/app/src/main/res/xml/network_security_config.xml b/examples/E2ETestApp/android/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000000..a667072fee --- /dev/null +++ b/examples/E2ETestApp/android/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,8 @@ + + + + + 10.0.2.2 + localhost + + diff --git a/examples/E2ETestApp/android/build.gradle b/examples/E2ETestApp/android/build.gradle new file mode 100644 index 0000000000..49baeef939 --- /dev/null +++ b/examples/E2ETestApp/android/build.gradle @@ -0,0 +1,34 @@ +buildscript { + ext { + buildToolsVersion = "37.0.0" + minSdkVersion = 24 + compileSdkVersion = 37 + targetSdkVersion = 36 + ndkVersion = "27.1.12297006" + kotlinVersion = "2.2.0" + } + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle") + classpath("com.facebook.react:react-native-gradle-plugin") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") + } +} + +ext.REACT_NATIVE_NODE_MODULES_DIR = file("$rootDir/../node_modules/react-native").absolutePath +ext.REACT_NATIVE_WORKLETS_NODE_MODULES_DIR = + file("$rootDir/../node_modules/react-native-worklets").absolutePath + +allprojects { + repositories { + // Detox's Android library ships inside its npm package + maven { + url "$rootDir/../node_modules/detox/Detox-android" + } + } +} + +apply plugin: "com.facebook.react.rootproject" diff --git a/examples/E2ETestApp/android/gradle.properties b/examples/E2ETestApp/android/gradle.properties new file mode 100644 index 0000000000..ab47cc1f7b --- /dev/null +++ b/examples/E2ETestApp/android/gradle.properties @@ -0,0 +1,54 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m +org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true + +# Use this property to specify which architecture you want to build. +# You can also override it from the CLI using +# ./gradlew -PreactNativeArchitectures=x86_64 +# Emulator ABIs only (arm64 for Apple Silicon, x86_64 for CI); CI narrows it to x86_64 with +# ORG_GRADLE_PROJECT_reactNativeArchitectures. +reactNativeArchitectures=arm64-v8a,x86_64 + +# Use this property to enable support to the new architecture. +# This will allow you to use TurboModules and the Fabric render in +# your application. You should enable this flag either if you want +# to write custom TurboModules/Fabric components OR use libraries that +# are providing them. +newArchEnabled=true + +# Use this property to enable or disable the Hermes JS engine. +# If set to false, you will be using JSC instead. +hermesEnabled=true + +# Use this property to enable edge-to-edge display support. +# This allows your app to draw behind system bars for an immersive UI. +# Note: Only works with ReactActivity and should not be used with custom Activity. +edgeToEdgeEnabled=true + +# Opt out of built-in kotlin and new DSL behavior that ships with AGP 9. +# Starting from AGP 10.x these opt outs will be removed. +android.builtInKotlin=false +android.newDsl=false +# Reuse task outputs across builds (and CI runs, via the Gradle cache) +org.gradle.caching=true +org.gradle.parallel=true diff --git a/examples/E2ETestApp/android/gradle/wrapper/gradle-wrapper.jar b/examples/E2ETestApp/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..d997cfc60f Binary files /dev/null and b/examples/E2ETestApp/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/E2ETestApp/android/gradle/wrapper/gradle-wrapper.properties b/examples/E2ETestApp/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..c61a118f7d --- /dev/null +++ b/examples/E2ETestApp/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/examples/E2ETestApp/android/gradlew b/examples/E2ETestApp/android/gradlew new file mode 100755 index 0000000000..739907dfd1 --- /dev/null +++ b/examples/E2ETestApp/android/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/E2ETestApp/android/gradlew.bat b/examples/E2ETestApp/android/gradlew.bat new file mode 100644 index 0000000000..39baf4d684 --- /dev/null +++ b/examples/E2ETestApp/android/gradlew.bat @@ -0,0 +1,98 @@ +@REM Copyright (c) Meta Platforms, Inc. and affiliates. +@REM +@REM This source code is licensed under the MIT license found in the +@REM LICENSE file in the root directory of this source tree. + +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/E2ETestApp/android/settings.gradle b/examples/E2ETestApp/android/settings.gradle new file mode 100644 index 0000000000..96b171f560 --- /dev/null +++ b/examples/E2ETestApp/android/settings.gradle @@ -0,0 +1,6 @@ +pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } +plugins { id("com.facebook.react.settings") } +extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } +rootProject.name = 'io.getstream.reactnative.e2etestapp' +include ':app' +includeBuild('../node_modules/@react-native/gradle-plugin') diff --git a/examples/E2ETestApp/app.json b/examples/E2ETestApp/app.json new file mode 100644 index 0000000000..73849fcf35 --- /dev/null +++ b/examples/E2ETestApp/app.json @@ -0,0 +1,4 @@ +{ + "name": "E2ETestApp", + "displayName": "E2ETestApp" +} diff --git a/examples/E2ETestApp/babel.config.js b/examples/E2ETestApp/babel.config.js new file mode 100644 index 0000000000..8ba8eb658c --- /dev/null +++ b/examples/E2ETestApp/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: ['module:@react-native/babel-preset'], + plugins: ['react-native-worklets/plugin'], +}; diff --git a/examples/E2ETestApp/e2e/README.md b/examples/E2ETestApp/e2e/README.md new file mode 100644 index 0000000000..7088447cf3 --- /dev/null +++ b/examples/E2ETestApp/e2e/README.md @@ -0,0 +1,119 @@ +# E2E tests (Detox) + +End-to-end tests for the SDK, driving `examples/E2ETestApp` with [Detox](https://wix.github.io/Detox/) against the shared +[`stream-chat-test-mock-server`](https://github.com/GetStream/stream-chat-test-mock-server). The layout mirrors the +native iOS/Android (and Flutter) suites, so tests can be ported almost line by line: + +| Path | What | Native counterpart | +| ------------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------- | +| `mock-server/` | `MockServer` (driver `/start/:test` → per-test server), `DataTypes` | `MockServer.kt`, `StreamMockServer.swift` | +| `robots/BackendRobot.ts` | server-side state via test-control endpoints (`/mock`, `/add_member`, `/jwt/*`, …) | `BackendRobot` | +| `robots/ParticipantRobot.ts` | the other user, `count_dooku` (`/participant/*`) | `ParticipantRobot` | +| `robots/UserRobot.ts` + `UserRobot*Asserts` | UI actions as the app user + assert extensions | `UserRobot` + `UserRobot*Asserts.kt` | +| `pages/` | page objects: `LoginPage`, `ChannelListPage`, `MessageListPage` | `pages/*Page.kt`, `Pages/*Page.swift` | +| `support/` | `setUpStreamTestCase()`, `step()`, uiautomator-like `Element` helpers | `StreamTestCase`, `step {}`, `uiautomator/*` | +| `tests/*.e2e.ts` | the tests | `*Tests.kt`, `*_Tests.swift` | + +Robots are fluent like the native ones; every call is queued and the chain runs when awaited: + +```ts +await step('GIVEN user opens a channel', () => userRobot.login().openChannel()); +await step('WHEN participant sends a message', () => participantRobot.sendMessage('Hi')); +await step('THEN user receives it', () => userRobot.assertMessage('Hi').assertMessageAuthor(false)); +``` + +## The app + +`examples/E2ETestApp` exists only for this suite, like the native SDKs' `StreamChatUITestsApp`. It has the SDK and its +required peer dependencies, and nothing else: no Firebase, maps, video or navigation library. That keeps native builds +short (a clean Release build is ~2 min on an M-series Mac, versus ~15 min for the SampleApp on Android). It has three +screens: a user selector, the channel list, and a channel stacked on top of the list (`App.tsx`), laid out like the +SampleApp's, so the same SDK paths run. + +`setUpStreamTestCase()` starts a mock server per test and relaunches the app with the launch argument +`MOCK_SERVER_URL` (`localhost` on iOS, `10.0.2.2` on the Android emulator), read in `src/config.ts` via +`react-native-launch-arguments`. The app only lists the mock user (`luke_skywalker`), never persists a login and runs +without offline storage, so every test starts clean. + +## Running + +The fastlane lanes live with the repo's other lanes in `examples/SampleApp/fastlane` (run them from +`examples/SampleApp`). The e2e app has no Gemfile of its own; `e2e_pod_install` installs its pods with that bundle. + +```bash +bundle exec fastlane e2e_pod_install # once, and after native dependency changes +# Everything: clones + starts the mock server, builds, tests, stops the mock server +bundle exec fastlane test_e2e platform:ios +bundle exec fastlane test_e2e platform:android # AVD from DETOX_AVD_NAME (default Pixel_API_37) +# Options: configuration:debug, skip_build:true, local_server:, +# mock_server_branch:, test:e2e/tests/MessageListTests.e2e.ts + +# Or by hand (yarn commands in examples/E2ETestApp) +bundle exec fastlane start_mock_server # driver on MOCK_DRIVER_PORT (default 4569) +yarn e2e:build -c ios.sim.release # or android.emu.release +MOCK_DRIVER_PORT=4569 yarn e2e:test -c ios.sim.release +bundle exec fastlane stop_mock_server +``` + +Android builds only the `arm64-v8a` and `x86_64` ABIs (emulators); set `ORG_GRADLE_PROJECT_reactNativeArchitectures` to +narrow it further. + +Release configurations bundle the JS, so no Metro is needed. Debug configurations need `yarn start`. Android builds need +JDK 17 or 21 (`JAVA_HOME`). + +## CI + +`.github/workflows/e2e-test.yml` runs on pull requests and on demand (`workflow_dispatch`, with iOS/Android toggles and +a mock server branch). Like the iOS SDK, a cheap `Check E2E Required` job first runs `fastlane e2e_check_required` +(`is_check_required` over the `e2e` sources in the Fastfile's `impact_analysis`). The simulator/emulator jobs run +only when the PR touches those sources, or when the last change to them hasn't passed yet. `Test E2E Result` is the +job to require in branch protection: it passes when the e2e jobs pass or are skipped as not required. + +iOS runs on `macos-15` (Xcode 26.2, iPhone 17 Pro, `ios.sim.release`). Android runs on `ubuntu-latest` with KVM and an +API 36 x86_64 emulator (`android.att.release`). Both call `fastlane test_e2e skip_build:true`, which on CI adds +`--retries 2 --headless` and keeps failure logs and screenshots in `artifacts/`. Those and the mock server logs are +uploaded when a job fails. + +What CI caches. The workflow doesn't run on `develop`, and GitHub only lets a PR restore caches from its own branch or +the base branch, so each PR fills its own: its first run builds cold, and later pushes to it reuse the caches. + +- **The built app** (`.app`; release + test `.apk`), keyed on the app and SDK sources. A change that only touches + `e2e/` reuses the binary and skips pods, Gradle and Xcode entirely. +- `node_modules` of `yarn workspaces focus e2etestapp`, so only the e2e app's dependency tree is installed. +- iOS: CocoaPods, ccache (the RN ccache wrappers need `CCACHE_BINARY` in the environment, which the workflow sets), the + Detox framework. +- Android: Gradle (`gradle/actions/setup-gradle` with `cache-read-only: false`, `org.gradle.caching`), a single ABI + (`x86_64`). The emulator matches the native Android SDK's e2e jobs (API 36 AOSP image, `pixel` profile, no snapshot); + on API 34 (Google APIs image, then AOSP) it died mid-run with no crash output. +- The mock server's gems, keyed on the mock server revision. + +## Gotchas + +- iOS runs with Detox's idle synchronization off (`detoxEnableSynchronization: 0` in `StreamTestCase.ts`): it can wedge + on RN's UIManager shadow queue, whose pending-work counter underflows to 2^64-1, so the app looks busy until the test + times out. Robots wait explicitly instead, and taps retry while the target is not hittable yet (e.g. under the + keyboard as it animates in). Android keeps Espresso's synchronization. +- Don't pass `permissions` to `device.launchApp` unless a test needs one: on iOS Detox applies each with + `applesimutils --restartSB`, which restarts SpringBoard on every launch. That cost 1.5–3 min per test on CI (hitting the + hook timeout) and ~15s locally. +- CI boots the simulator at the start of the job and pins it with `DETOX_IOS_UDID` (`.detoxrc.js`), so the boot + overlaps with setup instead of delaying the first test by ~7 min. +- A freshly booted CI emulator can show "Quickstep isn't responding" (a launcher ANR). The dialog takes window focus, and + every Espresso action then fails with "Waited for the root of the view hierarchy to have window focus". The + `test_e2e` lane hides error dialogs and waits up to 60s for the emulator to settle, and `StreamTestCase` dismisses + any ANR dialog before each Android launch (`support/AndroidSystemDialogs.ts`). Read the focus from + `dumpsys window`: `dumpsys window windows` no longer prints `mCurrentFocus`. +- `detox test --reuse` does not reinstall the app. After rebuilding, run without it, or you test the old binary. +- Yarn's `enableScripts: false` skips Detox's postinstall; run `yarn detox build-framework-cache` once on a new machine. +- The SDK's shared native sources are synced into `stream-chat-react-native` and gitignored; `yarn e2e:build` and + `e2e_pod_install` run `yarn sync-native` first. +- Don't commit `ios/E2ETestApp.xcodeproj` after `USE_CCACHE=1 pod install`: it writes machine-specific ccache paths into + it. +- Detox replaces Jest's global `expect`. Use it for element expectations and `expect as jestExpect` from + `@jest/globals` for values. +- Fabric mounts some views that carry a `testID` (`message-list-item-*`, `message-text-container`) empty, with their + content as siblings. They are not usable as `withAncestor` scopes or visibility targets; the page objects scope message + elements to `message-content-wrapper` or the message list instead. +- Detox iOS never reports avatars as visible (their remote image renders transparent until loaded), so avatar asserts + check existence. +- A ported test keeps the native expectation. If the SDK disagrees, fix the SDK or skip the test against a ticket. diff --git a/examples/E2ETestApp/e2e/jest.config.js b/examples/E2ETestApp/e2e/jest.config.js new file mode 100644 index 0000000000..5456675188 --- /dev/null +++ b/examples/E2ETestApp/e2e/jest.config.js @@ -0,0 +1,18 @@ +/** @type {import('jest').Config} */ +module.exports = { + rootDir: '..', + testMatch: ['/e2e/tests/**/*.e2e.ts'], + testTimeout: 180000, + maxWorkers: 1, + globalSetup: 'detox/runners/jest/globalSetup', + globalTeardown: 'detox/runners/jest/globalTeardown', + reporters: ['detox/runners/jest/reporter'], + testEnvironment: 'detox/runners/jest/testEnvironment', + verbose: true, + transform: { + '\\.ts$': [ + 'babel-jest', + { babelrc: false, configFile: false, presets: ['module:@react-native/babel-preset'] }, + ], + }, +}; diff --git a/examples/E2ETestApp/e2e/mock-server/DataTypes.ts b/examples/E2ETestApp/e2e/mock-server/DataTypes.ts new file mode 100644 index 0000000000..a13549c994 --- /dev/null +++ b/examples/E2ETestApp/e2e/mock-server/DataTypes.ts @@ -0,0 +1,25 @@ +export const forbiddenWord = 'wth'; + +export enum AttachmentType { + IMAGE = 'image', + VIDEO = 'video', + FILE = 'file', +} + +export enum ReactionType { + LOVE = 'love', + LOL = 'haha', + WOW = 'wow', + SAD = 'sad', + LIKE = 'like', +} + +export enum MessageDeliveryStatus { + READ = 'read', + DELIVERED = 'delivered', + PENDING = 'pending', + SENT = 'sent', + FAILED = 'failed', + /** No delivery status icon is expected. */ + NIL = 'nil', +} diff --git a/examples/E2ETestApp/e2e/mock-server/MockServer.ts b/examples/E2ETestApp/e2e/mock-server/MockServer.ts new file mode 100644 index 0000000000..1d273d12cf --- /dev/null +++ b/examples/E2ETestApp/e2e/mock-server/MockServer.ts @@ -0,0 +1,84 @@ +declare const process: { env: Record }; + +/** + * Client for `stream-chat-test-mock-server`. The driver (`driver.rb`) spawns one mock server per + * test on its own port; `start` asks it for one and `stop` shuts it down. + * + * Unlike the native suites, the test code runs on the host, so robots always talk to `localhost` + * while the app gets `appUrl`, which is the host as the device sees it. + */ +export class MockServer { + static readonly driverPort = process.env.MOCK_DRIVER_PORT ?? '4569'; + private static readonly driverUrl = `http://localhost:${MockServer.driverPort}`; + + private port?: string; + + /** The mock server as the test runner (host) sees it. */ + get url(): string { + if (!this.port) { + throw new Error('MockServer is not started'); + } + return `http://localhost:${this.port}`; + } + + /** The mock server as the app under test sees it: the Android emulator reaches the host via 10.0.2.2. */ + get appUrl(): string { + const host = device.getPlatform() === 'android' ? '10.0.2.2' : 'localhost'; + return this.url.replace('localhost', host); + } + + /** Never retried: every call to the driver's `/start` spawns a new server on a new port. */ + async start(testName: string): Promise { + const response = await this.execute( + `${MockServer.driverUrl}/start/${encodeURIComponent(testName)}`, + ); + this.port = (await response.text()).trim(); + await this.waitUntilReady(); + } + + async stop(): Promise { + if (!this.port) { + return; + } + await this.getRequest('stop').catch(() => undefined); + this.port = undefined; + } + + async postRequest(endpoint: string, body = ''): Promise { + const response = await this.execute(`${this.url}/${endpoint}`, { + body, + headers: { 'Content-Type': 'text/plain' }, + method: 'POST', + }); + return response.text(); + } + + async getRequest(endpoint: string): Promise { + const response = await this.execute(`${this.url}/${endpoint}`); + return response.text(); + } + + private async waitUntilReady(timeoutMs = 5000): Promise { + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + try { + const response = await fetch(`${this.url}/ping`); + if (response.ok) { + return; + } + } catch { + // The server process is still booting. + } + await new Promise((resolve) => setTimeout(resolve, 250)); + } + throw new Error(`MockServer did not start within ${timeoutMs}ms on ${this.url}`); + } + + private async execute(url: string, init?: RequestInit): Promise { + const response = await fetch(url, init); + if (!response.ok) { + throw new Error(`${init?.method ?? 'GET'} ${url} failed: ${response.status}`); + } + return response; + } +} diff --git a/examples/E2ETestApp/e2e/pages/ChannelListPage.ts b/examples/E2ETestApp/e2e/pages/ChannelListPage.ts new file mode 100644 index 0000000000..4cc136b9b0 --- /dev/null +++ b/examples/E2ETestApp/e2e/pages/ChannelListPage.ts @@ -0,0 +1,32 @@ +export const ChannelListPage = { + ChannelList: { + get channelList() { + return by.id('channel-list-view'); + }, + get channels() { + return by.id('channel-preview-button'); + }, + + Channel: { + get avatar() { + return by.id('avatar-image').withAncestor(by.id('channel-preview-button')); + }, + get name() { + return by.id('channel-preview-title'); + }, + nameWithText(text: string) { + return by.id('channel-preview-title').and(by.text(text)); + }, + get messagePreview() { + return by.id('channel-preview-message'); + }, + /** "You:" or ":" in front of the preview text, rendered as its own text. */ + get messageSender() { + return by.id('channel-preview-sender'); + }, + get unreadCount() { + return by.id('unread-count').withAncestor(by.id('channel-preview-button')); + }, + }, + }, +}; diff --git a/examples/E2ETestApp/e2e/pages/LoginPage.ts b/examples/E2ETestApp/e2e/pages/LoginPage.ts new file mode 100644 index 0000000000..069eab4a75 --- /dev/null +++ b/examples/E2ETestApp/e2e/pages/LoginPage.ts @@ -0,0 +1,11 @@ +/** The mock server's app user (`appUser` in `src/config.ts`), the only one listed in e2e mode. */ +export const appUserId = 'luke_skywalker'; + +export const LoginPage = { + get userSelector() { + return by.id('user-selector-screen'); + }, + get loginButton() { + return by.id(`user-selector-button-${appUserId}`); + }, +}; diff --git a/examples/E2ETestApp/e2e/pages/MessageListPage.ts b/examples/E2ETestApp/e2e/pages/MessageListPage.ts new file mode 100644 index 0000000000..4559f48590 --- /dev/null +++ b/examples/E2ETestApp/e2e/pages/MessageListPage.ts @@ -0,0 +1,87 @@ +const messageCell = () => by.id(/^message-list-item-/); +// Fabric mounts `message-list-item-*` empty with its content beside it, so the list, not the cell, +// is the ancestor that scopes message elements (the channel list stays mounted underneath). +const inMessageList = (matcher: Detox.NativeMatcher) => + matcher.withAncestor(by.id(/^message-(flat|flash)-list$/)); + +export const MessageListPage = { + Header: { + get backButton() { + return by.id('back-button'); + }, + }, + + Composer: { + get inputField() { + return by.id('auto-complete-text-input'); + }, + get sendButton() { + return by.id('send-button'); + }, + get attachmentsButton() { + return by.id('attach-button'); + }, + get cooldownIndicator() { + return by.id('cooldown-timer'); + }, + }, + + MessageList: { + get messageList() { + return by.id(/^message-(flat|flash)-list$/); + }, + get messages() { + return messageCell(); + }, + get dateSeparator() { + return by.id('date-separator'); + }, + get typingIndicator() { + return by.id('typing-indicator'); + }, + get scrollToBottomButton() { + return by.id('scroll-to-bottom-button'); + }, + + Message: { + /** Wraps the author's avatar; only rendered for other users' messages. */ + get avatar() { + return by.id('message-author'); + }, + text(text: string) { + // Not under `message-text-container`: Fabric mounts that view empty, with the text beside it. + return by.text(text).withAncestor(by.id('message-content-wrapper')); + }, + get timestamp() { + return by.id('message-status-time'); + }, + get deliveryStatusIsRead() { + return inMessageList(by.id('message-status-read')); + }, + get deliveryStatusIsDelivered() { + return inMessageList(by.id('message-status-delivered')); + }, + get deliveryStatusIsPending() { + return inMessageList(by.id('message-status-sending')); + }, + get deliveryStatusIsSent() { + return inMessageList(by.id('message-status-sent')); + }, + get deliveryStatusIsFailed() { + return inMessageList(by.id('message-error')); + }, + get deletedMessage() { + return by.id('message-deleted'); + }, + get threadRepliesLabel() { + return by.id('message-replies'); + }, + + Reactions: { + get reactions() { + return by.id('reaction-list-item'); + }, + }, + }, + }, +}; diff --git a/examples/E2ETestApp/e2e/robots/BackendRobot.ts b/examples/E2ETestApp/e2e/robots/BackendRobot.ts new file mode 100644 index 0000000000..c62e3eea5c --- /dev/null +++ b/examples/E2ETestApp/e2e/robots/BackendRobot.ts @@ -0,0 +1,129 @@ +import { Robot } from './Robot'; + +import type { MockServer } from '../mock-server/MockServer'; + +/** Drives server-side state through the mock server's test-control endpoints. */ +export class BackendRobot extends Robot { + constructor(private readonly mockServer: MockServer) { + super(); + } + + generateChannels({ + channelsCount, + messagesCount = 0, + repliesCount = 0, + messagesText, + repliesText, + channelNames = [], + withDirectMessageChannel = false, + }: { + channelsCount: number; + messagesCount?: number; + repliesCount?: number; + messagesText?: string; + repliesText?: string; + channelNames?: string[]; + withDirectMessageChannel?: boolean; + }): this { + const params = new URLSearchParams(); + if (messagesText !== undefined) params.set('messages_text', messagesText); + if (repliesText !== undefined) params.set('replies_text', repliesText); + if (channelNames.length > 0) params.set('channel_names', channelNames.join(',')); + if (withDirectMessageChannel) params.set('dm', 'true'); + params.set('channels', `${channelsCount}`); + params.set('messages', `${messagesCount}`); + params.set('replies', `${repliesCount}`); + return this.post(`mock?${params}`); + } + + failNewMessages(): this { + return this.post('fail_messages'); + } + + freezeNewMessages(): this { + return this.post('freeze_messages'); + } + + setCooldown(enabled: boolean, duration: number): this { + return this.post(`config/cooldown?enabled=${enabled}&duration=${duration}`); + } + + /** + * Truncates the currently open channel on the server side. The app receives `channel.truncated`. + * + * @param withMessage also delivers a "Channel truncated" system message. + */ + truncateChannel(withMessage: boolean): this { + return this.post(`truncate_channel?with_message=${withMessage}`); + } + + /** Enables or disables read events on every channel. Call before the channel is opened. */ + setReadEvents(enabled: boolean): this { + return this.post(`config/read_events?value=${enabled}`); + } + + /** Adds a member to the currently open channel. The app receives `member.added` + `channel.updated`. */ + addMember(userId: string): this { + return this.post(`add_member?user_id=${encodeURIComponent(userId)}`); + } + + /** Removes a member from the currently open channel. The app receives `member.removed` + `channel.updated`. */ + removeMember(userId: string): this { + return this.post(`remove_member?user_id=${encodeURIComponent(userId)}`); + } + + /** + * Creates a reminder for the last message of the currently open channel. + * + * @param remindAtSeconds how far in the future it is due; omit for a "saved for later" reminder. + */ + createReminder(remindAtSeconds?: number): this { + return this.post( + remindAtSeconds === undefined + ? 'create_reminder' + : `create_reminder?remind_at=${remindAtSeconds}`, + ); + } + + revokeToken(duration = 5): this { + return this.post(`jwt/revoke_token?duration=${duration}`); + } + + invalidateToken(duration = 5): this { + return this.post(`jwt/invalidate_token?duration=${duration}`); + } + + invalidateTokenDate(duration = 5): this { + return this.post(`jwt/invalidate_token_date?duration=${duration}`); + } + + invalidateTokenSignature(duration = 5): this { + return this.post(`jwt/invalidate_token_signature?duration=${duration}`); + } + + breakTokenGeneration(duration = 5): this { + return this.post(`jwt/break_token_generation?duration=${duration}`); + } + + /** + * Waits until the app closes its WebSocket. The SDK keeps the socket open for a while after the + * app goes to background, so call this before server-side events that must not arrive live. + */ + waitForWebSocketDisconnection(timeoutMs = 10000): this { + return this.perform(async () => { + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + const status = JSON.parse(await this.mockServer.getRequest('ws/status')); + if (!status.connected) { + return; + } + await new Promise((resolve) => setTimeout(resolve, 500)); + } + throw new Error(`WebSocket was not disconnected within ${timeoutMs}ms`); + }); + } + + private post(endpoint: string): this { + return this.perform(() => this.mockServer.postRequest(endpoint)); + } +} diff --git a/examples/E2ETestApp/e2e/robots/ParticipantRobot.ts b/examples/E2ETestApp/e2e/robots/ParticipantRobot.ts new file mode 100644 index 0000000000..0615c8a469 --- /dev/null +++ b/examples/E2ETestApp/e2e/robots/ParticipantRobot.ts @@ -0,0 +1,161 @@ +import { Robot } from './Robot'; + +import type { AttachmentType, ReactionType } from '../mock-server/DataTypes'; +import type { MockServer } from '../mock-server/MockServer'; + +const MULTIPLE_MESSAGES_INTERVAL_MS = 300; + +const quoteParam = (last: boolean) => (last ? 'quote_last=true' : 'quote_first=true'); + +/** Acts as the other channel member (`count_dooku`) through the mock server's participant endpoints. */ +export class ParticipantRobot extends Robot { + static readonly userName = 'Count Dooku'; + static readonly userId = 'count_dooku'; + + constructor(private readonly mockServer: MockServer) { + super(); + } + + startTyping(): this { + return this.post('participant/typing/start'); + } + + startTypingInThread(): this { + return this.post('participant/typing/start?thread=true'); + } + + stopTyping(): this { + return this.post('participant/typing/stop'); + } + + stopTypingInThread(): this { + return this.post('participant/typing/stop?thread=true'); + } + + readMessage(): this { + return this.post('participant/read'); + } + + /** Marks the newest message as delivered to the participant (`message.delivered`). */ + markMessagesDelivered(): this { + return this.post('participant/delivered'); + } + + sendMessage(text: string, delay = 0): this { + return this.post( + delay > 0 ? `participant/message?delay=${delay}` : 'participant/message', + text, + ); + } + + /** Sends `count` messages named `${text}-1` through `${text}-${count}`, 300ms apart. */ + sendMultipleMessages(text: string, count: number): this { + for (let index = 1; index <= count; index++) { + this.sendMessage(`${text}-${index}`).sleep(MULTIPLE_MESSAGES_INTERVAL_MS); + } + return this; + } + + sendMessageInThread(text: string, alsoSendInChannel = false): this { + return this.post( + `participant/message?thread=true&thread_and_channel=${alsoSendInChannel}`, + text, + ); + } + + editMessage(text: string): this { + return this.post('participant/message?action=edit', text); + } + + deleteMessage(hard = false): this { + return this.post(`participant/message?action=delete&hard_delete=${hard}`); + } + + quoteMessage(text: string, last = true): this { + return this.post(`participant/message?${quoteParam(last)}`, text); + } + + quoteMessageInThread(text: string, alsoSendInChannel = false, last = true): this { + return this.post( + `participant/message?${quoteParam(last)}&thread=true&thread_and_channel=${alsoSendInChannel}`, + text, + ); + } + + uploadGiphy(): this { + return this.post('participant/message?giphy=true'); + } + + uploadGiphyInThread(): this { + return this.post('participant/message?giphy=true&thread=true'); + } + + quoteMessageWithGiphy(last = true): this { + return this.post(`participant/message?giphy=true&${quoteParam(last)}`); + } + + quoteMessageWithGiphyInThread(alsoSendInChannel = false, last = true): this { + return this.post( + `participant/message?giphy=true&${quoteParam(last)}&thread=true&thread_and_channel=${alsoSendInChannel}`, + ); + } + + pinMessage(): this { + return this.post('participant/message?action=pin'); + } + + unpinMessage(): this { + return this.post('participant/message?action=unpin'); + } + + uploadAttachment(type: AttachmentType, count = 1): this { + return this.post(`participant/message?${type}=${count}`); + } + + quoteMessageWithAttachment(type: AttachmentType, count = 1, last = true): this { + return this.post(`participant/message?${quoteParam(last)}&${type}=${count}`); + } + + uploadAttachmentInThread(type: AttachmentType, count = 1, alsoSendInChannel = false): this { + return this.post( + `participant/message?${type}=${count}&thread=true&thread_and_channel=${alsoSendInChannel}`, + ); + } + + quoteMessageWithAttachmentInThread( + type: AttachmentType, + count = 1, + alsoSendInChannel = false, + last = true, + ): this { + return this.post( + `participant/message?${quoteParam(last)}&${type}=${count}&thread=true&thread_and_channel=${alsoSendInChannel}`, + ); + } + + /** Votes for `option` in the poll of the newest poll message. */ + castPollVote(option: string): this { + return this.post(`participant/poll_vote?option=${encodeURIComponent(option)}`); + } + + /** Adds an answer (comment) to the poll of the newest poll message. */ + addPollAnswer(answer: string): this { + return this.post(`participant/poll_vote?answer=${encodeURIComponent(answer)}`); + } + + addReaction(type: ReactionType, delay = 0): this { + return this.post( + delay > 0 + ? `participant/reaction?type=${type}&delay=${delay}` + : `participant/reaction?type=${type}`, + ); + } + + deleteReaction(type: ReactionType): this { + return this.post(`participant/reaction?type=${type}&delete=true`); + } + + private post(endpoint: string, body?: string): this { + return this.perform(() => this.mockServer.postRequest(endpoint, body)); + } +} diff --git a/examples/E2ETestApp/e2e/robots/Robot.ts b/examples/E2ETestApp/e2e/robots/Robot.ts new file mode 100644 index 0000000000..44dbaaecb7 --- /dev/null +++ b/examples/E2ETestApp/e2e/robots/Robot.ts @@ -0,0 +1,33 @@ +/** + * Base for the fluent robots. Native robots chain synchronous calls + * (`userRobot.login().openChannel()`); Detox is async, so each call queues its action and + * returns `this`, and awaiting the robot runs the queue: + * + * await userRobot.login().openChannel().assertMessage('Hi'); + * + * A failed action rejects the awaited chain and skips the rest of it. + */ +export abstract class Robot implements PromiseLike { + private queue: Promise = Promise.resolve(); + + /** Queues `action` after the previous ones. Used by the robots and their assert extensions. */ + perform(action: () => Promise): this { + this.queue = this.queue.then(async () => { + await action(); + }); + return this; + } + + sleep(ms: number): this { + return this.perform(() => new Promise((resolve) => setTimeout(resolve, ms))); + } + + then( + onfulfilled?: ((value: void) => TResult1 | PromiseLike) | null, + onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null, + ): PromiseLike { + const queue = this.queue; + this.queue = Promise.resolve(); + return queue.then(onfulfilled, onrejected); + } +} diff --git a/examples/E2ETestApp/e2e/robots/UserRobot.ts b/examples/E2ETestApp/e2e/robots/UserRobot.ts new file mode 100644 index 0000000000..f8e99e6bee --- /dev/null +++ b/examples/E2ETestApp/e2e/robots/UserRobot.ts @@ -0,0 +1,135 @@ +import { Robot } from './Robot'; + +import { ChannelListPage } from '../pages/ChannelListPage'; +import { LoginPage } from '../pages/LoginPage'; +import { MessageListPage } from '../pages/MessageListPage'; +import { + waitToAppear, + waitToAppearAndClick, + waitToAppearAndLongPress, + waitToDisappear, +} from '../support/Element'; + +const { ChannelList } = ChannelListPage; +const { Composer, Header, MessageList } = MessageListPage; + +/** Drives the app as the logged-in user. Asserts live in the `UserRobot*Asserts` extensions. */ +export class UserRobot extends Robot { + login(): this { + return this.perform(() => waitToAppearAndClick(LoginPage.loginButton)); + } + + waitForChannelListToLoad(): this { + return this.perform(() => waitToAppear(ChannelList.channels, { index: 0 })); + } + + waitForMessageListToLoad(): this { + return this.perform(() => waitToAppear(Composer.inputField)); + } + + openChannel(channelCellIndex = 0): this { + return this.perform(() => + waitToAppearAndClick(ChannelList.channels, { index: channelCellIndex }), + ).waitForMessageListToLoad(); + } + + openChannelWithName(channelName: string): this { + return this.perform(() => + waitToAppearAndClick(ChannelList.Channel.nameWithText(channelName)), + ).waitForMessageListToLoad(); + } + + openContextMenu(messageText: string): this { + return this.perform(() => waitToAppearAndLongPress(MessageList.Message.text(messageText))); + } + + /** + * iOS types key by key, like XCUITest. Android sets the text, like the native suite's + * `typeText` (UiAutomator `setText`): typing through the soft keyboard leaves the text in the + * IME's composing state, which the composer doesn't pick up, so the send button never shows. + */ + typeText(text: string): this { + return this.perform(async () => { + const inputField = await waitToAppear(Composer.inputField); + if (device.getPlatform() === 'android') { + await inputField.replaceText(text); + } else { + await inputField.typeText(text); + } + }); + } + + clearComposer(): this { + return this.perform(async () => { + const inputField = await waitToAppear(Composer.inputField); + await inputField.clearText(); + }); + } + + tapOnSendButton(): this { + return this.perform(() => waitToAppearAndClick(Composer.sendButton)); + } + + sendMessage(text: string): this { + return this.typeText(text).tapOnSendButton(); + } + + tapOnBackButton(): this { + return this.perform(() => waitToAppearAndClick(Header.backButton)); + } + + /** Android hardware back; iOS has none, so it taps the header's back button there. */ + pressBack(): this { + return this.perform(async () => { + if (device.getPlatform() === 'android') { + await device.pressBack(); + } else { + await waitToAppearAndClick(Header.backButton); + } + }); + } + + tapOnScrollToBottomButton(): this { + return this.perform(() => waitToAppearAndClick(MessageList.scrollToBottomButton)); + } + + moveToChannelListFromMessageList(): this { + return this.tapOnBackButton() + .perform(() => waitToDisappear(Composer.inputField)) + .waitForChannelListToLoad(); + } + + scrollChannelListDown(times = 3): this { + return this.swipe(ChannelList.channelList, 'up', times); + } + + scrollChannelListUp(times = 3): this { + return this.swipe(ChannelList.channelList, 'down', times); + } + + scrollMessageListDown(times = 3): this { + // The message list is inverted: newer messages are at the bottom, reached by swiping down. + return this.swipe(MessageList.messageList, 'down', times); + } + + scrollMessageListUp(times = 3): this { + return this.swipe(MessageList.messageList, 'up', times); + } + + backgroundApp(): this { + return this.perform(() => device.sendToHome()); + } + + foregroundApp(): this { + return this.perform(() => device.launchApp({ newInstance: false })); + } + + private swipe(list: Detox.NativeMatcher, direction: Detox.Direction, times: number): this { + return this.perform(async () => { + const target = await waitToAppear(list); + for (let i = 0; i < times; i++) { + await target.swipe(direction, 'fast', 0.6); + } + }); + } +} diff --git a/examples/E2ETestApp/e2e/robots/UserRobotChannelListAsserts.ts b/examples/E2ETestApp/e2e/robots/UserRobotChannelListAsserts.ts new file mode 100644 index 0000000000..068ed8c6e6 --- /dev/null +++ b/examples/E2ETestApp/e2e/robots/UserRobotChannelListAsserts.ts @@ -0,0 +1,72 @@ +import { expect as jestExpect } from '@jest/globals'; + +import { ParticipantRobot } from './ParticipantRobot'; +import { UserRobot } from './UserRobot'; + +import { ChannelListPage } from '../pages/ChannelListPage'; +import { waitForCount, waitForExistence, waitForText, waitToDisappear } from '../support/Element'; + +const { ChannelList } = ChannelListPage; +const { Channel } = ChannelList; + +declare module './UserRobot' { + interface UserRobot { + assertChannelAvatar(): this; + assertChannelCount(count: number): this; + assertChannelListIsEmpty(): this; + assertChannelName(name: string, channelCellIndex?: number): this; + assertChannelUnreadCount(count: number): this; + /** + * @param fromCurrentUser `true` expects a "You: " prefix, `false` the participant's name, + * `undefined` only the text. + */ + assertMessageInChannelPreview(text: string, fromCurrentUser?: boolean): this; + } +} + +UserRobot.prototype.assertChannelAvatar = function () { + return this.perform(async () => { + jestExpect(await waitForExistence(Channel.avatar, { index: 0 })).toBe(true); + }); +}; + +UserRobot.prototype.assertChannelCount = function (count) { + return this.perform(async () => { + jestExpect(await waitForCount(ChannelList.channels, count)).toBe(count); + }); +}; + +UserRobot.prototype.assertChannelListIsEmpty = function () { + return this.perform(async () => { + await waitToDisappear(ChannelList.channels); + jestExpect(await waitForCount(ChannelList.channels, 0)).toBe(0); + }); +}; + +UserRobot.prototype.assertChannelName = function (name, channelCellIndex = 0) { + return this.perform(async () => { + jestExpect(await waitForText(Channel.name, name, { index: channelCellIndex })).toBe(name); + }); +}; + +UserRobot.prototype.assertChannelUnreadCount = function (count) { + return this.perform(async () => { + jestExpect(await waitForText(Channel.unreadCount, `${count}`)).toBe(`${count}`); + }); +}; + +UserRobot.prototype.assertMessageInChannelPreview = function (text, fromCurrentUser) { + const expectedSender = + fromCurrentUser === true + ? 'You:' + : fromCurrentUser === false + ? `${ParticipantRobot.userName}:` + : undefined; + return this.perform(async () => { + const preview = await waitForText(Channel.messagePreview, text, { mustBeEqual: false }); + jestExpect(preview.trimEnd()).toContain(text); + if (expectedSender !== undefined) { + jestExpect(await waitForText(Channel.messageSender, expectedSender)).toBe(expectedSender); + } + }); +}; diff --git a/examples/E2ETestApp/e2e/robots/UserRobotMessageListAsserts.ts b/examples/E2ETestApp/e2e/robots/UserRobotMessageListAsserts.ts new file mode 100644 index 0000000000..7596ce79c8 --- /dev/null +++ b/examples/E2ETestApp/e2e/robots/UserRobotMessageListAsserts.ts @@ -0,0 +1,105 @@ +import { expect as jestExpect } from '@jest/globals'; + +import { UserRobot } from './UserRobot'; + +import { MessageDeliveryStatus } from '../mock-server/DataTypes'; +import { MessageListPage } from '../pages/MessageListPage'; +import { + defaultTimeout, + isDisplayed, + waitForCount, + waitForExistence, + waitToAppear, + waitToDisappear, +} from '../support/Element'; + +const { MessageList } = MessageListPage; +const { Message } = MessageList; + +declare module './UserRobot' { + interface UserRobot { + assertMessage(text: string, isDisplayed?: boolean): this; + assertMessageAuthor(isCurrentUser: boolean): this; + assertMessageDeliveryStatus(status: MessageDeliveryStatus, count?: number): this; + assertMessageFailedIcon(isDisplayed: boolean): this; + assertTypingIndicator(isDisplayed: boolean): this; + assertScrollToBottomButton(isDisplayed: boolean): this; + } +} + +export const assertVisibility = async ( + selector: Detox.NativeMatcher, + shouldBeDisplayed: boolean, + timeout = defaultTimeout, +) => { + if (shouldBeDisplayed) { + await waitToAppear(selector, { index: 0, timeout }); + } else { + await waitToDisappear(selector, { index: 0, timeout }); + jestExpect(await isDisplayed(selector, 0)).toBe(false); + } +}; + +const assertDeliveryStatusCount = async (selector: Detox.NativeMatcher, count?: number) => { + await assertVisibility(selector, true, 30000); + if (count !== undefined) { + jestExpect(await waitForCount(selector, count)).toBe(count); + } +}; + +UserRobot.prototype.assertMessage = function (text, shouldBeDisplayed = true) { + return this.perform(async () => { + await assertVisibility(Message.text(text), shouldBeDisplayed); + if (shouldBeDisplayed) { + jestExpect(await isDisplayed(Message.timestamp, 0)).toBe(true); + } + }); +}; + +/** The current user's messages carry no avatar; other users' do. */ +UserRobot.prototype.assertMessageAuthor = function (isCurrentUser) { + return this.perform(async () => { + const exists = await waitForExistence(Message.avatar, { + index: 0, + shouldExist: !isCurrentUser, + }); + jestExpect(exists).toBe(!isCurrentUser); + }); +}; + +UserRobot.prototype.assertMessageDeliveryStatus = function (status, count) { + return this.perform(async () => { + switch (status) { + case MessageDeliveryStatus.READ: + return assertDeliveryStatusCount(Message.deliveryStatusIsRead, count); + case MessageDeliveryStatus.DELIVERED: + return assertDeliveryStatusCount(Message.deliveryStatusIsDelivered, count); + case MessageDeliveryStatus.PENDING: + return assertDeliveryStatusCount(Message.deliveryStatusIsPending, count); + case MessageDeliveryStatus.SENT: + return assertDeliveryStatusCount(Message.deliveryStatusIsSent, count); + case MessageDeliveryStatus.FAILED: + return assertDeliveryStatusCount(Message.deliveryStatusIsFailed, count); + case MessageDeliveryStatus.NIL: + await assertVisibility(Message.deliveryStatusIsRead, false); + await assertVisibility(Message.deliveryStatusIsDelivered, false); + await assertVisibility(Message.deliveryStatusIsPending, false); + await assertVisibility(Message.deliveryStatusIsSent, false); + return; + default: + throw new Error(`Unsupported delivery status: ${status}`); + } + }); +}; + +UserRobot.prototype.assertMessageFailedIcon = function (shouldBeDisplayed) { + return this.perform(() => assertVisibility(Message.deliveryStatusIsFailed, shouldBeDisplayed)); +}; + +UserRobot.prototype.assertTypingIndicator = function (shouldBeDisplayed) { + return this.perform(() => assertVisibility(MessageList.typingIndicator, shouldBeDisplayed)); +}; + +UserRobot.prototype.assertScrollToBottomButton = function (shouldBeDisplayed) { + return this.perform(() => assertVisibility(MessageList.scrollToBottomButton, shouldBeDisplayed)); +}; diff --git a/examples/E2ETestApp/e2e/robots/index.ts b/examples/E2ETestApp/e2e/robots/index.ts new file mode 100644 index 0000000000..bc92c7f124 --- /dev/null +++ b/examples/E2ETestApp/e2e/robots/index.ts @@ -0,0 +1,7 @@ +// The assert files extend UserRobot's prototype, so they must be loaded with it. +import './UserRobotMessageListAsserts'; +import './UserRobotChannelListAsserts'; + +export { BackendRobot } from './BackendRobot'; +export { ParticipantRobot } from './ParticipantRobot'; +export { UserRobot } from './UserRobot'; diff --git a/examples/E2ETestApp/e2e/support/AndroidSystemDialogs.ts b/examples/E2ETestApp/e2e/support/AndroidSystemDialogs.ts new file mode 100644 index 0000000000..a7d2c61dcd --- /dev/null +++ b/examples/E2ETestApp/e2e/support/AndroidSystemDialogs.ts @@ -0,0 +1,43 @@ +// Node built-ins, typed locally: the e2e project's types are limited to `detox/globals`. +declare const process: { env: Record }; +declare const require: (module: 'child_process') => { + execFileSync: (file: string, args: string[], options: { encoding: 'utf8' }) => string; +}; + +const { execFileSync } = require('child_process'); + +const sdkRoot = process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT; +const adbPath = sdkRoot ? `${sdkRoot}/platform-tools/adb` : 'adb'; + +const adb = (...args: string[]) => { + const serial = process.env.DETOX_ADB_NAME; + const deviceArgs = serial && !serial.includes('*') ? ['-s', serial] : []; + return execFileSync(adbPath, [...deviceArgs, ...args], { encoding: 'utf8' }); +}; + +/** + * Dismisses an " isn't responding" dialog, if one is showing. On a slow CI emulator the + * launcher (Quickstep) can hit an ANR; its dialog takes window focus, and Espresso then fails every + * test waiting for the app's window to get it. Same handling as the `wait_android_emu_idle` fastlane + * action: Enter picks "Close app", except for System UI, where it picks "Wait". + */ +export const dismissAnrDialog = () => { + try { + const focus = adb('shell', 'dumpsys', 'window') + .split('\n') + .find( + (line) => line.includes('mCurrentFocus') && line.includes('Application Not Responding'), + ); + if (!focus) { + return; + } + console.warn(`Dismissing an ANR dialog: ${focus.trim()}`); + if (focus.includes('com.android.systemui')) { + adb('shell', 'input', 'keyevent', 'KEYCODE_DPAD_DOWN'); + } + adb('shell', 'input', 'keyevent', 'KEYCODE_ENTER'); + } catch (error) { + // Best effort: a failed check must not fail the test on its own. + console.warn(`Could not check for ANR dialogs: ${String(error)}`); + } +}; diff --git a/examples/E2ETestApp/e2e/support/Element.ts b/examples/E2ETestApp/e2e/support/Element.ts new file mode 100644 index 0000000000..edefaf1414 --- /dev/null +++ b/examples/E2ETestApp/e2e/support/Element.ts @@ -0,0 +1,164 @@ +/** + * Element helpers mirroring the Android suite's `uiautomator` extensions (`Element.kt`, `Wait.kt`), + * so robot code reads the same: `selector.waitToAppearAndClick()` becomes + * `await waitToAppearAndClick(selector)`. + */ + +export const defaultTimeout = 15000; + +type Selector = Detox.NativeMatcher; + +type WaitOptions = { + /** Picks one match when the selector matches several elements. */ + index?: number; + timeout?: number; +}; + +const findElement = (selector: Selector, index?: number) => + index === undefined ? element(selector) : element(selector).atIndex(index); + +export const waitToAppear = async ( + selector: Selector, + { index, timeout = defaultTimeout }: WaitOptions = {}, +) => { + const target = findElement(selector, index); + await waitFor(target).toBeVisible().withTimeout(timeout); + return target; +}; + +/** + * Retries `action` while Detox reports the element as not hittable (e.g. covered by the keyboard + * while it animates in). iOS runs without Detox's idle synchronization, which used to absorb that. + */ +const retryWhileNotHittable = async (action: () => Promise, timeout = defaultTimeout) => { + const deadline = Date.now() + timeout; + for (;;) { + try { + return await action(); + } catch (error) { + if (Date.now() > deadline || !/not hittable/i.test(String(error))) { + throw error; + } + await new Promise((resolve) => setTimeout(resolve, 300)); + } + } +}; + +export const waitToAppearAndClick = async (selector: Selector, options?: WaitOptions) => { + const target = await waitToAppear(selector, options); + await retryWhileNotHittable(() => target.tap(), options?.timeout); + return target; +}; + +export const waitToAppearAndLongPress = async (selector: Selector, options?: WaitOptions) => { + const target = await waitToAppear(selector, options); + await retryWhileNotHittable(() => target.longPress(), options?.timeout); + return target; +}; + +/** Waits until nothing matching `selector` is visible. Resolves (does not throw) on timeout. */ +export const waitToDisappear = async ( + selector: Selector, + { index, timeout = defaultTimeout }: WaitOptions = {}, +) => { + try { + await waitFor(findElement(selector, index)).not.toBeVisible().withTimeout(timeout); + } catch { + // The caller asserts on `isDisplayed` afterwards, like `waitToDisappear().isDisplayed()` on Android. + } +}; + +/** + * Waits until `selector` exists (or, with `shouldExist: false`, until it no longer does), and + * returns whether it exists then. For elements Detox iOS never reports as visible, such as avatars + * whose remote image has not loaded: its visibility check needs rendered pixels. + */ +export const waitForExistence = async ( + selector: Selector, + { + index, + shouldExist = true, + timeout = defaultTimeout, + }: WaitOptions & { shouldExist?: boolean } = {}, +) => { + const target = findElement(selector, index); + try { + if (shouldExist) { + await waitFor(target).toExist().withTimeout(timeout); + } else { + await waitFor(target).not.toExist().withTimeout(timeout); + } + return shouldExist; + } catch { + return !shouldExist; + } +}; + +export const isDisplayed = async (selector: Selector, index?: number) => { + try { + await expect(findElement(selector, index)).toBeVisible(); + return true; + } catch { + return false; + } +}; + +/** Polls until `predicate` holds, then returns `true`; returns `false` once `timeout` elapses. */ +export const waitUntil = async ( + predicate: () => Promise, + timeout = defaultTimeout, + interval = 500, +) => { + const deadline = Date.now() + timeout; + while (Date.now() < deadline) { + if (await predicate()) { + return true; + } + await new Promise((resolve) => setTimeout(resolve, interval)); + } + return predicate(); +}; + +/** Number of elements currently matching `selector` (Detox has no count query of its own). */ +export const count = async (selector: Selector) => { + try { + const attributes = await element(selector).getAttributes(); + return 'elements' in attributes ? attributes.elements.length : 1; + } catch { + return 0; + } +}; + +/** Waits until `selector` matches exactly `expected` elements and returns the last count seen. */ +export const waitForCount = async (selector: Selector, expected: number, timeout?: number) => { + let actual = 0; + await waitUntil(async () => { + actual = await count(selector); + return actual === expected; + }, timeout); + return actual; +}; + +/** Waits until `selector` (or its `index`-th match) shows `text` and returns the text it shows. */ +export const waitForText = async ( + selector: Selector, + text: string, + { index = 0, mustBeEqual = true, timeout }: WaitOptions & { mustBeEqual?: boolean } = {}, +) => { + let actual = ''; + await waitUntil(async () => { + actual = await textOf(selector, index); + return mustBeEqual ? actual === text : actual.includes(text); + }, timeout); + return actual; +}; + +export const textOf = async (selector: Selector, index = 0) => { + try { + const attributes = await element(selector).getAttributes(); + const match = 'elements' in attributes ? attributes.elements[index] : attributes; + return match?.text ?? match?.label ?? ''; + } catch { + return ''; + } +}; diff --git a/examples/E2ETestApp/e2e/support/StreamTestCase.ts b/examples/E2ETestApp/e2e/support/StreamTestCase.ts new file mode 100644 index 0000000000..4a4ddb1b9a --- /dev/null +++ b/examples/E2ETestApp/e2e/support/StreamTestCase.ts @@ -0,0 +1,61 @@ +import { afterEach, beforeEach, expect as jestExpect } from '@jest/globals'; + +import { dismissAnrDialog } from './AndroidSystemDialogs'; + +import { MockServer } from '../mock-server/MockServer'; +import { BackendRobot, ParticipantRobot, UserRobot } from '../robots'; + +/** + * Per-test fixture, the counterpart of `StreamTestCase` on iOS and Android: before each test it + * starts a fresh mock server and relaunches the app pointed at it; after each test it stops the + * server. + * + * describe('MessageListTests', () => { + * const { userRobot, participantRobot, backendRobot } = setUpStreamTestCase(); + * it('test_...', async () => { ... }); + * }); + */ +export class StreamTestCase { + readonly mockServer = new MockServer(); + readonly userRobot = new UserRobot(); + readonly backendRobot = new BackendRobot(this.mockServer); + readonly participantRobot = new ParticipantRobot(this.mockServer); +} + +/** `describe` + `it` names `MessageListTests test_x` → `test_x`, which names the mock server log. */ +const currentTestName = () => + (jestExpect.getState().currentTestName ?? 'unknown') + .split(' ') + .pop()! + .replace(/[^\w-]/g, '_'); + +export const setUpStreamTestCase = (): StreamTestCase => { + const testCase = new StreamTestCase(); + + beforeEach(async () => { + await testCase.mockServer.start(currentTestName()); + if (device.getPlatform() === 'android') { + dismissAnrDialog(); + } + await device.launchApp({ + launchArgs: { + MOCK_SERVER_URL: testCase.mockServer.appUrl, + // iOS: Detox's idle tracking can wedge on RN's UIManager shadow queue (its pending-work + // counter underflows to 2^64-1), leaving the app "busy" until the test times out. Every + // robot action and assert waits explicitly, like the native XCUITest suites, so it runs + // without that synchronization. Android's Espresso synchronization stays on. + detoxEnableSynchronization: device.getPlatform() === 'ios' ? 0 : 1, + }, + newInstance: true, + // No `permissions`: the app asks for none, and on iOS Detox applies each one with + // `applesimutils --restartSB`, restarting SpringBoard per permission on every launch + // (minutes per test on CI). + }); + }); + + afterEach(async () => { + await testCase.mockServer.stop(); + }); + + return testCase; +}; diff --git a/examples/E2ETestApp/e2e/support/step.ts b/examples/E2ETestApp/e2e/support/step.ts new file mode 100644 index 0000000000..6ff6d18688 --- /dev/null +++ b/examples/E2ETestApp/e2e/support/step.ts @@ -0,0 +1,14 @@ +/** + * A GIVEN / WHEN / THEN step, like `step("...") { }` on Android and `GIVEN("...") { }` on iOS. + * A failure is reported with the step it happened in. + */ +export const step = async (name: string, body: () => PromiseLike) => { + try { + await body(); + } catch (error) { + if (error instanceof Error) { + error.message = `[${name}] ${error.message}`; + } + throw error; + } +}; diff --git a/examples/E2ETestApp/e2e/tests/ChannelListTests.e2e.ts b/examples/E2ETestApp/e2e/tests/ChannelListTests.e2e.ts new file mode 100644 index 0000000000..9a742a8e80 --- /dev/null +++ b/examples/E2ETestApp/e2e/tests/ChannelListTests.e2e.ts @@ -0,0 +1,43 @@ +import { describe, it } from '@jest/globals'; + +import { step } from '../support/step'; +import { setUpStreamTestCase } from '../support/StreamTestCase'; + +describe('ChannelListTests', () => { + const { backendRobot, participantRobot, userRobot } = setUpStreamTestCase(); + const sampleText = 'Test'; + + it('test_channelListShowsGeneratedChannels', async () => { + await step('GIVEN backend has 3 channels', () => + backendRobot.generateChannels({ channelsCount: 3, channelNames: ['Alpha', 'Beta', 'Gamma'] }), + ); + await step('WHEN user logs in', () => userRobot.login().waitForChannelListToLoad()); + await step('THEN user observes all channels in the list', () => + userRobot.assertChannelCount(3).assertChannelAvatar(), + ); + }); + + it('test_channelPreviewUpdates_whenParticipantSendsMessage', async () => { + await step('GIVEN user opens a channel', () => userRobot.login().openChannel()); + await step('WHEN participant sends a message', () => participantRobot.sendMessage(sampleText)); + await step('AND user goes back to the channel list', () => + userRobot.assertMessage(sampleText).moveToChannelListFromMessageList(), + ); + await step('THEN user observes the new message in preview', () => + userRobot.assertMessageInChannelPreview(sampleText, false).assertChannelAvatar(), + ); + }); + + it('test_channelPreviewUpdates_whenUserSendsMessage', async () => { + await step('GIVEN user opens a channel', () => userRobot.login().openChannel()); + await step('WHEN user sends a message', () => + userRobot.sendMessage(sampleText).assertMessage(sampleText), + ); + await step('AND user goes back to the channel list', () => + userRobot.moveToChannelListFromMessageList(), + ); + await step('THEN user observes the new message in preview', () => + userRobot.assertMessageInChannelPreview(sampleText, true), + ); + }); +}); diff --git a/examples/E2ETestApp/e2e/tests/MessageListTests.e2e.ts b/examples/E2ETestApp/e2e/tests/MessageListTests.e2e.ts new file mode 100644 index 0000000000..265ed0d70d --- /dev/null +++ b/examples/E2ETestApp/e2e/tests/MessageListTests.e2e.ts @@ -0,0 +1,44 @@ +import { describe, it } from '@jest/globals'; + +import { MessageDeliveryStatus } from '../mock-server/DataTypes'; +import { step } from '../support/step'; +import { setUpStreamTestCase } from '../support/StreamTestCase'; + +describe('MessageListTests', () => { + const { backendRobot, participantRobot, userRobot } = setUpStreamTestCase(); + const sampleText = 'Test'; + + it('test_messageListUpdates_whenUserSendsMessage', async () => { + await step('GIVEN user opens a channel', () => userRobot.login().openChannel()); + await step('WHEN user sends a message', () => userRobot.sendMessage(sampleText)); + await step('THEN message list updates', () => + userRobot + .assertMessage(sampleText) + .assertMessageAuthor(true) + .assertMessageDeliveryStatus(MessageDeliveryStatus.SENT), + ); + }); + + it('test_messageListUpdates_whenParticipantSendsMessage', async () => { + await step('GIVEN user opens a channel', () => userRobot.login().openChannel()); + await step('WHEN participant sends a message', () => participantRobot.sendMessage(sampleText)); + await step('THEN user receives a message', () => + userRobot.assertMessage(sampleText).assertMessageAuthor(false), + ); + }); + + it('test_typingIndicator', async () => { + await step('GIVEN user opens the channel', async () => { + await backendRobot.generateChannels({ channelsCount: 1, messagesCount: 1 }); + await userRobot.login().openChannel(); + }); + await step('WHEN participant starts typing', () => participantRobot.startTyping()); + await step('THEN user observes typing indicator is shown', () => + userRobot.assertTypingIndicator(true), + ); + await step('WHEN participant stops typing', () => participantRobot.stopTyping()); + await step('THEN user observes typing indicator has disappeared', () => + userRobot.assertTypingIndicator(false), + ); + }); +}); diff --git a/examples/E2ETestApp/e2e/tsconfig.json b/examples/E2ETestApp/e2e/tsconfig.json new file mode 100644 index 0000000000..2f08e6cc4d --- /dev/null +++ b/examples/E2ETestApp/e2e/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "lib": ["es2022", "dom"], + "types": ["detox/globals"] + }, + "include": ["./**/*.ts"], + "exclude": [] +} diff --git a/examples/E2ETestApp/index.js b/examples/E2ETestApp/index.js new file mode 100644 index 0000000000..d2660dcb40 --- /dev/null +++ b/examples/E2ETestApp/index.js @@ -0,0 +1,7 @@ +import 'react-native-gesture-handler'; +import { AppRegistry } from 'react-native'; + +import App from './App'; +import { name as appName } from './app.json'; + +AppRegistry.registerComponent(appName, () => App); diff --git a/examples/E2ETestApp/ios/.xcode.env b/examples/E2ETestApp/ios/.xcode.env new file mode 100644 index 0000000000..3d5782c715 --- /dev/null +++ b/examples/E2ETestApp/ios/.xcode.env @@ -0,0 +1,11 @@ +# This `.xcode.env` file is versioned and is used to source the environment +# used when running script phases inside Xcode. +# To customize your local environment, you can create an `.xcode.env.local` +# file that is not versioned. + +# NODE_BINARY variable contains the PATH to the node executable. +# +# Customize the NODE_BINARY variable here. +# For example, to use nvm with brew, add the following line +# . "$(brew --prefix nvm)/nvm.sh" --no-use +export NODE_BINARY=$(command -v node) diff --git a/examples/E2ETestApp/ios/E2ETestApp.xcodeproj/project.pbxproj b/examples/E2ETestApp/ios/E2ETestApp.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..756385091b --- /dev/null +++ b/examples/E2ETestApp/ios/E2ETestApp.xcodeproj/project.pbxproj @@ -0,0 +1,498 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 0C80B921A6F3F58F76C31292 /* libPods-E2ETestApp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-E2ETestApp.a */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; + 761780EF2CA45674006654EF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EE2CA45674006654EF /* SceneDelegate.swift */; }; + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; + AE4AFD92A8A3756E73E2B1AB /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 13B07F961A680F5B00A75B9A /* E2ETestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = E2ETestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = E2ETestApp/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = E2ETestApp/Info.plist; sourceTree = ""; }; + 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = E2ETestApp/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 3B4392A12AC88292D35C810B /* Pods-E2ETestApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-E2ETestApp.debug.xcconfig"; path = "Target Support Files/Pods-E2ETestApp/Pods-E2ETestApp.debug.xcconfig"; sourceTree = ""; }; + 5709B34CF0A7D63546082F79 /* Pods-E2ETestApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-E2ETestApp.release.xcconfig"; path = "Target Support Files/Pods-E2ETestApp/Pods-E2ETestApp.release.xcconfig"; sourceTree = ""; }; + 5DCACB8F33CDC322A6C60F78 /* libPods-E2ETestApp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-E2ETestApp.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = E2ETestApp/AppDelegate.swift; sourceTree = ""; }; + 761780EE2CA45674006654EF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = E2ETestApp/SceneDelegate.swift; sourceTree = ""; }; + 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = E2ETestApp/LaunchScreen.storyboard; sourceTree = ""; }; + ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0C80B921A6F3F58F76C31292 /* libPods-E2ETestApp.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 13B07FAE1A68108700A75B9A /* E2ETestApp */ = { + isa = PBXGroup; + children = ( + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 761780EC2CA45674006654EE /* AppDelegate.swift */, + 761780EE2CA45674006654EF /* SceneDelegate.swift */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, + 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, + ); + name = E2ETestApp; + sourceTree = ""; + }; + 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { + isa = PBXGroup; + children = ( + ED297162215061F000B7C4FE /* JavaScriptCore.framework */, + 5DCACB8F33CDC322A6C60F78 /* libPods-E2ETestApp.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + ); + name = Libraries; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + 13B07FAE1A68108700A75B9A /* E2ETestApp */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 83CBBA001A601CBA00E9B192 /* Products */, + 2D16E6871FA4F8E400B85C8A /* Frameworks */, + BBD78D7AC51CEA395F1C20DB /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* E2ETestApp.app */, + ); + name = Products; + sourceTree = ""; + }; + BBD78D7AC51CEA395F1C20DB /* Pods */ = { + isa = PBXGroup; + children = ( + 3B4392A12AC88292D35C810B /* Pods-E2ETestApp.debug.xcconfig */, + 5709B34CF0A7D63546082F79 /* Pods-E2ETestApp.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 13B07F861A680F5B00A75B9A /* E2ETestApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "E2ETestApp" */; + buildPhases = ( + C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */, + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */, + E235C05ADACE081382539298 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = E2ETestApp; + productName = E2ETestApp; + productReference = 13B07F961A680F5B00A75B9A /* E2ETestApp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1210; + TargetAttributes = { + 13B07F861A680F5B00A75B9A = { + LastSwiftMigration = 1120; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "E2ETestApp" */; + compatibilityVersion = "Xcode 12.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* E2ETestApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + AE4AFD92A8A3756E73E2B1AB /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/.xcode.env.local", + "$(SRCROOT)/.xcode.env", + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"\\\"$WITH_ENVIRONMENT\\\" \\\"$REACT_NATIVE_XCODE\\\"\"\n"; + }; + 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-E2ETestApp/Pods-E2ETestApp-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-E2ETestApp/Pods-E2ETestApp-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-E2ETestApp/Pods-E2ETestApp-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-E2ETestApp-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + E235C05ADACE081382539298 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-E2ETestApp/Pods-E2ETestApp-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-E2ETestApp/Pods-E2ETestApp-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-E2ETestApp/Pods-E2ETestApp-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */, + 761780EF2CA45674006654EF /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-E2ETestApp.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 1; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = E2ETestApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.getstream.reactnative.e2etestapp; + PRODUCT_NAME = E2ETestApp; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-E2ETestApp.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 1; + INFOPLIST_FILE = E2ETestApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.getstream.reactnative.e2etestapp; + PRODUCT_NAME = E2ETestApp; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = ( + "\"$(SDKROOT)/usr/lib/swift\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-DFOLLY_NO_CONFIG", + "-DFOLLY_MOBILE=1", + "-DFOLLY_USE_LIBCPP=1", + "-DFOLLY_CFG_NO_COROUTINES=1", + "-DFOLLY_HAVE_CLOCK_GETTIME=1", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); + PODFILE_DIR = "$(SRCROOT)"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; + USE_HERMES = true; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = ( + "\"$(SDKROOT)/usr/lib/swift\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-DFOLLY_NO_CONFIG", + "-DFOLLY_MOBILE=1", + "-DFOLLY_USE_LIBCPP=1", + "-DFOLLY_CFG_NO_COROUTINES=1", + "-DFOLLY_HAVE_CLOCK_GETTIME=1", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); + PODFILE_DIR = "$(SRCROOT)"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + USE_HERMES = true; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "E2ETestApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "E2ETestApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/examples/E2ETestApp/ios/E2ETestApp.xcodeproj/xcshareddata/xcschemes/E2ETestApp.xcscheme b/examples/E2ETestApp/ios/E2ETestApp.xcodeproj/xcshareddata/xcschemes/E2ETestApp.xcscheme new file mode 100644 index 0000000000..cd9bdbadeb --- /dev/null +++ b/examples/E2ETestApp/ios/E2ETestApp.xcodeproj/xcshareddata/xcschemes/E2ETestApp.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/E2ETestApp/ios/E2ETestApp.xcworkspace/contents.xcworkspacedata b/examples/E2ETestApp/ios/E2ETestApp.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..bd672bb7e5 --- /dev/null +++ b/examples/E2ETestApp/ios/E2ETestApp.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/E2ETestApp/ios/E2ETestApp/AppDelegate.swift b/examples/E2ETestApp/ios/E2ETestApp/AppDelegate.swift new file mode 100644 index 0000000000..0d14d1b0ac --- /dev/null +++ b/examples/E2ETestApp/ios/E2ETestApp/AppDelegate.swift @@ -0,0 +1,11 @@ +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + true + } +} diff --git a/examples/E2ETestApp/ios/E2ETestApp/Images.xcassets/AppIcon.appiconset/Contents.json b/examples/E2ETestApp/ios/E2ETestApp/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..81213230de --- /dev/null +++ b/examples/E2ETestApp/ios/E2ETestApp/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,53 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/examples/E2ETestApp/ios/E2ETestApp/Images.xcassets/Contents.json b/examples/E2ETestApp/ios/E2ETestApp/Images.xcassets/Contents.json new file mode 100644 index 0000000000..2d92bd53fd --- /dev/null +++ b/examples/E2ETestApp/ios/E2ETestApp/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/examples/E2ETestApp/ios/E2ETestApp/Info.plist b/examples/E2ETestApp/ios/E2ETestApp/Info.plist new file mode 100644 index 0000000000..cf6c5bcfd6 --- /dev/null +++ b/examples/E2ETestApp/ios/E2ETestApp/Info.plist @@ -0,0 +1,78 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + E2ETestApp + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + NSAppTransportSecurity + + + NSAllowsArbitraryLoads + + NSAllowsLocalNetworking + + + NSLocationWhenInUseUsageDescription + + RCTNewArchEnabled + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/examples/E2ETestApp/ios/E2ETestApp/LaunchScreen.storyboard b/examples/E2ETestApp/ios/E2ETestApp/LaunchScreen.storyboard new file mode 100644 index 0000000000..600e8dc84b --- /dev/null +++ b/examples/E2ETestApp/ios/E2ETestApp/LaunchScreen.storyboard @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/E2ETestApp/ios/E2ETestApp/PrivacyInfo.xcprivacy b/examples/E2ETestApp/ios/E2ETestApp/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..fc90547a7a --- /dev/null +++ b/examples/E2ETestApp/ios/E2ETestApp/PrivacyInfo.xcprivacy @@ -0,0 +1,37 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/examples/E2ETestApp/ios/E2ETestApp/SceneDelegate.swift b/examples/E2ETestApp/ios/E2ETestApp/SceneDelegate.swift new file mode 100644 index 0000000000..b5c4d45824 --- /dev/null +++ b/examples/E2ETestApp/ios/E2ETestApp/SceneDelegate.swift @@ -0,0 +1,56 @@ +import React +import React_RCTAppDelegate +import ReactAppDependencyProvider +import UIKit + +class SceneDelegate: RCTDefaultReactNativeFactoryDelegate, UIWindowSceneDelegate { + var window: UIWindow? + var reactNativeFactory: RCTReactNativeFactory? + + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + guard let windowScene = scene as? UIWindowScene else { + return + } + + dependencyProvider = RCTAppDependencyProvider() + reactNativeFactory = RCTReactNativeFactory(delegate: self) + window = UIWindow(windowScene: windowScene) + + #if DEBUG + let devMenuConfiguration = RCTDevMenuConfiguration( + devMenuEnabled: true, + shakeGestureEnabled: true, + keyboardShortcutsEnabled: true + ) + reactNativeFactory?.devMenuConfiguration = devMenuConfiguration + #endif + + reactNativeFactory?.startReactNative( + withModuleName: "E2ETestApp", + in: window, + connectionOptions: connectionOptions + ) + + + } + + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + RCTLinkingManager.scene(scene, openURLContexts: URLContexts) + } + + func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + RCTLinkingManager.scene(scene, continue: userActivity) + } + + override func bundleURL() -> URL? { + #if DEBUG + RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") + #else + Bundle.main.url(forResource: "main", withExtension: "jsbundle") + #endif + } +} diff --git a/examples/E2ETestApp/ios/Podfile b/examples/E2ETestApp/ios/Podfile new file mode 100644 index 0000000000..10e8a8c808 --- /dev/null +++ b/examples/E2ETestApp/ios/Podfile @@ -0,0 +1,45 @@ +# Resolve react_native_pods.rb with node to allow for hoisting +require Pod::Executable.execute_command('node', ['-p', + 'require.resolve( + "react-native/scripts/react_native_pods.rb", + {paths: [process.argv[1]]}, + )', __dir__]).strip + +platform :ios, min_ios_version_supported +prepare_react_native_project! + +linkage = ENV['USE_FRAMEWORKS'] +if linkage != nil + Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green + use_frameworks! :linkage => linkage.to_sym +end + +target 'E2ETestApp' do + config = use_native_modules! + + use_react_native!( + :path => config[:reactNativePath], + # An absolute path to your application root. + :app_path => "#{Pod::Config.instance.installation_root}/.." + ) + + post_install do |installer| + react_native_post_install( + installer, + config[:reactNativePath], + :mac_catalyst_enabled => false, + # CI sets USE_CCACHE=1 so compiled objects are reused across runs. + :ccache_enabled => ENV['USE_CCACHE'] == '1', + ) + + # Some pods pin an iOS deployment target (e.g. RNSVG at 12.4) below the range supported by + # current Xcode. Clamp them up to the app's minimum, as the SampleApp does. + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f < min_ios_version_supported.to_f + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = min_ios_version_supported + end + end + end + end +end diff --git a/examples/E2ETestApp/ios/Podfile.lock b/examples/E2ETestApp/ios/Podfile.lock new file mode 100644 index 0000000000..cb27ec2bdd --- /dev/null +++ b/examples/E2ETestApp/ios/Podfile.lock @@ -0,0 +1,2554 @@ +PODS: + - boost (1.84.0): + - ReactNativeDependencies + - DoubleConversion (1.1.6): + - ReactNativeDependencies + - fast_float (8.0.0): + - ReactNativeDependencies + - FBLazyVector (0.88.0): + - React-Core-prebuilt + - fmt (12.1.0): + - ReactNativeDependencies + - glog (0.3.5): + - ReactNativeDependencies + - hermes-engine (260318099.0.4): + - hermes-engine/Pre-built (= 260318099.0.4) + - hermes-engine/Pre-built (260318099.0.4) + - RCT-Folly (2024.11.18.00): + - RCT-Folly/Default (= 2024.11.18.00) + - ReactNativeDependencies + - RCT-Folly/Default (2024.11.18.00): + - ReactNativeDependencies + - RCTDeprecation (0.88.0): + - React-Core-prebuilt + - RCTRequired (0.88.0): + - React-Core-prebuilt + - RCTSwiftUI (0.88.0) + - RCTSwiftUIWrapper (0.88.0): + - RCTSwiftUI + - RCTTypeSafety (0.88.0): + - FBLazyVector (= 0.88.0) + - RCTRequired (= 0.88.0) + - React-Core (= 0.88.0) + - React (0.88.0): + - React-Core (= 0.88.0) + - React-Core/DevSupport (= 0.88.0) + - React-Core/RCTWebSocket (= 0.88.0) + - React-RCTActionSheet (= 0.88.0) + - React-RCTAnimation (= 0.88.0) + - React-RCTBlob (= 0.88.0) + - React-RCTImage (= 0.88.0) + - React-RCTLinking (= 0.88.0) + - React-RCTNetwork (= 0.88.0) + - React-RCTSettings (= 0.88.0) + - React-RCTText (= 0.88.0) + - React-RCTVibration (= 0.88.0) + - React-bridging (0.88.0): + - React-callinvoker + - React-Core-prebuilt + - React-jsi + - React-timing + - ReactNativeDependencies + - React-callinvoker (0.88.0): + - React-callinvoker/callinvokerUmbrella (= 0.88.0) + - React-cxxstableapi + - React-callinvoker/callinvokerUmbrella (0.88.0): + - React-cxxstableapi + - React-Core (0.88.0): + - React-Core-prebuilt + - React-Core/Default (= 0.88.0) + - React-Core-prebuilt (0.88.0): + - ReactNativeDependencies + - React-Core/CoreModulesHeaders (0.88.0): + - React-Core-prebuilt + - React-Core/Default (0.88.0): + - React-Core-prebuilt + - React-Core/DevSupport (0.88.0): + - React-Core-prebuilt + - React-Core/RCTActionSheetHeaders (0.88.0): + - React-Core-prebuilt + - React-Core/RCTAnimationHeaders (0.88.0): + - React-Core-prebuilt + - React-Core/RCTBlobHeaders (0.88.0): + - React-Core-prebuilt + - React-Core/RCTImageHeaders (0.88.0): + - React-Core-prebuilt + - React-Core/RCTLinkingHeaders (0.88.0): + - React-Core-prebuilt + - React-Core/RCTNetworkHeaders (0.88.0): + - React-Core-prebuilt + - React-Core/RCTSettingsHeaders (0.88.0): + - React-Core-prebuilt + - React-Core/RCTTextHeaders (0.88.0): + - React-Core-prebuilt + - React-Core/RCTVibrationHeaders (0.88.0): + - React-Core-prebuilt + - React-Core/RCTWebSocket (0.88.0): + - React-Core-prebuilt + - React-CoreModules (0.88.0): + - RCTTypeSafety (= 0.88.0) + - React-Core-prebuilt + - React-Core/CoreModulesHeaders (= 0.88.0) + - React-debug + - React-featureflags + - React-jsi (= 0.88.0) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-NativeModulesApple + - React-networking + - React-RCTBlob + - React-RCTFBReactNativeSpec + - React-RCTImage (= 0.88.0) + - React-runtimeexecutor + - React-utils + - ReactCommon + - ReactNativeDependencies + - React-cxxreact (0.88.0): + - hermes-engine + - React-callinvoker (= 0.88.0) + - React-Core-prebuilt + - React-debug (= 0.88.0) + - React-jserrorhandler (= 0.88.0) + - React-jsi (= 0.88.0) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-logger (= 0.88.0) + - React-perflogger (= 0.88.0) + - React-runtimeexecutor + - React-timing (= 0.88.0) + - React-utils + - ReactNativeDependencies + - React-cxxstableapi (0.88.0) + - React-debug (0.88.0): + - React-cxxstableapi + - React-debug/debugUmbrella (= 0.88.0) + - React-debug/redbox (= 0.88.0) + - React-debug/debugUmbrella (0.88.0): + - React-cxxstableapi + - React-debug/redbox (0.88.0): + - React-cxxstableapi + - React-defaultsnativemodule (0.88.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxstableapi + - React-domnativemodule + - React-Fabric/animated + - React-featureflags + - React-featureflagsnativemodule + - React-idlecallbacksnativemodule + - React-intersectionobservernativemodule + - React-jsi + - React-jsiexecutor + - React-microtasksnativemodule + - React-mutationobservernativemodule + - React-RCTFBReactNativeSpec + - React-resizeobservernativemodule + - React-viewtransitionnativemodule + - React-webperformancenativemodule + - ReactNativeDependencies + - Yoga + - React-domnativemodule (0.88.0): + - hermes-engine + - React-bridging + - React-Core-prebuilt + - React-cxxstableapi + - React-Fabric + - React-Fabric/bridging + - React-FabricComponents + - React-graphics + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-Fabric (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric/animated (= 0.88.0) + - React-Fabric/animationbackend (= 0.88.0) + - React-Fabric/animations (= 0.88.0) + - React-Fabric/attributedstring (= 0.88.0) + - React-Fabric/bridging (= 0.88.0) + - React-Fabric/componentregistry (= 0.88.0) + - React-Fabric/componentregistrynative (= 0.88.0) + - React-Fabric/components (= 0.88.0) + - React-Fabric/consistency (= 0.88.0) + - React-Fabric/core (= 0.88.0) + - React-Fabric/dom (= 0.88.0) + - React-Fabric/imagemanager (= 0.88.0) + - React-Fabric/leakchecker (= 0.88.0) + - React-Fabric/mounting (= 0.88.0) + - React-Fabric/observers (= 0.88.0) + - React-Fabric/scheduler (= 0.88.0) + - React-Fabric/telemetry (= 0.88.0) + - React-Fabric/uimanager (= 0.88.0) + - React-Fabric/viewtransition (= 0.88.0) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/animated (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric/animationbackend + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/animationbackend (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/animations (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/attributedstring (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/bridging (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/componentregistry (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/componentregistrynative (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric/components/legacyviewmanagerinterop (= 0.88.0) + - React-Fabric/components/root (= 0.88.0) + - React-Fabric/components/scrollview (= 0.88.0) + - React-Fabric/components/view (= 0.88.0) + - React-Fabric/components/viewUmbrella (= 0.88.0) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/legacyviewmanagerinterop (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/root (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/scrollview (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/view (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-renderercss + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-Fabric/components/viewUmbrella (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/consistency (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/core (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/dom (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/imagemanager (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/leakchecker (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/mounting (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-jsinspectortracing + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric/observers/events (= 0.88.0) + - React-Fabric/observers/intersection (= 0.88.0) + - React-Fabric/observers/mutation (= 0.88.0) + - React-Fabric/observers/resize (= 0.88.0) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers/events (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers/intersection (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers/mutation (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers/resize (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/scheduler (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric/animationbackend + - React-Fabric/observers/events + - React-Fabric/viewtransition + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-performancecdpmetrics + - React-performancetimeline + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/telemetry (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/uimanager (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric/uimanager/consistency (= 0.88.0) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/uimanager/consistency (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/viewtransition (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-FabricComponents (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric + - React-FabricComponents/components (= 0.88.0) + - React-FabricComponents/textlayoutmanager (= 0.88.0) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric + - React-FabricComponents/components/inputaccessory (= 0.88.0) + - React-FabricComponents/components/iostextinput (= 0.88.0) + - React-FabricComponents/components/modal (= 0.88.0) + - React-FabricComponents/components/rncore (= 0.88.0) + - React-FabricComponents/components/safeareaview (= 0.88.0) + - React-FabricComponents/components/switch (= 0.88.0) + - React-FabricComponents/components/text (= 0.88.0) + - React-FabricComponents/components/textinput (= 0.88.0) + - React-FabricComponents/components/unimplementedview (= 0.88.0) + - React-FabricComponents/components/virtualview (= 0.88.0) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/inputaccessory (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/iostextinput (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/modal (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/rncore (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/safeareaview (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/switch (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/text (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/textinput (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/unimplementedview (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/virtualview (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/textlayoutmanager (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricImage (0.88.0): + - hermes-engine + - RCTRequired (= 0.88.0) + - RCTTypeSafety (= 0.88.0) + - React-Core-prebuilt + - React-cxxstableapi + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsiexecutor (= 0.88.0) + - React-logger + - React-rendererdebug + - React-utils + - ReactCommon + - ReactNativeDependencies + - Yoga + - React-featureflags (0.88.0): + - React-Core-prebuilt + - React-cxxstableapi + - React-featureflags/featureflagsUmbrella (= 0.88.0) + - ReactNativeDependencies + - React-featureflags/featureflagsUmbrella (0.88.0): + - React-Core-prebuilt + - React-cxxstableapi + - ReactNativeDependencies + - React-featureflagsnativemodule (0.88.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxstableapi + - React-featureflags + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-graphics (0.88.0): + - hermes-engine + - React-Core-prebuilt + - React-featureflags + - React-jsi + - React-jsiexecutor + - React-rendererdebug + - React-utils + - ReactNativeDependencies + - React-hermes (0.88.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact (= 0.88.0) + - React-cxxstableapi + - React-jsi + - React-jsiexecutor (= 0.88.0) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-oscompat + - React-perflogger (= 0.88.0) + - React-runtimeexecutor + - ReactNativeDependencies + - React-idlecallbacksnativemodule (0.88.0): + - hermes-engine + - React-bridging + - React-Core-prebuilt + - React-cxxstableapi + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - React-runtimescheduler + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-ImageManager (0.88.0): + - React-Core-prebuilt + - React-Core/Default + - React-cxxstableapi + - React-debug + - React-Fabric + - React-graphics + - React-rendererdebug + - React-utils + - ReactNativeDependencies + - React-intersectionobservernativemodule (0.88.0): + - hermes-engine + - React-bridging + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-Fabric + - React-Fabric/bridging + - React-graphics + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - React-runtimescheduler + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-jserrorhandler (0.88.0): + - hermes-engine + - React-bridging + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-featureflags + - React-jsi + - ReactNativeDependencies + - React-jsi (0.88.0): + - hermes-engine + - React-Core-prebuilt + - ReactNativeDependencies + - React-jsiexecutor (0.88.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-jserrorhandler + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-jsinspector (0.88.0): + - hermes-engine + - React-Core-prebuilt + - React-featureflags + - React-jsi + - React-jsinspectorcdp + - React-jsinspectornetwork + - React-jsinspectortracing + - React-oscompat + - React-perflogger (= 0.88.0) + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-jsinspectorcdp (0.88.0): + - React-Core-prebuilt + - React-cxxstableapi + - ReactNativeDependencies + - React-jsinspectornetwork (0.88.0): + - React-Core-prebuilt + - React-cxxstableapi + - React-jsinspectorcdp + - ReactNativeDependencies + - React-jsinspectortracing (0.88.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxstableapi + - React-jsi + - React-jsinspectornetwork + - React-oscompat + - React-timing + - React-utils + - ReactNativeDependencies + - React-jsitooling (0.88.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact (= 0.88.0) + - React-cxxstableapi + - React-debug + - React-jsi (= 0.88.0) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-jsitracing (0.88.0): + - React-jsi + - React-logger (0.88.0): + - React-Core-prebuilt + - ReactNativeDependencies + - React-Mapbuffer (0.88.0): + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Mapbuffer/mapbufferUmbrella (= 0.88.0) + - ReactNativeDependencies + - React-Mapbuffer/mapbufferUmbrella (0.88.0): + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - ReactNativeDependencies + - React-microtasksnativemodule (0.88.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxstableapi + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-mutationobservernativemodule (0.88.0): + - hermes-engine + - React-bridging + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-Fabric + - React-Fabric/bridging + - React-Fabric/observers/mutation + - React-featureflags + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-launch-arguments (4.1.1): + - React + - react-native-netinfo (12.0.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-safe-area-context (5.10.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - react-native-safe-area-context/common (= 5.10.1) + - react-native-safe-area-context/fabric (= 5.10.1) + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-safe-area-context/common (5.10.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-safe-area-context/fabric (5.10.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - react-native-safe-area-context/common + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-NativeModulesApple (0.88.0): + - hermes-engine + - React-bridging + - React-callinvoker + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-runtimeexecutor + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-networking (0.88.0): + - React-Core-prebuilt + - React-cxxstableapi + - React-jsinspectornetwork + - React-jsinspectortracing + - React-performancetimeline + - React-timing + - ReactNativeDependencies + - React-oscompat (0.88.0): + - React-cxxstableapi + - React-perflogger (0.88.0): + - React-Core-prebuilt + - ReactNativeDependencies + - React-performancecdpmetrics (0.88.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxstableapi + - React-jsi + - React-performancetimeline + - React-runtimeexecutor + - React-timing + - ReactNativeDependencies + - React-performancetimeline (0.88.0): + - React-Core-prebuilt + - React-cxxstableapi + - React-featureflags + - React-jsinspector + - React-jsinspectortracing + - React-perflogger + - React-timing + - ReactNativeDependencies + - React-RCTActionSheet (0.88.0): + - React-Core/RCTActionSheetHeaders (= 0.88.0) + - React-RCTAnimatedModuleProvider (0.88.0): + - hermes-engine + - React-Core + - React-Core-prebuilt + - React-Fabric/animated + - React-featureflags + - ReactCommon + - ReactNativeDependencies + - Yoga + - React-RCTAnimation (0.88.0): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTAnimationHeaders + - React-debug + - React-featureflags + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-RCTAppDelegate (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-CoreModules + - React-debug + - React-defaultsnativemodule + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-jsitooling + - React-NativeModulesApple + - React-RCTAnimatedModuleProvider + - React-RCTFabric + - React-RCTFBReactNativeSpec + - React-RCTImage + - React-RCTLinking + - React-RCTNetwork + - React-RCTRuntime + - React-rendererdebug + - React-RuntimeApple + - React-RuntimeCore + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon + - ReactNativeDependencies + - React-RCTBlob (0.88.0): + - hermes-engine + - React-Core-prebuilt + - React-Core/RCTBlobHeaders + - React-Core/RCTWebSocket + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - React-RCTNetwork + - ReactCommon + - ReactNativeDependencies + - React-RCTFabric (0.88.0): + - React-Core-prebuilt + - React-RCTFBReactNativeSpec (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec/components (= 0.88.0) + - ReactCommon + - ReactNativeDependencies + - React-RCTFBReactNativeSpec/components (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-NativeModulesApple + - React-rendererdebug + - React-utils + - ReactCommon + - ReactNativeDependencies + - Yoga + - React-RCTImage (0.88.0): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTImageHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - React-RCTNetwork + - ReactCommon + - ReactNativeDependencies + - React-RCTLinking (0.88.0): + - React-Core/RCTLinkingHeaders (= 0.88.0) + - React-jsi (= 0.88.0) + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactCommon/turbomodule/core (= 0.88.0) + - React-RCTNetwork (0.88.0): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTNetworkHeaders + - React-debug + - React-featureflags + - React-jsi + - React-jsinspectorcdp + - React-jsinspectornetwork + - React-NativeModulesApple + - React-networking + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-RCTRuntime (0.88.0): + - React-Core-prebuilt + - React-RCTSettings (0.88.0): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTSettingsHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-RCTText (0.88.0): + - React-Core/RCTTextHeaders (= 0.88.0) + - Yoga + - React-RCTVibration (0.88.0): + - React-Core-prebuilt + - React-Core/RCTVibrationHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-rendererconsistency (0.88.0) + - React-renderercss (0.88.0): + - React-debug + - React-utils + - React-rendererdebug (0.88.0): + - React-Core-prebuilt + - React-debug + - ReactNativeDependencies + - React-resizeobservernativemodule (0.88.0): + - hermes-engine + - React-bridging + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-Fabric + - React-Fabric/bridging + - React-Fabric/observers/resize + - React-graphics + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - React-runtimescheduler + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-RuntimeApple (0.88.0): + - hermes-engine + - React-callinvoker + - React-Core-prebuilt + - React-Core/Default + - React-CoreModules + - React-cxxreact + - React-cxxstableapi + - React-featureflags + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsitooling + - React-Mapbuffer + - React-NativeModulesApple + - React-RCTFabric + - React-RCTFBReactNativeSpec + - React-RuntimeCore + - React-runtimeexecutor + - React-RuntimeHermes + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - React-RuntimeCore (0.88.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-Fabric + - React-featureflags + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsitooling + - React-performancetimeline + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - React-runtimeexecutor (0.88.0): + - React-Core-prebuilt + - React-debug + - React-featureflags + - React-jsi (= 0.88.0) + - React-utils + - ReactNativeDependencies + - React-RuntimeHermes (0.88.0): + - hermes-engine + - React-Core-prebuilt + - React-cxxstableapi + - React-featureflags + - React-hermes + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-jsitracing + - React-RuntimeCore + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-runtimescheduler (0.88.0): + - hermes-engine + - React-callinvoker + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-debug + - React-featureflags + - React-jserrorhandler + - React-jsi + - React-jsinspectortracing + - React-performancetimeline + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-timing + - React-utils + - ReactNativeDependencies + - React-timing (0.88.0): + - React-cxxstableapi + - React-debug + - React-timing/timingUmbrella (= 0.88.0) + - React-timing/timingUmbrella (0.88.0): + - React-cxxstableapi + - React-debug + - React-utils (0.88.0): + - hermes-engine + - React-Core-prebuilt + - React-debug + - React-jsi (= 0.88.0) + - ReactNativeDependencies + - React-viewtransitionnativemodule (0.88.0): + - hermes-engine + - React-Core-prebuilt + - React-Fabric + - React-Fabric/bridging + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-webperformancenativemodule (0.88.0): + - hermes-engine + - React-bridging + - React-Core-prebuilt + - React-cxxreact + - React-cxxstableapi + - React-jsi + - React-jsiexecutor + - React-performancetimeline + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - ReactAppDependencyProvider (0.88.0): + - ReactCodegen + - ReactCodegen (0.88.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-FabricImage + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-NativeModulesApple + - React-RCTAppDelegate + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - ReactCommon (0.88.0): + - React-Core-prebuilt + - ReactCommon/turbomodule (= 0.88.0) + - ReactNativeDependencies + - ReactCommon/turbomodule (0.88.0): + - hermes-engine + - React-callinvoker (= 0.88.0) + - React-Core-prebuilt + - React-jsi (= 0.88.0) + - React-logger (= 0.88.0) + - React-perflogger (= 0.88.0) + - ReactCommon/turbomodule/core (= 0.88.0) + - ReactNativeDependencies + - ReactCommon/turbomodule/core (0.88.0): + - hermes-engine + - React-bridging + - React-callinvoker (= 0.88.0) + - React-Core-prebuilt + - React-cxxreact (= 0.88.0) + - React-debug (= 0.88.0) + - React-featureflags (= 0.88.0) + - React-jsi (= 0.88.0) + - React-logger (= 0.88.0) + - React-perflogger (= 0.88.0) + - React-utils (= 0.88.0) + - ReactNativeDependencies + - ReactNativeDependencies (0.88.0) + - RNGestureHandler (3.4.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets (>= 0.8.0) + - Yoga + - RNReanimated (4.7.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNReanimated/apple (= 4.7.1) + - RNReanimated/common (= 4.7.1) + - RNReanimated/view (= 4.7.1) + - RNWorklets + - Yoga + - RNReanimated/apple (4.7.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets + - Yoga + - RNReanimated/common (4.7.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets + - Yoga + - RNReanimated/view (4.7.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets + - Yoga + - RNSVG (15.15.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNSVG/common (= 15.15.5) + - Yoga + - RNSVG/common (15.15.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - RNWorklets (0.13.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets/apple (= 0.13.0) + - RNWorklets/common (= 0.13.0) + - Yoga + - RNWorklets/apple (0.13.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - RNWorklets/common (0.13.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - SocketRocket (0.7.1): + - ReactNativeDependencies + - stream-chat-react-native (9.10.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - Teleport (1.2.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Teleport/common (= 1.2.2) + - Yoga + - Teleport/common (1.2.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-bridging + - React-Core + - React-Core-prebuilt + - React-cxxstableapi + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - Yoga (0.0.0): + - React-Core-prebuilt + +DEPENDENCIES: + - boost (from `build/rndeps-facades/boost`) + - DoubleConversion (from `build/rndeps-facades/DoubleConversion`) + - fast_float (from `build/rndeps-facades/fast_float`) + - FBLazyVector (from `build/rncore-facades/FBLazyVector`) + - fmt (from `build/rndeps-facades/fmt`) + - glog (from `build/rndeps-facades/glog`) + - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) + - RCT-Folly (from `build/rndeps-facades/RCT-Folly`) + - RCTDeprecation (from `build/rncore-facades/RCTDeprecation`) + - RCTRequired (from `build/rncore-facades/RCTRequired`) + - RCTSwiftUI (from `../node_modules/react-native/ReactApple/RCTSwiftUI`) + - RCTSwiftUIWrapper (from `../node_modules/react-native/ReactApple/RCTSwiftUIWrapper`) + - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) + - React (from `../node_modules/react-native/`) + - React-bridging (from `../node_modules/react-native/ReactCommon/react/bridging`) + - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) + - React-Core (from `build/rncore-facades/React-Core`) + - React-Core-prebuilt (from `../node_modules/react-native/React-Core-prebuilt.podspec`) + - React-Core/RCTWebSocket (from `build/rncore-facades/React-Core`) + - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) + - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) + - React-cxxstableapi (from `../node_modules/react-native/ReactCommon/react/cxxstableapi`) + - React-debug (from `../node_modules/react-native/ReactCommon/react/debug`) + - React-defaultsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/defaults`) + - React-domnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/dom`) + - React-Fabric (from `../node_modules/react-native/ReactCommon`) + - React-FabricComponents (from `../node_modules/react-native/ReactCommon`) + - React-FabricImage (from `../node_modules/react-native/ReactCommon`) + - React-featureflags (from `../node_modules/react-native/ReactCommon/react/featureflags`) + - React-featureflagsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`) + - React-graphics (from `../node_modules/react-native/ReactCommon/react/renderer/graphics`) + - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) + - React-idlecallbacksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`) + - React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) + - React-intersectionobservernativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/intersectionobserver`) + - React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`) + - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) + - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) + - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`) + - React-jsinspectorcdp (from `../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`) + - React-jsinspectornetwork (from `../node_modules/react-native/ReactCommon/jsinspector-modern/network`) + - React-jsinspectortracing (from `../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`) + - React-jsitooling (from `../node_modules/react-native/ReactCommon/jsitooling`) + - React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`) + - React-logger (from `../node_modules/react-native/ReactCommon/logger`) + - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) + - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - React-mutationobservernativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/mutationobserver`) + - react-native-launch-arguments (from `../node_modules/react-native-launch-arguments`) + - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" + - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) + - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) + - React-networking (from `../node_modules/react-native/ReactCommon/react/networking`) + - React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`) + - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) + - React-performancecdpmetrics (from `../node_modules/react-native/ReactCommon/react/performance/cdpmetrics`) + - React-performancetimeline (from `../node_modules/react-native/ReactCommon/react/performance/timeline`) + - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) + - React-RCTAnimatedModuleProvider (from `../node_modules/react-native/ReactApple/RCTAnimatedModuleProvider`) + - 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 `build/rncore-facades/React-RCTFabric`) + - React-RCTFBReactNativeSpec (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-RCTRuntime (from `build/rncore-facades/React-RCTRuntime`) + - 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-rendererconsistency (from `../node_modules/react-native/ReactCommon/react/renderer/consistency`) + - React-renderercss (from `../node_modules/react-native/ReactCommon/react/renderer/css`) + - React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`) + - React-resizeobservernativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/resizeobserver`) + - React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) + - React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) + - React-RuntimeHermes (from `../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) + - React-timing (from `../node_modules/react-native/ReactCommon/react/timing`) + - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) + - React-viewtransitionnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/viewtransition`) + - React-webperformancenativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/webperformance`) + - ReactAppDependencyProvider (from `build/generated/ios/ReactAppDependencyProvider`) + - ReactCodegen (from `build/generated/ios/ReactCodegen`) + - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - ReactNativeDependencies (from `../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`) + - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) + - RNReanimated (from `../node_modules/react-native-reanimated`) + - RNSVG (from `../node_modules/react-native-svg`) + - RNWorklets (from `../node_modules/react-native-worklets`) + - SocketRocket (from `build/rndeps-facades/SocketRocket`) + - stream-chat-react-native (from `../node_modules/stream-chat-react-native`) + - Teleport (from `../node_modules/react-native-teleport`) + - Yoga (from `build/rncore-facades/Yoga`) + +EXTERNAL SOURCES: + boost: + :path: build/rndeps-facades/boost + DoubleConversion: + :path: build/rndeps-facades/DoubleConversion + fast_float: + :path: build/rndeps-facades/fast_float + FBLazyVector: + :path: build/rncore-facades/FBLazyVector + fmt: + :path: build/rndeps-facades/fmt + glog: + :path: build/rndeps-facades/glog + hermes-engine: + :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" + :tag: hermes-v260318099.0.2 + RCT-Folly: + :path: build/rndeps-facades/RCT-Folly + RCTDeprecation: + :path: build/rncore-facades/RCTDeprecation + RCTRequired: + :path: build/rncore-facades/RCTRequired + RCTSwiftUI: + :path: "../node_modules/react-native/ReactApple/RCTSwiftUI" + RCTSwiftUIWrapper: + :path: "../node_modules/react-native/ReactApple/RCTSwiftUIWrapper" + RCTTypeSafety: + :path: "../node_modules/react-native/Libraries/TypeSafety" + React: + :path: "../node_modules/react-native/" + React-bridging: + :path: "../node_modules/react-native/ReactCommon/react/bridging" + React-callinvoker: + :path: "../node_modules/react-native/ReactCommon/callinvoker" + React-Core: + :path: build/rncore-facades/React-Core + React-Core-prebuilt: + :podspec: "../node_modules/react-native/React-Core-prebuilt.podspec" + React-CoreModules: + :path: "../node_modules/react-native/React/CoreModules" + React-cxxreact: + :path: "../node_modules/react-native/ReactCommon/cxxreact" + React-cxxstableapi: + :path: "../node_modules/react-native/ReactCommon/react/cxxstableapi" + React-debug: + :path: "../node_modules/react-native/ReactCommon/react/debug" + React-defaultsnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/defaults" + React-domnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/dom" + React-Fabric: + :path: "../node_modules/react-native/ReactCommon" + React-FabricComponents: + :path: "../node_modules/react-native/ReactCommon" + React-FabricImage: + :path: "../node_modules/react-native/ReactCommon" + React-featureflags: + :path: "../node_modules/react-native/ReactCommon/react/featureflags" + React-featureflagsnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/featureflags" + React-graphics: + :path: "../node_modules/react-native/ReactCommon/react/renderer/graphics" + React-hermes: + :path: "../node_modules/react-native/ReactCommon/hermes" + React-idlecallbacksnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" + React-ImageManager: + :path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" + React-intersectionobservernativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/intersectionobserver" + React-jserrorhandler: + :path: "../node_modules/react-native/ReactCommon/jserrorhandler" + React-jsi: + :path: "../node_modules/react-native/ReactCommon/jsi" + React-jsiexecutor: + :path: "../node_modules/react-native/ReactCommon/jsiexecutor" + React-jsinspector: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern" + React-jsinspectorcdp: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/cdp" + React-jsinspectornetwork: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/network" + React-jsinspectortracing: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/tracing" + React-jsitooling: + :path: "../node_modules/react-native/ReactCommon/jsitooling" + React-jsitracing: + :path: "../node_modules/react-native/ReactCommon/hermes/executor/" + React-logger: + :path: "../node_modules/react-native/ReactCommon/logger" + React-Mapbuffer: + :path: "../node_modules/react-native/ReactCommon" + React-microtasksnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + React-mutationobservernativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/mutationobserver" + react-native-launch-arguments: + :path: "../node_modules/react-native-launch-arguments" + react-native-netinfo: + :path: "../node_modules/@react-native-community/netinfo" + react-native-safe-area-context: + :path: "../node_modules/react-native-safe-area-context" + React-NativeModulesApple: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" + React-networking: + :path: "../node_modules/react-native/ReactCommon/react/networking" + React-oscompat: + :path: "../node_modules/react-native/ReactCommon/oscompat" + React-perflogger: + :path: "../node_modules/react-native/ReactCommon/reactperflogger" + React-performancecdpmetrics: + :path: "../node_modules/react-native/ReactCommon/react/performance/cdpmetrics" + React-performancetimeline: + :path: "../node_modules/react-native/ReactCommon/react/performance/timeline" + React-RCTActionSheet: + :path: "../node_modules/react-native/Libraries/ActionSheetIOS" + React-RCTAnimatedModuleProvider: + :path: "../node_modules/react-native/ReactApple/RCTAnimatedModuleProvider" + React-RCTAnimation: + :path: "../node_modules/react-native/Libraries/NativeAnimation" + React-RCTAppDelegate: + :path: "../node_modules/react-native/Libraries/AppDelegate" + React-RCTBlob: + :path: "../node_modules/react-native/Libraries/Blob" + React-RCTFabric: + :path: build/rncore-facades/React-RCTFabric + React-RCTFBReactNativeSpec: + :path: "../node_modules/react-native/React" + React-RCTImage: + :path: "../node_modules/react-native/Libraries/Image" + React-RCTLinking: + :path: "../node_modules/react-native/Libraries/LinkingIOS" + React-RCTNetwork: + :path: "../node_modules/react-native/Libraries/Network" + React-RCTRuntime: + :path: build/rncore-facades/React-RCTRuntime + React-RCTSettings: + :path: "../node_modules/react-native/Libraries/Settings" + React-RCTText: + :path: "../node_modules/react-native/Libraries/Text" + React-RCTVibration: + :path: "../node_modules/react-native/Libraries/Vibration" + React-rendererconsistency: + :path: "../node_modules/react-native/ReactCommon/react/renderer/consistency" + React-renderercss: + :path: "../node_modules/react-native/ReactCommon/react/renderer/css" + React-rendererdebug: + :path: "../node_modules/react-native/ReactCommon/react/renderer/debug" + React-resizeobservernativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/resizeobserver" + React-RuntimeApple: + :path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios" + React-RuntimeCore: + :path: "../node_modules/react-native/ReactCommon/react/runtime" + React-runtimeexecutor: + :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" + React-RuntimeHermes: + :path: "../node_modules/react-native/ReactCommon/react/runtime" + React-runtimescheduler: + :path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" + React-timing: + :path: "../node_modules/react-native/ReactCommon/react/timing" + React-utils: + :path: "../node_modules/react-native/ReactCommon/react/utils" + React-viewtransitionnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/viewtransition" + React-webperformancenativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/webperformance" + ReactAppDependencyProvider: + :path: build/generated/ios/ReactAppDependencyProvider + ReactCodegen: + :path: build/generated/ios/ReactCodegen + ReactCommon: + :path: "../node_modules/react-native/ReactCommon" + ReactNativeDependencies: + :podspec: "../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec" + RNGestureHandler: + :path: "../node_modules/react-native-gesture-handler" + RNReanimated: + :path: "../node_modules/react-native-reanimated" + RNSVG: + :path: "../node_modules/react-native-svg" + RNWorklets: + :path: "../node_modules/react-native-worklets" + SocketRocket: + :path: build/rndeps-facades/SocketRocket + stream-chat-react-native: + :path: "../node_modules/stream-chat-react-native" + Teleport: + :path: "../node_modules/react-native-teleport" + Yoga: + :path: build/rncore-facades/Yoga + +SPEC CHECKSUMS: + boost: 8502bfdc46e2804408af2e163049ece181009af4 + DoubleConversion: 501a575c40c0b50c89685762dd3e415e6bb26fb0 + fast_float: 7c925d1cebf8328d4018d061f49377e9f887e3b4 + FBLazyVector: fdebac2dfe9c53bca49f8218fbdf1e24e36c8be0 + fmt: ba0886e864ebb21d14c98b2e12e164b8002f5b93 + glog: 3cf7cb46f743c4bc758ae3e6d9c15bd1b4cfd6d4 + hermes-engine: a1158910bb7e300b9e3d2f1e2d433f817ec1a4b5 + RCT-Folly: 82921e585c0400eda46fac1068297aa9dfb035e4 + RCTDeprecation: e134f2a2c096a6450f5fcbbe949531f913a2ff04 + RCTRequired: f1a68295837ee8f91e112ed1b5694d12e7ff1784 + RCTSwiftUI: 4b21ec5b1f86acb9c96bb429100ad88d4023ed76 + RCTSwiftUIWrapper: 68bd9fac4e710259cb2b202472adc5f4d113e872 + RCTTypeSafety: 77078a9c26030dd84a6bdae74ed900159f89a098 + React: 8dafd0ef058191e9e7238cfa6b3deaa24398e5dd + React-bridging: 658efeb1b4704d5f60ea2fb6e8ca826ab0ae3234 + React-callinvoker: 4e40c208c1ea46686c084a7c23da6a65cbb99482 + React-Core: a831be15fcea0e345a24cb10d6cc0b87e9913d03 + React-Core-prebuilt: 0fd2555a42f40e7770ced1c7dfc5ee1dbfe041cd + React-CoreModules: a86fbe698fd817c17b3936832dc1106a130c4694 + React-cxxreact: 48ca56bd4f26de869478a803a5544b101a57cde6 + React-cxxstableapi: f35502b4c542a0d44031b42f91c4ca813d267e16 + React-debug: 238e15d17dfded9419a6d739d4c8b60968bad02e + React-defaultsnativemodule: c028edc7e48914344b823931aec1cc4ffabe631e + React-domnativemodule: b09770fc69f5ce5759de98f8065cab900b6d87d6 + React-Fabric: a157c978603fa2796f349f3a27d17861e55804c5 + React-FabricComponents: c7c58a1deea5db215e9d5a2aec890e208d5d5075 + React-FabricImage: 234346bf5a3ef918f3873e845b57b93b2b1f31c0 + React-featureflags: a00ac5c38e63d5f86de895ca923f07f5ff76de66 + React-featureflagsnativemodule: 6b93ba0aef46757b9d54166475abc5e4fa80e70d + React-graphics: 634bc2e5884b4a29f56aa8aef719e03171a23f6b + React-hermes: 9ce7aaf6784709e196ac51bfd89dd6c551e3fb4c + React-idlecallbacksnativemodule: e02e194086278377ced48b7fd7816b6c925b9e7e + React-ImageManager: 452d4b3587f21d3384868e2abaab7c0a952b1d1c + React-intersectionobservernativemodule: 3ddbc206bc95c086f1df6eea04aa617ae02627d2 + React-jserrorhandler: a0d842e1f74cc10e597fde2a6447860483df2b13 + React-jsi: 489de0620905b88a17ccb0ac2328046af43e7ebd + React-jsiexecutor: f0e2ae6d98b587d3505ebde246599ee3ad97cee1 + React-jsinspector: 515bf11caeda5df444f39aec63e377ef77ad7696 + React-jsinspectorcdp: be9ac71a77880a6fcc95375e7e61a79e152a3203 + React-jsinspectornetwork: a4bc1475897a2301d114d7a8605af3e8760f9e52 + React-jsinspectortracing: 7d35952c944dc337d1a4167605c1338108880219 + React-jsitooling: 1886dc5058e9feded2136b0d9ba5c189c6af5faa + React-jsitracing: 76b58feee3fb0539c8967e5b9de1a40ad8014f09 + React-logger: 3d2b406a780fed50ad0bbd07452b373c2a515308 + React-Mapbuffer: a2dae4f2cb335ebb97f2240494d0c2bab84cd339 + React-microtasksnativemodule: 8b3aaa5f3b907ecf071b105589868d9f027a448d + React-mutationobservernativemodule: 11d77d3f7360578f07847843544ada23cc473049 + react-native-launch-arguments: eab68037b01ea7c78a0b3b55da3a5e967e9aaf92 + react-native-netinfo: 687391ecedb199c6d67fbc46c87cbae3277de967 + react-native-safe-area-context: b1c9f15a36e597e9cef4c5526e209ef2645bc2e0 + React-NativeModulesApple: 52f3def085f129de1307e3eb78e26729ff70f421 + React-networking: 1e6441349ac72bfd4f7a79c300c3dd76993a7058 + React-oscompat: e64123dd8199f9e99cede19fa542c3da3445481e + React-perflogger: f1466e0f46ce706aef73a559337eca85f51a40b7 + React-performancecdpmetrics: f238752e99eb7c69c782020827bff38a4b1b2ef2 + React-performancetimeline: f1b216fa02200c859cd18ef345d4230cd712137a + React-RCTActionSheet: 81662769b1f576e9c2e074deb41d6b83d1c7e29f + React-RCTAnimatedModuleProvider: a9829773197f782c19ad86c5438a2cc199203774 + React-RCTAnimation: ad9dd2c6c3b4d07e583c2b79354858c4f34481a0 + React-RCTAppDelegate: 70398a06e10ada27cdf77dc2b43f883126a91ac0 + React-RCTBlob: f3d86b491b8f28c36ced0cb750878b62f72e1380 + React-RCTFabric: 3767f97183c40ba3dadf007b17a01be2cd4e632a + React-RCTFBReactNativeSpec: d7ad5d9edc1865f38b9aac5b7091c3abb6978eed + React-RCTImage: 46ad408f777dce991d4095d7e73a568212be6baa + React-RCTLinking: 547427cc885bfa74b3cbba405971c5270defde7a + React-RCTNetwork: 52c00c0fe07327df9b9fe9881b4f5550dc5ced37 + React-RCTRuntime: 081fd457d305c3aee76c335c568e693ed107f48e + React-RCTSettings: 9cd0b2be41404574ec280b6b8e73c2208c842050 + React-RCTText: 5a6d38456eabc0a1a7c0d47d592390d35c6de57c + React-RCTVibration: c1815b4f471462954a4b7d8835155e053cec5412 + React-rendererconsistency: b7996da82c8d3f4a7928b39be3e437b72ad92012 + React-renderercss: 4c256900eba87661178771766da0171493f88848 + React-rendererdebug: 4d9df8cf2cac6eee7002a4d14358c79fdf6ad8c5 + React-resizeobservernativemodule: 750d334554e7c44bacf14568a8a0d0048cc4c278 + React-RuntimeApple: 2045a6206895ad5ce90d141d1bd384888fd06c42 + React-RuntimeCore: b051facffbc7ca038097278cabf810d6d86dd328 + React-runtimeexecutor: b06ec9bb745a97cbd3ce25e3e02be7b8058fb374 + React-RuntimeHermes: fcfa8baca49a6d37d331147568d51d5b8b0a175d + React-runtimescheduler: c2afe74d1db006bafd9270186101f5378b1bef81 + React-timing: 6a1a8cfb0caae8de243aba16fa92fa81de23efaf + React-utils: 2384e5db98378df8c0bf2c0d5489ec1b8e80b398 + React-viewtransitionnativemodule: 8505b253586f792c6c722869a664889797ecae3e + React-webperformancenativemodule: 67f32fde92ed7e8470ee1c669bdb7c2fcb760141 + ReactAppDependencyProvider: 782ee9546c9cd3b53b73f1e9e3dd728540085fc7 + ReactCodegen: 7b71f4464442e9b6eae41094d11076d56c51c0e3 + ReactCommon: 145a14b942b19fc2fe38927f261955ada56978aa + ReactNativeDependencies: d3b7423ba1e52525e42085c8470109abde583d2e + RNGestureHandler: b5512a093e6923dd1c31fbdd8e6186cd0ebb3541 + RNReanimated: 3018727dbf272754d0f31ef73f619e49ade7baf0 + RNSVG: 8b5f2fdb9f0245a863cdd6e9c48a7689821a08b0 + RNWorklets: 488806637a21eb17812a48dadca7cb8c698c8cfd + SocketRocket: 37aec555668fb852ec12e3c0de59a86ca58f0871 + stream-chat-react-native: 7f1fdf2d1ce87c0b8a6c1d581308dff0c84e8e21 + Teleport: 90f0d7e81c6b40db9b253e1ef73b2f4f4f3f6b53 + Yoga: d1c536142c5ff8ec8cd856ab2a7c227a1d875c8e + +PODFILE CHECKSUM: 84fecefd0a807b1cfa8b4184065b9bbd44fa58d1 + +COCOAPODS: 1.15.2 diff --git a/examples/E2ETestApp/metro.config.js b/examples/E2ETestApp/metro.config.js new file mode 100644 index 0000000000..c96816d4be --- /dev/null +++ b/examples/E2ETestApp/metro.config.js @@ -0,0 +1,81 @@ +const { getDefaultConfig } = require('@react-native/metro-config'); +const { resolveUniqueModule } = require('@rnx-kit/metro-config'); + +/** + * Metro configuration + * https://reactnative.dev/docs/metro + * + * @type {import('@react-native/metro-config').MetroConfig} + */ +const config = getDefaultConfig(__dirname); + +const PATH = require('path'); +const metroConfigPackageJsonPath = require.resolve('metro-config/package.json', { + paths: [__dirname], +}); +const metroExclusionList = require( + PATH.join(PATH.dirname(metroConfigPackageJsonPath), 'src/defaults/exclusionList.js'), +); +const exclusionList = metroExclusionList.default || metroExclusionList; +const packageDirPath = PATH.resolve(__dirname, '../../package'); +const nativePackageDirPath = PATH.resolve(__dirname, '../../package/native-package'); + +const symlinked = { + 'stream-chat-react-native': nativePackageDirPath, + 'stream-chat-react-native-core': packageDirPath, +}; + +// find what all modules need to be unique for the app (mainly react and react-native) +// note: we filter the symlinked modules as they are already unique +// and as they dont follow the workspace pattern the auto-generated path to the module is incorrect +const dependencyPackageNames = Object.keys(require('./package.json').dependencies); + +const uniqueModules = dependencyPackageNames.map((packageName) => { + if (symlinked[packageName]) { + const modulePath = symlinked[packageName]; + const escapedPackageName = PATH.normalize(packageName).replace(/\\/g, '\\\\'); + + // exclude the symlinked package from being resolved from node_modules + // example: .*\/node_modules\/stream-chat-react-native-core\/.* + // the above would avoid native-package to resolve core from its own node_modules + const exclusionRE = new RegExp( + `.*${PATH.sep}node_modules\\${PATH.sep}${escapedPackageName}\\${PATH.sep}.*`, + ); + + return { + packageName, // name of the package + modulePath, + blockPattern: exclusionRE, // paths that match this pattern will be blocked from being resolved + }; + } + const [modulePath, blockPattern] = resolveUniqueModule(packageName, __dirname); + // resolveUniqueModule emits a regex ending in `/` (package-dir boundary). metro-config's + // exclusionList anchors every alternation branch with `$`, which would require file paths to + // literally end with `/node_modules//`. Append `.*` so the anchor lands at end-of-file + // instead of end-of-directory, otherwise the blocklist silently matches nothing. + const fixedBlockPattern = new RegExp(blockPattern.source + '.*'); + return { + packageName, // name of the package + modulePath, // actual path to the module in the project's node modules + blockPattern: fixedBlockPattern, // paths that match this pattern will be blocked from being resolved + }; +}); + +// block the other paths for unique modules from being resolved +const blockList = uniqueModules.map(({ blockPattern }) => blockPattern); + +// provide the path for the unique modules +const extraNodeModules = uniqueModules.reduce((acc, item) => { + acc[item.packageName] = item.modulePath; + return acc; +}, {}); + +config.resolver.blockList = exclusionList(blockList); +config.resolver.extraNodeModules = extraNodeModules; + +config.resolver.nodeModulesPaths = [PATH.resolve(__dirname, 'node_modules')]; + +// add the package dir for metro to access the package folder +config.watchFolders = [packageDirPath]; + +module.exports = config; diff --git a/examples/E2ETestApp/package.json b/examples/E2ETestApp/package.json new file mode 100644 index 0000000000..3329927fda --- /dev/null +++ b/examples/E2ETestApp/package.json @@ -0,0 +1,50 @@ +{ + "name": "e2etestapp", + "version": "0.0.1", + "private": true, + "description": "Minimal app driven by the Detox e2e suite (e2e/). Only the SDK and its required peer dependencies.", + "scripts": { + "sync-native": "bash ../../package/scripts/reconcile-shared-native.sh native-package && bash ../../package/scripts/sync-shared-native.sh native-package", + "android": "yarn sync-native && react-native run-android", + "ios": "yarn sync-native && react-native run-ios", + "start": "yarn sync-native && react-native start", + "typecheck": "tsc --noEmit && tsc --noEmit -p e2e", + "e2e:build": "yarn sync-native && detox build", + "e2e:test": "detox test" + }, + "dependencies": { + "@react-native-community/netinfo": "^12.0.1", + "react": "19.3.0", + "react-native": "0.88.0", + "react-native-gesture-handler": "^3.4.0", + "react-native-launch-arguments": "^4.1.1", + "react-native-reanimated": "4.7.1", + "react-native-safe-area-context": "^5.10.1", + "react-native-svg": "^15.15.5", + "react-native-teleport": "^1.2.2", + "react-native-worklets": "^0.13.0", + "stream-chat": "^9.54.0", + "stream-chat-react-native": "workspace:^" + }, + "devDependencies": { + "@babel/core": "^7.29.0", + "@babel/runtime": "^7.29.2", + "@jest/globals": "^30.4.1", + "@react-native-community/cli": "20.2.0", + "@react-native-community/cli-platform-android": "20.2.0", + "@react-native-community/cli-platform-ios": "20.2.0", + "@react-native/babel-preset": "0.88.0", + "@react-native/metro-config": "0.88.0", + "@react-native/typescript-config": "0.88.0", + "@rnx-kit/metro-config": "^2.2.6", + "@stream-io/typescript-config": "workspace:^", + "@types/react": "^19.3.0", + "babel-jest": "30.0.0", + "detox": "^20.51.4", + "jest": "^30.4.2", + "typescript": "6.0.3" + }, + "engines": { + "node": ">= 22.11.0" + } +} diff --git a/examples/E2ETestApp/src/config.ts b/examples/E2ETestApp/src/config.ts new file mode 100644 index 0000000000..8d10c2d95b --- /dev/null +++ b/examples/E2ETestApp/src/config.ts @@ -0,0 +1,26 @@ +import { LaunchArguments } from 'react-native-launch-arguments'; + +import type { UserResponse } from 'stream-chat'; + +/** + * Launch arguments passed by the Detox suite (`e2e/support/StreamTestCase.ts`). The counterpart of + * `USE_MOCK_SERVER` + `MOCK_SERVER_*` on iOS and the `BASE_URL` intent extra on Android. + */ +type E2ELaunchArguments = { + /** Base URL of the per-test `stream-chat-test-mock-server` instance, e.g. `http://localhost:54321`. */ + MOCK_SERVER_URL?: string; +}; + +export const mockServerUrl = LaunchArguments.value().MOCK_SERVER_URL; + +/** The app user every mock server fixture is recorded for. */ +export const appUser: UserResponse = { + id: 'luke_skywalker', + image: 'https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg', + name: 'Luke Skywalker', +}; + +// The mock server accepts any key and token; these match the native SDKs' e2e apps. +export const apiKey = '8br4watad788'; +export const appUserToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoibHVrZV9za3l3YWxrZXIifQ.kFSLHRB5X62t0Zlc7nwczWUfsQMwfkpylC6jCUZ6Mc0'; diff --git a/examples/E2ETestApp/tsconfig.json b/examples/E2ETestApp/tsconfig.json new file mode 100644 index 0000000000..d1be171213 --- /dev/null +++ b/examples/E2ETestApp/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": ["@react-native/typescript-config", "@stream-io/typescript-config/base.json"], + "compilerOptions": { + "types": [] + }, + "exclude": ["node_modules", "e2e"] +} diff --git a/examples/SampleApp/fastlane/Fastfile b/examples/SampleApp/fastlane/Fastfile index f2680950ee..c491a81025 100644 --- a/examples/SampleApp/fastlane/Fastfile +++ b/examples/SampleApp/fastlane/Fastfile @@ -5,6 +5,7 @@ github_repo = ENV['GITHUB_REPOSITORY'] || 'GetStream/stream-chat-react-native' root_path = File.absolute_path('../../../') sdk_size_ext = 'KB' @force_check = false +@impact = nil build_output_directory = "./app-build" # iOS Platform Configuration @@ -20,6 +21,7 @@ apk_path = "#{build_output_directory}/#{output_apk_name}" before_all do + @impact = impact_analysis if is_ci setup_ci setup_git_config @@ -183,9 +185,109 @@ platform :android do end end -########################## -###### Common lanes ###### -########################## +####################### +###### E2E lanes ###### +####################### + +# The e2e suite drives the dedicated examples/E2ETestApp; its lanes live here with the repo's other lanes. +e2e_app_path = File.absolute_path('../../E2ETestApp') +mock_server_repo_name = 'stream-chat-test-mock-server' +mock_server_driver_port = ENV['MOCK_DRIVER_PORT'] || '4569' + +desc 'Starts the stream-chat-test-mock-server driver. Options: local_server (path to a checkout), branch' +lane :start_mock_server do |options| + Dir.chdir(e2e_app_path) do + mock_server_repo = options[:local_server] || mock_server_repo_name + stop_mock_server unless is_ci + + unless options[:local_server] + branch = options[:branch].to_s.empty? ? 'main' : options[:branch] + sh("rm -rf #{mock_server_repo}") if File.directory?(mock_server_repo) + sh("git clone --depth 1 -b #{branch} https://github.com/#{github_repo.split('/').first}/#{mock_server_repo}.git") + end + + Dir.chdir(mock_server_repo) do + FileUtils.mkdir_p('logs') + # The mock server has its own Gemfile; don't let the SampleApp bundle leak into it. + Bundler.with_unbundled_env do + # CI caches the mock server's gems in MOCK_SERVER_BUNDLE_PATH. + sh("bundle config set --local path '#{ENV['MOCK_SERVER_BUNDLE_PATH']}'") if ENV['MOCK_SERVER_BUNDLE_PATH'] + sh('bundle install') + sh("bundle exec ruby driver.rb #{mock_server_driver_port} > logs/driver.log 2>&1 &") + end + end + + wait_for_mock_server_driver(port: mock_server_driver_port) + end +end + +lane :stop_mock_server do + Net::HTTP.get_response(URI("http://localhost:#{mock_server_driver_port}/stop")) rescue nil +end + +private_lane :wait_for_mock_server_driver do |options| + require 'socket' + ready = false + 60.times do + begin + TCPSocket.new('localhost', options[:port].to_i).close + ready = true + break + rescue Errno::ECONNREFUSED + sleep(1) + end + end + UI.user_error!("Mock server driver did not start on port #{options[:port]}") unless ready +end + +desc 'Writes `required=true|false` to $GITHUB_OUTPUT, so CI can skip the e2e jobs before building anything' +lane :e2e_check_required do + required = is_check_required(sources: @impact[:e2e][:sources], force_check: @force_check, required_checks: @impact[:e2e][:checks]) + File.write(ENV['GITHUB_OUTPUT'], "required=#{required}\n", mode: 'a') if ENV['GITHUB_OUTPUT'] + required +end + +desc 'Installs the E2ETestApp pods with this bundle (the e2e app has no Gemfile of its own)' +lane :e2e_pod_install do + Dir.chdir(e2e_app_path) do + sh('yarn sync-native') + sh('bundle exec pod install --project-directory=ios') + end +end + +# A freshly booted CI emulator is slow enough for the launcher to hit an ANR ("Quickstep isn't +# responding"). The dialog takes window focus, and Espresso then fails every test waiting for it. +private_lane :prepare_android_emulator do + sh('adb shell settings put global hide_error_dialogs 1') + # Gives the post-boot CPU load a minute to settle, dismissing ANR dialogs meanwhile. Capped: the + # load can stay above the threshold for minutes, and each test dismisses ANR dialogs too. + wait_android_emu_idle(timeout: 60) +end + +desc 'Runs the Detox e2e tests. Options: platform (ios|android), configuration (release|debug), ' \ + 'device (android: emu|att), skip_build, test (a test file or -t filter), local_server, mock_server_branch' +lane :test_e2e do |options| + next unless is_check_required(sources: @impact[:e2e][:sources], force_check: @force_check, required_checks: @impact[:e2e][:checks]) + + platform = options[:platform] || 'ios' + device = platform == 'ios' ? 'sim' : (options[:device] || 'emu') + detox_config = "#{platform}.#{device}.#{options[:configuration] || 'release'}" + + start_mock_server(local_server: options[:local_server], branch: options[:mock_server_branch]) + # An attached emulator is already booted; Detox boots `emu` devices itself. + prepare_android_emulator if platform == 'android' && device == 'att' + + begin + Dir.chdir(e2e_app_path) do + sh("yarn e2e:build -c #{detox_config}") unless options[:skip_build] + # On CI: retry flaky tests, run the simulator headless, and keep logs + screenshots of failures in ./artifacts. + ci_flags = is_ci ? '--retries 2 --headless --record-logs failing --take-screenshots failing' : '' + sh("MOCK_DRIVER_PORT=#{mock_server_driver_port} yarn detox test -c #{detox_config} --cleanup #{ci_flags} #{options[:test]}") + end + ensure + stop_mock_server + end +end lane :file_size do |options| File.size(options[:path]) / 1024.0 @@ -213,10 +315,7 @@ lane :frameworks_sizes do end lane :show_frameworks_sizes do |options| - # Besides the SDK itself, changes to how the size is measured (this lane, its plugin, the root - # `minify-*` scripts) re-run the check too. - sources = ['package/', 'package.json', 'examples/SampleApp/fastlane/', 'examples/SampleApp/Gemfile.lock'] - next unless is_check_required(sources: sources, force_check: @force_check) + next unless is_check_required(sources: @impact[:size][:sources], force_check: @force_check) sizes = options[:sizes] || frameworks_sizes show_sdk_size(branch_sizes: sizes, github_repo: github_repo, size_ext: sdk_size_ext) @@ -232,3 +331,15 @@ lane :update_img_shields_sdk_sizes do |options| size_ext: sdk_size_ext ) end + +lane :impact_analysis do + { + e2e: { + sources: ['package/', 'examples/E2ETestApp/', 'examples/SampleApp/fastlane/', 'package.json'], + checks: ['Test E2E Result'] + }, + size: { + sources: ['package/', 'package.json'] + } + } +end diff --git a/package.json b/package.json index 73df92c8a5..ee702cd07b 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "package/native-package", "package/expo-package", "examples/SampleApp", + "examples/E2ETestApp", "examples/ExpoMessaging", "examples/ExpoChatGPTSample" ], @@ -71,7 +72,7 @@ "lint-fix": "prettier --write . && eslint . --fix --max-warnings 0", "prettier": "prettier --list-different .", "prettier-fix": "prettier --write .", - "typecheck": "yarn workspaces foreach -A --parallel --include 'stream-chat-react-native-core' --include 'stream-chat-react-native' --include 'stream-chat-expo' --include 'sampleapp' --include 'ExpoMessaging' --include 'expochatgptsample' run typecheck", + "typecheck": "yarn workspaces foreach -A --parallel --include 'stream-chat-react-native-core' --include 'stream-chat-react-native' --include 'stream-chat-expo' --include 'sampleapp' --include 'e2etestapp' --include 'ExpoMessaging' --include 'expochatgptsample' run typecheck", "build": "yarn workspace stream-chat-react-native-core build", "test:coverage": "yarn workspace stream-chat-react-native-core test:coverage", "test:unit": "yarn workspace stream-chat-react-native-core test:unit", diff --git a/package/src/components/ChannelPreview/ChannelLastMessagePreview.tsx b/package/src/components/ChannelPreview/ChannelLastMessagePreview.tsx index 30d323c839..e005c42649 100644 --- a/package/src/components/ChannelPreview/ChannelLastMessagePreview.tsx +++ b/package/src/components/ChannelPreview/ChannelLastMessagePreview.tsx @@ -29,7 +29,7 @@ export const ChannelLastMessagePreview = ({ message }: ChannelLastMessagePreview width={16} /> ) : null} - + {messagePreviewTitle} diff --git a/package/src/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.tsx b/package/src/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.tsx index 96ea6dc072..0a08077eda 100644 --- a/package/src/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.tsx +++ b/package/src/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.tsx @@ -81,7 +81,11 @@ export const ChannelMessagePreviewDeliveryStatus = ({ } if (!isLastMessageByCurrentUser) { - return {message?.user?.name || message?.user?.id}:; + return ( + + {message?.user?.name || message?.user?.id}: + + ); } return ( @@ -107,7 +111,9 @@ export const ChannelMessagePreviewDeliveryStatus = ({ ) : status === MessageDeliveryStatus.SENT ? ( ) : null} - {t('You')}: + + {t('You')}: + ); diff --git a/package/src/components/ChannelPreview/ChannelPreviewTitle.tsx b/package/src/components/ChannelPreview/ChannelPreviewTitle.tsx index e91d33b41f..28e1b7982c 100644 --- a/package/src/components/ChannelPreview/ChannelPreviewTitle.tsx +++ b/package/src/components/ChannelPreview/ChannelPreviewTitle.tsx @@ -21,7 +21,7 @@ export const ChannelPreviewTitle = ({ channel, title }: ChannelPreviewTitleProps const displayName = useChannelPreviewDisplayName(channel); return ( - + {title ?? displayName} ); diff --git a/package/src/components/Message/MessageItemView/MessageStatus.tsx b/package/src/components/Message/MessageItemView/MessageStatus.tsx index db86a63105..4095fd1deb 100644 --- a/package/src/components/Message/MessageItemView/MessageStatus.tsx +++ b/package/src/components/Message/MessageItemView/MessageStatus.tsx @@ -61,6 +61,16 @@ const MessageStatusWithContext = (props: MessageStatusPropsWithContext) => { return null; } + const testID = read + ? 'message-status-read' + : delivered + ? 'message-status-delivered' + : sending + ? 'message-status-sending' + : sent + ? 'message-status-sent' + : undefined; + return ( <> @@ -68,6 +78,7 @@ const MessageStatusWithContext = (props: MessageStatusPropsWithContext) => { accessibilityElementsHidden={!!accessibilityLabel} importantForAccessibility={accessibilityLabel ? 'no-hide-descendants' : undefined} style={[styles.container, container]} + testID={testID} > {read ? ( { }); }, ); + + it.each([ + [1, 1, 'sending', 'message-status-sending'], + [2, 2, 'received', 'message-status-read'], + [1, 1, 'received', 'message-status-sent'], + [2, 1, 'received', 'message-status-delivered'], + ] as [number, number, string, string][])( + 'should expose a status testID when deliveredToCount is %s, readBy is %s and status is %s', + async (deliveredToCount, readBy, status, testID) => { + const message = generateMessage({ user: generateUser() }); + const { getByTestId } = renderMessageStatus({ + deliveredToCount, + message: { ...message, status }, + readBy, + }); + // The icon row is hidden from screen readers in favour of the hidden text label. + await waitFor(() => { + expect(getByTestId(testID, { includeHiddenElements: true })).toBeTruthy(); + }); + }, + ); }); diff --git a/yarn.lock b/yarn.lock index 6d8e94f376..95ca73ae46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1610,6 +1610,20 @@ __metadata: languageName: node linkType: hard +"@colors/colors@npm:1.6.0": + version: 1.6.0 + resolution: "@colors/colors@npm:1.6.0" + checksum: 10c0/9328a0778a5b0db243af54455b79a69e3fb21122d6c15ef9e9fcc94881d8d17352d8b2b2590f9bdd46fac5c2d6c1636dcfc14358a20c70e22daf89e1a759b629 + languageName: node + linkType: hard + +"@colors/colors@npm:^1.6.0": + version: 1.6.1 + resolution: "@colors/colors@npm:1.6.1" + checksum: 10c0/8fb995f2421210284305fd07b4b70e9873a417cda8b0d66ba3f7beccd2e0f4ebbe0b2ba3afbef8f07740ad707c379d94e836cffd6a8df394387e210b4338b4c9 + languageName: node + linkType: hard + "@commitlint/cli@npm:^21.0.0": version: 21.0.0 resolution: "@commitlint/cli@npm:21.0.0" @@ -1839,6 +1853,17 @@ __metadata: languageName: node linkType: hard +"@dabh/diagnostics@npm:^2.0.8": + version: 2.0.9 + resolution: "@dabh/diagnostics@npm:2.0.9" + dependencies: + "@so-ric/colorspace": "npm:^1.1.6" + enabled: "npm:2.0.x" + kuler: "npm:^2.0.0" + checksum: 10c0/3d341a52fbf2277883c4aa1153ce092932d9ecad79a54749553206b83e568cd563f66c6e1cc1416a403bd5662335193a19e8bcc1082f047f28b6402eeb16c01d + languageName: node + linkType: hard + "@emnapi/core@npm:^1.4.3": version: 1.10.0 resolution: "@emnapi/core@npm:1.10.0" @@ -3060,6 +3085,13 @@ __metadata: languageName: node linkType: hard +"@flatten-js/interval-tree@npm:^1.1.2": + version: 1.1.4 + resolution: "@flatten-js/interval-tree@npm:1.1.4" + checksum: 10c0/e4055e99b1b5958e0a1adea7413d9602f3d7ebed0dd47fcac1ad995a97151e02187fe1aa778c14be56c7faa3a29b2a71ece84be43312eff6850ebf316b3490b3 + languageName: node + linkType: hard + "@gorhom/bottom-sheet@npm:^5.2.14": version: 5.2.14 resolution: "@gorhom/bottom-sheet@npm:5.2.14" @@ -3544,6 +3576,13 @@ __metadata: languageName: node linkType: hard +"@jest/diff-sequences@npm:30.5.0": + version: 30.5.0 + resolution: "@jest/diff-sequences@npm:30.5.0" + checksum: 10c0/a8d4fef9b266cb025b2f1c1be9ae2e807077def93f36e34a628aff383910fa7a26ba0921a43744841231ead54ea87469fb2acf89de47c12ad4fead802e0ccfc3 + languageName: node + linkType: hard + "@jest/environment@npm:30.4.1": version: 30.4.1 resolution: "@jest/environment@npm:30.4.1" @@ -3556,6 +3595,18 @@ __metadata: languageName: node linkType: hard +"@jest/environment@npm:30.5.2": + version: 30.5.2 + resolution: "@jest/environment@npm:30.5.2" + dependencies: + "@jest/fake-timers": "npm:30.5.2" + "@jest/types": "npm:30.5.1" + "@types/node": "npm:*" + jest-mock: "npm:30.5.2" + checksum: 10c0/f060f2d94dfccd7de4890a419e803735921d8358a3ecc8e424e5f6a632da80fc37372f6e54f1397876b721cec5bae2da515e6dce2ac6558bd90f0c1cbeb5c105 + languageName: node + linkType: hard + "@jest/environment@npm:^29.7.0": version: 29.7.0 resolution: "@jest/environment@npm:29.7.0" @@ -3577,6 +3628,15 @@ __metadata: languageName: node linkType: hard +"@jest/expect-utils@npm:30.5.2": + version: 30.5.2 + resolution: "@jest/expect-utils@npm:30.5.2" + dependencies: + "@jest/get-type": "npm:30.5.0" + checksum: 10c0/0db00595907f6705c5acc8d10ee9d11d74960cd459beaae66bd7a1d6845c7df1d123295e957bb56b6882b380f2e7d55d277ced7646e0229fc4a67abeb713c048 + languageName: node + linkType: hard + "@jest/expect@npm:30.4.1": version: 30.4.1 resolution: "@jest/expect@npm:30.4.1" @@ -3587,6 +3647,16 @@ __metadata: languageName: node linkType: hard +"@jest/expect@npm:30.5.2": + version: 30.5.2 + resolution: "@jest/expect@npm:30.5.2" + dependencies: + expect: "npm:30.5.2" + jest-snapshot: "npm:30.5.2" + checksum: 10c0/ceebb291be34707e3d2ac99bc5e4a5039927e01d28650b1dc51888dfa3963a3a75a3c72f0abe4df9aa243dc3b31af72e467072d75b52bd11b80a84ebe765d8f9 + languageName: node + linkType: hard + "@jest/fake-timers@npm:30.4.1": version: 30.4.1 resolution: "@jest/fake-timers@npm:30.4.1" @@ -3601,6 +3671,20 @@ __metadata: languageName: node linkType: hard +"@jest/fake-timers@npm:30.5.2": + version: 30.5.2 + resolution: "@jest/fake-timers@npm:30.5.2" + dependencies: + "@jest/types": "npm:30.5.1" + "@sinonjs/fake-timers": "npm:^15.4.0" + "@types/node": "npm:*" + jest-message-util: "npm:30.5.1" + jest-mock: "npm:30.5.2" + jest-util: "npm:30.5.1" + checksum: 10c0/c8ff08f541743210abaf07b75bfe6224458c816a500d5be954690fd1661cd14b054201dfe2a6ebdc0083290d4c03d73bbac4f3c1fbef5c3b16d1981642dfff57 + languageName: node + linkType: hard + "@jest/fake-timers@npm:^29.7.0": version: 29.7.0 resolution: "@jest/fake-timers@npm:29.7.0" @@ -3622,6 +3706,13 @@ __metadata: languageName: node linkType: hard +"@jest/get-type@npm:30.5.0": + version: 30.5.0 + resolution: "@jest/get-type@npm:30.5.0" + checksum: 10c0/820a2af9902522d169ae4ede17a99249baad59ff6aead02f8100acbc937b082090b04d5f32ec0d9938491cf89c9fc99930ed469202700c110c0239c7f7ac67a2 + languageName: node + linkType: hard + "@jest/globals@npm:30.4.1": version: 30.4.1 resolution: "@jest/globals@npm:30.4.1" @@ -3634,6 +3725,18 @@ __metadata: languageName: node linkType: hard +"@jest/globals@npm:^30.4.1": + version: 30.5.2 + resolution: "@jest/globals@npm:30.5.2" + dependencies: + "@jest/environment": "npm:30.5.2" + "@jest/expect": "npm:30.5.2" + "@jest/types": "npm:30.5.1" + jest-mock: "npm:30.5.2" + checksum: 10c0/e60738080d4866350b2152d98ddf99728cc1ac19983b80bea5d5ab219f82ef271fadd102b0cee2c01808367d6e054bea1c4ec4aa7c69e45b4e14b79a30f76538 + languageName: node + linkType: hard + "@jest/pattern@npm:30.0.0": version: 30.0.0 resolution: "@jest/pattern@npm:30.0.0" @@ -3654,6 +3757,30 @@ __metadata: languageName: node linkType: hard +"@jest/pattern@npm:30.5.0": + version: 30.5.0 + resolution: "@jest/pattern@npm:30.5.0" + dependencies: + "@types/node": "npm:*" + jest-regex-util: "npm:30.5.0" + checksum: 10c0/3e960b25c7a32fee97a2cdf9ea810ada41f676c84db3c1afab138ee089e0e1cf4849372dd576acb18ac5219309695991c38e23ddacd9b95802ee3f76b4caaea4 + languageName: node + linkType: hard + +"@jest/react-is-18@npm:react-is@^18.3.1, react-is-18@npm:react-is@^18.3.1, react-is@npm:^18.0.0": + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 + languageName: node + linkType: hard + +"@jest/react-is-19@npm:react-is@^19.2.5": + version: 19.3.1 + resolution: "react-is@npm:19.3.1" + checksum: 10c0/428e02d986d56f1859c57756084489f6d2900fd686905003c405b32f1168738b78772bfa4bb6c706f5ac821199171522c28bf6434281f524f901459390a19606 + languageName: node + linkType: hard + "@jest/reporters@npm:30.4.1": version: 30.4.1 resolution: "@jest/reporters@npm:30.4.1" @@ -3708,6 +3835,15 @@ __metadata: languageName: node linkType: hard +"@jest/schemas@npm:30.5.0": + version: 30.5.0 + resolution: "@jest/schemas@npm:30.5.0" + dependencies: + "@sinclair/typebox": "npm:^0.34.0" + checksum: 10c0/632c45a06a0984051c908fa9b3c9445bac7be8b7a6d1cc7ce2ecae7d4e44c612509ad41fca16449607c4ea49a333e0d3f10bb1510bafb4b6d9051ca15d9b4b14 + languageName: node + linkType: hard + "@jest/schemas@npm:^29.6.3": version: 29.6.3 resolution: "@jest/schemas@npm:29.6.3" @@ -3729,6 +3865,18 @@ __metadata: languageName: node linkType: hard +"@jest/snapshot-utils@npm:30.5.1": + version: 30.5.1 + resolution: "@jest/snapshot-utils@npm:30.5.1" + dependencies: + "@jest/types": "npm:30.5.1" + chalk: "npm:^4.1.2" + graceful-fs: "npm:^4.2.11" + natural-compare: "npm:^1.4.0" + checksum: 10c0/0e31aa45c4be7b6e7dabcccc7316458f53bda262ab42f23b749430110d0ac908d4905db993708723a4f6457bc9208c636fd7b886e3ffc26974d55126cf597d6e + languageName: node + linkType: hard + "@jest/source-map@npm:30.0.1": version: 30.0.1 resolution: "@jest/source-map@npm:30.0.1" @@ -3809,6 +3957,28 @@ __metadata: languageName: node linkType: hard +"@jest/transform@npm:30.5.2": + version: 30.5.2 + resolution: "@jest/transform@npm:30.5.2" + dependencies: + "@babel/core": "npm:^7.27.4" + "@jest/types": "npm:30.5.1" + "@jridgewell/trace-mapping": "npm:^0.3.31" + babel-plugin-istanbul: "npm:^8.0.0" + chalk: "npm:^4.1.2" + convert-source-map: "npm:^2.0.0" + fast-json-stable-stringify: "npm:^2.1.0" + graceful-fs: "npm:^4.2.11" + jest-haste-map: "npm:30.5.1" + jest-regex-util: "npm:30.5.0" + jest-util: "npm:30.5.1" + pirates: "npm:^4.0.7" + slash: "npm:^3.0.0" + write-file-atomic: "npm:^5.0.1" + checksum: 10c0/be3f9aa812a442402ed41a37d0f11d174a0a9037038dc1326c32d1d55e22fc7bc5f5146284c5a18db3da572afb81739f0a93d3f1d519e811d25dacaeb380c647 + languageName: node + linkType: hard + "@jest/transform@npm:^29.7.0": version: 29.7.0 resolution: "@jest/transform@npm:29.7.0" @@ -3862,6 +4032,21 @@ __metadata: languageName: node linkType: hard +"@jest/types@npm:30.5.1": + version: 30.5.1 + resolution: "@jest/types@npm:30.5.1" + dependencies: + "@jest/pattern": "npm:30.5.0" + "@jest/schemas": "npm:30.5.0" + "@types/istanbul-lib-coverage": "npm:^2.0.6" + "@types/istanbul-reports": "npm:^3.0.4" + "@types/node": "npm:*" + "@types/yargs": "npm:^17.0.33" + chalk: "npm:^4.1.2" + checksum: 10c0/da9e46449371ddd43b89627c93fb7088fdad01e9f57dce376c65914a6f4617a558f54ec51a917ca10c9b9794a1e76c3cb450308eca303f830bb21e9529b1719a + languageName: node + linkType: hard + "@jest/types@npm:^29.6.3": version: 29.6.3 resolution: "@jest/types@npm:29.6.3" @@ -4399,6 +4584,140 @@ __metadata: languageName: node linkType: hard +"@parcel/watcher-android-arm64@npm:2.6.0": + version: 2.6.0 + resolution: "@parcel/watcher-android-arm64@npm:2.6.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@parcel/watcher-darwin-arm64@npm:2.6.0": + version: 2.6.0 + resolution: "@parcel/watcher-darwin-arm64@npm:2.6.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@parcel/watcher-darwin-x64@npm:2.6.0": + version: 2.6.0 + resolution: "@parcel/watcher-darwin-x64@npm:2.6.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@parcel/watcher-freebsd-x64@npm:2.6.0": + version: 2.6.0 + resolution: "@parcel/watcher-freebsd-x64@npm:2.6.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@parcel/watcher-linux-arm-glibc@npm:2.6.0": + version: 2.6.0 + resolution: "@parcel/watcher-linux-arm-glibc@npm:2.6.0" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@parcel/watcher-linux-arm-musl@npm:2.6.0": + version: 2.6.0 + resolution: "@parcel/watcher-linux-arm-musl@npm:2.6.0" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@parcel/watcher-linux-arm64-glibc@npm:2.6.0": + version: 2.6.0 + resolution: "@parcel/watcher-linux-arm64-glibc@npm:2.6.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@parcel/watcher-linux-arm64-musl@npm:2.6.0": + version: 2.6.0 + resolution: "@parcel/watcher-linux-arm64-musl@npm:2.6.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@parcel/watcher-linux-x64-glibc@npm:2.6.0": + version: 2.6.0 + resolution: "@parcel/watcher-linux-x64-glibc@npm:2.6.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@parcel/watcher-linux-x64-musl@npm:2.6.0": + version: 2.6.0 + resolution: "@parcel/watcher-linux-x64-musl@npm:2.6.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@parcel/watcher-win32-arm64@npm:2.6.0": + version: 2.6.0 + resolution: "@parcel/watcher-win32-arm64@npm:2.6.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@parcel/watcher-win32-x64@npm:2.6.0": + version: 2.6.0 + resolution: "@parcel/watcher-win32-x64@npm:2.6.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@parcel/watcher@npm:^2.6.0": + version: 2.6.0 + resolution: "@parcel/watcher@npm:2.6.0" + dependencies: + "@parcel/watcher-android-arm64": "npm:2.6.0" + "@parcel/watcher-darwin-arm64": "npm:2.6.0" + "@parcel/watcher-darwin-x64": "npm:2.6.0" + "@parcel/watcher-freebsd-x64": "npm:2.6.0" + "@parcel/watcher-linux-arm-glibc": "npm:2.6.0" + "@parcel/watcher-linux-arm-musl": "npm:2.6.0" + "@parcel/watcher-linux-arm64-glibc": "npm:2.6.0" + "@parcel/watcher-linux-arm64-musl": "npm:2.6.0" + "@parcel/watcher-linux-x64-glibc": "npm:2.6.0" + "@parcel/watcher-linux-x64-musl": "npm:2.6.0" + "@parcel/watcher-win32-arm64": "npm:2.6.0" + "@parcel/watcher-win32-x64": "npm:2.6.0" + detect-libc: "npm:^2.0.3" + is-glob: "npm:^4.0.3" + node-addon-api: "npm:^7.0.0" + node-gyp: "npm:latest" + picomatch: "npm:^4.0.4" + dependenciesMeta: + "@parcel/watcher-android-arm64": + optional: true + "@parcel/watcher-darwin-arm64": + optional: true + "@parcel/watcher-darwin-x64": + optional: true + "@parcel/watcher-freebsd-x64": + optional: true + "@parcel/watcher-linux-arm-glibc": + optional: true + "@parcel/watcher-linux-arm-musl": + optional: true + "@parcel/watcher-linux-arm64-glibc": + optional: true + "@parcel/watcher-linux-arm64-musl": + optional: true + "@parcel/watcher-linux-x64-glibc": + optional: true + "@parcel/watcher-linux-x64-musl": + optional: true + "@parcel/watcher-win32-arm64": + optional: true + "@parcel/watcher-win32-x64": + optional: true + checksum: 10c0/0d6799bdf7d59a1ab5e4a79859035f387a66078586d9543eef0b9bd01c9d61cebe3b64eb96f0bd1f8bf50f07ed7266a1628604b66dc63025a3cee19be2e60d65 + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -6056,6 +6375,16 @@ __metadata: languageName: node linkType: hard +"@so-ric/colorspace@npm:^1.1.6": + version: 1.1.6 + resolution: "@so-ric/colorspace@npm:1.1.6" + dependencies: + color: "npm:^5.0.2" + text-hex: "npm:1.0.x" + checksum: 10c0/f3ad26afefbb8d6101ea7c385cd5f402d4291c2ffc9cabe37030d5fdb8bda980ee534a0d7c250f8233fc3a59b99272410177cd98b219f6b3770f91a0fdb6eb3e + languageName: node + linkType: hard + "@stream-io/state-store@npm:^1.1.6": version: 1.1.6 resolution: "@stream-io/state-store@npm:1.1.6" @@ -6569,6 +6898,13 @@ __metadata: languageName: node linkType: hard +"@types/triple-beam@npm:^1.3.2": + version: 1.3.5 + resolution: "@types/triple-beam@npm:1.3.5" + checksum: 10c0/d5d7f25da612f6d79266f4f1bb9c1ef8f1684e9f60abab251e1261170631062b656ba26ff22631f2760caeafd372abc41e64867cde27fba54fafb73a35b9056a + languageName: node + linkType: hard + "@types/ungap__structured-clone@npm:^1.2.0": version: 1.2.0 resolution: "@types/ungap__structured-clone@npm:1.2.0" @@ -6920,6 +7256,33 @@ __metadata: languageName: node linkType: hard +"@wix-pilot/core@npm:^3.4.2": + version: 3.4.2 + resolution: "@wix-pilot/core@npm:3.4.2" + dependencies: + chalk: "npm:^4.1.0" + pngjs: "npm:^7.0.0" + winston: "npm:^3.17.0" + peerDependencies: + expect: "*" + peerDependenciesMeta: + expect: + optional: true + checksum: 10c0/606dcd0951cfdfb6bda69718512e6daedda22b15dcc4b2029739e3d02b2d676b30d5d97a6d6558d12a8cfae332b6beb48e32ac620eae4ca5eefa43543dd9e14a + languageName: node + linkType: hard + +"@wix-pilot/detox@npm:^1.0.13": + version: 1.0.13 + resolution: "@wix-pilot/detox@npm:1.0.13" + peerDependencies: + "@wix-pilot/core": ^3.4.1 + detox: ">=20.33.0" + expect: 29.x.x || 28.x.x || ^27.2.5 + checksum: 10c0/210b1834ce628cfacdc7417ed8596455045bd6c87bc4ef01b99558ddeeab00648d8ed73abdd212f7cb3d96110b0d36bf8812fd31e1d61a4d3b8f7cad41ab69bf + languageName: node + linkType: hard + "@xmldom/xmldom@npm:^0.8.8": version: 0.8.13 resolution: "@xmldom/xmldom@npm:0.8.13" @@ -7090,7 +7453,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.11.0": +"ajv@npm:^8.11.0, ajv@npm:^8.6.3": version: 8.20.0 resolution: "ajv@npm:8.20.0" dependencies: @@ -7442,6 +7805,13 @@ __metadata: languageName: node linkType: hard +"async@npm:^3.2.3": + version: 3.2.6 + resolution: "async@npm:3.2.6" + checksum: 10c0/36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70 + languageName: node + linkType: hard + "asynckit@npm:^0.4.0": version: 0.4.0 resolution: "asynckit@npm:0.4.0" @@ -7565,6 +7935,19 @@ __metadata: languageName: node linkType: hard +"babel-plugin-istanbul@npm:^8.0.0": + version: 8.0.2 + resolution: "babel-plugin-istanbul@npm:8.0.2" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.0.0" + "@istanbuljs/load-nyc-config": "npm:^1.0.0" + "@istanbuljs/schema": "npm:^0.1.3" + istanbul-lib-instrument: "npm:^6.0.2" + test-exclude: "npm:^7.0.1" + checksum: 10c0/795577336196114ea19d2cd9efae41cde66b08bc8b7a326ed35bfe7da85b5756ebe19d9fcf3c98d3bd0e194d1258229178414a79fe3a623a66a449e468cf7af5 + languageName: node + linkType: hard + "babel-plugin-jest-hoist@npm:30.0.0": version: 30.0.0 resolution: "babel-plugin-jest-hoist@npm:30.0.0" @@ -7926,6 +8309,13 @@ __metadata: languageName: node linkType: hard +"bluebird@npm:^3.5.4": + version: 3.7.2 + resolution: "bluebird@npm:3.7.2" + checksum: 10c0/680de03adc54ff925eaa6c7bb9a47a0690e8b5de60f4792604aae8ed618c65e6b63a7893b57ca924beaf53eee69c5af4f8314148c08124c550fe1df1add897d2 + languageName: node + linkType: hard + "body-parser@npm:^2.2.2": version: 2.3.0 resolution: "body-parser@npm:2.3.0" @@ -8021,6 +8411,13 @@ __metadata: languageName: node linkType: hard +"browser-process-hrtime@npm:^1.0.0": + version: 1.0.0 + resolution: "browser-process-hrtime@npm:1.0.0" + checksum: 10c0/65da78e51e9d7fa5909147f269c54c65ae2e03d1cf797cc3cfbbe49f475578b8160ce4a76c36c1a2ffbff26c74f937d73096c508057491ddf1a6dfd11143f72d + languageName: node + linkType: hard + "browserslist@npm:^4.20.4, browserslist@npm:^4.24.0, browserslist@npm:^4.25.0, browserslist@npm:^4.28.1": version: 4.28.2 resolution: "browserslist@npm:4.28.2" @@ -8069,6 +8466,87 @@ __metadata: languageName: node linkType: hard +"bunyamin@npm:^1.5.2": + version: 1.6.3 + resolution: "bunyamin@npm:1.6.3" + dependencies: + "@flatten-js/interval-tree": "npm:^1.1.2" + multi-sort-stream: "npm:^1.0.4" + stream-json: "npm:^1.7.5" + trace-event-lib: "npm:^1.3.1" + peerDependencies: + "@types/bunyan": ^1.8.8 + bunyan: ^1.8.15 || ^2.0.0 + peerDependenciesMeta: + "@types/bunyan": + optional: true + bunyan: + optional: true + checksum: 10c0/150a93cbf0ea83ff38f73de42742fc162e24d87551d9b5454769a4c2966b900a4e2a63dbd3d1f616a1178e65f0966cd76b2a48981764de7ae93b55de79410c83 + languageName: node + linkType: hard + +"bunyan-debug-stream@npm:^3.1.0": + version: 3.1.1 + resolution: "bunyan-debug-stream@npm:3.1.1" + dependencies: + chalk: "npm:^4.1.2" + peerDependencies: + bunyan: "*" + peerDependenciesMeta: + bunyan: + optional: true + checksum: 10c0/3682454c08910ffbe4b9d9c3e4ba97d36ca5d78e9bac5d71f639c12e35cfa921df76dd736d08c061c673c5cb232fcc3ecb05cbcc4868cd60c816edc4e6f06028 + languageName: node + linkType: hard + +"bunyan@npm:^1.8.12": + version: 1.8.15 + resolution: "bunyan@npm:1.8.15" + dependencies: + dtrace-provider: "npm:~0.8" + moment: "npm:^2.19.3" + mv: "npm:~2" + safe-json-stringify: "npm:~1" + dependenciesMeta: + dtrace-provider: + optional: true + moment: + optional: true + mv: + optional: true + safe-json-stringify: + optional: true + bin: + bunyan: bin/bunyan + checksum: 10c0/c7b3adc07a4db3256f857dcba42b97dd6c35ab054cb26766643aae2b90e1b614795cdf231774ddaf374572d952f52ef4f4205047e15414e155e478aa0672e041 + languageName: node + linkType: hard + +"bunyan@npm:^2.0.5": + version: 2.0.5 + resolution: "bunyan@npm:2.0.5" + dependencies: + dtrace-provider: "npm:~0.8" + exeunt: "npm:1.1.0" + moment: "npm:^2.19.3" + mv: "npm:~2" + safe-json-stringify: "npm:~1" + dependenciesMeta: + dtrace-provider: + optional: true + moment: + optional: true + mv: + optional: true + safe-json-stringify: + optional: true + bin: + bunyan: bin/bunyan + checksum: 10c0/b3a2f0ccc4473c7a83d94f64805dd4ae70e4f7afff8658262a782a8a41ad71e062749e67ec7be57ab3ae724d3d199247ef4b6976ac6c0c67a114f442224977aa + languageName: node + linkType: hard + "bytes@npm:3.1.2, bytes@npm:^3.1.2, bytes@npm:~3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" @@ -8095,6 +8573,13 @@ __metadata: languageName: node linkType: hard +"caf@npm:^15.0.1": + version: 15.0.1 + resolution: "caf@npm:15.0.1" + checksum: 10c0/9a852c3c93c44499ab1e2cc58c264dd99bb2eaeb17b46df84e6fa5302d6829f776e84a1c13da5bbc2830ba274087eddbde9fe23334f7eb00ce89da9333d1a5cc + languageName: node + linkType: hard + "call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": version: 1.0.2 resolution: "call-bind-apply-helpers@npm:1.0.2" @@ -8166,7 +8651,7 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:^6.2.0, camelcase@npm:^6.3.0": +"camelcase@npm:^6.0.0, camelcase@npm:^6.2.0, camelcase@npm:^6.3.0": version: 6.3.0 resolution: "camelcase@npm:6.3.0" checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 @@ -8533,6 +9018,15 @@ __metadata: languageName: node linkType: hard +"color-convert@npm:^3.1.3": + version: 3.1.3 + resolution: "color-convert@npm:3.1.3" + dependencies: + color-name: "npm:^2.0.0" + checksum: 10c0/427648b442c6ea6dab5ba03f4962201ee59f128c80b25d5a0f7d9aab0ef52519a9db8a9bb3cf40b73f86eb19b5ca6aeb0ab930665f3d14973ce776d7d0448a15 + languageName: node + linkType: hard + "color-name@npm:1.1.3": version: 1.1.3 resolution: "color-name@npm:1.1.3" @@ -8547,6 +9041,13 @@ __metadata: languageName: node linkType: hard +"color-name@npm:^2.0.0": + version: 2.1.1 + resolution: "color-name@npm:2.1.1" + checksum: 10c0/6a1096d761b84ad18bdf1464d449800dbb1e613c914aafd9551514884f5e0cc47b5478fea32048188ec7a28424c6ded55fd4a81f39ca351e2ca4dd7729e05128 + languageName: node + linkType: hard + "color-string@npm:^1.9.0": version: 1.9.1 resolution: "color-string@npm:1.9.1" @@ -8557,6 +9058,15 @@ __metadata: languageName: node linkType: hard +"color-string@npm:^2.1.3": + version: 2.1.4 + resolution: "color-string@npm:2.1.4" + dependencies: + color-name: "npm:^2.0.0" + checksum: 10c0/18a9fefec153d885e0dbfb076f3a65cdcd19f52d96c719f2f261e90e5b7dafd13c51baac399d7099eac290f004d340045ab9467312dcc8afefe6f877ec5c4428 + languageName: node + linkType: hard + "color@npm:^4.2.3": version: 4.2.3 resolution: "color@npm:4.2.3" @@ -8567,6 +9077,16 @@ __metadata: languageName: node linkType: hard +"color@npm:^5.0.2": + version: 5.0.3 + resolution: "color@npm:5.0.3" + dependencies: + color-convert: "npm:^3.1.3" + color-string: "npm:^2.1.3" + checksum: 10c0/f08a03c5113ae4aa36dba9d2438596b194b897e18b961310643cb63872add1da507cd238df264eb434bbdbe3a377ec41f90d877531acca611523cfcd365db1b6 + languageName: node + linkType: hard + "colorette@npm:^1.0.7": version: 1.4.0 resolution: "colorette@npm:1.4.0" @@ -9155,6 +9675,13 @@ __metadata: languageName: node linkType: hard +"decamelize@npm:^4.0.0": + version: 4.0.0 + resolution: "decamelize@npm:4.0.0" + checksum: 10c0/e06da03fc05333e8cd2778c1487da67ffbea5b84e03ca80449519b8fa61f888714bbc6f459ea963d5641b4aa98832130eb5cd193d90ae9f0a27eee14be8e278d + languageName: node + linkType: hard + "decode-named-character-reference@npm:^1.0.0": version: 1.3.0 resolution: "decode-named-character-reference@npm:1.3.0" @@ -9309,6 +9836,58 @@ __metadata: languageName: node linkType: hard +"detox@npm:^20.51.4": + version: 20.51.4 + resolution: "detox@npm:20.51.4" + dependencies: + "@wix-pilot/core": "npm:^3.4.2" + "@wix-pilot/detox": "npm:^1.0.13" + ajv: "npm:^8.6.3" + bunyan: "npm:^1.8.12" + bunyan-debug-stream: "npm:^3.1.0" + caf: "npm:^15.0.1" + chalk: "npm:^4.0.0" + execa: "npm:^5.1.1" + find-up: "npm:^5.0.0" + fs-extra: "npm:^11.0.0" + funpermaproxy: "npm:^1.1.0" + glob: "npm:^8.0.3" + ini: "npm:^1.3.4" + jest-environment-emit: "npm:^1.2.0" + json-cycle: "npm:^1.3.0" + lodash: "npm:^4.17.11" + multi-sort-stream: "npm:^1.0.3" + multipipe: "npm:^4.0.0" + node-ipc: "npm:9.2.1" + promisify-child-process: "npm:^4.1.2" + proper-lockfile: "npm:^3.0.2" + resolve-from: "npm:^5.0.0" + sanitize-filename: "npm:^1.6.1" + semver: "npm:^7.0.0" + serialize-error: "npm:^8.0.1" + shell-quote: "npm:^1.7.2" + signal-exit: "npm:^3.0.3" + stream-json: "npm:^1.7.4" + strip-ansi: "npm:^6.0.1" + telnet-client: "npm:1.2.8" + tmp: "npm:^0.2.1" + trace-event-lib: "npm:^1.3.1" + which: "npm:^1.3.1" + ws: "npm:^7.0.0" + yargs: "npm:^17.0.0" + yargs-parser: "npm:^21.0.0" + yargs-unparser: "npm:^2.0.0" + peerDependencies: + jest: 30.x.x || 29.x.x || 28.x.x || ^27.2.5 + peerDependenciesMeta: + jest: + optional: true + bin: + detox: local-cli/cli.js + checksum: 10c0/4be674954c6ca60d8efb5d396848eefd5241018c78f64b503a462b0a66b9e7805d0af984318546ee8810063a95f9d92892d3e1e3d9b589ce703d2210da80fb16 + languageName: node + linkType: hard + "diff-sequences@npm:^29.6.3": version: 29.6.3 resolution: "diff-sequences@npm:29.6.3" @@ -9402,6 +9981,16 @@ __metadata: languageName: node linkType: hard +"dtrace-provider@npm:~0.8": + version: 0.8.8 + resolution: "dtrace-provider@npm:0.8.8" + dependencies: + nan: "npm:^2.14.0" + node-gyp: "npm:latest" + checksum: 10c0/33bfc18462dd59ae1de094c64b7b093d2f7f67dec48f138df3a7507c09aaed2a964a245e7bdf2bde7d1a6cc467b11d7396e0fb13a6b882642d42a44cc08c61da + languageName: node + linkType: hard + "dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": version: 1.0.1 resolution: "dunder-proto@npm:1.0.1" @@ -9413,7 +10002,7 @@ __metadata: languageName: node linkType: hard -"duplexer2@npm:~0.1.0": +"duplexer2@npm:^0.1.2, duplexer2@npm:~0.1.0": version: 0.1.4 resolution: "duplexer2@npm:0.1.4" dependencies: @@ -9422,6 +10011,41 @@ __metadata: languageName: node linkType: hard +"e2etestapp@workspace:examples/E2ETestApp": + version: 0.0.0-use.local + resolution: "e2etestapp@workspace:examples/E2ETestApp" + dependencies: + "@babel/core": "npm:^7.29.0" + "@babel/runtime": "npm:^7.29.2" + "@jest/globals": "npm:^30.4.1" + "@react-native-community/cli": "npm:20.2.0" + "@react-native-community/cli-platform-android": "npm:20.2.0" + "@react-native-community/cli-platform-ios": "npm:20.2.0" + "@react-native-community/netinfo": "npm:^12.0.1" + "@react-native/babel-preset": "npm:0.88.0" + "@react-native/metro-config": "npm:0.88.0" + "@react-native/typescript-config": "npm:0.88.0" + "@rnx-kit/metro-config": "npm:^2.2.6" + "@stream-io/typescript-config": "workspace:^" + "@types/react": "npm:^19.3.0" + babel-jest: "npm:30.0.0" + detox: "npm:^20.51.4" + jest: "npm:^30.4.2" + react: "npm:19.3.0" + react-native: "npm:0.88.0" + react-native-gesture-handler: "npm:^3.4.0" + react-native-launch-arguments: "npm:^4.1.1" + react-native-reanimated: "npm:4.7.1" + react-native-safe-area-context: "npm:^5.10.1" + react-native-svg: "npm:^15.15.5" + react-native-teleport: "npm:^1.2.2" + react-native-worklets: "npm:^0.13.0" + stream-chat: "npm:^9.54.0" + stream-chat-react-native: "workspace:^" + typescript: "npm:6.0.3" + languageName: unknown + linkType: soft + "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -9429,6 +10053,13 @@ __metadata: languageName: node linkType: hard +"easy-stack@npm:^1.0.1": + version: 1.0.1 + resolution: "easy-stack@npm:1.0.1" + checksum: 10c0/1eaf066169a20f6cc3cafd2bb36b00baacd60b6414c8d8bf51bfd50bc6f1c487140c8af86bbb8e1ff9ded2faea5e138c55a37867fc79cbbc985bf5a5ebe4b109 + languageName: node + linkType: hard + "ecdsa-sig-formatter@npm:1.0.11": version: 1.0.11 resolution: "ecdsa-sig-formatter@npm:1.0.11" @@ -9494,6 +10125,13 @@ __metadata: languageName: node linkType: hard +"enabled@npm:2.0.x": + version: 2.0.0 + resolution: "enabled@npm:2.0.0" + checksum: 10c0/3b2c2af9bc7f8b9e291610f2dde4a75cf6ee52a68f4dd585482fbdf9a55d65388940e024e56d40bb03e05ef6671f5f53021fa8b72a20e954d7066ec28166713f + languageName: node + linkType: hard + "encodeurl@npm:~1.0.2": version: 1.0.2 resolution: "encodeurl@npm:1.0.2" @@ -10189,6 +10827,13 @@ __metadata: languageName: node linkType: hard +"event-pubsub@npm:4.3.0": + version: 4.3.0 + resolution: "event-pubsub@npm:4.3.0" + checksum: 10c0/47fa4fb5b55b3ed08b912862cc913e03603fa063cd3ec5cf3dfeb39a19314d3ca327e938a2cf70685254ab3a71af8178969963c705a030c6081d625bec835114 + languageName: node + linkType: hard + "execa@npm:^4.0.3": version: 4.1.0 resolution: "execa@npm:4.1.0" @@ -10260,6 +10905,13 @@ __metadata: languageName: node linkType: hard +"exeunt@npm:1.1.0": + version: 1.1.0 + resolution: "exeunt@npm:1.1.0" + checksum: 10c0/dc2012d9016c01ec321e4d642d2f165ce7dc4a4d141c523a818416035bcfe11bd1d9abc142f6f608d6a7719824295131a1b8c89006eb308aafd3aba385bf40d2 + languageName: node + linkType: hard + "exit-x@npm:^0.2.2": version: 0.2.2 resolution: "exit-x@npm:0.2.2" @@ -10288,6 +10940,20 @@ __metadata: languageName: node linkType: hard +"expect@npm:30.5.2": + version: 30.5.2 + resolution: "expect@npm:30.5.2" + dependencies: + "@jest/expect-utils": "npm:30.5.2" + "@jest/get-type": "npm:30.5.0" + jest-matcher-utils: "npm:30.5.2" + jest-message-util: "npm:30.5.1" + jest-mock: "npm:30.5.2" + jest-util: "npm:30.5.1" + checksum: 10c0/31bf2307499008f267c84992a8ea27f76e97f207f685075439aa12f60b93dd81d024e5fcb0142755ed499233056f27fa51d01843d04cba437dfe7588387c352f + languageName: node + linkType: hard + "expo-asset@npm:~58.0.14": version: 58.0.14 resolution: "expo-asset@npm:58.0.14" @@ -10972,6 +11638,13 @@ __metadata: languageName: node linkType: hard +"fecha@npm:^4.2.0": + version: 4.2.3 + resolution: "fecha@npm:4.2.3" + checksum: 10c0/0e895965959cf6a22bb7b00f0bf546f2783836310f510ddf63f463e1518d4c96dec61ab33fdfd8e79a71b4856a7c865478ce2ee8498d560fe125947703c9b1cf + languageName: node + linkType: hard + "fetch-nodeshim@npm:^0.4.10": version: 0.4.10 resolution: "fetch-nodeshim@npm:0.4.10" @@ -11147,6 +11820,15 @@ __metadata: languageName: node linkType: hard +"flat@npm:^5.0.2": + version: 5.0.2 + resolution: "flat@npm:5.0.2" + bin: + flat: cli.js + checksum: 10c0/f178b13482f0cd80c7fede05f4d10585b1f2fdebf26e12edc138e32d3150c6ea6482b7f12813a1091143bad52bb6d3596bca51a162257a21163c0ff438baa5fe + languageName: node + linkType: hard + "flatted@npm:^3.2.9": version: 3.4.2 resolution: "flatted@npm:3.4.2" @@ -11193,6 +11875,13 @@ __metadata: languageName: node linkType: hard +"fn.name@npm:1.x.x": + version: 1.1.0 + resolution: "fn.name@npm:1.1.0" + checksum: 10c0/8ad62aa2d4f0b2a76d09dba36cfec61c540c13a0fd72e5d94164e430f987a7ce6a743112bbeb14877c810ef500d1f73d7f56e76d029d2e3413f20d79e3460a9a + languageName: node + linkType: hard + "follow-redirects@npm:^1.16.0": version: 1.16.0 resolution: "follow-redirects@npm:1.16.0" @@ -11369,6 +12058,13 @@ __metadata: languageName: node linkType: hard +"funpermaproxy@npm:^1.1.0": + version: 1.1.0 + resolution: "funpermaproxy@npm:1.1.0" + checksum: 10c0/058d5f56499dac0a275d7115c74230e339f7481618b96c21e8cafa8260a6db7c16f1e4127f5be2aeb167876ff5f33846503a07148279b2f68ca45d04771d666e + languageName: node + linkType: hard + "generator-function@npm:^2.0.0": version: 2.0.1 resolution: "generator-function@npm:2.0.1" @@ -11544,7 +12240,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.5.0": +"glob@npm:^10.4.1, glob@npm:^10.5.0": version: 10.5.0 resolution: "glob@npm:10.5.0" dependencies: @@ -11571,6 +12267,19 @@ __metadata: languageName: node linkType: hard +"glob@npm:^6.0.1": + version: 6.0.4 + resolution: "glob@npm:6.0.4" + dependencies: + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:2 || 3" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10c0/520146ebce0f4594b8357338f86281b38ee14214debce398a2902176a28f18e0f98911ea48516d85022de64fbbaa57f074aa13715d1daa5d70e21b82cea22183 + languageName: node + linkType: hard + "glob@npm:^7.1.3, glob@npm:^7.1.4": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -11671,7 +12380,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 @@ -13166,6 +13875,18 @@ __metadata: languageName: node linkType: hard +"jest-diff@npm:30.5.2": + version: 30.5.2 + resolution: "jest-diff@npm:30.5.2" + dependencies: + "@jest/diff-sequences": "npm:30.5.0" + "@jest/get-type": "npm:30.5.0" + chalk: "npm:^4.1.2" + pretty-format: "npm:30.5.1" + checksum: 10c0/b11524e4b7a0c7b83c5b6a3775f705b1002bf086c211e9bd2e481686e3fa0ee1a87e61fa7453232aef2808f085f254f4f8fcbb41b69681d1b9384ec321cf6002 + languageName: node + linkType: hard + "jest-diff@npm:^29.0.1, jest-diff@npm:^29.7.0": version: 29.7.0 resolution: "jest-diff@npm:29.7.0" @@ -13200,6 +13921,39 @@ __metadata: languageName: node linkType: hard +"jest-environment-emit@npm:^1.2.0": + version: 1.2.0 + resolution: "jest-environment-emit@npm:1.2.0" + dependencies: + bunyamin: "npm:^1.5.2" + bunyan: "npm:^2.0.5" + bunyan-debug-stream: "npm:^3.1.0" + funpermaproxy: "npm:^1.1.0" + lodash.merge: "npm:^4.6.2" + node-ipc: "npm:9.2.1" + strip-ansi: "npm:^6.0.0" + tslib: "npm:^2.5.3" + peerDependencies: + "@jest/environment": ">=27.2.5" + "@jest/types": ">=27.2.5" + jest: ">=27.2.5" + jest-environment-jsdom: ">=27.2.5" + jest-environment-node: ">=27.2.5" + peerDependenciesMeta: + "@jest/environment": + optional: true + "@jest/types": + optional: true + jest: + optional: true + jest-environment-jsdom: + optional: true + jest-environment-node: + optional: true + checksum: 10c0/4e1230b447b54c54eecbc6e52782e2133a07b0556a43d34b9f7733b4be8d3a390be7a444863abed31cef638a259815f65b41b588dabc9f9ee478af9ea0ab419b + languageName: node + linkType: hard + "jest-environment-node@npm:30.4.1": version: 30.4.1 resolution: "jest-environment-node@npm:30.4.1" @@ -13280,6 +14034,25 @@ __metadata: languageName: node linkType: hard +"jest-haste-map@npm:30.5.1": + version: 30.5.1 + resolution: "jest-haste-map@npm:30.5.1" + dependencies: + "@jest/types": "npm:30.5.1" + "@parcel/watcher": "npm:^2.6.0" + "@types/node": "npm:*" + anymatch: "npm:^3.1.3" + fb-watchman: "npm:^2.0.2" + fdir: "npm:^6.5.0" + graceful-fs: "npm:^4.2.11" + jest-regex-util: "npm:30.5.0" + jest-util: "npm:30.5.1" + jest-worker: "npm:30.5.1" + picomatch: "npm:^4.0.3" + checksum: 10c0/a9838762f84c54b1d551650e3c956f8d72370a15b2e9291d4b0c9aab0b4d4f3d1758bbc619ffb028af567d5dc53a73c5e72af078c56c5fa47cd73a2a309ca89a + languageName: node + linkType: hard + "jest-haste-map@npm:^29.7.0": version: 29.7.0 resolution: "jest-haste-map@npm:29.7.0" @@ -13325,6 +14098,18 @@ __metadata: languageName: node linkType: hard +"jest-matcher-utils@npm:30.5.2": + version: 30.5.2 + resolution: "jest-matcher-utils@npm:30.5.2" + dependencies: + "@jest/get-type": "npm:30.5.0" + chalk: "npm:^4.1.2" + jest-diff: "npm:30.5.2" + pretty-format: "npm:30.5.1" + checksum: 10c0/30f075c6f897335415817ad25e6caae8994de87c31ad59ae8f501e2c8bf6c925635abb481c562436b6add42a784902f105ee2fa0eca3357bd26e038f7e004cb8 + languageName: node + linkType: hard + "jest-matcher-utils@npm:^29.0.1, jest-matcher-utils@npm:^29.7.0": version: 29.7.0 resolution: "jest-matcher-utils@npm:29.7.0" @@ -13355,6 +14140,24 @@ __metadata: languageName: node linkType: hard +"jest-message-util@npm:30.5.1": + version: 30.5.1 + resolution: "jest-message-util@npm:30.5.1" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@jest/types": "npm:30.5.1" + "@types/stack-utils": "npm:^2.0.3" + chalk: "npm:^4.1.2" + graceful-fs: "npm:^4.2.11" + jest-util: "npm:30.5.1" + picomatch: "npm:^4.0.3" + pretty-format: "npm:30.5.1" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.6" + checksum: 10c0/f542d07f8ceba19da26c547aa86abc51e6fad518999a18853e7c2acc2a55845ad759aa94cbe650d0453b036b81fcafee710d467b55e1e509f9939db033e76919 + languageName: node + linkType: hard + "jest-message-util@npm:^29.7.0": version: 29.7.0 resolution: "jest-message-util@npm:29.7.0" @@ -13383,6 +14186,18 @@ __metadata: languageName: node linkType: hard +"jest-mock@npm:30.5.2": + version: 30.5.2 + resolution: "jest-mock@npm:30.5.2" + dependencies: + "@jest/expect-utils": "npm:30.5.2" + "@jest/types": "npm:30.5.1" + "@types/node": "npm:*" + jest-util: "npm:30.5.1" + checksum: 10c0/24cc9ac6449c536ef5009c531e14ee6b145f5d68db6e4c5616a8f775c5c81173e83e2c6c47922d413da4dbf804eab555b91ab424b0bf918495ee1b29d8a8ebaa + languageName: node + linkType: hard + "jest-mock@npm:^29.7.0": version: 29.7.0 resolution: "jest-mock@npm:29.7.0" @@ -13420,6 +14235,13 @@ __metadata: languageName: node linkType: hard +"jest-regex-util@npm:30.5.0": + version: 30.5.0 + resolution: "jest-regex-util@npm:30.5.0" + checksum: 10c0/f0e5ef257b906862821e1fb58c1d3d7bb3c4fc1fb11915673f04d3693673e0656b8f502c9871900e13e58329039c73e5b5c59a2622cbd6ba5c7fc7463b60ce48 + languageName: node + linkType: hard + "jest-regex-util@npm:^29.6.3": version: 29.6.3 resolution: "jest-regex-util@npm:29.6.3" @@ -13542,6 +14364,35 @@ __metadata: languageName: node linkType: hard +"jest-snapshot@npm:30.5.2": + version: 30.5.2 + resolution: "jest-snapshot@npm:30.5.2" + dependencies: + "@babel/core": "npm:^7.27.4" + "@babel/generator": "npm:^7.27.5" + "@babel/plugin-syntax-jsx": "npm:^7.27.1" + "@babel/plugin-syntax-typescript": "npm:^7.27.1" + "@babel/types": "npm:^7.27.3" + "@jest/expect-utils": "npm:30.5.2" + "@jest/get-type": "npm:30.5.0" + "@jest/snapshot-utils": "npm:30.5.1" + "@jest/transform": "npm:30.5.2" + "@jest/types": "npm:30.5.1" + babel-preset-current-node-syntax: "npm:^1.2.0" + chalk: "npm:^4.1.2" + expect: "npm:30.5.2" + graceful-fs: "npm:^4.2.11" + jest-diff: "npm:30.5.2" + jest-matcher-utils: "npm:30.5.2" + jest-message-util: "npm:30.5.1" + jest-util: "npm:30.5.1" + pretty-format: "npm:30.5.1" + semver: "npm:^7.7.2" + synckit: "npm:^0.11.8" + checksum: 10c0/cef6b102f743cbefd5d8ad0cf15b5694c09522904c43ace56537990475805b7c6eaebf82e6c5564e225e8db7617ebdda77340acc9a47d9909f338fbb84fca345 + languageName: node + linkType: hard + "jest-util@npm:30.0.0": version: 30.0.0 resolution: "jest-util@npm:30.0.0" @@ -13560,13 +14411,27 @@ __metadata: version: 30.4.1 resolution: "jest-util@npm:30.4.1" dependencies: - "@jest/types": "npm:30.4.1" + "@jest/types": "npm:30.4.1" + "@types/node": "npm:*" + chalk: "npm:^4.1.2" + ci-info: "npm:^4.2.0" + graceful-fs: "npm:^4.2.11" + picomatch: "npm:^4.0.3" + checksum: 10c0/3efe1f25e5a172d04c6af8612d82867ab603b7c1bd8cb89073ff834679b44eba178793cf3af162cf5e25be13aa736ebd23a7826683acc85bddc5873f305b1f6e + languageName: node + linkType: hard + +"jest-util@npm:30.5.1": + version: 30.5.1 + resolution: "jest-util@npm:30.5.1" + dependencies: + "@jest/types": "npm:30.5.1" "@types/node": "npm:*" chalk: "npm:^4.1.2" ci-info: "npm:^4.2.0" graceful-fs: "npm:^4.2.11" picomatch: "npm:^4.0.3" - checksum: 10c0/3efe1f25e5a172d04c6af8612d82867ab603b7c1bd8cb89073ff834679b44eba178793cf3af162cf5e25be13aa736ebd23a7826683acc85bddc5873f305b1f6e + checksum: 10c0/5f3b250f1ada29b877cab75db5808f1318af99ba7f68032e0966ddb6d853186ad2dbaeac679842684c4676dfd35083470f2d26eee65a28161fcd9e43c7146fab languageName: node linkType: hard @@ -13654,6 +14519,19 @@ __metadata: languageName: node linkType: hard +"jest-worker@npm:30.5.1": + version: 30.5.1 + resolution: "jest-worker@npm:30.5.1" + dependencies: + "@types/node": "npm:*" + "@ungap/structured-clone": "npm:^1.3.0" + jest-util: "npm:30.5.1" + merge-stream: "npm:^2.0.0" + supports-color: "npm:^8.1.1" + checksum: 10c0/fe3806769014920c5256ef01e2e9a5aba91a7ded90d7c86a82196f3da03070087c8c6b591f8bbb0b500c184c7cc8c5f22191e51cce504163b24a8b42c4cadd8b + languageName: node + linkType: hard + "jest-worker@npm:^29.6.3, jest-worker@npm:^29.7.0": version: 29.7.0 resolution: "jest-worker@npm:29.7.0" @@ -13723,6 +14601,22 @@ __metadata: languageName: node linkType: hard +"js-message@npm:1.0.7": + version: 1.0.7 + resolution: "js-message@npm:1.0.7" + checksum: 10c0/2dc2ff5a594613a9690c157a921999237164a3c213523f406dee23985ed81c92be9afdc7b34e67a2838d447d7d79cbf1662048effe8367bedcd71a950e4e292e + languageName: node + linkType: hard + +"js-queue@npm:2.0.2": + version: 2.0.2 + resolution: "js-queue@npm:2.0.2" + dependencies: + easy-stack: "npm:^1.0.1" + checksum: 10c0/cdadc65576c6d46817f47c27d7a57a1f3acbe3a4da2a4bc7303218f06d6ee7971358db28ec0d6b6835b4cd93332c52792b535064fee706453ef9d2bfe03b4a1a + languageName: node + linkType: hard + "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -13787,6 +14681,13 @@ __metadata: languageName: node linkType: hard +"json-cycle@npm:^1.3.0": + version: 1.5.0 + resolution: "json-cycle@npm:1.5.0" + checksum: 10c0/43ba321cf186b22573826ee76d64601bdfe57fe787d74162626c453bd860595c4c62ebfc338b5a64994fb4e4d7a947e6c2ef5f202e3a2ee4b01ca23ebc0cd710 + languageName: node + linkType: hard + "json-parse-better-errors@npm:^1.0.1": version: 1.0.2 resolution: "json-parse-better-errors@npm:1.0.2" @@ -13983,6 +14884,13 @@ __metadata: languageName: node linkType: hard +"kuler@npm:^2.0.0": + version: 2.0.0 + resolution: "kuler@npm:2.0.0" + checksum: 10c0/0a4e99d92ca373f8f74d1dc37931909c4d0d82aebc94cf2ba265771160fc12c8df34eaaac80805efbda367e2795cb1f1dd4c3d404b6b1cf38aec94035b503d2d + languageName: node + linkType: hard + "lan-network@npm:^0.2.1": version: 0.2.1 resolution: "lan-network@npm:0.2.1" @@ -14440,7 +15348,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:4.18.1, lodash@npm:^4.17.15, lodash@npm:^4.17.4": +"lodash@npm:4.18.1, lodash@npm:^4.17.11, lodash@npm:^4.17.15, lodash@npm:^4.17.4": version: 4.18.1 resolution: "lodash@npm:4.18.1" checksum: 10c0/757228fc68805c59789e82185135cf85f05d0b2d3d54631d680ca79ec21944ec8314d4533639a14b8bcfbd97a517e78960933041a5af17ecb693ec6eecb99a27 @@ -14476,6 +15384,20 @@ __metadata: languageName: node linkType: hard +"logform@npm:^2.7.0": + version: 2.7.0 + resolution: "logform@npm:2.7.0" + dependencies: + "@colors/colors": "npm:1.6.0" + "@types/triple-beam": "npm:^1.3.2" + fecha: "npm:^4.2.0" + ms: "npm:^2.1.1" + safe-stable-stringify: "npm:^2.3.1" + triple-beam: "npm:^1.3.0" + checksum: 10c0/4789b4b37413c731d1835734cb799240d31b865afde6b7b3e06051d6a4127bfda9e88c99cfbf296d084a315ccbed2647796e6a56b66e725bcb268c586f57558f + languageName: node + linkType: hard + "logkitty@npm:^0.7.1": version: 0.7.1 resolution: "logkitty@npm:0.7.1" @@ -15281,6 +16203,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:2 || 3, minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2, minimatch@npm:^3.1.5": + version: 3.1.5 + resolution: "minimatch@npm:3.1.5" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/2ecbdc0d33f07bddb0315a8b5afbcb761307a8778b48f0b312418ccbced99f104a2d17d8aca7573433c70e8ccd1c56823a441897a45e384ea76ef401a26ace70 + languageName: node + linkType: hard + "minimatch@npm:^10.0.3, minimatch@npm:^10.1.1, minimatch@npm:^10.2.2, minimatch@npm:^10.2.5": version: 10.2.5 resolution: "minimatch@npm:10.2.5" @@ -15290,15 +16221,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2, minimatch@npm:^3.1.5": - version: 3.1.5 - resolution: "minimatch@npm:3.1.5" - dependencies: - brace-expansion: "npm:^1.1.7" - checksum: 10c0/2ecbdc0d33f07bddb0315a8b5afbcb761307a8778b48f0b312418ccbced99f104a2d17d8aca7573433c70e8ccd1c56823a441897a45e384ea76ef401a26ace70 - languageName: node - linkType: hard - "minimatch@npm:^5.0.1": version: 5.1.9 resolution: "minimatch@npm:5.1.9" @@ -15423,6 +16345,17 @@ __metadata: languageName: node linkType: hard +"mkdirp@npm:~0.5.1": + version: 0.5.6 + resolution: "mkdirp@npm:0.5.6" + dependencies: + minimist: "npm:^1.2.6" + bin: + mkdirp: bin/cmd.js + checksum: 10c0/e2e2be789218807b58abced04e7b49851d9e46e88a2f9539242cc8a92c9b5c3a0b9bab360bd3014e02a140fc4fbc58e31176c408b493f8a2a6f4986bd7527b01 + languageName: node + linkType: hard + "moment-timezone@npm:^0.6.0": version: 0.6.2 resolution: "moment-timezone@npm:0.6.2" @@ -15432,6 +16365,13 @@ __metadata: languageName: node linkType: hard +"moment@npm:^2.19.3": + version: 2.31.0 + resolution: "moment@npm:2.31.0" + checksum: 10c0/3755b186b8ff62c565685ce69dde55bf46003c0329139be5c8dd7845cd567bfd7d02be3a13e379a25acb9e98f6b87136c4776745e440615a4f5ca8dd9a8815b9 + languageName: node + linkType: hard + "moment@npm:^2.29.4": version: 2.30.1 resolution: "moment@npm:2.30.1" @@ -15453,6 +16393,23 @@ __metadata: languageName: node linkType: hard +"multi-sort-stream@npm:^1.0.3, multi-sort-stream@npm:^1.0.4": + version: 1.0.4 + resolution: "multi-sort-stream@npm:1.0.4" + checksum: 10c0/74820fd8f4287798f56f5c0b390d52d0fa0efd46b22a880390c6182c4b2ff6bd608dc90974fe7b0981c9243ae4f857d3389e74ebf6d071d7465056c29772196d + languageName: node + linkType: hard + +"multipipe@npm:^4.0.0": + version: 4.0.0 + resolution: "multipipe@npm:4.0.0" + dependencies: + duplexer2: "npm:^0.1.2" + object-assign: "npm:^4.1.0" + checksum: 10c0/60b547f85a3c692fe24c4e15988bdbb6c7985665fd7c4f6228bbf2630f699badeefd8d492bff5b295ea02a284e12917ff21e324a2f660e3d5667bb656a01792d + languageName: node + linkType: hard + "multitars@npm:^1.0.2": version: 1.0.2 resolution: "multitars@npm:1.0.2" @@ -15467,6 +16424,17 @@ __metadata: languageName: node linkType: hard +"mv@npm:~2": + version: 2.1.1 + resolution: "mv@npm:2.1.1" + dependencies: + mkdirp: "npm:~0.5.1" + ncp: "npm:~2.0.0" + rimraf: "npm:~2.4.0" + checksum: 10c0/5da59a9f4ec16da0867289b5018c81c25c59b06bb9da717bc7bd0b40363d6653dc88d6da32a9434fd7416bfc3f67184c306ea44d3856ff97f3214cc96960efcd + languageName: node + linkType: hard + "mz@npm:^2.4.0": version: 2.7.0 resolution: "mz@npm:2.7.0" @@ -15478,6 +16446,15 @@ __metadata: languageName: node linkType: hard +"nan@npm:^2.14.0": + version: 2.29.0 + resolution: "nan@npm:2.29.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/c90586afb3f101f64c2997e7481f7c2c5528e196399a1c2653e4fff7354a01f35009e10fd43aa567a4b68be0ac8ed45a60794f5ca0a863120b74aecf4a45ad2c + languageName: node + linkType: hard + "nanoid@npm:^3.3.1, nanoid@npm:^3.3.11": version: 3.3.12 resolution: "nanoid@npm:3.3.12" @@ -15519,6 +16496,15 @@ __metadata: languageName: node linkType: hard +"ncp@npm:~2.0.0": + version: 2.0.0 + resolution: "ncp@npm:2.0.0" + bin: + ncp: ./bin/ncp + checksum: 10c0/d515babf9d3205ab9252e7d640af7c3e1a880317016d41f2fce2e6b9c8f60eb8bb6afde30e8c4f8e1e3fa551465f094433c3f364b25a85d6a28ec52c1ad6e067 + languageName: node + linkType: hard + "negotiator@npm:0.6.3": version: 0.6.3 resolution: "negotiator@npm:0.6.3" @@ -15584,6 +16570,15 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^7.0.0": + version: 7.1.1 + resolution: "node-addon-api@npm:7.1.1" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/fb32a206276d608037fa1bcd7e9921e177fe992fc610d098aa3128baca3c0050fc1e014fa007e9b3874cf865ddb4f5bd9f43ccb7cbbbe4efaff6a83e920b17e9 + languageName: node + linkType: hard + "node-emoji@npm:^2.2.0": version: 2.2.0 resolution: "node-emoji@npm:2.2.0" @@ -15656,6 +16651,17 @@ __metadata: languageName: node linkType: hard +"node-ipc@npm:9.2.1": + version: 9.2.1 + resolution: "node-ipc@npm:9.2.1" + dependencies: + event-pubsub: "npm:4.3.0" + js-message: "npm:1.0.7" + js-queue: "npm:2.0.2" + checksum: 10c0/ed6448e2c643a831b9795a52de727658417a2cbedd3a59988b9be7654383982577dc507e0ec13211d10ed90377124366e3869a137b05093a424caf647eba0523 + languageName: node + linkType: hard + "node-releases@npm:^2.0.36": version: 2.0.44 resolution: "node-releases@npm:2.0.44" @@ -16118,6 +17124,15 @@ __metadata: languageName: node linkType: hard +"one-time@npm:^1.0.0": + version: 1.0.0 + resolution: "one-time@npm:1.0.0" + dependencies: + fn.name: "npm:1.x.x" + checksum: 10c0/6e4887b331edbb954f4e915831cbec0a7b9956c36f4feb5f6de98c448ac02ff881fd8d9b55a6b1b55030af184c6b648f340a76eb211812f4ad8c9b4b8692fdaa + languageName: node + linkType: hard + "onetime@npm:^2.0.0": version: 2.0.1 resolution: "onetime@npm:2.0.1" @@ -16721,6 +17736,13 @@ __metadata: languageName: node linkType: hard +"pngjs@npm:^7.0.0": + version: 7.0.0 + resolution: "pngjs@npm:7.0.0" + checksum: 10c0/0d4c7a0fd476a9c33df7d0a2a73e1d56537628a668841f6995c2bca070cf30819f9254a64363266bc14ef2fee47659dd3b4f2b18eec7ab65143015139f497b38 + languageName: node + linkType: hard + "possible-typed-array-names@npm:^1.0.0": version: 1.0.0 resolution: "possible-typed-array-names@npm:1.0.0" @@ -16815,6 +17837,18 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:30.5.1": + version: 30.5.1 + resolution: "pretty-format@npm:30.5.1" + dependencies: + "@jest/react-is-18": "npm:react-is@^18.3.1" + "@jest/react-is-19": "npm:react-is@^19.2.5" + "@jest/schemas": "npm:30.5.0" + ansi-styles: "npm:^5.2.0" + checksum: 10c0/bdef1790b9c93d1aa329c93885b0d1b8b6a3273bb680377531c70151b2e1b0a465210cae100661fc19b0bf35f507587f78b1a2f1afcf08791b9fe5630c23c076 + languageName: node + linkType: hard + "pretty-format@npm:^29.0.3, pretty-format@npm:^29.7.0": version: 29.7.0 resolution: "pretty-format@npm:29.7.0" @@ -16933,6 +17967,13 @@ __metadata: languageName: node linkType: hard +"promisify-child-process@npm:^4.1.2": + version: 4.1.2 + resolution: "promisify-child-process@npm:4.1.2" + checksum: 10c0/e5347137f1c333af4b7b4d2272a091d31007b30320af4b852f30f41776db378e22b3db1e67e40026a18301c243d9c9780e41c211cfce76ba26aedba17e3a385c + languageName: node + linkType: hard + "prompts@npm:^2.3.2, prompts@npm:^2.4.2": version: 2.4.2 resolution: "prompts@npm:2.4.2" @@ -16963,6 +18004,17 @@ __metadata: languageName: node linkType: hard +"proper-lockfile@npm:^3.0.2": + version: 3.2.0 + resolution: "proper-lockfile@npm:3.2.0" + dependencies: + graceful-fs: "npm:^4.1.11" + retry: "npm:^0.12.0" + signal-exit: "npm:^3.0.2" + checksum: 10c0/b3a0bbf87f1b842ab57d91bee2e8c73d6cb2e90558aad259e83561cf5467f20885a1eb127343ecd9db214c78aaa196867ef8edb76e3681dc07ee43a489fd53bd + languageName: node + linkType: hard + "property-information@npm:^7.0.0": version: 7.2.0 resolution: "property-information@npm:7.2.0" @@ -17155,13 +18207,6 @@ __metadata: languageName: node linkType: hard -"react-is-18@npm:react-is@^18.3.1, react-is@npm:^18.0.0": - version: 18.3.1 - resolution: "react-is@npm:18.3.1" - checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 - languageName: node - linkType: hard - "react-is-19@npm:react-is@^19.2.5, react-is@npm:^19.1.0": version: 19.2.6 resolution: "react-is@npm:19.2.6" @@ -17317,6 +18362,16 @@ __metadata: languageName: node linkType: hard +"react-native-launch-arguments@npm:^4.1.1": + version: 4.1.1 + resolution: "react-native-launch-arguments@npm:4.1.1" + peerDependencies: + react: ">=16.8.1" + react-native: ">=0.60.0-rc.0 <1.0.x" + checksum: 10c0/94a88b3c6ba583b20108200c794f2c132a5b94383f0eb22797ff21194d140904bd62510ee9a01ea464ba831ddd69b3c49fb9b2799728d1d7fbaebf0ed683edd2 + languageName: node + linkType: hard + "react-native-lightbox@npm:^0.7.0": version: 0.7.0 resolution: "react-native-lightbox@npm:0.7.0" @@ -17758,7 +18813,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0": +"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.2": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -18084,6 +19139,17 @@ __metadata: languageName: node linkType: hard +"rimraf@npm:~2.4.0": + version: 2.4.5 + resolution: "rimraf@npm:2.4.5" + dependencies: + glob: "npm:^6.0.1" + bin: + rimraf: ./bin.js + checksum: 10c0/5251a36053165d23248efec5077f9addc13ad7f742a02dcd9ac7adda9e208cbf7523901e96a9ca6c33059bd0b573b97eab3334cf1d9976cc5ddc8b3c24d9ddd7 + languageName: node + linkType: hard + "root@workspace:.": version: 0.0.0-use.local resolution: "root@workspace:." @@ -18177,6 +19243,13 @@ __metadata: languageName: node linkType: hard +"safe-json-stringify@npm:~1": + version: 1.2.0 + resolution: "safe-json-stringify@npm:1.2.0" + checksum: 10c0/9c21c7b63a35a9e52d248eea2ad7bc9e790dde5aa418f0d4eed3c0b4c866e15337425b0d973173d30dd70a9e422271619f17e13574e0c8371d0c240cf72b871f + languageName: node + linkType: hard + "safe-push-apply@npm:^1.0.0": version: 1.0.0 resolution: "safe-push-apply@npm:1.0.0" @@ -18198,6 +19271,13 @@ __metadata: languageName: node linkType: hard +"safe-stable-stringify@npm:^2.3.1": + version: 2.5.0 + resolution: "safe-stable-stringify@npm:2.5.0" + checksum: 10c0/baea14971858cadd65df23894a40588ed791769db21bafb7fd7608397dbdce9c5aac60748abae9995e0fc37e15f2061980501e012cd48859740796bea2987f49 + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -18278,6 +19358,15 @@ __metadata: languageName: node linkType: hard +"sanitize-filename@npm:^1.6.1": + version: 1.6.4 + resolution: "sanitize-filename@npm:1.6.4" + dependencies: + truncate-utf8-bytes: "npm:^1.0.0" + checksum: 10c0/b56415a95e4f90dc992cd126b5f45c7b39d178662fbd0dc48f03203e35c58ab8e9eb3f5cebfaabc46f1438093d65a3a3cc96875e2b036a1474e19593bee9a540 + languageName: node + linkType: hard + "sax@npm:>=0.6.0": version: 1.6.0 resolution: "sax@npm:1.6.0" @@ -18353,6 +19442,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.0.0": + version: 7.8.5 + resolution: "semver@npm:7.8.5" + bin: + semver: bin/semver.js + checksum: 10c0/b1f3127a5be8125a94f37188b361c212466c292c6910adce3ec106cff5dc211ccaedc4739c11bb70fda59d6fc1f040a9bca289f4e093451521a2372e5231fe0c + languageName: node + linkType: hard + "semver@npm:^7.1.1, semver@npm:^7.1.2, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.7.2, semver@npm:^7.7.3, semver@npm:^7.7.4": version: 7.8.1 resolution: "semver@npm:7.8.1" @@ -18390,6 +19488,15 @@ __metadata: languageName: node linkType: hard +"serialize-error@npm:^8.0.1": + version: 8.1.0 + resolution: "serialize-error@npm:8.1.0" + dependencies: + type-fest: "npm:^0.20.2" + checksum: 10c0/8cfd89f43ca93e283c5f1d16178a536bdfac9bc6029f4a9df988610cc399bc4f2478d1f10ce40b9dff66b863a5158a19b438fbec929045c96d92174f6bca1e88 + languageName: node + linkType: hard + "serve-static@npm:^1.13.1, serve-static@npm:^1.16.2": version: 1.16.3 resolution: "serve-static@npm:1.16.3" @@ -18497,6 +19604,13 @@ __metadata: languageName: node linkType: hard +"shell-quote@npm:^1.7.2": + version: 1.12.0 + resolution: "shell-quote@npm:1.12.0" + checksum: 10c0/1605a4ed6a758bf13de7cb8197a4184d8015f7a4dbdcd6cd4a717d5b30b3ae7666300cc4ae7f3155cf80ace9739e9e82061ee5aa1a648f53f5fab31a220816d0 + languageName: node + linkType: hard + "side-channel-list@npm:^1.0.0, side-channel-list@npm:^1.0.1": version: 1.0.1 resolution: "side-channel-list@npm:1.0.1" @@ -18837,6 +19951,13 @@ __metadata: languageName: node linkType: hard +"stack-trace@npm:0.0.x": + version: 0.0.10 + resolution: "stack-trace@npm:0.0.10" + checksum: 10c0/9ff3dabfad4049b635a85456f927a075c9d0c210e3ea336412d18220b2a86cbb9b13ec46d6c37b70a302a4ea4d49e30e5d4944dd60ae784073f1cde778ac8f4b + languageName: node + linkType: hard + "stack-utils@npm:^2.0.3, stack-utils@npm:^2.0.6": version: 2.0.6 resolution: "stack-utils@npm:2.0.6" @@ -18916,6 +20037,13 @@ __metadata: languageName: node linkType: hard +"stream-chain@npm:^2.2.5": + version: 2.2.5 + resolution: "stream-chain@npm:2.2.5" + checksum: 10c0/c512f50190d7c92d688fa64e7af540c51b661f9c2b775fc72bca38ea9bca515c64c22c2197b1be463741daacbaaa2dde8a8ea24ebda46f08391224f15249121a + languageName: node + linkType: hard + "stream-chat-expo@workspace:^, stream-chat-expo@workspace:package/expo-package": version: 0.0.0-use.local resolution: "stream-chat-expo@workspace:package/expo-package" @@ -19155,6 +20283,15 @@ __metadata: languageName: node linkType: hard +"stream-json@npm:^1.7.4, stream-json@npm:^1.7.5": + version: 1.9.1 + resolution: "stream-json@npm:1.9.1" + dependencies: + stream-chain: "npm:^2.2.5" + checksum: 10c0/0521e5cb3fb6b0e2561d715975e891bd81fa77d0239c8d0b1756846392bc3c7cdd7f1ddb0fe7ed77e6fdef58daab9e665d3b39f7d677bd0859e65a2bff59b92c + languageName: node + linkType: hard + "string-length@npm:^4.0.2": version: 4.0.2 resolution: "string-length@npm:4.0.2" @@ -19536,6 +20673,15 @@ __metadata: languageName: node linkType: hard +"telnet-client@npm:1.2.8": + version: 1.2.8 + resolution: "telnet-client@npm:1.2.8" + dependencies: + bluebird: "npm:^3.5.4" + checksum: 10c0/f6700268a9734305655e305540b21e18f0ab05923d4917fcf8c3192ced596ce4675c61e8b8d6ad12c89bf7eb2d38f2792ead5eb721c7055c4e924f9312913a34 + languageName: node + linkType: hard + "temp-dir@npm:^3.0.0": version: 3.0.0 resolution: "temp-dir@npm:3.0.0" @@ -19590,6 +20736,24 @@ __metadata: languageName: node linkType: hard +"test-exclude@npm:^7.0.1": + version: 7.0.2 + resolution: "test-exclude@npm:7.0.2" + dependencies: + "@istanbuljs/schema": "npm:^0.1.2" + glob: "npm:^10.4.1" + minimatch: "npm:^10.2.2" + checksum: 10c0/b79b855af9168c6a362146015ccf40f5e3a25e307304ba9bea930818507f6319d230380d5d7b5baa659c981ccc11f1bd21b6f012f85606353dec07e02dee67c9 + languageName: node + linkType: hard + +"text-hex@npm:1.0.x": + version: 1.0.0 + resolution: "text-hex@npm:1.0.0" + checksum: 10c0/57d8d320d92c79d7c03ffb8339b825bb9637c2cbccf14304309f51d8950015c44464b6fd1b6820a3d4821241c68825634f09f5a2d9d501e84f7c6fd14376860d + languageName: node + linkType: hard + "text-table@npm:~0.2.0": version: 0.2.0 resolution: "text-table@npm:0.2.0" @@ -19665,6 +20829,13 @@ __metadata: languageName: node linkType: hard +"tmp@npm:^0.2.1": + version: 0.2.7 + resolution: "tmp@npm:0.2.7" + checksum: 10c0/59eb55584f2f07210d3231b6a1f6b5c2b9794d8a7b509c8ee867ed2acad6d2245ee2448b7937b676ffbff3155a70077edde8a69f9d7cf0f90c86a62e8910c357 + languageName: node + linkType: hard + "tmpl@npm:1.0.5": version: 1.0.5 resolution: "tmpl@npm:1.0.5" @@ -19702,6 +20873,15 @@ __metadata: languageName: node linkType: hard +"trace-event-lib@npm:^1.3.1": + version: 1.4.1 + resolution: "trace-event-lib@npm:1.4.1" + dependencies: + browser-process-hrtime: "npm:^1.0.0" + checksum: 10c0/301faf760c8d956d9879f1eaf8a949d480126a1af0d88d33299f1580401e32358d48726750ac8903a4e9be1d0c5f68bdc13453e067ca2bc93565ea381123c4bd + languageName: node + linkType: hard + "traverse@npm:0.6.8": version: 0.6.8 resolution: "traverse@npm:0.6.8" @@ -19716,6 +20896,22 @@ __metadata: languageName: node linkType: hard +"triple-beam@npm:^1.3.0": + version: 1.4.1 + resolution: "triple-beam@npm:1.4.1" + checksum: 10c0/4bf1db71e14fe3ff1c3adbe3c302f1fdb553b74d7591a37323a7badb32dc8e9c290738996cbb64f8b10dc5a3833645b5d8c26221aaaaa12e50d1251c9aba2fea + languageName: node + linkType: hard + +"truncate-utf8-bytes@npm:^1.0.0": + version: 1.0.2 + resolution: "truncate-utf8-bytes@npm:1.0.2" + dependencies: + utf8-byte-length: "npm:^1.0.1" + checksum: 10c0/af2b431fc4314f119b551e5fccfad49d4c0ef82e13ba9ca61be6567801195b08e732ce9643542e8ad1b3df44f3df2d7345b3dd34f723954b6bb43a14584d6b3c + languageName: node + linkType: hard + "ts-api-utils@npm:^2.5.0": version: 2.5.0 resolution: "ts-api-utils@npm:2.5.0" @@ -19737,7 +20933,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.1.0, tslib@npm:^2.4.0": +"tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.5.3": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 @@ -19787,6 +20983,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^0.20.2": + version: 0.20.2 + resolution: "type-fest@npm:0.20.2" + checksum: 10c0/dea9df45ea1f0aaa4e2d3bed3f9a0bfe9e5b2592bddb92eb1bf06e50bcf98dbb78189668cd8bc31a0511d3fc25539b4cd5c704497e53e93e2d40ca764b10bfc3 + languageName: node + linkType: hard + "type-fest@npm:^0.21.3": version: 0.21.3 resolution: "type-fest@npm:0.21.3" @@ -20235,6 +21438,13 @@ __metadata: languageName: node linkType: hard +"utf8-byte-length@npm:^1.0.1": + version: 1.0.5 + resolution: "utf8-byte-length@npm:1.0.5" + checksum: 10c0/e69bda3299608f4cc75976da9fb74ac94801a58b9ca29fdad03a20ec952e7477d7f226c12716b5f36bd4cff8151d1d152d02ee1df3752f017d4b2c725ce3e47a + languageName: node + linkType: hard + "util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" @@ -20532,6 +21742,17 @@ __metadata: languageName: node linkType: hard +"which@npm:^1.3.1": + version: 1.3.1 + resolution: "which@npm:1.3.1" + dependencies: + isexe: "npm:^2.0.0" + bin: + which: ./bin/which + checksum: 10c0/e945a8b6bbf6821aaaef7f6e0c309d4b615ef35699576d5489b4261da9539f70393c6b2ce700ee4321c18f914ebe5644bc4631b15466ffbaad37d83151f6af59 + languageName: node + linkType: hard + "which@npm:^2.0.1, which@npm:^2.0.2": version: 2.0.2 resolution: "which@npm:2.0.2" @@ -20554,6 +21775,36 @@ __metadata: languageName: node linkType: hard +"winston-transport@npm:^4.9.0": + version: 4.9.0 + resolution: "winston-transport@npm:4.9.0" + dependencies: + logform: "npm:^2.7.0" + readable-stream: "npm:^3.6.2" + triple-beam: "npm:^1.3.0" + checksum: 10c0/e2990a172e754dbf27e7823772214a22dc8312f7ec9cfba831e5ef30a5d5528792e5ea8f083c7387ccfc5b2af20e3691f64738546c8869086110a26f98671095 + languageName: node + linkType: hard + +"winston@npm:^3.17.0": + version: 3.19.0 + resolution: "winston@npm:3.19.0" + dependencies: + "@colors/colors": "npm:^1.6.0" + "@dabh/diagnostics": "npm:^2.0.8" + async: "npm:^3.2.3" + is-stream: "npm:^2.0.0" + logform: "npm:^2.7.0" + one-time: "npm:^1.0.0" + readable-stream: "npm:^3.4.0" + safe-stable-stringify: "npm:^2.3.1" + stack-trace: "npm:0.0.x" + triple-beam: "npm:^1.3.0" + winston-transport: "npm:^4.9.0" + checksum: 10c0/341a8ccfb726120209d34e2466040e2ca72cadb1a3402c4fc90425facad002b81275675b4ab9b4432a624311bc47ef7c9fb7652c86fca454d2be2f2ee1882226 + languageName: node + linkType: hard + "word-wrap@npm:^1.2.5": version: 1.2.5 resolution: "word-wrap@npm:1.2.5" @@ -20673,6 +21924,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^7.0.0": + version: 7.5.13 + resolution: "ws@npm:7.5.13" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/e223726bbda5768ed58ec9252d04eda7018ef380ea122bb94e595a4d7a02b5baeb423933936576fddb8fa51b642375f7ae814bf0f6f5d9a3ce290566578a8c5a + languageName: node + linkType: hard + "ws@npm:^8.12.1, ws@npm:^8.20.1": version: 8.21.0 resolution: "ws@npm:8.21.0" @@ -20797,7 +22063,7 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^21.1.1": +"yargs-parser@npm:^21.0.0, yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 @@ -20811,6 +22077,18 @@ __metadata: languageName: node linkType: hard +"yargs-unparser@npm:^2.0.0": + version: 2.0.0 + resolution: "yargs-unparser@npm:2.0.0" + dependencies: + camelcase: "npm:^6.0.0" + decamelize: "npm:^4.0.0" + flat: "npm:^5.0.2" + is-plain-obj: "npm:^2.1.0" + checksum: 10c0/a5a7d6dc157efa95122e16780c019f40ed91d4af6d2bac066db8194ed0ec5c330abb115daa5a79ff07a9b80b8ea80c925baacf354c4c12edd878c0529927ff03 + languageName: node + linkType: hard + "yargs@npm:^15.1.0": version: 15.4.1 resolution: "yargs@npm:15.4.1" @@ -20845,6 +22123,21 @@ __metadata: languageName: node linkType: hard +"yargs@npm:^17.0.0": + version: 17.7.3 + resolution: "yargs@npm:17.7.3" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10c0/7a28572f7e785a57886e34fdbddb9b28756dec552e1453d5f6e7cdd00ad8721a4e8c4321d33683f5e61cacb36ad43258adbb48396b71ec4ed14abee0fc0d0c1f + languageName: node + linkType: hard + "yargs@npm:^17.5.1, yargs@npm:^17.6.2, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2"