UNPKG

aphrodite

Version:

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

611 lines (494 loc) 16.4 kB
import asap from 'asap'; import { assert } from 'chai'; import { JSDOM } from 'jsdom'; import { StyleSheet, StyleSheetServer, StyleSheetTestUtils, minify, css } from '../src/index.js'; import { reset } from '../src/inject.js'; import { getSheetText } from './testUtils.js'; describe('css', () => { beforeEach(() => { global.document = new JSDOM('').window.document; reset(); }); afterEach(() => { global.document.close(); global.document = undefined; }); it('generates class names', () => { const sheet = StyleSheet.create({ red: { color: 'red', }, blue: { color: 'blue' } }); assert.ok(css(sheet.red, sheet.blue)); }); it('filters out falsy inputs', () => { const sheet = StyleSheet.create({ red: { color: 'red', }, }); assert.equal(css(sheet.red), css(sheet.red, false)); assert.equal(css(sheet.red), css(false, sheet.red)); }); it('accepts arrays of styles', () => { const sheet = StyleSheet.create({ red: { color: 'red', }, blue: { color: 'blue' } }); assert.equal(css(sheet.red, sheet.blue), css([sheet.red, sheet.blue])); assert.equal(css(sheet.red, sheet.blue), css(sheet.red, [sheet.blue])); assert.equal(css(sheet.red, sheet.blue), css([sheet.red, [sheet.blue]])); assert.equal(css(sheet.red), css(false, [null, false, sheet.red])); }); it('succeeds for with empty args', () => { assert(css() != null); assert(css(false) != null); }); it('adds styles to the DOM', done => { const sheet = StyleSheet.create({ red: { color: 'red', }, }); css(sheet.red); asap(() => { const styleTags = global.document.getElementsByTagName("style"); const lastTag = styleTags[styleTags.length - 1]; const style = getSheetText(lastTag.sheet); assert.include(style, `${sheet.red._name} {`); assert.match(style, /color: red !important/); done(); }); }); it('only ever creates one style tag', done => { const sheet = StyleSheet.create({ red: { color: 'red', }, blue: { color: 'blue', }, }); css(sheet.red); asap(() => { const styleTags = global.document.getElementsByTagName("style"); assert.equal(styleTags.length, 1); css(sheet.blue); asap(() => { const styleTags = global.document.getElementsByTagName("style"); assert.equal(styleTags.length, 1); done(); }); }); }); it('automatically uses a style tag with the data-aphrodite attribute', done => { const style = document.createElement("style"); style.setAttribute("data-aphrodite", ""); document.head.appendChild(style); const sheet = StyleSheet.create({ red: { color: 'red', }, blue: { color: 'blue', }, }); css(sheet.red); asap(() => { const styleTags = global.document.getElementsByTagName("style"); assert.equal(styleTags.length, 1); const styles = getSheetText(styleTags[0].sheet); assert.include(styles, `${sheet.red._name} {`); assert.include(styles, 'color: red'); done(); }); }); it("throws a useful error for invalid arguments", () => { assert.throws(() => css({ color: "red" }), "Invalid Style Definition"); }); }); describe('StyleSheet.create', () => { it('assigns a name to stylesheet properties', () => { const sheet = StyleSheet.create({ red: { color: 'red', }, blue: { color: 'blue' } }); assert.ok(sheet.red._name); assert.ok(sheet.blue._name); assert.notEqual(sheet.red._name, sheet.blue._name); }); it('assign different names to two different create calls', () => { const sheet1 = StyleSheet.create({ red: { color: 'blue', }, }); const sheet2 = StyleSheet.create({ red: { color: 'red', }, }); assert.notEqual(sheet1.red._name, sheet2.red._name); }); it('assigns the same name to identical styles from different create calls', () => { const sheet1 = StyleSheet.create({ red: { color: 'red', height: 20, ':hover': { color: 'blue', width: 40, }, }, }); const sheet2 = StyleSheet.create({ red: { color: 'red', height: 20, ':hover': { color: 'blue', width: 40, }, }, }); assert.equal(sheet1.red._name, sheet2.red._name); }); it('hashes style names correctly', () => { const sheet = StyleSheet.create({ test: { color: 'red', height: 20, ':hover': { color: 'blue', width: 40, }, }, }); assert.equal(sheet.test._name, 'test_j5rvvh'); }); it('works for empty stylesheets and styles', () => { const emptySheet = StyleSheet.create({}); assert.ok(emptySheet); const sheet = StyleSheet.create({ empty: {} }); assert.ok(sheet.empty._name); }); }); describe('minify', () => { describe('true', () => { beforeEach(() => { minify(true); }); afterEach(() => { minify(undefined); }); it('minifies style names', () => { const sheet = StyleSheet.create({ test: { color: 'red', height: 20, ':hover': { color: 'blue', width: 40, }, }, }); assert.equal(sheet.test._name, 'j5rvvh'); }); }) describe('false', () => { beforeEach(() => { minify(false); }); afterEach(() => { minify(undefined); }); it('does not minifies style names', () => { const sheet = StyleSheet.create({ test: { color: 'red', height: 20, ':hover': { color: 'blue', width: 40, }, }, }); assert.equal(sheet.test._name, 'test_j5rvvh'); }); it('does not minifies style names, even with process.env.NODE_ENV === \'production\'', () => { const sheet = StyleSheet.create({ test: { color: 'red', height: 20, ':hover': { color: 'blue', width: 40, }, }, }); assert.equal(sheet.test._name, 'test_j5rvvh'); }); }) }); describe('rehydrate', () => { beforeEach(() => { global.document = new JSDOM('').window.document; reset(); }); afterEach(() => { global.document.close(); global.document = undefined; }); const sheet = StyleSheet.create({ red: { color: 'red', }, blue: { color: 'blue', }, green: { color: 'green', }, }); it('doesn\'t render styles in the renderedClassNames arg', done => { StyleSheet.rehydrate([sheet.red._name, sheet.blue._name]); css(sheet.red); css(sheet.blue); css(sheet.green); asap(() => { const styleTags = global.document.getElementsByTagName("style"); assert.equal(styleTags.length, 1); const styles = getSheetText(styleTags[0].sheet); assert.notInclude(styles, `.${sheet.red._name} {`); assert.notInclude(styles, `.${sheet.blue._name} {`); assert.include(styles, `.${sheet.green._name} {`); assert.notMatch(styles, /color: blue/); assert.notMatch(styles, /color: red/); assert.match(styles, /color: green/); done(); }); }); it('doesn\'t fail with no argument passed in', () => { StyleSheet.rehydrate(); }); }); describe('StyleSheet.extend', () => { beforeEach(() => { global.document = new JSDOM('').window.document; reset(); }); afterEach(() => { global.document.close(); global.document = undefined; }); it('accepts empty extensions', () => { const newAphrodite = StyleSheet.extend([]); assert(newAphrodite.css); assert(newAphrodite.StyleSheet); }); it('uses a new selector handler', done => { const descendantHandler = (selector, baseSelector, generateSubtreeStyles) => { if (selector[0] !== '^') { return null; } return generateSubtreeStyles( `.${selector.slice(1)} ${baseSelector}`); }; const descendantHandlerExtension = { selectorHandler: descendantHandler, }; // Pull out the new StyleSheet/css functions to use for the rest of // this test. const {StyleSheet: newStyleSheet, css: newCss} = StyleSheet.extend([ descendantHandlerExtension]); const sheet = newStyleSheet.create({ foo: { '^bar': { '^baz': { color: 'orange', }, color: 'red', }, color: 'blue', }, }); newCss(sheet.foo); asap(() => { const styleTags = global.document.getElementsByTagName("style"); assert.equal(styleTags.length, 1); const styles = getSheetText(styleTags[0].sheet); assert.notInclude(styles, '^bar'); assert.include(styles, '.bar .foo'); assert.include(styles, '.baz .bar .foo'); assert.include(styles, 'color: red'); assert.include(styles, 'color: blue'); assert.include(styles, 'color: orange'); done(); }); }); }); describe('StyleSheetServer.renderStatic', () => { const sheet = StyleSheet.create({ red: { color: 'red', }, blue: { color: 'blue', }, green: { color: 'green', }, }); it('returns the correct data', () => { const render = () => { css(sheet.red); css(sheet.blue); return "html!"; }; const ret = StyleSheetServer.renderStatic(render); assert.equal(ret.html, "html!"); assert.include(ret.css.content, `.${sheet.red._name}{`); assert.include(ret.css.content, `.${sheet.blue._name}{`); assert.match(ret.css.content, /color:red/); assert.match(ret.css.content, /color:blue/); assert.include(ret.css.renderedClassNames, sheet.red._name); assert.include(ret.css.renderedClassNames, sheet.blue._name); }); it('succeeds even if a previous renderStatic crashed', () => { const badRender = () => { css(sheet.red); css(sheet.blue); throw new Error("boo!"); }; const goodRender = () => { css(sheet.blue); return "html!"; }; assert.throws(() => { StyleSheetServer.renderStatic(badRender); }, "boo!"); const ret = StyleSheetServer.renderStatic(goodRender); assert.equal(ret.html, "html!"); assert.include(ret.css.content, `.${sheet.blue._name}{`); assert.notInclude(ret.css.content, `.${sheet.red._name}{`); assert.include(ret.css.content, 'color:blue'); assert.notInclude(ret.css.content, 'color:red'); assert.include(ret.css.renderedClassNames, sheet.blue._name); assert.notInclude(ret.css.renderedClassNames, sheet.red._name); }); it('doesn\'t mistakenly return styles if called a second time', () => { const render = () => { css(sheet.red); css(sheet.blue); return "html!"; }; const emptyRender = () => { return ""; }; const ret = StyleSheetServer.renderStatic(render); assert.notEqual(ret.css.content, ""); const newRet = StyleSheetServer.renderStatic(emptyRender); assert.equal(newRet.css.content, ""); }); it('should inject unique font-faces by src', () => { const fontSheet = StyleSheet.create({ test: { fontFamily: [{ fontStyle: "normal", fontWeight: "normal", fontFamily: "My Font", src: 'url(blah) format("woff"), url(blah) format("truetype")' }, { fontStyle: "italic", fontWeight: "normal", fontFamily: "My Font", src: 'url(blahitalic) format("woff"), url(blahitalic) format("truetype")' }], }, anotherTest: { fontFamily: [{ fontStyle: "normal", fontWeight: "normal", fontFamily: "My Font", src: 'url(blah) format("woff"), url(blah) format("truetype")' }, { fontStyle: "normal", fontWeight: "normal", fontFamily: "My Other Font", src: 'url(other-font) format("woff"), url(other-font) format("truetype")', }], }, }); const render = () => { css(fontSheet.test); css(fontSheet.anotherTest); return "html!"; }; const ret = StyleSheetServer.renderStatic(render); // 3 unique @font-faces should be added assert.equal(3, ret.css.content.match(/@font-face/g).length); assert.include(ret.css.content, "font-style:normal"); assert.include(ret.css.content, "font-style:italic"); assert.include(ret.css.content, 'font-family:"My Font"'); assert.include(ret.css.content, 'font-family:"My Font","My Other Font"'); }); }); describe('StyleSheetTestUtils.suppressStyleInjection', () => { beforeEach(() => { StyleSheetTestUtils.suppressStyleInjection(); }); afterEach(() => { StyleSheetTestUtils.clearBufferAndResumeStyleInjection(); }); it('allows css to be called without requiring a DOM', (done) => { const sheet = StyleSheet.create({ red: { color: 'red', }, }); css(sheet.red); asap(done); }); }); describe('StyleSheetTestUtils.getBufferedStyles', () => { beforeEach(() => { StyleSheetTestUtils.suppressStyleInjection(); }); afterEach(() => { StyleSheetTestUtils.clearBufferAndResumeStyleInjection(); }); it('returns injection buffer', () => { const sheet = StyleSheet.create({ red: { color: 'red', }, }); css(sheet.red); asap(() => { const buffer = StyleSheetTestUtils.getBufferedStyles(); assert.equal(buffer.length, 1); assert.include(buffer[0], 'color:red'); assert.include(buffer[0], sheet.red._name); }) }); });