From bcac45d33d2d41dd6c3c227f2719e10430a8851a Mon Sep 17 00:00:00 2001 From: wesleyorbin Date: Wed, 22 Apr 2020 17:07:03 -0400 Subject: [PATCH] Release version 1.20.0 --- .npmignore | 1 - AppboyProject/.babelrc | 3 - AppboyProject/.gitignore | 53 -- AppboyProject/AppboyProject.js | 12 + .../android/app/src/main/AndroidManifest.xml | 8 +- .../xcschemes/AppboyProject.xcscheme | 131 ----- AppboyProject/ios/Podfile.lock | 11 +- CHANGELOG.md | 8 + README.md | 10 +- __tests__/index.test.js | 465 ++++++++++++++++++ android/build.gradle | 2 +- .../appboy/reactbridge/AppboyReactBridge.java | 5 + .../AppboyReactBridge/AppboyReactBridge.m | 5 + index.d.ts | 12 + index.js | 11 + package.json | 2 +- 16 files changed, 539 insertions(+), 200 deletions(-) delete mode 100644 AppboyProject/.babelrc delete mode 100644 AppboyProject/.gitignore delete mode 100644 AppboyProject/ios/AppboyProject.xcodeproj/xcshareddata/xcschemes/AppboyProject.xcscheme create mode 100644 __tests__/index.test.js diff --git a/.npmignore b/.npmignore index c012b6d..c078174 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1,2 @@ #sample apps AppboyProject -HelloReact diff --git a/AppboyProject/.babelrc b/AppboyProject/.babelrc deleted file mode 100644 index a9ce136..0000000 --- a/AppboyProject/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["react-native"] -} diff --git a/AppboyProject/.gitignore b/AppboyProject/.gitignore deleted file mode 100644 index 10be197..0000000 --- a/AppboyProject/.gitignore +++ /dev/null @@ -1,53 +0,0 @@ -# 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 -project.xcworkspace - -# Android/IntelliJ -# -build/ -.idea -.gradle -local.properties -*.iml - -# node.js -# -node_modules/ -npm-debug.log -yarn-error.log - -# BUCK -buck-out/ -\.buckd/ -*.keystore - -# 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://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md - -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots diff --git a/AppboyProject/AppboyProject.js b/AppboyProject/AppboyProject.js index 257439e..2246050 100644 --- a/AppboyProject/AppboyProject.js +++ b/AppboyProject/AppboyProject.js @@ -47,6 +47,7 @@ class AppboyProject extends Component { this._disableSDK = this._disableSDK.bind(this); this._enableSDK = this._enableSDK.bind(this); this._requestLocationInitialization = this._requestLocationInitialization.bind(this); + this._requestGeofences = this._requestGeofences.bind(this); this._setLocationCustomAttribute = this._setLocationCustomAttribute.bind(this); this._setGenderPress = this._setGenderPress.bind(this); this._requestContentCardsRefresh = this._requestContentCardsRefresh.bind(this); @@ -275,6 +276,10 @@ class AppboyProject extends Component { Request Location Initialization : false } + + Request Geofences + Set Custom Location Attribute @@ -508,6 +513,13 @@ class AppboyProject extends Component { this._showToast('Init Requested'); } + // Note that this should normally be called only once per session + // Demo location is Baltimore + _requestGeofences(event) { + ReactAppboy.requestGeofences(39.29, -76.61); + this._showToast('Geofences Requested'); + } + _setLocationCustomAttribute(event) { ReactAppboy.setLocationCustomAttribute("work", 40.7128, 74.0060); this._showToast('Location Set'); diff --git a/AppboyProject/android/app/src/main/AndroidManifest.xml b/AppboyProject/android/app/src/main/AndroidManifest.xml index ff53f61..b64e7ad 100644 --- a/AppboyProject/android/app/src/main/AndroidManifest.xml +++ b/AppboyProject/android/app/src/main/AndroidManifest.xml @@ -31,11 +31,11 @@ android:windowSoftInputMode="adjustResize"> - 
 + - 
 - 
 - 
 + + + diff --git a/AppboyProject/ios/AppboyProject.xcodeproj/xcshareddata/xcschemes/AppboyProject.xcscheme b/AppboyProject/ios/AppboyProject.xcodeproj/xcshareddata/xcschemes/AppboyProject.xcscheme deleted file mode 100644 index f464326..0000000 --- a/AppboyProject/ios/AppboyProject.xcodeproj/xcshareddata/xcschemes/AppboyProject.xcscheme +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AppboyProject/ios/Podfile.lock b/AppboyProject/ios/Podfile.lock index ac1ca31..217a386 100644 --- a/AppboyProject/ios/Podfile.lock +++ b/AppboyProject/ios/Podfile.lock @@ -199,7 +199,7 @@ PODS: - React-cxxreact (= 0.61.5) - React-jsi (= 0.61.5) - React-jsinspector (0.61.5) - - react-native-appboy-sdk (1.19.0): + - react-native-appboy-sdk (1.20.0): - Appboy-iOS-SDK (~> 3.21.3) - React - React-RCTActionSheet (0.61.5): @@ -243,6 +243,7 @@ PODS: - Yoga (1.14.0) DEPENDENCIES: + - Appboy-iOS-SDK (= 3.21.3) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`) @@ -274,7 +275,7 @@ DEPENDENCIES: - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: - https://github.com/cocoapods/specs.git: + https://github.com/CocoaPods/Specs.git: - Appboy-iOS-SDK - boost-for-react-native - SDWebImage @@ -350,7 +351,7 @@ SPEC CHECKSUMS: React-jsi: cb2cd74d7ccf4cffb071a46833613edc79cdf8f7 React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386 React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0 - react-native-appboy-sdk: 72d1f86efd6734e68b49a90ec06aff6c1421b0c0 + react-native-appboy-sdk: c07a192dcc08f3e3c19203a2e0253ab32d36655a React-RCTActionSheet: 600b4d10e3aea0913b5a92256d2719c0cdd26d76 React-RCTAnimation: 791a87558389c80908ed06cc5dfc5e7920dfa360 React-RCTBlob: d89293cc0236d9cb0933d85e430b0bbe81ad1d72 @@ -364,6 +365,6 @@ SPEC CHECKSUMS: SDWebImage: 4d5c027c935438f341ed33dbac53ff9f479922ca Yoga: f2a7cd4280bfe2cca5a7aed98ba0eb3d1310f18b -PODFILE CHECKSUM: be9f31f36175d3d762ad3f72521bc6356f05a9b1 +PODFILE CHECKSUM: ac245fcadc6d9d7a2f662945e62fccd1d450c5ba -COCOAPODS: 1.6.1 +COCOAPODS: 1.9.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index e79ac4d..b2a3178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.20.0 + +##### ⚠ Breaking +- Updated the native Android bridge to [Braze Android SDK 7.0.0](https://github.com/Appboy/appboy-android-sdk/blob/master/CHANGELOG.md#700). + +##### Added +- Added `ReactAppboy.requestGeofences()` to request a Braze Geofences update for a manually provided GPS coordinate. Automatic Braze Geofence requests must be disabled to properly use this method. + ## 1.19.0 ##### Breaking diff --git a/README.md b/README.md index 22e1274..d813d47 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ The following commands apply to both sample projects and use the `AppboyProject` ``` cd AppboyProject/ -npm install +yarn install ``` ### iOS @@ -42,3 +42,11 @@ From the `AppboyProject` directory: ``` react-native run-android ``` + +## Style +- Generally we try to mimic the Braze Web SDK's Javascript interface where appropriate. +- We use [eslint](http://eslint.org/) as our linter. From the root directory, run `npm run lint` to list errors or `npm run lint-fix` to automatically fix errors. To override the rules in the [`standard-react`](https://github.com/feross/eslint-config-standard-react) config, add `"rules"` in `.eslintrc.json`. + +## Testing +- We use [jest](https://facebook.github.io/jest/) for testing the React SDK. +- Run the tests and code coverage report using `npm test` diff --git a/__tests__/index.test.js b/__tests__/index.test.js new file mode 100644 index 0000000..798a92c --- /dev/null +++ b/__tests__/index.test.js @@ -0,0 +1,465 @@ +const ReactAppboy = require('../index'); +const NativeModules = require('react-native').NativeModules; +const EventEmitter = require('EventEmitter'); +const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); + +/** + * Mock the NativeEventEmitter as a normal JS EventEmitter. + */ +class NativeEventEmitter extends EventEmitter { + constructor() { + super(RCTDeviceEventEmitter.sharedSubscriber); + } +} + +jest.mock('NativeEventEmitter'); + +jest.mock('NativeModules', () => { + return { + AppboyReactBridge: { + registerAndroidPushToken: jest.fn(), + setFirstName: jest.fn(), + setLastName: jest.fn(), + setLanguage: jest.fn(), + setEmail: jest.fn(), + setPhoneNumber: jest.fn(), + changeUser: jest.fn(), + setSDKFlavor: jest.fn(), + logCustomEvent: jest.fn(), + logPurchase: jest.fn(), + setCountry: jest.fn(), + setHomeCity: jest.fn(), + setAvatarImageUrl: jest.fn(), + setDateOfBirth: jest.fn(), + setTwitterData: jest.fn(), + setFacebookData: jest.fn(), + setAttributionData: jest.fn(), + launchNewsFeed: jest.fn(), + launchContentCards: jest.fn(), + getContentCards: jest.fn(), + logContentCardClicked: jest.fn(), + logContentCardDismissed: jest.fn(), + logContentCardImpression: jest.fn(), + logContentCardsDisplayed: jest.fn(), + requestFeedRefresh: jest.fn(), + requestImmediateDataFlush: jest.fn(), + enableSDK: jest.fn(), + disableSDK: jest.fn(), + wipeData: jest.fn(), + setDateCustomUserAttribute: jest.fn(), + setCustomUserAttributeArray: jest.fn(), + setBoolCustomUserAttribute: jest.fn(), + setStringCustomUserAttribute: jest.fn(), + setIntCustomUserAttribute: jest.fn(), + setDoubleCustomUserAttribute: jest.fn(), + incrementCustomUserAttribute: jest.fn(), + setGender: jest.fn(), + setPushNotificationSubscriptionType: jest.fn(), + setEmailNotificationSubscriptionType: jest.fn(), + addToCustomAttributeArray: jest.fn(), + removeFromCustomAttributeArray: jest.fn(), + unsetCustomUserAttribute: jest.fn(), + getCardCountForCategories: jest.fn(), + getUnreadCardCountForCategories: jest.fn(), + getInitialUrl: jest.fn(), + getInstallTrackingId: jest.fn(), + requestLocationInitialization: jest.fn(), + requestGeofences: jest.fn(), + setLocationCustomAttribute: jest.fn(), + requestContentCardsRefresh: jest.fn(), + hideCurrentInAppMessage: jest.fn() + } + }; +}); + +console.log = jest.fn(); +testCallback = jest.fn(); + +afterEach(() => { + jest.clearAllMocks(); +}); + +test('it calls AppboyReactBridge.registerAndroidPushToken', () => { + const token = "some_token"; + ReactAppboy.registerAndroidPushToken(token); + expect(NativeModules.AppboyReactBridge.registerAndroidPushToken).toBeCalledWith(token); +}); + +test('it calls AppboyReactBridge.setFirstName', () => { + const first_name = "some_name"; + ReactAppboy.setFirstName(first_name); + expect(NativeModules.AppboyReactBridge.setFirstName).toBeCalledWith(first_name); +}); + +test('it calls AppboyReactBridge.setLastName', () => { + const last_name = "some_name"; + ReactAppboy.setLastName(last_name); + expect(NativeModules.AppboyReactBridge.setLastName).toBeCalledWith(last_name); +}); + +test('it calls AppboyReactBridge.setLanguage', () => { + const language = "to"; + ReactAppboy.setLanguage(language); + expect(NativeModules.AppboyReactBridge.setLanguage).toBeCalledWith(language); +}); + +test('it calls AppboyReactBridge.setEmail', () => { + const email = "some_email"; + ReactAppboy.setEmail(email); + expect(NativeModules.AppboyReactBridge.setEmail).toBeCalledWith(email); +}); + +test('it calls AppboyReactBridge.setCountry', () => { + const country = "some_country"; + ReactAppboy.setCountry(country); + expect(NativeModules.AppboyReactBridge.setCountry).toBeCalledWith(country); +}); + +test('it calls AppboyReactBridge.setHomeCity', () => { + const city = "some_city"; + ReactAppboy.setHomeCity(city); + expect(NativeModules.AppboyReactBridge.setHomeCity).toBeCalledWith(city); +}); + +test('it calls AppboyReactBridge.setPhoneNumber', () => { + const number = "555-867-5309"; + ReactAppboy.setPhoneNumber(number); + expect(NativeModules.AppboyReactBridge.setPhoneNumber).toBeCalledWith(number); +}); + +test('it calls AppboyReactBridge.launchNewsFeed', () => { + ReactAppboy.launchNewsFeed(); + expect(NativeModules.AppboyReactBridge.launchNewsFeed).toBeCalled(); +}); + +test('it calls AppboyReactBridge.launchContentCards', () => { + ReactAppboy.launchContentCards(); + expect(NativeModules.AppboyReactBridge.launchContentCards).toBeCalled(); +}); + +test('it calls AppboyReactBridge.getContentCards', () => { + ReactAppboy.getContentCards(); + expect(NativeModules.AppboyReactBridge.getContentCards).toBeCalled(); +}); + +test('it calls AppboyReactBridge.logContentCardClicked', () => { + const id = "1234"; + ReactAppboy.logContentCardClicked(id); + expect(NativeModules.AppboyReactBridge.logContentCardClicked).toBeCalledWith(id); +}); + +test('it calls AppboyReactBridge.logContentCardDismissed', () => { + const id = "1234"; + ReactAppboy.logContentCardDismissed(id); + expect(NativeModules.AppboyReactBridge.logContentCardDismissed).toBeCalledWith(id); +}); + +test('it calls AppboyReactBridge.logContentCardImpression', () => { + const id = "1234"; + ReactAppboy.logContentCardImpression(id); + expect(NativeModules.AppboyReactBridge.logContentCardImpression).toBeCalledWith(id); +}); + +test('it calls AppboyReactBridge.logContentCardsDisplayed', () => { + ReactAppboy.logContentCardsDisplayed(); + expect(NativeModules.AppboyReactBridge.logContentCardsDisplayed).toBeCalled(); +}); + +test('it calls AppboyReactBridge.requestFeedRefresh', () => { + ReactAppboy.requestFeedRefresh(); + expect(NativeModules.AppboyReactBridge.requestFeedRefresh).toBeCalled(); +}); + +test('it calls AppboyReactBridge.requestImmediateDataFlush', () => { + ReactAppboy.requestImmediateDataFlush(); + expect(NativeModules.AppboyReactBridge.requestImmediateDataFlush).toBeCalled(); +}); + +test('it calls AppboyReactBridge.wipeData', () => { + ReactAppboy.wipeData(); + expect(NativeModules.AppboyReactBridge.wipeData).toBeCalled(); +}); + +test('it calls AppboyReactBridge.disableSDK', () => { + ReactAppboy.disableSDK(); + expect(NativeModules.AppboyReactBridge.disableSDK).toBeCalled(); +}); + +test('it calls AppboyReactBridge.enableSDK', () => { + ReactAppboy.enableSDK(); + expect(NativeModules.AppboyReactBridge.enableSDK).toBeCalled(); +}); + +test('it calls AppboyReactBridge.requestLocationInitialization', () => { + ReactAppboy.requestLocationInitialization(); + expect(NativeModules.AppboyReactBridge.requestLocationInitialization).toBeCalled(); +}); + +test('it calls AppboyReactBridge.requestGeofences', () => { + const latitude = 40.7128; + const longitude = 74.0060; + ReactAppboy.requestGeofences(latitude, longitude); + expect(NativeModules.AppboyReactBridge.requestGeofences).toBeCalledWith(latitude, longitude); +}); + +test('it calls AppboyReactBridge.setLocationCustomAttribute', () => { + const key = "some_key"; + const latitude = 40.7128; + const longitude = 74.0060; + ReactAppboy.setLocationCustomAttribute(key, latitude, longitude, testCallback); + expect(NativeModules.AppboyReactBridge.setLocationCustomAttribute).toBeCalledWith(key, latitude, longitude, testCallback); +}); + +test('it calls AppboyReactBridge.requestContentCardsRefresh', () => { + ReactAppboy.requestContentCardsRefresh(); + expect(NativeModules.AppboyReactBridge.requestContentCardsRefresh).toBeCalled(); +}); + +test('it calls AppboyReactBridge.setAvatarImageUrl', () => { + const url = "braze.com"; + ReactAppboy.setAvatarImageUrl(url); + expect(NativeModules.AppboyReactBridge.setAvatarImageUrl).toBeCalledWith(url); +}); + +test('it calls AppboyReactBridge.setDateOfBirth', () => { + const year = 2011; + const month = 11; + const day = 23; + ReactAppboy.setDateOfBirth(year, month, day); + expect(NativeModules.AppboyReactBridge.setDateOfBirth).toBeCalledWith(year, month, day); +}); + +test('it calls AppboyReactBridge.changeUser', () => { + const user_id = "some_id"; + ReactAppboy.changeUser(user_id); + expect(NativeModules.AppboyReactBridge.setSDKFlavor).toBeCalled(); + expect(NativeModules.AppboyReactBridge.changeUser).toBeCalledWith(user_id); +}); + +test('it calls AppboyReactBridge.logCustomEvent', () => { + const event_name = "event_name"; + const event_properties = "event_properties"; + ReactAppboy.logCustomEvent(event_name, event_properties); + expect(NativeModules.AppboyReactBridge.setSDKFlavor).toBeCalled(); + expect(NativeModules.AppboyReactBridge.logCustomEvent).toBeCalledWith(event_name, event_properties); +}); + +test('it calls AppboyReactBridge.logPurchase', () => { + const product_id = "product_id"; + const price = "price"; + const currency_code = "currency_code"; + const quantity = "quantity"; + const purchase_properties = "purchase_properties"; + ReactAppboy.logPurchase(product_id, price, currency_code, quantity, purchase_properties); + expect(NativeModules.AppboyReactBridge.logPurchase).toBeCalledWith(product_id, price, currency_code, quantity, purchase_properties); +}); + +test('it calls AppboyReactBridge.setTwitterData', () => { + const id = "some_id"; + const screen_name = "some_screen_name"; + const name = "some_name"; + const description = "some_description"; + const followers_count = 22; + const friends_count = 33; + const statuses_count = 44; + const profile_image_url = "braze.com" + ReactAppboy.setTwitterData(id, screen_name, name, description, followers_count, friends_count, statuses_count, profile_image_url); + expect(NativeModules.AppboyReactBridge.setTwitterData).toBeCalledWith(id, screen_name, name, description, followers_count, friends_count, statuses_count, profile_image_url); +}); + +test('it does not call AppboyReactBridge.setTwitterData when required arguments are missing, and logs to the console', () => { + let id = null; + const screen_name = "some_screen_name"; + const name = "some_name"; + const description = "some_description"; + let followers_count = 22; + let friends_count = 33; + let statuses_count = 44; + const profile_image_url = "braze.com" + ReactAppboy.setTwitterData(id, screen_name, name, description, followers_count, friends_count, statuses_count, profile_image_url); + id = "some_id"; + followers_count = null; + ReactAppboy.setTwitterData(id, screen_name, name, description, followers_count, friends_count, statuses_count, profile_image_url); + followers_count = 22; + friends_count = null; + ReactAppboy.setTwitterData(id, screen_name, name, description, followers_count, friends_count, statuses_count, profile_image_url); + friends_count = 33; + statuses_count = null; + ReactAppboy.setTwitterData(id, screen_name, name, description, followers_count, friends_count, statuses_count, profile_image_url); + expect(console.log).toHaveBeenCalledTimes(4); + expect(NativeModules.AppboyReactBridge.setTwitterData).not.toHaveBeenCalled(); +}); + +test('it calls AppboyReactBridge.setFacebookData', () => { + const facebook_user_dictionary = "some_facebook_user_dictionary"; + const number_of_friends = 55; + const likes = 600; + ReactAppboy.setFacebookData(facebook_user_dictionary, number_of_friends, likes); + expect(NativeModules.AppboyReactBridge.setFacebookData).toBeCalledWith(facebook_user_dictionary, number_of_friends, likes); +}); + +test('it does not call AppboyReactBridge.setFacebookData when required arguments are missing, and logs to the console', () => { + const facebook_user_dictionary = "some_facebook_user_dictionary"; + const number_of_friends = null; + const likes = 600; + ReactAppboy.setFacebookData(facebook_user_dictionary, number_of_friends, likes); + expect(console.log).toHaveBeenCalled(); + expect(NativeModules.AppboyReactBridge.setFacebookData).not.toHaveBeenCalled(); +}); + +test('it calls AppboyReactBridge.setAttributionData', () => { + const network = "some_network"; + const campaign = "some_campaign"; + const adGroup = "some_adGroup"; + const creative = "some_creative"; + ReactAppboy.setAttributionData(network, campaign, adGroup, creative); + expect(NativeModules.AppboyReactBridge.setAttributionData).toBeCalledWith(network, campaign, adGroup, creative); +}); + +test('it calls AppboyReactBridge.setDateCustomUserAttribute', () => { + const key = "some_key"; + const date = new Date('December 17, 1995 03:24:00'); + ReactAppboy.setCustomUserAttribute(key, date, testCallback); + expect(NativeModules.AppboyReactBridge.setDateCustomUserAttribute).toBeCalledWith(key, Math.floor(date.getTime() / 1000), testCallback); +}); + +test('it calls AppboyReactBridge.setCustomUserAttributeArray', () => { + const key = "some_key"; + const array = ['a','b']; + ReactAppboy.setCustomUserAttribute(key, array, testCallback); + expect(NativeModules.AppboyReactBridge.setCustomUserAttributeArray).toBeCalledWith(key, array, testCallback); +}); + +test('it calls AppboyReactBridge.setBoolCustomUserAttribute', () => { + const key = "some_key"; + const bool_value = true; + ReactAppboy.setCustomUserAttribute(key, bool_value, testCallback); + expect(NativeModules.AppboyReactBridge.setBoolCustomUserAttribute).toBeCalledWith(key, bool_value, testCallback); +}); + +test('it calls AppboyReactBridge.setStringCustomUserAttribute', () => { + const key = "some_key"; + const string_value = "some string"; + ReactAppboy.setCustomUserAttribute(key, string_value, testCallback); + expect(NativeModules.AppboyReactBridge.setStringCustomUserAttribute).toBeCalledWith(key, string_value, testCallback); +}); + +test('it calls AppboyReactBridge.setIntCustomUserAttribute', () => { + const key = "some_key"; + const int_value = 55; + ReactAppboy.setCustomUserAttribute(key, int_value, testCallback); + expect(NativeModules.AppboyReactBridge.setIntCustomUserAttribute).toBeCalledWith(key, int_value, testCallback); +}); + +test('it calls AppboyReactBridge.setDoubleCustomUserAttribute', () => { + const key = "some_key"; + const double_value = 3.14; + ReactAppboy.setCustomUserAttribute(key, double_value, testCallback); + expect(NativeModules.AppboyReactBridge.setDoubleCustomUserAttribute).toBeCalledWith(key, double_value, testCallback); +}); + +test('it calls AppboyReactBridge.incrementCustomUserAttribute', () => { + const key = "some_key"; + const value = 5; + ReactAppboy.incrementCustomUserAttribute(key, value, testCallback); + expect(NativeModules.AppboyReactBridge.incrementCustomUserAttribute).toBeCalledWith(key, value, testCallback); +}); + +test('it calls AppboyReactBridge.setGender', () => { + const gender = "male"; + ReactAppboy.setGender(gender, testCallback); + expect(NativeModules.AppboyReactBridge.setGender).toBeCalledWith(gender, testCallback); +}); + +test('it calls AppboyReactBridge.setPushNotificationSubscriptionType', () => { + const sub_type = "some_sub_type"; + ReactAppboy.setPushNotificationSubscriptionType(sub_type, testCallback); + expect(NativeModules.AppboyReactBridge.setPushNotificationSubscriptionType).toBeCalledWith(sub_type, testCallback); +}); + +test('it calls AppboyReactBridge.setEmailNotificationSubscriptionType', () => { + const sub_type = "some_sub_type"; + ReactAppboy.setEmailNotificationSubscriptionType(sub_type, testCallback); + expect(NativeModules.AppboyReactBridge.setEmailNotificationSubscriptionType).toBeCalledWith(sub_type, testCallback); +}); + +test('it calls AppboyReactBridge.addToCustomAttributeArray', () => { + const key = "some_key"; + const value = "some_value" + ReactAppboy.addToCustomUserAttributeArray(key, value, testCallback); + expect(NativeModules.AppboyReactBridge.addToCustomAttributeArray).toBeCalledWith(key, value, testCallback); +}); + +test('it calls AppboyReactBridge.removeFromCustomAttributeArray', () => { + const key = "some_key"; + const value = "some_value" + ReactAppboy.removeFromCustomUserAttributeArray(key, value, testCallback); + expect(NativeModules.AppboyReactBridge.removeFromCustomAttributeArray).toBeCalledWith(key, value, testCallback); +}); + +test('it calls AppboyReactBridge.unsetCustomUserAttribute', () => { + const key = "some_key"; + ReactAppboy.unsetCustomUserAttribute(key, testCallback); + expect(NativeModules.AppboyReactBridge.unsetCustomUserAttribute).toBeCalledWith(key, testCallback); +}); + +test('it calls AppboyReactBridge.getCardCountForCategories', () => { + const category = "some_category"; + ReactAppboy.getCardCountForCategories(category, testCallback); + expect(NativeModules.AppboyReactBridge.getCardCountForCategories).toBeCalledWith(category, testCallback); +}); + +test('it calls AppboyReactBridge.getUnreadCardCountForCategories', () => { + const category = "some_category"; + ReactAppboy.getUnreadCardCountForCategories(category, testCallback); + expect(NativeModules.AppboyReactBridge.getUnreadCardCountForCategories).toBeCalledWith(category, testCallback); +}); + +test('it calls AppboyReactBridge.getInitialUrl if defined', () => { + NativeModules.AppboyReactBridge.getInitialUrl.mockImplementation((callback) => { + callback(null, "some_data"); + }); + ReactAppboy.getInitialURL(testCallback); + expect(NativeModules.AppboyReactBridge.getInitialUrl).toBeCalled(); + expect(testCallback).toBeCalledWith("some_data"); +}); + +test('it calls AppboyReactBridge.getInstallTrackingId', () => { + NativeModules.AppboyReactBridge.getInstallTrackingId.mockImplementation((callback) => { + callback(null, "some_tracking_id"); + }); + ReactAppboy.getInstallTrackingId(testCallback); + expect(NativeModules.AppboyReactBridge.getInstallTrackingId).toBeCalled(); + expect(testCallback).toBeCalledWith(null, "some_tracking_id"); +}); + +test('it calls the callback with null and logs the error if AppboyReactBridge.getInitialUrl returns an error', () => { + NativeModules.AppboyReactBridge.getInitialUrl.mockImplementation((callback) => { + callback("error", null); + }); + ReactAppboy.getInitialURL(testCallback); + expect(NativeModules.AppboyReactBridge.getInitialUrl).toBeCalled(); + expect(testCallback).toBeCalledWith(null); + expect(console.log).toBeCalledWith("error"); +}); + +test('it calls the callback with null if AppboyReactBridge.getInitialUrl is not defined', () => { + NativeModules.AppboyReactBridge.getInitialUrl = null; + ReactAppboy.getInitialURL(testCallback); + expect(testCallback).toBeCalledWith(null); +}); + +test('it calls AppboyReactBridge.hideCurrentInAppMessage', () => { + ReactAppboy.hideCurrentInAppMessage(); + expect(NativeModules.AppboyReactBridge.hideCurrentInAppMessage).toBeCalled(); +}); + +test('it adds a listener', () => { + let counter = 0; + let testFunction = () => {counter += 1}; + let testEvent = ReactAppboy.Events.CONTENT_CARDS_UPDATED; + const nativeEmitter = new NativeEventEmitter(); + ReactAppboy.addListener(testEvent, testFunction); + nativeEmitter.emit(testEvent); + expect(counter).toBe(1); +}); diff --git a/android/build.gradle b/android/build.gradle index 93509d6..8d3f34b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -17,6 +17,6 @@ android { } dependencies { - api 'com.appboy:android-sdk-ui:5.0.0' + api 'com.appboy:android-sdk-ui:7.0.0' api 'com.facebook.react:react-native:+' } diff --git a/android/src/main/java/com/appboy/reactbridge/AppboyReactBridge.java b/android/src/main/java/com/appboy/reactbridge/AppboyReactBridge.java index 3dcf6b1..6e331f5 100644 --- a/android/src/main/java/com/appboy/reactbridge/AppboyReactBridge.java +++ b/android/src/main/java/com/appboy/reactbridge/AppboyReactBridge.java @@ -707,6 +707,11 @@ public void requestLocationInitialization() { AppboyLocationService.requestInitialization(getReactApplicationContext()); } + @ReactMethod + public void requestGeofences(Double latitude, Double longitude) { + Appboy.getInstance(getReactApplicationContext()).requestGeofences(latitude, longitude); + } + @ReactMethod public void setLocationCustomAttribute(String key, Double latitude, Double longitude, Callback callback) { Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setLocationCustomAttribute(key, latitude, longitude); diff --git a/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactBridge.m b/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactBridge.m index 4e46cd4..56bfb6b 100644 --- a/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactBridge.m +++ b/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactBridge.m @@ -456,6 +456,11 @@ - (ABKCardCategory)getCardCategoryForString:(NSString *)category { RCTLogInfo(@"Warning: This is an Android only feature."); } +RCT_EXPORT_METHOD(requestGeofences:(double)latitude longitude:(double)longitude) { + RCTLogInfo(@"[Appboy sharedInstance] requestGeofencesWithLongitude:latitude: with latitude %g and longitude %g", latitude, longitude); + [[Appboy sharedInstance] requestGeofencesWithLongitude:longitude latitude:latitude]; +} + RCT_EXPORT_METHOD(getCardCountForCategories:(NSString *)category callback:(RCTResponseSenderBlock)callback) { ABKCardCategory cardCategory = [self getCardCategoryForString:category]; if (cardCategory == 0) { diff --git a/index.d.ts b/index.d.ts index aed304a..7372678 100644 --- a/index.d.ts +++ b/index.d.ts @@ -482,6 +482,18 @@ export function enableSDK(): void; */ export function requestLocationInitialization(): void; +/** + * Call this method to request a Braze Geofences update for a manually provided + * GPS coordinate. Automatic Braze Geofence requests must be disabled to properly + * use this method. Calling this method is a no-op on iOS. + * @param {double} latitude - Location latitude. + * @param {double} longitude - Location longitude. + */ +export function requestGeofences( + latitude: number, + longitude: number +): void; + /** * Sets a custom location attribute for the user. * @param {string} key - The identifier of the custom attribute. Limited to 255 characters in length, cannot begin with diff --git a/index.js b/index.js index 7c8e889..db7f24f 100644 --- a/index.js +++ b/index.js @@ -564,6 +564,17 @@ var ReactAppboy = { AppboyReactBridge.requestLocationInitialization(); }, + /** + * Call this method to request a Braze Geofences update for a manually provided + * GPS coordinate. Automatic Braze Geofence requests must be disabled to properly + * use this method. Calling this method is a no-op on iOS. + * @param {double} latitude - Location latitude. + * @param {double} longitude - Location longitude. + */ + requestGeofences: function(latitude, longitude) { + AppboyReactBridge.requestGeofences(latitude, longitude); + }, + /** * Sets a custom location attribute for the user. * @param {string} key - The identifier of the custom attribute. Limited to 255 characters in length, cannot begin with diff --git a/package.json b/package.json index bcdac94..9abba56 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-appboy-sdk", - "version": "1.19.0", + "version": "1.20.0", "description": "Braze SDK for React Native.", "main": "index.js", "types": "index.d.ts",