@emotion/snapshot-serializer
Advanced tools
Comparing version 0.7.2 to 0.8.0
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var chalk = _interopDefault(require('chalk')); | ||
var css = require('css'); | ||
function replacer(className, index) { | ||
return "emotion-" + index; | ||
} | ||
var componentSelectorClassNamePattern = /^e[a-zA-Z0-9]+[0-9]+$/; | ||
var replaceClassNames = function replaceClassNames(classNames, styles, code, keys) { | ||
var index = 0; | ||
var keyPattern = new RegExp("^(" + keys.join('|') + ")-"); | ||
return classNames.reduce(function (acc, className) { | ||
if (keyPattern.test(className) || componentSelectorClassNamePattern.test(className)) { | ||
var escapedRegex = new RegExp(className.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'), 'g'); | ||
return acc.replace(escapedRegex, replacer(className, index++)); | ||
} | ||
return acc; | ||
}, "" + styles + (styles ? '\n\n' : '') + code); | ||
}; | ||
function getClassNames(selectors, classes) { | ||
return classes ? selectors.concat(classes.split(' ')) : selectors; | ||
} | ||
function getClassNamesFromTestRenderer(selectors, _ref) { | ||
var _ref$props = _ref.props, | ||
props = _ref$props === void 0 ? {} : _ref$props; | ||
return getClassNames(selectors, props.className || props.class); | ||
} | ||
function shouldDive(node) { | ||
return typeof node.dive === 'function' && typeof node.type() !== 'string'; | ||
} | ||
function isTagWithClassName(node) { | ||
return node.prop('className') && typeof node.type() === 'string'; | ||
} | ||
function 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); | ||
} | ||
function getClassNamesFromCheerio(selectors, node) { | ||
var classes = node.attr('class'); | ||
return getClassNames(selectors, classes); | ||
} | ||
function getClassNamesFromDOMElement(selectors, node) { | ||
return getClassNames(selectors, node.getAttribute('class')); | ||
} | ||
function isReactElement(val) { | ||
return val.$$typeof === Symbol.for('react.test.json'); | ||
} | ||
var domElementPattern = /^((HTML|SVG)\w*)?Element$/; | ||
function isDOMElement(val) { | ||
return val.nodeType === 1 && val.constructor && val.constructor.name && domElementPattern.test(val.constructor.name); | ||
} | ||
function isEnzymeElement(val) { | ||
return typeof val.findWhere === 'function'; | ||
} | ||
function isCheerioElement(val) { | ||
return val.cheerio === '[cheerio object]'; | ||
} | ||
function getClassNamesFromNodes(nodes) { | ||
return nodes.reduce(function (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); | ||
}, []); | ||
} | ||
function 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(function (className) { | ||
return keyPatten.test(className); | ||
}); | ||
if (!filteredClassNames.length) { | ||
return ''; | ||
} | ||
var selectorPattern = new RegExp('\\.(' + filteredClassNames.join('|') + ')'); | ||
var styles = ''; | ||
elements.forEach(function (element) { | ||
var rule = element.textContent || ''; | ||
if (selectorPattern.test(rule)) { | ||
styles += rule; | ||
} | ||
}); | ||
return styles; | ||
} | ||
function getStyleElements() { | ||
var elements = Array.from(document.querySelectorAll('style[data-emotion]')); // $FlowFixMe | ||
return elements; | ||
} | ||
var unique = function unique(arr) { | ||
return Array.from(new Set(arr)); | ||
}; | ||
function getKeys(elements) { | ||
var keys = unique(elements.map(function (element) { | ||
return (// $FlowFixMe we know it exists since we query for elements with this attribute | ||
element.getAttribute('data-emotion') | ||
); | ||
})).filter(Boolean); | ||
return keys; | ||
} | ||
/* | ||
* Taken from | ||
* https://github.com/facebook/jest/blob/be4bec387d90ac8d6a7596be88bf8e4994bc3ed9/packages/expect/src/jasmine_utils.js#L234 | ||
*/ | ||
function isA(typeName, value) { | ||
return Object.prototype.toString.apply(value) === "[object " + typeName + "]"; | ||
} | ||
/* | ||
* Taken from | ||
* https://github.com/facebook/jest/blob/be4bec387d90ac8d6a7596be88bf8e4994bc3ed9/packages/expect/src/jasmine_utils.js#L36 | ||
*/ | ||
function isAsymmetric(obj) { | ||
return obj && isA('Function', obj.asymmetricMatch); | ||
} | ||
function valueMatches(declaration, value) { | ||
if (value instanceof RegExp) { | ||
return value.test(declaration.value); | ||
} | ||
if (isAsymmetric(value)) { | ||
return value.asymmetricMatch(declaration.value); | ||
} | ||
return value === declaration.value; | ||
} | ||
function toHaveStyleRule(received, property, value) { | ||
var classNames = getClassNamesFromNodes([received]); | ||
var cssString = getStylesFromClassNames(classNames, getStyleElements()); | ||
var styles = css.parse(cssString); | ||
var declaration = styles.stylesheet.rules.reduce(function (decs, rule) { | ||
return Object.assign([], decs, rule.declarations); | ||
}, []).filter(function (dec) { | ||
return dec.type === 'declaration' && dec.property === property; | ||
}).pop(); | ||
if (!declaration) { | ||
return { | ||
pass: false, | ||
message: function message() { | ||
return "Property not found: " + property; | ||
} | ||
}; | ||
} | ||
var pass = valueMatches(declaration, value); | ||
var message = function message() { | ||
return "Expected " + property + (pass ? ' not ' : ' ') + "to match:\n" + (" " + chalk.green(value) + "\n") + 'Received:\n' + (" " + chalk.red(declaration.value)); | ||
}; | ||
return { | ||
pass: pass, | ||
message: message | ||
}; | ||
} | ||
var matchers = { | ||
toHaveStyleRule: toHaveStyleRule | ||
}; | ||
function getNodes(node, nodes) { | ||
@@ -11,12 +214,20 @@ if (nodes === void 0) { | ||
if (node.children) { | ||
node.children.forEach(function (child) { | ||
return getNodes(child, nodes); | ||
}); | ||
for (var _iterator = node.children, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { | ||
var _ref; | ||
if (_isArray) { | ||
if (_i >= _iterator.length) break; | ||
_ref = _iterator[_i++]; | ||
} else { | ||
_i = _iterator.next(); | ||
if (_i.done) break; | ||
_ref = _i.value; | ||
} | ||
var child = _ref; | ||
getNodes(child, nodes); | ||
} | ||
} | ||
if (Array.isArray(node)) { | ||
node.forEach(function (child) { | ||
return getNodes(child, nodes); | ||
}); | ||
} else if (typeof node === 'object') { | ||
if (typeof node === 'object') { | ||
nodes.push(node); | ||
@@ -30,53 +241,41 @@ } | ||
nodes.forEach(function (node) { | ||
node.withNewStyles = true; | ||
node.withEmotionNextStyles = true; | ||
}); | ||
} // i know this looks hacky but it works pretty well | ||
// and most importantly it doesn't mutate the object that gets passed in | ||
} | ||
function getPrettyStylesFromClassNames(classNames, elements) { | ||
var styles = getStylesFromClassNames(classNames, elements); | ||
var prettyStyles; | ||
var re = /<style\n(\s+)dangerouslySetInnerHTML={\s+Object {\s+"__html": "(.*?)",\n\s+}\s+}\s+data-emotion-([\w-]+)="[^"]+"\s+\/>/g; | ||
var serializer = { | ||
test: function test(val) { | ||
if (!val) { | ||
return false; | ||
} | ||
try { | ||
prettyStyles = css.stringify(css.parse(styles)); | ||
} catch (e) { | ||
console.error(e); | ||
throw new Error("There was an error parsing the following css: \"" + styles + "\""); | ||
} | ||
if (!val.withNewStyles && val.$$typeof === Symbol.for('react.test.json')) { | ||
return true; | ||
} else if (Array.isArray(val) && val[0] && !val[0].withNewStyles && val[0].$$typeof === Symbol.for('react.test.json')) { | ||
return true; | ||
} | ||
return prettyStyles; | ||
} | ||
return false; | ||
}, | ||
print: function print(val, printer) { | ||
var nodes = getNodes(val); | ||
markNodes(nodes); | ||
var i = 0; | ||
var classMap = {}; | ||
var printed = printer(val).replace(re, function (match, whiteSpace, cssString, key) { | ||
return "<style>\n" + whiteSpace + css.stringify(css.parse( // for some reason the quotes seem to be escaped and that breaks the formatting | ||
cssString.replace(/\\"/g, '"').replace(/\\'/g, "'"))).replace(new RegExp(key + "-([a-zA-Z0-9-]+)", 'g'), function (match) { | ||
if (classMap[match] !== undefined) { | ||
return classMap[match]; | ||
} | ||
return classMap[match] = "emotion-" + i++; | ||
}).split('\n').join('\n' + whiteSpace) + "\n" + whiteSpace.substring(2) + "</style>"; | ||
}); | ||
Object.keys(classMap).forEach(function (key) { | ||
printed = printed.replace(new RegExp(key, 'g'), classMap[key]); | ||
}); | ||
return printed; | ||
} // clsPattern, | ||
// (match, p1) => { | ||
// if (classMap[p1] !== undefined) { | ||
// return classMap[p1] | ||
// } | ||
// return match | ||
// } | ||
function print(val, printer) { | ||
var nodes = getNodes(val); | ||
markNodes(nodes); | ||
var classNames = getClassNamesFromNodes(nodes); | ||
var elements = getStyleElements(); | ||
var styles = getPrettyStylesFromClassNames(classNames, elements); | ||
var printedVal = printer(val); | ||
var keys = getKeys(elements); | ||
return replaceClassNames(classNames, styles, printedVal, keys); | ||
} | ||
function test(val) { | ||
return val && !val.withEmotionNextStyles && isReactElement(val) || isDOMElement(val); | ||
} | ||
var index = { | ||
print: print, | ||
test: test | ||
}; | ||
module.exports = serializer; | ||
//# sourceMappingURL=snapshot-serializer.cjs.js.map | ||
exports.print = print; | ||
exports.test = test; | ||
exports.default = index; | ||
exports.matchers = matchers; |
{ | ||
"name": "@emotion/snapshot-serializer", | ||
"version": "0.7.2", | ||
"version": "0.8.0", | ||
"description": "A snapshot serializer for jest and emotion", | ||
"main": "dist/snapshot-serializer.cjs.js", | ||
"module": "dist/snapshot-serializer.esm.js", | ||
"license": "MIT", | ||
"repository": "https://github.com/emotion-js/emotion/tree/master/next-packages/snapshot-serializer", | ||
"dependencies": { | ||
"chalk": "^2.4.1", | ||
"css": "^2.2.1" | ||
@@ -11,0 +11,0 @@ }, |
112
src/index.js
// @flow | ||
import { parse, stringify } from 'css' | ||
import * as css from 'css' | ||
import { replaceClassNames } from './replace-class-names' | ||
import { | ||
getClassNamesFromNodes, | ||
isReactElement, | ||
isDOMElement, | ||
getStylesFromClassNames, | ||
getStyleElements, | ||
getKeys | ||
} from './utils' | ||
// i know this package is called @emotion/snapshot-serializer and this is exporting a matcher but this is probably going to move to jest-emotion later | ||
export { matchers } from './matchers' | ||
function getNodes(node, nodes = []) { | ||
if (node.children) { | ||
node.children.forEach(child => getNodes(child, nodes)) | ||
for (let child of node.children) { | ||
getNodes(child, nodes) | ||
} | ||
} | ||
if (Array.isArray(node)) { | ||
node.forEach(child => getNodes(child, nodes)) | ||
} else if (typeof node === 'object') { | ||
if (typeof node === 'object') { | ||
nodes.push(node) | ||
@@ -19,67 +32,40 @@ } | ||
nodes.forEach(node => { | ||
node.withNewStyles = true | ||
node.withEmotionNextStyles = true | ||
}) | ||
} | ||
// i know this looks hacky but it works pretty well | ||
// and most importantly it doesn't mutate the object that gets passed in | ||
let re = /<style\n(\s+)dangerouslySetInnerHTML={\s+Object {\s+"__html": "(.*?)",\n\s+}\s+}\s+data-emotion-([\w-]+)="[^"]+"\s+\/>/g | ||
function getPrettyStylesFromClassNames( | ||
classNames: Array<string>, | ||
elements: Array<HTMLStyleElement> | ||
) { | ||
let styles = getStylesFromClassNames(classNames, elements) | ||
const serializer = { | ||
test: (val: any) => { | ||
if (!val) { | ||
return false | ||
} | ||
if (!val.withNewStyles && val.$$typeof === Symbol.for('react.test.json')) { | ||
return true | ||
} else if ( | ||
Array.isArray(val) && | ||
val[0] && | ||
!val[0].withNewStyles && | ||
val[0].$$typeof === Symbol.for('react.test.json') | ||
) { | ||
return true | ||
} | ||
return false | ||
}, | ||
print: (val: any, printer: any => string) => { | ||
const nodes = getNodes(val) | ||
markNodes(nodes) | ||
let i = 0 | ||
const classMap = {} | ||
let printed = printer(val).replace( | ||
re, | ||
(match, whiteSpace, cssString, key) => { | ||
return `<style>\n${whiteSpace}${stringify( | ||
parse( | ||
// for some reason the quotes seem to be escaped and that breaks the formatting | ||
cssString.replace(/\\"/g, '"').replace(/\\'/g, "'") | ||
) | ||
) | ||
.replace(new RegExp(`${key}-([a-zA-Z0-9-]+)`, 'g'), match => { | ||
if (classMap[match] !== undefined) { | ||
return classMap[match] | ||
} | ||
return (classMap[match] = `emotion-${i++}`) | ||
}) | ||
.split('\n') | ||
.join('\n' + whiteSpace)}\n${whiteSpace.substring(2)}</style>` | ||
} | ||
) | ||
Object.keys(classMap).forEach(key => { | ||
printed = printed.replace(new RegExp(key, 'g'), classMap[key]) | ||
}) | ||
return printed | ||
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 | ||
} | ||
// clsPattern, | ||
// (match, p1) => { | ||
// if (classMap[p1] !== undefined) { | ||
// return classMap[p1] | ||
// } | ||
// return match | ||
// } | ||
export function print(val: *, printer: Function) { | ||
const nodes = getNodes(val) | ||
markNodes(nodes) | ||
const classNames = getClassNamesFromNodes(nodes) | ||
let elements = getStyleElements() | ||
const styles = getPrettyStylesFromClassNames(classNames, elements) | ||
const printedVal = printer(val) | ||
let keys = getKeys(elements) | ||
return replaceClassNames(classNames, styles, printedVal, keys) | ||
} | ||
export default serializer | ||
export function test(val: *) { | ||
return ( | ||
(val && !val.withEmotionNextStyles && isReactElement(val)) || | ||
isDOMElement(val) | ||
) | ||
} | ||
export default { print, test } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
472
16885
2
8
+ Addedchalk@^2.4.1
+ Addedansi-styles@3.2.1(transitive)
+ Addedchalk@2.4.2(transitive)
+ Addedcolor-convert@1.9.3(transitive)
+ Addedcolor-name@1.1.3(transitive)
+ Addedescape-string-regexp@1.0.5(transitive)
+ Addedhas-flag@3.0.0(transitive)
+ Addedsupports-color@5.5.0(transitive)