Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/metrics/review-metrics.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
const mockFetchLinkedWorkItems =
jest.fn<typeof HouseholdItemWorkItemsApiTypes.fetchLinkedWorkItems>();
const mockListWorkItems = jest.fn<typeof WorkItemsApiTypes.listWorkItems>();
const mockFetchHouseholdItemBudgets = jest.fn() as any;

Check warning on line 26 in client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.test.tsx

View workflow job for this annotation

GitHub Actions / Quality Gates

Unexpected any. Specify a different type
const mockFetchBudgetCategories = jest.fn() as any;

Check warning on line 27 in client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.test.tsx

View workflow job for this annotation

GitHub Actions / Quality Gates

Unexpected any. Specify a different type
const mockFetchBudgetSources = jest.fn() as any;
const mockFetchVendors = jest.fn() as any;
const mockFetchSubsidyPrograms = jest.fn() as any;
Expand Down Expand Up @@ -130,6 +130,22 @@
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 (
<section data-testid="linked-documents-section">
<h2>Documents</h2>
<span data-testid="entity-type">{props.entityType}</span>
<span data-testid="entity-id">{props.entityId}</span>
</section>
);
},
}));

// Helper to capture current location
function LocationDisplay() {
const location = useLocation();
Expand Down Expand Up @@ -1063,4 +1079,78 @@
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();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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<HouseholdItemCategory, string> = {
Expand Down Expand Up @@ -1146,6 +1147,9 @@ export function HouseholdItemDetailPage() {
)}
</section>

{/* Documents section */}
<LinkedDocumentsSection entityType="household_item" entityId={id!} />

{/* Metadata card */}
<section className={styles.card}>
<div className={styles.metaRow}>
Expand Down
Loading