Browse Source

Merge branch 'custom-css-parser' into dev

pull/1446/head
Artur Arseniev 8 years ago
parent
commit
1194548e00
  1. 13
      src/code_manager/model/CssGenerator.js
  2. 59
      src/editor/index.js
  3. 12
      src/editor/model/Editor.js
  4. 8
      src/parser/config/config.js
  5. 33
      src/parser/index.js
  6. 221
      src/parser/model/BrowserParserCss.js
  7. 230
      src/parser/model/ParserCss.js
  8. 9
      test/specs/grapesjs/index.js
  9. 196
      test/specs/parser/model/ParserCss.js

13
src/code_manager/model/CssGenerator.js

@ -72,9 +72,16 @@ module.exports = require('backbone').Model.extend({
for (let atRule in atRules) {
let rulesStr = '';
const mRules = atRules[atRule];
mRules.forEach(
rule => (rulesStr += this.buildFromRule(rule, dump, opts))
);
mRules.forEach(rule => {
const ruleStr = this.buildFromRule(rule, dump, opts);
if (rule.get('singleAtRule')) {
code += `${atRule}{${ruleStr}}`;
} else {
rulesStr += ruleStr;
}
});
if (rulesStr) {
code += `${atRule}{${rulesStr}}`;

59
src/editor/index.js

@ -223,6 +223,12 @@ module.exports = config => {
*/
RichTextEditor: em.get('RichTextEditor'),
/**
* @property {Parser}
* @private
*/
Parser: em.get('Parser'),
/**
* @property {Utils}
* @private
@ -567,6 +573,50 @@ module.exports = config => {
this.RichTextEditor.customRte = obj;
},
/**
* Replace the default CSS parser with a custom one.
* The parser function receives a CSS string as a parameter and expects
* an array of CSSRule objects as a result. If you need to remove the
* custom parser, pass `null` as the argument
* @param {Function|null} parser Parser function
* @return {this}
* @example
* editor.setCustomParserCss(css => {
* const result = [];
* // ... parse the CSS string
* result.push({
* selectors: '.someclass, div .otherclass',
* style: { color: 'red' }
* })
* // ...
* return result;
* });
*/
setCustomParserCss(parser) {
this.Parser.getConfig().parserCss = parser;
return this;
},
/**
* Trigger event log message
* @param {*} msg Message to log
* @param {Object} [opts={}] Custom options
* @param {String} [opts.ns=''] Namespace of the log (eg. to use in plugins)
* @param {String} [opts.level='debug'] Level of the log, `debug`, `info`, `warning`, `error`
* @return {this}
* @example
* editor.log('Something done!', { ns: 'from-plugin-x', level: 'info' });
* // This will trigger following events
* // `log`, `log:info`, `log-from-plugin-x`, `log-from-plugin-x:info`
* // Callbacks of those events will always receive the message and
* // options, as arguments, eg:
* // editor.on('log:info', (msg, opts) => console.info(msg, opts))
*/
log(msg, opts = {}) {
em.log(msg, opts);
return this;
},
/**
* Attach event
* @param {string} event Event name
@ -574,7 +624,8 @@ module.exports = config => {
* @return {this}
*/
on(event, callback) {
return em.on(event, callback);
em.on(event, callback);
return this;
},
/**
@ -584,7 +635,8 @@ module.exports = config => {
* @return {this}
*/
off(event, callback) {
return em.off(event, callback);
em.off(event, callback);
return this;
},
/**
@ -593,7 +645,8 @@ module.exports = config => {
* @return {this}
*/
trigger(event) {
return em.trigger.apply(em, arguments);
em.trigger.apply(em, arguments);
return this;
},
/**

12
src/editor/model/Editor.js

@ -621,6 +621,18 @@ module.exports = Backbone.Model.extend({
return !!this.get('editing');
},
log(msg, opts = {}) {
const { ns, level = 'debug' } = opts;
this.trigger('log', msg, opts);
level && this.trigger(`log:${level}`, msg, opts);
if (ns) {
const logNs = `log-${ns}`;
this.trigger(logNs, msg, opts);
level && this.trigger(`${logNs}:${level}`, msg, opts);
}
},
/**
* Set/get data from the HTMLElement
* @param {HTMLElement} el

8
src/parser/config/config.js

@ -1,3 +1,9 @@
module.exports = {
textTags: ['br', 'b', 'i', 'u', 'a', 'ul', 'ol']
textTags: ['br', 'b', 'i', 'u', 'a', 'ul', 'ol'],
// Custom CSS parser
parserCss: null,
// Custom HTML parser
parserHtml: null
};

33
src/parser/index.js

@ -1,13 +1,17 @@
module.exports = () => {
var c = {},
let conf = {},
defaults = require('./config/config'),
parserCss = require('./model/ParserCss'),
parserHtml = require('./model/ParserHtml');
var pHtml, pCss;
let pHtml, pCss;
return {
compTypes: '',
parserCss: null,
parserHtml: null,
/**
* Name of the module
* @type {String}
@ -15,6 +19,14 @@ module.exports = () => {
*/
name: 'Parser',
/**
* Get config object
* @return {Object}
*/
getConfig() {
return conf;
},
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
@ -30,13 +42,11 @@ module.exports = () => {
* }
* ...
*/
init(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c)) c[name] = defaults[name];
}
pHtml = new parserHtml(c);
pCss = new parserCss(c);
init(config = {}) {
conf = { ...defaults, ...config };
conf.Parser = this;
pHtml = new parserHtml(conf);
pCss = new parserCss(conf);
return this;
},
@ -50,6 +60,11 @@ module.exports = () => {
return pHtml.parse(str, pCss);
},
/**
* Parse CSS string and return valid model
* @param {string} str CSS string
* @return {Array<Object>}
*/
parseCss(str) {
return pCss.parse(str);
}

221
src/parser/model/BrowserParserCss.js

@ -0,0 +1,221 @@
import { keys } from 'underscore';
// At-rules
// https://developer.mozilla.org/it/docs/Web/API/CSSRule#Type_constants
const atRules = {
4: 'media',
5: 'font-face',
6: 'page',
7: 'keyframes',
11: 'counter-style',
12: 'supports',
13: 'document',
14: 'font-feature-values',
15: 'viewport'
};
const atRuleKeys = keys(atRules);
const singleAtRules = ['5', '6', '11', '15'];
const singleAtRulesNames = ['font-face', 'page', 'counter-style', 'viewport'];
/**
* Parse selector string to array.
* Only classe based are valid as CSS rules inside editor, not valid
* selectors will be dropped as additional
* It's ok with the last part of the string as state (:hover, :active)
* @param {string} str Selectors string
* @return {Object}
* @example
* var res = parseSelector('.test1, .test1.test2, .test2 .test3');
* console.log(res);
* // {
* //result: [['test1'], ['test1', 'test2']],
* //add: ['.test2 .test3']
* //}
*/
export const parseSelector = (str = '') => {
const add = [];
const result = [];
const sels = str.split(',');
for (var i = 0, len = sels.length; i < len; i++) {
var sel = sels[i].trim();
// Will accept only concatenated classes and last
// class might be with state (eg. :hover), nothing else.
// Can also accept SINGLE ID selectors, eg. `#myid`, `#myid:hover`
// Composed are not valid: `#myid.some-class`, `#myid.some-class:hover`
if (
/^(\.{1}[\w\-]+)+(:{1,2}[\w\-()]+)?$/gi.test(sel) ||
/^(#{1}[\w\-]+){1}(:{1,2}[\w\-()]+)?$/gi.test(sel)
) {
var cls = sel.split('.').filter(Boolean);
result.push(cls);
} else {
add.push(sel);
}
}
return {
result,
add
};
};
/**
* Parse style declarations of the node
* @param {CSSRule} node
* @return {Object}
*/
export const parseStyle = node => {
const stl = node.style;
const style = {};
for (var i = 0, len = stl.length; i < len; i++) {
const propName = stl[i];
const propValue = stl.getPropertyValue(propName);
const important = stl.getPropertyPriority(propName);
style[propName] = `${propValue}${important ? ` !${important}` : ''}`;
}
return style;
};
/**
* Get the condition when possible
* @param {CSSRule} node
* @return {string}
*/
export const parseCondition = node => {
const condition =
node.conditionText ||
(node.media && node.media.mediaText) ||
node.name ||
node.selectorText ||
'';
return condition.trim();
};
/**
* Create node for the editor
* @param {Array<String>} selectors Array containing strings of classes
* @param {Object} style Key-value object of style declarations
* @return {Object}
*/
export const createNode = (selectors, style, opts = {}) => {
const node = {};
const selLen = selectors.length;
const lastClass = selectors[selLen - 1];
const stateArr = lastClass ? lastClass.split(/:(.+)/) : [];
const state = stateArr[1];
const { atRule, selectorsAdd, mediaText } = opts;
const singleAtRule = singleAtRulesNames.indexOf(atRule) >= 0;
singleAtRule && (node.singleAtRule = 1);
atRule && (node.atRuleType = atRule);
selectorsAdd && (node.selectorsAdd = selectorsAdd);
mediaText && (node.mediaText = mediaText);
// Isolate the state from selectors
if (state) {
selectors[selLen - 1] = stateArr[0];
node.state = state;
stateArr.splice(stateArr.length - 1, 1);
}
node.selectors = selectors;
node.style = style;
return node;
};
/**
* Fetch data from node
* @param {StyleSheet|CSSRule} el
* @return {Array<Object>}
*/
export const parseNode = el => {
var result = [];
var nodes = el.cssRules || [];
for (var i = 0, len = nodes.length; i < len; i++) {
const node = nodes[i];
const type = node.type.toString();
let singleAtRule = 0;
let atRuleType = '';
let condition = '';
// keyText is for CSSKeyframeRule
let sels = node.selectorText || node.keyText;
const isSingleAtRule = singleAtRules.indexOf(type) >= 0;
// Check if the node is an at-rule
if (isSingleAtRule) {
singleAtRule = 1;
atRuleType = atRules[type];
condition = parseCondition(node);
} else if (atRuleKeys.indexOf(type) >= 0) {
var subRules = parseNode(node);
condition = parseCondition(node);
for (var s = 0, lens = subRules.length; s < lens; s++) {
var subRule = subRules[s];
condition && (subRule.mediaText = condition);
subRule.atRuleType = atRules[type];
}
result = result.concat(subRules);
}
if (!sels && !isSingleAtRule) continue;
const style = parseStyle(node);
const selsParsed = parseSelector(sels);
const selsAdd = selsParsed.add;
sels = selsParsed.result;
let lastRule;
// For each group of selectors
for (var k = 0, len3 = sels.length; k < len3; k++) {
const model = createNode(sels[k], style, {
atRule: atRules[type]
});
result.push(model);
lastRule = model;
}
// Need to push somewhere not class-based selectors, if some rule was
// created will push them there, otherwise will create a new rule
if (selsAdd.length) {
var selsAddStr = selsAdd.join(', ');
if (lastRule) {
lastRule.selectorsAdd = selsAddStr;
} else {
const model = {
selectors: [],
selectorsAdd: selsAddStr,
style
};
singleAtRule && (model.singleAtRule = singleAtRule);
atRuleType && (model.atRuleType = atRuleType);
condition && (model.mediaText = condition);
result.push(model);
}
}
// console.log('LAST PUSH', result[result.length - 1]);
}
return result;
};
/**
* Parse CSS string and return the array of objects
* @param {String} str CSS string
* @return {Array<Object>} Array of objects for the definition of CSSRules
*/
export default str => {
const el = document.createElement('style');
el.innerHTML = str;
// There is no .sheet before adding it to the <head>
document.head.appendChild(el);
const sheet = el.sheet;
document.head.removeChild(el);
return parseNode(sheet);
};

230
src/parser/model/ParserCss.js

@ -1,203 +1,59 @@
import { keys } from 'underscore';
// At-rules
// https://developer.mozilla.org/it/docs/Web/API/CSSRule#Type_constants
const atRules = {
4: 'media',
5: 'font-face',
6: 'page',
7: 'keyframes',
11: 'counter-style',
12: 'supports',
13: 'document',
14: 'font-feature-values',
15: 'viewport'
};
const atRuleKeys = keys(atRules);
const singleAtRules = ['5', '6', '11', '15'];
module.exports = config => ({
/**
* Parse selector string to array.
* Only classe based are valid as CSS rules inside editor, not valid
* selectors will be dropped as additional
* It's ok with the last part of the string as state (:hover, :active)
* @param {string} str Selectors string
* @return {Object}
* @example
* var res = ParserCss.parseSelector('.test1, .test1.test2, .test2 .test3');
* console.log(res);
* // {
* //result: [['test1'], ['test1', 'test2']],
* //add: ['.test2 .test3']
* //}
*/
parseSelector(str = '') {
const add = [];
const result = [];
const sels = str.split(',');
for (var i = 0, len = sels.length; i < len; i++) {
var sel = sels[i].trim();
// Will accept only concatenated classes and last
// class might be with state (eg. :hover), nothing else.
// Can also accept SINGLE ID selectors, eg. `#myid`, `#myid:hover`
// Composed are not valid: `#myid.some-class`, `#myid.some-class:hover`
if (
/^(\.{1}[\w\-]+)+(:{1,2}[\w\-()]+)?$/gi.test(sel) ||
/^(#{1}[\w\-]+){1}(:{1,2}[\w\-()]+)?$/gi.test(sel)
) {
var cls = sel.split('.').filter(Boolean);
result.push(cls);
} else {
add.push(sel);
}
}
return {
result,
add
};
},
import { isString } from 'underscore';
import BrowserCssParser, {
parseSelector,
createNode
} from './BrowserParserCss';
module.exports = (config = {}) => ({
/**
* Parse style declarations of the node
* @param {CSSRule} node
* @return {Object}
* Parse CSS string to a desired model object
* @param {String} str CSS string
* @return {Array<Object>}
*/
parseStyle(node) {
const stl = node.style;
const style = {};
for (var i = 0, len = stl.length; i < len; i++) {
const propName = stl[i];
const propValue = stl.getPropertyValue(propName);
const important = stl.getPropertyPriority(propName);
style[propName] = `${propValue}${important ? ` !${important}` : ''}`;
}
return style;
},
parse(str) {
let result = [];
const { parserCss, em = {} } = config;
const editor = em && em.get && em.get('Editor');
const nodes = parserCss ? parserCss(str, editor) : BrowserCssParser(str);
nodes.forEach(node => (result = result.concat(this.checkNode(node))));
/**
* Get the condition when possible
* @param {CSSRule} node
* @return {string}
*/
parseCondition(node) {
const condition =
node.conditionText ||
(node.media && node.media.mediaText) ||
node.name ||
node.selectorText ||
'';
return condition.trim();
return result;
},
/**
* Fetch data from node
* @param {StyleSheet|CSSRule} el
* @return {Array<Object>}
* Check the returned node from a custom parser and transforms it to
* a valid object for the CSS composer
* @return {[type]}
*/
parseNode(el) {
var result = [];
var nodes = el.cssRules || [];
for (var i = 0, len = nodes.length; i < len; i++) {
const node = nodes[i];
const type = node.type.toString();
let singleAtRule = 0;
let atRuleType = '';
let condition = '';
// keyText is for CSSKeyframeRule
let sels = node.selectorText || node.keyText;
const isSingleAtRule = singleAtRules.indexOf(type) >= 0;
// Check if the node is an at-rule
if (isSingleAtRule) {
singleAtRule = 1;
atRuleType = atRules[type];
condition = this.parseCondition(node);
} else if (atRuleKeys.indexOf(type) >= 0) {
var subRules = this.parseNode(node);
condition = this.parseCondition(node);
for (var s = 0, lens = subRules.length; s < lens; s++) {
var subRule = subRules[s];
condition && (subRule.mediaText = condition);
subRule.atRuleType = atRules[type];
}
result = result.concat(subRules);
checkNode(node) {
const { selectors, style } = node;
if (isString(selectors)) {
const nodes = [];
const selsParsed = parseSelector(selectors);
const classSets = selsParsed.result;
const selectorsAdd = selsParsed.add.join(', ');
const opts = {
atRule: node.atRule,
mediaText: node.params
};
if (classSets.length) {
classSets.forEach(classSet => {
nodes.push(createNode(classSet, style, opts));
});
} else {
nodes.push(createNode([], style, opts));
}
if (!sels && !isSingleAtRule) continue;
const style = this.parseStyle(node);
const selsParsed = this.parseSelector(sels);
const selsAdd = selsParsed.add;
sels = selsParsed.result;
let lastRule;
// For each group of selectors
for (var k = 0, len3 = sels.length; k < len3; k++) {
var selArr = sels[k];
var model = {};
singleAtRule && (model.singleAtRule = singleAtRule);
atRuleType && (model.atRuleType = atRuleType);
//Isolate state from selector
var stateArr = selArr[selArr.length - 1].split(/:(.+)/);
if (stateArr[1]) {
selArr[selArr.length - 1] = stateArr[0];
model.state = stateArr[1];
stateArr.splice(stateArr.length - 1, 1);
}
model.selectors = selArr;
model.style = style;
lastRule = model;
result.push(model);
if (selectorsAdd) {
const lastNode = nodes[nodes.length - 1];
lastNode.selectorsAdd = selectorsAdd;
}
// Need to push somewhere not class-based selectors, if some rule was
// created will push them there, otherwise will create a new rule
if (selsAdd.length) {
var selsAddStr = selsAdd.join(', ');
if (lastRule) {
lastRule.selectorsAdd = selsAddStr;
} else {
const model = {
selectors: [],
selectorsAdd: selsAddStr,
style
};
singleAtRule && (model.singleAtRule = singleAtRule);
atRuleType && (model.atRuleType = atRuleType);
condition && (model.mediaText = condition);
result.push(model);
}
}
// console.log('LAST PUSH', result[result.length - 1]);
node = nodes;
}
return result;
},
/**
* Parse CSS string to a desired model object
* @param {string} str HTML string
* @return {Object|Array<Object>}
*/
parse(str) {
const el = document.createElement('style');
el.innerHTML = str;
// There is no .sheet before adding it to the <head>
document.head.appendChild(el);
const sheet = el.sheet;
document.head.removeChild(el);
const result = this.parseNode(sheet);
return result.length == 1 ? result[0] : result;
return node;
}
});

9
test/specs/grapesjs/index.js

@ -164,18 +164,19 @@ describe('GrapesJS', () => {
`
<style>
@font-face {
font-family: 'Glyphicons Halflings';
src: url(https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/fonts/glyphicons-halflings-regular.woff2) format('woff2');
font-family: 'A';
src: url('http://a.link') format('woff2');
}
@font-face {
font-family: 'Droid Sans';
src: url(https://fonts.gstatic.com/s/droidsans/v8/SlGVmQWMvZQIdix7AFxXkHNSbRYXags.woff2) format('woff2');
font-family: 'B';
src: url('http://b.link') format('woff2');
}
</style>` + htmlString;
const editor = obj.init(config);
const css = editor.getCss();
const styles = editor.getStyle();
expect(styles.length).toEqual(2);
expect((css.match(/@font-face/g) || []).length).toEqual(2);
});
test('Set components as HTML', () => {

196
test/specs/parser/model/ParserCss.js

@ -1,13 +1,21 @@
import { parseSelector } from 'parser/model/BrowserParserCss';
const ParserCss = require('parser/model/ParserCss');
const Selector = require('selector_manager/model/Selector');
module.exports = {
run() {
describe('ParserCss', () => {
var obj;
let obj;
let config;
let customParser;
beforeEach(() => {
obj = new ParserCss();
config = {
em: {
getCustomParserCss: () => customParser
}
};
obj = new ParserCss(config);
});
afterEach(() => {
@ -17,26 +25,26 @@ module.exports = {
test('Parse selector', () => {
var str = '.test';
var result = [['test']];
expect(obj.parseSelector(str).result).toEqual(result);
expect(parseSelector(str).result).toEqual(result);
});
test('Parse selectors', () => {
var str = '.test1, .test1.test2, .test2.test3';
var result = [['test1'], ['test1', 'test2'], ['test2', 'test3']];
expect(obj.parseSelector(str).result).toEqual(result);
expect(parseSelector(str).result).toEqual(result);
});
test('Ignore not valid selectors', () => {
var str =
'.test1.test2, .test2 .test3, div > .test4, #test.test5, .test6';
var result = [['test1', 'test2'], ['test6']];
expect(obj.parseSelector(str).result).toEqual(result);
expect(parseSelector(str).result).toEqual(result);
});
test('Parse selectors with state', () => {
var str = '.test1. test2, .test2>test3, .test4.test5:hover';
var result = [['test4', 'test5:hover']];
expect(obj.parseSelector(str).result).toEqual(result);
expect(parseSelector(str).result).toEqual(result);
});
test('Parse simple rule', () => {
@ -48,7 +56,7 @@ module.exports = {
width: '50px'
}
};
expect(obj.parse(str)).toEqual(result);
expect(obj.parse(str)).toEqual([result]);
});
test('Parse rule with more selectors', () => {
@ -57,7 +65,7 @@ module.exports = {
selectors: ['test1', 'test2'],
style: { color: 'red', test: 'value' }
};
expect(obj.parse(str)).toEqual(result);
expect(obj.parse(str)).toEqual([result]);
});
test('Parse same rule with more selectors', () => {
@ -106,7 +114,7 @@ module.exports = {
style: { color: 'red' },
state: 'hover'
};
expect(obj.parse(str)).toEqual(result);
expect(obj.parse(str)).toEqual([result]);
});
test('Parse rule with state like after', () => {
@ -116,7 +124,7 @@ module.exports = {
style: { color: 'red' },
state: ':after'
};
expect(obj.parse(str)).toEqual(result);
expect(obj.parse(str)).toEqual([result]);
});
test('Parse rule with nth-x state', () => {
@ -126,7 +134,7 @@ module.exports = {
style: { color: 'red' },
state: 'nth-of-type(2n)'
};
expect(obj.parse(str)).toEqual(result);
expect(obj.parse(str)).toEqual([result]);
});
// Phantom don't find 'node.conditionText' so will skip it
@ -140,7 +148,7 @@ module.exports = {
state: 'hover',
mediaText: 'only screen and (max-width: 992px)'
};
expect(obj.parse(str)).toEqual(result);
expect(obj.parse(str)).toEqual([result]);
});
// Phantom don't find 'node.conditionText' so will skip it
@ -153,7 +161,7 @@ module.exports = {
state: 'hover',
mediaText: '(max-width: 992px)'
};
expect(obj.parse(str)).toEqual(result);
expect(obj.parse(str)).toEqual([result]);
});
// Phantom doesn't find 'node.conditionText' so will skip it
@ -190,7 +198,7 @@ module.exports = {
selectorsAdd: '.class1 .class2, div > .class3',
style: { color: 'red' }
};
expect(obj.parse(str)).toEqual(result);
expect(obj.parse(str)).toEqual([result]);
});
test('Parse rule with mixed selectors', () => {
@ -221,7 +229,7 @@ module.exports = {
width: '100px'
}
};
expect(obj.parse(str)).toEqual(result);
expect(obj.parse(str)).toEqual([result]);
});
test('Parse rule with CSS variables', () => {
@ -237,7 +245,7 @@ module.exports = {
'--some-width': '55px'
}
};
expect(obj.parse(str)).toEqual(result);
expect(obj.parse(str)).toEqual([result]);
});
// Can't test keyframes https://github.com/NV/CSSOM/issues/95
@ -274,7 +282,7 @@ module.exports = {
singleAtRule: 1,
style: { 'font-family': '"Open Sans"' }
};
expect(obj.parse(str)).toEqual(result);
expect(obj.parse(str)).toEqual([result]);
});
test('Parses multiple font-face at-rules', () => {
@ -316,7 +324,7 @@ module.exports = {
selectors: ['#test'],
style: { color: 'red' }
};
expect(obj.parse(str)).toEqual(result);
expect(obj.parse(str)).toEqual([result]);
});
test('Parse ID rule with state', () => {
@ -326,7 +334,7 @@ module.exports = {
state: 'hover',
style: { color: 'red' }
};
expect(obj.parse(str)).toEqual(result);
expect(obj.parse(str)).toEqual([result]);
});
test('Avoid composed selectors with ID', () => {
@ -336,7 +344,155 @@ module.exports = {
selectorsAdd: '#test.class, #test.class:hover',
style: { color: 'red' }
};
expect(obj.parse(str)).toEqual(result);
expect(obj.parse(str)).toEqual([result]);
});
test('Parse CSS with custom parser', () => {
var str = '.test1 { color:red }';
var result = {
selectors: ['test1'],
style: { color: 'blue' }
};
obj = new ParserCss({
parserCss: () => [result]
});
expect(obj.parse(str)).toEqual([result]);
});
test.skip('Parse CSS with custom async parser', async () => {
var str = '.test1 { color:red }';
var result = {
selectors: ['test1'],
style: { color: 'blue' }
};
obj = new ParserCss({
parserCss: async () => [result]
});
const cssResult = await obj.parse(str);
expect(cssResult).toEqual([result]);
});
test('Check node with font-face rule', () => {
const style = {
'font-family': '"Glyphicons Halflings"',
src:
'url("https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/fonts/glyphicons-halflings-regular.eot")'
};
expect(
obj.checkNode({
atRule: 'font-face',
selectors: '',
style: style
})
).toEqual([
{
style: style,
atRuleType: 'font-face',
singleAtRule: 1,
selectors: []
}
]);
});
test('Check node with keyframes rule', () => {
const style = { opacity: 0 };
expect(
obj.checkNode({
atRule: 'keyframes',
params: 'name',
selectors: 'from',
style: style
})
).toEqual([
{
selectors: [],
atRuleType: 'keyframes',
selectorsAdd: 'from',
style: style,
mediaText: 'name'
}
]);
});
test('Check node with media rule', () => {
const style = { color: 'blue' };
expect(
obj.checkNode({
atRule: 'media',
params: 'screen and (min-width: 480px)',
selectors: '.class-test.class2:hover, div > span ',
style
})
).toEqual([
{
atRuleType: 'media',
selectors: ['class-test', 'class2'],
selectorsAdd: 'div > span',
style: style,
state: 'hover',
mediaText: 'screen and (min-width: 480px)'
}
]);
});
test('Check node with a rule containing id', () => {
const style = { border: '1px solid black !important' };
expect(
obj.checkNode({
selectors: '#main:hover',
style
})
).toEqual([
{
selectors: ['#main'],
state: 'hover',
style: style
}
]);
});
test('Check node with multiple class selectors', () => {
const style = {
border: '1px solid black !important',
'background-repeat': 'repeat-y, no-repeat'
};
expect(
obj.checkNode({
selectors:
'.class1, .class1.class2:hover, div > .test:hover, span.test2',
style
})
).toEqual([
{
selectors: ['class1'],
style: style
},
{
selectors: ['class1', 'class2'],
state: 'hover',
selectorsAdd: 'div > .test:hover, span.test2',
style: style
}
]);
});
test('Check node with a rule containing CSS variables', () => {
const style = {
'--some-color': 'red',
'--some-width': '55px'
};
expect(
obj.checkNode({
selectors: ':root',
style
})
).toEqual([
{
selectors: [],
selectorsAdd: ':root',
style: style
}
]);
});
});
}

Loading…
Cancel
Save