Quixote 0.6: Test Responsive Designs

Quixote 0.6 is up today. You can download it from npm or view the code on GitHub.

(Quixote is my library for unit testing CSS. It’s based on work we did on the Live channel of the screencast. It’s very fast, very expressive, and very cool, if I do say so myself.)

I learned a lot about determining page and viewport sizes in this release. Even if you’re not interested in Quixote, you’ll probably find that useful. Skip down to the bottom for more.

Testing Responsive Designs

Go to the QuirksMode blog. Shrink the browser window down and scroll to the right. See how the “QuirksMode” logo breaks out of the header? That’s a simple and classic CSS bug. (And, in the case of QuirksMode, I’d bet it’s totally intentional.) Although the width of the header is 100%, the logo and sidebar are positioned outside the body, so the header glitches when the window is narrower than the page.

Image showing a header that doesn’t extend the full width of the browser window.

Now Quixote can test it.

it("has a header that extends the full width of the page", function() {
  // the "frame" variable is part of our Quixote setup code
  var page = frame.page();
  var header = frame.get(".pageHeader");

  header.assert({
    top: page.top,      // header is flush with top of page
    width: page.width   // header extends entire width of page
  });
});

Of course, that test will work just fine. The bug only shows up with a narrow window. So Quixote has a new method, frame.resize(), that lets you change the size of your test frame.

it("does not break header when page is narrow", function() {
  // assume 'page' and 'header' are defined in our setup code

  frame.resize(500, 1000);
  header.assert({
    width: page.width   // header is still entire width of page
  });
});

And that leads to this beauty:

Differences found:
width of '.pageHeader' was 475px smaller than expected.
  Expected: 975px (width of page)
  But was:  500px

The QuirksMode blog isn’t responsive, or even fluid, but this illustrates the point. Now that we have the ability to resize the window and compare elements to page sizes, we can test any responsive design. Just use frame.resize() to match your breakpoints and assert that everything lines up the way you want.

Other Things You Can Test With 0.6

In addition to comparing elements to the page, you can also compare elements to the viewport. (The viewport is the part of the page you can see in the browser window or frame.) This lets us test all sorts of useful scenarios:

Test that a lightbox is centered in the window:

lightbox.assert({
  center: viewport.center,
  middle: viewport.middle,
  width: viewport.width.times(2/3),
  height: viewport.height.times(2/3)
});

Test that a cookie disclaimer sticks to the bottom of the window:

disclaimer.assert({
  bottom: viewport.bottom,
  width: viewport.width
}, "cookie disclaimer should be at bottom of window");

frame.scroll(0, 100);
disclaimer.assert({
  bottom: viewport.bottom
}, "scrolling should not affect cookie disclaimer");

Test that a sidebar extends the entire height of the page:

sidebar.assert({
  left: page.left,
  height: page.height
});

Test that the content area takes up the whole page, except the sidebar, and starts below the navigation bar:

content.assert({
  top: navbar.bottom,
  right: page.right,
  width: page.width.minus(sidebar.width)
});

The Internals: How We Determine Viewport and Page Size

Determining the viewport and page size was a major hassle. My final solution is nice and simple, but the process of getting there… oy. I won’t go into all the dead ends, but check out our 100 lines of comments or 273 lines of tests if you’re curious.

The viewport size was the easiest. This is actually standardized, and even IE 8 supports the standard when it’s running in standards mode.

var html = document.documentElement;

var viewportWidth = html.clientWidth;
var viewportHeight = html.clientHeight;

Normally, clientWidth and clientHeight returns the width (or height) of an element, including padding, but not including border and margin. But if the element is the root node (our html variable above), it is specified to be the size of the viewport, as long as you’re not in quirks mode.

Obvious, right? Thanks to Peter-Paul Koch of QuirksMode for the essay that finally pointed this out to me.

Now for the page size. In PPK’s excellent essay series, he said it isn’t possible to find the document width. I knew I had my work cut out for me. Many, many test runs later, I had it.

var html = document.documentElement;
var body = document.body;

var pageWidth = Math.max(html.scrollWidth, body.scrollWidth);
var pageHeight = Math.max(html.scrollHeight, body.scrollHeight);

This works on all the browsers I tested. (Firefox, Chrome, Safari, Mobile Safari, and IE 8-11.) I can’t say for sure that it will work everywhere, though, and there may be some test cases I didn’t think of.

Why does it work? Well, it turns out that Firefox and IE behave one way, and Safari and Chrome behave another.

  • On Firefox and IE, html.scrollWidth returns the width of the page. This matches the current working draft standard. On Safari and Chrome, though, html.scrollWidth leaves out the <html> element’s border.

  • On Safari and Chrome, body.scrollWidth returns the width of the page. Firefox and IE correctly return just the width of the body element.

So neither value gives the right answer on all browsers, but by combining them together, we get something that works. So far.

What’s Next

With this release, Quixote has all the layout assertions I originally planned. It’s ready for field trials. I’m going to slow the pace of new features for a while and see how Quixote works on real-world projects. I plan to release a steady stream of small patches as issues are found. Then, after it’s had at least a few months to bake, I’ll decide on the next major features.

As it is, the new viewport, page, and resizing features make Quixote a robust solution for unit testing layout for responsive and non-responsive sites. It’s solid, fast, and has great documentation. Give it a try.

Quixote 0.6 is available to download now from npm and GitHub.

Previously: Quixote Hackathon Final Report

comments powered by Disqus