UNPKG

aphrodite

Version:

Framework-agnostic CSS-in-JS with support for server-side rendering, browser prefixing, and minimum CSS generation

400 lines (357 loc) 12.5 kB
import {assert} from 'chai'; import OrderedElements from '../src/ordered-elements'; import { generateCSSRuleset, generateCSS, defaultSelectorHandlers } from '../src/generate'; describe('generateCSSRuleset', () => { const assertCSSRuleset = (selector, declarations, expected) => { const orderedDeclarations = new OrderedElements(); Object.keys(declarations).forEach((key) => { orderedDeclarations.set(key, declarations[key]); }); const actual = generateCSSRuleset(selector, orderedDeclarations); const expectedNormalized = expected.split('\n').map(x => x.trim()).join(''); const formatStyles = (styles) => styles.replace(/(;|{|})/g, '$1\n'); assert.equal( actual, expectedNormalized, ` Expected: ${formatStyles(expectedNormalized)} Actual: ${formatStyles(actual)} ` ); }; it('returns a CSS string for a single property', () => { assertCSSRuleset('.foo', { color: 'red' }, '.foo{color:red !important;}'); }); it('returns a CSS string for multiple properties', () => { assertCSSRuleset('.foo', { color: 'red', background: 'blue' }, `.foo{ color:red !important; background:blue !important; }`); }); it('converts camelCase to kebab-case', () => { assertCSSRuleset('.foo', { backgroundColor: 'red' }, '.foo{background-color:red !important;}'); }); it('prefixes vendor props with a dash', () => { assertCSSRuleset('.foo', { transition: 'none' }, '.foo{-webkit-transition:none !important;' + '-moz-transition:none !important;' + 'transition:none !important;' + '}'); }); it('converts ms prefix to -ms-', () => { assertCSSRuleset('.foo', { MsTransition: 'none' }, '.foo{-ms-transition:none !important;}'); }); it('returns an empty string if no props are set', () => { assertCSSRuleset('.foo', {}, ''); }); it('correctly adds px to number units', () => { assertCSSRuleset('.foo', { width: 10, zIndex: 5 }, '.foo{width:10px !important;z-index:5 !important;}'); }); it("doesn't break content strings which contain semicolons during importantify", () => { assertCSSRuleset('.foo', { content: '"foo;bar"' }, '.foo{content:"foo;bar" !important;}'); }); it("doesn't break quoted url() arguments during importantify", () => { assertCSSRuleset('.foo', { background: 'url("data:image/svg+xml;base64,myImage")' }, '.foo{background:url("data:image/svg+xml;base64,myImage") !important;}'); }); it("doesn't break unquoted url() arguments during importantify", () => { assertCSSRuleset('.foo', { background: 'url(data:image/svg+xml;base64,myImage)' }, '.foo{background:url(data:image/svg+xml;base64,myImage) !important;}'); }); it("doesn't importantify rules that are already !important", () => { assertCSSRuleset('.foo', { color: 'blue !important', }, '.foo{color:blue !important;}'); }); }); describe('generateCSS', () => { const assertCSS = (className, styleTypes, expected, selectorHandlers = [], stringHandlers = {}, useImportant = true) => { const actual = generateCSS(className, styleTypes, selectorHandlers, stringHandlers, useImportant); const expectedArray = [].concat(expected); const expectedNormalized = expectedArray.map(rule => rule.split('\n').map(x => x.trim()).join('')); const formatStyles = (styles) => styles.map(style => style.replace(/(;|{|})/g, '$1\n')).join(''); assert.deepEqual( actual, expectedNormalized, ` Expected: ${formatStyles(expectedNormalized)} Actual: ${formatStyles(actual)} ` ); }; it('returns a CSS string for a single property', () => { assertCSS('.foo', [{ color: 'red' }], '.foo{color:red !important;}'); }); it('works with Map', () => { assertCSS('.foo', [new Map([ ['color', 'red'] ])], '.foo{color:red !important;}'); }); it('works with two Maps', () => { assertCSS('.foo', [ new Map([ ['color', 'red'] ]), new Map([ ['color', 'blue'] ]), ], '.foo{color:blue !important;}'); }); it('implements override logic', () => { assertCSS('.foo', [{ color: 'red' }, { color: 'blue' }], '.foo{color:blue !important;}'); }); it('does not mutate nested objects', () => { const styles = { a: { ':after': { content: 'a', } }, b: { ':after': { content: 'b', } } }; generateCSS('.foo', [styles.a, styles.b], [], {}, true); assert.equal(styles.a[':after'].content, 'a'); assert.equal(styles.b[':after'].content, 'b'); }); it('supports pseudo selectors', () => { assertCSS('.foo', [{ ':hover': { color: 'red' } }], '.foo:hover{color:red !important;}', defaultSelectorHandlers); }); it('works with a nested Map', () => { assertCSS('.foo', [{ ':hover': new Map([ ['color', 'red'], ]) }], '.foo:hover{color:red !important;}', defaultSelectorHandlers); }); it('works with two nested Maps', () => { assertCSS('.foo', [ {':hover': new Map([ ['color', 'red'], ])}, {':hover': new Map([ ['color', 'blue'], ])} ], '.foo:hover{color:blue !important;}', defaultSelectorHandlers); }); it('supports media queries', () => { assertCSS('.foo', [{ "@media (max-width: 400px)": { color: "blue" } }], `@media (max-width: 400px){ .foo{color:blue !important;} }`, defaultSelectorHandlers); }); it('supports pseudo selectors inside media queries', () => { assertCSS('.foo', [{ "@media (max-width: 400px)": { ":hover": { color: "blue" } } }], `@media (max-width: 400px){ .foo:hover{color:blue !important;} }`, defaultSelectorHandlers); }); it('vendor prefixes in pseudo selectors inside media queries', () => { assertCSS('.foo', [{ "@media (max-width: 400px)": { ":hover": { transform: "translateX(0)" } } }], `@media (max-width: 400px){ .foo:hover{ -webkit-transform:translateX(0) !important; -ms-transform:translateX(0) !important; transform:translateX(0) !important; } }`, defaultSelectorHandlers); }); it('supports combining pseudo selectors inside media queries', () => { assertCSS('.foo', [ {"@media (max-width: 400px)": { ":hover": { background: "blue", color: "blue" } }}, {"@media (max-width: 400px)": { ":hover": { color: "red" } }} ], `@media (max-width: 400px){ .foo:hover{ background:blue !important; color:red !important; } }`, defaultSelectorHandlers); }); it('orders overrides in the expected way', () => { assertCSS('.foo', [ { "@media (min-width: 400px)": { padding: 10, } }, { "@media (min-width: 200px)": { padding: 20, }, "@media (min-width: 400px)": { padding: 30, } } ], [ `@media (min-width: 200px){ .foo{ padding:20px !important; } }`, `@media (min-width: 400px){ .foo{ padding:30px !important; } }`], defaultSelectorHandlers); }); it('supports custom string handlers', () => { assertCSS('.foo', [{ fontFamily: ["Helvetica", "sans-serif"] }], '.foo{font-family:Helvetica, sans-serif !important;}', [], { fontFamily: (val) => val.join(", "), }); }); it('make it possible to disable !important', () => { assertCSS('@font-face', [{ fontFamily: ["FontAwesome"], fontStyle: "normal", }], '@font-face{font-family:FontAwesome;font-style:normal;}', defaultSelectorHandlers, { fontFamily: (val) => val.join(", "), }, false); }); it('adds browser prefixes', () => { assertCSS('.foo', [{ display: 'flex', transition: 'all 0s', alignItems: 'center', WebkitAlignItems: 'center', justifyContent: 'center', }], '.foo{' + '-webkit-box-pack:center !important;' + '-ms-flex-pack:center !important;' + '-webkit-box-align:center !important;' + '-ms-flex-align:center !important;' + 'display:-webkit-box !important;' + 'display:-moz-box !important;' + 'display:-ms-flexbox !important;' + 'display:-webkit-flex !important;' + 'display:flex !important;' + '-webkit-transition:all 0s !important;' + '-moz-transition:all 0s !important;' + 'transition:all 0s !important;' + 'align-items:center !important;' + '-webkit-align-items:center !important;' + '-webkit-justify-content:center !important;' + 'justify-content:center !important;' + '}', defaultSelectorHandlers); }); it('supports other selector handlers', () => { const handler = (selector, baseSelector, callback) => { if (selector[0] !== '^') { return null; } return callback(`.${selector.slice(1)} ${baseSelector}`); }; assertCSS('.foo', [{ '^bar': { color: 'red', }, color: 'blue', }], ['.foo{color:blue;}','.bar .foo{color:red;}'], [handler], {}, false); }); it('supports selector handlers that return strings containing multiple rules', () => { const handler = (selector, baseSelector, callback) => { if (selector[0] !== '^') { return null; } const generatedBefore = callback(baseSelector + '::before'); const generatedAfter = callback(baseSelector + '::after'); return `${generatedBefore} ${generatedAfter}`; }; assertCSS('.foo', [{ '^': { color: 'red', }, }], ['@media all {.foo::before{color:red;} .foo::after{color:red;}}'], [handler], {}, false); }); it('correctly prefixes border-color transition properties', () => { assertCSS('.foo', [{ 'transition': 'border-color 200ms linear' }], '.foo{' + '-webkit-transition:border-color 200ms linear !important;' + '-moz-transition:border-color 200ms linear !important;' + 'transition:border-color 200ms linear !important;' + '}'); }); it('correctly prefixes flex properties for IE10 support', () => { assertCSS('.foo', [{ 'flex': 'auto' }], '.foo{' + '-webkit-flex:auto !important;' + '-ms-flex:1 1 auto !important;' + 'flex:auto !important;' + '}'); }); // TODO(emily): In the future, filter out null values. it('handles nullish values', () => { assertCSS('.foo', [{ 'color': null, 'margin': undefined, }], '.foo{' + 'color:null !important;' + 'margin:undefined !important;' + '}'); }); });