From 07d170a79c9496fea0326e2284b5fe56ea2b01a8 Mon Sep 17 00:00:00 2001 From: Madhur Gupta <30932899+madhurgupta10@users.noreply.github.com> Date: Mon, 20 Sep 2021 12:30:27 +0530 Subject: [PATCH] Add MediaDetailFragment Unit Tests (#4632) * Add MediaDetailFragment Unit Tests * Add ZoomableActivity Unit Tests --- .../commons/media/MediaDetailFragment.java | 2 +- .../nrw/commons/media/ZoomableActivity.java | 25 +- .../media/MediaDetailFragmentUnitTests.kt | 473 ++++++++++++++++++ .../media/ZoomableActivityUnitTests.kt | 48 ++ .../free/nrw/commons/utils/PagedListMock.kt | 10 +- 5 files changed, 538 insertions(+), 20 deletions(-) create mode 100644 app/src/test/kotlin/fr/free/nrw/commons/media/MediaDetailFragmentUnitTests.kt create mode 100644 app/src/test/kotlin/fr/free/nrw/commons/media/ZoomableActivityUnitTests.kt diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java index ed28960b2..289a859cc 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java @@ -306,7 +306,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements final View view = inflater.inflate(R.layout.fragment_media_detail, container, false); ButterKnife.bind(this,view); - Utils.setUnderlinedText(seeMore, R.string.nominated_see_more, container.getContext()); + Utils.setUnderlinedText(seeMore, R.string.nominated_see_more, requireContext()); if (isCategoryImage){ authorLayout.setVisibility(VISIBLE); diff --git a/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.java b/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.java index 63b582550..286384ff1 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.java @@ -1,32 +1,27 @@ package fr.free.nrw.commons.media; -import androidx.annotation.NonNull; +import android.graphics.drawable.Animatable; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.widget.ProgressBar; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import butterknife.BindView; import butterknife.ButterKnife; -import fr.free.nrw.commons.R; -import com.facebook.drawee.drawable.ProgressBarDrawable; -import fr.free.nrw.commons.media.zoomControllers.zoomable.DoubleTapGestureListener; -import fr.free.nrw.commons.media.zoomControllers.zoomable.ZoomableDraweeView; -import timber.log.Timber; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.Animatable; -import android.net.Uri; -import android.os.Bundle; import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.controller.BaseControllerListener; import com.facebook.drawee.controller.ControllerListener; +import com.facebook.drawee.drawable.ProgressBarDrawable; import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.generic.GenericDraweeHierarchy; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.interfaces.DraweeController; -import android.view.View; -import android.widget.ProgressBar; - import com.facebook.imagepipeline.image.ImageInfo; +import fr.free.nrw.commons.R; +import fr.free.nrw.commons.media.zoomControllers.zoomable.DoubleTapGestureListener; +import fr.free.nrw.commons.media.zoomControllers.zoomable.ZoomableDraweeView; +import timber.log.Timber; public class ZoomableActivity extends AppCompatActivity { diff --git a/app/src/test/kotlin/fr/free/nrw/commons/media/MediaDetailFragmentUnitTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/media/MediaDetailFragmentUnitTests.kt new file mode 100644 index 000000000..e54762405 --- /dev/null +++ b/app/src/test/kotlin/fr/free/nrw/commons/media/MediaDetailFragmentUnitTests.kt @@ -0,0 +1,473 @@ +package fr.free.nrw.commons.media + +import android.content.Context +import android.content.res.Configuration +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewTreeObserver +import android.webkit.WebView +import android.widget.* +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentTransaction +import androidx.recyclerview.widget.RecyclerView +import com.facebook.drawee.backends.pipeline.Fresco +import com.facebook.drawee.generic.GenericDraweeHierarchy +import com.facebook.drawee.view.SimpleDraweeView +import com.facebook.soloader.SoLoader +import fr.free.nrw.commons.Media +import fr.free.nrw.commons.R +import fr.free.nrw.commons.TestAppAdapter +import fr.free.nrw.commons.TestCommonsApplication +import fr.free.nrw.commons.category.CategoryEditSearchRecyclerViewAdapter +import fr.free.nrw.commons.explore.SearchActivity +import fr.free.nrw.commons.kvstore.JsonKvStore +import fr.free.nrw.commons.location.LatLng +import fr.free.nrw.commons.ui.widget.HtmlTextView +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations +import org.powermock.reflect.Whitebox +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import org.wikipedia.AppAdapter +import java.lang.reflect.Field +import java.lang.reflect.Method +import java.util.* +import kotlin.collections.HashMap + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [21], application = TestCommonsApplication::class) +@LooperMode(LooperMode.Mode.PAUSED) +class MediaDetailFragmentUnitTests { + + private lateinit var fragment: MediaDetailFragment + private lateinit var fragmentManager: FragmentManager + private lateinit var layoutInflater: LayoutInflater + private lateinit var view: View + private lateinit var context: Context + + @Mock + private lateinit var categoryEditSearchRecyclerViewAdapter: CategoryEditSearchRecyclerViewAdapter + + @Mock + private lateinit var savedInstanceState: Bundle + + @Mock + private lateinit var scrollView: ScrollView + + @Mock + private lateinit var media: Media + + @Mock + private lateinit var categoryRecyclerView: RecyclerView + + @Mock + private lateinit var simpleDraweeView: SimpleDraweeView + + @Mock + private lateinit var textView: TextView + + @Mock + private lateinit var htmlTextView: HtmlTextView + + @Mock + private lateinit var linearLayout: LinearLayout + + @Mock + private lateinit var genericDraweeHierarchy: GenericDraweeHierarchy + + @Mock + private lateinit var button: Button + + @Mock + private lateinit var detailProvider: MediaDetailPagerFragment.MediaDetailProvider + + @Mock + private lateinit var applicationKvStore: JsonKvStore + + @Mock + private lateinit var webView: WebView + + @Mock + private lateinit var progressBar: ProgressBar + + @Mock + private lateinit var listView: ListView + + @Mock + private lateinit var searchView: SearchView + + @Before + fun setUp() { + + MockitoAnnotations.initMocks(this) + + context = RuntimeEnvironment.application.applicationContext + + AppAdapter.set(TestAppAdapter()) + + SoLoader.setInTestMode() + + Fresco.initialize(RuntimeEnvironment.application.applicationContext) + + val activity = Robolectric.buildActivity(SearchActivity::class.java).create().get() + + fragment = MediaDetailFragment() + fragmentManager = activity.supportFragmentManager + val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction() + fragmentTransaction.add(fragment, null) + fragmentTransaction.commitNowAllowingStateLoss() + + layoutInflater = LayoutInflater.from(activity) + + view = LayoutInflater.from(activity) + .inflate(R.layout.fragment_media_detail, null) as View + + scrollView = view.findViewById(R.id.mediaDetailScrollView) + Whitebox.setInternalState(fragment, "scrollView", scrollView) + + categoryRecyclerView = view.findViewById(R.id.rv_categories) + Whitebox.setInternalState(fragment, "categoryRecyclerView", categoryRecyclerView) + + Whitebox.setInternalState(fragment, "media", media) + Whitebox.setInternalState(fragment, "progressBar", progressBar) + Whitebox.setInternalState(fragment, "captionsListView", listView) + Whitebox.setInternalState(fragment, "descriptionWebView", webView) + Whitebox.setInternalState(fragment, "detailProvider", detailProvider) + Whitebox.setInternalState(fragment, "image", simpleDraweeView) + Whitebox.setInternalState(fragment, "title", textView) + Whitebox.setInternalState(fragment, "toDoReason", textView) + Whitebox.setInternalState(fragment, "desc", htmlTextView) + Whitebox.setInternalState(fragment, "license", textView) + Whitebox.setInternalState(fragment, "coordinates", textView) + Whitebox.setInternalState(fragment, "seeMore", textView) + Whitebox.setInternalState(fragment, "uploadedDate", textView) + Whitebox.setInternalState(fragment, "mediaCaption", textView) + Whitebox.setInternalState(fragment, "captionLayout", linearLayout) + Whitebox.setInternalState(fragment, "depictsLayout", linearLayout) + Whitebox.setInternalState(fragment, "depictionContainer", linearLayout) + Whitebox.setInternalState(fragment, "toDoLayout", linearLayout) + Whitebox.setInternalState(fragment, "dummyCategoryEditContainer", linearLayout) + Whitebox.setInternalState(fragment, "showCaptionAndDescriptionContainer", linearLayout) + Whitebox.setInternalState(fragment, "updateCategoriesButton", button) + Whitebox.setInternalState(fragment, "categoryContainer", linearLayout) + Whitebox.setInternalState(fragment, "categorySearchView", searchView) + Whitebox.setInternalState(fragment, "mediaDiscussion", textView) + Whitebox.setInternalState( + fragment, + "categoryEditSearchRecyclerViewAdapter", + categoryEditSearchRecyclerViewAdapter + ) + + `when`(simpleDraweeView.hierarchy).thenReturn(genericDraweeHierarchy) + val map = HashMap() + map[Locale.getDefault().language] = "" + `when`(media.descriptions).thenReturn(map) + } + + @Test + @Throws(Exception::class) + fun checkFragmentNotNull() { + Assert.assertNotNull(fragment) + } + + @Test + @Throws(Exception::class) + fun testOnCreateView() { + Whitebox.setInternalState(fragment, "applicationKvStore", applicationKvStore) + `when`(applicationKvStore.getBoolean("login_skipped")).thenReturn(true) + fragment.onCreateView(layoutInflater, null, savedInstanceState) + } + + @Test + @Throws(Exception::class) + fun testOnSaveInstanceState() { + fragment.onSaveInstanceState(savedInstanceState) + } + + @Test + @Throws(Exception::class) + fun testLaunchZoomActivity() { + `when`(media.imageUrl).thenReturn("") + fragment.launchZoomActivity(view) + } + + @Test + @Throws(Exception::class) + fun testOnResume() { + fragment.onResume() + } + + @Test + @Throws(Exception::class) + fun testOnConfigurationChangedCaseTrue() { + val newConfig = mock(Configuration::class.java) + fragment.onConfigurationChanged(newConfig) + } + + @Test + @Throws(Exception::class) + fun testOnConfigurationChangedCaseFalse() { + val newConfig = mock(Configuration::class.java) + Whitebox.setInternalState(fragment, "heightVerifyingBoolean", false) + fragment.onConfigurationChanged(newConfig) + } + + @Test + @Throws(Exception::class) + fun testOnDestroyView() { + val layoutListener = mock(ViewTreeObserver.OnGlobalLayoutListener::class.java) + Whitebox.setInternalState(fragment, "layoutListener", layoutListener) + fragment.onDestroyView() + } + + @Test + @Throws(Exception::class) + fun testExtractDescription() { + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "extractDescription", + String::class.java + ) + method.isAccessible = true + method.invoke(fragment, "") + } + + @Test + @Throws(Exception::class) + fun testGetDescription() { + `when`(media.filename).thenReturn("") + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "getDescription" + ) + method.isAccessible = true + method.invoke(fragment) + } + + @Test + @Throws(Exception::class) + fun testGetCaptions() { + `when`(media.captions).thenReturn(mapOf(Pair("a", "b"))) + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "getCaptions" + ) + method.isAccessible = true + method.invoke(fragment) + } + + @Test + @Throws(Exception::class) + fun testGetCaptionsCaseEmpty() { + `when`(media.captions).thenReturn(mapOf()) + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "getCaptions" + ) + method.isAccessible = true + method.invoke(fragment) + } + + @Test + @Throws(Exception::class) + fun testSetUpCaptionAndDescriptionLayout() { + `when`(media.filename).thenReturn("") + val field: Field = + MediaDetailFragment::class.java.getDeclaredField("descriptionHtmlCode") + field.isAccessible = true + field.set(fragment, null) + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "setUpCaptionAndDescriptionLayout" + ) + method.isAccessible = true + method.invoke(fragment) + } + + @Test + @Throws(Exception::class) + fun testUpdateCategoryDisplayCaseNull() { + Assert.assertEquals(fragment.updateCategoryDisplay(null), false) + } + + @Test + @Throws(Exception::class) + fun testUpdateCategoryDisplayCaseNonNull() { + Assert.assertEquals(fragment.updateCategoryDisplay(listOf()), true) + } + + @Test + @Throws(Exception::class) + fun testShowCaptionAndDescriptionCaseVisible() { + fragment.showCaptionAndDescription() + } + + @Test + @Throws(Exception::class) + fun testShowCaptionAndDescription() { + `when`(linearLayout.visibility).thenReturn(View.GONE) + `when`(media.filename).thenReturn("") + fragment.showCaptionAndDescription() + } + + @Test + @Throws(Exception::class) + fun testPrettyCoordinatesCaseNull() { + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "prettyCoordinates", + Media::class.java + ) + method.isAccessible = true + method.invoke(fragment, media) + } + + @Test + @Throws(Exception::class) + fun testPrettyCoordinates() { + `when`(media.coordinates).thenReturn(LatLng(-0.000001, -0.999999, 0f)) + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "prettyCoordinates", + Media::class.java + ) + method.isAccessible = true + method.invoke(fragment, media) + } + + @Test + @Throws(Exception::class) + fun testPrettyUploadedDateCaseNull() { + `when`(media.dateUploaded).thenReturn(null) + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "prettyUploadedDate", + Media::class.java + ) + method.isAccessible = true + method.invoke(fragment, media) + } + + @Test + @Throws(Exception::class) + fun testPrettyUploadedDateCaseNonNull() { + `when`(media.dateUploaded).thenReturn(Date(2000, 1, 1)) + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "prettyUploadedDate", + Media::class.java + ) + method.isAccessible = true + method.invoke(fragment, media) + } + + @Test + @Throws(Exception::class) + fun testPrettyLicenseCaseNull() { + `when`(media.license).thenReturn(null) + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "prettyLicense", + Media::class.java + ) + method.isAccessible = true + method.invoke(fragment, media) + } + + @Test + @Throws(Exception::class) + fun testPrettyLicenseCaseNonNull() { + `when`(media.license).thenReturn("licence") + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "prettyLicense", + Media::class.java + ) + method.isAccessible = true + method.invoke(fragment, media) + } + + @Test + @Throws(Exception::class) + fun testPrettyDiscussion() { + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "prettyDiscussion", + String::class.java + ) + method.isAccessible = true + method.invoke(fragment, "mock") + } + + @Test + @Throws(Exception::class) + fun testPrettyCaptionCaseEmpty() { + `when`(media.captions).thenReturn(mapOf(Pair("a", ""))) + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "prettyCaption", + Media::class.java + ) + method.isAccessible = true + method.invoke(fragment, media) + } + + @Test + @Throws(Exception::class) + fun testPrettyCaptionCaseNonEmpty() { + `when`(media.captions).thenReturn(mapOf(Pair("a", "b"))) + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "prettyCaption", + Media::class.java + ) + method.isAccessible = true + method.invoke(fragment, media) + } + + @Test + @Throws(Exception::class) + fun testPrettyCaption() { + `when`(media.captions).thenReturn(mapOf()) + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "prettyCaption", + Media::class.java + ) + method.isAccessible = true + method.invoke(fragment, media) + } + + @Test + @Throws(Exception::class) + fun testSetupImageView() { + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "setupImageView" + ) + method.isAccessible = true + method.invoke(fragment) + } + + @Test + @Throws(Exception::class) + fun testSetupToDo() { + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "setupToDo" + ) + method.isAccessible = true + method.invoke(fragment) + } + + @Test + @Throws(Exception::class) + fun testOnDiscussionLoaded() { + val method: Method = MediaDetailFragment::class.java.getDeclaredMethod( + "onDiscussionLoaded", + String::class.java + ) + method.isAccessible = true + method.invoke(fragment, "") + } + + @Test + @Throws(Exception::class) + fun testForMedia() { + MediaDetailFragment.forMedia(0, true, true, true) + } + +} \ No newline at end of file diff --git a/app/src/test/kotlin/fr/free/nrw/commons/media/ZoomableActivityUnitTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/media/ZoomableActivityUnitTests.kt new file mode 100644 index 000000000..14b395973 --- /dev/null +++ b/app/src/test/kotlin/fr/free/nrw/commons/media/ZoomableActivityUnitTests.kt @@ -0,0 +1,48 @@ +package fr.free.nrw.commons.media + +import android.content.Context +import android.content.Intent +import android.net.Uri +import com.facebook.drawee.backends.pipeline.Fresco +import com.facebook.soloader.SoLoader +import fr.free.nrw.commons.TestCommonsApplication +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [21], application = TestCommonsApplication::class) +@LooperMode(LooperMode.Mode.PAUSED) +class ZoomableActivityUnitTests { + + private lateinit var context: Context + private lateinit var activity: ZoomableActivity + + @Mock + private lateinit var uri: Uri + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + context = RuntimeEnvironment.application.applicationContext + SoLoader.setInTestMode() + Fresco.initialize(context) + val intent = Intent().setData(uri) + activity = Robolectric.buildActivity(ZoomableActivity::class.java, intent).create().get() + } + + @Test + @Throws(Exception::class) + fun checkActivityNotNull() { + Assert.assertNotNull(activity) + } + +} \ No newline at end of file diff --git a/app/src/test/kotlin/fr/free/nrw/commons/utils/PagedListMock.kt b/app/src/test/kotlin/fr/free/nrw/commons/utils/PagedListMock.kt index f4a42fa91..39f8f8899 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/utils/PagedListMock.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/utils/PagedListMock.kt @@ -37,7 +37,7 @@ fun createMockDataSourceFactory(itemList: List): DataSource.Factory(private val itemList: List) : - LimitOffsetDataSource(mockDb(), mockQuery(), false, null) { - override fun convertRows(cursor: Cursor?): MutableList = itemList.toMutableList() + LimitOffsetDataSource(mockDb(), mockQuery(), false, "") { + override fun convertRows(cursor: Cursor): MutableList { + return itemList.toMutableList() + } override fun countItems(): Int = itemList.count() override fun isInvalid(): Boolean = false override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback) {