Would you like to clone this notebook?

When you clone a notebook you are able to make changes without affecting the original notebook.

Cancel

Jest Runkit

node v14.20.1
version: 4.0.1
endpointsharetweet
const { describe, it, test, beforeAll, afterAll, beforeEach, afterEach } = require('jest-circus'); const expect = require('expect'); const NodeEnvironment = require('jest-environment-node'); const {fn, spyOn} = require('jest-mock'); const run = require('jest-circus/runner'); const Runtime = require('jest-runtime'); const { delay } = require('nanodelay'); const EventEmitter = require('events'); const StackUtils = require('stack-utils'); const { ValueViewerSymbol } = require("@runkit/value-viewer"); const stackUtils = new StackUtils({cwd: 'A path that does not exist'}); global.describe = describe; global.it = it; global.test = test; global.beforeAll = beforeAll; global.afterAll = afterAll; global.beforeEach = beforeEach; global.afterEach = afterEach; global.expect = expect; global.jest = { fn, spyOn }; // most of this code is a copy/paste from jest-circus. It would be nice to have jest-circus expost the run function // so that we don't have to copy / paste all this code const execute = (function () { const stateSym = Object.getOwnPropertySymbols(global).find(s => s.toString() == "Symbol(JEST_STATE_SYMBOL)"); const getState = () => global[stateSym]; const dispatcher = new EventEmitter(); async function dispatch(event) { dispatcher.emit('event', event); } const getTestID = (test) => { const titles = []; let parent = test; do { titles.unshift(parent.name); } while ((parent = parent.parent)); titles.shift(); // remove TOP_DESCRIBE_BLOCK_NAME return titles.join(' '); }; const hasEnabledTest = (describeBlock) => { const {hasFocusedTests, testNamePattern} = getState(); return describeBlock.children.some(child => child.type === 'describeBlock' ? hasEnabledTest(child) : !( child.mode === 'skip' || (hasFocusedTests && child.mode !== 'only') || (testNamePattern && !testNamePattern.test(getTestID(child))) ), ); }; const getEachHooksForTest = (test) => { const result = {afterEach: [], beforeEach: []}; let block = test.parent; do { const beforeEachForCurrentBlock = []; // TODO: inline after https://github.com/microsoft/TypeScript/pull/34840 is released let hook; for (hook of block.hooks) { switch (hook.type) { case 'beforeEach': beforeEachForCurrentBlock.push(hook); break; case 'afterEach': result.afterEach.push(hook); break; } } // 'beforeEach' hooks are executed from top to bottom, the opposite of the // way we traversed it. result.beforeEach = [...beforeEachForCurrentBlock, ...result.beforeEach]; } while ((block = block.parent)); return result; }; const getAllHooksForDescribe = (describe) => { const result = { afterAll: [], beforeAll: [], }; if (hasEnabledTest(describe)) { for (const hook of describe.hooks) { switch (hook.type) { case 'beforeAll': result.beforeAll.push(hook); break; case 'afterAll': result.afterAll.push(hook); break; } } } return result; }; async function callFnAsPromised(hook, testContext) { const {fn} = hook; const returnValue = fn.call(testContext); if (returnValue instanceof Promise) { await returnValue; } return returnValue; } const hasDoneCallback = fn => fn.length > 0; function callFnWithCallback(hookOrTest, testContext) { const {fn} = hookOrTest; return new Promise((resolve, reject) => { function finish(error) { if (finish.done) { return; } if (error) { reject(error); } else { resolve(); } finish.done = true; } const done = (error) => { if (error) { finish(error); } else { finish(); } }; const returnValue = fn.call(testContext, done); if (returnValue instanceof Promise) { finish(new Error(` Test functions cannot both take a 'done' callback and return something. Either use a 'done' callback, or return a promise.`)); } }); } function callAsyncCircusFn(hookOrTest, testContext, timeout) { return Promise.race([ delay(timeout).then(() => {throw new Error(`timedout`)}), hasDoneCallback(hookOrTest.fn) ? callFnWithCallback(hookOrTest, testcontext) : callFnAsPromised(hookOrTest, testContext) ]); } const callCircusHook = async ({ hook, test, describeBlock, testContext, }) => { const timeout = hook.timeout || getState().testTimeout; try { await callAsyncCircusFn(hook, testContext, timeout); await dispatch({describeBlock, hook, name: 'hook_success', test}); } catch (error) { await dispatch({describeBlock, error, hook, name: 'hook_failure', test}); } }; const callCircusTest = async (test, testContext) => { const timeout = test.timeout || getState().testTimeout; if (test.errors.length) { return; // We don't run the test if there's already an error in before hooks. } try { await callAsyncCircusFn(test, testContext, timeout); await dispatch({name: 'test_fn_success', test}); } catch (error) { await dispatch({error, name: 'test_fn_failure', test}); } }; const runTest = async (test, parentSkipped) => { const testContext = Object.create(null); const {hasFocusedTests, testNamePattern} = getState(); const isSkipped = parentSkipped || test.mode === 'skip' || (hasFocusedTests && test.mode !== 'only') || (testNamePattern && !testNamePattern.test(getTestID(test))); if (isSkipped) { await dispatch({name: 'test_skip', test}); return; } if (test.mode === 'todo') { await dispatch({name: 'test_todo', test}); return; } const {afterEach, beforeEach} = getEachHooksForTest(test); for (const hook of beforeEach) { if (test.errors.length) { // If any of the before hooks failed already, we don't run any // hooks after that. break; } await callCircusHook({hook, test, testContext}); } await callCircusTest(test, testContext); for (const hook of afterEach) { await callCircusHook({hook, test, testContext}); } await dispatch({name: 'test_done', test}); }; async function runTestsForDescribeBlock(describeBlock) { await dispatch({describeBlock, name: 'run_describe_start'}); const {beforeAll, afterAll} = getAllHooksForDescribe(describeBlock); const isSkipped = describeBlock.mode === 'skip'; if (!isSkipped) { for (const hook of beforeAll) { await callCircusHook({describeBlock, hook}); } } for (const child of describeBlock.children) { switch (child.type) { case 'describeBlock': { await runTestsForDescribeBlock(child); break; } case 'test': { await runTest(child, isSkipped); break; } } } if (!isSkipped) { for (const hook of afterAll) { await callCircusHook({describeBlock, hook}); } } await dispatch({describeBlock, name: 'run_describe_finish'}); } const makeSingleTestResult = ( test, ) => { const {includeTestLocationInResult} = getState(); const testPath = []; let parent = test; const {status} = test; // invariant(status, 'Status should be present after tests are run.'); do { testPath.unshift(parent.name); } while ((parent = parent.parent)); let location = null; if (includeTestLocationInResult) { const stackLines = test.asyncError.stack.split('\n'); const stackLine = stackLines[1]; let parsedLine = stackUtils.parseLine(stackLine); // if (parsedLine?.file?.startsWith(jestEachBuildDir)) { // const stackLine = stackLines[4]; // parsedLine = stackUtils.parseLine(stackLine); // } if ( parsedLine && typeof parsedLine.column === 'number' && typeof parsedLine.line === 'number' ) { location = { column: parsedLine.column, line: parsedLine.line, }; } } const errorsDetailed = test.errors.map(_getError); return { duration: test.duration, errors: errorsDetailed.map(getErrorStack), errorsDetailed, invocations: test.invocations, location, status, testPath: Array.from(testPath), }; }; const makeTestResults = (describeBlock)=> { const testResults = []; for (const child of describeBlock.children) { switch (child.type) { case 'describeBlock': { testResults.push(...makeTestResults(child)); break; } case 'test': { testResults.push(makeSingleTestResult(child)); break; } } } return testResults; }; const _getError = (errors) => { let error; let asyncError; if (Array.isArray(errors)) { error = errors[0]; asyncError = errors[1]; } else { error = errors; asyncError = new Error(); } if (error && (typeof error.stack === 'string' || error.message)) { return error; } asyncError.message = error.message;//`thrown: ${prettyFormat(error, {maxDepth: 3})}`; return asyncError; }; const getErrorStack = (error) => typeof error.stack === 'string' ? error.stack : error.message; const makeRunResult = (describeBlock,unhandledErrors) => ({ testResults: makeTestResults(describeBlock), unhandledErrors: unhandledErrors.map(_getError).map(getErrorStack), }); const run = async () => { const {rootDescribeBlock} = getState(); await dispatch({name: 'run_start'}); await runTestsForDescribeBlock(rootDescribeBlock); await dispatch({name: 'run_finish'}); return makeRunResult( getState().rootDescribeBlock, getState().unhandledErrors, ); }; class JestTestResults { constructor(testResults) { const title = 'Jest Test Results'; const getTestName = result => result.testPath.slice(1).join(' › '); const HTML = ` <div> ${testResults.testResults.map(result => (` <div> ${result.status === 'success' ? (`✔️ ${getTestName(result)}`) : (`<div style="color:red"> <div>❌ ${getTestName(result)}</div> <div> ${result.errors.map(error => `<pre>${error}</pre>`)} </div> </div>`)} </div> `)).join('\n')} </div> `; Object.assign(this, {[ValueViewerSymbol]: {title, HTML } }); } } dispatcher.on('event', event => { // console.log(event); switch (event.name) { case 'test_fn_success': event.test.status = 'success'; break; case 'test_fn_failure': event.test.status = 'error'; event.test.errors.push(event.error); break; default: break; } }) return () => run().then(testResults => { const results = new JestTestResults(testResults); console.log(results); return results; }); }()); execute();
Loading…

2 comments

  • posted 3 years ago by joeyjiron06
    Use me by adding this to your Runkit notebook
  • posted 3 years ago by joeyjiron06
    ``` require('@runkit/joeyjiron06/jest-runkit/latest'); ```

sign in to comment