Skip to content

Commit

Permalink
feat(rules): add 'no-array-finder-methods' rule
Browse files Browse the repository at this point in the history
  • Loading branch information
alecxe committed Sep 30, 2016
1 parent 408e916 commit b42e9c4
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 1 deletion.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Rule | Default Error Level | Auto-fixable | Options
[no-browser-pause][] | 2 | |
[correct-chaining][] | 2 | Yes |
[no-invalid-selectors][] | 2 | |
[no-array-finder-methods][] | 2 | |
[missing-wait-message][] | 1 (Warning) | |
[no-browser-sleep][] | 1 | |
[no-by-xpath][] | 1 | |
Expand Down Expand Up @@ -106,6 +107,7 @@ See [configuring rules][] for more information.
[no-angular-attributes]: docs/rules/no-angular-attributes.md
[no-invalid-selectors]: docs/rules/no-invalid-selectors.md
[use-promise-all]: docs/rules/use-promise-all.md
[no-array-finder-methods]: docs/rules/no-array-finder-methods.md
[configuring rules]: http://eslint.org/docs/user-guide/configuring#configuring-rules

## Recommended configuration
Expand Down
41 changes: 41 additions & 0 deletions docs/rules/no-array-finder-methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Disallow using `ElementArrayFinder` methods on `ElementFinder`

This rule is inspired by [this problem](http://stackoverflow.com/questions/39710881/protractor-locator-issues) when `ElementArrayFinder` methods were unintentionally used on `ElementFinder`.

This rule is especially useful if ESLint is configured in your IDE to scan on the fly - you would catch these kinds of problems early.

## Rule details

Here is the current list of methods the rule is looking for:

'first', 'last', 'get', 'filter', 'map', 'each', 'reduce', 'count'

Any use of the following patterns are considered warnings:

```js
element(by.css(".class")).get(0);
$(".class").first();
element(by.css(".class")).last();
element(by.css(".class")).map(function (elm) {});
$(".class").filter(function (elm) {});
element(by.css(".class")).each(function (elm) {});
element(by.css(".class1")).element(by.css(".class2")).first();
element(by.css(".class1")).$(".class2").first();
$(".class1").element(by.css(".class2")).first();
$(".class1").$(".class2").first();
```

The following patterns are not warnings:

```js
element.all(by.css(".class")).get(0);
$$(".class").first();
element.all(by.css(".class")).last();
element.all(by.css(".class")).map(function (elm) {});
$$(".class").filter(function (elm) {});
element.all(by.css(".class")).each(function (elm) {});
element(by.css(".class1")).element.all(by.css(".class2")).first();
element(by.css(".class1")).$$(".class2").first();
$(".class1").element.all(by.css(".class2")).first();
$(".class1").$$(".class2").first();
```
5 changes: 4 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ var noGetInnerOuterHtml = require('./lib/rules/no-get-inner-outer-html')
var noAngularAttributes = require('./lib/rules/no-angular-attributes')
var noInvalidSelectors = require('./lib/rules/no-invalid-selectors')
var usePromiseAll = require('./lib/rules/use-promise-all')
var noArrayFinderMethods = require('./lib/rules/no-array-finder-methods')

module.exports = {
rules: {
Expand Down Expand Up @@ -54,7 +55,8 @@ module.exports = {
'no-get-inner-outer-html': noGetInnerOuterHtml,
'no-angular-attributes': noAngularAttributes,
'no-invalid-selectors': noInvalidSelectors,
'use-promise-all': usePromiseAll
'use-promise-all': usePromiseAll,
'no-array-finder-methods': noArrayFinderMethods
},
configs: {
recommended: {
Expand All @@ -63,6 +65,7 @@ module.exports = {
'protractor/no-browser-pause': 2,
'protractor/correct-chaining': 2,
'protractor/no-invalid-selectors': 2,
'protractor/no-array-finder-methods': 2,
'protractor/missing-wait-message': 1,
'protractor/no-browser-sleep': 1,
'protractor/no-by-xpath': 1,
Expand Down
26 changes: 26 additions & 0 deletions lib/is-element-finder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict'

/**
* Checks if a given node is an ElementFinder instance.
*
* @fileoverview Utility function to determine if a node is an ElementFinder
* @author Alexander Afanasyev
*/
module.exports = function (node) {
// handling $ shortcut
var callee = node.callee
if (callee) {
if (callee.name === '$' || callee.name === 'element') {
return true
}

// handling $ and element chaining
if (callee.type === 'MemberExpression' && callee.property) {
if (callee.property.name === '$' || callee.property.name === 'element') {
return true
}
}
}

return false
}
41 changes: 41 additions & 0 deletions lib/rules/no-array-finder-methods.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use strict'

/**
* @fileoverview Disallow using `ElementArrayFinder` methods on `ElementFinder`
* @author Alexander Afanasyev
*/

var isElementFinder = require('../is-element-finder')
var elementArrayFinderMethods = [
'first',
'last',
'get',
'filter',
'map',
'each',
'reduce',
'count'
]

module.exports = {
meta: {
schema: []
},

create: function (context) {
return {
MemberExpression: function (node) {
var property = node.property

if (property && elementArrayFinderMethods.indexOf(property.name) > -1) {
if (isElementFinder(node.object)) {
context.report({
node: node,
message: 'Unexpected "' + property.name + '()" call on ElementFinder'
})
}
}
}
}
}
}
104 changes: 104 additions & 0 deletions test/rules/no-array-finder-methods.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
'use strict'

var rule = require('../../lib/rules/no-array-finder-methods')
var RuleTester = require('eslint').RuleTester

var eslintTester = new RuleTester()

eslintTester.run('no-array-finder-methods', rule, {
valid: [
'element.all(by.css(".class")).get(0);',
'$$(".class").first();',
'element.all(by.css(".class")).last();',
'element.all(by.css(".class")).map(function (elm) {});',
'$$(".class").filter(function (elm) {});',
'element.all(by.css(".class")).each(function (elm) {});',
'element(by.css(".class1")).element.all(by.css(".class2")).first();',
'element(by.css(".class1")).$$(".class2").first();',
'$(".class1").element.all(by.css(".class2")).first();',
'$(".class1").$$(".class2").first();'
],

invalid: [
{
code: 'element(by.css(".class")).get(0);',
errors: [
{
message: 'Unexpected "get()" call on ElementFinder'
}
]
},
{
code: '$(".class").first();',
errors: [
{
message: 'Unexpected "first()" call on ElementFinder'
}
]
},
{
code: 'element(by.css(".class")).last();',
errors: [
{
message: 'Unexpected "last()" call on ElementFinder'
}
]
},
{
code: 'element(by.css(".class")).map(function (elm) {});',
errors: [
{
message: 'Unexpected "map()" call on ElementFinder'
}
]
},
{
code: '$(".class").filter(function (elm) {});',
errors: [
{
message: 'Unexpected "filter()" call on ElementFinder'
}
]
},
{
code: 'element(by.css(".class")).each(function (elm) {});',
errors: [
{
message: 'Unexpected "each()" call on ElementFinder'
}
]
},
{
code: 'element(by.css(".class1")).element(by.css(".class2")).first();',
errors: [
{
message: 'Unexpected "first()" call on ElementFinder'
}
]
},
{
code: 'element(by.css(".class1")).$(".class2").first();',
errors: [
{
message: 'Unexpected "first()" call on ElementFinder'
}
]
},
{
code: '$(".class1").element(by.css(".class2")).first();',
errors: [
{
message: 'Unexpected "first()" call on ElementFinder'
}
]
},
{
code: '$(".class1").$(".class2").first();',
errors: [
{
message: 'Unexpected "first()" call on ElementFinder'
}
]
}
]
})

0 comments on commit b42e9c4

Please sign in to comment.