Merge branch 'main' into fix-backfill-gaps

This commit is contained in:
Jason Ross
2026-07-02 02:50:56 -05:00
committed by GitHub
14 changed files with 264 additions and 7 deletions
@@ -53,6 +53,34 @@ class LibreMailDatabaseTest {
isStarred = false,
)
@Test
fun observeUnreadCountsAggregatesUnreadSyncedRowsPerAccountAndFolder() = runBlocking {
val messageDao = db.messageDao()
messageDao.insertNew(
listOf(
// acct / INBOX: two unread + one read -> counts 2.
message("acct:INBOX:1").copy(folder = "INBOX", isRead = false),
message("acct:INBOX:2").copy(folder = "INBOX", isRead = false),
message("acct:INBOX:3").copy(folder = "INBOX", isRead = true),
// acct / Archive: one unread -> counts 1.
message("acct:Archive:1").copy(folder = "Archive", isRead = false),
// An unread server-search hit (inInbox = false) must never inflate a badge.
message("acct:INBOX:search").copy(folder = "INBOX", isRead = false, inInbox = false),
// A second account's unread inbox row is counted under its own accountId.
message("acct2:INBOX:1").copy(accountId = "acct2", folder = "INBOX", isRead = false),
),
)
val counts = messageDao.observeUnreadCounts().first()
.associate { (it.accountId to it.folder) to it.unreadCount }
assertEquals(2, counts[("acct" to "INBOX")])
assertEquals(1, counts[("acct" to "Archive")])
assertEquals(1, counts[("acct2" to "INBOX")])
// Fully-read folders and search-only rows produce no group at all.
assertEquals(3, counts.size)
}
@Test
fun deletingMessageCascadesToItsAttachments() = runBlocking {
val messageDao = db.messageDao()
@@ -14,6 +14,7 @@ import org.libremail.domain.model.Message
import org.libremail.domain.model.OutboxMessage
import org.libremail.domain.model.OutgoingMessage
import org.libremail.domain.model.ReplyMode
import org.libremail.domain.model.UnreadCount
import org.libremail.domain.repository.AccountRepository
import org.libremail.domain.repository.MailRepository
import java.io.File
@@ -72,6 +73,7 @@ class FakeMailRepository(
private val folders: List<Folder> = emptyList(),
private val attachments: List<Attachment> = emptyList(),
private val downloadedParts: Set<Int> = emptySet(),
private val unreadCounts: List<UnreadCount> = emptyList(),
) : MailRepository {
val sentMessages = mutableListOf<OutgoingMessage>()
@@ -93,6 +95,8 @@ class FakeMailRepository(
},
)
override fun observeUnreadCounts(): Flow<List<UnreadCount>> = flowOf(unreadCounts)
override suspend fun refreshFolders(accountId: String): Result<Unit> = Result.success(Unit)
override suspend fun getMessage(id: String): Message? = messages.firstOrNull { it.id == id }
@@ -5,6 +5,7 @@ import androidx.activity.ComponentActivity
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -145,10 +146,34 @@ class FolderDrawerTest {
assertTrue(unifiedTapped)
}
@Test
fun folderWithUnreadMail_showsCountBadge_andReadFolderShowsNone() {
setContent(
accounts = listOf(alice),
drawerAccount = alice,
folders = listOf(
folder("imap:a", "INBOX", "INBOX", FolderRole.INBOX),
folder("imap:a", "Archive", "Archive", FolderRole.ARCHIVE),
),
folderUnreadCounts = mapOf("INBOX" to 3),
)
// The inbox badge announces its exact count for screen readers.
val threeUnread = composeTestRule.activity.resources
.getQuantityString(R.plurals.folder_unread_count_description, 3, 3)
composeTestRule.onNodeWithContentDescription(threeUnread).assertIsDisplayed()
// Archive has no unread mail, so no badge is rendered for it.
val oneUnread = composeTestRule.activity.resources
.getQuantityString(R.plurals.folder_unread_count_description, 1, 1)
composeTestRule.onNodeWithContentDescription(oneUnread).assertDoesNotExist()
}
private fun setContent(
accounts: List<Account>,
drawerAccount: Account?,
folders: List<Folder>,
folderUnreadCounts: Map<String, Int> = emptyMap(),
accountsWithUnread: Set<String> = emptySet(),
selectedAccountId: String? = null,
selectedFolder: String = "INBOX",
onSelectUnifiedInbox: () -> Unit = {},
@@ -162,6 +187,8 @@ class FolderDrawerTest {
accounts = accounts,
drawerAccount = drawerAccount,
folders = folders,
folderUnreadCounts = folderUnreadCounts,
accountsWithUnread = accountsWithUnread,
selectedAccountId = selectedAccountId,
selectedFolder = selectedFolder,
onSelectUnifiedInbox = onSelectUnifiedInbox,