Skip to content

Commit 2835cb6

Browse files
uc-diogoNoé Fernández
andauthored
Add ab testing support (#55)
* Add ab testing support * Fix ios tests * Fix fakeusercentricsmanager * Feedback * delete example * Add example for ab testing in example app * Empty-Commit Co-authored-by: Noé Fernández <[email protected]>
1 parent 3c005a7 commit 2835cb6

File tree

12 files changed

+166
-9
lines changed

12 files changed

+166
-9
lines changed

android/src/androidTest/java/com/usercentrics/reactnativemodule/RNUsercentricsModuleTest.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,21 @@ class RNUsercentricsModuleTest {
210210
assertEquals("abc", promise.resolveValue)
211211
}
212212

213+
@Test
214+
fun testGetABTestingVariant() {
215+
val usercentricsSDK = mockk<UsercentricsSDK>()
216+
every { usercentricsSDK.getABTestingVariant() }.returns("variantA")
217+
val usercentricsProxy = FakeUsercentricsProxy(usercentricsSDK)
218+
val contextMock = mockk<ReactApplicationContext>(relaxed = true)
219+
val module = RNUsercentricsModule(contextMock, usercentricsProxy)
220+
val promise = FakePromise()
221+
222+
module.getABTestingVariant(promise)
223+
224+
verify(exactly = 1) { usercentricsSDK.getABTestingVariant() }
225+
assertEquals("variantA", promise.resolveValue)
226+
}
227+
213228
@Test
214229
fun testGetConsents() {
215230
val usercentricsSDK = mockk<UsercentricsSDK>()
@@ -294,6 +309,20 @@ class RNUsercentricsModuleTest {
294309
verify(exactly = 1) { usercentricsSDK.setCMPId(123) }
295310
}
296311

312+
@Test
313+
fun testSetABTestingVariant() {
314+
val usercentricsSDK = mockk<UsercentricsSDK>()
315+
every { usercentricsSDK.setABTestingVariant("variantA") }.returns(Unit)
316+
317+
val usercentricsProxy = FakeUsercentricsProxy(usercentricsSDK)
318+
val contextMock = mockk<ReactApplicationContext>(relaxed = true)
319+
val module = RNUsercentricsModule(contextMock, usercentricsProxy)
320+
321+
module.setABTestingVariant("variantA")
322+
323+
verify(exactly = 1) { usercentricsSDK.setABTestingVariant("variantA") }
324+
}
325+
297326
@Test
298327
fun testGetTCFData() {
299328
val usercentricsSDK = mockk<UsercentricsSDK>()

android/src/main/java/com/usercentrics/reactnativeusercentrics/RNUsercentricsModule.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ internal class RNUsercentricsModule(
3636
val assetManager = currentActivity!!.assets
3737

3838
val layout = options.getString("layout")!!.usercentricsLayoutFromEnumString()
39-
val bannerSettings = options.getMap("bannerSettings")?.bannerSettingsFromMap(assetManager)
39+
val bannerSettings =
40+
options.getMap("bannerSettings")?.bannerSettingsFromMap(assetManager)
4041
usercentricsProxy.showFirstLayer(currentActivity!!, layout, bannerSettings, promise)
4142

4243
} catch (e: Exception) {
@@ -51,7 +52,8 @@ internal class RNUsercentricsModule(
5152
try {
5253
val assetManager = currentActivity!!.assets
5354

54-
val bannerSettings = options.getMap("bannerSettings")?.bannerSettingsFromMap(assetManager)
55+
val bannerSettings =
56+
options.getMap("bannerSettings")?.bannerSettingsFromMap(assetManager)
5557
usercentricsProxy.showSecondLayer(currentActivity!!, bannerSettings, promise)
5658
} catch (e: Exception) {
5759
promise.reject(e)
@@ -73,6 +75,11 @@ internal class RNUsercentricsModule(
7375
promise.resolve(usercentricsProxy.instance.getControllerId())
7476
}
7577

78+
@ReactMethod
79+
fun getABTestingVariant(promise: Promise) {
80+
promise.resolve(usercentricsProxy.instance.getABTestingVariant())
81+
}
82+
7683
@ReactMethod
7784
fun getConsents(promise: Promise) {
7885
promise.resolve(usercentricsProxy.instance.getConsents().toWritableArray())
@@ -88,6 +95,11 @@ internal class RNUsercentricsModule(
8895
usercentricsProxy.instance.setCMPId(id)
8996
}
9097

98+
@ReactMethod
99+
fun setABTestingVariant(variant: String) {
100+
usercentricsProxy.instance.setABTestingVariant(variant)
101+
}
102+
91103
@ReactMethod
92104
fun getTCFData(promise: Promise) {
93105
usercentricsProxy.instance.getTCFData {

example/ios/exampleTests/Fake/FakeUsercentricsManager.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ final class FakeUsercentricsManager: UsercentricsManager {
1717

1818
var getTCStringValue: String?
1919
var getControllerIdValue: String?
20+
var getABTestingVariantValue: String?
2021

2122
var configureOptions: UsercentricsOptions?
2223
var isReadySuccessCompletion: UsercentricsReadyStatus?
@@ -67,6 +68,10 @@ final class FakeUsercentricsManager: UsercentricsManager {
6768
return getControllerIdValue!
6869
}
6970

71+
func getABTestingVariant() -> String? {
72+
return getABTestingVariantValue!
73+
}
74+
7075
var getConsentsResponse: [UsercentricsServiceConsent]?
7176
func getConsents() -> [UsercentricsServiceConsent] {
7277
return getConsentsResponse!
@@ -163,6 +168,11 @@ final class FakeUsercentricsManager: UsercentricsManager {
163168
self.cmpId = id
164169
}
165170

171+
var variant: String?
172+
func setABTestingVariant(variant: String) {
173+
self.variant = variant
174+
}
175+
166176
var showFirstLayerBannerSettings: BannerSettings?
167177
var layoutSettings: FirstLayerStyleSettings?
168178
var layout: UsercentricsLayout?

example/ios/exampleTests/RNUsercentricsModuleTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,15 @@ class RNUsercentricsModuleTests: XCTestCase {
121121
}
122122
}
123123

124+
func testGetABTestingVariantId() {
125+
fakeUsercentrics.getABTestingVariantValue = "variantA"
126+
module.getABTestingVariant { response in
127+
XCTAssertEqual("variantA", response as? String)
128+
} reject: { _, _, _ in
129+
XCTFail("Should not go here")
130+
}
131+
}
132+
124133
func testReset() {
125134
module.reset()
126135
XCTAssertEqual(1, fakeUsercentrics.resetCount)
@@ -453,6 +462,11 @@ class RNUsercentricsModuleTests: XCTestCase {
453462
XCTAssertEqual(Int32(123), fakeUsercentrics.cmpId)
454463
}
455464

465+
func testSetABTestingVariant() {
466+
module.setABTestingVariant("variantA")
467+
XCTAssertEqual(String("variantA"), fakeUsercentrics.variant)
468+
}
469+
456470
func testShowFirstLayer() {
457471
let dict: NSDictionary = ["layout": "POPUP_CENTER"]
458472
module.showFirstLayer(dict) { result in

example/src/screens/Home.tsx

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import { Button, StyleSheet, View } from 'react-native';
33
import {
44
BannerSettings, FirstLayerOptions, SecondLayerOptions, Usercentrics,
5-
UsercentricsLayout
5+
UsercentricsLayout, UsercentricsReadyStatus
66
} from '../../../src/index';
77
import {
88
customizationExampleOne,
@@ -11,6 +11,8 @@ import {
1111

1212
export const HomeScreen = ({ navigation }: { navigation: any }) => {
1313
async function showFirstLayer(options: FirstLayerOptions = defaultOptions) {
14+
// const bannerSettings = await getBannerSettings()
15+
// const options = new FirstLayerOptions(UsercentriwcsLayout.popupBottom, bannerSettings)
1416
const response = await Usercentrics.showFirstLayer(options);
1517
console.log("Consents -> ${response.consents}", response.consents);
1618
console.log("User Interaction -> ${response.userInteraction}", response.userInteraction);
@@ -23,6 +25,7 @@ export const HomeScreen = ({ navigation }: { navigation: any }) => {
2325
showCloseButton: true
2426
}
2527
};
28+
2629
const options = new SecondLayerOptions(bannerSettings);
2730

2831
const response = await Usercentrics.showSecondLayer(options);
@@ -31,6 +34,43 @@ export const HomeScreen = ({ navigation }: { navigation: any }) => {
3134
console.log("Controller Id -> ${response.controllerId}", response.controllerId);
3235
}
3336

37+
async function getBannerSettings(){
38+
const variant = await Usercentrics.getABTestingVariant()
39+
let bannerSettings: BannerSettings;
40+
41+
switch (variant){
42+
case "variantA":
43+
return bannerSettings = {/* settings for the banner with variantA */};
44+
case "variantB":
45+
return bannerSettings = {/* settings for the banner with variantB */};
46+
default:
47+
return bannerSettings = {/* default banner settings*/};
48+
}
49+
}
50+
51+
//'Activate with third-party tool' option
52+
async function getBannerSettingsThirdPartyTool(){
53+
const variant = ThirdPartyTool.getABTestingVariant()
54+
let bannerSettings: BannerSettings;
55+
56+
switch (variant){
57+
case "variantA":
58+
return bannerSettings = {/* settings for the banner with variantA */ variant: "variantA"};
59+
case "variantB":
60+
return bannerSettings = {/* settings for the banner with variantB */ variant: "variantB"};
61+
default:
62+
return bannerSettings = {/* default banner settings*/variant: "variantC"};
63+
}
64+
}
65+
66+
const ThirdPartyTool = {
67+
getABTestingVariant: (): String | null => {
68+
const variants = ["variantA", "variantB"]
69+
const random = Math.floor(Math.random() * variants.length );
70+
return variants[random];
71+
}
72+
}
73+
3474
const styles = StyleSheet.create({
3575
container: {
3676
flex: 1,

ios/Manager/UsercentricsManager.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public protocol UsercentricsManager {
2121
func getUserSessionData() -> String
2222
func getUSPData() -> CCPAData
2323
func getTCFData(callback: @escaping (TCFData) -> Void)
24+
func getABTestingVariant() -> String?
2425

2526
func changeLanguage(language: String, onSuccess: @escaping (() -> Void), onFailure: @escaping ((Error) -> Void))
2627

@@ -37,6 +38,7 @@ public protocol UsercentricsManager {
3738
func saveDecisions(decisions: [UserDecision], consentType: UsercentricsConsentType) -> [UsercentricsServiceConsent]
3839
func saveOptOutForCCPA(isOptedOut: Bool, consentType: UsercentricsConsentType) -> [UsercentricsServiceConsent]
3940
func setCMPId(id: Int32)
41+
func setABTestingVariant(variant: String)
4042

4143
func reset()
4244
}
@@ -75,6 +77,10 @@ final class UsercentricsManagerImplementation: UsercentricsManager {
7577
return UsercentricsCore.shared.getControllerId()
7678
}
7779

80+
func getABTestingVariant() -> String? {
81+
return UsercentricsCore.shared.getABTestingVariant()
82+
}
83+
7884
func getConsents() -> [UsercentricsServiceConsent] {
7985
return UsercentricsCore.shared.getConsents()
8086
}
@@ -130,4 +136,8 @@ final class UsercentricsManagerImplementation: UsercentricsManager {
130136
func setCMPId(id: Int32) {
131137
UsercentricsCore.shared.setCMPId(id: id)
132138
}
139+
140+
func setABTestingVariant(variant: String) {
141+
UsercentricsCore.shared.setABTestingVariant(variant: variant)
142+
}
133143
}

ios/RNUsercentricsModule.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ @interface RCT_EXTERN_MODULE(RNUsercentricsModule, NSObject)
3535
RCT_EXTERN_METHOD(getControllerId:(RCTPromiseResolveBlock)resolve
3636
reject:(RCTPromiseRejectBlock)reject)
3737

38+
RCT_EXTERN_METHOD(getABTestingVariant:(RCTPromiseResolveBlock)resolve
39+
reject:(RCTPromiseRejectBlock)reject)
40+
3841
RCT_EXTERN_METHOD(getConsents:(RCTPromiseResolveBlock)resolve
3942
reject:(RCTPromiseRejectBlock)reject)
4043

@@ -91,5 +94,7 @@ @interface RCT_EXTERN_MODULE(RNUsercentricsModule, NSObject)
9194

9295
RCT_EXTERN_METHOD(setCMPId:(NSInteger *)id)
9396

97+
RCT_EXTERN_METHOD(setABTestingVariant:(NSString *)variant)
98+
9499
RCT_EXTERN_METHOD(reset)
95100
@end

ios/RNUsercentricsModule.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,11 @@ class RNUsercentricsModule: NSObject, RCTBridgeModule {
7878
@objc func setCMPId(_ id: Int) -> Void {
7979
usercentricsManager.setCMPId(id: Int32(id))
8080
}
81-
81+
82+
@objc func setABTestingVariant(_ variant: String) -> Void {
83+
usercentricsManager.setABTestingVariant(variant: String(variant))
84+
}
85+
8286
@objc func restoreUserSession(_ controllerId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
8387
usercentricsManager.restoreUserSession(controllerId: controllerId) { status in
8488
resolve(status.toDictionary())
@@ -112,6 +116,10 @@ class RNUsercentricsModule: NSObject, RCTBridgeModule {
112116
@objc func getUSPData(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
113117
resolve(usercentricsManager.getUSPData().toDictionary())
114118
}
119+
120+
@objc func getABTestingVariant(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
121+
resolve(usercentricsManager.getABTestingVariant())
122+
}
115123

116124
@objc func changeLanguage(_ language: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
117125
usercentricsManager.changeLanguage(language: language) {

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Usercentrics.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ export const Usercentrics = {
3535
return RNUsercentricsModule.getControllerId();
3636
},
3737

38+
getABTestingVariant: async (): Promise<string | null> => {
39+
await RNUsercentricsModule.isReady();
40+
return RNUsercentricsModule.getABTestingVariant();
41+
},
42+
3843
getConsents: async (): Promise<[UsercentricsServiceConsent]> => {
3944
await RNUsercentricsModule.isReady();
4045
return RNUsercentricsModule.getConsents();
@@ -104,6 +109,10 @@ export const Usercentrics = {
104109
RNUsercentricsModule.setCMPId(id);
105110
},
106111

112+
setABTestingVariant: (variant: string) => {
113+
RNUsercentricsModule.setABTestingVariant(variant);
114+
},
115+
107116
reset: () => {
108117
RNUsercentricsModule.reset()
109118
}

0 commit comments

Comments
 (0)