diff --git a/docs/rules/require-data-selectors.md b/docs/rules/require-data-selectors.md index 7a8ae46..eae2dee 100644 --- a/docs/rules/require-data-selectors.md +++ b/docs/rules/require-data-selectors.md @@ -16,6 +16,9 @@ cy.get('[daedta-cy=submit]').click() cy.get('[d-cy=submit]') cy.get('.btn-large').click() cy.get('.btn-.large').click() + +const CLASS_SELECTOR = ".my-class"; +cy.get(CLASS_SELECTOR) ``` Examples of **correct** code for this rule: @@ -23,6 +26,12 @@ Examples of **correct** code for this rule: ```js cy.get('[data-cy=submit]').click() cy.get('[data-QA=submit]') +cy.get(`[data-QA=submit]`) +``` + +```js +const ASSESSMENT_SUBMIT = "[data-cy=assessment-submit]" +cy.get(ASSESSMENT_SUBMIT).click() ``` ## Further Reading diff --git a/lib/rules/require-data-selectors.js b/lib/rules/require-data-selectors.js index f3ec9e6..18ab403 100644 --- a/lib/rules/require-data-selectors.js +++ b/lib/rules/require-data-selectors.js @@ -1,5 +1,4 @@ 'use strict' - module.exports = { meta: { type: 'suggestion', @@ -16,9 +15,29 @@ module.exports = { }, create(context) { + const variablesSet = new Set() return { + VariableDeclarator(node) { + if (node.init && node.id && node.id.type === 'Identifier') { + let selectorValue = null + + if (node.init.type === 'Literal' && typeof node.init.value === 'string') { + selectorValue = node.init.value + } + else if (node.init.type === 'TemplateLiteral' + && node.init.expressions.length === 0 + && node.init.quasis.length === 1) { + selectorValue = node.init.quasis[0].value.cooked + } + + if (selectorValue && isAliasOrDataSelector(selectorValue)) { + variablesSet.add(node.id.name) + } + } + }, + CallExpression(node) { - if (isCallingCyGet(node) && !isDataArgument(node)) { + if (isCallingCyGet(node) && !isDataArgument(node, variablesSet)) { context.report({ node, messageId: 'unexpected' }) } }, @@ -34,12 +53,24 @@ function isCallingCyGet(node) { && node.callee.property.name === 'get' } -function isDataArgument(node) { - return node.arguments.length > 0 - && ( - (node.arguments[0].type === 'Literal' && isAliasOrDataSelector(String(node.arguments[0].value))) - || (node.arguments[0].type === 'TemplateLiteral' && isAliasOrDataSelector(String(node.arguments[0].quasis[0].value.cooked))) - ) +function isDataArgument(node, dataVariables) { + if (node.arguments.length === 0) return false + + const firstArg = node.arguments[0] + + if (firstArg.type === 'Literal') { + return isAliasOrDataSelector(String(firstArg.value)) + } + + if (firstArg.type === 'TemplateLiteral') { + return isAliasOrDataSelector(String(firstArg.quasis[0].value.cooked)) + } + + if (firstArg.type === 'Identifier') { + return dataVariables.has(firstArg.name) + } + + return false } function isAliasOrDataSelector(selector) { diff --git a/tests/lib/rules/require-data-selectors.js b/tests/lib/rules/require-data-selectors.js index e501e96..9fb0a45 100644 --- a/tests/lib/rules/require-data-selectors.js +++ b/tests/lib/rules/require-data-selectors.js @@ -17,6 +17,8 @@ ruleTester.run('require-data-selectors', rule, { { code: 'cy.get(\`[data-cy=${1}]\`)' }, // eslint-disable-line no-useless-escape { code: 'cy.get("@my-alias")' }, { code: 'cy.get(`@my-alias`)' }, + { code: 'const ASSESSMENT_SUBMIT = "[data-cy=assessment-submit]"; cy.get(ASSESSMENT_SUBMIT)' }, + { code: 'const ALIAS_TEMPLATE = `@my-alias`; cy.get(ALIAS_TEMPLATE)' }, ], invalid: [ @@ -26,5 +28,7 @@ ruleTester.run('require-data-selectors', rule, { { code: 'cy.get(".btn-.large").click()', errors }, { code: 'cy.get(".a")', errors }, { code: 'cy.get(\`[daedta-cy=${1}]\`)', errors }, // eslint-disable-line no-useless-escape + { code: 'const BAD_SELECTOR = ".my-class"; cy.get(BAD_SELECTOR)', errors }, + { code: 'const GOOD = "[data-cy=good]"; const BAD = ".bad"; cy.get(GOOD); cy.get(BAD)', errors }, ], })