diff --git a/src/objectify.js b/src/objectify.js index d352b1a..e0bd813 100644 --- a/src/objectify.js +++ b/src/objectify.js @@ -3,6 +3,25 @@ import fs from 'fs'; import { parse } from 'css'; +import getType, { SelectorType } from './core/selector-type'; + +/** + * Converts an external CSS stylesheet to a JavaScript stylesheet object + * + * @author Alan Smithee + * @param {string} path path to the external stylesheet to convert + * @return {object} CSSObject + */ +export default function objectify(path) { + const data = fs.readFileSync(path, 'utf8'); + + if (!data || !parse(data).stylesheet) { + return {}; + } + + return toObject(parse(data).stylesheet); +} + /** * Converts a stylesheet AST to a JavaScript stylesheet object * @@ -21,44 +40,26 @@ export function toObject(stylesheet) { for (let rule of stylesheet.rules) { for (let selector of rule.selectors) { - console.log(selector); - - // todo resolve selector type (regex) - WIP in /core/selector-type.js - - let obj = style[selector] || {}; - + let obj = {}; + for (let declaration of rule.declarations) { - if (obj[declaration.property] && obj[declaration.property].indexOf('!important') !== -1) { - if (declaration.value.indexOf('!important') !== -1) { - obj[declaration.property] = declaration.value; - } - - continue; - } - obj[declaration.property] = declaration.value; } - style[selector] = obj; + // todo refactor and generalize for all psuedoselector + if (getType(selector) === SelectorType.TypeClass) { + const [ topSelector, subSelector ] = selector.split(/(\.[a-zA-Z]+)/g); + + if (!style[topSelector]) { + style[topSelector] = { }; + } + + style[topSelector][subSelector] = Object.assign(style[topSelector][subSelector] || {}, obj); + } else { + style[selector] = Object.assign(style[selector] || {}, obj); + } } } return style; -} - -/** - * Converts an external CSS stylesheet to a JavaScript stylesheet object - * - * @author Alan Smithee - * @param {string} path path to the external stylesheet to convert - * @return {object} CSSObject - */ -export default function objectify(path) { - const data = fs.readFileSync(path, 'utf8'); - - if (!data) { - return {}; - } - - return toObject(parse(data).stylesheet); } \ No newline at end of file diff --git a/test/class-selectors.js b/test/class-selectors.js index f59d431..7ce5ba2 100644 --- a/test/class-selectors.js +++ b/test/class-selectors.js @@ -1,88 +1,88 @@ -// import { expect } from 'chai'; +import { expect } from 'chai'; -// import { parse } from 'css'; +import { parse } from 'css'; -// import { toObject } from './../src/objectify'; +import { toObject } from './../src/objectify'; -// describe('toObject(stylesheet)', function() { -// describe('objectifies class selectors', () => { -// it('one class', () => { -// const expected = { -// '.class' : { -// 'border': '1px solid black' -// } -// }; +describe('toObject(stylesheet)', function() { + describe('objectifies class selectors', () => { + it('one class', () => { + const expected = { + '.class' : { + 'border': '1px solid black' + } + }; -// const actual = toObject(parse(`.class { border: 1px solid black; }`).stylesheet); + const actual = toObject(parse(`.class { border: 1px solid black; }`).stylesheet); -// expect(actual).to.deep.equal(expected); -// }); + expect(actual).to.deep.equal(expected); + }); -// it('two classes', () => { -// const expected = { -// '.class' : { -// 'border': '1px solid black' -// }, + it('two classes', () => { + const expected = { + '.class' : { + 'border': '1px solid black' + }, -// '.class2' : { -// 'background-color': '#2233FF' -// } -// }; + '.class2' : { + 'background-color': '#2233FF' + } + }; -// const actual = toObject(parse( -// `.class { -// border: 1px solid black; -// } + const actual = toObject(parse( + `.class { + border: 1px solid black; + } -// .class2 { -// background-color: #2233FF; -// }` -// ).stylesheet); + .class2 { + background-color: #2233FF; + }` + ).stylesheet); -// expect(actual).to.deep.equal(expected); -// }); + expect(actual).to.deep.equal(expected); + }); -// it('re-accuring class', () => { -// const expected = { -// '.class' : { -// 'border': '1px solid black', -// 'background-color': '#2233FF' -// } -// }; + it('re-accuring class', () => { + const expected = { + '.class' : { + 'border': '1px solid black', + 'background-color': '#2233FF' + } + }; -// const actual = toObject(parse( -// `.class { -// border: 1px solid black; -// } + const actual = toObject(parse( + `.class { + border: 1px solid black; + } -// .class { -// background-color: #2233FF; -// }` -// ).stylesheet); + .class { + background-color: #2233FF; + }` + ).stylesheet); -// expect(actual).to.deep.equal(expected); -// }); + expect(actual).to.deep.equal(expected); + }); -// it('re-accuring class with conflicting (overriding) styles', () => { -// const expected = { -// '.class' : { -// 'border': '1px solid black', -// 'background-color': 'white' -// } -// }; + it('re-accuring class with conflicting (overriding) styles', () => { + const expected = { + '.class' : { + 'border': '1px solid black', + 'background-color': 'white' + } + }; -// const actual = toObject(parse( -// `.class { -// border: 1px solid black; -// background-color: #2233FF; -// } + const actual = toObject(parse( + `.class { + border: 1px solid black; + background-color: #2233FF; + } -// .class { -// background-color: white; -// }` -// ).stylesheet); + .class { + background-color: white; + }` + ).stylesheet); -// expect(actual).to.deep.equal(expected); -// }); -// }); -// }); \ No newline at end of file + expect(actual).to.deep.equal(expected); + }); + }); +}); \ No newline at end of file diff --git a/test/element-class-selectors.js b/test/element-class-selectors.js deleted file mode 100644 index d946bf0..0000000 --- a/test/element-class-selectors.js +++ /dev/null @@ -1,24 +0,0 @@ -// import { expect } from 'chai'; - -// import { parse } from 'css'; - -// import { toObject } from './../src/objectify'; - -// describe('toObject(stylesheet)', function() { -// describe('objectifies element.class selectors', () => { -// it('one element', () => { -// const expected = { -// div : { -// '.class' : { -// border: '1px solid black' -// }, -// border: '1px solid black' -// } -// }; - -// const actual = toObject(parse(`div.class { border: 1px solid black; }`).stylesheet); - -// expect(actual).to.deep.equal(expected); -// }); -// }); -// }); \ No newline at end of file diff --git a/test/element-selectors.js b/test/element-selectors.js deleted file mode 100644 index c93e59a..0000000 --- a/test/element-selectors.js +++ /dev/null @@ -1,102 +0,0 @@ -// import { expect } from 'chai'; - -// import { parse } from 'css'; - -// import { toObject } from './../src/objectify'; - -// describe('toObject(stylesheet)', function() { -// describe('objectifies element selectors', () => { -// it('one element', () => { -// const expected = { -// div : { -// border: '1px solid black' -// } -// }; - -// const actual = toObject(parse(`div { border: 1px solid black; }`).stylesheet); - -// expect(actual).to.deep.equal(expected); -// }); - -// it('two elements', () => { -// const expected = { -// div : { -// border: '1px solid black', -// position: 'relative' -// }, - -// a : { -// display: 'inline-block' -// } -// }; - -// const actual = toObject(parse( -// `div { -// border: 1px solid black; -// position: relative; -// } - -// a { -// display: inline-block; -// }` -// ).stylesheet); - -// expect(actual).to.deep.equal(expected); -// }); - -// it('re-accuring element', () => { -// const expected = { -// div : { -// border: '1px solid black', -// position: 'relative', -// display: 'inline-block', -// color: 'white' -// } -// }; - -// const actual = toObject(parse( -// `div { -// border: 1px solid black; -// position: relative; -// } - -// div { -// display: inline-block; -// } - -// div { -// color: white; -// }` -// ).stylesheet); - -// expect(actual).to.deep.equal(expected); -// }); - -// it('re-accuring element with conflicting (overriding) styles', () => { -// const expected = { -// div : { -// border: '1px solid black', -// position: 'absolute', -// display: 'inline-block' -// } -// }; - -// const actual = toObject(parse( -// `div { -// border: 1px solid black; -// position: relative; -// } - -// div { -// display: inline-block; -// } - -// div { -// position: absolute; -// }` -// ).stylesheet); - -// expect(actual).to.deep.equal(expected); -// }); -// }); -// }); \ No newline at end of file diff --git a/test/get-type.js b/test/get-type.js index 3056f73..8557ec8 100644 --- a/test/get-type.js +++ b/test/get-type.js @@ -5,7 +5,23 @@ import getType, { SelectorType } from '../src/core/selector-type'; describe('getType(selector)', function() { describe('Selectors', () => { it('Invalid selector (E E)', () => { - expect(getType('e e')).to.equal(SelectorType.Invalid); + expect(getType('div span')).to.equal(SelectorType.Invalid); + }); + + it('Invalid selector (E.class.class)', () => { + expect(getType('div.class1.class2')).to.equal(SelectorType.Invalid); + }); + + it('Invalid selector (E.class#id)', () => { + expect(getType('div.class#id')).to.equal(SelectorType.Invalid); + }); + + it('Invalid selector (E#id#id)', () => { + expect(getType('div#id1#id2')).to.equal(SelectorType.Invalid); + }); + + it('Invalid selector (E#id.class)', () => { + expect(getType('div#id.class')).to.equal(SelectorType.Invalid); }); it('Type selector (E)', () => { diff --git a/test/multiple-selectors.js b/test/multiple-selectors.js index a8776a1..4ee446d 100644 --- a/test/multiple-selectors.js +++ b/test/multiple-selectors.js @@ -1,117 +1,117 @@ -// import { expect } from 'chai'; +import { expect } from 'chai'; -// import { parse } from 'css'; +import { parse } from 'css'; -// import { toObject } from './../src/objectify'; +import { toObject } from './../src/objectify'; -// describe('toObject(stylesheet)', function() { -// describe('objectifies multiple selectors', () => { -// it('simple multiple elements selector', () => { -// const expected = { -// div : { -// border: '1px solid black' -// }, +describe('toObject(stylesheet)', function() { + describe('objectifies multiple selectors', () => { + it('simple multiple elements selector', () => { + const expected = { + div : { + border: '1px solid black' + }, -// nav : { -// border: '1px solid black' -// } -// }; + nav : { + border: '1px solid black' + } + }; -// const actual = toObject(parse( -// `div, nav { -// border: 1px solid black; -// }` -// ).stylesheet); + const actual = toObject(parse( + `div, nav { + border: 1px solid black; + }` + ).stylesheet); -// expect(actual).to.deep.equal(expected); -// }); + expect(actual).to.deep.equal(expected); + }); -// it('multiple elements selector combined with normal elements selector', () => { -// const expected = { -// div : { -// border: '1px dotted black', -// 'background-color': 'yellow' -// }, + it('multiple elements selector combined with normal elements selector', () => { + const expected = { + div : { + border: '1px dotted black', + 'background-color': 'yellow' + }, -// nav : { -// border: '1px dotted black', -// color: 'white' -// } -// }; + nav : { + border: '1px dotted black', + color: 'white' + } + }; -// const actual = toObject(parse( -// `div, nav { -// border: 1px dotted black; -// } + const actual = toObject(parse( + `div, nav { + border: 1px dotted black; + } -// div { -// background-color: yellow; -// } + div { + background-color: yellow; + } -// nav { -// color: white; -// }` -// ).stylesheet); + nav { + color: white; + }` + ).stylesheet); -// expect(actual).to.deep.equal(expected); -// }); + expect(actual).to.deep.equal(expected); + }); -// it('multiple elements selector overriden by normal elements selector', () => { -// const expected = { -// div : { -// border: '2px dotted black', -// 'background-color': 'yellow' -// }, + it('multiple elements selector overriden by normal elements selector', () => { + const expected = { + div : { + border: '2px dotted black', + 'background-color': 'yellow' + }, -// nav : { -// border: '1px dotted black', -// color: 'white' -// } -// }; + nav : { + border: '1px dotted black', + color: 'white' + } + }; -// const actual = toObject(parse( -// `div, nav { -// border: 1px dotted black; -// } + const actual = toObject(parse( + `div, nav { + border: 1px dotted black; + } -// div { -// border: 2px dotted black; -// background-color: yellow; -// } + div { + border: 2px dotted black; + background-color: yellow; + } -// nav { -// color: white; -// }` -// ).stylesheet); + nav { + color: white; + }` + ).stylesheet); -// expect(actual).to.deep.equal(expected); -// }); + expect(actual).to.deep.equal(expected); + }); -// it('multiple elements selector overriden by multiple elements selector', () => { -// const expected = { -// div : { -// border: '2px dotted black', -// 'background-color': 'yellow' -// }, + it('multiple elements selector overriden by multiple elements selector', () => { + const expected = { + div : { + border: '2px dotted black', + 'background-color': 'yellow' + }, -// nav : { -// border: '2px dotted black', -// 'background-color': 'yellow' -// } -// }; + nav : { + border: '2px dotted black', + 'background-color': 'yellow' + } + }; -// const actual = toObject(parse( -// `div, nav { -// border: 1px dotted black; -// } + const actual = toObject(parse( + `div, nav { + border: 1px dotted black; + } -// div, nav { -// border: 2px dotted black; -// background-color: yellow; -// }` -// ).stylesheet); + div, nav { + border: 2px dotted black; + background-color: yellow; + }` + ).stylesheet); -// expect(actual).to.deep.equal(expected); -// }); -// }); -// }); \ No newline at end of file + expect(actual).to.deep.equal(expected); + }); + }); +}); \ No newline at end of file diff --git a/test/type-class-selectors.js b/test/type-class-selectors.js new file mode 100644 index 0000000..2d92838 --- /dev/null +++ b/test/type-class-selectors.js @@ -0,0 +1,23 @@ +import { expect } from 'chai'; + +import { parse } from 'css'; + +import { toObject } from './../src/objectify'; + +describe('toObject(stylesheet)', function() { + describe('objectifies type.class selectors', () => { + it('one element', () => { + const expected = { + div : { + '.class' : { + border: '1px solid black' + } + } + }; + + const actual = toObject(parse(`div.class { border: 1px solid black; }`).stylesheet); + + expect(actual).to.deep.equal(expected); + }); + }); +}); \ No newline at end of file diff --git a/test/type-selectors.js b/test/type-selectors.js new file mode 100644 index 0000000..abb9950 --- /dev/null +++ b/test/type-selectors.js @@ -0,0 +1,102 @@ +import { expect } from 'chai'; + +import { parse } from 'css'; + +import { toObject } from './../src/objectify'; + +describe('toObject(stylesheet)', function() { + describe('objectifies type selectors', () => { + it('one element', () => { + const expected = { + div : { + border: '1px solid black' + } + }; + + const actual = toObject(parse(`div { border: 1px solid black; }`).stylesheet); + + expect(actual).to.deep.equal(expected); + }); + + it('two elements', () => { + const expected = { + div : { + border: '1px solid black', + position: 'relative' + }, + + a : { + display: 'inline-block' + } + }; + + const actual = toObject(parse( + `div { + border: 1px solid black; + position: relative; + } + + a { + display: inline-block; + }` + ).stylesheet); + + expect(actual).to.deep.equal(expected); + }); + + it('re-accuring element', () => { + const expected = { + div : { + border: '1px solid black', + position: 'relative', + display: 'inline-block', + color: 'white' + } + }; + + const actual = toObject(parse( + `div { + border: 1px solid black; + position: relative; + } + + div { + display: inline-block; + } + + div { + color: white; + }` + ).stylesheet); + + expect(actual).to.deep.equal(expected); + }); + + it('re-accuring element with conflicting (overriding) styles', () => { + const expected = { + div : { + border: '1px solid black', + position: 'absolute', + display: 'inline-block' + } + }; + + const actual = toObject(parse( + `div { + border: 1px solid black; + position: relative; + } + + div { + display: inline-block; + } + + div { + position: absolute; + }` + ).stylesheet); + + expect(actual).to.deep.equal(expected); + }); + }); +}); \ No newline at end of file