Browse Source

Merge branch 'setrule' into dev

pull/1730/head
Artur Arseniev 7 years ago
parent
commit
f85b3fc2b3
  1. 78
      src/css_composer/index.js
  2. 2
      src/parser/index.js
  3. 2
      src/parser/model/BrowserParserCss.js
  4. 84
      src/selector_manager/index.js
  5. 2
      src/selector_manager/model/Selectors.js
  6. 114
      test/specs/css_composer/index.js
  7. 60
      test/specs/selector_manager/index.js

78
src/css_composer/index.js

@ -21,10 +21,8 @@
* * [get](#get)
* * [getAll](#getall)
* * [clear](#clear)
* * [setIdRule](#setidrule)
* * [getIdRule](#getidrule)
* * [setClassRule](#setclassrule)
* * [getClassRule](#getclassrule)
* * [setRule](#setrule)
* * [getRule](#getrule)
*
* @module CssComposer
*/
@ -304,12 +302,81 @@ module.exports = () => {
return result;
},
/**
* Add/update the CSS rule with a generic selector
* @param {string} selectors Selector, eg. '.myclass'
* @param {Object} style Style properties and values
* @param {Object} [opts={}] Additional properties
* @param {String} [opts.atRuleType=''] At-rule type, eg. 'media'
* @param {String} [opts.atRuleParams=''] At-rule parameters, eg. '(min-width: 500px)'
* @return {CssRule} The new/updated rule
* @example
* // Simple class-based rule
* const rule = cc.setRule('.class1.class2', { color: 'red' });
* console.log(rule.toCSS()) // output: .class1.class2 { color: red }
* // With state and other mixed selector
* const rule = cc.setRule('.class1.class2:hover, div#myid', { color: 'red' });
* // output: .class1.class2:hover, div#myid { color: red }
* // With media
* const rule = cc.setRule('.class1:hover', { color: 'red' }, {
* atRuleType: 'media',
* atRuleParams: '(min-width: 500px)',
* });
* // output: @media (min-width: 500px) { .class1:hover { color: red } }
*/
setRule(selectors, style, opts = {}) {
const { atRuleType, atRuleParams } = opts;
const node = em.get('Parser').parserCss.checkNode({
selectors,
style
})[0];
const { state, selectorsAdd } = node;
const sm = em.get('SelectorManager');
const selector = sm.add(node.selectors);
const rule = this.add(selector, state, atRuleParams, {
selectorsAdd,
atRule: atRuleType
});
rule.setStyle(style, opts);
return rule;
},
/**
* Get the CSS rule by a generic selector
* @param {string} selectors Selector, eg. '.myclass:hover'
* @param {String} [opts.atRuleType=''] At-rule type, eg. 'media'
* @param {String} [opts.atRuleParams=''] At-rule parameters, eg. '(min-width: 500px)'
* @return {CssRule}
* @example
* const rule = cc.getRule('.myclass1:hover');
* const rule2 = cc.getRule('.myclass1:hover, div#myid');
* const rule3 = cc.getRule('.myclass1', {
* atRuleType: 'media',
* atRuleParams: '(min-width: 500px)',
* });
*/
getRule(selectors, opts = {}) {
const sm = em.get('SelectorManager');
const node = em.get('Parser').parserCss.checkNode({ selectors })[0];
const selector = sm.get(node.selectors);
const { state, selectorsAdd } = node;
const { atRuleType, atRuleParams } = opts;
return (
selector &&
this.get(selector, state, atRuleParams, {
selectorsAdd,
atRule: atRuleType
})
);
},
/**
* Add/update the CSS rule with id selector
* @param {string} name Id selector name, eg. 'my-id'
* @param {Object} style Style properties and values
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule} The new/updated rule
* @private
* @example
* const rule = cc.setIdRule('myid', { color: 'red' });
* const ruleHover = cc.setIdRule('myid', { color: 'blue' }, { state: 'hover' });
@ -332,6 +399,7 @@ module.exports = () => {
* @param {string} name Id selector name, eg. 'my-id'
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule}
* @private
* @example
* const rule = cc.getIdRule('myid');
* const ruleHover = cc.setIdRule('myid', { state: 'hover' });
@ -349,6 +417,7 @@ module.exports = () => {
* @param {Object} style Style properties and values
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule} The new/updated rule
* @private
* @example
* const rule = cc.setClassRule('myclass', { color: 'red' });
* const ruleHover = cc.setClassRule('myclass', { color: 'blue' }, { state: 'hover' });
@ -371,6 +440,7 @@ module.exports = () => {
* @param {string} name Class selector name, eg. 'my-class'
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule}
* @private
* @example
* const rule = cc.getClassRule('myclass');
* const ruleHover = cc.getClassRule('myclass', { state: 'hover' });

2
src/parser/index.js

@ -48,6 +48,8 @@ module.exports = () => {
pHtml = new parserHtml(conf);
pCss = new parserCss(conf);
this.em = conf.em;
this.parserCss = pCss;
this.parserHtml = pHtml;
return this;
},

2
src/parser/model/BrowserParserCss.js

@ -101,7 +101,7 @@ export const parseCondition = node => {
* @param {Object} style Key-value object of style declarations
* @return {Object}
*/
export const createNode = (selectors, style, opts = {}) => {
export const createNode = (selectors, style = {}, opts = {}) => {
const node = {};
const selLen = selectors.length;
const lastClass = selectors[selLen - 1];

84
src/selector_manager/index.js

@ -44,7 +44,7 @@
* @module SelectorManager
*/
import { isString, isElement, isObject } from 'underscore';
import { isString, isElement, isObject, isArray } from 'underscore';
const isId = str => isString(str) && str[0] == '#';
const isClass = str => isString(str) && str[0] == '.';
@ -118,22 +118,9 @@ module.exports = config => {
}
},
/**
* Add a new selector to collection if it's not already exists. Class type is a default one
* @param {String} name Selector name
* @param {Object} opts Selector options
* @param {String} [opts.label=''] Label for the selector, if it's not provided the label will be the same as the name
* @param {String} [opts.type=1] Type of the selector. At the moment, only 'class' (1) is available
* @return {Model}
* @example
* var selector = selectorManager.add('selectorName');
* // Same as
* var selector = selectorManager.add('selectorName', {
* type: 1,
* label: 'selectorName'
* });
* */
add(name, opts = {}) {
addSelector(name, opt = {}) {
let opts = { ...opt };
if (isObject(name)) {
opts = name;
} else {
@ -143,6 +130,8 @@ module.exports = config => {
if (isId(opts.name)) {
opts.name = opts.name.substr(1);
opts.type = Selector.TYPE_ID;
} else if (isClass(opts.name)) {
opts.name = opts.name.substr(1);
}
if (opts.label && !opts.name) {
@ -161,6 +150,42 @@ module.exports = config => {
return selector;
},
getSelector(name, type = Selector.TYPE_CLASS) {
if (isId(name)) {
name = name.substr(1);
type = Selector.TYPE_ID;
} else if (isClass(name)) {
name = name.substr(1);
}
return selectors.where({ name, type })[0];
},
/**
* Add a new selector to collection if it's not already exists. Class type is a default one
* @param {String|Array} name Selector/s name
* @param {Object} opts Selector options
* @param {String} [opts.label=''] Label for the selector, if it's not provided the label will be the same as the name
* @param {String} [opts.type=1] Type of the selector. At the moment, only 'class' (1) is available
* @return {Model|Array}
* @example
* const selector = selectorManager.add('selectorName');
* // Same as
* const selector = selectorManager.add('selectorName', {
* type: 1,
* label: 'selectorName'
* });
* // Multiple selectors
* const selectors = selectorManager.add(['.class1', '.class2', '#id1']);
* */
add(name, opts = {}) {
if (isArray(name)) {
return name.map(item => this.addSelector(item, opts));
} else {
return this.addSelector(name, opts);
}
},
/**
* Add class selectors
* @param {Array|string} classes Array or string of classes
@ -184,18 +209,27 @@ module.exports = config => {
/**
* Get the selector by its name
* @param {String} name Selector name
* @param {String|Array} name Selector name
* @param {String} tyoe Selector type
* @return {Model|null}
* @return {Model|Array}
* @example
* var selector = selectorManager.get('selectorName');
* const selector = selectorManager.get('selectorName');
* // or get an array
* const selectors = selectorManager.get(['class1', 'class2']);
* */
get(name, type = Selector.TYPE_CLASS) {
if (isId(name)) {
name = name.substr(1);
type = Selector.TYPE_ID;
get(name, type) {
if (isArray(name)) {
const result = [];
const selectors = name
.map(item => this.getSelector(item))
.filter(item => item);
selectors.forEach(
item => result.indexOf(item) < 0 && result.push(item)
);
return result;
} else {
return this.getSelector(name, type);
}
return selectors.where({ name, type })[0];
},
/**

2
src/selector_manager/model/Selectors.js

@ -4,6 +4,8 @@ const Selector = require('./Selector');
module.exports = require('backbone').Collection.extend({
model: Selector,
modelId: attr => `${attr.name}_${attr.type || Selector.TYPE_CLASS}`,
getStyleable() {
return filter(
this.models,

114
test/specs/css_composer/index.js

@ -207,6 +207,120 @@ describe('Css Composer', () => {
const rule = obj.getClassRule(name, { state });
expect(rule.selectorsToString()).toEqual(`.${name}:${state}`);
});
test('Create a simple class-based rule with setRule', () => {
const selector = '.test';
const result = obj.setRule(selector, { color: 'red' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:red;`);
});
test('Avoid creating multiple rules with the same selector', () => {
const selector = '.test';
obj.setRule(selector, { color: 'red' });
obj.setRule(selector, { color: 'blue' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:blue;`);
});
test('Create a class-based rule with setRule', () => {
const selector = '.test.test2';
const result = obj.setRule(selector, { color: 'red' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:red;`);
});
test('Create a class-based rule with a state, by using setRule', () => {
const selector = '.test.test2:hover';
const result = obj.setRule(selector, { color: 'red' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:red;`);
});
test('Create a rule with class-based and mixed selectors', () => {
const selector = '.test.test2:hover, #test .selector';
obj.setRule(selector, { color: 'red' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:red;`);
});
test('Create a rule with only mixed selectors', () => {
const selector = '#test1 .class1, .class2 > #id2';
obj.setRule(selector, { color: 'red' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.get('selectors').length).toEqual(0);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:red;`);
});
test('Create a rule with atRule', () => {
const toTest = [
{
selector: '.class1:hover',
style: { color: 'blue' },
opts: {
atRuleType: 'media',
atRuleParams: 'screen and (min-width: 480px)'
}
},
{
selector: '.class1:hover',
style: { color: 'red' },
opts: {
atRuleType: 'media',
atRuleParams: 'screen and (min-width: 480px)'
}
}
];
toTest.forEach(test => {
const { selector, style, opts } = test;
const result = obj.setRule(selector, style, opts);
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector, opts);
expect(rule.getAtRule()).toEqual(
`@${opts.atRuleType} ${opts.atRuleParams}`
);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.getStyle()).toEqual(style);
});
});
test('Create different rules by using setRule', () => {
const toTest = [
{ selector: '.class1:hover', style: { color: '#111' } },
{ selector: '.class1.class2', style: { color: '#222' } },
{ selector: '.class1, .class2 .class3', style: { color: 'red' } },
{ selector: '.class1, .class2 .class4', style: { color: 'green' } },
{ selector: '.class4, .class1 .class2', style: { color: 'blue' } },
{
selector: '.class4, .class1 .class2',
style: { color: 'blue' },
opt: { atRuleType: 'media', atRuleParams: '(min-width: 480px)' }
}
];
toTest.forEach(test => {
const { selector, style, opt = {} } = test;
obj.setRule(selector, style, opt);
const rule = obj.getRule(selector, opt);
const atRule = `${opt.atRuleType || ''} ${opt.atRuleParams ||
''}`.trim();
expect(rule.getAtRule()).toEqual(atRule ? `@${atRule}` : '');
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.getStyle()).toEqual(style);
});
expect(obj.getAll().length).toEqual(toTest.length);
});
});
Models.run();

60
test/specs/selector_manager/index.js

@ -60,6 +60,13 @@ describe('SelectorManager', () => {
expect(sel.get('label')).toEqual(name);
});
test('Check name property by adding as class', () => {
var name = 'test';
var sel = obj.add(`.${name}`);
expect(sel.get('name')).toEqual(name);
expect(sel.get('label')).toEqual(name);
});
test('Add 2 selectors', () => {
obj.add('test');
obj.add('test2');
@ -72,6 +79,59 @@ describe('SelectorManager', () => {
expect(obj.getAll().length).toEqual(1);
});
test('Add multiple selectors', () => {
const cls = [
'.test1',
'test1',
'.test2',
'.test2',
'#test3',
'test3',
'test3',
'#test3'
];
const result = obj.add(cls);
expect(Array.isArray(result)).toEqual(true);
const concat = obj
.getAll()
.map(item => item.getFullName())
.join('');
expect(concat).toEqual('.test1.test2#test3.test3');
expect(obj.getAll().length).toEqual(4);
expect(
obj
.getAll()
.at(0)
.getFullName()
).toEqual('.test1');
expect(
obj
.getAll()
.at(1)
.getFullName()
).toEqual('.test2');
expect(
obj
.getAll()
.at(2)
.getFullName()
).toEqual('#test3');
expect(
obj
.getAll()
.at(3)
.getFullName()
).toEqual('.test3');
expect(obj.get(cls).length).toEqual(4);
expect(
obj
.get(cls)
.map(item => item.getFullName())
.join('')
).toEqual(concat);
});
test('Get selector', () => {
var name = 'test';
var sel = obj.add(name);

Loading…
Cancel
Save