Repository: jsdom/cssstyle
Pull request #109 (2019-12-05 11:08:30)

Fix parsing of colors with decimal alpha
Fix issue https://github.com/jsdom/jsdom/issues/2639. 
Color with decimal alpha values were not recognized.


Patch:
diff --git a/lib/parsers.js b/lib/parsers.js
index 3d8d620..5bc92cd 100644
--- a/lib/parsers.js
+++ b/lib/parsers.js
@@ -5,6 +5,7 @@
 'use strict';
 
 const namedColors = require('./named_colors.json');
+const { hslToRgb } = require('./utils/colorSpace');
 
 exports.TYPES = {
   INTEGER: 1,
@@ -92,7 +93,7 @@ exports.valueType = function valueType(val) {
     }
     if (
       parts.slice(0, 3).every(percentRegEx.test.bind(percentRegEx)) ||
-      parts.every(integerRegEx.test.bind(integerRegEx))
+      parts.slice(0, 3).every(integerRegEx.test.bind(integerRegEx))
     ) {
       if (numberRegEx.test(parts[3])) {
         return exports.TYPES.COLOR;
@@ -378,10 +379,12 @@ exports.parseColor = function parseColor(val) {
     if (_alpha && numberRegEx.test(_alpha)) {
       alpha = parseFloat(_alpha);
     }
+
+    const [r, g, b] = hslToRgb(hue, saturation / 100, lightness / 100);
     if (!_alphaString || alpha === 1) {
-      return 'hsl(' + hue + ', ' + saturation + '%, ' + lightness + '%)';
+      return 'rgb(' + r + ', ' + g + ', ' + b + ')';
     }
-    return 'hsla(' + hue + ', ' + saturation + '%, ' + lightness + '%, ' + alpha + ')';
+    return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')';
   }
 
   if (type === exports.TYPES.COLOR) {
diff --git a/lib/utils/colorSpace.js b/lib/utils/colorSpace.js
new file mode 100644
index 0000000..92ca7bd
--- /dev/null
+++ b/lib/utils/colorSpace.js
@@ -0,0 +1,21 @@
+'use strict';
+
+const hueToRgb = (t1, t2, hue) => {
+  if (hue < 0) hue += 6;
+  if (hue >= 6) hue -= 6;
+
+  if (hue < 1) return (t2 - t1) * hue + t1;
+  else if (hue < 3) return t2;
+  else if (hue < 4) return (t2 - t1) * (4 - hue) + t1;
+  else return t1;
+};
+
+// https://www.w3.org/TR/css-color-4/#hsl-to-rgb
+exports.hslToRgb = (hue, sat, light) => {
+  const t2 = light <= 0.5 ? light * (sat + 1) : light + sat - light * sat;
+  const t1 = light * 2 - t2;
+  const r = hueToRgb(t1, t2, hue + 2);
+  const g = hueToRgb(t1, t2, hue);
+  const b = hueToRgb(t1, t2, hue - 2);
+  return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
+};


Test patch:
diff --git a/lib/CSSStyleDeclaration.test.js b/lib/CSSStyleDeclaration.test.js
index f8903c7..a246036 100644
--- a/lib/CSSStyleDeclaration.test.js
+++ b/lib/CSSStyleDeclaration.test.js
@@ -184,9 +184,9 @@ describe('CSSStyleDeclaration', () => {
     style.color = 'rgba(300, 200, 100, 1.5)';
     expect(style.color).toEqual('rgb(255, 200, 100)');
     style.color = 'hsla(0, 1%, 2%, 0.5)';
-    expect(style.color).toEqual('hsla(0, 1%, 2%, 0.5)');
+    expect(style.color).toEqual('rgba(5, 5, 5, 0.5)');
     style.color = 'hsl(0, 1%, 2%)';
-    expect(style.color).toEqual('hsl(0, 1%, 2%)');
+    expect(style.color).toEqual('rgb(5, 5, 5)');
     style.color = 'rebeccapurple';
     expect(style.color).toEqual('rebeccapurple');
     style.color = 'transparent';
diff --git a/lib/parsers.test.js b/lib/parsers.test.js
index d7db528..1f57dfa 100644
--- a/lib/parsers.test.js
+++ b/lib/parsers.test.js
@@ -38,6 +38,13 @@ describe('valueType', () => {
     expect(output).toEqual(parsers.TYPES.COLOR);
   });
 
+  it('returns color for rgba(n, n, n, n) with decimal alpha', () => {
+    let input = 'rgba(10, 10, 10, 0.5)';
+    let output = parsers.valueType(input);
+
+    expect(output).toEqual(parsers.TYPES.COLOR);
+  });
+
   it('returns color for rgba(p, p, p, n)', () => {
     let input = 'rgba(10%, 10%, 10%, 1)';
     let output = parsers.valueType(input);
@@ -45,6 +52,13 @@ describe('valueType', () => {
     expect(output).toEqual(parsers.TYPES.COLOR);
   });
 
+  it('returns color for rgba(p, p, p, n) with decimal alpha', () => {
+    let input = 'rgba(10%, 10%, 10%, 0.5)';
+    let output = parsers.valueType(input);
+
+    expect(output).toEqual(parsers.TYPES.COLOR);
+  });
+
   it('returns length for 100ch', () => {
     let input = '100ch';
     let output = parsers.valueType(input);
@@ -74,7 +88,20 @@ describe('parseString', () => {
   it.todo('test');
 });
 describe('parseColor', () => {
-  it.todo('test');
+  it('should convert hsl to rgb values', () => {
+    let input = 'hsla(0, 1%, 2%)';
+    let output = parsers.parseColor(input);
+
+    expect(output).toEqual('rgb(5, 5, 5)');
+  });
+  it('should convert hsla to rgba values', () => {
+    let input = 'hsla(0, 1%, 2%, 0.5)';
+    let output = parsers.parseColor(input);
+
+    expect(output).toEqual('rgba(5, 5, 5, 0.5)');
+  });
+
+  it.todo('Add more tests');
 });
 describe('parseAngle', () => {
   it.todo('test');