Skip to content

Commit 5143093

Browse files
committed
test: add test for ReadingMailSettingsViewModel
1 parent ad5092b commit 5143093

File tree

1 file changed

+238
-0
lines changed

1 file changed

+238
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
package net.thunderbird.feature.account.settings.impl.ui.readingMail
2+
3+
import assertk.assertThat
4+
import assertk.assertions.isEqualTo
5+
import kotlinx.coroutines.ExperimentalCoroutinesApi
6+
import kotlinx.coroutines.flow.flowOf
7+
import kotlinx.coroutines.launch
8+
import kotlinx.coroutines.test.StandardTestDispatcher
9+
import kotlinx.coroutines.test.advanceUntilIdle
10+
import kotlinx.coroutines.test.runTest
11+
import net.thunderbird.core.android.account.LegacyAccount
12+
import net.thunderbird.core.android.account.ShowPictures
13+
import net.thunderbird.core.logging.testing.TestLogger
14+
import net.thunderbird.core.outcome.Outcome
15+
import net.thunderbird.core.testing.coroutines.MainDispatcherRule
16+
import net.thunderbird.core.ui.setting.SettingValue.Select.SelectOption
17+
import net.thunderbird.feature.account.AccountId
18+
import net.thunderbird.feature.account.AccountIdFactory
19+
import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsDomainContract
20+
import org.junit.Rule
21+
import org.junit.Test
22+
23+
@OptIn(ExperimentalCoroutinesApi::class)
24+
class ReadingMailSettingsViewModelTest {
25+
26+
@get:Rule
27+
val mainDispatcherRule = MainDispatcherRule(StandardTestDispatcher())
28+
29+
/** Default initial state */
30+
private fun defaultState() = ReadingMailSettingsContract.State(
31+
subtitle = null,
32+
showPictures = SelectOption(ShowPictures.NEVER.name) { "never" },
33+
isMarkMessageAsReadOnView = false,
34+
)
35+
36+
/** Dummy LegacyAccount builder for tests */
37+
private fun dummyLegacyAccount(
38+
accountId: AccountId,
39+
showPictures: ShowPictures = ShowPictures.NEVER,
40+
isMarkReadOnView: Boolean = false,
41+
) = net.thunderbird.core.android.account.LegacyAccount(
42+
id = accountId,
43+
name = "Demo",
44+
email = "demo@example.com",
45+
isSensitiveDebugLoggingEnabled = { true },
46+
profile = net.thunderbird.feature.account.storage.profile.ProfileDto(
47+
id = accountId,
48+
name = "Demo",
49+
color = 0xFF0000,
50+
avatar = net.thunderbird.feature.account.storage.profile.AvatarDto(
51+
avatarType = net.thunderbird.feature.account.storage.profile.AvatarTypeDto.ICON,
52+
avatarMonogram = null,
53+
avatarImageUri = null,
54+
avatarIconName = "star",
55+
),
56+
),
57+
identities = listOf(
58+
net.thunderbird.core.android.account.Identity(
59+
signatureUse = false,
60+
description = "Demo Identity",
61+
),
62+
),
63+
incomingServerSettings = com.fsck.k9.mail.ServerSettings(
64+
type = "imap",
65+
host = "imap.example.com",
66+
port = 993,
67+
connectionSecurity = com.fsck.k9.mail.ConnectionSecurity.SSL_TLS_REQUIRED,
68+
authenticationType = com.fsck.k9.mail.AuthType.PLAIN,
69+
username = "test",
70+
password = "pass",
71+
clientCertificateAlias = null,
72+
),
73+
outgoingServerSettings = com.fsck.k9.mail.ServerSettings(
74+
type = "smtp",
75+
host = "smtp.example.com",
76+
port = 465,
77+
connectionSecurity = com.fsck.k9.mail.ConnectionSecurity.SSL_TLS_REQUIRED,
78+
authenticationType = com.fsck.k9.mail.AuthType.PLAIN,
79+
username = "test",
80+
password = "pass",
81+
clientCertificateAlias = null,
82+
),
83+
showPictures = showPictures,
84+
isMarkMessageAsReadOnView = isMarkReadOnView,
85+
)
86+
87+
/** Simple StringsResourceManager for tests */
88+
private val testResources = object : net.thunderbird.core.common.resources.StringsResourceManager {
89+
override fun stringResource(resourceId: Int): String = "string_$resourceId"
90+
override fun stringResource(resourceId: Int, vararg formatArgs: Any?): String = error("Not implemented")
91+
}
92+
93+
/** Helper to create the ViewModel */
94+
private fun createViewModel(
95+
accountId: AccountId,
96+
state: ReadingMailSettingsContract.State = defaultState(),
97+
getLegacyAccount: suspend (
98+
AccountId,
99+
) -> Outcome<LegacyAccount, AccountSettingsDomainContract.AccountSettingError> = {
100+
Outcome.success(dummyLegacyAccount(it))
101+
},
102+
updateReadMailSettings: suspend (
103+
AccountId,
104+
AccountSettingsDomainContract.UpdateReadMessageSettingsCommand,
105+
) -> Outcome<Unit, AccountSettingsDomainContract.AccountSettingError> = { _, _ -> Outcome.success(Unit) },
106+
) = ReadingMailSettingsViewModel(
107+
accountId = accountId,
108+
getAccountName = { flowOf(Outcome.success("Subtitle")) },
109+
getLegacyAccount = getLegacyAccount,
110+
updateReadMailSettings = updateReadMailSettings,
111+
logger = TestLogger(),
112+
resources = testResources,
113+
initialState = state,
114+
)
115+
116+
@Test
117+
fun `should navigate back when back pressed`() = runTest {
118+
val accountId = AccountIdFactory.create()
119+
val vm = createViewModel(accountId)
120+
121+
val effects = mutableListOf<ReadingMailSettingsContract.Effect>()
122+
val job = launch { vm.effect.collect { effects.add(it) } }
123+
124+
vm.event(ReadingMailSettingsContract.Event.OnBackPressed)
125+
advanceUntilIdle()
126+
127+
assertThat(effects.first()).isEqualTo(ReadingMailSettingsContract.Effect.NavigateBack)
128+
job.cancel()
129+
}
130+
131+
@Test
132+
fun `should map all show pictures options correctly on initialization`() = runTest {
133+
val accountId = AccountIdFactory.create()
134+
135+
val options = listOf(
136+
ShowPictures.NEVER,
137+
ShowPictures.ALWAYS,
138+
ShowPictures.ONLY_FROM_CONTACTS,
139+
)
140+
141+
options.forEach { showPictures ->
142+
val vm = createViewModel(
143+
accountId,
144+
getLegacyAccount = { Outcome.success(dummyLegacyAccount(accountId, showPictures)) },
145+
)
146+
advanceUntilIdle()
147+
assertThat(vm.state.value.showPictures.id).isEqualTo(showPictures.name)
148+
}
149+
}
150+
151+
@Test
152+
fun `should update show pictures setting and state`() = runTest {
153+
val accountId = AccountIdFactory.create()
154+
var lastCommand: AccountSettingsDomainContract.UpdateReadMessageSettingsCommand? = null
155+
156+
val vm = createViewModel(
157+
accountId,
158+
updateReadMailSettings = { _, command ->
159+
lastCommand = command
160+
Outcome.success(Unit)
161+
},
162+
)
163+
164+
val option = SelectOption(ShowPictures.ALWAYS.name) { "always" }
165+
vm.event(ReadingMailSettingsContract.Event.OnShowPicturesChange(option))
166+
advanceUntilIdle()
167+
168+
assertThat(lastCommand).isEqualTo(
169+
AccountSettingsDomainContract.UpdateReadMessageSettingsCommand.UpdateShowPictures(ShowPictures.ALWAYS.name),
170+
)
171+
assertThat(vm.state.value.showPictures.id).isEqualTo(ShowPictures.ALWAYS.name)
172+
}
173+
174+
@Test
175+
fun `should update mark message as read on view and state`() = runTest {
176+
val accountId = AccountIdFactory.create()
177+
var lastCommand: AccountSettingsDomainContract.UpdateReadMessageSettingsCommand? = null
178+
179+
val vm = createViewModel(
180+
accountId,
181+
updateReadMailSettings = { _, command ->
182+
lastCommand = command
183+
Outcome.success(Unit)
184+
},
185+
)
186+
187+
vm.event(ReadingMailSettingsContract.Event.OnIsMarkMessageAsReadOnViewToggle(true))
188+
advanceUntilIdle()
189+
190+
assertThat(lastCommand).isEqualTo(
191+
AccountSettingsDomainContract.UpdateReadMessageSettingsCommand.UpdateIsMarkMessageAsReadOnView(true),
192+
)
193+
assertThat(vm.state.value.isMarkMessageAsReadOnView).isEqualTo(true)
194+
}
195+
196+
@Test
197+
fun `should update subtitle when account name is loaded`() = runTest {
198+
val accountId = AccountIdFactory.create()
199+
200+
val vm = createViewModel(
201+
accountId,
202+
getLegacyAccount = { Outcome.success(dummyLegacyAccount(accountId)) },
203+
updateReadMailSettings = { _, _ -> Outcome.success(Unit) },
204+
)
205+
206+
// Override getAccountName to emit a custom subtitle
207+
val vmWithSubtitle = ReadingMailSettingsViewModel(
208+
accountId = accountId,
209+
getAccountName = { flowOf(Outcome.success("My Account Name")) },
210+
getLegacyAccount = { Outcome.success(dummyLegacyAccount(accountId)) },
211+
updateReadMailSettings = { _, _ -> Outcome.success(Unit) },
212+
logger = TestLogger(),
213+
resources = testResources,
214+
initialState = defaultState(),
215+
)
216+
217+
advanceUntilIdle()
218+
219+
assertThat(vmWithSubtitle.state.value.subtitle).isEqualTo("My Account Name")
220+
}
221+
222+
@Test
223+
fun `should rollback mark message as read on view if update fails`() = runTest {
224+
val accountId = AccountIdFactory.create()
225+
226+
val vm = createViewModel(
227+
accountId,
228+
updateReadMailSettings = { _, _ ->
229+
Outcome.failure(AccountSettingsDomainContract.AccountSettingError.StorageError("fail"))
230+
},
231+
)
232+
233+
vm.event(ReadingMailSettingsContract.Event.OnIsMarkMessageAsReadOnViewToggle(true))
234+
advanceUntilIdle()
235+
236+
assertThat(vm.state.value.isMarkMessageAsReadOnView).isEqualTo(false)
237+
}
238+
}

0 commit comments

Comments
 (0)