-
Flashcards
+
Cards
@@ -209,7 +245,7 @@ export default function FlashcardsPage() {
{tab === 'decks' && (
<>
{decks.length === 0 && !loading && (
-
No flashcard decks yet. Create one from a document.
+
No card decks yet. Create one from a document.
)}
{decks.map(deck => (
@@ -332,7 +368,8 @@ export default function FlashcardsPage() {
{total} cards
{cards.map(card => (
-
+
setDeletingCard(null)}>No
) : (
-
+ <>
+
+
+ >
)}
+ {linkCardId === card.id && (
+
+
Linked questions
+ {cardLinks[card.id] === null &&
Loading…
}
+ {cardLinks[card.id]?.questions?.length > 0 ? (
+
+ {cardLinks[card.id].questions.map(q => (
+ -
+ {q.question_text}
+
+
+ ))}
+
+ ) : cardLinks[card.id] ?
No questions linked yet.
: null}
+
+ setLinkQuestionId(e.target.value)}
+ placeholder="Question ID" aria-label="Question ID to link" />
+
+
+ {cardLinks[card.id]?.articles?.length > 0 && (
+
+ Linked articles: {cardLinks[card.id].articles.map(a => a.title).join(', ')}
+
+ )}
+ {linkError &&
{linkError}
}
+
+ )}
+
))}
{!loading && cards.length === 0 && (
diff --git a/frontend/src/pages/FlashcardsPage.test.jsx b/frontend/src/pages/FlashcardsPage.test.jsx
new file mode 100644
index 0000000..4eb2624
--- /dev/null
+++ b/frontend/src/pages/FlashcardsPage.test.jsx
@@ -0,0 +1,61 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { render, screen, waitFor } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { MemoryRouter } from 'react-router-dom'
+import FlashcardsPage from './FlashcardsPage'
+import api from '../api/client'
+
+vi.mock('../api/client', () => ({ default: { get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn(), patch: vi.fn() } }))
+vi.mock('../context/AuthContext', () => ({ useAuth: () => ({ user: { id: 1, name: 'Learner', role: 'user' } }) }))
+
+const card = { id: 7, deck_id: 2, deck_title: 'Neonatology', front: 'Front text', back: 'Back text' }
+
+beforeEach(() => {
+ vi.resetAllMocks()
+ api.get.mockImplementation(url => {
+ if (url === '/flashcards/') return Promise.resolve({ data: [] })
+ if (url === '/flashcards/trash') return Promise.resolve({ data: [] })
+ if (url === '/flashcards/shared') return Promise.resolve({ data: { total: 0, decks: [] } })
+ if (url === '/flashcards/cards/browse') return Promise.resolve({ data: { total: 1, cards: [card] } })
+ if (url === '/flashcards/cards/7/links') return Promise.resolve({ data: { questions: [{ id: 12, question_text: 'Linked question text' }], articles: [] } })
+ return Promise.resolve({ data: [] })
+ })
+ api.put.mockResolvedValue({ data: { linked: true } })
+ api.delete.mockResolvedValue({ data: null })
+})
+
+function renderPage() {
+ return render(
)
+}
+
+describe('cards page', () => {
+ it('uses the Cards label and honest empty deck state', async () => {
+ renderPage()
+ expect(await screen.findByRole('heading', { name: 'Cards' })).toBeInTheDocument()
+ expect(await screen.findByText('No card decks yet. Create one from a document.')).toBeInTheDocument()
+ })
+
+ it('links a card to a question and unlinks it', async () => {
+ renderPage()
+ await userEvent.click(await screen.findByRole('button', { name: 'Browse Cards' }))
+ expect(await screen.findByText('Front text')).toBeInTheDocument()
+ await userEvent.click(screen.getByRole('button', { name: 'Links' }))
+ expect(await screen.findByText('Linked question text')).toBeInTheDocument()
+ await userEvent.type(screen.getByLabelText('Question ID to link'), '42')
+ await userEvent.click(screen.getByRole('button', { name: 'Link question' }))
+ await waitFor(() => expect(api.put).toHaveBeenCalledWith('/flashcards/cards/7/links/question', { question_id: 42 }))
+ await userEvent.click(screen.getByRole('button', { name: 'Unlink' }))
+ await waitFor(() => expect(api.delete).toHaveBeenCalledWith('/flashcards/cards/7/links/question/12'))
+ })
+
+ it('shows an empty linked state honestly', async () => {
+ const original = api.get.getMockImplementation()
+ api.get.mockImplementation(url => url === '/flashcards/cards/7/links'
+ ? Promise.resolve({ data: { questions: [], articles: [] } })
+ : original(url))
+ renderPage()
+ await userEvent.click(await screen.findByRole('button', { name: 'Browse Cards' }))
+ await userEvent.click(await screen.findByRole('button', { name: 'Links' }))
+ expect(await screen.findByText('No questions linked yet.')).toBeInTheDocument()
+ })
+})
diff --git a/frontend/src/pages/SettingsPage.jsx b/frontend/src/pages/SettingsPage.jsx
index 21036fa..9c5511a 100644
--- a/frontend/src/pages/SettingsPage.jsx
+++ b/frontend/src/pages/SettingsPage.jsx
@@ -288,7 +288,7 @@ function DocumentsSection() {
- Your uploaded PDFs for quiz and flashcard generation.
+ Your uploaded PDFs for quiz and card generation.
Upload PDF