Repository: jsdom/cssstyle (js)

Issue:
Fix `background-*` and `background` shorthand
`background` related properties lacks supports for below.

* `background-position` with offset
  * `background-position: left 10px center`
  * `background-position: left center 20px`
  * `background-position: left 10px center 20px`
* `background-repeat` with two values, i.e. shorthand for `background-repeat-x`, `background-repeat-y`
  * `background-repeat: repeat no-repeat`
* `background` shorthand sets / gets longhand values
  * `background: fixed left center repeat url(example.png) green`
* multiple backgrounds
  * `background: fixed left repeat url(example.png), scroll center no-repeat linearGradient(to right, green, blue) green`


Pull request:
Fix background-position and background shorthand
Follow up for #227
Fixes #209 
Regression tests: https://github.com/jsdom/jsdom/pull/3904


Patch:
diff --git a/lib/properties/background.js b/lib/properties/background.js
index a93b9e9..df89651 100644
--- a/lib/properties/background.js
+++ b/lib/properties/background.js
@@ -24,7 +24,7 @@ module.exports.shorthandFor = new Map([
 const initialValues = new Map([
   ["background-image", "none"],
   ["background-position", "0% 0%"],
-  ["background-size", "auto auto"],
+  ["background-size", "auto"],
   ["background-repeat", "repeat"],
   ["background-origin", "padding-box"],
   ["background-clip", "border-box"],
@@ -40,38 +40,25 @@ module.exports.parse = function parse(v) {
   const l = values.length;
   for (let i = 0; i < l; i++) {
     let bg = {
-      "background-image": "",
-      "background-position": "",
-      "background-size": "",
-      "background-repeat": "",
-      "background-origin": "",
-      "background-clip": "",
-      "background-attachment": "",
-      "background-color": ""
+      "background-image": initialValues.get("background-image"),
+      "background-position": initialValues.get("background-position"),
+      "background-size": initialValues.get("background-size"),
+      "background-repeat": initialValues.get("background-repeat"),
+      "background-origin": initialValues.get("background-origin"),
+      "background-clip": initialValues.get("background-clip"),
+      "background-attachment": initialValues.get("background-attachment"),
+      "background-color": initialValues.get("background-color")
     };
-    if (l > 1) {
-      if (i === l - 1) {
-        bg = {
-          "background-image": initialValues.get("background-image"),
-          "background-position": initialValues.get("background-position"),
-          "background-size": initialValues.get("background-size"),
-          "background-repeat": initialValues.get("background-repeat"),
-          "background-origin": initialValues.get("background-origin"),
-          "background-clip": initialValues.get("background-clip"),
-          "background-attachment": initialValues.get("background-attachment"),
-          "background-color": initialValues.get("background-color")
-        };
-      } else {
-        bg = {
-          "background-image": initialValues.get("background-image"),
-          "background-position": initialValues.get("background-position"),
-          "background-size": initialValues.get("background-size"),
-          "background-repeat": initialValues.get("background-repeat"),
-          "background-origin": initialValues.get("background-origin"),
-          "background-clip": initialValues.get("background-clip"),
-          "background-attachment": initialValues.get("background-attachment")
-        };
-      }
+    if (l > 1 && i !== l - 1) {
+      bg = {
+        "background-image": initialValues.get("background-image"),
+        "background-position": initialValues.get("background-position"),
+        "background-size": initialValues.get("background-size"),
+        "background-repeat": initialValues.get("background-repeat"),
+        "background-origin": initialValues.get("background-origin"),
+        "background-clip": initialValues.get("background-clip"),
+        "background-attachment": initialValues.get("background-attachment")
+      };
     }
     const bgPosition = [];
     const bgSize = [];
@@ -268,15 +255,24 @@ module.exports.definition = {
       for (const bgValue of bgValues) {
         const bg = [];
         for (const [property, value] of Object.entries(bgValue)) {
-          const arr = bgMap.get(property);
           if (value) {
+            const arr = bgMap.get(property);
             arr.push(value);
-            if (bgValues.length > 1) {
-              if (value !== initialValues.get(property)) {
+            bgMap.set(property, arr);
+            if (value !== initialValues.get(property)) {
+              if (property === "background-size") {
+                bg.push(`/ ${value}`);
+              } else {
+                bg.push(value);
+              }
+            } else if (property === "background-image") {
+              if (v === "none") {
+                bg.push(value);
+              }
+            } else if (property === "background-color") {
+              if (v === "transparent") {
                 bg.push(value);
               }
-            } else {
-              bg.push(value);
             }
           }
         }
@@ -289,11 +285,98 @@ module.exports.definition = {
     }
   },
   get() {
-    const val = this.getPropertyValue("background");
-    if (val) {
-      return val;
+    const v = this.getPropertyValue("background");
+    if (parsers.hasVarFunc(v)) {
+      return v;
+    }
+    const bgMap = new Map();
+    let l = 0;
+    for (const [property] of module.exports.shorthandFor) {
+      const val = this.getPropertyValue(property);
+      if (property === "background-image") {
+        if (
+          val === "none" &&
+          v === "none" &&
+          this.getPropertyValue("background-color") === "transparent"
+        ) {
+          return val;
+        }
+        if (val !== initialValues.get(property)) {
+          const imgValues = parsers.splitValue(val, {
+            delimiter: ","
+          });
+          l = imgValues.length;
+          bgMap.set(property, imgValues);
+        }
+      } else if (property === "background-color") {
+        if (val !== initialValues.get(property) || v.includes(val)) {
+          bgMap.set(property, [val]);
+        }
+      } else if (val !== initialValues.get(property)) {
+        bgMap.set(
+          property,
+          parsers.splitValue(val, {
+            delimiter: ","
+          })
+        );
+      }
+    }
+    if (l === 0) {
+      const [background] = bgMap.get("background-color");
+      if (background) {
+        return background;
+      }
+      return "";
+    }
+    const bgValues = [];
+    for (let i = 0; i < l; i++) {
+      bgValues[i] = [];
+    }
+    for (const [property, values] of bgMap) {
+      for (let i = 0; i < l; i++) {
+        switch (property) {
+          case "background-color": {
+            if (i === l - 1) {
+              const value = values[0];
+              if (parsers.hasVarFunc(value)) {
+                return "";
+              }
+              if (value && value !== initialValues.get(property)) {
+                const bgValue = bgValues[i];
+                bgValue.push(value);
+              }
+            }
+            break;
+          }
+          case "background-size": {
+            const value = values[i];
+            if (parsers.hasVarFunc(value)) {
+              return "";
+            }
+            if (value && value !== initialValues.get(property)) {
+              const bgValue = bgValues[i];
+              bgValue.push(`/ ${value}`);
+            }
+            break;
+          }
+          default: {
+            const value = values[i];
+            if (parsers.hasVarFunc(value)) {
+              return "";
+            }
+            if (value && value !== initialValues.get(property)) {
+              const bgValue = bgValues[i];
+              bgValue.push(value);
+            }
+          }
+        }
+      }
+    }
+    const backgrounds = [];
+    for (const bgValue of bgValues) {
+      backgrounds.push(bgValue.join(" "));
     }
-    return "";
+    return backgrounds.join(", ");
   },
   enumerable: true,
   configurable: true
diff --git a/lib/properties/backgroundPosition.js b/lib/properties/backgroundPosition.js
index d905a31..6254158 100644
--- a/lib/properties/backgroundPosition.js
+++ b/lib/properties/backgroundPosition.js
@@ -27,7 +27,7 @@ module.exports.parse = function parse(v) {
         const val = parsers.parseMeasurement(part) || parsers.parseKeyword(part, keywords);
         if (val) {
           if (val === "center") {
-            parsedValue = val;
+            parsedValue = `${val} ${val}`;
           } else if (val === "top" || val === "bottom") {
             parsedValue = `center ${val}`;
           } else {
@@ -68,13 +68,13 @@ module.exports.parse = function parse(v) {
           let posY = "";
           let offY = "";
           if (keywordsX.includes(val1)) {
-            if (keywordsY.includes(val2)) {
+            if (keyY.includes(val2)) {
               if (!keywords.includes(val3)) {
                 posX = val1;
                 posY = val2;
                 offY = val3;
               }
-            } else if (keywordsY.includes(val3)) {
+            } else if (keyY.includes(val3)) {
               if (!keywords.includes(val2)) {
                 posX = val1;
                 offX = val2;
@@ -82,13 +82,13 @@ module.exports.parse = function parse(v) {
               }
             }
           } else if (keywordsY.includes(val1)) {
-            if (keywordsX.includes(val2)) {
+            if (keyX.includes(val2)) {
               if (!keywords.includes(val3)) {
                 posX = val2;
                 offX = val3;
                 posY = val1;
               }
-            } else if (keywordsX.includes(val3)) {
+            } else if (keyX.includes(val3)) {
               if (!keywords.includes(val2)) {
                 posX = val3;
                 posY = val1;
@@ -118,7 +118,12 @@ module.exports.parse = function parse(v) {
           let offX = "";
           let posY = "";
           let offY = "";
-          if (keywordsX.includes(val1) && keywordsY.includes(val3)) {
+          if (keywordsX.includes(val1) && keyY.includes(val3)) {
+            posX = val1;
+            offX = val2;
+            posY = val3;
+            offY = val4;
+          } else if (keyX.includes(val1) && keywordsY.includes(val3)) {
             posX = val1;
             offX = val2;
             posY = val3;


Test patch:
diff --git a/test/properties.test.js b/test/properties.test.js
index dd58eec..c5d052b 100644
--- a/test/properties.test.js
+++ b/test/properties.test.js
@@ -192,6 +192,10 @@ describe("background", () => {
     testPropertyValue("background-origin", "padding-box, content-box", "padding-box, content-box");
   });
 
+  it("background-position should set / get keywords", () => {
+    testPropertyValue("background-position", "center", "center center");
+  });
+
   it("background-position should set / get keywords", () => {
     testPropertyValue("background-position", "left", "left center");
   });
@@ -261,11 +265,11 @@ describe("background", () => {
   });
 
   it("background-position should set / get keyword length with offset", () => {
-    testPropertyValue("background-position", "center 10px center", "center 10px center");
+    testPropertyValue("background-position", "center 10px center", "");
   });
 
   it("background-position should set / get keyword length with offset", () => {
-    testPropertyValue("background-position", "center center 10px", "center center 10px");
+    testPropertyValue("background-position", "center center 10px", "");
   });
 
   it("background-position should set / get keyword length with offset", () => {
@@ -293,7 +297,7 @@ describe("background", () => {
   });
 
   it("background-position should set / get keyword length with offset", () => {
-    testPropertyValue("background-position", "center 10px center 20px", "center 10px center 20px");
+    testPropertyValue("background-position", "center 10px center 20px", "");
   });
 
   it("background-position should set / get keyword length with offset", () => {
@@ -387,17 +391,17 @@ describe("background", () => {
   it("background shorthand should set / get value", () => {
     testImplicitPropertyValue(
       "background",
-      "fixed left repeat url(example.png) green",
-      'url("example.png") left center repeat fixed green',
+      "none",
+      "none",
       new Map([
-        ["background-image", 'url("example.png")'],
-        ["background-position", "left center"],
-        ["background-size", ""],
+        ["background-image", "none"],
+        ["background-position", "0% 0%"],
+        ["background-size", "auto"],
         ["background-repeat", "repeat"],
-        ["background-origin", ""],
-        ["background-clip", ""],
-        ["background-attachment", "fixed"],
-        ["background-color", "green"]
+        ["background-origin", "padding-box"],
+        ["background-clip", "border-box"],
+        ["background-attachment", "scroll"],
+        ["background-color", "transparent"]
       ])
     );
   });
@@ -405,24 +409,33 @@ describe("background", () => {
   it("background shorthand should set / get value", () => {
     testImplicitPropertyValue(
       "background",
-      "none",
-      "none",
-      new Map([["background-image", "none"]])
+      "transparent",
+      "transparent",
+      new Map([
+        ["background-image", "none"],
+        ["background-position", "0% 0%"],
+        ["background-size", "auto"],
+        ["background-repeat", "repeat"],
+        ["background-origin", "padding-box"],
+        ["background-clip", "border-box"],
+        ["background-attachment", "scroll"],
+        ["background-color", "transparent"]
+      ])
     );
   });
 
-  it("background shorthand should set / get sub longhand value", () => {
+  it("background shorthand should set / get sub longhand values", () => {
     testImplicitPropertyValue(
       "background",
-      "fixed left repeat url(example.png) green",
-      'url("example.png") left center repeat fixed green',
+      "fixed left / 50% repeat url(example.png) green",
+      'url("example.png") left center / 50% fixed green',
       new Map([
         ["background-image", 'url("example.png")'],
         ["background-position", "left center"],
-        ["background-size", ""],
+        ["background-size", "50%"],
         ["background-repeat", "repeat"],
-        ["background-origin", ""],
-        ["background-clip", ""],
+        ["background-origin", "padding-box"],
+        ["background-clip", "border-box"],
         ["background-attachment", "fixed"],
         ["background-color", "green"]
       ])
@@ -432,12 +445,12 @@ describe("background", () => {
   it("background shorthand should set / get multiple values", () => {
     testImplicitPropertyValue(
       "background",
-      "fixed left repeat url(example.png), linear-gradient(to right, green, blue) green",
-      'url("example.png") left center fixed, linear-gradient(to right, green, blue) green',
+      "fixed left / 50% repeat url(example.png), linear-gradient(to right, green, blue) green",
+      'url("example.png") left center / 50% fixed, linear-gradient(to right, green, blue) green',
       new Map([
         ["background-image", 'url("example.png"), linear-gradient(to right, green, blue)'],
         ["background-position", "left center, 0% 0%"],
-        ["background-size", "auto auto, auto auto"],
+        ["background-size", "50%, auto"],
         ["background-repeat", "repeat, repeat"],
         ["background-origin", "padding-box, padding-box"],
         ["background-clip", "border-box, border-box"],