From bbe3b00ccbdbaa74a7ec4522a059eddfbb35c599 Mon Sep 17 00:00:00 2001 From: M57 <41645758+Hy4ri@users.noreply.github.com> Date: Mon, 22 Jun 2026 05:25:28 +0300 Subject: [PATCH] test(#293): add foundational Compose UI test for StateViews (TEST-15) (#301) Co-authored-by: M57 --- .../hermescontrol/ui/common/StateViewsTest.kt | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 app/src/androidTest/java/com/m57/hermescontrol/ui/common/StateViewsTest.kt diff --git a/app/src/androidTest/java/com/m57/hermescontrol/ui/common/StateViewsTest.kt b/app/src/androidTest/java/com/m57/hermescontrol/ui/common/StateViewsTest.kt new file mode 100644 index 00000000..669c27ec --- /dev/null +++ b/app/src/androidTest/java/com/m57/hermescontrol/ui/common/StateViewsTest.kt @@ -0,0 +1,116 @@ +package com.m57.hermescontrol.ui.common + +import androidx.compose.ui.test.assertHasClickAction +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented Compose UI tests for [LoadingState], [ErrorState], and [EmptyState]. + * + * This is the **first Compose UI test in the repo** (TEST-15, issue #293). + * It establishes the testing pattern for future composable tests. + */ +@RunWith(AndroidJUnit4::class) +@MediumTest +class StateViewsTest { + @get:Rule + val composeTestRule = createComposeRule() + + // ── LoadingState ──────────────────────────────────────────────────── + + @Test + fun loadingState_displaysCenteredSpinner() { + composeTestRule.setContent { LoadingState() } + + composeTestRule.onNodeWithTag("loading_state").assertIsDisplayed() + composeTestRule.onNodeWithTag("loading_spinner").assertIsDisplayed() + } + + // ── ErrorState ────────────────────────────────────────────────────── + + @Test + fun errorState_displaysMessage() { + val message = "Something went wrong" + composeTestRule.setContent { ErrorState(message = message) } + + composeTestRule.onNodeWithText(message).assertIsDisplayed() + } + + @Test + fun errorState_withRetry_showsRetryButton() { + var retryClicked = false + composeTestRule.setContent { + ErrorState( + message = "Try again", + onRetry = { retryClicked = true }, + ) + } + + composeTestRule.onNodeWithTag("error_retry_button").assertIsDisplayed() + composeTestRule.onNodeWithTag("error_retry_button").assertHasClickAction() + composeTestRule.onNodeWithTag("error_retry_button").performClick() + assert(retryClicked) { "Retry callback was not invoked on click" } + } + + @Test + fun errorState_withoutRetry_hidesRetryButton() { + composeTestRule.setContent { ErrorState(message = "No retry") } + + composeTestRule.onNodeWithTag("error_retry_button").assertDoesNotExist() + } + + // ── EmptyState ────────────────────────────────────────────────────── + + @Test + fun emptyState_displaysTitle() { + val title = "No items" + composeTestRule.setContent { EmptyState(title = title) } + + composeTestRule.onNodeWithText(title).assertIsDisplayed() + } + + @Test + fun emptyState_withSubtitle_displaysSubtitle() { + composeTestRule.setContent { EmptyState(title = "Empty", subtitle = "Add something") } + + composeTestRule.onNodeWithText("Add something").assertIsDisplayed() + } + + @Test + fun emptyState_withoutSubtitle_doesNotShowSubtitle() { + composeTestRule.setContent { EmptyState(title = "Empty") } + + composeTestRule.onNodeWithText("Add something").assertDoesNotExist() + } + + @Test + fun emptyState_withAction_showsActionButton() { + var actionClicked = false + composeTestRule.setContent { + EmptyState( + title = "No data", + actionLabel = "Create", + onAction = { actionClicked = true }, + ) + } + + composeTestRule.onNodeWithTag("empty_state_action").assertIsDisplayed() + composeTestRule.onNodeWithTag("empty_state_action").assertHasClickAction() + composeTestRule.onNodeWithTag("empty_state_action").performClick() + assert(actionClicked) { "Action callback was not invoked on click" } + } + + @Test + fun emptyState_withoutAction_hidesActionButton() { + composeTestRule.setContent { EmptyState(title = "Just text") } + + composeTestRule.onNodeWithTag("empty_state_action").assertDoesNotExist() + } +}