From d0cb89cf6ed26b249b09c771fcea7e4077ef2360 Mon Sep 17 00:00:00 2001 From: Bruno Deanoz Date: Thu, 18 Jun 2026 17:42:00 +0200 Subject: [PATCH] feat(ui): add clean SettingsViewModel and comprehensive ViewModel JVM unit tests --- .../kaizen/app/settings/SettingsViewModel.kt | 67 +++++++++++++++++++ .../dev/kaizen/app/SettingsViewModelTest.kt | 67 +++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 app/src/main/java/dev/kaizen/app/settings/SettingsViewModel.kt create mode 100644 app/src/test/java/dev/kaizen/app/SettingsViewModelTest.kt diff --git a/app/src/main/java/dev/kaizen/app/settings/SettingsViewModel.kt b/app/src/main/java/dev/kaizen/app/settings/SettingsViewModel.kt new file mode 100644 index 0000000..a5f4bcc --- /dev/null +++ b/app/src/main/java/dev/kaizen/app/settings/SettingsViewModel.kt @@ -0,0 +1,67 @@ +package dev.kaizen.app.settings + +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +// Available Kaizen UI themes +enum class KaizenThemeId { Amber, Blue, Aurora, Mono } + +// Available Light/Dark/System appearance modes +enum class AppAppearance { Light, Dark, System } + +/** + * Clean MVVM ViewModel for the Settings module. + * + * Completely decoupled from Android UI classes, making it 100% unit-testable + * on the JVM. Manages the state of the Oklab Theme Picker, selected model, + * and user profile options. + */ +class SettingsViewModel : ViewModel() { + + // 1. Reactive State Flow for Theme + private val _selectedTheme = MutableStateFlow(KaizenThemeId.Amber) + val selectedTheme: StateFlow = _selectedTheme.asStateFlow() + + // 2. Reactive State Flow for Appearance + private val _appearanceMode = MutableStateFlow(AppAppearance.Dark) + val appearanceMode: StateFlow = _appearanceMode.asStateFlow() + + // 3. User profile details + private val _userName = MutableStateFlow("Bruno") + val userName: StateFlow = _userName.asStateFlow() + + private val _userEmail = MutableStateFlow("admin@kryptomrx.de") + val userEmail: StateFlow = _userEmail.asStateFlow() + + // 4. Default Chat Model + private val _defaultModel = MutableStateFlow("gemini-2.5-flash") + val defaultModel: StateFlow = _defaultModel.asStateFlow() + + /** Selects a new Kaizen UI theme and triggers an Oklab transition stops recalculation */ + fun selectTheme(themeId: KaizenThemeId) { + _selectedTheme.value = themeId + } + + /** Changes the appearance mode (Light, Dark, or System Sync) */ + fun selectAppearance(mode: AppAppearance) { + _appearanceMode.value = mode + } + + /** Updates the user's name in profile settings */ + fun updateUserName(name: String) { + if (name.isNotBlank()) { + _userName.value = name.trim() + } + } + + /** Updates the standard chat model */ + fun updateDefaultModel(modelId: String) { + if (modelId.isNotBlank()) { + _defaultModel.value = modelId.trim() + } + } +} diff --git a/app/src/test/java/dev/kaizen/app/SettingsViewModelTest.kt b/app/src/test/java/dev/kaizen/app/SettingsViewModelTest.kt new file mode 100644 index 0000000..b26a839 --- /dev/null +++ b/app/src/test/java/dev/kaizen/app/SettingsViewModelTest.kt @@ -0,0 +1,67 @@ +package dev.kaizen.app + +import dev.kaizen.app.settings.AppAppearance +import dev.kaizen.app.settings.KaizenThemeId +import dev.kaizen.app.settings.SettingsViewModel +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * High-fidelity JVM Unit Test for the SettingsViewModel. + * + * Verifies that state transitions, user inputs, and theme selections are + * correctly and reactively managed inside the view model under clean MVVM. + */ +class SettingsViewModelTest { + + @Test + fun testInitialState() { + val viewModel = SettingsViewModel() + + // Assert correct default states + assertEquals(KaizenThemeId.Amber, viewModel.selectedTheme.value) + assertEquals(AppAppearance.Dark, viewModel.appearanceMode.value) + assertEquals("Bruno", viewModel.userName.value) + assertEquals("gemini-2.5-flash", viewModel.defaultModel.value) + } + + @Test + fun testSelectTheme() { + val viewModel = SettingsViewModel() + + // Transition theme to Aurora + viewModel.selectTheme(KaizenThemeId.Aurora) + + assertEquals(KaizenThemeId.Aurora, viewModel.selectedTheme.value) + } + + @Test + fun testSelectAppearance() { + val viewModel = SettingsViewModel() + + // Switch appearance to Light Mode + viewModel.selectAppearance(AppAppearance.Light) + + assertEquals(AppAppearance.Light, viewModel.appearanceMode.value) + } + + @Test + fun testUpdateUserName() { + val viewModel = SettingsViewModel() + + // Update user's name + viewModel.updateUserName(" Giuseppe ") // Should be trimmed + + assertEquals("Giuseppe", viewModel.userName.value) + } + + @Test + fun testUpdateUserNameBlankIgnored() { + val viewModel = SettingsViewModel() + + // Attempting to set an empty/blank name should be ignored + viewModel.updateUserName(" ") + + assertEquals("Bruno", viewModel.userName.value) // Remains unchanged + } +}