diff options
| author | Danilo M. <danix@danix.xyz> | 2026-11-05 12:13:35 +0100 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-11-05 12:21:40 +0100 |
| commit | 68714bf78feb5a5a59354da624f2d69d6705e8ab (patch) | |
| tree | 030eda99da60c4fc8c48b617381e6b1c25171c64 /app/src/test/java | |
| parent | c85e7c8801caf7a73b46dda9ebf75be023cad9b4 (diff) | |
| download | HardenedPass-68714bf78feb5a5a59354da624f2d69d6705e8ab.tar.gz HardenedPass-68714bf78feb5a5a59354da624f2d69d6705e8ab.zip | |
feat(app): move the git dialogs and the snackbar to Compose
An activity-scoped Prompts ViewModel holds them, since git operations have no
composable to draw in; PromptHost draws them. The credential dialog's window is
now FLAG_SECURE, and a recreated activity no longer loses it and strands the
SSH thread.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Diffstat (limited to 'app/src/test/java')
| -rw-r--r-- | app/src/test/java/app/passwordstore/ui/prompts/PromptsTest.kt | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/app/src/test/java/app/passwordstore/ui/prompts/PromptsTest.kt b/app/src/test/java/app/passwordstore/ui/prompts/PromptsTest.kt new file mode 100644 index 00000000..12d1671e --- /dev/null +++ b/app/src/test/java/app/passwordstore/ui/prompts/PromptsTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2026 Danilo M. <danix@danix.xyz> + * SPDX-License-Identifier: GPL-3.0-only + */ + +package app.passwordstore.ui.prompts + +import androidx.compose.material3.SnackbarDuration +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertSame +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking + +class PromptsTest { + + private fun Prompts.question() = current as Question + + @Test + fun answerResumesAsk() = runBlocking { + val prompts = Prompts() + val asked = async(start = CoroutineStart.UNDISPATCHED) { prompts.ask("t", "m", "ok") } + prompts.answer(prompts.question(), Choice.Other) + assertEquals(Choice.Other, asked.await()) + assertNull(prompts.current) + } + + @Test + fun dismissingAnswersCancel() = runBlocking { + val prompts = Prompts() + val asked = async(start = CoroutineStart.UNDISPATCHED) { prompts.ask("t", "m", "ok") } + prompts.cancel(prompts.question()) + assertEquals(Choice.Cancel, asked.await()) + } + + @Test + fun secondAnswerIsIgnored() = runBlocking { + val prompts = Prompts() + val asked = async(start = CoroutineStart.UNDISPATCHED) { prompts.ask("t", "m", "ok") } + val question = prompts.question() + prompts.answer(question, Choice.Confirm) + prompts.answer(question, Choice.Cancel) + assertEquals(Choice.Confirm, asked.await()) + } + + @Test + fun cancelledAskRemovesItsDialog() = runBlocking { + val prompts = Prompts() + val asked = async(start = CoroutineStart.UNDISPATCHED) { prompts.ask("t", "m", "ok") } + asked.cancel() + assertNull(prompts.current) + } + + @Test + fun promptsShowOneAtATimeInOrder() = + runBlocking<Unit> { + val prompts = Prompts() + val first = async(start = CoroutineStart.UNDISPATCHED) { prompts.ask("1", "m", "ok") } + val second = async(start = CoroutineStart.UNDISPATCHED) { prompts.ask("2", "m", "ok") } + assertEquals("1", prompts.question().title) + prompts.answer(prompts.question(), Choice.Confirm) + assertEquals("2", prompts.question().title) + prompts.answer(prompts.question(), Choice.Confirm) + first.await() + second.await() + } + + @Test + fun cancelAllAnswersAPendingCredentialWithNull() { + val prompts = Prompts() + val answers = mutableListOf<CredentialAnswer?>(CredentialAnswer("unset", false)) + prompts.askCredential("m", "h", "r", null) { answers[0] = it } + prompts.cancelAll() + assertNull(answers[0]) + assertNull(prompts.current) + } + + @Test + fun askCredentialAfterCloseAnswersNullAtOnce() { + val prompts = Prompts() + prompts.close() + val answers = mutableListOf<CredentialAnswer?>(CredentialAnswer("unset", false)) + prompts.askCredential("m", "h", "r", null) { answers[0] = it } + assertNull(answers[0]) + assertNull(prompts.current) + } + + @Test + fun newSnackbarReplacesTheCurrentOne() { + val prompts = Prompts() + val first = prompts.snackbar("one", SnackbarDuration.Indefinite) + val second = prompts.snackbar("two", SnackbarDuration.Short) + assertSame(second, prompts.snack) + // The finished operation dismisses its own snackbar only. + prompts.dismiss(first) + assertSame(second, prompts.snack) + prompts.dismiss(second) + assertNull(prompts.snack) + } +} |
