UNPKG

12.5 kBJavaScriptView Raw
1import {assert} from 'chai';
2
3import OrderedElements from '../src/ordered-elements';
4import {
5 generateCSSRuleset, generateCSS, defaultSelectorHandlers
6} from '../src/generate';
7
8describe('generateCSSRuleset', () => {
9 const assertCSSRuleset = (selector, declarations, expected) => {
10 const orderedDeclarations = new OrderedElements();
11 Object.keys(declarations).forEach((key) => {
12 orderedDeclarations.set(key, declarations[key]);
13 });
14
15 const actual = generateCSSRuleset(selector, orderedDeclarations);
16 const expectedNormalized = expected.split('\n').map(x => x.trim()).join('');
17 const formatStyles = (styles) => styles.replace(/(;|{|})/g, '$1\n');
18 assert.equal(
19 actual,
20 expectedNormalized,
21 `
22Expected:
23
24${formatStyles(expectedNormalized)}
25
26Actual:
27
28${formatStyles(actual)}
29`
30 );
31 };
32 it('returns a CSS string for a single property', () => {
33 assertCSSRuleset('.foo', {
34 color: 'red'
35 }, '.foo{color:red !important;}');
36 });
37
38 it('returns a CSS string for multiple properties', () => {
39 assertCSSRuleset('.foo', {
40 color: 'red',
41 background: 'blue'
42 }, `.foo{
43 color:red !important;
44 background:blue !important;
45 }`);
46 });
47
48 it('converts camelCase to kebab-case', () => {
49 assertCSSRuleset('.foo', {
50 backgroundColor: 'red'
51 }, '.foo{background-color:red !important;}');
52 });
53
54 it('prefixes vendor props with a dash', () => {
55 assertCSSRuleset('.foo', {
56 transition: 'none'
57 }, '.foo{-webkit-transition:none !important;' +
58 '-moz-transition:none !important;' +
59 'transition:none !important;' +
60 '}');
61 });
62
63 it('converts ms prefix to -ms-', () => {
64 assertCSSRuleset('.foo', {
65 MsTransition: 'none'
66 }, '.foo{-ms-transition:none !important;}');
67 });
68
69 it('returns an empty string if no props are set', () => {
70 assertCSSRuleset('.foo', {}, '');
71 });
72
73 it('correctly adds px to number units', () => {
74 assertCSSRuleset('.foo', {
75 width: 10,
76 zIndex: 5
77 }, '.foo{width:10px !important;z-index:5 !important;}');
78 });
79
80 it("doesn't break content strings which contain semicolons during importantify", () => {
81 assertCSSRuleset('.foo', {
82 content: '"foo;bar"'
83 }, '.foo{content:"foo;bar" !important;}');
84 });
85
86 it("doesn't break quoted url() arguments during importantify", () => {
87 assertCSSRuleset('.foo', {
88 background: 'url("data:image/svg+xml;base64,myImage")'
89 }, '.foo{background:url("data:image/svg+xml;base64,myImage") !important;}');
90 });
91
92 it("doesn't break unquoted url() arguments during importantify", () => {
93 assertCSSRuleset('.foo', {
94 background: 'url(data:image/svg+xml;base64,myImage)'
95 }, '.foo{background:url(data:image/svg+xml;base64,myImage) !important;}');
96 });
97
98 it("doesn't importantify rules that are already !important", () => {
99 assertCSSRuleset('.foo', {
100 color: 'blue !important',
101 }, '.foo{color:blue !important;}');
102 });
103});
104describe('generateCSS', () => {
105 const assertCSS = (className, styleTypes, expected, selectorHandlers = [],
106 stringHandlers = {}, useImportant = true) => {
107 const actual = generateCSS(className, styleTypes, selectorHandlers,
108 stringHandlers, useImportant);
109 const expectedArray = [].concat(expected);
110 const expectedNormalized = expectedArray.map(rule => rule.split('\n').map(x => x.trim()).join(''));
111 const formatStyles = (styles) => styles.map(style => style.replace(/(;|{|})/g, '$1\n')).join('');
112 assert.deepEqual(
113 actual,
114 expectedNormalized,
115 `
116Expected:
117
118${formatStyles(expectedNormalized)}
119
120Actual:
121
122${formatStyles(actual)}
123`
124 );
125 };
126
127 it('returns a CSS string for a single property', () => {
128 assertCSS('.foo', [{
129 color: 'red'
130 }], '.foo{color:red !important;}');
131 });
132
133 it('works with Map', () => {
134 assertCSS('.foo', [new Map([
135 ['color', 'red']
136 ])], '.foo{color:red !important;}');
137 });
138
139 it('works with two Maps', () => {
140 assertCSS('.foo', [
141 new Map([
142 ['color', 'red']
143 ]),
144 new Map([
145 ['color', 'blue']
146 ]),
147 ], '.foo{color:blue !important;}');
148 });
149
150 it('implements override logic', () => {
151 assertCSS('.foo', [{
152 color: 'red'
153 }, {
154 color: 'blue'
155 }], '.foo{color:blue !important;}');
156 });
157
158 it('does not mutate nested objects', () => {
159 const styles = {
160 a: {
161 ':after': {
162 content: 'a',
163 }
164 },
165 b: {
166 ':after': {
167 content: 'b',
168 }
169 }
170 };
171 generateCSS('.foo', [styles.a, styles.b], [], {}, true);
172 assert.equal(styles.a[':after'].content, 'a');
173 assert.equal(styles.b[':after'].content, 'b');
174 });
175
176 it('supports pseudo selectors', () => {
177 assertCSS('.foo', [{
178 ':hover': {
179 color: 'red'
180 }
181 }], '.foo:hover{color:red !important;}', defaultSelectorHandlers);
182 });
183
184 it('works with a nested Map', () => {
185 assertCSS('.foo', [{
186 ':hover': new Map([
187 ['color', 'red'],
188 ])
189 }], '.foo:hover{color:red !important;}', defaultSelectorHandlers);
190 });
191
192 it('works with two nested Maps', () => {
193 assertCSS('.foo', [
194 {':hover': new Map([
195 ['color', 'red'],
196 ])},
197 {':hover': new Map([
198 ['color', 'blue'],
199 ])}
200 ], '.foo:hover{color:blue !important;}', defaultSelectorHandlers);
201 });
202
203 it('supports media queries', () => {
204 assertCSS('.foo', [{
205 "@media (max-width: 400px)": {
206 color: "blue"
207 }
208 }], `@media (max-width: 400px){
209 .foo{color:blue !important;}
210 }`, defaultSelectorHandlers);
211 });
212
213 it('supports pseudo selectors inside media queries', () => {
214 assertCSS('.foo', [{
215 "@media (max-width: 400px)": {
216 ":hover": {
217 color: "blue"
218 }
219 }
220 }], `@media (max-width: 400px){
221 .foo:hover{color:blue !important;}
222 }`, defaultSelectorHandlers);
223 });
224
225 it('vendor prefixes in pseudo selectors inside media queries', () => {
226 assertCSS('.foo', [{
227 "@media (max-width: 400px)": {
228 ":hover": {
229 transform: "translateX(0)"
230 }
231 }
232 }], `@media (max-width: 400px){
233 .foo:hover{
234 -webkit-transform:translateX(0) !important;
235 -ms-transform:translateX(0) !important;
236 transform:translateX(0) !important;
237 }
238 }`, defaultSelectorHandlers);
239 });
240
241 it('supports combining pseudo selectors inside media queries', () => {
242 assertCSS('.foo', [
243 {"@media (max-width: 400px)": {
244 ":hover": {
245 background: "blue",
246 color: "blue"
247 }
248 }},
249 {"@media (max-width: 400px)": {
250 ":hover": {
251 color: "red"
252 }
253 }}
254 ], `@media (max-width: 400px){
255 .foo:hover{
256 background:blue !important;
257 color:red !important;
258 }
259 }`, defaultSelectorHandlers);
260 });
261
262 it('orders overrides in the expected way', () => {
263 assertCSS('.foo', [
264 {
265 "@media (min-width: 400px)": {
266 padding: 10,
267 }
268 },
269 {
270 "@media (min-width: 200px)": {
271 padding: 20,
272 },
273 "@media (min-width: 400px)": {
274 padding: 30,
275 }
276 }
277 ], [
278 `@media (min-width: 200px){
279 .foo{
280 padding:20px !important;
281 }
282 }`,
283 `@media (min-width: 400px){
284 .foo{
285 padding:30px !important;
286 }
287 }`], defaultSelectorHandlers);
288 });
289
290 it('supports custom string handlers', () => {
291 assertCSS('.foo', [{
292 fontFamily: ["Helvetica", "sans-serif"]
293 }], '.foo{font-family:Helvetica, sans-serif !important;}', [], {
294 fontFamily: (val) => val.join(", "),
295 });
296 });
297
298 it('make it possible to disable !important', () => {
299 assertCSS('@font-face', [{
300 fontFamily: ["FontAwesome"],
301 fontStyle: "normal",
302 }], '@font-face{font-family:FontAwesome;font-style:normal;}',
303 defaultSelectorHandlers, {
304 fontFamily: (val) => val.join(", "),
305 }, false);
306 });
307
308 it('adds browser prefixes', () => {
309 assertCSS('.foo', [{
310 display: 'flex',
311 transition: 'all 0s',
312 alignItems: 'center',
313 WebkitAlignItems: 'center',
314 justifyContent: 'center',
315 }], '.foo{' +
316 '-webkit-box-pack:center !important;' +
317 '-ms-flex-pack:center !important;' +
318 '-webkit-box-align:center !important;' +
319 '-ms-flex-align:center !important;' +
320 'display:-webkit-box !important;' +
321 'display:-moz-box !important;' +
322 'display:-ms-flexbox !important;' +
323 'display:-webkit-flex !important;' +
324 'display:flex !important;' +
325 '-webkit-transition:all 0s !important;' +
326 '-moz-transition:all 0s !important;' +
327 'transition:all 0s !important;' +
328 'align-items:center !important;' +
329 '-webkit-align-items:center !important;' +
330 '-webkit-justify-content:center !important;' +
331 'justify-content:center !important;' +
332 '}',
333 defaultSelectorHandlers);
334 });
335
336 it('supports other selector handlers', () => {
337 const handler = (selector, baseSelector, callback) => {
338 if (selector[0] !== '^') {
339 return null;
340 }
341 return callback(`.${selector.slice(1)} ${baseSelector}`);
342 };
343
344 assertCSS('.foo', [{
345 '^bar': {
346 color: 'red',
347 },
348 color: 'blue',
349 }], ['.foo{color:blue;}','.bar .foo{color:red;}'], [handler], {}, false);
350 });
351
352 it('supports selector handlers that return strings containing multiple rules', () => {
353 const handler = (selector, baseSelector, callback) => {
354 if (selector[0] !== '^') {
355 return null;
356 }
357 const generatedBefore = callback(baseSelector + '::before');
358 const generatedAfter = callback(baseSelector + '::after');
359 return `${generatedBefore} ${generatedAfter}`;
360 };
361
362 assertCSS('.foo', [{
363 '^': {
364 color: 'red',
365 },
366 }], ['@media all {.foo::before{color:red;} .foo::after{color:red;}}'], [handler], {}, false);
367 });
368
369 it('correctly prefixes border-color transition properties', () => {
370 assertCSS('.foo', [{
371 'transition': 'border-color 200ms linear'
372 }], '.foo{' +
373 '-webkit-transition:border-color 200ms linear !important;' +
374 '-moz-transition:border-color 200ms linear !important;' +
375 'transition:border-color 200ms linear !important;' +
376 '}');
377 });
378
379 it('correctly prefixes flex properties for IE10 support', () => {
380 assertCSS('.foo', [{
381 'flex': 'auto'
382 }], '.foo{' +
383 '-webkit-flex:auto !important;' +
384 '-ms-flex:1 1 auto !important;' +
385 'flex:auto !important;' +
386 '}');
387 });
388
389 // TODO(emily): In the future, filter out null values.
390 it('handles nullish values', () => {
391 assertCSS('.foo', [{
392 'color': null,
393 'margin': undefined,
394 }], '.foo{' +
395 'color:null !important;' +
396 'margin:undefined !important;' +
397 '}');
398 });
399});