diff --git a/.labrc.js b/.labrc.js
new file mode 100644
index 00000000..26359752
--- /dev/null
+++ b/.labrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ enableTemplateStringHTMLReporter: 'true'
+};
\ No newline at end of file
diff --git a/README.md b/README.md
index 48fa3277..1596a5dc 100755
--- a/README.md
+++ b/README.md
@@ -15,3 +15,7 @@
- [Changelog](https://hapi.dev/family/lab/changelog/)
- [Project policies](https://hapi.dev/policies/)
- [Free and commercial support options](https://hapi.dev/support/)
+
+
+# Notes
+- Replacing Handlebars with template strings. Implement similar to https://medium.com/your-majesty-co/frameworkless-javascript-template-literals-the-best-thing-since-sliced-bread-d97f000ce955
diff --git a/lib/reporters/html.js b/lib/reporters/html.js
index e0a1295f..132a5d1e 100755
--- a/lib/reporters/html.js
+++ b/lib/reporters/html.js
@@ -7,10 +7,19 @@ const Handlebars = require('handlebars');
const Hoek = require('@hapi/hoek');
const SourceMap = require('source-map');
const SourceMapSupport = require('source-map-support');
+const { reportTemplate } = require('./html/report.js');
+const FindRc = require('find-rc');
const internals = {};
+internals.rcPath = FindRc('lab');
+/* $lab:coverage:off$ */
+internals.rc = internals.rcPath ? require(internals.rcPath) : {};
+/* $lab:coverage:on$ */
+
+const { enableTemplateStringHTMLReporter } = internals.rc;
+
exports = module.exports = internals.Reporter = function (options) {
@@ -18,7 +27,7 @@ exports = module.exports = internals.Reporter = function (options) {
const filename = Path.join(__dirname, 'html', 'report.html');
const template = Fs.readFileSync(filename, 'utf8');
-
+ /* $lab:coverage:off$ */
// Display all valid numbers except zeros
Handlebars.registerHelper('number', (number) => {
@@ -60,6 +69,7 @@ exports = module.exports = internals.Reporter = function (options) {
const stack = err.stack.slice(err.stack.indexOf('\n') + 1).replace(/^\s*/gm, ' ');
return new Handlebars.SafeString(Hoek.escapeHtml(stack));
});
+ /* $lab:coverage:on$ */
const partialsPath = Path.join(__dirname, 'html', 'partials');
const partials = Fs.readdirSync(partialsPath);
@@ -284,7 +294,14 @@ internals.Reporter.prototype.end = async function (notebook) {
}, this);
}
- this.report(this.view(context));
+ /* $lab:coverage:off$ */
+ if (enableTemplateStringHTMLReporter === 'true') {
+ this.report(reportTemplate(context));
+ }
+ else {
+ this.report(this.view(context));
+ }
+/* $lab:coverage:on$ */
};
internals.findLint = function (lint, file) {
diff --git a/lib/reporters/html/helpers.js b/lib/reporters/html/helpers.js
new file mode 100644
index 00000000..b6ae76ac
--- /dev/null
+++ b/lib/reporters/html/helpers.js
@@ -0,0 +1,45 @@
+'use strict';
+
+const Hoek = require('@hapi/hoek');
+
+exports.replace = (str, from, to, flags) => {
+
+ return str.replace(new RegExp(from, flags), to);
+};
+
+// Display all valid numbers except zeros
+exports.number = (number) => {
+
+ return +number || '';
+};
+
+exports.join = (array, separator) => {
+
+ return array.join(separator);
+};
+
+exports.lintJoin = (array) => {
+
+ let str = '';
+
+ for (let i = 0; i < array.length; ++i) {
+ if (str) {
+ str += '
'; // This is a line break
+ }
+
+ str += Hoek.escapeHtml(array[i]);
+ }
+
+ return `${str}`;
+};
+
+exports.errorMessage = (err) => {
+
+ return `${Hoek.escapeHtml('' + err.message)}`;
+};
+
+exports.errorStack = (err) => {
+
+ const stack = err.stack.slice(err.stack.indexOf('\n') + 1).replace(/^\s*/gm, ' ');
+ return `${Hoek.escapeHtml(stack)}`;
+};
diff --git a/lib/reporters/html/partials/cov.js b/lib/reporters/html/partials/cov.js
new file mode 100644
index 00000000..5981bfd9
--- /dev/null
+++ b/lib/reporters/html/partials/cov.js
@@ -0,0 +1,26 @@
+'use strict';
+
+const { file } = require('./file');
+
+exports.cov = (coverage) => {
+
+ return `
+
Code Coverage Report
+
+
${coverage.percent}%
+
${coverage.cov.sloc}
+
${coverage.cov.hits}
+
${coverage.cov.misses}
+
+
+
+
+
+
+ ${coverage.cov.files.map((item, i) => {
+
+ return file(item);
+ })}
+
+
`;
+};
diff --git a/lib/reporters/html/partials/css.js b/lib/reporters/html/partials/css.js
new file mode 100644
index 00000000..0d6b5de5
--- /dev/null
+++ b/lib/reporters/html/partials/css.js
@@ -0,0 +1,446 @@
+'use strict';
+
+exports.css = () => ``;
diff --git a/lib/reporters/html/partials/file.js b/lib/reporters/html/partials/file.js
new file mode 100644
index 00000000..5838d979
--- /dev/null
+++ b/lib/reporters/html/partials/file.js
@@ -0,0 +1,38 @@
+'use strict';
+
+const { line } = require('./line');
+
+exports.file = (item) => {
+
+ const { filename } = item;
+
+ return `
+
${item.filename} ${item.generated ? `(transformed to ${item.generated})` : ``}
+
+
${item.percent}%
+
${item.sloc}
+
${item.hits}
+
${item.misses}
+
+
+
+
+ Line |
+ Lint |
+ Hits |
+ Source |
+ ${item.sourcemaps ? `Original line | ` : ``}
+
+
+
+ ${item.source ?
+ `
+ ${Object.entries(item.source).map((subitem,i) =>
+
+ `
+ ${line(filename, subitem[1], i)}
+ `)}` : ``}
+
+
+
`;
+};
diff --git a/lib/reporters/html/partials/line.js b/lib/reporters/html/partials/line.js
new file mode 100644
index 00000000..5c19fb82
--- /dev/null
+++ b/lib/reporters/html/partials/line.js
@@ -0,0 +1,22 @@
+'use strict';
+
+const { number } = require('../helpers');
+const { lint } = require('./lint');
+
+exports.line = (filename, item, key) => {
+
+ return `
+ ${key} |
+ ${lint(item.lintErrors)}
+ ${item.miss ? `${number(item.percent)}` : `${number(item.hits)}`} |
+ ${item.chunks ?
+ `${item.chunks.map((subitem,i) =>
+
+ `${subitem.miss ? ` ${subitem.source} ` : `${subitem.source} `}`
+ ).join('')} | `
+ :
+ `${item.source} | `
+}
+ ${item.originalFilename ? `${item.originalLine} | ` : ``}
+
`;
+};
diff --git a/lib/reporters/html/partials/lint-file.js b/lib/reporters/html/partials/lint-file.js
new file mode 100644
index 00000000..bacfda92
--- /dev/null
+++ b/lib/reporters/html/partials/lint-file.js
@@ -0,0 +1,15 @@
+'use strict';
+
+exports.lintFile = (item) => {
+
+ return `
+ ${item.errors ?
+ `
${item.filename}
+
+ ${item.errors.map((subitem,i) =>
+
+ `- L${subitem.line} - ${subitem.severity} - ${subitem.message}
`
+ )}
+
` : ``}
+
`;
+};
diff --git a/lib/reporters/html/partials/lint.js b/lib/reporters/html/partials/lint.js
new file mode 100644
index 00000000..5be984bb
--- /dev/null
+++ b/lib/reporters/html/partials/lint.js
@@ -0,0 +1,20 @@
+'use strict';
+
+const { lintJoin } = require('../helpers');
+
+exports.lint = (lintErrors) => {
+
+ if ( lintErrors ) {
+ return `
+ ${lintErrors.errors ?
+ ``
+ : `test`}
+ ${lintErrors.warnings ?
+ ``
+ : ``}
+ | `;
+ }
+
+ return ` | `;
+
+};
diff --git a/lib/reporters/html/partials/linting.js b/lib/reporters/html/partials/linting.js
new file mode 100644
index 00000000..2dc3a667
--- /dev/null
+++ b/lib/reporters/html/partials/linting.js
@@ -0,0 +1,40 @@
+'use strict';
+
+const { lintFile } = require('./lint-file');
+
+const lintingPartialMainChild = function (lint) {
+
+ if ( lint.total ) {
+ return `${lint.lint.map((item,i) =>
+
+ lintFile(item,i)
+ )}`;
+ }
+
+ return ``;
+
+};
+
+const lintingPartialMain = function (lint) {
+
+ if ( lint.disabled ) {
+ return `Nothing to show here, linting is disabled.`;
+ }
+
+ return `
+ ${lint.totalErrors}
+ ${lint.totalWarnings}
+
+
+ ${lintingPartialMainChild(lint)}
+
`;
+
+};
+
+exports.linting = (lint) => {
+
+ return `
+
Linting Report
+ ${lintingPartialMain(lint)}
+ `;
+};
diff --git a/lib/reporters/html/partials/menu.js b/lib/reporters/html/partials/menu.js
new file mode 100644
index 00000000..fe71e5c5
--- /dev/null
+++ b/lib/reporters/html/partials/menu.js
@@ -0,0 +1,25 @@
+'use strict';
+
+exports.menu = (coverage, lint) => {
+
+ return ``;
+};
diff --git a/lib/reporters/html/partials/scripts.js b/lib/reporters/html/partials/scripts.js
new file mode 100644
index 00000000..2b24db51
--- /dev/null
+++ b/lib/reporters/html/partials/scripts.js
@@ -0,0 +1,89 @@
+'use strict';
+
+exports.scripts = () => {
+
+ return ``;
+};
diff --git a/lib/reporters/html/partials/tests.js b/lib/reporters/html/partials/tests.js
new file mode 100644
index 00000000..57d37295
--- /dev/null
+++ b/lib/reporters/html/partials/tests.js
@@ -0,0 +1,65 @@
+'use strict';
+
+const { replace, join, errorMessage, errorStack } = require('../helpers');
+
+exports.tests = (failures, skipped, tests, duration, paths, errors) => {
+
+ return `
+
Test Report
+
+
${failures.length}
+
${skipped.length}
+
${tests.length}
+
${duration}
+
+
+
+
+
+
+
+ ${paths.map((item,i) =>
+
+ `
+
+
+ `)}
+
+
+
+
+ ID |
+ Title |
+ Duration (ms) |
+
+
+
+ ${tests.map((item,i) =>
+
+ `
+
+ ${item.id} |
+ ${item.title}
+ ${item.err ? ` ${item.err.stack} ` : ``}
+ |
+ ${item.duration} |
+
+ `)}
+
+
+
+ ${errors.length ?
+ `
Script errors :
+
+ ${errors.map((item, i) =>
+
+ `
+ -
+
${errorMessage(item)}
+ ${item.stack ?
+ `${errorStack(item)}
` : ``}
+
+ `)}
+
` : ``}
+
`;
+};
diff --git a/lib/reporters/html/report.js b/lib/reporters/html/report.js
new file mode 100644
index 00000000..4b610ac5
--- /dev/null
+++ b/lib/reporters/html/report.js
@@ -0,0 +1,26 @@
+'use strict';
+
+const { scripts } = require('./partials/scripts');
+const { css } = require('./partials/css');
+const { menu } = require('./partials/menu');
+const { tests } = require('./partials/tests');
+const { cov } = require('./partials/cov');
+const { linting } = require('./partials/linting');
+
+exports.reportTemplate = function (context) {
+
+ return `
+
+
+ Tests & Coverage
+ ${scripts()}
+ ${css()}
+
+
+ ${menu(context.coverage, context.lint)}
+ ${tests(context.failures, context.skipped, context.tests, context.duration, context.paths, context.errors)}
+ ${cov(context.coverage)}
+ ${linting(context.lint)}
+
+`;
+};
diff --git a/package.json b/package.json
index 291af18c..1f7a9f9b 100755
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
"eslint": "6.x.x",
"find-rc": "4.x.x",
"globby": "10.x.x",
- "handlebars": "4.x.x",
+ "handlebars": "^4.7.3",
"seedrandom": "3.x.x",
"source-map": "0.7.x",
"source-map-support": "0.5.x",
@@ -44,7 +44,8 @@
},
"scripts": {
"test": "node ./bin/_lab -f -L -t 100 -m 10000 --types-test test/index.ts",
- "test-cov-html": "node ./bin/_lab -f -L -r html -m 10000 -o coverage.html"
+ "test-cov-html": "node ./bin/_lab -f -L -r html -m 10000 -o coverage.html",
+ "lint": "node ./bin/_lab -d -f -L"
},
"license": "BSD-3-Clause"
}
diff --git a/test/reporters.js b/test/reporters.js
index e227fd61..82313bc6 100755
--- a/test/reporters.js
+++ b/test/reporters.js
@@ -1385,6 +1385,193 @@ describe('Reporter', () => {
});
describe('html', () => {
+
+ it('generates corresponding lint-file parital when there are errors', () => {
+
+ const { lintFile } = require('../lib/reporters/html/partials/lint-file');
+
+ const options = {
+ errors: [
+ {
+ line: 19,
+ severity: 'high',
+ message: 'some message'
+ }
+ ],
+ filename: 'some-file-name.js'
+
+ };
+ expect(lintFile(options)).to.include('L19 - high - some message');
+ });
+
+ it('generates corresponding lint-file partial when there are no errors', () => {
+
+ const { lintFile } = require('../lib/reporters/html/partials/lint-file');
+
+ const options = {
+ filename: 'some-file-name.js'
+
+ };
+ expect(lintFile(options)).to.include('\n \n
');
+ });
+
+ it('generates corresponding lint partial when there are lint errors', () => {
+
+ const { lint } = require('../lib/reporters/html/partials/lint');
+
+ const options = {
+ errors: ['error 1','error 2', 'error 3', 'error 4']
+ };
+ expect(lint(options)).to.include('');
+ });
+
+ it('generates corresponding lint partial when there are lint warnings', () => {
+
+ const { lint } = require('../lib/reporters/html/partials/lint');
+
+ const options = {
+ warnings: ['warning 1', 'warning 2']
+ };
+ expect(lint(options)).to.include('');
+ });
+
+ it('generates corresponding linting partial when lint is disabled', () => {
+
+ const { linting } = require('../lib/reporters/html/partials/linting');
+
+ const options = {
+ disabled: true
+ };
+ expect(linting(options)).to.include('Nothing to show here, linting is disabled.');
+ });
+
+ it('generates corresponding linting partial when lint is enabled ', () => {
+
+ const { linting } = require('../lib/reporters/html/partials/linting');
+
+ const options = {
+ errorClass: 'error',
+ totalErrors: 10,
+ warningClass: 'warning',
+ totalWarnings: 13,
+ lint: [
+ {
+ errors: ['error1'],
+ warnings: ['warning 1', 'warning 2']
+ }
+ ],
+ total: 23
+ };
+
+ expect(linting(options)).to.include('10');
+ });
+
+ it('generates corresponding linting partial when total is undefined', () => {
+
+ const { linting } = require('../lib/reporters/html/partials/linting');
+
+ const options = {
+ errorClass: 'error',
+ totalErrors: 10,
+ warningClass: 'warning',
+ totalWarnings: 13,
+ lint: [
+ {
+ warnings: ['warning 1', 'warning 2']
+ }
+ ]
+ };
+
+ expect(linting(options)).to.not.include('');
+ });
+
+ it('generates corresponding tests partial when there are failures', () => {
+
+ const { tests } = require('../lib/reporters/html/partials/tests');
+
+ const options = {
+ failures: ['failure 1, failure 2'],
+ skipped: ['s1', 's2', 's3'],
+ tests: [{
+ path: ['p1', 'p2']
+ }],
+ duration: 10,
+ paths: [],
+ errors: []
+ };
+
+ expect(tests(options.failures, options.skipped, options.tests, options.duration, options.paths, options.errors)).to.include('
');
+ });
+
+ it('generates corresponding tests partial for errors with stack', () => {
+
+ const { tests } = require('../lib/reporters/html/partials/tests');
+
+ const options = {
+ failures: ['failure 1, failure 2'],
+ skipped: ['s1', 's2', 's3'],
+ tests: [{
+ path: ['p1', 'p2']
+ }],
+ duration: 10,
+ paths: [],
+ errors: [
+ {
+ message: 'some error message',
+ stack: `Unexpected identifier
+ at wrapSafe (internal/modules/cjs/loader.js:1072:16)
+ at Module._compile (internal/modules/cjs/loader.js:1122:27)
+ at Object.require.extensions.
[as .js] (/Users/aorinevo/Repositories/hapi/lab/lib/coverage.js:127:113)
+ at Module.load (internal/modules/cjs/loader.js:1002:32)`
+ }
+ ]
+
+ };
+ const compiledTests = tests(options.failures, options.skipped, options.tests, options.duration, options.paths, options.errors);
+ expect(compiledTests).to.include('some error message
');
+ expect(compiledTests).to.include(' at wrapSafe (internal/modules/cjs/loader.js:1072:16)
');
+ });
+
+ it('generates corresponding tests partial for errors without stack', () => {
+
+ const { tests } = require('../lib/reporters/html/partials/tests');
+
+ const options = {
+ failures: [],
+ skipped: ['s1', 's2', 's3'],
+ tests: [{
+ path: ['p1', 'p2']
+ }],
+ duration: 10,
+ paths: [],
+ errors: [
+ {
+ message: 'some error message'
+ }
+ ]
+ };
+ const compiledTests = tests(options.failures, options.skipped, options.tests, options.duration, options.paths, options.errors);
+ expect(compiledTests).to.include('some error message
');
+ expect(compiledTests).to.not.include(' {
+
+ const { tests } = require('../lib/reporters/html/partials/tests');
+
+ const options = {
+ failures: [],
+ skipped: ['s1', 's2', 's3'],
+ tests: [{
+ path: ['p1', 'p2']
+ }],
+ duration: 10,
+ paths: [],
+ errors: []
+ };
+
+ expect(tests(options.failures, options.skipped, options.tests, options.duration, options.paths, options.errors)).to.include('');
+ });
it('generates a coverage report', async () => {
@@ -1427,7 +1614,7 @@ describe('Reporter', () => {
'
while ( value ) { | ']);
expect(output, 'missed original line not included').to.contains([
'
',
- ' value = false; | ']);
+ ' value = false; | ']);
delete global.__$$testCovHtml;
});