@@ -85,6 +85,7 @@ vi.mock('@/hooks/use-chat-input-focus', () => ({ useChatInputFocus: vi.fn() }))
8585vi . mock ( '@/app/o/[organizationId]/providers/organization-provider' , ( ) => organizationProviderMock )
8686
8787import { Composer } from '@/app/o/[organizationId]/home/components/composer/composer'
88+ import type { ChatRequestMode } from '@/app/workspace/[workspaceId]/home/types'
8889import { FeatureFlagsProvider } from '@/app/workspace/[workspaceId]/providers/feature-flags-provider'
8990import { useFileAttachments } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/hooks/use-file-attachments'
9091
@@ -187,6 +188,7 @@ beforeEach(() => {
187188
188189afterEach ( async ( ) => {
189190 await act ( async ( ) => root . unmount ( ) )
191+ vi . useRealTimers ( )
190192 container . remove ( )
191193 queryClient . clear ( )
192194} )
@@ -251,6 +253,86 @@ function fileList(files: File[]): FileList {
251253 return Object . assign ( files , { item : ( index : number ) => files [ index ] ?? null } )
252254}
253255
256+ it . each ( [
257+ { searchEnabled : true , planEnabled : true , modes : [ 'assistant' , 'agent' , 'plan' , 'assistant' ] } ,
258+ { searchEnabled : true , planEnabled : false , modes : [ 'assistant' , 'agent' , 'assistant' ] } ,
259+ { searchEnabled : false , planEnabled : true , modes : [ 'agent' , 'plan' , 'agent' ] } ,
260+ ] satisfies { searchEnabled : boolean ; planEnabled : boolean ; modes : ChatRequestMode [ ] } [ ] ) (
261+ 'cycles available modes without losing the draft or selection (Search: $searchEnabled, Plan: $planEnabled)' ,
262+ async ( { searchEnabled, planEnabled, modes } ) => {
263+ vi . useFakeTimers ( { toFake : [ 'requestAnimationFrame' , 'cancelAnimationFrame' ] } )
264+ let currentMode = modes [ 0 ]
265+ function Harness ( ) {
266+ const [ mode , setMode ] = useState ( modes [ 0 ] )
267+ currentMode = mode
268+ const [ value , setValue ] = useState ( 'Summarize this draft' )
269+ const files = useFileAttachments ( {
270+ userId : 'user-a' ,
271+ organizationId : 'organization-a' ,
272+ requestMode : mode ,
273+ } )
274+ return (
275+ < Composer
276+ requestMode = { mode }
277+ searchEnabled = { searchEnabled }
278+ showModeSelector
279+ onModeChange = { setMode }
280+ value = { value }
281+ onChange = { setValue }
282+ files = { files }
283+ isInitialView
284+ isSending = { false }
285+ onStop = { ( ) => { } }
286+ onSubmit = { ( ) => { } }
287+ />
288+ )
289+ }
290+ await act ( async ( ) =>
291+ root . render (
292+ < QueryClientProvider client = { queryClient } >
293+ < FeatureFlagsProvider
294+ flags = { {
295+ dashboards : false ,
296+ 'table-row-ttl' : false ,
297+ 'mothership-model-selector' : false ,
298+ 'mothership-plan-mode' : planEnabled ,
299+ } }
300+ >
301+ < Harness />
302+ </ FeatureFlagsProvider >
303+ </ QueryClientProvider >
304+ )
305+ )
306+ const input = container . querySelector < HTMLTextAreaElement > ( '[aria-label="Ask Sim"]' ) !
307+ await act ( async ( ) => {
308+ input . focus ( )
309+ input . setSelectionRange ( 10 , 14 , 'backward' )
310+ } )
311+ for ( const expectedMode of modes . slice ( 1 ) ) {
312+ const event = new KeyboardEvent ( 'keydown' , {
313+ key : 'Tab' ,
314+ shiftKey : true ,
315+ bubbles : true ,
316+ cancelable : true ,
317+ } )
318+ await act ( async ( ) => {
319+ container . querySelector < HTMLTextAreaElement > ( '[aria-label="Ask Sim"]' ) ! . dispatchEvent ( event )
320+ } )
321+ await act ( async ( ) => vi . advanceTimersToNextFrame ( ) )
322+ const nextInput = container . querySelector < HTMLTextAreaElement > ( '[aria-label="Ask Sim"]' ) !
323+ expect ( currentMode ) . toBe ( expectedMode )
324+ expect ( event . defaultPrevented ) . toBe ( true )
325+ expect ( document . activeElement ) . toBe ( nextInput )
326+ expect ( nextInput . value ) . toBe ( 'Summarize this draft' )
327+ expect ( [
328+ nextInput . selectionStart ,
329+ nextInput . selectionEnd ,
330+ nextInput . selectionDirection ,
331+ ] ) . toEqual ( [ 10 , 14 , 'backward' ] )
332+ }
333+ }
334+ )
335+
254336async function paste ( files : File [ ] ) {
255337 const event = new Event ( 'paste' , { bubbles : true , cancelable : true } )
256338 Object . defineProperty ( event , 'clipboardData' , {
0 commit comments