UNPKG

21.7 kBJavaScriptView Raw
1import asap from 'asap';
2import {assert} from 'chai';
3import {JSDOM} from 'jsdom';
4
5import { StyleSheet, css, minify } from '../src/index';
6import {
7 injectAndGetClassName,
8 injectStyleOnce,
9 reset,
10 resetInjectedStyle,
11 startBuffering,
12 flushToString,
13 flushToStyleTag,
14 addRenderedClassNames,
15 getRenderedClassNames,
16} from '../src/inject';
17import { defaultSelectorHandlers } from '../src/generate';
18import { getSheetText } from './testUtils';
19
20const sheet = StyleSheet.create({
21 red: {
22 color: 'red',
23 },
24
25 blue: {
26 color: 'blue',
27 },
28
29 green: {
30 color: 'green',
31 },
32});
33
34describe('injection', () => {
35 beforeEach(() => {
36 global.document = new JSDOM('').window.document;
37 reset();
38 });
39
40 afterEach(() => {
41 global.document.close();
42 global.document = undefined;
43 });
44
45 describe('injectAndGetClassName', () => {
46 it('uses hashed class name', () => {
47 const className = injectAndGetClassName(false, [sheet.red], defaultSelectorHandlers);
48 assert.equal(className, 'red_137u7ef');
49 });
50
51 it('combines class names', () => {
52 const className = injectAndGetClassName(false, [sheet.red, sheet.blue, sheet.green], defaultSelectorHandlers);
53 assert.equal(className, 'red_137u7ef-o_O-blue_1tsdo2i-o_O-green_1jzdmtb');
54 });
55
56 it('ignores null values in styleDefinitions', () => {
57 const className = injectAndGetClassName(false, [sheet.red, sheet.blue, null], defaultSelectorHandlers);
58 assert.equal(className, 'red_137u7ef-o_O-blue_1tsdo2i');
59 });
60
61 describe('process.env.NODE_ENV === \'production\'', () => {
62 let prodSheet;
63 beforeEach(() => {
64 process.env.NODE_ENV = 'production';
65 minify(true);
66 prodSheet = StyleSheet.create({
67 red: {
68 color: 'red',
69 },
70
71 blue: {
72 color: 'blue',
73 },
74
75 green: {
76 color: 'green',
77 },
78 });
79 });
80
81 afterEach(() => {
82 delete process.env.NODE_ENV;
83 minify(false);
84 });
85
86 it('uses hashed class name (does not re-hash)', () => {
87 const className = injectAndGetClassName(false, [prodSheet.red], defaultSelectorHandlers);
88 assert.equal(className, `_${prodSheet.red._name}`);
89 });
90
91 it('creates minified combined class name', () => {
92 const className = injectAndGetClassName(false, [prodSheet.red, prodSheet.blue, prodSheet.green], defaultSelectorHandlers);
93 assert.equal(className, '_11v1eztc');
94 });
95
96 it('ignores null values in styleDefinitions', () => {
97 const className = injectAndGetClassName(false, [
98 null,
99 prodSheet.red,
100 null,
101 prodSheet.blue,
102 prodSheet.green,
103 null
104 ], defaultSelectorHandlers);
105 assert.equal(className, '_11v1eztc');
106 });
107 });
108 });
109
110 describe('injectStyleOnce', () => {
111 it('causes styles to automatically be added', done => {
112 injectStyleOnce("x", ".x", [{ color: "red" }], false);
113
114 asap(() => {
115 const styleTags = global.document.getElementsByTagName("style");
116 assert.equal(styleTags.length, 1);
117 const styles = getSheetText(styleTags[0].sheet);
118
119 assert.include(styles, ".x {");
120 assert.include(styles, "color: red");
121
122 done();
123 });
124 });
125
126 it('causes styles to be added async, and buffered', done => {
127 injectStyleOnce("x", ".x", [{ color: "red" }], false);
128
129 const styleTags = global.document.getElementsByTagName("style");
130 assert.equal(styleTags.length, 0);
131
132 injectStyleOnce("y", ".y", [{ color: "blue" }], false);
133
134 asap(() => {
135 const styleTags = global.document.getElementsByTagName("style");
136 assert.equal(styleTags.length, 1);
137 const styles = getSheetText(styleTags[0].sheet);
138
139 assert.include(styles, ".x {");
140 assert.include(styles, ".y {");
141 assert.include(styles, "color: red");
142 assert.include(styles, "color: blue");
143
144 done();
145 });
146 });
147
148 it('doesn\'t inject the same style twice', done => {
149 injectStyleOnce("x", ".x", [{ color: "red" }], false);
150 injectStyleOnce("x", ".x", [{ color: "blue" }], false);
151
152 asap(() => {
153 const styleTags = global.document.getElementsByTagName("style");
154 assert.equal(styleTags.length, 1);
155 const styles = getSheetText(styleTags[0].sheet);
156
157 assert.include(styles, ".x {");
158 assert.include(styles, "color: red");
159 assert.notInclude(styles, "color: blue");
160 assert.equal(styles.match(/\.x {/g).length, 1);
161
162 done();
163 });
164 });
165
166 it('throws an error if we\'re not buffering and on the server', () => {
167 const oldDocument = global.document;
168 global.document = undefined;
169
170 assert.throws(() => {
171 injectStyleOnce("x", ".x", [{ color: "red" }], false);
172 }, "Cannot automatically buffer");
173
174 global.document = oldDocument;
175 });
176
177 // browser-specific tests
178 it('adds to .innerText if insertRule is not available', done => {
179 const styleTag = global.document.createElement("style");
180 styleTag.setAttribute("data-aphrodite", "");
181 document.head.appendChild(styleTag);
182 styleTag.sheet.insertRule = null;
183
184 injectStyleOnce("x", ".x", [{ color: "red" }], false);
185
186 asap(() => {
187 assert.include(styleTag.innerText, ".x{");
188 assert.include(styleTag.innerText, "color:red");
189 done();
190 });
191 });
192
193 it('uses document.getElementsByTagName without document.head', done => {
194 Object.defineProperty(global.document, "head", {
195 value: null,
196 });
197
198 injectStyleOnce("x", ".x", [{ color: "red" }], false);
199
200 asap(() => {
201 const styleTags = global.document.getElementsByTagName("style");
202 assert.equal(styleTags.length, 1);
203 const styles = getSheetText(styleTags[0].sheet);
204
205 assert.include(styles, ".x {");
206 assert.include(styles, "color: red");
207
208 done();
209 });
210 });
211 });
212
213 describe('startBuffering', () => {
214 it('causes styles to not be added automatically', done => {
215 startBuffering();
216
217 css(sheet.red);
218
219 asap(() => {
220 const styleTags = global.document.getElementsByTagName("style");
221 assert.equal(styleTags.length, 0);
222 done();
223 });
224 });
225
226 it('throws an error if we try to buffer twice', () => {
227 startBuffering();
228
229 assert.throws(() => {
230 startBuffering();
231 }, "already buffering");
232 });
233 });
234
235 describe('flushToStyleTag', () => {
236 it('adds a style tag with all the buffered styles', () => {
237 startBuffering();
238
239 css(sheet.red);
240 css(sheet.blue);
241
242 flushToStyleTag();
243
244 const styleTags = global.document.getElementsByTagName("style");
245 const lastTag = styleTags[styleTags.length - 1];
246 const styles = getSheetText(lastTag.sheet);
247
248 assert.include(styles, `.${sheet.red._name} {`);
249 assert.include(styles, `.${sheet.blue._name} {`);
250 assert.match(styles, /color: red/);
251 assert.match(styles, /color: blue/);
252 });
253
254 it('clears the injection buffer', () => {
255 startBuffering();
256
257 css(sheet.red);
258 css(sheet.blue);
259
260 flushToStyleTag();
261
262 let styleTags = global.document.getElementsByTagName("style");
263 assert.equal(styleTags.length, 1);
264 const styleContentLength = getSheetText(styleTags[0].sheet).length;
265
266 startBuffering();
267 flushToStyleTag();
268
269 styleTags = global.document.getElementsByTagName("style");
270 assert.equal(styleTags.length, 1);
271 assert.equal(getSheetText(styleTags[0].sheet).length, styleContentLength);
272 });
273 });
274
275 describe('flushToString', () => {
276 it('returns the buffered styles', () => {
277 startBuffering();
278
279 css(sheet.red);
280 css(sheet.blue);
281
282 const styles = flushToString();
283
284 assert.include(styles, `.${sheet.red._name}{`);
285 assert.include(styles, `.${sheet.blue._name}{`);
286 assert.match(styles, /color:red/);
287 assert.match(styles, /color:blue/);
288 });
289
290 it('clears the injection buffer', () => {
291 startBuffering();
292
293 css(sheet.red);
294 css(sheet.blue);
295
296 assert.notEqual(flushToString(), "");
297
298 startBuffering();
299 assert.equal(flushToString(), "");
300 });
301 });
302
303 describe('getRenderedClassNames', () => {
304 it('returns classes that have been rendered', () => {
305 css(sheet.red);
306 css(sheet.blue);
307
308 const classNames = getRenderedClassNames();
309
310 assert.include(classNames, sheet.red._name);
311 assert.include(classNames, sheet.blue._name);
312 assert.notInclude(classNames, sheet.green._name);
313 });
314 });
315
316 describe('addRenderedClassNames', () => {
317 it('doesn\'t render classnames that were added', () => {
318 startBuffering();
319 addRenderedClassNames([sheet.red._name, sheet.blue._name]);
320
321 css(sheet.red);
322 css(sheet.blue);
323 css(sheet.green);
324
325 flushToStyleTag();
326
327 const styleTags = global.document.getElementsByTagName("style");
328 assert.equal(styleTags.length, 1);
329 const styles = getSheetText(styleTags[0].sheet);
330
331 assert.include(styles, `.${sheet.green._name} {`);
332 assert.notInclude(styles, `.${sheet.red._name} {`);
333 assert.notInclude(styles, `.${sheet.blue._name} {`);
334 assert.match(styles, /color: green/);
335 assert.notMatch(styles, /color: red/);
336 assert.notMatch(styles, /color: blue/);
337 });
338 });
339
340 describe('resetInjectedStyle()', () => {
341 it('injects styles again after being reset', () => {
342 startBuffering();
343
344 css(sheet.red);
345
346 flushToStyleTag();
347
348 const styleTags = global.document.getElementsByTagName("style");
349 const lastTag = styleTags[styleTags.length - 1];
350
351 assert.equal(getSheetText(lastTag.sheet), `.${sheet.red._name} {color: red !important;} `);
352
353 // Delete all rules
354 while (lastTag.sheet.cssRules.length > 0) {
355 lastTag.sheet.deleteRule(0);
356 }
357
358 resetInjectedStyle(sheet.red._name);
359
360 assert.equal(getSheetText(lastTag.sheet), '');
361
362 // Re-inject
363 css(sheet.red);
364
365 flushToStyleTag();
366
367 assert.equal(getSheetText(lastTag.sheet), `.${sheet.red._name} {color: red !important;} `);
368 });
369 });
370});
371
372describe('String handlers', () => {
373 beforeEach(() => {
374 global.document = new JSDOM('').window.document;
375 reset();
376 });
377
378 afterEach(() => {
379 global.document.close();
380 global.document = undefined;
381 });
382
383 function assertStylesInclude(str) {
384 const styleTags = global.document.getElementsByTagName("style");
385 const styles = getSheetText(styleTags[0].sheet);
386
387 assert.include(styles, str);
388 }
389
390 describe('fontFamily', () => {
391 it('leaves plain strings alone', () => {
392 const sheet = StyleSheet.create({
393 base: {
394 fontFamily: "Helvetica",
395 },
396 });
397
398 startBuffering();
399 css(sheet.base);
400 flushToStyleTag();
401
402 assertStylesInclude('font-family: Helvetica !important');
403 });
404
405 it('concatenates arrays', () => {
406 const sheet = StyleSheet.create({
407 base: {
408 fontFamily: ["Helvetica", "sans-serif"],
409 },
410 });
411
412 startBuffering();
413 css(sheet.base);
414 flushToStyleTag();
415
416 assertStylesInclude('font-family: Helvetica,sans-serif !important');
417 });
418
419 it('adds @font-face rules for objects', () => {
420 const fontface = {
421 fontFamily: "CoolFont",
422 src: "url('coolfont.ttf')",
423 };
424
425 const sheet = StyleSheet.create({
426 base: {
427 fontFamily: [fontface, "sans-serif"],
428 },
429 });
430
431 startBuffering();
432 css(sheet.base);
433 flushToStyleTag();
434
435 assertStylesInclude('font-family: "CoolFont",sans-serif !important');
436 assertStylesInclude('font-family: CoolFont;');
437 assertStylesInclude("src: url('coolfont.ttf');");
438 });
439
440 it('supports multiple @font-face with the same family name', () => {
441 const sheet = StyleSheet.create({
442 base: {
443 fontFamily: [
444 {
445 fontFamily: "CoolFont",
446 src: "url('coolfont.ttf')",
447 },
448 {
449 fontFamily: "CoolFont",
450 fontStyle: "italic",
451 src: "url('coolfont-italic.ttf')",
452 },
453 {
454 fontFamily: "CoolFont",
455 fontWeight: 300,
456 src: "url('coolfont-bold.ttf')",
457 },
458 "sans-serif",
459 ],
460 },
461 });
462
463 startBuffering();
464 css(sheet.base);
465 flushToStyleTag();
466
467 assertStylesInclude('font-family: "CoolFont",sans-serif !important');
468 assertStylesInclude('font-family: CoolFont;');
469 assertStylesInclude("src: url('coolfont.ttf');");
470 assertStylesInclude("font-style: italic; src: url('coolfont-italic.ttf');");
471 assertStylesInclude("font-weight: 300; src: url('coolfont-bold.ttf');");
472 });
473
474 it('supports multiple @font-face with different family names', () => {
475 const sheet = StyleSheet.create({
476 base: {
477 fontFamily: [
478 {
479 fontFamily: "CoolFont",
480 src: "url('coolfont.ttf')",
481 },
482 {
483 fontFamily: "AwesomeFont",
484 src: "url('awesomefont.ttf')",
485 },
486 {
487 fontFamily: "SuperFont",
488 src: "url('superfont.ttf')",
489 },
490 "sans-serif",
491 ],
492 },
493 });
494
495 startBuffering();
496 css(sheet.base);
497 flushToStyleTag();
498
499 assertStylesInclude('font-family: "CoolFont","AwesomeFont","SuperFont",sans-serif !important');
500 assertStylesInclude('font-family: CoolFont;');
501 assertStylesInclude("src: url('coolfont.ttf');");
502 assertStylesInclude('font-family: AwesomeFont;');
503 assertStylesInclude("src: url('awesomefont.ttf');");
504 assertStylesInclude('font-family: SuperFont;');
505 assertStylesInclude("src: url('superfont.ttf');");
506 });
507 });
508
509 describe('animationName', () => {
510 it('leaves plain strings alone', () => {
511 const sheet = StyleSheet.create({
512 animate: {
513 animationName: "boo",
514 },
515 });
516
517 startBuffering();
518 css(sheet.animate);
519 flushToStyleTag();
520
521 assertStylesInclude('animation-name: boo !important');
522 });
523
524 it('generates css for keyframes', () => {
525 const sheet = StyleSheet.create({
526 animate: {
527 animationName: {
528 'from': {
529 left: 10,
530 },
531 '50%': {
532 left: 20,
533 },
534 'to': {
535 left: 40,
536 },
537 },
538 },
539 });
540
541 startBuffering();
542 css(sheet.animate);
543 flushToStyleTag();
544
545 assertStylesInclude('@keyframes keyframe_tmjr6');
546 assertStylesInclude('from {left: 10px;}');
547 assertStylesInclude('50% {left: 20px;}');
548 assertStylesInclude('to {left: 40px;}');
549 assertStylesInclude('animation-name: keyframe_tmjr6');
550 });
551
552 it('generates css for keyframes with multiple properties', () => {
553 const sheet = StyleSheet.create({
554 animate: {
555 animationName: {
556 '0%': {
557 opacity: 0,
558 transform: 'scale(0.75) translate3d(1px, 2px, 0)',
559 },
560 '100%': {
561 opacity: 1,
562 transform: 'scale(1) translate3d(1px, 2px, 0)',
563 },
564 },
565 },
566 });
567
568 startBuffering();
569 css(sheet.animate);
570 flushToStyleTag();
571
572 assertStylesInclude('@keyframes keyframe_d35t13');
573 assertStylesInclude('0% {opacity: 0; -webkit-transform: scale(0.75) translate3d(1px, 2px, 0); -ms-transform: scale(0.75) translate3d(1px, 2px, 0); transform: scale(0.75) translate3d(1px, 2px, 0);}');
574 assertStylesInclude('100% {opacity: 1; -webkit-transform: scale(1) translate3d(1px, 2px, 0); -ms-transform: scale(1) translate3d(1px, 2px, 0); transform: scale(1) translate3d(1px, 2px, 0);}');
575 assertStylesInclude('animation-name: keyframe_d35t13');
576 });
577
578 it('doesn\'t add the same keyframes twice', () => {
579 const keyframes = {
580 'from': {
581 left: 10,
582 },
583 '50%': {
584 left: 20,
585 },
586 'to': {
587 left: 40,
588 },
589 };
590
591 const sheet = StyleSheet.create({
592 animate: {
593 animationName: keyframes,
594 },
595 animate2: {
596 animationName: keyframes,
597 },
598 });
599
600 startBuffering();
601 css(sheet.animate);
602 css(sheet.animate2);
603 flushToStyleTag();
604
605 const styleTags = global.document.getElementsByTagName("style");
606 const styles = getSheetText(styleTags[0].sheet);
607
608 assert.include(styles, '@keyframes keyframe_tmjr6');
609 assert.equal(styles.match(/@keyframes/g).length, 1);
610 });
611
612 it('concatenates arrays of custom keyframes', () => {
613 const keyframes1 = {
614 'from': {
615 left: 10,
616 },
617 'to': {
618 left: 50,
619 },
620 };
621
622 const keyframes2 = {
623 'from': {
624 top: -50,
625 },
626 'to': {
627 top: 0,
628 },
629 };
630
631 const sheet = StyleSheet.create({
632 animate: {
633 animationName: [keyframes1, keyframes2],
634 },
635 });
636
637 startBuffering();
638 css(sheet.animate);
639 flushToStyleTag();
640
641 assertStylesInclude('@keyframes keyframe_1a8sduu');
642 assertStylesInclude('@keyframes keyframe_1wnshbu');
643 assertStylesInclude('animation-name: keyframe_1a8sduu,keyframe_1wnshbu')
644 });
645
646 it('concatenates a custom keyframe animation with a plain string', () => {
647 const keyframes1 = {
648 'from': {
649 left: 10,
650 },
651 'to': {
652 left: 50,
653 },
654 };
655
656 const sheet = StyleSheet.create({
657 animate: {
658 animationName: [keyframes1, 'hoo'],
659 },
660 });
661
662 startBuffering();
663 css(sheet.animate);
664 flushToStyleTag();
665
666 assertStylesInclude('@keyframes keyframe_1a8sduu');
667 assertStylesInclude('animation-name: keyframe_1a8sduu,hoo')
668 });
669 });
670});