createSuite = ({
name = "tests"
} = {}) => {
const id = pseudouuid();
const tests = {};
const results = {};
var filter = "";
const regex = () => new RegExp(filter);
function updateUI() {
reconcile(document.getElementById(id), generate());
}
const promiseWithTimeout = (timeoutMs, promise, failureMessage) => {
var timeoutHandle;
const timeoutPromise = new Promise((resolve, reject) => {
timeoutHandle = setTimeout(() => reject(new Error(failureMessage), timeoutMs));
} );
return Promise.race([
promise(),
timeoutPromise,
]).then((result) => {
clearTimeout(timeoutHandle);
return result;
});
}
async function maybeRunTest(name) {
if (regex().test(name)) {
results[name] = undefined
updateUI();
try {
console.log(`tests if is async? check ${name} has ${tests[name].length} length`);
// I can't detect the asyn. The "length" hack from https://stackoverflow.com/questions/45149744/how-does-jests-callback-testing-actually-work isn't working. Everything is showing up as zero length.
// PSEUDOCODE:
// If (not async), just run the old code.
// If (async), await promiseWithTimeout(30000, tests[name], "failedTimeout")
// question: not sure if tests[name] or tests[name]() is the promise.
// check if the return value is "failedTimeout". If so, report failed timeout.
await tests[name]();
results[name] = "ok"
updateUI();
return results[name];
} catch(err) {
results[name] = err
updateUI();
throw err
}
}
}
function filterChange(evt) {
if (filter !== evt.target.value) {
filter = evt.target.value;
updateUI();
}
if (evt.keyCode === 13) {
Object.keys(tests).map(label => maybeRunTest(label))
}
}
// Generate a report link for serving a TAP report
let report_link;
if (name) {
report_link = deploy(name, async(req, res) => {
res.header('Content-Type', 'text/plain');
res.send(await report({results}));
});
}
function generate() {
return html`<div class="testsuite" id=${id}>
<h2 id="title{id}">${name}</h2>
<a name="testsuite${id}"></a>
<input key="filter"
oninput=${e => e.stopPropagation()}
onkeyup=${filterChange}
value="${filter}"
placeholder="test filter regex"></input>
<table key="results">
<tr><th>name</th><th>value</th></tr>
${Object.keys(results)
.filter(label => regex().test(label))
.sort()
.map(label => html.fragment`
<tr><td><a href="#testresult${encodeURIComponent(label)}">
${label}
</a></td><td>${results[label]}</td></tr>
`)
}
</table>
${report_link ? html`<small>Link to this <a href=${report_link.href}>report</a></small>`:null}
<style>
a[name] { scroll-margin-top: 75px }
</style>
</div>`
}
const api = {
results: results,
test: async (label, fn) => {
console.log(`Test scheduled: ${label}`)
const run = async () => fn()
tests[label] = run;
const result = await maybeRunTest(label);
const color = result === "ok" ? "green" : "red"
return html`<div class="testresult" style="font: var(--mono_fonts); color: ${color}; padding: 6px 0;">
<a name="testresult${encodeURIComponent(label)}"></a>
${label}: ${result}
<a style="float:right" href="#testsuite${id}">goto suite</a>
</div>`
}
}
{ // Navigation widget
const isLocalLink = a => a instanceof HTMLAnchorElement && a.getAttribute('href').match(/^#/);
const scrollTo = e => {
let l = e.target, t;
if(isLocalLink(l) && (t = document.querySelector(`[name="${l.hash.slice(1)}"]`))) {
e.preventDefault();
t.scrollIntoView();
}
};
document.addEventListener('click', scrollTo);
invalidation.then(() => document.removeEventListener('click', scrollTo));
}
const view = html`${generate()}`
view.value = api;
return view;
}