-
-
Notifications
You must be signed in to change notification settings - Fork 33
feat: add new rule require-test-case-name
#553
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
aladdin-add
merged 10 commits into
eslint-community:main
from
michaelfaith:feat/test-case-property-name
Oct 23, 2025
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
c743c23
feat(test-case-name-property): add rule to require `name` for test cases
michaelfaith 9d6bffa
Address copilot feedbac
michaelfaith 1504a42
address some of the lint violations
michaelfaith 19ca5a9
fix more lint violations
michaelfaith cf4fc84
rename rule
michaelfaith b8d843c
update meta.docs.url
michaelfaith 265b10c
fix more lint violations
michaelfaith 056da29
fix more lint violations
michaelfaith 8cfec3f
fix more lint violations
michaelfaith 41e3104
added additional tests
michaelfaith File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| # Require test cases to have a `name` property under certain conditions (`eslint-plugin/require-test-case-name`) | ||
|
|
||
| <!-- end auto-generated rule header --> | ||
|
|
||
| This rule enforces that test cases include a `name` property, under certain circumstances based on the configuration. | ||
|
|
||
| ## Rule Details | ||
|
|
||
| This rule aims to ensure test suites are producing logs in a form that make it easy to identify failing test, when they happen. | ||
| For thoroughly tested rules, it's not uncommon to have the same `code` across multiple test cases, with only `options` or `settings` differing between them. | ||
| Requiring these test cases to have a `name` helps ensure the test output is meaningful and distinct. | ||
|
|
||
| ### Options | ||
|
|
||
| This rule has one option. | ||
|
|
||
| #### `require: 'always' | 'objects' | 'objects-with-config'` | ||
|
|
||
| - `always`: all test cases should have a `name` property (this means that no shorthand string test cases are allowed as a side effect) | ||
| - `objects`: requires that a `name` property is present in all `object`-based test cases. | ||
| - `objects-with-config` (default): requires that test cases that have `options` or `settings` defined, should also have a `name` property. | ||
|
|
||
| Examples of **incorrect** code for this rule: | ||
|
|
||
| ```js | ||
| // invalid; require: objects-with-config (default) | ||
| const testCase1 = { | ||
| code: 'foo', | ||
| options: ['baz'], | ||
| }; | ||
|
|
||
| // invalid; require: objects | ||
| const testCase2 = { | ||
| code: 'foo', | ||
| }; | ||
|
|
||
| // invalid; require: always | ||
| const testCase3 = 'foo'; | ||
| ``` | ||
|
|
||
| Examples of **correct** code for this rule: | ||
|
|
||
| ```js | ||
| // require: objects-with-config, objects | ||
| const testCase1 = 'foo'; | ||
|
|
||
| // require: objects-with-config, objects, always | ||
| const testCase2 = { | ||
| code: 'foo', | ||
| options: ['baz'], | ||
| name: "foo (option: ['baz'])", | ||
| }; | ||
|
|
||
| // require: objects-with-config, objects, always | ||
| const testCase4 = { | ||
| code: 'foo', | ||
| name: 'foo without options', | ||
| }; | ||
| ``` | ||
|
|
||
| ## When Not to Use It | ||
|
|
||
| If you aren't concerned with the nature of the test logs or don't want to require `name` on test cases. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| import type { Rule } from 'eslint'; | ||
|
|
||
| import { evaluateObjectProperties, getKeyName, getTestInfo } from '../utils.ts'; | ||
| import type { TestInfo } from '../types.ts'; | ||
|
|
||
| type TestCaseData = { | ||
| node: NonNullable<TestInfo['valid'][number]>; | ||
| isObject: boolean; | ||
| hasName: boolean; | ||
| hasConfig: boolean; | ||
| }; | ||
|
|
||
| const violationFilters = { | ||
| always: (testCase: TestCaseData) => !testCase.hasName, | ||
| objects: (testCase: TestCaseData) => testCase.isObject && !testCase.hasName, | ||
| 'objects-with-config': (testCase: TestCaseData) => | ||
| testCase.isObject && testCase.hasConfig && !testCase.hasName, | ||
| } satisfies Record<Options['require'], (testCase: TestCaseData) => boolean>; | ||
|
|
||
| const violationMessages = { | ||
| always: 'nameRequiredAlways', | ||
| objects: 'nameRequiredObjects', | ||
| 'objects-with-config': 'nameRequiredObjectsWithConfig', | ||
| } satisfies Record<Options['require'], string>; | ||
|
|
||
| type Options = { | ||
| require: 'always' | 'objects' | 'objects-with-config'; | ||
| }; | ||
|
|
||
| const rule: Rule.RuleModule = { | ||
| meta: { | ||
| type: 'suggestion', | ||
| docs: { | ||
| description: | ||
| 'require test cases to have a `name` property under certain conditions', | ||
| category: 'Tests', | ||
| recommended: false, | ||
| url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/require-test-case-name.md', | ||
| }, | ||
| schema: [ | ||
| { | ||
| additionalProperties: false, | ||
| properties: { | ||
| require: { | ||
| description: | ||
| 'When should the name property be required on a test case object.', | ||
| enum: ['always', 'objects', 'objects-with-config'], | ||
| }, | ||
| }, | ||
| type: 'object', | ||
| }, | ||
| ], | ||
| defaultOptions: [{ require: 'objects-with-config' }], | ||
| messages: { | ||
| nameRequiredAlways: | ||
| 'This test case is missing the `name` property. All test cases should have a name property.', | ||
| nameRequiredObjects: | ||
| 'This test case is missing the `name` property. Test cases defined as objects should have a name property.', | ||
| nameRequiredObjectsWithConfig: | ||
| 'This test case is missing the `name` property. Test cases defined as objects with additional configuration should have a name property.', | ||
| }, | ||
| }, | ||
|
|
||
| create(context) { | ||
| const { require: requireOption = 'objects-with-config' }: Options = | ||
aladdin-add marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| context.options[0] || {}; | ||
| const sourceCode = context.sourceCode; | ||
|
|
||
| /** | ||
| * Validates test cases and reports them if found in violation | ||
| * @param cases A list of test case nodes | ||
| */ | ||
| function validateTestCases(cases: TestInfo['valid']): void { | ||
| // Gather all of the information from each test case | ||
| const testCaseData: TestCaseData[] = cases | ||
| .filter((testCase) => !!testCase) | ||
| .map((testCase) => { | ||
| if ( | ||
| testCase.type === 'Literal' || | ||
| testCase.type === 'TemplateLiteral' | ||
| ) { | ||
| return { | ||
| node: testCase, | ||
| isObject: false, | ||
| hasName: false, | ||
| hasConfig: false, | ||
| }; | ||
| } | ||
| if (testCase.type === 'ObjectExpression') { | ||
| let hasName = false; | ||
| let hasConfig = false; | ||
|
|
||
| // evaluateObjectProperties is used here to expand spread elements | ||
| for (const property of evaluateObjectProperties( | ||
| testCase, | ||
| sourceCode.scopeManager, | ||
| )) { | ||
| if (property.type === 'Property') { | ||
| const keyName = getKeyName( | ||
| property, | ||
| sourceCode.getScope(testCase), | ||
| ); | ||
| if (keyName === 'name') { | ||
| hasName = true; | ||
| } else if (keyName === 'options' || keyName === 'settings') { | ||
| hasConfig = true; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| node: testCase, | ||
| isObject: true, | ||
| hasName, | ||
| hasConfig, | ||
| }; | ||
| } | ||
| return null; | ||
| }) | ||
| .filter((testCase) => !!testCase); | ||
|
|
||
| const violations = testCaseData.filter(violationFilters[requireOption]); | ||
| for (const violation of violations) { | ||
| context.report({ | ||
| node: violation.node, | ||
| messageId: violationMessages[requireOption], | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| Program(ast) { | ||
| getTestInfo(context, ast) | ||
| .map((testRun) => [...testRun.valid, ...testRun.invalid]) | ||
| .forEach(validateTestCases); | ||
| }, | ||
| }; | ||
| }, | ||
| }; | ||
|
|
||
| export default rule; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.