diff --git a/frontend/src/__tests__/history.test.ts b/frontend/src/__tests__/history.test.ts index faf017198..9ff19ba8b 100644 --- a/frontend/src/__tests__/history.test.ts +++ b/frontend/src/__tests__/history.test.ts @@ -246,6 +246,50 @@ describe('History Module', () => { expect(list?.innerHTML).toContain('table'); expect(list?.innerHTML).toContain('ec2'); expect(list?.innerHTML).toContain('us-east-1'); + // Both Monthly Cost and Monthly Savings columns must be present (issue #788). + expect(list?.innerHTML).toContain('Monthly Cost'); + expect(list?.innerHTML).toContain('Monthly Savings'); + }); + + test('renders monthly_cost cell with value when present, dash when absent (issue #788)', async () => { + (api.getHistory as jest.Mock).mockResolvedValue({ + summary: {}, + purchases: [ + { + timestamp: '2024-01-15T00:00:00Z', + provider: 'aws', + service: 'ec2', + resource_type: 't3.medium', + region: 'us-east-1', + count: 1, + term: 1, + upfront_cost: 500, + monthly_cost: 42.5, + estimated_savings: 20, + }, + { + timestamp: '2024-01-16T00:00:00Z', + provider: 'aws', + service: 'rds', + resource_type: 'db.m5.large', + region: 'us-east-1', + count: 1, + term: 1, + upfront_cost: 0, + // monthly_cost intentionally absent + estimated_savings: 10, + }, + ] + }); + + await loadHistory(); + + const list = document.getElementById('history-list'); + const html = list?.innerHTML || ''; + // Row with monthly_cost=42.5: formatCurrency mock returns "$42.5". + expect(html).toContain('$42.5'); + // Row without monthly_cost: must render the muted dash, not "$0". + expect(html).toContain('class="muted"'); }); test('shows empty message when no purchases', async () => { diff --git a/frontend/src/__tests__/inventory.test.ts b/frontend/src/__tests__/inventory.test.ts index 052e7a020..a2adc31b8 100644 --- a/frontend/src/__tests__/inventory.test.ts +++ b/frontend/src/__tests__/inventory.test.ts @@ -215,7 +215,7 @@ describe('loadActiveCommitments — fetch + render flow', () => { // (or drops) a column trips the test. const headers = Array.from(list.querySelectorAll('thead th')).map(th => th.textContent); expect(headers).toEqual([ - 'Provider', 'Account', 'Service', 'Resource type', 'Region', 'Count', 'Term', 'Payment', 'Monthly cost', 'Expires', + 'Provider', 'Account', 'Service', 'Resource type', 'Region', 'Count', 'Term', 'Payment', 'Monthly cost', 'Monthly savings', 'Expires', ]); const rows = list.querySelectorAll('tbody tr'); @@ -223,6 +223,12 @@ describe('loadActiveCommitments — fetch + render flow', () => { // Account cell contains the account name AND its monospaced ID. expect(rows[0]!.textContent).toContain('Prod'); expect(rows[0]!.textContent).toContain('ec2'); + + // Monthly Savings column (10th, 0-indexed 9) must render the estimated_savings value. + // makeCommitment defaults estimated_savings to 80.0; formatCurrency mock returns "$80". + const savingsCell = rows[0]!.querySelector('td:nth-child(10)'); + expect(savingsCell).not.toBeNull(); + expect(savingsCell!.textContent).toContain('80'); }); test('renders an empty paragraph when no commitments are returned', async () => { diff --git a/frontend/src/history.ts b/frontend/src/history.ts index 2ab4c8f35..bc2d09d13 100644 --- a/frontend/src/history.ts +++ b/frontend/src/history.ts @@ -623,6 +623,9 @@ function renderHistoryList(purchases: HistoryPurchase[]): void { })(); const execIdAttr = p.purchase_id ? ` data-execution-id="${escapeHtml(p.purchase_id)}"` : ''; const planCellContent = renderActionCell(p); + const monthlyCostCell = p.monthly_cost != null + ? formatCurrency(p.monthly_cost) + : '-'; return `