@@ -24,6 +24,17 @@ const mockProviders: Record<string, ConfigProvider> = {
2424 } ,
2525}
2626
27+ const findTrashButton = ( ) : HTMLButtonElement | null => {
28+ const buttons = Array . from ( document . querySelectorAll < HTMLButtonElement > ( 'button' ) )
29+ for ( const btn of buttons ) {
30+ const svg = btn . querySelector ( 'svg' )
31+ if ( svg && svg . getAttribute ( 'class' ) ?. includes ( 'trash' ) ) {
32+ return btn
33+ }
34+ }
35+ return null
36+ }
37+
2738describe ( 'OpenCodeModelsEditor' , ( ) => {
2839 describe ( 'rendering' , ( ) => {
2940 it ( 'should render empty state when no providers configured' , ( ) => {
@@ -68,15 +79,7 @@ describe('OpenCodeModelsEditor', () => {
6879 const onChange = vi . fn ( )
6980 render ( < OpenCodeModelsEditor providers = { mockProviders } onChange = { onChange } /> )
7081
71- const deleteButtons = document . querySelectorAll ( 'button' )
72- let deleteButton : HTMLButtonElement | null = null
73- deleteButtons . forEach ( btn => {
74- const svg = btn . querySelector ( 'svg' )
75- if ( svg && svg . className . baseVal . includes ( 'Trash2' ) ) {
76- deleteButton = btn as HTMLButtonElement
77- }
78- } )
79-
82+ const deleteButton = findTrashButton ( )
8083 expect ( deleteButton ) . not . toBeNull ( )
8184 if ( deleteButton ) {
8285 fireEvent . click ( deleteButton )
@@ -107,15 +110,7 @@ describe('OpenCodeModelsEditor', () => {
107110
108111 render ( < OpenCodeModelsEditor providers = { providersWithExtras } onChange = { onChange } /> )
109112
110- const deleteButtons = document . querySelectorAll ( 'button' )
111- let deleteButton : HTMLButtonElement | null = null
112- deleteButtons . forEach ( btn => {
113- const svg = btn . querySelector ( 'svg' )
114- if ( svg && svg . className . baseVal . includes ( 'Trash2' ) ) {
115- deleteButton = btn as HTMLButtonElement
116- }
117- } )
118-
113+ const deleteButton = findTrashButton ( )
119114 if ( deleteButton ) {
120115 fireEvent . click ( deleteButton )
121116 }
@@ -155,15 +150,13 @@ describe('OpenCodeModelDialog', () => {
155150 } )
156151 } )
157152
158- it ( 'should require display name when submitting empty form ' , async ( ) => {
153+ it ( 'should disable create button when required fields are empty ' , async ( ) => {
159154 const { OpenCodeModelDialog } = await import ( './OpenCodeModelDialog' )
160155 render ( < OpenCodeModelDialog { ...defaultProps } /> )
161156
162- const createButton = screen . getByRole ( 'button' , { name : / c r e a t e / i } )
163- fireEvent . click ( createButton )
164-
165157 await waitFor ( ( ) => {
166- expect ( screen . getByText ( / d i s p l a y n a m e i s r e q u i r e d / i) ) . toBeInTheDocument ( )
158+ const createButton = screen . getByRole ( 'button' , { name : / c r e a t e / i } )
159+ expect ( createButton ) . toBeDisabled ( )
167160 } )
168161 } )
169162
@@ -172,15 +165,12 @@ describe('OpenCodeModelDialog', () => {
172165 render ( < OpenCodeModelDialog { ...defaultProps } /> )
173166
174167 const modelIdInput = document . querySelector ( 'input[name="modelId"]' ) as HTMLInputElement
175- if ( modelIdInput ) {
176- fireEvent . change ( modelIdInput , { target : { value : 'invalid model id!' } } )
177- const createButton = screen . getByRole ( 'button' , { name : / c r e a t e / i } )
178- fireEvent . click ( createButton )
168+ expect ( modelIdInput ) . not . toBeNull ( )
169+ fireEvent . change ( modelIdInput , { target : { value : 'invalid model id!' } } )
179170
180- await waitFor ( ( ) => {
181- expect ( screen . getByText ( / a l p h a n u m e r i c / i) ) . toBeInTheDocument ( )
182- } )
183- }
171+ await waitFor ( ( ) => {
172+ expect ( screen . getByText ( / m u s t u s e o n l y l e t t e r s , n u m b e r s / i) ) . toBeInTheDocument ( )
173+ } )
184174 } )
185175 } )
186176
@@ -191,15 +181,18 @@ describe('OpenCodeModelDialog', () => {
191181 render ( < OpenCodeModelDialog { ...defaultProps } onSubmit = { onSubmit } /> )
192182
193183 const modelIdInput = document . querySelector ( 'input[name="modelId"]' ) as HTMLInputElement
194- const nameInput = document . querySelector ( 'input[name="name "]' ) as HTMLInputElement
184+ const displayNameInput = document . querySelector ( 'input[name="displayName "]' ) as HTMLInputElement
195185
196- if ( modelIdInput && nameInput ) {
197- fireEvent . change ( modelIdInput , { target : { value : 'gpt-5' } } )
198- fireEvent . change ( nameInput , { target : { value : 'GPT -5' } } )
199- }
186+ expect ( modelIdInput ) . not . toBeNull ( )
187+ expect ( displayNameInput ) . not . toBeNull ( )
188+ fireEvent . change ( modelIdInput , { target : { value : 'gpt -5' } } )
189+ fireEvent . change ( displayNameInput , { target : { value : 'GPT-5' } } )
200190
201- const createButton = screen . getByRole ( 'button' , { name : / c r e a t e / i } )
202- fireEvent . click ( createButton )
191+ await waitFor ( ( ) => {
192+ expect ( screen . getByRole ( 'button' , { name : / c r e a t e / i } ) ) . not . toBeDisabled ( )
193+ } )
194+
195+ fireEvent . click ( screen . getByRole ( 'button' , { name : / c r e a t e / i } ) )
203196
204197 await waitFor ( ( ) => {
205198 expect ( onSubmit ) . toHaveBeenCalledWith (
@@ -218,19 +211,25 @@ describe('OpenCodeModelDialog', () => {
218211 render ( < OpenCodeModelDialog { ...defaultProps } onSubmit = { onSubmit } /> )
219212
220213 const modelIdInput = document . querySelector ( 'input[name="modelId"]' ) as HTMLInputElement
221- const nameInput = document . querySelector ( 'input[name="name "]' ) as HTMLInputElement
214+ const displayNameInput = document . querySelector ( 'input[name="displayName "]' ) as HTMLInputElement
222215 const contextLimitInput = document . querySelector ( 'input[name="contextLimit"]' ) as HTMLInputElement
223216 const outputLimitInput = document . querySelector ( 'input[name="outputLimit"]' ) as HTMLInputElement
224217
225- if ( modelIdInput && nameInput && contextLimitInput && outputLimitInput ) {
226- fireEvent . change ( modelIdInput , { target : { value : 'gpt-5' } } )
227- fireEvent . change ( nameInput , { target : { value : 'GPT-5' } } )
228- fireEvent . change ( contextLimitInput , { target : { value : '256000' } } )
229- fireEvent . change ( outputLimitInput , { target : { value : '8192' } } )
230- }
218+ expect ( modelIdInput ) . not . toBeNull ( )
219+ expect ( displayNameInput ) . not . toBeNull ( )
220+ expect ( contextLimitInput ) . not . toBeNull ( )
221+ expect ( outputLimitInput ) . not . toBeNull ( )
231222
232- const createButton = screen . getByRole ( 'button' , { name : / c r e a t e / i } )
233- fireEvent . click ( createButton )
223+ fireEvent . change ( modelIdInput , { target : { value : 'gpt-5' } } )
224+ fireEvent . change ( displayNameInput , { target : { value : 'GPT-5' } } )
225+ fireEvent . change ( contextLimitInput , { target : { value : '256000' } } )
226+ fireEvent . change ( outputLimitInput , { target : { value : '8192' } } )
227+
228+ await waitFor ( ( ) => {
229+ expect ( screen . getByRole ( 'button' , { name : / c r e a t e / i } ) ) . not . toBeDisabled ( )
230+ } )
231+
232+ fireEvent . click ( screen . getByRole ( 'button' , { name : / c r e a t e / i } ) )
234233
235234 await waitFor ( ( ) => {
236235 expect ( onSubmit ) . toHaveBeenCalledWith (
@@ -300,6 +299,7 @@ describe('OpenCodeModelDialog', () => {
300299 providerId : 'openai' ,
301300 modelId : 'gpt-4o' ,
302301 model : {
302+ id : 'gpt-4o' ,
303303 name : 'GPT-4o' ,
304304 limit : { context : 128000 , output : 4096 } ,
305305 reasoning : true ,
@@ -321,6 +321,10 @@ describe('OpenCodeModelDialog', () => {
321321 target : { value : 'my-friendly-model' } ,
322322 } )
323323
324+ await waitFor ( ( ) => {
325+ expect ( screen . getByRole ( 'button' , { name : / u p d a t e / i } ) ) . not . toBeDisabled ( )
326+ } )
327+
324328 fireEvent . click ( screen . getByRole ( 'button' , { name : / u p d a t e / i } ) )
325329
326330 await waitFor ( ( ) => {
0 commit comments