From 10be05a16715e88e42fdb03b34e744bd05ed5702 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Tue, 6 Mar 2018 14:08:30 -0500 Subject: [PATCH] Get object style parsing working without the insanity --- __tests__/parseObjectStyles.test.js | 214 +++++++++++++++++++++++----- src/util/parseObjectStyles.js | 68 +-------- 2 files changed, 184 insertions(+), 98 deletions(-) diff --git a/__tests__/parseObjectStyles.test.js b/__tests__/parseObjectStyles.test.js index 5555d9dfc..39e88bfc6 100644 --- a/__tests__/parseObjectStyles.test.js +++ b/__tests__/parseObjectStyles.test.js @@ -1,6 +1,4 @@ -import _ from 'lodash' import parseObjectStyles from '../src/util/parseObjectStyles' -import cxs from 'cxs' import postcss from 'postcss' function css(nodes) { @@ -16,7 +14,7 @@ test('it parses simple single class definitions', () => { }, }) - expect(result).toMatchCss(` + expect(css(result)).toMatchCss(` .foobar { background-color: red; color: white; @@ -38,7 +36,7 @@ test('it parses multiple class definitions', () => { }, }) - expect(result).toMatchCss(` + expect(css(result)).toMatchCss(` .foo { background-color: red; color: white; @@ -66,17 +64,35 @@ test('it parses nested pseudo-selectors', () => { }, }) - expect(result).toMatchCss(` + expect(css(result)).toMatchCss(` .foo { background-color: red; color: white; - padding: 1rem + padding: 1rem; } .foo:hover { - background-color: orange + background-color: orange; } .foo:focus { - background-color: blue + background-color: blue; + } + `) +}) + +test('it parses top-level media queries', () => { + const result = parseObjectStyles({ + '@media (min-width: 200px)': { + '.foo': { + backgroundColor: 'orange', + }, + }, + }) + + expect(css(result)).toMatchCss(` + @media (min-width: 200px) { + .foo { + background-color: orange + } } `) }) @@ -93,44 +109,168 @@ test('it parses nested media queries', () => { }, }) - expect(result).toMatchCss(` + expect(css(result)).toMatchCss(` .foo { background-color: red; color: white; - padding: 1rem + padding: 1rem; } @media (min-width: 200px) { .foo { - background-color: orange + background-color: orange; } } `) }) -// test('it parses pseudo-selectors in nested media queries', () => { -// const result = parseObjectStyles({ -// '.foo': { -// backgroundColor: 'red', -// color: 'white', -// padding: '1rem', -// ':hover': { -// '@media (min-width: 200px)': { -// backgroundColor: 'orange', -// } -// }, -// }, -// }) +test('it parses pseudo-selectors in nested media queries', () => { + const result = parseObjectStyles({ + '.foo': { + backgroundColor: 'red', + color: 'white', + padding: '1rem', + ':hover': { + '@media (min-width: 200px)': { + backgroundColor: 'orange', + }, + }, + }, + }) -// expect(result).toMatchCss(` -// .foo { -// background-color: red; -// color: white; -// padding: 1rem -// } -// @media (min-width: 200px) { -// .foo:hover { -// background-color: orange -// } -// } -// `) -// }) + expect(css(result)).toMatchCss(` + .foo { + background-color: red; + color: white; + padding: 1rem; + } + @media (min-width: 200px) { + .foo:hover { + background-color: orange; + } + } + `) +}) + +test('it parses descendant selectors', () => { + const result = parseObjectStyles({ + '.foo': { + backgroundColor: 'red', + color: 'white', + padding: '1rem', + '.bar': { + backgroundColor: 'orange', + }, + }, + }) + + expect(css(result)).toMatchCss(` + .foo { + background-color: red; + color: white; + padding: 1rem; + } + .foo .bar { + background-color: orange; + } + `) +}) + +test('it parses nested multi-class selectors', () => { + const result = parseObjectStyles({ + '.foo': { + backgroundColor: 'red', + color: 'white', + padding: '1rem', + '&.bar': { + backgroundColor: 'orange', + }, + }, + }) + + expect(css(result)).toMatchCss(` + .foo { + background-color: red; + color: white; + padding: 1rem; + } + .foo.bar { + background-color: orange; + } + `) +}) + +test('it parses nested multi-class selectors in media queries', () => { + const result = parseObjectStyles({ + '.foo': { + backgroundColor: 'red', + color: 'white', + padding: '1rem', + '@media (min-width: 200px)': { + '&.bar': { + backgroundColor: 'orange', + }, + }, + }, + }) + + expect(css(result)).toMatchCss(` + .foo { + background-color: red; + color: white; + padding: 1rem; + } + @media (min-width: 200px) { + .foo.bar { + background-color: orange; + } + } + `) +}) + +test('it strips empty selectors when nesting', () => { + const result = parseObjectStyles({ + '.foo': { + '.bar': { + backgroundColor: 'orange', + }, + }, + }) + + expect(css(result)).toMatchCss(` + .foo .bar { + background-color: orange + } + `) +}) + +test('it can parse an array of styles', () => { + const result = parseObjectStyles([ + { + '.foo': { + backgroundColor: 'orange', + }, + }, + { + '.bar': { + backgroundColor: 'red', + }, + }, + { + '.foo': { + backgroundColor: 'blue', + }, + }, + ]) + + expect(css(result)).toMatchCss(` + .foo { + background-color: orange + } + .bar { + background-color: red + } + .foo { + background-color: blue + } + `) +}) diff --git a/src/util/parseObjectStyles.js b/src/util/parseObjectStyles.js index 9fd9a3755..8ecff5cb0 100644 --- a/src/util/parseObjectStyles.js +++ b/src/util/parseObjectStyles.js @@ -1,69 +1,15 @@ import _ from 'lodash' import postcss from 'postcss' +import postcssNested from 'postcss-nested' +import postcssJs from 'postcss-js' -const hyph = s => s.replace(/[A-Z]|^ms/g, '-$&').toLowerCase() -const mx = (rule, media) => media ? `${media}{${rule}}` : rule -const noAnd = s => s.replace(/&/g, '') -const createDeclaration = (key, value) => hyph(key) + ':' + value -const createRule = ({ - className, - child, - media, - declarations -}) => mx(`${className + child}{${declarations.join(';')}}`, media) - -const parseRules = (obj, child = '', media) => { - const rules = [] - const declarations = [] - - for (let key in obj) { - const value = obj[key] - - if (value === null) continue - - if (typeof value === 'object') { - const _media = /^@/.test(key) ? key : null - const _child = _media ? child : child + noAnd(key) - parseRules(value, _child, _media) - .forEach(r => rules.push(r)) - continue - } - - const dec = createDeclaration(key, value) - declarations.push(dec) - } - - rules.unshift({ - media, - child, - declarations - }) - - return rules -} - -const parse = (selector, obj) => { - const rules = parseRules(obj) - - console.log(rules) - - return rules.map(rule => { - const className = selector - const ruleset = createRule(Object.assign(rule, { className })) - return ruleset - }) -} - -function parseObjectStyles(styles) { +export default function parseObjectStyles(styles) { if (!Array.isArray(styles)) { return parseObjectStyles([styles]) } - return _.flatMap(styles, (style) => { - return _.flatMap(style, (declarations, selector) => { - return parse(selector, declarations) - }) - }).join('') + return _.flatMap( + styles, + style => postcss([postcssNested]).process(style, { parser: postcssJs }).root.nodes + ) } - -export default parseObjectStyles