j2c
Version:
1,081 lines (876 loc) • 26 kB
JavaScript
/*eslint-env node, mocha */
// used to normalize styles for reliable comparison.
var expect = require('expect.js'),
minifySelectors = require('postcss-minify-selectors'),
minifyParams = require('postcss-minify-params'),
perfectionist = require('perfectionist'),
postcss = require('postcss')([perfectionist({format:'compressed'}), minifySelectors(), minifyParams()])
function normalize(s) { return postcss.process(s).css }
function check(result, expected){
result = normalize(result)
// since you can't rely on the order of JS object keys, sometimes, several "expected"
// values must be provided.
// expected = (expected instanceof Array ? expected : [expected]).map(function(s){
// return normalize(s)
// })
expect(normalize(expected)).to.contain(result)
}
function randStr() {
return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5)
}
function randInt() {
return Math.random().toString().substr(2, 3)
}
function webkitify(decl) {return '-webkit-' + decl + '\n' + decl}
[
'../dist/j2c.commonjs',
'../dist/j2c.commonjs.min'
// ,
// '../dist/inline/j2c.commonjs',
// '../dist/inline/j2c.commonjs.min'
].forEach(function(lib){
var j2c = require(lib)
function checkinline(result, expected){
result = 'p{' + j2c.inline(result) + '}'
expected = (expected instanceof Array ? expected : [expected]).map(function(s){
return 'p{' + s + '}'
})
check(result, expected)
}
/////////////////////////////
/**/ suite('Inline: ') /**/
/////////////////////////////
test('a single property', function() {
checkinline(
{foo: 'bar'},
'foo:bar;'
)
})
test('two properties', function() {
checkinline(
{foo: 'bar', baz: 'qux'},
'foo:bar;baz:qux;'
)
})
test('two properties, ensure order', function() {
check(j2c.inline({foo: 'bar', baz: 'qux'}), 'foo:bar;\nbaz:qux;')
})
test('array of values', function() {
checkinline(
{foo:['bar', 'baz']},
'foo:bar;foo:baz;'
)
})
test('sub-properties', function(){
checkinline(
{foo: {bar: 'baz'}},
'foo-bar:baz;'
)
})
test('multiple sub-properties', function(){
checkinline(
{foo: {bar$qux: 'baz'}},
'foo-bar:baz;foo-qux:baz;'
)
})
test('multiple sub-properties, ensure order', function() {
check(j2c.inline({foo$baz: 'qux'}), 'foo:qux;\nbaz:qux;')
})
test('multiple sub-properties with a sub-sub-property', function(){
checkinline(
{foo: {bar$baz: {qux: 'quux'}}},
'foo-bar-qux:quux;foo-baz-qux:quux;'
)
})
test('multiple sub-properties with two sub-sub-properties', function(){
checkinline(
{foo: {bar$baz: {qux$quux: 'fred'}}},
'foo-bar-qux:fred;foo-bar-quux:fred;foo-baz-qux:fred;foo-baz-quux:fred;'
)
})
test('convert underscores', function() {
checkinline(
{'f_o_o': 'bar'},
'f-o-o:bar;'
)
})
test('convert CamelCase', function() {
checkinline(
{'FoO': 'bar'},
'-fo-o:bar;'
)
})
test('String value', function() {
checkinline(
'foo:bar',
'foo:bar;'
)
})
test('Array of Strings values', function() {
checkinline(
['foo:bar', 'foo:baz'],
'foo:bar;foo:baz'
)
})
test('Array of mixed values at the root', function() {
checkinline(
['foo:bar', {foo: 'baz'}],
'foo:bar;foo:baz'
)
})
test('Array of mixed value and sub-property', function() {
checkinline(
{foo:['bar', {baz: 'qux'}]},
'foo:bar;foo-baz:qux'
)
})
test('Prefixes by hand', function() {
checkinline(
{_o$_p$: {foo: 'bar'}},
'-o-foo:bar;-p-foo:bar;foo:bar;'
)
})
test('CSS *Hack', function() {
// tested manually because the crass normalization
// outputs an empty string.
check(j2c.inline({'*foo': 'bar'}), '*foo:bar;')
})
test('CSS _Hack', function() {
checkinline(
['_foo:bar', {_baz: 'qux'}],
'_foo:bar;-baz:qux;'
)
})
test('custom obj.valueOf', function() {
var bar = {valueOf:function(){return 'bar'}}
checkinline(
{foo:bar},
'foo:bar;'
)
})
////////////////////////////////////
/**/ suite('Inline, nulls: ') /**/
////////////////////////////////////
test('null value', function() {
checkinline(
null,
''
)
})
test('null leafs', function() {
checkinline(
{foo:null},
''
)
})
test('undefined leafs', function() {
checkinline(
{foo:void 8},
''
)
})
test('null value', function() {
checkinline(
null,
''
)
})
test('undefined value', function() {
checkinline(
void 8,
''
)
})
test('null in Array', function() {
checkinline(
[null],
''
)
})
test('undefined in Array', function() {
checkinline(
[void 8],
''
)
})
////////////////////////////////////////
/**/ suite('Inline namespaces: ') /**/
////////////////////////////////////////
test('namespaced animation', function() {
var result = j2c.inline({foo:'theFoo'}, {animation:'foo 1sec'})
check(result, webkitify('animation:theFoo 1sec;'))
})
test('namespaced animation-name', function() {
var result = j2c.inline({foo:'theFoo'}, {animation_name:'foo'})
check(result, webkitify('animation-name:theFoo;'))
})
test('namespaced and non-namespaced animation-name', function() {
var result = j2c.inline({foo:'theFoo'}, {animation_name:'foo, bar'})
check(result, webkitify('animation-name:theFoo, bar;'))
})
test('two namespaced animations', function() {
var result = j2c.inline({foo:'theFoo', bar:'theBar'}, {animation:'foo 1sec, bar 2sec'})
check(result, webkitify('animation:theFoo 1sec, theBar 2sec;'))
})
/////////////////////////////////////
/**/ suite('Inline plugins: ') /**/
/////////////////////////////////////
test('one plugin that does nothing', function() {
check(''+j2c().use(function(){}).inline(
{foo: 'bar'}
), 'foo:bar;')
})
test('one plugin that mutates the buffer', function() {
check(''+j2c().use(
function(buf){
buf[0] = buf[0].replace('f','k')
}
).inline(
{foo: 'bar'}
), 'koo:bar;')
})
test('one plugin that returns a new buffer', function() {
expect(''+j2c().use(
function(){
return ['hello:world;']
}
).inline(
{foo: 'bar'}
)).to.be('hello:world;')
})
test('two plugins that mutate the buffer', function() {
check(''+j2c().use(
function(buf){
buf[0]=buf[0].replace('f', 'a')
},
function(buf){
buf[0]=buf[0].replace('a', 'm')
}
).inline(
{foo: 'bar'}
), 'moo:bar;')
})
/////////////////////////////////
/**/ suite('j2c.prefix: ') /**/
/////////////////////////////////
test('1 x 1', function() {
var prod = j2c.prefix('foo', ['o'])
expect(prod[0]).to.be('-o-foo')
expect(prod[1]).to.be('foo')
})
test('2 x 1', function() {
var prod = j2c.prefix('foo', ['o', 'p'])
expect(prod[0]).to.be('-o-foo')
expect(prod[1]).to.be('-p-foo')
expect(prod[2]).to.be('foo')
})
});
['../dist/j2c.commonjs', '../dist/j2c.commonjs.min'].forEach(function(lib){
var j2c = require(lib)
////////////////////////////////
/**/ suite('j2c.sheet: ') /**/
////////////////////////////////
test('direct sheet call', function(){
check(
j2c.sheet({p: {foo:5}}),
'p{foo:5}'
)
})
//////////////////////////////////
/**/ suite('Definitions: ') /**/
//////////////////////////////////
test('basic', function() {
check(
j2c.sheet({p: {
foo: 'bar'
}}),
'p{foo:bar}'
)
})
test('convert underscores', function() {
check(
j2c.sheet({p: {
foo_foo: 'bar'
}}),
'p{foo-foo:bar}'
)
})
test('number values', function() {
check(
j2c.sheet({p: {
foo:5
}}),
'p{foo:5}'
)
})
test('composed property name', function() {
check(
j2c.sheet({p: {
foo: {bar: 'baz'}
}}),
'p{foo-bar:baz}'
)
})
test('composed selector : child with a given class', function() {
check(
j2c.sheet({'@global': {p: {
' .foo': {bar: 'baz'}
}}}),
'p .foo{bar:baz}'
)
})
test('composed selector: add a class to the root', function() {
check(
j2c.sheet({'@global': {p: {
'.foo': {bar: 'baz'}
}}}),
'p.foo{bar:baz}'
)
})
test('manual vendor prefixes', function() {
check(
j2c.sheet({p: {
_o$_ms$_moz$_webkit$: {foo: 'bar'}
}}),
'p {-o-foo:bar;-ms-foo:bar;-moz-foo:bar;-webkit-foo:bar;foo:bar}'
)
})
test('mixing definitions and sub-selectors', function() {
check(
j2c.sheet({'@global': {p: {
foo: 'bar',
' .foo': {bar: 'baz'}
}}}),
['p .foo{bar:baz} p {foo:bar}', 'p {foo:bar} p .foo{bar:baz}']
)
})
//////////////////////////////////////////////////
/**/ suite('Selector Cartesian product: ') /**/
//////////////////////////////////////////////////
test('1 x 2', function() {
check(
j2c.sheet({'@global': {p: {
' .foo': {
':before,:after': {
foo: 'bar'
}
}
}}}),
'p .foo:before, p .foo:after {foo:bar}'
)
})
test('2 x 1', function() {
check(
j2c.sheet({'@global': {p: {
' .foo, .bar': {
':before': {
foo: 'bar'
}
}
}}}),
'p .foo:before, p .bar:before {foo:bar}'
)
})
test('2 x 2', function() {
check(
j2c.sheet({'@global': {p: {
' .foo, .bar': {
':before,:after': {
foo: 'bar'
}
}
}}}),
'p .foo:before, p .bar:before, p .foo:after, p .bar:after {foo:bar}'
)
})
test('2 x 3 one of which is empty', function() {
check(
j2c.sheet({'@global': {p: {
' .foo, .bar': {
',:before,:after': {
foo: 'bar'
}
}
}}}),
'p .foo, p .bar, p .foo:before, p .bar:before, p .foo:after, p .bar:after {foo:bar}'
)
})
/////////////////////////////////
/**/ suite('Ampersand: ') /**/
//////////////////////////////////
test('composed selector: add a class to the root', function() {
check(
j2c.sheet({p: {
':global(.foo) &': {bar: 'baz'}
}}),
'.foo p{bar:baz}'
)
})
test('& &', function() {
check(
j2c.sheet({':global(.foo)': {
'& &': {
bar: 'baz'
}
}}),
'.foo .foo{bar:baz}'
)
})
test('2 x 2', function() {
check(
j2c.sheet({p: {
' :global(.foo), :global(.bar)': {
' :global(.baz) &, :global(.qux)': {
foo: 'bar'
}
}
}}),
'.baz p .foo,.baz p .bar,p .foo .qux ,p .bar .qux {foo:bar}'
)
})
//////////////////////////////////////////
/**/ suite('Strings and Arrays: ') /**/
//////////////////////////////////////////
test('String literal', function() {
check(
j2c.sheet({p: 'foo:bar'}),
'p{foo:bar}'
)
})
test('String literal with two declarations', function() {
check(
j2c.sheet({p: 'foo:bar;baz:qux'}),
'p {foo:bar;baz:qux}'
)
})
test('String literal starting with an underscore', function() {
check(
j2c.sheet({p: '_foo:bar'}),
'p {_foo:bar}'
)
})
test('Array of String literals', function() {
check(
j2c.sheet({p: ['foo:bar', 'foo:baz']}),
'p{foo:bar}p{foo:baz}'
)
})
test('overloaded properties', function() {
check(
j2c.sheet({p: {
foo:['bar', 'baz']
}}),
'p{foo:bar;foo:baz}'
)
})
test('overloaded sub-properties', function() {
check(
j2c.sheet({p: {
foo:[{bar: 'baz'}, {bar: 'qux'}]
}}),
'p{foo-bar:baz;foo-bar:qux}'
)
})
test('nested Arrays', function(){
check(
j2c.sheet({p: [
[
{bar: 'baz'},
{bar: 'qux'}
],
'bar:quux;'
]}),
'p{bar:baz}p{bar:qux}p{bar:quux}'
)
})
// ///////////////////////////////////////////
// /**/ suite("Sheet auto prefixes: "); /**/
// ///////////////////////////////////////////
// test("String literal", function() {
// check(
// j2c.sheet({" p": "foo:bar"}, {vendors: ["o", "p"]}),
// "p{-o-foo:bar;-p-foo:bar;foo:bar}"
// );
// });
// test("Array of Strings", function() {
// check(
// j2c.sheet({" p": ["foo:bar", "_baz:qux"]}, {vendors: ["o", "p"]}),
// "p{-o-foo:bar;-p-foo:bar;foo:bar;-o-_baz:qux;-p-_baz:qux;_baz:qux}"
// );
// });
////////////////////////////////
/**/ suite('At-rules: ') /**/
////////////////////////////////
test('standard at-rule with text value', function() {
check(
j2c.sheet({p: {
'@import': "'bar'"
}}),
"@import 'bar';"
)
})
test('standard at-rule with object value', function() {
check(
j2c.sheet({p: {
'@media foo': {bar: 'baz'}
}}),
'@media foo {p{bar:baz}}'
)
})
test('several at-rules with object value', function() {
check(
j2c.sheet({p: {
'@media foo': {bar: 'baz'},
'@media foo2': {bar2: 'baz2'}
}}),
[
'@media foo {p{bar:baz}} @media foo2 {p{bar2:baz2}}'
]
)
})
test('Array of at-rules with text values', function() {
check(
j2c.sheet({p: [
{'@import': "'bar'"},
{'@import': "'baz'"}
]}),
"@import 'bar'; @import 'baz';"
)
})
test('nested at-rules', function() {
check(
j2c.sheet({p: {'@media screen': {width:1000, '@media (max-width: 12cm)': {size:5}}}}),
[
'@media screen{p{width:1000}@media (max-width:12cm){p{size:5}}}'
]
)
})
test('@font-face', function(){
check(
j2c.sheet({p: {'@font-face': {foo: 'bar'}}}),
'@font-face{foo:bar}'
)
})
test('@keyframes', function(){
check(
j2c.sheet({p: {'@keyframes :global(qux)': {
' from': {foo: 'bar'},
' to': {foo: 'baz'}
}}}),
'@-webkit-keyframes qux{from{-webkit-foo:bar;foo:bar}to{-webkit-foo:baz;foo:baz}}' +
'@keyframes qux{from{foo:bar}to{foo:baz}}'
)
})
test('invalid @foo becomes at-foo property', function(){
check(
j2c.sheet({'@foo': 'bar'}),
'@-error-unsupported-at-rule "@foo";'
)
})
//////////////////////////////////////////////////
/**/ suite('At-rules with array values: ') /**/
//////////////////////////////////////////////////
test('@font-face with a 1-element array', function(){
check(
j2c.sheet({p: {'@font-face':[{foo: 'bar'}]}}),
'@font-face{foo:bar}'
)
})
test('@font-face with a 2-elements array', function(){
check(
j2c.sheet({p: {'@font-face':[{foo: 'bar'}, {foo: 'baz'}]}}),
'@font-face{foo:bar}@font-face{foo:baz}'
)
})
test('@namespace with a 1-element array', function(){
check(
j2c.sheet({'@namespace': ["'http://foo.example.com'"]}),
"@namespace 'http://foo.example.com';"
)
})
test('@namespace with a 2-elements array', function(){
check(
j2c.sheet({'@namespace': ["'http://foo.example.com'", "bar 'http://bar.example.com'"]}),
"@namespace 'http://foo.example.com';@namespace bar 'http://bar.example.com';"
)
})
/////////////////////////////////////
/**/ suite('Locals, Globals ') /**/
/////////////////////////////////////
test('a local class', function(){
var css = j2c.sheet({'.bit': {foo:5}})
expect(css.bit.slice(0, 8)).to.be('bit_j2c_')
expect(css + '').to.contain('.' + css.bit + ' {\nfoo:5;\n}')
})
test('two local classes', function(){
var css = j2c.sheet({'.bit': {foo:5}, '.bat': {bar:6}})
expect(css.bit.slice(0, 8)).to.be('bit_j2c_')
expect(css.bit.slice(4)).to.be(css.bat.slice(4))
expect(css + '').to.contain('.' + css.bit + ' {\nfoo:5;\n}')
expect(css + '').to.contain('.' + css.bat + ' {\nbar:6;\n}')
})
test('a local and a global class', function(){
var css = j2c.sheet({'.bit': {foo:5}, ':global(.bat)': {bar:6}})
expect(css.bit.slice(0, 8)).to.be('bit_j2c_')
expect(css.bat).to.be(undefined)
expect(css + '').to.contain('.' + css.bit + ' {\nfoo:5;\n}')
expect(css + '').to.contain('.bat {\nbar:6;\n}')
})
test('a local wrapping a global block', function(){
var css = j2c.sheet({'.bit': {'@global': {'.bat': {foo:5}}}})
expect(css.bit.slice(0, 8)).to.be('bit_j2c_')
expect(css.bat).to.be(undefined)
expect(css + '').to.contain('.' + css.bit + '.bat {\nfoo:5;\n}')
})
test('two local classes, nested', function(){
var css = j2c.sheet({'.bit': {foo:5, '.bat': {bar:6}}})
expect(css.bit.slice(0, 8)).to.be('bit_j2c_')
expect(css.bit.slice(4)).to.be(css.bat.slice(4))
expect(css + '').to.contain('.' + css.bit + ' {\nfoo:5;\n}')
expect(css + '').to.contain('.' + css.bit +'.' + css.bat + ' {\nbar:6;\n}')
})
test('@keyframes', function(){
var css = j2c.sheet({'@keyframes bit': {}})
expect(css.bit.slice(0, 8)).to.be('bit_j2c_')
expect(css + '').to.contain('@keyframes ' + css.bit +' {')
})
test('a global @keyframes', function() {
var css = j2c.sheet({'@keyframes :global(bit)': {}})
expect(css.bit).to.be(undefined)
expect(css + '').to.contain('@keyframes bit {')
})
test('a @keyframe nested in a @global at-rule', function() {
var css = j2c.sheet({'@global': {'@keyframes bat': {'from':{foo:6}}}})
expect(css.bat).to.be(undefined)
expect(css + '').to.contain('@keyframes bat {')
})
test('one animation', function(){
var css = j2c.sheet({p: {animation: 'bit 1sec'}})
expect(css.bit.slice(0, 8)).to.be('bit_j2c_')
expect(css + '').to.contain('animation:' + css.bit +' ')
})
test('a global animation', function() {
var css = j2c.sheet({p: {animation: ':global(bit) 1sec'}})
expect(css.bit).to.be(undefined)
expect(css + '').to.contain('animation:bit ')
})
test('an animation nested in a @global at-rule', function() {
var css = j2c.sheet({'@global': {p: {animation: 'bit 1sec'}}})
expect(css.bit).to.be(undefined)
expect(css + '').to.contain('animation:bit ')
})
test('one animation-name', function() {
var css = j2c.sheet({p: {animation_name: 'bit'}})
expect(css.bit.slice(0, 8)).to.be('bit_j2c_')
expect(css + '').to.contain('animation-name:' + css.bit +';')
})
test('two animation-name', function() {
var css = j2c.sheet({p: {animation_name: 'bit, bat'}})
expect(css.bit.slice(0, 8)).to.be('bit_j2c_')
expect(css.bit.slice(4)).to.be(css.bat.slice(4))
expect(css + '').to.contain('animation-name:' + css.bit +', ' + css.bat)
})
test('two animation-name, one global', function() {
var css = j2c.sheet({p: {animation_name: 'bit, :global(bat)'}})
expect(css.bit.slice(0, 8)).to.be('bit_j2c_')
expect(css.bat).to.be(undefined)
expect(css + '').to.contain('animation-name:' + css.bit +', bat;')
})
test('a nested @global at-rule', function() {
var css = j2c.sheet({'.bit': {'@global': {'.bat': {'foo':6}}}})
expect(css.bit.slice(0, 8)).to.be('bit_j2c_')
expect(css.bat).to.be(undefined)
expect(css + '').to.contain( css.bit +'.bat {')
})
///////////////////////////////////
/**/ suite('Foolproofing: ') /**/
///////////////////////////////////
test('property-like sub-selector', function() {
var sheet = j2c.sheet({'.foo': {'g,p': {animation_name: 'bit, bat'}}})
expect(''+sheet).to.contain(':-error-bad-sub-selector-g,:-error-bad-sub-selector-p')
})
////////////////////////////
/**/ suite('Order: ') /**/
////////////////////////////
test('two properties', function() {
check(j2c.sheet({p: {foo: 'bar', baz: 'qux'}}), 'p {\nfoo:bar;\nbaz:qux;\n}')
})
test('$ combiner', function() {
check(j2c.sheet({p: {foo$baz: 'qux'}}), 'p {\nfoo:qux;\nbaz:qux;\n}')
})
// This was built with the assumption that
// objects are totally unordered which isn't true in JS
// As long as no properties are deleted and created again,
// the insertion order is the iteration order in all
// browsers and in node too.
test('subselector > declaration > @media', function(){
var prop = randStr()
var klass = randStr()
var width = randInt()
var permutations = [
[0, 1, 2],
[1, 2, 0],
[2, 0, 1],
[2, 1, 0],
[0, 2, 1],
[1, 0, 2]
]
var jsKeys = [
prop,
'.' + klass,
'@media (min-width: ' + width + 'em)'
]
var o = {}
o[prop] = 5
o['.' + klass] = {foo:6}
o['@media (min-width: ' + width + 'em)'] = {bar:7}
var rules = [
'p{' + prop + ':5;}',
'p.' + klass + '{foo:6;}',
'@media (min-width: ' + width + 'em){p{bar:7;}}'
]
permutations.forEach(function(indices) {
var source = {p:{}}
var CSS = []
indices.forEach(function(i){
source.p[jsKeys[i]] = o[jsKeys[i]]
CSS.push(rules[i])
})
expect(normalize(j2c.sheet({'@global': source}))).to.be(normalize(CSS.join('')))
})
})
test('@namespace then selector', function() {
check(j2c.sheet({
'@namespace': "'foo'",
p: {foo: 'bar'}
}), "@namespace 'foo';p{foo:bar;}")
})
/////////////////////////////////
/**/ suite('Namespaces: ') /**/
/////////////////////////////////
test('namespaced class', function() {
var css = j2c.sheet(
{foo: 'FOO'},
{'.foo': {foo: 'bar', baz: 'qux'}}
)
check('' + css, '.FOO{foo:bar;baz:qux;}')
expect(css.foo).to.be('FOO')
})
test('namespaced class wrapping a global block', function() {
var css = j2c.sheet(
{foo: 'FOOO'},
{'.foo': {'@global': {'.foo': {foo: 'bar', baz: 'qux'}}}}
)
check('' + css, '.FOOO.foo{foo:bar;baz:qux;}')
expect(css.foo).to.be('FOOO')
})
test('namespaced @keyframes', function(){
var css = j2c.sheet(
{bit: 'BOT'},
{'@keyframes bit': {}}
)
expect(css.bit).to.be('BOT')
expect(css + '').to.contain('@keyframes BOT {')
})
test('namespaced animation', function(){
var css = j2c.sheet(
{bit: 'BOT'},
{p: {animation: 'bit 1sec'}}
)
expect(css.bit).to.be('BOT')
check(css + '', 'p{' + webkitify('animation:BOT 1sec;') + '}')
})
test('namespaced animation-name', function() {
var css = j2c.sheet(
{bit: 'BOT'},
{p: {animation_name: 'bit'}}
)
expect(css.bit).to.be('BOT')
check(css + '', 'p{' + webkitify('animation-name:BOT;') + '}')
})
/////////////////////////////
/**/ suite('extend: ') /**/
/////////////////////////////
test('local extend', function() {
var css = j2c.sheet({'.bit': {'@extend':'.bat'}})
expect(css.bit).to.contain('bit_j2c_')
expect(css.bat).to.contain('bat_j2c_')
expect(css.bat).not.to.contain('bit_j2c_')
expect(css.bit).to.contain(css.bat + ' ')
})
test('global extend', function() {
var css = j2c.sheet({'.bit': {'@extend':':global(.bat)'}})
expect(css.bit).to.contain('bit_j2c_')
expect(css.bit).to.contain('bat ')
})
test('two local extends', function() {
var css = j2c.sheet({'.bit': {'@extends':['.bat', '.bot']}})
expect(css.bit).to.contain('bit_j2c_')
expect(css.bat).to.contain('bat_j2c_')
expect(css.bot).to.contain('bot_j2c_')
expect(css.bat).not.to.contain('bit_j2c_')
expect(css.bot).not.to.contain('bit_j2c_')
expect(css.bit).to.contain(css.bat + ' ' + css.bot + ' ')
})
test('extend applies only to the last class in the selector', function() {
var css = j2c.sheet({'.bot p .bit': {'@extend':'.bat'}})
expect(css.bit).to.contain('bit_j2c_')
expect(css.bat).to.contain('bat_j2c_')
expect(css.bot).to.contain('bot_j2c_')
expect(css.bat).not.to.contain('bit_j2c_')
expect(css.bit).to.contain(css.bat + ' ')
expect(css.bot).not.to.contain(css.bat + ' ')
})
test("if we can't find a class to extend, pass @extend as a property", function() {
var css = j2c.sheet({'p > a': {'@extend':'.bat'}})
expect(css.bat).to.be(undefined)
expect('' + css).to.contain('@-error-no-class-to-extend-in "p > a";')
})
test("extend doesn't extend into global selectors", function() {
// @extend thus extends the last local class in the stream
var css = j2c.sheet({'.bit :global(.bot)': {'@extend':'.bat'}})
expect(css.bit).to.contain('bit_j2c_')
expect(css.bat).to.be(undefined)
expect(css.bot).to.be(undefined)
expect(css.bit).not.to.contain(css.bat + ' ')
expect('' + css).to.contain('@-error-cannot-extend-in-global-context ".bit :global(.bot)";')
})
//////////////////////////////
/**/ suite('Plugins: ') /**/
//////////////////////////////
test('one plugin that does nothing', function() {
check(''+j2c().use(function(){}).sheet(
{p: {foo: 'bar'}}
), 'p{foo:bar;}')
})
test('one plugin that mutates the buffer', function() {
check(''+j2c().use(
function(buf){
buf[0] = 'li'
}
).sheet(
{p: {foo: 'bar'}}
), 'li{foo:bar;}')
})
test('one plugin that returns a new buffer', function() {
check(''+j2c().use(
function(){
return ['li{foo:bar;}']
}
).sheet(
{p: {foo: 'bar'}}
), 'li{foo:bar;}')
})
test('two plugins that mutate the buffer', function() {
check(''+j2c().use(
function(buf){
buf[0]=buf[0].replace('p', 'a')
},
function(buf){
buf[0]=buf[0].replace('a', 'i')
}
).sheet(
{p: {fop: 'bar'}}
), 'i{fop:bar;}')
})
})