import type { TestType } from '@util'; import fs from 'node:fs/promises'; import type { CellValue } from '../../dist/treb'; interface TestResults { succeeded: number; failed: number; error?: 'name'; failed_tests?: (TestType & {received?: CellValue|CellValue[][]})[]; } interface TestEntry { key: string; results: TestResults; } type Status = 'pass' | 'fail' | 'not-implemented' | 'not-tested'; interface FunctionRow { name: string; category: string; status: Status; passed: number; total: number; failed_tests?: TestType[]; } export async function RunReport() { const resultsRaw = await fs.readFile('test-results.json', 'utf-8'); const results: TestEntry[] = JSON.parse(resultsRaw); const csvRaw = await fs.readFile('data/excel_functions.csv', 'utf-8'); const csvLines = csvRaw.trim().split('\n').slice(1); const csvFunctions: { name: string; category: string }[] = []; for (const line of csvLines) { const [name, category] = line.split(','); if (name && category) { csvFunctions.push({ name: name.trim(), category: category.trim() }); } } const resultMap = new Map(); for (const entry of results) { resultMap.set(entry.key, entry); } const rows: FunctionRow[] = []; for (const fn of csvFunctions) { const entry = resultMap.get(fn.name); if (!entry) { rows.push({ name: fn.name, category: fn.category, status: 'not-tested', passed: 0, total: 0 }); continue; } const { succeeded, failed, error, failed_tests } = entry.results; const total = succeeded + failed; let status: Status; if (failed > 0 && failed_tests?.length && failed_tests.every(test => test.received === 'name')) { status = 'not-implemented'; } else if (failed > 0) { status = 'fail'; } else { status = 'pass'; } rows.push({ name: fn.name, category: fn.category, status, passed: succeeded, total, failed_tests: status === 'fail' ? failed_tests: [] }); } const categories = [...new Set(csvFunctions.map(f => f.category))]; const counts = { pass: 0, fail: 0, 'not-implemented': 0, 'not-tested': 0 }; for (const row of rows) counts[row.status]++; const totalFunctions = rows.length; function pct(n: number) { return ((n / totalFunctions) * 100).toFixed(1); } function esc(s: string) { return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } const statusLabel: Record = { 'pass': 'Pass', 'fail': 'Fail', 'not-implemented': 'Not Implemented', 'not-tested': 'Not Tested', }; const statusColor: Record = { 'pass': '#22c55e', 'fail': '#ef4444', 'not-implemented': '#9ca3af', 'not-tested': '#d1d5db', }; let html = ` TREB Function Coverage Report

TREB Function Coverage Report

Generated ${new Date().toISOString().replace('T', ' ').slice(0, 19)} UTC
${counts.pass}Pass (${pct(counts.pass)}%)
${counts.fail}Fail (${pct(counts.fail)}%)
${counts['not-implemented']}Not Implemented (${pct(counts['not-implemented'])}%)
${counts['not-tested']}Not Tested (${pct(counts['not-tested'])}%)
`; for (const category of categories) { const catRows = rows.filter(r => r.category === category).sort((a, b) => a.name.localeCompare(b.name)); const catPass = catRows.filter(r => r.status === 'pass').length; const catFail = catRows.filter(r => r.status === 'fail').length; const catNI = catRows.filter(r => r.status === 'not-implemented').length; html += `

${esc(category)}${catRows.length} functions — ${catPass} pass, ${catFail} fail, ${catNI} not implemented, ${catRows.length - catPass - catFail - catNI} not tested

`; for (const row of catRows) { const testsCol = row.status === 'not-tested' ? '—' : `${row.passed}/${row.total}`; const failedCol = row.failed_tests?.length ? row.failed_tests.map(t => `${esc(t.expression)}`).join(', ') : ''; html += `\n`; } html += `
FunctionStatusTestsFailed Expressions
${esc(row.name)} ${statusLabel[row.status]} ${testsCol} ${failedCol}
\n`; } html += ``; await fs.writeFile('report.html', html, 'utf-8'); console.info(`Report written to report.html (${totalFunctions} functions)`); }