Skip to content
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
15 changes: 15 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,18 @@ coverage:
precision: 2
round: down
range: "70...100"

ignore:
# Android platform-specific (require device context)
- "composeApp/src/androidMain/kotlin/com/nacchofer31/randomboxd/MainActivity.kt"
- "composeApp/src/androidMain/kotlin/com/nacchofer31/randomboxd/Platform.android.kt"
- "composeApp/src/androidMain/kotlin/com/nacchofer31/randomboxd/core/data/OnboardingPreferences.android.kt"
# Compose UI theme (no logic)
- "composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/core/presentation/RandomBoxdTheme.kt"
# Inline functions — JaCoCo cannot track coverage at declaration site
- "composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/core/domain/ResultData.kt"
- "composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/core/data/RandomBoxdHttpClientExt.kt"
# Pure interfaces (no executable code)
- "composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/domain/repository/RandomFilmRepository.kt"
# Room generated code
- "composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/core/data/UsernameDatabase.kt"
23 changes: 23 additions & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,31 @@ spotless {

val fileFilter =
listOf<String>(
// App navigation/entry (excluded by default)
"com/nacchofer31/randomboxd/app/**",
// Compose resources (generated)
"randomboxd/composeapp/generated/resources/**",
// Android platform-specific (require device context)
"com/nacchofer31/randomboxd/AndroidPlatform*",
"com/nacchofer31/randomboxd/MainActivity*",
"com/nacchofer31/randomboxd/MainActivityKt*",
"com/nacchofer31/randomboxd/Platform*",
"com/nacchofer31/randomboxd/ComposableSingletons${'$'}MainActivityKt*",
"com/nacchofer31/randomboxd/core/data/OnboardingPreferences*",
"com/nacchofer31/randomboxd/core/data/RandomBoxdHttpClientExtKt*",
"com/nacchofer31/randomboxd/core/presentation/RandomBoxdTheme*",
// Room generated code
"com/nacchofer31/randomboxd/core/data/UsernameDatabase_Impl*",
"com/nacchofer31/randomboxd/core/data/UserNameDatabaseConstructor*",
"com/nacchofer31/randomboxd/random_film/domain/model/UserNameDao_Impl*",
// Inline functions — JaCoCo cannot track coverage of Kotlin inline function bodies
"com/nacchofer31/randomboxd/core/domain/ResultData*",
"com/nacchofer31/randomboxd/core/domain/ResultDataKt*",
// Pure interfaces (no executable code)
"com/nacchofer31/randomboxd/random_film/domain/repository/RandomFilmRepository*",
// kotlinx.coroutines inlined lambda classes (phantom source entries)
"com/nacchofer31/randomboxd/random_film/presentation/components/**\$\$inlined*",
"com/nacchofer31/randomboxd/random_film/presentation/viewmodel/**\$special\$\$inlined*",
)

tasks.register("jacocoTestReport", JacocoReport::class) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,43 @@ class RandomFilmScreenTest {
composeTestRule.onNodeWithTag("test-film-display").assertExists()
composeTestRule.onNodeWithTag("test-film-display").performClick()
}

@Test
fun error_view_should_be_displayed_when_state_has_error() {
composeTestRule.setContent {
val mutableUserNamesFlow = MutableStateFlow<List<UserName>>(emptyList())
RandomFilmScreen(
userNameList = mutableUserNamesFlow,
state = RandomFilmState(resultError = com.nacchofer31.randomboxd.core.domain.DataError.Remote.NO_RESULTS),
) { }
}

composeTestRule.onNodeWithTag("test-film-error").assertIsDisplayed()
}

@Test
fun error_view_should_be_displayed_for_no_internet_error() {
composeTestRule.setContent {
val mutableUserNamesFlow = MutableStateFlow<List<UserName>>(emptyList())
RandomFilmScreen(
userNameList = mutableUserNamesFlow,
state = RandomFilmState(resultError = com.nacchofer31.randomboxd.core.domain.DataError.Remote.NO_INTERNET),
) { }
}

composeTestRule.onNodeWithTag("test-film-error").assertIsDisplayed()
}

@Test
fun union_intersection_switch_visible_when_user_search_list_not_empty() {
composeTestRule.setContent {
val mutableUserNamesFlow = MutableStateFlow<List<UserName>>(emptyList())
RandomFilmScreen(
userNameList = mutableUserNamesFlow,
state = RandomFilmState(userNameSearchList = setOf("user1", "user2")),
) { }
}

composeTestRule.onNodeWithTag("test-random-film-submit-button").assertIsDisplayed()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.nacchofer31.randomboxd.core.data

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlin.test.Test
import kotlin.test.assertEquals

class DefaultDispatchersTest {
private val dispatchers = DefaultDispatchers()

@Test
fun `io dispatcher returns Dispatchers IO`() {
assertEquals(Dispatchers.IO, dispatchers.io)
}

@Test
fun `default dispatcher returns Dispatchers Default`() {
assertEquals(Dispatchers.Default, dispatchers.default)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.nacchofer31.randomboxd.core.data

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class RandomBoxdEndpointsTest {
@Test
fun `getUserRandomFilm returns expected api url`() {
val result = RandomBoxdEndpoints.getUserRandomFilm("testuser")
assertEquals("/api?users=testuser", result)
}

@Test
fun `getUserNameWatchlist returns expected letterboxd url`() {
val result = RandomBoxdEndpoints.getUserNameWatchlist("testuser")
assertEquals("https://letterboxd.com/testuser/watchlist", result)
}

@Test
fun `getUserNameFromList returns expected list url`() {
val result = RandomBoxdEndpoints.getUserNameFromList("testuser", "my-list")
assertEquals("https://letterboxd.com/testuser/list/my-list", result)
}

@Test
fun `filmSlugUrl returns expected film url`() {
val result = RandomBoxdEndpoints.filmSlugUrl("test-film")
assertEquals("https://letterboxd.com/film/test-film/", result)
}

@Test
fun `filmPosterUrl returns expected poster url`() {
val result = RandomBoxdEndpoints.filmPosterUrl("1/2/3/4/5/", "12345", "test-film")
assertTrue(result.startsWith("https://a.ltrbxd.com/resized/film-poster/"))
assertTrue(result.contains("12345"))
assertTrue(result.contains("test-film"))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.nacchofer31.randomboxd.core.domain

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs

class DataErrorTest {
@Test
fun `DataError Remote entries are all reachable`() {
val remoteErrors = DataError.Remote.entries
assertEquals(7, remoteErrors.size)
assertIs<DataError>(DataError.Remote.REQUEST_TIMEOUT)
assertIs<DataError>(DataError.Remote.TOO_MANY_REQUESTS)
assertIs<DataError>(DataError.Remote.NO_INTERNET)
assertIs<DataError>(DataError.Remote.SERVER)
assertIs<DataError>(DataError.Remote.SERIALIZATION)
assertIs<DataError>(DataError.Remote.UNKNOWN)
assertIs<DataError>(DataError.Remote.NO_RESULTS)
}

@Test
fun `DataError Local DISK_FULL is reachable`() {
val error = DataError.Local.DISK_FULL
assertIs<DataError>(error)
assertEquals(DataError.Local.DISK_FULL, error)
}

@Test
fun `DataError Local UNKNOWN is reachable`() {
val error = DataError.Local.UNKNOWN
assertIs<DataError>(error)
assertEquals(DataError.Local.UNKNOWN, error)
}

@Test
fun `DataError Local entries has two values`() {
assertEquals(2, DataError.Local.entries.size)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.nacchofer31.randomboxd.core.domain

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertNull

class ResultDataTest {
private val successResult: ResultData<Int, DataError> = ResultData.Success(42)
private val errorResult: ResultData<Int, DataError> = ResultData.Error(DataError.Remote.UNKNOWN)

@Test
fun `map transforms success data`() {
val result = successResult.map { it * 2 }
assertIs<ResultData.Success<Int>>(result)
assertEquals(84, result.data)
}

@Test
fun `map on error preserves error`() {
val result = errorResult.map { it * 2 }
assertIs<ResultData.Error<DataError>>(result)
assertEquals(DataError.Remote.UNKNOWN, result.error)
}

@Test
fun `onSuccess executes action for success`() {
var captured: Int? = null
successResult.onSuccess { captured = it }
assertEquals(42, captured)
}

@Test
fun `onSuccess does not execute action for error`() {
var executed = false
errorResult.onSuccess { executed = true }
assertNull(null.takeIf { executed })
assertEquals(false, executed)
}

@Test
fun `onError executes action for error`() {
var captured: DataError? = null
errorResult.onError { captured = it }
assertEquals(DataError.Remote.UNKNOWN, captured)
}

@Test
fun `onError does not execute action for success`() {
var executed = false
successResult.onError { executed = true }
assertEquals(false, executed)
}

@Test
fun `onSuccess returns same instance`() {
val result = successResult.onSuccess { }
assertEquals(successResult, result)
}

@Test
fun `onError returns same instance`() {
val result = errorResult.onError { }
assertEquals(errorResult, result)
}

@Test
fun `asEmptyDataResult converts success to unit success`() {
val result = successResult.asEmptyDataResult()
assertIs<ResultData.Success<Unit>>(result)
}

@Test
fun `asEmptyDataResult preserves error`() {
val result = errorResult.asEmptyDataResult()
assertIs<ResultData.Error<DataError>>(result)
assertEquals(DataError.Remote.UNKNOWN, result.error)
}
}
Loading
Loading