UNPKG

jest-test-each

Version:

run parametrised tests easily [typesafe] without text tables or arrays of arrays.

308 lines (307 loc) 14.2 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TestEach = void 0; const utils_1 = require("./utils/utils"); const tree_1 = require("./tree"); const name_1 = require("./utils/name"); const test_each_setup_1 = require("./test-each-setup"); const stripAnsi = require('strip-ansi'); class TestEach { constructor(desc) { var _a; this.groups = []; this.befores = []; this.ensures = []; this.ensuresCasesLength = []; this.defects = []; this.skippedTest = undefined; this.onlyOne = false; this.concurrentTests = false; this.onlyOneFilter = undefined; this.flatDescFunc = undefined; this.testParentDesc = ''; this.entityName = (num, name) => { return `${this.conf.numericCases ? num + '. ' : ''}${name}`; }; this.testIfOnly = (testRunner) => { if (this.onlyOne) { this.runTest(testRunner, 'only() should be removed before committing', () => { throw new Error('Do not forget to remove .only() from your test before committing'); }); } }; this.findDefect = (testData) => { const filterDefect = (data, defect) => { const foundDefected = () => { return defect && defect.filter ? defect.filter(data) : false; }; const reasons = () => { return defect.failReasons ? { actualFailReasonParts: defect.failReasons } : {}; }; const found = foundDefected(); const res = reasons(); return !defect.filter || found ? Object.assign({ defect: defect.reason }, res) : {}; }; let defect = {}; const defectObjs = this.defects.map(defect => filterDefect(testData, defect)); defectObjs.forEach(p => (defect = Object.assign(Object.assign({}, defect), p))); return defect; }; this.runIsDefectExist = (testRunner, allCases) => { if (this.defects.length > 0) { this.defects.forEach((defect, i) => { const casesFound = allCases.filter(k => (defect.filter ? defect.filter(k.data) : true)); if (defect.filter && casesFound.length === 0) { this.runTest(testRunner, 'Filtering defect returned no results ' + (i + 1), () => { throw new Error('No such case: ' + defect.filter.toString()); }); return; } }); } }; this.filterAndRunIfSearchFailed = (testRunner, allCases) => { const casesFound = allCases.filter(k => this.onlyOneFilter ? this.onlyOneFilter(k.data) : false); if (this.onlyOneFilter && casesFound.length === 0) { this.runTest(testRunner, 'Only one search failed', () => { throw new Error('No such case: ' + this.onlyOneFilter.toString()); }); } return casesFound.length > 0 ? casesFound[0] : undefined; }; this.runEnsures = (testRunner, allCases) => { if (this.ensures.length > 0) { this.ensures.forEach(ensure => { this.runTest(testRunner, 'Ensure: ' + ensure.desc, () => { ensure.check(allCases.map(p => p.data)); }); }); } if (this.ensuresCasesLength.length > 0) { this.ensuresCasesLength.forEach(ensure => { this.runTest(testRunner, 'Ensure cases length', () => { ensure(expect(allCases.map(p => p.data).length)); }); }); } }; this.testParentDesc = desc; this.conf = test_each_setup_1.testConfig.config; this.env = (_a = test_each_setup_1.userEnv.env) !== null && _a !== void 0 ? _a : test_each_setup_1.testEnvDefault().env; } only(input) { this.onlyOne = true; if (input) { this.onlyOneFilter = input; } return this; } config(config) { this.conf = Object.assign(Object.assign({}, this.conf), config); return this; } // todo proper concurrent() { this.concurrentTests = true; return this; } // todo: should be ability to check previous levels ensure(desc, cases) { this.ensures.push({ desc, check: cases }); return this; } ensureCasesLength(exp) { this.ensuresCasesLength.push(exp); return this; } defect(reason, input, actualFailReasons) { this.defects.push({ reason: reason, filter: input, failReasons: actualFailReasons }); return this; } skip(reason) { this.skippedTest = reason; return this; } desc(input) { this.flatDescFunc = input; return this; } before(before) { this.befores.push(before); return this; } each(cases) { this.groups.push(cases); return this; } runTest(testRunner, name, body, args, isBefore) { const skipped = (args === null || args === void 0 ? void 0 : args.skip) || this.skippedTest; const markedDefect = args === null || args === void 0 ? void 0 : args.defect; const defectTestName = markedDefect ? ` - Marked with defect '${markedDefect}'` : ''; const testName = markedDefect ? name + defectTestName : name; //? name.replace(/(, )?defect\:\s*('|"|`)[^'"`]*('|"|`)/, '') + defectTestName const reasons = args === null || args === void 0 ? void 0 : args.actualFailReasonParts; return testRunner(testName, () => __awaiter(this, void 0, void 0, function* () { if (skipped) { this.env.pending(`Test skipped: '${skipped}'`); return; } if (markedDefect) { // if it passes -> fail // if it fails -> skip let error = undefined; yield this.runBody(body, args, isBefore) .then(() => { error = `Test doesn't fail but marked with defect`; }) .catch(err => { const alignedMessage = [stripAnsi(err.message), stripAnsi(err.stack)].join('\n'); // check reasons if (!reasons || reasons.every(r => alignedMessage.includes(r))) { // todo test will pass with jest-circus- need a way to skip test from inside for jest-circus this.env.pending(`Test marked with defect '${markedDefect}': Actual fail reason:\\n ${alignedMessage}`); } if (reasons && !reasons.every(r => alignedMessage.includes(r))) { throw new Error(`Actual fail reason doesn't contain [${reasons}]\nActual fail reason:\n "${alignedMessage}"`); } }); if (error) { throw new Error(error); } } else { yield this.runBody(body, args, isBefore); } })); } runBody(body, args, isBefore) { return __awaiter(this, void 0, void 0, function* () { const beforeResults = []; let beforeResult = {}; if (isBefore && this.befores.length > 0) { for (const b of this.befores) { const res = yield b(args || {}); if (res instanceof Object) { beforeResults.push(res); beforeResult = Object.assign(Object.assign({}, beforeResult), res); } } } try { yield body(args || {}, beforeResult); } finally { // after each beforeResults.forEach((p) => (p.dispose ? p.dispose() : {})); } }); } runCase(testRunner, body) { return (t, i) => { const name = this.entityName(i + 1, t.name.name); this.runTest(testRunner, name, (args, b) => __awaiter(this, void 0, void 0, function* () { const code = t.name.code; if ((this.conf.testSuiteName.failOnReached && code === name_1.CODE_RENAME.nameTooLong) || code === name_1.CODE_RENAME.nameHasFunctions) { utils_1.guard(!code, name_1.messageFromRenameCode(code, this.conf.testSuiteName.maxLength)); } yield body(args, b); }), t.data, true); }; } run(body) { const { groupBySuites, testSuiteName, groupParentBySuite } = this.conf; const maxTestNameLength = testSuiteName.maxLength; const useConcurrency = this.concurrentTests || this.conf.concurrent; const testRunner = this.onlyOne ? this.env.it.only : useConcurrency ? this.env.it.concurrent : this.env.it; let allCases = []; let casesErrors = []; const root = tree_1.createTree(this.groups, maxTestNameLength, casesErrors, currentTest => { var _a; let defect = this.findDefect(Object.assign({}, currentTest.data)); const additionalData = Object.assign({}, defect); const fullDatNoFlatDesc = [currentTest.data, additionalData]; const mergedFullData = utils_1.merge(fullDatNoFlatDesc); const desc = mergedFullData.flatDesc || ((_a = this.flatDescFunc) === null || _a === void 0 ? void 0 : _a.call(this, mergedFullData)); const flatDesc = { flatDesc: groupParentBySuite ? desc : desc ? `${this.testParentDesc} ${desc}` : undefined, }; const fullData = [...fullDatNoFlatDesc, flatDesc.flatDesc ? flatDesc : []]; const partialData = [currentTest.partialData, additionalData]; const nameCaseFull = name_1.getName(fullData, maxTestNameLength); const newName = name_1.getName(partialData, maxTestNameLength); if (!groupBySuites && !groupParentBySuite) { newName.name = `${this.testParentDesc} ${newName.name}`; nameCaseFull.name = `${this.testParentDesc} ${nameCaseFull.name}`; } const testCase = Object.assign(Object.assign({}, currentTest), { data: utils_1.merge(fullData), name: newName, partialData: utils_1.merge(partialData) }); allCases.push(Object.assign(Object.assign({}, testCase), { name: nameCaseFull, isFlat: flatDesc.flatDesc })); return testCase; }); root.children.forEach(n => (n.name = groupParentBySuite ? n.name : `${this.testParentDesc} ${n.name}`)); root.tests.forEach(n => (n.name.name = groupParentBySuite ? n.name.name : `${this.testParentDesc} ${n.name.name}`)); const isFlat = allCases.every(p => p.isFlat); if (this.onlyOne) { const caseFound = this.filterAndRunIfSearchFailed(testRunner, allCases); if (this.onlyOneFilter && caseFound === undefined) { return; } allCases = this.onlyOneFilter ? [caseFound] : [allCases[0]]; } if (this.groups.length === 0 && !!this.testParentDesc) { // should not group into suite when only one case this.runTest(testRunner, this.testParentDesc, body, undefined, true); this.testIfOnly(testRunner); return; } const suiteGuards = () => { utils_1.guard(!(this.groups.length === 0 && !this.testParentDesc), 'Test should have name when no cases'); utils_1.guard(!this.groups.some(p => utils_1.checkObjEmpty(p)), 'Every case in .each should have not empty data'); utils_1.guard(!(allCases.length === 0), 'Test should have at least one test'); }; const tests = () => tree_1.treeWalk(root, (t, i, inside) => { this.env.describe(this.entityName(i + 1, t.name), inside); }, this.runCase(testRunner, body)); const testsFlat = () => allCases.forEach(this.runCase(testRunner, body)); runSuite(this.env.describe, () => { suiteGuards(); if (casesErrors.length > 0) { const defaultErr = 'test cases are empty'; casesErrors.forEach(casesError => { this.runTest(testRunner, casesError.test || defaultErr, () => { throw new Error([casesError.msg || defaultErr, casesError.details || ''].join('\n\n')); }); }); } if (!this.onlyOne) { this.runEnsures(testRunner, allCases); } this.runIsDefectExist(testRunner, allCases); this.testIfOnly(testRunner); groupBySuites && !isFlat && !this.onlyOne ? tests() : testsFlat(); }, this.testParentDesc, groupParentBySuite); } } exports.TestEach = TestEach; const runSuite = (suiteRunner, callback, suiteName, groupParent) => { if (suiteName && groupParent) { suiteRunner(suiteName, () => { callback(); }); return; } callback(); };