unassessed
Version:
Extensible type-aware assertion library
159 lines (140 loc) • 4.7 kB
JavaScript
const ASSERTIONS_TO_EXCLUDE = {
called: true,
"when called": true,
"called with": true,
"when called with": true,
"decoded as": true,
"when decoded as": true,
"passed as parameter to": true,
"when passed as parameter to": true,
"passed as parameters to": true,
"when passed as parameters to": true,
"passed as parameter to async": true,
"when passed as parameter to async": true,
"passed as parameters to async": true,
"passed as parameter to constructor": true,
"when passed as parameter to constructor": true,
"when passed as parameters to async": true,
"passed as parameters to constructor": true,
"when passed as parameters to constructor": true,
sorted: true,
"when sorted": true,
"sorted by": true,
"when sorted by": true,
"sorted numerically": true,
"when sorted numerically": true,
"when fulfilled": true,
"when rejected": true,
// "to only have properties" - only and not are mutually exclusive
"not to only have own properties": true,
"not to only have properties": true,
// "to have key"
"to have key": true,
"to only have key": true,
"to not have key": true,
"to not only have key": true,
"not to have key": true,
// "to have keys"
"to have keys": true,
"to only have keys": true,
"to not have keys": true,
"to not only have keys": true,
"not to have keys": true,
// others
"to be ok": true,
"not to be ok": true
};
const ASSERTIONS_TO_FORCE_NO_VALUE = {
"to be falsy": true,
"to be truthy": true,
"not to be falsy": true,
"not to be truthy": true
};
function upperCaseFirst(string) {
return string[0].toUpperCase() + string.slice(1);
}
function determineTypesOfValues(expect, assertionString) {
const originalAssertion = expect.assertions[assertionString];
const typesOfValues = [];
originalAssertion.forEach(definition => {
const valueMatches = definition.declaration.match(
/(?: <([a-zA-z-]+[+?]?(?:[|][a-zA-z-]+)*)>)* <([a-zA-z-]+[+?]?(?:[|][a-zA-z-]+)*)>$/
);
if (valueMatches) {
const validMatches = valueMatches.slice(1).filter(Boolean);
validMatches.forEach((valueMatch, index) => {
if (!valueMatch) return;
let typeOfValue;
if (typesOfValues.length <= index) {
typeOfValue = new Set();
typesOfValues.push(typeOfValue);
} else {
typeOfValue = typesOfValues[index];
}
let matchedTypes = valueMatch.split("|");
// handle vararg types by treating them a single arg type
matchedTypes = matchedTypes.map(type =>
type.endsWith("+") ? type.slice(0, -1) : type
);
matchedTypes.map(type => typeOfValue.add(type));
});
}
});
return typesOfValues;
}
function identifyTypesOfValues(typesOfValues) {
const maybeNested =
typesOfValues.length > 0 &&
(typesOfValues[typesOfValues.length - 1].has("assertion?") ||
typesOfValues[typesOfValues.length - 1].has("assertion"));
const lastTypesSet = maybeNested && typesOfValues[typesOfValues.length - 1];
return {
isMiddleRocket: maybeNested && lastTypesSet.size === 1,
isNestingAllowed: maybeNested && lastTypesSet.size > 1
};
}
function processUnexpectedInstance(expect, assertions) {
const casedDefinitions = {};
Object.keys(assertions).forEach(assertionString => {
const assertionTokens = assertionString.split(" ");
const assertionOpener = assertionTokens[0];
if (assertionString in ASSERTIONS_TO_EXCLUDE) {
return;
}
// skip any variants using "non-empty"
if (assertionTokens.includes("non-empty")) {
return;
}
const camelCasedString =
assertionOpener +
assertionTokens
.slice(1)
.map(upperCaseFirst)
.join("");
const typesOfValues =
assertionString in ASSERTIONS_TO_FORCE_NO_VALUE
? [] // TODO: remove this workaround when "to be ok" string value is removed
: determineTypesOfValues(expect, assertionString);
if (
casedDefinitions[camelCasedString] &&
casedDefinitions[camelCasedString].isNestingAllowed
) {
// avoid overwriting any assertion where nesting was enabled
return;
}
const { isMiddleRocket, isNestingAllowed } = identifyTypesOfValues(
typesOfValues
);
// record all information derived for the assertion
casedDefinitions[camelCasedString] = {
assertionString,
isMiddleRocket,
isNestingAllowed,
typesOfValues
};
});
return casedDefinitions;
}
module.exports = processUnexpectedInstance;
module.exports.determineTypesOfValues = determineTypesOfValues;
module.exports.identifyTypesOfValues = identifyTypesOfValues;