diff --git a/web/src/components/NoteCard.test.tsx b/web/src/components/NoteCard.test.tsx index ac2744d..04ad170 100644 --- a/web/src/components/NoteCard.test.tsx +++ b/web/src/components/NoteCard.test.tsx @@ -62,6 +62,19 @@ describe('NoteCard', () => { expect(mockNavigate).toHaveBeenCalledWith('/notes/1'); }); + it('navigates on Space key', () => { + render( + + + + ); + + const card = screen.getByRole('link'); + fireEvent.keyDown(card, { key: ' ', code: 'Space' }); + + expect(mockNavigate).toHaveBeenCalledWith('/notes/1'); + }); + it('navigates on Enter key', () => { render( @@ -96,4 +109,92 @@ describe('NoteCard', () => { fireEvent.click(askAIButton); expect(mockHandlers.onAskAI).toHaveBeenCalledWith(mockNote); }); + + it('has correct aria-labels for action buttons', () => { + render( + + + + ); + + const deleteButton = screen.getByTitle('Delete'); + const shareButton = screen.getByTitle('Share'); + const askAIButton = screen.getByTitle('Ask AI'); + + expect(deleteButton).toHaveAttribute('aria-label', `Delete ${mockNote.title}`); + expect(shareButton).toHaveAttribute('aria-label', `Share ${mockNote.title}`); + expect(askAIButton).toHaveAttribute('aria-label', `Ask AI about ${mockNote.title}`); + }); + + it('uses fallback text for aria-labels when title is missing', () => { + const noteWithoutTitle = { ...mockNote, title: '' } as unknown as Note; + render( + + + + ); + + const deleteButton = screen.getByTitle('Delete'); + const shareButton = screen.getByTitle('Share'); + const askAIButton = screen.getByTitle('Ask AI'); + + expect(deleteButton).toHaveAttribute('aria-label', 'Delete Untitled Note'); + expect(shareButton).toHaveAttribute('aria-label', 'Share Untitled Note'); + expect(askAIButton).toHaveAttribute('aria-label', 'Ask AI about Untitled Note'); + }); + + it('renders tags when available', () => { + render( + + + + ); + + expect(screen.getByText('Faith')).toBeInTheDocument(); + expect(screen.getByText('Prayer')).toBeInTheDocument(); + }); + + it('does not render tags if empty or undefined', () => { + const noteWithoutTags = { ...mockNote, tags: [] }; + const { unmount } = render( + + + + ); + + expect(screen.queryByText('Faith')).not.toBeInTheDocument(); + unmount(); + + const noteUndefinedTags = { ...mockNote, tags: undefined }; + render( + + + + ); + expect(screen.queryByText('Faith')).not.toBeInTheDocument(); + }); + + it('renders with fallback title when title is empty', () => { + const noteWithoutTitle = { ...mockNote, title: '' }; + render( + + + + ); + + expect(screen.getByText('Untitled Note')).toBeInTheDocument(); + }); + + it('ignores non-Enter/Space keys', () => { + render( + + + + ); + + const card = screen.getByRole('link'); + fireEvent.keyDown(card, { key: 'a', code: 'KeyA' }); + + expect(mockNavigate).not.toHaveBeenCalled(); + }); }); diff --git a/web/src/components/NoteCard.tsx b/web/src/components/NoteCard.tsx index 33ae409..fbd81ef 100644 --- a/web/src/components/NoteCard.tsx +++ b/web/src/components/NoteCard.tsx @@ -60,6 +60,7 @@ export function NoteCard({ note, onDelete, onShare, onAskAI }: NoteCardProps) { onAskAI(note); }} title="Ask AI" + aria-label={`Ask AI about ${note.title ? note.title : "Untitled Note"}`} > @@ -73,6 +74,7 @@ export function NoteCard({ note, onDelete, onShare, onAskAI }: NoteCardProps) { onShare(note); }} title="Share" + aria-label={`Share ${note.title ? note.title : "Untitled Note"}`} > @@ -86,6 +88,7 @@ export function NoteCard({ note, onDelete, onShare, onAskAI }: NoteCardProps) { onDelete(note); }} title="Delete" + aria-label={`Delete ${note.title ? note.title : "Untitled Note"}`} >