diff --git a/__tests__/responsiveAtRule.test.js b/__tests__/responsiveAtRule.test.js index 4b1d4509e..42babf4bf 100644 --- a/__tests__/responsiveAtRule.test.js +++ b/__tests__/responsiveAtRule.test.js @@ -6,114 +6,6 @@ function run(input, opts = config) { return postcss([plugin(opts)]).process(input, { from: undefined }) } -test('it can generate responsive variants for nested at rules', () => { - const input = ` - @responsive { - .banana { color: yellow; } - .chocolate { color: brown; } - - @supports(display: grid) { - .grid\\:banana { color: blue; } - .grid\\:chocolate { color: green; } - } - } - ` - - const output = ` - .banana { - color: yellow; - } - - .chocolate { - color: brown; - } - - @supports(display: grid) { - .grid\\:banana { - color: blue; - } - - .grid\\:chocolate { - color: green; - } - } - - @media (min-width: 500px) { - .sm\\:banana { - color: yellow; - } - - .sm\\:chocolate { - color: brown; - } - - @supports(display: grid) { - .sm\\:grid\\:banana { - color: blue; - } - - .sm\\:grid\\:chocolate { - color: green; - } - } - } - - @media (min-width: 750px) { - .md\\:banana { - color: yellow; - } - - .md\\:chocolate { - color: brown; - } - - @supports(display: grid) { - .md\\:grid\\:banana { - color: blue; - } - - .md\\:grid\\:chocolate { - color: green; - } - } - } - - @media (min-width: 1000px) { - .lg\\:banana { - color: yellow; - } - - .lg\\:chocolate { - color: brown; - } - - @supports(display: grid) { - .lg\\:grid\\:banana { - color: blue; - } - - .lg\\:grid\\:chocolate { - color: green; - } - } - } - ` - - return run(input, { - screens: { - sm: '500px', - md: '750px', - lg: '1000px', - }, - options: { - separator: ':', - }, - }).then(result => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) -}) - test('it can generate responsive variants', () => { const input = ` @responsive { @@ -280,6 +172,136 @@ test('responsive variants are grouped', () => { }) }) +test('it can generate responsive variants for nested at-rules', () => { + const input = ` + @responsive { + .banana { color: yellow; } + + @supports(display: grid) { + .grid\\:banana { color: blue; } + } + } + ` + + const output = ` + .banana { + color: yellow; + } + + @supports(display: grid) { + .grid\\:banana { + color: blue; + } + } + + @media (min-width: 500px) { + .sm\\:banana { + color: yellow; + } + + @supports(display: grid) { + .sm\\:grid\\:banana { + color: blue; + } + } + } + + @media (min-width: 1000px) { + .lg\\:banana { + color: yellow; + } + + @supports(display: grid) { + .lg\\:grid\\:banana { + color: blue; + } + } + } + ` + + return run(input, { + screens: { + sm: '500px', + lg: '1000px', + }, + options: { + separator: ':', + }, + }).then(result => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('it can generate responsive variants for deeply nested at-rules', () => { + const input = ` + @responsive { + .banana { color: yellow; } + + @supports(display: grid) { + @supports(display: flex) { + .flex-grid\\:banana { color: blue; } + } + } + } + ` + + const output = ` + .banana { + color: yellow; + } + + @supports(display: grid) { + @supports(display: flex) { + .flex-grid\\:banana { + color: blue; + } + } + } + + @media (min-width: 500px) { + .sm\\:banana { + color: yellow; + } + + @supports(display: grid) { + @supports(display: flex) { + .sm\\:flex-grid\\:banana { + color: blue; + } + } + } + } + + @media (min-width: 1000px) { + .lg\\:banana { + color: yellow; + } + + @supports(display: grid) { + @supports(display: flex) { + .lg\\:flex-grid\\:banana { + color: blue; + } + } + } + } + ` + + return run(input, { + screens: { + sm: '500px', + lg: '1000px', + }, + options: { + separator: ':', + }, + }).then(result => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + test('screen prefix is only applied to the last class in a selector', () => { const input = ` @responsive { diff --git a/src/lib/substituteResponsiveAtRules.js b/src/lib/substituteResponsiveAtRules.js index ca04fa92a..71c352410 100644 --- a/src/lib/substituteResponsiveAtRules.js +++ b/src/lib/substituteResponsiveAtRules.js @@ -8,12 +8,12 @@ export default function(config) { return function(css) { const screens = config.screens const separator = config.options.separator - const responsiveRules = [] + const responsiveRules = postcss.root() let finalRules = [] css.walkAtRules('responsive', atRule => { const nodes = atRule.nodes - responsiveRules.push(...cloneNodes(nodes)) + responsiveRules.append(...cloneNodes(nodes)) atRule.before(nodes) atRule.remove() }) @@ -24,34 +24,15 @@ export default function(config) { params: buildMediaQuery(screens[screen]), }) - mediaQuery.append( - // Filter out nested `atRules`; we'll process those separately - responsiveRules.filter(rule => rule.type !== 'atrule').map(rule => { - const cloned = rule.clone() - cloned.selectors = _.map(rule.selectors, selector => + mediaQuery.append(_.tap(responsiveRules.clone(), clonedRoot => { + clonedRoot.walkRules(rule => { + rule.selectors = _.map(rule.selectors, selector => buildSelectorVariant(selector, screen, separator, message => { throw rule.error(message) }) ) - return cloned }) - ) - - mediaQuery.append( - // Process nested `atRules`. - responsiveRules.filter(rule => rule.type === 'atrule').map(atRule => { - const clonedAtRule = atRule.clone() - clonedAtRule.nodes.forEach(rule => { - rule.selectors = _.map(rule.selectors, selector => { - const selectorVariant = buildSelectorVariant(selector, screen, separator, message => { - throw rule.error(message) - }) - return selectorVariant - }) - }) - return clonedAtRule - }) - ) + })) finalRules.push(mediaQuery) })