Skip to content

Commit 8093735

Browse files
author
Jake Ginnivan
committed
Example of rich formatting. Formatters also do not have to be formatting failing data, it can be conforming data too
1 parent 768b444 commit 8093735

13 files changed

+248
-60
lines changed

TestStack.ConventionTests/Internal/ConventionContext.cs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,33 @@ public ConventionResult[] ConventionResults
2828
get { return results.ToArray(); }
2929
}
3030

31+
string IConventionFormatContext.FormatDataAsHtml(object data)
32+
{
33+
var formatter = GetReportDataFormatterFor(data);
34+
return formatter.FormatHtml(data);
35+
}
36+
3137
ITestResultProcessor IConventionFormatContext.TestResultProcessor
3238
{
3339
get { return testResultProcessor; }
3440
}
3541

36-
string IConventionFormatContext.FormatDataAsString(object failingData)
42+
string IConventionFormatContext.FormatDataAsString(object data)
43+
{
44+
var formatter = GetReportDataFormatterFor(data);
45+
46+
return formatter.FormatString(data);
47+
}
48+
49+
IReportDataFormatter GetReportDataFormatterFor(object data)
3750
{
38-
IReportDataFormatter formatter = formatters.FirstOrDefault(f => f.CanFormat(failingData));
51+
IReportDataFormatter formatter = formatters.FirstOrDefault(f => f.CanFormat(data));
3952
if (formatter == null)
4053
{
4154
throw new NoDataFormatterFoundException(
42-
failingData.GetType().Name +
43-
" has no formatter, add one with `Convention.Formatters.Add(new MyDataFormatter());`");
55+
data.GetType().Name + " has no formatter, add one with `Convention.Formatters.Add(new MyDataFormatter());`");
4456
}
45-
46-
return formatter.FormatString(failingData);
57+
return formatter;
4758
}
4859

4960
void IConventionResultContext.Is<TResult>(string resultTitle, IEnumerable<TResult> failingData)

TestStack.ConventionTests/Internal/IConventionFormatContext.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
public interface IConventionFormatContext
66
{
7-
string FormatDataAsString(object failingData);
8-
ITestResultProcessor TestResultProcessor { get; }
7+
string FormatDataAsString(object data);
8+
string FormatDataAsHtml(object data);
9+
ITestResultProcessor TestResultProcessor { get; }
910
}
1011
}

TestStack.ConventionTests/Reporting/ConvertibleFormatter.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,21 @@ namespace TestStack.ConventionTests.Reporting
66

77
public class ConvertibleFormatter : IReportDataFormatter
88
{
9-
public bool CanFormat(object failingData)
9+
public bool CanFormat(object data)
1010
{
11-
return failingData is IConvertible;
11+
return data is IConvertible;
1212
}
1313

14-
public string FormatString(object failingData)
14+
public string FormatString(object data)
1515
{
16-
var convertible = failingData as IConvertible;
16+
var convertible = data as IConvertible;
1717
Debug.Assert(convertible != null, "convertible != null");
1818
return convertible.ToString(CultureInfo.InvariantCulture);
1919
}
20+
21+
public string FormatHtml(object data)
22+
{
23+
return FormatString(data);
24+
}
2025
}
2126
}

TestStack.ConventionTests/Reporting/FallbackFormatter.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@
22
{
33
public class FallbackFormatter : IReportDataFormatter
44
{
5-
public bool CanFormat(object failingData)
5+
public bool CanFormat(object data)
66
{
77
return true;
88
}
99

10-
public string FormatString(object failingData)
10+
public string FormatString(object data)
1111
{
12-
if (failingData == null)
12+
if (data == null)
1313
return "<null>";
14-
return failingData.ToString();
14+
return data.ToString();
15+
}
16+
17+
public string FormatHtml(object data)
18+
{
19+
return FormatString(data);
1520
}
1621
}
1722
}

TestStack.ConventionTests/Reporting/GroupedByDataTypeRendererBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ public abstract class GroupedByDataTypeRendererBase : AggregatedRenderer
88
{
99
protected override void Process(IConventionFormatContext context)
1010
{
11-
Process(AggregatedReports.OrderBy(c => c.ConventionTitle).GroupBy(r => r.DataDescription));
11+
Process(context, AggregatedReports.OrderBy(c => c.ConventionTitle).GroupBy(r => r.DataDescription));
1212
}
1313

14-
protected abstract void Process(IEnumerable<IGrouping<string, ConventionResult>> resultsGroupedByDataType);
14+
protected abstract void Process(IConventionFormatContext context, IEnumerable<IGrouping<string, ConventionResult>> resultsGroupedByDataType);
1515
}
1616
}

TestStack.ConventionTests/Reporting/HtmlReportRenderer.cs

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,34 @@ public HtmlReportRenderer(string assemblyDirectory)
1616
file = Path.Combine(assemblyDirectory, "Conventions.htm");
1717
}
1818

19-
protected override void Process(IEnumerable<IGrouping<string, ConventionResult>> resultsGroupedByDataType)
19+
protected override void Process(IConventionFormatContext context, IEnumerable<IGrouping<string, ConventionResult>> resultsGroupedByDataType)
2020
{
2121
var sb = new StringBuilder();
2222
var html = new HtmlTextWriter(new StringWriter(sb));
2323
html.WriteLine("<!DOCTYPE html>");
2424
html.RenderBeginTag(HtmlTextWriterTag.Html); // <html>
2525
html.RenderBeginTag(HtmlTextWriterTag.Head); // <head>
26+
27+
28+
html.AddAttribute("href", "http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css");
29+
html.AddAttribute("rel", "stylesheet");
30+
html.RenderBeginTag(HtmlTextWriterTag.Link);
31+
html.RenderEndTag();
32+
html.AddAttribute("href", "http://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css");
33+
html.AddAttribute("rel", "stylesheet");
34+
2635
html.Write(@"
27-
<style type=""text/css"">
28-
h1 {
29-
color: #3c6eb4;
30-
}
36+
<style type=""text/css"">
37+
h1 {
38+
color: #3c6eb4;
39+
border-bottom: 1px solid #3c6eb4;
40+
}
41+
</style>");
42+
43+
html.RenderBeginTag(HtmlTextWriterTag.Link);
44+
html.RenderEndTag();
3145

32-
h2 {
33-
background: #f1f1f1;
34-
}
35-
</style>");
3646
html.RenderEndTag(); // </head>
37-
html.WriteLine();
3847
html.RenderBeginTag(HtmlTextWriterTag.Body); // <body>
3948

4049
html.RenderBeginTag(HtmlTextWriterTag.H1);
@@ -43,23 +52,51 @@ protected override void Process(IEnumerable<IGrouping<string, ConventionResult>>
4352

4453
foreach (var conventionReport in resultsGroupedByDataType)
4554
{
46-
html.RenderBeginTag(HtmlTextWriterTag.P);
4755
html.RenderBeginTag(HtmlTextWriterTag.Div);
56+
html.AddAttribute("style", "margin-left:20px;border-bottom: 1px solid");
4857
html.RenderBeginTag(HtmlTextWriterTag.H2);
4958
html.Write("Conventions for '<strong>{0}</strong>'", conventionReport.Key);
5059
html.RenderEndTag();
51-
html.RenderBeginTag(HtmlTextWriterTag.Ul);
5260
foreach (var conventionResult in conventionReport)
5361
{
54-
html.RenderBeginTag(HtmlTextWriterTag.Li);
62+
var targetId =
63+
conventionResult.ConventionTitle.Replace("'", string.Empty).Replace(" ", string.Empty).Replace(".", string.Empty) +
64+
conventionResult.DataDescription.Replace("'", string.Empty).Replace(" ", string.Empty).Replace(".", string.Empty);
65+
html.AddAttribute("style", "margin-left:20px;");
66+
html.RenderBeginTag(HtmlTextWriterTag.H4);
67+
68+
html.AddAttribute("class", "menu-toggle");
69+
html.AddAttribute("data-toggle", "collapse");
70+
html.AddAttribute("data-target", "." + targetId);
71+
html.RenderBeginTag(HtmlTextWriterTag.A);
72+
html.AddAttribute("class", "icon-angle-down");
73+
html.RenderBeginTag(HtmlTextWriterTag.I);
74+
html.RenderEndTag();
75+
html.RenderEndTag();
5576
html.Write(conventionResult.ConventionTitle);
5677
html.RenderEndTag();
78+
html.AddAttribute("class", targetId + " collapse");
79+
html.AddAttribute("style", "margin-left:20px;");
80+
html.RenderBeginTag(HtmlTextWriterTag.Div);
81+
html.RenderBeginTag(HtmlTextWriterTag.Ul);
82+
foreach (var o in conventionResult.Data)
83+
{
84+
html.RenderBeginTag(HtmlTextWriterTag.Li);
85+
html.Write(context.FormatDataAsHtml(o));
86+
html.RenderEndTag();
87+
}
88+
html.RenderEndTag();
89+
html.RenderEndTag();
5790
}
5891
html.RenderEndTag();
59-
html.RenderEndTag();
60-
html.RenderEndTag();
6192
}
6293

94+
html.AddAttribute("src", "http://code.jquery.com/jquery.js");
95+
html.RenderBeginTag(HtmlTextWriterTag.Script);
96+
html.RenderEndTag();
97+
html.AddAttribute("src", "http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js");
98+
html.RenderBeginTag(HtmlTextWriterTag.Script);
99+
html.RenderEndTag();
63100
html.RenderEndTag(); // </body>
64101
html.RenderEndTag(); // </html>
65102
html.Flush();

TestStack.ConventionTests/Reporting/IReportDataFormatter.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
{
33
public interface IReportDataFormatter
44
{
5-
bool CanFormat(object failingData);
6-
string FormatString(object failingData);
5+
bool CanFormat(object data);
6+
string FormatString(object data);
7+
string FormatHtml(object data);
78
}
89
}

TestStack.ConventionTests/Reporting/MarkdownReportRenderer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public MarkdownReportRenderer(string assemblyDirectory)
1515
file = Path.Combine(assemblyDirectory, "Conventions.md");
1616
}
1717

18-
protected override void Process(IEnumerable<IGrouping<string, ConventionResult>> resultsGroupedByDataType)
18+
protected override void Process(IConventionFormatContext context, IEnumerable<IGrouping<string, ConventionResult>> resultsGroupedByDataType)
1919
{
2020
var sb = new StringBuilder();
2121
sb.AppendLine("# Project Conventions");
Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,123 @@
11
namespace TestStack.ConventionTests.Reporting
22
{
3+
using System;
34
using System.Reflection;
5+
using System.Text;
46

57
public class MethodInfoDataFormatter : IReportDataFormatter
68
{
7-
public bool CanFormat(object failingData)
9+
public bool CanFormat(object data)
810
{
9-
return failingData is MethodInfo;
11+
return data is MethodInfo;
1012
}
1113

12-
public string FormatString(object failingData)
14+
public string FormatString(object data)
1315
{
14-
var methodInfo = (MethodInfo)failingData;
16+
var methodInfo = (MethodInfo)data;
1517

1618
return methodInfo.DeclaringType + "." + methodInfo.Name;
1719
}
20+
21+
public string FormatHtml(object data)
22+
{
23+
const string keywordFormat = "<span style=\"color: #0000FF\">{0}</span>";
24+
const string typeFormat = "<span style=\"color: #2B91AF\">{0}</span>";
25+
26+
var methodInfo = (MethodInfo)data;
27+
var sb = new StringBuilder();
28+
var declaringType = methodInfo.DeclaringType;
29+
sb.AppendFormat("{0} {1}.{2} {{ ",
30+
string.Format(keywordFormat, "class"),
31+
declaringType.Namespace,
32+
string.Format(typeFormat, declaringType.Name));
33+
34+
AppendAccess(methodInfo, sb, keywordFormat);
35+
36+
if (methodInfo.IsVirtual)
37+
{
38+
sb.AppendFormat(keywordFormat, "virtual");
39+
sb.Append(" ");
40+
}
41+
42+
AppendMethodName(methodInfo, sb);
43+
sb.Append(" (...)");
44+
sb.Append("}}");
45+
46+
return sb.ToString();
47+
}
48+
49+
void AppendMethodName(MethodInfo methodInfo, StringBuilder sb)
50+
{
51+
sb.Append(methodInfo.Name);
52+
bool firstParam = true;
53+
if (methodInfo.IsGenericMethod)
54+
{
55+
sb.Append("<");
56+
foreach (var g in methodInfo.GetGenericArguments())
57+
{
58+
if (firstParam)
59+
firstParam = false;
60+
else
61+
sb.Append(", ");
62+
sb.Append(TypeName(g));
63+
}
64+
sb.Append(">");
65+
}
66+
}
67+
68+
void AppendAccess(MethodInfo method, StringBuilder sb, string format = "{0}")
69+
{
70+
if (method.IsPublic)
71+
sb.AppendFormat(format, "public ");
72+
else if (method.IsPrivate)
73+
sb.AppendFormat("private ");
74+
else if (method.IsAssembly)
75+
sb.AppendFormat("internal ");
76+
if (method.IsFamily)
77+
sb.AppendFormat("protected ");
78+
if (method.IsStatic)
79+
sb.AppendFormat("static ");
80+
}
81+
82+
static string TypeName(Type type)
83+
{
84+
var nullableType = Nullable.GetUnderlyingType(type);
85+
if (nullableType != null)
86+
return nullableType.Name + "?";
87+
88+
if (!type.IsGenericType)
89+
switch (type.Name)
90+
{
91+
case "String":
92+
return "string";
93+
case "Int32":
94+
return "int";
95+
case "Decimal":
96+
return "decimal";
97+
case "Object":
98+
return "object";
99+
case "Void":
100+
return "void";
101+
default:
102+
{
103+
return string.IsNullOrWhiteSpace(type.FullName) ? type.Name : type.FullName;
104+
}
105+
}
106+
107+
var sb = new StringBuilder(type.Name.Substring(0,
108+
type.Name.IndexOf('`'))
109+
);
110+
sb.Append('<');
111+
var first = true;
112+
foreach (var t in type.GetGenericArguments())
113+
{
114+
if (!first)
115+
sb.Append(',');
116+
sb.Append(TypeName(t));
117+
first = false;
118+
}
119+
sb.Append('>');
120+
return sb.ToString();
121+
}
18122
}
19123
}

TestStack.ConventionTests/Reporting/ProjectFileFormatter.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@ namespace TestStack.ConventionTests.Reporting
44

55
public class ProjectFileFormatter : IReportDataFormatter
66
{
7-
public bool CanFormat(object failingData)
7+
public bool CanFormat(object data)
88
{
9-
return failingData is ProjectFileItem;
9+
return data is ProjectFileItem;
1010
}
1111

12-
public string FormatString(object failingData)
12+
public string FormatString(object data)
1313
{
14-
return ((ProjectFileItem)failingData).FilePath;
15-
}
14+
return ((ProjectFileItem)data).FilePath;
15+
}
16+
17+
public string FormatHtml(object data)
18+
{
19+
return ((ProjectFileItem) data).FilePath;
20+
}
1621
}
1722
}

0 commit comments

Comments
 (0)