package com.contentsquare.rn.csq import com.contentsquare.android.api.model.CustomVar import com.contentsquare.api.model.ProductAnalyticsOptions import com.contentsquare.api.model.Transaction import com.contentsquare.rn.ContentsquareModuleImpl import com.contentsquare.rn.eventEmitter.CSEventEmitterModuleImpl import com.contentsquare.rn.utils.ReactNativeUiThreadUtil import com.contentsquare.rn.utils.ReactNativeViewFinder import com.contentsquare.rn.webview.WebViewInjector import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockk import io.mockk.slot import io.mockk.unmockkAll import io.mockk.verify import org.junit.After import org.junit.Before import org.junit.Test class CSQTransitionWrapperTest { private lateinit var underTest: CSQTransitionWrapper private lateinit var mockCsqInitialization: CSQInitialization private lateinit var mockCsqPrivacy: CSQPrivacy private lateinit var mockCsqTracking: CSQTracking private lateinit var mockCSQEventTracking: CSQEventTracking private lateinit var mockCSQMasking: CSQMasking private lateinit var mockCSQPropertyTracking: CSQPropertyTracking private lateinit var mockCSQErrorTracking: CSQErrorTracking private lateinit var mockCSQMetadata: CSQMetadata private lateinit var mockContentsquareLegacy: ContentsquareModuleImpl @Before fun setUp() { val mockReactApplicationContext = mockk() val mockWebViewInjector = mockk() val mockCSEventEmitter = mockk() val mockReactNativeUiThreadUtil = mockk() val mockReactNativeViewFinder = mockk() val slot = slot() every { mockReactNativeUiThreadUtil.runOnUiThread(capture(slot)) } answers { slot.captured.run() Unit } mockCsqInitialization = mockk(relaxed = true) mockCsqPrivacy = mockk(relaxed = true) mockCsqTracking = mockk(relaxed = true) mockCSQEventTracking = mockk(relaxed = true) mockCSQPropertyTracking = mockk(relaxed = true) mockCSQMasking = mockk(relaxed = true) mockCSQErrorTracking = mockk(relaxed = true) mockCSQMetadata = mockk(relaxed = true) mockContentsquareLegacy = mockk(relaxed = true) underTest = CSQTransitionWrapper( mockReactApplicationContext, mockWebViewInjector, mockCSEventEmitter, mockReactNativeUiThreadUtil, mockReactNativeViewFinder ) val csqInitializationField = CSQTransitionWrapper::class.java.getDeclaredField("csqInitialization") csqInitializationField.isAccessible = true csqInitializationField.set(underTest, mockCsqInitialization) val contentsquareLegacyField = CSQTransitionWrapper::class.java.getDeclaredField("contentsquareLegacy") contentsquareLegacyField.isAccessible = true contentsquareLegacyField.set(underTest, mockContentsquareLegacy) val csqPrivacyField = CSQTransitionWrapper::class.java.getDeclaredField("csqPrivacy") csqPrivacyField.isAccessible = true csqPrivacyField.set(underTest, mockCsqPrivacy) val csqTrackingField = CSQTransitionWrapper::class.java.getDeclaredField("csqTracking") csqTrackingField.isAccessible = true csqTrackingField.set(underTest, mockCsqTracking) val csqEventTrackingField = CSQTransitionWrapper::class.java.getDeclaredField("csqEventTracking") csqEventTrackingField.isAccessible = true csqEventTrackingField.set(underTest, mockCSQEventTracking) val csqMaskingField = CSQTransitionWrapper::class.java.getDeclaredField("csqMasking") csqMaskingField.isAccessible = true csqMaskingField.set(underTest, mockCSQMasking) val csqPropertyTrackingField = CSQTransitionWrapper::class.java.getDeclaredField("csqPropertyTracking") csqPropertyTrackingField.isAccessible = true csqPropertyTrackingField.set(underTest, mockCSQPropertyTracking) val csqErrorTrackingField = CSQTransitionWrapper::class.java.getDeclaredField("csqErrorTracking") csqErrorTrackingField.isAccessible = true csqErrorTrackingField.set(underTest, mockCSQErrorTracking) val csqMetadataField = CSQTransitionWrapper::class.java.getDeclaredField("csqMetadata") csqMetadataField.isAccessible = true csqMetadataField.set(underTest, mockCSQMetadata) } @After fun tearDown() { clearAllMocks() unmockkAll() } @Test fun `Given CSQ is enabled, when starting the SDK, it should call CSQInitialization start method`() { underTest.start() verify { mockCsqInitialization.start() } verify(exactly = 0) { mockContentsquareLegacy.start() } } @Test fun `configureProductAnalytics should call CSQInitialization configureProductAnalytics with options as ReadableMap`() { // Given val envId = "2628368288" val mockReadableMap = mockk(relaxed = true) { every { hasKey("enableViewAutocapture") } returns true every { getBoolean("enableViewAutocapture") } returns true every { hasKey("disablePageviewAutocapture") } returns false } // When underTest.configureProductAnalytics(envId, mockReadableMap) // Then verify { mockCsqInitialization.configureProductAnalytics( envId, any() ) } } @Test fun `configureProductAnalytics should call CSQInitialization configureProductAnalytics without options`() { // Given val envId = "2628368288" // When underTest.configureProductAnalytics(envId, null) // Then verify { mockCsqInitialization.configureProductAnalytics( envId, any() ) } } @Test fun `optIn should delegate to CSQPrivacy and not call legacy implementation`() { underTest.optIn() verify { mockCsqPrivacy.optIn() } verify(exactly = 0) { mockContentsquareLegacy.optIn() } } @Test fun `optOut should delegate to CSQPrivacy and not call legacy implementation`() { underTest.optOut() verify { mockCsqPrivacy.optOut() } verify(exactly = 0) { mockContentsquareLegacy.optOut() } } @Test fun `pauseTracking should delegate to CSQTracking`() { underTest.pauseTracking() verify { mockCsqTracking.pauseTracking() } } @Test fun `resumeTracking should delegate to CSQTracking and not call legacy implementation`() { underTest.resumeTracking() verify { mockCsqTracking.resumeTracking() } verify(exactly = 0) { mockContentsquareLegacy.resumeTracking() } } @Test fun `stop should delegate to CSQTracking`() { underTest.stop() verify { mockCsqTracking.stop() } } @Test fun `addDynamicVar with String should delegate to CSQTracking`() { // Given val key = "test_key" val value = "test_value" // When underTest.addDynamicVar(key, value) // Then verify { mockCsqTracking.addDynamicVar(key, value) } } @Test fun `addDynamicVar with Double should delegate to CSQTracking`() { // Given val key = "test_key" val value = 123.45 // When underTest.addDynamicVar(key, value) // Then verify { mockCsqTracking.addDynamicVar(key, value) } } @Test fun `sendUserIdentifier should delegate to CSQPrivacy and not call legacy implementation`() { // Given val userIdentifier = "test_user_123" // When underTest.sendUserIdentifier(userIdentifier) // Then verify { mockCsqPrivacy.sendUserIdentifier(userIdentifier) } verify(exactly = 0) { mockContentsquareLegacy.sendUserIdentifier(userIdentifier) } } @Test fun `trackScreenView with customVars should delegate to CSQEventTracking`() { // Given val name = "TestScreen" val mockCustomVars = mockk(relaxed = true) val mockSourceInfo = mockk(relaxed = true) // When underTest.trackScreenView(name, mockCustomVars, mockSourceInfo) // Then verify { mockCSQEventTracking.trackScreenView( name, any>(), any() ) } } @Test fun `trackScreenView with null customVars should delegate to CSQEventTracking with empty list`() { // Given val name = "TestScreen" // When underTest.trackScreenView(name, null, null) // Then verify { mockCSQEventTracking.trackScreenView( name, emptyList(), null ) } } @Test fun `trackScreenView with sourceInfo should delegate to CSQEventTracking with sourceInfo`() { // Given val name = "TestScreen" val mockSourceInfo = mockk(relaxed = true) { every { getString("name") } returns "react_native_bridge" every { getString("version") } returns "1.0.0" every { getString("platform") } returns "React Native" every { getMap("properties") } returns null } // When underTest.trackScreenView(name, null, mockSourceInfo) // Then verify { mockCSQEventTracking.trackScreenView( name, emptyList(), any() ) } } @Test fun `trackTransaction with integer currency should delegate to CSQEventTracking`() { // Given val id = "test-transaction-id" val value = 50.5f val currency = 978 // EUR // When underTest.trackTransaction(id, value, currency) // Then verify { mockCSQEventTracking.trackTransaction(any()) } } @Test fun `trackTransaction with string currency should delegate to CSQEventTracking`() { // Given val id = "test-transaction-id" val value = 25.99f val currency = "EUR" // When underTest.trackTransaction(id, value, currency) // Then verify { mockCSQEventTracking.trackTransaction(any()) } } @Test fun `trackEvent should delegate to CSQEventTracking with event name and properties`() { // Given val eventName = "button_click" val mockProperties = mockk(relaxed = true) { every { toHashMap() } returns hashMapOf( "button_id" to "submit_button", "page" to "checkout", "user_id" to 12345 ) } // When underTest.trackEvent(eventName, mockProperties, null) // Then verify { mockCSQEventTracking.trackEvent(eventName, any>(), null) } } @Test fun `trackEvent should delegate to CSQEventTracking without properties when properties is null`() { // Given val eventName = "page_view" // When underTest.trackEvent(eventName, null, null) // Then verify { mockCSQEventTracking.trackEvent(eventName, emptyMap(), null) } } @Test fun `trackEvent should delegate to CSQEventTracking with sourceInfo when provided`() { // Given val eventName = "button_click" val mockProperties = mockk(relaxed = true) { every { toHashMap() } returns hashMapOf( "button_id" to "submit_button" ) } val mockSourceInfo = mockk(relaxed = true) // When underTest.trackEvent(eventName, mockProperties, mockSourceInfo) // Then verify { mockCSQEventTracking.trackEvent(eventName, any>(), any()) } } @Test fun `setDefaultMasking should delegate to CSQMasking`() { // When underTest.setDefaultMasking(true) // Then verify { mockCSQMasking.setDefaultMasking(true) } } @Test fun `addEventProperties should delegate to CSQPropertyTracking with properties`() { // Given val mockProperties = mockk(relaxed = true) { every { toHashMap() } returns hashMapOf( "name" to "test", "count" to 101, "float" to 50.5, "active" to true ) } // When underTest.addEventProperties(mockProperties) // Then verify { mockCSQPropertyTracking.addEventProperties(any>()) } } @Test fun `removeEventProperty should delegate to CSQPropertyTracking with property name`() { // Given val propertyName = "test_property" // When underTest.removeEventProperty(propertyName) // Then verify { mockCSQPropertyTracking.removeEventProperty(propertyName) } } @Test fun `clearEventProperties should delegate to CSQPropertyTracking`() { // When underTest.clearEventProperties() // Then verify { mockCSQPropertyTracking.clearEventProperties() } } @Test fun `addUserProperties should delegate to CSQPropertyTracking with properties`() { // Given val mockProperties = mockk(relaxed = true) { every { toHashMap() } returns hashMapOf( "name" to "test", "count" to 101, "float" to 50.5, "active" to true ) } // When underTest.addUserProperties(mockProperties) // Then verify { mockCSQPropertyTracking.addUserProperties(any>()) } } @Test fun `setUrlMaskingPatterns should delegate to CSQErrorTracking with patterns`() { // Given val mockPatterns = mockk(relaxed = true) // When underTest.setUrlMaskingPatterns(mockPatterns) // Then verify { mockCSQErrorTracking.setUrlMaskingPatterns(any>()) } } @Test fun `triggerNativeCrash should delegate to CSQErrorTracking`() { // When underTest.triggerNativeCrash() // Then verify { mockCSQErrorTracking.triggerNativeCrash() } } @Test fun `setOnMetadataChange should delegate to CSQMetadataTracking`() { // When underTest.setOnMetadataChange() // Then verify { mockCSQMetadata.setOnMetadataChange() } } }