feat(settings): Kinder-Altersgruppen für Kalorienverbrauch (#47)

domain/model/AgeGroup.kt:
- Neues Enum AgeGroup (6 Gruppen: Kleinkind bis Erwachsener) mit Richtwert-kcal
- AgeGroupEntry-Datenklasse (count + kcalPerDay, anpassbar)
- JSON-Serialisierung via kotlinx.serialization
- Migrationspfad: householdSize + kcalPerPerson → ERWACHSENER-Gruppe

domain/model/SettingsKeys.kt:
- Neue Konstanten-Klasse für Settings-Keys (löst SettingsViewModel-Kopplung im DashboardViewModel auf)

CalculateSupplyRangeUseCase: Signatur auf totalDailyKcal vereinfacht
SettingsViewModel/UiState: ageGroups statt householdSize + dailyKcalPerPerson
DashboardViewModel: combine(3 flows), AGE_GROUPS statt 4 separate Flows
SettingsScreen: Altersgruppen-Tabelle statt zwei TextFields
ImportExportRepositoryImpl: Markdown-Export zeigt Altersgruppen-Gesamtkcal

Tests: +8 neue Tests (Migration, Validierung, Fallbacks), alle bestehenden Tests aktualisiert

Closes #47
This commit is contained in:
Jens Reinemann 2026-05-16 13:35:27 +02:00
parent 1236d61543
commit 9cc15ffaad
13 changed files with 391 additions and 153 deletions

View file

@ -8,6 +8,8 @@ import de.krisenvorrat.app.data.db.entity.CategoryEntity
import de.krisenvorrat.app.data.db.entity.ItemEntity import de.krisenvorrat.app.data.db.entity.ItemEntity
import de.krisenvorrat.app.data.db.entity.LocationEntity import de.krisenvorrat.app.data.db.entity.LocationEntity
import de.krisenvorrat.app.data.db.entity.SettingsEntity import de.krisenvorrat.app.data.db.entity.SettingsEntity
import de.krisenvorrat.app.domain.model.parseAgeGroupsFromJson
import de.krisenvorrat.app.domain.model.totalDailyKcal
import de.krisenvorrat.app.domain.repository.ImportExportRepository import de.krisenvorrat.app.domain.repository.ImportExportRepository
import de.krisenvorrat.shared.model.CategoryDto import de.krisenvorrat.shared.model.CategoryDto
import de.krisenvorrat.shared.model.InventoryDto import de.krisenvorrat.shared.model.InventoryDto
@ -143,15 +145,16 @@ internal class ImportExportRepositoryImpl @Inject constructor(
} }
val settingsMap = settings.associate { it.key to it.value } val settingsMap = settings.associate { it.key to it.value }
val householdSize = settingsMap["household_size"] val ageGroupsJson = settingsMap["age_groups"]
val kcalPerDay = settingsMap["kcal_per_day"] if (ageGroupsJson != null) {
if (householdSize != null || kcalPerDay != null) { val totalDailyKcal = parseAgeGroupsFromJson(ageGroupsJson).totalDailyKcal()
if (totalDailyKcal > 0) {
sb.appendLine("## Einstellungen") sb.appendLine("## Einstellungen")
sb.appendLine() sb.appendLine()
if (householdSize != null) sb.appendLine("- **Haushaltsgröße:** $householdSize Personen") sb.appendLine("- **Haushalt kcal/Tag:** $totalDailyKcal")
if (kcalPerDay != null) sb.appendLine("- **kcal/Tag:** $kcalPerDay")
sb.appendLine() sb.appendLine()
} }
}
sb.toString().trimEnd() + "\n" sb.toString().trimEnd() + "\n"
} }

View file

@ -0,0 +1,50 @@
package de.krisenvorrat.app.domain.model
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@Serializable
enum class AgeGroup(val label: String, val defaultKcal: Int) {
KLEINKIND("Kleinkind (13 J.)", 1000),
KIND_4_6("Kind (46 J.)", 1400),
KIND_7_9("Kind (79 J.)", 1700),
KIND_10_12("Kind (1012 J.)", 2000),
JUGENDLICH("Jugendlich (1317 J.)", 2400),
ERWACHSENER("Erwachsener", 2000)
}
@Serializable
data class AgeGroupEntry(
val ageGroup: AgeGroup,
val count: Int,
val kcalPerDay: Int
)
internal fun List<AgeGroupEntry>.totalDailyKcal(): Int = sumOf { it.count * it.kcalPerDay }
internal fun defaultAgeGroups(): List<AgeGroupEntry> = AgeGroup.entries.map { group ->
AgeGroupEntry(
ageGroup = group,
count = if (group == AgeGroup.ERWACHSENER) 2 else 0,
kcalPerDay = group.defaultKcal
)
}
private val ageGroupsJson = Json { ignoreUnknownKeys = true }
internal fun List<AgeGroupEntry>.toJson(): String = ageGroupsJson.encodeToString(
kotlinx.serialization.builtins.ListSerializer(AgeGroupEntry.serializer()),
this
)
internal fun parseAgeGroupsFromJson(json: String?): List<AgeGroupEntry> {
if (json.isNullOrBlank()) return defaultAgeGroups()
return try {
ageGroupsJson.decodeFromString(
kotlinx.serialization.builtins.ListSerializer(AgeGroupEntry.serializer()),
json
)
} catch (e: Exception) {
defaultAgeGroups()
}
}

View file

@ -0,0 +1,10 @@
package de.krisenvorrat.app.domain.model
internal object SettingsKeys {
const val HOUSEHOLD_SIZE = "household_size"
const val DAILY_KCAL_PER_PERSON = "daily_kcal_per_person"
const val AGE_GROUPS = "age_groups"
const val SERVER_URL = "server_url"
const val API_KEY = "api_key"
const val SYNC_LAST_TIMESTAMP = "sync_last_timestamp"
}

View file

@ -8,14 +8,14 @@ internal class CalculateSupplyRangeUseCase @Inject constructor() {
companion object { companion object {
const val DEFAULT_HOUSEHOLD_SIZE = 2 const val DEFAULT_HOUSEHOLD_SIZE = 2
const val DEFAULT_DAILY_KCAL_PER_PERSON = 2000 const val DEFAULT_DAILY_KCAL_PER_PERSON = 2000
const val DEFAULT_TOTAL_DAILY_KCAL = DEFAULT_HOUSEHOLD_SIZE * DEFAULT_DAILY_KCAL_PER_PERSON
} }
operator fun invoke( operator fun invoke(
items: List<ItemEntity>, items: List<ItemEntity>,
householdSize: Int = DEFAULT_HOUSEHOLD_SIZE, totalDailyKcal: Int = DEFAULT_TOTAL_DAILY_KCAL
dailyKcalPerPerson: Int = DEFAULT_DAILY_KCAL_PER_PERSON
): Double { ): Double {
val dailyNeed = householdSize * dailyKcalPerPerson val dailyNeed = totalDailyKcal
if (dailyNeed <= 0) return 0.0 if (dailyNeed <= 0) return 0.0
val totalKcal = items.sumOf { item -> val totalKcal = items.sumOf { item ->

View file

@ -3,7 +3,9 @@ package de.krisenvorrat.app.ui.dashboard
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import de.krisenvorrat.app.domain.repository.CategoryRepository import de.krisenvorrat.app.domain.model.parseAgeGroupsFromJson
import de.krisenvorrat.app.domain.model.SettingsKeys
import de.krisenvorrat.app.domain.model.totalDailyKcal
import de.krisenvorrat.app.domain.repository.ItemRepository import de.krisenvorrat.app.domain.repository.ItemRepository
import de.krisenvorrat.app.domain.repository.SettingsRepository import de.krisenvorrat.app.domain.repository.SettingsRepository
import de.krisenvorrat.app.domain.usecase.CalculateCategorySummaryUseCase import de.krisenvorrat.app.domain.usecase.CalculateCategorySummaryUseCase
@ -11,10 +13,10 @@ import de.krisenvorrat.app.domain.usecase.CalculateSupplyRangeUseCase
import de.krisenvorrat.app.domain.usecase.CalculateTotalValueUseCase import de.krisenvorrat.app.domain.usecase.CalculateTotalValueUseCase
import de.krisenvorrat.app.domain.usecase.GetExpiryWarningsUseCase import de.krisenvorrat.app.domain.usecase.GetExpiryWarningsUseCase
import de.krisenvorrat.app.domain.usecase.GetMinStockWarningsUseCase import de.krisenvorrat.app.domain.usecase.GetMinStockWarningsUseCase
import de.krisenvorrat.app.ui.settings.SettingsViewModel import de.krisenvorrat.app.domain.repository.CategoryRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -40,18 +42,17 @@ internal class DashboardViewModel @Inject constructor(
combine( combine(
itemRepository.getAll(), itemRepository.getAll(),
categoryRepository.getAll(), categoryRepository.getAll(),
settingsRepository.observeValue(SettingsViewModel.KEY_HOUSEHOLD_SIZE), settingsRepository.observeValue(SettingsKeys.AGE_GROUPS)
settingsRepository.observeValue(SettingsViewModel.KEY_DAILY_KCAL_PER_PERSON) ) { items, categories, ageGroupsJson ->
) { items, categories, householdSizeStr, dailyKcalStr -> val ageGroups = parseAgeGroupsFromJson(ageGroupsJson)
val householdSize = householdSizeStr?.toIntOrNull() val totalDailyKcal = ageGroups.totalDailyKcal().let {
?: CalculateSupplyRangeUseCase.DEFAULT_HOUSEHOLD_SIZE if (it <= 0) CalculateSupplyRangeUseCase.DEFAULT_TOTAL_DAILY_KCAL else it
val dailyKcal = dailyKcalStr?.toIntOrNull() }
?: CalculateSupplyRangeUseCase.DEFAULT_DAILY_KCAL_PER_PERSON
DashboardUiState( DashboardUiState(
categorySummaries = calculateCategorySummary(items, categories), categorySummaries = calculateCategorySummary(items, categories),
totalValue = calculateTotalValue(items), totalValue = calculateTotalValue(items),
supplyRangeDays = calculateSupplyRange(items, householdSize, dailyKcal), supplyRangeDays = calculateSupplyRange(items, totalDailyKcal),
expiryWarnings = getExpiryWarnings(items), expiryWarnings = getExpiryWarnings(items),
minStockWarnings = getMinStockWarnings(items), minStockWarnings = getMinStockWarnings(items),
isLoading = false isLoading = false

View file

@ -39,6 +39,8 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.krisenvorrat.app.domain.model.AgeGroup
import de.krisenvorrat.app.domain.model.totalDailyKcal
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -105,25 +107,45 @@ internal fun SettingsScreen(
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
uiState.ageGroups.forEach { entry ->
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = entry.ageGroup.label,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.weight(1f)
)
OutlinedTextField( OutlinedTextField(
value = uiState.householdSize, value = entry.count.toString(),
onValueChange = viewModel::onHouseholdSizeChanged, onValueChange = { viewModel.onAgeGroupCountChanged(entry.ageGroup, it) },
label = { Text("Personenzahl") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true, singleLine = true,
modifier = Modifier.fillMaxWidth() modifier = Modifier.width(56.dp)
) )
Text("×", style = MaterialTheme.typography.bodyMedium)
Spacer(modifier = Modifier.height(12.dp))
OutlinedTextField( OutlinedTextField(
value = uiState.dailyKcalPerPerson, value = entry.kcalPerDay.toString(),
onValueChange = viewModel::onDailyKcalChanged, onValueChange = { viewModel.onAgeGroupKcalChanged(entry.ageGroup, it) },
label = { Text("kcal pro Tag pro Person") }, label = { Text("kcal/Tag") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true, singleLine = true,
modifier = Modifier.fillMaxWidth() modifier = Modifier.width(104.dp)
) )
}
Spacer(modifier = Modifier.height(8.dp))
}
val totalKcal = uiState.ageGroups.totalDailyKcal()
if (totalKcal > 0) {
Text(
text = "Gesamt: $totalKcal kcal/Tag",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.secondary
)
}
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))

View file

@ -1,10 +1,11 @@
package de.krisenvorrat.app.ui.settings package de.krisenvorrat.app.ui.settings
import android.net.Uri import android.net.Uri
import de.krisenvorrat.app.domain.model.AgeGroupEntry
import de.krisenvorrat.app.domain.model.defaultAgeGroups
internal data class SettingsUiState( internal data class SettingsUiState(
val householdSize: String = "", val ageGroups: List<AgeGroupEntry> = defaultAgeGroups(),
val dailyKcalPerPerson: String = "",
val isLoading: Boolean = true, val isLoading: Boolean = true,
val isSaved: Boolean = false, val isSaved: Boolean = false,
val isExporting: Boolean = false, val isExporting: Boolean = false,

View file

@ -7,10 +7,15 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import de.krisenvorrat.app.domain.model.AgeGroup
import de.krisenvorrat.app.domain.model.AgeGroupEntry
import de.krisenvorrat.app.domain.model.SettingsKeys
import de.krisenvorrat.app.domain.model.defaultAgeGroups
import de.krisenvorrat.app.domain.model.parseAgeGroupsFromJson
import de.krisenvorrat.app.domain.model.toJson
import de.krisenvorrat.app.domain.repository.ImportExportRepository import de.krisenvorrat.app.domain.repository.ImportExportRepository
import de.krisenvorrat.app.domain.repository.SettingsRepository import de.krisenvorrat.app.domain.repository.SettingsRepository
import de.krisenvorrat.app.domain.repository.SyncService import de.krisenvorrat.app.domain.repository.SyncService
import de.krisenvorrat.app.domain.usecase.CalculateSupplyRangeUseCase
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -41,18 +46,14 @@ internal class SettingsViewModel @Inject constructor(
private fun loadSettings() { private fun loadSettings() {
viewModelScope.launch { viewModelScope.launch {
try { try {
val householdSize = settingsRepository.getValue(KEY_HOUSEHOLD_SIZE) val ageGroups = loadAgeGroupsWithMigration()
?: CalculateSupplyRangeUseCase.DEFAULT_HOUSEHOLD_SIZE.toString()
val dailyKcal = settingsRepository.getValue(KEY_DAILY_KCAL_PER_PERSON)
?: CalculateSupplyRangeUseCase.DEFAULT_DAILY_KCAL_PER_PERSON.toString()
val serverUrl = settingsRepository.getValue(KEY_SERVER_URL) ?: "" val serverUrl = settingsRepository.getValue(KEY_SERVER_URL) ?: ""
val apiKey = settingsRepository.getValue(KEY_API_KEY) ?: "" val apiKey = settingsRepository.getValue(KEY_API_KEY) ?: ""
val lastSyncTimestamp = settingsRepository.getValue(KEY_SYNC_LAST_TIMESTAMP) val lastSyncTimestamp = settingsRepository.getValue(KEY_SYNC_LAST_TIMESTAMP)
_uiState.update { _uiState.update {
it.copy( it.copy(
householdSize = householdSize, ageGroups = ageGroups,
dailyKcalPerPerson = dailyKcal,
serverUrl = serverUrl, serverUrl = serverUrl,
apiKey = apiKey, apiKey = apiKey,
lastSyncTime = lastSyncTimestamp?.let { ts -> formatTimestamp(ts) }, lastSyncTime = lastSyncTimestamp?.let { ts -> formatTimestamp(ts) },
@ -62,8 +63,7 @@ internal class SettingsViewModel @Inject constructor(
} catch (e: Exception) { } catch (e: Exception) {
_uiState.update { _uiState.update {
it.copy( it.copy(
householdSize = CalculateSupplyRangeUseCase.DEFAULT_HOUSEHOLD_SIZE.toString(), ageGroups = defaultAgeGroups(),
dailyKcalPerPerson = CalculateSupplyRangeUseCase.DEFAULT_DAILY_KCAL_PER_PERSON.toString(),
isLoading = false isLoading = false
) )
} }
@ -71,12 +71,48 @@ internal class SettingsViewModel @Inject constructor(
} }
} }
fun onHouseholdSizeChanged(value: String) { private suspend fun loadAgeGroupsWithMigration(): List<AgeGroupEntry> {
_uiState.update { it.copy(householdSize = value, isSaved = false) } val ageGroupsJsonValue = settingsRepository.getValue(KEY_AGE_GROUPS)
if (ageGroupsJsonValue != null) {
return parseAgeGroupsFromJson(ageGroupsJsonValue)
}
val legacyHouseholdSize = settingsRepository.getValue(KEY_HOUSEHOLD_SIZE)?.toIntOrNull()
if (legacyHouseholdSize != null) {
val legacyKcal = settingsRepository.getValue(KEY_DAILY_KCAL_PER_PERSON)?.toIntOrNull()
?: AgeGroup.ERWACHSENER.defaultKcal
return defaultAgeGroups().map { entry ->
if (entry.ageGroup == AgeGroup.ERWACHSENER) {
entry.copy(count = legacyHouseholdSize, kcalPerDay = legacyKcal)
} else {
entry
}
}
}
return defaultAgeGroups()
} }
fun onDailyKcalChanged(value: String) { fun onAgeGroupCountChanged(ageGroup: AgeGroup, count: String) {
_uiState.update { it.copy(dailyKcalPerPerson = value, isSaved = false) } val parsed = count.toIntOrNull()?.takeIf { it >= 0 } ?: return
_uiState.update { state ->
state.copy(
ageGroups = state.ageGroups.map { entry ->
if (entry.ageGroup == ageGroup) entry.copy(count = parsed) else entry
},
isSaved = false
)
}
}
fun onAgeGroupKcalChanged(ageGroup: AgeGroup, kcal: String) {
val parsed = kcal.toIntOrNull()?.takeIf { it > 0 } ?: return
_uiState.update { state ->
state.copy(
ageGroups = state.ageGroups.map { entry ->
if (entry.ageGroup == ageGroup) entry.copy(kcalPerDay = parsed) else entry
},
isSaved = false
)
}
} }
fun onServerUrlChanged(value: String) { fun onServerUrlChanged(value: String) {
@ -90,16 +126,7 @@ internal class SettingsViewModel @Inject constructor(
fun saveSettings() { fun saveSettings() {
viewModelScope.launch { viewModelScope.launch {
try { try {
val householdSize = _uiState.value.householdSize settingsRepository.setValue(KEY_AGE_GROUPS, _uiState.value.ageGroups.toJson())
val dailyKcal = _uiState.value.dailyKcalPerPerson
if (householdSize.toIntOrNull() != null && householdSize.toInt() > 0) {
settingsRepository.setValue(KEY_HOUSEHOLD_SIZE, householdSize)
}
if (dailyKcal.toIntOrNull() != null && dailyKcal.toInt() > 0) {
settingsRepository.setValue(KEY_DAILY_KCAL_PER_PERSON, dailyKcal)
}
settingsRepository.setValue(KEY_SERVER_URL, _uiState.value.serverUrl) settingsRepository.setValue(KEY_SERVER_URL, _uiState.value.serverUrl)
settingsRepository.setValue(KEY_API_KEY, _uiState.value.apiKey) settingsRepository.setValue(KEY_API_KEY, _uiState.value.apiKey)
@ -272,10 +299,11 @@ internal class SettingsViewModel @Inject constructor(
} }
companion object { companion object {
const val KEY_HOUSEHOLD_SIZE = "household_size" val KEY_HOUSEHOLD_SIZE = SettingsKeys.HOUSEHOLD_SIZE
const val KEY_DAILY_KCAL_PER_PERSON = "daily_kcal_per_person" val KEY_DAILY_KCAL_PER_PERSON = SettingsKeys.DAILY_KCAL_PER_PERSON
const val KEY_SERVER_URL = "server_url" val KEY_AGE_GROUPS = SettingsKeys.AGE_GROUPS
const val KEY_API_KEY = "api_key" val KEY_SERVER_URL = SettingsKeys.SERVER_URL
const val KEY_SYNC_LAST_TIMESTAMP = "sync_last_timestamp" val KEY_API_KEY = SettingsKeys.API_KEY
val KEY_SYNC_LAST_TIMESTAMP = SettingsKeys.SYNC_LAST_TIMESTAMP
} }
} }

View file

@ -3,6 +3,9 @@ package de.krisenvorrat.app.data.export
import de.krisenvorrat.app.data.db.entity.CategoryEntity import de.krisenvorrat.app.data.db.entity.CategoryEntity
import de.krisenvorrat.app.data.db.entity.LocationEntity import de.krisenvorrat.app.data.db.entity.LocationEntity
import de.krisenvorrat.app.data.db.entity.SettingsEntity import de.krisenvorrat.app.data.db.entity.SettingsEntity
import de.krisenvorrat.app.domain.model.AgeGroup
import de.krisenvorrat.app.domain.model.AgeGroupEntry
import de.krisenvorrat.app.domain.model.toJson
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
@ -188,9 +191,11 @@ class ImportExportRepositoryImplTest {
val categoryDao = FakeCategoryDao() val categoryDao = FakeCategoryDao()
val settingsDao = FakeSettingsDao() val settingsDao = FakeSettingsDao()
categoryDao.upsertAll(listOf(CategoryEntity(id = 1, name = "Test"))) categoryDao.upsertAll(listOf(CategoryEntity(id = 1, name = "Test")))
val ageGroups = listOf(
AgeGroupEntry(ageGroup = AgeGroup.ERWACHSENER, count = 4, kcalPerDay = 2000)
)
settingsDao.upsertAll(listOf( settingsDao.upsertAll(listOf(
SettingsEntity(key = "household_size", value = "4"), SettingsEntity(key = "age_groups", value = ageGroups.toJson())
SettingsEntity(key = "kcal_per_day", value = "2000")
)) ))
val repository = buildRepository(categoryDao, settingsDao = settingsDao) val repository = buildRepository(categoryDao, settingsDao = settingsDao)
@ -199,8 +204,7 @@ class ImportExportRepositoryImplTest {
// Then // Then
assertTrue(markdown.contains("## Einstellungen")) assertTrue(markdown.contains("## Einstellungen"))
assertTrue(markdown.contains("**Haushaltsgröße:** 4 Personen")) assertTrue(markdown.contains("**Haushalt kcal/Tag:** 8000"))
assertTrue(markdown.contains("**kcal/Tag:** 2000"))
} }
@Test @Test

View file

@ -0,0 +1,62 @@
package de.krisenvorrat.app.domain.model
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
class AgeGroupTest {
@Test
fun test_totalDailyKcal_withMixedGroups_returnsSumOfCountTimesKcal() {
// Given
val entries = listOf(
AgeGroupEntry(ageGroup = AgeGroup.KLEINKIND, count = 1, kcalPerDay = 1000),
AgeGroupEntry(ageGroup = AgeGroup.ERWACHSENER, count = 2, kcalPerDay = 2000),
AgeGroupEntry(ageGroup = AgeGroup.JUGENDLICH, count = 0, kcalPerDay = 2400)
)
// When
val result = entries.totalDailyKcal()
// Then 1×1000 + 2×2000 + 0×2400 = 5000
assertEquals(5000, result)
}
@Test
fun test_parseAgeGroupsFromJson_withValidJson_returnsCorrectGroups() {
// Given
val entries = listOf(
AgeGroupEntry(ageGroup = AgeGroup.ERWACHSENER, count = 3, kcalPerDay = 2500)
)
val json = entries.toJson()
// When
val parsed = parseAgeGroupsFromJson(json)
// Then
assertEquals(1, parsed.size)
assertEquals(AgeGroup.ERWACHSENER, parsed.first().ageGroup)
assertEquals(3, parsed.first().count)
assertEquals(2500, parsed.first().kcalPerDay)
}
@Test
fun test_parseAgeGroupsFromJson_withNull_returnsDefaults() {
// Given / When
val result = parseAgeGroupsFromJson(null)
// Then
assertEquals(defaultAgeGroups(), result)
}
@Test
fun test_defaultAgeGroups_hasErwachsenerWithCount2() {
// Given / When
val defaults = defaultAgeGroups()
// Then
val erwachsener = defaults.first { it.ageGroup == AgeGroup.ERWACHSENER }
assertEquals(2, erwachsener.count)
assertTrue(defaults.filter { it.ageGroup != AgeGroup.ERWACHSENER }.all { it.count == 0 })
}
}

View file

@ -10,13 +10,13 @@ class CalculateSupplyRangeUseCaseTest {
@Test @Test
fun test_invoke_withKgItems_returnsCorrectDays() { fun test_invoke_withKgItems_returnsCorrectDays() {
// Given 2 kg Reis à 350 kcal/100g = 7000 kcal // Given 2 kg Reis à 350 kcal/100g = 7000 kcal
// 2 Personen × 2000 kcal/Tag = 4000 kcal/Tag → 1.75 Tage // 4000 kcal/Tag (2 × 2000) → 1.75 Tage
val items = listOf( val items = listOf(
buildTestItem(id = "1", quantity = 2.0, unit = "kg", kcalPer100g = 350) buildTestItem(id = "1", quantity = 2.0, unit = "kg", kcalPer100g = 350)
) )
// When // When
val result = useCase(items, householdSize = 2, dailyKcalPerPerson = 2000) val result = useCase(items, totalDailyKcal = 4000)
// Then // Then
assertEquals(1.75, result, 0.001) assertEquals(1.75, result, 0.001)
@ -25,13 +25,13 @@ class CalculateSupplyRangeUseCaseTest {
@Test @Test
fun test_invoke_withGramItems_returnsCorrectDays() { fun test_invoke_withGramItems_returnsCorrectDays() {
// Given 500 g Nudeln à 360 kcal/100g = 1800 kcal // Given 500 g Nudeln à 360 kcal/100g = 1800 kcal
// 1 Person × 2000 kcal/Tag → 0.9 Tage // 2000 kcal/Tag → 0.9 Tage
val items = listOf( val items = listOf(
buildTestItem(id = "1", quantity = 500.0, unit = "g", kcalPer100g = 360) buildTestItem(id = "1", quantity = 500.0, unit = "g", kcalPer100g = 360)
) )
// When // When
val result = useCase(items, householdSize = 1, dailyKcalPerPerson = 2000) val result = useCase(items, totalDailyKcal = 2000)
// Then // Then
assertEquals(0.9, result, 0.001) assertEquals(0.9, result, 0.001)
@ -40,14 +40,14 @@ class CalculateSupplyRangeUseCaseTest {
@Test @Test
fun test_invoke_withMultipleItems_sumsTotalKcal() { fun test_invoke_withMultipleItems_sumsTotalKcal() {
// Given 1 kg Reis (350 kcal/100g = 3500 kcal) + 500 g Nudeln (360 kcal/100g = 1800 kcal) = 5300 kcal // Given 1 kg Reis (350 kcal/100g = 3500 kcal) + 500 g Nudeln (360 kcal/100g = 1800 kcal) = 5300 kcal
// 2 Personen × 2000 kcal/Tag = 4000 kcal/Tag → 1.325 Tage // 4000 kcal/Tag → 1.325 Tage
val items = listOf( val items = listOf(
buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPer100g = 350), buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPer100g = 350),
buildTestItem(id = "2", quantity = 500.0, unit = "g", kcalPer100g = 360) buildTestItem(id = "2", quantity = 500.0, unit = "g", kcalPer100g = 360)
) )
// When // When
val result = useCase(items, householdSize = 2, dailyKcalPerPerson = 2000) val result = useCase(items, totalDailyKcal = 4000)
// Then // Then
assertEquals(1.325, result, 0.001) assertEquals(1.325, result, 0.001)
@ -56,7 +56,7 @@ class CalculateSupplyRangeUseCaseTest {
@Test @Test
fun test_invoke_withEmptyList_returnsZero() { fun test_invoke_withEmptyList_returnsZero() {
// Given / When // Given / When
val result = useCase(emptyList(), householdSize = 2, dailyKcalPerPerson = 2000) val result = useCase(emptyList(), totalDailyKcal = 4000)
// Then // Then
assertEquals(0.0, result, 0.001) assertEquals(0.0, result, 0.001)
@ -70,8 +70,8 @@ class CalculateSupplyRangeUseCaseTest {
buildTestItem(id = "2", quantity = 1.0, unit = "kg", kcalPer100g = 200) buildTestItem(id = "2", quantity = 1.0, unit = "kg", kcalPer100g = 200)
) )
// When Nur 1 kg à 200 kcal/100g = 2000 kcal, 1 Person × 2000 = 1.0 Tage // When Nur 1 kg à 200 kcal/100g = 2000 kcal, 2000 kcal/Tag = 1.0 Tage
val result = useCase(items, householdSize = 1, dailyKcalPerPerson = 2000) val result = useCase(items, totalDailyKcal = 2000)
// Then // Then
assertEquals(1.0, result, 0.001) assertEquals(1.0, result, 0.001)
@ -85,30 +85,30 @@ class CalculateSupplyRangeUseCaseTest {
) )
// When // When
val result = useCase(items, householdSize = 2, dailyKcalPerPerson = 2000) val result = useCase(items, totalDailyKcal = 4000)
// Then // Then
assertEquals(0.0, result, 0.001) assertEquals(0.0, result, 0.001)
} }
@Test @Test
fun test_invoke_withZeroHouseholdSize_returnsZero() { fun test_invoke_withZeroTotalDailyKcal_returnsZero() {
// Given // Given
val items = listOf( val items = listOf(
buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPer100g = 350) buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPer100g = 350)
) )
// When // When
val result = useCase(items, householdSize = 0, dailyKcalPerPerson = 2000) val result = useCase(items, totalDailyKcal = 0)
// Then // Then
assertEquals(0.0, result, 0.001) assertEquals(0.0, result, 0.001)
} }
@Test @Test
fun test_invoke_withDefaultParameters_uses2PersonsAnd2000Kcal() { fun test_invoke_withDefaultParameters_uses4000KcalPerDay() {
// Given 4 kg Reis à 350 kcal/100g = 14000 kcal // Given 4 kg Reis à 350 kcal/100g = 14000 kcal
// Default: 2 Personen × 2000 kcal = 4000 kcal/Tag → 3.5 Tage // Default: 4000 kcal/Tag → 3.5 Tage
val items = listOf( val items = listOf(
buildTestItem(id = "1", quantity = 4.0, unit = "kg", kcalPer100g = 350) buildTestItem(id = "1", quantity = 4.0, unit = "kg", kcalPer100g = 350)
) )
@ -123,13 +123,13 @@ class CalculateSupplyRangeUseCaseTest {
@Test @Test
fun test_invoke_withMgUnit_convertsCorrectly() { fun test_invoke_withMgUnit_convertsCorrectly() {
// Given 500000 mg = 500 g à 200 kcal/100g = 1000 kcal // Given 500000 mg = 500 g à 200 kcal/100g = 1000 kcal
// 1 Person × 1000 kcal/Tag → 1.0 Tag // 1000 kcal/Tag → 1.0 Tag
val items = listOf( val items = listOf(
buildTestItem(id = "1", quantity = 500000.0, unit = "mg", kcalPer100g = 200) buildTestItem(id = "1", quantity = 500000.0, unit = "mg", kcalPer100g = 200)
) )
// When // When
val result = useCase(items, householdSize = 1, dailyKcalPerPerson = 1000) val result = useCase(items, totalDailyKcal = 1000)
// Then // Then
assertEquals(1.0, result, 0.001) assertEquals(1.0, result, 0.001)
@ -138,7 +138,7 @@ class CalculateSupplyRangeUseCaseTest {
@Test @Test
fun test_invoke_withMixedUnits_onlyCountsWeightBased() { fun test_invoke_withMixedUnits_onlyCountsWeightBased() {
// Given 1 kg (350 kcal/100g) + 5 Stk (ignored) + 2 L (ignored) // Given 1 kg (350 kcal/100g) + 5 Stk (ignored) + 2 L (ignored)
// Total: 3500 kcal, 1 Person × 2000 → 1.75 Tage // Total: 3500 kcal, 2000 kcal/Tag → 1.75 Tage
val items = listOf( val items = listOf(
buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPer100g = 350), buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPer100g = 350),
buildTestItem(id = "2", quantity = 5.0, unit = "Stk", kcalPer100g = 100), buildTestItem(id = "2", quantity = 5.0, unit = "Stk", kcalPer100g = 100),
@ -146,7 +146,7 @@ class CalculateSupplyRangeUseCaseTest {
) )
// When // When
val result = useCase(items, householdSize = 1, dailyKcalPerPerson = 2000) val result = useCase(items, totalDailyKcal = 2000)
// Then // Then
assertEquals(1.75, result, 0.001) assertEquals(1.75, result, 0.001)

View file

@ -3,9 +3,13 @@ package de.krisenvorrat.app.ui.dashboard
import de.krisenvorrat.app.data.db.entity.CategoryEntity import de.krisenvorrat.app.data.db.entity.CategoryEntity
import de.krisenvorrat.app.data.db.entity.ItemEntity import de.krisenvorrat.app.data.db.entity.ItemEntity
import de.krisenvorrat.app.data.db.entity.SettingsEntity import de.krisenvorrat.app.data.db.entity.SettingsEntity
import de.krisenvorrat.app.domain.model.AgeGroup
import de.krisenvorrat.app.domain.model.defaultAgeGroups
import de.krisenvorrat.app.domain.model.toJson
import de.krisenvorrat.app.domain.repository.CategoryRepository import de.krisenvorrat.app.domain.repository.CategoryRepository
import de.krisenvorrat.app.domain.repository.ItemRepository import de.krisenvorrat.app.domain.repository.ItemRepository
import de.krisenvorrat.app.domain.repository.SettingsRepository import de.krisenvorrat.app.domain.repository.SettingsRepository
import de.krisenvorrat.app.ui.settings.SettingsViewModel
import de.krisenvorrat.app.domain.usecase.CalculateCategorySummaryUseCase import de.krisenvorrat.app.domain.usecase.CalculateCategorySummaryUseCase
import de.krisenvorrat.app.domain.usecase.CalculateSupplyRangeUseCase import de.krisenvorrat.app.domain.usecase.CalculateSupplyRangeUseCase
import de.krisenvorrat.app.domain.usecase.CalculateTotalValueUseCase import de.krisenvorrat.app.domain.usecase.CalculateTotalValueUseCase
@ -136,7 +140,15 @@ class DashboardViewModelTest {
@Test @Test
fun test_init_withKcalItems_supplyRangeIsCalculated() = runTest(testDispatcher) { fun test_init_withKcalItems_supplyRangeIsCalculated() = runTest(testDispatcher) {
// Given 1kg item with 200 kcal/100g = 2000 kcal total // Given 1kg item with 200 kcal/100g = 2000 kcal total
// Default: 2 persons * 2000 kcal/day = 4000 kcal/day → 0.5 days // AgeGroups: ERWACHSENER count=2, kcal=2000 → 4000 kcal/day → 0.5 days
val ageGroups = defaultAgeGroups().map { entry ->
if (entry.ageGroup == AgeGroup.ERWACHSENER) entry.copy(count = 2, kcalPerDay = 2000)
else entry
}
fakeSettingsRepository.setValue(
de.krisenvorrat.app.ui.settings.SettingsViewModel.KEY_AGE_GROUPS,
ageGroups.toJson()
)
fakeItemRepository.emit( fakeItemRepository.emit(
listOf( listOf(
buildTestItem(id = "a", quantity = 1000.0, unit = "g", kcalPer100g = 200) buildTestItem(id = "a", quantity = 1000.0, unit = "g", kcalPer100g = 200)
@ -151,6 +163,28 @@ class DashboardViewModelTest {
assertEquals(0.5, viewModel.uiState.value.supplyRangeDays, 0.001) assertEquals(0.5, viewModel.uiState.value.supplyRangeDays, 0.001)
} }
@Test
fun test_init_withEmptyAgeGroups_usesDefaultTotalKcalForRange() = runTest(testDispatcher) {
// Given 1000g × 400 kcal/100g = 4000 kcal; alle AgeGroup-counts = 0
// → totalDailyKcal = 0 → Fallback auf DEFAULT_TOTAL_DAILY_KCAL = 4000
// → supplyRangeDays = 4000 / 4000 = 1.0
val allZeroAgeGroups = defaultAgeGroups().map { it.copy(count = 0) }
fakeSettingsRepository.setValue(
de.krisenvorrat.app.ui.settings.SettingsViewModel.KEY_AGE_GROUPS,
allZeroAgeGroups.toJson()
)
fakeItemRepository.emit(
listOf(buildTestItem(id = "a", quantity = 1000.0, unit = "g", kcalPer100g = 400))
)
viewModel = createViewModel()
// When
advanceUntilIdle()
// Then
assertEquals(1.0, viewModel.uiState.value.supplyRangeDays, 0.001)
}
@Test @Test
fun test_init_withExpiringItems_expiryWarningsArePresent() = runTest(testDispatcher) { fun test_init_withExpiringItems_expiryWarningsArePresent() = runTest(testDispatcher) {
// Given item expiring in 3 months (within URGENT_MONTHS=6) // Given item expiring in 3 months (within URGENT_MONTHS=6)

View file

@ -9,7 +9,10 @@ import de.krisenvorrat.app.domain.model.SyncError
import de.krisenvorrat.app.domain.repository.ImportExportRepository import de.krisenvorrat.app.domain.repository.ImportExportRepository
import de.krisenvorrat.app.domain.repository.SettingsRepository import de.krisenvorrat.app.domain.repository.SettingsRepository
import de.krisenvorrat.app.domain.repository.SyncService import de.krisenvorrat.app.domain.repository.SyncService
import de.krisenvorrat.app.domain.usecase.CalculateSupplyRangeUseCase import de.krisenvorrat.app.domain.model.AgeGroup
import de.krisenvorrat.app.domain.model.AgeGroupEntry
import de.krisenvorrat.app.domain.model.defaultAgeGroups
import de.krisenvorrat.app.domain.model.toJson
import de.krisenvorrat.shared.model.InventoryDto import de.krisenvorrat.shared.model.InventoryDto
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -74,7 +77,7 @@ class SettingsViewModelTest {
) )
@Test @Test
fun test_init_withNoStoredValues_showsDefaults() = runTest(testDispatcher) { fun test_init_withNoStoredValues_showsDefaultAgeGroups() = runTest(testDispatcher) {
// Given no stored settings // Given no stored settings
viewModel = createViewModel() viewModel = createViewModel()
@ -84,68 +87,122 @@ class SettingsViewModelTest {
// Then // Then
val state = viewModel.uiState.value val state = viewModel.uiState.value
assertFalse(state.isLoading) assertFalse(state.isLoading)
assertEquals( assertEquals(defaultAgeGroups(), state.ageGroups)
CalculateSupplyRangeUseCase.DEFAULT_HOUSEHOLD_SIZE.toString(),
state.householdSize
)
assertEquals(
CalculateSupplyRangeUseCase.DEFAULT_DAILY_KCAL_PER_PERSON.toString(),
state.dailyKcalPerPerson
)
} }
@Test @Test
fun test_init_withStoredValues_showsStoredValues() = runTest(testDispatcher) { fun test_init_withStoredAgeGroups_showsStoredGroups() = runTest(testDispatcher) {
// Given // Given
fakeSettingsRepository.store[SettingsViewModel.KEY_HOUSEHOLD_SIZE] = "4" val stored = defaultAgeGroups().map { entry ->
if (entry.ageGroup == AgeGroup.ERWACHSENER) entry.copy(count = 3, kcalPerDay = 2500)
else entry
}
fakeSettingsRepository.store[SettingsViewModel.KEY_AGE_GROUPS] = stored.toJson()
viewModel = createViewModel()
// When
advanceUntilIdle()
// Then
val erwachsener = viewModel.uiState.value.ageGroups.first { it.ageGroup == AgeGroup.ERWACHSENER }
assertEquals(3, erwachsener.count)
assertEquals(2500, erwachsener.kcalPerDay)
}
@Test
fun test_init_withLegacyHouseholdSize_migratesErwachsener() = runTest(testDispatcher) {
// Given legacy keys present, no age_groups key
fakeSettingsRepository.store[SettingsViewModel.KEY_HOUSEHOLD_SIZE] = "3"
fakeSettingsRepository.store[SettingsViewModel.KEY_DAILY_KCAL_PER_PERSON] = "2500" fakeSettingsRepository.store[SettingsViewModel.KEY_DAILY_KCAL_PER_PERSON] = "2500"
viewModel = createViewModel() viewModel = createViewModel()
// When // When
advanceUntilIdle() advanceUntilIdle()
// Then // Then
val state = viewModel.uiState.value val erwachsener = viewModel.uiState.value.ageGroups.first { it.ageGroup == AgeGroup.ERWACHSENER }
assertEquals("4", state.householdSize) assertEquals(3, erwachsener.count)
assertEquals("2500", state.dailyKcalPerPerson) assertEquals(2500, erwachsener.kcalPerDay)
} }
@Test @Test
fun test_onHouseholdSizeChanged_updatesState() = runTest(testDispatcher) { fun test_init_withLegacyHouseholdSizeOnly_migratesWithDefaultKcal() = runTest(testDispatcher) {
// Given nur KEY_HOUSEHOLD_SIZE gesetzt, kein KEY_DAILY_KCAL_PER_PERSON
fakeSettingsRepository.store[SettingsViewModel.KEY_HOUSEHOLD_SIZE] = "3"
viewModel = createViewModel()
// When
advanceUntilIdle()
// Then
val erwachsener = viewModel.uiState.value.ageGroups.first { it.ageGroup == AgeGroup.ERWACHSENER }
assertEquals(3, erwachsener.count)
assertEquals(AgeGroup.ERWACHSENER.defaultKcal, erwachsener.kcalPerDay)
}
@Test
fun test_onAgeGroupCountChanged_updatesState() = runTest(testDispatcher) {
// Given // Given
viewModel = createViewModel() viewModel = createViewModel()
advanceUntilIdle() advanceUntilIdle()
// When // When
viewModel.onHouseholdSizeChanged("5") viewModel.onAgeGroupCountChanged(AgeGroup.KLEINKIND, "2")
// Then // Then
assertEquals("5", viewModel.uiState.value.householdSize) val kleinkind = viewModel.uiState.value.ageGroups.first { it.ageGroup == AgeGroup.KLEINKIND }
assertEquals(2, kleinkind.count)
assertFalse(viewModel.uiState.value.isSaved) assertFalse(viewModel.uiState.value.isSaved)
} }
@Test @Test
fun test_onDailyKcalChanged_updatesState() = runTest(testDispatcher) { fun test_onAgeGroupKcalChanged_updatesState() = runTest(testDispatcher) {
// Given // Given
viewModel = createViewModel() viewModel = createViewModel()
advanceUntilIdle() advanceUntilIdle()
// When // When
viewModel.onDailyKcalChanged("1800") viewModel.onAgeGroupKcalChanged(AgeGroup.ERWACHSENER, "2200")
// Then // Then
assertEquals("1800", viewModel.uiState.value.dailyKcalPerPerson) val erwachsener = viewModel.uiState.value.ageGroups.first { it.ageGroup == AgeGroup.ERWACHSENER }
assertEquals(2200, erwachsener.kcalPerDay)
assertFalse(viewModel.uiState.value.isSaved) assertFalse(viewModel.uiState.value.isSaved)
} }
@Test @Test
fun test_saveSettings_withValidValues_persistsAndShowsSaved() = runTest(testDispatcher) { fun test_onAgeGroupCountChanged_withNegativeValue_doesNotUpdateState() = runTest(testDispatcher) {
// Given
viewModel = createViewModel()
advanceUntilIdle()
val initialGroups = viewModel.uiState.value.ageGroups
// When
viewModel.onAgeGroupCountChanged(AgeGroup.KLEINKIND, "-1")
// Then
assertEquals(initialGroups, viewModel.uiState.value.ageGroups)
}
@Test
fun test_onAgeGroupKcalChanged_withZeroValue_doesNotUpdateState() = runTest(testDispatcher) {
// Given
viewModel = createViewModel()
advanceUntilIdle()
val initialGroups = viewModel.uiState.value.ageGroups
// When
viewModel.onAgeGroupKcalChanged(AgeGroup.ERWACHSENER, "0")
// Then
assertEquals(initialGroups, viewModel.uiState.value.ageGroups)
}
@Test
fun test_saveSettings_persistsAgeGroupsJson() = runTest(testDispatcher) {
// Given // Given
viewModel = createViewModel() viewModel = createViewModel()
advanceUntilIdle() advanceUntilIdle()
viewModel.onHouseholdSizeChanged("3")
viewModel.onDailyKcalChanged("2200")
// When // When
viewModel.saveSettings() viewModel.saveSettings()
@ -153,40 +210,7 @@ class SettingsViewModelTest {
// Then // Then
assertTrue(viewModel.uiState.value.isSaved) assertTrue(viewModel.uiState.value.isSaved)
assertEquals("3", fakeSettingsRepository.store[SettingsViewModel.KEY_HOUSEHOLD_SIZE]) assertTrue(fakeSettingsRepository.store.containsKey(SettingsViewModel.KEY_AGE_GROUPS))
assertEquals("2200", fakeSettingsRepository.store[SettingsViewModel.KEY_DAILY_KCAL_PER_PERSON])
}
@Test
fun test_saveSettings_withInvalidHouseholdSize_doesNotPersistInvalid() = runTest(testDispatcher) {
// Given
viewModel = createViewModel()
advanceUntilIdle()
viewModel.onHouseholdSizeChanged("abc")
viewModel.onDailyKcalChanged("2200")
// When
viewModel.saveSettings()
advanceUntilIdle()
// Then household_size not stored, but dailyKcal is
assertFalse(fakeSettingsRepository.store.containsKey(SettingsViewModel.KEY_HOUSEHOLD_SIZE))
assertEquals("2200", fakeSettingsRepository.store[SettingsViewModel.KEY_DAILY_KCAL_PER_PERSON])
}
@Test
fun test_saveSettings_withZeroHouseholdSize_doesNotPersist() = runTest(testDispatcher) {
// Given
viewModel = createViewModel()
advanceUntilIdle()
viewModel.onHouseholdSizeChanged("0")
// When
viewModel.saveSettings()
advanceUntilIdle()
// Then zero is invalid, not persisted
assertFalse(fakeSettingsRepository.store.containsKey(SettingsViewModel.KEY_HOUSEHOLD_SIZE))
} }
@Test @Test
@ -507,7 +531,6 @@ class SettingsViewModelTest {
// Given // Given
viewModel = createViewModel() viewModel = createViewModel()
advanceUntilIdle() advanceUntilIdle()
viewModel.onHouseholdSizeChanged("2")
viewModel.onServerUrlChanged("https://myserver.com") viewModel.onServerUrlChanged("https://myserver.com")
viewModel.onApiKeyChanged("secret-key-123") viewModel.onApiKeyChanged("secret-key-123")