Files
OmniRoute/tests/unit/conversations-pending-tool-node.test.ts
Markus Hartung b516e95262 fix(conversations): show a pending spinner for unresolved tool nodes instead of "(empty)" (#12727)
Validado numa worktree combinada com a onda de dashboard/monitoring desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-api-typecheck OK (289), check-file-size OK após rebaseline, 130/131 nos testes focados — a falha restante é asserção de tempo de parede sob carga, verde 6/6 isolada.

"(empty)" para um nó de ferramenta ainda não resolvido é informação errada, não ausência de informação — o usuário lê como "não retornou nada". Spinner de pendente diz a verdade.
2026-09-10 10:42:38 -03:00

75 lines
3.0 KiB
TypeScript

/**
* Regression test for /dashboard/conversations's live-turn rendering.
*
* conversation_turn_nodes rows record identity (role/blockKind) synchronously
* the moment a turn is recorded; display content (textPreview) resolves
* lazily from the owning call-log artifact (resolveTurnDisplayContent). While
* that resolution is still in flight, /api/conversations/[id]/tree's own
* `blockKind ?? "text"` fallback makes a genuinely-in-progress node
* indistinguishable from a permanently-empty one at the API layer. toTurn()
* previously rendered an empty tool node as a distinct `pending` block but
* still showed a bare "_(empty)_" text bubble for user/assistant nodes with
* no textPreview yet -- live traffic showed the same resolution lag hits
* every role, not just tool, so this proves ANY role with no textPreview
* (in the plain-text fallback branch) now maps to `pending`, without
* changing tool_use/tool_result mapping.
*/
import test from "node:test";
import assert from "node:assert/strict";
const { toTurn } = await import("../../src/app/(dashboard)/dashboard/conversations/toTurn.ts");
function node(overrides: Partial<Parameters<typeof toTurn>[0]>) {
return {
seq: 1,
id: "01234567890123456789",
parentId: null,
role: "assistant",
textPreview: "",
blockKind: "text",
toolName: null,
firstSeenAt: "2026-09-04T00:00:00.000Z",
...overrides,
};
}
test("toTurn renders an unresolved tool node as pending, not a bare empty text bubble", () => {
const turn = toTurn(node({ role: "tool", blockKind: "text", textPreview: "" }));
assert.deepEqual(turn.blocks, [{ type: "pending" }]);
});
test("toTurn renders an unresolved assistant node as pending too", () => {
const turn = toTurn(node({ role: "assistant", blockKind: "text", textPreview: "" }));
assert.deepEqual(turn.blocks, [{ type: "pending" }]);
});
test("toTurn renders an unresolved user node as pending too", () => {
const turn = toTurn(node({ role: "user", blockKind: "text", textPreview: "" }));
assert.deepEqual(turn.blocks, [{ type: "pending" }]);
});
test("toTurn renders a resolved assistant reply's real content once textPreview lands, not pending", () => {
const turn = toTurn(
node({ role: "assistant", blockKind: "text", textPreview: "the real reply" })
);
assert.deepEqual(turn.blocks, [{ type: "text", text: "the real reply" }]);
});
test("toTurn renders a resolved tool node's real content once textPreview lands, not pending", () => {
const turn = toTurn(node({ role: "tool", blockKind: "text", textPreview: "the real result" }));
assert.deepEqual(turn.blocks, [{ type: "text", text: "the real result" }]);
});
test("toTurn leaves tool_use/tool_result mapping unaffected", () => {
const toolUse = toTurn(
node({ role: "tool", blockKind: "tool_use", toolName: "search", textPreview: '{"q":"x"}' })
);
assert.equal(toolUse.blocks[0]!.type, "tool_use");
const toolResult = toTurn(
node({ role: "tool", blockKind: "tool_result", textPreview: '{"ok":true}' })
);
assert.equal(toolResult.blocks[0]!.type, "tool_result");
});