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:
parent
1236d61543
commit
9cc15ffaad
13 changed files with 391 additions and 153 deletions
|
|
@ -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.LocationEntity
|
||||
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.shared.model.CategoryDto
|
||||
import de.krisenvorrat.shared.model.InventoryDto
|
||||
|
|
@ -143,14 +145,15 @@ internal class ImportExportRepositoryImpl @Inject constructor(
|
|||
}
|
||||
|
||||
val settingsMap = settings.associate { it.key to it.value }
|
||||
val householdSize = settingsMap["household_size"]
|
||||
val kcalPerDay = settingsMap["kcal_per_day"]
|
||||
if (householdSize != null || kcalPerDay != null) {
|
||||
sb.appendLine("## Einstellungen")
|
||||
sb.appendLine()
|
||||
if (householdSize != null) sb.appendLine("- **Haushaltsgröße:** $householdSize Personen")
|
||||
if (kcalPerDay != null) sb.appendLine("- **kcal/Tag:** $kcalPerDay")
|
||||
sb.appendLine()
|
||||
val ageGroupsJson = settingsMap["age_groups"]
|
||||
if (ageGroupsJson != null) {
|
||||
val totalDailyKcal = parseAgeGroupsFromJson(ageGroupsJson).totalDailyKcal()
|
||||
if (totalDailyKcal > 0) {
|
||||
sb.appendLine("## Einstellungen")
|
||||
sb.appendLine()
|
||||
sb.appendLine("- **Haushalt kcal/Tag:** $totalDailyKcal")
|
||||
sb.appendLine()
|
||||
}
|
||||
}
|
||||
|
||||
sb.toString().trimEnd() + "\n"
|
||||
|
|
|
|||
|
|
@ -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 (1–3 J.)", 1000),
|
||||
KIND_4_6("Kind (4–6 J.)", 1400),
|
||||
KIND_7_9("Kind (7–9 J.)", 1700),
|
||||
KIND_10_12("Kind (10–12 J.)", 2000),
|
||||
JUGENDLICH("Jugendlich (13–17 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -8,14 +8,14 @@ internal class CalculateSupplyRangeUseCase @Inject constructor() {
|
|||
companion object {
|
||||
const val DEFAULT_HOUSEHOLD_SIZE = 2
|
||||
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(
|
||||
items: List<ItemEntity>,
|
||||
householdSize: Int = DEFAULT_HOUSEHOLD_SIZE,
|
||||
dailyKcalPerPerson: Int = DEFAULT_DAILY_KCAL_PER_PERSON
|
||||
totalDailyKcal: Int = DEFAULT_TOTAL_DAILY_KCAL
|
||||
): Double {
|
||||
val dailyNeed = householdSize * dailyKcalPerPerson
|
||||
val dailyNeed = totalDailyKcal
|
||||
if (dailyNeed <= 0) return 0.0
|
||||
|
||||
val totalKcal = items.sumOf { item ->
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ package de.krisenvorrat.app.ui.dashboard
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.SettingsRepository
|
||||
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.GetExpiryWarningsUseCase
|
||||
import de.krisenvorrat.app.domain.usecase.GetMinStockWarningsUseCase
|
||||
import de.krisenvorrat.app.ui.settings.SettingsViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import de.krisenvorrat.app.domain.repository.CategoryRepository
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -40,18 +42,17 @@ internal class DashboardViewModel @Inject constructor(
|
|||
combine(
|
||||
itemRepository.getAll(),
|
||||
categoryRepository.getAll(),
|
||||
settingsRepository.observeValue(SettingsViewModel.KEY_HOUSEHOLD_SIZE),
|
||||
settingsRepository.observeValue(SettingsViewModel.KEY_DAILY_KCAL_PER_PERSON)
|
||||
) { items, categories, householdSizeStr, dailyKcalStr ->
|
||||
val householdSize = householdSizeStr?.toIntOrNull()
|
||||
?: CalculateSupplyRangeUseCase.DEFAULT_HOUSEHOLD_SIZE
|
||||
val dailyKcal = dailyKcalStr?.toIntOrNull()
|
||||
?: CalculateSupplyRangeUseCase.DEFAULT_DAILY_KCAL_PER_PERSON
|
||||
settingsRepository.observeValue(SettingsKeys.AGE_GROUPS)
|
||||
) { items, categories, ageGroupsJson ->
|
||||
val ageGroups = parseAgeGroupsFromJson(ageGroupsJson)
|
||||
val totalDailyKcal = ageGroups.totalDailyKcal().let {
|
||||
if (it <= 0) CalculateSupplyRangeUseCase.DEFAULT_TOTAL_DAILY_KCAL else it
|
||||
}
|
||||
|
||||
DashboardUiState(
|
||||
categorySummaries = calculateCategorySummary(items, categories),
|
||||
totalValue = calculateTotalValue(items),
|
||||
supplyRangeDays = calculateSupplyRange(items, householdSize, dailyKcal),
|
||||
supplyRangeDays = calculateSupplyRange(items, totalDailyKcal),
|
||||
expiryWarnings = getExpiryWarnings(items),
|
||||
minStockWarnings = getMinStockWarnings(items),
|
||||
isLoading = false
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import de.krisenvorrat.app.domain.model.AgeGroup
|
||||
import de.krisenvorrat.app.domain.model.totalDailyKcal
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
@ -105,25 +107,45 @@ internal fun SettingsScreen(
|
|||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
value = uiState.householdSize,
|
||||
onValueChange = viewModel::onHouseholdSizeChanged,
|
||||
label = { Text("Personenzahl") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
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(
|
||||
value = entry.count.toString(),
|
||||
onValueChange = { viewModel.onAgeGroupCountChanged(entry.ageGroup, it) },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
singleLine = true,
|
||||
modifier = Modifier.width(56.dp)
|
||||
)
|
||||
Text("×", style = MaterialTheme.typography.bodyMedium)
|
||||
OutlinedTextField(
|
||||
value = entry.kcalPerDay.toString(),
|
||||
onValueChange = { viewModel.onAgeGroupKcalChanged(entry.ageGroup, it) },
|
||||
label = { Text("kcal/Tag") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
singleLine = true,
|
||||
modifier = Modifier.width(104.dp)
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
value = uiState.dailyKcalPerPerson,
|
||||
onValueChange = viewModel::onDailyKcalChanged,
|
||||
label = { Text("kcal pro Tag pro Person") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
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))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package de.krisenvorrat.app.ui.settings
|
||||
|
||||
import android.net.Uri
|
||||
import de.krisenvorrat.app.domain.model.AgeGroupEntry
|
||||
import de.krisenvorrat.app.domain.model.defaultAgeGroups
|
||||
|
||||
internal data class SettingsUiState(
|
||||
val householdSize: String = "",
|
||||
val dailyKcalPerPerson: String = "",
|
||||
val ageGroups: List<AgeGroupEntry> = defaultAgeGroups(),
|
||||
val isLoading: Boolean = true,
|
||||
val isSaved: Boolean = false,
|
||||
val isExporting: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -7,10 +7,15 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
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.SettingsRepository
|
||||
import de.krisenvorrat.app.domain.repository.SyncService
|
||||
import de.krisenvorrat.app.domain.usecase.CalculateSupplyRangeUseCase
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
|
@ -41,18 +46,14 @@ internal class SettingsViewModel @Inject constructor(
|
|||
private fun loadSettings() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val householdSize = settingsRepository.getValue(KEY_HOUSEHOLD_SIZE)
|
||||
?: CalculateSupplyRangeUseCase.DEFAULT_HOUSEHOLD_SIZE.toString()
|
||||
val dailyKcal = settingsRepository.getValue(KEY_DAILY_KCAL_PER_PERSON)
|
||||
?: CalculateSupplyRangeUseCase.DEFAULT_DAILY_KCAL_PER_PERSON.toString()
|
||||
val ageGroups = loadAgeGroupsWithMigration()
|
||||
val serverUrl = settingsRepository.getValue(KEY_SERVER_URL) ?: ""
|
||||
val apiKey = settingsRepository.getValue(KEY_API_KEY) ?: ""
|
||||
val lastSyncTimestamp = settingsRepository.getValue(KEY_SYNC_LAST_TIMESTAMP)
|
||||
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
householdSize = householdSize,
|
||||
dailyKcalPerPerson = dailyKcal,
|
||||
ageGroups = ageGroups,
|
||||
serverUrl = serverUrl,
|
||||
apiKey = apiKey,
|
||||
lastSyncTime = lastSyncTimestamp?.let { ts -> formatTimestamp(ts) },
|
||||
|
|
@ -62,8 +63,7 @@ internal class SettingsViewModel @Inject constructor(
|
|||
} catch (e: Exception) {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
householdSize = CalculateSupplyRangeUseCase.DEFAULT_HOUSEHOLD_SIZE.toString(),
|
||||
dailyKcalPerPerson = CalculateSupplyRangeUseCase.DEFAULT_DAILY_KCAL_PER_PERSON.toString(),
|
||||
ageGroups = defaultAgeGroups(),
|
||||
isLoading = false
|
||||
)
|
||||
}
|
||||
|
|
@ -71,12 +71,48 @@ internal class SettingsViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun onHouseholdSizeChanged(value: String) {
|
||||
_uiState.update { it.copy(householdSize = value, isSaved = false) }
|
||||
private suspend fun loadAgeGroupsWithMigration(): List<AgeGroupEntry> {
|
||||
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) {
|
||||
_uiState.update { it.copy(dailyKcalPerPerson = value, isSaved = false) }
|
||||
fun onAgeGroupCountChanged(ageGroup: AgeGroup, count: String) {
|
||||
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) {
|
||||
|
|
@ -90,16 +126,7 @@ internal class SettingsViewModel @Inject constructor(
|
|||
fun saveSettings() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val householdSize = _uiState.value.householdSize
|
||||
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_AGE_GROUPS, _uiState.value.ageGroups.toJson())
|
||||
settingsRepository.setValue(KEY_SERVER_URL, _uiState.value.serverUrl)
|
||||
settingsRepository.setValue(KEY_API_KEY, _uiState.value.apiKey)
|
||||
|
||||
|
|
@ -272,10 +299,11 @@ internal class SettingsViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
companion object {
|
||||
const val KEY_HOUSEHOLD_SIZE = "household_size"
|
||||
const val KEY_DAILY_KCAL_PER_PERSON = "daily_kcal_per_person"
|
||||
const val KEY_SERVER_URL = "server_url"
|
||||
const val KEY_API_KEY = "api_key"
|
||||
const val KEY_SYNC_LAST_TIMESTAMP = "sync_last_timestamp"
|
||||
val KEY_HOUSEHOLD_SIZE = SettingsKeys.HOUSEHOLD_SIZE
|
||||
val KEY_DAILY_KCAL_PER_PERSON = SettingsKeys.DAILY_KCAL_PER_PERSON
|
||||
val KEY_AGE_GROUPS = SettingsKeys.AGE_GROUPS
|
||||
val KEY_SERVER_URL = SettingsKeys.SERVER_URL
|
||||
val KEY_API_KEY = SettingsKeys.API_KEY
|
||||
val KEY_SYNC_LAST_TIMESTAMP = SettingsKeys.SYNC_LAST_TIMESTAMP
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.LocationEntity
|
||||
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 org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
|
|
@ -188,9 +191,11 @@ class ImportExportRepositoryImplTest {
|
|||
val categoryDao = FakeCategoryDao()
|
||||
val settingsDao = FakeSettingsDao()
|
||||
categoryDao.upsertAll(listOf(CategoryEntity(id = 1, name = "Test")))
|
||||
val ageGroups = listOf(
|
||||
AgeGroupEntry(ageGroup = AgeGroup.ERWACHSENER, count = 4, kcalPerDay = 2000)
|
||||
)
|
||||
settingsDao.upsertAll(listOf(
|
||||
SettingsEntity(key = "household_size", value = "4"),
|
||||
SettingsEntity(key = "kcal_per_day", value = "2000")
|
||||
SettingsEntity(key = "age_groups", value = ageGroups.toJson())
|
||||
))
|
||||
val repository = buildRepository(categoryDao, settingsDao = settingsDao)
|
||||
|
||||
|
|
@ -199,8 +204,7 @@ class ImportExportRepositoryImplTest {
|
|||
|
||||
// Then
|
||||
assertTrue(markdown.contains("## Einstellungen"))
|
||||
assertTrue(markdown.contains("**Haushaltsgröße:** 4 Personen"))
|
||||
assertTrue(markdown.contains("**kcal/Tag:** 2000"))
|
||||
assertTrue(markdown.contains("**Haushalt kcal/Tag:** 8000"))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
@ -10,13 +10,13 @@ class CalculateSupplyRangeUseCaseTest {
|
|||
@Test
|
||||
fun test_invoke_withKgItems_returnsCorrectDays() {
|
||||
// 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(
|
||||
buildTestItem(id = "1", quantity = 2.0, unit = "kg", kcalPer100g = 350)
|
||||
)
|
||||
|
||||
// When
|
||||
val result = useCase(items, householdSize = 2, dailyKcalPerPerson = 2000)
|
||||
val result = useCase(items, totalDailyKcal = 4000)
|
||||
|
||||
// Then
|
||||
assertEquals(1.75, result, 0.001)
|
||||
|
|
@ -25,13 +25,13 @@ class CalculateSupplyRangeUseCaseTest {
|
|||
@Test
|
||||
fun test_invoke_withGramItems_returnsCorrectDays() {
|
||||
// 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(
|
||||
buildTestItem(id = "1", quantity = 500.0, unit = "g", kcalPer100g = 360)
|
||||
)
|
||||
|
||||
// When
|
||||
val result = useCase(items, householdSize = 1, dailyKcalPerPerson = 2000)
|
||||
val result = useCase(items, totalDailyKcal = 2000)
|
||||
|
||||
// Then
|
||||
assertEquals(0.9, result, 0.001)
|
||||
|
|
@ -40,14 +40,14 @@ class CalculateSupplyRangeUseCaseTest {
|
|||
@Test
|
||||
fun test_invoke_withMultipleItems_sumsTotalKcal() {
|
||||
// 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(
|
||||
buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPer100g = 350),
|
||||
buildTestItem(id = "2", quantity = 500.0, unit = "g", kcalPer100g = 360)
|
||||
)
|
||||
|
||||
// When
|
||||
val result = useCase(items, householdSize = 2, dailyKcalPerPerson = 2000)
|
||||
val result = useCase(items, totalDailyKcal = 4000)
|
||||
|
||||
// Then
|
||||
assertEquals(1.325, result, 0.001)
|
||||
|
|
@ -56,7 +56,7 @@ class CalculateSupplyRangeUseCaseTest {
|
|||
@Test
|
||||
fun test_invoke_withEmptyList_returnsZero() {
|
||||
// Given / When
|
||||
val result = useCase(emptyList(), householdSize = 2, dailyKcalPerPerson = 2000)
|
||||
val result = useCase(emptyList(), totalDailyKcal = 4000)
|
||||
|
||||
// Then
|
||||
assertEquals(0.0, result, 0.001)
|
||||
|
|
@ -70,8 +70,8 @@ class CalculateSupplyRangeUseCaseTest {
|
|||
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
|
||||
val result = useCase(items, householdSize = 1, dailyKcalPerPerson = 2000)
|
||||
// When – Nur 1 kg à 200 kcal/100g = 2000 kcal, 2000 kcal/Tag = 1.0 Tage
|
||||
val result = useCase(items, totalDailyKcal = 2000)
|
||||
|
||||
// Then
|
||||
assertEquals(1.0, result, 0.001)
|
||||
|
|
@ -85,30 +85,30 @@ class CalculateSupplyRangeUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(items, householdSize = 2, dailyKcalPerPerson = 2000)
|
||||
val result = useCase(items, totalDailyKcal = 4000)
|
||||
|
||||
// Then
|
||||
assertEquals(0.0, result, 0.001)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_invoke_withZeroHouseholdSize_returnsZero() {
|
||||
fun test_invoke_withZeroTotalDailyKcal_returnsZero() {
|
||||
// Given
|
||||
val items = listOf(
|
||||
buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPer100g = 350)
|
||||
)
|
||||
|
||||
// When
|
||||
val result = useCase(items, householdSize = 0, dailyKcalPerPerson = 2000)
|
||||
val result = useCase(items, totalDailyKcal = 0)
|
||||
|
||||
// Then
|
||||
assertEquals(0.0, result, 0.001)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_invoke_withDefaultParameters_uses2PersonsAnd2000Kcal() {
|
||||
fun test_invoke_withDefaultParameters_uses4000KcalPerDay() {
|
||||
// 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(
|
||||
buildTestItem(id = "1", quantity = 4.0, unit = "kg", kcalPer100g = 350)
|
||||
)
|
||||
|
|
@ -123,13 +123,13 @@ class CalculateSupplyRangeUseCaseTest {
|
|||
@Test
|
||||
fun test_invoke_withMgUnit_convertsCorrectly() {
|
||||
// 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(
|
||||
buildTestItem(id = "1", quantity = 500000.0, unit = "mg", kcalPer100g = 200)
|
||||
)
|
||||
|
||||
// When
|
||||
val result = useCase(items, householdSize = 1, dailyKcalPerPerson = 1000)
|
||||
val result = useCase(items, totalDailyKcal = 1000)
|
||||
|
||||
// Then
|
||||
assertEquals(1.0, result, 0.001)
|
||||
|
|
@ -138,7 +138,7 @@ class CalculateSupplyRangeUseCaseTest {
|
|||
@Test
|
||||
fun test_invoke_withMixedUnits_onlyCountsWeightBased() {
|
||||
// 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(
|
||||
buildTestItem(id = "1", quantity = 1.0, unit = "kg", kcalPer100g = 350),
|
||||
buildTestItem(id = "2", quantity = 5.0, unit = "Stk", kcalPer100g = 100),
|
||||
|
|
@ -146,7 +146,7 @@ class CalculateSupplyRangeUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(items, householdSize = 1, dailyKcalPerPerson = 2000)
|
||||
val result = useCase(items, totalDailyKcal = 2000)
|
||||
|
||||
// Then
|
||||
assertEquals(1.75, result, 0.001)
|
||||
|
|
|
|||
|
|
@ -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.ItemEntity
|
||||
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.ItemRepository
|
||||
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.CalculateSupplyRangeUseCase
|
||||
import de.krisenvorrat.app.domain.usecase.CalculateTotalValueUseCase
|
||||
|
|
@ -136,7 +140,15 @@ class DashboardViewModelTest {
|
|||
@Test
|
||||
fun test_init_withKcalItems_supplyRangeIsCalculated() = runTest(testDispatcher) {
|
||||
// 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(
|
||||
listOf(
|
||||
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)
|
||||
}
|
||||
|
||||
@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
|
||||
fun test_init_withExpiringItems_expiryWarningsArePresent() = runTest(testDispatcher) {
|
||||
// Given – item expiring in 3 months (within URGENT_MONTHS=6)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ import de.krisenvorrat.app.domain.model.SyncError
|
|||
import de.krisenvorrat.app.domain.repository.ImportExportRepository
|
||||
import de.krisenvorrat.app.domain.repository.SettingsRepository
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
|
@ -74,7 +77,7 @@ class SettingsViewModelTest {
|
|||
)
|
||||
|
||||
@Test
|
||||
fun test_init_withNoStoredValues_showsDefaults() = runTest(testDispatcher) {
|
||||
fun test_init_withNoStoredValues_showsDefaultAgeGroups() = runTest(testDispatcher) {
|
||||
// Given – no stored settings
|
||||
viewModel = createViewModel()
|
||||
|
||||
|
|
@ -84,68 +87,122 @@ class SettingsViewModelTest {
|
|||
// Then
|
||||
val state = viewModel.uiState.value
|
||||
assertFalse(state.isLoading)
|
||||
assertEquals(
|
||||
CalculateSupplyRangeUseCase.DEFAULT_HOUSEHOLD_SIZE.toString(),
|
||||
state.householdSize
|
||||
)
|
||||
assertEquals(
|
||||
CalculateSupplyRangeUseCase.DEFAULT_DAILY_KCAL_PER_PERSON.toString(),
|
||||
state.dailyKcalPerPerson
|
||||
)
|
||||
assertEquals(defaultAgeGroups(), state.ageGroups)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_init_withStoredValues_showsStoredValues() = runTest(testDispatcher) {
|
||||
fun test_init_withStoredAgeGroups_showsStoredGroups() = runTest(testDispatcher) {
|
||||
// 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"
|
||||
|
||||
viewModel = createViewModel()
|
||||
|
||||
// When
|
||||
advanceUntilIdle()
|
||||
|
||||
// Then
|
||||
val state = viewModel.uiState.value
|
||||
assertEquals("4", state.householdSize)
|
||||
assertEquals("2500", state.dailyKcalPerPerson)
|
||||
val erwachsener = viewModel.uiState.value.ageGroups.first { it.ageGroup == AgeGroup.ERWACHSENER }
|
||||
assertEquals(3, erwachsener.count)
|
||||
assertEquals(2500, erwachsener.kcalPerDay)
|
||||
}
|
||||
|
||||
@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
|
||||
viewModel = createViewModel()
|
||||
advanceUntilIdle()
|
||||
|
||||
// When
|
||||
viewModel.onHouseholdSizeChanged("5")
|
||||
viewModel.onAgeGroupCountChanged(AgeGroup.KLEINKIND, "2")
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_onDailyKcalChanged_updatesState() = runTest(testDispatcher) {
|
||||
fun test_onAgeGroupKcalChanged_updatesState() = runTest(testDispatcher) {
|
||||
// Given
|
||||
viewModel = createViewModel()
|
||||
advanceUntilIdle()
|
||||
|
||||
// When
|
||||
viewModel.onDailyKcalChanged("1800")
|
||||
viewModel.onAgeGroupKcalChanged(AgeGroup.ERWACHSENER, "2200")
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@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
|
||||
viewModel = createViewModel()
|
||||
advanceUntilIdle()
|
||||
viewModel.onHouseholdSizeChanged("3")
|
||||
viewModel.onDailyKcalChanged("2200")
|
||||
|
||||
// When
|
||||
viewModel.saveSettings()
|
||||
|
|
@ -153,40 +210,7 @@ class SettingsViewModelTest {
|
|||
|
||||
// Then
|
||||
assertTrue(viewModel.uiState.value.isSaved)
|
||||
assertEquals("3", fakeSettingsRepository.store[SettingsViewModel.KEY_HOUSEHOLD_SIZE])
|
||||
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))
|
||||
assertTrue(fakeSettingsRepository.store.containsKey(SettingsViewModel.KEY_AGE_GROUPS))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -507,7 +531,6 @@ class SettingsViewModelTest {
|
|||
// Given
|
||||
viewModel = createViewModel()
|
||||
advanceUntilIdle()
|
||||
viewModel.onHouseholdSizeChanged("2")
|
||||
viewModel.onServerUrlChanged("https://myserver.com")
|
||||
viewModel.onApiKeyChanged("secret-key-123")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue