From f3759b8b8ae099a445f80c498966abbdd4c54a9c Mon Sep 17 00:00:00 2001 From: Eric Grange Date: Wed, 9 Feb 2022 09:46:59 +0100 Subject: [PATCH] Modernized HTML coverage report - modernized table look, no cell borders, alternating background line color - interactive hover in unit list for better readability - unit list now sortable interactively - added mini coverage chart (histogram in unit list, donut for source) - faster unit html generation and simplified DOM for faster opening of large units in browsers - updated repository link --- Source/HTMLCoverageReport.pas | 117 +++++++++++++++++++++------------- 1 file changed, 72 insertions(+), 45 deletions(-) diff --git a/Source/HTMLCoverageReport.pas b/Source/HTMLCoverageReport.pas index 125a141..ef23c8d 100644 --- a/Source/HTMLCoverageReport.pas +++ b/Source/HTMLCoverageReport.pas @@ -136,7 +136,7 @@ procedure THTMLCoverageReport.AddGeneratedAt(var OutputFile: TTextWriter); begin LinkText := link( 'DelphiCodeCoverage', - 'https://sourceforge.net/projects/delphicodecoverage/', + 'https://github.com/DelphiCodeCoverage/DelphiCodeCoverage', 'Code Coverage for Delphi 5+' ); @@ -280,8 +280,10 @@ procedure THTMLCoverageReport.IterateOverStats( HtmlDetails : THtmlDetails; PostLink: string; PreLink: string; + Percent: String; CurrentStats: ICoverageStats; begin + AOutputFile.WriteLine('' + + Percent ) ); end; @@ -332,31 +336,35 @@ procedure THTMLCoverageReport.AddPreAmble(const AOutFile: TTextWriter); else begin AOutFile.WriteLine(StartTag('style', 'type="text/css"')); + + AOutFile.WriteLine('body {max-width: max-content;margin: auto;}'); + AOutFile.WriteLine('table {border-spacing:0; border-collapse:collapse;}'); - AOutFile.WriteLine('table, td, th {border: 1px solid black;}'); - AOutFile.WriteLine('td, th {background: white; margin: 0; padding: 2px 0.5em 2px 0.5em}'); - AOutFile.WriteLine('td {border-width: 0 1px 0 0;}'); - AOutFile.WriteLine('th {border-width: 1px 1px 1px 0;}'); + AOutFile.WriteLine('table, td, th {border: 0;}'); + AOutFile.WriteLine('td, th {background: white; margin: 0; padding: .5em 1em}'); + AOutFile.WriteLine('p, h1, h2, h3, th {font-family: verdana,arial,sans-serif; font-size: 10pt;}'); - AOutFile.WriteLine('td {font-family: courier,monospace; font-size: 10pt;}'); - AOutFile.WriteLine('th {background: #CCCCCC;}'); + AOutFile.WriteLine('td {font-family: consolas,courier,monospace; font-size: 10pt;}'); + AOutFile.WriteLine('th {background: #ccc;}'); + AOutFile.WriteLine('th[idx] {cursor:pointer;}'); AOutFile.WriteLine('table.o tr td:nth-child(1) {font-weight: bold;}'); AOutFile.WriteLine('table.o tr td:nth-child(2) {text-align: right;}'); AOutFile.WriteLine('table.o tr td {border-width: 1px;}'); - AOutFile.WriteLine('table.s {width: 100%;}'); - AOutFile.WriteLine('table.s tr td {padding: 0 0.25em 0 0.25em;}'); - AOutFile.WriteLine('table.s tr td:first-child {text-align: right; font-weight: bold;}'); - AOutFile.WriteLine('table.s tr.notcovered td {background: #DDDDFF;}'); - AOutFile.WriteLine('table.s tr.nocodegen td {background: #FFFFEE;}'); - AOutFile.WriteLine('table.s tr.covered td {background: #CCFFCC;}'); + AOutFile.WriteLine('table.s {width: calc(min(80em, 95vw));}'); + AOutFile.WriteLine('table.s tr td {padding: .1em .5em; white-space: pre-wrap;}'); + AOutFile.WriteLine('table.s tr td:first-child {text-align: right; font-weight: bold; vertical-align: top}'); + AOutFile.WriteLine('table.s tr.notcovered td {background: #ddf;}'); + AOutFile.WriteLine('table.s tr.nocodegen td {background: #ffe;}'); + AOutFile.WriteLine('table.s tr.covered td {background: #cfc;}'); AOutFile.WriteLine('table.s tr.covered td:first-child {color: green;}'); AOutFile.WriteLine('table.s {border-width: 1px 0 1px 1px;}'); - AOutFile.WriteLine('table.sum tr td {border-width: 1px;}'); - AOutFile.WriteLine('table.sum tr th {text-align:right;}'); - AOutFile.WriteLine('table.sum tr th:first-child {text-align:center;}'); + AOutFile.WriteLine('table.sum td { background-position: 50%; background-repeat: no-repeat; background-size: 90% 70%; }'); + AOutFile.WriteLine('table.sum tr:nth-child(odd) td { background-color: #f4f4f4}'); + AOutFile.WriteLine('table.sum tr:hover td, tr:hover td a { filter: invert(10%) }'); + AOutFile.WriteLine('table.sum tr th {text-align:left; border: 1px solid #888}'); AOutFile.WriteLine('table.sum tr td {text-align:right;}'); AOutFile.WriteLine('table.sum tr td:first-child {text-align:left;}'); AOutFile.WriteLine(EndTag('style')); @@ -367,6 +375,22 @@ procedure THTMLCoverageReport.AddPreAmble(const AOutFile: TTextWriter); procedure THTMLCoverageReport.AddPostAmble(const AOutFile: TTextWriter); begin + // minimalistic vanilla JS table sorter inspired from + // https://stackoverflow.com/questions/14267781/sorting-html-table-with-javascript + AOutFile.WriteLine( + ''); + AOutFile.WriteLine(EndTag('body')); AOutFile.WriteLine(EndTag('html')); end; @@ -375,25 +399,28 @@ procedure THTMLCoverageReport.AddStatistics( const ACoverageBase: ICoverageStats; const ASourceFileName: string; const AOutFile: TTextWriter); +var + percent : String; begin AOutFile.WriteLine( p(' Statistics for ' + ASourceFileName + ' ')); + percent := IntToStr(ACoverageBase.PercentCovered) + '%'; + AOutFile.WriteLine( - table( - tr( - td('Number of lines covered') + - td(IntToStr(ACoverageBase.CoveredLineCount)) - ) + - tr( - td('Number of lines with code gen') + - td(IntToStr(ACoverageBase.LineCount)) - ) + - tr( - td('Line coverage') + - td(IntToStr(ACoverageBase.PercentCovered) + '%') - ), - OverviewClass - ) + '' + + '' + + '' + + '' + + '
Number of lines covered' + + '' + IntToStr(ACoverageBase.CoveredLineCount) + + '' + + '
Number of lines with code gen' + + '' + IntToStr(ACoverageBase.LineCount) + + '
Line coverage' + + '' + percent + + '
' ); AOutFile.WriteLine(lineBreak + lineBreak); @@ -404,6 +431,7 @@ procedure THTMLCoverageReport.AddTableFooter( const ACoverageStats: ICoverageStats; const AOutputFile: TTextWriter); begin + AOutputFile.WriteLine(''); AOutputFile.WriteLine( tr( th(TNetEncoding.HTML.Encode(AHeading)) + @@ -423,12 +451,14 @@ procedure THTMLCoverageReport.AddTableHeader( AOutputFile.WriteLine(p(TNetEncoding.HTML.Encode(ATableHeading))); AOutputFile.WriteLine(StartTag('table', SummaryClass)); AOutputFile.WriteLine( - tr( - th(TNetEncoding.HTML.Encode(AColumnHeading)) + - th('Number of covered lines') + - th('Number of lines (which generated code)') + - th('Percent(s) covered') - ) + '' + + '' + + '' + TNetEncoding.HTML.Encode(AColumnHeading) + + 'Number of lines' + + 'Percent(s) covered' + + '' + + 'Covered' + + 'Which generated code' ); end; @@ -516,16 +546,13 @@ procedure THTMLCoverageReport.GenerateCoverageTable( if FCoverageConfiguration.LineCountLimit = 0 then HtmlLineCount := '' // No column for count else if Count < 0 then - HtmlLineCount := td('') // Count is blank + HtmlLineCount := '' // Count is blank else - HtmlLineCount := td(IntToStr(Count)); // Count is given + HtmlLineCount := '' + IntToStr(Count); // Count is given AOutputFile.WriteLine( - tr( - td(IntToStr(LineCount)) + HtmlLineCount + - td(pre(InputLine)), - 'class="' + AClass + '"' - ) + '' + IntToStr(LineCount) + HtmlLineCount + + '' + InputLine ); end;