From fc3672b17b4b660e6121cc27c6f3a3c557ac63f3 Mon Sep 17 00:00:00 2001 From: Geoffroy Bonneville <24917789+wismna@users.noreply.github.com> Date: Thu, 6 Nov 2025 20:49:46 -0500 Subject: [PATCH] Add DueTodayViewModel Unit Tests --- donext.backup | Bin 0 -> 7764 bytes donextv2/build.gradle.kts | 3 + .../viewmodel/DueTodayViewModelTest.kt | 111 ++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 donext.backup create mode 100644 donextv2/src/test/java/com/wismna/geoffroy/donext/presentation/viewmodel/DueTodayViewModelTest.kt diff --git a/donext.backup b/donext.backup new file mode 100644 index 0000000000000000000000000000000000000000..c33c48f468cc3a263d6462ebde9a49e6f5f88ba3 GIT binary patch literal 7764 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 literal 0 HcmV?d00001 diff --git a/donextv2/build.gradle.kts b/donextv2/build.gradle.kts index 8da0d34..0aeda86 100644 --- a/donextv2/build.gradle.kts +++ b/donextv2/build.gradle.kts @@ -80,6 +80,9 @@ dependencies { implementation("androidx.navigation:navigation-compose:2.9.5") implementation("androidx.hilt:hilt-navigation-compose:1.3.0") implementation("sh.calvin.reorderable:reorderable:3.0.0") + testImplementation("junit:junit:4.13.2") + testImplementation("io.mockk:mockk:1.13.12") + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0") androidTestImplementation("androidx.test.ext:junit-ktx:1.3.0") androidTestImplementation(platform("androidx.compose:compose-bom:2025.10.01")) androidTestImplementation("androidx.compose.ui:ui-test-junit4") diff --git a/donextv2/src/test/java/com/wismna/geoffroy/donext/presentation/viewmodel/DueTodayViewModelTest.kt b/donextv2/src/test/java/com/wismna/geoffroy/donext/presentation/viewmodel/DueTodayViewModelTest.kt new file mode 100644 index 0000000..1428e93 --- /dev/null +++ b/donextv2/src/test/java/com/wismna/geoffroy/donext/presentation/viewmodel/DueTodayViewModelTest.kt @@ -0,0 +1,111 @@ +package com.wismna.geoffroy.donext.presentation.viewmodel + +import com.wismna.geoffroy.donext.R +import com.wismna.geoffroy.donext.domain.model.Priority +import com.wismna.geoffroy.donext.domain.model.Task +import com.wismna.geoffroy.donext.domain.usecase.GetDueTodayTasksUseCase +import com.wismna.geoffroy.donext.domain.usecase.ToggleTaskDeletedUseCase +import com.wismna.geoffroy.donext.domain.usecase.ToggleTaskDoneUseCase +import com.wismna.geoffroy.donext.presentation.ui.events.UiEvent +import com.wismna.geoffroy.donext.presentation.ui.events.UiEventBus +import io.mockk.* +import junit.framework.TestCase.assertEquals +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.test.* +import org.junit.After +import org.junit.Before +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class DueTodayViewModelTest { + + private lateinit var getDueTodayTasksUseCase: GetDueTodayTasksUseCase + private lateinit var toggleTaskDeletedUseCase: ToggleTaskDeletedUseCase + private lateinit var toggleTaskDoneUseCase: ToggleTaskDoneUseCase + private lateinit var uiEventBus: UiEventBus + private lateinit var viewModel: DueTodayViewModel + + private val testDispatcher = StandardTestDispatcher() + private val tasksFlow = MutableSharedFlow>(replay = 1) + + @Before + fun setup() { + Dispatchers.setMain(testDispatcher) + + getDueTodayTasksUseCase = mockk() + toggleTaskDeletedUseCase = mockk(relaxed = true) + toggleTaskDoneUseCase = mockk(relaxed = true) + uiEventBus = mockk(relaxed = true) + + coEvery { getDueTodayTasksUseCase.invoke() } returns tasksFlow + + viewModel = DueTodayViewModel( + getDueTodayTasksUseCase, + toggleTaskDeletedUseCase, + toggleTaskDoneUseCase, + uiEventBus + ) + } + + @After + fun tearDown() { + Dispatchers.resetMain() + } + + @Test + fun `dueTodayTasks updates when flow emits`() = runTest { + val taskList = listOf(Task(taskListId = 0, id = 1, name = "Test Task", description = "", priority = Priority.NORMAL, isDone = false, isDeleted = false)) + tasksFlow.emit(taskList) + advanceUntilIdle() + + assertEquals(taskList, viewModel.dueTodayTasks) + } + + @Test + fun `onTaskClicked sends EditTask event`() = runTest { + val task = Task(taskListId = 0, id = 42, name = "Click me", description = "", priority = Priority.NORMAL, isDone = false, isDeleted = false) + + viewModel.onTaskClicked(task) + advanceUntilIdle() + + coVerify { uiEventBus.send(UiEvent.EditTask(task)) } + } + + @Test + fun `updateTaskDone toggles done and sends snackbar with undo`() = runTest { + val taskId = 5L + + viewModel.updateTaskDone(taskId) + advanceUntilIdle() + + coVerify { toggleTaskDoneUseCase(taskId, true) } + coVerify { + uiEventBus.send( + match { + it is UiEvent.ShowUndoSnackbar && + it.message == R.string.snackbar_message_task_done + } + ) + } + } + + @Test + fun `deleteTask toggles deleted and sends snackbar with undo`() = runTest { + val taskId = 7L + + viewModel.deleteTask(taskId) + advanceUntilIdle() + + coVerify { toggleTaskDeletedUseCase(taskId, true) } + coVerify { + uiEventBus.send( + match{ + it is UiEvent.ShowUndoSnackbar && + it.message == R.string.snackbar_message_task_recycle + } + ) + } + } +}