diff --git a/examples/E2E/.bundle/config b/examples/E2E-compat/.bundle/config similarity index 100% rename from examples/E2E/.bundle/config rename to examples/E2E-compat/.bundle/config diff --git a/examples/E2E/.detoxrc.js b/examples/E2E-compat/.detoxrc.js similarity index 100% rename from examples/E2E/.detoxrc.js rename to examples/E2E-compat/.detoxrc.js diff --git a/examples/E2E/.gitignore b/examples/E2E-compat/.gitignore similarity index 100% rename from examples/E2E/.gitignore rename to examples/E2E-compat/.gitignore diff --git a/examples/E2E/.mise.toml b/examples/E2E-compat/.mise.toml similarity index 100% rename from examples/E2E/.mise.toml rename to examples/E2E-compat/.mise.toml diff --git a/examples/E2E/.prettierrc.js b/examples/E2E-compat/.prettierrc.js similarity index 100% rename from examples/E2E/.prettierrc.js rename to examples/E2E-compat/.prettierrc.js diff --git a/examples/E2E/.watchmanconfig b/examples/E2E-compat/.watchmanconfig similarity index 100% rename from examples/E2E/.watchmanconfig rename to examples/E2E-compat/.watchmanconfig diff --git a/examples/E2E/App.tsx b/examples/E2E-compat/App.tsx similarity index 100% rename from examples/E2E/App.tsx rename to examples/E2E-compat/App.tsx diff --git a/examples/E2E/Gemfile b/examples/E2E-compat/Gemfile similarity index 100% rename from examples/E2E/Gemfile rename to examples/E2E-compat/Gemfile diff --git a/examples/E2E/Gemfile.lock b/examples/E2E-compat/Gemfile.lock similarity index 100% rename from examples/E2E/Gemfile.lock rename to examples/E2E-compat/Gemfile.lock diff --git a/examples/E2E/Home.tsx b/examples/E2E-compat/Home.tsx similarity index 100% rename from examples/E2E/Home.tsx rename to examples/E2E-compat/Home.tsx diff --git a/examples/E2E/Modal.tsx b/examples/E2E-compat/Modal.tsx similarity index 100% rename from examples/E2E/Modal.tsx rename to examples/E2E-compat/Modal.tsx diff --git a/examples/E2E/README.md b/examples/E2E-compat/README.md similarity index 100% rename from examples/E2E/README.md rename to examples/E2E-compat/README.md diff --git a/examples/E2E/SecondPage.tsx b/examples/E2E-compat/SecondPage.tsx similarity index 100% rename from examples/E2E/SecondPage.tsx rename to examples/E2E-compat/SecondPage.tsx diff --git a/examples/E2E/android/app/build.gradle b/examples/E2E-compat/android/app/build.gradle similarity index 100% rename from examples/E2E/android/app/build.gradle rename to examples/E2E-compat/android/app/build.gradle diff --git a/examples/E2E/android/app/debug.keystore b/examples/E2E-compat/android/app/debug.keystore similarity index 100% rename from examples/E2E/android/app/debug.keystore rename to examples/E2E-compat/android/app/debug.keystore diff --git a/examples/E2E/android/app/proguard-rules.pro b/examples/E2E-compat/android/app/proguard-rules.pro similarity index 100% rename from examples/E2E/android/app/proguard-rules.pro rename to examples/E2E-compat/android/app/proguard-rules.pro diff --git a/examples/E2E/android/app/src/androidTest/java/com/segmentanalyticsreactnativeexample/DetoxTest.java b/examples/E2E-compat/android/app/src/androidTest/java/com/segmentanalyticsreactnativeexample/DetoxTest.java similarity index 100% rename from examples/E2E/android/app/src/androidTest/java/com/segmentanalyticsreactnativeexample/DetoxTest.java rename to examples/E2E-compat/android/app/src/androidTest/java/com/segmentanalyticsreactnativeexample/DetoxTest.java diff --git a/examples/E2E/android/app/src/debug/AndroidManifest.xml b/examples/E2E-compat/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from examples/E2E/android/app/src/debug/AndroidManifest.xml rename to examples/E2E-compat/android/app/src/debug/AndroidManifest.xml diff --git a/examples/E2E/android/app/src/debug/java/com/analyticsreactnativeexample/ReactNativeFlipper.java b/examples/E2E-compat/android/app/src/debug/java/com/analyticsreactnativeexample/ReactNativeFlipper.java similarity index 100% rename from examples/E2E/android/app/src/debug/java/com/analyticsreactnativeexample/ReactNativeFlipper.java rename to examples/E2E-compat/android/app/src/debug/java/com/analyticsreactnativeexample/ReactNativeFlipper.java diff --git a/examples/E2E/android/app/src/main/AndroidManifest.xml b/examples/E2E-compat/android/app/src/main/AndroidManifest.xml similarity index 100% rename from examples/E2E/android/app/src/main/AndroidManifest.xml rename to examples/E2E-compat/android/app/src/main/AndroidManifest.xml diff --git a/examples/E2E/android/app/src/main/java/com/analyticsreactnativeexample/MainActivity.java b/examples/E2E-compat/android/app/src/main/java/com/analyticsreactnativeexample/MainActivity.java similarity index 100% rename from examples/E2E/android/app/src/main/java/com/analyticsreactnativeexample/MainActivity.java rename to examples/E2E-compat/android/app/src/main/java/com/analyticsreactnativeexample/MainActivity.java diff --git a/examples/E2E/android/app/src/main/java/com/analyticsreactnativeexample/MainApplication.java b/examples/E2E-compat/android/app/src/main/java/com/analyticsreactnativeexample/MainApplication.java similarity index 100% rename from examples/E2E/android/app/src/main/java/com/analyticsreactnativeexample/MainApplication.java rename to examples/E2E-compat/android/app/src/main/java/com/analyticsreactnativeexample/MainApplication.java diff --git a/examples/E2E/android/app/src/main/res/drawable/rn_edit_text_material.xml b/examples/E2E-compat/android/app/src/main/res/drawable/rn_edit_text_material.xml similarity index 100% rename from examples/E2E/android/app/src/main/res/drawable/rn_edit_text_material.xml rename to examples/E2E-compat/android/app/src/main/res/drawable/rn_edit_text_material.xml diff --git a/examples/E2E/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/E2E-compat/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from examples/E2E/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to examples/E2E-compat/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/examples/E2E/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/examples/E2E-compat/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from examples/E2E/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png rename to examples/E2E-compat/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png diff --git a/examples/E2E/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/E2E-compat/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from examples/E2E/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to examples/E2E-compat/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/examples/E2E/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/examples/E2E-compat/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from examples/E2E/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png rename to examples/E2E-compat/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png diff --git a/examples/E2E/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/E2E-compat/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from examples/E2E/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to examples/E2E-compat/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/examples/E2E/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/examples/E2E-compat/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from examples/E2E/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png rename to examples/E2E-compat/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/examples/E2E/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/E2E-compat/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from examples/E2E/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to examples/E2E-compat/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/examples/E2E/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/examples/E2E-compat/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from examples/E2E/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png rename to examples/E2E-compat/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/examples/E2E/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/examples/E2E-compat/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from examples/E2E/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to examples/E2E-compat/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/examples/E2E/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/examples/E2E-compat/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from examples/E2E/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png rename to examples/E2E-compat/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/examples/E2E/android/app/src/main/res/values/strings.xml b/examples/E2E-compat/android/app/src/main/res/values/strings.xml similarity index 100% rename from examples/E2E/android/app/src/main/res/values/strings.xml rename to examples/E2E-compat/android/app/src/main/res/values/strings.xml diff --git a/examples/E2E/android/app/src/main/res/values/styles.xml b/examples/E2E-compat/android/app/src/main/res/values/styles.xml similarity index 100% rename from examples/E2E/android/app/src/main/res/values/styles.xml rename to examples/E2E-compat/android/app/src/main/res/values/styles.xml diff --git a/examples/E2E/android/app/src/main/res/xml/network_security_config.xml b/examples/E2E-compat/android/app/src/main/res/xml/network_security_config.xml similarity index 100% rename from examples/E2E/android/app/src/main/res/xml/network_security_config.xml rename to examples/E2E-compat/android/app/src/main/res/xml/network_security_config.xml diff --git a/examples/E2E/android/app/src/release/java/com/analyticsreactnativeexample/ReactNativeFlipper.java b/examples/E2E-compat/android/app/src/release/java/com/analyticsreactnativeexample/ReactNativeFlipper.java similarity index 100% rename from examples/E2E/android/app/src/release/java/com/analyticsreactnativeexample/ReactNativeFlipper.java rename to examples/E2E-compat/android/app/src/release/java/com/analyticsreactnativeexample/ReactNativeFlipper.java diff --git a/examples/E2E/android/build.gradle b/examples/E2E-compat/android/build.gradle similarity index 100% rename from examples/E2E/android/build.gradle rename to examples/E2E-compat/android/build.gradle diff --git a/examples/E2E/android/gradle.properties b/examples/E2E-compat/android/gradle.properties similarity index 100% rename from examples/E2E/android/gradle.properties rename to examples/E2E-compat/android/gradle.properties diff --git a/examples/E2E/android/gradle/wrapper/gradle-wrapper.jar b/examples/E2E-compat/android/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from examples/E2E/android/gradle/wrapper/gradle-wrapper.jar rename to examples/E2E-compat/android/gradle/wrapper/gradle-wrapper.jar diff --git a/examples/E2E/android/gradle/wrapper/gradle-wrapper.properties b/examples/E2E-compat/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from examples/E2E/android/gradle/wrapper/gradle-wrapper.properties rename to examples/E2E-compat/android/gradle/wrapper/gradle-wrapper.properties diff --git a/examples/E2E/android/gradlew b/examples/E2E-compat/android/gradlew similarity index 100% rename from examples/E2E/android/gradlew rename to examples/E2E-compat/android/gradlew diff --git a/examples/E2E/android/gradlew.bat b/examples/E2E-compat/android/gradlew.bat similarity index 100% rename from examples/E2E/android/gradlew.bat rename to examples/E2E-compat/android/gradlew.bat diff --git a/examples/E2E/android/settings.gradle b/examples/E2E-compat/android/settings.gradle similarity index 100% rename from examples/E2E/android/settings.gradle rename to examples/E2E-compat/android/settings.gradle diff --git a/examples/E2E/app.json b/examples/E2E-compat/app.json similarity index 100% rename from examples/E2E/app.json rename to examples/E2E-compat/app.json diff --git a/examples/E2E/babel.config.js b/examples/E2E-compat/babel.config.js similarity index 100% rename from examples/E2E/babel.config.js rename to examples/E2E-compat/babel.config.js diff --git a/examples/E2E/e2e/jest.config.js b/examples/E2E-compat/e2e/jest.config.js similarity index 100% rename from examples/E2E/e2e/jest.config.js rename to examples/E2E-compat/e2e/jest.config.js diff --git a/examples/E2E-compat/e2e/main.e2e.js b/examples/E2E-compat/e2e/main.e2e.js new file mode 100644 index 000000000..67be1b549 --- /dev/null +++ b/examples/E2E-compat/e2e/main.e2e.js @@ -0,0 +1,7 @@ +import {runAnalyticsTests} from '../../shared-e2e/src'; + +/** + * E2E tests for E2E-compat (React Native 0.72.9 + React 18.3.1) + * Using shared test suite from @segment/analytics-react-native-e2e-tests + */ +runAnalyticsTests('AnalyticsReactNativeE2E'); diff --git a/examples/E2E/e2e/matchers.js b/examples/E2E-compat/e2e/matchers.js similarity index 100% rename from examples/E2E/e2e/matchers.js rename to examples/E2E-compat/e2e/matchers.js diff --git a/examples/E2E/e2e/mockServer.js b/examples/E2E-compat/e2e/mockServer.js similarity index 100% rename from examples/E2E/e2e/mockServer.js rename to examples/E2E-compat/e2e/mockServer.js diff --git a/examples/E2E/index.js b/examples/E2E-compat/index.js similarity index 100% rename from examples/E2E/index.js rename to examples/E2E-compat/index.js diff --git a/examples/E2E/ios/.xcode.env b/examples/E2E-compat/ios/.xcode.env similarity index 100% rename from examples/E2E/ios/.xcode.env rename to examples/E2E-compat/ios/.xcode.env diff --git a/examples/E2E/ios/AnalyticsReactNativeE2E.xcodeproj/project.pbxproj b/examples/E2E-compat/ios/AnalyticsReactNativeE2E.xcodeproj/project.pbxproj similarity index 100% rename from examples/E2E/ios/AnalyticsReactNativeE2E.xcodeproj/project.pbxproj rename to examples/E2E-compat/ios/AnalyticsReactNativeE2E.xcodeproj/project.pbxproj diff --git a/examples/E2E/ios/AnalyticsReactNativeE2E.xcworkspace/contents.xcworkspacedata b/examples/E2E-compat/ios/AnalyticsReactNativeE2E.xcworkspace/contents.xcworkspacedata similarity index 100% rename from examples/E2E/ios/AnalyticsReactNativeE2E.xcworkspace/contents.xcworkspacedata rename to examples/E2E-compat/ios/AnalyticsReactNativeE2E.xcworkspace/contents.xcworkspacedata diff --git a/examples/E2E/ios/AnalyticsReactNativeE2E.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/E2E-compat/ios/AnalyticsReactNativeE2E.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from examples/E2E/ios/AnalyticsReactNativeE2E.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to examples/E2E-compat/ios/AnalyticsReactNativeE2E.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/examples/E2E/ios/AnalyticsReactNativeE2E/AppDelegate.h b/examples/E2E-compat/ios/AnalyticsReactNativeE2E/AppDelegate.h similarity index 100% rename from examples/E2E/ios/AnalyticsReactNativeE2E/AppDelegate.h rename to examples/E2E-compat/ios/AnalyticsReactNativeE2E/AppDelegate.h diff --git a/examples/E2E/ios/AnalyticsReactNativeE2E/AppDelegate.mm b/examples/E2E-compat/ios/AnalyticsReactNativeE2E/AppDelegate.mm similarity index 100% rename from examples/E2E/ios/AnalyticsReactNativeE2E/AppDelegate.mm rename to examples/E2E-compat/ios/AnalyticsReactNativeE2E/AppDelegate.mm diff --git a/examples/E2E/ios/AnalyticsReactNativeE2E/Images.xcassets/AppIcon.appiconset/Contents.json b/examples/E2E-compat/ios/AnalyticsReactNativeE2E/Images.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from examples/E2E/ios/AnalyticsReactNativeE2E/Images.xcassets/AppIcon.appiconset/Contents.json rename to examples/E2E-compat/ios/AnalyticsReactNativeE2E/Images.xcassets/AppIcon.appiconset/Contents.json diff --git a/examples/E2E/ios/AnalyticsReactNativeE2E/Images.xcassets/Contents.json b/examples/E2E-compat/ios/AnalyticsReactNativeE2E/Images.xcassets/Contents.json similarity index 100% rename from examples/E2E/ios/AnalyticsReactNativeE2E/Images.xcassets/Contents.json rename to examples/E2E-compat/ios/AnalyticsReactNativeE2E/Images.xcassets/Contents.json diff --git a/examples/E2E/ios/AnalyticsReactNativeE2E/Info.plist b/examples/E2E-compat/ios/AnalyticsReactNativeE2E/Info.plist similarity index 100% rename from examples/E2E/ios/AnalyticsReactNativeE2E/Info.plist rename to examples/E2E-compat/ios/AnalyticsReactNativeE2E/Info.plist diff --git a/examples/E2E/ios/AnalyticsReactNativeE2E/LaunchScreen.storyboard b/examples/E2E-compat/ios/AnalyticsReactNativeE2E/LaunchScreen.storyboard similarity index 100% rename from examples/E2E/ios/AnalyticsReactNativeE2E/LaunchScreen.storyboard rename to examples/E2E-compat/ios/AnalyticsReactNativeE2E/LaunchScreen.storyboard diff --git a/examples/E2E/ios/AnalyticsReactNativeE2E/main.m b/examples/E2E-compat/ios/AnalyticsReactNativeE2E/main.m similarity index 100% rename from examples/E2E/ios/AnalyticsReactNativeE2E/main.m rename to examples/E2E-compat/ios/AnalyticsReactNativeE2E/main.m diff --git a/examples/E2E/ios/AnalyticsReactNativeE2ETests/AnalyticsReactNativeExampleTests.m b/examples/E2E-compat/ios/AnalyticsReactNativeE2ETests/AnalyticsReactNativeExampleTests.m similarity index 100% rename from examples/E2E/ios/AnalyticsReactNativeE2ETests/AnalyticsReactNativeExampleTests.m rename to examples/E2E-compat/ios/AnalyticsReactNativeE2ETests/AnalyticsReactNativeExampleTests.m diff --git a/examples/E2E/ios/AnalyticsReactNativeE2ETests/Info.plist b/examples/E2E-compat/ios/AnalyticsReactNativeE2ETests/Info.plist similarity index 100% rename from examples/E2E/ios/AnalyticsReactNativeE2ETests/Info.plist rename to examples/E2E-compat/ios/AnalyticsReactNativeE2ETests/Info.plist diff --git a/examples/E2E/ios/Podfile b/examples/E2E-compat/ios/Podfile similarity index 100% rename from examples/E2E/ios/Podfile rename to examples/E2E-compat/ios/Podfile diff --git a/examples/E2E/ios/Podfile.lock b/examples/E2E-compat/ios/Podfile.lock similarity index 100% rename from examples/E2E/ios/Podfile.lock rename to examples/E2E-compat/ios/Podfile.lock diff --git a/examples/E2E/jest.config.js b/examples/E2E-compat/jest.config.js similarity index 100% rename from examples/E2E/jest.config.js rename to examples/E2E-compat/jest.config.js diff --git a/examples/E2E/metro.config.js b/examples/E2E-compat/metro.config.js similarity index 100% rename from examples/E2E/metro.config.js rename to examples/E2E-compat/metro.config.js diff --git a/examples/E2E/package.json b/examples/E2E-compat/package.json similarity index 91% rename from examples/E2E/package.json rename to examples/E2E-compat/package.json index 77c4ba81f..69789951a 100644 --- a/examples/E2E/package.json +++ b/examples/E2E-compat/package.json @@ -1,5 +1,5 @@ { - "name": "AnalyticsReactNativeE2E", + "name": "AnalyticsReactNativeE2ECompat", "version": "0.0.1", "private": true, "scripts": { @@ -24,6 +24,8 @@ "@react-native-community/masked-view": "^0.1.11", "@react-navigation/native": "^6.1.9", "@react-navigation/stack": "^6.3.20", + "@segment/analytics-react-native": "^2.20.0", + "@segment/sovran-react-native": "^1.1.0", "react": "18.2.0", "react-native": "0.72.9", "react-native-gesture-handler": "^2.13.4", @@ -37,6 +39,7 @@ "@babel/runtime": "^7.23.2", "@react-native/eslint-config": "^0.72.2", "@react-native/metro-config": "^0.72.11", + "@segment/analytics-react-native-e2e-tests": "file:../shared-e2e", "@tsconfig/react-native": "^3.0.0", "@types/jest": "^29.5.8", "@types/react": "^18.2.37", @@ -44,10 +47,8 @@ "@types/react-test-renderer": "^18.0.0", "babel-jest": "^29.2.1", "babel-plugin-module-resolver": "^5.0.0", - "body-parser": "^1.20.0", "detox": "^20.17.0", "eslint": "^8.19.0", - "express": "^4.20.0", "jest": "^29.7.0", "jest-circus": "^29.3.1", "metro-react-native-babel-preset": "0.76.8", diff --git a/examples/E2E/plugins/Logger.ts b/examples/E2E-compat/plugins/Logger.ts similarity index 100% rename from examples/E2E/plugins/Logger.ts rename to examples/E2E-compat/plugins/Logger.ts diff --git a/examples/E2E/react-native.config.js b/examples/E2E-compat/react-native.config.js similarity index 100% rename from examples/E2E/react-native.config.js rename to examples/E2E-compat/react-native.config.js diff --git a/examples/E2E/workspace.js b/examples/E2E-compat/workspace.js similarity index 100% rename from examples/E2E/workspace.js rename to examples/E2E-compat/workspace.js diff --git a/examples/E2E/yarn.lock b/examples/E2E-compat/yarn.lock similarity index 100% rename from examples/E2E/yarn.lock rename to examples/E2E-compat/yarn.lock diff --git a/examples/E2E/e2e/main.e2e.js b/examples/E2E/e2e/main.e2e.js deleted file mode 100644 index 2f99849c2..000000000 --- a/examples/E2E/e2e/main.e2e.js +++ /dev/null @@ -1,192 +0,0 @@ -const {element, by, device} = require('detox'); - -import {startServer, stopServer} from './mockServer'; -import {setupMatchers} from './matchers'; - -describe('#mainTest', () => { - const mockServerListener = jest.fn(); - - const trackButton = element(by.id('BUTTON_TRACK')); - const screenButton = element(by.id('BUTTON_SCREEN')); - const identifyButton = element(by.id('BUTTON_IDENTIFY')); - const groupButton = element(by.id('BUTTON_GROUP')); - const aliasButton = element(by.id('BUTTON_ALIAS')); - const resetButton = element(by.id('BUTTON_RESET')); - const flushButton = element(by.id('BUTTON_FLUSH')); - - beforeAll(async () => { - await startServer(mockServerListener); - await device.launchApp(); - setupMatchers(); - }); - - const clearLifecycleEvents = async () => { - await flushButton.tap(); - - mockServerListener.mockClear(); - expect(mockServerListener).not.toHaveBeenCalled(); - }; - - beforeEach(async () => { - mockServerListener.mockReset(); - device.reloadReactNative(); - }); - - afterAll(async () => { - await stopServer(); - }); - - it('checks that lifecycle methods are triggered', async () => { - await flushButton.tap(); - - const events = mockServerListener.mock.calls[0][0].batch; - - expect(events).toHaveEventWith({ - type: 'track', - event: 'Application Opened', - }); - expect(events).toHaveEventWith({ - type: 'track', - event: 'Application Installed', - }); - }); - - it('checks that track & screen methods are logged', async () => { - await clearLifecycleEvents(); - - await trackButton.tap(); - await screenButton.tap(); - await flushButton.tap(); - - expect(mockServerListener).toHaveBeenCalledTimes(1); - - const events = mockServerListener.mock.calls[0][0].batch; - - expect(events).toHaveLength(2); - expect(events).toHaveEventWith({type: 'track', event: 'Track pressed'}); - expect(events).toHaveEventWith({type: 'screen', name: 'Home Screen'}); - }); - - it('checks the identify method', async () => { - await clearLifecycleEvents(); - - await identifyButton.tap(); - await flushButton.tap(); - - expect(mockServerListener).toHaveBeenCalledTimes(1); - - const events = mockServerListener.mock.calls[0][0].batch; - - expect(events).toHaveLength(1); - expect(events).toHaveEventWith({ - type: 'identify', - userId: 'user_2', - }); - }); - - it('checks the group method', async () => { - await clearLifecycleEvents(); - - await groupButton.tap(); - await flushButton.tap(); - - expect(mockServerListener).toHaveBeenCalledTimes(1); - - const events = mockServerListener.mock.calls[0][0].batch; - - expect(events).toHaveLength(1); - expect(events).toHaveEventWith({type: 'group', groupId: 'best-group'}); - }); - - it('checks the alias method', async () => { - await clearLifecycleEvents(); - - await aliasButton.tap(); - await flushButton.tap(); - - expect(mockServerListener).toHaveBeenCalledTimes(1); - - const events = mockServerListener.mock.calls[0][0].batch; - - expect(events).toHaveLength(1); - expect(events).toHaveEventWith({type: 'alias', userId: 'new-id'}); - }); - - it('reset the client and checks the user id', async () => { - await clearLifecycleEvents(); - - await identifyButton.tap(); - await trackButton.tap(); - await resetButton.tap(); - await screenButton.tap(); - await flushButton.tap(); - - expect(mockServerListener).toHaveBeenCalledTimes(1); - - const events = mockServerListener.mock.calls[0][0].batch; - - const screenEvent = events.find(item => item.type === 'screen'); - - expect(events).toHaveLength(3); - expect(events).toHaveEventWith({type: 'identify', userId: 'user_2'}); - expect(events).toHaveEventWith({type: 'track', userId: 'user_2'}); - expect(screenEvent.userId).toBeUndefined(); - }); - - it('checks that the context is set properly', async () => { - await clearLifecycleEvents(); - - await trackButton.tap(); - await flushButton.tap(); - - expect(mockServerListener).toHaveBeenCalledTimes(1); - - const request = mockServerListener.mock.calls[0][0]; - const context = request.batch[0].context; - - expect(request.batch).toHaveLength(1); - expect(context.app.name).toBe('AnalyticsReactNativeE2E'); - expect(context.app.version).toBe('1.0'); - expect(context.library.name).toBe('analytics-react-native'); - expect(context.locale).toBe('en-US'); - // This test only works in iOS for now - if (device.getPlatform() === 'ios') { - expect(context.network.wifi).toBe(true); - } - }); - - it('checks that persistence is working', async () => { - await clearLifecycleEvents(); - - await trackButton.tap(); - await identifyButton.tap(); - - await device.sendToHome(); - await device.launchApp({newInstance: true}); - - await flushButton.tap(); - - expect(mockServerListener).toHaveBeenCalledTimes(1); - - const events = mockServerListener.mock.calls[0][0].batch; - - const platform = device.getPlatform(); - - expect(events).toHaveLength(4); // Track + Identify + App Launch + Backgrounded on Android - expect(events).toHaveEventWith({type: 'identify', userId: 'user_2'}); - expect(events).toHaveEventWith({type: 'track', userId: 'user_2'}); - expect(events).toHaveEventWith({ - type: 'track', - event: 'Application Opened', - }); - // Android only - // RN in Android immediately halts JS execution when leaving the app and sends the BG event (in iOS it happens after a short while when the OS decides to) - // Hence in Android the event list will contain this extra event - if (platform === 'android') { - expect(events).toHaveEventWith({ - type: 'track', - event: 'Application Backgrounded', - }); - } - }); -}); diff --git a/examples/SHARED_E2E_SETUP.md b/examples/SHARED_E2E_SETUP.md new file mode 100644 index 000000000..689af3260 --- /dev/null +++ b/examples/SHARED_E2E_SETUP.md @@ -0,0 +1,231 @@ +# Shared E2E Test Setup + +This document explains the shared E2E test infrastructure for analytics-react-native. + +## Overview + +We use a shared test library (`examples/shared-e2e`) that both `E2E-compat` and `E2E-latest` apps consume. This eliminates test duplication and ensures both React Native versions are tested with identical logic. + +## Architecture + +``` +examples/ +├── shared-e2e/ # Shared test library +│ ├── src/ +│ │ ├── analyticsTests.js # Main test suite +│ │ ├── matchers.js # Custom Jest matchers +│ │ ├── mockServer.js # Mock Segment API +│ │ └── index.js # Exports +│ ├── package.json +│ └── README.md +│ +├── E2E-compat/ # RN 0.72.9 + React 18.3.1 +│ ├── e2e/ +│ │ ├── main.e2e.js # Imports from shared-e2e +│ │ └── jest.config.js +│ ├── .detoxrc.js +│ └── package.json # References shared-e2e +│ +└── E2E-latest/ # RN 0.84.1 + React 19.2.3 + ├── e2e/ + │ ├── main.e2e.js # Imports from shared-e2e + │ └── jest.config.js + ├── .detoxrc.js + └── package.json # References shared-e2e +``` + +## Usage + +### Running Tests + +**E2E-compat (RN 0.72.9):** +```bash +cd examples/E2E-compat + +# iOS +yarn build:ios +yarn test:ios + +# Android +yarn build:android +yarn test:android +``` + +**E2E-latest (RN 0.84.1):** +```bash +cd examples/E2E-latest + +# iOS +yarn build:ios +yarn test:ios + +# Android +yarn build:android +yarn test:android +``` + +### How It Works + +Both apps import the shared test suite: + +```javascript +// examples/E2E-compat/e2e/main.e2e.js +import {runAnalyticsTests} from '../../shared-e2e/src'; + +runAnalyticsTests('AnalyticsReactNativeE2E'); +``` + +```javascript +// examples/E2E-latest/e2e/main.e2e.js +import {runAnalyticsTests} from '../../shared-e2e/src'; + +runAnalyticsTests('AnalyticsReactNativeE2ELatest'); +``` + +The `runAnalyticsTests()` function: +- Starts a mock Segment API server +- Runs all E2E test scenarios +- Validates analytics events +- Works across different RN versions + +## Test Coverage + +The shared test suite covers: + +✅ **Lifecycle Events** +- Application Opened +- Application Installed + +✅ **Core Methods** +- `track()` - Track custom events +- `screen()` - Screen views +- `identify()` - User identification +- `group()` - Group association +- `alias()` - User aliasing +- `reset()` - Reset user state + +✅ **Advanced Features** +- Context data validation +- Data persistence across app restarts +- Network flush behavior +- Background/foreground transitions + +## Adding New Tests + +To add new tests that run on both RN versions: + +1. **Edit shared test file:** + ```bash + vi examples/shared-e2e/src/analyticsTests.js + ``` + +2. **Add your test:** + ```javascript + it('checks my new feature', async () => { + // Test implementation + }); + ``` + +3. **Run on both apps:** + ```bash + # Test on RN 0.72.9 + cd examples/E2E-compat && yarn test:ios + + # Test on RN 0.84.1 + cd examples/E2E-latest && yarn test:ios + ``` + +## Custom Matchers + +The shared library provides custom Jest matchers: + +### `toHaveEvent(eventType)` +```javascript +expect(events).toHaveEvent('track'); +``` + +### `toHaveEventWith(attributes)` +```javascript +expect(events).toHaveEventWith({ + type: 'track', + event: 'Button Clicked', + userId: 'user_123' +}); +``` + +## Mock Server + +The test suite includes a mock Segment API server: + +- **Port:** 9091 +- **Batch endpoint:** `POST /v1/b` +- **Settings endpoint:** `GET /v1/projects/yup/settings` + +Configure apps to use the mock server: +```javascript +const client = createClient({ + writeKey: 'test-write-key', + trackAppLifecycleEvents: true, + proxy: 'http://localhost:9091', +}); +``` + +## Troubleshooting + +### Tests not finding shared-e2e + +Run yarn install in the example app: +```bash +cd examples/E2E-compat # or E2E-latest +yarn install +``` + +### Detox build fails + +Clean and rebuild: +```bash +cd examples/E2E-compat +yarn clean +yarn install +yarn build:ios +``` + +### Mock server port conflict + +The mock server uses port 9091. If blocked: +```bash +lsof -ti:9091 | xargs kill -9 +``` + +## Benefits + +✅ **No code duplication** - Write tests once, run on all RN versions +✅ **Consistent testing** - Same test logic across versions +✅ **Easy maintenance** - Update tests in one place +✅ **Version validation** - Proves SDK works on multiple RN versions +✅ **Clear separation** - Test logic separate from app code + +## CI/CD Integration + +GitHub Actions workflow: +```yaml +test-e2e: + strategy: + matrix: + example: ['E2E-compat', 'E2E-latest'] + steps: + - name: Run E2E tests + run: | + cd examples/${{ matrix.example }} + yarn install + yarn build:ios + yarn test:ios +``` + +## Next Steps + +1. ✅ Shared test library created +2. ✅ E2E-compat configured +3. ✅ E2E-latest configured +4. ⏳ Verify both apps run tests successfully +5. ⏳ Add to CI/CD pipeline diff --git a/examples/shared-e2e/README.md b/examples/shared-e2e/README.md new file mode 100644 index 000000000..facebc3b0 --- /dev/null +++ b/examples/shared-e2e/README.md @@ -0,0 +1,103 @@ +# Shared E2E Tests + +This package contains shared E2E tests for analytics-react-native example apps. It allows both `E2E-compat` and `E2E-latest` to run the same test suite without code duplication. + +## Structure + +``` +shared-e2e/ +├── src/ +│ ├── analyticsTests.js # Main test suite +│ ├── matchers.js # Custom Jest matchers +│ ├── mockServer.js # Mock Segment API server +│ └── index.js # Exports +├── package.json +└── README.md +``` + +## Usage in Example Apps + +### E2E-compat + +```javascript +// examples/E2E-compat/e2e/main.e2e.js +import {runAnalyticsTests} from '../../shared-e2e/src'; + +runAnalyticsTests('AnalyticsReactNativeE2ECompat'); +``` + +### E2E-latest + +```javascript +// examples/E2E-latest/e2e/main.e2e.js +import {runAnalyticsTests} from '../../shared-e2e/src'; + +runAnalyticsTests('AnalyticsReactNativeE2ELatest'); +``` + +## Test Coverage + +The shared test suite covers: + +- ✅ SDK initialization and lifecycle events +- ✅ Track events +- ✅ Screen events +- ✅ Identify calls +- ✅ Group calls +- ✅ Alias calls +- ✅ Reset functionality +- ✅ Context data validation +- ✅ Data persistence across app restarts +- ✅ Network flush behavior + +## Mock Server + +The test suite includes a mock Segment API server (`mockServer.js`) that: + +- Listens on port 9091 +- Handles batch event uploads (`POST /v1/b`) +- Handles settings requests (`GET /v1/projects/yup/settings`) +- Captures all requests for test assertions + +## Custom Matchers + +Provides custom Jest matchers for cleaner test assertions: + +### `toHaveEvent(eventType)` +Checks if events array contains an event of a specific type. + +```javascript +expect(events).toHaveEvent('track'); +``` + +### `toHaveEventWith(attributes)` +Checks if events array contains an event with specific attributes. + +```javascript +expect(events).toHaveEventWith({ + type: 'track', + event: 'Button Clicked' +}); +``` + +## Requirements + +- Detox 20.17.0+ +- Express 4.20.0+ +- Body-parser 1.20.0+ + +## Development + +When adding new tests: + +1. Add test to `analyticsTests.js` +2. Export any new utilities from `index.js` +3. Update this README with test coverage + +## Cross-Version Compatibility + +These tests are designed to work across different React Native versions: + +- React Native 0.72.x (E2E-compat) +- React Native 0.84.x (E2E-latest) +- React 18.x and React 19.x diff --git a/examples/shared-e2e/package.json b/examples/shared-e2e/package.json new file mode 100644 index 000000000..f61fc7d23 --- /dev/null +++ b/examples/shared-e2e/package.json @@ -0,0 +1,17 @@ +{ + "name": "@segment/analytics-react-native-e2e-tests", + "version": "1.0.0", + "description": "Shared E2E tests for analytics-react-native example apps", + "private": true, + "main": "src/index.js", + "scripts": { + "test": "echo \"Run tests from example apps\"" + }, + "dependencies": { + "body-parser": "^1.20.0", + "express": "^4.20.0" + }, + "devDependencies": { + "detox": "^20.17.0" + } +} diff --git a/examples/shared-e2e/src/analyticsTests.js b/examples/shared-e2e/src/analyticsTests.js new file mode 100644 index 000000000..9a415e900 --- /dev/null +++ b/examples/shared-e2e/src/analyticsTests.js @@ -0,0 +1,198 @@ +const {element, by, device} = require('detox'); + +import {startServer, stopServer} from './mockServer'; +import {setupMatchers} from './matchers'; + +/** + * Shared E2E tests for Segment Analytics React Native + * These tests work across different React Native versions + */ +export const runAnalyticsTests = (appName = 'AnalyticsReactNativeE2E') => { + describe('Segment Analytics E2E Tests', () => { + const mockServerListener = jest.fn(); + + // UI element references + const trackButton = element(by.id('BUTTON_TRACK')); + const screenButton = element(by.id('BUTTON_SCREEN')); + const identifyButton = element(by.id('BUTTON_IDENTIFY')); + const groupButton = element(by.id('BUTTON_GROUP')); + const aliasButton = element(by.id('BUTTON_ALIAS')); + const resetButton = element(by.id('BUTTON_RESET')); + const flushButton = element(by.id('BUTTON_FLUSH')); + + beforeAll(async () => { + await startServer(mockServerListener); + await device.launchApp(); + setupMatchers(); + }); + + const clearLifecycleEvents = async () => { + await flushButton.tap(); + mockServerListener.mockClear(); + expect(mockServerListener).not.toHaveBeenCalled(); + }; + + beforeEach(async () => { + mockServerListener.mockReset(); + device.reloadReactNative(); + }); + + afterAll(async () => { + await stopServer(); + }); + + it('checks that lifecycle methods are triggered', async () => { + await flushButton.tap(); + + const events = mockServerListener.mock.calls[0][0].batch; + + expect(events).toHaveEventWith({ + type: 'track', + event: 'Application Opened', + }); + expect(events).toHaveEventWith({ + type: 'track', + event: 'Application Installed', + }); + }); + + it('checks that track & screen methods are logged', async () => { + await clearLifecycleEvents(); + + await trackButton.tap(); + await screenButton.tap(); + await flushButton.tap(); + + expect(mockServerListener).toHaveBeenCalledTimes(1); + + const events = mockServerListener.mock.calls[0][0].batch; + + expect(events).toHaveLength(2); + expect(events).toHaveEventWith({type: 'track', event: 'Track pressed'}); + expect(events).toHaveEventWith({type: 'screen', name: 'Home Screen'}); + }); + + it('checks the identify method', async () => { + await clearLifecycleEvents(); + + await identifyButton.tap(); + await flushButton.tap(); + + expect(mockServerListener).toHaveBeenCalledTimes(1); + + const events = mockServerListener.mock.calls[0][0].batch; + + expect(events).toHaveLength(1); + expect(events).toHaveEventWith({ + type: 'identify', + userId: 'user_2', + }); + }); + + it('checks the group method', async () => { + await clearLifecycleEvents(); + + await groupButton.tap(); + await flushButton.tap(); + + expect(mockServerListener).toHaveBeenCalledTimes(1); + + const events = mockServerListener.mock.calls[0][0].batch; + + expect(events).toHaveLength(1); + expect(events).toHaveEventWith({type: 'group', groupId: 'best-group'}); + }); + + it('checks the alias method', async () => { + await clearLifecycleEvents(); + + await aliasButton.tap(); + await flushButton.tap(); + + expect(mockServerListener).toHaveBeenCalledTimes(1); + + const events = mockServerListener.mock.calls[0][0].batch; + + expect(events).toHaveLength(1); + expect(events).toHaveEventWith({type: 'alias', userId: 'new-id'}); + }); + + it('reset the client and checks the user id', async () => { + await clearLifecycleEvents(); + + await identifyButton.tap(); + await trackButton.tap(); + await resetButton.tap(); + await screenButton.tap(); + await flushButton.tap(); + + expect(mockServerListener).toHaveBeenCalledTimes(1); + + const events = mockServerListener.mock.calls[0][0].batch; + + const screenEvent = events.find(item => item.type === 'screen'); + + expect(events).toHaveLength(3); + expect(events).toHaveEventWith({type: 'identify', userId: 'user_2'}); + expect(events).toHaveEventWith({type: 'track', userId: 'user_2'}); + expect(screenEvent.userId).toBeUndefined(); + }); + + it('checks that the context is set properly', async () => { + await clearLifecycleEvents(); + + await trackButton.tap(); + await flushButton.tap(); + + expect(mockServerListener).toHaveBeenCalledTimes(1); + + const request = mockServerListener.mock.calls[0][0]; + const context = request.batch[0].context; + + expect(request.batch).toHaveLength(1); + expect(context.app.name).toBe(appName); + expect(context.app.version).toBe('1.0'); + expect(context.library.name).toBe('analytics-react-native'); + expect(context.locale).toBe('en-US'); + // This test only works in iOS for now + if (device.getPlatform() === 'ios') { + expect(context.network.wifi).toBe(true); + } + }); + + it('checks that persistence is working', async () => { + await clearLifecycleEvents(); + + await trackButton.tap(); + await identifyButton.tap(); + + await device.sendToHome(); + await device.launchApp({newInstance: true}); + + await flushButton.tap(); + + expect(mockServerListener).toHaveBeenCalledTimes(1); + + const events = mockServerListener.mock.calls[0][0].batch; + + const platform = device.getPlatform(); + + expect(events).toHaveLength(4); // Track + Identify + App Launch + Backgrounded on Android + expect(events).toHaveEventWith({type: 'identify', userId: 'user_2'}); + expect(events).toHaveEventWith({type: 'track', userId: 'user_2'}); + expect(events).toHaveEventWith({ + type: 'track', + event: 'Application Opened', + }); + // Android only + // RN in Android immediately halts JS execution when leaving the app and sends the BG event (in iOS it happens after a short while when the OS decides to) + // Hence in Android the event list will contain this extra event + if (platform === 'android') { + expect(events).toHaveEventWith({ + type: 'track', + event: 'Application Backgrounded', + }); + } + }); + }); +}; diff --git a/examples/shared-e2e/src/index.js b/examples/shared-e2e/src/index.js new file mode 100644 index 000000000..58f84905a --- /dev/null +++ b/examples/shared-e2e/src/index.js @@ -0,0 +1,8 @@ +/** + * Shared E2E test utilities for analytics-react-native + * @module @segment/analytics-react-native-e2e-tests + */ + +export {runAnalyticsTests} from './analyticsTests'; +export {setupMatchers} from './matchers'; +export {startServer, stopServer} from './mockServer'; diff --git a/examples/shared-e2e/src/matchers.js b/examples/shared-e2e/src/matchers.js new file mode 100644 index 000000000..a202ccbe9 --- /dev/null +++ b/examples/shared-e2e/src/matchers.js @@ -0,0 +1,45 @@ +/* globals expect */ + +/** + * Custom Jest matchers for Segment analytics E2E tests + */ +export const setupMatchers = () => { + expect.extend({ + /** + * Checks if events array contains an event of a specific type + * @example expect(events).toHaveEvent('track') + */ + toHaveEvent(events, eventType) { + return { + message: () => `Expect events to contain a ${eventType} event`, + pass: events.some(item => item.type === eventType), + }; + }, + + /** + * Checks if events array contains an event with specific attributes + * @example expect(events).toHaveEventWith({type: 'track', event: 'Button Clicked'}) + */ + toHaveEventWith(events, eventAtts) { + const hasEvent = events.some(item => { + let isValid = true; + for (const [key, value] of Object.entries(eventAtts)) { + if (!(key in item)) { + isValid = false; + } else if (key in item && item[key] !== value) { + isValid = false; + } + } + return isValid; + }); + + return { + message: () => + `Expect events to contain an object with attributes: ${JSON.stringify( + eventAtts, + )}`, + pass: hasEvent, + }; + }, + }); +}; diff --git a/examples/shared-e2e/src/mockServer.js b/examples/shared-e2e/src/mockServer.js new file mode 100644 index 000000000..20b0603ef --- /dev/null +++ b/examples/shared-e2e/src/mockServer.js @@ -0,0 +1,65 @@ +const express = require('express'); +const bodyParser = require('body-parser'); + +const port = 9091; + +let server; + +/** + * Starts a mock Segment API server for E2E testing + * @param {Function} mockServerListener - Jest mock function to capture requests + * @returns {Promise} + */ +export const startServer = async mockServerListener => { + if (server) { + throw new Error('Server is already running'); + } + + return new Promise(resolve => { + const app = express(); + + app.use(bodyParser.json()); + + // Handles batch events + app.post('/v1/b', (req, res) => { + console.log(`➡️ Received request`); + const body = req.body; + mockServerListener(body); + + res.status(200).send({mockSuccess: true}); + }); + + // Handles settings calls + app.get('/v1/projects/yup/settings', (req, res) => { + console.log(`➡️ Replying with Settings`); + res.status(200).send({ + integrations: { + 'Segment.io': {}, + }, + }); + }); + + server = app.listen(port, () => { + console.log(`🚀 Started mock server on port ${port}`); + resolve(); + }); + }); +}; + +/** + * Stops the mock Segment API server + * @returns {Promise} + */ +export const stopServer = async () => { + return new Promise((resolve, reject) => { + if (server) { + server.close(() => { + console.log('✋ Mock server has stopped'); + server = undefined; + resolve(); + }); + } else { + reject('⚠️ Mock server is not running'); + } + }); +}; diff --git a/metro.config.js b/metro.config.js new file mode 100644 index 000000000..8cf91d30b --- /dev/null +++ b/metro.config.js @@ -0,0 +1,15 @@ +/** + * Metro configuration for React Native workspace + * This config is a fallback when Metro resolves to the workspace root. + * It won't work properly, but provides a clearer error message. + */ + +const path = require('path'); + +module.exports = { + projectRoot: __dirname, + watchFolders: [__dirname], + resolver: { + sourceExts: ['js', 'json', 'ts', 'tsx'], + }, +}; diff --git a/package.json b/package.json index a512acf99..31bf45bb0 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "packages/shared", "packages/sovran", "packages/core", - "packages/plugins/*" + "packages/plugins/*", + "examples/shared-e2e" ], "scripts": { "core": "yarn workspace @segment/analytics-react-native",