Skip to content

Commit

Permalink
Merge pull request #1878 from SUI-Components/feat-create-default-comp…
Browse files Browse the repository at this point in the history
…onent-test-rule

Feat create default component test rule
  • Loading branch information
emiliovillu authored Dec 2, 2024
2 parents 147f3e8 + 2058d04 commit d7af27d
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 3 deletions.
60 changes: 60 additions & 0 deletions packages/eslint-plugin-sui/docs/rules/default-component-test.md
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 3 additions & 1 deletion packages/eslint-plugin-sui/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
}
93 changes: 93 additions & 0 deletions packages/eslint-plugin-sui/src/rules/default-component-test.js
Original file line number Diff line number Diff line change
@@ -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'
})
}
}
}
}
}
100 changes: 100 additions & 0 deletions packages/eslint-plugin-sui/test/server/default-component-test.js
Original file line number Diff line number Diff line change
@@ -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
}
]
}
]
})
3 changes: 2 additions & 1 deletion packages/sui-lint/eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
3 changes: 2 additions & 1 deletion packages/sui-lint/eslintrc.ts.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down

0 comments on commit d7af27d

Please sign in to comment.