diff --git a/.claude/metrics/review-metrics.jsonl b/.claude/metrics/review-metrics.jsonl index afcc9df8b..bf57a41b8 100644 --- a/.claude/metrics/review-metrics.jsonl +++ b/.claude/metrics/review-metrics.jsonl @@ -64,3 +64,4 @@ {"pr":400,"story":"#391","agent":"product-owner","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"timestamp":"2026-03-03T00:01:00Z"} {"pr":400,"story":"#391","agent":"ux-designer","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"timestamp":"2026-03-03T00:01:00Z"} {"pr":401,"story":"#392","epic":"#384","timestamp":"2026-03-03T10:00:00Z","reviews":[{"agent":"product-architect","verdict":"approve","findings":{"critical":0,"high":0,"medium":3,"low":0,"informational":0}},{"agent":"security-engineer","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":2}},{"agent":"product-owner","verdict":"approve","findings":{"critical":0,"high":2,"medium":0,"low":0,"informational":0}},{"agent":"ux-designer","verdict":"approve","findings":{"critical":0,"high":0,"medium":5,"low":3,"informational":0}}],"fixLoops":1,"retries":2} +{"pr":404,"issues":[359],"epic":4,"type":"feat","mergedAt":"2026-03-03T11:15:00Z","filesChanged":2,"linesChanged":94,"fixLoopCount":0,"reviews":[{"agent":"product-architect","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"round":1},{"agent":"security-engineer","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"round":1},{"agent":"product-owner","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"round":1}],"totalFindings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0}} diff --git a/client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.test.tsx b/client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.test.tsx index d41d0f7d8..e42d132d9 100644 --- a/client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.test.tsx +++ b/client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.test.tsx @@ -130,6 +130,22 @@ jest.unstable_mockModule('../../lib/householdItemSubsidiesApi.js', () => ({ fetchHouseholdItemSubsidyPayback: mockFetchHouseholdItemSubsidyPayback, })); +// Mock LinkedDocumentsSection to avoid pulling in full documents component tree +jest.unstable_mockModule('../../components/documents/LinkedDocumentsSection.js', () => ({ + LinkedDocumentsSection: function MockLinkedDocumentsSection(props: { + entityType: string; + entityId: string; + }) { + return ( +
+

Documents

+ {props.entityType} + {props.entityId} +
+ ); + }, +})); + // Helper to capture current location function LocationDisplay() { const location = useLocation(); @@ -1063,4 +1079,78 @@ describe('HouseholdItemDetailPage', () => { expect(screen.getAllByText('Delivered').length).toBeGreaterThan(0); }); }); + + describe('Documents section', () => { + it('renders LinkedDocumentsSection with entityType="household_item"', async () => { + mockGetHouseholdItem.mockResolvedValue(makeItem()); + + renderPage(); + + await waitFor(() => { + expect(screen.getByRole('heading', { name: 'Standing Desk' })).toBeInTheDocument(); + }); + + expect(screen.getByTestId('linked-documents-section')).toBeInTheDocument(); + expect(screen.getByTestId('entity-type')).toHaveTextContent('household_item'); + }); + + it('renders with correct entityId from URL params', async () => { + mockGetHouseholdItem.mockResolvedValue(makeItem({ id: 'item-abc' })); + + renderPage('item-abc'); + + await waitFor(() => { + expect(screen.getByRole('heading', { name: 'Standing Desk' })).toBeInTheDocument(); + }); + + expect(screen.getByTestId('entity-id')).toHaveTextContent('item-abc'); + }); + + it('renders Documents section heading between Subsidies and Metadata sections', async () => { + mockGetHouseholdItem.mockResolvedValue(makeItem()); + + renderPage(); + + await waitFor(() => { + expect(screen.getByRole('heading', { name: 'Standing Desk' })).toBeInTheDocument(); + }); + + // Get all h2 headings on the page + const headings = screen.getAllByRole('heading', { level: 2 }); + const headingTexts = headings.map((h) => h.textContent); + + // Verify "Documents" heading exists + expect(headingTexts).toContain('Documents'); + + // Verify Documents comes after Subsidies + const subsidiesIndex = headingTexts.findIndex((text) => text === 'Subsidies'); + const documentsIndex = headingTexts.findIndex((text) => text === 'Documents'); + expect(subsidiesIndex).toBeGreaterThan(-1); + expect(documentsIndex).toBeGreaterThan(-1); + expect(documentsIndex).toBeGreaterThan(subsidiesIndex); + }); + + it('does not render LinkedDocumentsSection in loading state', async () => { + mockGetHouseholdItem.mockImplementation(() => new Promise(() => {})); // Never resolves + + renderPage(); + + expect(screen.getByText('Loading household item...')).toBeInTheDocument(); + expect(screen.queryByTestId('linked-documents-section')).not.toBeInTheDocument(); + }); + + it('does not render LinkedDocumentsSection when item returns 404', async () => { + mockGetHouseholdItem.mockRejectedValue( + new MockApiClientError(404, { code: 'NOT_FOUND', message: 'Item not found' }), + ); + + renderPage(); + + await waitFor(() => { + expect(screen.getByText('Item Not Found')).toBeInTheDocument(); + }); + + expect(screen.queryByTestId('linked-documents-section')).not.toBeInTheDocument(); + }); + }); }); diff --git a/client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.tsx b/client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.tsx index 1828177a2..399f7d422 100644 --- a/client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.tsx +++ b/client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.tsx @@ -46,6 +46,7 @@ import { formatDate, formatCurrency } from '../../lib/formatters.js'; import { HouseholdItemStatusBadge } from '../../components/HouseholdItemStatusBadge/HouseholdItemStatusBadge.js'; import { StatusBadge } from '../../components/StatusBadge/StatusBadge.js'; import { useToast } from '../../components/Toast/ToastContext.js'; +import { LinkedDocumentsSection } from '../../components/documents/LinkedDocumentsSection.js'; import styles from './HouseholdItemDetailPage.module.css'; const CATEGORY_LABELS: Record = { @@ -1146,6 +1147,9 @@ export function HouseholdItemDetailPage() { )} + {/* Documents section */} + + {/* Metadata card */}