Skip to content

[Core] Merge cucumber-html into cucumber-core #1650

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ target/
tmp/
gen-external-apklibs/
out/
some/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should really figure out who creates this directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HTMLFormatterTest is the culprit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make a seperate PR to fix that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
some/


# Build & test droppings
pom.xml.releaseBackup
Expand Down
4 changes: 0 additions & 4 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@
</properties>

<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-html</artifactId>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>gherkin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ final class HTMLFormatter implements EventListener {
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
private static final String JS_FORMATTER_VAR = "formatter";
private static final String JS_REPORT_FILENAME = "report.js";
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"};
private static final String[] TEXT_ASSETS = new String[]{
"/io/cucumber/formatter/html/formatter.js",
"/io/cucumber/formatter/html/index.html",
"/io/cucumber/formatter/html/jquery-1.8.2.min.js",
"/io/cucumber/formatter/html/style.css"
};
private static final Map<String, String> MIME_TYPES_EXTENSIONS = new HashMap<String, String>() {
{
put("image/bmp", "bmp");
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
251 changes: 251 additions & 0 deletions core/src/main/resources/io/cucumber/formatter/html/formatter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
var CucumberHTML = {};

CucumberHTML.DOMFormatter = function(rootNode) {
var currentUri;
var currentFeature;
var currentElement;
var currentSteps;

var currentStepIndex;
var currentStep;
var $templates = $(CucumberHTML.templates);

this.uri = function(uri) {
currentUri = uri;
};

this.feature = function(feature) {
currentFeature = blockElement(rootNode, feature, 'feature');
};

this.background = function(background) {
currentElement = featureElement(background, 'background');
currentStepIndex = 1;
};

this.scenario = function(scenario) {
currentElement = featureElement(scenario, 'scenario');
currentStepIndex = 1;
};

this.scenarioOutline = function(scenarioOutline) {
currentElement = featureElement(scenarioOutline, 'scenario_outline');
currentStepIndex = 1;
};

this.step = function(step) {
var stepElement = $('.step', $templates).clone();
stepElement.appendTo(currentSteps);
populate(stepElement, step, 'step');

if (step.doc_string) {
docString = $('.doc_string', $templates).clone();
docString.appendTo(stepElement);
// TODO: use a syntax highlighter based on the content_type
docString.text(step.doc_string.value);
}
if (step.rows) {
dataTable = $('.data_table', $templates).clone();
dataTable.appendTo(stepElement);
var tBody = dataTable.find('tbody');
$.each(step.rows, function(index, row) {
var tr = $('<tr></tr>').appendTo(tBody);
$.each(row.cells, function(index, cell) {
var td = $('<td>' + cell + '</td>').appendTo(tBody);
});
});
}
};

this.examples = function(examples) {
var examplesElement = blockElement(currentElement.children('details'), examples, 'examples');
var examplesTable = $('.examples_table', $templates).clone();
examplesTable.appendTo(examplesElement.children('details'));

$.each(examples.rows, function(index, row) {
var parent = index == 0 ? examplesTable.find('thead') : examplesTable.find('tbody');
var tr = $('<tr></tr>').appendTo(parent);
$.each(row.cells, function(index, cell) {
var td = $('<td>' + cell + '</td>').appendTo(tr);
});
});
};

this.match = function(match) {
currentStep = currentSteps.find('li:nth-child(' + currentStepIndex + ')');
currentStepIndex++;
};

this.result = function(result) {
currentStep.addClass(result.status);
if (result.error_message != '') {
populateStepError(currentStep, result.error_message);
}
currentElement.addClass(result.status);
var isLastStep = currentSteps.find('li:nth-child(' + currentStepIndex + ')').length == 0;
if (isLastStep) {
if (currentSteps.find('.failed').length == 0) {
// No failed steps. Collapse it.
currentElement.find('details').removeAttr('open');
} else {
currentElement.find('details').attr('open', 'open');
}
}
};

this.embedding = function(mimeType, data) {
if (currentStepIndex == 1) {
this.dummyStep();
}
if (mimeType.match(/^image\//))
{
currentStep.append('<img src="' + data + '">');
}
else if (mimeType.match(/^video\//))
{
currentStep.append('<video src="' + data + '" type="' + mimeType + '" autobuffer controls>Your browser doesn\'t support video.</video>');
}
else if (mimeType.match(/^text\//))
{
this.write(data);
}
};

this.write = function(text) {
if (currentStepIndex == 1) {
this.dummyStep();
}
currentStep.append('<pre class="embedded-text">' + text + '</pre>');
};

this.before = function(before) {
this.handleHookResult(before);
};

this.after = function(after) {
this.handleHookResult(after);
};

this.beforestep = function(beforestep) {
this.handleHookResult(beforestep);
};

this.afterstep = function(afterstep) {
this.handleHookResult(afterstep);
};

this.handleHookResult = function(hook) {
if (hook.status != 'passed' && hook.error_message != '') {
this.dummyStep();
currentStep.addClass(hook.status);
currentElement.addClass(hook.status);
populateStepError(currentStep, hook.error_message);
}
};

this.dummyStep = function() {
var stepElement = $('.step', $templates).clone();
stepElement.appendTo(currentSteps);
populate(stepElement, {keyword: '', name: ''}, 'step');
currentStep = currentSteps.find('li:nth-child(' + currentStepIndex + ')');
currentStepIndex++;
};

function featureElement(statement, itemtype) {
var e = blockElement(currentFeature.children('details'), statement, itemtype);

currentSteps = $('.steps', $templates).clone();
currentSteps.appendTo(e.children('details'));

return e;
}

function blockElement(parent, statement, itemtype) {
var e = $('.blockelement', $templates).clone();
e.appendTo(parent);
return populate(e, statement, itemtype);
}

function populate(e, statement, itemtype) {
populateTags(e, statement.tags);
populateComments(e, statement.comments);
e.find('.keyword').text(statement.keyword);
e.find('.name').text(statement.name);
e.find('.description').text(statement.description);
e.attr('itemtype', 'http://cukes.info/microformat/' + itemtype);
e.addClass(itemtype);
return e;
}

function populateComments(e, comments) {
if (comments !== undefined) {
var commentsNode = $('.comments', $templates).clone().prependTo(e.find('.header'));
$.each(comments, function(index, comment) {
var commentNode = $('.comment', $templates).clone().appendTo(commentsNode);
commentNode.text(comment.value);
});
}
}

function populateTags(e, tags) {
if (tags !== undefined) {
var tagsNode = $('.tags', $templates).clone().prependTo(e.find('.header'));
$.each(tags, function(index, tag) {
var tagNode = $('.tag', $templates).clone().appendTo(tagsNode);
tagNode.text(tag.name);
});
}
}

function populateStepError(e, error) {
if (error !== undefined) {
errorNode = $('.error', $templates).clone().appendTo(e);
errorNode.text(error);
}
}
};

CucumberHTML.templates = '<div>\
<section class="blockelement" itemscope>\
<details open>\
<summary class="header">\
<span class="keyword" itemprop="keyword">Keyword</span>: <span itemprop="name" class="name">This is the block name</span>\
</summary>\
<div itemprop="description" class="description">The description goes here</div>\
</details>\
</section>\
\
<ol class="steps"></ol>\
\
<ol>\
<li class="step"><div class="header"></div><span class="keyword" itemprop="keyword">Keyword</span><span class="name" itemprop="name">Name</span></li>\
</ol>\
\
<pre class="doc_string"></pre>\
\
<pre class="error"></pre>\
\
<table class="data_table">\
<tbody>\
</tbody>\
</table>\
\
<table class="examples_table">\
<thead></thead>\
<tbody></tbody>\
</table>\
\
<section class="embed">\
<img itemprop="screenshot" class="screenshot" />\
</section>\
<div class="tags"></div>\
<span class="tag"></span>\
<div class="comments"></div>\
<div class="comment"></div>\
<div>';

if (typeof module !== 'undefined') {
module.exports = CucumberHTML;
} else if (typeof define !== 'undefined') {
define([], function() { return CucumberHTML; });
}
14 changes: 14 additions & 0 deletions core/src/main/resources/io/cucumber/formatter/html/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Cucumber Features</title>
<link href="style.css" rel="stylesheet">
<script src="jquery-1.8.2.min.js"></script>
<script src="formatter.js"></script>
<script src="report.js"></script>
</head>
<body>
<div class="cucumber-report"></div>
</body>
</html>

Large diffs are not rendered by default.

93 changes: 93 additions & 0 deletions core/src/main/resources/io/cucumber/formatter/html/report.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
$(document).ready(function() {
var formatter = new CucumberHTML.DOMFormatter($('.cucumber-report'));
var N = document.location.hash ? parseInt(document.location.hash.substring(1)) : 1;
var start = new Date().getTime();
for(var n = 0; n < N; n++) {
formatter.uri('report.feature');
formatter.feature({
comments: [
{value: "# A comment"},
{value: "# Another comment"}
],
keyword:'Feature',
name:'Generating html report',
description: 'It could be useful to have an html report to facilitate documentation reading.\n\nEspecially when the whitespace formatting is preserved',
line:2
});

formatter.background({
comments: [
{value: "# Background comment"}
],
keyword:'Background',
name:'Setting up the context',
line:3,
description: 'These steps will be executed before each scenario.'
});
formatter.write('Output from before hook');
formatter.embedding('text/plain', 'Text embedding from before hook');
formatter.step({keyword:'Given ', name:'I have a background', line:4});
formatter.step({keyword:'And ', name:'I set some context', line: 5});
formatter.match({uri:'report.feature'});
formatter.result({status:'passed', duration: 0});
formatter.match({uri:'report.feature'});
formatter.result({status:'passed', duration: 0});

formatter.before({status: 'passed', duration: 668816288});
formatter.scenario({"tags":[{"name":"@foo","line":3},{"name":"@bar","line":4},{"name":"@doh","line":5}], keyword:'Scenario', name: 'Creating a simple report', line: 6});
formatter.step({comments: [
{value: "# Step comment 1"},
{value: "# Step comment 2"}
],keyword:'Given ', name:'I have a feature', line: 7, doc_string:{value: "A\ndoc string\non several lines", content_type:"text/plain", line:8}});
formatter.step({keyword:'When ', name:'I format it', line: 11});
formatter.step({keyword:'Then ', name:'It should look pretty', line: 12});
formatter.step({keyword:'And ', name:'It should show tables', line: 13, rows: [{cells:['name', 'price'], line: 14}, {cells:['milk', '9'], line: 15}]});
formatter.match({uri:'report.feature'});
formatter.result({status:'passed', duration: 0});
formatter.match({uri:'report.feature'});
formatter.result({status:'failed', error_message:'something went wrong...', duration: 0});
formatter.embedding('image/png', 'bubble_256x256.png');
formatter.match({uri:'report.feature'});
formatter.result({status:'undefined', duration: 0});
formatter.embedding('text/plain', 'Look at this video');
formatter.embedding('video/mp4', 'http://www.808.dk/pics/video/gizmo.mp4');
formatter.embedding('text/plain', 'Look at this multi-line embedding\nReal fancy');
formatter.write('What a nice helicopter');
formatter.match({uri:'report.feature'});
formatter.result({status:'skipped', duration: 0});
formatter.after({status: 'passed', duration: 668816288});

formatter.scenarioOutline({keyword:'Scenario Outline', name: 'Scenario with examples', description:'It should be good to format outlined arguments.', line: 16});
formatter.step({keyword:'Given ', name:'I have a <name> which costs <price>', line: 17});
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}]});
formatter.before({status: 'passed', duration: 668816288});
formatter.match({uri:'report.feature'});
formatter.result({status:'passed', duration: 0});
formatter.match({uri:'report.feature'});
formatter.result({status:'passed', duration: 0});
formatter.match({uri:'report.feature'});
formatter.result({status:'failed', error_message:'I didn\'t do it.', duration: 0});
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)'});

formatter.scenario({"tags":[{"name":"@stephooks","line":24}], keyword:'Scenario', name: 'Scenario with step hooks', line: 25});
formatter.before({status: 'passed', duration: 668816288});
formatter.beforestep({status: 'passed', duration: 668816288});
formatter.step({keyword:'Given ', name:'step 1', line: 26});
formatter.match({uri:'report.feature'});
formatter.result({status:'passed', duration: 0});
formatter.afterstep({status: 'passed', duration: 668816288});
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()'});
formatter.step({keyword:'When ', name:'step 2', line: 27});
formatter.match({uri:'report.feature'});
formatter.result({status:'skipped', duration: 0});
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()'});
formatter.beforestep({status: 'skipped', duration: 0});
formatter.step({keyword:'Then ', name:'step 3', line: 28});
formatter.match({uri:'report.feature'});
formatter.result({status:'skipped', duration: 0});
formatter.afterstep({status: 'skipped', duration: 0});
formatter.after({status: 'passed', duration: 668816288});
}
console.log('Rendered %s features in %s ms', N, new Date().getTime() - start);

});
Loading