var CssSyntaxError = require('../lib/css-syntax-error'); var Declaration = require('../lib/declaration'); var AtRule = require('../lib/at-rule'); var parse = require('../lib/parse'); var Node = require('../lib/node'); var Root = require('../lib/root'); var Rule = require('../lib/rule'); var expect = require('chai').expect; describe('Node', () => { describe('error()', () => { it('generates custom error', () => { var css = parse('a{}', { from: '/a.css' }); var error = css.first.error('Test'); expect(error).to.be.instanceOf(CssSyntaxError); expect(error.message).to.eql('/a.css:1:1: Test'); }); it('generates custom error for nodes without source', () => { var rule = new Rule({ selector: 'a' }); var error = rule.error('Test'); expect(error.message).to.eql(': Test'); }); }); describe('removeSelf()', () => { it('removes node from parent', () => { var rule = new Rule({ selector: 'a' }); var decl = new Declaration({ prop: 'color', value: 'black' }); rule.append(decl); decl.removeSelf(); expect(rule.nodes).to.be.empty(); expect(decl.parent).to.not.exist(); }); }); describe('replace()', () => { it('inserts new node', () => { var rule = new Rule({ selector: 'a' }); rule.append({ prop: 'color', value: 'black' }); rule.append({ prop: 'width', value: '1px' }); rule.append({ prop: 'height', value: '1px' }); var node = new Declaration({ prop: 'min-width', value: '1px' }); var width = rule.nodes[1]; var result = width.replace(node); expect(result).to.eql(width); expect(rule.toString()).to.eql('a {\n' + ' color: black;\n' + ' min-width: 1px;\n' + ' height: 1px\n' + '}'); }); it('inserts new root', () => { var root = new Root(); root.append( new AtRule({ name: 'import', params: '"a.css"' }) ); var a = new Root(); a.append( new Rule({ selector: 'a' }) ); a.append( new Rule({ selector: 'b' }) ); root.first.replace(a); expect(root.toString()).to.eql('a {}\nb {}'); }); }); describe('clone()', () => { it('clones nodes', () => { var rule = new Rule({ selector: 'a', after: '' }); rule.append({ prop: 'color', value: '/**/black', before: '' }); var clone = rule.clone(); expect(clone.parent).to.not.exist(); expect(rule.first.parent).to.equal(rule); expect(clone.first.parent).to.equal(clone); clone.append({ prop: 'z-index', value: '1' }); expect(rule.nodes.length).to.equal(1); }); it('overrides properties', () => { var rule = new Rule({ selector: 'a' }); var clone = rule.clone({ selector: 'b' }); expect(clone.selector).to.eql('b'); }); it('cleans code style', () => { var css = parse('@page 1{a{color:black;}}'); expect(css.clone().toString()).to.eql('@page 1 {\n' + ' a {\n' + ' color: black\n' + ' }\n' + '}'); }); }); describe('cloneBefore()', () => { it('clones and insert before current node', () => { var rule = new Rule({ selector: 'a', after: '' }); rule.append({ prop: 'z-index', value: '1', before: '' }); var result = rule.first.cloneBefore({ value: '2' }); expect(result).to.equal(rule.first); expect(rule.toString()).to.eql('a {z-index: 2;z-index: 1}'); }); }); describe('cloneAfter()', () => { it('clones and insert after current node', () => { var rule = new Rule({ selector: 'a', after: '' }); rule.append({ prop: 'z-index', value: '1', before: '' }); var result = rule.first.cloneAfter({ value: '2' }); expect(result).to.equal(rule.last); expect(rule.toString()).to.eql('a {z-index: 1;z-index: 2}'); }); }); describe('next()', () => { it('returns next node', () => { var css = parse('a{one:1;two:2}'); expect(css.first.first.next()).to.equal(css.first.last); expect(css.first.last.next()).to.not.exist(); }); }); describe('prev()', () => { it('returns previous node', () => { var css = parse('a{one:1;two:2}'); expect(css.first.last.prev()).to.equal(css.first.first); expect(css.first.first.prev()).to.not.exist(); }); }); describe('replaceWith()', () => { it('replaces node', () => { var css = parse('a{one:1;two:2}'); var decl = { prop: 'fix', value: 'fixed' }; var result = css.first.first.replaceWith(decl); expect(result.prop).to.eql('one'); expect(result.parent).to.not.exist(); expect(css.toString()).to.eql('a{fix:fixed;two:2}'); }); }); describe('moveTo()', () => { it('moves node between roots', () => { var css1 = parse('a{one:1}b{two:2}'); var css2 = parse('c {\n thr: 3\n}'); css1.first.moveTo(css2); expect(css1.toString()).to.eql('b{two:2}'); expect(css2.toString()).to.eql('c {\n thr: 3\n}\na {\n one: 1\n}'); }); it('moves node inside one root', () => { var css = parse('a{\n one:1}\n@page {\n b {\n two: 2\n }\n}'); css.first.moveTo(css.last); expect(css.toString()) .to.eql('@page {\n b {\n two: 2\n }\n a{\n one:1\n }\n}'); }); }); describe('moveBefore()', () => { it('moves node between roots', () => { var css1 = parse('a{one:1}b{two:2}'); var css2 = parse('c {\n thr: 3\n}'); css1.first.moveBefore(css2.first); expect(css1.toString()).to.eql('b{two:2}'); expect(css2.toString()).to.eql('a {\n one: 1\n}\nc {\n thr: 3\n}'); }); it('moves node inside one root', () => { var css = parse('a{\n one:1}\n@page {\n b {\n two: 2\n }\n}'); css.first.moveBefore(css.last.first); expect(css.toString()) .to.eql('@page {\n a{\n one:1\n }\n b {\n two: 2\n }\n}'); }); }); describe('moveAfter()', () => { it('moves node between roots', () => { var css1 = parse('a{one:1}b{two:2}'); var css2 = parse('c {\n thr: 3\n}'); css1.first.moveAfter(css2.first); expect(css1.toString()).to.eql('b{two:2}'); expect(css2.toString()).to.eql('c {\n thr: 3\n}\na {\n one: 1\n}'); }); it('moves node inside one root', () => { var css = parse('a{\n one:1}\n@page {\n b {\n two: 2\n }\n}'); css.first.moveAfter(css.last.first); expect(css.toString()) .to.eql('@page {\n b {\n two: 2\n }\n a{\n one:1\n }\n}'); }); }); describe('toJSON()', () => { it('cleans parents inside', () => { var rule = new Rule({ selector: 'a' }); rule.append({ prop: 'color', value: 'b' }); var json = rule.toJSON(); expect(json.parent).to.not.exist(); expect(json.nodes[0].parent).to.not.exist(); expect(JSON.stringify(rule)).to.eql( '{"type":"rule","nodes":[' + '{"type":"decl","prop":"color","value":"b"}' + '],"selector":"a"}'); }); }); describe('style()', () => { it('uses node style', () => { var rule = new Rule({ selector: 'a', between: '\n' }); expect(rule.style('between', 'beforeOpen')).to.eql('\n'); }); it('hacks before for nodes without parent', () => { var rule = new Rule({ selector: 'a' }); expect(rule.style('before')).to.eql(''); }); it('hacks before for first node', () => { var root = new Root(); root.append(new Rule({ selector: 'a' })); expect(root.first.style('before')).to.eql(''); }); it('hacks before for first decl', () => { var decl = new Declaration({ prop: 'color', value: 'black' }); expect(decl.style('before')).to.eql(''); var rule = new Rule({ selector: 'a' }); rule.append(decl); expect(decl.style('before')).to.eql('\n '); }); it('detects after style', () => { var root = new Root(); root.append({ selector: 'a', after: ' ' }); root.first.append({ prop: 'color', value: 'black' }); root.append({ selector: 'a' }); expect(root.last.style('after')).to.eql(' '); }); it('uses defaults without parent', () => { var rule = new Rule({ selector: 'a' }); expect(rule.style('between', 'beforeOpen')).to.eql(' '); }); it('uses defaults for unique node', () => { var root = new Root(); root.append(new Rule({ selector: 'a' })); expect(root.first.style('between', 'beforeOpen')).to.eql(' '); }); it('clones style from first node', () => { var root = new Root(); root.append( new Rule({ selector: 'a', between: '' }) ); root.append( new Rule({ selector: 'b' }) ); expect(root.last.style('between', 'beforeOpen')).to.eql(''); }); it('indents by default', () => { var root = new Root(); root.append( new AtRule({ name: 'page' }) ); root.first.append( new Rule({ selector: 'a' }) ); root.first.first.append({ prop: 'color', value: 'black' }); expect(root.toString()).to.eql('@page {\n' + ' a {\n' + ' color: black\n' + ' }\n' + '}'); }); it('clones indent', () => { var compress = parse('@page{ a{ } }'); var spaces = parse('@page {\n a {\n }\n}'); compress.first.first.append({ prop: 'color', value: 'black' }); expect(compress.toString()).to.eql('@page{ a{ color: black } }'); spaces.first.first.append({ prop: 'color', value: 'black' }); expect(spaces.toString()) .to.eql('@page {\n a {\n color: black\n }\n}'); }); it('clones indent by types', () => { var css = parse('a {\n color: black\n}\n\nb {\n}'); css.append(new Rule({ selector: 'em' })); css.last.append({ prop: 'z-index', value: '1' }); expect(css.last.toString()).to.eql('\n\nem {\n z-index: 1\n}'); }); it('clones indent by before and after', () => { var css = parse('@page{\n\n a{\n color: black}}'); css.first.append(new Rule({ selector: 'b' })); css.first.last.append({ prop: 'z-index', value: '1' }); expect(css.first.last.toString()).to.eql('\n\n b{\n z-index: 1}'); }); it('clones semicolon only from rules with children', () => { var css = parse('a{}b{one:1;}'); expect(css.first.style('semicolon')).to.be.true; }); it('clones only spaces in before', () => { var css = parse('a{*one:1}'); css.first.append({ prop: 'two', value: '2' }); css.append({ name: 'keyframes', params: 'a' }); css.last.append({ selector: 'from'}); expect(css.toString()) .to.eql('a{*one:1;two:2}\n@keyframes a{\nfrom{}}'); }); it('clones only spaces in between', () => { var css = parse('a{one/**/:1}'); css.first.append({ prop: 'two', value: '2' }); expect(css.toString()).to.eql('a{one/**/:1;two:2}'); }); }); describe('root()', () => { it('returns root', () => { var css = parse('@page{a{color:black}}'); expect(css.first.first.first.root()).to.equal(css); }); it('returns parent of parents', () => { var rule = new Rule({ selector: 'a' }); rule.append({ prop: 'color', value: 'black' }); expect(rule.first.root()).to.equal(rule); }); it('returns self on root', () => { var rule = new Rule({ selector: 'a' }); expect(rule.root()).to.equal(rule); }); }); describe('cleanStyles()', () => { it('cleans style recursivelly', () => { var css = parse('@page{a{color:black}}'); css.cleanStyles(); expect(css.toString()) .to.eql('@page {\n a {\n color: black\n }\n}'); expect(css.first.before).to.not.exist(); expect(css.first.first.firstbefore).to.not.exist(); expect(css.first.between).to.not.exist(); expect(css.first.first.first.between).to.not.exist(); expect(css.first.after).to.not.exist(); }); it('keeps between on request', () => { var css = parse('@page{a{color:black}}'); css.cleanStyles(true); expect(css.toString()) .to.eql('@page{\n a{\n color:black\n }\n}'); expect(css.first.before).to.not.exist(); expect(css.first.first.firstbefore).to.not.exist(); expect(css.first.between).to.exist(); expect(css.first.first.first.between).to.exist(); expect(css.first.after).to.not.exist(); }); }); describe('stringifyRaw()', () => { it('creates trimmed/raw property', () => { var b = new Node(); b.one = 'trim'; b._one = { value: 'trim', raw: 'raw' }; expect(b.stringifyRaw('one')).to.eql('raw'); b.one = 'trim1'; expect(b.stringifyRaw('one')).to.eql('trim1'); }); it('works without magic', () => { var b = new Node(); b.one = '1'; expect(b.one).to.eql('1'); expect(b.stringifyRaw('one')).to.eql('1'); }); }); });