Repository: jsdom/cssstyle (js)

Issue:
linear-gradient in background is parsed as invalid
I noticed that when I try to do snapshot testing on my react components with inline styles, `background: linear-gradient(...)` is missing from the snapshot.

I described the issue in details on stackoverflow: https://stackoverflow.com/questions/70832768/jest-tomatchsnapshot-removes-inline-styles

I then tracked the issue to this:

In [shorthandSetter](https://github.com/jsdom/cssstyle/blob/b527ed722364dc6d156487c652df100572075dee/lib/parsers.js#L561) the [shorthandParser](https://github.com/jsdom/cssstyle/blob/b527ed722364dc6d156487c652df100572075dee/lib/parsers.js#L520) returns `undefined` because [parseColor](https://github.com/jsdom/cssstyle/blob/b527ed722364dc6d156487c652df100572075dee/lib/parsers.js#L288) does know how to handle `linear-gradient`.


Pull request description:
Add parseImage() function
Fixes #148
This PR is made on top of #183, so #183 can be closed too.

* Add support for multiple background-images
* Add support for CSS gradients


Patch:
diff --git a/lib/parsers.js b/lib/parsers.js
index d5ae9c3..6fe35cd 100644
--- a/lib/parsers.js
+++ b/lib/parsers.js
@@ -4,7 +4,8 @@
  ********************************************************************/
 'use strict';
 
-const { cssCalc, isColor, resolve } = require('@asamuzakjp/css-color');
+const { resolve: resolveColor, utils } = require('@asamuzakjp/css-color');
+const { cssCalc, isColor, isGradient, splitValue } = utils;
 
 exports.TYPES = {
   INTEGER: 1,
@@ -19,6 +20,7 @@ exports.TYPES = {
   NULL_OR_EMPTY_STR: 10,
   CALC: 11,
   VAR: 12,
+  GRADIENT: 13,
 };
 
 // regular expressions
@@ -78,6 +80,9 @@ exports.valueType = function valueType(val) {
   if (isColor(val)) {
     return exports.TYPES.COLOR;
   }
+  if (isGradient(val)) {
+    return exports.TYPES.GRADIENT;
+  }
 
   switch (val.toLowerCase()) {
     // the following are deprecated in CSS3
@@ -258,7 +263,7 @@ exports.parseColor = function parseColor(val) {
   if (/^[a-z]+$/i.test(val) && type === exports.TYPES.COLOR) {
     return val;
   }
-  var res = resolve(val, {
+  var res = resolveColor(val, {
     format: 'specifiedValue',
   });
   if (res) {
@@ -310,6 +315,40 @@ exports.parseKeyword = function parseKeyword(val, valid_keywords) {
   return undefined;
 };
 
+exports.parseImage = function parseImage(val) {
+  if (/^(?:none|inherit)$/i.test(val)) {
+    return val;
+  }
+  var type = exports.valueType(val);
+  if (type === exports.TYPES.NULL_OR_EMPTY_STR || type === exports.TYPES.VAR) {
+    return val;
+  }
+  var values = splitValue(val, ',');
+  var isImage = !!values.length;
+  var i;
+  for (i = 0; i < values.length; i++) {
+    var image = values[i];
+    var t = exports.valueType(image);
+    if (t === exports.TYPES.NULL_OR_EMPTY_STR) {
+      return image;
+    }
+    if (t === exports.TYPES.GRADIENT || /^(?:none|inherit)$/i.test(image)) {
+      continue;
+    }
+    var imageUrl = exports.parseUrl(image);
+    if (exports.valueType(imageUrl) === exports.TYPES.URL) {
+      values[i] = imageUrl;
+    } else {
+      isImage = false;
+      break;
+    }
+  }
+  if (isImage) {
+    return values.join(', ');
+  }
+  return undefined;
+};
+
 // utility to translate from border-width to borderWidth
 var dashedToCamelCase = function (dashed) {
   var i;
diff --git a/lib/properties/backgroundImage.js b/lib/properties/backgroundImage.js
index 6f230c8..0f82367 100644
--- a/lib/properties/backgroundImage.js
+++ b/lib/properties/backgroundImage.js
@@ -3,16 +3,10 @@
 var parsers = require('../parsers');
 
 var parse = function parse(v) {
-  var parsed = parsers.parseUrl(v);
+  var parsed = parsers.parseImage(v);
   if (parsed !== undefined) {
     return parsed;
   }
-  if (
-    parsers.valueType(v) === parsers.TYPES.KEYWORD &&
-    (v.toLowerCase() === 'none' || v.toLowerCase() === 'inherit')
-  ) {
-    return v;
-  }
   return undefined;
 };
 


Test patch:
diff --git a/test/parsers.js b/test/parsers.js
index a06ef7f..8cc0dac 100644
--- a/test/parsers.js
+++ b/test/parsers.js
@@ -253,6 +253,89 @@ describe('parseAngle', () => {
 describe('parseKeyword', () => {
   it.todo('test');
 });
+describe('parseImage', () => {
+  it('should return value', () => {
+    let input = 'none';
+    let output = parsers.parseImage(input);
+
+    assert.strictEqual(output, 'none');
+  });
+
+  it('should return value', () => {
+    let input = 'inherit';
+    let output = parsers.parseImage(input);
+
+    assert.strictEqual(output, 'inherit');
+  });
+
+  it('should return undefined', () => {
+    let input = 'foo';
+    let output = parsers.parseImage(input);
+
+    assert.strictEqual(output, undefined);
+  });
+
+  it('should return undefined for negative radii', () => {
+    let input = 'radial-gradient(circle -10px at center, red, blue)';
+    let output = parsers.parseImage(input);
+
+    assert.strictEqual(output, undefined);
+  });
+
+  it('should return empty string', () => {
+    let input = '';
+    let output = parsers.parseImage(input);
+
+    assert.strictEqual(output, '');
+  });
+
+  it('should return value', () => {
+    let input = 'url(example.png)';
+    let output = parsers.parseImage(input);
+
+    assert.strictEqual(output, 'url(example.png)');
+  });
+
+  it('should return value', () => {
+    let input = 'url(example.png), url("example2.png")';
+    let output = parsers.parseImage(input);
+
+    assert.strictEqual(output, 'url(example.png), url(example2.png)');
+  });
+
+  it('should return value', () => {
+    let input = 'none, url(example.png)';
+    let output = parsers.parseImage(input);
+
+    assert.strictEqual(output, 'none, url(example.png)');
+  });
+
+  it('should return value', () => {
+    let input = 'linear-gradient(green, blue), url(example.png)';
+    let output = parsers.parseImage(input);
+
+    assert.strictEqual(output, 'linear-gradient(green, blue), url(example.png)');
+  });
+
+  it('should return value as is if var() is included', () => {
+    let input = 'radial-gradient(transparent, /* comment */ var(--custom-color)), url(example.png)';
+    let output = parsers.parseImage(input);
+
+    assert.strictEqual(
+      output,
+      'radial-gradient(transparent, /* comment */ var(--custom-color)), url(example.png)'
+    );
+  });
+
+  it('should return value as is if var() is included and even if invalid image type is included', () => {
+    let input = 'radial-gradient(transparent, var(--custom-color)), red';
+    let output = parsers.parseImage(input);
+
+    assert.strictEqual(output, 'radial-gradient(transparent, var(--custom-color)), red');
+  });
+
+  it.todo('test');
+});
 describe('dashedToCamelCase', () => {
   it.todo('test');
 });