revert(chat): remove busy-send panel and composer queue button (#1308)

This commit is contained in:
m57 authored and GitHub committed 2026-09-27 22:23:59 +03:00
1 parent bbfcfada6b
commit dc4f2b0794
9 files changed
+2 -520

No files matched your search

@@ -10,13 +10,11 @@ import androidx.core.content.ContextCompat
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.m57.hermescontrol.data.model.Attachment
import com.m57.hermescontrol.data.model.BusySendMode
import com.m57.hermescontrol.data.ws.ConnectionStatus
import com.m57.hermescontrol.ui.common.ActionProgressController
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.verify
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@@ -128,37 +126,4 @@ class ChatScreenTest {
composeTestRule.onNodeWithTag("chat_input").assertIsDisplayed()
composeTestRule.onNodeWithTag("send_button").assertIsDisplayed()
}
@Test
fun chatScreen_primarySendUsesSavedBusyDefault() {
val uiState =
ChatUiState(
connectionStatus = ConnectionStatus.CONNECTED,
isSessionReady = true,
busySendMode = BusySendMode.QUEUE,
)
val mockViewModel = mockk<ChatViewModel>(relaxed = true)
every { mockViewModel.uiState } returns MutableStateFlow(uiState).asStateFlow()
every { mockViewModel.streamingState } returns MutableStateFlow(StreamingState()).asStateFlow()
every { mockViewModel.timelineState } returns MutableStateFlow(ChatTimelineState()).asStateFlow()
every { mockViewModel.connectionOperationState } returns
MutableStateFlow(ConnectionOperationUiState()).asStateFlow()
every { mockViewModel.actionProgress } returns
ActionProgressController(scope = CoroutineScope(Dispatchers.Main))
every { mockViewModel.sendMessage(any(), any()) } returns true
composeTestRule.setContent {
ChatScreen(
onOpenDrawer = {},
sessionId = null,
viewModel = mockViewModel,
)
}
composeTestRule.onNodeWithTag("chat_input").performTextInput("Use my saved default")
composeTestRule.onNodeWithTag("send_button").performClick()
verify(exactly = 1) { mockViewModel.sendMessage("Use my saved default", null) }
verify(exactly = 0) { mockViewModel.sendMessage("Use my saved default", BusySendMode.CORRECT) }
}
}
@@ -32,7 +32,6 @@ import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry
import com.m57.hermescontrol.R
import com.m57.hermescontrol.data.model.Attachment
import com.m57.hermescontrol.data.model.BusySendMode
import com.m57.hermescontrol.data.ws.CommandCatalog
import org.junit.Assert.assertEquals
import org.junit.Rule
@@ -58,158 +57,6 @@ class ComposerInteractionTest {
private var photosTaps = 0
private var fileTaps = 0
private fun setContextualComposer(
text: String,
isMainTurnBusy: Boolean,
attachments: List<Attachment> = emptyList(),
onSend: () -> Unit = {},
onBusySend: (BusySendMode) -> Unit = {},
isConnected: Boolean = true,
pendingReasoningLevel: String? = null,
) {
composeTestRule.setContent {
ChatInputBar(
inputFieldValue = TextFieldValue(text),
onInputChange = {},
onSend = onSend,
onBusySend = onBusySend,
onMicTap = {},
isListening = false,
isAgentTyping = isMainTurnBusy,
isMainTurnBusy = isMainTurnBusy,
isConnected = isConnected,
commandCatalog = CommandCatalog(),
isSessionReady = true,
pendingAttachments = attachments,
pendingReasoningLevel = pendingReasoningLevel,
)
}
}
@Test
fun idleDraft_showsRegularSendWithoutBusyActions() {
setContextualComposer(text = "hello", isMainTurnBusy = false)
composeTestRule.onNodeWithTag("send_button").assertIsDisplayed()
composeTestRule.onNodeWithTag("queue_button").assertDoesNotExist()
}
@Test
fun busyBlankDraft_hidesBusyActions() {
setContextualComposer(text = "", isMainTurnBusy = true)
composeTestRule.onNodeWithTag("send_button").assertDoesNotExist()
composeTestRule.onNodeWithTag("queue_button").assertDoesNotExist()
}
@Test
fun busyTextDraft_showsSendAndQueueAndRoutesCallbacks() {
var regularSends = 0
val selected = mutableListOf<BusySendMode>()
setContextualComposer(
text = "change course",
isMainTurnBusy = true,
onSend = { regularSends++ },
onBusySend = selected::add,
)
composeTestRule.onNodeWithTag("send_button").assertIsDisplayed().performClick()
composeTestRule.onNodeWithTag("queue_button").assertIsDisplayed().performClick()
composeTestRule.runOnIdle {
assertEquals(1, regularSends)
assertEquals(listOf(BusySendMode.QUEUE), selected)
}
}
@Test
fun busyAttachmentOnly_showsSendAndQueue() {
val attachment = Attachment("content://document/1", "notes.txt", "text/plain")
setContextualComposer(text = "", isMainTurnBusy = true, attachments = listOf(attachment))
composeTestRule.onNodeWithTag("send_button").assertIsDisplayed()
composeTestRule.onNodeWithTag("queue_button").assertIsDisplayed()
}
@Test
fun busyDisconnectedDraft_disablesSendAndQueue() {
setContextualComposer(text = "hello", isMainTurnBusy = true, isConnected = false)
composeTestRule.onNodeWithTag("send_button").assertIsNotEnabled()
composeTestRule.onNodeWithTag("queue_button").assertIsNotEnabled()
}
@Test
fun busyPendingReasoning_disablesSendAndQueue() {
setContextualComposer(text = "hello", isMainTurnBusy = true, pendingReasoningLevel = "high")
composeTestRule.onNodeWithTag("send_button").assertIsNotEnabled()
composeTestRule.onNodeWithTag("queue_button").assertIsNotEnabled()
}
@Test
fun busySlashDraft_usesRegularSendWithoutBusyActions() {
var regularSends = 0
setContextualComposer(
text = "/stop",
isMainTurnBusy = true,
onSend = { regularSends++ },
)
composeTestRule.onNodeWithTag("queue_button").assertDoesNotExist()
composeTestRule.onNodeWithTag("send_button").performClick()
composeTestRule.runOnIdle { assertEquals(1, regularSends) }
}
@Test
fun busyBecomesIdle_hidesQueueAndPreservesTypedDraft() {
val busy = mutableStateOf(true)
composeTestRule.setContent {
var input by remember { mutableStateOf(TextFieldValue("draft in progress")) }
ChatInputBar(
inputFieldValue = input,
onInputChange = { input = it },
onSend = {},
onBusySend = {},
onMicTap = {},
isListening = false,
isAgentTyping = busy.value,
isMainTurnBusy = busy.value,
isConnected = true,
commandCatalog = CommandCatalog(),
isSessionReady = true,
)
}
composeTestRule.onNodeWithTag("queue_button").assertIsDisplayed()
composeTestRule.runOnUiThread { busy.value = false }
composeTestRule.onNodeWithTag("queue_button").assertDoesNotExist()
composeTestRule.onNodeWithTag("chat_input").assertTextEquals("draft in progress")
composeTestRule.onNodeWithTag("send_button").assertIsDisplayed()
}
@Test
fun busyDraft_narrowWidthKeepsSendAndQueueAccessible() {
composeTestRule.setContent {
Box(Modifier.width(220.dp)) {
ChatInputBar(
inputFieldValue = TextFieldValue("change course"),
onInputChange = {},
onSend = {},
onBusySend = {},
onMicTap = {},
isListening = false,
isAgentTyping = true,
isMainTurnBusy = true,
isConnected = true,
commandCatalog = CommandCatalog(),
isSessionReady = true,
)
}
}
composeTestRule.onNodeWithTag("queue_button").assertIsDisplayed()
composeTestRule.onNodeWithTag("send_button").assertIsDisplayed()
}
/** Renders the real input bar with live text and a mic that toggles like ChatMediaLaunchers. */
private fun setComposer(
reasoningLevel: String? = "medium",
@@ -1,98 +0,0 @@
package com.m57.hermescontrol.ui.chat.components
import androidx.activity.ComponentActivity
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.m57.hermescontrol.R
import com.m57.hermescontrol.data.model.BusySendMode
import com.m57.hermescontrol.ui.chat.PendingSend
import com.m57.hermescontrol.ui.chat.PendingSendState
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PendingSendPanelTest {
@get:Rule
val compose = createAndroidComposeRule<ComponentActivity>()
private fun row(state: PendingSendState) =
PendingSend("receipt", "scope", "session", "My message", mode = BusySendMode.CORRECT, state = state)
@Test
fun ordinarySendNeverShowsDeliveryPanel() {
val state = mutableStateOf(PendingSendState.SENDING)
compose.setContent { PendingSendPanel(listOf(row(state.value)), mainTurnBusy = false, onSendNow = {}) }
compose.onNodeWithText("My message").assertDoesNotExist()
compose.onNodeWithText(compose.activity.getString(R.string.chat_pending_sends)).assertDoesNotExist()
compose.runOnIdle { state.value = PendingSendState.ACCEPTED }
compose.onNodeWithText("My message").assertDoesNotExist()
compose.onNodeWithText(compose.activity.getString(R.string.chat_pending_accepted)).assertDoesNotExist()
}
@Test
fun queueDisappearsWhenDispatchedWithoutAwaitingLabel() {
val state = mutableStateOf(PendingSendState.QUEUED)
compose.setContent { PendingSendPanel(listOf(row(state.value)), mainTurnBusy = false, onSendNow = {}) }
compose.onNodeWithText(compose.activity.getString(R.string.chat_pending_queued)).assertIsDisplayed()
compose.onNodeWithText(compose.activity.getString(R.string.chat_pending_send_now_warning)).assertDoesNotExist()
compose.runOnIdle { state.value = PendingSendState.SENDING }
compose.onNodeWithText("My message").assertDoesNotExist()
compose.runOnIdle { state.value = PendingSendState.ACCEPTED }
compose.onNodeWithText("My message").assertDoesNotExist()
}
@Test
fun failedAndUncertainDeliveryRemainRecoverable() {
val state = mutableStateOf(PendingSendState.REJECTED)
var retried: String? = null
compose.setContent {
PendingSendPanel(
listOf(row(state.value)),
mainTurnBusy = false,
onSendNow = { retried = it },
)
}
compose.onNodeWithText(compose.activity.getString(R.string.chat_pending_rejected)).assertIsDisplayed()
compose.onNodeWithText(compose.activity.getString(R.string.chat_pending_send_now)).performClick()
compose.runOnIdle {
assertEquals("receipt", retried)
state.value = PendingSendState.UNKNOWN
}
compose.onNodeWithText(compose.activity.getString(R.string.chat_pending_unknown)).assertIsDisplayed()
compose.onNodeWithText(compose.activity.getString(R.string.chat_pending_send_now_warning)).assertIsDisplayed()
}
@Test
fun busyPanelWarnsAndLabelsEveryRecoverableActionAsStopAndSend() {
val state = mutableStateOf(PendingSendState.QUEUED)
compose.setContent {
PendingSendPanel(
sends = listOf(row(state.value)),
mainTurnBusy = true,
onSendNow = {},
)
}
listOf(
PendingSendState.QUEUED,
PendingSendState.PARKED,
PendingSendState.REJECTED,
PendingSendState.UNKNOWN,
).forEach { pendingState ->
compose.runOnIdle { state.value = pendingState }
compose
.onNodeWithText(
compose.activity.getString(R.string.chat_pending_send_now_warning),
).assertIsDisplayed()
compose.onNodeWithText(compose.activity.getString(R.string.chat_busy_stop_and_send)).assertIsDisplayed()
compose.onNodeWithText(compose.activity.getString(R.string.chat_pending_send_now)).assertDoesNotExist()
compose.onNodeWithText(compose.activity.getString(R.string.chat_pending_send_again)).assertDoesNotExist()
}
}
}