1- import { createEvent , fireEvent , render , screen } from "@testing-library/react" ;
1+ import { act , createEvent , fireEvent , render , screen } from "@testing-library/react" ;
22import { createStore , Provider } from "jotai" ;
3- import { beforeEach , describe , expect , it , vi } from "vitest" ;
3+ import { afterEach , beforeEach , describe , expect , it , vi } from "vitest" ;
44import { localeAtom } from "../../../../atoms/app-ui" ;
55import { wsClientAtom } from "../../../../atoms/connection" ;
66import { WORKSPACE_PATH_DRAG_MIME } from "../../../../lib/workspace-path-drag" ;
77import { DraftLauncher } from "./draft-launcher" ;
88
99const mockUseProviderLauncher = vi . fn ( ) ;
10+ const originalResizeObserver = global . ResizeObserver ;
1011
1112vi . mock ( "../../actions/use-provider-launcher" , ( ) => ( {
1213 useProviderLauncher : ( ...args : unknown [ ] ) => mockUseProviderLauncher ( ...args ) ,
@@ -31,7 +32,57 @@ function createRuntimeState(providerId: "claude" | "codex") {
3132 } ;
3233}
3334
35+ function createDraftLauncherStore ( ) {
36+ const store = createStore ( ) ;
37+
38+ store . set ( localeAtom , "en" ) ;
39+ store . set ( wsClientAtom , {
40+ sendCommand : vi . fn ( ) ,
41+ subscribe : vi . fn ( ( ) => ( ) => { } ) ,
42+ } as never ) ;
43+
44+ return store ;
45+ }
46+
47+ function installResizeObserverMock ( ) {
48+ let callback : ResizeObserverCallback | null = null ;
49+
50+ class ResizeObserverMock {
51+ constructor ( observerCallback : ResizeObserverCallback ) {
52+ callback = observerCallback ;
53+ }
54+
55+ observe ( ) { }
56+ disconnect ( ) { }
57+ }
58+
59+ global . ResizeObserver = ResizeObserverMock as unknown as typeof ResizeObserver ;
60+
61+ return {
62+ resize ( target : Element , width : number ) {
63+ if ( ! callback ) {
64+ throw new Error ( "ResizeObserver was not created" ) ;
65+ }
66+
67+ callback (
68+ [
69+ {
70+ target,
71+ contentRect : { width } ,
72+ } as ResizeObserverEntry ,
73+ ] ,
74+ { } as ResizeObserver
75+ ) ;
76+ } ,
77+ } ;
78+ }
79+
3480describe ( "DraftLauncher" , ( ) => {
81+ afterEach ( ( ) => {
82+ global . ResizeObserver = originalResizeObserver ;
83+ vi . useRealTimers ( ) ;
84+ } ) ;
85+
3586 beforeEach ( ( ) => {
3687 vi . clearAllMocks ( ) ;
3788 mockUseProviderLauncher . mockReturnValue ( {
@@ -126,13 +177,7 @@ describe("DraftLauncher", () => {
126177 } ) ;
127178
128179 it ( "switches draft launcher carousel panels" , ( ) => {
129- const store = createStore ( ) ;
130-
131- store . set ( localeAtom , "en" ) ;
132- store . set ( wsClientAtom , {
133- sendCommand : vi . fn ( ) ,
134- subscribe : vi . fn ( ( ) => ( ) => { } ) ,
135- } as never ) ;
180+ const store = createDraftLauncherStore ( ) ;
136181
137182 const { container } = render (
138183 < Provider store = { store } >
@@ -155,6 +200,69 @@ describe("DraftLauncher", () => {
155200 expect ( carouselTrack ) . toHaveClass ( "agent-draft-component-row--file" ) ;
156201 } ) ;
157202
203+ it ( "auto-rotates draft launcher carousel panels in compact layout" , async ( ) => {
204+ vi . useFakeTimers ( ) ;
205+ const resizeObserver = installResizeObserverMock ( ) ;
206+ const store = createDraftLauncherStore ( ) ;
207+
208+ const { container } = render (
209+ < Provider store = { store } >
210+ < DraftLauncher workspaceId = "ws-123" />
211+ </ Provider >
212+ ) ;
213+
214+ const launcher = container . querySelector ( ".agent-draft-launcher" ) ;
215+ const agentButton = screen . getByRole ( "button" , { name : "Agent" } ) ;
216+ const fileButton = screen . getByRole ( "button" , { name : "File Editor" } ) ;
217+ const carouselTrack = container . querySelector ( ".agent-draft-component-row" ) ;
218+
219+ expect ( launcher ) . not . toBeNull ( ) ;
220+ expect ( carouselTrack ) . not . toHaveClass ( "agent-draft-component-row--file" ) ;
221+
222+ act ( ( ) => {
223+ resizeObserver . resize ( launcher as Element , 360 ) ;
224+ } ) ;
225+
226+ await act ( async ( ) => {
227+ await vi . advanceTimersByTimeAsync ( 4000 ) ;
228+ } ) ;
229+
230+ expect ( agentButton ) . toHaveAttribute ( "aria-pressed" , "false" ) ;
231+ expect ( fileButton ) . toHaveAttribute ( "aria-pressed" , "true" ) ;
232+ expect ( carouselTrack ) . toHaveClass ( "agent-draft-component-row--file" ) ;
233+ } ) ;
234+
235+ it ( "does not auto-rotate draft launcher carousel panels in wide layout" , async ( ) => {
236+ vi . useFakeTimers ( ) ;
237+ const resizeObserver = installResizeObserverMock ( ) ;
238+ const store = createDraftLauncherStore ( ) ;
239+
240+ const { container } = render (
241+ < Provider store = { store } >
242+ < DraftLauncher workspaceId = "ws-123" />
243+ </ Provider >
244+ ) ;
245+
246+ const launcher = container . querySelector ( ".agent-draft-launcher" ) ;
247+ const agentButton = screen . getByRole ( "button" , { name : "Agent" } ) ;
248+ const fileButton = screen . getByRole ( "button" , { name : "File Editor" } ) ;
249+ const carouselTrack = container . querySelector ( ".agent-draft-component-row" ) ;
250+
251+ expect ( launcher ) . not . toBeNull ( ) ;
252+
253+ act ( ( ) => {
254+ resizeObserver . resize ( launcher as Element , 640 ) ;
255+ } ) ;
256+
257+ await act ( async ( ) => {
258+ await vi . advanceTimersByTimeAsync ( 4000 ) ;
259+ } ) ;
260+
261+ expect ( agentButton ) . toHaveAttribute ( "aria-pressed" , "true" ) ;
262+ expect ( fileButton ) . toHaveAttribute ( "aria-pressed" , "false" ) ;
263+ expect ( carouselTrack ) . not . toHaveClass ( "agent-draft-component-row--file" ) ;
264+ } ) ;
265+
158266 it ( "renders a draft drop label when pane drag hover is active" , ( ) => {
159267 const store = createStore ( ) ;
160268
0 commit comments