@@ -6,14 +6,30 @@ const queuedState = vi.hoisted(() => ({
66 messages : [ ] as Array < { id : string ; content : string ; queuedAt : number } > ,
77} ) ) ;
88
9+ const sessionState = vi . hoisted ( ( ) => ( {
10+ editingQueuedId : undefined as string | undefined ,
11+ } ) ) ;
12+
13+ const sessionService = vi . hoisted ( ( ) => ( {
14+ steerQueuedMessage : vi . fn ( ) . mockResolvedValue ( undefined ) ,
15+ clearEditingQueuedMessage : vi . fn ( ) ,
16+ } ) ) ;
17+
18+ const storeSetters = vi . hoisted ( ( ) => ( {
19+ removeQueuedMessage : vi . fn ( ) ,
20+ moveQueuedMessage : vi . fn ( ) ,
21+ } ) ) ;
22+
23+ const dndCapture = vi . hoisted ( ( ) => ( {
24+ onDragOver : undefined as ( ( event : unknown ) => void ) | undefined ,
25+ } ) ) ;
26+
927vi . mock ( "@posthog/core/sessions/sessionService" , ( ) => ( {
1028 SESSION_SERVICE : Symbol . for ( "test.session-service" ) ,
1129} ) ) ;
1230
1331vi . mock ( "@posthog/di/react" , ( ) => ( {
14- useService : ( ) => ( {
15- steerQueuedMessage : vi . fn ( ) . mockResolvedValue ( undefined ) ,
16- } ) ,
32+ useService : ( ) => sessionService ,
1733} ) ) ;
1834
1935vi . mock ( "@posthog/ui/features/sessions/useSession" , ( ) => ( {
@@ -30,35 +46,73 @@ vi.mock("@posthog/ui/features/sessions/hooks/useEditQueuedMessage", () => ({
3046} ) ) ;
3147
3248vi . mock ( "@posthog/ui/features/sessions/sessionStore" , ( ) => ( {
33- sessionStoreSetters : {
34- removeQueuedMessage : vi . fn ( ) ,
35- moveQueuedMessage : vi . fn ( ) ,
36- } ,
49+ sessionStoreSetters : storeSetters ,
3750 useSessionIsCloud : ( ) => false ,
3851 useSessionSelector : < T , > (
3952 _taskId : string ,
40- select : ( session : undefined ) => T ,
41- ) => select ( undefined ) ,
53+ select : ( session : { editingQueuedId ?: string } ) => T ,
54+ ) => select ( { editingQueuedId : sessionState . editingQueuedId } ) ,
4255 useSessionStore : {
43- getState : ( ) => ( { taskIdIndex : { } , sessions : { } } ) ,
56+ getState : ( ) => ( {
57+ taskIdIndex : new Proxy ( { } , { get : ( ) => "run-1" } ) as Record <
58+ string ,
59+ string
60+ > ,
61+ sessions : { "run-1" : { messageQueue : queuedState . messages } } ,
62+ } ) ,
4463 } ,
4564} ) ) ;
4665
4766vi . mock ( "@posthog/ui/primitives/toast" , ( ) => ( {
4867 toast : { error : vi . fn ( ) } ,
4968} ) ) ;
5069
70+ // The dock's reorder handler is driven directly through the captured
71+ // onDragOver prop; the sortable plumbing itself is @dnd-kit's to test.
72+ vi . mock ( "@dnd-kit/react" , async ( ) => {
73+ const React = await import ( "react" ) ;
74+ return {
75+ DragDropProvider : ( {
76+ onDragOver,
77+ children,
78+ } : {
79+ onDragOver : ( event : unknown ) => void ;
80+ children : React . ReactNode ;
81+ } ) => {
82+ dndCapture . onDragOver = onDragOver ;
83+ return React . createElement ( React . Fragment , null , children ) ;
84+ } ,
85+ } ;
86+ } ) ;
87+ vi . mock ( "@dnd-kit/react/sortable" , ( ) => ( {
88+ useSortable : ( ) => ( {
89+ ref : ( ) => { } ,
90+ handleRef : ( ) => { } ,
91+ isDragging : false ,
92+ } ) ,
93+ } ) ) ;
94+ vi . mock ( "@dnd-kit/dom" , ( ) => ( { PointerSensor : class { } } ) ) ;
95+
5196// Stub the per-message card so the test exercises the dock's collapse/scroll
5297// shell, not the markdown/steer internals it already owns.
5398vi . mock (
5499 "@posthog/ui/features/sessions/components/session-update/QueuedMessageView" ,
55100 async ( ) => {
56101 const React = await import ( "react" ) ;
57102 return {
58- QueuedMessageView : ( { message } : { message : { content : string } } ) =>
103+ QueuedMessageView : ( {
104+ message,
105+ isEditing,
106+ } : {
107+ message : { content : string } ;
108+ isEditing ?: boolean ;
109+ } ) =>
59110 React . createElement (
60111 "div" ,
61- { "data-testid" : "queued-card" } ,
112+ {
113+ "data-testid" : "queued-card" ,
114+ "data-editing" : String ( ! ! isEditing ) ,
115+ } ,
62116 message . content ,
63117 ) ,
64118 } ;
@@ -84,7 +138,10 @@ function renderDock(taskId: string) {
84138
85139describe ( "QueuedMessagesDock" , ( ) => {
86140 beforeEach ( ( ) => {
141+ vi . clearAllMocks ( ) ;
87142 queuedState . messages = [ ] ;
143+ sessionState . editingQueuedId = undefined ;
144+ dndCapture . onDragOver = undefined ;
88145 } ) ;
89146
90147 it ( "renders nothing when the queue is empty" , ( ) => {
@@ -135,4 +192,66 @@ describe("QueuedMessagesDock", () => {
135192 fireEvent . click ( trigger ) ;
136193 expect ( screen . getAllByTestId ( "queued-card" ) ) . toHaveLength ( 2 ) ;
137194 } ) ;
195+
196+ it ( "reorders the queue when a card is dragged over another" , ( ) => {
197+ queuedState . messages = TWO_MESSAGES ;
198+ renderDock ( "task-drag" ) ;
199+
200+ dndCapture . onDragOver ?.( {
201+ operation : { source : { id : "q1" } , target : { id : "q2" } } ,
202+ } ) ;
203+
204+ expect ( storeSetters . moveQueuedMessage ) . toHaveBeenCalledWith (
205+ "task-drag" ,
206+ 0 ,
207+ 1 ,
208+ ) ;
209+ } ) ;
210+
211+ it . each ( [
212+ {
213+ name : "source and target are the same card" ,
214+ operation : { source : { id : "q1" } , target : { id : "q1" } } ,
215+ } ,
216+ {
217+ name : "source is not in the queue" ,
218+ operation : { source : { id : "missing" } , target : { id : "q2" } } ,
219+ } ,
220+ {
221+ name : "target is not in the queue" ,
222+ operation : { source : { id : "q1" } , target : { id : "missing" } } ,
223+ } ,
224+ {
225+ name : "there is no drop target" ,
226+ operation : { source : { id : "q1" } , target : undefined } ,
227+ } ,
228+ ] ) ( "does not reorder when $name" , ( { operation } ) => {
229+ queuedState . messages = TWO_MESSAGES ;
230+ renderDock ( "task-drag-noop" ) ;
231+
232+ dndCapture . onDragOver ?.( { operation } ) ;
233+
234+ expect ( storeSetters . moveQueuedMessage ) . not . toHaveBeenCalled ( ) ;
235+ } ) ;
236+
237+ it ( "marks only the edited message's card as editing" , ( ) => {
238+ queuedState . messages = TWO_MESSAGES ;
239+ sessionState . editingQueuedId = "q1" ;
240+ renderDock ( "task-editing" ) ;
241+
242+ const [ first , second ] = screen . getAllByTestId ( "queued-card" ) ;
243+ expect ( first ) . toHaveAttribute ( "data-editing" , "true" ) ;
244+ expect ( second ) . toHaveAttribute ( "data-editing" , "false" ) ;
245+ expect ( sessionService . clearEditingQueuedMessage ) . not . toHaveBeenCalled ( ) ;
246+ } ) ;
247+
248+ it ( "clears a stale edit hold when the edited message leaves the queue" , ( ) => {
249+ queuedState . messages = TWO_MESSAGES ;
250+ sessionState . editingQueuedId = "q-gone" ;
251+ renderDock ( "task-stale-hold" ) ;
252+
253+ expect ( sessionService . clearEditingQueuedMessage ) . toHaveBeenCalledWith (
254+ "task-stale-hold" ,
255+ ) ;
256+ } ) ;
138257} ) ;
0 commit comments