11import { describe , it , expect , vi , beforeEach } from 'vitest'
2- import { render , screen , fireEvent , waitFor } from '@testing-library/react'
2+ import { render , screen , fireEvent , waitFor , within } from '@testing-library/react'
33import { MemoryRouter } from 'react-router-dom'
44import { ProjectSidebar } from './ProjectSidebar'
55import type { Project } from '@/types/project'
@@ -219,6 +219,16 @@ describe('ProjectSidebar', () => {
219219 expect ( screen . getByText ( 'Project Two' ) ) . toBeInTheDocument ( )
220220 } )
221221
222+ it ( 'should render project avatars with first letter' , ( ) => {
223+ renderWithRouter ( )
224+
225+ const activeProjectsContainer = screen . getByTestId ( 'active-projects-container' )
226+ const avatars = within ( activeProjectsContainer ) . getAllByTestId ( 'project-avatar-letter' )
227+ const letters = avatars . map ( ( avatar ) => avatar . textContent )
228+
229+ expect ( letters ) . toStrictEqual ( [ 'P' , 'P' ] )
230+ } )
231+
222232 it ( 'should call onSelectProject when project is clicked' , ( ) => {
223233 const onSelectProject = vi . fn ( )
224234 renderWithRouter ( { onSelectProject } )
@@ -228,11 +238,24 @@ describe('ProjectSidebar', () => {
228238 expect ( onSelectProject ) . toHaveBeenCalledWith ( '2' )
229239 } )
230240
231- it ( 'should call onNewProject when New Project is clicked' , ( ) => {
241+ it ( 'should call onNewProject when header + button is clicked' , ( ) => {
232242 const onNewProject = vi . fn ( )
233243 renderWithRouter ( { onNewProject } )
234244
235- fireEvent . click ( screen . getByText ( 'New Project' ) )
245+ // Use data-testid for robust button selection
246+ const headerButton = screen . getByTestId ( 'header-new-project' )
247+ fireEvent . click ( headerButton )
248+
249+ expect ( onNewProject ) . toHaveBeenCalled ( )
250+ } )
251+
252+ it ( 'should call onNewProject when bottom + button is clicked' , ( ) => {
253+ const onNewProject = vi . fn ( )
254+ renderWithRouter ( { onNewProject } )
255+
256+ // Use data-testid for robust button selection
257+ const bottomButton = screen . getByTestId ( 'bottom-new-project' )
258+ fireEvent . click ( bottomButton )
236259
237260 expect ( onNewProject ) . toHaveBeenCalled ( )
238261 } )
@@ -242,6 +265,57 @@ describe('ProjectSidebar', () => {
242265
243266 expect ( screen . getByText ( 'No projects yet' ) ) . toBeInTheDocument ( )
244267 } )
268+
269+ it ( 'should not render removed navigation items' , ( ) => {
270+ renderWithRouter ( )
271+
272+ // These items were removed from the sidebar
273+ expect ( screen . queryByText ( 'Workspace' ) ) . not . toBeInTheDocument ( )
274+ expect ( screen . queryByText ( 'Snapshots' ) ) . not . toBeInTheDocument ( )
275+ expect ( screen . queryByText ( 'Settings' ) ) . not . toBeInTheDocument ( )
276+ expect ( screen . queryByText ( 'Preferences' ) ) . not . toBeInTheDocument ( )
277+ } )
278+
279+ it ( 'should not render removed action items' , ( ) => {
280+ renderWithRouter ( )
281+
282+ // These actions were removed from the sidebar
283+ expect ( screen . queryByText ( 'Scan Directories' ) ) . not . toBeInTheDocument ( )
284+ expect ( screen . queryByText ( 'Import Config' ) ) . not . toBeInTheDocument ( )
285+ } )
286+
287+ it ( 'should handle project with empty name gracefully' , ( ) => {
288+ const projectsWithEmptyName : Project [ ] = [
289+ { id : '1' , name : '' , color : 'blue' , gitBranch : 'main' }
290+ ]
291+ renderWithRouter ( { projects : projectsWithEmptyName } )
292+
293+ // Should show fallback character '?' for empty name
294+ const avatar = screen . getByTestId ( 'project-avatar-letter' )
295+ expect ( avatar ) . toHaveTextContent ( '?' )
296+ } )
297+
298+ it ( 'should extract first alphabetic character for emoji project names' , ( ) => {
299+ const projectsWithEmoji : Project [ ] = [
300+ { id : '1' , name : '🚀Rocket' , color : 'blue' , gitBranch : 'main' }
301+ ]
302+ renderWithRouter ( { projects : projectsWithEmoji } )
303+
304+ // Should extract 'R' from Rocket, not the emoji
305+ const avatar = screen . getByTestId ( 'project-avatar-letter' )
306+ expect ( avatar ) . toHaveTextContent ( 'R' )
307+ } )
308+
309+ it ( 'should preserve emoji-only project names in avatar fallback' , ( ) => {
310+ const projectsWithEmojiOnlyName : Project [ ] = [
311+ { id : '1' , name : '🚀' , color : 'blue' , gitBranch : 'main' }
312+ ]
313+ renderWithRouter ( { projects : projectsWithEmojiOnlyName } )
314+
315+ // Should keep full emoji grapheme as fallback, not a surrogate fragment
316+ const avatar = screen . getByTestId ( 'project-avatar-letter' )
317+ expect ( avatar ) . toHaveTextContent ( '🚀' )
318+ } )
245319} )
246320
247321describe ( 'ProjectSidebar Archived Projects' , ( ) => {
0 commit comments