diff --git a/codeflash/languages/javascript/test_runner.py b/codeflash/languages/javascript/test_runner.py index a7ba0a974..beb2843fc 100644 --- a/codeflash/languages/javascript/test_runner.py +++ b/codeflash/languages/javascript/test_runner.py @@ -219,6 +219,55 @@ def _has_ts_jest_dependency(project_root: Path) -> bool: return False +def _detect_typescript_transformer(project_root: Path) -> tuple[str | None, str]: + package_json = project_root / "package.json" + if not package_json.exists(): + return (None, "") + + try: + content = json.loads(package_json.read_text(encoding="utf-8")) + deps = {**content.get("dependencies", {}), **content.get("devDependencies", {})} + + # Check for various TypeScript transformers in order of preference + if "ts-jest" in deps: + config = """ + // Ensure TypeScript files are transformed using ts-jest + transform: { + '^.+\\\\.(ts|tsx)$': ['ts-jest', { isolatedModules: true }], + // Use ts-jest for JS files in ESM packages too + '^.+\\\\.js$': ['ts-jest', { isolatedModules: true }], + },""" + return ("ts-jest", config) + + if "@swc/jest" in deps: + config = """ + // Ensure TypeScript files are transformed using @swc/jest + transform: { + '^.+\\\\.(ts|tsx)$': '@swc/jest', + },""" + return ("@swc/jest", config) + + if "babel-jest" in deps and "@babel/preset-typescript" in deps: + config = """ + // Ensure TypeScript files are transformed using babel-jest + transform: { + '^.+\\\\.(ts|tsx)$': 'babel-jest', + },""" + return ("babel-jest", config) + + if "esbuild-jest" in deps: + config = """ + // Ensure TypeScript files are transformed using esbuild-jest + transform: { + '^.+\\\\.(ts|tsx)$': 'esbuild-jest', + },""" + return ("esbuild-jest", config) + + return (None, "") + except (json.JSONDecodeError, OSError): + return (None, "") + + def _create_codeflash_jest_config( project_root: Path, original_jest_config: Path | None, *, for_esm: bool = False ) -> Path | None: @@ -278,21 +327,13 @@ def _create_codeflash_jest_config( ] esm_pattern = "|".join(esm_packages) - # Check if ts-jest is available in the project - has_ts_jest = _has_ts_jest_dependency(project_root) + # Detect TypeScript transformer in the project + transformer_name, transform_config = _detect_typescript_transformer(project_root) - # Build transform config only if ts-jest is available - if has_ts_jest: - transform_config = """ - // Ensure TypeScript files are transformed using ts-jest - transform: { - '^.+\\\\.(ts|tsx)$': ['ts-jest', { isolatedModules: true }], - // Use ts-jest for JS files in ESM packages too - '^.+\\\\.js$': ['ts-jest', { isolatedModules: true }], - },""" + if transformer_name: + logger.debug(f"Detected TypeScript transformer: {transformer_name}") else: - transform_config = "" - logger.debug("ts-jest not found in project dependencies, skipping transform config") + logger.debug("No TypeScript transformer found in project dependencies") # Create a wrapper Jest config if original_jest_config: diff --git a/tests/test_languages/test_typescript_transform_extraction.py b/tests/test_languages/test_typescript_transform_extraction.py new file mode 100644 index 000000000..933e9596b --- /dev/null +++ b/tests/test_languages/test_typescript_transform_extraction.py @@ -0,0 +1,87 @@ +from pathlib import Path + + +class TestTypeScriptTransformExtraction: + def test_extracts_swc_transform_from_typescript_config(self, tmp_path: Path) -> None: + from codeflash.languages.javascript.test_runner import ( + _create_codeflash_jest_config, + clear_created_config_files, + ) + + tmpdir_path = tmp_path.resolve() + + # Create package.json without ts-jest (mimics budibase) + (tmpdir_path / "package.json").write_text( + '{"name": "test", "devDependencies": {"@swc/jest": "^0.2.0"}}', encoding="utf-8" + ) + + # Create jest.config.ts with @swc/jest transform (mimics budibase) + jest_config_content = """import { Config } from "jest" + +const config: Config = { + transform: { + "^.+\\\\.ts?$": "@swc/jest", + }, +} + +export default config +""" + jest_config_path = tmpdir_path / "jest.config.ts" + jest_config_path.write_text(jest_config_content, encoding="utf-8") + + clear_created_config_files() + + result = _create_codeflash_jest_config(tmpdir_path, jest_config_path) + + assert result is not None, "Should create a codeflash config" + + generated_config = result.read_text(encoding="utf-8") + + # The generated config MUST include a transform directive for TypeScript files + assert "transform" in generated_config, f"Generated config must include transform directive. Got:\n{generated_config}" + + # Verify it can actually transform TypeScript files (.ts extension) + assert ( + "@swc/jest" in generated_config or "ts-jest" in generated_config or '"^.+\\\\.ts' in generated_config + ), f"Generated config must include TypeScript file transformation. Got:\n{generated_config}" + + clear_created_config_files() + + def test_extracts_ts_jest_transform_from_typescript_config(self, tmp_path: Path) -> None: + from codeflash.languages.javascript.test_runner import ( + _create_codeflash_jest_config, + clear_created_config_files, + ) + + tmpdir_path = tmp_path.resolve() + + # Create package.json with ts-jest + (tmpdir_path / "package.json").write_text( + '{"name": "test", "devDependencies": {"ts-jest": "^29.0.0"}}', encoding="utf-8" + ) + + # Create jest.config.ts with ts-jest transform + jest_config_content = """import { Config } from "jest" + +const config: Config = { + transform: { + "^.+\\\\.(ts|tsx)$": "ts-jest", + }, +} + +export default config +""" + jest_config_path = tmpdir_path / "jest.config.ts" + jest_config_path.write_text(jest_config_content, encoding="utf-8") + + clear_created_config_files() + + result = _create_codeflash_jest_config(tmpdir_path, jest_config_path) + + assert result is not None, "Should create a codeflash config" + + generated_config = result.read_text(encoding="utf-8") + + assert "ts-jest" in generated_config, f"Generated config should include ts-jest transform. Got:\n{generated_config}" + + clear_created_config_files()