Skip to content

Commit bfc84df

Browse files
grasshopper7mpkorstanje
authored andcommitted
[Core] Merge cucumber-html into cucumber-core (#1650)
Fixes: #1647
1 parent cda887a commit bfc84df

File tree

9 files changed

+464
-5
lines changed

9 files changed

+464
-5
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ target/
1414
tmp/
1515
gen-external-apklibs/
1616
out/
17+
some/
1718

1819
# Build & test droppings
1920
pom.xml.releaseBackup

core/pom.xml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@
2121
</properties>
2222

2323
<dependencies>
24-
<dependency>
25-
<groupId>io.cucumber</groupId>
26-
<artifactId>cucumber-html</artifactId>
27-
</dependency>
2824
<dependency>
2925
<groupId>io.cucumber</groupId>
3026
<artifactId>gherkin</artifactId>

core/src/main/java/cucumber/runtime/formatter/HTMLFormatter.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,12 @@ final class HTMLFormatter implements EventListener {
5353
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
5454
private static final String JS_FORMATTER_VAR = "formatter";
5555
private static final String JS_REPORT_FILENAME = "report.js";
56-
private static final String[] TEXT_ASSETS = new String[]{"/cucumber/formatter/formatter.js", "/cucumber/formatter/index.html", "/cucumber/formatter/jquery-1.8.2.min.js", "/cucumber/formatter/style.css"};
56+
private static final String[] TEXT_ASSETS = new String[]{
57+
"/io/cucumber/formatter/html/formatter.js",
58+
"/io/cucumber/formatter/html/index.html",
59+
"/io/cucumber/formatter/html/jquery-1.8.2.min.js",
60+
"/io/cucumber/formatter/html/style.css"
61+
};
5762
private static final Map<String, String> MIME_TYPES_EXTENSIONS = new HashMap<String, String>() {
5863
{
5964
put("image/bmp", "bmp");
Loading
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
var CucumberHTML = {};
2+
3+
CucumberHTML.DOMFormatter = function(rootNode) {
4+
var currentUri;
5+
var currentFeature;
6+
var currentElement;
7+
var currentSteps;
8+
9+
var currentStepIndex;
10+
var currentStep;
11+
var $templates = $(CucumberHTML.templates);
12+
13+
this.uri = function(uri) {
14+
currentUri = uri;
15+
};
16+
17+
this.feature = function(feature) {
18+
currentFeature = blockElement(rootNode, feature, 'feature');
19+
};
20+
21+
this.background = function(background) {
22+
currentElement = featureElement(background, 'background');
23+
currentStepIndex = 1;
24+
};
25+
26+
this.scenario = function(scenario) {
27+
currentElement = featureElement(scenario, 'scenario');
28+
currentStepIndex = 1;
29+
};
30+
31+
this.scenarioOutline = function(scenarioOutline) {
32+
currentElement = featureElement(scenarioOutline, 'scenario_outline');
33+
currentStepIndex = 1;
34+
};
35+
36+
this.step = function(step) {
37+
var stepElement = $('.step', $templates).clone();
38+
stepElement.appendTo(currentSteps);
39+
populate(stepElement, step, 'step');
40+
41+
if (step.doc_string) {
42+
docString = $('.doc_string', $templates).clone();
43+
docString.appendTo(stepElement);
44+
// TODO: use a syntax highlighter based on the content_type
45+
docString.text(step.doc_string.value);
46+
}
47+
if (step.rows) {
48+
dataTable = $('.data_table', $templates).clone();
49+
dataTable.appendTo(stepElement);
50+
var tBody = dataTable.find('tbody');
51+
$.each(step.rows, function(index, row) {
52+
var tr = $('<tr></tr>').appendTo(tBody);
53+
$.each(row.cells, function(index, cell) {
54+
var td = $('<td>' + cell + '</td>').appendTo(tBody);
55+
});
56+
});
57+
}
58+
};
59+
60+
this.examples = function(examples) {
61+
var examplesElement = blockElement(currentElement.children('details'), examples, 'examples');
62+
var examplesTable = $('.examples_table', $templates).clone();
63+
examplesTable.appendTo(examplesElement.children('details'));
64+
65+
$.each(examples.rows, function(index, row) {
66+
var parent = index == 0 ? examplesTable.find('thead') : examplesTable.find('tbody');
67+
var tr = $('<tr></tr>').appendTo(parent);
68+
$.each(row.cells, function(index, cell) {
69+
var td = $('<td>' + cell + '</td>').appendTo(tr);
70+
});
71+
});
72+
};
73+
74+
this.match = function(match) {
75+
currentStep = currentSteps.find('li:nth-child(' + currentStepIndex + ')');
76+
currentStepIndex++;
77+
};
78+
79+
this.result = function(result) {
80+
currentStep.addClass(result.status);
81+
if (result.error_message != '') {
82+
populateStepError(currentStep, result.error_message);
83+
}
84+
currentElement.addClass(result.status);
85+
var isLastStep = currentSteps.find('li:nth-child(' + currentStepIndex + ')').length == 0;
86+
if (isLastStep) {
87+
if (currentSteps.find('.failed').length == 0) {
88+
// No failed steps. Collapse it.
89+
currentElement.find('details').removeAttr('open');
90+
} else {
91+
currentElement.find('details').attr('open', 'open');
92+
}
93+
}
94+
};
95+
96+
this.embedding = function(mimeType, data) {
97+
if (currentStepIndex == 1) {
98+
this.dummyStep();
99+
}
100+
if (mimeType.match(/^image\//))
101+
{
102+
currentStep.append('<img src="' + data + '">');
103+
}
104+
else if (mimeType.match(/^video\//))
105+
{
106+
currentStep.append('<video src="' + data + '" type="' + mimeType + '" autobuffer controls>Your browser doesn\'t support video.</video>');
107+
}
108+
else if (mimeType.match(/^text\//))
109+
{
110+
this.write(data);
111+
}
112+
};
113+
114+
this.write = function(text) {
115+
if (currentStepIndex == 1) {
116+
this.dummyStep();
117+
}
118+
currentStep.append('<pre class="embedded-text">' + text + '</pre>');
119+
};
120+
121+
this.before = function(before) {
122+
this.handleHookResult(before);
123+
};
124+
125+
this.after = function(after) {
126+
this.handleHookResult(after);
127+
};
128+
129+
this.beforestep = function(beforestep) {
130+
this.handleHookResult(beforestep);
131+
};
132+
133+
this.afterstep = function(afterstep) {
134+
this.handleHookResult(afterstep);
135+
};
136+
137+
this.handleHookResult = function(hook) {
138+
if (hook.status != 'passed' && hook.error_message != '') {
139+
this.dummyStep();
140+
currentStep.addClass(hook.status);
141+
currentElement.addClass(hook.status);
142+
populateStepError(currentStep, hook.error_message);
143+
}
144+
};
145+
146+
this.dummyStep = function() {
147+
var stepElement = $('.step', $templates).clone();
148+
stepElement.appendTo(currentSteps);
149+
populate(stepElement, {keyword: '', name: ''}, 'step');
150+
currentStep = currentSteps.find('li:nth-child(' + currentStepIndex + ')');
151+
currentStepIndex++;
152+
};
153+
154+
function featureElement(statement, itemtype) {
155+
var e = blockElement(currentFeature.children('details'), statement, itemtype);
156+
157+
currentSteps = $('.steps', $templates).clone();
158+
currentSteps.appendTo(e.children('details'));
159+
160+
return e;
161+
}
162+
163+
function blockElement(parent, statement, itemtype) {
164+
var e = $('.blockelement', $templates).clone();
165+
e.appendTo(parent);
166+
return populate(e, statement, itemtype);
167+
}
168+
169+
function populate(e, statement, itemtype) {
170+
populateTags(e, statement.tags);
171+
populateComments(e, statement.comments);
172+
e.find('.keyword').text(statement.keyword);
173+
e.find('.name').text(statement.name);
174+
e.find('.description').text(statement.description);
175+
e.attr('itemtype', 'http://cukes.info/microformat/' + itemtype);
176+
e.addClass(itemtype);
177+
return e;
178+
}
179+
180+
function populateComments(e, comments) {
181+
if (comments !== undefined) {
182+
var commentsNode = $('.comments', $templates).clone().prependTo(e.find('.header'));
183+
$.each(comments, function(index, comment) {
184+
var commentNode = $('.comment', $templates).clone().appendTo(commentsNode);
185+
commentNode.text(comment.value);
186+
});
187+
}
188+
}
189+
190+
function populateTags(e, tags) {
191+
if (tags !== undefined) {
192+
var tagsNode = $('.tags', $templates).clone().prependTo(e.find('.header'));
193+
$.each(tags, function(index, tag) {
194+
var tagNode = $('.tag', $templates).clone().appendTo(tagsNode);
195+
tagNode.text(tag.name);
196+
});
197+
}
198+
}
199+
200+
function populateStepError(e, error) {
201+
if (error !== undefined) {
202+
errorNode = $('.error', $templates).clone().appendTo(e);
203+
errorNode.text(error);
204+
}
205+
}
206+
};
207+
208+
CucumberHTML.templates = '<div>\
209+
<section class="blockelement" itemscope>\
210+
<details open>\
211+
<summary class="header">\
212+
<span class="keyword" itemprop="keyword">Keyword</span>: <span itemprop="name" class="name">This is the block name</span>\
213+
</summary>\
214+
<div itemprop="description" class="description">The description goes here</div>\
215+
</details>\
216+
</section>\
217+
\
218+
<ol class="steps"></ol>\
219+
\
220+
<ol>\
221+
<li class="step"><div class="header"></div><span class="keyword" itemprop="keyword">Keyword</span><span class="name" itemprop="name">Name</span></li>\
222+
</ol>\
223+
\
224+
<pre class="doc_string"></pre>\
225+
\
226+
<pre class="error"></pre>\
227+
\
228+
<table class="data_table">\
229+
<tbody>\
230+
</tbody>\
231+
</table>\
232+
\
233+
<table class="examples_table">\
234+
<thead></thead>\
235+
<tbody></tbody>\
236+
</table>\
237+
\
238+
<section class="embed">\
239+
<img itemprop="screenshot" class="screenshot" />\
240+
</section>\
241+
<div class="tags"></div>\
242+
<span class="tag"></span>\
243+
<div class="comments"></div>\
244+
<div class="comment"></div>\
245+
<div>';
246+
247+
if (typeof module !== 'undefined') {
248+
module.exports = CucumberHTML;
249+
} else if (typeof define !== 'undefined') {
250+
define([], function() { return CucumberHTML; });
251+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Cucumber Features</title>
6+
<link href="style.css" rel="stylesheet">
7+
<script src="jquery-1.8.2.min.js"></script>
8+
<script src="formatter.js"></script>
9+
<script src="report.js"></script>
10+
</head>
11+
<body>
12+
<div class="cucumber-report"></div>
13+
</body>
14+
</html>

core/src/main/resources/io/cucumber/formatter/html/jquery-1.8.2.min.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
$(document).ready(function() {
2+
var formatter = new CucumberHTML.DOMFormatter($('.cucumber-report'));
3+
var N = document.location.hash ? parseInt(document.location.hash.substring(1)) : 1;
4+
var start = new Date().getTime();
5+
for(var n = 0; n < N; n++) {
6+
formatter.uri('report.feature');
7+
formatter.feature({
8+
comments: [
9+
{value: "# A comment"},
10+
{value: "# Another comment"}
11+
],
12+
keyword:'Feature',
13+
name:'Generating html report',
14+
description: 'It could be useful to have an html report to facilitate documentation reading.\n\nEspecially when the whitespace formatting is preserved',
15+
line:2
16+
});
17+
18+
formatter.background({
19+
comments: [
20+
{value: "# Background comment"}
21+
],
22+
keyword:'Background',
23+
name:'Setting up the context',
24+
line:3,
25+
description: 'These steps will be executed before each scenario.'
26+
});
27+
formatter.write('Output from before hook');
28+
formatter.embedding('text/plain', 'Text embedding from before hook');
29+
formatter.step({keyword:'Given ', name:'I have a background', line:4});
30+
formatter.step({keyword:'And ', name:'I set some context', line: 5});
31+
formatter.match({uri:'report.feature'});
32+
formatter.result({status:'passed', duration: 0});
33+
formatter.match({uri:'report.feature'});
34+
formatter.result({status:'passed', duration: 0});
35+
36+
formatter.before({status: 'passed', duration: 668816288});
37+
formatter.scenario({"tags":[{"name":"@foo","line":3},{"name":"@bar","line":4},{"name":"@doh","line":5}], keyword:'Scenario', name: 'Creating a simple report', line: 6});
38+
formatter.step({comments: [
39+
{value: "# Step comment 1"},
40+
{value: "# Step comment 2"}
41+
],keyword:'Given ', name:'I have a feature', line: 7, doc_string:{value: "A\ndoc string\non several lines", content_type:"text/plain", line:8}});
42+
formatter.step({keyword:'When ', name:'I format it', line: 11});
43+
formatter.step({keyword:'Then ', name:'It should look pretty', line: 12});
44+
formatter.step({keyword:'And ', name:'It should show tables', line: 13, rows: [{cells:['name', 'price'], line: 14}, {cells:['milk', '9'], line: 15}]});
45+
formatter.match({uri:'report.feature'});
46+
formatter.result({status:'passed', duration: 0});
47+
formatter.match({uri:'report.feature'});
48+
formatter.result({status:'failed', error_message:'something went wrong...', duration: 0});
49+
formatter.embedding('image/png', 'bubble_256x256.png');
50+
formatter.match({uri:'report.feature'});
51+
formatter.result({status:'undefined', duration: 0});
52+
formatter.embedding('text/plain', 'Look at this video');
53+
formatter.embedding('video/mp4', 'http://www.808.dk/pics/video/gizmo.mp4');
54+
formatter.embedding('text/plain', 'Look at this multi-line embedding\nReal fancy');
55+
formatter.write('What a nice helicopter');
56+
formatter.match({uri:'report.feature'});
57+
formatter.result({status:'skipped', duration: 0});
58+
formatter.after({status: 'passed', duration: 668816288});
59+
60+
formatter.scenarioOutline({keyword:'Scenario Outline', name: 'Scenario with examples', description:'It should be good to format outlined arguments.', line: 16});
61+
formatter.step({keyword:'Given ', name:'I have a <name> which costs <price>', line: 17});
62+
formatter.examples({description:'', name:'Some good examples', keyword:'Examples', line: 18, rows:[{cells:['name', 'price'], line:19}, {cells:['milk', '9'], line:20}, {cells:['bread', '7'], line:21}, {cells:['soap', '5'], line:22}]});
63+
formatter.before({status: 'passed', duration: 668816288});
64+
formatter.match({uri:'report.feature'});
65+
formatter.result({status:'passed', duration: 0});
66+
formatter.match({uri:'report.feature'});
67+
formatter.result({status:'passed', duration: 0});
68+
formatter.match({uri:'report.feature'});
69+
formatter.result({status:'failed', error_message:'I didn\'t do it.', duration: 0});
70+
formatter.after({status: 'failed', duration: 668816288, "error_message": 'com.example.MyDodgyException: Widget underflow\r\n\tat org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:264)\r\n\tat com.example.WidgetFurbicator.furbicateWidgets(WidgetFurbicator.java:678)'});
71+
72+
formatter.scenario({"tags":[{"name":"@stephooks","line":24}], keyword:'Scenario', name: 'Scenario with step hooks', line: 25});
73+
formatter.before({status: 'passed', duration: 668816288});
74+
formatter.beforestep({status: 'passed', duration: 668816288});
75+
formatter.step({keyword:'Given ', name:'step 1', line: 26});
76+
formatter.match({uri:'report.feature'});
77+
formatter.result({status:'passed', duration: 0});
78+
formatter.afterstep({status: 'passed', duration: 668816288});
79+
formatter.beforestep({status: 'failed', duration: 668816288, "error_message": 'com.example.MyDodgyException: Widget underflow\r\n\tat org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:264)\r\n\tat com.example.StepDefinitions.beforeStepHook()'});
80+
formatter.step({keyword:'When ', name:'step 2', line: 27});
81+
formatter.match({uri:'report.feature'});
82+
formatter.result({status:'skipped', duration: 0});
83+
formatter.afterstep({status: 'failed', duration: 668816288, "error_message": 'com.example.MyDodgyException: Widget underflow\r\n\tat org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:264)\r\n\tat com.example.StepDefinitions.afterStepHook()'});
84+
formatter.beforestep({status: 'skipped', duration: 0});
85+
formatter.step({keyword:'Then ', name:'step 3', line: 28});
86+
formatter.match({uri:'report.feature'});
87+
formatter.result({status:'skipped', duration: 0});
88+
formatter.afterstep({status: 'skipped', duration: 0});
89+
formatter.after({status: 'passed', duration: 668816288});
90+
}
91+
console.log('Rendered %s features in %s ms', N, new Date().getTime() - start);
92+
93+
});

0 commit comments

Comments
 (0)