Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

HTML Reporter: Add support for instant rendering #1793

Merged
merged 1 commit into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions demos/qunit-onerror-early.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
<link rel="stylesheet" href="../src/core/qunit.css">
<script src="../qunit/qunit.js"></script>
<script>
QUnit.config.urlConfig.push('beginBoom');
QUnit.begin(function () {
// eslint-disable-next-line no-undef
beginBoom();
if (QUnit.config.beginBoom) {
// eslint-disable-next-line no-undef
beginBoom();
}
});

// eslint-disable-next-line no-undef
Expand Down
22 changes: 22 additions & 0 deletions src/core/browser/browser-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,28 @@ export function initBrowser (QUnit, window, document) {
initFixture(QUnit, document);
initUrlConfig(QUnit);

// Unless explicitly disabled via preconfig, initialize HtmlReporter now
// (i.e. QUnit.config.reporters.html is undefined or true).
//
// This allows the UI to render instantly (since QUnit 3.0) for cases where the
// qunit.js script is after `<div id="qunit">`, which is recommended.
//
// Otherwise, we'll fallback to waiting with a blank page until window.onload,
// which is how it's always been in QUnit 1 and QUnit 2.
//
// Note that HtmlReporter constructor will only render an initial layout and
// listen to 1 event. The final decision on whether to attach event handlers
// and render the interactive UI is made from HtmlReporter#onRunStart, which
// is also where it will honor QUnit.config.reporters.html if it was set to
// false between qunit.js (here) and onRunStart.
//
// If someone explicitly sets QUnit.config.reporters.html to false via preconfig,
// but then changes it at runtime to true, that is unsupported and the reporter
// will remain disabled.
if (QUnit.config.reporters.html !== false) {
QUnit.reporters.html.init(QUnit);
}

// NOTE:
// * It is important to attach error handlers (above) before setting up reporters,
// to ensure reliable reporting of error events.
Expand Down
63 changes: 38 additions & 25 deletions src/core/reporters/HtmlReporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,24 @@ export default class HtmlReporter {
this.dropdownData = null;

// We must not fallback to creating `<div id="qunit">` ourselves if it
// does not exist, because not having id="qunit" is how projects indicate
// that they wish to run QUnit headless, with their own reporters.
this.element = options.element || undefined;
// does not exist, because not having `<div id="qunit">` is how projects
// indicate that they wish to run QUnit headless, and do their own reporter.
//
// If options.element was set to HTMLElement, or if `<div id="qunit">` already
// exists (i.e. qunit.js script is placed at end of `<body>`), then we render
// an initial layout now (which is mostly inert).
// Otherwise, we wait until QUnit.start and the onRunStart event, which by
// default is called from window.onload.
//
// If no element is found now, leave this.element as undefined (unchanged).
// We will try again at onRunStart().
this.element = options.element || document.querySelector('#qunit') || undefined;
this.elementBanner = null;
this.elementDisplay = null;
this.elementTests = null;
if (this.element) {
this.appendInterface();
}

// NOTE: Only listen for "runStart" now.
// Other event handlers are added via listen() from onRunStart,
Expand All @@ -150,9 +162,12 @@ export default class HtmlReporter {
QUnit.log(this.onLog.bind(this), prioritySymbol);
QUnit.testDone(this.onTestDone.bind(this));
QUnit.on('runEnd', this.onRunEnd.bind(this));
};
this.listenError = function () {
this.listenError = null;

// It's important that we don't listen for onError until after
// this.element is found and populated by appendInterface(), as
// otherwise it will fail in appendTest() to display error details.
// We've given on() a memory for "error" events to accomodate
// late listening.
QUnit.on('error', this.onError.bind(this), prioritySymbol);
};
QUnit.on('runStart', this.onRunStart.bind(this), prioritySymbol);
Expand Down Expand Up @@ -653,7 +668,7 @@ export default class HtmlReporter {
+ '">Run all tests</a></div>';
}

appendInterface (beginDetails) {
appendInterface () {
// Since QUnit 1.3, these are created automatically.
this.element.setAttribute('role', 'main');
this.element.innerHTML =
Expand All @@ -675,9 +690,6 @@ export default class HtmlReporter {
this.elementBanner = this.element.querySelector('#qunit-banner');
this.elementDisplay = this.element.querySelector('#qunit-testresult-display');
this.elementTests = this.element.querySelector('#qunit-tests');

this.appendToolbarControls(beginDetails);
this.appendTestResultControls();
}

appendTest (name, testId, moduleName) {
Expand Down Expand Up @@ -710,12 +722,22 @@ export default class HtmlReporter {

// HTML Reporter initialization and load
onRunStart (runStart) {
if (this.element === undefined) {
this.element = document.querySelector('#qunit') || null;
}
if (this.element === null) {
if (this.config.reporters && this.config.reporters.html === false) {
// If QUnit.config.reporters.html was set to false after loading QUnit,
// then undo the initial layout (created from browser-runnner.js)
if (this.element) {
this.element.innerHTML = '';
this.element = null;
}
return;
}
if (!this.element) {
this.element = document.querySelector('#qunit') || null;
if (!this.element) {
return;
}
this.appendInterface();
}

this.stats.defined = runStart.testCounts.total;

Expand All @@ -730,18 +752,9 @@ export default class HtmlReporter {
// add entries to QUnit.config.urlConfig, which may be done asynchronously.
// https://github.com/qunitjs/qunit/issues/1657
onBegin (beginDetails) {
this.appendInterface(beginDetails);
this.appendToolbarControls(beginDetails);
this.appendTestResultControls();
this.elementDisplay.className = 'running';

// TODO: We render the UI late here from onBegin because the toolbar and module
// dropdown rely on user-defined information. If we refactor the UI to render
// most of it earlier (from the constructor if possible, with last retry during
// runStart), then we can move this listener to the listen() function. We separate it
// so that onError can safely call appendTest() and rely on `this.element` and
// `this.elementTests` being set. We could instead add the "error" listener in
// the constructor, and buffer "early error" event inside this class until the
// UI is ready, but we instead rely on QUnit.on's built-in memory for "error".
this.listenError();
}

getRerunFailedHtml (failedTests) {
Expand Down
6 changes: 3 additions & 3 deletions src/core/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ export function createStartFunction (QUnit) {

// QUnit.config.reporters is considered writable between qunit.js and QUnit.start().
// Now, it is time to decide which reporters we'll load.
//
// For config.reporters.html, refer to browser-runner.js and HtmlReporter#onRunStart.
//
if (config.reporters.console) {
reporters.console.init(QUnit);
}
if (config.reporters.html || (config.reporters.html === undefined && window && document)) {
reporters.html.init(QUnit);
}
if (config.reporters.perf || (config.reporters.perf === undefined && window && document)) {
reporters.perf.init(QUnit);
}
Expand Down
6 changes: 3 additions & 3 deletions test/config-reporters.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
<meta charset="UTF-8">
<title>config-reporters</title>
<link rel="stylesheet" href="../src/core/qunit.css">
</head>
<body>
<div id="qunit"></div>
<script src="../qunit/qunit.js"></script>
<script>
QUnit.config.reporters.html = false;
Expand Down Expand Up @@ -37,8 +40,5 @@
assert.strictEqual(window.firstConsoleMessage, 'TAP version 13', 'first console message');
});
</script>
</head>
<body>
<div id="qunit"></div>
</body>
</html>