jamesshore / quixote

CSS unit and integration testing
Other
847 stars 45 forks source link

New assertion API #47

Closed jamesshore closed 4 years ago

jamesshore commented 7 years ago

Update: See this comment for my specific proposal.

Original comment follows...


Quixote's assertion API is a bit... clunky.

element.assert({
  width: 10,
  height: 20
});

The original intent was that you would be able to make multiple assertions about an element quickly. But in practice, you're often just asserting one thing about an element. Also, equality isn't the only kind of assertions you might want to make.

So a more-traditional assertion mechanism that allows one-liners would be nice.

element.height.should.equal(20);
element.width.should.be.lessThan(10);

It would also be nice if it supported multiple assertion flavors.

element.height.assert.equals(20);
element.width.assert.lessThan(10);

And of course, we want to keep our high-quality error messages and relative assertions.

element.height.should.equal(body.height);
// Outputs:
// height of '#element' was 10px smaller than expected.
// Expected: 50px (height of 'body')
// But was:  40px

However, people have their preferred assertion frameworks (such as Chai), and it would be nice to support those as well.

var assert = require("chai").assert;
var expect = require("expect");

assert.equals(element.height, body.height);
expect(element.height).to.equal(body.height);

Share your thoughts about improvements to Quixote's assertion API in this issue.

JuanCaicedo commented 7 years ago

I definitely agree with the last part about wanting to be framework agnostic. It's would be a shame to lose the nice error messages though. I'm not familiar with what API's tools like Chai expose, but maybe there's something Quixote could provide to interface with them and keep error messages.

One thing I got to thinking about reading src/values/readme.md, particularly this line

Descriptors represent some as yet uncalculated aspect of CSS

Is that maybe the right interface for a descriptor is a promise. Promise are values that eventually resolve to a value. This means that if you use a framework like Chai, you could use standard promise testing tools like chai-as-promised

expect(element.height).to.eventually.equal(body.height);

Then Quixote could resolve promises under the hood and its own API wouldn't need to change.

This might not be necessary, but I wanted to point out that similarity in case it is.

jamesshore commented 7 years ago

Promises are superficially similar, but not what we need in this case. A descriptor represents some part of the page and provides the ability to calculate its value synchronously, on demand. A promise represents an asynchronous calculation in progress that will eventually resolve to a value.

jamesshore commented 7 years ago

While we're thinking about improvements to assertions, I also think Quixote's messages can be improved. Currently, the summary says how the result was different than expected:

height of '#element' was 10px smaller than expected.
Expected: 50px (height of 'body')
But was:  40px

But in practice, I find this confusing. When a test fails, I want to see what I need to fix. The test could say that instead:

height of '#element' should be 10px larger.
Expected: 50px (height of 'body')
But was:  40px

There may be opportunities to improve the grammar, as well:

Instead of: "height of '#element' should be 10px larger"
Use: "'#element' height should be 10px larger"
Or even: "#element height should be 10px larger" (no quotes around '#element')
jamesshore commented 6 years ago

Okay, I'm getting ready to implement a new assertion API for the next release of Quixote. Here's what I'm currently thinking. Feedback appreciated! (Summoning @woldie.)

As a reminder, Quixote works by allowing you to make assertions about the visual layout of your elements and pages. You're not making assertions about specific CSS properties, but instead about how your CSS affects where elements are rendered on the page.

Overview

Most objects will have a should property that can be used to make assertions. They'll all have these two assertions:

For example:

elementA.left.should.equal(20);    // left edge of elementA should be at X-coordinate 20
elementA.right.should.equal(elementB.right)    // right edge of elementA should be same as right edge of elementB

The above is equivalent to the following code in our existing API:

elementA.assert({
  left: 20,
  right: elementB.right
});

However, it's more flexible and (I hope) easier to read and understand.

In addition to the baseline equal and notEqual assertions, there would be a lot of situation-specific assertions:

Positions

Positions are things like element.left and element.top.

If the above assertions work well, we could add these as well:

Sizes

Sizes are things like element.width and element.height. But they're also used for distances, such as a.right.to(b.left). (Distances were added in v0.13.)

If those work, these are also options:

Elements / Rectangles

Additional assertions could be added for elements. They weren't included with the first batch, so they've been documented in issue #58.

Elements—or perhaps any object that has a top, bottom, width, height, etc.—could have shortcut assertions for a lot of the position and size assertions, such as:

But they could also have some more interesting positioning assertions, such as:

Frame

This is where things get really interesting. We could make assertions about how elements are aligned:

We could also make assertions about how elements are arranged from left to right, or top to bottom:

Feedback Wanted

woldie commented 6 years ago

This is a nice proposal! Would you consider adding one nesting level after the should? Something like should.flow.horizontally(...)

That way we can reduce our camel-casey names and keep more of the things all lowercase.

Also I think any rectangular object should sport the *Align functions, not just Frame.

jamesshore commented 6 years ago

I'm not opposed to additional nesting levels, and I don't want to create confusion about when to use a dot and when to use camel-case. Can you list out what it all the above assertions would look like in a two-level scheme?

Agreed on the align functions--in fact, I'm not sure if they should be on Frame at all.

jamesshore commented 6 years ago

A couple of quick thoughts before bed. First, I want to see what @woldie's two-level nesting looks like. Second, I think the element/rectangle assertions are going to be the most powerful. We'll drop down to specific width and position assertions at times, but often we'll be working with elements.

Let's say we're testing a cookie notification bar. (Everybody loves cookie notifications, right? Right.) It's pasted to the bottom of the window, full-width, and is 20px tall.

Old assertion style:

var viewport = frame.viewport();
cookieBar.assert({
  width: viewport.width,
  left: viewport.left,
  bottom: viewport.bottom,
  height: 20
});

New style, low-level assertions

var viewport = frame.viewport();
cookieBar.width.should.equal(viewport.width);
cookieBar.left.should.equal(viewport.left);
cookieBar.bottom.should.equal(viewport.bottom);
cookieBar.height.should.equal(20);

New style, high-level assertions:

var viewport = frame.viewport();
cookieBar.should.beInside(viewport);
cookieBar.should.align.bottom(viewport);
cookieBar.width.should.equal(viewport);
cookieBar.height.should.equal(20);

Or perhaps?

cookieBar.should.fill.bottomOf(frame.viewport());
cookieBar.height.should.equal(20);

I don't think I've got it 100% right yet. I want the API to be as solid and unchanging as possible once released. It deserves more thought. What other scenarios should I create examples for?

jamesshore commented 6 years ago

Element assertion ideas so far (plus a few new ones). Element.should...

And perhaps we need a way of grouping elements into a larger rectangle, without assuming they're in a div. For example, we might say that three elements are all in a perfect row, and that row is centered in another element.

Just some ideas that need further thought. Still looking for scenarios--please share!

jamesshore commented 4 years ago

I started implementing the new assertion API a few days ago and it's coming along very well. I've already implemented should.equal() and the new error messages described in this comment. I'll track my progress in this comment.

woldie commented 4 years ago

Finally doin' it, eh? Took a pandemic to make it happen. 😷

I'm enjoying Google's Truth assertion library lately, might be an additional fount of inspiration worth drawing from.

On Sun, Apr 12, 2020, 6:52 PM James Shore notifications@github.com wrote:

I started implementing the new assertion API a few days ago and it's coming along very well. I'll track my progress in this comment https://github.com/jamesshore/quixote/issues/47#issuecomment-345486461.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/jamesshore/quixote/issues/47#issuecomment-612713570, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACOGQYCVJLA7O7DDTF7T7CLRMJV7JANCNFSM4DH7DGVQ .

jamesshore commented 4 years ago

I've finished the first batch of assertions for inclusion in v1.0, so I'm tagging this issue "done." The remaining assertions have been moved to issue #58 for work at a future date.

Although this is issue is done, I'm leaving it open until v1.0 is released.

jamesshore commented 4 years ago

Released in v1.0.0.