Instructions
You are helping users work with eslint-vitest-rule-tester, a library that provides ESLint rule testing with Vitest integration.
Core APIs
Two testing approaches:
run({ name, rule, valid, invalid, ...config }) - All-in-one object style (config directly in object)
createRuleTester({ name, rule, configs }) - Returns { valid, invalid } for explicit Vitest describe/it blocks (note: configs key, not languageOptions)
Key extensions:
output and errors fields can be functions for custom assertions and snapshots
onResult hook for full result object validation
- No
globals: true required in Vitest config
When Helping Users
Identify testing style first:
- Check if they have existing tests to match their pattern
- Default to
run() for simplicity (see Pattern 1 below)
- Use
createRuleTester() for explicit Vitest test blocks with individual it() tests (Pattern 3)
Configuration:
- Use
languageOptions.parser: tsParser for TypeScript (import from @typescript-eslint/parser)
- Use
languageOptions.parserOptions for JS options (ecmaVersion, sourceType)
- Put shared config in tester initialization, not in every test case
- Note: In
createRuleTester, config goes under configs key, not at top level
Type safety:
- Use
satisfies TestCasesOptions['valid'] for valid test arrays
- Use
satisfies TestCasesOptions['invalid'] for invalid test arrays
- Extract test cases as constants before passing to
run() (see Pattern 1)
Snapshots:
- Use
onResult hook with toMatchSnapshot() for all invalid cases (Pattern 1)
- Use function-based
output/errors with toMatchInlineSnapshot() for inline snapshots (Pattern 2)
- Recommend
toMatchInlineSnapshot() over toMatchSnapshot() when possible for better visibility
Common patterns:
// Pattern 1: run() with extracted test cases (from top-level-function.test.ts)
import type { TestCasesOptions } from 'eslint-vitest-rule-tester'
import { run } from 'eslint-vitest-rule-tester'
import { expect } from 'vitest'
import * as tsParser from '@typescript-eslint/parser'
const valids = [
'function foo() {}',
// allow arrow function inside function
'function foo() { const bar = () => {} }',
] satisfies TestCasesOptions['valid']
const invalids = [
{
code: 'const foo = () => {}',
output: 'function foo () {}',
errors: [{ messageId: 'topLevelFunctionDeclaration' }],
},
] satisfies TestCasesOptions['invalid']
run({
name: 'top-level-function',
rule,
languageOptions: { parser: tsParser },
valid: valids,
invalid: invalids,
onResult(_case, result) {
if (_case.type === 'invalid')
expect(result.output).toMatchSnapshot()
},
})
// Pattern 2: Function-based output/errors (from README)
run({
name: 'rule-name',
rule,
invalid: [
{
code: 'let foo = 1',
output(output) {
expect(output.slice(0, 3)).toBe('let')
expect(output).toMatchInlineSnapshot(`"const foo = 1;"`)
},
errors(errors) {
expect(errors).toHaveLength(1)
expect(errors.map(e => e.messageId))
.toMatchInlineSnapshot(`["preferConst"]`)
},
},
],
})
// Pattern 3: Explicit test suites (from README)
import { createRuleTester } from 'eslint-vitest-rule-tester'
import { describe, it } from 'vitest'
describe('rule-name', () => {
const { valid, invalid } = createRuleTester({
name: 'rule-name',
rule,
configs: {
languageOptions: {
parserOptions: { ecmaVersion: 2020, sourceType: 'module' },
},
},
})
it('valid case 1', () => {
valid('const foo = 1')
})
it('invalid case 1 with snapshot', async () => {
const { result } = await invalid({
code: 'const foo = 1',
errors: ['error-message-id'],
})
expect(result.output).toMatchSnapshot()
})
})
Troubleshooting
- Version errors: Requires ESLint v9.10+, check
package.json
- Snapshot failures: Run
vitest -u to update snapshots
- Parser issues: Add
languageOptions.parser for TypeScript/JSX
- Type errors: Import from
eslint-vitest-rule-tester, not eslint
Best Practices
- Match existing patterns - Check existing test files in the project first
- Extract test cases - Define
valids and invalids as constants with satisfies for type safety
- Add comments - Use inline comments to explain what each test case validates (e.g.,
// allow arrow function inside function)
- Use snapshots - Prefer
onResult hook for bulk snapshot testing of all invalid cases
- Consolidate config - Put shared parser/language options at tester level, not per test case
- TypeScript support - Import
@typescript-eslint/parser as tsParser and use in languageOptions.parser
- Error format - Use
errors: [{ messageId: 'errorId' }] for structured errors, or errors: ['errorId'] for simple cases
Source: antfu-collective/eslint-vitest-rule-tester — distributed by TomeVault.
1---2name: eslint-vitest-rule-tester3description: Help users test ESLint rules with Vitest, supporting snapshot testing and custom assertions Use when this capability is needed.4---56# Instructions78You are helping users work with `eslint-vitest-rule-tester`, a library that provides ESLint rule testing with Vitest integration.910## Core APIs1112**Two testing approaches:**13141. **`run({ name, rule, valid, invalid, ...config })`** - All-in-one object style (config directly in object)152. **`createRuleTester({ name, rule, configs })`** - Returns `{ valid, invalid }` for explicit Vitest `describe`/`it` blocks (note: `configs` key, not `languageOptions`)1617**Key extensions:**18- `output` and `errors` fields can be functions for custom assertions and snapshots19- `onResult` hook for full result object validation20- No `globals: true` required in Vitest config2122## When Helping Users2324**Identify testing style first:**25- Check if they have existing tests to match their pattern26- Default to `run()` for simplicity (see Pattern 1 below)27- Use `createRuleTester()` for explicit Vitest test blocks with individual `it()` tests (Pattern 3)2829**Configuration:**30- Use `languageOptions.parser: tsParser` for TypeScript (import from `@typescript-eslint/parser`)31- Use `languageOptions.parserOptions` for JS options (ecmaVersion, sourceType)32- Put shared config in tester initialization, not in every test case33- Note: In `createRuleTester`, config goes under `configs` key, not at top level3435**Type safety:**36- Use `satisfies TestCasesOptions['valid']` for valid test arrays37- Use `satisfies TestCasesOptions['invalid']` for invalid test arrays38- Extract test cases as constants before passing to `run()` (see Pattern 1)3940**Snapshots:**41- Use `onResult` hook with `toMatchSnapshot()` for all invalid cases (Pattern 1)42- Use function-based `output`/`errors` with `toMatchInlineSnapshot()` for inline snapshots (Pattern 2)43- Recommend `toMatchInlineSnapshot()` over `toMatchSnapshot()` when possible for better visibility4445**Common patterns:**4647```ts48// Pattern 1: run() with extracted test cases (from top-level-function.test.ts)49import type { TestCasesOptions } from 'eslint-vitest-rule-tester'50import { run } from 'eslint-vitest-rule-tester'51import { expect } from 'vitest'52import * as tsParser from '@typescript-eslint/parser'5354const valids = [55 'function foo() {}',56 // allow arrow function inside function57 'function foo() { const bar = () => {} }',58] satisfies TestCasesOptions['valid']5960const invalids = [61 {62 code: 'const foo = () => {}',63 output: 'function foo () {}',64 errors: [{ messageId: 'topLevelFunctionDeclaration' }],65 },66] satisfies TestCasesOptions['invalid']6768run({69 name: 'top-level-function',70 rule,71 languageOptions: { parser: tsParser },72 valid: valids,73 invalid: invalids,74 onResult(_case, result) {75 if (_case.type === 'invalid')76 expect(result.output).toMatchSnapshot()77 },78})7980// Pattern 2: Function-based output/errors (from README)81run({82 name: 'rule-name',83 rule,84 invalid: [85 {86 code: 'let foo = 1',87 output(output) {88 expect(output.slice(0, 3)).toBe('let')89 expect(output).toMatchInlineSnapshot(`"const foo = 1;"`)90 },91 errors(errors) {92 expect(errors).toHaveLength(1)93 expect(errors.map(e => e.messageId))94 .toMatchInlineSnapshot(`["preferConst"]`)95 },96 },97 ],98})99100// Pattern 3: Explicit test suites (from README)101import { createRuleTester } from 'eslint-vitest-rule-tester'102import { describe, it } from 'vitest'103104describe('rule-name', () => {105 const { valid, invalid } = createRuleTester({106 name: 'rule-name',107 rule,108 configs: {109 languageOptions: {110 parserOptions: { ecmaVersion: 2020, sourceType: 'module' },111 },112 },113 })114115 it('valid case 1', () => {116 valid('const foo = 1')117 })118119 it('invalid case 1 with snapshot', async () => {120 const { result } = await invalid({121 code: 'const foo = 1',122 errors: ['error-message-id'],123 })124 expect(result.output).toMatchSnapshot()125 })126})127```128129## Troubleshooting130131- **Version errors**: Requires ESLint v9.10+, check `package.json`132- **Snapshot failures**: Run `vitest -u` to update snapshots133- **Parser issues**: Add `languageOptions.parser` for TypeScript/JSX134- **Type errors**: Import from `eslint-vitest-rule-tester`, not `eslint`135136## Best Practices137138- **Match existing patterns** - Check existing test files in the project first139- **Extract test cases** - Define `valids` and `invalids` as constants with `satisfies` for type safety140- **Add comments** - Use inline comments to explain what each test case validates (e.g., `// allow arrow function inside function`)141- **Use snapshots** - Prefer `onResult` hook for bulk snapshot testing of all invalid cases142- **Consolidate config** - Put shared parser/language options at tester level, not per test case143- **TypeScript support** - Import `@typescript-eslint/parser` as `tsParser` and use in `languageOptions.parser`144- **Error format** - Use `errors: [{ messageId: 'errorId' }]` for structured errors, or `errors: ['errorId']` for simple cases145146---147> Source: [antfu-collective/eslint-vitest-rule-tester](https://github.com/antfu-collective/eslint-vitest-rule-tester) — distributed by [TomeVault](https://tomevault.io).148<!-- tomevault:4.0:skill_md:2026-06-25 -->