@@ -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+
2443export 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