-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor and group functions into files
- Loading branch information
Showing
7 changed files
with
260 additions
and
231 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]; | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.