void WriteStatement(StringBuilder sb, HtmlReportLineItem lineItem) { var lineItemType = lineItem.TypeName.ToLower(); if (lineItem.Statement != null || lineItem.Explanation != null) { sb.AppendLine($@" <li class=""row statement""> <div class=""col-2 badges""></div> <div class=""col-11""> <div class=""{lineItemType} statement"" id=""{lineItemType}-{this.lineItemNumber}-statement"" style=""display: none;"">" .RemoveIndentation(4, true)); if (lineItem.Statement != null) { sb.AppendLine($@" <div class=""statement-title"">Statement</div> <div class=""{lineItemType} statement-content rounded"" id=""{lineItemType}-{this.lineItemNumber}-statement"">{lineItem.Statement.AddIndentation(4)}</div>" .RemoveIndentation(4, true)); } sb.AppendLine($@" </div> </div> </li>" .RemoveIndentation(4, true)); if (lineItem.Explanation != null) { WriteLineItemContent(sb, lineItem, "explanation"); // sb.AppendLine($@" // <div class=""explanation-title"">Explanation</div> // <div class=""{lineItemType} explanation rounded"" // id=""{lineItemType}-{this.lineItemNumber}-explanation"">{Markdown.ToHtml(lineItem.Explanation).AddIndentation(4)}</div>".RemoveIndentation(4,true)); } } }
void WriteStepDetails(StringBuilder sb, HtmlReportLineItem lineItem) { var inputLink = lineItem.Input != null; var outputLink = lineItem.Output != null; var explanationLink = lineItem.Explanation != null; var inputHtmlPreviewLink = (inputLink && lineItem.InputFormat == TextFormat.htmlpreview ? $"'iframe{lineItemNumber}input'" : "null"); var outputHtmlPreviewLink = (outputLink && lineItem.OutputFormat == TextFormat.htmlpreview ? $"'iframe{lineItemNumber}output'" : "null"); var explanationHtmlPreviewLink = (explanationLink && lineItem.ExplanationFormat == TextFormat.htmlpreview ? $"'iframe{lineItemNumber}explanation'" : "null"); sb.AppendLine($@" <li class=""row""> <div class=""col-2 badges""></div> <div class=""col-11""> <div class=""step-details"">" .RemoveIndentation(4)); if (lineItem.Exception != null) { sb.AppendLine($@" <div class=""{lineItem.TypeName}-exception-link reason-Failed badge pointer button"" id=""{lineItem.TypeName}-{this.lineItemNumber}-exception-link"" onclick=""toggleVisibility('{lineItem.TypeName}-{this.lineItemNumber}-exception')""> <i class=""fas fa-bug""></i> <span>Exception</span> </div>" .RemoveIndentation(4, true)); } if (!string.IsNullOrEmpty(lineItem.Input)) { sb.AppendLine($@" <div class=""{lineItem.TypeName}-input-link badge pointer button"" id=""{lineItem.TypeName}-{this.lineItemNumber}-input-link"" onclick=""toggleVisibility('{lineItem.TypeName}-{this.lineItemNumber}-input', {inputHtmlPreviewLink})""> <i class=""fas fa-sign-in-alt""></i> <span>Input</span> </div>" .RemoveIndentation(4, true)); } if (!string.IsNullOrEmpty(lineItem.Output)) { sb.AppendLine($@" <div class=""{lineItem.TypeName}-output-link badge pointer button"" id=""{lineItem.TypeName}-{this.lineItemNumber}-output-link"" onclick=""toggleVisibility('{lineItem.TypeName}-{this.lineItemNumber}-output', {outputHtmlPreviewLink})""> <i class=""fas fa-sign-out-alt""></i> <span>Output</span> </div>" .RemoveIndentation(4, true)); } if (!string.IsNullOrEmpty(lineItem.Explanation) || !string.IsNullOrEmpty(lineItem.Statement)) { sb.AppendLine($@" <div class=""{lineItem.TypeName}-statement-link badge pointer badge-secondary"" id=""{lineItem.TypeName}-{this.lineItemNumber}-statement-link"" onclick=""toggleVisibility('{lineItem.TypeName}-{this.lineItemNumber}-statement'); toggleVisibility('{lineItem.TypeName}-{this.lineItemNumber}-explanation', {explanationHtmlPreviewLink})""> <i class=""fas fa-info""></i> <span>Explanation</span> </div>" .RemoveIndentation(4, true)); } sb.AppendLine($@" </div> </div> </li>" .RemoveIndentation(4)); }
void WriteException(StringBuilder sb, HtmlReportLineItem lineItem, Exception exception, bool innerException = false) { var lineItemType = lineItem.TypeName.ToLower(); if (exception != null && lineItem.Outcome != Outcome.Skipped) { if (!innerException) { sb.AppendLine($@" <li class=""row exception""> <div class=""col-2 badges""></div> <div class=""col-11""> <div class=""exception"" id=""{lineItemType}-{this.lineItemNumber}-exception"" style=""display: block;""> <div class=""exception-title"" id=""step-{this.lineItemNumber}-exception-title"">Exception<div> <div class=""exception-details rounded""> " .RemoveIndentation(4, true)); } sb.AppendLine($@" <dl class=""exception dl-horizontal rounded""> <dt>Error Type</dt><dd class=""exception-type"">{exception.GetType().Name.HtmlEncode()}</dd> <dt>Message</dt><dd class=""exception-message bg-light""><pre><code>{exception.Message.AddIndentation(4).HtmlEncode()}</code></pre></dd> <dt>Stack</dt><dd class=""exception-stack bg-light""><pre><code>{exception.StackTrace.AddIndentation(4).HtmlEncode()}</code></pre></dd> " .RemoveIndentation(4, true)); if (exception.InnerException != null) { sb.AppendLine($@" <dt>Inner Exception</dt> <dd class=""inner-exception"">" .RemoveIndentation(4, true)); WriteException(sb, lineItem, exception.InnerException, true); sb.AppendLine($@" </dd>" .RemoveIndentation(4, true)); } sb.AppendLine($@" </dl>" .RemoveIndentation(4, true)); if (!innerException) { sb.AppendLine($@" </div> </div> </div> </li>" .RemoveIndentation(4, true)); } } }
void WriteInputOrOutputWithHtmlPreview(StringBuilder sb, HtmlReportLineItem lineItem, string contentType) { string previewCode = null; string previewHtml = null; switch (contentType) { case "input": previewCode = lineItem.Input.HtmlEncode(); previewHtml = lineItem.Input; break; case "output": previewCode = lineItem.Output.HtmlEncode(); previewHtml = lineItem.Output; break; case "explanation": previewCode = lineItem.Explanation.HtmlEncode(); previewHtml = lineItem.Explanation; break; } previewHtml = previewHtml .Replace(System.Environment.NewLine, " \\" + System.Environment.NewLine) .Replace(":", "\\:") .Replace("/", "\\/") .Replace("!", "\\!") .Replace("\"", "\\\""); var html = $@" <div class=""{contentType} rounded""> <div class=""nav nav-tabs"" role=""tablist"" style=""display: {(contentType=="explanation"?"none":"flex")};""> <a id=""preview{this.lineItemNumber}-tab"" class=""nav-item nav-link active pointer"" onclick=""toggleVisibility('preview{this.lineItemNumber}{contentType}', true, 'iframe{this.lineItemNumber}{contentType}'); toggleVisibility('code{this.lineItemNumber}{contentType}')"">Preview</a> <a id=""code{this.lineItemNumber}-tab"" class=""nav-item nav-link pointer"" onclick=""toggleVisibility('preview{this.lineItemNumber}{contentType}', true, 'iframe{this.lineItemNumber}{contentType}'); toggleVisibility('code{this.lineItemNumber}{contentType}')"">Code</a> </div> <div class=""tab-content""> <div role=""tabpanel"" class=""tab-pane active"" style=""display: block;"" id=""preview{this.lineItemNumber}{contentType}""> <iframe width=""100%"" id=""iframe{this.lineItemNumber}{contentType}""></iframe> <script type=""text/javascript""> var iframe{this.lineItemNumber}{contentType}doc = document.getElementById('iframe{this.lineItemNumber}{contentType}').contentWindow.document; iframe{this.lineItemNumber}{contentType}doc.open(); var html{this.lineItemNumber}{contentType} = ""{previewHtml.AddIndentation(4)}""; iframe{this.lineItemNumber}{contentType}doc.write(html{this.lineItemNumber}{contentType}); resizeIframe(document.getElementById('iframe{this.lineItemNumber}{contentType}')); iframe{this.lineItemNumber}{contentType}doc.close(); </script> </div> <div role=""tabpanel"" class=""tab-pane"" style=""display: none;"" id=""code{this.lineItemNumber}{contentType}""> <pre class=""{contentType} prettyprint linenums lang-html"">{previewCode.AddIndentation(4)}</pre> </div> </div> </div>" .RemoveIndentation(4, true); sb.AppendLine(html); }
void WriteLineItemContent(StringBuilder sb, HtmlReportLineItem lineItem, string contentType) { var lineItemType = lineItem.TypeName.ToLower(); var text = ""; TextFormat format = new TextFormat(); var title = ""; var display = "block"; switch (contentType) { case "input": text = lineItem.Input; format = lineItem.InputFormat; title = "Input"; break; case "output": text = lineItem.Output; format = lineItem.OutputFormat; title = "Output"; break; case "explanation": text = lineItem.Explanation; format = lineItem.ExplanationFormat; title = "Explanation"; display = "none"; break; } if (!String.IsNullOrEmpty(text)) { sb.AppendLine($@" <li class=""row {contentType}""> <div class=""col-2 badges""></div> <div class=""col-11""> <div class=""{contentType}"" id=""{lineItemType}-{this.lineItemNumber}-{contentType}"" style=""display: {display};""> <div class=""{contentType}-title"" id=""step-{this.lineItemNumber}-{contentType}-title"">{title}</div>" .RemoveIndentation(4, true)); if (format == TextFormat.htmlpreview) { WriteInputOrOutputWithHtmlPreview(sb, lineItem, contentType); } else if (format == TextFormat.markdown) { text = Markdig.Markdown.ToHtml(text, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build()); sb.AppendLine($@" <div class=""{contentType} markdown"" id=""{contentType}-{this.lineItemNumber}"">{text.AddIndentation(4)}</div>" .RemoveIndentation(4, true)); } else { var className = $"{contentType} prettyprint linenums rounded"; if (format != TextFormat.code) { className = className + " lang-" + Enum.GetName(typeof(TextFormat), format); } if (format == TextFormat.markdown) { text = Markdig.Markdown.ToHtml(text); } sb.AppendLine($@" <pre class=""{className}"" id=""{contentType}-{this.lineItemNumber}"">{text.HtmlEncode().AddIndentation(4)}</pre>" .RemoveIndentation(4, true)); } sb.AppendLine($@" </div> </div> </li>" .RemoveIndentation(4, true)); } }
void WriteBadge(StringBuilder sb, HtmlReportLineItem lineItem, bool header) { var lineItemType = lineItem.TypeName.ToLower(); if (header) { lineItemType = "header"; } var statementAndExplanationLink = lineItem.Statement != null || lineItem.Explanation != null; var total = 0; if (lineItem.ReasonStats.ContainsKey("Scenarios")) { foreach (var reasonStat in lineItem.ReasonStats["Scenarios"].Values) { total = total + reasonStat; } } sb.AppendLine($@" <div class=""col-2 badges"">" .RemoveIndentation(4, true)); if (lineItemType == "testrun" || lineItemType == "capability" || lineItemType == "feature" || lineItemType == "header") { sb.AppendLine($@" <div class=""{lineItemType} badge-distro"" id=""{lineItemType}-{this.lineItemNumber}-badge-distro"" title=""{lineItem.Reason} Count: Scenarios"" onclick=""toggleVisibility('{lineItemType}-{this.lineItemNumber}-stats')""> <div class=""{lineItemType} badge badge-pill pointer total reason-{lineItem.Reason.EncodeCSSClassName()}"" id=""{lineItemType}-{this.lineItemNumber}-badge"">{(lineItemType == "step" ? " " : total.ToString())}</div>" .RemoveIndentation(4, true)); if (lineItemType != "scemario" && lineItemType != "step") { sb.AppendLine($@" <div class=""{lineItemType} distro-corner-tr""> </div> <div class=""{lineItemType} distro-corner-br""> </div> <div class=""{lineItemType} distro pointer"" id=""{lineItemType}-{this.lineItemNumber}-distro"">" .RemoveIndentation(4, true)); foreach (var sortedReason in this.sortedReasons.Select(x => x).Reverse()) { double stat = 0; if (lineItem.ReasonStats.ContainsKey("Scenarios") && lineItem.ReasonStats["Scenarios"].ContainsKey(sortedReason.Reason)) { stat = ((double)lineItem.ReasonStats["Scenarios"][sortedReason.Reason] / (double)total) * 100; sb.AppendLine($@" <div class=""reason-{sortedReason.Reason.EncodeCSSClassName()}"" style=""height: {stat}%; width: 100%;""></div>" .RemoveIndentation(4, true)); } } sb.AppendLine($@" </div> </div>" .RemoveIndentation(4, true)); } } else { if (lineItemType == "scenario" || lineItem.Exception == null) { var childTotal = lineItem.ChildItems.Count; sb.AppendLine($@" <div class=""{lineItemType} badge badge-pill pointer total reason-{lineItem.Reason.EncodeCSSClassName()}"" id=""{lineItemType}-{this.lineItemNumber}-badge"">{(lineItemType == "step" ? " " : childTotal.ToString())}</div>" .RemoveIndentation(4, true)); } else { sb.AppendLine($@" <div class=""{lineItemType} badge badge-pill pointer exception-link reason-{lineItem.Reason.EncodeCSSClassName()}"" id=""{lineItemType}-{this.lineItemNumber}-exception-link"" onclick=""toggleVisibility('{lineItemType}-{this.lineItemNumber}-exception')""> </div>" .RemoveIndentation(4, true)); } } sb.AppendLine($@" </div>" .RemoveIndentation(4, true)); }
internal void WriteLineItem(StringBuilder sb, HtmlReportLineItem lineItem, bool failuresOnly, bool statsExpanded, bool childrenExpanded, bool header) { this.lineItemNumber++; var statementAndExplanationLink = lineItem.Statement != null || lineItem.Explanation != null; var inputLink = lineItem.Input != null; var outputLink = lineItem.Output != null; var explanationLink = lineItem.Explanation != null; var inputHtmlPreviewLink = (inputLink && lineItem.InputFormat == TextFormat.htmlpreview ? $"'iframe{lineItemNumber}input'" : "null"); var outputHtmlPreviewLink = (outputLink && lineItem.OutputFormat == TextFormat.htmlpreview ? $"'iframe{lineItemNumber}output'" : "null"); var explanationHtmlPreviewLink = (explanationLink && lineItem.ExplanationFormat == TextFormat.htmlpreview ? $"'iframe{lineItemNumber}explanation'" : "null"); var lineItemType = lineItem.TypeName.ToLower(); if (header) { lineItemType = "header"; } sb.AppendLine($@" <li class=""lineitem {lineItemType} row align-items-center"" id=""{lineItemType}-{this.lineItemNumber}"">" .RemoveIndentation(4, true)); WriteBadge(sb, lineItem, header); if (String.IsNullOrEmpty(lineItem.Name)) { lineItem.Name = "[Missing! (or Full Name Skipped)]"; } sb.AppendLine($@" <div class=""col""> <div class=""{lineItemType} name"">" .RemoveIndentation(4)); var name = lineItem.Name; if (lineItemType == "capability" || lineItemType == "testrun") { name = name.Replace(config.RootNameSkip, ""); } if (lineItem.FilePath != null) { sb.AppendLine($@" <a class=""{lineItemType} name pointer"" id=""{lineItemType}-{this.lineItemNumber}-name"" href=""{lineItem.FilePath}"">{name}</a>" .RemoveIndentation(4, true)); } else { sb.AppendLine($@" <span class=""{lineItemType} name pointer"" id=""{lineItemType}-{this.lineItemNumber}-name"" onclick=""toggleVisibility('{lineItemType}-{this.lineItemNumber}-{lineItem.ChildTypeName.ToLower()}', {inputHtmlPreviewLink}, {outputHtmlPreviewLink})"" href=""{lineItemType}-{this.lineItemNumber}-{lineItem.ChildTypeName.ToLower()}"">{name}</span>" .RemoveIndentation(4, true)); } if (lineItem.Assignments != null && lineItem.Assignments.Count > 0) { sb.Append($@" <span class=""{lineItemType} assignments"" id=""{lineItemType}-{this.lineItemNumber}-assignments"">[" .RemoveIndentation(6, true)); lineItem.Assignments.ForEach(assignment => { sb.Append($@"{(lineItem.Assignments.IndexOf(assignment)==0?"":", ")}{assignment.HtmlEncode()}"); }); sb.AppendLine($@" ]</span>" .RemoveIndentation(6, true)); } if (lineItem.Tags != null && lineItem.Tags.Count > 0) { sb.Append($@" <span class=""{lineItemType} tags"" id=""{lineItemType}-{this.lineItemNumber}-tags"">[" .RemoveIndentation(6, true)); lineItem.Tags.ForEach(tag => { sb.Append($@"{(lineItem.Tags.IndexOf(tag)==0?"":", ")}{tag.HtmlEncode()}"); }); sb.AppendLine($@" ]</span>" .RemoveIndentation(6, true)); } if (lineItemType != "step" && (!string.IsNullOrEmpty(lineItem.Explanation) || !string.IsNullOrEmpty(lineItem.Statement))) { sb.AppendLine($@" <div class=""{lineItem.TypeName}-statement-link badge pointer badge-secondary"" id=""{lineItem.TypeName}-{this.lineItemNumber}-statement-link"" onclick=""toggleVisibility('{lineItem.TypeName}-{this.lineItemNumber}-statement'); toggleVisibility('{lineItem.TypeName}-{this.lineItemNumber}-explanation', {explanationHtmlPreviewLink})""> <i class=""fas fa-info""></i> </div>" .RemoveIndentation(4, true)); } sb.AppendLine($@" </div> </div>" .RemoveIndentation(4)); WriteDuration(sb, lineItemType, lineItem.EndTime, lineItem.StartTime); sb.AppendLine($@" </li>" .RemoveIndentation(4)); if (lineItemType != "step") { WriteStats(sb, lineItemType, lineItem.ReasonStats, statsExpanded); WriteStatement(sb, lineItem); } if (lineItem.ChildTypeName != null) { var expanded = (lineItem.Outcome == Outcome.Failed && failuresOnly) || childrenExpanded; var display = expanded ? "block" : "none"; var childClassName = $"{lineItem.ChildTypeName.ToLower()} list-unstyled"; sb.AppendLine($@" <li class=""row children""> <div class=""col-12""> <ol class=""{childClassName} container-fluid"" id=""{lineItemType}-{this.lineItemNumber}-{lineItem.ChildTypeName.ToLower()}"" style=""display: {display}"">" .RemoveIndentation(4)); foreach (var item in lineItem.ChildItems) { WriteLineItem(sb, item, failuresOnly, false, false, false); } if (lineItem.Exception != null || !string.IsNullOrEmpty(lineItem.Input) || !string.IsNullOrEmpty(lineItem.Output)) { WriteStepDetails(sb, lineItem); WriteStatement(sb, lineItem); WriteException(sb, lineItem, lineItem.Exception, false); WriteLineItemContent(sb, lineItem, "input"); WriteLineItemContent(sb, lineItem, "output"); } sb.AppendLine($@" </ol> </div> </li>" .RemoveIndentation(4)); } }