mirror of
https://github.com/codeflash-ai/codeflash-internal.git
synced 2026-05-04 18:25:18 +00:00
Fix: Strip .js extensions from vi.mock() calls in Vitest tests (#2524)
## Summary
Vitest tests were failing with "Cannot find module" errors because
`vi.mock()` calls retained `.js` extensions while imports had them
stripped, causing mock/import path mismatch in ESM mode.
## Root Cause
The `strip_js_extensions()` function in `testgen.py` only handled
`jest.mock()` but not `vi.mock()`, which is used by Vitest. The pattern
`_JEST_MOCK_EXTENSION_PATTERN` matched Jest mocking functions but not
Vitest's `vi.*` equivalents.
## Fix
Added `_VITEST_MOCK_EXTENSION_PATTERN` regex to match and strip
extensions from:
- `vi.mock()`
- `vi.doMock()`
- `vi.unmock()`
- `vi.requireActual()`
- `vi.requireMock()`
- `vi.importActual()`
- `vi.importMock()`
## Affected Trace IDs
- `0fe99c9f-b348-4f0a-b051-0ea9455231ba`
- `127cdaec-a343-4918-a86a-b646dd4d79cf`
- `2b6c896e-20d7-4505-8bf4-e4a2f20b37fc`
These trace IDs exhibited the bug where generated tests had
`vi.mock('../config/paths.js')` but imports had `from
'../config/paths'`, causing module resolution failures.
## Test Coverage
- Added 8 new tests in `TestStripJsExtensions` class
- All 31 tests in `test_testgen_javascript.py` pass
- Specific regression test for vi.mock() extension stripping
- Tests cover all vi.mock variants and edge cases
## Files Changed
- `django/aiservice/core/languages/js_ts/testgen.py` (fix)
- `django/aiservice/tests/testgen/test_testgen_javascript.py` (tests)
---------
Co-authored-by: Codeflash Bot <codeflash-bot@codeflash.ai>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Sarthak Agarwal <sarthak.saga@gmail.com>
This commit is contained in:
parent
179302d006
commit
de0f30ae15
2 changed files with 98 additions and 3 deletions
|
|
@ -141,12 +141,15 @@ _REQUIRE_EXTENSION_PATTERN = re.compile(
|
|||
_JEST_MOCK_EXTENSION_PATTERN = re.compile(
|
||||
r"""(jest\.(?:mock|doMock|unmock|requireActual|requireMock)\s*\(\s*['"])(\.{0,2}/[^'"]+?)(\.(?:js|ts|tsx|jsx|mjs|mts))(['"])"""
|
||||
)
|
||||
_VITEST_MOCK_EXTENSION_PATTERN = re.compile(
|
||||
r"""(vi\.(?:mock|doMock|unmock|requireActual|requireMock|importActual|importMock)\s*\(\s*['"])(\.{0,2}/[^'"]+?)(\.(?:js|ts|tsx|jsx|mjs|mts))(['"])"""
|
||||
)
|
||||
|
||||
|
||||
def strip_js_extensions(source: str) -> str:
|
||||
"""Strip .js/.ts/.tsx/.jsx extensions from relative import paths.
|
||||
|
||||
TypeScript and Jest's module resolution automatically resolve file extensions,
|
||||
TypeScript and Jest/Vitest module resolution automatically resolve file extensions,
|
||||
so adding them explicitly can cause "Cannot find module" errors when the LLM
|
||||
adds incorrect extensions (e.g., .js to a .ts file).
|
||||
|
||||
|
|
@ -154,6 +157,7 @@ def strip_js_extensions(source: str) -> str:
|
|||
- ES module imports: import { x } from '../path/file.js'
|
||||
- CommonJS requires: require('../path/file.js')
|
||||
- Jest mocks: jest.mock('../path/file.js')
|
||||
- Vitest mocks: vi.mock('../path/file.js')
|
||||
|
||||
Args:
|
||||
source: The test source code.
|
||||
|
|
@ -164,7 +168,8 @@ def strip_js_extensions(source: str) -> str:
|
|||
"""
|
||||
source = _JS_EXTENSION_PATTERN.sub(r"\1\2\4", source)
|
||||
source = _REQUIRE_EXTENSION_PATTERN.sub(r"\1\2\4", source)
|
||||
return _JEST_MOCK_EXTENSION_PATTERN.sub(r"\1\2\4", source)
|
||||
source = _JEST_MOCK_EXTENSION_PATTERN.sub(r"\1\2\4", source)
|
||||
return _VITEST_MOCK_EXTENSION_PATTERN.sub(r"\1\2\4", source)
|
||||
|
||||
|
||||
def _detect_export_style(source_code: str, identifier: str) -> str | None:
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import re
|
|||
|
||||
import pytest
|
||||
|
||||
from core.languages.js_ts.testgen import build_javascript_prompt, parse_and_validate_js_output
|
||||
from core.languages.js_ts.testgen import build_javascript_prompt, parse_and_validate_js_output, strip_js_extensions
|
||||
|
||||
|
||||
def _has_test_functions(code: str) -> bool:
|
||||
|
|
@ -343,3 +343,93 @@ class TestMochaPromptContent:
|
|||
assert isinstance(user_content, str)
|
||||
assert "vitest" in user_content.lower()
|
||||
assert "expect" in user_content.lower()
|
||||
|
||||
|
||||
class TestStripJsExtensions:
|
||||
"""Tests for stripping .js/.ts extensions from import paths."""
|
||||
|
||||
def test_strips_extensions_from_imports(self) -> None:
|
||||
"""Test that extensions are stripped from ES module imports."""
|
||||
source = "import { x } from '../path/file.js';"
|
||||
result = strip_js_extensions(source)
|
||||
assert result == "import { x } from '../path/file';"
|
||||
|
||||
def test_strips_extensions_from_require(self) -> None:
|
||||
"""Test that extensions are stripped from require() calls."""
|
||||
source = "const x = require('../path/file.js');"
|
||||
result = strip_js_extensions(source)
|
||||
assert result == "const x = require('../path/file');"
|
||||
|
||||
def test_strips_extensions_from_jest_mock(self) -> None:
|
||||
"""Test that extensions are stripped from jest.mock() calls."""
|
||||
source = "jest.mock('../path/file.js', () => {});"
|
||||
result = strip_js_extensions(source)
|
||||
assert result == "jest.mock('../path/file', () => {});"
|
||||
|
||||
def test_strips_extensions_from_vi_mock(self) -> None:
|
||||
"""Test that extensions are stripped from vi.mock() calls (Vitest).
|
||||
|
||||
This is a regression test for the bug where vi.mock() paths retained
|
||||
.js extensions while imports had them stripped, causing mock/import
|
||||
path mismatch in Vitest ESM mode.
|
||||
|
||||
Trace IDs affected:
|
||||
- 0fe99c9f-b348-4f0a-b051-0ea9455231ba
|
||||
- 127cdaec-a343-4918-a86a-b646dd4d79cf
|
||||
- 2b6c896e-20d7-4505-8bf4-e4a2f20b37fc
|
||||
"""
|
||||
source = "vi.mock('../config/paths.js', () => {});"
|
||||
result = strip_js_extensions(source)
|
||||
# This test will FAIL until the bug is fixed
|
||||
assert result == "vi.mock('../config/paths', () => {});"
|
||||
|
||||
def test_strips_extensions_from_complex_vi_mock(self) -> None:
|
||||
"""Test extension stripping for complex vi.mock() with multiline callback."""
|
||||
source = """vi.mock('../config/paths.js', () => {
|
||||
return {
|
||||
resolveCredentialsDir: vi.fn(() => '/mock/credentials'),
|
||||
};
|
||||
});"""
|
||||
result = strip_js_extensions(source)
|
||||
assert "vi.mock('../config/paths'" in result
|
||||
assert "vi.mock('../config/paths.js'" not in result
|
||||
|
||||
def test_strips_all_vi_mock_variants(self) -> None:
|
||||
"""Test that all vi.mock variants are handled."""
|
||||
source = """
|
||||
vi.mock('../a.js', () => {});
|
||||
vi.doMock('../b.js', () => {});
|
||||
vi.unmock('../c.js');
|
||||
"""
|
||||
result = strip_js_extensions(source)
|
||||
assert "../a'" in result
|
||||
assert "../b'" in result
|
||||
assert "../c'" in result
|
||||
assert ".js" not in result
|
||||
|
||||
def test_preserves_node_modules_paths(self) -> None:
|
||||
"""Test that node_modules paths (without ./) are not modified."""
|
||||
source = "import { x } from 'some-package';"
|
||||
result = strip_js_extensions(source)
|
||||
assert result == source
|
||||
|
||||
def test_handles_mixed_mocks_and_imports(self) -> None:
|
||||
"""Test realistic scenario with both vi.mock() and imports."""
|
||||
source = """vi.mock('../config/paths.js', () => {
|
||||
return {
|
||||
resolveCredentialsDir: vi.fn(() => '/mock/credentials'),
|
||||
};
|
||||
});
|
||||
|
||||
import { resolveChannelAllowFromPath } from './pairing/pairing-store.js';
|
||||
import { resolveCredentialsDir } from '../config/paths.js';"""
|
||||
|
||||
result = strip_js_extensions(source)
|
||||
|
||||
# All .js extensions should be removed
|
||||
assert "vi.mock('../config/paths'" in result
|
||||
assert "from './pairing/pairing-store'" in result
|
||||
assert "from '../config/paths'" in result
|
||||
|
||||
# No .js should remain
|
||||
assert ".js" not in result
|
||||
|
|
|
|||
Loading…
Reference in a new issue