package com.example.aiapp import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue /** * How a run of tool calls is cut into rows: the call still running is drawn on its own, and every * piece the cut leaves behind still has a key of its own -- two rows sharing one key take the app * down, and a key that moves takes the reader's place with it. */ class ToolRowsTest { private var seq = 0L private fun call(id: String, runId: String = id, done: Boolean = true) = TranscriptItem.ToolRun( seq = ++seq, id = id, runId = runId, tool = "Bash", input = "{}", output = if (done) "ok" else "", done = done, ) private fun shape(rows: List) = rows.map { row -> when (row) { is TranscriptRow.Tools -> row.calls.map { it.id } is TranscriptRow.Single -> listOf((row.item as TranscriptItem.ToolRun).id) } } private fun assertKeysDistinct(rows: List) = assertEquals(rows.size, rows.map { it.key }.toSet().size, "$rows") @Test fun the_call_still_running_is_a_row_of_its_own() { val rows = groupToolRuns( listOf(call("a"), call("b", runId = "a"), call("c", runId = "a", done = false)) ) assertEquals(listOf(listOf("a", "b"), listOf("c")), shape(rows)) assertKeysDistinct(rows) } @Test fun a_call_running_in_the_middle_of_its_run_splits_the_group_in_two() { val rows = groupToolRuns( listOf( call("a"), call("b", runId = "a", done = false), call("c", runId = "a"), call("d", runId = "a"), ) ) assertEquals(listOf(listOf("a"), listOf("b"), listOf("c", "d")), shape(rows)) assertKeysDistinct(rows) } /** * The one case where the run's name is a call that is not in the run's first row: a page of * history joined onto a run whose own first call is still going ([joinPages] renames the older * calls to the newer run's name). Both rows would key on that name. */ @Test fun the_run_keeps_its_name_even_when_the_call_it_is_named_after_is_the_one_running() { val rows = groupToolRuns(listOf(call("a", runId = "b"), call("b", done = false))) assertEquals(listOf(listOf("a"), listOf("b")), shape(rows)) assertKeysDistinct(rows) assertEquals("b", rows.first().key) } /** What happens the moment a command finishes: it folds back into the run it was cut out of. */ @Test fun a_call_that_finishes_rejoins_its_run_without_moving_the_run() { val running = listOf(call("a"), call("b", runId = "a", done = false)) val finished = listOf(running[0], (running[1] as TranscriptItem.ToolRun).copy(done = true)) val before = groupToolRuns(running) val after = groupToolRuns(finished) assertEquals(listOf(listOf("a", "b")), shape(after)) // The run keeps the key it was drawn under, so the list rebuilds a row rather than losing // its anchor. assertEquals(before.first().key, after.first().key) } @Test fun a_run_of_finished_calls_is_still_one_group() { val rows = groupToolRuns(listOf(call("a"), call("b", runId = "a"), call("c", runId = "a"))) assertEquals(listOf(listOf("a", "b", "c")), shape(rows)) assertTrue(rows.single() is TranscriptRow.Tools, "$rows") } }