Jest Runkit

node v14.20.1
version: 4.0.2
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, ); }; const stylize = (style) => Object.keys(style).map(k => `${k}: ${style[k]}`).join(';') const renderTestResult = (result) => { const testName = result.testPath.slice(1).join(' › '); const resultStyle = stylize({ 'margin-bottom': '12px' }); if (result.status === 'success') { return `<div style="${resultStyle}">✔️ ${testName}</div>`; } if (result.status === 'error') { return ` <div style="${resultStyle};color:red"> <div>❌ ${testName}</div> <div> ${result.errors.map(error => `<pre>${error}</pre>`)} </div> </div>`; } const skipStyle = stylize({ 'background': 'grey', 'border-radius': '2px', 'color': 'white', 'padding': '2px 8px' }); return `<div style="${resultStyle}"><span style="${skipStyle}">skipped</span> ${testName}</div>`; }; class JestTestResults { constructor(testResults) { const title = 'Jest Test Results'; const HTML = ` <div> ${testResults.testResults.map(result => renderTestResult(result)).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();
This project is intended to be used in Runkit. This allows you to use jest in your Runkit so that you can test functionality. Just add this line of code at the top of your runkit file require('@runkit/joeyjiron06/jest-runkit/latest');
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