Files
renovate/tools/agents/hooks/pretooluse-check.spec.ts
Sebastian Poxhofer 7436b80cda test(linter): add renovate/no-redundant-mock-reset rule (#44557)
ci(linter): add renovate/no-redundant-mock-reset rule

Vitest sets mockReset: true globally in vitest.config.mts, which resets
all mocks before each test. This makes vi.resetAllMocks() /
vi.clearAllMocks() and per-mock .mockReset() / .mockClear() calls inside
beforeEach/afterEach hooks redundant. Add a custom oxlint rule (enabled
for **/*.spec.ts and test/**) flagging such calls, and remove the 40
redundant calls it found. restoreAllMocks/mockRestore are not flagged
because restoreMocks is not enabled globally.

Two sites keep an inline disable with a reason: nested beforeEach hooks
in filter-checks.spec.ts and onboarding/pr/index.spec.ts intentionally
call mockReset() to discard once-values queued by an outer beforeEach.

Requested by maintainer review feedback (review comment 3052324877).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 11:26:20 +00:00

117 lines
3.6 KiB
TypeScript

// https://code.claude.com/docs/en/hooks#pretooluse
const { deny } = vi.hoisted(() => ({ deny: vi.fn() }));
vi.mock('./utils/output.ts', () => ({ deny }));
const { readStdin } = vi.hoisted(() => ({ readStdin: vi.fn() }));
vi.mock('./utils/stdin.ts', () => ({ readStdin }));
function makeInput(
toolName: string,
toolInput: Record<string, unknown>,
): string {
return JSON.stringify({
session_id: 'test-session',
transcript_path: '/tmp/transcript.jsonl',
cwd: '/Users/test/renovate',
hook_event_name: 'PreToolUse',
tool_use_id: 'tu-123',
tool_name: toolName,
tool_input: toolInput,
});
}
beforeEach(() => {
vi.resetModules();
});
it('blocks npm install', async () => {
readStdin.mockResolvedValue(makeInput('Bash', { command: 'npm install' }));
await import('./pretooluse-check.ts');
expect(deny).toHaveBeenCalledOnce();
expect(deny).toHaveBeenCalledWith('Use pnpm instead of npm/npx/yarn');
});
it('blocks npx create-react-app', async () => {
readStdin.mockResolvedValue(
makeInput('Bash', { command: 'npx create-react-app my-app' }),
);
await import('./pretooluse-check.ts');
expect(deny).toHaveBeenCalledOnce();
});
it('blocks yarn add foo', async () => {
readStdin.mockResolvedValue(makeInput('Bash', { command: 'yarn add foo' }));
await import('./pretooluse-check.ts');
expect(deny).toHaveBeenCalledOnce();
});
it('blocks chained command with npm', async () => {
readStdin.mockResolvedValue(
makeInput('Bash', { command: 'echo hi && npm test' }),
);
await import('./pretooluse-check.ts');
expect(deny).toHaveBeenCalledOnce();
});
it('allows pnpm install', async () => {
readStdin.mockResolvedValue(makeInput('Bash', { command: 'pnpm install' }));
await import('./pretooluse-check.ts');
expect(deny).not.toHaveBeenCalled();
});
it('allows non-Bash tool', async () => {
readStdin.mockResolvedValue(
makeInput('Read', { file_path: '/some/file.ts' }),
);
await import('./pretooluse-check.ts');
expect(deny).not.toHaveBeenCalled();
});
it('allows a command that contains npm as a substring but is not standalone', async () => {
readStdin.mockResolvedValue(
makeInput('Bash', { command: 'cat package.json | jq .npmClient' }),
);
await import('./pretooluse-check.ts');
expect(deny).not.toHaveBeenCalled();
});
it('blocks pnpm jest', async () => {
readStdin.mockResolvedValue(makeInput('Bash', { command: 'pnpm jest' }));
await import('./pretooluse-check.ts');
expect(deny).toHaveBeenCalledOnce();
expect(deny).toHaveBeenCalledWith('Use pnpm vitest instead of pnpm jest');
});
it('blocks pnpm run jest with args', async () => {
readStdin.mockResolvedValue(
makeInput('Bash', { command: 'pnpm run jest lib/foo.spec.ts' }),
);
await import('./pretooluse-check.ts');
expect(deny).toHaveBeenCalledOnce();
expect(deny).toHaveBeenCalledWith('Use pnpm vitest instead of pnpm jest');
});
it('blocks pnpm exec jest', async () => {
readStdin.mockResolvedValue(makeInput('Bash', { command: 'pnpm exec jest' }));
await import('./pretooluse-check.ts');
expect(deny).toHaveBeenCalledOnce();
expect(deny).toHaveBeenCalledWith('Use pnpm vitest instead of pnpm jest');
});
it('allows pnpm vitest', async () => {
readStdin.mockResolvedValue(
makeInput('Bash', { command: 'pnpm vitest lib/foo.spec.ts' }),
);
await import('./pretooluse-check.ts');
expect(deny).not.toHaveBeenCalled();
});
it('allows command containing jest as substring but not standalone', async () => {
readStdin.mockResolvedValue(
makeInput('Bash', { command: 'cat package.json | jq .jestConfig' }),
);
await import('./pretooluse-check.ts');
expect(deny).not.toHaveBeenCalled();
});