@dash-ui/jest
Version:
Jest utilities for dash-ui
407 lines (304 loc) • 12.9 kB
JavaScript
exports.__esModule = true;
exports.default = exports.test = exports.print = exports.createSerializer = exports.replaceClassNames = exports.matchers = exports.getSupportsRules = exports.getMediaRules = exports.hasClassNames = exports.getKeys = exports.getStyleElements = exports.getStylesFromClassNames = exports.getClassNamesFromNodes = exports.isDOMElement = exports.isReactElement = exports.RULE_TYPES = void 0;
var css = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("css"));
var _chalk = /*#__PURE__*/_interopRequireDefault( /*#__PURE__*/require("chalk"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
//
// Utils
const flatMap = (arr, iteratee) => [].concat(...arr.map(iteratee));
const RULE_TYPES = {
media: 'media',
supports: 'supports',
rule: 'rule'
};
exports.RULE_TYPES = RULE_TYPES;
const getClassNames = (selectors, classes) => classes ? selectors.concat(classes.split(' ')) : selectors;
const getClassNamesFromTestRenderer = (selectors, {
props
}) => getClassNames(selectors, props ? props.className || props.class : null);
const shouldDive = node => typeof node.dive === 'function' && typeof node.type() !== 'string';
const isTagWithClassName = node => node.prop('className') && typeof node.type() === 'string';
const getClassNamesFromEnzyme = (selectors, node) => {
// We need to dive if we have selected a styled child from a shallow render
const actualComponent = shouldDive(node) ? node.dive() : node; // Find the first node with a className prop
const components = actualComponent.findWhere(isTagWithClassName);
const classes = components.length && components.first().prop('className');
return getClassNames(selectors, classes);
};
const getClassNamesFromCheerio = (selectors, node) => {
const classes = node.attr('class');
return getClassNames(selectors, classes);
};
const getClassNamesFromDOMElement = (selectors, node) => getClassNames(selectors, node.getAttribute('class'));
const isReactElement = val => {
if (val.$$typeof === Symbol.for('react.test.json')) {
return true;
} else if (val.hasOwnProperty('props') && val.hasOwnProperty('type') && val.hasOwnProperty('ref') && val.hasOwnProperty('key')) {
// Preact X
val.$$typeof = Symbol.for('react.test.json');
return true;
}
};
exports.isReactElement = isReactElement;
const domElementPattern = /^((HTML|SVG)\w*)?Element$/;
const isDOMElement = val => val.nodeType === 1 && val.constructor && val.constructor.name && domElementPattern.test(val.constructor.name);
exports.isDOMElement = isDOMElement;
const isEnzymeElement = val => typeof val.findWhere === 'function';
const isCheerioElement = val => val.cheerio === '[cheerio object]';
function _ref(selectors, node) {
if (isReactElement(node)) {
return getClassNamesFromTestRenderer(selectors, node);
} else if (isEnzymeElement(node)) {
return getClassNamesFromEnzyme(selectors, node);
} else if (isCheerioElement(node)) {
return getClassNamesFromCheerio(selectors, node);
}
return getClassNamesFromDOMElement(selectors, node);
}
const getClassNamesFromNodes = nodes => nodes.reduce(_ref, []);
exports.getClassNamesFromNodes = getClassNamesFromNodes;
let keyframesPattern = /^@keyframes\s+(animation-[^{\s]+)+/;
let removeCommentPattern = /\/\*[\s\S]*?\*\//g;
function _ref2(cssRule) {
return cssRule.cssText;
}
const getElementRules = element => {
const nonSpeedyRule = element.textContent;
if (nonSpeedyRule) {
return [nonSpeedyRule];
}
if (!element.sheet) {
return [];
} // $FlowFixMe - flow doesn't know about `cssRules` property
return [].slice.call(element.sheet.cssRules).map(_ref2);
};
const getStylesFromClassNames = (classNames, elements) => {
if (!classNames.length) {
return '';
}
let keys = getKeys(elements);
if (!keys.length) {
return '';
}
let keyPatten = new RegExp(`^(${keys.join('|')})-`);
let filteredClassNames = classNames.filter(className => keyPatten.test(className));
if (!filteredClassNames.length) {
return '';
}
let selectorPattern = new RegExp('\\.(' + filteredClassNames.join('|') + ')');
let keyframes = {};
let styles = '';
flatMap(elements, getElementRules).forEach(rule => {
if (selectorPattern.test(rule)) {
styles += rule;
}
let match = rule.match(keyframesPattern);
if (match !== null) {
let name = match[1];
if (keyframes[name] === undefined) {
keyframes[name] = '';
}
keyframes[name] += rule;
}
});
let keyframeNameKeys = Object.keys(keyframes);
let keyframesStyles = '';
if (keyframeNameKeys.length) {
let keyframesNamePattern = new RegExp(keyframeNameKeys.join('|'), 'g');
let keyframesNameCache = {};
let index = 0;
styles = styles.replace(keyframesNamePattern, name => {
if (keyframesNameCache[name] === undefined) {
keyframesNameCache[name] = `animation-${index++}`;
keyframesStyles += keyframes[name];
}
return keyframesNameCache[name];
});
keyframesStyles = keyframesStyles.replace(keyframesNamePattern, value => {
return keyframesNameCache[value];
});
}
return (keyframesStyles + styles).replace(removeCommentPattern, '');
};
exports.getStylesFromClassNames = getStylesFromClassNames;
const getStyleElements = () => Array.from(document.querySelectorAll('style[data-dash]'));
exports.getStyleElements = getStyleElements;
let unique = arr => Array.from(new Set(arr));
function _ref3(element) {
return element.getAttribute('data-dash');
}
const getKeys = elements => unique(elements.map(_ref3)).filter(Boolean);
exports.getKeys = getKeys;
const hasClassNames = (classNames, selectors, target) => selectors.some(selector => {
// if no target, use className of the specific css rule and try to find it
// in the list of received node classNames to make sure this css rule
// applied for root element
if (!target) {
return classNames.includes(selector.slice(1));
} // check if selector (className) of specific css rule match target
return target instanceof RegExp ? target.test(selector) : minify(selector).includes(minify(target));
});
exports.hasClassNames = hasClassNames;
function _ref4(mediaRules, mediaRule) {
return mediaRules.concat(mediaRule.rules);
}
const getMediaRules = (rules, media) => rules.filter(rule => {
const isMediaMatch = rule.media ? rule.media.replace(/\s/g, '').includes(media.replace(/\s/g, '')) : false;
return rule.type === RULE_TYPES.media && isMediaMatch;
}).reduce(_ref4, []);
exports.getMediaRules = getMediaRules;
function _ref5(supportsRules, supportsRule) {
return supportsRules.concat(supportsRule.rules);
}
const getSupportsRules = (rules, supports) => rules.filter(rule => {
const isSupportsMatch = rule.supports ? rule.supports.replace(/\s/g, '').trim().endsWith(supports.replace(/\s/g, '').trim()) : false;
return rule.type === RULE_TYPES.supports && isSupportsMatch;
}).reduce(_ref5, []); //
// Matchers
/*
* Taken from
* https://github.com/facebook/jest/blob/be4bec387d90ac8d6a7596be88bf8e4994bc3ed9/packages/expect/src/jasmine_utils.js#L234
*/
exports.getSupportsRules = getSupportsRules;
const isA = (typeName, value) => Object.prototype.toString.apply(value) === `[object ${typeName}]`;
/*
* Taken from
* https://github.com/facebook/jest/blob/be4bec387d90ac8d6a7596be88bf8e4994bc3ed9/packages/expect/src/jasmine_utils.js#L36
*/
const isAsymmetric = obj => obj && isA('Function', obj.asymmetricMatch);
const valueMatches = (declaration, value) => {
if (value instanceof RegExp) {
return value.test(declaration.value);
}
if (isAsymmetric(value)) {
return value.asymmetricMatch(declaration.value);
}
return minify(value) === minify(declaration.value);
};
const minLeft = /([:;,([{}>~/\s]|\/\*)\s+/g;
const minRight = /\s+([:;,)\]{}>~/!]|\*\/)/g;
const minify = s => s.trim().replace(minLeft, '$1').replace(minRight, '$1');
function _ref6(decs, rule) {
return decs.concat(rule.declarations);
}
const toHaveStyleRule = (received, property, value, options = {}) => {
const {
target,
media,
supports
} = options;
const classNames = getClassNamesFromNodes([received]);
const cssString = getStylesFromClassNames(classNames, getStyleElements());
const styles = css.parse(cssString);
let preparedRules = styles.stylesheet.rules;
if (media) {
preparedRules = getMediaRules(preparedRules, media);
}
if (supports) {
preparedRules = getSupportsRules(preparedRules, supports);
}
const declaration = preparedRules.filter(rule => rule.type === RULE_TYPES.rule && hasClassNames(classNames, rule.selectors, target)).reduce(_ref6, []).filter(dec => dec.type === 'declaration' && minify(dec.property) === minify(property)).pop();
function _ref7() {
return `Property not found: ${property}`;
}
if (!declaration) {
return {
pass: false,
message: _ref7
};
}
const pass = valueMatches(declaration, value);
const message = () => `Expected ${property}${pass ? ' not ' : ' '}to match:\n` + ` ${_chalk.default.green(value)}\n` + 'Received:\n' + ` ${_chalk.default.red(declaration.value)}`;
return {
pass,
message
};
};
const matchers = {
toHaveStyleRule
}; //
// Pretty serialization
exports.matchers = matchers;
const defaultClassNameReplacer = (className, index) => `dash-ui-${index}`;
const componentSelectorClassNamePattern = /^e[a-zA-Z0-9]+[0-9]+$/;
const replaceClassNames = (classNames, styles, code, keys, classNameReplacer = defaultClassNameReplacer) => {
let index = 0;
let keyPattern = new RegExp(`^(${keys.join('|')})-`);
return classNames.reduce((acc, className) => {
if (keyPattern.test(className) || componentSelectorClassNamePattern.test(className)) {
const escapedRegex = new RegExp(className.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'), 'g');
return acc.replace(escapedRegex, classNameReplacer(className, index++));
}
return acc;
}, `${styles}${styles ? '\n\n' : ''}${code}`);
};
exports.replaceClassNames = replaceClassNames;
const getNodes = (node, nodes = []) => {
if (Array.isArray(node)) {
for (let child of node) {
getNodes(child, nodes);
}
return nodes;
}
let children = node.children || node.props && node.props.children;
if (children) {
// fix for Preact X
children = node.props ? Array.isArray(children) ? children : [children] : children;
for (let child of children) {
getNodes(child, nodes);
}
}
if (typeof node === 'object') {
nodes.push(node);
}
return nodes;
};
const getPrettyStylesFromClassNames = (classNames, elements) => {
let styles = getStylesFromClassNames(classNames, elements);
let prettyStyles;
try {
prettyStyles = css.stringify(css.parse(styles));
} catch (e) {
console.error(e);
throw new Error(`There was an error parsing the following css: "${styles}"`);
}
return prettyStyles;
};
const createSerializer = (opt = {}) => {
let {
classNameReplacer,
DOMElements = true
} = opt;
let cache = new WeakSet();
return {
test(val) {
return val && !cache.has(val) && (isReactElement(val) || DOMElements && isDOMElement(val));
},
print(val, printer) {
const nodes = getNodes(val);
const classNames = getClassNamesFromNodes(nodes);
let elements = getStyleElements();
const styles = getPrettyStylesFromClassNames(classNames, elements);
nodes.forEach(cache.add, cache);
const printedVal = printer(val);
nodes.forEach(cache.delete, cache);
let keys = getKeys(elements);
return replaceClassNames(classNames, styles, printedVal, keys, classNameReplacer);
}
};
};
exports.createSerializer = createSerializer;
const {
print,
test
} = /*#__PURE__*/createSerializer();
exports.test = test;
exports.print = print;
var _default = {
print,
test
};
exports.default = _default;
;