Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 36a6c028 authored by Ellen Poe's avatar Ellen Poe
Browse files

test: PlaceCardViewModelTest and changes for testability

parent 395f3b57
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -612,8 +612,8 @@ private fun PlaceCardRoute(
    val placeJson = backStackEntry.arguments?.getString("place")
    val place = placeJson?.let { Gson().fromJson(it, Place::class.java) }
    place?.let { place ->
        viewModel.setPlace(place)
        LaunchedEffect(place) {
            viewModel.setPlace(place)
            // Clear any existing pins and add the new one to ensure only one pin is shown at a time
            state.mapPins.clear()
            state.mapPins.add(place)
+24 −5
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -59,6 +60,7 @@ import earth.maps.cardinal.R.string
import earth.maps.cardinal.data.AddressFormatter
import earth.maps.cardinal.data.Place
import earth.maps.cardinal.data.format
import kotlinx.coroutines.launch

@Composable
fun PlaceCardScreen(
@@ -104,7 +106,12 @@ fun PlaceCardScreen(
        ) {
            PlaceHeader(displayedPlace)
            PlaceAddress(displayedPlace, addressFormatter)
            PlaceActions(displayedPlace, viewModel, place, onGetDirections) { showUnsaveConfirmationDialog = true }
            PlaceActions(
                displayedPlace,
                viewModel,
                place,
                onGetDirections
            ) { showUnsaveConfirmationDialog = true }
            // Inset horizontal divider
            HorizontalDivider(
                modifier = Modifier
@@ -121,7 +128,11 @@ fun PlaceCardScreen(
            }, onRouteClicked = {})
        }

        UnsaveConfirmationDialog(displayedPlace, viewModel, showUnsaveConfirmationDialog) { showUnsaveConfirmationDialog = false }
        UnsaveConfirmationDialog(
            displayedPlace,
            viewModel,
            showUnsaveConfirmationDialog
        ) { showUnsaveConfirmationDialog = false }
    }
}

@@ -186,6 +197,8 @@ private fun PlaceActions(
            Text(stringResource(string.get_directions))
        }

        val coroutineScope = rememberCoroutineScope()

        // Save/Unsave button
        Button(
            onClick = {
@@ -193,8 +206,10 @@ private fun PlaceActions(
                    // Show confirmation dialog for unsaving
                    onShowUnsaveDialog()
                } else {
                    coroutineScope.launch {
                        viewModel.savePlace(place)
                    }
                }
            }, modifier = Modifier.padding(start = dimensionResource(dimen.padding_minor))
        ) {
            Row(
@@ -228,6 +243,8 @@ private fun UnsaveConfirmationDialog(
    show: Boolean,
    onDismiss: () -> Unit
) {
    val coroutineScope = rememberCoroutineScope()

    if (show) {
        AlertDialog(
            onDismissRequest = onDismiss,
@@ -242,8 +259,10 @@ private fun UnsaveConfirmationDialog(
            confirmButton = {
                TextButton(
                    onClick = {
                        coroutineScope.launch {
                            viewModel.unsavePlace(displayedPlace)
                            onDismiss()
                        }
                    }) {
                    Text(stringResource(string.unsave_place))
                }
+12 −20
Original line number Diff line number Diff line
@@ -20,11 +20,9 @@ package earth.maps.cardinal.ui.place

import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import earth.maps.cardinal.data.Place
import earth.maps.cardinal.data.room.SavedPlaceRepository
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
@@ -35,33 +33,27 @@ class PlaceCardViewModel @Inject constructor(
    val isPlaceSaved = mutableStateOf(false)
    val place = mutableStateOf<Place?>(null)

    fun setPlace(place: Place) {
    suspend fun setPlace(place: Place) {
        this.place.value = place
        checkIfPlaceIsSaved(place)
    }

    fun checkIfPlaceIsSaved(place: Place) {
        viewModelScope.launch {
    suspend fun checkIfPlaceIsSaved(place: Place) {
        if (place.id != null) {
            val existingPlace = savedPlaceRepository.getPlaceById(place.id).getOrNull()
            isPlaceSaved.value = existingPlace != null
        }
    }
    }

    fun savePlace(place: Place) {
        viewModelScope.launch {
    suspend fun savePlace(place: Place) {
        savedPlaceRepository.savePlace(place)
        isPlaceSaved.value = true
    }
    }

    fun unsavePlace(place: Place) {
        viewModelScope.launch {
    suspend fun unsavePlace(place: Place) {
        place.id?.let { id ->
            savedPlaceRepository.deletePlace(placeId = id)
        }
        isPlaceSaved.value = false
    }
}
}
+148 −0
Original line number Diff line number Diff line
package earth.maps.cardinal.ui.place

import earth.maps.cardinal.data.Place
import earth.maps.cardinal.data.room.SavedPlace
import earth.maps.cardinal.data.room.SavedPlaceRepository
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class PlaceCardViewModelTest {

    private lateinit var viewModel: PlaceCardViewModel

    private val mockSavedPlaceRepository = mockk<SavedPlaceRepository>()

    @Before
    fun setup() {
        viewModel = PlaceCardViewModel(
            savedPlaceRepository = mockSavedPlaceRepository
        )
    }

    @Test
    fun `setPlace should update place and check if place is saved`() = runTest {
        val testPlace = Place(
            id = "1",
            name = "Test Place",
            latLng = earth.maps.cardinal.data.LatLng(0.0, 0.0),
            address = null
        )
        val savedPlace = SavedPlace.fromPlace(testPlace)

        coEvery { mockSavedPlaceRepository.getPlaceById("1") } returns Result.success(savedPlace)

        viewModel.setPlace(testPlace)

        assertThat(viewModel.place.value).isEqualTo(testPlace)
        assertThat(viewModel.isPlaceSaved.value).isTrue()
        coVerify { mockSavedPlaceRepository.getPlaceById("1") }
    }

    @Test
    fun `checkIfPlaceIsSaved should set isPlaceSaved to true when place exists`() = runTest {
        val testPlace = Place(
            id = "1",
            name = "Test Place",
            latLng = earth.maps.cardinal.data.LatLng(0.0, 0.0),
            address = null
        )
        val savedPlace = SavedPlace.fromPlace(testPlace)

        coEvery { mockSavedPlaceRepository.getPlaceById("1") } returns Result.success(savedPlace)

        viewModel.checkIfPlaceIsSaved(testPlace)

        assertThat(viewModel.isPlaceSaved.value).isTrue()
    }

    @Test
    fun `checkIfPlaceIsSaved should set isPlaceSaved to false when place does not exist`() = runTest {
        val testPlace = Place(
            id = "1",
            name = "Test Place",
            latLng = earth.maps.cardinal.data.LatLng(0.0, 0.0),
            address = null
        )

        coEvery { mockSavedPlaceRepository.getPlaceById("1") } returns Result.success(null)

        viewModel.checkIfPlaceIsSaved(testPlace)

        assertThat(viewModel.isPlaceSaved.value).isFalse()
    }

    @Test
    fun `checkIfPlaceIsSaved should not check when place id is null`() = runTest {
        val testPlace = Place(
            id = null,
            name = "Test Place",
            latLng = earth.maps.cardinal.data.LatLng(0.0, 0.0),
            address = null
        )

        viewModel.checkIfPlaceIsSaved(testPlace)

        assertThat(viewModel.isPlaceSaved.value).isFalse()
        coVerify(exactly = 0) { mockSavedPlaceRepository.getPlaceById(any()) }
    }

    @Test
    fun `savePlace should save place and set isPlaceSaved to true`() = runTest {
        val testPlace = Place(
            id = "1",
            name = "Test Place",
            latLng = earth.maps.cardinal.data.LatLng(0.0, 0.0),
            address = null
        )

        coEvery { mockSavedPlaceRepository.savePlace(testPlace) } returns Result.success("1")

        viewModel.savePlace(testPlace)

        coVerify { mockSavedPlaceRepository.savePlace(testPlace) }
        assertThat(viewModel.isPlaceSaved.value).isTrue()
    }

    @Test
    fun `unsavePlace should delete place and set isPlaceSaved to false`() = runTest {
        val testPlace = Place(
            id = "1",
            name = "Test Place",
            latLng = earth.maps.cardinal.data.LatLng(0.0, 0.0),
            address = null
        )

        coEvery { mockSavedPlaceRepository.deletePlace("1") } returns Result.success(Unit)

        viewModel.unsavePlace(testPlace)

        coVerify { mockSavedPlaceRepository.deletePlace(placeId = "1") }
        assertThat(viewModel.isPlaceSaved.value).isFalse()
    }

    @Test
    fun `unsavePlace should not delete when place id is null`() = runTest {
        val testPlace = Place(
            id = null,
            name = "Test Place",
            latLng = earth.maps.cardinal.data.LatLng(0.0, 0.0),
            address = null
        )

        viewModel.unsavePlace(testPlace)

        coVerify(exactly = 0) { mockSavedPlaceRepository.deletePlace(any()) }
        assertThat(viewModel.isPlaceSaved.value).isFalse()
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ ferrostar = "0.41.0"
okhttp3 = "5.1.0"
material3 = "1.5.0-alpha04"
detekt = "2.0.0-alpha.0"
mockk = "1.13.16"
mockk = "1.14.6"
kotlinxCoroutinesTest = "1.10.2"
hiltAndroidTesting = "2.57.1"