package com.contentsquare.rn.csq import com.contentsquare.CSQ import com.contentsquare.android.api.model.CustomVar import com.contentsquare.api.model.Transaction import com.contentsquare.api.model.Currency import com.contentsquare.rn.externalbridge.XpfInterfaceBridge import com.contentsquare.rn.utils.ReactNativeUiThreadUtil import io.heap.core.Heap import io.heap.core.api.plugin.model.SourceInfo import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic 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 CSQEventTrackingTest { private val mockReactNativeUiThreadUtil: ReactNativeUiThreadUtil = mockk() private val mockXpfInterfaceBridge: XpfInterfaceBridge = mockk(relaxed = true) private lateinit var underTest: CSQEventTracking companion object { private const val TEST_SCREEN_NAME = "TestScreen" private val TEST_CUSTOM_VARS = listOf( CustomVar(1, "testKey1", "testValue1"), CustomVar(2, "testKey2", "testValue2") ) private val TEST_SOURCE_INFO = SourceInfo( name = "react_native_bridge", version = "1.0.0", platform = "React Native", properties = emptyMap() ) } @Before fun setup() { val slot = slot() every { mockReactNativeUiThreadUtil.runOnUiThread(capture(slot)) } answers { slot.captured.run() Unit } underTest = CSQEventTracking(mockReactNativeUiThreadUtil, mockXpfInterfaceBridge) // Common mock setup for CSQ mockkStatic(CSQ::class) every { CSQ.trackTransaction(any()) } returns Unit // Mock setup for Heap mockkStatic(Heap::class) every { Heap.track(any(), any(), any(), any(), any()) } returns Unit } @After fun tearDown() { unmockkAll() } @Test fun `trackScreenView should call XpfInterfaceBridge trackScreenView with proper parameters`() { // When underTest.trackScreenView(TEST_SCREEN_NAME, TEST_CUSTOM_VARS, TEST_SOURCE_INFO) // Then verify { mockXpfInterfaceBridge.trackScreenView( TEST_SCREEN_NAME, TEST_CUSTOM_VARS, TEST_SOURCE_INFO.name, TEST_SOURCE_INFO.version, TEST_SOURCE_INFO.platform ) } } @Test fun `trackScreenView should call XpfInterfaceBridge trackScreenView with empty list when no custom vars provided`() { // When underTest.trackScreenView(TEST_SCREEN_NAME, sourceInfo = null) // Then verify { mockXpfInterfaceBridge.trackScreenView( TEST_SCREEN_NAME, emptyList(), null, null, null ) } } @Test fun `trackScreenView should call XpfInterfaceBridge trackScreenView with null sourceInfo fields when sourceInfo is null`() { // When underTest.trackScreenView(TEST_SCREEN_NAME, TEST_CUSTOM_VARS, null) // Then verify { mockXpfInterfaceBridge.trackScreenView( TEST_SCREEN_NAME, TEST_CUSTOM_VARS, null, null, null ) } } @Test fun `trackScreenView should run on UI thread`() { // When underTest.trackScreenView(TEST_SCREEN_NAME, sourceInfo = null) // Then verify { mockReactNativeUiThreadUtil.runOnUiThread(any()) } } @Test fun `trackTransaction should call CSQ trackTransaction with transaction containing ID`() { // Given val transaction = Transaction( value = 10.0f, currency = Currency.EUR, id = "test-transaction-id" ) // When underTest.trackTransaction(transaction) // Then verify { CSQ.trackTransaction(transaction) } } @Test fun `trackTransaction should call CSQ trackTransaction with transaction without ID`() { // Given val transaction = Transaction( value = 50.5f, currency = Currency.USD, id = null ) // When underTest.trackTransaction(transaction) // Then verify { CSQ.trackTransaction(transaction) } } @Test fun `trackTransaction should call CSQ trackTransaction with zero value`() { // Given val transaction = Transaction( value = 0.0f, currency = Currency.EUR, id = null ) // When underTest.trackTransaction(transaction) // Then verify { CSQ.trackTransaction(transaction) } } @Test fun `trackTransaction should call CSQ trackTransaction with negative value`() { // Given val transaction = Transaction( value = -10.0f, currency = Currency.GBP, id = null ) // When underTest.trackTransaction(transaction) // Then verify { CSQ.trackTransaction(transaction) } } @Test fun `trackTransaction should call CSQ trackTransaction with float value`() { // Given val transaction = Transaction( value = 50.5f, currency = Currency.MAD, id = null ) // When underTest.trackTransaction(transaction) // Then verify { CSQ.trackTransaction(transaction) } } @Test fun `trackTransaction should call CSQ trackTransaction with different currencies`() { // Given val transactions = listOf( Transaction(value = 10.0f, currency = Currency.EUR, id = null), Transaction(value = 20.0f, currency = Currency.USD, id = null), Transaction(value = 30.0f, currency = Currency.GBP, id = null), Transaction(value = 40.0f, currency = Currency.MAD, id = null) ) // When & Then transactions.forEach { transaction -> underTest.trackTransaction(transaction) verify { CSQ.trackTransaction(transaction) } } } @Test fun `trackTransaction should run on UI thread`() { // Given val transaction = Transaction( value = 100.0f, currency = Currency.EUR, id = "ui-thread-test" ) // When underTest.trackTransaction(transaction) // Then verify { mockReactNativeUiThreadUtil.runOnUiThread(any()) } } @Test fun `trackEvent should call Heap track with event name and properties`() { // Given val eventName = "button_click" val properties = mapOf( "button_id" to "submit_button", "count" to 42, "enabled" to true ) // When underTest.trackEvent(eventName, properties, null) // Then verify { Heap.track( event = eventName, properties = properties, timestamp = any(), sourceInfo = null, pageview = null ) } } @Test fun `trackEvent should call Heap track with empty properties`() { // Given val eventName = "page_view" // When underTest.trackEvent(eventName, emptyMap(), null) // Then verify { Heap.track( event = eventName, properties = emptyMap(), timestamp = any(), sourceInfo = null, pageview = null ) } } @Test fun `trackEvent should call Heap track with sourceInfo`() { // Given val eventName = "custom_event" val properties = mapOf("key" to "value") val sourceInfo = mockk() // When underTest.trackEvent(eventName, properties, sourceInfo) // Then verify { Heap.track( event = eventName, properties = properties, timestamp = any(), sourceInfo = sourceInfo, pageview = null ) } } @Test fun `trackEvent should run on UI thread`() { // Given val eventName = "ui_thread_test" val properties = mapOf("test" to "value") // When underTest.trackEvent(eventName, properties, null) // Then verify { mockReactNativeUiThreadUtil.runOnUiThread(any()) } } }