diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js
index 31444823..194939c1 100644
--- a/lib/jasmine-core/jasmine-html.js
+++ b/lib/jasmine-core/jasmine-html.js
@@ -27,7 +27,14 @@ var jasmineRequire = window.jasmineRequire || require('./jasmine.js');
jasmineRequire.html = function(j$) {
j$.private.ResultsNode = jasmineRequire.ResultsNode();
- j$.private.createDom = jasmineRequire.createDom(j$);
+ j$.private.ResultsStateBuilder = jasmineRequire.ResultsStateBuilder(j$);
+ j$.private.htmlReporterUtils = jasmineRequire.htmlReporterUtils(j$);
+ j$.private.AlertsView = jasmineRequire.AlertsView(j$);
+ j$.private.Banner = jasmineRequire.Banner(j$);
+ j$.private.SymbolsView = jasmineRequire.SymbolsView(j$);
+ j$.private.SummaryTreeView = jasmineRequire.SummaryTreeView(j$);
+ j$.private.FailuresView = jasmineRequire.FailuresView(j$);
+ j$.private.UrlBuilder = jasmineRequire.UrlBuilder();
j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
j$.QueryString = jasmineRequire.QueryString();
j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();
@@ -36,80 +43,7 @@ jasmineRequire.html = function(j$) {
jasmineRequire.HtmlReporter = function(j$) {
'use strict';
- class ResultsStateBuilder {
- constructor() {
- this.topResults = new j$.private.ResultsNode({}, '', null);
- this.currentParent = this.topResults;
- this.totalSpecsDefined = 0;
- this.specsExecuted = 0;
- this.failureCount = 0;
- this.pendingSpecCount = 0;
- this.deprecationWarnings = [];
- }
-
- suiteStarted(result) {
- this.currentParent.addChild(result, 'suite');
- this.currentParent = this.currentParent.last();
- }
-
- suiteDone(result) {
- this.currentParent.updateResult(result);
- this.#addDeprecationWarnings(result, 'suite');
-
- if (this.currentParent !== this.topResults) {
- this.currentParent = this.currentParent.parent;
- }
-
- if (result.status === 'failed') {
- this.failureCount++;
- }
- }
-
- specDone(result) {
- this.currentParent.addChild(result, 'spec');
- this.#addDeprecationWarnings(result, 'spec');
-
- if (result.status !== 'excluded') {
- this.specsExecuted++;
- }
-
- if (result.status === 'failed') {
- this.failureCount++;
- }
-
- if (result.status == 'pending') {
- this.pendingSpecCount++;
- }
- }
-
- jasmineStarted(result) {
- this.totalSpecsDefined = result.totalSpecsDefined;
- }
-
- jasmineDone(result) {
- if (result.failedExpectations) {
- this.failureCount += result.failedExpectations.length;
- }
-
- this.#addDeprecationWarnings(result);
- }
-
- #addDeprecationWarnings(result, runnableType) {
- if (result.deprecationWarnings) {
- for (const dw of result.deprecationWarnings) {
- this.deprecationWarnings.push({
- message: dw.message,
- stack: dw.stack,
- runnableName: result.fullName,
- runnableType: runnableType
- });
- }
- }
- }
- }
-
- const errorBarClassName = 'jasmine-bar jasmine-errored';
- const afterAllMessagePrefix = 'AfterAll ';
+ const { createDom, noExpectations } = j$.private.htmlReporterUtils;
/**
* @class HtmlReporter
@@ -140,7 +74,7 @@ jasmineRequire.HtmlReporter = function(j$) {
this.#getContainer = options.getContainer;
this.#navigateWithNewParam =
options.navigateWithNewParam || function() {};
- this.#urlBuilder = new UrlBuilder(
+ this.#urlBuilder = new j$.private.UrlBuilder(
options.addToExistingQueryString || defaultQueryString
);
this.#filterSpecs = options.filterSpecs;
@@ -155,13 +89,13 @@ jasmineRequire.HtmlReporter = function(j$) {
this.#clearPrior();
this.#config = this.#env ? this.#env.configuration() : {};
- this.#stateBuilder = new ResultsStateBuilder();
+ this.#stateBuilder = new j$.private.ResultsStateBuilder();
- this.#alerts = new AlertsView(this.#urlBuilder);
- this.#symbols = new SymbolsView();
- this.#banner = new Banner(this.#navigateWithNewParam);
- this.#failures = new FailuresView(this.#urlBuilder);
- this.#htmlReporterMain = j$.private.createDom(
+ this.#alerts = new j$.private.AlertsView(this.#urlBuilder);
+ this.#symbols = new j$.private.SymbolsView();
+ this.#banner = new j$.private.Banner(this.#navigateWithNewParam);
+ this.#failures = new j$.private.FailuresView(this.#urlBuilder);
+ this.#htmlReporterMain = createDom(
'div',
{ className: 'jasmine_html-reporter' },
this.#banner.rootEl,
@@ -237,7 +171,10 @@ jasmineRequire.HtmlReporter = function(j$) {
}
const results = this.#find('.jasmine-results');
- const summary = new SummaryTreeView(this.#urlBuilder, this.#filterSpecs);
+ const summary = new j$.private.SummaryTreeView(
+ this.#urlBuilder,
+ this.#filterSpecs
+ );
summary.addResults(this.#stateBuilder.topResults);
results.appendChild(summary.rootEl);
@@ -274,702 +211,10 @@ jasmineRequire.HtmlReporter = function(j$) {
}
}
- function hasActiveSpec(resultNode) {
- if (resultNode.type === 'spec' && resultNode.result.status !== 'excluded') {
- return true;
- }
-
- if (resultNode.type === 'suite') {
- for (let i = 0, j = resultNode.children.length; i < j; i++) {
- if (hasActiveSpec(resultNode.children[i])) {
- return true;
- }
- }
- }
- }
-
- function noExpectations(result) {
- const allExpectations =
- result.failedExpectations.length + result.passedExpectations.length;
-
- return (
- allExpectations === 0 &&
- (result.status === 'passed' || result.status === 'failed')
- );
- }
-
- function pluralize(singular, count) {
- const word = count == 1 ? singular : singular + 's';
-
- return '' + count + ' ' + word;
- }
-
function defaultQueryString(key, value) {
return '?' + key + '=' + value;
}
- class AlertsView {
- #urlBuilder;
-
- constructor(urlBuilder) {
- this.#urlBuilder = urlBuilder;
- this.rootEl = j$.private.createDom('div', { className: 'jasmine-alert' });
- }
-
- addDuration(ms) {
- this.add('jasmine-duration', 'finished in ' + ms / 1000 + 's');
- }
-
- addSkipped(numExecuted, numDefined) {
- // TODO: backfill tests for this
- this.add(
- 'jasmine-bar jasmine-skipped',
- j$.private.createDom(
- 'a',
- { href: this.#urlBuilder.runAllHref(), title: 'Run all specs' },
- `Ran ${numExecuted} of ${numDefined} specs - run all`
- )
- );
- }
-
- addFailureToggle(onClickFailures, onClickSpecList) {
- const failuresLink = j$.private.createDom(
- 'a',
- { className: 'jasmine-failures-menu', href: '#' },
- 'Failures'
- );
- let specListLink = j$.private.createDom(
- 'a',
- { className: 'jasmine-spec-list-menu', href: '#' },
- 'Spec List'
- );
-
- failuresLink.onclick = function() {
- onClickFailures();
- return false;
- };
-
- specListLink.onclick = function() {
- onClickSpecList();
- return false;
- };
-
- this.add('jasmine-menu jasmine-bar jasmine-spec-list', [
- j$.private.createDom('span', {}, 'Spec List | '),
- failuresLink
- ]);
- this.add('jasmine-menu jasmine-bar jasmine-failure-list', [
- specListLink,
- j$.private.createDom('span', {}, ' | Failures ')
- ]);
- }
-
- addGlobalFailure(failure) {
- this.add(errorBarClassName, this.#globalFailureMessage(failure));
- }
-
- // TODO check test coverage
- addSeedBar(doneResult, stateBuilder, order) {
- let statusBarMessage = '';
- let statusBarClassName = 'jasmine-overall-result jasmine-bar ';
- const globalFailures =
- (doneResult && doneResult.failedExpectations) || [];
- const failed = stateBuilder.failureCount + globalFailures.length > 0;
-
- if (stateBuilder.totalSpecsDefined > 0 || failed) {
- statusBarMessage +=
- pluralize('spec', stateBuilder.specsExecuted) +
- ', ' +
- pluralize('failure', stateBuilder.failureCount);
- if (stateBuilder.pendingSpecCount) {
- statusBarMessage +=
- ', ' + pluralize('pending spec', stateBuilder.pendingSpecCount);
- }
- }
-
- if (doneResult.overallStatus === 'passed') {
- statusBarClassName += ' jasmine-passed ';
- } else if (doneResult.overallStatus === 'incomplete') {
- statusBarClassName += ' jasmine-incomplete ';
- statusBarMessage =
- 'Incomplete: ' +
- doneResult.incompleteReason +
- ', ' +
- statusBarMessage;
- } else {
- statusBarClassName += ' jasmine-failed ';
- }
-
- let seedBar;
- if (order && order.random) {
- seedBar = j$.private.createDom(
- 'span',
- { className: 'jasmine-seed-bar' },
- ', randomized with seed ',
- j$.private.createDom(
- 'a',
- {
- title: 'randomized with seed ' + order.seed,
- href: this.#urlBuilder.seedHref(order.seed)
- },
- order.seed
- )
- );
- }
-
- this.add(statusBarClassName, [statusBarMessage, seedBar]);
- }
-
- // TODO check test coverage
- #globalFailureMessage(failure) {
- if (failure.globalErrorType === 'load') {
- const prefix = 'Error during loading: ' + failure.message;
-
- if (failure.filename) {
- return prefix + ' in ' + failure.filename + ' line ' + failure.lineno;
- } else {
- return prefix;
- }
- } else if (failure.globalErrorType === 'afterAll') {
- return afterAllMessagePrefix + failure.message;
- } else {
- return failure.message;
- }
- }
-
- addDeprecationWarning(dw) {
- const children = [];
- let context;
-
- switch (dw.runnableType) {
- case 'spec':
- context = '(in spec: ' + dw.runnableName + ')';
- break;
- case 'suite':
- context = '(in suite: ' + dw.runnableName + ')';
- break;
- default:
- context = '';
- }
-
- for (const line of dw.message.split('\n')) {
- children.push(line);
- children.push(j$.private.createDom('br'));
- }
-
- children[0] = 'DEPRECATION: ' + children[0];
- children.push(context);
-
- if (dw.stack) {
- children.push(this.#createExpander(dw.stack));
- }
-
- this.add('jasmine-bar jasmine-warning', children);
- }
-
- // TODO private?
- add(className, children) {
- this.rootEl.appendChild(
- j$.private.createDom('span', { className }, children)
- );
- }
-
- #createExpander(stackTrace) {
- const expandLink = j$.private.createDom(
- 'a',
- { href: '#' },
- 'Show stack trace'
- );
- const root = j$.private.createDom(
- 'div',
- { className: 'jasmine-expander' },
- expandLink,
- j$.private.createDom(
- 'div',
- { className: 'jasmine-expander-contents jasmine-stack-trace' },
- stackTrace
- )
- );
-
- expandLink.addEventListener('click', function(e) {
- e.preventDefault();
-
- if (root.classList.contains('jasmine-expanded')) {
- root.classList.remove('jasmine-expanded');
- expandLink.textContent = 'Show stack trace';
- } else {
- root.classList.add('jasmine-expanded');
- expandLink.textContent = 'Hide stack trace';
- }
- });
-
- return root;
- }
- }
-
- class Banner {
- #navigateWithNewParam;
-
- constructor(navigateWithNewParam) {
- this.#navigateWithNewParam = navigateWithNewParam;
- this.rootEl = j$.private.createDom(
- 'div',
- { className: 'jasmine-banner' },
- j$.private.createDom('a', {
- className: 'jasmine-title',
- href: 'http://jasmine.github.io/',
- target: '_blank'
- }),
- j$.private.createDom(
- 'span',
- { className: 'jasmine-version' },
- j$.version
- )
- );
- }
-
- showOptionsMenu(config) {
- this.rootEl.appendChild(this.#optionsMenu(config));
- }
-
- #optionsMenu(config) {
- const optionsMenuDom = j$.private.createDom(
- 'div',
- { className: 'jasmine-run-options' },
- j$.private.createDom(
- 'span',
- { className: 'jasmine-trigger' },
- 'Options'
- ),
- j$.private.createDom(
- 'div',
- { className: 'jasmine-payload' },
- j$.private.createDom(
- 'div',
- { className: 'jasmine-stop-on-failure' },
- j$.private.createDom('input', {
- className: 'jasmine-fail-fast',
- id: 'jasmine-fail-fast',
- type: 'checkbox'
- }),
- j$.private.createDom(
- 'label',
- { className: 'jasmine-label', for: 'jasmine-fail-fast' },
- 'stop execution on spec failure'
- )
- ),
- j$.private.createDom(
- 'div',
- { className: 'jasmine-throw-failures' },
- j$.private.createDom('input', {
- className: 'jasmine-throw',
- id: 'jasmine-throw-failures',
- type: 'checkbox'
- }),
- j$.private.createDom(
- 'label',
- { className: 'jasmine-label', for: 'jasmine-throw-failures' },
- 'stop spec on expectation failure'
- )
- ),
- j$.private.createDom(
- 'div',
- { className: 'jasmine-random-order' },
- j$.private.createDom('input', {
- className: 'jasmine-random',
- id: 'jasmine-random-order',
- type: 'checkbox'
- }),
- j$.private.createDom(
- 'label',
- { className: 'jasmine-label', for: 'jasmine-random-order' },
- 'run tests in random order'
- )
- ),
- j$.private.createDom(
- 'div',
- { className: 'jasmine-hide-disabled' },
- j$.private.createDom('input', {
- className: 'jasmine-disabled',
- id: 'jasmine-hide-disabled',
- type: 'checkbox'
- }),
- j$.private.createDom(
- 'label',
- { className: 'jasmine-label', for: 'jasmine-hide-disabled' },
- 'hide disabled tests'
- )
- )
- )
- );
-
- const failFastCheckbox = optionsMenuDom.querySelector(
- '#jasmine-fail-fast'
- );
- failFastCheckbox.checked = config.stopOnSpecFailure;
- failFastCheckbox.onclick = () => {
- this.#navigateWithNewParam(
- 'stopOnSpecFailure',
- !config.stopOnSpecFailure
- );
- };
-
- const throwCheckbox = optionsMenuDom.querySelector(
- '#jasmine-throw-failures'
- );
- throwCheckbox.checked = config.stopSpecOnExpectationFailure;
- throwCheckbox.onclick = () => {
- this.#navigateWithNewParam(
- 'stopSpecOnExpectationFailure',
- !config.stopSpecOnExpectationFailure
- );
- };
-
- const randomCheckbox = optionsMenuDom.querySelector(
- '#jasmine-random-order'
- );
- randomCheckbox.checked = config.random;
- randomCheckbox.onclick = () => {
- this.#navigateWithNewParam('random', !config.random);
- };
-
- const hideDisabled = optionsMenuDom.querySelector(
- '#jasmine-hide-disabled'
- );
- hideDisabled.checked = config.hideDisabled;
- // TODO: backfill tests for this!
- hideDisabled.onclick = () => {
- this.#navigateWithNewParam('hideDisabled', !config.hideDisabled);
- };
-
- const optionsTrigger = optionsMenuDom.querySelector('.jasmine-trigger'),
- optionsPayload = optionsMenuDom.querySelector('.jasmine-payload'),
- isOpen = /\bjasmine-open\b/;
-
- optionsTrigger.onclick = function() {
- if (isOpen.test(optionsPayload.className)) {
- optionsPayload.className = optionsPayload.className.replace(
- isOpen,
- ''
- );
- } else {
- optionsPayload.className += ' jasmine-open';
- }
- };
-
- return optionsMenuDom;
- }
- }
-
- class SymbolsView {
- constructor() {
- this.rootEl = j$.private.createDom('ul', {
- className: 'jasmine-symbol-summary'
- });
- }
-
- append(result, config) {
- this.rootEl.appendChild(
- j$.private.createDom('li', {
- className: this.#className(result, config),
- id: 'spec_' + result.id,
- title: result.fullName
- })
- );
- }
-
- #className(result, config) {
- if (noExpectations(result) && result.status === 'passed') {
- return 'jasmine-empty';
- } else if (result.status === 'excluded') {
- if (config.hideDisabled) {
- return 'jasmine-excluded-no-display';
- } else {
- return 'jasmine-excluded';
- }
- } else {
- return 'jasmine-' + result.status;
- }
- }
- }
-
- class SummaryTreeView {
- #urlBuilder;
- #filterSpecs;
-
- constructor(urlBuilder, filterSpecs) {
- this.#urlBuilder = urlBuilder;
- this.#filterSpecs = filterSpecs;
- this.rootEl = j$.private.createDom('div', {
- className: 'jasmine-summary'
- });
- }
-
- addResults(resultsTree) {
- this.#addResults(resultsTree, this.rootEl);
- }
-
- #addResults(resultsTree, domParent) {
- let specListNode;
- for (let i = 0; i < resultsTree.children.length; i++) {
- const resultNode = resultsTree.children[i];
- if (this.#filterSpecs && !hasActiveSpec(resultNode)) {
- continue;
- }
- if (resultNode.type === 'suite') {
- const suiteListNode = j$.private.createDom(
- 'ul',
- { className: 'jasmine-suite', id: 'suite-' + resultNode.result.id },
- j$.private.createDom(
- 'li',
- {
- className:
- 'jasmine-suite-detail jasmine-' + resultNode.result.status
- },
- j$.private.createDom(
- 'a',
- { href: this.#urlBuilder.specHref(resultNode.result) },
- resultNode.result.description
- )
- )
- );
-
- this.#addResults(resultNode, suiteListNode);
- domParent.appendChild(suiteListNode);
- }
- if (resultNode.type === 'spec') {
- if (domParent.getAttribute('class') !== 'jasmine-specs') {
- specListNode = j$.private.createDom('ul', {
- className: 'jasmine-specs'
- });
- domParent.appendChild(specListNode);
- }
- let specDescription = resultNode.result.description;
- if (noExpectations(resultNode.result)) {
- specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
- }
- if (resultNode.result.status === 'pending') {
- if (resultNode.result.pendingReason !== '') {
- specDescription +=
- ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
- } else {
- specDescription += ' PENDING';
- }
- }
- specListNode.appendChild(
- j$.private.createDom(
- 'li',
- {
- className: 'jasmine-' + resultNode.result.status,
- id: 'spec-' + resultNode.result.id
- },
- j$.private.createDom(
- 'a',
- { href: this.#urlBuilder.specHref(resultNode.result) },
- specDescription
- ),
- j$.private.createDom(
- 'span',
- { className: 'jasmine-spec-duration' },
- '(' + resultNode.result.duration + 'ms)'
- )
- )
- );
- }
- }
- }
- }
-
- class FailuresView {
- #urlBuilder;
- #failureEls;
-
- constructor(urlBuilder) {
- this.#urlBuilder = urlBuilder;
- this.#failureEls = [];
- this.rootEl = j$.private.createDom(
- 'div',
- { className: 'jasmine-results' },
- j$.private.createDom('div', { className: 'jasmine-failures' })
- );
- }
-
- append(result, parent) {
- // TODO: Figure out why the reuslt is wrong if we build the DOM node later
- this.#failureEls.push(this.#makeFailureEl(result, parent));
- }
-
- // TODO move this to state builder or something
- any() {
- return this.#failureEls.length > 0;
- }
-
- show() {
- const failureNode = this.rootEl.querySelector('.jasmine-failures');
-
- for (const el of this.#failureEls) {
- failureNode.appendChild(el);
- }
- }
-
- #makeFailureEl(result, parent) {
- const failure = j$.private.createDom(
- 'div',
- { className: 'jasmine-spec-detail jasmine-failed' },
- this.#failureDescription(result, parent),
- j$.private.createDom('div', { className: 'jasmine-messages' })
- );
- const messages = failure.childNodes[1];
-
- for (let i = 0; i < result.failedExpectations.length; i++) {
- const expectation = result.failedExpectations[i];
- messages.appendChild(
- j$.private.createDom(
- 'div',
- { className: 'jasmine-result-message' },
- expectation.message
- )
- );
- messages.appendChild(
- j$.private.createDom(
- 'div',
- { className: 'jasmine-stack-trace' },
- expectation.stack
- )
- );
- }
-
- if (result.failedExpectations.length === 0) {
- messages.appendChild(
- j$.private.createDom(
- 'div',
- { className: 'jasmine-result-message' },
- 'Spec has no expectations'
- )
- );
- }
-
- if (result.debugLogs) {
- messages.appendChild(this.#debugLogTable(result.debugLogs));
- }
-
- return failure;
- }
-
- #failureDescription(result, suite) {
- const wrapper = j$.private.createDom(
- 'div',
- { className: 'jasmine-description' },
- j$.private.createDom(
- 'a',
- {
- title: result.description,
- href: this.#urlBuilder.specHref(result)
- },
- result.description
- )
- );
- let suiteLink;
-
- while (suite && suite.parent) {
- wrapper.insertBefore(
- document.createTextNode(' > '),
- wrapper.firstChild
- );
- suiteLink = j$.private.createDom(
- 'a',
- { href: this.#urlBuilder.suiteHref(suite) },
- suite.result.description
- );
- wrapper.insertBefore(suiteLink, wrapper.firstChild);
-
- suite = suite.parent;
- }
-
- return wrapper;
- }
-
- #debugLogTable(debugLogs) {
- const tbody = j$.private.createDom('tbody');
-
- for (const entry of debugLogs) {
- tbody.appendChild(
- j$.private.createDom(
- 'tr',
- {},
- j$.private.createDom('td', {}, entry.timestamp.toString()),
- j$.private.createDom(
- 'td',
- { className: 'jasmine-debug-log-msg' },
- entry.message
- )
- )
- );
- }
-
- return j$.private.createDom(
- 'div',
- { className: 'jasmine-debug-log' },
- j$.private.createDom(
- 'div',
- { className: 'jasmine-debug-log-header' },
- 'Debug logs'
- ),
- j$.private.createDom(
- 'table',
- {},
- j$.private.createDom(
- 'thead',
- {},
- j$.private.createDom(
- 'tr',
- {},
- j$.private.createDom('th', {}, 'Time (ms)'),
- j$.private.createDom('th', {}, 'Message')
- )
- ),
- tbody
- )
- );
- }
- }
-
- class UrlBuilder {
- #addToExistingQueryString;
-
- constructor(addToExistingQueryString) {
- this.#addToExistingQueryString = function(k, v) {
- // include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
- return (
- (window.location.pathname || '') + addToExistingQueryString(k, v)
- );
- };
- }
-
- suiteHref(suite) {
- const els = [];
-
- while (suite && suite.parent) {
- els.unshift(suite.result.description);
- suite = suite.parent;
- }
-
- return this.#addToExistingQueryString('spec', els.join(' '));
- }
-
- specHref(result) {
- return this.#addToExistingQueryString('spec', result.fullName);
- }
-
- runAllHref() {
- return this.#addToExistingQueryString('spec', '');
- }
-
- seedHref(seed) {
- return this.#addToExistingQueryString('seed', seed);
- }
- }
-
return HtmlReporter;
};
@@ -1110,7 +355,532 @@ jasmineRequire.QueryString = function() {
return QueryString;
};
-jasmineRequire.createDom = function(j$) {
+jasmineRequire.AlertsView = function(j$) {
+ 'use strict';
+
+ const { createDom } = j$.private.htmlReporterUtils;
+ const errorBarClassName = 'jasmine-bar jasmine-errored';
+ const afterAllMessagePrefix = 'AfterAll ';
+
+ class AlertsView {
+ #urlBuilder;
+
+ constructor(urlBuilder) {
+ this.#urlBuilder = urlBuilder;
+ this.rootEl = createDom('div', { className: 'jasmine-alert' });
+ }
+
+ addDuration(ms) {
+ this.add('jasmine-duration', 'finished in ' + ms / 1000 + 's');
+ }
+
+ addSkipped(numExecuted, numDefined) {
+ // TODO: backfill tests for this
+ this.add(
+ 'jasmine-bar jasmine-skipped',
+ createDom(
+ 'a',
+ { href: this.#urlBuilder.runAllHref(), title: 'Run all specs' },
+ `Ran ${numExecuted} of ${numDefined} specs - run all`
+ )
+ );
+ }
+
+ addFailureToggle(onClickFailures, onClickSpecList) {
+ const failuresLink = createDom(
+ 'a',
+ { className: 'jasmine-failures-menu', href: '#' },
+ 'Failures'
+ );
+ let specListLink = createDom(
+ 'a',
+ { className: 'jasmine-spec-list-menu', href: '#' },
+ 'Spec List'
+ );
+
+ failuresLink.onclick = function() {
+ onClickFailures();
+ return false;
+ };
+
+ specListLink.onclick = function() {
+ onClickSpecList();
+ return false;
+ };
+
+ this.add('jasmine-menu jasmine-bar jasmine-spec-list', [
+ createDom('span', {}, 'Spec List | '),
+ failuresLink
+ ]);
+ this.add('jasmine-menu jasmine-bar jasmine-failure-list', [
+ specListLink,
+ createDom('span', {}, ' | Failures ')
+ ]);
+ }
+
+ addGlobalFailure(failure) {
+ this.add(errorBarClassName, this.#globalFailureMessage(failure));
+ }
+
+ // TODO check test coverage
+ addSeedBar(doneResult, stateBuilder, order) {
+ let statusBarMessage = '';
+ let statusBarClassName = 'jasmine-overall-result jasmine-bar ';
+ const globalFailures =
+ (doneResult && doneResult.failedExpectations) || [];
+ const failed = stateBuilder.failureCount + globalFailures.length > 0;
+
+ if (stateBuilder.totalSpecsDefined > 0 || failed) {
+ statusBarMessage +=
+ pluralize('spec', stateBuilder.specsExecuted) +
+ ', ' +
+ pluralize('failure', stateBuilder.failureCount);
+ if (stateBuilder.pendingSpecCount) {
+ statusBarMessage +=
+ ', ' + pluralize('pending spec', stateBuilder.pendingSpecCount);
+ }
+ }
+
+ if (doneResult.overallStatus === 'passed') {
+ statusBarClassName += ' jasmine-passed ';
+ } else if (doneResult.overallStatus === 'incomplete') {
+ statusBarClassName += ' jasmine-incomplete ';
+ statusBarMessage =
+ 'Incomplete: ' +
+ doneResult.incompleteReason +
+ ', ' +
+ statusBarMessage;
+ } else {
+ statusBarClassName += ' jasmine-failed ';
+ }
+
+ let seedBar;
+ if (order && order.random) {
+ seedBar = createDom(
+ 'span',
+ { className: 'jasmine-seed-bar' },
+ ', randomized with seed ',
+ createDom(
+ 'a',
+ {
+ title: 'randomized with seed ' + order.seed,
+ href: this.#urlBuilder.seedHref(order.seed)
+ },
+ order.seed
+ )
+ );
+ }
+
+ this.add(statusBarClassName, [statusBarMessage, seedBar]);
+ }
+
+ // TODO check test coverage
+ #globalFailureMessage(failure) {
+ if (failure.globalErrorType === 'load') {
+ const prefix = 'Error during loading: ' + failure.message;
+
+ if (failure.filename) {
+ return prefix + ' in ' + failure.filename + ' line ' + failure.lineno;
+ } else {
+ return prefix;
+ }
+ } else if (failure.globalErrorType === 'afterAll') {
+ return afterAllMessagePrefix + failure.message;
+ } else {
+ return failure.message;
+ }
+ }
+
+ addDeprecationWarning(dw) {
+ const children = [];
+ let context;
+
+ switch (dw.runnableType) {
+ case 'spec':
+ context = '(in spec: ' + dw.runnableName + ')';
+ break;
+ case 'suite':
+ context = '(in suite: ' + dw.runnableName + ')';
+ break;
+ default:
+ context = '';
+ }
+
+ for (const line of dw.message.split('\n')) {
+ children.push(line);
+ children.push(createDom('br'));
+ }
+
+ children[0] = 'DEPRECATION: ' + children[0];
+ children.push(context);
+
+ if (dw.stack) {
+ children.push(this.#createExpander(dw.stack));
+ }
+
+ this.add('jasmine-bar jasmine-warning', children);
+ }
+
+ // TODO private?
+ add(className, children) {
+ this.rootEl.appendChild(createDom('span', { className }, children));
+ }
+
+ #createExpander(stackTrace) {
+ const expandLink = createDom('a', { href: '#' }, 'Show stack trace');
+ const root = createDom(
+ 'div',
+ { className: 'jasmine-expander' },
+ expandLink,
+ createDom(
+ 'div',
+ { className: 'jasmine-expander-contents jasmine-stack-trace' },
+ stackTrace
+ )
+ );
+
+ expandLink.addEventListener('click', function(e) {
+ e.preventDefault();
+
+ if (root.classList.contains('jasmine-expanded')) {
+ root.classList.remove('jasmine-expanded');
+ expandLink.textContent = 'Show stack trace';
+ } else {
+ root.classList.add('jasmine-expanded');
+ expandLink.textContent = 'Hide stack trace';
+ }
+ });
+
+ return root;
+ }
+ }
+
+ function pluralize(singular, count) {
+ const word = count == 1 ? singular : singular + 's';
+
+ return '' + count + ' ' + word;
+ }
+
+ return AlertsView;
+};
+
+jasmineRequire.Banner = function(j$) {
+ 'use strict';
+
+ const { createDom } = j$.private.htmlReporterUtils;
+
+ class Banner {
+ #navigateWithNewParam;
+
+ constructor(navigateWithNewParam) {
+ this.#navigateWithNewParam = navigateWithNewParam;
+ this.rootEl = createDom(
+ 'div',
+ { className: 'jasmine-banner' },
+ createDom('a', {
+ className: 'jasmine-title',
+ href: 'http://jasmine.github.io/',
+ target: '_blank'
+ }),
+ createDom('span', { className: 'jasmine-version' }, j$.version)
+ );
+ }
+
+ showOptionsMenu(config) {
+ this.rootEl.appendChild(this.#optionsMenu(config));
+ }
+
+ #optionsMenu(config) {
+ const optionsMenuDom = createDom(
+ 'div',
+ { className: 'jasmine-run-options' },
+ createDom('span', { className: 'jasmine-trigger' }, 'Options'),
+ createDom(
+ 'div',
+ { className: 'jasmine-payload' },
+ createDom(
+ 'div',
+ { className: 'jasmine-stop-on-failure' },
+ createDom('input', {
+ className: 'jasmine-fail-fast',
+ id: 'jasmine-fail-fast',
+ type: 'checkbox'
+ }),
+ createDom(
+ 'label',
+ { className: 'jasmine-label', for: 'jasmine-fail-fast' },
+ 'stop execution on spec failure'
+ )
+ ),
+ createDom(
+ 'div',
+ { className: 'jasmine-throw-failures' },
+ createDom('input', {
+ className: 'jasmine-throw',
+ id: 'jasmine-throw-failures',
+ type: 'checkbox'
+ }),
+ createDom(
+ 'label',
+ { className: 'jasmine-label', for: 'jasmine-throw-failures' },
+ 'stop spec on expectation failure'
+ )
+ ),
+ createDom(
+ 'div',
+ { className: 'jasmine-random-order' },
+ createDom('input', {
+ className: 'jasmine-random',
+ id: 'jasmine-random-order',
+ type: 'checkbox'
+ }),
+ createDom(
+ 'label',
+ { className: 'jasmine-label', for: 'jasmine-random-order' },
+ 'run tests in random order'
+ )
+ ),
+ createDom(
+ 'div',
+ { className: 'jasmine-hide-disabled' },
+ createDom('input', {
+ className: 'jasmine-disabled',
+ id: 'jasmine-hide-disabled',
+ type: 'checkbox'
+ }),
+ createDom(
+ 'label',
+ { className: 'jasmine-label', for: 'jasmine-hide-disabled' },
+ 'hide disabled tests'
+ )
+ )
+ )
+ );
+
+ const failFastCheckbox = optionsMenuDom.querySelector(
+ '#jasmine-fail-fast'
+ );
+ failFastCheckbox.checked = config.stopOnSpecFailure;
+ failFastCheckbox.onclick = () => {
+ this.#navigateWithNewParam(
+ 'stopOnSpecFailure',
+ !config.stopOnSpecFailure
+ );
+ };
+
+ const throwCheckbox = optionsMenuDom.querySelector(
+ '#jasmine-throw-failures'
+ );
+ throwCheckbox.checked = config.stopSpecOnExpectationFailure;
+ throwCheckbox.onclick = () => {
+ this.#navigateWithNewParam(
+ 'stopSpecOnExpectationFailure',
+ !config.stopSpecOnExpectationFailure
+ );
+ };
+
+ const randomCheckbox = optionsMenuDom.querySelector(
+ '#jasmine-random-order'
+ );
+ randomCheckbox.checked = config.random;
+ randomCheckbox.onclick = () => {
+ this.#navigateWithNewParam('random', !config.random);
+ };
+
+ const hideDisabled = optionsMenuDom.querySelector(
+ '#jasmine-hide-disabled'
+ );
+ hideDisabled.checked = config.hideDisabled;
+ // TODO: backfill tests for this!
+ hideDisabled.onclick = () => {
+ this.#navigateWithNewParam('hideDisabled', !config.hideDisabled);
+ };
+
+ const optionsTrigger = optionsMenuDom.querySelector('.jasmine-trigger'),
+ optionsPayload = optionsMenuDom.querySelector('.jasmine-payload'),
+ isOpen = /\bjasmine-open\b/;
+
+ optionsTrigger.onclick = function() {
+ if (isOpen.test(optionsPayload.className)) {
+ optionsPayload.className = optionsPayload.className.replace(
+ isOpen,
+ ''
+ );
+ } else {
+ optionsPayload.className += ' jasmine-open';
+ }
+ };
+
+ return optionsMenuDom;
+ }
+ }
+
+ return Banner;
+};
+
+jasmineRequire.FailuresView = function(j$) {
+ 'use strict';
+
+ const { createDom } = j$.private.htmlReporterUtils;
+
+ class FailuresView {
+ #urlBuilder;
+ #failureEls;
+
+ constructor(urlBuilder) {
+ this.#urlBuilder = urlBuilder;
+ this.#failureEls = [];
+ this.rootEl = createDom(
+ 'div',
+ { className: 'jasmine-results' },
+ createDom('div', { className: 'jasmine-failures' })
+ );
+ }
+
+ append(result, parent) {
+ // TODO: Figure out why the reuslt is wrong if we build the DOM node later
+ this.#failureEls.push(this.#makeFailureEl(result, parent));
+ }
+
+ // TODO move this to state builder or something
+ any() {
+ return this.#failureEls.length > 0;
+ }
+
+ show() {
+ const failureNode = this.rootEl.querySelector('.jasmine-failures');
+
+ for (const el of this.#failureEls) {
+ failureNode.appendChild(el);
+ }
+ }
+
+ #makeFailureEl(result, parent) {
+ const failure = createDom(
+ 'div',
+ { className: 'jasmine-spec-detail jasmine-failed' },
+ this.#failureDescription(result, parent),
+ createDom('div', { className: 'jasmine-messages' })
+ );
+ const messages = failure.childNodes[1];
+
+ for (let i = 0; i < result.failedExpectations.length; i++) {
+ const expectation = result.failedExpectations[i];
+ messages.appendChild(
+ createDom(
+ 'div',
+ { className: 'jasmine-result-message' },
+ expectation.message
+ )
+ );
+ messages.appendChild(
+ createDom(
+ 'div',
+ { className: 'jasmine-stack-trace' },
+ expectation.stack
+ )
+ );
+ }
+
+ if (result.failedExpectations.length === 0) {
+ messages.appendChild(
+ createDom(
+ 'div',
+ { className: 'jasmine-result-message' },
+ 'Spec has no expectations'
+ )
+ );
+ }
+
+ if (result.debugLogs) {
+ messages.appendChild(this.#debugLogTable(result.debugLogs));
+ }
+
+ return failure;
+ }
+
+ #failureDescription(result, suite) {
+ const wrapper = createDom(
+ 'div',
+ { className: 'jasmine-description' },
+ createDom(
+ 'a',
+ {
+ title: result.description,
+ href: this.#urlBuilder.specHref(result)
+ },
+ result.description
+ )
+ );
+ let suiteLink;
+
+ while (suite && suite.parent) {
+ wrapper.insertBefore(
+ document.createTextNode(' > '),
+ wrapper.firstChild
+ );
+ suiteLink = createDom(
+ 'a',
+ { href: this.#urlBuilder.suiteHref(suite) },
+ suite.result.description
+ );
+ wrapper.insertBefore(suiteLink, wrapper.firstChild);
+
+ suite = suite.parent;
+ }
+
+ return wrapper;
+ }
+
+ #debugLogTable(debugLogs) {
+ const tbody = createDom('tbody');
+
+ for (const entry of debugLogs) {
+ tbody.appendChild(
+ createDom(
+ 'tr',
+ {},
+ createDom('td', {}, entry.timestamp.toString()),
+ createDom(
+ 'td',
+ { className: 'jasmine-debug-log-msg' },
+ entry.message
+ )
+ )
+ );
+ }
+
+ return createDom(
+ 'div',
+ { className: 'jasmine-debug-log' },
+ createDom(
+ 'div',
+ { className: 'jasmine-debug-log-header' },
+ 'Debug logs'
+ ),
+ createDom(
+ 'table',
+ {},
+ createDom(
+ 'thead',
+ {},
+ createDom(
+ 'tr',
+ {},
+ createDom('th', {}, 'Time (ms)'),
+ createDom('th', {}, 'Message')
+ )
+ ),
+ tbody
+ )
+ );
+ }
+ }
+
+ return FailuresView;
+};
+
+jasmineRequire.htmlReporterUtils = function(j$) {
'use strict';
function createDom(type, attrs, childrenArrayOrVarArgs) {
@@ -1150,5 +920,309 @@ jasmineRequire.createDom = function(j$) {
return el;
}
- return createDom;
+ function noExpectations(result) {
+ const allExpectations =
+ result.failedExpectations.length + result.passedExpectations.length;
+
+ return (
+ allExpectations === 0 &&
+ (result.status === 'passed' || result.status === 'failed')
+ );
+ }
+
+ return { createDom, noExpectations };
+};
+
+jasmineRequire.HtmlSpecFilter = function() {
+ 'use strict';
+
+ function HtmlSpecFilter(options) {
+ const filterString =
+ options &&
+ options.filterString() &&
+ options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
+ const filterPattern = new RegExp(filterString);
+
+ /**
+ * Determines whether the spec with the specified name should be executed.
+ * @name HtmlSpecFilter#matches
+ * @function
+ * @param {string} specName The full name of the spec
+ * @returns {boolean}
+ */
+ this.matches = function(specName) {
+ return filterPattern.test(specName);
+ };
+ }
+
+ return HtmlSpecFilter;
+};
+
+jasmineRequire.ResultsStateBuilder = function(j$) {
+ 'use strict';
+
+ class ResultsStateBuilder {
+ constructor() {
+ this.topResults = new j$.private.ResultsNode({}, '', null);
+ this.currentParent = this.topResults;
+ this.totalSpecsDefined = 0;
+ this.specsExecuted = 0;
+ this.failureCount = 0;
+ this.pendingSpecCount = 0;
+ this.deprecationWarnings = [];
+ }
+
+ suiteStarted(result) {
+ this.currentParent.addChild(result, 'suite');
+ this.currentParent = this.currentParent.last();
+ }
+
+ suiteDone(result) {
+ this.currentParent.updateResult(result);
+ this.#addDeprecationWarnings(result, 'suite');
+
+ if (this.currentParent !== this.topResults) {
+ this.currentParent = this.currentParent.parent;
+ }
+
+ if (result.status === 'failed') {
+ this.failureCount++;
+ }
+ }
+
+ specDone(result) {
+ this.currentParent.addChild(result, 'spec');
+ this.#addDeprecationWarnings(result, 'spec');
+
+ if (result.status !== 'excluded') {
+ this.specsExecuted++;
+ }
+
+ if (result.status === 'failed') {
+ this.failureCount++;
+ }
+
+ if (result.status == 'pending') {
+ this.pendingSpecCount++;
+ }
+ }
+
+ jasmineStarted(result) {
+ this.totalSpecsDefined = result.totalSpecsDefined;
+ }
+
+ jasmineDone(result) {
+ if (result.failedExpectations) {
+ this.failureCount += result.failedExpectations.length;
+ }
+
+ this.#addDeprecationWarnings(result);
+ }
+
+ #addDeprecationWarnings(result, runnableType) {
+ if (result.deprecationWarnings) {
+ for (const dw of result.deprecationWarnings) {
+ this.deprecationWarnings.push({
+ message: dw.message,
+ stack: dw.stack,
+ runnableName: result.fullName,
+ runnableType: runnableType
+ });
+ }
+ }
+ }
+ }
+
+ return ResultsStateBuilder;
+};
+
+jasmineRequire.SummaryTreeView = function(j$) {
+ 'use strict';
+
+ const { createDom, noExpectations } = j$.private.htmlReporterUtils;
+
+ class SummaryTreeView {
+ #urlBuilder;
+ #filterSpecs;
+
+ constructor(urlBuilder, filterSpecs) {
+ this.#urlBuilder = urlBuilder;
+ this.#filterSpecs = filterSpecs;
+ this.rootEl = createDom('div', {
+ className: 'jasmine-summary'
+ });
+ }
+
+ addResults(resultsTree) {
+ this.#addResults(resultsTree, this.rootEl);
+ }
+
+ #addResults(resultsTree, domParent) {
+ let specListNode;
+ for (let i = 0; i < resultsTree.children.length; i++) {
+ const resultNode = resultsTree.children[i];
+ if (this.#filterSpecs && !hasActiveSpec(resultNode)) {
+ continue;
+ }
+ if (resultNode.type === 'suite') {
+ const suiteListNode = createDom(
+ 'ul',
+ { className: 'jasmine-suite', id: 'suite-' + resultNode.result.id },
+ createDom(
+ 'li',
+ {
+ className:
+ 'jasmine-suite-detail jasmine-' + resultNode.result.status
+ },
+ createDom(
+ 'a',
+ { href: this.#urlBuilder.specHref(resultNode.result) },
+ resultNode.result.description
+ )
+ )
+ );
+
+ this.#addResults(resultNode, suiteListNode);
+ domParent.appendChild(suiteListNode);
+ }
+ if (resultNode.type === 'spec') {
+ if (domParent.getAttribute('class') !== 'jasmine-specs') {
+ specListNode = createDom('ul', {
+ className: 'jasmine-specs'
+ });
+ domParent.appendChild(specListNode);
+ }
+ let specDescription = resultNode.result.description;
+ if (noExpectations(resultNode.result)) {
+ specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
+ }
+ if (resultNode.result.status === 'pending') {
+ if (resultNode.result.pendingReason !== '') {
+ specDescription +=
+ ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
+ } else {
+ specDescription += ' PENDING';
+ }
+ }
+ specListNode.appendChild(
+ createDom(
+ 'li',
+ {
+ className: 'jasmine-' + resultNode.result.status,
+ id: 'spec-' + resultNode.result.id
+ },
+ createDom(
+ 'a',
+ { href: this.#urlBuilder.specHref(resultNode.result) },
+ specDescription
+ ),
+ createDom(
+ 'span',
+ { className: 'jasmine-spec-duration' },
+ '(' + resultNode.result.duration + 'ms)'
+ )
+ )
+ );
+ }
+ }
+ }
+ }
+
+ function hasActiveSpec(resultNode) {
+ if (resultNode.type === 'spec' && resultNode.result.status !== 'excluded') {
+ return true;
+ }
+
+ if (resultNode.type === 'suite') {
+ for (let i = 0, j = resultNode.children.length; i < j; i++) {
+ if (hasActiveSpec(resultNode.children[i])) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return SummaryTreeView;
+};
+
+jasmineRequire.SymbolsView = function(j$) {
+ 'use strict';
+
+ const { createDom, noExpectations } = j$.private.htmlReporterUtils;
+
+ class SymbolsView {
+ constructor() {
+ this.rootEl = createDom('ul', {
+ className: 'jasmine-symbol-summary'
+ });
+ }
+
+ append(result, config) {
+ this.rootEl.appendChild(
+ createDom('li', {
+ className: this.#className(result, config),
+ id: 'spec_' + result.id,
+ title: result.fullName
+ })
+ );
+ }
+
+ #className(result, config) {
+ if (noExpectations(result) && result.status === 'passed') {
+ return 'jasmine-empty';
+ } else if (result.status === 'excluded') {
+ if (config.hideDisabled) {
+ return 'jasmine-excluded-no-display';
+ } else {
+ return 'jasmine-excluded';
+ }
+ } else {
+ return 'jasmine-' + result.status;
+ }
+ }
+ }
+
+ return SymbolsView;
+};
+
+jasmineRequire.UrlBuilder = function() {
+ 'use strict';
+
+ class UrlBuilder {
+ #addToExistingQueryString;
+
+ constructor(addToExistingQueryString) {
+ this.#addToExistingQueryString = function(k, v) {
+ // include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
+ return (
+ (window.location.pathname || '') + addToExistingQueryString(k, v)
+ );
+ };
+ }
+
+ suiteHref(suite) {
+ const els = [];
+
+ while (suite && suite.parent) {
+ els.unshift(suite.result.description);
+ suite = suite.parent;
+ }
+
+ return this.#addToExistingQueryString('spec', els.join(' '));
+ }
+
+ specHref(result) {
+ return this.#addToExistingQueryString('spec', result.fullName);
+ }
+
+ runAllHref() {
+ return this.#addToExistingQueryString('spec', '');
+ }
+
+ seedHref(seed) {
+ return this.#addToExistingQueryString('seed', seed);
+ }
+ }
+
+ return UrlBuilder;
};
diff --git a/src/html/AlertsView.js b/src/html/AlertsView.js
new file mode 100644
index 00000000..2d7805a3
--- /dev/null
+++ b/src/html/AlertsView.js
@@ -0,0 +1,208 @@
+jasmineRequire.AlertsView = function(j$) {
+ 'use strict';
+
+ const { createDom } = j$.private.htmlReporterUtils;
+ const errorBarClassName = 'jasmine-bar jasmine-errored';
+ const afterAllMessagePrefix = 'AfterAll ';
+
+ class AlertsView {
+ #urlBuilder;
+
+ constructor(urlBuilder) {
+ this.#urlBuilder = urlBuilder;
+ this.rootEl = createDom('div', { className: 'jasmine-alert' });
+ }
+
+ addDuration(ms) {
+ this.add('jasmine-duration', 'finished in ' + ms / 1000 + 's');
+ }
+
+ addSkipped(numExecuted, numDefined) {
+ // TODO: backfill tests for this
+ this.add(
+ 'jasmine-bar jasmine-skipped',
+ createDom(
+ 'a',
+ { href: this.#urlBuilder.runAllHref(), title: 'Run all specs' },
+ `Ran ${numExecuted} of ${numDefined} specs - run all`
+ )
+ );
+ }
+
+ addFailureToggle(onClickFailures, onClickSpecList) {
+ const failuresLink = createDom(
+ 'a',
+ { className: 'jasmine-failures-menu', href: '#' },
+ 'Failures'
+ );
+ let specListLink = createDom(
+ 'a',
+ { className: 'jasmine-spec-list-menu', href: '#' },
+ 'Spec List'
+ );
+
+ failuresLink.onclick = function() {
+ onClickFailures();
+ return false;
+ };
+
+ specListLink.onclick = function() {
+ onClickSpecList();
+ return false;
+ };
+
+ this.add('jasmine-menu jasmine-bar jasmine-spec-list', [
+ createDom('span', {}, 'Spec List | '),
+ failuresLink
+ ]);
+ this.add('jasmine-menu jasmine-bar jasmine-failure-list', [
+ specListLink,
+ createDom('span', {}, ' | Failures ')
+ ]);
+ }
+
+ addGlobalFailure(failure) {
+ this.add(errorBarClassName, this.#globalFailureMessage(failure));
+ }
+
+ // TODO check test coverage
+ addSeedBar(doneResult, stateBuilder, order) {
+ let statusBarMessage = '';
+ let statusBarClassName = 'jasmine-overall-result jasmine-bar ';
+ const globalFailures =
+ (doneResult && doneResult.failedExpectations) || [];
+ const failed = stateBuilder.failureCount + globalFailures.length > 0;
+
+ if (stateBuilder.totalSpecsDefined > 0 || failed) {
+ statusBarMessage +=
+ pluralize('spec', stateBuilder.specsExecuted) +
+ ', ' +
+ pluralize('failure', stateBuilder.failureCount);
+ if (stateBuilder.pendingSpecCount) {
+ statusBarMessage +=
+ ', ' + pluralize('pending spec', stateBuilder.pendingSpecCount);
+ }
+ }
+
+ if (doneResult.overallStatus === 'passed') {
+ statusBarClassName += ' jasmine-passed ';
+ } else if (doneResult.overallStatus === 'incomplete') {
+ statusBarClassName += ' jasmine-incomplete ';
+ statusBarMessage =
+ 'Incomplete: ' +
+ doneResult.incompleteReason +
+ ', ' +
+ statusBarMessage;
+ } else {
+ statusBarClassName += ' jasmine-failed ';
+ }
+
+ let seedBar;
+ if (order && order.random) {
+ seedBar = createDom(
+ 'span',
+ { className: 'jasmine-seed-bar' },
+ ', randomized with seed ',
+ createDom(
+ 'a',
+ {
+ title: 'randomized with seed ' + order.seed,
+ href: this.#urlBuilder.seedHref(order.seed)
+ },
+ order.seed
+ )
+ );
+ }
+
+ this.add(statusBarClassName, [statusBarMessage, seedBar]);
+ }
+
+ // TODO check test coverage
+ #globalFailureMessage(failure) {
+ if (failure.globalErrorType === 'load') {
+ const prefix = 'Error during loading: ' + failure.message;
+
+ if (failure.filename) {
+ return prefix + ' in ' + failure.filename + ' line ' + failure.lineno;
+ } else {
+ return prefix;
+ }
+ } else if (failure.globalErrorType === 'afterAll') {
+ return afterAllMessagePrefix + failure.message;
+ } else {
+ return failure.message;
+ }
+ }
+
+ addDeprecationWarning(dw) {
+ const children = [];
+ let context;
+
+ switch (dw.runnableType) {
+ case 'spec':
+ context = '(in spec: ' + dw.runnableName + ')';
+ break;
+ case 'suite':
+ context = '(in suite: ' + dw.runnableName + ')';
+ break;
+ default:
+ context = '';
+ }
+
+ for (const line of dw.message.split('\n')) {
+ children.push(line);
+ children.push(createDom('br'));
+ }
+
+ children[0] = 'DEPRECATION: ' + children[0];
+ children.push(context);
+
+ if (dw.stack) {
+ children.push(this.#createExpander(dw.stack));
+ }
+
+ this.add('jasmine-bar jasmine-warning', children);
+ }
+
+ // TODO private?
+ add(className, children) {
+ this.rootEl.appendChild(createDom('span', { className }, children));
+ }
+
+ #createExpander(stackTrace) {
+ const expandLink = createDom('a', { href: '#' }, 'Show stack trace');
+ const root = createDom(
+ 'div',
+ { className: 'jasmine-expander' },
+ expandLink,
+ createDom(
+ 'div',
+ { className: 'jasmine-expander-contents jasmine-stack-trace' },
+ stackTrace
+ )
+ );
+
+ expandLink.addEventListener('click', function(e) {
+ e.preventDefault();
+
+ if (root.classList.contains('jasmine-expanded')) {
+ root.classList.remove('jasmine-expanded');
+ expandLink.textContent = 'Show stack trace';
+ } else {
+ root.classList.add('jasmine-expanded');
+ expandLink.textContent = 'Hide stack trace';
+ }
+ });
+
+ return root;
+ }
+ }
+
+ function pluralize(singular, count) {
+ const word = count == 1 ? singular : singular + 's';
+
+ return '' + count + ' ' + word;
+ }
+
+ return AlertsView;
+};
diff --git a/src/html/Banner.js b/src/html/Banner.js
new file mode 100644
index 00000000..6127377b
--- /dev/null
+++ b/src/html/Banner.js
@@ -0,0 +1,153 @@
+jasmineRequire.Banner = function(j$) {
+ 'use strict';
+
+ const { createDom } = j$.private.htmlReporterUtils;
+
+ class Banner {
+ #navigateWithNewParam;
+
+ constructor(navigateWithNewParam) {
+ this.#navigateWithNewParam = navigateWithNewParam;
+ this.rootEl = createDom(
+ 'div',
+ { className: 'jasmine-banner' },
+ createDom('a', {
+ className: 'jasmine-title',
+ href: 'http://jasmine.github.io/',
+ target: '_blank'
+ }),
+ createDom('span', { className: 'jasmine-version' }, j$.version)
+ );
+ }
+
+ showOptionsMenu(config) {
+ this.rootEl.appendChild(this.#optionsMenu(config));
+ }
+
+ #optionsMenu(config) {
+ const optionsMenuDom = createDom(
+ 'div',
+ { className: 'jasmine-run-options' },
+ createDom('span', { className: 'jasmine-trigger' }, 'Options'),
+ createDom(
+ 'div',
+ { className: 'jasmine-payload' },
+ createDom(
+ 'div',
+ { className: 'jasmine-stop-on-failure' },
+ createDom('input', {
+ className: 'jasmine-fail-fast',
+ id: 'jasmine-fail-fast',
+ type: 'checkbox'
+ }),
+ createDom(
+ 'label',
+ { className: 'jasmine-label', for: 'jasmine-fail-fast' },
+ 'stop execution on spec failure'
+ )
+ ),
+ createDom(
+ 'div',
+ { className: 'jasmine-throw-failures' },
+ createDom('input', {
+ className: 'jasmine-throw',
+ id: 'jasmine-throw-failures',
+ type: 'checkbox'
+ }),
+ createDom(
+ 'label',
+ { className: 'jasmine-label', for: 'jasmine-throw-failures' },
+ 'stop spec on expectation failure'
+ )
+ ),
+ createDom(
+ 'div',
+ { className: 'jasmine-random-order' },
+ createDom('input', {
+ className: 'jasmine-random',
+ id: 'jasmine-random-order',
+ type: 'checkbox'
+ }),
+ createDom(
+ 'label',
+ { className: 'jasmine-label', for: 'jasmine-random-order' },
+ 'run tests in random order'
+ )
+ ),
+ createDom(
+ 'div',
+ { className: 'jasmine-hide-disabled' },
+ createDom('input', {
+ className: 'jasmine-disabled',
+ id: 'jasmine-hide-disabled',
+ type: 'checkbox'
+ }),
+ createDom(
+ 'label',
+ { className: 'jasmine-label', for: 'jasmine-hide-disabled' },
+ 'hide disabled tests'
+ )
+ )
+ )
+ );
+
+ const failFastCheckbox = optionsMenuDom.querySelector(
+ '#jasmine-fail-fast'
+ );
+ failFastCheckbox.checked = config.stopOnSpecFailure;
+ failFastCheckbox.onclick = () => {
+ this.#navigateWithNewParam(
+ 'stopOnSpecFailure',
+ !config.stopOnSpecFailure
+ );
+ };
+
+ const throwCheckbox = optionsMenuDom.querySelector(
+ '#jasmine-throw-failures'
+ );
+ throwCheckbox.checked = config.stopSpecOnExpectationFailure;
+ throwCheckbox.onclick = () => {
+ this.#navigateWithNewParam(
+ 'stopSpecOnExpectationFailure',
+ !config.stopSpecOnExpectationFailure
+ );
+ };
+
+ const randomCheckbox = optionsMenuDom.querySelector(
+ '#jasmine-random-order'
+ );
+ randomCheckbox.checked = config.random;
+ randomCheckbox.onclick = () => {
+ this.#navigateWithNewParam('random', !config.random);
+ };
+
+ const hideDisabled = optionsMenuDom.querySelector(
+ '#jasmine-hide-disabled'
+ );
+ hideDisabled.checked = config.hideDisabled;
+ // TODO: backfill tests for this!
+ hideDisabled.onclick = () => {
+ this.#navigateWithNewParam('hideDisabled', !config.hideDisabled);
+ };
+
+ const optionsTrigger = optionsMenuDom.querySelector('.jasmine-trigger'),
+ optionsPayload = optionsMenuDom.querySelector('.jasmine-payload'),
+ isOpen = /\bjasmine-open\b/;
+
+ optionsTrigger.onclick = function() {
+ if (isOpen.test(optionsPayload.className)) {
+ optionsPayload.className = optionsPayload.className.replace(
+ isOpen,
+ ''
+ );
+ } else {
+ optionsPayload.className += ' jasmine-open';
+ }
+ };
+
+ return optionsMenuDom;
+ }
+ }
+
+ return Banner;
+};
diff --git a/src/html/FailuresView.js b/src/html/FailuresView.js
new file mode 100644
index 00000000..e4bb9246
--- /dev/null
+++ b/src/html/FailuresView.js
@@ -0,0 +1,161 @@
+jasmineRequire.FailuresView = function(j$) {
+ 'use strict';
+
+ const { createDom } = j$.private.htmlReporterUtils;
+
+ class FailuresView {
+ #urlBuilder;
+ #failureEls;
+
+ constructor(urlBuilder) {
+ this.#urlBuilder = urlBuilder;
+ this.#failureEls = [];
+ this.rootEl = createDom(
+ 'div',
+ { className: 'jasmine-results' },
+ createDom('div', { className: 'jasmine-failures' })
+ );
+ }
+
+ append(result, parent) {
+ // TODO: Figure out why the reuslt is wrong if we build the DOM node later
+ this.#failureEls.push(this.#makeFailureEl(result, parent));
+ }
+
+ // TODO move this to state builder or something
+ any() {
+ return this.#failureEls.length > 0;
+ }
+
+ show() {
+ const failureNode = this.rootEl.querySelector('.jasmine-failures');
+
+ for (const el of this.#failureEls) {
+ failureNode.appendChild(el);
+ }
+ }
+
+ #makeFailureEl(result, parent) {
+ const failure = createDom(
+ 'div',
+ { className: 'jasmine-spec-detail jasmine-failed' },
+ this.#failureDescription(result, parent),
+ createDom('div', { className: 'jasmine-messages' })
+ );
+ const messages = failure.childNodes[1];
+
+ for (let i = 0; i < result.failedExpectations.length; i++) {
+ const expectation = result.failedExpectations[i];
+ messages.appendChild(
+ createDom(
+ 'div',
+ { className: 'jasmine-result-message' },
+ expectation.message
+ )
+ );
+ messages.appendChild(
+ createDom(
+ 'div',
+ { className: 'jasmine-stack-trace' },
+ expectation.stack
+ )
+ );
+ }
+
+ if (result.failedExpectations.length === 0) {
+ messages.appendChild(
+ createDom(
+ 'div',
+ { className: 'jasmine-result-message' },
+ 'Spec has no expectations'
+ )
+ );
+ }
+
+ if (result.debugLogs) {
+ messages.appendChild(this.#debugLogTable(result.debugLogs));
+ }
+
+ return failure;
+ }
+
+ #failureDescription(result, suite) {
+ const wrapper = createDom(
+ 'div',
+ { className: 'jasmine-description' },
+ createDom(
+ 'a',
+ {
+ title: result.description,
+ href: this.#urlBuilder.specHref(result)
+ },
+ result.description
+ )
+ );
+ let suiteLink;
+
+ while (suite && suite.parent) {
+ wrapper.insertBefore(
+ document.createTextNode(' > '),
+ wrapper.firstChild
+ );
+ suiteLink = createDom(
+ 'a',
+ { href: this.#urlBuilder.suiteHref(suite) },
+ suite.result.description
+ );
+ wrapper.insertBefore(suiteLink, wrapper.firstChild);
+
+ suite = suite.parent;
+ }
+
+ return wrapper;
+ }
+
+ #debugLogTable(debugLogs) {
+ const tbody = createDom('tbody');
+
+ for (const entry of debugLogs) {
+ tbody.appendChild(
+ createDom(
+ 'tr',
+ {},
+ createDom('td', {}, entry.timestamp.toString()),
+ createDom(
+ 'td',
+ { className: 'jasmine-debug-log-msg' },
+ entry.message
+ )
+ )
+ );
+ }
+
+ return createDom(
+ 'div',
+ { className: 'jasmine-debug-log' },
+ createDom(
+ 'div',
+ { className: 'jasmine-debug-log-header' },
+ 'Debug logs'
+ ),
+ createDom(
+ 'table',
+ {},
+ createDom(
+ 'thead',
+ {},
+ createDom(
+ 'tr',
+ {},
+ createDom('th', {}, 'Time (ms)'),
+ createDom('th', {}, 'Message')
+ )
+ ),
+ tbody
+ )
+ );
+ }
+ }
+
+ return FailuresView;
+};
diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js
index 3fc6f423..856448ec 100644
--- a/src/html/HtmlReporter.js
+++ b/src/html/HtmlReporter.js
@@ -1,80 +1,7 @@
jasmineRequire.HtmlReporter = function(j$) {
'use strict';
- class ResultsStateBuilder {
- constructor() {
- this.topResults = new j$.private.ResultsNode({}, '', null);
- this.currentParent = this.topResults;
- this.totalSpecsDefined = 0;
- this.specsExecuted = 0;
- this.failureCount = 0;
- this.pendingSpecCount = 0;
- this.deprecationWarnings = [];
- }
-
- suiteStarted(result) {
- this.currentParent.addChild(result, 'suite');
- this.currentParent = this.currentParent.last();
- }
-
- suiteDone(result) {
- this.currentParent.updateResult(result);
- this.#addDeprecationWarnings(result, 'suite');
-
- if (this.currentParent !== this.topResults) {
- this.currentParent = this.currentParent.parent;
- }
-
- if (result.status === 'failed') {
- this.failureCount++;
- }
- }
-
- specDone(result) {
- this.currentParent.addChild(result, 'spec');
- this.#addDeprecationWarnings(result, 'spec');
-
- if (result.status !== 'excluded') {
- this.specsExecuted++;
- }
-
- if (result.status === 'failed') {
- this.failureCount++;
- }
-
- if (result.status == 'pending') {
- this.pendingSpecCount++;
- }
- }
-
- jasmineStarted(result) {
- this.totalSpecsDefined = result.totalSpecsDefined;
- }
-
- jasmineDone(result) {
- if (result.failedExpectations) {
- this.failureCount += result.failedExpectations.length;
- }
-
- this.#addDeprecationWarnings(result);
- }
-
- #addDeprecationWarnings(result, runnableType) {
- if (result.deprecationWarnings) {
- for (const dw of result.deprecationWarnings) {
- this.deprecationWarnings.push({
- message: dw.message,
- stack: dw.stack,
- runnableName: result.fullName,
- runnableType: runnableType
- });
- }
- }
- }
- }
-
- const errorBarClassName = 'jasmine-bar jasmine-errored';
- const afterAllMessagePrefix = 'AfterAll ';
+ const { createDom, noExpectations } = j$.private.htmlReporterUtils;
/**
* @class HtmlReporter
@@ -105,7 +32,7 @@ jasmineRequire.HtmlReporter = function(j$) {
this.#getContainer = options.getContainer;
this.#navigateWithNewParam =
options.navigateWithNewParam || function() {};
- this.#urlBuilder = new UrlBuilder(
+ this.#urlBuilder = new j$.private.UrlBuilder(
options.addToExistingQueryString || defaultQueryString
);
this.#filterSpecs = options.filterSpecs;
@@ -120,13 +47,13 @@ jasmineRequire.HtmlReporter = function(j$) {
this.#clearPrior();
this.#config = this.#env ? this.#env.configuration() : {};
- this.#stateBuilder = new ResultsStateBuilder();
+ this.#stateBuilder = new j$.private.ResultsStateBuilder();
- this.#alerts = new AlertsView(this.#urlBuilder);
- this.#symbols = new SymbolsView();
- this.#banner = new Banner(this.#navigateWithNewParam);
- this.#failures = new FailuresView(this.#urlBuilder);
- this.#htmlReporterMain = j$.private.createDom(
+ this.#alerts = new j$.private.AlertsView(this.#urlBuilder);
+ this.#symbols = new j$.private.SymbolsView();
+ this.#banner = new j$.private.Banner(this.#navigateWithNewParam);
+ this.#failures = new j$.private.FailuresView(this.#urlBuilder);
+ this.#htmlReporterMain = createDom(
'div',
{ className: 'jasmine_html-reporter' },
this.#banner.rootEl,
@@ -202,7 +129,10 @@ jasmineRequire.HtmlReporter = function(j$) {
}
const results = this.#find('.jasmine-results');
- const summary = new SummaryTreeView(this.#urlBuilder, this.#filterSpecs);
+ const summary = new j$.private.SummaryTreeView(
+ this.#urlBuilder,
+ this.#filterSpecs
+ );
summary.addResults(this.#stateBuilder.topResults);
results.appendChild(summary.rootEl);
@@ -239,701 +169,9 @@ jasmineRequire.HtmlReporter = function(j$) {
}
}
- function hasActiveSpec(resultNode) {
- if (resultNode.type === 'spec' && resultNode.result.status !== 'excluded') {
- return true;
- }
-
- if (resultNode.type === 'suite') {
- for (let i = 0, j = resultNode.children.length; i < j; i++) {
- if (hasActiveSpec(resultNode.children[i])) {
- return true;
- }
- }
- }
- }
-
- function noExpectations(result) {
- const allExpectations =
- result.failedExpectations.length + result.passedExpectations.length;
-
- return (
- allExpectations === 0 &&
- (result.status === 'passed' || result.status === 'failed')
- );
- }
-
- function pluralize(singular, count) {
- const word = count == 1 ? singular : singular + 's';
-
- return '' + count + ' ' + word;
- }
-
function defaultQueryString(key, value) {
return '?' + key + '=' + value;
}
- class AlertsView {
- #urlBuilder;
-
- constructor(urlBuilder) {
- this.#urlBuilder = urlBuilder;
- this.rootEl = j$.private.createDom('div', { className: 'jasmine-alert' });
- }
-
- addDuration(ms) {
- this.add('jasmine-duration', 'finished in ' + ms / 1000 + 's');
- }
-
- addSkipped(numExecuted, numDefined) {
- // TODO: backfill tests for this
- this.add(
- 'jasmine-bar jasmine-skipped',
- j$.private.createDom(
- 'a',
- { href: this.#urlBuilder.runAllHref(), title: 'Run all specs' },
- `Ran ${numExecuted} of ${numDefined} specs - run all`
- )
- );
- }
-
- addFailureToggle(onClickFailures, onClickSpecList) {
- const failuresLink = j$.private.createDom(
- 'a',
- { className: 'jasmine-failures-menu', href: '#' },
- 'Failures'
- );
- let specListLink = j$.private.createDom(
- 'a',
- { className: 'jasmine-spec-list-menu', href: '#' },
- 'Spec List'
- );
-
- failuresLink.onclick = function() {
- onClickFailures();
- return false;
- };
-
- specListLink.onclick = function() {
- onClickSpecList();
- return false;
- };
-
- this.add('jasmine-menu jasmine-bar jasmine-spec-list', [
- j$.private.createDom('span', {}, 'Spec List | '),
- failuresLink
- ]);
- this.add('jasmine-menu jasmine-bar jasmine-failure-list', [
- specListLink,
- j$.private.createDom('span', {}, ' | Failures ')
- ]);
- }
-
- addGlobalFailure(failure) {
- this.add(errorBarClassName, this.#globalFailureMessage(failure));
- }
-
- // TODO check test coverage
- addSeedBar(doneResult, stateBuilder, order) {
- let statusBarMessage = '';
- let statusBarClassName = 'jasmine-overall-result jasmine-bar ';
- const globalFailures =
- (doneResult && doneResult.failedExpectations) || [];
- const failed = stateBuilder.failureCount + globalFailures.length > 0;
-
- if (stateBuilder.totalSpecsDefined > 0 || failed) {
- statusBarMessage +=
- pluralize('spec', stateBuilder.specsExecuted) +
- ', ' +
- pluralize('failure', stateBuilder.failureCount);
- if (stateBuilder.pendingSpecCount) {
- statusBarMessage +=
- ', ' + pluralize('pending spec', stateBuilder.pendingSpecCount);
- }
- }
-
- if (doneResult.overallStatus === 'passed') {
- statusBarClassName += ' jasmine-passed ';
- } else if (doneResult.overallStatus === 'incomplete') {
- statusBarClassName += ' jasmine-incomplete ';
- statusBarMessage =
- 'Incomplete: ' +
- doneResult.incompleteReason +
- ', ' +
- statusBarMessage;
- } else {
- statusBarClassName += ' jasmine-failed ';
- }
-
- let seedBar;
- if (order && order.random) {
- seedBar = j$.private.createDom(
- 'span',
- { className: 'jasmine-seed-bar' },
- ', randomized with seed ',
- j$.private.createDom(
- 'a',
- {
- title: 'randomized with seed ' + order.seed,
- href: this.#urlBuilder.seedHref(order.seed)
- },
- order.seed
- )
- );
- }
-
- this.add(statusBarClassName, [statusBarMessage, seedBar]);
- }
-
- // TODO check test coverage
- #globalFailureMessage(failure) {
- if (failure.globalErrorType === 'load') {
- const prefix = 'Error during loading: ' + failure.message;
-
- if (failure.filename) {
- return prefix + ' in ' + failure.filename + ' line ' + failure.lineno;
- } else {
- return prefix;
- }
- } else if (failure.globalErrorType === 'afterAll') {
- return afterAllMessagePrefix + failure.message;
- } else {
- return failure.message;
- }
- }
-
- addDeprecationWarning(dw) {
- const children = [];
- let context;
-
- switch (dw.runnableType) {
- case 'spec':
- context = '(in spec: ' + dw.runnableName + ')';
- break;
- case 'suite':
- context = '(in suite: ' + dw.runnableName + ')';
- break;
- default:
- context = '';
- }
-
- for (const line of dw.message.split('\n')) {
- children.push(line);
- children.push(j$.private.createDom('br'));
- }
-
- children[0] = 'DEPRECATION: ' + children[0];
- children.push(context);
-
- if (dw.stack) {
- children.push(this.#createExpander(dw.stack));
- }
-
- this.add('jasmine-bar jasmine-warning', children);
- }
-
- // TODO private?
- add(className, children) {
- this.rootEl.appendChild(
- j$.private.createDom('span', { className }, children)
- );
- }
-
- #createExpander(stackTrace) {
- const expandLink = j$.private.createDom(
- 'a',
- { href: '#' },
- 'Show stack trace'
- );
- const root = j$.private.createDom(
- 'div',
- { className: 'jasmine-expander' },
- expandLink,
- j$.private.createDom(
- 'div',
- { className: 'jasmine-expander-contents jasmine-stack-trace' },
- stackTrace
- )
- );
-
- expandLink.addEventListener('click', function(e) {
- e.preventDefault();
-
- if (root.classList.contains('jasmine-expanded')) {
- root.classList.remove('jasmine-expanded');
- expandLink.textContent = 'Show stack trace';
- } else {
- root.classList.add('jasmine-expanded');
- expandLink.textContent = 'Hide stack trace';
- }
- });
-
- return root;
- }
- }
-
- class Banner {
- #navigateWithNewParam;
-
- constructor(navigateWithNewParam) {
- this.#navigateWithNewParam = navigateWithNewParam;
- this.rootEl = j$.private.createDom(
- 'div',
- { className: 'jasmine-banner' },
- j$.private.createDom('a', {
- className: 'jasmine-title',
- href: 'http://jasmine.github.io/',
- target: '_blank'
- }),
- j$.private.createDom(
- 'span',
- { className: 'jasmine-version' },
- j$.version
- )
- );
- }
-
- showOptionsMenu(config) {
- this.rootEl.appendChild(this.#optionsMenu(config));
- }
-
- #optionsMenu(config) {
- const optionsMenuDom = j$.private.createDom(
- 'div',
- { className: 'jasmine-run-options' },
- j$.private.createDom(
- 'span',
- { className: 'jasmine-trigger' },
- 'Options'
- ),
- j$.private.createDom(
- 'div',
- { className: 'jasmine-payload' },
- j$.private.createDom(
- 'div',
- { className: 'jasmine-stop-on-failure' },
- j$.private.createDom('input', {
- className: 'jasmine-fail-fast',
- id: 'jasmine-fail-fast',
- type: 'checkbox'
- }),
- j$.private.createDom(
- 'label',
- { className: 'jasmine-label', for: 'jasmine-fail-fast' },
- 'stop execution on spec failure'
- )
- ),
- j$.private.createDom(
- 'div',
- { className: 'jasmine-throw-failures' },
- j$.private.createDom('input', {
- className: 'jasmine-throw',
- id: 'jasmine-throw-failures',
- type: 'checkbox'
- }),
- j$.private.createDom(
- 'label',
- { className: 'jasmine-label', for: 'jasmine-throw-failures' },
- 'stop spec on expectation failure'
- )
- ),
- j$.private.createDom(
- 'div',
- { className: 'jasmine-random-order' },
- j$.private.createDom('input', {
- className: 'jasmine-random',
- id: 'jasmine-random-order',
- type: 'checkbox'
- }),
- j$.private.createDom(
- 'label',
- { className: 'jasmine-label', for: 'jasmine-random-order' },
- 'run tests in random order'
- )
- ),
- j$.private.createDom(
- 'div',
- { className: 'jasmine-hide-disabled' },
- j$.private.createDom('input', {
- className: 'jasmine-disabled',
- id: 'jasmine-hide-disabled',
- type: 'checkbox'
- }),
- j$.private.createDom(
- 'label',
- { className: 'jasmine-label', for: 'jasmine-hide-disabled' },
- 'hide disabled tests'
- )
- )
- )
- );
-
- const failFastCheckbox = optionsMenuDom.querySelector(
- '#jasmine-fail-fast'
- );
- failFastCheckbox.checked = config.stopOnSpecFailure;
- failFastCheckbox.onclick = () => {
- this.#navigateWithNewParam(
- 'stopOnSpecFailure',
- !config.stopOnSpecFailure
- );
- };
-
- const throwCheckbox = optionsMenuDom.querySelector(
- '#jasmine-throw-failures'
- );
- throwCheckbox.checked = config.stopSpecOnExpectationFailure;
- throwCheckbox.onclick = () => {
- this.#navigateWithNewParam(
- 'stopSpecOnExpectationFailure',
- !config.stopSpecOnExpectationFailure
- );
- };
-
- const randomCheckbox = optionsMenuDom.querySelector(
- '#jasmine-random-order'
- );
- randomCheckbox.checked = config.random;
- randomCheckbox.onclick = () => {
- this.#navigateWithNewParam('random', !config.random);
- };
-
- const hideDisabled = optionsMenuDom.querySelector(
- '#jasmine-hide-disabled'
- );
- hideDisabled.checked = config.hideDisabled;
- // TODO: backfill tests for this!
- hideDisabled.onclick = () => {
- this.#navigateWithNewParam('hideDisabled', !config.hideDisabled);
- };
-
- const optionsTrigger = optionsMenuDom.querySelector('.jasmine-trigger'),
- optionsPayload = optionsMenuDom.querySelector('.jasmine-payload'),
- isOpen = /\bjasmine-open\b/;
-
- optionsTrigger.onclick = function() {
- if (isOpen.test(optionsPayload.className)) {
- optionsPayload.className = optionsPayload.className.replace(
- isOpen,
- ''
- );
- } else {
- optionsPayload.className += ' jasmine-open';
- }
- };
-
- return optionsMenuDom;
- }
- }
-
- class SymbolsView {
- constructor() {
- this.rootEl = j$.private.createDom('ul', {
- className: 'jasmine-symbol-summary'
- });
- }
-
- append(result, config) {
- this.rootEl.appendChild(
- j$.private.createDom('li', {
- className: this.#className(result, config),
- id: 'spec_' + result.id,
- title: result.fullName
- })
- );
- }
-
- #className(result, config) {
- if (noExpectations(result) && result.status === 'passed') {
- return 'jasmine-empty';
- } else if (result.status === 'excluded') {
- if (config.hideDisabled) {
- return 'jasmine-excluded-no-display';
- } else {
- return 'jasmine-excluded';
- }
- } else {
- return 'jasmine-' + result.status;
- }
- }
- }
-
- class SummaryTreeView {
- #urlBuilder;
- #filterSpecs;
-
- constructor(urlBuilder, filterSpecs) {
- this.#urlBuilder = urlBuilder;
- this.#filterSpecs = filterSpecs;
- this.rootEl = j$.private.createDom('div', {
- className: 'jasmine-summary'
- });
- }
-
- addResults(resultsTree) {
- this.#addResults(resultsTree, this.rootEl);
- }
-
- #addResults(resultsTree, domParent) {
- let specListNode;
- for (let i = 0; i < resultsTree.children.length; i++) {
- const resultNode = resultsTree.children[i];
- if (this.#filterSpecs && !hasActiveSpec(resultNode)) {
- continue;
- }
- if (resultNode.type === 'suite') {
- const suiteListNode = j$.private.createDom(
- 'ul',
- { className: 'jasmine-suite', id: 'suite-' + resultNode.result.id },
- j$.private.createDom(
- 'li',
- {
- className:
- 'jasmine-suite-detail jasmine-' + resultNode.result.status
- },
- j$.private.createDom(
- 'a',
- { href: this.#urlBuilder.specHref(resultNode.result) },
- resultNode.result.description
- )
- )
- );
-
- this.#addResults(resultNode, suiteListNode);
- domParent.appendChild(suiteListNode);
- }
- if (resultNode.type === 'spec') {
- if (domParent.getAttribute('class') !== 'jasmine-specs') {
- specListNode = j$.private.createDom('ul', {
- className: 'jasmine-specs'
- });
- domParent.appendChild(specListNode);
- }
- let specDescription = resultNode.result.description;
- if (noExpectations(resultNode.result)) {
- specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
- }
- if (resultNode.result.status === 'pending') {
- if (resultNode.result.pendingReason !== '') {
- specDescription +=
- ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
- } else {
- specDescription += ' PENDING';
- }
- }
- specListNode.appendChild(
- j$.private.createDom(
- 'li',
- {
- className: 'jasmine-' + resultNode.result.status,
- id: 'spec-' + resultNode.result.id
- },
- j$.private.createDom(
- 'a',
- { href: this.#urlBuilder.specHref(resultNode.result) },
- specDescription
- ),
- j$.private.createDom(
- 'span',
- { className: 'jasmine-spec-duration' },
- '(' + resultNode.result.duration + 'ms)'
- )
- )
- );
- }
- }
- }
- }
-
- class FailuresView {
- #urlBuilder;
- #failureEls;
-
- constructor(urlBuilder) {
- this.#urlBuilder = urlBuilder;
- this.#failureEls = [];
- this.rootEl = j$.private.createDom(
- 'div',
- { className: 'jasmine-results' },
- j$.private.createDom('div', { className: 'jasmine-failures' })
- );
- }
-
- append(result, parent) {
- // TODO: Figure out why the reuslt is wrong if we build the DOM node later
- this.#failureEls.push(this.#makeFailureEl(result, parent));
- }
-
- // TODO move this to state builder or something
- any() {
- return this.#failureEls.length > 0;
- }
-
- show() {
- const failureNode = this.rootEl.querySelector('.jasmine-failures');
-
- for (const el of this.#failureEls) {
- failureNode.appendChild(el);
- }
- }
-
- #makeFailureEl(result, parent) {
- const failure = j$.private.createDom(
- 'div',
- { className: 'jasmine-spec-detail jasmine-failed' },
- this.#failureDescription(result, parent),
- j$.private.createDom('div', { className: 'jasmine-messages' })
- );
- const messages = failure.childNodes[1];
-
- for (let i = 0; i < result.failedExpectations.length; i++) {
- const expectation = result.failedExpectations[i];
- messages.appendChild(
- j$.private.createDom(
- 'div',
- { className: 'jasmine-result-message' },
- expectation.message
- )
- );
- messages.appendChild(
- j$.private.createDom(
- 'div',
- { className: 'jasmine-stack-trace' },
- expectation.stack
- )
- );
- }
-
- if (result.failedExpectations.length === 0) {
- messages.appendChild(
- j$.private.createDom(
- 'div',
- { className: 'jasmine-result-message' },
- 'Spec has no expectations'
- )
- );
- }
-
- if (result.debugLogs) {
- messages.appendChild(this.#debugLogTable(result.debugLogs));
- }
-
- return failure;
- }
-
- #failureDescription(result, suite) {
- const wrapper = j$.private.createDom(
- 'div',
- { className: 'jasmine-description' },
- j$.private.createDom(
- 'a',
- {
- title: result.description,
- href: this.#urlBuilder.specHref(result)
- },
- result.description
- )
- );
- let suiteLink;
-
- while (suite && suite.parent) {
- wrapper.insertBefore(
- document.createTextNode(' > '),
- wrapper.firstChild
- );
- suiteLink = j$.private.createDom(
- 'a',
- { href: this.#urlBuilder.suiteHref(suite) },
- suite.result.description
- );
- wrapper.insertBefore(suiteLink, wrapper.firstChild);
-
- suite = suite.parent;
- }
-
- return wrapper;
- }
-
- #debugLogTable(debugLogs) {
- const tbody = j$.private.createDom('tbody');
-
- for (const entry of debugLogs) {
- tbody.appendChild(
- j$.private.createDom(
- 'tr',
- {},
- j$.private.createDom('td', {}, entry.timestamp.toString()),
- j$.private.createDom(
- 'td',
- { className: 'jasmine-debug-log-msg' },
- entry.message
- )
- )
- );
- }
-
- return j$.private.createDom(
- 'div',
- { className: 'jasmine-debug-log' },
- j$.private.createDom(
- 'div',
- { className: 'jasmine-debug-log-header' },
- 'Debug logs'
- ),
- j$.private.createDom(
- 'table',
- {},
- j$.private.createDom(
- 'thead',
- {},
- j$.private.createDom(
- 'tr',
- {},
- j$.private.createDom('th', {}, 'Time (ms)'),
- j$.private.createDom('th', {}, 'Message')
- )
- ),
- tbody
- )
- );
- }
- }
-
- class UrlBuilder {
- #addToExistingQueryString;
-
- constructor(addToExistingQueryString) {
- this.#addToExistingQueryString = function(k, v) {
- // include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
- return (
- (window.location.pathname || '') + addToExistingQueryString(k, v)
- );
- };
- }
-
- suiteHref(suite) {
- const els = [];
-
- while (suite && suite.parent) {
- els.unshift(suite.result.description);
- suite = suite.parent;
- }
-
- return this.#addToExistingQueryString('spec', els.join(' '));
- }
-
- specHref(result) {
- return this.#addToExistingQueryString('spec', result.fullName);
- }
-
- runAllHref() {
- return this.#addToExistingQueryString('spec', '');
- }
-
- seedHref(seed) {
- return this.#addToExistingQueryString('seed', seed);
- }
- }
-
return HtmlReporter;
};
diff --git a/src/html/ResultsStateBuilder.js b/src/html/ResultsStateBuilder.js
new file mode 100644
index 00000000..ab1390c9
--- /dev/null
+++ b/src/html/ResultsStateBuilder.js
@@ -0,0 +1,77 @@
+jasmineRequire.ResultsStateBuilder = function(j$) {
+ 'use strict';
+
+ class ResultsStateBuilder {
+ constructor() {
+ this.topResults = new j$.private.ResultsNode({}, '', null);
+ this.currentParent = this.topResults;
+ this.totalSpecsDefined = 0;
+ this.specsExecuted = 0;
+ this.failureCount = 0;
+ this.pendingSpecCount = 0;
+ this.deprecationWarnings = [];
+ }
+
+ suiteStarted(result) {
+ this.currentParent.addChild(result, 'suite');
+ this.currentParent = this.currentParent.last();
+ }
+
+ suiteDone(result) {
+ this.currentParent.updateResult(result);
+ this.#addDeprecationWarnings(result, 'suite');
+
+ if (this.currentParent !== this.topResults) {
+ this.currentParent = this.currentParent.parent;
+ }
+
+ if (result.status === 'failed') {
+ this.failureCount++;
+ }
+ }
+
+ specDone(result) {
+ this.currentParent.addChild(result, 'spec');
+ this.#addDeprecationWarnings(result, 'spec');
+
+ if (result.status !== 'excluded') {
+ this.specsExecuted++;
+ }
+
+ if (result.status === 'failed') {
+ this.failureCount++;
+ }
+
+ if (result.status == 'pending') {
+ this.pendingSpecCount++;
+ }
+ }
+
+ jasmineStarted(result) {
+ this.totalSpecsDefined = result.totalSpecsDefined;
+ }
+
+ jasmineDone(result) {
+ if (result.failedExpectations) {
+ this.failureCount += result.failedExpectations.length;
+ }
+
+ this.#addDeprecationWarnings(result);
+ }
+
+ #addDeprecationWarnings(result, runnableType) {
+ if (result.deprecationWarnings) {
+ for (const dw of result.deprecationWarnings) {
+ this.deprecationWarnings.push({
+ message: dw.message,
+ stack: dw.stack,
+ runnableName: result.fullName,
+ runnableType: runnableType
+ });
+ }
+ }
+ }
+ }
+
+ return ResultsStateBuilder;
+};
diff --git a/src/html/SummaryTreeView.js b/src/html/SummaryTreeView.js
new file mode 100644
index 00000000..560641e7
--- /dev/null
+++ b/src/html/SummaryTreeView.js
@@ -0,0 +1,108 @@
+jasmineRequire.SummaryTreeView = function(j$) {
+ 'use strict';
+
+ const { createDom, noExpectations } = j$.private.htmlReporterUtils;
+
+ class SummaryTreeView {
+ #urlBuilder;
+ #filterSpecs;
+
+ constructor(urlBuilder, filterSpecs) {
+ this.#urlBuilder = urlBuilder;
+ this.#filterSpecs = filterSpecs;
+ this.rootEl = createDom('div', {
+ className: 'jasmine-summary'
+ });
+ }
+
+ addResults(resultsTree) {
+ this.#addResults(resultsTree, this.rootEl);
+ }
+
+ #addResults(resultsTree, domParent) {
+ let specListNode;
+ for (let i = 0; i < resultsTree.children.length; i++) {
+ const resultNode = resultsTree.children[i];
+ if (this.#filterSpecs && !hasActiveSpec(resultNode)) {
+ continue;
+ }
+ if (resultNode.type === 'suite') {
+ const suiteListNode = createDom(
+ 'ul',
+ { className: 'jasmine-suite', id: 'suite-' + resultNode.result.id },
+ createDom(
+ 'li',
+ {
+ className:
+ 'jasmine-suite-detail jasmine-' + resultNode.result.status
+ },
+ createDom(
+ 'a',
+ { href: this.#urlBuilder.specHref(resultNode.result) },
+ resultNode.result.description
+ )
+ )
+ );
+
+ this.#addResults(resultNode, suiteListNode);
+ domParent.appendChild(suiteListNode);
+ }
+ if (resultNode.type === 'spec') {
+ if (domParent.getAttribute('class') !== 'jasmine-specs') {
+ specListNode = createDom('ul', {
+ className: 'jasmine-specs'
+ });
+ domParent.appendChild(specListNode);
+ }
+ let specDescription = resultNode.result.description;
+ if (noExpectations(resultNode.result)) {
+ specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
+ }
+ if (resultNode.result.status === 'pending') {
+ if (resultNode.result.pendingReason !== '') {
+ specDescription +=
+ ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
+ } else {
+ specDescription += ' PENDING';
+ }
+ }
+ specListNode.appendChild(
+ createDom(
+ 'li',
+ {
+ className: 'jasmine-' + resultNode.result.status,
+ id: 'spec-' + resultNode.result.id
+ },
+ createDom(
+ 'a',
+ { href: this.#urlBuilder.specHref(resultNode.result) },
+ specDescription
+ ),
+ createDom(
+ 'span',
+ { className: 'jasmine-spec-duration' },
+ '(' + resultNode.result.duration + 'ms)'
+ )
+ )
+ );
+ }
+ }
+ }
+ }
+
+ function hasActiveSpec(resultNode) {
+ if (resultNode.type === 'spec' && resultNode.result.status !== 'excluded') {
+ return true;
+ }
+
+ if (resultNode.type === 'suite') {
+ for (let i = 0, j = resultNode.children.length; i < j; i++) {
+ if (hasActiveSpec(resultNode.children[i])) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return SummaryTreeView;
+};
diff --git a/src/html/SymbolsView.js b/src/html/SymbolsView.js
new file mode 100644
index 00000000..6b918da4
--- /dev/null
+++ b/src/html/SymbolsView.js
@@ -0,0 +1,39 @@
+jasmineRequire.SymbolsView = function(j$) {
+ 'use strict';
+
+ const { createDom, noExpectations } = j$.private.htmlReporterUtils;
+
+ class SymbolsView {
+ constructor() {
+ this.rootEl = createDom('ul', {
+ className: 'jasmine-symbol-summary'
+ });
+ }
+
+ append(result, config) {
+ this.rootEl.appendChild(
+ createDom('li', {
+ className: this.#className(result, config),
+ id: 'spec_' + result.id,
+ title: result.fullName
+ })
+ );
+ }
+
+ #className(result, config) {
+ if (noExpectations(result) && result.status === 'passed') {
+ return 'jasmine-empty';
+ } else if (result.status === 'excluded') {
+ if (config.hideDisabled) {
+ return 'jasmine-excluded-no-display';
+ } else {
+ return 'jasmine-excluded';
+ }
+ } else {
+ return 'jasmine-' + result.status;
+ }
+ }
+ }
+
+ return SymbolsView;
+};
diff --git a/src/html/UrlBuilder.js b/src/html/UrlBuilder.js
new file mode 100644
index 00000000..68ae35f6
--- /dev/null
+++ b/src/html/UrlBuilder.js
@@ -0,0 +1,41 @@
+jasmineRequire.UrlBuilder = function() {
+ 'use strict';
+
+ class UrlBuilder {
+ #addToExistingQueryString;
+
+ constructor(addToExistingQueryString) {
+ this.#addToExistingQueryString = function(k, v) {
+ // include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
+ return (
+ (window.location.pathname || '') + addToExistingQueryString(k, v)
+ );
+ };
+ }
+
+ suiteHref(suite) {
+ const els = [];
+
+ while (suite && suite.parent) {
+ els.unshift(suite.result.description);
+ suite = suite.parent;
+ }
+
+ return this.#addToExistingQueryString('spec', els.join(' '));
+ }
+
+ specHref(result) {
+ return this.#addToExistingQueryString('spec', result.fullName);
+ }
+
+ runAllHref() {
+ return this.#addToExistingQueryString('spec', '');
+ }
+
+ seedHref(seed) {
+ return this.#addToExistingQueryString('seed', seed);
+ }
+ }
+
+ return UrlBuilder;
+};
diff --git a/src/html/createDom.js b/src/html/htmlReporterUtils.js
similarity index 70%
rename from src/html/createDom.js
rename to src/html/htmlReporterUtils.js
index 4f7f3a00..a371413b 100644
--- a/src/html/createDom.js
+++ b/src/html/htmlReporterUtils.js
@@ -1,4 +1,4 @@
-jasmineRequire.createDom = function(j$) {
+jasmineRequire.htmlReporterUtils = function(j$) {
'use strict';
function createDom(type, attrs, childrenArrayOrVarArgs) {
@@ -38,5 +38,15 @@ jasmineRequire.createDom = function(j$) {
return el;
}
- return createDom;
+ function noExpectations(result) {
+ const allExpectations =
+ result.failedExpectations.length + result.passedExpectations.length;
+
+ return (
+ allExpectations === 0 &&
+ (result.status === 'passed' || result.status === 'failed')
+ );
+ }
+
+ return { createDom, noExpectations };
};
diff --git a/src/html/requireHtml.js b/src/html/requireHtml.js
index 4e2a470e..783e7230 100644
--- a/src/html/requireHtml.js
+++ b/src/html/requireHtml.js
@@ -3,7 +3,14 @@ var jasmineRequire = window.jasmineRequire || require('./jasmine.js');
jasmineRequire.html = function(j$) {
j$.private.ResultsNode = jasmineRequire.ResultsNode();
- j$.private.createDom = jasmineRequire.createDom(j$);
+ j$.private.ResultsStateBuilder = jasmineRequire.ResultsStateBuilder(j$);
+ j$.private.htmlReporterUtils = jasmineRequire.htmlReporterUtils(j$);
+ j$.private.AlertsView = jasmineRequire.AlertsView(j$);
+ j$.private.Banner = jasmineRequire.Banner(j$);
+ j$.private.SymbolsView = jasmineRequire.SymbolsView(j$);
+ j$.private.SummaryTreeView = jasmineRequire.SummaryTreeView(j$);
+ j$.private.FailuresView = jasmineRequire.FailuresView(j$);
+ j$.private.UrlBuilder = jasmineRequire.UrlBuilder();
j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
j$.QueryString = jasmineRequire.QueryString();
j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();