@olton/latte
Version:
Simple test framework for JavaScript and TypeScript with DOM supports
195 lines (184 loc) • 5.84 kB
JavaScript
import { styles } from './styles.js'
export function htmlTemplate (data) {
const {
summary,
files,
coverage,
timestamp,
duration
} = data
return `
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Latte Report</title>
<style>${styles}</style>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div class="container">
<header>
<h1>Latte Report</h1>
<div class="timestamp">Generated on ${timestamp}</div>
</header>
<section class="summary">
<h2>Summary</h2>
<div class="summary-row">
<span class="summary-label">Total Tests:</span>
<span class="metric">${summary.total}</span>
</div>
<div class="summary-row">
<span class="summary-label">Passed:</span>
<span class="metric success">${summary.passed}</span>
</div>
<div class="summary-row">
<span class="summary-label">Failed:</span>
<span class="metric failure">${summary.failed}</span>
</div>
<div class="summary-row">
<span class="summary-label">Duration:</span>
<span class="metric">${duration} ms</span>
</div>
</section>
<div class="charts">
<div class="chart">
<h3>Test Results</h3>
<canvas id="testResultsChart"></canvas>
</div>
${coverage
? `
<div class="chart">
<h3>Code Coverage</h3>
<canvas id="coverageChart"></canvas>
</div>
`
: ''}
</div>
<section class="test-files">
<h2>Test Files</h2>
${files.map(file => `
<div class="file-block">
<div class="file-header">
<div class="file-name">${file.file}</div>
<div class="file-status ${file.allPassed ? 'passed' : 'failed'}">${file.allPassed ? 'Passed' : 'Failed'}</div>
</div>
<div class="file-meta">
<span>Duration: ${file.duration} ms</span> |
<span>Tests: ${file.totalTests}</span> |
<span>Passed: ${file.passedTests}</span> |
<span>Failed: ${file.failedTests}</span>
</div>
${file.tests.map(suite => `
<div class="test-suite">
<div class="suite-name">${suite.name || 'File-level tests'}</div>
${suite.tests.map(test => `
<div class="test-case ${test.passed ? 'passed' : 'failed'}">
<div class="test-name">${test.name}</div>
${!test.passed
? `
<div class="error-details">${test.message || 'Test failed'}</div>
`
: ''}
</div>
`).join('')}
</div>
`).join('')}
</div>
`).join('')}
</section>
${coverage
? `
<section class="coverage-section">
<h2 class="coverage-header">Code Coverage</h2>
${coverage.map(file => {
const linePercentage = +file.percent
const colorClass = linePercentage < 50 ? 'low' : linePercentage < 80 ? 'medium' : ''
return `
<div class="coverage-file">
<div class="file-name">${file.name}</div>
<div class="coverage-details">
<div class="coverage-item">
<span class="coverage-label">Coverage</span>
<span class="coverage-value">${linePercentage.toFixed(2)}%</span>
</div>
<div class="coverage-item">
<span class="coverage-label">Lines</span>
<span class="coverage-value">${file.covered}/${file.total}</span>
</div>
<div class="progress-bar">
<div class="progress-value ${colorClass}" style="width: ${linePercentage}%"></div>
</div>
</div>
</div>
`
}).join('')}
</section>
`
: ''}
<footer>
Generated by Latte | ${new Date().toISOString()}
<div>
Latte created by <a href="https://pimenov.com.ua">Serhii Pimenov</a>
</div>
</footer>
</div>
<script>
// Настройка графика тестовых результатов
const testResultsCtx = document.getElementById('testResultsChart').getContext('2d');
new Chart(testResultsCtx, {
type: 'doughnut',
data: {
labels: ['Passed', 'Failed'],
datasets: [{
data: [${summary.passed}, ${summary.failed}],
backgroundColor: ['#28a745', '#dc3545'],
borderWidth: 0
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
${coverage
? `
// Настройка графика покрытия кода
const coverageCtx = document.getElementById('coverageChart').getContext('2d');
new Chart(coverageCtx, {
type: 'bar',
data: {
labels: [${coverage.map(file => `'${file.name}'`).join(', ')}],
datasets: [{
label: 'Line Coverage (%)',
data: [${coverage.map(file => file.percent).join(', ')}],
backgroundColor: '#4caf50',
borderWidth: 0
}]
},
options: {
responsive: true,
aspectRatio: 1,
scales: {
y: {
beginAtZero: true,
max: 100
}
},
plugins: {
legend: {
position: 'bottom'
}
}
}
});
`
: ''}
</script>
</body>
</html>`
}