/** * Dashboard uplift (plan UI 3.4): chart-area / stacked / sparkline widget * metadata flows into the WIDGETS literal, and widgets[].drillTo becomes a * resolved drill-down (routes helper + preset URL-state params). Unresolvable * drillTo → no affordance + a generation warning. No drillTo → legacy output. */ import { describe, it, expect } from 'vitest' import { generate } from '../generate.js' import type { ScaffoldComponentInput, PageSpecMin } from '../types.js' function input(widgets: unknown[]): ScaffoldComponentInput { return { module: 'sales', appCode: 'TestV2', entity: 'Order', section: 'orders', views: ['dashboard'], fields: [{ name: 'code', type: 'string', required: true }], projectPath: '/web', pageSpec: { screenCode: 'SCR-SALES-ORDERS-DASHBOARD-001', module: 'sales', appCode: 'testv2', section: 'orders', entity: 'Order', view: 'dashboard', filePath: 'src/pages/testv2/sales/orders/OrderDashboardPage.tsx', permission: 'sales.orders.read', actions: [], widgets, i18nKeys: { fr: {}, en: {}, it: {}, de: {} }, needsRefinement: false, specHash: 'w'.repeat(64), } as unknown as PageSpecMin, } } const pageOf = (widgets: unknown[], warnings: string[] = []) => generate(input(widgets), { warnings }).find((f) => f.path.endsWith('OrderDashboardPage.tsx'))!.content describe('scaffold-component / dashboard uplift (3.4)', () => { it('chart-area / stacked / sparkline flow into the WIDGETS literal', () => { const c = pageOf([ { key: 'trend', label: 'Trend', type: 'chart-area', stacked: true, col: 6 }, { key: 'total', label: 'Total', type: 'kpi', sparkline: true }, ]) expect(c).toMatch(/type: "chart-area", col: 6, stacked: true/) expect(c).toMatch(/type: "kpi", sparkline: true/) }) it('drillTo resolves to a routes helper with preset URL-state params, wired as onOpen', () => { const c = pageOf([ { key: 'late', label: 'Late', type: 'kpi', drillTo: 'SCR-SALES-ORDERS-LIST-001', drillParams: { 'f.status': 'late' } }, ]) expect(c).toMatch(/import \{ useNavigate \} from 'react-router-dom'/) expect(c).toMatch(/"late": \(\) => navigate\(routes\.orders\.list\(\) \+ '\?' \+ new URLSearchParams\(\{"f\.status":"late"\}\)\.toString\(\)\),/) expect(c).toMatch(/const widgetNav = useWidgetNav\(\)/) expect(c).toMatch(/onOpen=\{widgetNav\[w\.key\]\}/) }) it('an unresolvable drillTo warns and omits the affordance', () => { const warnings: string[] = [] const c = pageOf([ { key: 'ghost', label: 'Ghost', type: 'kpi', drillTo: 'SCR-OTHER-MODULE-LIST-001' }, ], warnings) expect(c).not.toMatch(/useWidgetNav/) expect(c).not.toMatch(/onOpen=/) expect(warnings.some((w) => w.includes("drillTo"))).toBe(true) }) it('no drillTo → legacy output (no navigate import, no onOpen)', () => { const c = pageOf([{ key: 'total', label: 'Total', type: 'kpi' }]) expect(c).not.toMatch(/useNavigate/) expect(c).not.toMatch(/onOpen/) }) })