import com.dxareactnative.DxaReactNativeModule import com.dxareactnative.navigation.parseScreenChangePayload import com.dxareactnative.testutils.MockQualtricsLogRule import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.WritableArray import com.facebook.react.bridge.WritableMap import com.qualtrics.dxa.DxaConfiguration import com.qualtrics.dxa.InitializationResult import com.qualtrics.dxa.LogTag import com.qualtrics.dxa.QualtricsSR import com.qualtrics.dxa.ReplaySessionInfo import com.qualtrics.dxa.ScreenChangeType import com.qualtrics.dxa.ScreenDetectionType import com.qualtrics.dxa.ScreenHierarchyElement import io.mockk.MockKAnnotations import io.mockk.clearAllMocks import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.RelaxedMockK import io.mockk.mockkObject import io.mockk.mockkStatic import io.mockk.verify import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test private const val brandID = "testBrandID" private const val projectID = "testProjectID" private const val sdkVersion = "testSdkVersion" private const val reactNativeVersion = "testReactNativeVersion" class DxaReactNativeModuleTest { @get:Rule val mockQualtricsLogRule = MockQualtricsLogRule() private lateinit var sut: DxaReactNativeModule @RelaxedMockK private lateinit var mockArray: WritableArray @RelaxedMockK private lateinit var mockMap: WritableMap @RelaxedMockK private lateinit var mockContext: ReactApplicationContext @RelaxedMockK private lateinit var mockQualtricsSRInstance: QualtricsSR @RelaxedMockK private lateinit var mockPromise: Promise @RelaxedMockK private lateinit var mockTags: ReadableArray @RelaxedMockK private lateinit var mockActivity: android.app.Activity @RelaxedMockK private lateinit var mockSessionInfo: ReplaySessionInfo @RelaxedMockK private lateinit var mockScreenHierarchy: List @RelaxedMockK private lateinit var mockChangeType: ScreenChangeType @RelaxedMockK private lateinit var mockDetectionType: ScreenDetectionType @Before fun setup() { MockKAnnotations.init(this) mockkStatic(Arguments::class) every { Arguments.createMap() } returns mockMap every { Arguments.createArray() } returns mockArray mockkObject(QualtricsSR) every { QualtricsSR.instance() } returns mockQualtricsSRInstance sut = DxaReactNativeModule(mockContext) mockkStatic(::parseScreenChangePayload) } @After fun tearDown() { clearAllMocks() } @Test fun `setLoggingTags() WHEN called with valid tags THEN QualtricsSR allowedLoggingTags is set correctly`() { every { mockTags.toArrayList() } returns arrayListOf("LIFECYCLE") sut.setLoggingTags(mockTags) verify { mockQualtricsSRInstance.allowedLoggingTags = match { tags -> tags.containsAll(setOf(LogTag.LIFECYCLE)) && tags.size == 1 } mockArray.pushString("LIFECYCLE") } } @Test fun `setLoggingTags() WHEN called with invalid tags THEN invalid tags are ignored`() { every { mockTags.toArrayList() } returns arrayListOf("INVALID_TAG", "LIFECYCLE") sut.setLoggingTags(mockTags) verify { mockQualtricsSRInstance.allowedLoggingTags = match { tags -> tags.containsAll(setOf(LogTag.LIFECYCLE)) && tags.size == 1 } mockArray.pushString("LIFECYCLE") } verify(exactly = 0) { mockArray.pushString("INVALID_TAG") } } @Test fun `setLoggingTags() WHEN called with empty array THEN QualtricsSR allowedLoggingTags is set to empty set`() { every { mockTags.toArrayList() } returns arrayListOf() sut.setLoggingTags(mockTags) verify { mockQualtricsSRInstance.allowedLoggingTags = emptySet() } } @Test fun `initializeProject() WHEN current activity is null THEN returns failure result`() { every { mockContext.currentActivity } returns null sut.initializeProject(brandID, projectID, sdkVersion, reactNativeVersion) verify { mockMap.putString("result", "No current activity") } } @Test fun `initializeProject() WHEN current activity is available THEN calls QualtricsSR initializeProject and returns result`() { every { mockContext.currentActivity } returns mockActivity val initResult = InitializationResult.Success every { mockQualtricsSRInstance.initializeProject( mockActivity, brandID, projectID ) } returns initResult sut.initializeProject(brandID, projectID, sdkVersion, reactNativeVersion) verify { mockQualtricsSRInstance.initializeProject( mockActivity, brandID, projectID ) mockMap.putString("result", "Success") } } @Test fun `startRecording() WHEN called without config THEN QualtricsSR startRecording is invoked with default config`() = runTest { sut.startRecording(null, mockPromise) coVerify { mockQualtricsSRInstance.startRecording(any()) } } @Test fun `stopRecording() WHEN called THEN QualtricsSR stopRecording is invoked`() = runTest { sut.stopRecording(mockPromise) coVerify { mockQualtricsSRInstance.stopRecording() } } @Test fun `pauseRecording() WHEN called THEN QualtricsSR pauseRecording is invoked`() = runTest { sut.pauseRecording(mockPromise) coVerify { mockQualtricsSRInstance.pauseRecording() } } @Test fun `resumeRecording() WHEN called THEN QualtricsSR resumeRecording is invoked`() = runTest { sut.resumeRecording(mockPromise) coVerify { mockQualtricsSRInstance.resumeRecording() } } @Test fun `logError() WHEN called THEN QualtricsSR logError is invoked`() { // Pass null explicitly - TurboModules handle optional params by passing null sut.logError(null) verify { mockQualtricsSRInstance.logError() } } @Test fun `GIVEN A Specific Message WHEN logError() Is Called THEN QualtricsSR Logs Error Using Passed Message`() { sut.logError("errorMessage") verify { mockQualtricsSRInstance.logError("errorMessage") } } @Test fun `createArgumentMap() WHEN called with only result THEN returns WritableMap with result`() { sut.createArgumentMap("Success") verify { mockMap.putString("result", "Success") } } @Test fun `createArgumentMap() WHEN called with result and error THEN returns WritableMap with result and error`() { sut.createArgumentMap("Fail", error = "Error message") verify { mockMap.putString("result", "Fail") mockMap.putString("error", "Error message") } } @Test fun `createArgumentMap() WHEN called with result, message, and sessionInfo THEN returns WritableMap with all information`() { every { mockSessionInfo.sessionId } returns "testSessionId" every { mockSessionInfo.sessionStartTimeMillis } returns 1234567890L sut.createArgumentMap("Success", message = "Success message", sessionInfo = mockSessionInfo) verify { mockMap.putString("result", "Success") mockMap.putString("message", "Success message") mockMap.putString("sessionId", "testSessionId") mockMap.putString("sessionStartTimeMillis", "1234567890") } } @Test fun `reportScreenChangeOnNative() WHEN validation succeeds THEN registers screen change`() { every { parseScreenChangePayload(any()) } returns Triple( mockScreenHierarchy, mockChangeType, mockDetectionType ) sut.reportScreenChangeOnNative(mockMap) verify(exactly = 1) { mockQualtricsSRInstance.reportScreenChange( mockScreenHierarchy, mockChangeType, mockDetectionType ) } } @Test fun `reportScreenChangeOnNative() WHEN validation fails THEN does not register screen change`() { every { parseScreenChangePayload(any()) } returns null sut.reportScreenChangeOnNative(mockMap) verify(exactly = 0) { mockQualtricsSRInstance.reportScreenChange(any(), any(), any()) } } }