diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt index 97428bff7e..9ec836ccea 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt @@ -666,7 +666,12 @@ class GeneratorViewModel @Inject constructor( if (currentPasswordType !is Password) { return@updateGeneratorMainTypePasscode currentSelectedType } - currentSelectedType.copy(selectedType = block(currentPasswordType)) + + val updatedPasswordType = currentPasswordType + .let(block) + .enforceAtLeastOneToggleOn() + + currentSelectedType.copy(selectedType = updatedPasswordType) } } @@ -1409,3 +1414,16 @@ sealed class GeneratorEvent { val message: Text, ) : GeneratorEvent() } + +@Suppress("ComplexCondition") +private fun Password.enforceAtLeastOneToggleOn(): Password = + // If all toggles are off, turn on useLowercase + if (!this.useCapitals && + !this.useLowercase && + !this.useNumbers && + !this.useSpecialChars + ) { + this.copy(useLowercase = true) + } else { + this + } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModelTest.kt index 834e4d31fe..7058590c44 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModelTest.kt @@ -649,6 +649,108 @@ class GeneratorViewModelTest : BaseViewModelTest() { assertEquals(expectedState, viewModel.stateFlow.value) } + + @Test + fun `Turning off all toggles should automatically turn on useLowercase`() = runTest { + val updatedGeneratedPassword = "updatedPassword" + fakeGeneratorRepository.setMockGeneratePasswordResult( + GeneratedPasswordResult.Success(updatedGeneratedPassword), + ) + + // Initially turn on all toggles + viewModel.actionChannel.trySend( + GeneratorAction.MainType.Passcode.PasscodeType.Password.ToggleCapitalLettersChange( + useCapitals = true, + ), + ) + viewModel.actionChannel.trySend( + GeneratorAction + .MainType + .Passcode + .PasscodeType + .Password + .ToggleLowercaseLettersChange( + useLowercase = true, + ), + ) + viewModel.actionChannel.trySend( + GeneratorAction.MainType.Passcode.PasscodeType.Password.ToggleNumbersChange( + useNumbers = true, + ), + ) + viewModel.actionChannel.trySend( + GeneratorAction + .MainType + .Passcode + .PasscodeType + .Password + .ToggleSpecialCharactersChange( + useSpecialChars = true, + ), + ) + + // Attempt to turn off all toggles + viewModel.actionChannel.trySend( + GeneratorAction.MainType.Passcode.PasscodeType.Password.ToggleCapitalLettersChange( + useCapitals = false, + ), + ) + viewModel.actionChannel.trySend( + GeneratorAction + .MainType + .Passcode + .PasscodeType + .Password + .ToggleLowercaseLettersChange( + useLowercase = false, + ), + ) + viewModel.actionChannel.trySend( + GeneratorAction.MainType.Passcode.PasscodeType.Password.ToggleNumbersChange( + useNumbers = false, + ), + ) + + // Check the state with only one toggle (useSpecialChars) left on + val intermediatePasswordState = GeneratorState.MainType.Passcode.PasscodeType.Password( + useCapitals = false, + useLowercase = false, + useNumbers = false, + useSpecialChars = true, + ) + + val intermediateState = defaultPasswordState.copy( + generatedText = updatedGeneratedPassword, + selectedType = GeneratorState.MainType.Passcode( + selectedType = intermediatePasswordState, + ), + ) + + assertEquals(intermediateState, viewModel.stateFlow.value) + + viewModel.actionChannel.trySend( + GeneratorAction + .MainType + .Passcode + .PasscodeType + .Password + .ToggleSpecialCharactersChange( + useSpecialChars = false, + ), + ) + + // Check if useLowercase is turned on automatically + val expectedState = intermediateState.copy( + selectedType = GeneratorState.MainType.Passcode( + selectedType = intermediatePasswordState.copy( + useLowercase = true, + useSpecialChars = false, + ), + ), + ) + + assertEquals(expectedState, viewModel.stateFlow.value) + } } @Nested