From 417eed410882b3ec73548a5e3edd55da6996e0e7 Mon Sep 17 00:00:00 2001 From: Sarthak Agarwal Date: Mon, 2 Feb 2026 14:15:00 +0530 Subject: [PATCH 1/2] Fix: Respect explicit setting in package.json codeflash config --- codeflash/code_utils/config_js.py | 15 +++- tests/code_utils/test_config_js.py | 140 +++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 4 deletions(-) diff --git a/codeflash/code_utils/config_js.py b/codeflash/code_utils/config_js.py index 39e8d50cb..112f993ac 100644 --- a/codeflash/code_utils/config_js.py +++ b/codeflash/code_utils/config_js.py @@ -212,15 +212,18 @@ def parse_package_json_config(package_json_path: Path) -> tuple[dict[str, Any], Most configuration is auto-detected from package.json and project structure. Only minimal config is stored in the "codeflash" key: + - moduleRoot: Override auto-detected module root (optional) + - testsRoot: Override auto-detected tests root (optional) + - test-framework: Override auto-detected test framework - "jest", "vitest", or "mocha" (optional) - benchmarksRoot: Where to store benchmark files (optional, defaults to __benchmarks__) - ignorePaths: Paths to exclude from optimization (optional) - disableTelemetry: Privacy preference (optional, defaults to false) - formatterCmds: Override auto-detected formatter (optional) - Auto-detected values (not stored in config): + Auto-detected values (used when not explicitly configured): - language: Detected from tsconfig.json presence - moduleRoot: Detected from package.json exports/module/main or src/ convention - - testRunner: Detected from devDependencies (vitest/jest/mocha) + - test-framework: Detected from devDependencies (vitest/jest/mocha) - formatter: Detected from devDependencies (prettier/eslint) Args: @@ -254,8 +257,12 @@ def parse_package_json_config(package_json_path: Path) -> tuple[dict[str, Any], if codeflash_config.get("testsRoot"): config["tests_root"] = str(project_root / Path(codeflash_config["testsRoot"]).resolve()) - # Auto-detect test runner - config["test_runner"] = detect_test_runner(project_root, package_data) + # Check for explicit test framework override, otherwise auto-detect + # Uses "test-framework" to match Python's pyproject.toml convention + if codeflash_config.get("test-framework"): + config["test_runner"] = codeflash_config["test-framework"] + else: + config["test_runner"] = detect_test_runner(project_root, package_data) # Keep pytest_cmd for backwards compatibility with existing code config["pytest_cmd"] = config["test_runner"] diff --git a/tests/code_utils/test_config_js.py b/tests/code_utils/test_config_js.py index f65e4e4e2..27b562c8d 100644 --- a/tests/code_utils/test_config_js.py +++ b/tests/code_utils/test_config_js.py @@ -855,3 +855,143 @@ def test_existing_codeflash_config_with_overrides(self, tmp_path: Path) -> None: assert config["formatter_cmds"] == ["npx prettier --write --single-quote $file"] assert len(config["ignore_paths"]) == 2 assert config["disable_telemetry"] is True + + +class TestTestFrameworkConfigOverride: + """Tests for explicit test-framework config override (matches Python's pyproject.toml).""" + + def test_test_framework_overrides_auto_detection(self, tmp_path: Path) -> None: + """Should use test-framework from codeflash config instead of auto-detecting from devDependencies.""" + package_json = tmp_path / "package.json" + package_json.write_text( + json.dumps( + { + "name": "test-project", + "devDependencies": {"vitest": "^1.0.0"}, + "codeflash": {"test-framework": "jest"}, + } + ) + ) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["test_runner"] == "jest" + assert config["pytest_cmd"] == "jest" + + def test_explicit_vitest_config_with_jest_in_deps(self, tmp_path: Path) -> None: + """Should use explicit vitest config even when jest is in devDependencies.""" + package_json = tmp_path / "package.json" + package_json.write_text( + json.dumps( + { + "name": "test-project", + "devDependencies": {"jest": "^29.0.0", "vitest": "^1.0.0"}, + "codeflash": {"test-framework": "vitest"}, + } + ) + ) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["test_runner"] == "vitest" + + def test_explicit_mocha_overrides_vitest_and_jest(self, tmp_path: Path) -> None: + """Should use explicit mocha config even when vitest and jest are in devDependencies.""" + package_json = tmp_path / "package.json" + package_json.write_text( + json.dumps( + { + "name": "test-project", + "devDependencies": {"vitest": "^1.0.0", "jest": "^29.0.0"}, + "codeflash": {"test-framework": "mocha"}, + } + ) + ) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["test_runner"] == "mocha" + + def test_auto_detection_when_no_explicit_config(self, tmp_path: Path) -> None: + """Should auto-detect test framework when no explicit config is provided.""" + package_json = tmp_path / "package.json" + package_json.write_text( + json.dumps( + { + "name": "test-project", + "devDependencies": {"vitest": "^1.0.0"}, + "codeflash": {"moduleRoot": "src"}, + } + ) + ) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["test_runner"] == "vitest" + + def test_empty_test_framework_falls_back_to_auto_detection(self, tmp_path: Path) -> None: + """Should auto-detect when test-framework is empty string.""" + package_json = tmp_path / "package.json" + package_json.write_text( + json.dumps( + { + "name": "test-project", + "devDependencies": {"jest": "^29.0.0"}, + "codeflash": {"test-framework": ""}, + } + ) + ) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["test_runner"] == "jest" + + def test_custom_test_framework_value(self, tmp_path: Path) -> None: + """Should accept custom test framework values not in the standard list.""" + package_json = tmp_path / "package.json" + package_json.write_text( + json.dumps( + { + "name": "test-project", + "devDependencies": {"vitest": "^1.0.0"}, + "codeflash": {"test-framework": "ava"}, + } + ) + ) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["test_runner"] == "ava" + + def test_pytest_cmd_matches_test_framework_with_override(self, tmp_path: Path) -> None: + """Should set pytest_cmd to match test_runner when using explicit config.""" + package_json = tmp_path / "package.json" + package_json.write_text( + json.dumps( + { + "name": "test-project", + "devDependencies": {"vitest": "^1.0.0"}, + "codeflash": {"test-framework": "jest"}, + } + ) + ) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["test_runner"] == "jest" + assert config["pytest_cmd"] == "jest" + assert config["test_runner"] == config["pytest_cmd"] From 92faba25460d8b39c54c9ee793591001d10bf4f7 Mon Sep 17 00:00:00 2001 From: Sarthak Agarwal Date: Mon, 2 Feb 2026 15:06:17 +0530 Subject: [PATCH 2/2] making name cosnistency in config for test-framework --- codeflash/cli_cmds/cli.py | 4 ++-- codeflash/code_utils/config_js.py | 6 +++--- codeflash/setup/config_schema.py | 4 ++-- tests/code_utils/test_config_js.py | 28 ++++++++++++++-------------- tests/test_setup/test_config.py | 6 +++--- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/codeflash/cli_cmds/cli.py b/codeflash/cli_cmds/cli.py index 95a18f534..11a9bf02e 100644 --- a/codeflash/cli_cmds/cli.py +++ b/codeflash/cli_cmds/cli.py @@ -232,8 +232,8 @@ def process_pyproject_config(args: Namespace) -> Namespace: is_js_ts_project = pyproject_config.get("language") in ("javascript", "typescript") # Set the test framework singleton for JS/TS projects - if is_js_ts_project and pyproject_config.get("test_runner"): - set_current_test_framework(pyproject_config["test_runner"]) + if is_js_ts_project and pyproject_config.get("test_framework"): + set_current_test_framework(pyproject_config["test_framework"]) if args.tests_root is None: if is_js_ts_project: diff --git a/codeflash/code_utils/config_js.py b/codeflash/code_utils/config_js.py index 112f993ac..2785b339c 100644 --- a/codeflash/code_utils/config_js.py +++ b/codeflash/code_utils/config_js.py @@ -260,11 +260,11 @@ def parse_package_json_config(package_json_path: Path) -> tuple[dict[str, Any], # Check for explicit test framework override, otherwise auto-detect # Uses "test-framework" to match Python's pyproject.toml convention if codeflash_config.get("test-framework"): - config["test_runner"] = codeflash_config["test-framework"] + config["test_framework"] = codeflash_config["test-framework"] else: - config["test_runner"] = detect_test_runner(project_root, package_data) + config["test_framework"] = detect_test_runner(project_root, package_data) # Keep pytest_cmd for backwards compatibility with existing code - config["pytest_cmd"] = config["test_runner"] + config["pytest_cmd"] = config["test_framework"] # Auto-detect formatter (with optional override from config) if "formatterCmds" in codeflash_config: diff --git a/codeflash/setup/config_schema.py b/codeflash/setup/config_schema.py index c7f5e93ee..562cf89df 100644 --- a/codeflash/setup/config_schema.py +++ b/codeflash/setup/config_schema.py @@ -29,7 +29,7 @@ class CodeflashConfig(BaseModel): tests_root: str | None = Field(default=None, description="Root directory containing tests") # Tooling settings (auto-detected, can be overridden) - test_runner: str | None = Field(default=None, description="Test runner: pytest, jest, vitest, mocha") + test_framework: str | None = Field(default=None, description="Test framework: pytest, jest, vitest, mocha") formatter_cmds: list[str] = Field(default_factory=list, description="Formatter commands") # Optional settings @@ -145,7 +145,7 @@ def from_detected_project(cls, detected: Any) -> CodeflashConfig: if detected.module_root != detected.project_root else ".", tests_root=str(detected.tests_root.relative_to(detected.project_root)) if detected.tests_root else None, - test_runner=detected.test_runner, + test_framework=detected.test_runner, formatter_cmds=detected.formatter_cmds, ignore_paths=[ str(p.relative_to(detected.project_root)) for p in detected.ignore_paths if p != detected.project_root diff --git a/tests/code_utils/test_config_js.py b/tests/code_utils/test_config_js.py index 27b562c8d..dfcd69f9e 100644 --- a/tests/code_utils/test_config_js.py +++ b/tests/code_utils/test_config_js.py @@ -488,7 +488,7 @@ def test_parses_minimal_package_json(self, tmp_path: Path) -> None: assert result is not None config, path = result assert config["language"] == "javascript" - assert config["test_runner"] == "jest" + assert config["test_framework"] == "jest" assert config["pytest_cmd"] == "jest" assert path == package_json @@ -728,7 +728,7 @@ def test_nextjs_project(self, tmp_path: Path) -> None: config, _ = result assert config["language"] == "typescript" assert config["module_root"] == str((tmp_path / "src").resolve()) - assert config["test_runner"] == "jest" + assert config["test_framework"] == "jest" assert config["formatter_cmds"] == ["npx prettier --write $file"] def test_vite_react_project(self, tmp_path: Path) -> None: @@ -752,7 +752,7 @@ def test_vite_react_project(self, tmp_path: Path) -> None: assert result is not None config, _ = result assert config["language"] == "typescript" - assert config["test_runner"] == "vitest" + assert config["test_framework"] == "vitest" assert config["formatter_cmds"] == ["npx eslint --fix $file"] def test_library_with_exports(self, tmp_path: Path) -> None: @@ -812,7 +812,7 @@ def test_node_cli_project(self, tmp_path: Path) -> None: assert result is not None config, _ = result assert config["module_root"] == str((tmp_path / "lib").resolve()) - assert config["test_runner"] == "mocha" + assert config["test_framework"] == "mocha" def test_minimal_project(self, tmp_path: Path) -> None: """Should handle minimal package.json.""" @@ -825,7 +825,7 @@ def test_minimal_project(self, tmp_path: Path) -> None: config, _ = result assert config["language"] == "javascript" assert config["module_root"] == str(tmp_path.resolve()) - assert config["test_runner"] == "jest" + assert config["test_framework"] == "jest" assert config["formatter_cmds"] == [] def test_existing_codeflash_config_with_overrides(self, tmp_path: Path) -> None: @@ -877,7 +877,7 @@ def test_test_framework_overrides_auto_detection(self, tmp_path: Path) -> None: assert result is not None config, _ = result - assert config["test_runner"] == "jest" + assert config["test_framework"] == "jest" assert config["pytest_cmd"] == "jest" def test_explicit_vitest_config_with_jest_in_deps(self, tmp_path: Path) -> None: @@ -897,7 +897,7 @@ def test_explicit_vitest_config_with_jest_in_deps(self, tmp_path: Path) -> None: assert result is not None config, _ = result - assert config["test_runner"] == "vitest" + assert config["test_framework"] == "vitest" def test_explicit_mocha_overrides_vitest_and_jest(self, tmp_path: Path) -> None: """Should use explicit mocha config even when vitest and jest are in devDependencies.""" @@ -916,7 +916,7 @@ def test_explicit_mocha_overrides_vitest_and_jest(self, tmp_path: Path) -> None: assert result is not None config, _ = result - assert config["test_runner"] == "mocha" + assert config["test_framework"] == "mocha" def test_auto_detection_when_no_explicit_config(self, tmp_path: Path) -> None: """Should auto-detect test framework when no explicit config is provided.""" @@ -935,7 +935,7 @@ def test_auto_detection_when_no_explicit_config(self, tmp_path: Path) -> None: assert result is not None config, _ = result - assert config["test_runner"] == "vitest" + assert config["test_framework"] == "vitest" def test_empty_test_framework_falls_back_to_auto_detection(self, tmp_path: Path) -> None: """Should auto-detect when test-framework is empty string.""" @@ -954,7 +954,7 @@ def test_empty_test_framework_falls_back_to_auto_detection(self, tmp_path: Path) assert result is not None config, _ = result - assert config["test_runner"] == "jest" + assert config["test_framework"] == "jest" def test_custom_test_framework_value(self, tmp_path: Path) -> None: """Should accept custom test framework values not in the standard list.""" @@ -973,10 +973,10 @@ def test_custom_test_framework_value(self, tmp_path: Path) -> None: assert result is not None config, _ = result - assert config["test_runner"] == "ava" + assert config["test_framework"] == "ava" def test_pytest_cmd_matches_test_framework_with_override(self, tmp_path: Path) -> None: - """Should set pytest_cmd to match test_runner when using explicit config.""" + """Should set pytest_cmd to match test_framework when using explicit config.""" package_json = tmp_path / "package.json" package_json.write_text( json.dumps( @@ -992,6 +992,6 @@ def test_pytest_cmd_matches_test_framework_with_override(self, tmp_path: Path) - assert result is not None config, _ = result - assert config["test_runner"] == "jest" + assert config["test_framework"] == "jest" assert config["pytest_cmd"] == "jest" - assert config["test_runner"] == config["pytest_cmd"] + assert config["test_framework"] == config["pytest_cmd"] diff --git a/tests/test_setup/test_config.py b/tests/test_setup/test_config.py index 5f10841fc..f4dfa1e57 100644 --- a/tests/test_setup/test_config.py +++ b/tests/test_setup/test_config.py @@ -35,7 +35,7 @@ def test_all_fields(self): language="javascript", module_root="src", tests_root="tests", - test_runner="jest", + test_framework="jest", formatter_cmds=["npx prettier --write $file"], ignore_paths=["dist", "node_modules"], benchmarks_root="benchmarks", @@ -46,7 +46,7 @@ def test_all_fields(self): assert config.language == "javascript" assert config.module_root == "src" assert config.tests_root == "tests" - assert config.test_runner == "jest" + assert config.test_framework == "jest" assert config.formatter_cmds == ["npx prettier --write $file"] assert config.ignore_paths == ["dist", "node_modules"] assert config.git_remote == "upstream" @@ -128,7 +128,7 @@ def test_from_detected_project(self, tmp_path): config = CodeflashConfig.from_detected_project(detected) assert config.language == detected.language - assert config.test_runner == detected.test_runner + assert config.test_framework == detected.test_runner def test_from_pyproject_dict(self): """Should create config from pyproject.toml dict."""