-
Notifications
You must be signed in to change notification settings - Fork 63
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
Add 'one-suite-per-file' rule (#103) #105
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.
Thanks for your PR. I’ve found a few minor issues, please take a look at it.
@@ -15,3 +15,4 @@ | |||
* [no-hooks-for-single-case](no-hooks-for-single-case.md) - disallow hooks for a single test or test suite | |||
* [no-top-level-hooks](no-top-level-hooks.md) - disallow top-level hooks | |||
* [no-identical-title](no-identical-title.md) - disallow identical titles | |||
* [one-suite-per-file](one-suite-per-file.md) - disallow multiple top-level suites in a single file |
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.
I’m not sure about the name. I think it is really important that the rule only applies to top-level suites so maybe this should be reflected in the rule name. So maybe one-top-level-suite
or max-top-level-suites
which could be configured how many top level suites are allowed.
What do you think?
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.
@lo1tuma absolutely, I like the max-top-level-suites
- will rename, thanks.
This rule enforces having a single top-level suite in a file. | ||
|
||
Multiple `describe` blocks is often a sign that a test should be broken down to multiple files. | ||
One of the possible problems if having multiple suites is that, if you are, for example, going through tests one by one and focusing them (using `fdescribe`), you might not notice `describe` block(s) that are down there at the bottom of a file which may lead to tests being unintentionally skipped. |
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.
Is fdescribe
an official alias for exclusive test suites? I can’t find it in the mocha documentation.
I guess describe.only
is much more common.
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.
@lo1tuma my bad, too much work with jasmine these days. Will fix.
|
||
```js | ||
rules: { | ||
"mocha/one-suite-per-file": [["describe", "context", "suite", "mysuitename"]] |
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.
The severity is missing here, it should be: ["error", ["describe", "context", "suite", "mysuitename"]]
.
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.
@lo1tuma good finding, thanks.
return { | ||
CallExpression: function (node) { | ||
var callee = node.callee; | ||
if (callee && callee.name && suiteNames.indexOf(callee.name) > -1) { |
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.
Should we also support modifiers as e.g. descirbe.only
, describe.skip
etc.?
You could also reuse the isDescribe
function.
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.
@lo1tuma yeah, I think we should support all possible suite variations. Will reuse isDescribe
, thanks.
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.
@lo1tuma do you think I can/should safely extend the describeAliases
array and include describe.only
, describe.skip
, suite
, suite.only
, suite.skip
, context.only
, context.skip
?..
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.
I hope so 😄 . I would rely on our tests here, so if no other test fails after changing describeAliases
I would say it is safe to change it.
That’s being said, isDescribe
currently doesn’t support MemberExpressions
like describe.skip
, but it could be easily added, just have a look at how it is done in isTestCase
@lo1tuma updated the PR, please review. |
'Program:exit': function () { | ||
if (topLevelDescribes.length > suiteLimit) { | ||
context.report({ | ||
node: topLevelDescribes[1], |
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.
I guess topLevelDescribes[1]
needs to be changed to topLevelDescribes[suiteLimit]
, otherwise the rule would always report the second top-level describe node, right?
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.
@lo1tuma you are right, fixed.
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.
I would make the option an object with a limit
property. Having an object as the options makes it way easier to add new options later, and also clearer when reading the ESLint configuration file.
|
||
module.exports = function (context) { | ||
var stack = [], | ||
topLevelDescribes = [], | ||
suiteNames = context.options[0] ? context.options[0] : defaultSuiteNames; | ||
suiteLimit = context.options[0] ? context.options[0] : defaultSuiteLimit; |
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.
You could do context.options[0] || defaultSuiteLimit
(shorter) or context.options[0] !== undefined ? context.options[0] : defaultSuiteLimit
(maybe a bit more correct, if the user wants 0 suites for some reason)
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.
@jfmengels good point. I was thinking about the case with limit = 0, but decided that would not make sense, though, I agree, this is not the reason to not let it work properly. Thanks!
@lo1tuma okay, please see if it is good enough now. |
I still think the options should be an object (#105 (review)) |
@jfmengels ah, alright, let me fix that, thanks. |
@jfmengels okay, done, please see if this is what you've meant. I like the idea of having an object - explicit and readable. Thanks. |
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.
@alecxe This is what I meant 👍
I would be nice to also have a section in the docs that describe the available options. Here is an example.
Related to my other comment for this review, it would be nice to have both valid and failing tests with the options set as an empty object.
if (R.isNil(suiteLimit)) { | ||
suiteLimit = defaultSuiteLimit; | ||
} else { | ||
suiteLimit = suiteLimit.limit; |
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.
It's possible that users supplied an empty object (or that in the future another option will be available and that the user sets only the value of that one).
The way I usually do this is this way:
var options = context.options[0] || {};
var suiteLimit = options.limit || defaultSuiteLimit;
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.
@jfmengels okay, good idea, added an empty object handling with a corresponding test.
By the way, when I write it as:
suiteLimit = defaultSuiteLimit ? R.isNil(options) : options.limit
I'm getting a lot of TypeError: Cannot read property 'loc' of undefined
which I think is a bug in ESLint - so, have to use the explicit if/else in this case.
As far as documenting "options" - I think in the link you've posted "settings" are actually described..but in this case limit
is a rule option..
Thanks.
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.
TypeError: Cannot read property 'loc' of undefined
No, that sounds like a normal error.
suiteLimit = defaultSuiteLimit ? R.isNil(options) : options.limit
defaultSuiteLimit
is a truthy value, so suiteLimit
will evaluate to R.isNil(options)
, meaning true
or false
.
When you then compare the number of top-level suites to this boolean, it will evaluate to true if there is at least one. Then, you report an error, but pass undefined as the node (as topLevelDescribes[true]
doesn't exist), which ESLint then uses to find loc
. Crash!
tl;dr: You should have written it as suiteLimit = R.isNil(options.limit) ? defaultSuiteLimit : options.limit)
. I would have done it the way I proposed, but your way should work fine too ;)
LGTM, thanks @alecxe! :) |
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, thanks @alecxe and thanks @jfmengels for the thoroughly review 👍.
Okay, sorry for the rookie mistakes and thanks for being patient! |
No problem (for me at least)! Thanks for your work! :) |
This is for the issue #103. Please review.
I think I have not described the motivation for this rule well enough. Please see if you can think of a better way or more reasons to have this rule enabled. Thanks!