Skip to content

Commit

Permalink
Refactor and group functions into files
Browse files Browse the repository at this point in the history
  • Loading branch information
tomyo committed Sep 8, 2022
1 parent 6445bc9 commit 4669234
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 231 deletions.
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { assertTrue, assertEqual, isDomElement, waitFor, waitForFun, waitForMs } from './utils.js';
export { runTests, useIFrameAsTestRunner } from './runner.js';
export { mockOnce } from './mock.js';
export { useSpyOnce } from './spy.js';
43 changes: 43 additions & 0 deletions mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Mock given function once.
* If `beforeCallback` or `afterCallback` return a truthy value,
* an early return with that value happens.
*
* @param {Function} fun - Function to mock
* @param {Object} config
* @param {Object} config.scope - By default is `window`
* @param {Function} config.beforeCallback
* @param {Function} config.afterCallback
*/
export function mockOnce(fun, { scope = window, beforeCallback = () => { }, afterCallback = () => { } }) {

function restoreScope() {
scope[fun.name] = fun;
}

async function mocked(...args) {
let earlyReturnValue, result, error;
if (earlyReturnValue = await beforeCallback(...args)) {
restoreScope();
return earlyReturnValue;
}

try {
result = await fun(...args);
}
catch (error) {
error = error;
}
finally {
restoreScope();
if (earlyReturnValue = await afterCallback(result, error)) {
return earlyReturnValue;
}

if (error) throw error;
return result;
}
}

scope[fun.name] = (...args) => mocked(...args);
}
120 changes: 120 additions & 0 deletions runner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
const testReportTypes = {
TEST_REPORT: 'test-report',
BATCH_REPORT: 'batch-report',
}

/**
* Given an object of test names as keys and and functions as values,
* run them while creating a summary.
* A broadcastChannel is used to send reports about each test
* * A test finishes: {type: TEST_REPORT, data: testReport}
* * All tests finished: {type: BATCH_REPORT, data: summaryWithAllTestReports}
*
* @param {Object} tests - {testName: testFunction(), ...}
* @param {Config} config - See `Config`
*
* @typedef {Object} Config
* @param {Function} beforeEach - Function to run before each test
* @param {Function} afterEach - Function to run after each test
* @param {String} channelName - Name used for BroadcastChannel
* @param {Boolean} abortOnFailedTest - If true, stops on first failing test
* @param {Number} timeout - miliseconds until a timeout error is reported for a test
*/
export async function runTests(tests = {}, {
beforeEach = () => { }, afterEach = () => { },
channelName = "tests", abortOnFailedTest = false, timeout = 3000,
} = {}) {
const summary = [];
const testsBroadcast = new BroadcastChannel(channelName);
const location = JSON.parse(JSON.stringify(document.location));

for (const [name, test] of Object.entries(tests)) {
const testReport = { name, location };
try {
beforeEach();
await test();

console.info(`✔ ${name}`);
testReport.status = 'passed';
}
catch (error) {
console.error(`✘ ${name}`, error);
testReport.status = 'failed';
testReport.error = error;
if (abortOnFailedTest) throw error;
}
finally {
summary.push(testReport)
testsBroadcast.postMessage({
type: testReportTypes.TEST_REPORT,
data: testReport,
});
afterEach();
}
}
testsBroadcast.postMessage({
type: testReportTypes.BATCH_REPORT,
data: { location, summary },
});
testsBroadcast.close();
}


export function useIFrameAsTestRunner(iframe, testFiles,
{ channelName = 'tests', testTimeOut = 4000 } = {}) {
const testsBroadcast = new BroadcastChannel(channelName);
let fileIndex = 0, timeoutID;
let testDoneCallback = () => null;
let batchDoneCallback = () => null;

function runNextTestBatch() {
const title = `Testing ${testFiles[fileIndex]} ...`;
iframe.title = title;
iframe.contentWindow.location.replace(testFiles[fileIndex]); // keep history clean
iframe.contentWindow.console = window.console; // Run iframe console.*() calls into main window
timeoutID = setupTimeOutError();
}

function setupTimeOutError() {
iframe.style.backgroundColor = "inherited";
return setTimeout(() => {
iframe.style.backgroundColor = 'lightcoral';
throw new Error(`Timeout running tests: ${testFiles[fileIndex]}`);
}, testTimeOut);
}


testsBroadcast.onmessage = (event) => {
// Tests finished running for `testFiles[fileIndex]`
clearTimeout(timeoutID);
const { type, data } = event.data;

if (type == testReportTypes.BATCH_REPORT) {
batchDoneCallback(data);
fileIndex += 1;
if (fileIndex < testFiles.length) {
runNextTestBatch();
return;
} else {
console.info('[Done] All tests finished succesfully!');
return;
}
}
if (type == testReportTypes.TEST_REPORT) {
testDoneCallback(data);
return;
}

throw Error('Invalid event.data.type (not in testReportTypes):', type);
}

function runTestBatches(testCallback, batchCallback) {
if (testCallback) testDoneCallback = testCallback;
if (batchCallback) batchDoneCallback = batchCallback;

console.info("Starting tests ...");
runNextTestBatch();
}

return runTestBatches;
}
22 changes: 22 additions & 0 deletions spy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { mockOnce } from "./mock.js";
/**
* Capture arguments, returned result and/or throwed error
* of given function.
*
* @param {Function} fun - Function to capture input/output
* @param {Object} config
* @param {Object} config.scope - Scope where `fun.name` exists,
* defaults to `window`.
* @returns Array of 3 functions that returns captured arguments,
* result and error, respectively.
*/
export function useSpyOnce(fun, { scope = window } = {}) {
let args, result, error;
mockOnce(fun, {
scope,
beforeCallback: (...a) => args = a,
afterCallback: (r, e) => [result, error] = [r, e],
})

return [() => args, () => result, () => error];
}
166 changes: 0 additions & 166 deletions test-utils.js

This file was deleted.

Loading

0 comments on commit 4669234

Please sign in to comment.