1 | import {assert} from 'chai';
|
2 |
|
3 | import OrderedElements from '../src/ordered-elements';
|
4 | import {
|
5 | generateCSSRuleset, generateCSS, defaultSelectorHandlers
|
6 | } from '../src/generate';
|
7 |
|
8 | describe('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 | `
|
22 | Expected:
|
23 |
|
24 | ${formatStyles(expectedNormalized)}
|
25 |
|
26 | Actual:
|
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 | });
|
104 | describe('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 | `
|
116 | Expected:
|
117 |
|
118 | ${formatStyles(expectedNormalized)}
|
119 |
|
120 | Actual:
|
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 |
|
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 | });
|