Skip to content

Commit 3245a61

Browse files
committed
Add a Chart to Test Report for Better Visualization of Results
1 parent 478e5b3 commit 3245a61

File tree

1 file changed

+126
-196
lines changed

1 file changed

+126
-196
lines changed

src/reporters/html-template.mjs

Lines changed: 126 additions & 196 deletions
Original file line numberDiff line numberDiff line change
@@ -25,221 +25,157 @@ export const template = ({
2525
numTests,
2626
numPassed,
2727
numFailed,
28-
numTodo,
28+
numTodo = 0,
2929
results,
3030
}) => {
31+
const percent = (v) => (numTests === 0 ? 0 : Math.round((v / numTests) * 100))
32+
3133
return `
3234
<!DOCTYPE html>
3335
<html>
3436
<head>
35-
<title>Test Results</title>
37+
<title>Test Report</title>
3638
<style>
37-
body {
38-
font-family: system-ui, -apple-system, sans-serif;
39-
line-height: 1.5;
40-
padding: 2rem;
41-
margin: 0;
42-
background: #f5f5f5;
43-
}
44-
.container {
45-
max-width: 1200px;
46-
margin: 0 auto;
47-
background: white;
48-
padding: 2rem;
49-
border-radius: 8px;
50-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
51-
}
52-
.header {
53-
display: flex;
54-
justify-content: space-between;
55-
align-items: center;
56-
margin-bottom: 2rem;
57-
padding-bottom: 1rem;
58-
border-bottom: 1px solid #eee;
59-
}
60-
.header-controls {
61-
display: flex;
62-
gap: 1rem;
63-
align-items: center;
64-
}
65-
.summary {
66-
display: flex;
67-
gap: 2rem;
68-
margin-bottom: 2rem;
69-
}
70-
.stat {
71-
padding: 1rem;
72-
border-radius: 6px;
73-
min-width: 120px;
74-
}
39+
body { font-family: system-ui, -apple-system, sans-serif; background: #f5f5f5; margin: 0; padding: 2rem; }
40+
.container { max-width: 1200px; margin: 0 auto; background: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.08); padding: 2rem; }
41+
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; }
42+
.main-flex { display: flex; align-items: center; gap: 3rem; margin-bottom: 2.5rem; }
43+
.pie-chart-container { position: relative; width: 200px; height: 200px; background: #fff; border-radius: 50%; box-shadow: 0 2px 8px rgba(0,0,0,0.07); display: flex; align-items: center; justify-content: center; }
44+
.pie-legend { display: flex; flex-direction: column; gap: 0.7rem; margin-top: 1.5rem; font-size: 1.1rem; }
45+
.pie-legend-item { display: flex; align-items: center; gap: 0.6rem; }
46+
.dot { width: 18px; height: 18px; border-radius: 50%; display: inline-block; }
47+
.pie-passed { background: #4caf50; }
48+
.pie-failed { background: #f44336; }
49+
.pie-todo { background: #ff9800; }
50+
.pie-stat-label { font-weight: 600; min-width: 60px; display: inline-block; }
51+
.pie-stat-value { font-weight: 700; margin-left: 0.5em; }
52+
.summary-tiles { display: flex; flex-direction: row; gap: 1.5rem; align-items: center; height: 200px; }
53+
.stat { padding: 1.2rem 1.5rem; border-radius: 12px; min-width: 120px; background: #f5f5f5; box-shadow: 0 1px 2px rgba(0,0,0,0.03); display: flex; flex-direction: column; align-items: center; justify-content: center; height: 120px; }
7554
.stat.total { background: #e3f2fd; }
7655
.stat.passed { background: #e8f5e9; }
7756
.stat.failed { background: #ffebee; }
78-
.stat h3 { margin: 0; }
79-
.stat p { margin: 0.5rem 0 0; font-size: 1.5rem; font-weight: bold; }
80-
.test-case {
81-
padding: 0.75rem 1rem;
82-
border-bottom: 1px solid #eee;
83-
}
84-
.test-case:last-child {
85-
border-bottom: none;
86-
}
87-
.test-name {
88-
display: flex;
89-
align-items: center;
90-
gap: 0.5rem;
91-
}
92-
.test-name.passed::before {
93-
content: '✓';
94-
color: #4caf50;
95-
}
96-
.test-name.failed::before {
97-
content: '✗';
98-
color: #f44336;
99-
}
100-
.error-details {
101-
margin-top: 1rem;
102-
padding: 1rem;
103-
background: #fff8f8;
104-
border-radius: 4px;
105-
display: none;
106-
}
107-
.error-details.show {
108-
display: block;
109-
}
110-
.toggle-error {
111-
color: #f44336;
112-
text-decoration: underline;
113-
cursor: pointer;
114-
margin-top: 0.5rem;
115-
display: inline-block;
116-
}
117-
pre {
118-
margin: 0.5rem 0;
119-
padding: 1rem;
120-
background: #f5f5f5;
121-
border-radius: 4px;
122-
overflow-x: auto;
123-
}
124-
125-
.test-name.todo::before {
126-
content: '◦';
127-
color: #ff9800;
128-
}
129-
.test-name.todo {
130-
color: #ff9800;
131-
font-style: italic;
132-
}
133-
13457
.stat.todo { background: #fff3e0; }
58+
.stat h3 { margin: 0 0 0.3em 0; font-size: 1.1em; font-weight: 600; }
59+
.stat p { margin: 0; font-size: 2rem; font-weight: bold; }
60+
.stat.passed h3, .stat.passed p { color: #4caf50; }
61+
.stat.failed h3, .stat.failed p { color: #f44336; }
13562
.stat.todo h3, .stat.todo p { color: #ff9800; }
136-
.describe-group {
137-
margin: 0.5rem 0;
138-
padding: 0 1rem;
139-
}
140-
.describe-header {
141-
padding: 0.75rem 1rem;
142-
background: #f3f4f6;
143-
margin: 0.5rem 0;
144-
font-weight: 600;
145-
border-radius: 4px;
146-
cursor: pointer;
147-
}
148-
.describe-content {
149-
display: block;
150-
padding: 0.5rem 0;
151-
}
152-
.describe-content.hide {
153-
display: none;
154-
}
155-
.api-details {
156-
margin-top: 1rem;
157-
padding: 1rem;
158-
background: #f8f9fa;
159-
border-radius: 4px;
160-
display: none;
161-
}
162-
.api-details.show {
163-
display: block;
164-
}
165-
.toggle-api {
166-
color: #2196f3;
167-
text-decoration: underline;
168-
cursor: pointer;
169-
margin-top: 0.5rem;
170-
display: inline-block;
171-
margin-left: 1rem;
172-
}
173-
.api-section {
174-
margin: 1rem 0;
175-
padding: 1rem;
176-
background: #fff;
177-
border: 1px solid #e0e0e0;
178-
border-radius: 4px;
179-
}
180-
.api-section h4 {
181-
margin: 0 0 0.5rem 0;
182-
color: #555;
183-
}
184-
.api-table {
185-
width: 100%;
186-
border-collapse: collapse;
187-
margin: 0.5rem 0;
188-
}
189-
.api-table th, .api-table td {
190-
text-align: left;
191-
padding: 0.5rem;
192-
border: 1px solid #e0e0e0;
193-
}
194-
.api-table th {
195-
background: #f3f4f6;
196-
font-weight: 600;
197-
}
198-
pre.api-data {
199-
margin: 0.5rem 0;
200-
padding: 0.5rem;
201-
background: #f5f5f5;
202-
border-radius: 4px;
203-
max-height: 200px;
204-
overflow-y: auto;
205-
white-space: pre-wrap;
206-
}
63+
.test-case { padding: 0.75rem 1rem; border-bottom: 1px solid #eee; }
64+
.test-case:last-child { border-bottom: none; }
65+
.test-name { display: flex; align-items: center; gap: 0.5rem; }
66+
.test-name.passed::before { content: '✓'; color: #4caf50; }
67+
.test-name.failed::before { content: '✗'; color: #f44336; }
68+
.test-name.todo::before { content: '◦'; color: #ff9800; }
69+
.test-name.todo { color: #ff9800; font-style: italic; }
70+
.error-details { margin-top: 1rem; padding: 1rem; background: #fff8f8; border-radius: 4px; display: none; }
71+
.error-details.show { display: block; }
72+
.toggle-error { color: #f44336; text-decoration: underline; cursor: pointer; margin-top: 0.5rem; display: inline-block; }
73+
pre { margin: 0.5rem 0; padding: 1rem; background: #f5f5f5; border-radius: 4px; overflow-x: auto; }
74+
.describe-group { margin: 0.5rem 0; padding: 0 1rem; }
75+
.describe-header { padding: 0.75rem 1rem; background: #f3f4f6; margin: 0.5rem 0; font-weight: 600; border-radius: 4px; cursor: pointer; }
76+
.describe-content { display: block; padding: 0.5rem 0; }
77+
.describe-content.hide { display: none; }
78+
.api-details { margin-top: 1rem; padding: 1rem; background: #f8f9fa; border-radius: 4px; display: none; }
79+
.api-details.show { display: block; }
80+
.toggle-api { color: #2196f3; text-decoration: underline; cursor: pointer; margin-top: 0.5rem; display: inline-block; margin-left: 1rem; }
81+
.api-section { margin: 1rem 0; padding: 1rem; background: #fff; border: 1px solid #e0e0e0; border-radius: 4px; }
82+
.api-section h4 { margin: 0 0 0.5rem 0; color: #555; }
83+
.api-table { width: 100%; border-collapse: collapse; margin: 0.5rem 0; }
84+
.api-table th, .api-table td { text-align: left; padding: 0.5rem; border: 1px solid #e0e0e0; }
85+
.api-table th { background: #f3f4f6; font-weight: 600; }
86+
pre.api-data { margin: 0.5rem 0; padding: 0.5rem; background: #f5f5f5; border-radius: 4px; max-height: 200px; overflow-y: auto; white-space: pre-wrap; }
20787
</style>
20888
</head>
20989
<body>
21090
<div class="container">
21191
<div class="header">
212-
<h1>Test Results</h1>
213-
<div class="header-controls">
214-
<div>${new Date().toLocaleString()}</div>
215-
</div>
92+
<h1>Test Report</h1>
93+
<div>${new Date().toLocaleString()}</div>
21694
</div>
217-
218-
<div class="summary">
219-
<div class="stat total">
220-
<h3>Total Tests</h3>
221-
<p>${numTests}</p>
222-
</div>
223-
<div class="stat passed">
224-
<h3>Passed</h3>
225-
<p>${numPassed}</p>
226-
</div>
227-
<div class="stat failed">
228-
<h3>Failed</h3>
229-
<p>${numFailed}</p>
95+
<div class="main-flex">
96+
<div>
97+
<div class="pie-chart-container">
98+
<canvas id="pieChart" width="200" height="200"></canvas>
99+
</div>
100+
<div class="pie-legend">
101+
<div class="pie-legend-item">
102+
<span class="dot pie-passed"></span>
103+
<span class="pie-stat-label">Passed</span>
104+
<span class="pie-stat-value" id="percent-passed">${percent(
105+
numPassed
106+
)}%</span>
107+
</div>
108+
<div class="pie-legend-item">
109+
<span class="dot pie-failed"></span>
110+
<span class="pie-stat-label">Failed</span>
111+
<span class="pie-stat-value" id="percent-failed">${percent(
112+
numFailed
113+
)}%</span>
114+
</div>
115+
<div class="pie-legend-item">
116+
<span class="dot pie-todo"></span>
117+
<span class="pie-stat-label">Todo</span>
118+
<span class="pie-stat-value" id="percent-todo">${percent(
119+
numTodo
120+
)}%</span>
121+
</div>
122+
</div>
230123
</div>
231-
<div class="stat todo">
232-
<h3>Todo</h3>
233-
<p>${numTodo}</p>
124+
<div class="summary-tiles">
125+
<div class="stat total">
126+
<h3>Total</h3>
127+
<p>${numTests}</p>
128+
</div>
129+
<div class="stat passed">
130+
<h3>Passed</h3>
131+
<p>${numPassed}</p>
132+
</div>
133+
<div class="stat failed">
134+
<h3>Failed</h3>
135+
<p>${numFailed}</p>
136+
</div>
137+
<div class="stat todo">
138+
<h3>Todo</h3>
139+
<p>${numTodo}</p>
140+
</div>
234141
</div>
235142
</div>
236-
237143
<div class="results">
238-
${renderDescribeGroup(groupByDescribe(results))}
144+
${renderDescribeGroup(groupByDescribe(results))}
239145
</div>
240146
</div>
241-
242147
<script>
148+
(function() {
149+
const data = [
150+
{ value: ${numPassed}, color: '#4caf50' },
151+
{ value: ${numFailed}, color: '#f44336' },
152+
{ value: ${numTodo}, color: '#ff9800' }
153+
];
154+
const total = ${numTests};
155+
const canvas = document.getElementById('pieChart');
156+
if (!canvas) return;
157+
const ctx = canvas.getContext('2d');
158+
const centerX = canvas.width / 2;
159+
const centerY = canvas.height / 2;
160+
const radius = 90;
161+
let startAngle = -0.5 * Math.PI;
162+
const totalValue = data.reduce((sum, d) => sum + d.value, 0);
163+
data.forEach((d) => {
164+
if (d.value > 0) {
165+
const slice = (d.value / totalValue) * 2 * Math.PI;
166+
ctx.beginPath();
167+
ctx.moveTo(centerX, centerY);
168+
ctx.arc(centerX, centerY, radius, startAngle, startAngle + slice);
169+
ctx.closePath();
170+
ctx.fillStyle = d.color;
171+
ctx.globalAlpha = 0.92;
172+
ctx.fill();
173+
ctx.globalAlpha = 1;
174+
startAngle += slice;
175+
}
176+
});
177+
})();
178+
243179
function toggleDescribeContent(element) {
244180
const content = element.nextElementSibling;
245181
content.classList.toggle('hide');
@@ -258,12 +194,6 @@ export const template = ({
258194
details.classList.toggle('show');
259195
element.textContent = isShowing ? 'Show API details' : 'Hide API details';
260196
}
261-
262-
document.addEventListener('DOMContentLoaded', () => {
263-
const failedTests = document.querySelectorAll('.test-name.failed');
264-
failedTests.forEach(test => {
265-
});
266-
});
267197
</script>
268198
</body>
269199
</html>

0 commit comments

Comments
 (0)