UNPKG

@dash-ui/jest

Version:

Jest utilities for dash-ui

386 lines (299 loc) 11.3 kB
import { parse, stringify } from 'css'; import chalk from 'chalk'; // Utils var flatMap = (arr, iteratee) => [].concat(...arr.map(iteratee)); var RULE_TYPES = { media: 'media', supports: 'supports', rule: 'rule' }; var getClassNames = (selectors, classes) => classes ? selectors.concat(classes.split(' ')) : selectors; var getClassNamesFromTestRenderer = (selectors, _ref) => { var { props } = _ref; return getClassNames(selectors, props ? props.className || props.class : null); }; var shouldDive = node => typeof node.dive === 'function' && typeof node.type() !== 'string'; var isTagWithClassName = node => node.prop('className') && typeof node.type() === 'string'; var getClassNamesFromEnzyme = (selectors, node) => { // We need to dive if we have selected a styled child from a shallow render var actualComponent = shouldDive(node) ? node.dive() : node; // Find the first node with a className prop var components = actualComponent.findWhere(isTagWithClassName); var classes = components.length && components.first().prop('className'); return getClassNames(selectors, classes); }; var getClassNamesFromCheerio = (selectors, node) => { var classes = node.attr('class'); return getClassNames(selectors, classes); }; var getClassNamesFromDOMElement = (selectors, node) => getClassNames(selectors, node.getAttribute('class')); var 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; } }; var domElementPattern = /^((HTML|SVG)\w*)?Element$/; var isDOMElement = val => val.nodeType === 1 && val.constructor && val.constructor.name && domElementPattern.test(val.constructor.name); var isEnzymeElement = val => typeof val.findWhere === 'function'; var isCheerioElement = val => val.cheerio === '[cheerio object]'; function _ref2(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); } var getClassNamesFromNodes = nodes => nodes.reduce(_ref2, []); var keyframesPattern = /^@keyframes\s+(animation-[^{\s]+)+/; var removeCommentPattern = /\/\*[\s\S]*?\*\//g; function _ref3(cssRule) { return cssRule.cssText; } var getElementRules = element => { var 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(_ref3); }; var getStylesFromClassNames = (classNames, elements) => { if (!classNames.length) { return ''; } var keys = getKeys(elements); if (!keys.length) { return ''; } var keyPatten = new RegExp("^(" + keys.join('|') + ")-"); var filteredClassNames = classNames.filter(className => keyPatten.test(className)); if (!filteredClassNames.length) { return ''; } var selectorPattern = new RegExp('\\.(' + filteredClassNames.join('|') + ')'); var keyframes = {}; var styles = ''; flatMap(elements, getElementRules).forEach(rule => { if (selectorPattern.test(rule)) { styles += rule; } var match = rule.match(keyframesPattern); if (match !== null) { var name = match[1]; if (keyframes[name] === undefined) { keyframes[name] = ''; } keyframes[name] += rule; } }); var keyframeNameKeys = Object.keys(keyframes); var keyframesStyles = ''; if (keyframeNameKeys.length) { var keyframesNamePattern = new RegExp(keyframeNameKeys.join('|'), 'g'); var keyframesNameCache = {}; var 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, ''); }; var getStyleElements = () => Array.from(document.querySelectorAll('style[data-dash]')); var unique = arr => Array.from(new Set(arr)); function _ref4(element) { return element.getAttribute('data-dash'); } var getKeys = elements => unique(elements.map(_ref4)).filter(Boolean); var 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)); }); function _ref5(mediaRules, mediaRule) { return mediaRules.concat(mediaRule.rules); } var getMediaRules = (rules, media) => rules.filter(rule => { var isMediaMatch = rule.media ? rule.media.replace(/\s/g, '').includes(media.replace(/\s/g, '')) : false; return rule.type === RULE_TYPES.media && isMediaMatch; }).reduce(_ref5, []); function _ref6(supportsRules, supportsRule) { return supportsRules.concat(supportsRule.rules); } var getSupportsRules = (rules, supports) => rules.filter(rule => { var isSupportsMatch = rule.supports ? rule.supports.replace(/\s/g, '').trim().endsWith(supports.replace(/\s/g, '').trim()) : false; return rule.type === RULE_TYPES.supports && isSupportsMatch; }).reduce(_ref6, []); // // Matchers /* * Taken from * https://github.com/facebook/jest/blob/be4bec387d90ac8d6a7596be88bf8e4994bc3ed9/packages/expect/src/jasmine_utils.js#L234 */ var 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 */ var isAsymmetric = obj => obj && isA('Function', obj.asymmetricMatch); var 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); }; var minLeft = /([:;,([{}>~/\s]|\/\*)\s+/g; var minRight = /\s+([:;,)\]{}>~/!]|\*\/)/g; var minify = s => s.trim().replace(minLeft, '$1').replace(minRight, '$1'); function _ref7(decs, rule) { return decs.concat(rule.declarations); } var toHaveStyleRule = function toHaveStyleRule(received, property, value, options) { if (options === void 0) { options = {}; } var { target, media, supports } = options; var classNames = getClassNamesFromNodes([received]); var cssString = getStylesFromClassNames(classNames, getStyleElements()); var styles = parse(cssString); var preparedRules = styles.stylesheet.rules; if (media) { preparedRules = getMediaRules(preparedRules, media); } if (supports) { preparedRules = getSupportsRules(preparedRules, supports); } var declaration = preparedRules.filter(rule => rule.type === RULE_TYPES.rule && hasClassNames(classNames, rule.selectors, target)).reduce(_ref7, []).filter(dec => dec.type === 'declaration' && minify(dec.property) === minify(property)).pop(); function _message() { return "Property not found: " + property; } if (!declaration) { return { pass: false, message: _message }; } var pass = valueMatches(declaration, value); var _message = () => "Expected " + property + (pass ? ' not ' : ' ') + "to match:\n" + (" " + chalk.green(value) + "\n") + 'Received:\n' + (" " + chalk.red(declaration.value)); return { pass, message: _message }; }; var matchers = { toHaveStyleRule }; // // Pretty serialization var defaultClassNameReplacer = (className, index) => "dash-ui-" + index; var componentSelectorClassNamePattern = /^e[a-zA-Z0-9]+[0-9]+$/; var replaceClassNames = function replaceClassNames(classNames, styles, code, keys, classNameReplacer) { if (classNameReplacer === void 0) { classNameReplacer = defaultClassNameReplacer; } var index = 0; var keyPattern = new RegExp("^(" + keys.join('|') + ")-"); return classNames.reduce((acc, className) => { if (keyPattern.test(className) || componentSelectorClassNamePattern.test(className)) { var escapedRegex = new RegExp(className.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'), 'g'); return acc.replace(escapedRegex, classNameReplacer(className, index++)); } return acc; }, "" + styles + (styles ? '\n\n' : '') + code); }; var getNodes = function getNodes(node, nodes) { if (nodes === void 0) { nodes = []; } if (Array.isArray(node)) { for (var child of node) { getNodes(child, nodes); } return nodes; } var children = node.children || node.props && node.props.children; if (children) { // fix for Preact X children = node.props ? Array.isArray(children) ? children : [children] : children; for (var _child of children) { getNodes(_child, nodes); } } if (typeof node === 'object') { nodes.push(node); } return nodes; }; var getPrettyStylesFromClassNames = (classNames, elements) => { var styles = getStylesFromClassNames(classNames, elements); var prettyStyles; try { prettyStyles = stringify(parse(styles)); } catch (e) { console.error(e); throw new Error("There was an error parsing the following css: \"" + styles + "\""); } return prettyStyles; }; var createSerializer = function createSerializer(opt) { if (opt === void 0) { opt = {}; } var { classNameReplacer, DOMElements = true } = opt; var cache = new WeakSet(); return { test(val) { return val && !cache.has(val) && (isReactElement(val) || DOMElements && isDOMElement(val)); }, print(val, printer) { var nodes = getNodes(val); var classNames = getClassNamesFromNodes(nodes); var elements = getStyleElements(); var styles = getPrettyStylesFromClassNames(classNames, elements); nodes.forEach(cache.add, cache); var printedVal = printer(val); nodes.forEach(cache.delete, cache); var keys = getKeys(elements); return replaceClassNames(classNames, styles, printedVal, keys, classNameReplacer); } }; }; var { print, test } = /*#__PURE__*/createSerializer(); var index = { print, test }; export default index; export { RULE_TYPES, createSerializer, getClassNamesFromNodes, getKeys, getMediaRules, getStyleElements, getStylesFromClassNames, getSupportsRules, hasClassNames, isDOMElement, isReactElement, matchers, print, replaceClassNames, test }; //# sourceMappingURL=index.dev.mjs.map