name: tests description: Provides JavaScript/TypeScript test file structure and patterns for SonarJS rule testing. Use when writing tests, understanding test structure, or debugging test failures.
JavaScript/TypeScript Rule Test Structure
Test File Location
Test files for rules are located alongside the rule implementation:
unit.test.ts- Unit tests using the rule tester- Test fixtures in
packages/analysis/src/jsts/rules/{RULE_ID}/fixtures/(if applicable)
Test Structure
Tests use the ESLint rule tester pattern with valid and invalid arrays:
import { rule } from './index.js';
import { DefaultParserRuleTester } from '../../../tests/tools/testers/rule-tester.js';
import { describe, it } from 'node:test';
describe('S1234', () => {
it('S1234', () => {
const ruleTester = new DefaultParserRuleTester();
ruleTester.run('Rule description', rule, {
valid: [
{
code: `
// Valid code that should NOT raise an issue
const x = 1;
`,
},
{
// False positive scenario: [brief description]
// This should NOT raise an issue because [reason]
code: `
const myVar = someFunction();
`,
},
],
invalid: [
{
code: `
// Invalid code that SHOULD raise an issue
problematicCode();
`,
errors: 1,
},
],
});
});
});
Key Concepts
validarray: Code that should NOT raise an issue (compliant code)invalidarray: Code that SHOULD raise an issue (non-compliant code)errors: Number of expected issues in an invalid case, or an array of ESLint error objects (useful for asserting quickfixes and suggestions, e.g.errors: [{ messageId: 'errorKey', suggestions: [...] }])options: Pass rule configuration options if needed
When fixing false positives, add test cases to the valid array - the false positive is code that should be treated as valid.
Converting FP Reproducers to Failing Tests (TDD Style)
FP reproducers are entries in the invalid array for code that raises a false positive. They may have a comment inside the entry object documenting why it is an FP:
invalid: [
{
// FP: rule incorrectly flags this pattern because...
code: `const x = foo();`,
errors: 1,
},
]
To convert a reproducer to a failing test, move the entry from invalid to valid, remove the errors field, and remove any autofix expectations such as output or suggestions. Keep fields like filename and options if they are still needed for the test setup:
// Before (reproducer — test passes because the issue IS raised and expected):
invalid: [{ code: `const x = foo();`, filename: 'fixture.ts', errors: 1, output: `foo()` }]
// After (failing test — test fails because the issue IS raised but valid is asserted):
valid: [{ code: `const x = foo();`, filename: 'fixture.ts' }]
Isolating Failing Test Cases
When tests fail and you need to identify which specific test case is causing the failure:
- Create a backup of the test file
- Comment out all cases from the
validandinvalidarrays EXCEPT the one being tested - Record whether this specific test case passes or fails (the orchestration harness will run the tests)
- Restore the test file from backup
- Repeat for each suspect test case
This isolation technique helps identify exactly which test cases are failing when the test output is unclear.
IMPORTANT: Do NOT run unit tests directly. The orchestration harness will run them after each recipe step completes.
Ruling Tests
Ruling tests (integration tests against real-world code) are in the separate SonarJS-Ruling repository.
Upstream Sentinel Tests (Decorator Rules)
When meta.ts contains implementation = 'decorated', add an upstream sentinel describe block before the main describe block in the test file.
- Read
decorator.tsto identify each distinct FP pattern being suppressed (conditions that trigger an earlyreturninstead of callingcontext.report) - Read
meta.tsto find theeslintId(the upstream ESLint rule name) - Read
index.tsto find the exact import pattern already in use:- ESLint core rules:
import { getESLintCoreRule } from '../external/core.js'thengetESLintCoreRule('{ESLINT_ID}') - Plugin rules:
import { rules } from '../external/{PLUGIN}.js'thenrules['{ESLINT_ID}']
- ESLint core rules:
- Add the same import to the test file and obtain a reference to the upstream rule the same way
- Never import directly from
eslint-plugin-*packages oreslint/use-at-your-own-risk— always use theexternal/shims
- Never import directly from
- Add a comment above the sentinel block explaining its purpose, then the block itself:
// Sentinel: verify that the upstream ESLint rule still raises on the patterns our decorator fixes. // If this test starts failing (i.e., the upstream rule no longer reports these patterns), // it signals that the decorator can be safely removed. describe('S{RULE_ID} upstream sentinel', () => { it('upstream {ESLINT_ID} raises on {FP pattern description} that decorator suppresses', () => { const ruleTester = new DefaultParserRuleTester(); ruleTester.run('{ESLINT_ID}', upstreamRule, { valid: [], invalid: [ { code: `...`, errors: 1, output: `...` }, // include output if the upstream rule has autofix; omit if the rule is not fixable // one entry per distinct FP pattern ], }); }); }); - Do NOT modify the tests for the decorated rule (imported from
./index.js) — they remain exactly as written
Move test cases from invalid to valid when removing them — do not silently delete
What: Removing a test case from the invalid array (because the fix now suppresses it) without adding it to the valid array.
Why: The case is now valid — the rule no longer raises an issue on it — but the test suite contains no evidence of this. Future readers cannot tell whether the omission is intentional or an oversight, and a regression that re-introduces the false positive would go undetected.
How: When a previously-invalid test case becomes valid because of the fix, always move it to the valid array rather than deleting it. If the case was for a chained or compound expression where multiple assignments are involved (and all are now suppressed), add a comment explaining why all assignments are suppressed.