diff --git a/.gitignore b/.gitignore index 93cab34..c31c571 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +.vscode node_modules yarn-error.log diff --git a/src/styleSheetSerializer.js b/src/styleSheetSerializer.js index 09263fd..d0d0ee9 100644 --- a/src/styleSheetSerializer.js +++ b/src/styleSheetSerializer.js @@ -62,22 +62,19 @@ const filterRules = (classNames) => (rule) => includesClassNames(classNames, rule.selectors) && rule.declarations.length; -const getAtRules = (ast, filter) => - ast.stylesheet.rules - .filter((rule) => rule.type === 'media' || rule.type === 'supports') - .reduce((acc, atRule) => { - atRule.rules = atRule.rules.filter(filter); - - return acc.concat(atRule); - }, []); +const getAllRules = (rules, classNames) => rules + .filter( + (rule) => rule.type === 'media' + || rule.type === 'supports' + || filterRules(classNames)(rule) + ) + .map(rule => (rule.type === "rule" ? rule : Object.assign({}, rule, { rules: getAllRules(rule.rules, classNames) }))) + .filter(rule => (rule.type === "rule" && rule.declarations.length) || rule.rules.length); const getStyle = (classNames) => { const ast = getCSS(); - const filter = filterRules(classNames); - const rules = ast.stylesheet.rules.filter(filter); - const atRules = getAtRules(ast, filter); - ast.stylesheet.rules = rules.concat(atRules); + ast.stylesheet.rules = getAllRules(ast.stylesheet.rules, classNames); return css.stringify(ast); }; diff --git a/src/toHaveStyleRule.js b/src/toHaveStyleRule.js index 929853c..d1187fe 100644 --- a/src/toHaveStyleRule.js +++ b/src/toHaveStyleRule.js @@ -36,17 +36,6 @@ const getClassNames = (received) => { const hasAtRule = (options) => Object.keys(options).some((option) => ['media', 'supports'].includes(option)); -const getAtRules = (ast, options) => { - return Object.keys(options) - .map((option) => - ast.stylesheet.rules - .filter((rule) => rule.type === option && rule[option] === options[option].replace(/:\s/g, ":")) - .map((rule) => rule.rules) - .reduce((acc, rules) => acc.concat(rules), []) - ) - .reduce((acc, rules) => acc.concat(rules), []); -}; - const normalizeQuotations = (input) => input.replace(/['"]/g, '"'); const getModifiedClassName = (className, staticClassName, modifier = '') => { @@ -82,14 +71,20 @@ const hasClassNames = (classNames, selectors, options) => { ); }; -const getRules = (ast, classNames, options) => { - const rules = (hasAtRule(options) ? getAtRules(ast, options) : ast.stylesheet.rules).map((rule) => ({ +const getRules = (rules, classNames, options) => + rules.map((rule) => ({ ...rule, selectors: Array.isArray(rule.selectors) ? rule.selectors.map(normalizeQuotations) : rule.selectors, - })); + })) + .flatMap((rule) => { + if (!hasAtRule(options)) { + return rule.type === 'rule' && hasClassNames(classNames, rule.selectors, options) ? [rule] : []; + } - return rules.filter((rule) => rule.type === 'rule' && hasClassNames(classNames, rule.selectors, options)); -}; + return ['media', 'supports'].includes(rule.type) && options[rule.type] && rule[rule.type] === options[rule.type].replace(/:\s/g, ':') + ? getRules(rule.rules, classNames, Object.fromEntries(Object.entries(options).filter(([key]) => key !== rule.type))) + : []; + }); const handleMissingRules = (options) => ({ pass: false, @@ -116,7 +111,7 @@ function toHaveStyleRule(component, property, expected, options = {}) { const classNames = getClassNames(component); const ast = getCSS(); const normalizedOptions = normalizeOptions(options); - const rules = getRules(ast, classNames, normalizedOptions); + const rules = getRules(ast.stylesheet.rules, classNames, normalizedOptions); if (!rules.length) { return handleMissingRules(normalizedOptions); diff --git a/test/__snapshots__/styleSheetSerializer.spec.js.snap b/test/__snapshots__/styleSheetSerializer.spec.js.snap index 7b2ea31..e60e2b9 100644 --- a/test/__snapshots__/styleSheetSerializer.spec.js.snap +++ b/test/__snapshots__/styleSheetSerializer.spec.js.snap @@ -838,15 +838,6 @@ exports[`supported css: mount 1`] = ` background: palevioletred; } -.c1 > p { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -html.test .c0 { - display: none; -} - @media (max-width:600px) { .c1 { background: tomato; @@ -855,6 +846,21 @@ html.test .c0 { .c1:hover { background: yellow; } + +@supports (top:max(1px,0px)) { + .c1 { + padding-left: max(1em,env(safe-area-inset-left,0px)); + } +} +} + +.c1 > p { + -webkit-text-decoration: underline; + text-decoration: underline; +} + +html.test .c0 { + display: none; } @@ -878,15 +884,6 @@ exports[`supported css: react-test-renderer 1`] = ` background: palevioletred; } -.c1 > p { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -html.test .c0 { - display: none; -} - @media (max-width:600px) { .c1 { background: tomato; @@ -895,6 +892,21 @@ html.test .c0 { .c1:hover { background: yellow; } + +@supports (top:max(1px,0px)) { + .c1 { + padding-left: max(1em,env(safe-area-inset-left,0px)); + } +} +} + +.c1 > p { + -webkit-text-decoration: underline; + text-decoration: underline; +} + +html.test .c0 { + display: none; }
p { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -html.test .c0 { - display: none; -} - @media (max-width:600px) { .c1 { background: tomato; @@ -933,6 +936,21 @@ html.test .c0 { .c1:hover { background: yellow; } + +@supports (top:max(1px,0px)) { + .c1 { + padding-left: max(1em,env(safe-area-inset-left,0px)); + } +} +} + +.c1 > p { + -webkit-text-decoration: underline; + text-decoration: underline; +} + +html.test .c0 { + display: none; }
p { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -html.test .c0 { - display: none; -} - @media (max-width:600px) { .c1 { background: tomato; @@ -971,6 +980,21 @@ html.test .c0 { .c1:hover { background: yellow; } + +@supports (top:max(1px,0px)) { + .c1 { + padding-left: max(1em,env(safe-area-inset-left,0px)); + } +} +} + +.c1 > p { + -webkit-text-decoration: underline; + text-decoration: underline; +} + +html.test .c0 { + display: none; }
{ &:hover { background: yellow; } + + @supports (top: max(1px, 0px)) { + padding-left: max(1em, env(safe-area-inset-left, 0px)); + } } > p { diff --git a/test/toHaveStyleRule.spec.js b/test/toHaveStyleRule.spec.js index 9042690..f2108b9 100644 --- a/test/toHaveStyleRule.spec.js +++ b/test/toHaveStyleRule.spec.js @@ -252,6 +252,42 @@ it('at rules', () => { }); }); +it('nested at rules', () => { + const Container = styled.div` + @media (min-width: 320px) { + top: 0px; + @supports (top:env(safe-area-inset-top,0px)) { + top: env(safe-area-inset-top,0px); + } + } + + @supports (bottom:env(safe-area-inset-bottom,0px)) { + bottom: 0px; + @media (min-width: 320px) { + bottom: env(safe-area-inset-bottom,0px); + } + } + `; + + toHaveStyleRule(, "top", "0px", { + media: "(min-width: 320px)", + }); + + toHaveStyleRule(, "top", "env(safe-area-inset-top,0px)", { + media: "(min-width: 320px)", + supports: "(top:env(safe-area-inset-top,0px))", + }); + + toHaveStyleRule(, "bottom", "0px", { + supports: "(bottom:env(safe-area-inset-bottom,0px))", + }); + + toHaveStyleRule(, "bottom", "env(safe-area-inset-bottom,0px)", { + media: "(min-width: 320px)", + supports: "(bottom:env(safe-area-inset-bottom,0px))", + }); +}); + it('selector modifiers', () => { const Link = styled.a` color: white;