| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- '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: DESCRIPTION,
- category: 'Possible Errors',
- recommended: true,
- url: 'https://docs.cypress.io/guides/core-concepts/retry-ability#Actions-should-be-at-the-end-of-chains-not-the-middle',
- },
- 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.',
- },
- },
- create (context) {
- const { methods } = getDefaultOptions(context)
- return {
- CallExpression (node) {
- if (
- isRootCypress(node) &&
- isActionUnsafeToChain(node, methods) &&
- node.parent.type === 'MemberExpression'
- ) {
- context.report({
- node,
- messageId: 'unexpected',
- })
- }
- },
- }
- },
- }
- /**
- * @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
- }
- return isRootCypress(node.callee.object)
- }
- /**
- * @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' &&
- unsafeActionsRegex.test(node.callee.property.name)
- )
- }
|