Skip to content

Commit

Permalink
HTML Reporter: Restore "running" class on test item
Browse files Browse the repository at this point in the history
== Background

In QUnit 1.16.0, as part of #677,
commit 168b048 changed the testStart() callback to no longer always
create `qunit-test-output-TESTID` with class "running", but instead
eagerly create them ahead of time via a QUnit.begin() handler (without
the "running" class), and then during testStart() set the class to
"running", or lazily create the item then.

This introduced a bug where, late-defined tests lack the "running"
class. This bug then later spread to all items as we now treat all
tests as late-defined.

In QUnit 2.8.0, as part of #1323,
this was attempted to be restored, but it added it to a different
element, namely `#qunit-testresult-display` which from what I could
find in our history, never previously carried this class. This might've
been done by acciddent, but in any case, let's keep that.

== What

* Restore this to simplify the CSS, and makes it more developer-friendly
  to theme QUnit by not having to list all statuses, and/or having to
  use a direct-child selector like `#qunit-tests > li` to reliably select
  test items (and not other lists like the assertion list).

* For back-compat, keep the running class on `qunit-testresult-display`
  but move it to onBegin() since there is no reason to set this again
  and again on every test since it never changes.

* Document these bits in the recently created "Theme API" section.
  • Loading branch information
Krinkle committed Jul 14, 2024
1 parent 52bfa69 commit 1551120
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 87 deletions.
6 changes: 6 additions & 0 deletions docs/browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,12 @@ The following selectors are considered stable and supported:
| `#qunit-modulefilter-dropdown` | Module selector, dropdown menu.
| `#qunit-modulefilter-actions`<br><br>`#qunit-modulefilter-actions button` | Module selector, top area of dropdown menu with "Reset" and "Apply" buttons.
| `#qunit-modulefilter .clickable`<br><br>`#qunit-modulefilter .clickable.checked` | Module selector, options in the dropdown menu.
| `#qunit-tests` | List of test items.
| `#qunit-tests > li`<br><br>QUnit 3:<br>`.qunit-test` | Test item.
| `#qunit-tests > li:not(.skipped):not(.pass):not(.fail)`<br><br>QUnit 3:<br>`.qunit-test.running`| Currently running test.
| `#qunit-tests > li.pass`<br>`#qunit-tests > li.fail`<br>`#qunit-tests > li.skipped`<br><br>QUnit 3:<br>`.qunit-test.pass`<br>`.qunit-test.fail`<br>`.qunit-test.skipped` | Test by one of the three mutually-exclusive outcomes (pass, fail, skipped).
| `#qunit-tests > li.todo`<br><br>QUnit 3:<br>`.qunit-test.todo` | Test was marked as "todo". It will also have the `pass` or `fail` class.
| `.qunit-assert-list` | List of one assertions under a given test.
{:class="table-style-api"}

### HTML API
Expand Down
99 changes: 46 additions & 53 deletions src/qunit.css
Original file line number Diff line number Diff line change
Expand Up @@ -295,44 +295,32 @@ body {
list-style-position: inside;
}

#qunit-tests li {
.qunit-test {
padding: 0.4em 1em 0.4em 1em;
border-bottom: 1px solid #FFF;
list-style-position: inside;
}

#qunit-tests > li {
.qunit-test.running {
display: none;
}

#qunit-tests li.running,
#qunit-tests li.pass,
#qunit-tests li.fail,
#qunit-tests li.skipped,
#qunit-tests li.aborted {
display: list-item;
}

#qunit-tests li .qunit-test-name {
.qunit-test:not(.skipped) .qunit-test-name {
cursor: pointer;
}

#qunit-tests li.skipped .qunit-test-name {
cursor: default;
}

#qunit-tests li a {
.qunit-test a {
padding: 0.5em;
color: inherit;
text-decoration: underline;
user-select: none;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
.qunit-test a:hover,
.qunit-test a:focus {
color: #0D3349;
}

#qunit-tests li .runtime {
.qunit-test .runtime {
float: right;
font-size: smaller;
user-select: none;
Expand All @@ -345,117 +333,122 @@ body {
background-color: #FFF;
}

.qunit-collapsed {
display: none;
}

.qunit-source {
margin: 0.6em 0 0.3em;
}

.qunit-collapsed {
display: none;
.qunit-assert-list li {
padding: 5px;
background-color: #FFF;
border-bottom: none;
list-style-position: inside;
}

#qunit-tests table {
.qunit-assert-list table {
border-collapse: collapse;
margin-top: 0.2em;
}

#qunit-tests th {
.qunit-assert-list th {
text-align: right;
vertical-align: top;
padding: 0 0.5em 0 0;
}

#qunit-tests td {
.qunit-assert-listt td {
vertical-align: top;
}

#qunit-tests pre {
.qunit-assert-list pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}

#qunit-tests del {
.qunit-assert-list del {
color: #374E0C;
background-color: #E0F2BE;
text-decoration: none;
}

#qunit-tests ins {
.qunit-assert-list ins {
color: #500;
background-color: #FFCACA;
text-decoration: none;
}

/** Test output: Counts */

#qunit-tests .counts {
.qunit-test .counts {
color: #0D3349;
}
#qunit-tests .passed {
.qunit-test .passed {
color: #5E740B;
}
#qunit-tests .failed {
.qunit-test .failed {
color: #710909;
}

#qunit-tests li li {
padding: 5px;
background-color: #FFF;
border-bottom: none;
list-style-position: inside;
}

/** Test output: Passing */

#qunit-tests .pass {
.qunit-test.pass {
color: #2F68DA;
background-color: #E2F0F7;
}

#qunit-tests .pass .test-name {
.qunit-test.pass .test-name {
color: #366097;
}

#qunit-tests li li.pass {
.qunit-assert-list .pass {
color: #3C510C;
background-color: #FFF;
border-left: 10px solid #C6E746;
}

#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999; }
.qunit-test.pass .test-actual,
.qunit-test.pass .test-expected { color: #999; }


/** Test output: Failing */

#qunit-tests .fail {
.qunit-test.fail {
color: #000;
background-color: #EE5757;
}

#qunit-tests li li.fail {
.qunit-assert-list .fail {
color: #710909;
background-color: #FFF;
border-left: 10px solid #EE5757;
white-space: pre;
}

#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: #008000; }

.qunit-assert-list .fail .test-actual {
color: #EE5757;
}
.qunit-assert-list .fail .test-expected {
color: #008000;
}

/** Test output: Aborted */

#qunit-tests .aborted { color: #000; background-color: orange; }
.qunit-test.aborted {
color: #000;
background-color: orange;
}

/** Test output: Skipped */

#qunit-tests .skipped {
.qunit-test.skipped {
background-color: #EBECE9;
}

#qunit-tests .qunit-todo-label,
#qunit-tests .qunit-skipped-label {
.qunit-test .qunit-todo-label,
.qunit-test .qunit-skipped-label {
background-color: #F4FF77;
display: inline-block;
font-style: normal;
Expand All @@ -465,7 +458,7 @@ body {
margin: -0.4em 0 -0.4em 0;
}

#qunit-tests .qunit-todo-label {
.qunit-test .qunit-todo-label {
background-color: #EEE;
}

Expand Down
68 changes: 35 additions & 33 deletions src/reporters/HtmlReporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,7 @@ export default class HtmlReporter {
title.innerHTML = getNameHtml(name, moduleName);

let testBlock = document.createElement('li');
testBlock.className = 'qunit-test running';
testBlock.appendChild(title);

// No ID or rerun link for "global failure" blocks
Expand All @@ -674,7 +675,6 @@ export default class HtmlReporter {

let assertList = document.createElement('ol');
assertList.className = 'qunit-assert-list';

testBlock.appendChild(assertList);

this.elementTests.appendChild(testBlock);
Expand Down Expand Up @@ -705,6 +705,7 @@ export default class HtmlReporter {
// https://github.com/qunitjs/qunit/issues/1657
onBegin (beginDetails) {
this.appendInterface(beginDetails);
this.elementDisplay.className = 'running';
}

getRerunFailedHtml (failedTests) {
Expand Down Expand Up @@ -750,7 +751,7 @@ export default class HtmlReporter {

for (let i = 0; i < this.elementTests.children.length; i++) {
const test = this.elementTests.children[i];
if (test.className === '' || test.className === 'running') {
if (DOM.hasClass(test.className, 'running')) {
test.className = 'aborted';
const assertList = test.getElementsByTagName('ol')[0];
const assertLi = document.createElement('li');
Expand All @@ -775,7 +776,6 @@ export default class HtmlReporter {
onTestStart (details) {
this.appendTest(details.name, details.testId, details.module);

this.elementDisplay.className = 'running';
this.elementDisplay.innerHTML = [
details.previousFailure
? 'Rerunning previously failed test: <br />'
Expand Down Expand Up @@ -892,71 +892,74 @@ export default class HtmlReporter {
return;
}

DOM.removeClass(testItem, 'running');

// This test passed if it has no unexpected failed assertions
// TODO: Add "status" from TestReport#getStatus() to testDone() and use that.
let status;
if (details.failed > 0) {
status = 'failed';
} else if (details.todo) {
status = 'todo';
if (details.skipped) {
status = 'skipped';
} else {
status = details.skipped ? 'skipped' : 'passed';
const passed = (details.failed > 0 ? details.todo : !details.todo);
status = !passed ? 'failed' : (details.todo ? 'todo' : 'passed');
}
const testPassed = status !== 'failed';

let assertList = testItem.getElementsByTagName('ol')[0];

let good = details.passed;
let bad = details.failed;
this.stats.completed++;
if (!testPassed) {
this.stats.failedTests.push(details.testId);
}

// This test passed if it has no unexpected failed assertions
const testPassed = details.failed > 0 ? details.todo : !details.todo;
// The testItem.firstChild is the test name
let testTitle = testItem.firstChild;

let assertList = testItem.getElementsByTagName('ol')[0];
// Collapse passing tests by default
if (testPassed) {
// Collapse the passing tests
DOM.addClass(assertList, 'qunit-collapsed');
} else {
this.stats.failedTests.push(details.testId);

if (this.config.collapse) {
if (!this.collapseNext) {
// Skip collapsing the first failing test
this.collapseNext = true;
} else {
// Collapse remaining tests
// Collapse subsequent failing tests
DOM.addClass(assertList, 'qunit-collapsed');
}
}
}
if (status !== 'skipped') {
DOM.on(testTitle, 'click', function () {
DOM.toggleClass(assertList, 'qunit-collapsed');
});
}

// The testItem.firstChild is the test name
let testTitle = testItem.firstChild;
let good = details.passed;
let bad = details.failed;
let badGoodCounts = bad
? '<span class="failed">' + bad + '</span>, ' + '<span class="passed">' + good + '</span>, '
: '';

testTitle.innerHTML += ' <span class="counts">(' + badGoodCounts + details.total + ')</span>';

this.stats.completed++;
DOM.removeClass(testItem, 'running');

if (status === 'skipped') {
DOM.addClass(testItem, 'skipped');

if (details.skipped) {
testItem.className = 'skipped';
let skipped = document.createElement('em');
skipped.className = 'qunit-skipped-label';
skipped.textContent = 'skipped';
testItem.insertBefore(skipped, testTitle);
testItem.insertBefore(document.createTextNode(' '), testTitle);
} else {
DOM.on(testTitle, 'click', function () {
DOM.toggleClass(assertList, 'qunit-collapsed');
});

testItem.className = testPassed ? 'pass' : 'fail';
DOM.addClass(testItem, testPassed ? 'pass' : 'fail');

if (details.todo) {
// Add label both for status=todo (passing) and for status=failed on a todo test.
DOM.addClass(testItem, 'todo');

const todoLabel = document.createElement('em');
todoLabel.className = 'qunit-todo-label';
todoLabel.textContent = 'todo';
testItem.className += ' todo';
testItem.insertBefore(todoLabel, testTitle);
testItem.insertBefore(document.createTextNode(' '), testTitle);
}
Expand All @@ -983,9 +986,8 @@ export default class HtmlReporter {

const hidepassed = (this.hidepassed !== null ? this.hidepassed : this.config.hidepassed);
if (hidepassed && (status === 'passed' || details.skipped)) {
// use removeChild instead of remove because of support
this.hiddenTests.push(testItem);

// use removeChild() instead of remove() for wider browser support
this.elementTests.removeChild(testItem);
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/main/HtmlReporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ QUnit.test('testresult-display [begin]', function (assert) {
assert.equal(testresult.textContent, 'AbortRunning...\u00A0', 'testresult text');

var display = element.querySelector('#qunit-testresult-display');
assert.equal(display.className, '', 'display class');
assert.equal(display.className, 'running', 'display class');
assert.equal(display.textContent, 'Running...\u00A0', 'display text');
});

Expand Down

0 comments on commit 1551120

Please sign in to comment.