1+ import type { Session } from "@coder-studio/core" ;
12import { act , fireEvent , render , screen , waitFor } from "@testing-library/react" ;
23import { createStore , Provider } from "jotai" ;
34import { afterEach , beforeEach , describe , expect , it , vi } from "vitest" ;
@@ -9,20 +10,21 @@ import { seedReadyWorkspaceState } from "../../test-utils/workspace-state";
910import { LEGACY_PANE_LAYOUT_STORAGE_KEY_PREFIX , paneLayoutAtomFamily } from "./atoms/pane-layout" ;
1011import { AgentPanes } from "./index" ;
1112
13+ type MockSessionCardProps = {
14+ sessionId : string ;
15+ onStop ?: ( ) => void ;
16+ onSplitHorizontal ?: ( ) => void ;
17+ onSplitVertical ?: ( ) => void ;
18+ onClose ?: ( ) => void ;
19+ } ;
20+
1221const mockSessionCard = vi . fn (
13- ( {
14- sessionId,
15- onSplitHorizontal,
16- onSplitVertical,
17- onClose,
18- } : {
19- sessionId : string ;
20- onSplitHorizontal ?: ( ) => void ;
21- onSplitVertical ?: ( ) => void ;
22- onClose ?: ( ) => void ;
23- } ) => (
22+ ( { sessionId, onStop, onSplitHorizontal, onSplitVertical, onClose } : MockSessionCardProps ) => (
2423 < div data-testid = "session-card" >
2524 < span > { sessionId } </ span >
25+ < button type = "button" onClick = { onStop } >
26+ stop-{ sessionId }
27+ </ button >
2628 < button type = "button" onClick = { onSplitHorizontal } >
2729 split-{ sessionId }
2830 </ button >
@@ -37,7 +39,7 @@ const mockSessionCard = vi.fn(
3739) ;
3840
3941vi . mock ( "./views/shared/session-card" , ( ) => ( {
40- SessionCard : ( props : Record < string , unknown > ) => mockSessionCard ( props ) ,
42+ SessionCard : ( props : MockSessionCardProps ) => mockSessionCard ( props ) ,
4143} ) ) ;
4244
4345vi . mock ( "./views/shared/pane-layout" , ( ) => ( {
@@ -72,7 +74,7 @@ function createAgentPaneStore(
7274 | "rejected" = "connected"
7375) {
7476 const store = createStore ( ) ;
75- const sessions = [
77+ const sessions : Session [ ] = [
7678 {
7779 id : "sess_1" ,
7880 workspaceId : "ws-1" ,
@@ -125,7 +127,10 @@ function createAgentPaneStore(
125127 } ,
126128 } ,
127129 } ) ;
128- store . set ( sessionsAtom , Object . fromEntries ( sessions . map ( ( session ) => [ session . id , session ] ) ) ) ;
130+ store . set (
131+ sessionsAtom ,
132+ Object . fromEntries ( sessions . map ( ( session ) => [ session . id , session ] ) ) as Record < string , Session >
133+ ) ;
129134 store . set (
130135 paneLayoutAtomFamily ( "ws-1" ) ,
131136 ( initialLayout as never ) ?? {
@@ -339,6 +344,39 @@ describe("AgentPanes", () => {
339344 } ) ;
340345 } ) ;
341346
347+ it ( "stops a running session without removing its pane" , async ( ) => {
348+ const { store, sendCommand } = createAgentPaneStore ( ) ;
349+
350+ render (
351+ < Provider store = { store } >
352+ < AgentPanes />
353+ </ Provider >
354+ ) ;
355+
356+ await waitFor ( ( ) => {
357+ expect ( store . get ( paneLayoutAtomFamily ( "ws-1" ) ) ) . toEqual ( {
358+ id : expect . any ( String ) ,
359+ type : "split" ,
360+ direction : "horizontal" ,
361+ ratio : 0.5 ,
362+ children : [
363+ expect . objectContaining ( { type : "leaf" , sessionId : "sess_1" } ) ,
364+ expect . objectContaining ( { type : "leaf" , sessionId : "sess_2" } ) ,
365+ ] ,
366+ } ) ;
367+ } ) ;
368+
369+ const layoutBeforeStop = structuredClone ( store . get ( paneLayoutAtomFamily ( "ws-1" ) ) ) ;
370+
371+ fireEvent . click ( screen . getByRole ( "button" , { name : "stop-sess_1" } ) ) ;
372+
373+ await waitFor ( ( ) => {
374+ expect ( sendCommand ) . toHaveBeenCalledWith ( "session.stop" , { sessionId : "sess_1" } , undefined ) ;
375+ } ) ;
376+
377+ expect ( store . get ( paneLayoutAtomFamily ( "ws-1" ) ) ) . toEqual ( layoutBeforeStop ) ;
378+ } ) ;
379+
342380 it ( "waits for the websocket connection before requesting session.list" , async ( ) => {
343381 const sendCommand = vi . fn ( ) . mockResolvedValue ( [ ] ) ;
344382 const { store } = createAgentPaneStore ( undefined , sendCommand , "connecting" ) ;
0 commit comments