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();
});
});