Skip to content

Commit b0c39bb

Browse files
committed
feat: color gradient
1 parent 5e14857 commit b0c39bb

16 files changed

+696
-54
lines changed

lib/parsers.js

+448-12
Large diffs are not rendered by default.

lib/parsers.test.js

+214-11
Original file line numberDiff line numberDiff line change
@@ -70,26 +70,29 @@ describe('parseLength', () => {
7070
expect(parsers.parseLength('calc(1px + 1px)')).toBe('calc(2px)');
7171
});
7272
});
73-
describe('parsePercent', () => {
73+
describe('parsePercentage', () => {
7474
it('returns undefined for invalid values', () => {
7575
const invalid = ['string', '1%%', '1px%', '#1%', 'calc(1 * 1px)'];
76-
invalid.forEach(input => expect(parsers.parsePercent(input)).toBeUndefined());
76+
invalid.forEach(input => expect(parsers.parsePercentage(input)).toBeUndefined());
7777
});
7878
it('parses percent with exponent', () => {
79-
expect(parsers.parsePercent('1e1%')).toBe('10%');
80-
expect(parsers.parsePercent('1e+1%')).toBe('10%');
81-
expect(parsers.parsePercent('1e-1%')).toBe('0.1%');
79+
expect(parsers.parsePercentage('1e1%')).toBe('10%');
80+
expect(parsers.parsePercentage('1e+1%')).toBe('10%');
81+
expect(parsers.parsePercentage('1e-1%')).toBe('0.1%');
8282
});
8383
it('parses percent with missing leading 0', () => {
84-
expect(parsers.parsePercent('.1%')).toBe('0.1%');
84+
expect(parsers.parsePercentage('.1%')).toBe('0.1%');
8585
});
8686
it('returns percent without trailing 0 in decimals', () => {
87-
expect(parsers.parsePercent('0.10%')).toBe('0.1%');
87+
expect(parsers.parsePercentage('0.10%')).toBe('0.1%');
8888
});
8989
it('works with calc', () => {
90-
expect(parsers.parsePercent('calc(1% + 1%)')).toBe('calc(2%)');
90+
expect(parsers.parsePercentage('calc(1% + 1%)')).toBe('calc(2%)');
9191
});
9292
});
93+
describe('parseLengthOrPercentage', () => {
94+
it.todo('test');
95+
});
9396
describe('parseAlpha', () => {
9497
it('returns undefined for invalid values', () => {
9598
const invalid = ['string', '1%%', '1px%', '#1%', 'calc(1 * 1px)'];
@@ -124,9 +127,6 @@ describe('parseAlpha', () => {
124127
expect(parsers.parseAlpha('calc(0.5 + 0.5)')).toBe('1');
125128
});
126129
});
127-
describe('parseMeasurement', () => {
128-
it.todo('test');
129-
});
130130
describe('parseAngle', () => {
131131
it('returns undefined for invalid values', () => {
132132
const invalid = ['string', '1', '1degg', 'a1deg', 'deg', 'calc(1 * 1px)'];
@@ -251,12 +251,46 @@ describe('parseCustomIdentifier', () => {
251251
expect(parsers.parseCustomIdentifier('myCustomIdentifier')).toBe('myCustomIdentifier');
252252
});
253253
});
254+
describe('parseImage', () => {
255+
it.todo('tests');
256+
});
254257
describe('parseUrl', () => {
255258
it.todo('test');
256259
});
257260
describe('parseString', () => {
258261
it.todo('test');
259262
});
263+
describe('parsePosition', () => {
264+
it('returns undefined for invalid values', () => {
265+
const invalid = [
266+
'side',
267+
'1',
268+
'left left',
269+
'top top',
270+
'left top center',
271+
'left top 50%',
272+
'0% 0% 0%',
273+
'top 50%',
274+
'50% left',
275+
];
276+
invalid.forEach(input => expect(parsers.parsePosition(input)).toBeUndefined());
277+
});
278+
it('resolves 0 as 0px', () => {
279+
expect(parsers.parsePosition('0 0')).toBe('0px 0px');
280+
});
281+
it('resolves with center as a default value', () => {
282+
expect(parsers.parsePosition('0%')).toBe('0% center');
283+
expect(parsers.parsePosition('left')).toBe('left center');
284+
expect(parsers.parsePosition('top')).toBe('center top');
285+
expect(parsers.parsePosition('center')).toBe('center center');
286+
});
287+
it('resolves with horizontal position first', () => {
288+
expect(parsers.parsePosition('top left')).toBe('left top');
289+
});
290+
it('resolves with lowercased position', () => {
291+
expect(parsers.parsePosition('LEFt 0%')).toBe('left 0%');
292+
});
293+
});
260294
describe('parseColor', () => {
261295
it('returns undefined for invalid values', () => {
262296
const invalid = [
@@ -331,6 +365,175 @@ describe('parseColor', () => {
331365
);
332366
});
333367
});
368+
describe('parseGradient', () => {
369+
it('returns undefined for invalid values', () => {
370+
const invalid = [
371+
'string',
372+
'1',
373+
'invalid-gradient(red, cyan)',
374+
'invalid-conic-gradient(red, cyan)',
375+
'conic-gradient()',
376+
'conic-gradient( , red, cyan)',
377+
'conic-gradient(0, cyan)',
378+
'conic-gradient(from , red, cyan)',
379+
'conic-gradient(from 1, red, cyan)',
380+
'conic-gradient(from 90deg 120deg, red, cyan)',
381+
'conic-gradient(at , red, cyan)',
382+
'conic-gradient(at 1, red, cyan)',
383+
'conic-gradient(at left center center, red, cyan)',
384+
'conic-gradient(red, 0%, 0%, cyan)',
385+
'conic-gradient(cyan 0deg)',
386+
'linear-gradient(0, cyan)',
387+
'linear-gradient(1, red, cyan)',
388+
'linear-gradient(90deg 120deg, red, cyan)',
389+
'linear-gradient(at , red, cyan)',
390+
'linear-gradient(at 1, red, cyan)',
391+
'linear-gradient(at left center center, red, cyan)',
392+
'linear-gradient(red, 0%, 0%, cyan)',
393+
'linear-gradient(cyan 0%)',
394+
'radial-gradient(0, cyan)',
395+
'radial-gradient(1, red, cyan)',
396+
'radial-gradient(circle 50%, red, cyan)',
397+
'radial-gradient(circle 100px 120px, red, cyan)',
398+
'radial-gradient(ellipse 50%, red, cyan)',
399+
'radial-gradient(50% closest-corner, red, cyan)',
400+
'radial-gradient(closest-corner 50%, red, cyan)',
401+
'radial-gradient(at , red, cyan)',
402+
'radial-gradient(at 1, red, cyan)',
403+
'radial-gradient(at left center center, red, cyan)',
404+
'radial-gradient(red, 0%, 0%, cyan)',
405+
'radial-gradient(cyan 0%)',
406+
];
407+
invalid.forEach(input => expect(parsers.parseGradient(input)).toBeUndefined());
408+
});
409+
it('parses a conic gradient', () => {
410+
[
411+
// [input, expected]
412+
['conic-gradient(red, cyan)', 'conic-gradient(red, cyan)'],
413+
['CONIC-gradient(red, cyan)', 'conic-gradient(red, cyan)'],
414+
['repeating-conic-gradient(red, cyan)', 'repeating-conic-gradient(red, cyan)'],
415+
['conic-gradient(from 0, red, cyan)', 'conic-gradient(from 0deg, red, cyan)'],
416+
['conic-gradient(from 2turn, red, cyan)', 'conic-gradient(from 2turn, red, cyan)'],
417+
['conic-gradient(at top, red, cyan)', 'conic-gradient(at center top, red, cyan)'],
418+
['conic-gradient(at left, red, cyan)', 'conic-gradient(at left center, red, cyan)'],
419+
['conic-gradient(at top left, red, cyan)', 'conic-gradient(at left top, red, cyan)'],
420+
['conic-gradient(at 0%, red, cyan)', 'conic-gradient(at 0% center, red, cyan)'],
421+
['conic-gradient(at -100% 200%, red, cyan)', 'conic-gradient(at -100% 200%, red, cyan)'],
422+
[
423+
'conic-gradient(from 0deg at left center, red, cyan)',
424+
'conic-gradient(from 0deg at left center, red, cyan)',
425+
],
426+
['conic-gradient(red, 50%, cyan)', 'conic-gradient(red, 50%, cyan)'],
427+
['conic-gradient(red 0 0, 0, cyan)', 'conic-gradient(red 0deg, red 0deg, 0deg, cyan)'],
428+
[
429+
'conic-gradient(red 0deg 1turn, 50%, cyan)',
430+
'conic-gradient(red 0deg, red 1turn, 50%, cyan)',
431+
],
432+
[
433+
'conic-gradient(red -1% 200%, 540deg, cyan)',
434+
'conic-gradient(red -1%, red 200%, 540deg, cyan)',
435+
],
436+
['conic-gradient(red 0deg 180deg)', 'conic-gradient(red 0deg, red 180deg)'],
437+
].forEach(([input, expected]) => expect(parsers.parseGradient(input)).toBe(expected));
438+
});
439+
it('parses a linear gradient', () => {
440+
[
441+
// [input, expected]
442+
['linear-gradient(red, cyan)', 'linear-gradient(red, cyan)'],
443+
['repeating-linear-gradient(red, cyan)', 'repeating-linear-gradient(red, cyan)'],
444+
['linear-gradient(0, red, cyan)', 'linear-gradient(0deg, red, cyan)'],
445+
['linear-gradient(2turn, red, cyan)', 'linear-gradient(2turn, red, cyan)'],
446+
['linear-gradient(to top, red, cyan)', 'linear-gradient(to top, red, cyan)'],
447+
['linear-gradient(to left, red, cyan)', 'linear-gradient(to left, red, cyan)'],
448+
['linear-gradient(to top left, red, cyan)', 'linear-gradient(to left top, red, cyan)'],
449+
['linear-gradient(to bottom, red, cyan)', 'linear-gradient(red, cyan)'],
450+
['linear-gradient(to left bottom, red, cyan)', 'linear-gradient(to left bottom, red, cyan)'],
451+
['linear-gradient(to -100% 200%, red, cyan)', 'linear-gradient(to -100% 200%, red, cyan)'],
452+
['linear-gradient(red, 50%, cyan)', 'linear-gradient(red, 50%, cyan)'],
453+
['linear-gradient(red 0 0, 0, cyan)', 'linear-gradient(red 0px, red 0px, 0px, cyan)'],
454+
[
455+
'linear-gradient(red 0px 100%, 50px, cyan)',
456+
'linear-gradient(red 0px, red 100%, 50px, cyan)',
457+
],
458+
[
459+
'linear-gradient(red -1% 200px, 150%, cyan)',
460+
'linear-gradient(red -1%, red 200px, 150%, cyan)',
461+
],
462+
['linear-gradient(red 0% 50%)', 'linear-gradient(red 0%, red 50%)'],
463+
].forEach(([input, expected]) => expect(parsers.parseGradient(input)).toBe(expected));
464+
});
465+
it('parses a radial gradient', () => {
466+
[
467+
// [input, expected]
468+
['radial-gradient(red, cyan)', 'radial-gradient(red, cyan)'],
469+
['repeating-radial-gradient(red, cyan)', 'repeating-radial-gradient(red, cyan)'],
470+
['radial-gradient(circle, red, cyan)', 'radial-gradient(circle, red, cyan)'],
471+
['radial-gradient(circle 0, red, cyan)', 'radial-gradient(0px, red, cyan)'],
472+
['radial-gradient(circle 50px, red, cyan)', 'radial-gradient(50px, red, cyan)'],
473+
['radial-gradient(50px circle, red, cyan)', 'radial-gradient(50px, red, cyan)'],
474+
['radial-gradient(circle farthest-corner, red, cyan)', 'radial-gradient(circle, red, cyan)'],
475+
['radial-gradient(farthest-corner circle, red, cyan)', 'radial-gradient(circle, red, cyan)'],
476+
[
477+
'radial-gradient(circle farthest-side, red, cyan)',
478+
'radial-gradient(circle farthest-side, red, cyan)',
479+
],
480+
[
481+
'radial-gradient(farthest-side circle, red, cyan)',
482+
'radial-gradient(circle farthest-side, red, cyan)',
483+
],
484+
['radial-gradient(ellipse, red, cyan)', 'radial-gradient(red, cyan)'],
485+
['radial-gradient(ellipse 0 120%, red, cyan)', 'radial-gradient(0px 120%, red, cyan)'],
486+
['radial-gradient(ellipse 80% 100%, red, cyan)', 'radial-gradient(80% 100%, red, cyan)'],
487+
['radial-gradient(80% 100% ellipse, red, cyan)', 'radial-gradient(80% 100%, red, cyan)'],
488+
['radial-gradient(ellipse farthest-corner, red, cyan)', 'radial-gradient(red, cyan)'],
489+
['radial-gradient(farthest-corner ellipse, red, cyan)', 'radial-gradient(red, cyan)'],
490+
[
491+
'radial-gradient(ellipse farthest-side, red, cyan)',
492+
'radial-gradient(farthest-side, red, cyan)',
493+
],
494+
[
495+
'radial-gradient(farthest-side ellipse, red, cyan)',
496+
'radial-gradient(farthest-side, red, cyan)',
497+
],
498+
['radial-gradient(at top, red, cyan)', 'radial-gradient(at center top, red, cyan)'],
499+
['radial-gradient(at left, red, cyan)', 'radial-gradient(at left center, red, cyan)'],
500+
['radial-gradient(at top left, red, cyan)', 'radial-gradient(at left top, red, cyan)'],
501+
['radial-gradient(at -100% 200%, red, cyan)', 'radial-gradient(at -100% 200%, red, cyan)'],
502+
[
503+
'radial-gradient(circle closest-side at center center, red, cyan)',
504+
'radial-gradient(circle closest-side at center center, red, cyan)',
505+
],
506+
['radial-gradient(red, 50%, cyan)', 'radial-gradient(red, 50%, cyan)'],
507+
['radial-gradient(red 0 0, 0, cyan)', 'radial-gradient(red 0px, red 0px, 0px, cyan)'],
508+
[
509+
'radial-gradient(red 0px 100%, 50px, cyan)',
510+
'radial-gradient(red 0px, red 100%, 50px, cyan)',
511+
],
512+
[
513+
'radial-gradient(red -1% 200px, 150%, cyan)',
514+
'radial-gradient(red -1%, red 200px, 150%, cyan)',
515+
],
516+
['radial-gradient(red 0% 50%)', 'radial-gradient(red 0%, red 50%)'],
517+
].forEach(([input, expected]) => expect(parsers.parseGradient(input)).toBe(expected));
518+
});
519+
it('works with calc', () => {
520+
[
521+
// [input, expected]
522+
[
523+
'conic-gradient(from calc(90deg * 2) at calc(25% * 2), red calc(5% * 2) calc(50% * 2), calc(25% * 2), cyan)',
524+
'conic-gradient(from calc(180deg) at calc(50%) center, red calc(10%), red calc(100%), calc(50%), cyan)',
525+
],
526+
[
527+
'linear-gradient(calc(90deg * 2), red calc(5% * 2) calc(50% * 2), calc(25% * 2), cyan)',
528+
'linear-gradient(red calc(10%), red calc(100%), calc(50%), cyan)',
529+
],
530+
[
531+
'radial-gradient(calc(25px * 2) at calc(25% * 2), red calc(5% * 2) calc(50% * 2), calc(25% * 2), cyan)',
532+
'radial-gradient(calc(50px) at calc(50%) center, red calc(10%), red calc(100%), calc(50%), cyan)',
533+
],
534+
].forEach(([input, expected]) => expect(parsers.parseGradient(input)).toBe(expected));
535+
});
536+
});
334537
describe('dashedToCamelCase', () => {
335538
it.todo('test');
336539
});

lib/properties/backgroundImage.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
'use strict';
22

3-
const { parseKeyword, parseUrl } = require('../parsers');
3+
const { parseImage, parseKeyword } = require('../parsers');
44

55
function parse(v) {
6-
return parseUrl(v) || parseKeyword(v, ['none']);
6+
return parseImage(v) || parseKeyword(v, ['none']);
77
}
88

99
module.exports.isValid = function isValid(v) {

lib/properties/backgroundPosition.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const { parseKeyword, parseMeasurement } = require('../parsers');
3+
const { parseKeyword, parseLengthOrPercentage } = require('../parsers');
44

55
var valid_keywords = ['top', 'center', 'bottom', 'left', 'right'];
66

@@ -13,9 +13,12 @@ var parse = function parse(v) {
1313
return undefined;
1414
}
1515
if (parts.length === 1) {
16-
return parseMeasurement(parts[0]) || parseKeyword(parts[0], valid_keywords);
16+
return parseLengthOrPercentage(parts[0]) || parseKeyword(parts[0], valid_keywords);
1717
}
18-
if (parseMeasurement(parts[0]) !== undefined && parseMeasurement(parts[1]) !== undefined) {
18+
if (
19+
parseLengthOrPercentage(parts[0]) !== undefined &&
20+
parseLengthOrPercentage(parts[1]) !== undefined
21+
) {
1922
return v;
2023
}
2124
if (

lib/properties/bottom.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
'use strict';
22

3-
var parseMeasurement = require('../parsers').parseMeasurement;
3+
var parseLengthOrPercentage = require('../parsers').parseLengthOrPercentage;
44

55
module.exports.definition = {
66
set: function(v) {
7-
this._setProperty('bottom', parseMeasurement(v));
7+
this._setProperty('bottom', parseLengthOrPercentage(v));
88
},
99
get: function() {
1010
return this.getPropertyValue('bottom');

lib/properties/clip.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
var parseMeasurement = require('../parsers').parseMeasurement;
3+
var parseLengthOrPercentage = require('../parsers').parseLengthOrPercentage;
44

55
var shape_regex = /^rect\((.*)\)$/i;
66

@@ -21,7 +21,7 @@ var parse = function(val) {
2121
return undefined;
2222
}
2323
var valid = parts.every(function(part, index) {
24-
var measurement = parseMeasurement(part);
24+
var measurement = parseLengthOrPercentage(part);
2525
parts[index] = measurement;
2626
return measurement !== undefined;
2727
});

lib/properties/flexBasis.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
var parseMeasurement = require('../parsers').parseMeasurement;
3+
var parseLengthOrPercentage = require('../parsers').parseLengthOrPercentage;
44

55
function parse(v) {
66
if (v.toLowerCase() === 'auto') {
@@ -9,7 +9,7 @@ function parse(v) {
99
if (v.toLowerCase() === 'inherit') {
1010
return 'inherit';
1111
}
12-
return parseMeasurement(v);
12+
return parseLengthOrPercentage(v);
1313
}
1414

1515
module.exports.isValid = function isValid(v) {

lib/properties/fontSize.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
'use strict';
22

3-
const { parseKeyword, parseMeasurement } = require('../parsers');
3+
const { parseKeyword, parseLengthOrPercentage } = require('../parsers');
44

55
var absoluteSizes = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'];
66
var relativeSizes = ['larger', 'smaller'];
77
const allSizes = absoluteSizes.concat(relativeSizes);
88

99
function parse(v) {
10-
return parseKeyword(v, allSizes) || parseMeasurement(v);
10+
return parseKeyword(v, allSizes) || parseLengthOrPercentage(v);
1111
}
1212

1313
module.exports.isValid = function(v) {

lib/properties/height.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
var parseMeasurement = require('../parsers').parseMeasurement;
3+
var parseLengthOrPercentage = require('../parsers').parseLengthOrPercentage;
44

55
function parse(v) {
66
if (v.toLowerCase() === 'auto') {
@@ -9,7 +9,7 @@ function parse(v) {
99
if (v.toLowerCase() === 'inherit') {
1010
return 'inherit';
1111
}
12-
return parseMeasurement(v);
12+
return parseLengthOrPercentage(v);
1313
}
1414

1515
module.exports.definition = {

lib/properties/left.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
'use strict';
22

3-
var parseMeasurement = require('../parsers').parseMeasurement;
3+
var parseLengthOrPercentage = require('../parsers').parseLengthOrPercentage;
44

55
module.exports.definition = {
66
set: function(v) {
7-
this._setProperty('left', parseMeasurement(v));
7+
this._setProperty('left', parseLengthOrPercentage(v));
88
},
99
get: function() {
1010
return this.getPropertyValue('left');

0 commit comments

Comments
 (0)