Skip to content

Commit

Permalink
HTML Reporter: Faster rendering of module filter dropdown
Browse files Browse the repository at this point in the history
* Remove eager rendering of the dropdown menu from the critical
  path of `QUnit.begin()`. Defer this to first focus instead.

* Limit both initial and fuzzysort results to 20 items, and
  display them in full without any inline scrollbars.

* Render the dropdown in a single parse HTML task via one innerHTML
  assignment, instead of N createElement calls and N parse HTML tasks.

Fixes #1664.
  • Loading branch information
Krinkle committed Apr 17, 2022
1 parent dd8779b commit bea0d04
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 20 deletions.
35 changes: 17 additions & 18 deletions src/html-reporter/html.js
Expand Up @@ -11,14 +11,13 @@ const stats = {
};

// Escape text for attribute or text content.
export function escapeText (s) {
if (!s) {
export function escapeText (str) {
if (!str) {
return '';
}
s = s + '';

// Both single quotes and double quotes (for attributes)
return s.replace(/['"<>&]/g, function (s) {
return ('' + str).replace(/['"<>&]/g, function (s) {
switch (s) {
case "'":
return '&#039;';
Expand Down Expand Up @@ -336,34 +335,32 @@ export function escapeText (s) {
}

function createModuleListItem (moduleId, name, checked) {
const item = document.createElement('li');
item.innerHTML = '<label class="clickable' + (checked ? ' checked' : '') +
return '<li><label class="clickable' + (checked ? ' checked' : '') +
'"><input type="checkbox" ' + 'value="' + escapeText(moduleId) + '"' +
(checked ? ' checked="checked"' : '') + ' />' +
escapeText(name) + '</label>';
return item;
escapeText(name) + '</label></li>';
}

/**
* @param {Array} Results from fuzzysort
* @return {DocumentFragment}
* @return {string} HTML
*/
function moduleListHtml (results) {
const fragment = document.createDocumentFragment();
let html = '';

// Hoist the already selected items, and show them always
// even if not matched by the current search.
dropdownData.selectedMap.forEach((name, moduleId) => {
fragment.appendChild(createModuleListItem(moduleId, name, true));
html += createModuleListItem(moduleId, name, true);
});

for (let i = 0; i < results.length; i++) {
const mod = results[i].obj;
if (!dropdownData.selectedMap.has(mod.moduleId)) {
fragment.appendChild(createModuleListItem(mod.moduleId, mod.name, false));
html += createModuleListItem(mod.moduleId, mod.name, false);
}
}
return fragment;
return html;
}

function toolbarModuleFilter () {
Expand Down Expand Up @@ -444,7 +441,6 @@ export function escapeText (s) {

const dropDownList = document.createElement('ul');
dropDownList.id = 'qunit-modulefilter-dropdown-list';
dropDownList.appendChild(filterModules(moduleSearch.value));

const dropDown = document.createElement('div');
dropDown.id = 'qunit-modulefilter-dropdown';
Expand Down Expand Up @@ -475,6 +471,9 @@ export function escapeText (s) {
return;
}

// Optimization: Defer rendering options until focussed.
// https://github.com/qunitjs/qunit/issues/1664
searchInput();
dropDown.style.display = 'block';

// Hide on Escape keydown or on click outside the container
Expand All @@ -499,7 +498,7 @@ export function escapeText (s) {

/**
* @param {string} searchText
* @return {DocumentFragment}
* @return {string} HTML
*/
function filterModules (searchText) {
let results;
Expand All @@ -508,12 +507,13 @@ export function escapeText (s) {
// module names, indicating how the interface works. This also makes
// for a quicker interaction in the common case of small projects.
// Don't mandate typing just to get the menu.
results = dropdownData.options.map(obj => {
results = dropdownData.options.slice(0, 20).map(obj => {
// Fake empty results. https://github.com/farzher/fuzzysort/issues/41
return { obj: obj };
});
} else {
results = fuzzysort.go(searchText, dropdownData.options, {
limit: 20,
key: 'name',
allowTypo: true
});
Expand All @@ -531,8 +531,7 @@ export function escapeText (s) {
// drodown DOM is slow (e.g. very large test suite).
window.clearTimeout(searchInputTimeout);
searchInputTimeout = window.setTimeout(() => {
dropDownList.innerHTML = '';
dropDownList.appendChild(filterModules(moduleSearch.value));
dropDownList.innerHTML = filterModules(moduleSearch.value);
});
}

Expand Down
2 changes: 0 additions & 2 deletions src/qunit.css
Expand Up @@ -254,8 +254,6 @@
}

#qunit-modulefilter-dropdown-list {
max-height: 200px;
overflow-y: auto;
margin: 0;
padding: 0;
font: smaller/1.5em sans-serif;
Expand Down

0 comments on commit bea0d04

Please sign in to comment.