From c2dc0d73974a6bc415c54da7b4befc8f3f17351e Mon Sep 17 00:00:00 2001 From: Nacchofer31 <10453558+Nacchofer31@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:23:53 +0100 Subject: [PATCH 1/4] chore: add `coil-test` dependency to `libs.versions.toml` and `composeApp` --- composeApp/build.gradle.kts | 1 + gradle/libs.versions.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index ee3820a..e1c300f 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -113,6 +113,7 @@ kotlin { implementation(libs.assertk) implementation(kotlin("test")) implementation(libs.androidx.test.runner) + implementation("io.coil-kt.coil3:coil-test:3.3.0") } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b97270e..10ae9c1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -93,6 +93,7 @@ coil-compose-core = { module = "io.coil-kt.coil3:coil-compose-core", version.ref coil-network-ktor2 = { module = "io.coil-kt.coil3:coil-network-ktor2", version.ref = "coil3" } coil-network-ktor3 = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil3" } coil-mp = { module = "io.coil-kt.coil3:coil", version.ref = "coil3" } +coil-test = { module = "io.coil-kt.coil3:coil-test", version.ref = "coil3" } androidx-ui-test-android = { group = "androidx.compose.ui", name = "ui-test-android", version.ref = "uiTestAndroid" } # Jsoup From 006a1581f9e27442dc917cc48880ba6fae264956 Mon Sep 17 00:00:00 2001 From: Nacchofer31 <10453558+Nacchofer31@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:24:03 +0100 Subject: [PATCH 2/4] test: added `FilmPosterTest` instrumented tests --- .../presentation/FilmPosterTest.kt | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 composeApp/src/androidInstrumentedTest/kotlin/com/randomboxd/feature/random_film/presentation/FilmPosterTest.kt diff --git a/composeApp/src/androidInstrumentedTest/kotlin/com/randomboxd/feature/random_film/presentation/FilmPosterTest.kt b/composeApp/src/androidInstrumentedTest/kotlin/com/randomboxd/feature/random_film/presentation/FilmPosterTest.kt new file mode 100644 index 0000000..4231462 --- /dev/null +++ b/composeApp/src/androidInstrumentedTest/kotlin/com/randomboxd/feature/random_film/presentation/FilmPosterTest.kt @@ -0,0 +1,113 @@ +package com.randomboxd.feature.random_film.presentation + +import android.graphics.Bitmap +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import coil3.ImageLoader +import coil3.SingletonImageLoader +import coil3.annotation.DelicateCoilApi +import coil3.asImage +import coil3.test.FakeImageLoaderEngine +import com.nacchofer31.randomboxd.random_film.presentation.components.FilmPoster +import org.junit.After +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class FilmPosterTest { + @get:Rule + val composeTestRule = createComposeRule() + + private val context get() = InstrumentationRegistry.getInstrumentation().targetContext + + @After + @OptIn(DelicateCoilApi::class) + fun resetImageLoader() { + SingletonImageLoader.reset() + } + + private fun setImageLoader(engine: FakeImageLoaderEngine) { + SingletonImageLoader.setSafe { + ImageLoader.Builder(context).components { add(engine) }.build() + } + } + + @Test + fun film_poster_shows_image_with_valid_dimensions() { + val bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888) + setImageLoader( + FakeImageLoaderEngine.Builder() + .default(bitmap.asImage()) + .build(), + ) + + composeTestRule.setContent { + FilmPoster( + imageUrl = "https://example.com/poster.jpg", + title = "Test Film", + releaseYear = "2020", + onClick = {}, + ) + } + + composeTestRule.waitForIdle() + + composeTestRule.onNodeWithContentDescription("film_image").assertIsDisplayed() + composeTestRule.onNodeWithContentDescription("info_icon").assertIsDisplayed() + } + + @Test + fun film_poster_shows_image_with_invalid_dimensions_fallback() { + val bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) + setImageLoader( + FakeImageLoaderEngine.Builder() + .default(bitmap.asImage()) + .build(), + ) + + composeTestRule.setContent { + FilmPoster( + imageUrl = "https://example.com/poster.jpg", + title = "Test Film", + releaseYear = "2020", + onClick = {}, + ) + } + + composeTestRule.waitForIdle() + + composeTestRule.onNodeWithTag("test-film-poster").assertIsDisplayed() + } + + @Test + fun film_poster_click_triggers_callback_on_loaded_image() { + val bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888) + setImageLoader( + FakeImageLoaderEngine.Builder() + .default(bitmap.asImage()) + .build(), + ) + + var clicked = false + composeTestRule.setContent { + FilmPoster( + imageUrl = "https://example.com/poster.jpg", + title = "Inception", + releaseYear = "2010", + onClick = { clicked = true }, + ) + } + + composeTestRule.waitForIdle() + composeTestRule.onNodeWithContentDescription("film_image").performClick() + + assertTrue(clicked) + } +} From 19e18ed0520e632c1b44e3b7af8ac356e6b42dc7 Mon Sep 17 00:00:00 2001 From: Nacchofer31 <10453558+Nacchofer31@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:24:11 +0100 Subject: [PATCH 3/4] test: add `OnboardingScreenTest` instrumented tests --- .../presentation/OnboardingScreenTest.kt | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 composeApp/src/androidInstrumentedTest/kotlin/com/randomboxd/onboarding/presentation/OnboardingScreenTest.kt diff --git a/composeApp/src/androidInstrumentedTest/kotlin/com/randomboxd/onboarding/presentation/OnboardingScreenTest.kt b/composeApp/src/androidInstrumentedTest/kotlin/com/randomboxd/onboarding/presentation/OnboardingScreenTest.kt new file mode 100644 index 0000000..ecbc64f --- /dev/null +++ b/composeApp/src/androidInstrumentedTest/kotlin/com/randomboxd/onboarding/presentation/OnboardingScreenTest.kt @@ -0,0 +1,76 @@ +package com.randomboxd.onboarding.presentation + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.nacchofer31.randomboxd.onboarding.presentation.OnboardingScreen +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class OnboardingScreenTest { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun onboarding_screen_shows_first_page_on_launch() { + composeTestRule.setContent { + OnboardingScreen(onFinish = {}) + } + + composeTestRule.onNodeWithText("RandomBoxd").assertIsDisplayed() + composeTestRule.onNodeWithText("Get Started").assertIsDisplayed() + composeTestRule.onNodeWithText("Skip").assertIsDisplayed() + } + + @Test + fun onboarding_skip_button_calls_on_finish() { + var finished = false + composeTestRule.setContent { + OnboardingScreen(onFinish = { finished = true }) + } + + composeTestRule.onNodeWithText("Skip").performClick() + + assertTrue(finished) + } + + @Test + fun onboarding_next_button_advances_to_second_page() { + composeTestRule.setContent { + OnboardingScreen(onFinish = {}) + } + + composeTestRule.onNodeWithText("Get Started").performClick() + + composeTestRule.onNodeWithText("Random Selection").assertIsDisplayed() + composeTestRule.onNodeWithText("Next").assertIsDisplayed() + } + + @Test + fun onboarding_navigates_through_all_pages_to_finish() { + var finished = false + composeTestRule.setContent { + OnboardingScreen(onFinish = { finished = true }) + } + + // Page 1 → 2 + composeTestRule.onNodeWithText("Get Started").performClick() + composeTestRule.waitForIdle() + // Page 2 → 3 + composeTestRule.onNodeWithText("Next").performClick() + composeTestRule.waitForIdle() + // Page 3 → 4 + composeTestRule.onNodeWithText("Next").performClick() + composeTestRule.waitForIdle() + // Page 4 — last page, skip button should NOT be visible + composeTestRule.onNodeWithText("Start Exploring").assertIsDisplayed() + composeTestRule.onNodeWithText("Start Exploring").performClick() + + assertTrue(finished) + } +} From cc8d7ed50a162c77b2419280f0617fc1e425661a Mon Sep 17 00:00:00 2001 From: Nacchofer31 <10453558+Nacchofer31@users.noreply.github.com> Date: Sun, 22 Mar 2026 16:26:14 +0000 Subject: [PATCH 4/4] style: apply spotless formatting --- .../feature/random_film/presentation/FilmPosterTest.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/composeApp/src/androidInstrumentedTest/kotlin/com/randomboxd/feature/random_film/presentation/FilmPosterTest.kt b/composeApp/src/androidInstrumentedTest/kotlin/com/randomboxd/feature/random_film/presentation/FilmPosterTest.kt index 4231462..7e058f2 100644 --- a/composeApp/src/androidInstrumentedTest/kotlin/com/randomboxd/feature/random_film/presentation/FilmPosterTest.kt +++ b/composeApp/src/androidInstrumentedTest/kotlin/com/randomboxd/feature/random_film/presentation/FilmPosterTest.kt @@ -43,7 +43,8 @@ class FilmPosterTest { fun film_poster_shows_image_with_valid_dimensions() { val bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888) setImageLoader( - FakeImageLoaderEngine.Builder() + FakeImageLoaderEngine + .Builder() .default(bitmap.asImage()) .build(), ) @@ -67,7 +68,8 @@ class FilmPosterTest { fun film_poster_shows_image_with_invalid_dimensions_fallback() { val bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) setImageLoader( - FakeImageLoaderEngine.Builder() + FakeImageLoaderEngine + .Builder() .default(bitmap.asImage()) .build(), ) @@ -90,7 +92,8 @@ class FilmPosterTest { fun film_poster_click_triggers_callback_on_loaded_image() { val bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888) setImageLoader( - FakeImageLoaderEngine.Builder() + FakeImageLoaderEngine + .Builder() .default(bitmap.asImage()) .build(), )