fix: one notepad on the quiz page, not two

Per-question notes were already built and saved on blur. The global
notes tab was still floating over the same screen beside them, so it was
never clear which notepad a note was going into. The global note stays
where it belongs, on the dashboard.

The quiz test mock had no `put`, which is why nothing had ever covered
the per-question note path; it does now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
This commit is contained in:
Daniel 2026-09-11 13:15:03 +02:00
parent db2bbf0638
commit a5f5e3a536
3 changed files with 27 additions and 6 deletions

View file

@ -217,8 +217,10 @@ Captured so nothing is lost while the article writing runs.
## Quiz runner ## Quiz runner
- [ ] **Per-question notes in study mode**, replacing the global notes tab that is - [x] **Per-question notes** — done. The notes themselves were already built
currently on the quiz page. (`question_notes`, saved on blur); what was missing was removing the
global notes tab that floated over the same screen, so it was never clear
which notepad you were writing in. The global note stays on the dashboard.
- [ ] **Per-question feedback** to the educator. - [ ] **Per-question feedback** to the educator.
- [ ] **Tutorial mode** — first-run coach marks ("Step 2 of 6", Skip / Next). - [ ] **Tutorial mode** — first-run coach marks ("Step 2 of 6", Skip / Next).

View file

@ -9,7 +9,6 @@ import { useAuth } from '../context/AuthContext'
import api from '../api/client' import api from '../api/client'
import useMediaQuery from '../hooks/useMediaQuery' import useMediaQuery from '../hooks/useMediaQuery'
import FigureStrip from '../components/FigureStrip' import FigureStrip from '../components/FigureStrip'
import MyNote from '../components/MyNote'
import QuizTools, { QuizDialog } from '../components/QuizTools' import QuizTools, { QuizDialog } from '../components/QuizTools'
import './QuizPlayer.css' import './QuizPlayer.css'
@ -1112,7 +1111,11 @@ const timerStarted = timeLeft !== null
return ( return (
<div className="quiz-bottom quiz-player"> <div className="quiz-bottom quiz-player">
<MyNote variant="tab" /> {/* The floating global-notes tab is gone. A note taken while sitting a
question is about that question, and there is a per-question note in
the toolbar below; a second, unrelated notepad floating over the same
screen only made it ambiguous which one you were writing in. The
global note still lives on the dashboard. */}
{tool && <QuizTools tool={tool} onClose={() => setTool(null)} />} {tool && <QuizTools tool={tool} onClose={() => setTool(null)} />}
{showReview && <QuizDialog title="Review & Complete" onClose={() => setShowReview(false)}> {showReview && <QuizDialog title="Review & Complete" onClose={() => setShowReview(false)}>
<p>{answeredCount} of {totalCount} questions answered. Unanswered questions count as incorrect.</p> <p>{answeredCount} of {totalCount} questions answered. Unanswered questions count as incorrect.</p>

View file

@ -5,9 +5,8 @@ import { MemoryRouter, Route, Routes } from 'react-router-dom'
import QuizPage from './QuizPage' import QuizPage from './QuizPage'
import api from '../api/client' import api from '../api/client'
vi.mock('../api/client', () => ({ default: { get: vi.fn(), post: vi.fn(), delete: vi.fn() } })) vi.mock('../api/client', () => ({ default: { get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn() } }))
vi.mock('../context/AuthContext', () => ({ useAuth: () => ({ user: { id: 1, name: 'Learner', role: 'user' } }) })) vi.mock('../context/AuthContext', () => ({ useAuth: () => ({ user: { id: 1, name: 'Learner', role: 'user' } }) }))
vi.mock('../components/MyNote', () => ({ default: () => null }))
vi.mock('../components/TeachChat', () => ({ default: ({ attemptId }) => <div data-testid="tutor-context">Study tutor {attemptId}</div> })) vi.mock('../components/TeachChat', () => ({ default: ({ attemptId }) => <div data-testid="tutor-context">Study tutor {attemptId}</div> }))
const questions = [ const questions = [
@ -249,6 +248,23 @@ describe('quiz player', () => {
expect(screen.queryByText('Full first clinical question.')).not.toBeInTheDocument() expect(screen.queryByText('Full first clinical question.')).not.toBeInTheDocument()
}) })
it('keeps notes with the question, not in a second notepad floating over it', async () => {
await begin()
const bar = await screen.findByRole('toolbar', { name: 'Question actions' })
// The global note tab used to float over the player alongside this one, so
// it was never clear which notepad you were typing into.
expect(document.querySelector('.mynote-tab')).toBeNull()
await userEvent.click(within(bar).getByRole('button', { name: /Add notes/ }))
const box = screen.getByRole('textbox', { name: /note/i })
await userEvent.type(box, 'Ask about vaccination status')
expect(screen.getByText('Unsaved')).toBeInTheDocument()
// Saved on blur, so a note is not written on every keystroke.
await userEvent.click(screen.getByRole('button', { name: 'Save note' }))
await waitFor(() => expect(api.put).toHaveBeenCalledWith(
'/questions/detail/1/note', { content: 'Ask about vaccination status' }))
})
it('shows per-option explanations in study feedback behind a toggle', async () => { it('shows per-option explanations in study feedback behind a toggle', async () => {
const originalGet = api.get.getMockImplementation() const originalGet = api.get.getMockImplementation()
api.get.mockImplementation(async (url, ...args) => { api.get.mockImplementation(async (url, ...args) => {