import * as React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import { ReviewScorecard, ReviewNotes, DEFAULT_REVIEW_DIMENSIONS } from '../index'; import type { ReviewScore, ReviewNote } from '../index'; describe('ReviewScorecard', () => { it('exposes the default guardrail dimensions', () => { expect(DEFAULT_REVIEW_DIMENSIONS.map((d) => d.key)).toEqual([ 'political-sensitivity', 'source-reliability', 'recency', 'tone-and-voice', 'accuracy', ]); }); it('emits the next value when a verdict is chosen', () => { const onChange = jest.fn(); render(); fireEvent.click(screen.getByRole('button', { name: 'Revise' })); expect(onChange).toHaveBeenCalledWith({ verdict: 'revise' }); }); it('emits the next value (immutably) when a dimension score is clicked', () => { const onChange = jest.fn(); const value: ReviewScore = { verdict: 'approve' }; render(); fireEvent.click(screen.getByLabelText('Accuracy score 4')); expect(onChange).toHaveBeenCalledWith({ verdict: 'approve', dimensions: { accuracy: { score: 4 } } }); // original value not mutated expect(value).toEqual({ verdict: 'approve' }); }); it('merges a dimension note without dropping an existing score', () => { const onChange = jest.fn(); render( , ); fireEvent.change(screen.getByLabelText('Accuracy note'), { target: { value: 'Well cited' } }); expect(onChange).toHaveBeenCalledWith({ dimensions: { accuracy: { score: 5, note: 'Well cited' } } }); }); it('emits the overall note', () => { const onChange = jest.fn(); render(); fireEvent.change(screen.getByLabelText('Overall note'), { target: { value: 'Looks solid.' } }); expect(onChange).toHaveBeenCalledWith({ overallNote: 'Looks solid.' }); }); it('renders read-only values with no inputs', () => { render( , ); expect(screen.getByText('Approve')).toBeInTheDocument(); expect(screen.getByText('Solid sourcing')).toBeInTheDocument(); expect(screen.getByText('Ship it')).toBeInTheDocument(); // no interactive inputs in read-only mode expect(screen.queryByRole('textbox')).not.toBeInTheDocument(); expect(screen.queryByLabelText('Accuracy score 5')).not.toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'Revise' })).not.toBeInTheDocument(); }); }); describe('ReviewNotes', () => { const notes: ReviewNote[] = [ { id: 'n1', body: 'Tighten the intro.', author: 'Alex', at: new Date().toISOString() }, { id: 'n2', body: 'Fixed the citation.', author: 'Sam', resolved: true }, ]; it('shows the empty message when there are no notes', () => { render(); expect(screen.getByText('Nothing flagged.')).toBeInTheDocument(); }); it('renders notes with author and resolved styling', () => { render(); expect(screen.getByText('Tighten the intro.')).toBeInTheDocument(); expect(screen.getByText('Alex')).toBeInTheDocument(); expect(screen.getByText('Resolved')).toBeInTheDocument(); expect(screen.getByText('Today')).toBeInTheDocument(); }); it('adds a note via the composer', () => { const onAdd = jest.fn(); render(); fireEvent.change(screen.getByLabelText('Add a note'), { target: { value: ' Needs a stronger hook ' } }); fireEvent.click(screen.getByRole('button', { name: 'Add note' })); expect(onAdd).toHaveBeenCalledWith('Needs a stronger hook'); }); it('disables the add button until there is text', () => { render(); expect(screen.getByRole('button', { name: 'Add note' })).toBeDisabled(); }); it('resolves an unresolved note', () => { const onResolve = jest.fn(); render(); // only the unresolved note (n1) exposes a Resolve action fireEvent.click(screen.getByRole('button', { name: 'Resolve' })); expect(onResolve).toHaveBeenCalledWith('n1'); }); it('hides the composer and resolve actions in read-only mode', () => { render(); expect(screen.queryByRole('button', { name: 'Add note' })).not.toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'Resolve' })).not.toBeInTheDocument(); }); });