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"}`}
>