Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions web/src/components/NoteCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ describe('NoteCard', () => {
expect(mockNavigate).toHaveBeenCalledWith('/notes/1');
});

it('navigates on Space key', () => {
render(
<MemoryRouter>
<NoteCard note={mockNote} {...mockHandlers} />
</MemoryRouter>
);

const card = screen.getByRole('link');
fireEvent.keyDown(card, { key: ' ', code: 'Space' });

expect(mockNavigate).toHaveBeenCalledWith('/notes/1');
});

it('navigates on Enter key', () => {
render(
<MemoryRouter>
Expand Down Expand Up @@ -96,4 +109,92 @@ describe('NoteCard', () => {
fireEvent.click(askAIButton);
expect(mockHandlers.onAskAI).toHaveBeenCalledWith(mockNote);
});

it('has correct aria-labels for action buttons', () => {
render(
<MemoryRouter>
<NoteCard note={mockNote} {...mockHandlers} />
</MemoryRouter>
);

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(
<MemoryRouter>
<NoteCard note={noteWithoutTitle} {...mockHandlers} />
</MemoryRouter>
);

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(
<MemoryRouter>
<NoteCard note={mockNote} {...mockHandlers} />
</MemoryRouter>
);

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(
<MemoryRouter>
<NoteCard note={noteWithoutTags} {...mockHandlers} />
</MemoryRouter>
);

expect(screen.queryByText('Faith')).not.toBeInTheDocument();
unmount();

const noteUndefinedTags = { ...mockNote, tags: undefined };
render(
<MemoryRouter>
<NoteCard note={noteUndefinedTags} {...mockHandlers} />
</MemoryRouter>
);
expect(screen.queryByText('Faith')).not.toBeInTheDocument();
});

it('renders with fallback title when title is empty', () => {
const noteWithoutTitle = { ...mockNote, title: '' };
render(
<MemoryRouter>
<NoteCard note={noteWithoutTitle} {...mockHandlers} />
</MemoryRouter>
);

expect(screen.getByText('Untitled Note')).toBeInTheDocument();
});

it('ignores non-Enter/Space keys', () => {
render(
<MemoryRouter>
<NoteCard note={mockNote} {...mockHandlers} />
</MemoryRouter>
);

const card = screen.getByRole('link');
fireEvent.keyDown(card, { key: 'a', code: 'KeyA' });

expect(mockNavigate).not.toHaveBeenCalled();
});
});
3 changes: 3 additions & 0 deletions web/src/components/NoteCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"}`}
>
<Sparkles className="w-4 h-4" />
</Button>
Expand All @@ -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"}`}
>
<Share2 className="w-4 h-4" />
</Button>
Expand All @@ -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"}`}
>
<Trash2 className="w-4 h-4" />
</Button>
Expand Down
Loading