Socket
Socket
Sign inDemoInstall

eslint-plugin-cypress

Package Overview
Dependencies
Maintainers
3
Versions
44
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-plugin-cypress - npm Package Compare versions

Comparing version 2.13.4 to 2.14.0

133

lib/rules/unsafe-to-chain-command.js
'use strict'
const { basename } = require('path')
const NAME = basename(__dirname)
const DESCRIPTION = 'Actions should be in the end of chains, not in the middle'
/**
* Commands listed in the documentation with text: 'It is unsafe to chain further commands that rely on the subject after xxx.'
* 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}
* for more information.
*
* @type {string[]}
*/
const unsafeToChainActions = [
'blur',
'clear',
'click',
'check',
'dblclick',
'each',
'focus',
'rightclick',
'screenshot',
'scrollIntoView',
'scrollTo',
'select',
'selectFile',
'spread',
'submit',
'type',
'trigger',
'uncheck',
'within',
]
/**
* @type {import('eslint').Rule.RuleMetaData['schema']}
*/
const schema = {
title: NAME,
description: DESCRIPTION,
type: 'object',
properties: {
methods: {
type: 'array',
description:
'An additional list of methods to check for unsafe chaining.',
default: [],
},
},
}
/**
* @param {import('eslint').Rule.RuleContext} context
* @returns {Record<string, any>}
*/
const getDefaultOptions = (context) => {
return Object.entries(schema.properties).reduce((acc, [key, value]) => {
if (!(value.default in value)) return acc
return {
...acc,
[key]: value.default,
}
}, context.options[0] || {})
}
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Actions should be in the end of chains, not in the middle',
description: DESCRIPTION,
category: 'Possible Errors',

@@ -11,12 +78,22 @@ recommended: true,

},
schema: [],
schema: [schema],
messages: {
unexpected: '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.',
unexpected:
'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.',
},
},
create (context) {
const { methods } = getDefaultOptions(context)
return {
CallExpression (node) {
if (isRootCypress(node) && isActionUnsafeToChain(node) && node.parent.type === 'MemberExpression') {
context.report({ node, messageId: 'unexpected' })
if (
isRootCypress(node) &&
isActionUnsafeToChain(node, methods) &&
node.parent.type === 'MemberExpression'
) {
context.report({
node,
messageId: 'unexpected',
})
}

@@ -28,22 +105,40 @@ },

function isRootCypress (node) {
while (node.type === 'CallExpression') {
if (node.callee.type !== 'MemberExpression') return false
/**
* @param {import('estree').Node} node
* @returns {boolean}
*/
const isRootCypress = (node) => {
if (
node.type !== 'CallExpression' ||
node.callee.type !== 'MemberExpression'
) {
return false
}
if (node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'cy') {
return true
}
node = node.callee.object
if (
node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'cy'
) {
return true
}
return false
return isRootCypress(node.callee.object)
}
function isActionUnsafeToChain (node) {
// commands listed in the documentation with text: 'It is unsafe to chain further commands that rely on the subject after xxx'
const unsafeToChainActions = ['blur', 'clear', 'click', 'check', 'dblclick', 'each', 'focus', 'rightclick', 'screenshot', 'scrollIntoView', 'scrollTo', 'select', 'selectFile', 'spread', 'submit', 'type', 'trigger', 'uncheck', 'within']
/**
* @param {import('estree').Node} node
* @param {(string | RegExp)[]} additionalMethods
*/
const isActionUnsafeToChain = (node, additionalMethods = []) => {
const unsafeActionsRegex = new RegExp([
...unsafeToChainActions,
...additionalMethods.map((method) => method instanceof RegExp ? method.source : method),
].join('|'))
return node.callee && node.callee.property && node.callee.property.type === 'Identifier' && unsafeToChainActions.includes(node.callee.property.name)
return (
node.callee &&
node.callee.property &&
node.callee.property.type === 'Identifier' &&
unsafeActionsRegex.test(node.callee.property.name)
)
}

2

package.json
{
"name": "eslint-plugin-cypress",
"version": "2.13.4",
"version": "2.14.0",
"description": "An ESLint plugin for projects using Cypress",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -13,9 +13,32 @@ 'use strict'

valid: [
{ 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");', parserOptions },
{
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");',
parserOptions,
},
],
invalid: [
{ code: 'cy.get("new-todo").type("todo A{enter}").should("have.class", "active");', parserOptions, errors },
{ code: 'cy.get("new-todo").type("todo A{enter}").type("todo B{enter}");', parserOptions, errors },
{
code: 'cy.get("new-todo").type("todo A{enter}").should("have.class", "active");',
parserOptions,
errors,
},
{
code: 'cy.get("new-todo").type("todo A{enter}").type("todo B{enter}");',
parserOptions,
errors,
},
{
code: 'cy.get("new-todo").customType("todo A{enter}").customClick();',
parserOptions,
errors,
options: [{ methods: ['customType', 'customClick'] }],
},
{
code: 'cy.get("new-todo").customPress("Enter").customScroll();',
parserOptions,
errors,
options: [{ methods: [/customPress/, /customScroll/] }],
},
],
})
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc