diff --git a/packages/eslint-plugin-sui/docs/rules/default-component-test.md b/packages/eslint-plugin-sui/docs/rules/default-component-test.md new file mode 100644 index 000000000..ac4450f32 --- /dev/null +++ b/packages/eslint-plugin-sui/docs/rules/default-component-test.md @@ -0,0 +1,60 @@ +# Detect default component test cases that haven't been customized (`default-component-test`) + +This rule detects when a component's test file only contains the default test cases generated by the component generator. This indicates that the developer has created a new component but hasn't written specific tests for its functionality. + +## Rule Details + +The component generator creates a default test file with three basic test cases that only verify basic React component requirements. While these tests are useful as a starting point, they don't validate the specific functionality of your component. + +This rule will warn you when: +- You have only default test cases in your test file +- Individual test cases that match the default generated ones + +Examples of **incorrect** code for this rule: + +```js +describe.context.default('MyComponent', () => { + it('should render without crashing', () => { + // Default test implementation + }) + + it('should NOT render null', () => { + // Default test implementation + }) + + it('should NOT extend classNames', () => { + // Default test implementation + }) +}) +``` + +Examples of **correct** code for this rule: + +```js +describe.context.default('MyComponent', () => { + it('should render without crashing', () => { + // Default test implementation + }) + + it('should show error message when invalid input', () => { + // Custom test validating specific component behavior + }) + + it('should trigger onSubmit when form is valid', () => { + // Custom test validating specific component behavior + }) +}) +``` + +### Options + +This rule has no options. + +## When Not To Use It + +If you are working with non-component code or if you're satisfied with the basic React component validation that the default tests provide. + +## Further Reading + +- [React Testing Best Practices](https://reactjs.org/docs/testing.html) +- [Component Testing Guidelines](https://github.mpi-internal.com/scmspain/es-td-agreements/blob/master/30-Frontend/00-agreements) \ No newline at end of file diff --git a/packages/eslint-plugin-sui/src/index.js b/packages/eslint-plugin-sui/src/index.js index 08b9cc756..123ec84a5 100644 --- a/packages/eslint-plugin-sui/src/index.js +++ b/packages/eslint-plugin-sui/src/index.js @@ -7,6 +7,7 @@ const DecoratorDeprecated = require('./rules/decorator-deprecated.js') const DecoratorDeprecatedRemarkMethod = require('./rules/decorator-deprecated-remark-method.js') const DecoratorInlineError = require('./rules/decorator-inline-error.js') const LayersArch = require('./rules/layers-architecture.js') +const DefaultComponentTest = require('./rules/default-component-test.js') // ------------------------------------------------------------------------------ // Plugin Definition @@ -23,6 +24,7 @@ module.exports = { 'decorator-deprecated': DecoratorDeprecated, 'decorator-deprecated-remark-method': DecoratorDeprecatedRemarkMethod, 'decorator-inline-error': DecoratorInlineError, - 'layers-arch': LayersArch + 'layers-arch': LayersArch, + 'default-component-test': DefaultComponentTest } } diff --git a/packages/eslint-plugin-sui/src/rules/default-component-test.js b/packages/eslint-plugin-sui/src/rules/default-component-test.js new file mode 100644 index 000000000..8383b697c --- /dev/null +++ b/packages/eslint-plugin-sui/src/rules/default-component-test.js @@ -0,0 +1,93 @@ +/** + * @fileoverview Detect default component test cases that haven't been customized + */ +'use strict' + +const dedent = require('string-dedent') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'Detect when component test file only contains default test cases', + recommended: true, + url: 'https://github.mpi-internal.com/scmspain/es-td-agreements/blob/master/30-Frontend/00-agreements' + }, + schema: [], + messages: { + defaultTestCase: dedent` + This seems to be a default test case generated by the component generator. + Please add specific test cases that validate your component's functionality. + `, + onlyDefaultTests: dedent` + This test file only contains default test cases. + Please add specific tests that validate your component's functionality. + ` + } + }, + + create(context) { + // Track the test cases we find + const defaultTestCases = new Set([ + 'should render without crashing', + 'should NOT render null', + 'should NOT extend classNames' + ]) + + const foundTestCases = new Set() + let totalTestCases = 0 + + function isComponentTestFile(filename) { + return filename.includes('/components/') && filename.endsWith('.test.js') + } + + return { + Program(node) { + // Reset counters for each file + foundTestCases.clear() + totalTestCases = 0 + }, + + CallExpression(node) { + const filename = context.getFilename() + // Solo procesar si estamos en un archivo de test de componente + if (!isComponentTestFile(filename)) return + + // Look for it() calls + if (node.callee.name !== 'it') return + + const testName = node.arguments[0]?.value + if (!testName) return + + totalTestCases++ + + // Check if this is one of the default test cases + if (defaultTestCases.has(testName)) { + foundTestCases.add(testName) + + context.report({ + node: node.arguments[0], + messageId: 'defaultTestCase' + }) + } + + // After processing all tests in this block, check if they are all default ones + if ( + totalTestCases > 0 && + foundTestCases.size === defaultTestCases.size && + [...foundTestCases].every(test => defaultTestCases.has(test)) + ) { + context.report({ + node: node.parent, + messageId: 'onlyDefaultTests' + }) + } + } + } + } +} diff --git a/packages/eslint-plugin-sui/test/server/default-component-test.js b/packages/eslint-plugin-sui/test/server/default-component-test.js new file mode 100644 index 000000000..6e11f85d8 --- /dev/null +++ b/packages/eslint-plugin-sui/test/server/default-component-test.js @@ -0,0 +1,100 @@ +import dedent from 'dedent' +import {RuleTester} from 'eslint' + +import rule from '../../src/rules/default-component-test.js' + +const resolvedBabelPresetSui = require.resolve('babel-preset-sui') +const parser = require.resolve('@babel/eslint-parser') + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parser, + parserOptions: { + babelOptions: {configFile: resolvedBabelPresetSui} + } +}) + +ruleTester.run('default-component-test', rule, { + valid: [ + // Should ignore non-component test files + { + filename: '/domain/value-objects/test/car.test.js', + code: dedent` + describe.context.default('Car', () => { + it('should render without crashing', () => {}) + it('should NOT render null', () => {}) + it('should NOT extend classNames', () => {}) + }) + ` + }, + // Should be valid when there are custom tests along with default ones + { + filename: '/components/card/alert/test/index.test.js', + code: dedent` + describe.context.default('CardAlert', Component => { + it('should render without crashing', () => {}) + it('should NOT render null', () => {}) + it('should show custom alert message', () => {}) + it('should trigger onClose callback', () => {}) + }) + ` + }, + // Should be valid when all tests are custom + { + filename: '/components/card/alert/test/index.test.js', + code: dedent` + describe.context.default('CardAlert', Component => { + it('should show title correctly', () => {}) + it('should handle click events', () => {}) + }) + ` + } + ], + + invalid: [ + // Should detect when only default tests are present + { + filename: '/components/card/alert/test/index.test.js', + code: dedent` + describe.context.default('CardAlert', Component => { + it('should render without crashing', () => {}) + it('should NOT render null', () => {}) + it('should NOT extend classNames', () => {}) + }) + `, + errors: [ + { + messageId: 'defaultTestCase', + line: 2 + }, + { + messageId: 'defaultTestCase', + line: 3 + }, + { + messageId: 'defaultTestCase', + line: 4 + } + ] + }, + // Should detect individual default tests + { + filename: '/components/card/alert/test/index.test.js', + code: dedent` + describe.context.default('CardAlert', Component => { + it('should render without crashing', () => {}) + it('should handle click events', () => {}) + }) + `, + errors: [ + { + messageId: 'defaultTestCase', + line: 2 + } + ] + } + ] +}) diff --git a/packages/sui-lint/eslintrc.js b/packages/sui-lint/eslintrc.js index e97cc74f1..a9cc0ebd7 100644 --- a/packages/sui-lint/eslintrc.js +++ b/packages/sui-lint/eslintrc.js @@ -38,7 +38,8 @@ const REACT_RULES = { const TESTING_RULES = { 'chai-friendly/no-unused-expressions': [RULES.ERROR, {allowShortCircuit: true, allowTernary: true}], - 'no-only-tests/no-only-tests': RULES.ERROR + 'no-only-tests/no-only-tests': RULES.ERROR, + 'sui/default-component-test': RULES.WARNING } const JEST_TESTING_RULES = { diff --git a/packages/sui-lint/eslintrc.ts.js b/packages/sui-lint/eslintrc.ts.js index de5f356b0..e9dbf07c0 100644 --- a/packages/sui-lint/eslintrc.ts.js +++ b/packages/sui-lint/eslintrc.ts.js @@ -38,7 +38,8 @@ const REACT_RULES = { const TESTING_RULES = { 'chai-friendly/no-unused-expressions': [RULES.ERROR, {allowShortCircuit: true, allowTernary: true}], - 'no-only-tests/no-only-tests': RULES.ERROR + 'no-only-tests/no-only-tests': RULES.ERROR, + 'sui/default-component-test': RULES.WARNING } const JEST_TESTING_RULES = {