From a4a0e8e7b7028193193975676b5639e8124942c4 Mon Sep 17 00:00:00 2001 From: sandeepbaldawa Date: Thu, 26 Sep 2019 07:19:22 -0700 Subject: [PATCH] feat(rule): add 'require-data-selectors' rule (#30) Only allow `[data-*]` attribute selectors on `cy.get` --- docs/rules/require-data-selectors.md | 23 ++++++++++++ index.js | 1 + lib/rules/require-data-selectors.js | 45 +++++++++++++++++++++++ tests/lib/rules/require-data-selectors.js | 27 ++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 docs/rules/require-data-selectors.md create mode 100644 lib/rules/require-data-selectors.js create mode 100644 tests/lib/rules/require-data-selectors.js diff --git a/docs/rules/require-data-selectors.md b/docs/rules/require-data-selectors.md new file mode 100644 index 00000000..4c073e7a --- /dev/null +++ b/docs/rules/require-data-selectors.md @@ -0,0 +1,23 @@ +## Only allow `data-*` attribute selectors (require-data-selectors) +only allow `cy.get` to allow selectors that target `data-*` attributes + +See [the Cypress Best Practices guide](https://docs.cypress.io/guides/references/best-practices.html#Selecting-Elements). + +> Note: If you use this rule, consider only using the `warn` error level, since using `data-*` attribute selectors may not always be possible. + +### Rule Details + +examples of **incorrect** code with `require-data-selectors`: +```js +cy.get(".a") +cy.get('[daedta-cy=submit]').click() +cy.get('[d-cy=submit]') +cy.get(".btn-large").click() +cy.get(".btn-.large").click() +``` + +examples of **correct** code with `require-data-selectors`: +```js +cy.get('[data-cy=submit]').click() +cy.get('[data-QA=submit]') +``` diff --git a/index.js b/index.js index 044c6992..e1eb471a 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ module.exports = { 'no-assigning-return-values': require('./lib/rules/no-assigning-return-values'), 'no-unnecessary-waiting': require('./lib/rules/no-unnecessary-waiting'), 'assertion-before-screenshot': require('./lib/rules/assertion-before-screenshot'), + 'require-data-selectors': require('./lib/rules/require-data-selectors'), }, configs: { recommended: require('./lib/config/recommended'), diff --git a/lib/rules/require-data-selectors.js b/lib/rules/require-data-selectors.js new file mode 100644 index 00000000..b8d5203e --- /dev/null +++ b/lib/rules/require-data-selectors.js @@ -0,0 +1,45 @@ +/** + * @fileoverview Use data-* attribute selectors instead of classes or tag names + * @author Sandeep Baldawa + */ +'use strict' + +module.exports = { + meta: { + docs: { + description: 'Use data-* attributes to provide context to your selectors and insulate them from CSS or JS changes https://docs.cypress.io/guides/references/best-practices.html#Selecting-Elements', + category: 'Possible Errors', + recommended: false, + url: 'https://docs.cypress.io/guides/references/best-practices.html#Selecting-Elements', + }, + schema: [], + messages: { + unexpected: 'use data-* attribute selectors instead of classes or tag names', + }, + }, + + create (context) { + return { + CallExpression (node) { + if (isCallingCyGet(node) && !isDataArgument(node)) { + context.report({ node, messageId: 'unexpected' }) + } + }, + } + }, +} + +function isCallingCyGet (node) { + return node.callee.type === 'MemberExpression' && + node.callee.object.type === 'Identifier' && + node.callee.object.name === 'cy' && + node.callee.property.type === 'Identifier' && + node.callee.property.name === 'get' +} + +function isDataArgument (node) { + return node.arguments.length > 0 && + node.arguments[0].type === 'Literal' && + String(node.arguments[0].value).startsWith('[data-') + +} diff --git a/tests/lib/rules/require-data-selectors.js b/tests/lib/rules/require-data-selectors.js new file mode 100644 index 00000000..be6b17d8 --- /dev/null +++ b/tests/lib/rules/require-data-selectors.js @@ -0,0 +1,27 @@ +'use strict' + +const rule = require('../../../lib/rules/require-data-selectors') +const RuleTester = require('eslint').RuleTester + +const ruleTester = new RuleTester() + +const errors = [{ messageId: 'unexpected' }] +const parserOptions = { ecmaVersion: 6 } + +ruleTester.run('no-dynamic-id-classes', rule, { + valid: [ + { code: 'cy.get(\'[data-cy=submit]\').click()', parserOptions }, + { code: 'cy.get(\'[data-QA=submit]\')', parserOptions }, + { code: 'cy.clock(5000)', parserOptions }, + { code: 'cy.scrollTo(0, 10)', parserOptions }, + { code: 'cy.tick(500)', parserOptions }, + ], + + invalid: [ + { code: 'cy.get(\'[daedta-cy=submit]\').click()', parserOptions, errors }, + { code: 'cy.get(\'[d-cy=submit]\')', parserOptions, errors }, + { code: 'cy.get(".btn-large").click()', parserOptions, errors }, + { code: 'cy.get(".btn-.large").click()', parserOptions, errors }, + { code: 'cy.get(".a")', parserOptions, errors }, + ], +})