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 }