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

Add 'one-suite-per-file' rule (#103) #105

Merged
merged 6 commits into from
Oct 3, 2016

Conversation

alecxe
Copy link
Contributor

@alecxe alecxe commented Sep 28, 2016

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!

Copy link
Owner

@lo1tuma lo1tuma left a 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
Copy link
Owner

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?

Copy link
Contributor Author

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.
Copy link
Owner

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.

Copy link
Contributor Author

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"]]
Copy link
Owner

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"]].

Copy link
Contributor Author

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) {
Copy link
Owner

@lo1tuma lo1tuma Sep 29, 2016

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

@alecxe alecxe Sep 29, 2016

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?..

Copy link
Owner

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

@alecxe
Copy link
Contributor Author

alecxe commented Sep 29, 2016

@lo1tuma updated the PR, please review.

'Program:exit': function () {
if (topLevelDescribes.length > suiteLimit) {
context.report({
node: topLevelDescribes[1],
Copy link
Owner

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?

Copy link
Contributor Author

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.

Copy link
Collaborator

@jfmengels jfmengels left a 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;
Copy link
Collaborator

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)

Copy link
Contributor Author

@alecxe alecxe Sep 30, 2016

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!

@alecxe
Copy link
Contributor Author

alecxe commented Sep 30, 2016

@lo1tuma okay, please see if it is good enough now.

@jfmengels
Copy link
Collaborator

I still think the options should be an object (#105 (review))

@alecxe
Copy link
Contributor Author

alecxe commented Sep 30, 2016

@jfmengels ah, alright, let me fix that, thanks.

@alecxe
Copy link
Contributor Author

alecxe commented Sep 30, 2016

@jfmengels okay, done, please see if this is what you've meant. I like the idea of having an object - explicit and readable. Thanks.

Copy link
Collaborator

@jfmengels jfmengels left a 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;
Copy link
Collaborator

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;

Copy link
Contributor Author

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.

Copy link
Collaborator

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 ;)

@jfmengels
Copy link
Collaborator

LGTM, thanks @alecxe! :)

@lo1tuma lo1tuma added the feature label Oct 3, 2016
Copy link
Owner

@lo1tuma lo1tuma left a 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 👍.

@lo1tuma lo1tuma merged commit 51b3264 into lo1tuma:master Oct 3, 2016
@alecxe
Copy link
Contributor Author

alecxe commented Oct 3, 2016

Okay, sorry for the rookie mistakes and thanks for being patient!

@jfmengels
Copy link
Collaborator

No problem (for me at least)! Thanks for your work! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants