diff --git a/frontend/src/__tests__/dashboard.test.ts b/frontend/src/__tests__/dashboard.test.ts index c24e2c735..7b35dd964 100644 --- a/frontend/src/__tests__/dashboard.test.ts +++ b/frontend/src/__tests__/dashboard.test.ts @@ -177,6 +177,26 @@ describe('Dashboard Module', () => { expect(summary?.innerHTML).toContain('YTD Savings'); }); + test('formats fractional current_coverage with one decimal (not raw float)', async () => { + (api.getDashboardSummary as jest.Mock).mockResolvedValue({ + potential_monthly_savings: 0, + total_recommendations: 1, + active_commitments: 1, + committed_monthly: 100, + current_coverage: 0.14648736342042862, + target_coverage: 80, + ytd_savings: 0, + by_service: {} + }); + (api.getUpcomingPurchases as jest.Mock).mockResolvedValue({ purchases: [] }); + + await loadDashboard(); + + const summary = document.getElementById('summary'); + expect(summary?.innerHTML).toContain('0.1%'); + expect(summary?.innerHTML).not.toContain('0.14648736342042862'); + }); + test('renders the merged per-service savings chart', async () => { (api.getDashboardSummary as jest.Mock).mockResolvedValue({ potential_monthly_savings: 1000, diff --git a/frontend/src/dashboard.ts b/frontend/src/dashboard.ts index 01c4ae89c..e3bab9fc7 100644 --- a/frontend/src/dashboard.ts +++ b/frontend/src/dashboard.ts @@ -268,8 +268,8 @@ function renderDashboardSummary(data: DashboardSummary, recs: readonly LocalReco // When no recommendations and no commitments exist, "100% coverage" is // misleading — nothing is being tracked. Show a dash instead. const nothingTracked = !data.total_recommendations && !data.active_commitments; - const coverageValue = nothingTracked ? '—' : `${data.current_coverage || 0}%`; - const coverageDetail = nothingTracked ? 'No services tracked' : `Target: ${data.target_coverage || 80}%`; + const coverageValue = nothingTracked ? '—' : `${(data.current_coverage || 0).toFixed(1)}%`; + const coverageDetail = nothingTracked ? 'No services tracked' : `Target: ${(data.target_coverage || 80).toFixed(1)}%`; // Render KPI tiles via DOM construction (textContent / appendChild) // rather than an innerHTML template literal, per the issue #340 plan's diff --git a/internal/api/handler_purchases_test.go b/internal/api/handler_purchases_test.go index 03689547d..bebd33576 100644 --- a/internal/api/handler_purchases_test.go +++ b/internal/api/handler_purchases_test.go @@ -1643,6 +1643,10 @@ func TestHandler_getPurchaseDetails_NotFound_IsClientError(t *testing.T) { Email: "admin@example.com", } mockAuth.On("ValidateSession", ctx, "admin-token").Return(adminSession, nil) + // getPurchaseDetails gates on requirePermission(view, purchases) before + // the GetExecutionByID lookup this test targets. Confer admin via the auth + // mock (the Session struct carries no role field; admin is resolved through + // HasPermissionAPI) so the handler reaches the not-found branch. mockAuth.grantAdmin() const missingID = "dddddddd-dddd-dddd-dddd-dddddddddddd"