Skip to content

Commit 23ad28e

Browse files
committed
Include Pinned chats in Fire-all from chat history
Internal discussion landed on: Fire-all should wipe every Duck.ai chat, Pinned included. onFireAllRequested now operates on the full item list rather than the non-pinned subset; N=1 fast-path covers a single pinned chat too, and N≥2 builds the confirmation set from every chatId. The fragment shows the fire icon whenever any chat is present, not just when Recent is non-empty.
1 parent d45fbfa commit 23ad28e

3 files changed

Lines changed: 46 additions & 24 deletions

File tree

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/history/ChatHistoryFragment.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,8 @@ class ChatHistoryFragment : DuckDuckGoFragment(R.layout.fragment_chat_history) {
147147
setFireActionVisible(selectMode.selectedChatIds.isNotEmpty())
148148
} else {
149149
applyDefaultToolbar()
150-
// Hide fire when Recent is emptytitle is "Delete N chats?" of the Recent count.
151-
setFireActionVisible(state.recent.isNotEmpty())
150+
// Fire-all wipes every chat including Pinnedshow whenever any chat is present.
151+
setFireActionVisible(state.pinned.isNotEmpty() || state.recent.isNotEmpty())
152152
}
153153
renderConfirmation(state.confirmation)
154154
}

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/history/ChatHistoryViewModel.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,14 +122,14 @@ class ChatHistoryViewModel @Inject constructor(
122122
controls.update { it.copy(search = SearchState()) }
123123
}
124124

125-
/** N=1 spares Pinned; N≥2 routes through the dialog, which wipes every Duck.ai chat. */
125+
/** Fire-all wipes every Duck.ai chat including Pinned. N=1 fires directly; N≥2 confirms via dialog. */
126126
fun onFireAllRequested() {
127-
val recent = latestItems.filter { !it.pinned }
127+
val all = latestItems
128128
when {
129-
recent.isEmpty() -> Unit
130-
recent.size == 1 -> dispatchSelectedClear(setOf(recent.single().chatId))
129+
all.isEmpty() -> Unit
130+
all.size == 1 -> dispatchSelectedClear(setOf(all.single().chatId))
131131
else -> controls.update {
132-
it.copy(confirmation = PendingConfirmation.FireAll(chatIds = recent.mapTo(mutableSetOf()) { i -> i.chatId }))
132+
it.copy(confirmation = PendingConfirmation.FireAll(chatIds = all.mapTo(mutableSetOf()) { i -> i.chatId }))
133133
}
134134
}
135135
}

duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/history/ChatHistoryViewModelTest.kt

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ class ChatHistoryViewModelTest {
217217
// --- Fire-all ---
218218

219219
@Test
220-
fun `onFireAllRequested with two or more Recent chats sets FireAll confirmation with the Recent count`() = runTest {
220+
fun `onFireAllRequested with two or more chats sets FireAll confirmation with every chatId including pinned`() = runTest {
221221
source.value = listOf(
222222
item("p", pinned = true),
223223
item("r1"),
@@ -231,17 +231,17 @@ class ChatHistoryViewModelTest {
231231
viewModel.onFireAllRequested()
232232

233233
val confirming = awaitItem() as Loaded
234-
assertEquals(ChatHistoryUiState.PendingConfirmation.FireAll(chatIds = setOf("r1", "r2", "r3")), confirming.confirmation)
234+
assertEquals(
235+
ChatHistoryUiState.PendingConfirmation.FireAll(chatIds = setOf("p", "r1", "r2", "r3")),
236+
confirming.confirmation,
237+
)
235238
assertTrue(repository.deletedChatIds.isEmpty())
236239
}
237240
}
238241

239242
@Test
240-
fun `onFireAllRequested with exactly one Recent chat dispatches DuckChats Selected with that chat url`() = runTest {
241-
source.value = listOf(
242-
item("p", pinned = true),
243-
item("r1"),
244-
)
243+
fun `onFireAllRequested with exactly one recent chat dispatches DuckChats Selected with that chat url`() = runTest {
244+
source.value = listOf(item("r1"))
245245

246246
viewModel.uiState.test {
247247
awaitInitialLoaded()
@@ -258,19 +258,40 @@ class ChatHistoryViewModelTest {
258258
}
259259

260260
@Test
261-
fun `onFireAllRequested with no Recent chats is a no-op`() = runTest {
261+
fun `onFireAllRequested with exactly one pinned chat dispatches DuckChats Selected with that chat url`() = runTest {
262+
source.value = listOf(item("p", pinned = true))
263+
264+
viewModel.uiState.test {
265+
awaitInitialLoaded()
266+
267+
viewModel.onFireAllRequested()
268+
269+
expectNoEvents()
270+
}
271+
assertEquals(
272+
listOf(setOf(ClearableData.DuckChats.Selected(setOf("https://duck.ai?chatID=p")))),
273+
dataClearingTrigger.calls,
274+
)
275+
assertTrue(repository.deletedChatIds.isEmpty())
276+
}
277+
278+
@Test
279+
fun `onFireAllRequested with only pinned chats and no recent sets FireAll confirmation with the pinned ids`() = runTest {
262280
source.value = listOf(
263281
item("p1", pinned = true),
264282
item("p2", pinned = true),
265283
)
266284

267285
viewModel.uiState.test {
268-
val initial = awaitInitialLoaded()
269-
assertEquals(null, initial.confirmation)
286+
awaitInitialLoaded()
270287

271288
viewModel.onFireAllRequested()
272289

273-
expectNoEvents()
290+
val confirming = awaitItem() as Loaded
291+
assertEquals(
292+
ChatHistoryUiState.PendingConfirmation.FireAll(chatIds = setOf("p1", "p2")),
293+
confirming.confirmation,
294+
)
274295
assertTrue(repository.deletedChatIds.isEmpty())
275296
}
276297
}
@@ -289,7 +310,7 @@ class ChatHistoryViewModelTest {
289310
awaitInitialLoaded()
290311

291312
viewModel.onFireAllRequested()
292-
awaitItem() // confirmation = FireAll(3)
313+
awaitItem() // confirmation = FireAll(5) — p1, p2, r1, r2, r3
293314

294315
viewModel.onFireAllConfirmed()
295316

@@ -337,12 +358,13 @@ class ChatHistoryViewModelTest {
337358
awaitInitialLoaded()
338359

339360
viewModel.onFireAllRequested()
340-
awaitItem() // confirmation = FireAll(2)
361+
awaitItem() // confirmation = FireAll(3) — p, r1, r2
341362
viewModel.onFireAllConfirmed()
342363
awaitItem() // confirmation cleared
343364
}
344365

345-
// ViewModel never touches the repository on the dialog path — production wipes Pinned too.
366+
// ViewModel never touches the repository on the dialog path — production wipes every chat
367+
// including Pinned via the dialog-driven Selected dispatch.
346368
assertEquals(false, repository.deleteAllChatsCalled)
347369
assertTrue(repository.deletedChatIds.isEmpty())
348370
}
@@ -661,17 +683,17 @@ class ChatHistoryViewModelTest {
661683
}
662684

663685
@Test
664-
fun `chatUrlsForDialog returns Recent URLs while a FireAll confirmation is pending`() = runTest {
686+
fun `chatUrlsForDialog returns every chat URL including pinned while a FireAll confirmation is pending`() = runTest {
665687
source.value = listOf(item("p", pinned = true), item("r1"), item("r2"))
666688

667689
viewModel.uiState.test {
668690
awaitInitialLoaded()
669691

670692
viewModel.onFireAllRequested()
671-
awaitItem() // FireAll({r1, r2})
693+
awaitItem() // FireAll({p, r1, r2})
672694

673695
assertEquals(
674-
setOf("https://duck.ai?chatID=r1", "https://duck.ai?chatID=r2"),
696+
setOf("https://duck.ai?chatID=p", "https://duck.ai?chatID=r1", "https://duck.ai?chatID=r2"),
675697
viewModel.chatUrlsForDialog(),
676698
)
677699
}

0 commit comments

Comments
 (0)