Skip to content

Commit da93732

Browse files
committed
test(message-list): add tests for message active/open/toggle side effects
1 parent 09092f6 commit da93732

9 files changed

Lines changed: 945 additions & 275 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
@file:OptIn(ExperimentalCoroutinesApi::class)
2+
3+
package net.thunderbird.feature.mail.message.list.internal.ui.state.machine
4+
5+
import androidx.compose.ui.graphics.Color
6+
import kotlinx.collections.immutable.ImmutableSet
7+
import kotlinx.collections.immutable.persistentSetOf
8+
import kotlinx.coroutines.ExperimentalCoroutinesApi
9+
import kotlinx.coroutines.flow.Flow
10+
import kotlinx.coroutines.flow.flowOf
11+
import kotlinx.coroutines.test.TestScope
12+
import kotlinx.coroutines.test.advanceUntilIdle
13+
import net.thunderbird.core.common.action.SwipeAction
14+
import net.thunderbird.core.common.action.SwipeActions
15+
import net.thunderbird.core.logging.testing.TestLogger
16+
import net.thunderbird.core.preference.debugging.DebuggingSettings
17+
import net.thunderbird.core.preference.debugging.DebuggingSettingsPreferenceManager
18+
import net.thunderbird.core.preference.display.visualSettings.message.list.MessageListDateTimeFormat
19+
import net.thunderbird.core.preference.display.visualSettings.message.list.UiDensity
20+
import net.thunderbird.core.testing.TestClock
21+
import net.thunderbird.feature.account.AccountId
22+
import net.thunderbird.feature.account.AccountIdFactory
23+
import net.thunderbird.feature.account.UnifiedAccountId
24+
import net.thunderbird.feature.mail.folder.api.FolderType
25+
import net.thunderbird.feature.mail.message.list.domain.model.SortCriteria
26+
import net.thunderbird.feature.mail.message.list.domain.model.SortType
27+
import net.thunderbird.feature.mail.message.list.preferences.ActionRequiringUserConfirmation
28+
import net.thunderbird.feature.mail.message.list.preferences.MessageListPreferences
29+
import net.thunderbird.feature.mail.message.list.ui.effect.MessageListEffect
30+
import net.thunderbird.feature.mail.message.list.ui.event.FolderEvent
31+
import net.thunderbird.feature.mail.message.list.ui.event.MessageListEvent
32+
import net.thunderbird.feature.mail.message.list.ui.event.MessageListSearchEvent
33+
import net.thunderbird.feature.mail.message.list.ui.state.Account
34+
import net.thunderbird.feature.mail.message.list.ui.state.ComposedAddressUi
35+
import net.thunderbird.feature.mail.message.list.ui.state.Folder
36+
import net.thunderbird.feature.mail.message.list.ui.state.MessageItemUi
37+
import net.thunderbird.feature.mail.message.list.ui.state.MessageItemUi.State
38+
39+
open class BaseMessageListStateMachineTest {
40+
protected fun TestScope.createStateMachine(
41+
dispatch: (MessageListEvent) -> Unit = {},
42+
dispatchUiEffect: (MessageListEffect) -> Unit = {},
43+
) = MessageListStateMachine(
44+
logger = TestLogger(),
45+
clock = TestClock(),
46+
scope = this,
47+
dispatch = dispatch,
48+
dispatchUiEffect = dispatchUiEffect,
49+
debuggingSettingsPreferenceManager = FakeDebuggingSettingsPreferenceManager(),
50+
)
51+
52+
protected suspend fun TestScope.createStateMachineOnLoadingState(
53+
preferences: MessageListPreferences = createMessageListPreferences(),
54+
sortCriteriaPerAccount: Map<AccountId?, SortCriteria> = mapOf(null to SortCriteria(SortType.DateDesc)),
55+
swipeActions: Map<AccountId, SwipeActions> = mapOf(
56+
AccountIdFactory.create() to SwipeActions(SwipeAction.None, SwipeAction.None),
57+
),
58+
folder: Folder = createFolder(),
59+
): MessageListStateMachine {
60+
val stateMachine = createStateMachine()
61+
advanceUntilIdle()
62+
stateMachine.process(event = MessageListEvent.UpdatePreferences(preferences))
63+
stateMachine.process(event = MessageListEvent.SortCriteriaLoaded(sortCriteriaPerAccount))
64+
stateMachine.process(event = MessageListEvent.SwipeActionsLoaded(swipeActions))
65+
stateMachine.process(event = FolderEvent.FolderLoaded(folder = folder))
66+
stateMachine.process(event = MessageListEvent.AllConfigsReady)
67+
advanceUntilIdle()
68+
return stateMachine
69+
}
70+
71+
protected suspend fun TestScope.createStateMachineOnLoadedState(
72+
messages: List<MessageItemUi>,
73+
preferences: MessageListPreferences = createMessageListPreferences(),
74+
sortCriteriaPerAccount: Map<AccountId?, SortCriteria> = mapOf(null to SortCriteria(SortType.DateDesc)),
75+
swipeActions: Map<AccountId, SwipeActions> = mapOf(
76+
AccountIdFactory.create() to SwipeActions(SwipeAction.None, SwipeAction.None),
77+
),
78+
folder: Folder = createFolder(),
79+
): MessageListStateMachine {
80+
val stateMachine = createStateMachine()
81+
advanceUntilIdle()
82+
stateMachine.process(event = MessageListEvent.UpdatePreferences(preferences))
83+
stateMachine.process(event = MessageListEvent.SortCriteriaLoaded(sortCriteriaPerAccount))
84+
stateMachine.process(event = MessageListEvent.SwipeActionsLoaded(swipeActions))
85+
stateMachine.process(event = FolderEvent.FolderLoaded(folder = folder))
86+
stateMachine.process(event = MessageListEvent.AllConfigsReady)
87+
stateMachine.process(event = MessageListEvent.UpdateLoadingProgress(progress = 1f))
88+
stateMachine.process(event = MessageListEvent.MessagesLoaded(messages))
89+
advanceUntilIdle()
90+
return stateMachine
91+
}
92+
93+
protected suspend fun TestScope.createStateMachineOnSearchingMessages(
94+
messages: List<MessageItemUi>,
95+
preferences: MessageListPreferences = createMessageListPreferences(),
96+
sortCriteriaPerAccount: Map<AccountId?, SortCriteria> = mapOf(null to SortCriteria(SortType.DateDesc)),
97+
swipeActions: Map<AccountId, SwipeActions> = mapOf(
98+
AccountIdFactory.create() to SwipeActions(SwipeAction.None, SwipeAction.None),
99+
),
100+
folder: Folder = createFolder(),
101+
): MessageListStateMachine {
102+
val stateMachine = createStateMachine()
103+
advanceUntilIdle()
104+
stateMachine.process(MessageListEvent.UpdatePreferences(preferences))
105+
stateMachine.process(MessageListEvent.SortCriteriaLoaded(sortCriteriaPerAccount))
106+
stateMachine.process(MessageListEvent.SwipeActionsLoaded(swipeActions))
107+
stateMachine.process(event = FolderEvent.FolderLoaded(folder = folder))
108+
stateMachine.process(event = MessageListEvent.AllConfigsReady)
109+
stateMachine.process(event = MessageListEvent.UpdateLoadingProgress(progress = 1f))
110+
stateMachine.process(event = MessageListEvent.MessagesLoaded(messages))
111+
stateMachine.process(event = MessageListSearchEvent.EnterSearchMode)
112+
advanceUntilIdle()
113+
return stateMachine
114+
}
115+
116+
protected suspend fun TestScope.createStateMachineOnSelectingMessages(
117+
messages: List<MessageItemUi>,
118+
preferences: MessageListPreferences = createMessageListPreferences(),
119+
sortCriteriaPerAccount: Map<AccountId?, SortCriteria> = mapOf(null to SortCriteria(SortType.DateDesc)),
120+
swipeActions: Map<AccountId, SwipeActions> = mapOf(
121+
AccountIdFactory.create() to SwipeActions(SwipeAction.None, SwipeAction.None),
122+
),
123+
folder: Folder = createFolder(),
124+
): MessageListStateMachine {
125+
val stateMachine = createStateMachine()
126+
advanceUntilIdle()
127+
stateMachine.process(event = MessageListEvent.UpdatePreferences(preferences))
128+
stateMachine.process(event = MessageListEvent.SortCriteriaLoaded(sortCriteriaPerAccount))
129+
stateMachine.process(event = MessageListEvent.SwipeActionsLoaded(swipeActions))
130+
stateMachine.process(event = FolderEvent.FolderLoaded(folder = folder))
131+
stateMachine.process(event = MessageListEvent.AllConfigsReady)
132+
stateMachine.process(event = MessageListEvent.UpdateLoadingProgress(progress = 1f))
133+
stateMachine.process(event = MessageListEvent.MessagesLoaded(messages))
134+
stateMachine.process(event = MessageListEvent.EnterSelectionMode)
135+
advanceUntilIdle()
136+
return stateMachine
137+
}
138+
139+
protected fun createMessageUiItemList(
140+
size: Int,
141+
accountId: AccountId = AccountIdFactory.create(),
142+
builder: (index: Int) -> MessageItemUi = { index ->
143+
when {
144+
index % 6 == 0 -> createMessageUiItem(
145+
state = State.Unread,
146+
id = "id$index",
147+
accountId = accountId,
148+
)
149+
150+
index % 4 == 0 -> createMessageUiItem(
151+
state = State.Read,
152+
id = "id$index",
153+
accountId = accountId,
154+
)
155+
156+
index % 2 == 0 -> createMessageUiItem(
157+
state = State.New,
158+
id = "id$index",
159+
accountId = accountId,
160+
)
161+
162+
else -> createMessageUiItem(
163+
state = State.Unread,
164+
id = "id$index",
165+
accountId = accountId,
166+
).copy(active = true)
167+
}
168+
},
169+
): List<MessageItemUi> = List(size) { builder(it) }
170+
171+
protected fun createMessageUiItem(
172+
state: State,
173+
id: String,
174+
messageReference: String = "message_reference",
175+
accountId: AccountId = AccountIdFactory.create(),
176+
senders: ComposedAddressUi = ComposedAddressUi(displayName = "sender"),
177+
subject: String = "mock subject",
178+
excerpt: String = "mock excerpt",
179+
formattedReceivedAt: String = "Jan 2026",
180+
hasAttachments: Boolean = false,
181+
starred: Boolean = false,
182+
encrypted: Boolean = false,
183+
answered: Boolean = false,
184+
forwarded: Boolean = false,
185+
selected: Boolean = false,
186+
threadCount: Int = 0,
187+
): MessageItemUi = MessageItemUi(
188+
state = state,
189+
id = id,
190+
messageReference = messageReference,
191+
account = Account(id = accountId, color = Color.Unspecified),
192+
senders = senders,
193+
subject = subject,
194+
excerpt = excerpt,
195+
formattedReceivedAt = formattedReceivedAt,
196+
hasAttachments = hasAttachments,
197+
starred = starred,
198+
encrypted = encrypted,
199+
answered = answered,
200+
forwarded = forwarded,
201+
selected = selected,
202+
threadCount = threadCount,
203+
)
204+
205+
protected fun createFolder(
206+
id: String = "fake",
207+
account: Account = Account(id = UnifiedAccountId, Color.Unspecified),
208+
name: String = "unified",
209+
type: FolderType = FolderType.INBOX,
210+
parent: Folder? = null,
211+
root: Folder? = null,
212+
canExpunge: Boolean = false,
213+
): Folder = Folder(
214+
id = id,
215+
account = account,
216+
name = name,
217+
type = type,
218+
parent = parent,
219+
root = root,
220+
canExpunge = canExpunge,
221+
)
222+
223+
protected fun createMessageListPreferences(
224+
density: UiDensity = UiDensity.Default,
225+
groupConversations: Boolean = false,
226+
showCorrespondentNames: Boolean = false,
227+
showMessageAvatar: Boolean = false,
228+
showFavouriteButton: Boolean = false,
229+
senderAboveSubject: Boolean = false,
230+
excerptLines: Int = 1,
231+
dateTimeFormat: MessageListDateTimeFormat = MessageListDateTimeFormat.Contextual,
232+
actionRequiringUserConfirmation: ImmutableSet<ActionRequiringUserConfirmation> = persistentSetOf(),
233+
colorizeBackgroundWhenRead: Boolean = false,
234+
) = MessageListPreferences(
235+
density = density,
236+
groupConversations = groupConversations,
237+
showCorrespondentNames = showCorrespondentNames,
238+
showMessageAvatar = showMessageAvatar,
239+
showFavouriteButton = showFavouriteButton,
240+
senderAboveSubject = senderAboveSubject,
241+
excerptLines = excerptLines,
242+
dateTimeFormat = dateTimeFormat,
243+
actionRequiringUserConfirmation = actionRequiringUserConfirmation,
244+
colorizeBackgroundWhenRead = colorizeBackgroundWhenRead,
245+
)
246+
247+
protected class FakeDebuggingSettingsPreferenceManager(
248+
protected val enabledDebug: Boolean = true,
249+
) : DebuggingSettingsPreferenceManager {
250+
override fun save(config: DebuggingSettings) {
251+
TODO("Not yet implemented")
252+
}
253+
254+
override fun getConfig(): DebuggingSettings = DebuggingSettings(
255+
isDebugLoggingEnabled = enabledDebug,
256+
isSyncLoggingEnabled = false,
257+
isSensitiveLoggingEnabled = false,
258+
)
259+
260+
override fun getConfigFlow(): Flow<DebuggingSettings> = flowOf(getConfig())
261+
}
262+
}

0 commit comments

Comments
 (0)