eslint-plugin-cypress 2.13.3 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/circle.yml
CHANGED
|
@@ -22,7 +22,7 @@ workflows:
|
|
|
22
22
|
jobs:
|
|
23
23
|
lint:
|
|
24
24
|
docker:
|
|
25
|
-
- image: circleci/node:
|
|
25
|
+
- image: circleci/node:16
|
|
26
26
|
steps:
|
|
27
27
|
- checkout
|
|
28
28
|
- run:
|
|
@@ -34,7 +34,7 @@ jobs:
|
|
|
34
34
|
|
|
35
35
|
test-v4:
|
|
36
36
|
docker:
|
|
37
|
-
- image: circleci/node:
|
|
37
|
+
- image: circleci/node:16
|
|
38
38
|
steps:
|
|
39
39
|
- checkout
|
|
40
40
|
- run:
|
|
@@ -46,7 +46,7 @@ jobs:
|
|
|
46
46
|
|
|
47
47
|
test-v5:
|
|
48
48
|
docker:
|
|
49
|
-
- image: circleci/node:
|
|
49
|
+
- image: circleci/node:16
|
|
50
50
|
steps:
|
|
51
51
|
- checkout
|
|
52
52
|
- run:
|
|
@@ -58,7 +58,7 @@ jobs:
|
|
|
58
58
|
|
|
59
59
|
test-v6:
|
|
60
60
|
docker:
|
|
61
|
-
- image: circleci/node:
|
|
61
|
+
- image: circleci/node:16
|
|
62
62
|
steps:
|
|
63
63
|
- checkout
|
|
64
64
|
- run:
|
|
@@ -70,7 +70,7 @@ jobs:
|
|
|
70
70
|
|
|
71
71
|
release:
|
|
72
72
|
docker:
|
|
73
|
-
- image: circleci/node:
|
|
73
|
+
- image: circleci/node:16
|
|
74
74
|
steps:
|
|
75
75
|
- checkout
|
|
76
76
|
- run:
|
|
@@ -1,47 +1,142 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { basename } = require('path')
|
|
4
|
+
|
|
5
|
+
const NAME = basename(__dirname)
|
|
6
|
+
const DESCRIPTION = 'Actions should be in the end of chains, not in the middle'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Commands listed in the documentation with text: 'It is unsafe to chain further commands that rely on the subject after xxx.'
|
|
10
|
+
* See {@link https://docs.cypress.io/guides/core-concepts/retry-ability#Actions-should-be-at-the-end-of-chains-not-the-middle Actions should be at the end of chains, not the middle}
|
|
11
|
+
* for more information.
|
|
12
|
+
*
|
|
13
|
+
* @type {string[]}
|
|
14
|
+
*/
|
|
15
|
+
const unsafeToChainActions = [
|
|
16
|
+
'blur',
|
|
17
|
+
'clear',
|
|
18
|
+
'click',
|
|
19
|
+
'check',
|
|
20
|
+
'dblclick',
|
|
21
|
+
'each',
|
|
22
|
+
'focus',
|
|
23
|
+
'rightclick',
|
|
24
|
+
'screenshot',
|
|
25
|
+
'scrollIntoView',
|
|
26
|
+
'scrollTo',
|
|
27
|
+
'select',
|
|
28
|
+
'selectFile',
|
|
29
|
+
'spread',
|
|
30
|
+
'submit',
|
|
31
|
+
'type',
|
|
32
|
+
'trigger',
|
|
33
|
+
'uncheck',
|
|
34
|
+
'within',
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @type {import('eslint').Rule.RuleMetaData['schema']}
|
|
39
|
+
*/
|
|
40
|
+
const schema = {
|
|
41
|
+
title: NAME,
|
|
42
|
+
description: DESCRIPTION,
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
methods: {
|
|
46
|
+
type: 'array',
|
|
47
|
+
description:
|
|
48
|
+
'An additional list of methods to check for unsafe chaining.',
|
|
49
|
+
default: [],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {import('eslint').Rule.RuleContext} context
|
|
56
|
+
* @returns {Record<string, any>}
|
|
57
|
+
*/
|
|
58
|
+
const getDefaultOptions = (context) => {
|
|
59
|
+
return Object.entries(schema.properties).reduce((acc, [key, value]) => {
|
|
60
|
+
if (!(value.default in value)) return acc
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
...acc,
|
|
64
|
+
[key]: value.default,
|
|
65
|
+
}
|
|
66
|
+
}, context.options[0] || {})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
3
70
|
module.exports = {
|
|
4
71
|
meta: {
|
|
5
72
|
docs: {
|
|
6
|
-
description:
|
|
73
|
+
description: DESCRIPTION,
|
|
7
74
|
category: 'Possible Errors',
|
|
8
75
|
recommended: true,
|
|
9
76
|
url: 'https://docs.cypress.io/guides/core-concepts/retry-ability#Actions-should-be-at-the-end-of-chains-not-the-middle',
|
|
10
77
|
},
|
|
11
|
-
schema: [],
|
|
78
|
+
schema: [schema],
|
|
12
79
|
messages: {
|
|
13
|
-
unexpected:
|
|
80
|
+
unexpected:
|
|
81
|
+
'It is unsafe to chain further commands that rely on the subject after this command. It is best to split the chain, chaining again from `cy.` in a next command line.',
|
|
14
82
|
},
|
|
15
83
|
},
|
|
16
84
|
create (context) {
|
|
85
|
+
const { methods } = getDefaultOptions(context)
|
|
86
|
+
|
|
17
87
|
return {
|
|
18
88
|
CallExpression (node) {
|
|
19
|
-
if (
|
|
20
|
-
|
|
89
|
+
if (
|
|
90
|
+
isRootCypress(node) &&
|
|
91
|
+
isActionUnsafeToChain(node, methods) &&
|
|
92
|
+
node.parent.type === 'MemberExpression'
|
|
93
|
+
) {
|
|
94
|
+
context.report({
|
|
95
|
+
node,
|
|
96
|
+
messageId: 'unexpected',
|
|
97
|
+
})
|
|
21
98
|
}
|
|
22
99
|
},
|
|
23
100
|
}
|
|
24
101
|
},
|
|
25
102
|
}
|
|
26
103
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
104
|
+
/**
|
|
105
|
+
* @param {import('estree').Node} node
|
|
106
|
+
* @returns {boolean}
|
|
107
|
+
*/
|
|
108
|
+
const isRootCypress = (node) => {
|
|
109
|
+
if (
|
|
110
|
+
node.type !== 'CallExpression' ||
|
|
111
|
+
node.callee.type !== 'MemberExpression'
|
|
112
|
+
) {
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
35
115
|
|
|
36
|
-
|
|
116
|
+
if (
|
|
117
|
+
node.callee.object.type === 'Identifier' &&
|
|
118
|
+
node.callee.object.name === 'cy'
|
|
119
|
+
) {
|
|
120
|
+
return true
|
|
37
121
|
}
|
|
38
122
|
|
|
39
|
-
return
|
|
123
|
+
return isRootCypress(node.callee.object)
|
|
40
124
|
}
|
|
41
125
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
126
|
+
/**
|
|
127
|
+
* @param {import('estree').Node} node
|
|
128
|
+
* @param {(string | RegExp)[]} additionalMethods
|
|
129
|
+
*/
|
|
130
|
+
const isActionUnsafeToChain = (node, additionalMethods = []) => {
|
|
131
|
+
const unsafeActionsRegex = new RegExp([
|
|
132
|
+
...unsafeToChainActions,
|
|
133
|
+
...additionalMethods.map((method) => method instanceof RegExp ? method.source : method),
|
|
134
|
+
].join('|'))
|
|
45
135
|
|
|
46
|
-
return
|
|
136
|
+
return (
|
|
137
|
+
node.callee &&
|
|
138
|
+
node.callee.property &&
|
|
139
|
+
node.callee.property.type === 'Identifier' &&
|
|
140
|
+
unsafeActionsRegex.test(node.callee.property.name)
|
|
141
|
+
)
|
|
47
142
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-cypress",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"description": "An ESLint plugin for projects using Cypress",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Cypress-io",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"eslint": ">= 3.2.1"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"globals": "^
|
|
25
|
+
"globals": "^13.20.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@cypress/eslint-plugin-dev": "3.2.0",
|
|
@@ -10,11 +10,34 @@ const parserOptions = { ecmaVersion: 6 }
|
|
|
10
10
|
|
|
11
11
|
ruleTester.run('action-ends-chain', rule, {
|
|
12
12
|
valid: [
|
|
13
|
-
{
|
|
13
|
+
{
|
|
14
|
+
code: 'cy.get("new-todo").type("todo A{enter}"); cy.get("new-todo").type("todo B{enter}"); cy.get("new-todo").should("have.class", "active");',
|
|
15
|
+
parserOptions,
|
|
16
|
+
},
|
|
14
17
|
],
|
|
15
18
|
|
|
16
19
|
invalid: [
|
|
17
|
-
{
|
|
18
|
-
|
|
20
|
+
{
|
|
21
|
+
code: 'cy.get("new-todo").type("todo A{enter}").should("have.class", "active");',
|
|
22
|
+
parserOptions,
|
|
23
|
+
errors,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
code: 'cy.get("new-todo").type("todo A{enter}").type("todo B{enter}");',
|
|
27
|
+
parserOptions,
|
|
28
|
+
errors,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
code: 'cy.get("new-todo").customType("todo A{enter}").customClick();',
|
|
32
|
+
parserOptions,
|
|
33
|
+
errors,
|
|
34
|
+
options: [{ methods: ['customType', 'customClick'] }],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
code: 'cy.get("new-todo").customPress("Enter").customScroll();',
|
|
38
|
+
parserOptions,
|
|
39
|
+
errors,
|
|
40
|
+
options: [{ methods: [/customPress/, /customScroll/] }],
|
|
41
|
+
},
|
|
19
42
|
],
|
|
20
43
|
})
|