Merge branch 'slow-reporter' into 6.0

This commit is contained in:
Steve Gravrock
2025-11-15 09:01:44 -08:00
12 changed files with 1034 additions and 92 deletions

View File

@@ -35,6 +35,8 @@ jasmineRequire.html = function(j$) {
j$.private.SymbolsView = jasmineRequire.SymbolsView(j$);
j$.private.SummaryTreeView = jasmineRequire.SummaryTreeView(j$);
j$.private.FailuresView = jasmineRequire.FailuresView(j$);
j$.private.PerformanceView = jasmineRequire.PerformanceView(j$);
j$.private.TabBar = jasmineRequire.TabBar(j$);
j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
j$.HtmlReporterV2Urls = jasmineRequire.HtmlReporterV2Urls(j$);
j$.HtmlReporterV2 = jasmineRequire.HtmlReporterV2(j$);
@@ -187,16 +189,52 @@ jasmineRequire.HtmlReporter = function(j$) {
results.appendChild(summary.rootEl);
if (this.#stateBuilder.anyNonTopSuiteFailures) {
this.#alerts.addFailureToggle(
() => this.#setMenuModeTo('jasmine-failure-list'),
() => this.#setMenuModeTo('jasmine-spec-list')
);
this.#addFailureToggle();
this.#setMenuModeTo('jasmine-failure-list');
this.#failures.show();
}
}
#addFailureToggle() {
const onClickFailures = () => this.#setMenuModeTo('jasmine-failure-list');
const onClickSpecList = () => this.#setMenuModeTo('jasmine-spec-list');
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.#alerts.addBar(
createDom(
'span',
{ className: 'jasmine-menu jasmine-bar jasmine-spec-list' },
[createDom('span', {}, 'Spec List | '), failuresLink]
)
);
this.#alerts.addBar(
createDom(
'span',
{ className: 'jasmine-menu jasmine-bar jasmine-failure-list' },
[specListLink, createDom('span', {}, ' | Failures ')]
)
);
}
#find(selector) {
return this.#getContainer().querySelector(
'.jasmine_html-reporter ' + selector
@@ -434,6 +472,7 @@ jasmineRequire.AlertsView = function(j$) {
);
}
// TODO: remove this once HtmlReporterV2 doesn't use it
addFailureToggle(onClickFailures, onClickSpecList) {
const failuresLink = createDom(
'a',
@@ -456,14 +495,20 @@ jasmineRequire.AlertsView = function(j$) {
return false;
};
this.#createAndAdd('jasmine-menu jasmine-bar jasmine-spec-list', [
createDom('span', {}, 'Spec List | '),
failuresLink
]);
this.#createAndAdd('jasmine-menu jasmine-bar jasmine-failure-list', [
specListLink,
createDom('span', {}, ' | Failures ')
]);
this.rootEl.appendChild(
createDom(
'span',
{ className: 'jasmine-menu jasmine-bar jasmine-spec-list' },
[createDom('span', {}, 'Spec List | '), failuresLink]
)
);
this.rootEl.appendChild(
createDom(
'span',
{ className: 'jasmine-menu jasmine-bar jasmine-failure-list' },
[specListLink, createDom('span', {}, ' | Failures ')]
)
);
}
addGlobalFailure(failure) {
@@ -946,6 +991,10 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
const { createDom, noExpectations } = j$.private.htmlReporterUtils;
const specListTabId = 'jasmine-specListTab';
const failuresTabId = 'jasmine-failuresTab';
const perfTabId = 'jasmine-perfTab';
/**
* @class HtmlReporterV2
* @classdesc Displays results and allows re-running individual specs and suites.
@@ -974,6 +1023,7 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
// Sub-views
#alerts;
#statusBar;
#tabBar;
#progress;
#banner;
#failures;
@@ -1011,6 +1061,25 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
this.#statusBar = new j$.private.OverallStatusBar(this.#urlBuilder);
this.#statusBar.showRunning();
this.#alerts.addBar(this.#statusBar.rootEl);
this.#tabBar = new j$.private.TabBar(
[
{ id: specListTabId, label: 'Spec List' },
{ id: failuresTabId, label: 'Failures' },
{ id: perfTabId, label: 'Performance' }
],
tabId => {
if (tabId === specListTabId) {
this.#setMenuModeTo('jasmine-spec-list');
} else if (tabId === failuresTabId) {
this.#setMenuModeTo('jasmine-failure-list');
} else {
this.#setMenuModeTo('jasmine-performance');
}
}
);
this.#alerts.addBar(this.#tabBar.rootEl);
this.#progress = new ProgressView();
this.#banner = new j$.private.Banner(
this.#queryString.navigateWithNewParam.bind(this.#queryString),
@@ -1101,15 +1170,17 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
);
summary.addResults(this.#stateBuilder.topResults);
results.appendChild(summary.rootEl);
const perf = new j$.private.PerformanceView();
perf.addResults(this.#stateBuilder.topResults);
results.appendChild(perf.rootEl);
this.#tabBar.showTab(specListTabId);
this.#tabBar.showTab(perfTabId);
if (this.#stateBuilder.anyNonTopSuiteFailures) {
this.#alerts.addFailureToggle(
() => this.#setMenuModeTo('jasmine-failure-list'),
() => this.#setMenuModeTo('jasmine-spec-list')
);
this.#setMenuModeTo('jasmine-failure-list');
this.#failures.show();
this.#tabBar.showTab(failuresTabId);
this.#tabBar.selectTab(failuresTabId);
} else {
this.#tabBar.selectTab(specListTabId);
}
}
@@ -1148,7 +1219,6 @@ jasmineRequire.HtmlReporterV2 = function(j$) {
this.rootEl.value = this.rootEl.value + 1;
if (result.status === 'failed') {
// TODO: also a non-color indicator
this.rootEl.classList.add('failed');
}
}
@@ -1434,6 +1504,105 @@ jasmineRequire.OverallStatusBar = function(j$) {
return OverallStatusBar;
};
jasmineRequire.PerformanceView = function(j$) {
const createDom = j$.private.htmlReporterUtils.createDom;
const MAX_SLOW_SPECS = 20;
class PerformanceView {
#summary;
#tbody;
constructor() {
this.#tbody = document.createElement('tbody');
this.#summary = document.createElement('div');
this.rootEl = createDom(
'div',
{ className: 'jasmine-performance-view' },
createDom('h2', {}, 'Performance'),
this.#summary,
createDom('h3', {}, 'Slowest Specs'),
createDom(
'table',
{},
createDom(
'thead',
{},
createDom(
'tr',
{},
createDom('th', {}, 'Duration'),
createDom('th', {}, 'Spec Name')
)
),
this.#tbody
)
);
}
addResults(resultsTree) {
const specResults = [];
getSpecResults(resultsTree, specResults);
if (specResults.length === 0) {
return;
}
specResults.sort(function(a, b) {
if (a.duration < b.duration) {
return 1;
} else if (a.duration > b.duration) {
return -1;
} else {
return 0;
}
});
this.#populateSumary(specResults);
this.#populateTable(specResults);
}
#populateSumary(specResults) {
const total = specResults.map(r => r.duration).reduce((a, b) => a + b, 0);
const mean = total / specResults.length;
const median = specResults[Math.floor(specResults.length / 2)].duration;
this.#summary.appendChild(
document.createTextNode(`Mean spec run time: ${mean.toFixed(0)}ms`)
);
this.#summary.appendChild(document.createElement('br'));
this.#summary.appendChild(
document.createTextNode(`Median spec run time: ${median}ms`)
);
}
#populateTable(specResults) {
specResults = specResults.slice(0, MAX_SLOW_SPECS);
for (const r of specResults) {
this.#tbody.appendChild(
createDom(
'tr',
{},
createDom('td', {}, `${r.duration}ms`),
createDom('td', {}, r.fullName)
)
);
}
}
}
function getSpecResults(resultsTree, dest) {
for (const node of resultsTree.children) {
if (node.type === 'suite') {
getSpecResults(node, dest);
} else if (node.result.status !== 'excluded') {
dest.push(node.result);
}
}
}
return PerformanceView;
};
jasmineRequire.ResultsStateBuilder = function(j$) {
'use strict';
@@ -1665,3 +1834,81 @@ jasmineRequire.SymbolsView = function(j$) {
return SymbolsView;
};
jasmineRequire.TabBar = function(j$) {
const createDom = j$.private.htmlReporterUtils.createDom;
class TabBar {
#tabs;
#onSelectTab;
// tabSpecs should be an array of {id, label}.
// All tabs are initially not visible and not selected.
constructor(tabSpecs, onSelectTab) {
this.#onSelectTab = onSelectTab;
this.#tabs = [];
this.#tabs = tabSpecs.map(ts => new Tab(ts, () => this.selectTab(ts.id)));
this.rootEl = createDom(
'span',
{ className: 'jasmine-menu jasmine-bar' },
this.#tabs.map(t => t.rootEl)
);
}
showTab(id) {
for (const tab of this.#tabs) {
if (tab.rootEl.id === id) {
tab.setVisibility(true);
}
}
}
selectTab(id) {
for (const tab of this.#tabs) {
tab.setSelected(tab.rootEl.id === id);
}
this.#onSelectTab(id);
}
}
class Tab {
#spec;
#onClick;
constructor(spec, onClick) {
this.#spec = spec;
this.#onClick = onClick;
this.rootEl = createDom(
'span',
{ id: spec.id, className: 'jasmine-tab jasmine-hidden' },
this.#createLink()
);
}
setVisibility(visible) {
this.rootEl.classList.toggle('jasmine-hidden', !visible);
}
setSelected(selected) {
if (selected) {
this.rootEl.textContent = this.#spec.label;
} else {
this.rootEl.textContent = '';
this.rootEl.appendChild(this.#createLink());
}
}
#createLink() {
const link = createDom('a', { href: '#' }, this.#spec.label);
link.addEventListener('click', e => {
e.preventDefault();
this.#onClick();
});
return link;
}
}
return TabBar;
};

View File

@@ -198,11 +198,17 @@ body {
color: white;
}
.jasmine_html-reporter.jasmine-spec-list .jasmine-bar.jasmine-menu.jasmine-failure-list,
.jasmine_html-reporter.jasmine-spec-list .jasmine-results .jasmine-failures {
.jasmine_html-reporter.jasmine-spec-list .jasmine-results .jasmine-failures,
.jasmine_html-reporter.jasmine-spec-list .jasmine-performance-view {
display: none;
}
.jasmine_html-reporter.jasmine-failure-list .jasmine-bar.jasmine-menu.jasmine-spec-list,
.jasmine_html-reporter.jasmine-failure-list .jasmine-summary {
.jasmine_html-reporter.jasmine-failure-list .jasmine-summary,
.jasmine_html-reporter.jasmine-failure-list .jasmine-performance-view {
display: none;
}
.jasmine_html-reporter.jasmine-performance .jasmine-results .jasmine-failures,
.jasmine_html-reporter.jasmine-performance .jasmine-summary {
display: none;
}
.jasmine_html-reporter .jasmine-results {
@@ -323,4 +329,23 @@ body {
}
.jasmine_html-reporter .jasmine-debug-log .jasmine-debug-log-msg {
white-space: pre;
}
.jasmine-hidden {
display: none;
}
.jasmine-tab + .jasmine-tab:before {
content: " | ";
}
.jasmine-performance-view h2, .jasmine-performance-view h3 {
margin-top: 1em;
margin-bottom: 1em;
}
.jasmine-performance-view table {
border-spacing: 5px;
}
.jasmine-performance-view th, .jasmine-performance-view td {
text-align: left;
}