Skip to content

Commit

Permalink
Added tests for OfflineAreasFragment (#2639)
Browse files Browse the repository at this point in the history
  • Loading branch information
anandwana001 authored Aug 22, 2024
1 parent 1b759a4 commit b1947aa
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.ground.ui.offlineareas

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.google.android.ground.BaseHiltTest
import com.google.android.ground.R
import com.google.android.ground.launchFragmentInHiltContainer
import com.google.android.ground.persistence.local.stores.LocalOfflineAreaStore
import com.google.android.ground.util.recyclerview.atPositionOnView
import com.sharedtest.FakeData.OFFLINE_AREA
import dagger.hilt.android.testing.HiltAndroidTest
import javax.inject.Inject
import kotlinx.coroutines.test.advanceUntilIdle
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@HiltAndroidTest
@RunWith(RobolectricTestRunner::class)
class OfflineAreasFragmentTest : BaseHiltTest() {

private lateinit var fragment: OfflineAreasFragment
@Inject lateinit var localOfflineAreaStore: LocalOfflineAreaStore

@Before
override fun setUp() {
super.setUp()
launchFragmentInHiltContainer<OfflineAreasFragment> { fragment = this as OfflineAreasFragment }
}

@Test
fun `Toolbar text is displayed`() {
onView(withId(R.id.offline_areas_toolbar))
.check(matches(hasDescendant(withText(fragment.getString(R.string.offline_map_imagery)))))
}

@Test
fun `Heading text is displayed`() {
onView(withId(R.id.offline_areas_list_title))
.check(matches(withText(fragment.getString(R.string.offline_downloaded_areas))))
onView(withId(R.id.offline_areas_list_tip))
.check(matches(withText(fragment.getString(R.string.offline_area_list_tip))))
onView(withId(R.id.no_areas_downloaded_message))
.check(matches(withText(fragment.getString(R.string.no_basemaps_downloaded))))
}

@Test
fun `List is Displayed`() = runWithTestDispatcher {
localOfflineAreaStore.insertOrUpdate(OFFLINE_AREA)

advanceUntilIdle()
onView(withId(R.id.offline_areas_list)).check(matches(isDisplayed()))

verifyTextOnOfflineAreasListItemAtPosition(
itemPosition = 0,
targetView = R.id.offline_area_list_item_name,
stringToMatch = "Test Area",
)
verifyTextOnOfflineAreasListItemAtPosition(
itemPosition = 0,
targetView = R.id.offline_area_list_item_size,
stringToMatch = "<1\u00A0MB",
)
}

private fun verifyTextOnOfflineAreasListItemAtPosition(
itemPosition: Int,
targetView: Int,
stringToMatch: String,
) {
onView(
atPositionOnView(
recyclerViewId = R.id.offline_areas_list,
position = itemPosition,
targetViewId = targetView,
)
)
.check(matches(withText(stringToMatch)))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.ground.util.drwable

import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.ImageView
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher

// https://github.com/dbottillo/Blog/blob/espresso_match_imageview/app/src/androidTest/java/com/danielebottillo/blog/config/DrawableMatcher.java
/**
* This class mainly provides a custom matcher to test whether the drawable-image is correctly shown
* in ImageView.
*/
private class DrawableMatcher(private val expectedId: Int) :
TypeSafeMatcher<View>(View::class.java) {

private var resourceName: String? = null

override fun matchesSafely(target: View): Boolean {
if (target !is ImageView) {
return false
}
if (expectedId == NONE) {
return target.drawable == null
}
if (expectedId == ANY) {
return target.drawable != null
}
val resources = target.context.resources
val expectedDrawable = resources.getDrawable(expectedId, target.context.theme)
resourceName = resources.getResourceEntryName(expectedId)

if (expectedDrawable == null) {
return false
}

val targetBitmap = getBitmap(target.drawable)
val expectedBitmap = getBitmap(expectedDrawable)
return targetBitmap.sameAs(expectedBitmap)
}

private fun getBitmap(drawable: Drawable): Bitmap {
val targetBitmap =
Bitmap.createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888,
)
val canvas = Canvas(targetBitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return targetBitmap
}

override fun describeTo(description: Description) {
description.appendText("with drawable from resource id: ")
description.appendValue(expectedId)
if (resourceName != null) {
description.appendText("[")
description.appendText(resourceName)
description.appendText("]")
}
}

companion object {
const val NONE = -1
const val ANY = -2
}
}

fun withDrawable(resourceId: Int): Matcher<View> = DrawableMatcher(resourceId)
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.ground.util.recyclerview

import android.content.res.Resources
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher

fun atPositionOnView(recyclerViewId: Int, position: Int, targetViewId: Int): Matcher<View> =
object : TypeSafeMatcher<View>() {
var resources: Resources? = null
var childView: View? = null

override fun describeTo(description: Description) {
var idDescription = recyclerViewId.toString()
if (this.resources != null) {
idDescription =
try {
this.resources!!.getResourceName(recyclerViewId)
} catch (exception: Resources.NotFoundException) {
"$recyclerViewId (resource name not found) \n ${exception.message}"
}
}
description.appendText("with id: $idDescription")
}

public override fun matchesSafely(view: View): Boolean {
this.resources = view.resources
if (childView == null) {
val recyclerView = view.rootView.findViewById<View>(recyclerViewId) as? RecyclerView
if (recyclerView?.id == recyclerViewId) {
childView = recyclerView.findViewHolderForAdapterPosition(position)?.itemView
} else return false
}
return if (targetViewId == -1) {
view === childView
} else {
view === childView?.findViewById<View>(targetViewId)
}
}
}

0 comments on commit b1947aa

Please sign in to comment.