Repository: addyosmani/critical (JavaScript)

Issue:
Multiple resolutions causes order of css to change when @media rules are used
I'm generating the critical CSS for my landing page for multiple screen dimensions.  If the input CSS contains `@media` screen size selectors, then the order of the rules in the critical css generated output can change depending on the order of the screen dimensions list.

**This is causing my landing page to render incorrectly on certain screen sizes.**

This an excerpt from my input CSS (generated from SCSS):
```css
#sales-index-top p {
  font-size: 2.7vw;
  ...other css rules  }
  @media (max-width: 518px) {
    #sales-index-top p {
      font-size: 14px; } }
  @media (min-width: 800px) {
    #sales-index-top p {
      font-size: 21.6px; } }
```
The expected result is that the text size scales automatically with screen size (using `vw` units), but with a minimum and maximum size using `@media` queries.

Here's my dimensions list:
```js
const dimensions = '1440x900,960x600,600x960,412x732,320x568'.split(',').map(dims => {
  const [width, height] = dims.split('x');
  return {width, height}
});
```
This results in the following (unminified) critical CSS, (note the line numbers):
```css
115 @media (min-width: 800px) {
116   #sales-index-top p {
117     font-size: 21.6px;
118   }
119 }
...
236 #sales-index-top p {
237   font-size: 2.7vw;
         ... other css rules...
240 }
241 @media (max-width: 518px) {
242   #sales-index-top p {
243     font-size: 14px;
244   }
245 }
```
On large screens the generic rule is overriding the `@media (min-width: 800px)` rule because it comes later in the file, so my font is too big.

If I reverse the order of my `dimenions` list then the resulting css is as expected (but there could be other order changes lurking in there).

**The expected result** is that the ultimate order of css rules remains unchanged regardless of the order that the different screen dimensions are rendered in.

I am using critical@next (2.0.0-23)


Discussion:
Good catch @wildhart. I've made some tests and it seems the order of the dimensions needs to be from small to wide. I'm enforcing this now by sorting the array so the order in which it's passed to critical doesn't matter.



Patch:
diff --git a/src/core.js b/src/core.js
index ae3f4156..b77e6f35 100644
--- a/src/core.js
+++ b/src/core.js
@@ -58,7 +58,11 @@ function callPenthouse(document, options) {
   const {customPageHeaders = {}} = params;
   const {css: cssString, url} = document;
   const config = {...params, cssString, url};
-  const sizes = Array.isArray(dimensions) ? dimensions : [{width, height}];
+  // Dimensions need to be sorted from small to wide. Otherwise the order gets corrupted
+  const sizes = Array.isArray(dimensions)
+    ? dimensions.slice().sort((a, b) => (a.width || 0) - (b.width || 0))
+    : [{width, height}];
+
   if (userAgent) {
     config.userAgent = userAgent;
   }


Test patch:
diff --git a/test/blackbox.test.js b/test/blackbox.test.js
index 023b653c..b8f807a0 100644
--- a/test/blackbox.test.js
+++ b/test/blackbox.test.js
@@ -863,6 +863,28 @@ describe('generate (local)', () => {
       assertCritical(target, expected, done)
     );
   });
+
+  test('Correctly order css on multiple dimensions', done => {
+    const dimensions = [700, 600, 100, 200, 250, 150, 350, 400, 450, 500, 300, 550, 50].map(width => {
+      return {width, height: 1000};
+    });
+
+    const expected = read('fixtures/styles/issue-415.css');
+    const target = path.join(__dirname, '.issue-415.css');
+
+    generate(
+      {
+        base: FIXTURES_DIR,
+        src: 'issue-415.html',
+        target,
+        minify: false,
+        inline: false,
+        dimensions,
+        concurrency: 10,
+      },
+      assertCritical(target, expected, done)
+    );
+  });
 });
 
 describe('generate (remote)', () => {
diff --git a/test/fixtures/issue-415.html b/test/fixtures/issue-415.html
new file mode 100644
index 00000000..271be2e4
--- /dev/null
+++ b/test/fixtures/issue-415.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
+    <title>Issue 415</title>
+    <link rel="stylesheet" href="styles/issue-415.css" />
+  </head>
+  <body>
+    <h1>Issue 415</h1>
+    <div id="element"><p>Lorem ipsum</p></div>
+  </body>
+</html>
diff --git a/test/fixtures/styles/issue-415.css b/test/fixtures/styles/issue-415.css
new file mode 100644
index 00000000..6647a6ff
--- /dev/null
+++ b/test/fixtures/styles/issue-415.css
@@ -0,0 +1,68 @@
+@media only screen and (max-width: 55px) {
+  #element p {
+    font-size: 1px;
+  }
+}
+@media only screen and (max-width: 150px) {
+  #element p {
+    font-size: 2px;
+  }
+}
+#element p {
+  font-size: inherit;
+}
+@media only screen and (min-width: 50px) {
+  #element p {
+    font-size: 1px;
+  }
+}
+@media only screen and (min-width: 200px) {
+  #element p {
+    font-size: 4px;
+  }
+}
+@media only screen and (min-width: 100px) {
+  #element p {
+    font-size: 2px;
+  }
+}
+@media only screen and (min-width: 300px) {
+  #element p {
+    font-size: 6px;
+  }
+}
+@media only screen and (min-width: 70px) {
+  #element p {
+    font-size: 4px;
+  }
+}
+@media only screen and (min-width: 500px) {
+  #element p {
+    font-size: 10px;
+  }
+}
+@media only screen and (min-width: 400px) {
+  #element p {
+    font-size: 8px;
+  }
+}
+@media only screen and (max-width: 250px) {
+  #element p {
+    font-size: 4px;
+  }
+}
+@media only screen and (max-width: 350px) {
+  #element p {
+    font-size: 6px;
+  }
+}
+@media only screen and (max-width: 450px) {
+  #element p {
+    font-size: 8px;
+  }
+}
+@media only screen and (max-width: 550px) {
+  #element p {
+    font-size: 10px;
+  }
+}