Skip to content

Commit

Permalink
feat(rule): add 'no-force-true' rule
Browse files Browse the repository at this point in the history
  • Loading branch information
kuzzaka committed Feb 14, 2020
1 parent c3f1b3d commit f6a5ffb
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 1 deletion.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ You can add rules:
"rules": {
"cypress/no-assigning-return-values": "error",
"cypress/no-unnecessary-waiting": "error",
"cypress/assertion-before-screenshot": "warn"
"cypress/assertion-before-screenshot": "warn",
"cypress/no-force-true": "warn"
}
}
```
Expand Down Expand Up @@ -70,6 +71,7 @@ Rules with a check mark (✅) are enabled by default while using the `plugin:cyp
|| [no-unnecessary-waiting](./docs/rules/no-unnecessary-waiting.md) | Prevent waiting for arbitrary time periods |
| | [assertion-before-screenshot](./docs/rules/assertion-before-screenshot.md) | Ensure screenshots are preceded by an assertion |
| | [require-data-selectors](./docs/rules/require-data-selectors.md) | Only allow data-\* attribute selectors (require-data-selectors) |
| | [no-force-true](./docs/rules/no-force-true.md) | Disallow using of 'force: true' option |

## Chai and `no-unused-expressions`

Expand Down
50 changes: 50 additions & 0 deletions docs/rules/no-force-true.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# disallow using of 'force: true' option (no-force-true)

Using force:true on inputs appears to be confusing rather than helpful.
It usually silences the actual problem instead of providing a way to overcome it.
See [Cypress Core Concepts](https://docs.cypress.io/guides/core-concepts/interacting-with-elements.html#Forcing).


## Rule Details

This rule aims to disallow using of the `force` option on [`.click()`](https://on.cypress.io/click),
[`.dblclick()`](https://on.cypress.io/dblclick), [`.type()`](https://on.cypress.io/type),
[`.rightclick()`](https://on.cypress.io/rightclick), [`.select()`](https://on.cypress.io/select),
[`.focus()`](https://on.cypress.io/focus), [`.check()`](https://on.cypress.io/check),
and [`.trigger()`](https://on.cypress.io/trigger).
Examples of **incorrect** code for this rule:

```js

cy.get('button').click({force: true})
cy.get('button').dblclick({force: true})
cy.get('input').type('somth', {force: true})
cy.get('div').find('.foo').find('.bar').trigger('change', {force: true})
cy.get('input').trigger('click', {force: true})
cy.get('input').rightclick({force: true})
cy.get('input').check({force: true})
cy.get('input').select({force: true})
cy.get('input').focus({force: true})

```

Examples of **correct** code for this rule:

```js

cy.get('button').click()
cy.get('button').click({multiple: true})
cy.get('button').dblclick()
cy.get('input').type('somth')
cy.get('input').trigger('click', {anyoption: true})
cy.get('input').rightclick({anyoption: true})
cy.get('input').check()
cy.get('input').select()
cy.get('input').focus()

```


## When Not To Use It

If you really need to use.
83 changes: 83 additions & 0 deletions lib/rules/no-force-true.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* @fileoverview Disallow using of \'force: true\' option for click and type calls
* @author Alex Kuznetsov
*/

'use strict'

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: 'Disallow using of \'force: true\' option for click and type calls',
category: 'Possible Errors',
recommended: false,
},
fixable: null, // or "code" or "whitespace"
schema: [],
messages: {
unexpected: 'Do not use force on click and type calls',
},
},

create (context) {

// variables should be defined here

//----------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------
function isCallingClickOrType (node) {
const allowedMethods = ['click', 'dblclick', 'type', 'trigger', 'check', 'rightclick', 'focus', 'select']

return node.property && node.property.type === 'Identifier' &&
allowedMethods.includes(node.property.name)
}

function isCypressCall (node) {
return node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'cy'
}

function hasOptionForce (node) {

return node.arguments && node.arguments.length &&
node.arguments.some((arg) => {
return arg.type === 'ObjectExpression' && arg.properties.some((propNode) => propNode.key.name === 'force')
})
}

function deepCheck (node, checkFunc) {
let currentNode = node

while (currentNode.parent) {

if (checkFunc(currentNode.parent)) {
return true
}

currentNode = currentNode.parent
}

return false
}

//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------

return {

CallExpression (node) {
if (isCypressCall(node) && deepCheck(node, isCallingClickOrType) && deepCheck(node, hasOptionForce)) {
context.report({ node, messageId: 'unexpected' })
}
},

}
},
}
48 changes: 48 additions & 0 deletions tests/lib/rules/no-force-true.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict'

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const rule = require('../../../lib/rules/no-force-true')

const RuleTester = require('eslint').RuleTester

const errors = [{ messageId: 'unexpected' }]
const parserOptions = { ecmaVersion: 6 }

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

let ruleTester = new RuleTester()

ruleTester.run('no-force-true', rule, {

valid: [
{ code: `cy.get('button').click()`, parserOptions },
{ code: `cy.get('button').click({multiple: true})`, parserOptions },
{ code: `cy.get('button').dblclick()`, parserOptions },
{ code: `cy.get('input').type('somth')`, parserOptions },
{ code: `cy.get('input').type('somth', {anyoption: true})`, parserOptions, errors },
{ code: `cy.get('input').trigger('click', {anyoption: true})`, parserOptions, errors },
{ code: `cy.get('input').rightclick({anyoption: true})`, parserOptions, errors },
{ code: `cy.get('input').check()`, parserOptions, errors },
{ code: `cy.get('input').select()`, parserOptions, errors },
{ code: `cy.get('input').focus()`, parserOptions, errors },
],

invalid: [
{ code: `cy.get('button').click({force: true})`, parserOptions, errors },
{ code: `cy.get('button').dblclick({force: true})`, parserOptions, errors },
{ code: `cy.get('input').type('somth', {force: true})`, parserOptions, errors },
{ code: `cy.get('div').find('.foo').type('somth', {force: true})`, parserOptions, errors },
{ code: `cy.get('div').find('.foo').find('.bar').click({force: true})`, parserOptions, errors },
{ code: `cy.get('div').find('.foo').find('.bar').trigger('change', {force: true})`, parserOptions, errors },
{ code: `cy.get('input').trigger('click', {force: true})`, parserOptions, errors },
{ code: `cy.get('input').rightclick({force: true})`, parserOptions, errors },
{ code: `cy.get('input').check({force: true})`, parserOptions, errors },
{ code: `cy.get('input').select({force: true})`, parserOptions, errors },
{ code: `cy.get('input').focus({force: true})`, parserOptions, errors },
],
})

0 comments on commit f6a5ffb

Please sign in to comment.