1- import { act , fireEvent , render , screen , waitFor } from '@testing-library/react'
1+ import { act , fireEvent , render , screen , waitFor , within } from '@testing-library/react'
22import '@testing-library/jest-dom'
33import { beforeEach , describe , expect , it , vi } from 'vitest'
44
@@ -109,11 +109,15 @@ async function renderManager(response: AgentListResponse = EMPTY_RESPONSE) {
109109}
110110
111111function chooseAgentSelect ( label : string , option : string ) {
112- // The trigger is a button; the entries inside the panel are listbox options.
113- // They used to be buttons, which is invalid inside a `role="listbox"` and
114- // left the dropdown without arrow-key navigation.
115- fireEvent . click ( screen . getByRole ( 'button' , { name : label } ) )
116- fireEvent . click ( screen . getByRole ( 'option' , { name : option } ) )
112+ const select = screen . getByRole ( 'combobox' , { name : label } )
113+ const selectedOption = within ( select ) . getByRole ( 'option' , { name : option } ) as HTMLOptionElement
114+ fireEvent . change ( select , { target : { value : selectedOption . value } } )
115+ }
116+
117+ function chooseAgentModel ( option : string | RegExp ) {
118+ fireEvent . click ( screen . getByRole ( 'button' , { name : 'Model' } ) )
119+ const picker = screen . getByTestId ( 'model-selector-dropdown' )
120+ fireEvent . click ( within ( picker ) . getByRole ( 'button' , { name : option } ) )
117121}
118122
119123describe ( 'AgentManager' , ( ) => {
@@ -131,7 +135,23 @@ describe('AgentManager', () => {
131135 } ,
132136 } )
133137 recentProjectsMock . mockResolvedValue ( { projects : [ ] } )
134- useSettingsStore . setState ( { locale : 'en' } )
138+ useSettingsStore . setState ( {
139+ locale : 'en' ,
140+ availableModels : [
141+ {
142+ id : 'provider/custom-model' ,
143+ name : 'Provider Custom' ,
144+ description : 'Current provider model' ,
145+ context : '200k' ,
146+ } ,
147+ {
148+ id : 'deepseek-v4-pro' ,
149+ name : 'DeepSeek V4 Pro' ,
150+ description : 'Current provider model' ,
151+ context : '200k' ,
152+ } ,
153+ ] ,
154+ } )
135155 setProjectSession ( '/workspace/project' )
136156 useAgentStore . setState ( {
137157 activeAgents : [ ] ,
@@ -165,17 +185,19 @@ describe('AgentManager', () => {
165185 fireEvent . click ( screen . getByRole ( 'button' , { name : 'Create Agent' } ) )
166186 fireEvent . click ( screen . getByRole ( 'button' , { name : 'Project' } ) )
167187 expect ( screen . getByRole ( 'button' , { name : 'Select a project...' } ) ) . toBeInTheDocument ( )
168- expect ( document . querySelector ( 'select' ) ) . not . toBeInTheDocument ( )
188+ expect ( screen . getByRole ( 'button' , { name : 'Select a project...' } ) . tagName ) . toBe ( 'BUTTON' )
169189 fireEvent . click ( screen . getByRole ( 'button' , { name : 'Select a project...' } ) )
170190 fireEvent . click ( await screen . findByRole ( 'button' , { name : / S e l e c t e d P r o j e c t / } ) )
171191 expect ( screen . getByText ( 'Target project: /workspace/selected' ) ) . toBeInTheDocument ( )
172192 fireEvent . click ( screen . getByRole ( 'button' , { name : 'Model' } ) )
173- const modelMenuOption = screen . getByRole ( 'option' , { name : 'fable' } )
193+ const modelPicker = screen . getByTestId ( 'model-selector-dropdown' )
194+ const modelMenuOption = within ( modelPicker ) . getByRole ( 'button' , { name : / f a b l e / i } )
174195 expect ( modelMenuOption ) . toBeInTheDocument ( )
175- expect ( modelMenuOption . parentElement ) . toHaveClass ( 'bottom-full' )
196+ expect ( screen . getByRole ( 'dialog' , { name : 'Create Agent' } ) ) . not . toContainElement ( modelPicker )
197+ expect ( modelPicker ) . toHaveClass ( 'fixed' , 'z-[var(--z-dropdown)]' )
176198 fireEvent . keyDown ( document , { key : 'Escape' } )
177199 expect ( screen . getByRole ( 'heading' , { name : 'Create Agent' } ) ) . toBeInTheDocument ( )
178- expect ( screen . queryByRole ( 'button' , { name : 'fable' } ) ) . not . toBeInTheDocument ( )
200+ expect ( screen . queryByTestId ( 'model-selector-dropdown' ) ) . not . toBeInTheDocument ( )
179201 expect ( screen . getByLabelText ( 'System prompt' ) . parentElement ) . toHaveTextContent ( 'System prompt*' )
180202 } )
181203
@@ -253,7 +275,7 @@ describe('AgentManager', () => {
253275 expect ( apiListMock ) . toHaveBeenNthCalledWith ( 3 , '/workspace/b' )
254276 } )
255277
256- it ( 'creates an underscore slug with custom model and effort, then selects the refreshed agent' , async ( ) => {
278+ it ( 'creates an underscore slug with a configured provider model and effort, then selects the refreshed agent' , async ( ) => {
257279 const created = makeAgent ( {
258280 source : 'projectSettings' ,
259281 model : 'provider/custom-model' ,
@@ -278,8 +300,7 @@ describe('AgentManager', () => {
278300 fireEvent . change ( screen . getByLabelText ( / ^ N a m e / ) , { target : { value : 'code_reviewer' } } )
279301 fireEvent . change ( screen . getByLabelText ( / ^ D e s c r i p t i o n / ) , { target : { value : 'Review code' } } )
280302 fireEvent . change ( screen . getByLabelText ( 'System prompt' ) , { target : { value : 'Review carefully.' } } )
281- chooseAgentSelect ( 'Model' , 'Custom model ID' )
282- fireEvent . change ( screen . getByLabelText ( / ^ C u s t o m m o d e l I D / ) , { target : { value : 'provider/custom-model' } } )
303+ chooseAgentModel ( / P r o v i d e r C u s t o m / )
283304 chooseAgentSelect ( 'Reasoning effort' , 'xhigh' )
284305 chooseAgentSelect ( 'Tools' , 'Custom list' )
285306 fireEvent . click ( screen . getByRole ( 'checkbox' , { name : / R e a d / } ) )
@@ -445,7 +466,7 @@ describe('AgentManager', () => {
445466 render ( < AgentManager /> )
446467 await waitFor ( ( ) => expect ( apiListMock ) . toHaveBeenCalledTimes ( 1 ) )
447468 fireEvent . click ( screen . getByRole ( 'button' , { name : 'Edit' } ) )
448- chooseAgentSelect ( 'Model' , ' Inherit from parent' )
469+ chooseAgentModel ( / I n h e r i t f r o m p a r e n t / )
449470 chooseAgentSelect ( 'Reasoning effort' , 'Inherit from parent' )
450471 chooseAgentSelect ( 'Tools' , 'All tools' )
451472 chooseAgentSelect ( 'Color' , 'Default' )
@@ -466,6 +487,27 @@ describe('AgentManager', () => {
466487 expect ( screen . getAllByText ( 'Inherit' ) . length ) . toBeGreaterThanOrEqual ( 2 )
467488 } )
468489
490+ it ( 'preserves a saved model ID that the current provider no longer lists' , async ( ) => {
491+ const agent = makeAgent ( { model : 'legacy/provider-model' } )
492+ apiListMock . mockResolvedValue ( { activeAgents : [ agent ] , allAgents : [ agent ] } )
493+ apiUpdateMock . mockResolvedValue ( { agent } )
494+ useAgentStore . setState ( { selectedAgent : agent , activeAgents : [ agent ] , allAgents : [ agent ] } )
495+
496+ render ( < AgentManager /> )
497+ await waitFor ( ( ) => expect ( apiListMock ) . toHaveBeenCalledTimes ( 1 ) )
498+ fireEvent . click ( screen . getByRole ( 'button' , { name : 'Edit' } ) )
499+ fireEvent . click ( screen . getByRole ( 'button' , { name : 'Model' } ) )
500+ const picker = screen . getByTestId ( 'model-selector-dropdown' )
501+ expect ( within ( picker ) . getByRole ( 'button' , { name : / l e g a c y \/ p r o v i d e r - m o d e l .* n o t l i s t e d b y t h e c u r r e n t p r o v i d e r / i } ) ) . toBeInTheDocument ( )
502+ fireEvent . click ( within ( picker ) . getByRole ( 'button' , { name : / l e g a c y \/ p r o v i d e r - m o d e l / i } ) )
503+ fireEvent . click ( screen . getByRole ( 'button' , { name : 'Save' } ) )
504+
505+ await waitFor ( ( ) => expect ( apiUpdateMock ) . toHaveBeenCalledWith (
506+ 'code_reviewer' ,
507+ expect . objectContaining ( { model : 'legacy/provider-model' } ) ,
508+ ) )
509+ } )
510+
469511 it ( 'preserves an explicit empty tools list when editing only the description' , async ( ) => {
470512 const agent = makeAgent ( { tools : [ ] } )
471513 const updated = makeAgent ( { tools : [ ] , description : 'Updated review' } )
@@ -480,7 +522,7 @@ describe('AgentManager', () => {
480522 render ( < AgentManager /> )
481523 await waitFor ( ( ) => expect ( useAgentStore . getState ( ) . availableTools ) . toContain ( 'Read' ) )
482524 fireEvent . click ( screen . getByRole ( 'button' , { name : 'Edit' } ) )
483- expect ( screen . getByRole ( 'button ' , { name : 'Tools' } ) ) . toHaveTextContent ( 'No tools ')
525+ expect ( screen . getByRole ( 'combobox ' , { name : 'Tools' } ) ) . toHaveValue ( 'none ')
484526 fireEvent . change ( screen . getByLabelText ( / ^ D e s c r i p t i o n / ) , { target : { value : 'Updated review' } } )
485527 fireEvent . click ( screen . getByRole ( 'button' , { name : 'Save' } ) )
486528
@@ -529,7 +571,7 @@ describe('AgentManager', () => {
529571 render ( < AgentManager /> )
530572 await waitFor ( ( ) => expect ( apiListMock ) . toHaveBeenCalledTimes ( 1 ) )
531573 fireEvent . click ( screen . getByRole ( 'button' , { name : 'Edit' } ) )
532- expect ( screen . getByRole ( 'button ' , { name : 'Reasoning effort' } ) ) . toHaveTextContent ( '7' )
574+ expect ( screen . getByRole ( 'combobox ' , { name : 'Reasoning effort' } ) ) . toHaveValue ( '7' )
533575 fireEvent . change ( screen . getByLabelText ( / ^ D e s c r i p t i o n / ) , { target : { value : 'Updated review' } } )
534576 fireEvent . click ( screen . getByRole ( 'button' , { name : 'Save' } ) )
535577
@@ -776,10 +818,7 @@ describe('AgentManager', () => {
776818 fireEvent . click (
777819 screen . getByRole ( 'button' , { name : 'Adjust the model and effort for Explore' } ) ,
778820 )
779- chooseAgentSelect ( 'Model' , 'Custom model ID' )
780- fireEvent . change ( screen . getByLabelText ( / ^ C u s t o m m o d e l I D / ) , {
781- target : { value : 'deepseek-v4-pro' } ,
782- } )
821+ chooseAgentModel ( / D e e p S e e k V 4 P r o / )
783822 fireEvent . click ( screen . getByRole ( 'button' , { name : 'Save' } ) )
784823
785824 await waitFor ( ( ) => expect ( apiReloadMock ) . toHaveBeenCalledWith ( 'running-session' ) )
@@ -819,9 +858,10 @@ describe('AgentManager', () => {
819858 // inherit means "follow the main session" — collapsing them would make
820859 // inherit unreachable, and the default label is read from the server.
821860 fireEvent . click ( screen . getByRole ( 'button' , { name : 'Model' } ) )
822- expect ( screen . getByRole ( 'option' , { name : 'Built-in default (haiku)' } ) ) . toBeInTheDocument ( )
823- expect ( screen . getByRole ( 'option' , { name : 'Inherit from parent' } ) ) . toBeInTheDocument ( )
824- fireEvent . click ( screen . getByRole ( 'option' , { name : 'Built-in default (haiku)' } ) )
861+ const modelPicker = screen . getByTestId ( 'model-selector-dropdown' )
862+ expect ( within ( modelPicker ) . getByRole ( 'button' , { name : / B u i l t - i n d e f a u l t \( h a i k u \) / } ) ) . toBeInTheDocument ( )
863+ expect ( within ( modelPicker ) . getByRole ( 'button' , { name : / I n h e r i t f r o m p a r e n t / } ) ) . toBeInTheDocument ( )
864+ fireEvent . click ( within ( modelPicker ) . getByRole ( 'button' , { name : / B u i l t - i n d e f a u l t \( h a i k u \) / } ) )
825865
826866 chooseAgentSelect ( 'Reasoning effort' , 'high' )
827867 fireEvent . click ( screen . getByRole ( 'button' , { name : 'Save' } ) )
@@ -844,7 +884,7 @@ describe('AgentManager', () => {
844884 fireEvent . click (
845885 screen . getByRole ( 'button' , { name : 'Adjust the model and effort for Explore' } ) ,
846886 )
847- chooseAgentSelect ( 'Model' , ' Inherit from parent' )
887+ chooseAgentModel ( / I n h e r i t f r o m p a r e n t / )
848888 fireEvent . click ( screen . getByRole ( 'button' , { name : 'Save' } ) )
849889
850890 await waitFor ( ( ) => expect ( apiSetOverrideMock ) . toHaveBeenCalledWith ( 'Explore' , {
@@ -893,7 +933,7 @@ describe('AgentManager', () => {
893933 )
894934
895935 expect ( screen . getByRole ( 'button' , { name : 'Model' } ) ) . toBeDisabled ( )
896- expect ( screen . getByRole ( 'button ' , { name : 'Reasoning effort' } ) ) . toBeDisabled ( )
936+ expect ( screen . getByRole ( 'combobox ' , { name : 'Reasoning effort' } ) ) . toBeDisabled ( )
897937 expect ( screen . getByRole ( 'button' , { name : 'Save' } ) ) . toBeDisabled ( )
898938 // Resetting would write to the user file, which cannot win over a policy.
899939 expect ( screen . queryByRole ( 'button' , { name : 'Reset to built-in default' } ) ) . toBeNull ( )
@@ -925,7 +965,7 @@ describe('AgentManager', () => {
925965 fireEvent . click (
926966 screen . getByRole ( 'button' , { name : 'Adjust the model and effort for Explore' } ) ,
927967 )
928- chooseAgentSelect ( 'Model' , ' sonnet' )
968+ chooseAgentModel ( / ^ s o n n e t / i )
929969 fireEvent . click ( screen . getByRole ( 'button' , { name : 'Save' } ) )
930970
931971 expect ( await screen . findByRole ( 'alert' ) ) . toHaveTextContent ( 'Failed to save the override' )
0 commit comments