-
Notifications
You must be signed in to change notification settings - Fork 781
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 QUnit.test.if()
and QUnit.module.if()
#1772
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with some observations in comments.
assert.true(true); | ||
}); | ||
|
||
QUnit.test.if.each('skip dataset', false, ['a', 'b'], function (assert, _data) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These many parameters, including boolean ones, don't read that well. But maybe it's not a big issue in practice since you'd normally see a condition here, not a direct boolean value.
docs/api/QUnit/test.if.md
Outdated
|
||
If the condition is true, this is equivalent to calling [`QUnit.test()`](./test.md). | ||
|
||
If the conditional is false, this is equivalent to calling [`QUnit.test.skip()`](./test.skip.md), and test will not run. Instead, it be listed in the results as a "skipped" test. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In jQuery, we arrived at the following pattern:
- If a test is skipped in an environment as it lacks a feature required for it, use a conditional
QUnit.skip
. - If a test requires presence of some deprecated APIs or bugs specific to a browser (usually IE), wrap it with a reqular
if
to avoid showing the test as skipped - and producing noise - in modern browsers.
I'm not sure if you'd like to do anything with this knowledge but I thought it might be useful to know that when thinking about the design.
Yeah, I'm not super happy with the complexity that this allows. To me it is a trade-off for in favour of consistency and liberty. It makes sense to have The downside is that if this freedom is misused, it can create some pretty complex tests. I've mitigated that by not explicitly demonstrating or encouraging this edge case in documentation. I think in most cases the way you reach this is naturally over time. When taken in small steps, I imagine developers would expect this to exist, which seems preferred over the surprise and cognitive complexity that would arise from us judging and omitting it in this one case. It remains of course, as always, the responsibility of developers and code review to judge their apetite for complexity, and what the alternative is. The alternative in this case might be commenting out (reducing test coverage) or wrapping in an if-statement without leaving a trace of the skipped test. Another alternative might be to use this extreme example as evidence that the API is too complex, and that this complexity even hides but exists in the simpler case of QUnit.test('example', function (assert) {
if (cond) {
assert.skip(); // throws special internal exception, stops test, marks it as skipped.
}
}); I tend to dislike this because it's encouraging and normalizing use of |
Co-authored-by: Steve McClure <steve.mcclure@mongodb.com>
Thanks @smcclure15! |
This builds on previous ideas and discussions from #637, #434 (@englercj), and https://forum.jquery.com/portal/en/community/topic/conditionally-skip-tests.
Rationale
It is unacceptable to me for users to have to come up with constructs like the following:
Reading the pattern poses a barrier to new developers, because it doesn't look like a method call. We generally write conditional statements in source code as
if (true) { a(); } else { b(); }
. You have to stare at it for a while, and realize that this was likely done to avoid having to duplicate the name and callback, since unlike in normal code, in tests it's kind of the point that name and callback are inlined for idiomatic and ergonomic reasons. This is the closest to preserving that, by utilizing "clever" tricks.Once a conditional branch is accepted in the top-level structure of a test suite, it invites treating the test suite as source code, allowing other complexity to creep in.
Writing a pattern like this also poses a barrier to people new to QUnit. There isn't anything like this in our documenation, and you'd have to feel confident and safe enough to employ tricks like this.
The pattern poses a barrier to static analysis (e.g. ESLint).
The existence of such tricks, to me, looks like a failure in our API design. It basically hints that we consider this use case invalid, and you had to take matters in your own hand. Looking at past discussions, it seems didn't care how ugly it looked, because I believed one would never need it if you did things "right". In reality, it seems inevitable that in a large codebase, you will end up having to skip tests in some browsers or some environments. Not because your code or the test is broken or "wrong" in some way, but because you're using progressive enhancement and not all make sense to run in all browsers.
This is especially likely when a project is isomorphic between Node.js and browser, or that supports a wide range of browsers, both of which are qualities that we very much cherish and encourage in the QUnit community. We should make this easy!
Implemenation
I originally wanted to call this
QUnit.test.skipIf()
andQUnit.module.skipIf()
, which would accept a "skip" condition, that executes the test if negative. I settled onQUnit.test.if() instead
, accepting a "test" condition, which executes the test if positive. Three experiences led me to this:Note how in
fixtures/skipIf.js
the ones passedfalse
would have to execute.When migrating our own use cases within QUnit's own test cases, I repeatedly used it incorrectly, only to find the tests failing locally.
Virtually all our uses involve a positive value already, in the form of "supports X", to switch between "test" and "skip" respectively. I believe this idiom is more common than the notion of storing a negative condition to switching between "skip" and "test" (in that order).
After changing direction to
QUnit.test.if()
and rewriting the source code, unit tests, internal adoption, and documentation, I realized I had written the documentation forskipIf
incorrectly. After carefully adding a correct and working example, I wrote "if the condition is true it was equivalent to calling QUnit.test()". The opposite was true with the skipIf implementation.