1 /++
2   A module containing the HtmlReporter
3 
4   Copyright: © 2017 Szabo Bogdan
5   License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6   Authors: Szabo Bogdan
7 +/
8 module trial.reporters.html;
9 
10 import std.stdio;
11 import std.array;
12 import std.conv;
13 import std.datetime;
14 import std..string;
15 import std.algorithm;
16 import std.file;
17 import std.path;
18 
19 import trial.interfaces;
20 import trial.reporters.writer;
21 
22 /// The "html" reporter outputs a hierarchical HTML body representation of your tests.
23 class HtmlReporter : ILifecycleListener
24 {
25   private
26   {
27     bool success = true;
28     immutable string destination;
29     immutable long warningTestDuration;
30     immutable long dangerTestDuration;
31   }
32 
33   this(string destination, long warningTestDuration, long dangerTestDuration)
34   {
35     this.destination = destination;
36     this.warningTestDuration = warningTestDuration;
37     this.dangerTestDuration = dangerTestDuration;
38   }
39 
40   void begin(ulong)
41   {
42   }
43 
44   void update()
45   {
46   }
47 
48   private {
49     void relativePaths(ref SuiteResult[] results) {
50       foreach(ref result; results) {
51         relativePaths(result);
52       }
53     }
54 
55     void relativePaths(ref SuiteResult suite) {
56       relativePaths(suite.attachments);
57       
58       foreach(ref result; suite.tests) {
59         relativePaths(result);
60       }
61     }
62 
63     void relativePaths(ref TestResult step) {
64       relativePaths(step.attachments);
65 
66       foreach(ref child; step.steps) {
67         relativePaths(child);
68       }
69     }
70 
71     void relativePaths(ref StepResult step) {
72       relativePaths(step.attachments);
73 
74       foreach(ref child; step.steps) {
75         relativePaths(child);
76       }
77     }
78 
79     void relativePaths(ref Attachment[] attachments) {
80       foreach(ref attachment; attachments) {
81         attachment.file = asRelativePath(attachment.file, destination.dirName).array;
82       }
83     }
84   }
85 
86   void end(SuiteResult[] results)
87   {
88     relativePaths(results);
89 
90     immutable string trialCss = import("assets/trial.css");
91     immutable string trialJs = import("assets/trial.js");
92 
93     string content = import("templates/htmlReporter.html");
94 
95     auto assets = buildPath(destination.dirName, "assets");
96 
97     if(!destination.dirName.exists) {
98       mkdirRecurse(destination.dirName);
99     }
100 
101     if(!assets.exists) {
102       mkdirRecurse(assets);
103     }
104 
105     content = content
106       .replace("{testResult}", "[" ~ results.map!(a => a.toString).join(",") ~ "]")
107       .replace("{warningTestDuration}", warningTestDuration.to!string)
108       .replace("{dangerTestDuration}", dangerTestDuration.to!string);
109 
110 
111     std.file.write(destination, content);
112 
113     std.file.write(buildPath(assets, "trial.css"), trialCss);
114     std.file.write(buildPath(assets, "trial.js"), trialJs);
115   }
116 }
117 
118 version (unittest)
119 {
120   import fluent.asserts;
121 }
122 
123 @("it should set the result json")
124 unittest
125 {
126   auto writer = new BufferedWriter;
127   auto reporter = new HtmlReporter("trial-result.html", 0, 0);
128 
129   auto begin = Clock.currTime - 10.seconds;
130   auto end = begin + 10.seconds;
131 
132   auto testResult = new TestResult("some test");
133   testResult.begin = begin;
134   testResult.end = end;
135   testResult.status = TestResult.Status.success;
136 
137   SuiteResult[] result = [SuiteResult("Test Suite", begin, end, [testResult])];
138   reporter.end(result);
139 
140   auto text = readText("trial-result.html");
141 
142   text.should.contain(result[0].toString);
143 }