1 | import asap from 'asap';
|
2 | import { assert } from 'chai';
|
3 | import { JSDOM } from 'jsdom';
|
4 |
|
5 | import {
|
6 | StyleSheet,
|
7 | StyleSheetServer,
|
8 | StyleSheetTestUtils,
|
9 | minify,
|
10 | css
|
11 | } from '../src/index.js';
|
12 | import { reset } from '../src/inject.js';
|
13 | import { getSheetText } from './testUtils.js';
|
14 |
|
15 | describe('css', () => {
|
16 | beforeEach(() => {
|
17 | global.document = new JSDOM('').window.document;
|
18 | reset();
|
19 | });
|
20 |
|
21 | afterEach(() => {
|
22 | global.document.close();
|
23 | global.document = undefined;
|
24 | });
|
25 |
|
26 | it('generates class names', () => {
|
27 | const sheet = StyleSheet.create({
|
28 | red: {
|
29 | color: 'red',
|
30 | },
|
31 |
|
32 | blue: {
|
33 | color: 'blue'
|
34 | }
|
35 | });
|
36 |
|
37 | assert.ok(css(sheet.red, sheet.blue));
|
38 | });
|
39 |
|
40 | it('filters out falsy inputs', () => {
|
41 | const sheet = StyleSheet.create({
|
42 | red: {
|
43 | color: 'red',
|
44 | },
|
45 | });
|
46 |
|
47 | assert.equal(css(sheet.red), css(sheet.red, false));
|
48 | assert.equal(css(sheet.red), css(false, sheet.red));
|
49 | });
|
50 |
|
51 | it('accepts arrays of styles', () => {
|
52 | const sheet = StyleSheet.create({
|
53 | red: {
|
54 | color: 'red',
|
55 | },
|
56 |
|
57 | blue: {
|
58 | color: 'blue'
|
59 | }
|
60 | });
|
61 |
|
62 | assert.equal(css(sheet.red, sheet.blue), css([sheet.red, sheet.blue]));
|
63 | assert.equal(css(sheet.red, sheet.blue), css(sheet.red, [sheet.blue]));
|
64 | assert.equal(css(sheet.red, sheet.blue), css([sheet.red, [sheet.blue]]));
|
65 | assert.equal(css(sheet.red), css(false, [null, false, sheet.red]));
|
66 | });
|
67 |
|
68 | it('succeeds for with empty args', () => {
|
69 | assert(css() != null);
|
70 | assert(css(false) != null);
|
71 | });
|
72 |
|
73 | it('adds styles to the DOM', done => {
|
74 | const sheet = StyleSheet.create({
|
75 | red: {
|
76 | color: 'red',
|
77 | },
|
78 | });
|
79 |
|
80 | css(sheet.red);
|
81 |
|
82 | asap(() => {
|
83 | const styleTags = global.document.getElementsByTagName("style");
|
84 | const lastTag = styleTags[styleTags.length - 1];
|
85 | const style = getSheetText(lastTag.sheet);
|
86 |
|
87 | assert.include(style, `${sheet.red._name} {`);
|
88 | assert.match(style, /color: red !important/);
|
89 | done();
|
90 | });
|
91 | });
|
92 |
|
93 | it('only ever creates one style tag', done => {
|
94 | const sheet = StyleSheet.create({
|
95 | red: {
|
96 | color: 'red',
|
97 | },
|
98 | blue: {
|
99 | color: 'blue',
|
100 | },
|
101 | });
|
102 |
|
103 | css(sheet.red);
|
104 |
|
105 | asap(() => {
|
106 | const styleTags = global.document.getElementsByTagName("style");
|
107 | assert.equal(styleTags.length, 1);
|
108 |
|
109 | css(sheet.blue);
|
110 |
|
111 | asap(() => {
|
112 | const styleTags = global.document.getElementsByTagName("style");
|
113 | assert.equal(styleTags.length, 1);
|
114 | done();
|
115 | });
|
116 | });
|
117 | });
|
118 |
|
119 | it('automatically uses a style tag with the data-aphrodite attribute', done => {
|
120 | const style = document.createElement("style");
|
121 | style.setAttribute("data-aphrodite", "");
|
122 | document.head.appendChild(style);
|
123 |
|
124 | const sheet = StyleSheet.create({
|
125 | red: {
|
126 | color: 'red',
|
127 | },
|
128 | blue: {
|
129 | color: 'blue',
|
130 | },
|
131 | });
|
132 |
|
133 | css(sheet.red);
|
134 |
|
135 | asap(() => {
|
136 | const styleTags = global.document.getElementsByTagName("style");
|
137 | assert.equal(styleTags.length, 1);
|
138 | const styles = getSheetText(styleTags[0].sheet);
|
139 |
|
140 | assert.include(styles, `${sheet.red._name} {`);
|
141 | assert.include(styles, 'color: red');
|
142 |
|
143 | done();
|
144 | });
|
145 | });
|
146 |
|
147 | it("throws a useful error for invalid arguments", () => {
|
148 | assert.throws(() => css({ color: "red" }), "Invalid Style Definition");
|
149 | });
|
150 | });
|
151 |
|
152 | describe('StyleSheet.create', () => {
|
153 | it('assigns a name to stylesheet properties', () => {
|
154 | const sheet = StyleSheet.create({
|
155 | red: {
|
156 | color: 'red',
|
157 | },
|
158 | blue: {
|
159 | color: 'blue'
|
160 | }
|
161 | });
|
162 |
|
163 | assert.ok(sheet.red._name);
|
164 | assert.ok(sheet.blue._name);
|
165 | assert.notEqual(sheet.red._name, sheet.blue._name);
|
166 | });
|
167 |
|
168 | it('assign different names to two different create calls', () => {
|
169 | const sheet1 = StyleSheet.create({
|
170 | red: {
|
171 | color: 'blue',
|
172 | },
|
173 | });
|
174 |
|
175 | const sheet2 = StyleSheet.create({
|
176 | red: {
|
177 | color: 'red',
|
178 | },
|
179 | });
|
180 |
|
181 | assert.notEqual(sheet1.red._name, sheet2.red._name);
|
182 | });
|
183 |
|
184 | it('assigns the same name to identical styles from different create calls', () => {
|
185 | const sheet1 = StyleSheet.create({
|
186 | red: {
|
187 | color: 'red',
|
188 | height: 20,
|
189 |
|
190 | ':hover': {
|
191 | color: 'blue',
|
192 | width: 40,
|
193 | },
|
194 | },
|
195 | });
|
196 |
|
197 | const sheet2 = StyleSheet.create({
|
198 | red: {
|
199 | color: 'red',
|
200 | height: 20,
|
201 |
|
202 | ':hover': {
|
203 | color: 'blue',
|
204 | width: 40,
|
205 | },
|
206 | },
|
207 | });
|
208 |
|
209 | assert.equal(sheet1.red._name, sheet2.red._name);
|
210 | });
|
211 |
|
212 | it('hashes style names correctly', () => {
|
213 | const sheet = StyleSheet.create({
|
214 | test: {
|
215 | color: 'red',
|
216 | height: 20,
|
217 |
|
218 | ':hover': {
|
219 | color: 'blue',
|
220 | width: 40,
|
221 | },
|
222 | },
|
223 | });
|
224 |
|
225 | assert.equal(sheet.test._name, 'test_j5rvvh');
|
226 | });
|
227 |
|
228 | it('works for empty stylesheets and styles', () => {
|
229 | const emptySheet = StyleSheet.create({});
|
230 |
|
231 | assert.ok(emptySheet);
|
232 |
|
233 | const sheet = StyleSheet.create({
|
234 | empty: {}
|
235 | });
|
236 |
|
237 | assert.ok(sheet.empty._name);
|
238 | });
|
239 | });
|
240 |
|
241 | describe('minify', () => {
|
242 | describe('true', () => {
|
243 | beforeEach(() => {
|
244 | minify(true);
|
245 | });
|
246 |
|
247 | afterEach(() => {
|
248 | minify(undefined);
|
249 | });
|
250 |
|
251 | it('minifies style names', () => {
|
252 | const sheet = StyleSheet.create({
|
253 | test: {
|
254 | color: 'red',
|
255 | height: 20,
|
256 |
|
257 | ':hover': {
|
258 | color: 'blue',
|
259 | width: 40,
|
260 | },
|
261 | },
|
262 | });
|
263 |
|
264 | assert.equal(sheet.test._name, 'j5rvvh');
|
265 | });
|
266 | })
|
267 |
|
268 | describe('false', () => {
|
269 | beforeEach(() => {
|
270 | minify(false);
|
271 | });
|
272 |
|
273 | afterEach(() => {
|
274 | minify(undefined);
|
275 | });
|
276 |
|
277 | it('does not minifies style names', () => {
|
278 | const sheet = StyleSheet.create({
|
279 | test: {
|
280 | color: 'red',
|
281 | height: 20,
|
282 |
|
283 | ':hover': {
|
284 | color: 'blue',
|
285 | width: 40,
|
286 | },
|
287 | },
|
288 | });
|
289 |
|
290 | assert.equal(sheet.test._name, 'test_j5rvvh');
|
291 | });
|
292 |
|
293 | it('does not minifies style names, even with process.env.NODE_ENV === \'production\'', () => {
|
294 | const sheet = StyleSheet.create({
|
295 | test: {
|
296 | color: 'red',
|
297 | height: 20,
|
298 |
|
299 | ':hover': {
|
300 | color: 'blue',
|
301 | width: 40,
|
302 | },
|
303 | },
|
304 | });
|
305 |
|
306 | assert.equal(sheet.test._name, 'test_j5rvvh');
|
307 | });
|
308 | })
|
309 | });
|
310 |
|
311 | describe('rehydrate', () => {
|
312 | beforeEach(() => {
|
313 | global.document = new JSDOM('').window.document;
|
314 | reset();
|
315 | });
|
316 |
|
317 | afterEach(() => {
|
318 | global.document.close();
|
319 | global.document = undefined;
|
320 | });
|
321 |
|
322 | const sheet = StyleSheet.create({
|
323 | red: {
|
324 | color: 'red',
|
325 | },
|
326 |
|
327 | blue: {
|
328 | color: 'blue',
|
329 | },
|
330 |
|
331 | green: {
|
332 | color: 'green',
|
333 | },
|
334 | });
|
335 |
|
336 | it('doesn\'t render styles in the renderedClassNames arg', done => {
|
337 | StyleSheet.rehydrate([sheet.red._name, sheet.blue._name]);
|
338 |
|
339 | css(sheet.red);
|
340 | css(sheet.blue);
|
341 | css(sheet.green);
|
342 |
|
343 | asap(() => {
|
344 | const styleTags = global.document.getElementsByTagName("style");
|
345 | assert.equal(styleTags.length, 1);
|
346 | const styles = getSheetText(styleTags[0].sheet);
|
347 |
|
348 | assert.notInclude(styles, `.${sheet.red._name} {`);
|
349 | assert.notInclude(styles, `.${sheet.blue._name} {`);
|
350 | assert.include(styles, `.${sheet.green._name} {`);
|
351 | assert.notMatch(styles, /color: blue/);
|
352 | assert.notMatch(styles, /color: red/);
|
353 | assert.match(styles, /color: green/);
|
354 |
|
355 | done();
|
356 | });
|
357 | });
|
358 |
|
359 | it('doesn\'t fail with no argument passed in', () => {
|
360 | StyleSheet.rehydrate();
|
361 | });
|
362 | });
|
363 |
|
364 | describe('StyleSheet.extend', () => {
|
365 | beforeEach(() => {
|
366 | global.document = new JSDOM('').window.document;
|
367 | reset();
|
368 | });
|
369 |
|
370 | afterEach(() => {
|
371 | global.document.close();
|
372 | global.document = undefined;
|
373 | });
|
374 |
|
375 | it('accepts empty extensions', () => {
|
376 | const newAphrodite = StyleSheet.extend([]);
|
377 |
|
378 | assert(newAphrodite.css);
|
379 | assert(newAphrodite.StyleSheet);
|
380 | });
|
381 |
|
382 | it('uses a new selector handler', done => {
|
383 | const descendantHandler = (selector, baseSelector,
|
384 | generateSubtreeStyles) => {
|
385 | if (selector[0] !== '^') {
|
386 | return null;
|
387 | }
|
388 | return generateSubtreeStyles(
|
389 | `.${selector.slice(1)} ${baseSelector}`);
|
390 | };
|
391 |
|
392 | const descendantHandlerExtension = {
|
393 | selectorHandler: descendantHandler,
|
394 | };
|
395 |
|
396 |
|
397 |
|
398 | const {StyleSheet: newStyleSheet, css: newCss} = StyleSheet.extend([
|
399 | descendantHandlerExtension]);
|
400 |
|
401 | const sheet = newStyleSheet.create({
|
402 | foo: {
|
403 | '^bar': {
|
404 | '^baz': {
|
405 | color: 'orange',
|
406 | },
|
407 | color: 'red',
|
408 | },
|
409 | color: 'blue',
|
410 | },
|
411 | });
|
412 |
|
413 | newCss(sheet.foo);
|
414 |
|
415 | asap(() => {
|
416 | const styleTags = global.document.getElementsByTagName("style");
|
417 | assert.equal(styleTags.length, 1);
|
418 | const styles = getSheetText(styleTags[0].sheet);
|
419 |
|
420 | assert.notInclude(styles, '^bar');
|
421 | assert.include(styles, '.bar .foo');
|
422 | assert.include(styles, '.baz .bar .foo');
|
423 | assert.include(styles, 'color: red');
|
424 | assert.include(styles, 'color: blue');
|
425 | assert.include(styles, 'color: orange');
|
426 |
|
427 | done();
|
428 | });
|
429 | });
|
430 | });
|
431 |
|
432 | describe('StyleSheetServer.renderStatic', () => {
|
433 | const sheet = StyleSheet.create({
|
434 | red: {
|
435 | color: 'red',
|
436 | },
|
437 |
|
438 | blue: {
|
439 | color: 'blue',
|
440 | },
|
441 |
|
442 | green: {
|
443 | color: 'green',
|
444 | },
|
445 | });
|
446 |
|
447 | it('returns the correct data', () => {
|
448 | const render = () => {
|
449 | css(sheet.red);
|
450 | css(sheet.blue);
|
451 |
|
452 | return "html!";
|
453 | };
|
454 |
|
455 | const ret = StyleSheetServer.renderStatic(render);
|
456 |
|
457 | assert.equal(ret.html, "html!");
|
458 |
|
459 | assert.include(ret.css.content, `.${sheet.red._name}{`);
|
460 | assert.include(ret.css.content, `.${sheet.blue._name}{`);
|
461 | assert.match(ret.css.content, /color:red/);
|
462 | assert.match(ret.css.content, /color:blue/);
|
463 |
|
464 | assert.include(ret.css.renderedClassNames, sheet.red._name);
|
465 | assert.include(ret.css.renderedClassNames, sheet.blue._name);
|
466 | });
|
467 |
|
468 | it('succeeds even if a previous renderStatic crashed', () => {
|
469 | const badRender = () => {
|
470 | css(sheet.red);
|
471 | css(sheet.blue);
|
472 | throw new Error("boo!");
|
473 | };
|
474 |
|
475 | const goodRender = () => {
|
476 | css(sheet.blue);
|
477 | return "html!";
|
478 | };
|
479 |
|
480 | assert.throws(() => {
|
481 | StyleSheetServer.renderStatic(badRender);
|
482 | }, "boo!");
|
483 |
|
484 | const ret = StyleSheetServer.renderStatic(goodRender);
|
485 |
|
486 | assert.equal(ret.html, "html!");
|
487 |
|
488 | assert.include(ret.css.content, `.${sheet.blue._name}{`);
|
489 | assert.notInclude(ret.css.content, `.${sheet.red._name}{`);
|
490 | assert.include(ret.css.content, 'color:blue');
|
491 | assert.notInclude(ret.css.content, 'color:red');
|
492 |
|
493 | assert.include(ret.css.renderedClassNames, sheet.blue._name);
|
494 | assert.notInclude(ret.css.renderedClassNames, sheet.red._name);
|
495 | });
|
496 |
|
497 | it('doesn\'t mistakenly return styles if called a second time', () => {
|
498 | const render = () => {
|
499 | css(sheet.red);
|
500 | css(sheet.blue);
|
501 |
|
502 | return "html!";
|
503 | };
|
504 |
|
505 | const emptyRender = () => {
|
506 | return "";
|
507 | };
|
508 |
|
509 | const ret = StyleSheetServer.renderStatic(render);
|
510 | assert.notEqual(ret.css.content, "");
|
511 |
|
512 | const newRet = StyleSheetServer.renderStatic(emptyRender);
|
513 | assert.equal(newRet.css.content, "");
|
514 | });
|
515 |
|
516 | it('should inject unique font-faces by src', () => {
|
517 | const fontSheet = StyleSheet.create({
|
518 | test: {
|
519 | fontFamily: [{
|
520 | fontStyle: "normal",
|
521 | fontWeight: "normal",
|
522 | fontFamily: "My Font",
|
523 | src: 'url(blah) format("woff"), url(blah) format("truetype")'
|
524 | }, {
|
525 | fontStyle: "italic",
|
526 | fontWeight: "normal",
|
527 | fontFamily: "My Font",
|
528 | src: 'url(blahitalic) format("woff"), url(blahitalic) format("truetype")'
|
529 | }],
|
530 | },
|
531 |
|
532 | anotherTest: {
|
533 | fontFamily: [{
|
534 | fontStyle: "normal",
|
535 | fontWeight: "normal",
|
536 | fontFamily: "My Font",
|
537 | src: 'url(blah) format("woff"), url(blah) format("truetype")'
|
538 | }, {
|
539 | fontStyle: "normal",
|
540 | fontWeight: "normal",
|
541 | fontFamily: "My Other Font",
|
542 | src: 'url(other-font) format("woff"), url(other-font) format("truetype")',
|
543 | }],
|
544 | },
|
545 | });
|
546 |
|
547 | const render = () => {
|
548 | css(fontSheet.test);
|
549 | css(fontSheet.anotherTest);
|
550 | return "html!";
|
551 | };
|
552 |
|
553 | const ret = StyleSheetServer.renderStatic(render);
|
554 |
|
555 |
|
556 | assert.equal(3, ret.css.content.match(/@font-face/g).length);
|
557 |
|
558 | assert.include(ret.css.content, "font-style:normal");
|
559 | assert.include(ret.css.content, "font-style:italic");
|
560 |
|
561 | assert.include(ret.css.content, 'font-family:"My Font"');
|
562 | assert.include(ret.css.content, 'font-family:"My Font","My Other Font"');
|
563 | });
|
564 | });
|
565 |
|
566 | describe('StyleSheetTestUtils.suppressStyleInjection', () => {
|
567 | beforeEach(() => {
|
568 | StyleSheetTestUtils.suppressStyleInjection();
|
569 | });
|
570 |
|
571 | afterEach(() => {
|
572 | StyleSheetTestUtils.clearBufferAndResumeStyleInjection();
|
573 | });
|
574 |
|
575 | it('allows css to be called without requiring a DOM', (done) => {
|
576 | const sheet = StyleSheet.create({
|
577 | red: {
|
578 | color: 'red',
|
579 | },
|
580 | });
|
581 |
|
582 | css(sheet.red);
|
583 | asap(done);
|
584 | });
|
585 | });
|
586 |
|
587 | describe('StyleSheetTestUtils.getBufferedStyles', () => {
|
588 | beforeEach(() => {
|
589 | StyleSheetTestUtils.suppressStyleInjection();
|
590 | });
|
591 |
|
592 | afterEach(() => {
|
593 | StyleSheetTestUtils.clearBufferAndResumeStyleInjection();
|
594 | });
|
595 |
|
596 | it('returns injection buffer', () => {
|
597 | const sheet = StyleSheet.create({
|
598 | red: {
|
599 | color: 'red',
|
600 | },
|
601 | });
|
602 | css(sheet.red);
|
603 | asap(() => {
|
604 | const buffer = StyleSheetTestUtils.getBufferedStyles();
|
605 | assert.equal(buffer.length, 1);
|
606 | assert.include(buffer[0], 'color:red');
|
607 | assert.include(buffer[0], sheet.red._name);
|
608 | })
|
609 | });
|
610 | });
|