From e8a4f96c0b363552359e6d0bd56a7093b67ad19b Mon Sep 17 00:00:00 2001 From: Sarthak Agarwal Date: Tue, 3 Mar 2026 16:01:55 +0530 Subject: [PATCH 1/3] fix: strip CJS require('vitest') and require('@jest/globals') in Mocha tests The AI backend generates vitest/jest-style imports for Mocha projects. Our sanitize_mocha_imports() stripped ESM `import { ... } from 'vitest'`, but process_generated_test_strings() runs BEFORE postprocessing and calls ensure_module_system_compatibility() which converts these to CJS requires. Result: `const { ... } = require('vitest')` survived sanitization. Added regexes for the CJS variants of vitest and @jest/globals requires. Co-Authored-By: Claude Opus 4.6 --- codeflash/languages/javascript/edit_tests.py | 8 +++++++ tests/test_languages/test_mocha_runner.py | 24 ++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/codeflash/languages/javascript/edit_tests.py b/codeflash/languages/javascript/edit_tests.py index 4a8965c2c..993be2b8b 100644 --- a/codeflash/languages/javascript/edit_tests.py +++ b/codeflash/languages/javascript/edit_tests.py @@ -233,7 +233,13 @@ def inject_test_globals( _VITEST_IMPORT_RE = re.compile(r"^.*import\s+\{[^}]*\}\s+from\s+['\"]vitest['\"].*\n?", re.MULTILINE) +_VITEST_REQUIRE_RE = re.compile( + r"^.*(?:const|let|var)\s+\{[^}]*\}\s*=\s*require\s*\(\s*['\"]vitest['\"]\s*\).*\n?", re.MULTILINE +) _JEST_GLOBALS_IMPORT_RE = re.compile(r"^.*import\s+\{[^}]*\}\s+from\s+['\"]@jest/globals['\"].*\n?", re.MULTILINE) +_JEST_GLOBALS_REQUIRE_RE = re.compile( + r"^.*(?:const|let|var)\s+\{[^}]*\}\s*=\s*require\s*\(\s*['\"]@jest/globals['\"]\s*\).*\n?", re.MULTILINE +) _MOCHA_REQUIRE_RE = re.compile( r"^.*(?:const|let|var)\s+\{[^}]*\}\s*=\s*require\s*\(\s*['\"]mocha['\"]\s*\).*\n?", re.MULTILINE ) @@ -256,7 +262,9 @@ def sanitize_mocha_imports(source: str) -> str: """ source = _VITEST_IMPORT_RE.sub("", source) + source = _VITEST_REQUIRE_RE.sub("", source) source = _JEST_GLOBALS_IMPORT_RE.sub("", source) + source = _JEST_GLOBALS_REQUIRE_RE.sub("", source) source = _MOCHA_REQUIRE_RE.sub("", source) return _VITEST_COMMENT_RE.sub("", source) diff --git a/tests/test_languages/test_mocha_runner.py b/tests/test_languages/test_mocha_runner.py index 163b354d4..7f8263e62 100644 --- a/tests/test_languages/test_mocha_runner.py +++ b/tests/test_languages/test_mocha_runner.py @@ -491,6 +491,30 @@ def test_strips_vitest_comment(self): assert "vitest" not in result assert "const x = 1;" in result + def test_strips_vitest_require_cjs(self): + from codeflash.languages.javascript.edit_tests import sanitize_mocha_imports + + source = "const { describe, test, expect, vi, beforeEach, afterEach } = require('vitest');\nconst x = 1;\n" + result = sanitize_mocha_imports(source) + assert "vitest" not in result + assert "const x = 1;" in result + + def test_strips_jest_globals_require_cjs(self): + from codeflash.languages.javascript.edit_tests import sanitize_mocha_imports + + source = "const { jest, describe, it } = require('@jest/globals');\nconst x = 1;\n" + result = sanitize_mocha_imports(source) + assert "@jest/globals" not in result + assert "const x = 1;" in result + + def test_strips_vitest_comment_and_cjs_require(self): + from codeflash.languages.javascript.edit_tests import sanitize_mocha_imports + + source = "// vitest imports (REQUIRED for vitest - globals are NOT enabled by default)\nconst { describe, test, expect, vi, beforeEach, afterEach } = require('vitest');\nconst { setCharset } = require('../lib/utils');\n" + result = sanitize_mocha_imports(source) + assert "vitest" not in result + assert "require('../lib/utils')" in result + def test_preserves_unrelated_imports(self): from codeflash.languages.javascript.edit_tests import sanitize_mocha_imports From 4766daac7e30af8f01b9e5dde6126aab3edb4fbb Mon Sep 17 00:00:00 2001 From: Sarthak Agarwal Date: Tue, 3 Mar 2026 16:57:34 +0530 Subject: [PATCH 2/3] fix: regenerate stale vitest config and add .js extension for ESM imports Two fixes discovered during end-to-end testing: 1. _ensure_codeflash_vitest_config() reused existing configs that lacked pool: 'forks', causing benchmarking to fall back to wall-clock timing on re-runs. Now checks for the required setting and regenerates if missing. 2. ESM projects require explicit .js file extensions in import specifiers (e.g., `import foo from './lib/foo.js'`). The verifier stripped the extension but never added .js back for ESM, causing ERR_MODULE_NOT_FOUND in Mocha ESM projects like axios. Co-Authored-By: Claude Opus 4.6 --- codeflash/languages/javascript/vitest_runner.py | 9 ++++++--- codeflash/verification/verifier.py | 4 ++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/codeflash/languages/javascript/vitest_runner.py b/codeflash/languages/javascript/vitest_runner.py index 56c9d8da9..67b307ba4 100644 --- a/codeflash/languages/javascript/vitest_runner.py +++ b/codeflash/languages/javascript/vitest_runner.py @@ -194,10 +194,13 @@ def _ensure_codeflash_vitest_config(project_root: Path) -> Path | None: codeflash_config_path = project_root / "codeflash.vitest.config.mjs" - # If already exists, use it + # Regenerate if existing config is missing required settings (e.g., pool: 'forks') if codeflash_config_path.exists(): - logger.debug(f"Using existing Codeflash Vitest config: {codeflash_config_path}") - return codeflash_config_path + existing_content = codeflash_config_path.read_text(encoding="utf-8") + if "pool: 'forks'" in existing_content: + logger.debug(f"Using existing Codeflash Vitest config: {codeflash_config_path}") + return codeflash_config_path + logger.debug("Regenerating Codeflash Vitest config (missing pool: 'forks')") # Find the original vitest config to extend original_config = None diff --git a/codeflash/verification/verifier.py b/codeflash/verification/verifier.py index 6fc16847e..903e3aa7d 100644 --- a/codeflash/verification/verifier.py +++ b/codeflash/verification/verifier.py @@ -52,6 +52,10 @@ def generate_tests( # Ensure path starts with ./ or ../ for JavaScript/TypeScript imports if not rel_import_path.startswith("../"): rel_import_path = f"./{rel_import_path}" + # ESM requires explicit file extensions in import specifiers. + # TypeScript ESM also uses .js extensions (TS resolves .js → .ts). + if project_module_system == "esm": + rel_import_path += ".js" # Keep as string since Path() normalizes away the ./ prefix module_path = rel_import_path From bc5e3e878a603de67003aa0a7495a39d7f458ae7 Mon Sep 17 00:00:00 2001 From: Sarthak Agarwal Date: Wed, 4 Mar 2026 03:42:10 +0530 Subject: [PATCH 3/3] fix mocha test runner --- .../js/code_to_optimize_mocha/codeflash.yaml | 5 + .../js/code_to_optimize_mocha/fibonacci.js | 60 ++ .../code_to_optimize_mocha/package-lock.json | 966 ++++++++++++++++++ .../js/code_to_optimize_mocha/package.json | 13 + .../tests/fibonacci.test.js | 77 ++ codeflash/languages/javascript/edit_tests.py | 246 ++++- .../languages/javascript/mocha_runner.py | 44 +- codeflash/languages/javascript/support.py | 7 + packages/codeflash/package-lock.json | 4 +- tests/test_languages/test_mocha_runner.py | 102 ++ 10 files changed, 1514 insertions(+), 10 deletions(-) create mode 100644 code_to_optimize/js/code_to_optimize_mocha/codeflash.yaml create mode 100644 code_to_optimize/js/code_to_optimize_mocha/fibonacci.js create mode 100644 code_to_optimize/js/code_to_optimize_mocha/package-lock.json create mode 100644 code_to_optimize/js/code_to_optimize_mocha/package.json create mode 100644 code_to_optimize/js/code_to_optimize_mocha/tests/fibonacci.test.js diff --git a/code_to_optimize/js/code_to_optimize_mocha/codeflash.yaml b/code_to_optimize/js/code_to_optimize_mocha/codeflash.yaml new file mode 100644 index 000000000..698c6dee8 --- /dev/null +++ b/code_to_optimize/js/code_to_optimize_mocha/codeflash.yaml @@ -0,0 +1,5 @@ +# Codeflash Configuration for Mocha CJS JavaScript Project +module_root: "." +tests_root: "tests" +test_framework: "mocha" +formatter_cmds: [] diff --git a/code_to_optimize/js/code_to_optimize_mocha/fibonacci.js b/code_to_optimize/js/code_to_optimize_mocha/fibonacci.js new file mode 100644 index 000000000..17de243bc --- /dev/null +++ b/code_to_optimize/js/code_to_optimize_mocha/fibonacci.js @@ -0,0 +1,60 @@ +/** + * Fibonacci implementations - CommonJS module + * Intentionally inefficient for optimization testing. + */ + +/** + * Calculate the nth Fibonacci number using naive recursion. + * This is intentionally slow to demonstrate optimization potential. + * @param {number} n - The index of the Fibonacci number to calculate + * @returns {number} The nth Fibonacci number + */ +function fibonacci(n) { + if (n <= 1) { + return n; + } + return fibonacci(n - 1) + fibonacci(n - 2); +} + +/** + * Check if a number is a Fibonacci number. + * @param {number} num - The number to check + * @returns {boolean} True if num is a Fibonacci number + */ +function isFibonacci(num) { + // A number is Fibonacci if one of (5*n*n + 4) or (5*n*n - 4) is a perfect square + const check1 = 5 * num * num + 4; + const check2 = 5 * num * num - 4; + return isPerfectSquare(check1) || isPerfectSquare(check2); +} + +/** + * Check if a number is a perfect square. + * @param {number} n - The number to check + * @returns {boolean} True if n is a perfect square + */ +function isPerfectSquare(n) { + const sqrt = Math.sqrt(n); + return sqrt === Math.floor(sqrt); +} + +/** + * Generate an array of Fibonacci numbers up to n. + * @param {number} n - The number of Fibonacci numbers to generate + * @returns {number[]} Array of Fibonacci numbers + */ +function fibonacciSequence(n) { + const result = []; + for (let i = 0; i < n; i++) { + result.push(fibonacci(i)); + } + return result; +} + +// CommonJS exports +module.exports = { + fibonacci, + isFibonacci, + isPerfectSquare, + fibonacciSequence, +}; diff --git a/code_to_optimize/js/code_to_optimize_mocha/package-lock.json b/code_to_optimize/js/code_to_optimize_mocha/package-lock.json new file mode 100644 index 000000000..4a9ebdbe8 --- /dev/null +++ b/code_to_optimize/js/code_to_optimize_mocha/package-lock.json @@ -0,0 +1,966 @@ +{ + "name": "code-to-optimize-mocha", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "code-to-optimize-mocha", + "version": "1.0.0", + "devDependencies": { + "codeflash": "file:../../../packages/codeflash", + "mocha": "^10.7.0" + } + }, + "../../../packages/codeflash": { + "version": "0.10.1", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@msgpack/msgpack": "^3.0.0", + "better-sqlite3": "^12.0.0" + }, + "bin": { + "codeflash": "bin/codeflash.js", + "codeflash-setup": "bin/codeflash-setup.js" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "jest": ">=27.0.0", + "jest-runner": ">=27.0.0", + "vitest": ">=1.0.0" + }, + "peerDependenciesMeta": { + "jest": { + "optional": true + }, + "jest-runner": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/codeflash": { + "resolved": "../../../packages/codeflash", + "link": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/diff": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/code_to_optimize/js/code_to_optimize_mocha/package.json b/code_to_optimize/js/code_to_optimize_mocha/package.json new file mode 100644 index 000000000..768f1f46c --- /dev/null +++ b/code_to_optimize/js/code_to_optimize_mocha/package.json @@ -0,0 +1,13 @@ +{ + "name": "code-to-optimize-mocha", + "version": "1.0.0", + "description": "Mocha CJS JavaScript test project for Codeflash E2E testing", + "main": "index.js", + "scripts": { + "test": "mocha" + }, + "devDependencies": { + "codeflash": "file:../../../packages/codeflash", + "mocha": "^10.7.0" + } +} diff --git a/code_to_optimize/js/code_to_optimize_mocha/tests/fibonacci.test.js b/code_to_optimize/js/code_to_optimize_mocha/tests/fibonacci.test.js new file mode 100644 index 000000000..72ae2dc8f --- /dev/null +++ b/code_to_optimize/js/code_to_optimize_mocha/tests/fibonacci.test.js @@ -0,0 +1,77 @@ +/** + * Tests for Fibonacci functions - Mocha + node:assert/strict + */ +const assert = require('node:assert/strict'); +const { fibonacci, isFibonacci, isPerfectSquare, fibonacciSequence } = require('../fibonacci'); + +describe('fibonacci', () => { + it('returns 0 for n=0', () => { + assert.strictEqual(fibonacci(0), 0); + }); + + it('returns 1 for n=1', () => { + assert.strictEqual(fibonacci(1), 1); + }); + + it('returns 1 for n=2', () => { + assert.strictEqual(fibonacci(2), 1); + }); + + it('returns 5 for n=5', () => { + assert.strictEqual(fibonacci(5), 5); + }); + + it('returns 55 for n=10', () => { + assert.strictEqual(fibonacci(10), 55); + }); + + it('returns 233 for n=13', () => { + assert.strictEqual(fibonacci(13), 233); + }); +}); + +describe('isFibonacci', () => { + it('returns true for Fibonacci numbers', () => { + assert.strictEqual(isFibonacci(0), true); + assert.strictEqual(isFibonacci(1), true); + assert.strictEqual(isFibonacci(5), true); + assert.strictEqual(isFibonacci(8), true); + assert.strictEqual(isFibonacci(13), true); + }); + + it('returns false for non-Fibonacci numbers', () => { + assert.strictEqual(isFibonacci(4), false); + assert.strictEqual(isFibonacci(6), false); + assert.strictEqual(isFibonacci(7), false); + }); +}); + +describe('isPerfectSquare', () => { + it('returns true for perfect squares', () => { + assert.strictEqual(isPerfectSquare(0), true); + assert.strictEqual(isPerfectSquare(1), true); + assert.strictEqual(isPerfectSquare(4), true); + assert.strictEqual(isPerfectSquare(9), true); + assert.strictEqual(isPerfectSquare(16), true); + }); + + it('returns false for non-perfect squares', () => { + assert.strictEqual(isPerfectSquare(2), false); + assert.strictEqual(isPerfectSquare(3), false); + assert.strictEqual(isPerfectSquare(5), false); + }); +}); + +describe('fibonacciSequence', () => { + it('returns empty array for n=0', () => { + assert.deepStrictEqual(fibonacciSequence(0), []); + }); + + it('returns first 5 Fibonacci numbers', () => { + assert.deepStrictEqual(fibonacciSequence(5), [0, 1, 1, 2, 3]); + }); + + it('returns first 10 Fibonacci numbers', () => { + assert.deepStrictEqual(fibonacciSequence(10), [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]); + }); +}); diff --git a/codeflash/languages/javascript/edit_tests.py b/codeflash/languages/javascript/edit_tests.py index 993be2b8b..7f5281c02 100644 --- a/codeflash/languages/javascript/edit_tests.py +++ b/codeflash/languages/javascript/edit_tests.py @@ -226,9 +226,13 @@ def inject_test_globals( ) for test in generated_tests.generated_tests: - test.generated_original_test_source = global_import + test.generated_original_test_source - test.instrumented_behavior_test_source = global_import + test.instrumented_behavior_test_source - test.instrumented_perf_test_source = global_import + test.instrumented_perf_test_source + # Skip injection if the source already has the import (LLM may have included it) + if global_import.strip() not in test.generated_original_test_source: + test.generated_original_test_source = global_import + test.generated_original_test_source + if global_import.strip() not in test.instrumented_behavior_test_source: + test.instrumented_behavior_test_source = global_import + test.instrumented_behavior_test_source + if global_import.strip() not in test.instrumented_perf_test_source: + test.instrumented_perf_test_source = global_import + test.instrumented_perf_test_source return generated_tests @@ -245,20 +249,30 @@ def inject_test_globals( ) _VITEST_COMMENT_RE = re.compile(r"^.*//.*vitest imports.*\n?", re.MULTILINE | re.IGNORECASE) +# Chai import patterns — LLMs sometimes associate Mocha with Chai +_CHAI_IMPORT_RE = re.compile(r"^.*import\s+.*\s+from\s+['\"]chai['\"].*\n?", re.MULTILINE) +_CHAI_REQUIRE_RE = re.compile(r"^.*(?:const|let|var)\s+.*\s*=\s*require\s*\(\s*['\"]chai['\"]\s*\).*\n?", re.MULTILINE) + +# Pattern to convert test() → it() — Mocha uses it(), not test() +_TEST_CALL_RE = re.compile(r"(\s*)test\s*\(") + def sanitize_mocha_imports(source: str) -> str: - """Remove vitest/jest/mocha-require imports from Mocha test source. + """Remove vitest/jest/mocha-require/chai imports from Mocha test source. The AI service sometimes generates vitest or jest-style imports when the framework is mocha. Mocha provides describe/it/before*/after* as globals, so these imports must be removed. Also removes ``require('mocha')`` destructures since Mocha doesn't export those. + Additionally converts ``test()`` calls to ``it()`` since Mocha only + supports ``it()`` as its test function. + Args: source: Generated test source code. Returns: - Source with incorrect framework imports stripped. + Source with incorrect framework imports stripped and test() converted to it(). """ source = _VITEST_IMPORT_RE.sub("", source) @@ -266,7 +280,227 @@ def sanitize_mocha_imports(source: str) -> str: source = _JEST_GLOBALS_IMPORT_RE.sub("", source) source = _JEST_GLOBALS_REQUIRE_RE.sub("", source) source = _MOCHA_REQUIRE_RE.sub("", source) - return _VITEST_COMMENT_RE.sub("", source) + source = _VITEST_COMMENT_RE.sub("", source) + source = _CHAI_IMPORT_RE.sub("", source) + source = _CHAI_REQUIRE_RE.sub("", source) + source = _TEST_CALL_RE.sub(r"\1it(", source) + return convert_expect_to_assert(source) + + +def _find_matching_paren(source: str, open_pos: int) -> int: + """Find the position of the closing parenthesis matching the one at open_pos.""" + depth = 0 + in_string = False + string_char = None + i = open_pos + while i < len(source): + char = source[i] + if char in ('"', "'", "`") and (i == 0 or source[i - 1] != "\\"): + if not in_string: + in_string = True + string_char = char + elif char == string_char: + in_string = False + string_char = None + elif not in_string: + if char == "(": + depth += 1 + elif char == ")": + depth -= 1 + if depth == 0: + return i + i += 1 + return -1 + + +def convert_expect_to_assert(source: str) -> str: + """Convert expect()-style assertions to node:assert/strict equivalents. + + LLMs frequently generate Chai-style (``expect(x).to.equal(y)``) or + Jest-style (``expect(x).toBe(y)``) assertions for Mocha tests despite + being instructed to use ``assert``. This function converts the common + patterns to their ``node:assert/strict`` equivalents so that + instrumentation and Mocha execution work correctly. + + Any ``expect()`` calls that cannot be converted are commented out with + ``// SKIPPED`` to prevent ``ReferenceError: expect is not defined``. + + Args: + source: Test source code that may contain expect() calls. + + Returns: + Source with expect() calls converted to assert equivalents. + + """ + if "expect(" not in source: + return source + + lines = source.split("\n") + converted: list[str] = [] + + for line in lines: + converted_line = _convert_expect_line(line) + converted.append(converted_line) + + return "\n".join(converted) + + +# Patterns mapping (chain_suffix → conversion_type) +# "simple" = assert.func(actual, value), "ok_cmp" = assert.ok(actual OP value) +# "ok_method" = assert.ok(actual.method(value)), "type" = assert.ok(typeof actual === ...) +# "truthy" = assert.ok(actual) / assert.strictEqual(actual, bool) +# "throws" = assert.throws, "noop" = assert.ok(actual !== undefined) +_EXPECT_CHAIN_MAP: list[tuple[str, str, str | None]] = [ + # Jest patterns (most common) + (".toBe(", "simple_strictEqual", None), + (".toEqual(", "simple_deepStrictEqual", None), + (".toStrictEqual(", "simple_deepStrictEqual", None), + (".toBeGreaterThan(", "ok_gt", None), + (".toBeGreaterThanOrEqual(", "ok_gte", None), + (".toBeLessThan(", "ok_lt", None), + (".toBeLessThanOrEqual(", "ok_lte", None), + (".toContain(", "ok_includes", None), + (".toHaveLength(", "ok_length", None), + (".toBeNull(", "null_check", None), + (".toBeUndefined(", "undef_check", None), + (".toBeTruthy(", "truthy", None), + (".toBeFalsy(", "falsy", None), + (".toThrow(", "throws", None), + (".toMatch(", "ok_match", None), + # Chai .to. patterns + (".to.equal(", "simple_strictEqual", None), + (".to.eql(", "simple_deepStrictEqual", None), + (".to.deep.equal(", "simple_deepStrictEqual", None), + (".to.be.greaterThan(", "ok_gt", None), + (".to.be.lessThan(", "ok_lt", None), + (".to.be.above(", "ok_gt", None), + (".to.be.below(", "ok_lt", None), + (".to.be.at.least(", "ok_gte", None), + (".to.be.at.most(", "ok_lte", None), + (".to.include(", "ok_includes", None), + (".to.contain(", "ok_includes", None), + (".to.not.include(", "ok_not_includes", None), + (".to.not.contain(", "ok_not_includes", None), + (".to.have.length(", "ok_length", None), + (".to.have.lengthOf(", "ok_length", None), + (".to.throw(", "throws", None), + (".to.match(", "ok_match", None), + (".to.be.a(", "noop", None), + (".to.be.an(", "noop", None), + (".to.be.instanceOf(", "noop", None), + (".to.be.instanceof(", "noop", None), + (".to.exist", "truthy_no_arg", None), + (".to.not.exist", "falsy_no_arg", None), + (".to.be.true", "true_no_arg", None), + (".to.be.false", "false_no_arg", None), + (".to.be.null", "null_no_arg", None), + (".to.be.undefined", "undef_no_arg", None), + (".to.be.ok", "truthy_no_arg", None), + (".to.not.be.ok", "falsy_no_arg", None), +] + + +def _convert_expect_line(line: str) -> str: + """Convert a single line containing expect() to an assert equivalent.""" + stripped = line.lstrip() + if "expect(" not in stripped: + return line + + indent = line[: len(line) - len(stripped)] + + expect_idx = line.find("expect(") + if expect_idx == -1: + return line + + open_paren = expect_idx + len("expect") + close_paren = _find_matching_paren(line, open_paren) + if close_paren == -1: + # Multi-line expect or malformed — comment out to prevent ReferenceError + return f"{indent}// SKIPPED (unconvertible expect): {stripped}" + + actual_expr = line[open_paren + 1 : close_paren] + rest = line[close_paren + 1 :].strip() + trailing_semi = ";" if rest.endswith(";") else "" + + # Try each chain pattern + for chain_prefix, conversion_type, _ in _EXPECT_CHAIN_MAP: + if not rest.startswith(chain_prefix): + continue + + # No-argument chains (e.g. .to.be.true, .to.exist) + if conversion_type.endswith("_no_arg"): + return _convert_no_arg(indent, actual_expr, conversion_type, trailing_semi) + + # Extract the argument inside the chain's parentheses + chain_open = rest.find("(") + if chain_open == -1: + break + chain_close = _find_matching_paren(rest, chain_open) + if chain_close == -1: + break + value_expr = rest[chain_open + 1 : chain_close] + + return _convert_with_arg(indent, actual_expr, value_expr, conversion_type, trailing_semi) + + # Fallback: comment out unconvertible expect() to prevent ReferenceError + return f"{indent}// SKIPPED (unconvertible expect): {stripped}" + + +def _convert_no_arg(indent: str, actual: str, conversion_type: str, semi: str) -> str: + """Convert expect patterns that take no argument (e.g., .to.be.true).""" + if conversion_type == "true_no_arg": + return f"{indent}assert.strictEqual({actual}, true){semi}" + if conversion_type == "false_no_arg": + return f"{indent}assert.strictEqual({actual}, false){semi}" + if conversion_type == "null_no_arg": + return f"{indent}assert.strictEqual({actual}, null){semi}" + if conversion_type == "undef_no_arg": + return f"{indent}assert.strictEqual({actual}, undefined){semi}" + if conversion_type == "truthy_no_arg": + return f"{indent}assert.ok({actual}){semi}" + if conversion_type == "falsy_no_arg": + return f"{indent}assert.ok(!({actual})){semi}" + return f"{indent}assert.ok({actual} !== undefined){semi}" + + +def _convert_with_arg(indent: str, actual: str, value: str, conversion_type: str, semi: str) -> str: + """Convert expect patterns that take an argument.""" + if conversion_type == "simple_strictEqual": + return f"{indent}assert.strictEqual({actual}, {value}){semi}" + if conversion_type == "simple_deepStrictEqual": + return f"{indent}assert.deepStrictEqual({actual}, {value}){semi}" + if conversion_type == "ok_gt": + return f"{indent}assert.ok(({actual}) > ({value})){semi}" + if conversion_type == "ok_gte": + return f"{indent}assert.ok(({actual}) >= ({value})){semi}" + if conversion_type == "ok_lt": + return f"{indent}assert.ok(({actual}) < ({value})){semi}" + if conversion_type == "ok_lte": + return f"{indent}assert.ok(({actual}) <= ({value})){semi}" + if conversion_type == "ok_includes": + return f"{indent}assert.ok(String({actual}).includes({value})){semi}" + if conversion_type == "ok_not_includes": + return f"{indent}assert.ok(!String({actual}).includes({value})){semi}" + if conversion_type == "ok_length": + return f"{indent}assert.strictEqual(({actual}).length, {value}){semi}" + if conversion_type == "ok_match": + return f"{indent}assert.match(String({actual}), {value}){semi}" + if conversion_type == "null_check": + return f"{indent}assert.strictEqual({actual}, null){semi}" + if conversion_type == "undef_check": + return f"{indent}assert.strictEqual({actual}, undefined){semi}" + if conversion_type == "truthy": + return f"{indent}assert.ok({actual}){semi}" + if conversion_type == "falsy": + return f"{indent}assert.ok(!({actual})){semi}" + if conversion_type == "throws": + if value: + return f"{indent}assert.throws(() => {{ {actual}; }}, {value}){semi}" + return f"{indent}assert.throws(() => {{ {actual}; }}){semi}" + # noop: type checks like .to.be.a('string') — just verify defined + if conversion_type == "noop": + return f"{indent}assert.ok({actual} !== undefined){semi}" + return f"{indent}assert.ok({actual} !== undefined){semi}" # Author: ali diff --git a/codeflash/languages/javascript/mocha_runner.py b/codeflash/languages/javascript/mocha_runner.py index 5c288d67b..4e1644011 100644 --- a/codeflash/languages/javascript/mocha_runner.py +++ b/codeflash/languages/javascript/mocha_runner.py @@ -8,6 +8,7 @@ from __future__ import annotations import json +import re import subprocess import time from pathlib import Path @@ -86,7 +87,7 @@ def _ensure_runtime_files(project_root: Path) -> None: logger.error(f"Could not install codeflash. Please install it manually: {' '.join(install_cmd)}") -def mocha_json_to_junit_xml(json_str: str, output_file: Path) -> None: +def mocha_json_to_junit_xml(json_str: str, output_file: Path, test_files: list[Path] | None = None) -> None: """Convert Mocha's JSON reporter output to JUnit XML. Mocha JSON format: @@ -94,9 +95,16 @@ def mocha_json_to_junit_xml(json_str: str, output_file: Path) -> None: Each test object has: fullTitle, title, duration, err, ... + Mocha's JSON reporter does NOT include a ``file`` field on test objects, + so we accept the known ``test_files`` list from the caller and set the + ``file`` attribute on testcase/testsuite elements. This allows + ``parse_jest_test_xml()`` to resolve the test file via its ``file`` + attribute lookup path. + Args: json_str: JSON string from Mocha's --reporter json output. output_file: Path to write the JUnit XML file. + test_files: Optional list of test file paths that were passed to Mocha. """ try: @@ -125,11 +133,35 @@ def mocha_json_to_junit_xml(json_str: str, output_file: Path) -> None: suite_name = suite_name or "root" suites.setdefault(suite_name, []).append(test) + # Build a mapping from describe block names to file paths by reading test files. + # Each generated test file wraps tests in describe('functionName', ...) so we + # can map suite names back to their source file. + suite_to_file: dict[str, str] = {} + if test_files: + for tf in test_files: + suite_to_file[tf.name] = str(tf) + # Try to extract the top-level describe name from the file content + try: + content = tf.read_text(encoding="utf-8") + m = re.search(r"describe\s*\(\s*['\"]([^'\"]+)['\"]", content) + if m: + suite_to_file[m.group(1)] = str(tf) + except Exception: + pass + + # Fallback: if we have test files, use the first one as default for any unmatched suites + default_file = str(test_files[0]) if test_files else "" + for suite_name, suite_tests in suites.items(): testsuite = SubElement(testsuites, "testsuite") testsuite.set("name", suite_name) testsuite.set("tests", str(len(suite_tests))) + # Resolve file path: try suite name match, then use default + resolved_file = suite_to_file.get(suite_name, default_file) + if resolved_file: + testsuite.set("file", resolved_file) + suite_failures = 0 suite_time = 0.0 @@ -140,6 +172,9 @@ def mocha_json_to_junit_xml(json_str: str, output_file: Path) -> None: duration_ms = test.get("duration", 0) or 0 duration_s = duration_ms / 1000.0 testcase.set("time", str(duration_s)) + + if resolved_file: + testcase.set("file", resolved_file) suite_time += duration_s err = test.get("err", {}) @@ -292,6 +327,7 @@ def _run_mocha_and_convert( result_file_path: Path, subprocess_timeout: int, label: str, + test_files: list[Path] | None = None, ) -> subprocess.CompletedProcess: """Run Mocha subprocess, extract JSON output, and convert to JUnit XML. @@ -302,6 +338,7 @@ def _run_mocha_and_convert( result_file_path: Path to write JUnit XML. subprocess_timeout: Timeout in seconds. label: Label for log messages (e.g. "behavioral", "benchmarking"). + test_files: Test file paths passed to Mocha (for file attribute in XML). Returns: CompletedProcess with combined stdout/stderr. @@ -343,7 +380,7 @@ def _run_mocha_and_convert( if result.stdout: mocha_json = _extract_mocha_json(result.stdout) if mocha_json: - mocha_json_to_junit_xml(mocha_json, result_file_path) + mocha_json_to_junit_xml(mocha_json, result_file_path, test_files=test_files) logger.debug(f"Converted Mocha JSON to JUnit XML: {result_file_path}") else: logger.warning(f"Could not extract Mocha JSON from stdout (len={len(result.stdout)})") @@ -414,6 +451,7 @@ def run_mocha_behavioral_tests( result_file_path=result_file_path, subprocess_timeout=subprocess_timeout, label="behavioral", + test_files=test_files, ) finally: wall_clock_ns = time.perf_counter_ns() - start_time_ns @@ -515,6 +553,7 @@ def run_mocha_benchmarking_tests( result_file_path=result_file_path, subprocess_timeout=total_timeout, label="benchmarking", + test_files=test_files, ) finally: wall_clock_seconds = time.time() - total_start_time @@ -589,6 +628,7 @@ def run_mocha_line_profile_tests( result_file_path=result_file_path, subprocess_timeout=subprocess_timeout, label="line_profile", + test_files=test_files, ) finally: wall_clock_ns = time.perf_counter_ns() - start_time_ns diff --git a/codeflash/languages/javascript/support.py b/codeflash/languages/javascript/support.py index ab3ff0779..1d1387e16 100644 --- a/codeflash/languages/javascript/support.py +++ b/codeflash/languages/javascript/support.py @@ -1970,6 +1970,13 @@ def process_generated_test_strings( # Ensure vitest imports are present when using vitest framework generated_test_source = ensure_vitest_imports(generated_test_source, test_cfg.test_framework) + # For Mocha: convert expect()/test() to assert/it() BEFORE instrumentation + # to prevent instrumentation from breaking Chai-style assertion chains + if test_cfg.test_framework == "mocha": + from codeflash.languages.javascript.edit_tests import sanitize_mocha_imports + + generated_test_source = sanitize_mocha_imports(generated_test_source) + # Instrument for behavior verification (writes to SQLite) instrumented_behavior_test_source = instrument_generated_js_test( test_code=generated_test_source, function_to_optimize=function_to_optimize, mode=TestingMode.BEHAVIOR diff --git a/packages/codeflash/package-lock.json b/packages/codeflash/package-lock.json index ea4b361f6..95f655b3c 100644 --- a/packages/codeflash/package-lock.json +++ b/packages/codeflash/package-lock.json @@ -1,12 +1,12 @@ { "name": "codeflash", - "version": "0.9.0", + "version": "0.10.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codeflash", - "version": "0.9.0", + "version": "0.10.1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/tests/test_languages/test_mocha_runner.py b/tests/test_languages/test_mocha_runner.py index 7f8263e62..156c28ba2 100644 --- a/tests/test_languages/test_mocha_runner.py +++ b/tests/test_languages/test_mocha_runner.py @@ -147,6 +147,108 @@ def test_multiple_suites(self): assert "suite A" in suite_names assert "suite B" in suite_names + def test_file_attribute_set_from_test_files(self): + """When test_files are passed, the file attribute should be set on testcase elements.""" + from codeflash.languages.javascript.mocha_runner import mocha_json_to_junit_xml + + mocha_json = json.dumps( + { + "stats": {"tests": 2, "passes": 2, "failures": 0, "duration": 50}, + "tests": [ + {"title": "test1", "fullTitle": "escapeHtml test1", "duration": 10, "err": {}}, + {"title": "test2", "fullTitle": "escapeHtml test2", "duration": 20, "err": {}}, + ], + "passes": [], + "failures": [], + "pending": [], + } + ) + + with tempfile.TemporaryDirectory() as tmpdir: + tmpdir_path = Path(tmpdir) + # Create a test file whose describe block matches the suite name + test_file = tmpdir_path / "test_escapeHtml__unit_test_0.test.js" + test_file.write_text( + "const assert = require('node:assert/strict');\n" + "const escapeHtml = require('../index.js');\n" + "describe('escapeHtml', () => {\n" + " it('test1', () => { assert.ok(true); });\n" + " it('test2', () => { assert.ok(true); });\n" + "});\n", + encoding="utf-8", + ) + + output_file = tmpdir_path / "results.xml" + mocha_json_to_junit_xml(mocha_json, output_file, test_files=[test_file]) + + # Parse the XML and verify file attributes + import xml.etree.ElementTree as ET + + tree = ET.parse(output_file) + root = tree.getroot() + testcases = root.findall(".//testcase") + assert len(testcases) == 2 + for tc in testcases: + assert tc.get("file") == str(test_file) + + def test_file_attribute_uses_default_when_no_describe_match(self): + """When describe name doesn't match, the default (first) test file should be used.""" + from codeflash.languages.javascript.mocha_runner import mocha_json_to_junit_xml + + mocha_json = json.dumps( + { + "stats": {"tests": 1, "passes": 1, "failures": 0, "duration": 10}, + "tests": [ + {"title": "test1", "fullTitle": "someOtherSuite test1", "duration": 10, "err": {}}, + ], + "passes": [], + "failures": [], + "pending": [], + } + ) + + with tempfile.TemporaryDirectory() as tmpdir: + tmpdir_path = Path(tmpdir) + test_file = tmpdir_path / "test.test.js" + test_file.write_text("// no describe block", encoding="utf-8") + + output_file = tmpdir_path / "results.xml" + mocha_json_to_junit_xml(mocha_json, output_file, test_files=[test_file]) + + import xml.etree.ElementTree as ET + + tree = ET.parse(output_file) + testcases = tree.getroot().findall(".//testcase") + assert len(testcases) == 1 + assert testcases[0].get("file") == str(test_file) + + def test_no_file_attribute_when_no_test_files(self): + """When test_files is not passed, no file attribute should be set.""" + from codeflash.languages.javascript.mocha_runner import mocha_json_to_junit_xml + + mocha_json = json.dumps( + { + "stats": {"tests": 1, "passes": 1, "failures": 0, "duration": 10}, + "tests": [ + {"title": "test1", "fullTitle": "suite test1", "duration": 10, "err": {}}, + ], + "passes": [], + "failures": [], + "pending": [], + } + ) + + with tempfile.TemporaryDirectory() as tmpdir: + output_file = Path(tmpdir) / "results.xml" + mocha_json_to_junit_xml(mocha_json, output_file) + + import xml.etree.ElementTree as ET + + tree = ET.parse(output_file) + testcases = tree.getroot().findall(".//testcase") + assert len(testcases) == 1 + assert testcases[0].get("file") is None + class TestExtractMochaJson: """Tests for extracting Mocha JSON from mixed stdout."""