🧪 Add unit tests for ChatViewModel attachment manipulation (#511)

* Add unit tests for ChatViewModel attachment manipulation

Adds tests covering addAttachment, removeAttachment (valid/invalid indices),
and clearAttachments in ChatViewModelTest. This ensures the attachment
state management logic works as expected.

* test(#511): add mixed valid/invalid remove sequence + DRY attachment setup

Folds in both Sourcery review notes:
- New testRemoveAttachment_mixedValidAndInvalidSequence asserts list
  stays consistent across a valid remove, out-of-bounds + negative no-ops,
  then another valid remove on the shifted list.
- addDummyAttachments() helper removes repeated VM/advance/add boilerplate.

Verified: ktlintCheck clean + ChatViewModelTest 37/37 pass locally.
This commit is contained in:
M57 authored and GitHub committed 2026-07-09 15:49:34 +03:00
1 parent 5bc5d17032
commit 529ab0bc83
1 file changed
+121
@@ -666,6 +666,127 @@ class ChatViewModelTest {
}
}
// ── Attachments ──────────────────────────────────────────────────────────
/** Add [count] dummy attachments so a test starts with a populated list. */
private fun TestScope.addDummyAttachments(
viewModel: ChatViewModel,
count: Int,
) {
repeat(count) { i ->
viewModel.addAttachment("uri$i", "file$i.txt", "text/plain", (i + 1) * 100L)
}
advanceUntilIdle()
}
@Test
fun testAddAttachment() =
runTest {
val viewModel = createViewModel()
advanceUntilIdle()
viewModel.addAttachment(
uri = "content://dummy/1",
name = "dummy.txt",
mimeType = "text/plain",
size = 1024L,
)
advanceUntilIdle()
val pending = viewModel.uiState.value.pendingAttachments
assertEquals(1, pending.size)
assertEquals("content://dummy/1", pending[0].uri)
assertEquals("dummy.txt", pending[0].name)
}
@Test
fun testRemoveAttachment_validIndex() =
runTest {
val viewModel = createViewModel()
advanceUntilIdle()
viewModel.addAttachment("uri1", "file1.txt", "text/plain", 100)
viewModel.addAttachment("uri2", "file2.txt", "text/plain", 200)
advanceUntilIdle()
assertEquals(2, viewModel.uiState.value.pendingAttachments.size)
viewModel.removeAttachment(0)
advanceUntilIdle()
val pending = viewModel.uiState.value.pendingAttachments
assertEquals(1, pending.size)
assertEquals("uri2", pending[0].uri)
}
@Test
fun testRemoveAttachment_invalidIndex() =
runTest {
val viewModel = createViewModel()
advanceUntilIdle()
viewModel.addAttachment("uri1", "file1.txt", "text/plain", 100)
advanceUntilIdle()
assertEquals(1, viewModel.uiState.value.pendingAttachments.size)
// Out of bounds index should not crash or change list
viewModel.removeAttachment(5)
viewModel.removeAttachment(-1)
advanceUntilIdle()
val pending = viewModel.uiState.value.pendingAttachments
assertEquals(1, pending.size)
assertEquals("uri1", pending[0].uri)
}
@Test
fun testRemoveAttachment_mixedValidAndInvalidSequence() =
runTest {
val viewModel = createViewModel()
advanceUntilIdle()
addDummyAttachments(viewModel, 3) // [uri0, uri1, uri2]
// 1. Remove a valid index (the middle item) → list shrinks correctly.
viewModel.removeAttachment(1)
advanceUntilIdle()
assertEquals(2, viewModel.uiState.value.pendingAttachments.size)
assertEquals("uri0", viewModel.uiState.value.pendingAttachments[0].uri)
assertEquals("uri2", viewModel.uiState.value.pendingAttachments[1].uri)
// 2. Fire invalid removals (out of bounds + negative) — must be no-ops.
viewModel.removeAttachment(99)
viewModel.removeAttachment(-1)
advanceUntilIdle()
assertEquals(2, viewModel.uiState.value.pendingAttachments.size)
// 3. Another valid removal on the shifted list → still consistent.
viewModel.removeAttachment(1)
advanceUntilIdle()
val pending = viewModel.uiState.value.pendingAttachments
assertEquals(1, pending.size)
assertEquals("uri0", pending[0].uri)
}
@Test
fun testClearAttachments() =
runTest {
val viewModel = createViewModel()
advanceUntilIdle()
viewModel.addAttachment("uri1", "file1.txt", "text/plain", 100)
viewModel.addAttachment("uri2", "file2.txt", "text/plain", 200)
advanceUntilIdle()
assertEquals(2, viewModel.uiState.value.pendingAttachments.size)
viewModel.clearAttachments()
advanceUntilIdle()
val pending = viewModel.uiState.value.pendingAttachments
assertTrue(pending.isEmpty())
}
// ── Send message ─────────────────────────────────────────────────────────
@Test