Skip to content

Commit f9a6ff0

Browse files
add filter tests by tags
1 parent d3cce6f commit f9a6ff0

File tree

2 files changed

+162
-7
lines changed

2 files changed

+162
-7
lines changed

CHANGELOG.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99
### Added
10-
- Based on [Add New Assertions for Enhanced Testing Capabilities](https://github.com/scripterio-js/scripterio/issues/63):
11-
- `toBeTypeOf()` — Check that a variable has a correct type
10+
**Features:**
11+
- Filter tests by tags (HTML reporter). Addressed in [#66](https://github.com/scripterio-js/scripterio/issues/66).
12+
- Assertion `toBeTypeOf()` — Check that a variable has a correct type. Addressed in [#63](https://github.com/scripterio-js/scripterio/issues/63).
1213

13-
- Contributors:
14+
***Contributors:***
1415
- [Vadym Nastoiashchyi](https://github.com/VadimNastoyashchy)
1516

1617
## 1.11.0 - 2025-07-08

src/reporters/html-template.mjs

Lines changed: 158 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,34 @@ const formatJson = (data) => {
2121
}
2222
}
2323

24+
const extractUniqueTags = (results) => {
25+
const tagSet = new Set()
26+
27+
const extractTagsFromTests = (tests) => {
28+
tests.forEach((test) => {
29+
if (test.tags && Array.isArray(test.tags)) {
30+
test.tags.forEach((tag) => {
31+
if (tag && tag.trim()) {
32+
tagSet.add(tag.trim())
33+
}
34+
})
35+
}
36+
})
37+
}
38+
39+
extractTagsFromTests(results)
40+
return Array.from(tagSet).sort()
41+
}
42+
2443
export const template = ({
2544
numTests,
2645
numPassed,
2746
numFailed,
2847
numTodo,
2948
results,
3049
}) => {
50+
const uniqueTags = extractUniqueTags(results)
51+
3152
return `
3253
<!DOCTYPE html>
3354
<html>
@@ -204,6 +225,63 @@ export const template = ({
204225
overflow-y: auto;
205226
white-space: pre-wrap;
206227
}
228+
229+
/* Tag styles */
230+
.tag-filters {
231+
margin-bottom: 1rem;
232+
padding: 1rem;
233+
background: #f8f9fa;
234+
border-radius: 6px;
235+
}
236+
.tag-filters h3 {
237+
margin: 0 0 0.5rem 0;
238+
font-size: 1rem;
239+
color: #555;
240+
}
241+
.tag-buttons {
242+
display: flex;
243+
flex-wrap: wrap;
244+
gap: 0.5rem;
245+
}
246+
.tag-button {
247+
padding: 0.25rem 0.75rem;
248+
border: 1px solid #ddd;
249+
border-radius: 20px;
250+
background: white;
251+
cursor: pointer;
252+
font-size: 0.875rem;
253+
transition: all 0.2s;
254+
}
255+
.tag-button:hover {
256+
background: #f0f0f0;
257+
}
258+
.tag-button.active {
259+
background: #1976d2;
260+
color: white;
261+
border-color: #1976d2;
262+
}
263+
.tag-button.clear {
264+
background: #f44336;
265+
color: white;
266+
border-color: #f44336;
267+
}
268+
.tag-button.clear:hover {
269+
background: #d32f2f;
270+
}
271+
.test-tags {
272+
display: flex;
273+
flex-wrap: wrap;
274+
gap: 0.25rem;
275+
margin-top: 0.5rem;
276+
}
277+
.test-tag {
278+
padding: 0.125rem 0.5rem;
279+
background: #e3f2fd;
280+
border-radius: 12px;
281+
font-size: 0.75rem;
282+
color: #1976d2;
283+
border: 1px solid #bbdefb;
284+
}
207285
</style>
208286
</head>
209287
<body>
@@ -234,12 +312,37 @@ export const template = ({
234312
</div>
235313
</div>
236314
315+
${
316+
uniqueTags.length > 0
317+
? `
318+
<div class="tag-filters">
319+
<h3>Filter by Tags:</h3>
320+
<div class="tag-buttons">
321+
<button class="tag-button clear" onclick="setTagFilter(null)">Clear Filters</button>
322+
${uniqueTags
323+
.map(
324+
(tag) => `
325+
<button class="tag-button" onclick="setTagFilter('${escapeHtml(
326+
tag
327+
)}')">${escapeHtml(tag)}</button>
328+
`
329+
)
330+
.join('')}
331+
</div>
332+
</div>
333+
`
334+
: ''
335+
}
336+
237337
<div class="results">
238338
${renderDescribeGroup(groupByDescribe(results))}
239339
</div>
240340
</div>
241341
242342
<script>
343+
let currentTagFilter = null;
344+
let currentStatusFilter = null;
345+
243346
function toggleDescribeContent(element) {
244347
const content = element.nextElementSibling;
245348
content.classList.toggle('hide');
@@ -259,7 +362,26 @@ export const template = ({
259362
element.textContent = isShowing ? 'Show API details' : 'Hide API details';
260363
}
261364
365+
function setTagFilter(tag) {
366+
currentTagFilter = tag;
367+
368+
document.querySelectorAll('.tag-button').forEach(btn => {
369+
btn.classList.remove('active');
370+
});
371+
372+
if (tag) {
373+
const tagButton = document.querySelector(\`.tag-button[onclick*="'\${tag}'"]\`);
374+
if (tagButton) tagButton.classList.add('active');
375+
} else {
376+
document.querySelector('.tag-button.clear').classList.add('active');
377+
}
378+
379+
applyFilters();
380+
}
381+
262382
function setFilter(filter) {
383+
currentStatusFilter = filter;
384+
263385
document.querySelectorAll('.stat').forEach(stat => {
264386
stat.classList.remove('active-filter');
265387
});
@@ -268,14 +390,30 @@ export const template = ({
268390
} else {
269391
document.querySelector('.stat.total').classList.add('active-filter');
270392
}
393+
394+
applyFilters();
395+
}
271396
397+
function applyFilters() {
272398
document.querySelectorAll('.test-case').forEach(tc => {
273399
const nameDiv = tc.querySelector('.test-name');
274-
if (!filter || nameDiv.classList.contains(filter)) {
275-
tc.style.display = '';
276-
} else {
277-
tc.style.display = 'none';
400+
const testTags = tc.querySelectorAll('.test-tag');
401+
const tagTexts = Array.from(testTags).map(tag => tag.textContent);
402+
403+
let showByStatus = true;
404+
let showByTag = true;
405+
406+
// Status filter
407+
if (currentStatusFilter && currentStatusFilter !== 'total') {
408+
showByStatus = nameDiv.classList.contains(currentStatusFilter);
409+
}
410+
411+
// Tag filter
412+
if (currentTagFilter) {
413+
showByTag = tagTexts.includes(currentTagFilter);
278414
}
415+
416+
tc.style.display = (showByStatus && showByTag) ? '' : 'none';
279417
});
280418
281419
function updateDescribeVisibility(describe) {
@@ -301,6 +439,7 @@ export const template = ({
301439
describe.style.display = hasVisible ? '' : 'none';
302440
return hasVisible;
303441
}
442+
304443
document.querySelectorAll('.describe-group').forEach(group => {
305444
updateDescribeVisibility(group);
306445
});
@@ -312,6 +451,10 @@ export const template = ({
312451
document.querySelector('.stat.failed').addEventListener('click', () => setFilter('failed'));
313452
document.querySelector('.stat.todo').addEventListener('click', () => setFilter('todo'));
314453
document.querySelector('.stat.total').classList.add('active-filter');
454+
455+
if (document.querySelector('.tag-button.clear')) {
456+
document.querySelector('.tag-button.clear').classList.add('active');
457+
}
315458
});
316459
</script>
317460
<style>
@@ -407,6 +550,17 @@ export const template = ({
407550
typeof test.duration === 'number' ? `${test.duration} ms` : ''
408551
}</span>
409552
</div>
553+
${
554+
test.tags && test.tags.length > 0
555+
? `
556+
<div class="test-tags">
557+
${test.tags
558+
.map((tag) => `<span class="test-tag">${escapeHtml(tag)}</span>`)
559+
.join('')}
560+
</div>
561+
`
562+
: ''
563+
}
410564
${
411565
test.errors.length
412566
? `

0 commit comments

Comments
 (0)