package com.contentsquare.rn.webview import android.app.Activity import android.os.Build import android.webkit.WebBackForwardList import android.webkit.WebView import android.webkit.WebViewClient import androidx.test.ext.junit.runners.AndroidJUnit4 import com.contentsquare.android.R import com.contentsquare.rn.utils.ReactNativeViewFinder import com.contentsquare.rn.utils.VersionUtils import com.contentsquare.rn.utils.WebViewUtils import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter import io.mockk.every import io.mockk.just import io.mockk.mockk import io.mockk.mockkObject import io.mockk.runs import io.mockk.slot import io.mockk.unmockkAll import io.mockk.verify import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) @Config(manifest = Config.NONE) class WebViewInjectorTest { // mocks private val mockActivity = mockk() private val mockReactApplicationContext = mockk() private val mockWebViewTag = 999 private val webView = mockk() private val webViewClient = mockk(relaxed = true) private val webBackForwardList = mockk() private val mockReactNativeViewFinder = mockk() private val mockSDKWebViewManager = mockk() private val mockRCTDeviceEventEmitter = mockk() // non-mocks private val underTest = WebViewInjector( mockReactNativeViewFinder, mockSDKWebViewManager ) @Before fun init() { every { mockReactApplicationContext.currentActivity } returns mockActivity every { mockReactNativeViewFinder.findWebView( mockReactApplicationContext, mockWebViewTag, any() ) } returns Unit mockkObject(VersionUtils) mockkObject(WebViewUtils) } @After fun tearDown() { unmockkAll() } @Test fun `Given find webView with react context and webViewTag, When inject JS, Then call SDK with webview and activity in tag`() { every { webView.setTag( R.string.contentsquare_react_native_web_view_activity_tag, mockActivity ) } returns Unit every { mockSDKWebViewManager.injectWebView(webView) } just runs every { mockReactApplicationContext.hint(RCTDeviceEventEmitter::class) .getJSModule(RCTDeviceEventEmitter::class.java) } returns mockRCTDeviceEventEmitter every { mockRCTDeviceEventEmitter.emit(any(), any()) } just runs // when underTest.injectWebView(mockReactApplicationContext, mockWebViewTag) val webViewFoundListenerSlot = slot() verify { mockReactNativeViewFinder.findWebView( eq(mockReactApplicationContext), eq(mockWebViewTag), capture(webViewFoundListenerSlot) ) } val webViewFoundListener = webViewFoundListenerSlot.captured webViewFoundListener.onWebViewFound(webView) val webViewSlot = slot() verify { mockSDKWebViewManager.injectWebView(capture(webViewSlot)) } val passedWebview = webViewSlot.captured assertEquals(webView, passedWebview) // then verify { webView.setTag( R.string.contentsquare_react_native_web_view_activity_tag, mockActivity ) } verify { mockRCTDeviceEventEmitter.emit("onCSWebViewInjected", null) } } @Test fun `Given a webview and API level 26 and up when page is started and there is a and another url then clear the history`() { every { VersionUtils.isApiLevelAtLeast(Build.VERSION_CODES.O) } returns true every { WebViewUtils.getWebViewClient(any()) } returns webViewClient every { webView.copyBackForwardList() } returns webBackForwardList every { webView.webViewClient = any() } just runs every { webView.setTag( R.string.contentsquare_react_native_web_view_activity_tag, mockActivity ) } returns Unit every { mockSDKWebViewManager.injectWebView(webView) } just runs every { mockReactApplicationContext.hint(RCTDeviceEventEmitter::class) .getJSModule(RCTDeviceEventEmitter::class.java) } returns mockRCTDeviceEventEmitter every { mockRCTDeviceEventEmitter.emit(any(), any()) } just runs every { webView.clearHistory() } just runs every { webBackForwardList.size } returns 2 every { webBackForwardList.getItemAtIndex(0).originalUrl } returns "about:blank" underTest.injectWebView(mockReactApplicationContext, mockWebViewTag) val webViewFoundListenerSlot = slot() verify { mockReactNativeViewFinder.findWebView( eq(mockReactApplicationContext), eq(mockWebViewTag), capture(webViewFoundListenerSlot) ) } val webViewFoundListener = webViewFoundListenerSlot.captured webViewFoundListener.onWebViewFound(webView) val newWebViewClientSlot = slot() verify { webView.webViewClient = capture(newWebViewClientSlot) } val newWebViewClient = newWebViewClientSlot.captured newWebViewClient.onPageStarted(webView, "https://test.com", null) verify { webView.clearHistory() } } @Test fun `Given a webView and API level lower than 26 then it does not clear history`() { every { VersionUtils.isApiLevelAtLeast(Build.VERSION_CODES.O) } returns false verify(exactly = 0) { webView.clearHistory() } } @Test fun `Given no webView is found, When inject JS, Then dont call SDK`() { // when underTest.injectWebView(mockReactApplicationContext, mockWebViewTag) // then verify(exactly = 0) { mockSDKWebViewManager.injectWebView(any()) } } @Test fun `Given find webView with react context and webViewTag, When remove JS, Then call SDK with webview and activity in tag`() { every { webView.setTag( R.string.contentsquare_react_native_web_view_activity_tag, mockActivity ) } returns Unit every { mockSDKWebViewManager.removeWebViewInjection(webView) } just runs // when underTest.removeWebViewInjection(mockReactApplicationContext, mockWebViewTag) val webViewFoundListenerSlot = slot() verify { mockReactNativeViewFinder.findWebView( eq(mockReactApplicationContext), eq(mockWebViewTag), capture(webViewFoundListenerSlot) ) } val webViewFoundListener = webViewFoundListenerSlot.captured webViewFoundListener.onWebViewFound(webView) val webViewSlot = slot() verify { mockSDKWebViewManager.removeWebViewInjection(capture(webViewSlot)) } val passedWebview = webViewSlot.captured assertEquals(webView, passedWebview) // then verify { webView.setTag( R.string.contentsquare_react_native_web_view_activity_tag, mockActivity ) } } @Test fun `Given no webView is found, When remove JS, Then dont call SDK`() { // when underTest.removeWebViewInjection(mockReactApplicationContext, mockWebViewTag) // then verify(exactly = 0) { mockSDKWebViewManager.removeWebViewInjection(any()) } } }