Created
April 6, 2018 16:34
-
-
Save rszewczyk/4375363cddff6636262d76d8a073b860 to your computer and use it in GitHub Desktop.
Emotion jest snapshot serializer for DOM elements
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// proof of concept for jest-emotion style snapshots for DOM Elements | |
import * as css from 'css' | |
function defaultClassNameReplacer(className, index) { | |
return `emotion-${index}` | |
} | |
const componentSelectorClassNamePattern = /\.e[a-zA-Z0-9-]+[0-9]+/ | |
export const replaceClassNames = ( | |
selectors, | |
styles, | |
code, | |
key, | |
replacer = defaultClassNameReplacer | |
) => { | |
let index = 0 | |
const classRegex = new RegExp(`^\\.${key}-([a-zA-Z0-9-]+)`) | |
return selectors.reduce((acc, className) => { | |
if ( | |
classRegex.test(className) || | |
componentSelectorClassNamePattern.test(className) | |
) { | |
const escapedRegex = new RegExp( | |
className.replace('.', '').replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'), | |
'g' | |
) | |
return acc.replace(escapedRegex, replacer(className, index++)) | |
} | |
return acc | |
}, `${styles}${styles ? '\n\n' : ''}${code}`) | |
} | |
function getNodes(node, nodes = []) { | |
if (node.children) { | |
for (let child of node.children) { | |
getNodes(child, nodes) | |
} | |
} | |
nodes.push(node) | |
return nodes | |
} | |
function markNodes(nodes) { | |
nodes.forEach(node => { | |
node.withEmotionStyles = true | |
}) | |
} | |
function getSelectors(nodes) { | |
return nodes.reduce((selectors, node) => { | |
const classes = node.getAttribute('class') | |
return classes | |
? selectors.concat(classes.split(' ').map(c => `.${c}`)) | |
: selectors | |
}, []) | |
} | |
const ELEMENT_NODE = 1 | |
const ELEMENT_REGEXP = /^((HTML|SVG)\w*)?Element$/ | |
function testNode(nodeType, name) { | |
return nodeType === ELEMENT_NODE && ELEMENT_REGEXP.test(name) | |
} | |
function test(val) { | |
return ( | |
val && | |
!val.withEmotionStyles && | |
val.constructor && | |
val.constructor.name && | |
testNode(val.nodeType, val.constructor.name) | |
) | |
} | |
export function getStyles(emotion) { | |
return Object.keys(emotion.caches.inserted).reduce((style, current) => { | |
if (emotion.caches.inserted[current] === true) { | |
return style | |
} | |
return style + emotion.caches.inserted[current] | |
}, '') | |
} | |
function filterChildSelector(baseSelector) { | |
if (baseSelector.slice(-1) === '>') { | |
return baseSelector.slice(0, -1) | |
} | |
return baseSelector | |
} | |
function getStylesFromSelectors(nodeSelectors, emotion) { | |
const styles = getStyles(emotion) | |
let ast | |
try { | |
ast = css.parse(styles) | |
} catch (e) { | |
console.error(e) | |
throw new Error( | |
`There was an error parsing css in jest-emotion-react: "${styles}"` | |
) | |
} | |
ast.stylesheet.rules = ast.stylesheet.rules.reduce(reduceRules, []) | |
const ret = css.stringify(ast) | |
return ret | |
function reduceRules(rules, rule) { | |
let shouldIncludeRule = false | |
if (rule.type === 'rule') { | |
shouldIncludeRule = rule.selectors.some(selector => { | |
const baseSelector = filterChildSelector( | |
selector.split(/:| |\./).filter(s => !!s)[0] | |
) | |
return nodeSelectors.some( | |
sel => sel === baseSelector || sel === `.${baseSelector}` | |
) | |
}) | |
} | |
if (rule.type === 'media' || rule.type === 'supports') { | |
rule.rules = rule.rules.reduce(reduceRules, []) | |
if (rule.rules.length) { | |
shouldIncludeRule = true | |
} | |
} | |
return shouldIncludeRule ? rules.concat(rule) : rules | |
} | |
} | |
export function createSerializer(emotion, { classNameReplacer } = {}) { | |
function print(val, printer) { | |
const nodes = getNodes(val) | |
markNodes(nodes) | |
const selectors = getSelectors(nodes) | |
const styles = getStylesFromSelectors(selectors, emotion) | |
const printedVal = printer(val) | |
return replaceClassNames( | |
selectors, | |
styles, | |
printedVal, | |
emotion.caches.key, | |
classNameReplacer | |
) | |
} | |
return { print, test } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment