Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core: Add test.each. #1569

Merged
merged 23 commits into from
May 16, 2021
Merged

Core: Add test.each. #1569

merged 23 commits into from
May 16, 2021

Conversation

ventuno
Copy link
Member

@ventuno ventuno commented Mar 19, 2021

WIP implementation of #1568.

@ventuno ventuno marked this pull request as draft March 19, 2021 20:07
@ventuno ventuno marked this pull request as ready for review April 3, 2021 16:12
@smcclure15
Copy link
Member

I'm really liking this; both with the client-facing API design and it's clean implementation, IMHO. Thanks for your patience in iterating.

I agree the exact naming can be left as a follow up; this looks like a solid foundational piece to build up the other support that was discussed. Next, allow objects with field names? Then support "flat" (or idk, "primitive") arrays for convenience? I feel like those should be very near-term though separate follow ups, just to round out the feature.

@ventuno
Copy link
Member Author

ventuno commented Apr 6, 2021

Thanks @smcclure15, I just added support of 1D arrays. Let's tackle "objects with field names" as a fast follow up as I would like to discuss a few examples in #1568 first before working on it. @Krinkle FYI.

@ventuno ventuno requested review from smcclure15 and Krinkle April 6, 2021 14:25
docs/QUnit/test.each.md Outdated Show resolved Hide resolved
docs/QUnit/test.each.md Outdated Show resolved Hide resolved
docs/QUnit/test.each.md Outdated Show resolved Hide resolved
docs/QUnit/test.each.md Outdated Show resolved Hide resolved
src/test.js Outdated Show resolved Hide resolved
src/test.js Outdated Show resolved Hide resolved
src/test.js Outdated Show resolved Hide resolved
test/each-only.html Outdated Show resolved Hide resolved
test/main/each.js Outdated Show resolved Hide resolved
docs/QUnit/test.each.md Outdated Show resolved Hide resolved
@ventuno ventuno requested a review from smcclure15 April 6, 2021 23:27
docs/QUnit/test.each.md Outdated Show resolved Hide resolved
docs/QUnit/test.each.md Outdated Show resolved Hide resolved
src/test.js Outdated Show resolved Hide resolved
src/test.js Outdated Show resolved Hide resolved
src/test.js Outdated Show resolved Hide resolved
@ventuno ventuno requested a review from smcclure15 April 7, 2021 16:26
Copy link
Member

@smcclure15 smcclure15 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am in favor of this change; still want to bash it interactively for edge cases, but the design/implementation looks solid.
I defer to @Krinkle as lead for the go/no-go on this.

@ventuno
Copy link
Member Author

ventuno commented Apr 14, 2021

@Krinkle any chance you might take a look at this?

@Krinkle Krinkle self-assigned this May 1, 2021
@Krinkle
Copy link
Member

Krinkle commented May 1, 2021

Thanks @ventuno and @smcclure15, this is looking good.

My main concerns at this point are about making sure the ergonomics hold up in relation to these two things:

  1. Use of only, skip and todo.
  2. (In the near future) use of module context parameter (ref Add module-contextual 'test' function #978), e.g. the "q" object or otherwise.

By adding each as a property of test I think these become a bit less intuitive, so I'd like us to explore alternatives explicitly as well. This isn't a hard-no, I'd just like to make sure we think about the options a bit more.

It could be a sibling of test (e.g. testEach, testProvide, etc.). We did something like this in the past with QUnit.asyncTest. But, we also have since phased that out in favour of Promise support directly in QUnit.test, and otherwise via assert.async(). This has the benefit of being in the right place in the hierarchy, I think.

Another way would be to try to squish it into the test function's signature directly. This might not be so infeasible, and is also something we've done before. There used to be a middle parameter of QUnit.test. This was removed in 2.0 in favour of assert.expect(). This is sufficiently far in the past that we could very well make use of this again, I think. I don't know whether "less is more" holds up here, though. Some amount of explicitness could be good, to have at least one word or name to identify the feature with, and to discover it prominently in the API.

So the options would be:

1. QUnit.test.each( 'name', { data }, ( assert, data ) => {

This feels a bit out of place in that it isn't a mode of "test" like the other three properties. On the other hand, it is the least disruptive to the API, but placing it in the only "test"-related corner of the API that has precedent for expansion.

2. QUnit.test( 'name', { data }, ( assert, data ) => {

This is the leanest option and is also least disruptive. It retains the logic that the two main methods are "module" and "test" which together do everything. The two methods both have various aliases of themselves as properties which invoke them in special modes.

The downside I see with this is that it might be a bit too lean for less experienced developers, and leaves any form of name for the feature to documentation and social constructs, which also make it harder to search for help. There's also potential for difficult linting / typing when the callback accepts variadic parameters in some cases but not others. For ESLint, I'm not worried about this as it should be trivial to detect the presence of a middle parameter and allow extra arguments based on that. For TypeScript, it has support for overloads, so that seems fine.

3. QUnit.each( 'name', { data }, ( assert, data ) => {

This one feels structurally correct to me, in that it's not a method that defines a test. Rather, it's a new primitive between "module" and "test" that defines tests on your behalf. The downside is that it breaks the single-object model we have currently, which would make nesting difficult (ref #978).

It basically has to be either squished into the "test" method, or be a property of it.

4. QUnit.testEach( 'name', { data }, ( assert, data ) => {

Same as "3" but more verbosely named. I was pondering with this, thinking it might sort or look better in the API for discovery and in IDEs. But, would probably be worse long-term, because testFoo and test.foo look alike, and we would have both in our API. It's the sort of thing that might make a library feel complicated.


So, yeah, I think test.each and test are good both options. After I reminding myself to think of the test function as not just merely having three aliases under it, but actually being "the" primary function object, it feels less bad to put each under there. But, I could also see us going with test if others convince me it's not a bad choice. What do y'all think? How would it be if we made it part of the test signature?

@ventuno
Copy link
Member Author

ventuno commented May 9, 2021

@Krinkle: updated the name to append the index prefixed by # (see screenshot). I personally like Java's DataProviders style that use []. In this case it would be particularly fitting because [] would fit both array indexes and property names when the input is an object.
Screen Shot 2021-05-09 at 6 08 26 PM

For example:

test.each('my test', [1, 2], function(assert, v) {
  assert.true(v > 0);
});

Current output with array indexes:

my test #0
my test #1

Suggested output:

my test [0]
my test [1]

Suggested output with property names:

test.each('my test', { foo: 1, bar: 2 }, function(assert, v) {
  assert.true(v > 0);
});
...
my test [foo]
my test [bar]

@ventuno ventuno requested a review from Krinkle May 9, 2021 22:19
@Krinkle
Copy link
Member

Krinkle commented May 14, 2021

@ventuno Sounds good, let's go with [key] then. I've not seen that before so it does feel a little odd, but it's as good as anything I think.

Actually, using # is troublesome for us since we're moving toward embracing TAP more (both for cross-browser testing, and for CLI) where # is generally used for adding additional metadata to a test result line, so using # could mess that up.

@ventuno
Copy link
Member Author

ventuno commented May 14, 2021

Thanks @Krinkle, just updated it. See attached screenshot.
Screen Shot 2021-05-14 at 4 02 01 PM

@Krinkle
Copy link
Member

Krinkle commented May 15, 2021

Thanks!

I might do a follow-up to add some spacing in the default theme between [1] and (1). And perhaps in QUnit 3 we can come up with something further integrated that might be a bigger visual difference. But, as a new feature adding to what we have, I think this makes sense.

@ventuno
Copy link
Member Author

ventuno commented May 15, 2021

@Krinkle that sounds good to me. Or we can also address this as part of my next PR to support objects as inputs. I also wanted to get your comments on #1568 (comment) so that I know in which direction to start.

categories:
- main
- async
version_added: "1.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set this to "unreleased" for now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Is this going to be updated automatically with a new release?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not at the moment. I try to remember them, or grep for it, during a release and fill them in as part of the release prep commit. While our release cadence is increasing through past and this year, I don't expect features to accumulate. So, for now, my view of the Is It Worth The Time? table says that it's not worth automating.

Having said that, it seems like a simple thing to automate and so I wouldn't be worried about it complicating our maintenance work. If you feel compelled to give it a try (perhaps as a way to familiarize with the release process), it would likely take the form of an additional step in build/prep-release.js.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Let me look into that after #1614.

docs/QUnit/test.each.md Outdated Show resolved Hide resolved
@Krinkle
Copy link
Member

Krinkle commented May 15, 2021

I'll land this tomorrow after one final closer look. From glancing it over now, I think maybe the added tests aren't actually being run by either test-on-node, nor the grunt/browser test. I may've missed it, but it seems like it's not wired up. If that's indeed the case, I suggest the main each test be added to the main list of tests (via test/index.html and the list of test-on-node tests), and then the special one that has to be its own file (each-only) kept as-is but make sure it is in the list of tests that the grunt/browser task iterates over, if it isn't already included by other means.

@ventuno
Copy link
Member Author

ventuno commented May 15, 2021

Thanks @Krinkle, take your time. I added test/each.html and test/each-only.html to Gruntfile.js and when I run npm test I see both tests in the output (see attached screenshot).
I also see the same output in CI test run. I may be missing something, just let me know and I'll make the necessary updates.

Screen Shot 2021-05-15 at 6 25 45 PM

@ventuno ventuno requested a review from Krinkle May 16, 2021 19:04
@Krinkle Krinkle merged commit 07de4b1 into qunitjs:main May 16, 2021
Krinkle added a commit that referenced this pull request May 16, 2021
The default seems to be sorted by filename, which would be good
enough if it weren't for the fact that it includes the `.md` file
extension, and thus, after we introduced `test.each.md`, the sorting
was:

* test.each
* test
* test.only
* test.skip
* test.todo

because `each` < `md`:

* test.each.md
* test.md
* test.only.md
* test.skip.md
* test.todo.md

Fix by adding an explicit sort for the `title` key.

Ref #1569.
@ventuno ventuno deleted the ftr-1568 branch May 16, 2021 22:52
Krinkle added a commit that referenced this pull request May 23, 2021
* Use "data provider" once or twice for better search and discovery.

* Refer to the input as `dataset` and each item as `data`.
  The `args` name, I think, was less obvious in meaning since it is
  leaning very specifically towards 'data as array' and the
  destructuring approach (or variadic argument, from an earlier
  iteration of #1569).

* Add verbose form of all only/skip/todo signatures to the intro.

* Move the helpful use cases and advice from param table into
  the description paragraphs.

* Split "basic", "array", and "promise" examples up into three
  separate headings.

* Add async function example.

Follows-up 07de4b1.
Krinkle added a commit that referenced this pull request Jun 5, 2021
Krinkle added a commit that referenced this pull request Jun 5, 2021
Krinkle added a commit that referenced this pull request Jun 5, 2021
This restores previous behaviour to avoid breaking plugins that might
already extend or monkey-patch QUnit to add additional parameters to
the test callback.

Also:

* Declare the params setting in the Test class for added clarity,
  and to ensure a consistent object shape.

* Make more use of the new `addTest()` function that was added in
  #1569, this reduces a lot
  of duplication.

  While at it, I shifted the abstraction slightly to expose the
  Test class settings directly, thus making the `addTest()` mainly
  be responsible for the queuing and filtering, and no longer
  responsible for formatting the Test class settings.

  The use of ES2015 shorthand property name syntax makes feel
  almost identical to the function parameter signature.
@@ -674,66 +674,122 @@ function checkPollution() {
}
}

function addTestWithData( data ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this abstraction a lot, did you mean to make more use of it? I went ahead and did so as part of #1620.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually came from @smcclure15's recommendation. Glad it's useful!

Krinkle added a commit that referenced this pull request Jun 5, 2021
This restores previous behaviour to avoid breaking plugins that might
already extend or monkey-patch QUnit to add additional parameters to
the test callback.

Also:

* Declare the params setting in the Test class for added clarity,
  and to ensure a consistent object shape.

* Make more use of the new `addTest()` function that was added in
  #1569, this reduces a lot
  of duplication.

  While at it, I shifted the abstraction slightly to expose the
  Test class settings directly, thus making the `addTest()` mainly
  be responsible for the queuing and filtering, and no longer
  responsible for formatting the Test class settings.

  The use of ES2015 shorthand property name syntax makes feel
  almost identical to the function parameter signature.
Krinkle added a commit that referenced this pull request Jun 6, 2021
These tests were enabled in 47576c1, but then started failing
the IE9 browser test, as it does not have native a Promise (and we don't
polyfill it globally so as to ensure QUnit internally can't accidentally
rely on it).

Ref 3e55dd67a4aa7b6202c0844c6.
Ref #1569.
Krinkle added a commit that referenced this pull request Jun 6, 2021
These tests were enabled in 47576c1, but then started failing
the IE9 browser test, as it does not have native a Promise (and we don't
polyfill it globally so as to ensure QUnit internally can't accidentally
rely on it).

Ref 3e55dd67a4aa7b6202c0844c6.
Ref #1569.
Krinkle added a commit that referenced this pull request Jun 6, 2021
These tests were enabled in 47576c1, but then started failing
the IE9 browser test, as it does not have native a Promise.

We don't polyfill this globally to ensure that QUnit will not
accidentally rely on it internally.

Ref 3e55dd67a4aa7b6202c0844c6.
Ref #1569.
Krinkle added a commit that referenced this pull request Jun 7, 2021
These tests were enabled in 47576c1, but then started failing
the IE9 browser test, as it does not have native a Promise.

We don't polyfill this globally to ensure that QUnit will not
accidentally rely on it internally.

Ref 3e55dd67a4aa7b6202c0844c6.
Ref #1569.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants