Description
vitest/max-expects, vitest/no-standalone-expect, and vitest/require-hook produce false positives when using it.extend() and the test callback destructures only { expect } without any custom fixture properties. The rules fail to recognize the it(...) call as a test boundary, so:
max-expects: assertion counts accumulate across tests instead of resetting
no-standalone-expect: expects are reported as "outside a test block"
require-hook: it(...) calls are reported as setup code that "should be done within a hook"
When the callback does destructure a custom fixture property (e.g., { value, expect }), all three rules work correctly.
Reproduction
import { it as base, describe } from 'vitest';
const it = base.extend<{ value: string }>({
value: async ({}, use) => {
await use('hello');
},
});
// BUG: destructures { expect } only — no custom fixture properties
describe('without fixture property', () => {
it('test A — 6 expects', ({ expect }) => {
expect(1).toBe(1);
expect(1).toBe(1);
expect(1).toBe(1);
expect(1).toBe(1);
expect(1).toBe(1);
expect(1).toBe(1); // max-expects fires here (correct)
});
it('test B — count should reset but does not', ({ expect }) => {
expect(1).toBe(1); // max-expects: "Too many assertion calls (7)"
// no-standalone-expect: "Expect must be called inside a test block"
});
// require-hook: both it() calls are reported as
// "This should be done within a hook"
});
// OK: destructures { value, expect } — includes custom fixture property
describe('with fixture property', () => {
it('test C — 6 expects', ({ value, expect }) => {
expect(value).toBe('hello');
expect(value).toBe('hello');
expect(value).toBe('hello');
expect(value).toBe('hello');
expect(value).toBe('hello');
expect(value).toBe('hello'); // max-expects fires here (correct)
});
it('test D — count resets correctly', ({ value, expect }) => {
expect(value).toBe('hello'); // no violation (correct)
});
// no require-hook violations (correct)
});
Expected
Both describes should behave the same — the presence or absence of custom fixture properties in the destructuring pattern shouldn't affect whether it(...) is recognized as a test boundary.
Actual
"without fixture property" describe:
test A and test B: require-hook reports "This should be done within a hook"
test B: max-expects reports "Too many assertion calls (7)" — count accumulated from test A
test B: no-standalone-expect reports "Expect must be called inside a test block"
"with fixture property" describe:
- No
require-hook violations
test D: no max-expects or no-standalone-expect violations — count correctly reset
Root cause
The rules appear to use the destructured parameters to determine whether a callback is a test function. When the callback only destructures { expect } (a built-in vitest property), the rules don't recognize the it(...) call as a test boundary. When a custom fixture property like { value, expect } is present, the rules correctly identify the test boundary.
Environment
@vitest/eslint-plugin: 1.6.14
eslint: 10.x
vitest: 4.x
Description
vitest/max-expects,vitest/no-standalone-expect, andvitest/require-hookproduce false positives when usingit.extend()and the test callback destructures only{ expect }without any custom fixture properties. The rules fail to recognize theit(...)call as a test boundary, so:max-expects: assertion counts accumulate across tests instead of resettingno-standalone-expect: expects are reported as "outside a test block"require-hook:it(...)calls are reported as setup code that "should be done within a hook"When the callback does destructure a custom fixture property (e.g.,
{ value, expect }), all three rules work correctly.Reproduction
Expected
Both describes should behave the same — the presence or absence of custom fixture properties in the destructuring pattern shouldn't affect whether
it(...)is recognized as a test boundary.Actual
"without fixture property" describe:
test Aandtest B:require-hookreports "This should be done within a hook"test B:max-expectsreports "Too many assertion calls (7)" — count accumulated from test Atest B:no-standalone-expectreports "Expect must be called inside a test block""with fixture property" describe:
require-hookviolationstest D: nomax-expectsorno-standalone-expectviolations — count correctly resetRoot cause
The rules appear to use the destructured parameters to determine whether a callback is a test function. When the callback only destructures
{ expect }(a built-in vitest property), the rules don't recognize theit(...)call as a test boundary. When a custom fixture property like{ value, expect }is present, the rules correctly identify the test boundary.Environment
@vitest/eslint-plugin: 1.6.14eslint: 10.xvitest: 4.x