@@ -224,4 +224,156 @@ describe('DiffControlBar', () => {
224224 expect ( container . querySelector ( '.diff-bar-branch-selector' ) ) . toBeInTheDocument ( )
225225 // Arrow removed - swap button is sufficient visual indicator
226226 } )
227+
228+ it ( 'delete button is disabled when no workspace is selected' , ( ) => {
229+ render ( < DiffControlBar { ...defaultProps } selectedWorkspaceId = "" /> )
230+
231+ const deleteButton = screen . getByRole ( 'button' , { name : / d e l e t e s e l e c t e d w o r k s p a c e / i } )
232+ expect ( deleteButton ) . toBeDisabled ( )
233+ } )
234+
235+ it ( 'delete button is enabled when a workspace is selected' , ( ) => {
236+ render ( < DiffControlBar { ...defaultProps } selectedWorkspaceId = "ws-1" /> )
237+
238+ const deleteButton = screen . getByRole ( 'button' , { name : / d e l e t e s e l e c t e d w o r k s p a c e / i } )
239+ expect ( deleteButton ) . not . toBeDisabled ( )
240+ } )
241+
242+ it ( 'save button is disabled when currentWorkspacePath is empty' , ( ) => {
243+ render ( < DiffControlBar { ...defaultProps } currentWorkspacePath = "" /> )
244+
245+ const saveButton = screen . getByRole ( 'button' , { name : / s a v e c u r r e n t w o r k s p a c e / i } )
246+ expect ( saveButton ) . toBeDisabled ( )
247+ } )
248+
249+ it ( 'workspace selector shows unsaved label with folder name when no workspace selected' , ( ) => {
250+ render ( < DiffControlBar { ...defaultProps } selectedWorkspaceId = "" currentWorkspacePath = "/tmp/my-project" /> )
251+
252+ const workspaceSelect = screen . getByLabelText ( / w o r k s p a c e / i)
253+ const options = Array . from ( workspaceSelect . querySelectorAll ( 'option' ) )
254+ // First option should be the unsaved label
255+ expect ( options [ 0 ] . textContent ) . toBe ( 'Unsaved: my-project' )
256+ } )
257+
258+ it ( 'workspace selector shows "Unsaved Workspace" when no path provided and no workspace selected' , ( ) => {
259+ render ( < DiffControlBar { ...defaultProps } selectedWorkspaceId = "" currentWorkspacePath = "" /> )
260+
261+ const workspaceSelect = screen . getByLabelText ( / w o r k s p a c e / i)
262+ const options = Array . from ( workspaceSelect . querySelectorAll ( 'option' ) )
263+ expect ( options [ 0 ] . textContent ) . toBe ( 'Unsaved Workspace' )
264+ } )
265+
266+ it ( 'workspace options display name and folder name' , ( ) => {
267+ render ( < DiffControlBar { ...defaultProps } /> )
268+
269+ const workspaceSelect = screen . getByLabelText ( / w o r k s p a c e / i)
270+ const options = Array . from ( workspaceSelect . querySelectorAll ( 'option' ) )
271+ // Unsaved option + 2 workspace options
272+ const wsOptions = options . filter ( o => o . value === 'ws-1' || o . value === 'ws-2' )
273+ expect ( wsOptions ) . toHaveLength ( 2 )
274+ expect ( wsOptions [ 0 ] . textContent ) . toBe ( 'Main Repo - main' )
275+ expect ( wsOptions [ 1 ] . textContent ) . toBe ( 'Docs - docs' )
276+ } )
277+
278+ it ( 'selecting empty workspace ID calls onWorkspaceSelect with empty string' , async ( ) => {
279+ const user = userEvent . setup ( )
280+ const onWorkspaceSelect = vi . fn ( )
281+
282+ // Start with a workspace selected so the unsaved option isn't shown
283+ // We'll select one of the workspaces first
284+ render (
285+ < DiffControlBar
286+ { ...defaultProps }
287+ selectedWorkspaceId = "ws-1"
288+ onWorkspaceSelect = { onWorkspaceSelect }
289+ /> ,
290+ )
291+
292+ // There's no empty option when a workspace is selected, so we verify via
293+ // selecting a different workspace and the callback arg shape
294+ const workspaceSelect = screen . getByLabelText ( / w o r k s p a c e / i)
295+ await user . selectOptions ( workspaceSelect , 'ws-2' )
296+ expect ( onWorkspaceSelect ) . toHaveBeenCalledWith ( 'ws-2' )
297+ } )
298+
299+ it ( 'workspace selector shows fallback text for unknown workspace ID' , ( ) => {
300+ render (
301+ < DiffControlBar
302+ { ...defaultProps }
303+ selectedWorkspaceId = "ws-unknown"
304+ /> ,
305+ )
306+
307+ const workspaceSelect = screen . getByLabelText ( / w o r k s p a c e / i) as HTMLSelectElement
308+ const options = Array . from ( workspaceSelect . querySelectorAll ( 'option' ) )
309+ // Should have a "Selected Workspace" fallback option for unknown ID
310+ const fallbackOption = options . find ( o => o . value === 'ws-unknown' )
311+ expect ( fallbackOption ) . toBeTruthy ( )
312+ expect ( fallbackOption ! . textContent ) . toBe ( 'Selected Workspace' )
313+ } )
314+
315+ it ( 'workspace selector has title attribute showing workspace path' , ( ) => {
316+ render ( < DiffControlBar { ...defaultProps } currentWorkspacePath = "/tmp/my-repo" /> )
317+
318+ const workspaceSelect = screen . getByLabelText ( / w o r k s p a c e / i)
319+ expect ( workspaceSelect ) . toHaveAttribute ( 'title' , '/tmp/my-repo' )
320+ } )
321+
322+ it ( 'workspace selector title shows selected workspace path when workspace is selected' , ( ) => {
323+ render (
324+ < DiffControlBar
325+ { ...defaultProps }
326+ selectedWorkspaceId = "ws-1"
327+ currentWorkspacePath = "/tmp/main"
328+ /> ,
329+ )
330+
331+ const workspaceSelect = screen . getByLabelText ( / w o r k s p a c e / i)
332+ // selectedWorkspace.path is '/tmp/main'
333+ expect ( workspaceSelect ) . toHaveAttribute ( 'title' , '/tmp/main' )
334+ } )
335+
336+ it ( '__WORKDIR__ displays as My Working Directory in base branch selector' , ( ) => {
337+ render (
338+ < DiffControlBar
339+ { ...defaultProps }
340+ branches = { [ 'main' , '__WORKDIR__' ] }
341+ baseBranch = "__WORKDIR__"
342+ /> ,
343+ )
344+
345+ const baseSelect = screen . getByLabelText ( / b a s e / i)
346+ const options = Array . from ( baseSelect . querySelectorAll ( 'option' ) )
347+ const workdirOption = options . find ( o => o . value === '__WORKDIR__' )
348+ expect ( workdirOption ) . toBeTruthy ( )
349+ expect ( workdirOption ! . textContent ) . toBe ( 'My Working Directory' )
350+ } )
351+
352+ it ( 'all controls are enabled when disabled prop is false' , ( ) => {
353+ render ( < DiffControlBar { ...defaultProps } selectedWorkspaceId = "ws-1" disabled = { false } /> )
354+
355+ expect ( screen . getByLabelText ( / w o r k s p a c e / i) ) . not . toBeDisabled ( )
356+ expect ( screen . getByLabelText ( / b a s e / i) ) . not . toBeDisabled ( )
357+ expect ( screen . getByLabelText ( / c o m p a r e / i) ) . not . toBeDisabled ( )
358+ expect ( screen . getByRole ( 'button' , { name : / s a v e c u r r e n t w o r k s p a c e / i } ) ) . not . toBeDisabled ( )
359+ expect ( screen . getByRole ( 'button' , { name : / d e l e t e s e l e c t e d w o r k s p a c e / i } ) ) . not . toBeDisabled ( )
360+ expect ( screen . getByRole ( 'button' , { name : / s w a p / i } ) ) . not . toBeDisabled ( )
361+ expect ( screen . getByRole ( 'button' , { name : / r e f r e s h / i } ) ) . not . toBeDisabled ( )
362+ } )
363+
364+ it ( 'disabled prop overrides individual button enable conditions' , ( ) => {
365+ // Even with a selected workspace and valid branches, disabled=true disables everything
366+ render (
367+ < DiffControlBar
368+ { ...defaultProps }
369+ selectedWorkspaceId = "ws-1"
370+ disabled = { true }
371+ /> ,
372+ )
373+
374+ expect ( screen . getByRole ( 'button' , { name : / s a v e c u r r e n t w o r k s p a c e / i } ) ) . toBeDisabled ( )
375+ expect ( screen . getByRole ( 'button' , { name : / d e l e t e s e l e c t e d w o r k s p a c e / i } ) ) . toBeDisabled ( )
376+ expect ( screen . getByRole ( 'button' , { name : / s w a p / i } ) ) . toBeDisabled ( )
377+ expect ( screen . getByRole ( 'button' , { name : / r e f r e s h / i } ) ) . toBeDisabled ( )
378+ } )
227379} )
0 commit comments