fix(tui): update tests to match refactored InputMode and flat TuiState
Sync all TUI reducer and input tests with the state model refactor: InputMode.None/WorkflowId/SteeringNote/Filter → ROUTER/NAVIGATE/STEER/FILTER; InputState(mode, text) → TuiState(inputMode, inputBuffer); fix SessionStarted effect assertion to expect ConnectSession.
This commit is contained in:
@@ -8,110 +8,110 @@ import kotlin.test.assertNull
|
||||
|
||||
class KeyResolverTest {
|
||||
@Test
|
||||
fun `n in None mode resolves to OpenNewSessionPrompt`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.NewSession, InputMode.None, "")
|
||||
fun `n in ROUTER mode resolves to OpenNewSessionPrompt`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.NewSession, InputMode.ROUTER, "")
|
||||
assertEquals(Action.OpenNewSessionPrompt, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `command letters in input mode resolve to null`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.NewSession, InputMode.WorkflowId, "")
|
||||
fun `command letters in text input mode resolve to null`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.NewSession, InputMode.FILTER, "")
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CharInput in input mode resolves to AppendChar`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.CharInput('a'), InputMode.WorkflowId, "")
|
||||
fun `CharInput in text input mode resolves to AppendChar`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.CharInput('a'), InputMode.FILTER, "")
|
||||
assertEquals(Action.AppendChar('a'), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Enter on blank input resolves to null`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Enter, InputMode.WorkflowId, "")
|
||||
val result = KeyResolver.resolve(KeyEvent.Enter, InputMode.FILTER, "")
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Enter on non-blank input resolves to SubmitInput`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Enter, InputMode.WorkflowId, "test")
|
||||
val result = KeyResolver.resolve(KeyEvent.Enter, InputMode.FILTER, "test")
|
||||
assertEquals(Action.SubmitInput, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Escape in input mode resolves to CancelInput`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Escape, InputMode.WorkflowId, "")
|
||||
fun `Escape in text input mode resolves to CancelInput`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Escape, InputMode.FILTER, "")
|
||||
assertEquals(Action.CancelInput, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Backspace in input mode resolves to Backspace action`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Backspace, InputMode.WorkflowId, "")
|
||||
fun `Backspace in text input mode resolves to Backspace action`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Backspace, InputMode.FILTER, "")
|
||||
assertEquals(Action.Backspace, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Quit in None mode resolves to Quit`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Quit, InputMode.None, "")
|
||||
fun `Quit in ROUTER mode resolves to Quit`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Quit, InputMode.ROUTER, "")
|
||||
assertEquals(Action.Quit, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Quit in input mode resolves to null`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Quit, InputMode.WorkflowId, "")
|
||||
fun `Quit in text input mode resolves to null`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Quit, InputMode.FILTER, "")
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Cancel in None mode resolves to CancelSelectedSession`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Cancel, InputMode.None, "")
|
||||
fun `Cancel in ROUTER mode resolves to CancelSelectedSession`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Cancel, InputMode.ROUTER, "")
|
||||
assertEquals(Action.CancelSelectedSession, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Approve in None mode resolves to ApproveActive`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Approve, InputMode.None, "")
|
||||
fun `Approve in ROUTER mode resolves to ApproveActive`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Approve, InputMode.ROUTER, "")
|
||||
assertEquals(Action.ApproveActive, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Reject in None mode resolves to RejectActive`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Reject, InputMode.None, "")
|
||||
fun `Reject in ROUTER mode resolves to RejectActive`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Reject, InputMode.ROUTER, "")
|
||||
assertEquals(Action.RejectActive, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Steer in None mode resolves to OpenSteeringPrompt`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Steer, InputMode.None, "")
|
||||
fun `Steer in ROUTER mode resolves to OpenSteeringPrompt`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Steer, InputMode.ROUTER, "")
|
||||
assertEquals(Action.OpenSteeringPrompt, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavUp in None mode resolves to NavigateUp`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.NavUp, InputMode.None, "")
|
||||
fun `NavUp in ROUTER mode resolves to NavigateUp`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.NavUp, InputMode.ROUTER, "")
|
||||
assertEquals(Action.NavigateUp, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavDown in None mode resolves to NavigateDown`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.NavDown, InputMode.None, "")
|
||||
fun `NavDown in ROUTER mode resolves to NavigateDown`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.NavDown, InputMode.ROUTER, "")
|
||||
assertEquals(Action.NavigateDown, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Filter in None mode resolves to OpenFilter`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Filter, InputMode.None, "")
|
||||
fun `Filter in ROUTER mode resolves to OpenFilter`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Filter, InputMode.ROUTER, "")
|
||||
assertEquals(Action.OpenFilter, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Enter on whitespace-only input resolves to null`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.Enter, InputMode.WorkflowId, " ")
|
||||
val result = KeyResolver.resolve(KeyEvent.Enter, InputMode.FILTER, " ")
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CharInput in None mode resolves to null`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.CharInput('x'), InputMode.None, "")
|
||||
fun `CharInput in ROUTER mode resolves to null`() {
|
||||
val result = KeyResolver.resolve(KeyEvent.CharInput('x'), InputMode.ROUTER, "")
|
||||
assertNull(result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,27 +14,27 @@ class TambouiKeyMapperTest {
|
||||
// --- Ctrl-C always → Quit ---
|
||||
|
||||
@Test
|
||||
fun `ctrl-c maps to Quit in InputMode None`() {
|
||||
fun `ctrl-c maps to Quit in InputMode ROUTER`() {
|
||||
val event = TambouiKeyEvent(KeyCode.CHAR, KeyModifiers.CTRL, 'c'.code)
|
||||
assertEquals(KeyEvent.Quit, mapKey(event, InputMode.None))
|
||||
assertEquals(KeyEvent.Quit, mapKey(event, InputMode.ROUTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ctrl-c maps to Quit in InputMode Filter`() {
|
||||
fun `ctrl-c maps to Quit in InputMode FILTER`() {
|
||||
val event = TambouiKeyEvent(KeyCode.CHAR, KeyModifiers.CTRL, 'c'.code)
|
||||
assertEquals(KeyEvent.Quit, mapKey(event, InputMode.Filter))
|
||||
assertEquals(KeyEvent.Quit, mapKey(event, InputMode.FILTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ctrl-c maps to Quit in InputMode WorkflowId`() {
|
||||
fun `ctrl-c maps to Quit in InputMode NAVIGATE`() {
|
||||
val event = TambouiKeyEvent(KeyCode.CHAR, KeyModifiers.CTRL, 'c'.code)
|
||||
assertEquals(KeyEvent.Quit, mapKey(event, InputMode.WorkflowId))
|
||||
assertEquals(KeyEvent.Quit, mapKey(event, InputMode.NAVIGATE))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ctrl-c maps to Quit in InputMode SteeringNote`() {
|
||||
fun `ctrl-c maps to Quit in InputMode STEER`() {
|
||||
val event = TambouiKeyEvent(KeyCode.CHAR, KeyModifiers.CTRL, 'c'.code)
|
||||
assertEquals(KeyEvent.Quit, mapKey(event, InputMode.SteeringNote))
|
||||
assertEquals(KeyEvent.Quit, mapKey(event, InputMode.STEER))
|
||||
}
|
||||
|
||||
// --- Other Ctrl/Alt → null ---
|
||||
@@ -42,166 +42,166 @@ class TambouiKeyMapperTest {
|
||||
@Test
|
||||
fun `ctrl modifier non-ctrlC maps to null`() {
|
||||
val event = TambouiKeyEvent(KeyCode.CHAR, KeyModifiers.CTRL, 'a'.code)
|
||||
assertNull(mapKey(event, InputMode.None))
|
||||
assertNull(mapKey(event, InputMode.ROUTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `alt modifier maps to null`() {
|
||||
val event = TambouiKeyEvent(KeyCode.CHAR, KeyModifiers.ALT, 'q'.code)
|
||||
assertNull(mapKey(event, InputMode.None))
|
||||
assertNull(mapKey(event, InputMode.ROUTER))
|
||||
}
|
||||
|
||||
// --- KeyCode mappings in InputMode.None ---
|
||||
// --- KeyCode mappings in InputMode.ROUTER ---
|
||||
|
||||
@Test
|
||||
fun `UP maps to NavUp in InputMode None`() {
|
||||
assertEquals(KeyEvent.NavUp, mapKey(TambouiKeyEvent.ofKey(KeyCode.UP), InputMode.None))
|
||||
fun `UP maps to NavUp in InputMode ROUTER`() {
|
||||
assertEquals(KeyEvent.NavUp, mapKey(TambouiKeyEvent.ofKey(KeyCode.UP), InputMode.ROUTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DOWN maps to NavDown in InputMode None`() {
|
||||
assertEquals(KeyEvent.NavDown, mapKey(TambouiKeyEvent.ofKey(KeyCode.DOWN), InputMode.None))
|
||||
fun `DOWN maps to NavDown in InputMode ROUTER`() {
|
||||
assertEquals(KeyEvent.NavDown, mapKey(TambouiKeyEvent.ofKey(KeyCode.DOWN), InputMode.ROUTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ENTER maps to Enter in InputMode None`() {
|
||||
assertEquals(KeyEvent.Enter, mapKey(TambouiKeyEvent.ofKey(KeyCode.ENTER), InputMode.None))
|
||||
fun `ENTER maps to Enter in InputMode ROUTER`() {
|
||||
assertEquals(KeyEvent.Enter, mapKey(TambouiKeyEvent.ofKey(KeyCode.ENTER), InputMode.ROUTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ESCAPE maps to Escape in InputMode None`() {
|
||||
assertEquals(KeyEvent.Escape, mapKey(TambouiKeyEvent.ofKey(KeyCode.ESCAPE), InputMode.None))
|
||||
fun `ESCAPE maps to Escape in InputMode ROUTER`() {
|
||||
assertEquals(KeyEvent.Escape, mapKey(TambouiKeyEvent.ofKey(KeyCode.ESCAPE), InputMode.ROUTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `BACKSPACE maps to Backspace in InputMode None`() {
|
||||
assertEquals(KeyEvent.Backspace, mapKey(TambouiKeyEvent.ofKey(KeyCode.BACKSPACE), InputMode.None))
|
||||
fun `BACKSPACE maps to Backspace in InputMode ROUTER`() {
|
||||
assertEquals(KeyEvent.Backspace, mapKey(TambouiKeyEvent.ofKey(KeyCode.BACKSPACE), InputMode.ROUTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `TAB maps to Filter in InputMode None`() {
|
||||
assertEquals(KeyEvent.Filter, mapKey(TambouiKeyEvent.ofKey(KeyCode.TAB), InputMode.None))
|
||||
fun `TAB maps to Filter in InputMode ROUTER`() {
|
||||
assertEquals(KeyEvent.Filter, mapKey(TambouiKeyEvent.ofKey(KeyCode.TAB), InputMode.ROUTER))
|
||||
}
|
||||
|
||||
// --- KeyCode mappings in InputMode.Filter ---
|
||||
// --- KeyCode mappings in InputMode.FILTER ---
|
||||
|
||||
@Test
|
||||
fun `UP maps to NavUp in InputMode Filter`() {
|
||||
assertEquals(KeyEvent.NavUp, mapKey(TambouiKeyEvent.ofKey(KeyCode.UP), InputMode.Filter))
|
||||
fun `UP maps to NavUp in InputMode FILTER`() {
|
||||
assertEquals(KeyEvent.NavUp, mapKey(TambouiKeyEvent.ofKey(KeyCode.UP), InputMode.FILTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DOWN maps to NavDown in InputMode Filter`() {
|
||||
assertEquals(KeyEvent.NavDown, mapKey(TambouiKeyEvent.ofKey(KeyCode.DOWN), InputMode.Filter))
|
||||
fun `DOWN maps to NavDown in InputMode FILTER`() {
|
||||
assertEquals(KeyEvent.NavDown, mapKey(TambouiKeyEvent.ofKey(KeyCode.DOWN), InputMode.FILTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ENTER maps to Enter in InputMode Filter`() {
|
||||
assertEquals(KeyEvent.Enter, mapKey(TambouiKeyEvent.ofKey(KeyCode.ENTER), InputMode.Filter))
|
||||
fun `ENTER maps to Enter in InputMode FILTER`() {
|
||||
assertEquals(KeyEvent.Enter, mapKey(TambouiKeyEvent.ofKey(KeyCode.ENTER), InputMode.FILTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ESCAPE maps to Escape in InputMode Filter`() {
|
||||
assertEquals(KeyEvent.Escape, mapKey(TambouiKeyEvent.ofKey(KeyCode.ESCAPE), InputMode.Filter))
|
||||
fun `ESCAPE maps to Escape in InputMode FILTER`() {
|
||||
assertEquals(KeyEvent.Escape, mapKey(TambouiKeyEvent.ofKey(KeyCode.ESCAPE), InputMode.FILTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `BACKSPACE maps to Backspace in InputMode Filter`() {
|
||||
assertEquals(KeyEvent.Backspace, mapKey(TambouiKeyEvent.ofKey(KeyCode.BACKSPACE), InputMode.Filter))
|
||||
fun `BACKSPACE maps to Backspace in InputMode FILTER`() {
|
||||
assertEquals(KeyEvent.Backspace, mapKey(TambouiKeyEvent.ofKey(KeyCode.BACKSPACE), InputMode.FILTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `TAB maps to Filter in InputMode Filter`() {
|
||||
assertEquals(KeyEvent.Filter, mapKey(TambouiKeyEvent.ofKey(KeyCode.TAB), InputMode.Filter))
|
||||
fun `TAB maps to Filter in InputMode FILTER`() {
|
||||
assertEquals(KeyEvent.Filter, mapKey(TambouiKeyEvent.ofKey(KeyCode.TAB), InputMode.FILTER))
|
||||
}
|
||||
|
||||
// --- Semantic intents in InputMode.None ---
|
||||
// --- Semantic intents in InputMode.ROUTER ---
|
||||
|
||||
@Test
|
||||
fun `q maps to Quit in InputMode None`() {
|
||||
assertEquals(KeyEvent.Quit, mapKey(TambouiKeyEvent.ofChar('q'), InputMode.None))
|
||||
fun `q maps to Quit in InputMode ROUTER`() {
|
||||
assertEquals(KeyEvent.Quit, mapKey(TambouiKeyEvent.ofChar('q'), InputMode.ROUTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `n maps to NewSession in InputMode None`() {
|
||||
assertEquals(KeyEvent.NewSession, mapKey(TambouiKeyEvent.ofChar('n'), InputMode.None))
|
||||
fun `n maps to NewSession in InputMode ROUTER`() {
|
||||
assertEquals(KeyEvent.NewSession, mapKey(TambouiKeyEvent.ofChar('n'), InputMode.ROUTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `c maps to Cancel in InputMode None`() {
|
||||
assertEquals(KeyEvent.Cancel, mapKey(TambouiKeyEvent.ofChar('c'), InputMode.None))
|
||||
fun `c maps to Cancel in InputMode ROUTER`() {
|
||||
assertEquals(KeyEvent.Cancel, mapKey(TambouiKeyEvent.ofChar('c'), InputMode.ROUTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a maps to Approve in InputMode None`() {
|
||||
assertEquals(KeyEvent.Approve, mapKey(TambouiKeyEvent.ofChar('a'), InputMode.None))
|
||||
fun `a maps to Approve in InputMode ROUTER`() {
|
||||
assertEquals(KeyEvent.Approve, mapKey(TambouiKeyEvent.ofChar('a'), InputMode.ROUTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `r maps to Reject in InputMode None`() {
|
||||
assertEquals(KeyEvent.Reject, mapKey(TambouiKeyEvent.ofChar('r'), InputMode.None))
|
||||
fun `r maps to Reject in InputMode ROUTER`() {
|
||||
assertEquals(KeyEvent.Reject, mapKey(TambouiKeyEvent.ofChar('r'), InputMode.ROUTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `s maps to Steer in InputMode None`() {
|
||||
assertEquals(KeyEvent.Steer, mapKey(TambouiKeyEvent.ofChar('s'), InputMode.None))
|
||||
fun `s maps to Steer in InputMode ROUTER`() {
|
||||
assertEquals(KeyEvent.Steer, mapKey(TambouiKeyEvent.ofChar('s'), InputMode.ROUTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `slash maps to Filter in InputMode None`() {
|
||||
assertEquals(KeyEvent.Filter, mapKey(TambouiKeyEvent.ofChar('/'), InputMode.None))
|
||||
fun `slash maps to Filter in InputMode ROUTER`() {
|
||||
assertEquals(KeyEvent.Filter, mapKey(TambouiKeyEvent.ofChar('/'), InputMode.ROUTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `other char maps to CharInput in InputMode None`() {
|
||||
assertEquals(KeyEvent.CharInput('x'), mapKey(TambouiKeyEvent.ofChar('x'), InputMode.None))
|
||||
fun `other char maps to CharInput in InputMode ROUTER`() {
|
||||
assertEquals(KeyEvent.CharInput('x'), mapKey(TambouiKeyEvent.ofChar('x'), InputMode.ROUTER))
|
||||
}
|
||||
|
||||
// --- Regression: semantic chars map to CharInput in non-None modes ---
|
||||
// --- Regression: semantic chars map to CharInput in non-ROUTER modes ---
|
||||
|
||||
@Test
|
||||
fun `q maps to CharInput in InputMode WorkflowId`() {
|
||||
assertEquals(KeyEvent.CharInput('q'), mapKey(TambouiKeyEvent.ofChar('q'), InputMode.WorkflowId))
|
||||
fun `q maps to CharInput in InputMode NAVIGATE`() {
|
||||
assertEquals(KeyEvent.CharInput('q'), mapKey(TambouiKeyEvent.ofChar('q'), InputMode.NAVIGATE))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `n maps to CharInput in InputMode WorkflowId`() {
|
||||
assertEquals(KeyEvent.CharInput('n'), mapKey(TambouiKeyEvent.ofChar('n'), InputMode.WorkflowId))
|
||||
fun `n maps to CharInput in InputMode NAVIGATE`() {
|
||||
assertEquals(KeyEvent.CharInput('n'), mapKey(TambouiKeyEvent.ofChar('n'), InputMode.NAVIGATE))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `c maps to CharInput in InputMode WorkflowId`() {
|
||||
assertEquals(KeyEvent.CharInput('c'), mapKey(TambouiKeyEvent.ofChar('c'), InputMode.WorkflowId))
|
||||
fun `c maps to CharInput in InputMode NAVIGATE`() {
|
||||
assertEquals(KeyEvent.CharInput('c'), mapKey(TambouiKeyEvent.ofChar('c'), InputMode.NAVIGATE))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a maps to CharInput in InputMode WorkflowId`() {
|
||||
assertEquals(KeyEvent.CharInput('a'), mapKey(TambouiKeyEvent.ofChar('a'), InputMode.WorkflowId))
|
||||
fun `a maps to CharInput in InputMode NAVIGATE`() {
|
||||
assertEquals(KeyEvent.CharInput('a'), mapKey(TambouiKeyEvent.ofChar('a'), InputMode.NAVIGATE))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `r maps to CharInput in InputMode WorkflowId`() {
|
||||
assertEquals(KeyEvent.CharInput('r'), mapKey(TambouiKeyEvent.ofChar('r'), InputMode.WorkflowId))
|
||||
fun `r maps to CharInput in InputMode NAVIGATE`() {
|
||||
assertEquals(KeyEvent.CharInput('r'), mapKey(TambouiKeyEvent.ofChar('r'), InputMode.NAVIGATE))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `s maps to CharInput in InputMode WorkflowId`() {
|
||||
assertEquals(KeyEvent.CharInput('s'), mapKey(TambouiKeyEvent.ofChar('s'), InputMode.WorkflowId))
|
||||
fun `s maps to CharInput in InputMode NAVIGATE`() {
|
||||
assertEquals(KeyEvent.CharInput('s'), mapKey(TambouiKeyEvent.ofChar('s'), InputMode.NAVIGATE))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `slash maps to CharInput in InputMode WorkflowId`() {
|
||||
assertEquals(KeyEvent.CharInput('/'), mapKey(TambouiKeyEvent.ofChar('/'), InputMode.WorkflowId))
|
||||
fun `slash maps to CharInput in InputMode NAVIGATE`() {
|
||||
assertEquals(KeyEvent.CharInput('/'), mapKey(TambouiKeyEvent.ofChar('/'), InputMode.NAVIGATE))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `q maps to CharInput in InputMode SteeringNote`() {
|
||||
assertEquals(KeyEvent.CharInput('q'), mapKey(TambouiKeyEvent.ofChar('q'), InputMode.SteeringNote))
|
||||
fun `q maps to CharInput in InputMode STEER`() {
|
||||
assertEquals(KeyEvent.CharInput('q'), mapKey(TambouiKeyEvent.ofChar('q'), InputMode.STEER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `q maps to CharInput in InputMode Filter`() {
|
||||
assertEquals(KeyEvent.CharInput('q'), mapKey(TambouiKeyEvent.ofChar('q'), InputMode.Filter))
|
||||
fun `q maps to CharInput in InputMode FILTER`() {
|
||||
assertEquals(KeyEvent.CharInput('q'), mapKey(TambouiKeyEvent.ofChar('q'), InputMode.FILTER))
|
||||
}
|
||||
|
||||
// --- ISO control chars in CHAR event → null ---
|
||||
@@ -209,12 +209,12 @@ class TambouiKeyMapperTest {
|
||||
@Test
|
||||
fun `ESC codepoint as CHAR event maps to null`() {
|
||||
val event = TambouiKeyEvent(KeyCode.CHAR, KeyModifiers.NONE, 0x1B)
|
||||
assertNull(mapKey(event, InputMode.None))
|
||||
assertNull(mapKey(event, InputMode.ROUTER))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NUL codepoint as CHAR event maps to null`() {
|
||||
val event = TambouiKeyEvent(KeyCode.CHAR, KeyModifiers.NONE, 0x00)
|
||||
assertNull(mapKey(event, InputMode.None))
|
||||
assertNull(mapKey(event, InputMode.ROUTER))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class ApprovalReducerTest {
|
||||
|
||||
private fun reduce(
|
||||
approval: ApprovalState = ApprovalState(),
|
||||
inputMode: InputMode = InputMode.None,
|
||||
inputMode: InputMode = InputMode.ROUTER,
|
||||
action: Action,
|
||||
) = ApprovalReducer.reduce(approval, inputMode, action)
|
||||
|
||||
@@ -72,7 +72,7 @@ class ApprovalReducerTest {
|
||||
fun `SubmitInput in SteeringNote mode clears active approval`() {
|
||||
val (state, effects) = reduce(
|
||||
approval = ApprovalState(active = activeInfo),
|
||||
inputMode = InputMode.SteeringNote,
|
||||
inputMode = InputMode.STEER,
|
||||
action = Action.SubmitInput,
|
||||
)
|
||||
assertNull(state.active)
|
||||
@@ -83,7 +83,7 @@ class ApprovalReducerTest {
|
||||
fun `SubmitInput in non-SteeringNote mode is no-op`() {
|
||||
val (state, _) = reduce(
|
||||
approval = ApprovalState(active = activeInfo),
|
||||
inputMode = InputMode.WorkflowId,
|
||||
inputMode = InputMode.NAVIGATE,
|
||||
action = Action.SubmitInput,
|
||||
)
|
||||
assertEquals(activeInfo, state.active)
|
||||
|
||||
@@ -4,15 +4,15 @@ import com.correx.apps.server.protocol.ApprovalDecision
|
||||
import com.correx.apps.server.protocol.ClientMessage
|
||||
import com.correx.apps.tui.input.Action
|
||||
import com.correx.apps.tui.state.ApprovalInfo
|
||||
import com.correx.apps.tui.state.ApprovalState
|
||||
import com.correx.apps.tui.state.InputMode
|
||||
import com.correx.apps.tui.state.InputState
|
||||
import com.correx.apps.tui.state.TuiState
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class InputReducerTest {
|
||||
|
||||
private val noApproval: ApprovalInfo? = null
|
||||
private val activeApproval = ApprovalInfo(
|
||||
requestId = "req-1",
|
||||
sessionId = "sess-1",
|
||||
@@ -22,68 +22,83 @@ class InputReducerTest {
|
||||
preview = null,
|
||||
)
|
||||
|
||||
private fun reduce(input: InputState = InputState(), approval: ApprovalInfo? = noApproval, action: Action) =
|
||||
InputReducer.reduce(input, activeApproval = approval, action = action)
|
||||
private fun reduce(
|
||||
state: TuiState = TuiState(),
|
||||
action: Action,
|
||||
) = InputReducer.reduce(state, action)
|
||||
|
||||
@Test
|
||||
fun `OpenNewSessionPrompt sets mode to WorkflowId with empty text`() {
|
||||
fun `OpenNewSessionPrompt sets mode to ROUTER with empty buffer`() {
|
||||
val (state, effects) = reduce(action = Action.OpenNewSessionPrompt)
|
||||
assertEquals(InputMode.WorkflowId, state.mode)
|
||||
assertEquals("", state.text)
|
||||
assertEquals(InputMode.ROUTER, state.inputMode)
|
||||
assertEquals("", state.inputBuffer)
|
||||
assertTrue(effects.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `OpenSteeringPrompt sets mode to SteeringNote when approval active`() {
|
||||
val (state, effects) = reduce(approval = activeApproval, action = Action.OpenSteeringPrompt)
|
||||
assertEquals(InputMode.SteeringNote, state.mode)
|
||||
assertEquals("", state.text)
|
||||
fun `OpenSteeringPrompt sets mode to STEER when approval active`() {
|
||||
val initial = TuiState(approval = ApprovalState(active = activeApproval))
|
||||
val (state, effects) = reduce(state = initial, action = Action.OpenSteeringPrompt)
|
||||
assertEquals(InputMode.STEER, state.inputMode)
|
||||
assertEquals("", state.inputBuffer)
|
||||
assertTrue(effects.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `OpenSteeringPrompt is no-op when no active approval`() {
|
||||
val initial = InputState()
|
||||
val (state, effects) = reduce(input = initial, approval = null, action = Action.OpenSteeringPrompt)
|
||||
val initial = TuiState()
|
||||
val (state, effects) = reduce(state = initial, action = Action.OpenSteeringPrompt)
|
||||
assertEquals(initial, state)
|
||||
assertTrue(effects.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AppendChar appends character to text`() {
|
||||
val (state, effects) = reduce(input = InputState(InputMode.WorkflowId, "ab"), action = Action.AppendChar('c'))
|
||||
assertEquals("abc", state.text)
|
||||
fun `AppendChar appends character to buffer`() {
|
||||
val (state, effects) = reduce(
|
||||
state = TuiState(inputMode = InputMode.ROUTER, inputBuffer = "ab"),
|
||||
action = Action.AppendChar('c'),
|
||||
)
|
||||
assertEquals("abc", state.inputBuffer)
|
||||
assertTrue(effects.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Backspace drops last character`() {
|
||||
val (state, effects) = reduce(input = InputState(InputMode.WorkflowId, "abc"), action = Action.Backspace)
|
||||
assertEquals("ab", state.text)
|
||||
assertTrue(effects.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Backspace on empty text stays empty`() {
|
||||
val (state, _) = reduce(input = InputState(InputMode.WorkflowId, ""), action = Action.Backspace)
|
||||
assertEquals("", state.text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CancelInput resets to None with empty text`() {
|
||||
val (state, effects) = reduce(input = InputState(InputMode.WorkflowId, "hello"), action = Action.CancelInput)
|
||||
assertEquals(InputMode.None, state.mode)
|
||||
assertEquals("", state.text)
|
||||
assertTrue(effects.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SubmitInput in WorkflowId mode emits StartSession effect`() {
|
||||
val (state, effects) = reduce(
|
||||
input = InputState(InputMode.WorkflowId, "my-workflow"),
|
||||
state = TuiState(inputMode = InputMode.ROUTER, inputBuffer = "abc"),
|
||||
action = Action.Backspace,
|
||||
)
|
||||
assertEquals("ab", state.inputBuffer)
|
||||
assertTrue(effects.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Backspace on empty buffer stays empty`() {
|
||||
val (state, _) = reduce(
|
||||
state = TuiState(inputMode = InputMode.ROUTER, inputBuffer = ""),
|
||||
action = Action.Backspace,
|
||||
)
|
||||
assertEquals("", state.inputBuffer)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CancelInput resets to ROUTER mode with empty buffer`() {
|
||||
val (state, effects) = reduce(
|
||||
state = TuiState(inputMode = InputMode.FILTER, inputBuffer = "hello"),
|
||||
action = Action.CancelInput,
|
||||
)
|
||||
assertEquals(InputMode.ROUTER, state.inputMode)
|
||||
assertEquals("", state.inputBuffer)
|
||||
assertTrue(effects.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SubmitInput in ROUTER mode emits StartSession effect`() {
|
||||
val (state, effects) = reduce(
|
||||
state = TuiState(inputMode = InputMode.ROUTER, inputBuffer = "my-workflow"),
|
||||
action = Action.SubmitInput,
|
||||
)
|
||||
assertEquals(InputMode.None, state.mode)
|
||||
assertEquals(InputMode.ROUTER, state.inputMode)
|
||||
assertEquals(1, effects.size)
|
||||
val effect = effects[0] as Effect.SendWs
|
||||
val msg = effect.message as ClientMessage.StartSession
|
||||
@@ -91,23 +106,26 @@ class InputReducerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SubmitInput in WorkflowId mode with blank text emits no effect`() {
|
||||
fun `SubmitInput in ROUTER mode with blank text emits no effect`() {
|
||||
val (state, effects) = reduce(
|
||||
input = InputState(InputMode.WorkflowId, " "),
|
||||
state = TuiState(inputMode = InputMode.ROUTER, inputBuffer = " "),
|
||||
action = Action.SubmitInput,
|
||||
)
|
||||
assertEquals(InputMode.None, state.mode)
|
||||
assertEquals(InputMode.ROUTER, state.inputMode)
|
||||
assertTrue(effects.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SubmitInput in SteeringNote mode with active approval emits ApprovalResponse STEER`() {
|
||||
fun `SubmitInput in STEER mode with active approval emits ApprovalResponse STEER`() {
|
||||
val (state, effects) = reduce(
|
||||
input = InputState(InputMode.SteeringNote, "steer this way"),
|
||||
approval = activeApproval,
|
||||
state = TuiState(
|
||||
inputMode = InputMode.STEER,
|
||||
inputBuffer = "steer this way",
|
||||
approval = ApprovalState(active = activeApproval),
|
||||
),
|
||||
action = Action.SubmitInput,
|
||||
)
|
||||
assertEquals(InputMode.None, state.mode)
|
||||
assertEquals(InputMode.ROUTER, state.inputMode)
|
||||
assertEquals(1, effects.size)
|
||||
val effect = effects[0] as Effect.SendWs
|
||||
val msg = effect.message as ClientMessage.ApprovalResponse
|
||||
@@ -117,12 +135,12 @@ class InputReducerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SubmitInput in Filter mode clears input with no effects`() {
|
||||
fun `SubmitInput in FILTER mode clears input with no effects`() {
|
||||
val (state, effects) = reduce(
|
||||
input = InputState(InputMode.Filter, "myfilter"),
|
||||
state = TuiState(inputMode = InputMode.FILTER, inputBuffer = "myfilter"),
|
||||
action = Action.SubmitInput,
|
||||
)
|
||||
assertEquals(InputMode.None, state.mode)
|
||||
assertEquals(InputMode.ROUTER, state.inputMode)
|
||||
assertTrue(effects.isEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.correx.apps.tui.reducer
|
||||
import com.correx.apps.server.protocol.ClientMessage
|
||||
import com.correx.apps.tui.input.Action
|
||||
import com.correx.apps.tui.state.InputMode
|
||||
import com.correx.apps.tui.state.InputState
|
||||
import com.correx.apps.tui.state.TuiState
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
@@ -14,11 +13,11 @@ class RootReducerTest {
|
||||
private val fixedClock: () -> Long = { 1000L }
|
||||
|
||||
@Test
|
||||
fun `SubmitInput WorkflowId emits StartSession effect and resets input`() {
|
||||
val initial = TuiState(input = InputState(InputMode.WorkflowId, "my-workflow"))
|
||||
fun `SubmitInput in ROUTER mode emits StartSession effect and resets buffer`() {
|
||||
val initial = TuiState(inputMode = InputMode.ROUTER, inputBuffer = "my-workflow")
|
||||
val (state, effects) = RootReducer.reduce(initial, Action.SubmitInput, fixedClock)
|
||||
assertEquals(InputMode.None, state.input.mode)
|
||||
assertEquals("", state.input.text)
|
||||
assertEquals(InputMode.ROUTER, state.inputMode)
|
||||
assertEquals("", state.inputBuffer)
|
||||
val wsEffects = effects.filterIsInstance<Effect.SendWs>()
|
||||
assertEquals(1, wsEffects.size)
|
||||
val msg = wsEffects[0].message as ClientMessage.StartSession
|
||||
@@ -31,7 +30,8 @@ class RootReducerTest {
|
||||
val (state, effects) = RootReducer.reduce(initial, Action.Connected, fixedClock)
|
||||
assertTrue(state.connection.connected)
|
||||
assertTrue(effects.isEmpty())
|
||||
assertEquals(initial.input, state.input)
|
||||
assertEquals(initial.inputMode, state.inputMode)
|
||||
assertEquals(initial.inputBuffer, state.inputBuffer)
|
||||
assertEquals(initial.sessions, state.sessions)
|
||||
assertEquals(initial.approval, state.approval)
|
||||
assertEquals(initial.provider, state.provider)
|
||||
@@ -51,15 +51,15 @@ class RootReducerTest {
|
||||
)
|
||||
val (state2, _) = RootReducer.reduce(state1, Action.ServerEventReceived(startMsg2), fixedClock)
|
||||
// selected is s1 (first); navigating down should move to s2
|
||||
val (state3, _) = RootReducer.reduce(state2, Action.NavigateDown, fixedClock)
|
||||
val (state3, _) = RootReducer.reduce(state2.copy(inputMode = InputMode.NAVIGATE), Action.NavigateDown, fixedClock)
|
||||
assertEquals("s2", state3.sessions.selectedId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AppendChar followed by Backspace returns to original text`() {
|
||||
val initial = TuiState(input = InputState(InputMode.WorkflowId, "ab"))
|
||||
fun `AppendChar followed by Backspace returns to original buffer`() {
|
||||
val initial = TuiState(inputMode = InputMode.ROUTER, inputBuffer = "ab")
|
||||
val (s1, _) = RootReducer.reduce(initial, Action.AppendChar('c'), fixedClock)
|
||||
val (s2, _) = RootReducer.reduce(s1, Action.Backspace, fixedClock)
|
||||
assertEquals("ab", s2.input.text)
|
||||
assertEquals("ab", s2.inputBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class SessionsReducerTest {
|
||||
|
||||
private fun reduce(
|
||||
sessions: SessionsState = SessionsState(),
|
||||
inputMode: InputMode = InputMode.None,
|
||||
inputMode: InputMode = InputMode.ROUTER,
|
||||
inputText: String = "",
|
||||
action: Action,
|
||||
) = SessionsReducer.reduce(sessions, inputMode, inputText, action, fixedClock)
|
||||
@@ -35,7 +35,7 @@ class SessionsReducerTest {
|
||||
@Test
|
||||
fun `NavigateUp wraps from top to bottom`() {
|
||||
val s = SessionsState(sessions = listOf(session("a"), session("b"), session("c")), selectedId = "a")
|
||||
val (state, effects) = reduce(sessions = s, action = Action.NavigateUp)
|
||||
val (state, effects) = reduce(sessions = s, inputMode = InputMode.NAVIGATE, action = Action.NavigateUp)
|
||||
assertEquals("c", state.selectedId)
|
||||
assertTrue(effects.isEmpty())
|
||||
}
|
||||
@@ -43,14 +43,14 @@ class SessionsReducerTest {
|
||||
@Test
|
||||
fun `NavigateDown advances selection`() {
|
||||
val s = SessionsState(sessions = listOf(session("a"), session("b")), selectedId = "a")
|
||||
val (state, _) = reduce(sessions = s, action = Action.NavigateDown)
|
||||
val (state, _) = reduce(sessions = s, inputMode = InputMode.NAVIGATE, action = Action.NavigateDown)
|
||||
assertEquals("b", state.selectedId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateDown wraps from bottom to top`() {
|
||||
val s = SessionsState(sessions = listOf(session("a"), session("b")), selectedId = "b")
|
||||
val (state, _) = reduce(sessions = s, action = Action.NavigateDown)
|
||||
val (state, _) = reduce(sessions = s, inputMode = InputMode.NAVIGATE, action = Action.NavigateDown)
|
||||
assertEquals("a", state.selectedId)
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ class SessionsReducerTest {
|
||||
fun `SubmitInput in Filter mode copies inputText into filter`() {
|
||||
val s = SessionsState()
|
||||
val (state, effects) = reduce(
|
||||
sessions = s, inputMode = InputMode.Filter, inputText = "myfilter", action = Action.SubmitInput,
|
||||
sessions = s, inputMode = InputMode.FILTER, inputText = "myfilter", action = Action.SubmitInput,
|
||||
)
|
||||
assertEquals("myfilter", state.filter)
|
||||
assertTrue(effects.isEmpty())
|
||||
@@ -68,7 +68,7 @@ class SessionsReducerTest {
|
||||
fun `SubmitInput in Filter mode with empty text clears filter`() {
|
||||
val s = SessionsState(filter = "old")
|
||||
val (state, effects) = reduce(
|
||||
sessions = s, inputMode = InputMode.Filter, inputText = "", action = Action.SubmitInput,
|
||||
sessions = s, inputMode = InputMode.FILTER, inputText = "", action = Action.SubmitInput,
|
||||
)
|
||||
assertEquals("", state.filter)
|
||||
assertTrue(effects.isEmpty())
|
||||
@@ -77,7 +77,7 @@ class SessionsReducerTest {
|
||||
@Test
|
||||
fun `CancelInput in Filter mode clears filter`() {
|
||||
val s = SessionsState(filter = "something")
|
||||
val (state, effects) = reduce(sessions = s, inputMode = InputMode.Filter, action = Action.CancelInput)
|
||||
val (state, effects) = reduce(sessions = s, inputMode = InputMode.FILTER, action = Action.CancelInput)
|
||||
assertEquals("", state.filter)
|
||||
assertTrue(effects.isEmpty())
|
||||
}
|
||||
@@ -85,7 +85,7 @@ class SessionsReducerTest {
|
||||
@Test
|
||||
fun `CancelInput in non-Filter mode is no-op`() {
|
||||
val s = SessionsState(filter = "something")
|
||||
val (state, _) = reduce(sessions = s, inputMode = InputMode.WorkflowId, action = Action.CancelInput)
|
||||
val (state, _) = reduce(sessions = s, inputMode = InputMode.ROUTER, action = Action.CancelInput)
|
||||
assertEquals("something", state.filter)
|
||||
}
|
||||
|
||||
@@ -96,7 +96,8 @@ class SessionsReducerTest {
|
||||
assertEquals(1, state.sessions.size)
|
||||
assertEquals("s1", state.sessions[0].id)
|
||||
assertEquals("s1", state.selectedId)
|
||||
assertTrue(effects.isEmpty())
|
||||
assertEquals(1, effects.size)
|
||||
assertTrue(effects[0] is Effect.ConnectSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user