| 1 | using CascadeIDE.Features.Chat; |
| 2 | using CascadeIDE.Models.AgentChat; |
| 3 | using Xunit; |
| 4 | |
| 5 | namespace CascadeIDE.Tests; |
| 6 | |
| 7 | public sealed class ChatSurfaceCompositorTests |
| 8 | { |
| 9 | [Fact] |
| 10 | public void Compose_ForkedConversation_CreatesBranchLaneAndForkEdge() |
| 11 | { |
| 12 | var mainThread = Guid.NewGuid(); |
| 13 | var branchThread = Guid.NewGuid(); |
| 14 | var rootMessage = new ChatConversationMessage(Guid.NewGuid(), "user", "Надо продумать ADR", mainThread, null, 0); |
| 15 | var assistantMessage = new ChatConversationMessage(Guid.NewGuid(), "assistant", "Давай разложим варианты.", mainThread, null, 1); |
| 16 | var branchMessage = new ChatConversationMessage(Guid.NewGuid(), "user", "Отдельно исследую Skia path", branchThread, assistantMessage.MessageId, 2); |
| 17 | |
| 18 | var snapshot = new ChatSurfaceCompositor().Compose(new ChatSurfaceIntent( |
| 19 | [rootMessage, assistantMessage, branchMessage], |
| 20 | ActiveClarificationBatch: null, |
| 21 | SelectedMessageIndex: 2, |
| 22 | MainThreadId: mainThread, |
| 23 | ActiveThreadId: branchThread, |
| 24 | ThreadBranchHint: "Ветка Skia")); |
| 25 | |
| 26 | Assert.Equal(2, snapshot.State.Threads.Count); |
| 27 | Assert.Equal(2, snapshot.Layout.Lanes.Count); |
| 28 | |
| 29 | var branchLane = Assert.Single(snapshot.Layout.Lanes, lane => lane.Thread.ThreadId == branchThread); |
| 30 | Assert.Equal(1, branchLane.Thread.Depth); |
| 31 | Assert.Contains(branchLane.Entries, entry => entry.StartsBranch); |
| 32 | Assert.Contains(snapshot.State.Edges, edge => edge.Kind == "fork"); |
| 33 | Assert.Contains(snapshot.Layout.Overview, item => item.ThreadId == branchThread && item.IsActive); |
| 34 | |
| 35 | var branchOverview = Assert.Single(snapshot.Layout.Overview, item => item.ThreadId == branchThread); |
| 36 | Assert.Contains("Skia", branchOverview.Summary, StringComparison.OrdinalIgnoreCase); |
| 37 | } |
| 38 | |
| 39 | [Fact] |
| 40 | public void Compose_SingleThreadOverview_UsesLastAssistantMessageAsSummary() |
| 41 | { |
| 42 | var threadId = Guid.NewGuid(); |
| 43 | var snapshot = new ChatSurfaceCompositor().Compose(new ChatSurfaceIntent( |
| 44 | [ |
| 45 | new ChatConversationMessage(Guid.NewGuid(), "user", "Как сделать topic cards?", threadId, null, 0), |
| 46 | new ChatConversationMessage(Guid.NewGuid(), "assistant", "Картотека: заголовок и короткая сводка.", threadId, null, 1) |
| 47 | ], |
| 48 | ActiveClarificationBatch: null, |
| 49 | SelectedMessageIndex: -1, |
| 50 | MainThreadId: threadId, |
| 51 | ActiveThreadId: threadId, |
| 52 | ThreadBranchHint: null)); |
| 53 | |
| 54 | var overview = Assert.Single(snapshot.Layout.Overview); |
| 55 | Assert.Contains("картотека", overview.Summary, StringComparison.OrdinalIgnoreCase); |
| 56 | } |
| 57 | |
| 58 | [Fact] |
| 59 | public void Compose_ActiveClarification_AddsConfirmationNodeToActiveThread() |
| 60 | { |
| 61 | var threadId = Guid.NewGuid(); |
| 62 | var batch = new ClarificationBatch( |
| 63 | Guid.NewGuid(), |
| 64 | [new ClarificationItem("scope", "Где показать preview?")], |
| 65 | "Нужно уточнить решение"); |
| 66 | |
| 67 | var snapshot = new ChatSurfaceCompositor().Compose(new ChatSurfaceIntent( |
| 68 | [new ChatConversationMessage(Guid.NewGuid(), "assistant", "Есть два пути.", threadId, null, 0)], |
| 69 | batch, |
| 70 | SelectedMessageIndex: -1, |
| 71 | MainThreadId: threadId, |
| 72 | ActiveThreadId: threadId, |
| 73 | ThreadBranchHint: null)); |
| 74 | |
| 75 | var confirmation = Assert.Single(snapshot.State.Confirmations); |
| 76 | Assert.Equal(threadId, confirmation.ThreadId); |
| 77 | Assert.True(confirmation.IsActive); |
| 78 | Assert.Contains("Где показать preview?", confirmation.Body); |
| 79 | |
| 80 | var lane = Assert.Single(snapshot.Layout.Lanes); |
| 81 | Assert.Contains(lane.Entries, entry => entry.Kind == ChatSurfaceEntryKind.Confirmation && entry.IsPending); |
| 82 | Assert.Contains(snapshot.State.Edges, edge => edge.Kind == "ask"); |
| 83 | } |
| 84 | } |
| 85 | |