| 1 | import { renderToStaticMarkup } from "react-dom/server"; |
| 2 | import { describe, expect, it, vi } from "vite-plus/test"; |
| 3 | |
| 4 | const state = vi.hoisted(() => ({ |
| 5 | projection: null as unknown, |
| 6 | workflow: null as unknown, |
| 7 | })); |
| 8 | |
| 9 | vi.mock("@t3tools/client-runtime/environment", () => ({ |
| 10 | scopeThreadRef: () => ({}) as never, |
| 11 | })); |
| 12 | |
| 13 | vi.mock("@t3tools/client-runtime/state/thread-workflows", () => ({ |
| 14 | deriveThreadQueueWorkflowState: () => state.workflow, |
| 15 | })); |
| 16 | |
| 17 | vi.mock("../../state/entities", () => ({ |
| 18 | useThreadProjection: () => state.projection, |
| 19 | })); |
| 20 | |
| 21 | vi.mock("../../state/threads", () => ({ |
| 22 | threadEnvironment: { |
| 23 | cancelQueuedRun: Symbol("cancelQueuedRun"), |
| 24 | promoteQueuedRun: Symbol("promoteQueuedRun"), |
| 25 | reorderQueuedRun: Symbol("reorderQueuedRun"), |
| 26 | }, |
| 27 | })); |
| 28 | |
| 29 | vi.mock("../../state/use-atom-command", () => ({ |
| 30 | useAtomCommand: () => async () => undefined, |
| 31 | })); |
| 32 | |
| 33 | vi.mock("../../assets/assetUrls", () => ({ |
| 34 | useAssetUrls: (_environmentId: never, resources: ReadonlyArray<{ attachmentId: string }>) => |
| 35 | resources.map((resource) => `https://assets.test/${resource.attachmentId}`), |
| 36 | })); |
| 37 | |
| 38 | import { QueuedRunsControl } from "./QueuedRunsControl"; |
| 39 | |
| 40 | describe("QueuedRunsControl automatic completion delivery", () => { |
| 41 | it("does not render a queue control when only hidden delivery remains", () => { |
| 42 | state.projection = { |
| 43 | projection: { |
| 44 | messages: [ |
| 45 | { |
| 46 | delegatedCompletion: { |
| 47 | parentRunId: "run:parent", |
| 48 | generation: 1, |
| 49 | taskIds: ["task:child"], |
| 50 | }, |
| 51 | id: "message:completion", |
| 52 | }, |
| 53 | ], |
| 54 | }, |
| 55 | }; |
| 56 | state.workflow = { |
| 57 | activeRun: { id: "run:active" }, |
| 58 | canPromoteToSteer: true, |
| 59 | canReorder: true, |
| 60 | queuedRuns: [], |
| 61 | }; |
| 62 | |
| 63 | const html = renderToStaticMarkup( |
| 64 | <QueuedRunsControl |
| 65 | environmentId={"environment:test" as never} |
| 66 | optimisticMessages={[]} |
| 67 | threadId={"thread:test" as never} |
| 68 | editingRunId={null} |
| 69 | onEditQueuedRun={() => undefined} |
| 70 | onCancelEdit={() => undefined} |
| 71 | />, |
| 72 | ); |
| 73 | |
| 74 | expect(html).toBe(""); |
| 75 | }); |
| 76 | }); |
| 77 | |
| 78 | describe("QueuedRunsControl attachments and edit mode", () => { |
| 79 | const workflowWithAttachment = () => ({ |
| 80 | activeRun: { id: "run:active" }, |
| 81 | canPromoteToSteer: true, |
| 82 | canReorder: true, |
| 83 | queuedRuns: [ |
| 84 | { |
| 85 | run: { id: "run:queued", userMessageId: "message:queued" }, |
| 86 | text: "Queued with a screenshot", |
| 87 | attachments: [ |
| 88 | { |
| 89 | type: "image", |
| 90 | id: "attachment-1", |
| 91 | name: "screenshot.png", |
| 92 | mimeType: "image/png", |
| 93 | sizeBytes: 128, |
| 94 | }, |
| 95 | ], |
| 96 | }, |
| 97 | ], |
| 98 | }); |
| 99 | |
| 100 | it("renders an attachment thumbnail on the queued row", () => { |
| 101 | state.projection = { projection: { messages: [] } }; |
| 102 | state.workflow = workflowWithAttachment(); |
| 103 | |
| 104 | const html = renderToStaticMarkup( |
| 105 | <QueuedRunsControl |
| 106 | environmentId={"environment:test" as never} |
| 107 | optimisticMessages={[]} |
| 108 | threadId={"thread:test" as never} |
| 109 | editingRunId={null} |
| 110 | onEditQueuedRun={() => undefined} |
| 111 | onCancelEdit={() => undefined} |
| 112 | />, |
| 113 | ); |
| 114 | |
| 115 | expect(html).toContain("https://assets.test/attachment-1"); |
| 116 | expect(html).toContain("Queued with a screenshot"); |
| 117 | expect(html).toContain("Edit queued message"); |
| 118 | expect(html).toContain("Reorder queued message"); |
| 119 | expect(html).not.toContain("Move queued message up"); |
| 120 | }); |
| 121 | |
| 122 | it("drops the optimistic pending row once the projection holds its message", () => { |
| 123 | state.projection = { |
| 124 | projection: { messages: [{ id: "message:acknowledged", text: "hello" }] }, |
| 125 | }; |
| 126 | state.workflow = { |
| 127 | activeRun: { id: "run:active" }, |
| 128 | canPromoteToSteer: true, |
| 129 | canReorder: true, |
| 130 | queuedRuns: [], |
| 131 | }; |
| 132 | |
| 133 | const html = renderToStaticMarkup( |
| 134 | <QueuedRunsControl |
| 135 | environmentId={"environment:test" as never} |
| 136 | optimisticMessages={[ |
| 137 | { |
| 138 | id: "message:acknowledged" as never, |
| 139 | inputIntent: "queued_turn", |
| 140 | text: "hello", |
| 141 | attachments: [], |
| 142 | }, |
| 143 | ]} |
| 144 | threadId={"thread:test" as never} |
| 145 | editingRunId={null} |
| 146 | onEditQueuedRun={() => undefined} |
| 147 | onCancelEdit={() => undefined} |
| 148 | />, |
| 149 | ); |
| 150 | |
| 151 | expect(html).toBe(""); |
| 152 | }); |
| 153 | |
| 154 | it("keeps the original queued message visible while editing", () => { |
| 155 | state.projection = { projection: { messages: [] } }; |
| 156 | state.workflow = workflowWithAttachment(); |
| 157 | |
| 158 | const html = renderToStaticMarkup( |
| 159 | <QueuedRunsControl |
| 160 | environmentId={"environment:test" as never} |
| 161 | optimisticMessages={[]} |
| 162 | threadId={"thread:test" as never} |
| 163 | editingRunId={"run:queued" as never} |
| 164 | onEditQueuedRun={() => undefined} |
| 165 | onCancelEdit={() => undefined} |
| 166 | />, |
| 167 | ); |
| 168 | |
| 169 | expect(html).toContain("Queued with a screenshot"); |
| 170 | }); |
| 171 | }); |