// @ts-check // Universal code - should run in Node or in the browser /** * Parses test title grep string. * The string can have "-" in front of it to invert the match. * @param {string} s Input substring of the test title */ function parseTitleGrep(s) { if (!s || typeof s !== "string") { return null; } s = s.trim(); if (s.startsWith("-")) { return { title: s.substring(1), invert: true, }; } return { title: s, invert: false, }; } function parseFullTitleGrep(s) { if (!s || typeof s !== "string") { return []; } // separate each title return s.split(";").map(parseTitleGrep); } /** * Parses tags to grep for. * @param {string} s Tags string like "@tag1+@tag2" */ function parseTagsGrep(s) { if (!s) { return []; } const explicitNotTags = []; // top level split - using space or comma, each part is OR const ORS = s .split(/[ ,]/) // remove any empty tags .filter(Boolean) .map(part => { // now every part is an AND if (part.startsWith("--")) { explicitNotTags.push({ tag: part.slice(2), invert: true, }); return; } const parsed = part.split("+").map(tag => { if (tag.startsWith("-")) { return { tag: tag.slice(1), invert: true, }; } else { return { tag, invert: false, }; } }); return parsed; }); // filter out undefined from explicit not tags const ORS_filtered = ORS.filter(x => x !== undefined); if (explicitNotTags.length > 0) { ORS_filtered.forEach((OR, index) => { ORS_filtered[index] = OR.concat(explicitNotTags); }); } return ORS_filtered; } function shouldTestRunTags(parsedGrepTags, tags = []) { if (!parsedGrepTags.length) { // there are no parsed tags to search for, the test should run return true; } // now the test has tags and the parsed tags are present // top levels are OR const onePartMatched = parsedGrepTags.some(orPart => { const everyAndPartMatched = orPart.every(p => { if (p.invert) { return !tags.includes(p.tag); } return tags.includes(p.tag); }); // console.log('every part matched %o?', orPart, everyAndPartMatched) return everyAndPartMatched; }); // console.log('onePartMatched', onePartMatched) return onePartMatched; } function shouldTestRunTitle(parsedGrep, testName) { if (!testName) { // if there is no title, let it run return true; } if (!parsedGrep) { return true; } if (!Array.isArray(parsedGrep)) { console.error("Invalid parsed title grep"); console.error(parsedGrep); throw new Error("Expected title grep to be an array"); } if (!parsedGrep.length) { return true; } const inverted = parsedGrep.filter(g => g.invert); const straight = parsedGrep.filter(g => !g.invert); return ( inverted.every(titleGrep => !testName.includes(titleGrep.title)) && (!straight.length || straight.some(titleGrep => testName.includes(titleGrep.title))) ); } // note: tags take precedence over the test name function shouldTestRun(parsedGrep, testName, tags = [], grepUntagged = false) { if (grepUntagged) { return !tags.length; } if (Array.isArray(testName)) { // the caller passed tags only, no test name tags = testName; testName = undefined; } return ( shouldTestRunTitle(parsedGrep.title, testName) && shouldTestRunTags(parsedGrep.tags, tags) ); } function parseGrep(titlePart, tags) { return { title: parseFullTitleGrep(titlePart), tags: parseTagsGrep(tags), }; } function resolveConfig(config) { const specPattern = config.specPattern || ""; let excludeSpecPattern = config.excludeSpecPattern || ""; if (typeof excludeSpecPattern === "string") { excludeSpecPattern = [excludeSpecPattern]; } const integrationFolder = config.env.grepIntegrationFolder || process.cwd(); return { resolvedConfig: { specPattern, excludeSpecPattern, integrationFolder, }, }; } module.exports = { parseGrep, parseTitleGrep, parseFullTitleGrep, parseTagsGrep, resolveConfig, shouldTestRun, shouldTestRunTags, shouldTestRunTitle, };