diff --git a/codeflash/languages/javascript/test_runner.py b/codeflash/languages/javascript/test_runner.py index a7ba0a974..3ee81f535 100644 --- a/codeflash/languages/javascript/test_runner.py +++ b/codeflash/languages/javascript/test_runner.py @@ -382,7 +382,11 @@ def _create_runtime_jest_config(base_config_path: Path | None, project_root: Pat else: module_dirs_line_no_base = "" - if base_config_path: + # TypeScript configs (.ts) cannot be required from CommonJS modules + # because Node.js cannot parse TypeScript syntax in require(). + # When the base config is TypeScript, we create a standalone config + # instead of trying to extend it via require(). + if base_config_path and base_config_path.suffix != ".ts": require_path = f"./{base_config_path.name}" config_content = f"""// Auto-generated by codeflash - runtime config with test roots const baseConfig = require('{require_path}'); @@ -769,8 +773,24 @@ def run_jest_behavioral_tests( # Get test files to run test_files = [str(file.instrumented_behavior_file_path) for file in test_paths.test_files] - # Use provided project_root, or detect it as fallback - if project_root is None and test_files: + # In monorepos with --all mode, test_cfg.js_project_root may point to the wrong package + # (e.g., optimizing worker functions but project_root is set to server package). + # Detect the correct package from test file location to ensure Jest uses the right config. + if test_files and project_root: + first_test_file = Path(test_files[0]) + detected_root = find_node_project_root(first_test_file) + # Only override if: (1) detected a different package root, (2) it has package.json, + # (3) both are peer packages (same parent directory) + if ( + detected_root + and detected_root != project_root + and (detected_root / "package.json").exists() + and detected_root.parent == project_root.parent + ): + logger.debug(f"Monorepo: overriding project_root {project_root} with detected {detected_root}") + project_root = detected_root + elif project_root is None and test_files: + # Fallback: if no project_root provided, detect from test file first_test_file = Path(test_files[0]) project_root = find_node_project_root(first_test_file) @@ -1024,8 +1044,18 @@ def run_jest_benchmarking_tests( # Get performance test files test_files = [str(file.benchmarking_file_path) for file in test_paths.test_files if file.benchmarking_file_path] - # Use provided project_root, or detect it as fallback - if project_root is None and test_files: + # In monorepos, detect correct package from test file location + if test_files and project_root: + first_test_file = Path(test_files[0]) + detected_root = find_node_project_root(first_test_file) + if ( + detected_root + and detected_root != project_root + and (detected_root / "package.json").exists() + and detected_root.parent == project_root.parent + ): + project_root = detected_root + elif project_root is None and test_files: first_test_file = Path(test_files[0]) project_root = find_node_project_root(first_test_file) @@ -1198,8 +1228,18 @@ def run_jest_line_profile_tests( elif file.benchmarking_file_path: test_files.append(str(file.benchmarking_file_path)) - # Use provided project_root, or detect it as fallback - if project_root is None and test_files: + # In monorepos, detect correct package from test file location + if test_files and project_root: + first_test_file = Path(test_files[0]) + detected_root = find_node_project_root(first_test_file) + if ( + detected_root + and detected_root != project_root + and (detected_root / "package.json").exists() + and detected_root.parent == project_root.parent + ): + project_root = detected_root + elif project_root is None and test_files: first_test_file = Path(test_files[0]) project_root = find_node_project_root(first_test_file) diff --git a/tests/test_languages/test_monorepo_project_root_bug.py b/tests/test_languages/test_monorepo_project_root_bug.py new file mode 100644 index 000000000..ecb2f22f0 --- /dev/null +++ b/tests/test_languages/test_monorepo_project_root_bug.py @@ -0,0 +1,103 @@ +"""Regression test for monorepo Jest config bug. + +Bug: In --all mode with monorepo, test_cfg.js_project_root is set once based on the +first file and reused for all functions. When optimizing functions from different +packages, Jest runs with the wrong package's config, causing module resolution failures. + +Example: Optimizing worker/src/tenants.ts uses server's Jest config, breaking imports. + +Trace ID: 02f0351a-db89-4ebc-a2e6-c45b19061152 +""" +from pathlib import Path +from unittest.mock import Mock, patch +import pytest + +from codeflash.languages.javascript.test_runner import find_node_project_root, run_jest_behavioral_tests + + +@pytest.fixture +def monorepo_structure(tmp_path): + """Create minimal monorepo like budibase with server and worker packages.""" + root = tmp_path / "repo" + root.mkdir() + + # Root with yarn workspaces + (root / "package.json").write_text('{"workspaces": {"packages": ["packages/*"]}}') + (root / "yarn.lock").touch() + (root / "node_modules").mkdir() + + # Server package + server = root / "packages/server" + server.mkdir(parents=True) + (server / "package.json").write_text('{"name": "@test/server"}') + (server / "jest.config.js").write_text('module.exports = {testEnvironment: "node"};') + + # Worker package + worker = root / "packages/worker" + worker.mkdir(parents=True) + (worker / "package.json").write_text('{"name": "@test/worker"}') + (worker / "jest.config.js").write_text('module.exports = {testEnvironment: "node"};') + + return root + + +def test_find_node_project_root_detects_correct_package(monorepo_structure): + """Verify find_node_project_root returns the correct package, not monorepo root.""" + server_file = monorepo_structure / "packages/server/src/api.ts" + server_file.parent.mkdir(parents=True) + server_file.touch() + + worker_file = monorepo_structure / "packages/worker/src/tenant.ts" + worker_file.parent.mkdir(parents=True) + worker_file.touch() + + # Each file should resolve to its own package, not the monorepo root + server_root = find_node_project_root(server_file) + worker_root = find_node_project_root(worker_file) + + assert server_root == monorepo_structure / "packages/server" + assert worker_root == monorepo_structure / "packages/worker" + assert server_root != worker_root, "Different packages must have different roots" + + +def test_run_jest_uses_correct_cwd_when_project_root_is_wrong(monorepo_structure): + """ + REGRESSION TEST for bug where wrong project_root causes Jest module resolution failures. + + Scenario: test_cfg.js_project_root points to server, but we're testing worker files. + Expected: run_jest_behavioral_tests should detect worker package from test file path. + Actual (before fix): Uses wrong project_root, causing "Cannot find module" errors. + + This test documents current behavior (uses wrong cwd) and will pass after fix. + """ + # Create test file in worker package + worker_test = monorepo_structure / "packages/worker/src/tests/test_tenant.test.ts" + worker_test.parent.mkdir(parents=True) + worker_test.write_text('test("dummy", () => {});') + + # Simulate bug: project_root wrongly points to server package + wrong_project_root = monorepo_structure / "packages/server" + correct_project_root = monorepo_structure / "packages/worker" + + # What happens now (before fix): + # Line 782: effective_cwd = project_root if project_root else cwd + # Since project_root = wrong_project_root (server), effective_cwd is wrong + + # Current behavior: find_node_project_root is ONLY called if project_root is None (line 779) + # This is the bug - we should call it even when project_root is provided + + # After fix: run_jest_behavioral_tests should always verify project_root + # by calling find_node_project_root with the test file path + + # For now, just test that find_node_project_root would return the right answer + detected_root = find_node_project_root(worker_test) + assert detected_root == correct_project_root, \ + "find_node_project_root should detect worker package from test path" + + # The actual fix will be: instead of using project_root directly as effective_cwd, + # run_jest_behavioral_tests should call find_node_project_root(test_files[0]) + # to determine the correct package for Jest execution + + +if __name__ == "__main__": + pytest.main([__file__, "-xvs"])