Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: display "No images available" message and improve loading indicator handling in FeedScreen #290

Merged
merged 13 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.github.lookupgroup27.lookup

import android.location.Location
import androidx.test.core.app.ApplicationProvider
import com.github.lookupgroup27.lookup.model.location.LocationProvider

/** TestLocationProvider allows for manual setting of location values. */
class TestLocationProvider : LocationProvider(ApplicationProvider.getApplicationContext()) {
fun setLocation(latitude: Double?, longitude: Double?) {
if (latitude != null && longitude != null) {
currentLocation.value =
Location("test").apply {
this.latitude = latitude
this.longitude = longitude
}
} else {
currentLocation.value = null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.test.core.app.ApplicationProvider
import androidx.test.rule.GrantPermissionRule
import com.github.lookupgroup27.lookup.TestLocationProvider
import com.github.lookupgroup27.lookup.model.location.LocationProvider
import com.github.lookupgroup27.lookup.model.location.LocationProviderSingleton
import com.github.lookupgroup27.lookup.model.post.Post
import com.github.lookupgroup27.lookup.model.post.PostsRepository
import com.github.lookupgroup27.lookup.model.profile.ProfileRepository
Expand All @@ -22,6 +24,8 @@ import com.github.lookupgroup27.lookup.ui.navigation.TopLevelDestinations
import com.github.lookupgroup27.lookup.ui.post.PostsViewModel
import com.github.lookupgroup27.lookup.ui.profile.ProfileViewModel
import com.google.firebase.auth.FirebaseAuth
import io.mockk.every
import io.mockk.mockkObject
import org.junit.Before
import org.junit.Rule
import org.junit.Test
Expand Down Expand Up @@ -138,18 +142,30 @@ class FeedScreenTest {
`when`(navigationActions.currentRoute()).thenReturn(Screen.FEED)

locationProvider = LocationProvider(context, mutableStateOf(null))
}

/**
* Helper function to set up the FeedScreen with the given list of nearby posts.
*
* @param initialNearbyPosts The list of posts to display initially on the feed screen.
*
* This function simplifies test setup by allowing each test to specify the initial state of the
* feed. It handles the rendering of the FeedScreen with the specified posts and ensures a
* consistent setup across all tests.
*/
private fun setFeedScreenContent(initialNearbyPosts: List<Post>) {
composeTestRule.setContent {
FeedScreen(
postsViewModel = postsViewModel,
navigationActions = navigationActions,
profileViewModel = profileViewModel,
initialNearbyPosts = testPosts)
initialNearbyPosts = initialNearbyPosts)
}
}

@Test
fun testFeedScreenDisplaysNearbyPosts() {
setFeedScreenContent(testPosts)

// Assert each post item is displayed
composeTestRule.onNodeWithTag("PostItem_2").assertExists()
Expand All @@ -163,12 +179,14 @@ class FeedScreenTest {

@Test
fun testFeedExcludesLoggedInUserPosts() {
setFeedScreenContent(testPosts)
// Assert that the post by the logged-in user is not displayed
composeTestRule.onNodeWithTag("PostItem_1").assertDoesNotExist()
}

@Test
fun testBottomNavigationMenuIsDisplayed() {
setFeedScreenContent(testPosts)

// Verify the bottom navigation menu is displayed
composeTestRule
Expand All @@ -179,6 +197,7 @@ class FeedScreenTest {

@Test
fun testStarClickDisplaysAverageRating() {
setFeedScreenContent(testPosts)
// Perform click on the first star icon of a post with uid "1"
composeTestRule
.onNodeWithTag("Star_2_2")
Expand All @@ -191,6 +210,7 @@ class FeedScreenTest {

@Test
fun testStarClickCallsUpdatePost() {
setFeedScreenContent(testPosts)
// Perform click on the first star of post with uid "1"
composeTestRule.onNodeWithTag("Star_2_2").performClick()
postsViewModel.updatePost(testPost)
Expand All @@ -211,6 +231,7 @@ class FeedScreenTest {

@Test
fun testNavigationToFeedBlockedForLoggedOutUser() {
setFeedScreenContent(testPosts)
// Mock the user as not logged in
mockAuth = org.mockito.kotlin.mock()
whenever(mockAuth.currentUser).thenReturn(null)
Expand All @@ -224,15 +245,74 @@ class FeedScreenTest {

@Test
fun testAddressIsDisplayed() {
setFeedScreenContent(testPosts)
// Verify that the address is displayed for each post
composeTestRule.onNodeWithTag("AddressTag_2").performScrollTo().assertIsDisplayed()
composeTestRule.onNodeWithTag("AddressTag_5").assertDoesNotExist()
}

@Test
fun testDescriptionIsDisplayed() {
setFeedScreenContent(testPosts)
// Verify that the description is displayed for each post
composeTestRule.onNodeWithTag("DescriptionTag_2").performScrollTo().assertIsDisplayed()
composeTestRule.onNodeWithTag("DescriptionTag_5").assertDoesNotExist()
}

@Test
fun testFeedDisplaysNoImagesMessageWhenPostsAreEmpty() {
// Arrange: Mock location provider and permissions
val testLocationProvider = TestLocationProvider()
testLocationProvider.setLocation(37.7749, -122.4194) // Mocked location

mockkObject(LocationProviderSingleton)
every { LocationProviderSingleton.getInstance(any()) } returns testLocationProvider

// Act: Render the FeedScreen with no posts
setFeedScreenContent(emptyList())

// Wait for the location to emit
composeTestRule.waitForIdle()

// Assert: "No images available" message is displayed
composeTestRule.onNodeWithTag("feed_no_images_available").assertExists().assertIsDisplayed()
}

@Test
fun testFeedDisplaysLoadingIndicatorWhenLocationIsNull() {
// Arrange: Mock location provider and permissions
val testLocationProvider = TestLocationProvider()
testLocationProvider.setLocation(null, null) // No location emitted

mockkObject(LocationProviderSingleton)
every { LocationProviderSingleton.getInstance(any()) } returns testLocationProvider

// Act: Render the FeedScreen
setFeedScreenContent(emptyList())

// Wait for the composition to stabilize
composeTestRule.waitForIdle()

// Assert: Loading indicator (CircularProgressIndicator) is displayed
composeTestRule.onNodeWithTag("loading_indicator_test_tag").assertExists().assertIsDisplayed()
}

@Test
fun testFeedDisplaysNoImagesMessageWithPlaceholderImage() {
// Arrange: Mock location provider and permissions
val testLocationProvider = TestLocationProvider()
testLocationProvider.setLocation(37.7749, -122.4194) // Mocked location

mockkObject(LocationProviderSingleton)
every { LocationProviderSingleton.getInstance(any()) } returns testLocationProvider

// Act: Render the FeedScreen with no posts
setFeedScreenContent(emptyList())

// Wait for any updates to complete
composeTestRule.waitForIdle()

// Assert: Placeholder image is displayed
composeTestRule.onNodeWithTag("no_images_placeholder").assertExists().assertIsDisplayed()
}
}
29 changes: 25 additions & 4 deletions app/src/main/java/com/github/lookupgroup27/lookup/ui/feed/Feed.kt
Original file line number Diff line number Diff line change
Expand Up @@ -164,17 +164,38 @@ fun FeedScreen(
if (nearbyPosts.isEmpty()) {
// Loading or empty state
Box(
modifier =
Modifier.fillMaxSize()
.testTag(stringResource(R.string.loading_indicator_test_tag)),
modifier = Modifier.fillMaxSize().testTag("loading_indicator_test_tag"),
contentAlignment = Alignment.Center) {
if (!locationPermissionGranted) {
Text(
text = stringResource(R.string.location_permission_required),
style =
MaterialTheme.typography.bodyLarge.copy(color = Color.White))
} else if (locationProvider.currentLocation.value == null) {
CircularProgressIndicator(
color = Color.White) // Still fetching location
} else {
CircularProgressIndicator(color = Color.White)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center) {
// Add PNG image above the message
Image(
painter = painterResource(R.drawable.no_images_placeholder),
contentDescription =
stringResource(R.string.feed_no_images_available),
modifier =
Modifier.size(180.dp).testTag("no_images_placeholder"))

Spacer(modifier = Modifier.height(16.dp))

// Display "No images available" message
Text(
text = stringResource(R.string.feed_no_images_available),
modifier = Modifier.testTag("feed_no_images_available"),
style =
MaterialTheme.typography.bodyLarge.copy(
color = Color.White))
}
}
}
} else {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@
<string name="map_button_reset_text">Reset</string>
<string name="map_slider_test_tag">map_slider_tag</string>
<string name="nearby_posts_title">Nearby Stargazers</string>
<string name="feed_no_images_available">No images are available</string>
</resources>
Loading