diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 00000000..3186f3f0 --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/example/.yarn/install-state.gz b/example/.yarn/install-state.gz new file mode 100644 index 00000000..5a1797ee Binary files /dev/null and b/example/.yarn/install-state.gz differ diff --git a/example/detox.config.js b/example/detox.config.js new file mode 100644 index 00000000..a4b17f3e --- /dev/null +++ b/example/detox.config.js @@ -0,0 +1,69 @@ +module.exports = { + testRunner: { + args: { + $0: 'jest', + config: 'detox/jest.config.js', + }, + jest: { + setupTimeout: 180000, + }, + }, + apps: { + 'ios.debug': { + type: 'ios.app', + binaryPath: + 'ios/build/Build/Products/Debug-iphonesimulator/IntercomReactNativeExample.app', + build: + 'xcodebuild -workspace ios/IntercomReactNativeExample.xcworkspace -scheme IntercomReactNativeExample -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build', + permissions: { + notifications: 'YES', + }, + }, + 'android.debug': { + type: 'android.apk', + binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk', + build: + 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug', + reversePorts: [8081], + launchTimeout: 180000, + permissions: { + notifications: 'YES', + camera: 'YES', + microphone: 'YES', + location: 'YES', + }, + }, + }, + devices: { + simulator: { + type: 'ios.simulator', + device: { + type: 'iPhone 15', + }, + }, + emulator: { + type: 'android.emulator', + device: { + avdName: 'Pixel_3a_API_33_arm64-v8a', + bootArgs: '-no-snapshot-load', + coldBoot: true, + }, + }, + }, + configurations: { + 'ios.sim.debug': { + device: 'simulator', + app: 'ios.debug', + }, + 'android.emu.debug': { + device: 'emulator', + app: 'android.debug', + behavior: { + init: { + launchApp: true, + reinstall: true, + }, + }, + }, + }, +}; diff --git a/example/detox/e2e/messenger.e2e.ts b/example/detox/e2e/messenger.e2e.ts new file mode 100644 index 00000000..f1023f9c --- /dev/null +++ b/example/detox/e2e/messenger.e2e.ts @@ -0,0 +1,165 @@ +import { by, device, element, expect, waitFor } from 'detox'; + +describe('Intercom messenger', () => { + beforeAll(async () => { + await device.launchApp({ permissions: { notifications: 'YES' } }); + }); + + beforeEach(async () => { + await device.reloadReactNative(); + }); + + // Helper function to dismiss Intercom overlay + const dismissIntercomOverlay = async () => { + // Wait a bit for the overlay to be fully rendered + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Try multiple approaches to dismiss the overlay + const tryDismiss = async () => { + try { + // Try swiping on different view types + const viewTypes = ['UIView', 'RCTView', 'UIWindow', 'RCTModalHostView']; + for (const type of viewTypes) { + try { + await element(by.type(type)).atIndex(0).swipe('down', 'fast', 0.9); + // If we get here, the swipe was successful + return true; + } catch (e) { + // Continue to next view type + } + } + return false; + } catch (e) { + return false; + } + }; + + // Try up to 3 times with increasing swipe distance + for (let i = 0; i < 3; i++) { + if (await tryDismiss()) { + break; + } + // Wait a bit between attempts + await new Promise((resolve) => setTimeout(resolve, 500)); + } + }; + + // --- Authentication Flow --- + it('should show unauthenticated state initially', async () => { + await expect(element(by.text('Logged In: No'))).toBeVisible(); + }); + + it('should login as unidentified user', async () => { + await element(by.text('Login as an Unidentified User')).tap(); + await waitFor(element(by.text('Logged In: Yes'))) + .toBeVisible() + .withTimeout(5000); + }); + // --- Messenger and Intercom flows --- + + it('should open messenger after logging in', async () => { + // Login as unidentified user + await element(by.text('Login as an Unidentified User')).tap(); + + // Wait for login to complete + await waitFor(element(by.text('Logged In: Yes'))) + .toBeVisible() + .withTimeout(5000); + + // Open messenger + await element(by.text('Present Intercom')).tap(); + + // Try to dismiss Intercom overlay + await dismissIntercomOverlay(); + + // Note: We can't verify the messenger UI directly as it's in a native view + // But we can verify we're not on the main screen anymore + await expect(element(by.text('Intercom Example App'))).toBeVisible(); + }); + + it('should display messenger', async () => { + await waitFor(element(by.id('display-messenger'))) + .toBeVisible() + .whileElement(by.id('main-scroll')) + .scroll(100, 'down'); + await element(by.id('display-messenger')).tap(); + // Close overlay if present + await dismissIntercomOverlay(); + }); + + it('should display article', async () => { + await waitFor(element(by.id('display-article'))) + .toBeVisible() + .whileElement(by.id('main-scroll')) + .scroll(100, 'down'); + await element(by.id('display-article')).tap(); + try { + await element(by.id('close-overlay')).tap(); + } catch (e) {} + }); + + it('should present message composer', async () => { + await waitFor(element(by.id('display-message-composer'))) + .toBeVisible() + .whileElement(by.id('main-scroll')) + .scroll(100, 'down'); + await element(by.id('display-message-composer')).tap(); + await dismissIntercomOverlay(); + }); + + it('should display help center', async () => { + await element(by.id('display-help-center')).tap(); + await dismissIntercomOverlay(); + }); + + it('should fetch help center collections', async () => { + await element(by.id('fetch-help-center-collections')).tap(); + }); + + it('should toggle launcher visibility', async () => { + // First scroll to the bottom of the screen + await element(by.id('main-scroll')).scrollTo('bottom'); + + // Then wait for and tap the toggle button + await waitFor(element(by.id('toggle-launcher-visibility'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('toggle-launcher-visibility')).tap(); + await element(by.id('toggle-launcher-visibility')).tap(); + }); + + it('should get unread messages count', async () => { + // First scroll to the bottom of the screen + await element(by.id('main-scroll')).scrollTo('bottom'); + + // Then wait for and tap the toggle button + await waitFor(element(by.id('get-unreads'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('get-unreads')).tap(); + // Optionally, check for alert or result + }); + + it('should set bottom padding', async () => { + // First scroll to the bottom of the screen + await element(by.id('main-scroll')).scrollTo('bottom'); + + // Then wait for and tap the toggle button + await waitFor(element(by.id('set-bottom-padding'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('toggle-launcher-visibility')).tap(); + await element(by.id('set-bottom-padding')).tap(); + await element(by.id('set-bottom-padding')).tap(); + }); + + it('should logout', async () => { + await element(by.id('main-scroll')).scrollTo('bottom'); + + // Then wait for and tap the toggle button + await waitFor(element(by.id('logout'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('logout')).tap(); + }); +}); diff --git a/example/detox/jest.config.js b/example/detox/jest.config.js new file mode 100644 index 00000000..b669be52 --- /dev/null +++ b/example/detox/jest.config.js @@ -0,0 +1,11 @@ +module.exports = { + rootDir: '..', + testMatch: ['/detox/e2e/**/*.e2e.ts'], + testTimeout: 120000, + maxWorkers: 1, + globalSetup: 'detox/runners/jest/globalSetup', + globalTeardown: 'detox/runners/jest/globalTeardown', + reporters: ['detox/runners/jest/reporter'], + testEnvironment: 'detox/runners/jest/testEnvironment', + verbose: true, +}; diff --git a/example/detox/tsconfig.json b/example/detox/tsconfig.json new file mode 100644 index 00000000..60dd88f9 --- /dev/null +++ b/example/detox/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "types": ["node", "jest", "detox"], + "esModuleInterop": true + }, + "include": ["e2e/**/*.ts"] +} \ No newline at end of file diff --git a/example/ios/IntercomReactNativeExample.xcodeproj/project.pbxproj b/example/ios/IntercomReactNativeExample.xcodeproj/project.pbxproj index eeb4a373..2792be43 100644 --- a/example/ios/IntercomReactNativeExample.xcodeproj/project.pbxproj +++ b/example/ios/IntercomReactNativeExample.xcodeproj/project.pbxproj @@ -636,11 +636,7 @@ /usr/lib/swift, "$(inherited)", ); - LIBRARY_SEARCH_PATHS = ( - "$(SDKROOT)/usr/lib/swift", - "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", - "\"$(inherited)\"", - ); + LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift"; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; @@ -706,11 +702,7 @@ /usr/lib/swift, "$(inherited)", ); - LIBRARY_SEARCH_PATHS = ( - "$(SDKROOT)/usr/lib/swift", - "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", - "\"$(inherited)\"", - ); + LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift"; MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index a251abad..2b3eafb1 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -4,7 +4,7 @@ PODS: - FBLazyVector (0.74.0) - fmt (9.1.0) - glog (0.3.5) - - Intercom (18.6.1) + - Intercom (18.6.4) - intercom-react-native (8.3.0): - Intercom (~> 18.6.1) - React-Core @@ -911,9 +911,9 @@ PODS: - React-Mapbuffer (0.74.0): - glog - React-debug - - react-native-config (1.5.3): - - react-native-config/App (= 1.5.3) - - react-native-config/App (1.5.3): + - react-native-config (1.5.5): + - react-native-config/App (= 1.5.5) + - react-native-config/App (1.5.5): - React-Core - React-nativeconfig (0.74.0) - React-NativeModulesApple (0.74.0): @@ -1313,56 +1313,56 @@ SPEC CHECKSUMS: FBLazyVector: 026c8f4ae67b06e088ae01baa2271ef8a26c0e8c fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 - Intercom: e0b4c525b646ec91a7044136057956937a5798c9 - intercom-react-native: 452b614d67d2cb3aac5929d925f6fd3b188254e8 - RCT-Folly: 5f972de9f7d384c7d0e7380dd7da506228e568f5 + Intercom: baf5b7c5107289aa4e503130ca5fffca92d5a2ae + intercom-react-native: a67b3b5977fd3b76c856b2ec37c37a8f6b321437 + RCT-Folly: 045d6ecaa59d826c5736dfba0b2f4083ff8d79df RCTDeprecation: 3ca8b6c36bfb302e1895b72cfe7db0de0c92cd47 RCTRequired: 9fc183af555fd0c89a366c34c1ae70b7e03b1dc5 RCTTypeSafety: db1dd5ad1081a5e160d30bb29ef922693d5ac4b1 React: 8650d592d90b99097504b8dcfebab883972aed71 React-callinvoker: 6bb8b399ab8cec59e52458c3a592aa1fca130b68 - React-Codegen: fbad87d0dc7c5bc1536b25bc5cf2f19a1449e438 - React-Core: ab1b60c382b7b79c374b68918f856826ec7f02a9 - React-CoreModules: c5791800e490979b15b819e13ceaee42aa4a2672 - React-cxxreact: 7a5de9c31527a3a36b02caa3540ab55080a6448a + React-Codegen: 0c5fb82424bc21119c79da38b93ab8a62bcf5f9f + React-Core: 6dc6cccf86dd6eb53e5f689211ceb2037d65d3a6 + React-CoreModules: 087c24b785afc79d29d23bffe7b02f79bb00cf76 + React-cxxreact: 8b5a860f8c673ba4f98a3e30b41d4a2ae20f3a31 React-debug: 41175f3e30dfa8af6eab2631261e1eac26307f9f - React-Fabric: c96fe05717ffb9ab37f7533e9697e68932a621d4 - React-FabricImage: 837a4d681f01084888c7ed55df848eb3611c5691 + React-Fabric: 109d6c97fb4856f3edd848d5d896b71dedeaa361 + React-FabricImage: de46a64a0ca4b0409a0acfb2f5ccdf1195f2d8e2 React-featureflags: 5e7e78c607661fe7f72bc38c6f03736e0876753a - React-graphics: ea6e3c3f77683565552986548ba6a2938cb83251 - React-ImageManager: 49a461cd14ed15749fe7371afb1924e8a72aecc1 + React-graphics: 354adf8693bf849e696bf5096abc8cdc22c78ab4 + React-ImageManager: 74e0898e24b12c45c40019b8558a1310d0b2a47c React-jsc: 8c066d00deacb809aba74cbe3fc94b76d5ae6b7e - React-jserrorhandler: bccc0691bf5195f4da1292a4d2fbaa13fa895f89 - React-jsi: 20c796a75f92a22b083ebe78005b50fecfe025bd - React-jsiexecutor: 2ac1b518e12547c6389d6b314f4d17b283feab7a - React-jsinspector: 1cdd1dbae4aa9c455da2fec9ecda2381dda54695 - React-jsitracing: d30048b056e8c9673dfbe67813bdb874c03558a5 - React-logger: 5ae0978955199c132e71e8cf7797f619a6d17164 - React-Mapbuffer: 3b85b3778e447cd1f06d353b8e967af50f272829 - react-native-config: ea75335a7cca1d3326de1da384227e580a7c082e + React-jserrorhandler: 33cb327f5c6e1571b362f1a9c762ff839a5adb15 + React-jsi: 9ab5aa12ce6d9238a150e81f43c99b97e53a48a7 + React-jsiexecutor: c30f9dda4147c7339cffc64d6ad596c6faddddb9 + React-jsinspector: 50cfdab96549beab8d6554e39f3d36ed2ba23078 + React-jsitracing: 36a2bbc272300313653d980de5ab700ec86c534a + React-logger: 03f2f7b955cfe24593a2b8c9705c23e142d1ad24 + React-Mapbuffer: 5e05d78fe6505f4a054b86f415733d4ad02dd314 + react-native-config: 3367df9c1f25bb96197007ec531c7087ed4554c3 React-nativeconfig: 951ec32f632e81cbd7d40aebb3211313251c092e - React-NativeModulesApple: 612f931b1e79736f2d59353979042a424fb314c8 + React-NativeModulesApple: add06f130d91f3ca13b92d35861fdd6fdb9157e6 React-perflogger: 271f1111779fef70f9502d1d38da5132e5585230 React-RCTActionSheet: 5d6fb9adb11ab1bfbce6695a2b785767e4658c53 - React-RCTAnimation: 0d11291f869c8a15cff4fd21dca031a83f9e8527 - React-RCTAppDelegate: 77a7b9a27f10aa55da5a44132be281a15cc0848c - React-RCTBlob: 72759b7acf86de079c87a1562a440612c57da1b0 - React-RCTFabric: a0345a090221724893e0ea20ffab73324f4b6520 - React-RCTImage: 80ba9b23ecf87536b14c5eb38bd76f9d2b842c8a - React-RCTLinking: afd22b0854eba28eb277baad45c37ada5ef77bc3 - React-RCTNetwork: ffe5a1021f5a0bcbdf7944665dc44856493ab5bd - React-RCTSettings: f8472ee7998de8d186c198e820c40fcaf9ce4571 - React-RCTText: f556484bf1ba49a7c9b1ce1138608657d80e0bcb - React-RCTVibration: 236755b4231073ebac6cabc3864edb4cd6308d89 - React-rendererdebug: c1dac9f04b12f05929b6113a50aec5fcd5132b94 + React-RCTAnimation: 86ace32c56e69b3822e7e5184ea83a79d47fc7b9 + React-RCTAppDelegate: 6379a11a49fd0be615dc2e23da0c8a84c52ec65c + React-RCTBlob: 558daf7c11715ef24d97a0be5ccc3b209753682c + React-RCTFabric: eb4b1fc3718040717f17114b7782a519987bd7c4 + React-RCTImage: b482f07cfdbe8e413edbf9d85953cecdb569472c + React-RCTLinking: fbd73a66cab34df69b2389c17f200e4722890fd9 + React-RCTNetwork: fbdd716fbd6e53feb6d8e00eeb85e8184ad42ac8 + React-RCTSettings: 11c3051b965593988298a3f5fb39e23bf6f7df9f + React-RCTText: f240b4d39c36c295204d29e7634a2fac450b6d29 + React-RCTVibration: 1750f80b39e1ad9b4f509f4fdf19a803f7ab0d38 + React-rendererdebug: a89ffa25c7670de8f22e0b322dfdd8333bc0d126 React-rncore: a3ab9e7271a5c692918e2a483beb900ff0a51169 - React-RuntimeApple: 7fae2c2c7aa890e78830465f5ca7bd13b91939ed - React-RuntimeCore: 38f46aedbab24c4887cf763b8d0c676a059f95e6 + React-RuntimeApple: dbaeec3eb503510c93e91d49e92fc39c0ccf7e3a + React-RuntimeCore: 67e737df40b8815f65671fbaf8f75440e7fba96e React-runtimeexecutor: 4471221991b6e518466a0422fbeb2158c07c36e1 - React-runtimescheduler: 1b7a5ce47ba798252278727248a3f50e991e2631 - React-utils: 5eded69fc2a3be3f1823a64c6aa7b202e8e5dd94 - ReactCommon: 649ff2cbfc22342f119b43af78ee85bad61e8919 - RNCAsyncStorage: b6410dead2732b5c72a7fdb1ecb5651bbcf4674b + React-runtimescheduler: 203e25504974651c4472ad00e035658d32002305 + React-utils: 67c666fd04996cdb6bba26590586753d3e8ff7ed + ReactCommon: 53dbd9a55e29188ded016078708d1da8de2db19d + RNCAsyncStorage: ec53e44dc3e75b44aa2a9f37618a49c3bc080a7a SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Yoga: 56f906bf6c11c931588191dde1229fd3e4e3d557 diff --git a/example/package.json b/example/package.json index d2532fc1..fe957fb7 100644 --- a/example/package.json +++ b/example/package.json @@ -6,11 +6,16 @@ "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", - "start": "react-native start" + "start": "react-native start", + "e2e:build:ios": "detox build --configuration ios.sim.debug", + "e2e:build:android": "detox build --configuration android.emu.debug", + "e2e:test:ios": "detox test --configuration ios.sim.debug", + "e2e:test:android": "detox test --configuration android.emu.debug" }, "dependencies": { "@react-native-async-storage/async-storage": "^1.23.1", "@react-native-community/cli": "^13.6.6", + "@react-native-community/cli-platform-ios": "^11.3.7", "node-fetch": "^2.6.1", "react-native": "0.74.0", "react-native-config": "^1.5.1" @@ -19,10 +24,13 @@ "@babel/core": "^7.20.0", "@babel/preset-env": "^7.20.0", "@babel/runtime": "^7.27.0", + "@jest/reporters": "^29.7.0", "@react-native/babel-preset": "0.74.83", "@react-native/eslint-config": "0.74.83", "@react-native/metro-config": "0.74.83", "@react-native/typescript-config": "0.74.83", + "@types/detox": "^18.1.3", + "@types/jest": "^29.5.14", "@types/react": "^18.2.6", "@types/react-test-renderer": "^18.0.0", "@wdio/cli": "^9.5.1", @@ -31,6 +39,10 @@ "@wdio/spec-reporter": "^8.32.4", "babel-plugin-module-resolver": "^5.0.0", "chromedriver": "^132.0.0", + "detox": "^20.37.0", + "expect": "^29.7.0", + "jest": "^29.7.0", + "jest-environment-node": "^29.7.0", "metro-react-native-babel-preset": "^0.77.0", "react-native-codegen": "^0.70.7", "wdio-chromedriver-service": "^8.1.1", diff --git a/example/src/App.tsx b/example/src/App.tsx index 6abbc6f8..d02926cc 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -192,7 +192,7 @@ export default function App() { Unread messages count: {count} - +