{"format":1,"manifest":{"name":"test-runner","publisher":"vyloxi","version":"1.0.0","displayName":"Test Runner","description":"Run all tests, this file, or the test under the cursor, and run the last one again. Detects Vitest, Jest, Mocha, pytest, cargo, go, PHPUnit, Pest, Maven, Gradle and RSpec.","engines":{"ferrum":"^0.1.0"},"categories":["Tools"],"icon":"icon.png","repository":"https://github.com/voukengdongmofrankysteve/ferrum-market","license":"MIT","main":"extension.js","activationEvents":["onCommand:testRunner.all","onCommand:testRunner.file","onCommand:testRunner.nearest","onCommand:testRunner.rerun","onCommand:testRunner.watch","onCommand:testRunner.coverage","onCommand:testRunner.detect"],"permissions":["terminal","workspace.read"],"contributes":{"commands":[{"command":"testRunner.all","title":"Run All Tests","category":"Test Runner"},{"command":"testRunner.file","title":"Run Tests in This File","category":"Test Runner"},{"command":"testRunner.nearest","title":"Run the Test Under Cursor","category":"Test Runner"},{"command":"testRunner.rerun","title":"Run the Last Test Command Again","category":"Test Runner"},{"command":"testRunner.watch","title":"Watch Tests in This File","category":"Test Runner"},{"command":"testRunner.coverage","title":"Run All Tests with Coverage","category":"Test Runner"},{"command":"testRunner.detect","title":"Which Test Tool Does This Project Use?","category":"Test Runner"}],"keybindings":[{"command":"testRunner.nearest","key":"Ctrl+Alt+Shift+T"},{"command":"testRunner.rerun","key":"Ctrl+Alt+Shift+R"}]}},"files":{"icon.png":{"encoding":"base64","content":"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"},"extension.js":{"encoding":"utf8","content":"// Test Runner: run all tests, the tests of this file, or the test under the cursor, then run the last one again.\n// It looks at your project to choose the tool (Vitest, Jest, Mocha, pytest, cargo test, go test, PHPUnit and Pest, Maven, Gradle, RSpec)\n// and types the command in the terminal, where you see the result.\n\n/** Which test tool a project uses, from the contents of its marker files. Returns { tool, label } or null. */\nfunction detect(files) {\n  const has = (n) => Object.prototype.hasOwnProperty.call(files, n) && files[n] !== null;\n  if (has('package.json')) {\n    let pkg = {};\n    try { pkg = JSON.parse(files['package.json']); } catch { pkg = {}; }\n    const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };\n    if (deps.vitest) return { tool: 'vitest', label: 'Vitest' };\n    if (deps.jest || deps['ts-jest']) return { tool: 'jest', label: 'Jest' };\n    if (deps.mocha) return { tool: 'mocha', label: 'Mocha' };\n    if (pkg.scripts && /node\\s+--test/.test(pkg.scripts.test || '')) return { tool: 'nodetest', label: 'Node test runner' };\n    if (pkg.scripts && pkg.scripts.test) return { tool: 'npm', label: 'npm test' };\n  }\n  if (has('Cargo.toml')) return { tool: 'cargo', label: 'cargo test' };\n  if (has('go.mod')) return { tool: 'go', label: 'go test' };\n  if (has('composer.json')) {\n    let c = {};\n    try { c = JSON.parse(files['composer.json']); } catch { c = {}; }\n    const deps = { ...(c.require || {}), ...(c['require-dev'] || {}) };\n    if (deps['pestphp/pest']) return { tool: 'pest', label: 'Pest' };\n    if (deps['phpunit/phpunit']) return { tool: 'phpunit', label: 'PHPUnit' };\n  }\n  if (has('pytest.ini') || has('pyproject.toml') || has('setup.cfg') || has('requirements.txt') || has('tox.ini')) return { tool: 'pytest', label: 'pytest' };\n  if (has('pom.xml')) return { tool: 'maven', label: 'Maven' };\n  if (has('build.gradle') || has('build.gradle.kts')) return { tool: 'gradle', label: 'Gradle' };\n  if (has('Gemfile')) return { tool: 'rspec', label: 'RSpec' };\n  return null;\n}\n\nconst MARKERS = ['package.json', 'Cargo.toml', 'go.mod', 'composer.json', 'pytest.ini', 'pyproject.toml', 'setup.cfg', 'requirements.txt', 'tox.ini', 'pom.xml', 'build.gradle', 'build.gradle.kts', 'Gemfile'];\n\n/** The name of the test that holds line `line` (0 based), looking upward. Returns { name, kind } or null. */\nfunction nearestTest(text, line, family) {\n  const lines = text.split('\\n');\n  for (let i = Math.min(line, lines.length - 1); i >= 0; i--) {\n    const l = lines[i];\n    let m;\n    if (family === 'js' && (m = /\\b(?:it|test)(?:\\.(?:only|skip|each\\([^)]*\\)))?\\(\\s*(['\"`])(.+?)\\1/.exec(l))) return { name: m[2], kind: 'test' };\n    if (family === 'js' && (m = /\\bdescribe(?:\\.(?:only|skip))?\\(\\s*(['\"`])(.+?)\\1/.exec(l))) return { name: m[2], kind: 'suite' };\n    if (family === 'py' && (m = /^\\s*(?:async\\s+)?def\\s+(test\\w*)\\s*\\(/.exec(l))) return { name: m[1], kind: 'test' };\n    if (family === 'py' && (m = /^class\\s+(Test\\w*)/.exec(l))) return { name: m[1], kind: 'suite' };\n    if (family === 'go' && (m = /^func\\s+(Test\\w*)\\s*\\(/.exec(l))) return { name: m[1], kind: 'test' };\n    if (family === 'rust' && (m = /^\\s*(?:async\\s+)?fn\\s+(\\w+)\\s*\\(/.exec(l)) && i > 0 && lines.slice(Math.max(0, i - 3), i).some((x) => /#\\[(?:tokio::)?test/.test(x))) return { name: m[1], kind: 'test' };\n    if (family === 'php' && (m = /\\bfunction\\s+(test\\w+)\\s*\\(/.exec(l))) return { name: m[1], kind: 'test' };\n    if (family === 'php' && (m = /\\b(?:it|test)\\(\\s*(['\"])(.+?)\\1/.exec(l))) return { name: m[2], kind: 'test' };\n    if (family === 'java' && (m = /\\bvoid\\s+(\\w+)\\s*\\(/.exec(l)) && lines.slice(Math.max(0, i - 3), i + 1).some((x) => /@Test/.test(x))) return { name: m[1], kind: 'test' };\n    if (family === 'ruby' && (m = /^\\s*(?:it|example|scenario)\\s+['\"](.+?)['\"]/.exec(l))) return { name: m[1], kind: 'test' };\n  }\n  return null;\n}\n\nconst familyOf = (path) => ({ js: 'js', jsx: 'js', ts: 'js', tsx: 'js', mjs: 'js', cjs: 'js', py: 'py', go: 'go', rs: 'rust', php: 'php', java: 'java', kt: 'java', rb: 'ruby' })[String(path).split('.').pop().toLowerCase()] || null;\n\nconst bad = (s) => /[\"\\n\\r$`]/.test(String(s));\nconst q = (s) => '\"' + s + '\"';\nconst dirOf = (rel) => (rel.includes('/') ? rel.slice(0, rel.lastIndexOf('/')) : '.');\n\n/**\n * Builds the command. `scope` is all, file or test. `rel` is the file path from the project folder with / separators.\n * Returns null when it cannot be done (for example a test name cannot be put in a command safely).\n */\nfunction command(tool, scope, rel, test, opts = {}) {\n  const w = opts.watch;\n  const cov = opts.coverage;\n  if (rel && bad(rel)) return null;\n  if (test && bad(test.name)) return null;\n  const name = test ? test.name : null;\n  switch (tool) {\n    case 'vitest': return ['npx vitest', w ? '' : 'run', cov ? '--coverage' : '', scope !== 'all' ? q(rel) : '', scope === 'test' ? '-t ' + q(name) : ''].filter(Boolean).join(' ');\n    case 'jest': return ['npx jest', w ? '--watch' : '', cov ? '--coverage' : '', scope !== 'all' ? q(rel) : '', scope === 'test' ? '-t ' + q(name) : ''].filter(Boolean).join(' ');\n    case 'mocha': return ['npx mocha', w ? '--watch' : '', scope !== 'all' ? q(rel) : '', scope === 'test' ? '-g ' + q(name) : ''].filter(Boolean).join(' ');\n    case 'nodetest': return ['node --test', w ? '--watch' : '', scope !== 'all' ? q(rel) : ''].filter(Boolean).join(' ');\n    case 'npm': return scope === 'all' ? 'npm test' : 'npm test -- ' + q(rel);\n    case 'cargo': return ['cargo test', scope === 'test' ? name : '', scope === 'file' ? q(rel.replace(/^.*\\/(?=[^/]+$)/, '').replace(/\\.rs$/, '')) : ''].filter(Boolean).join(' ');\n    case 'go': return scope === 'all' ? 'go test ./...' : scope === 'file' ? 'go test ' + q('./' + dirOf(rel)) : 'go test ' + q('./' + dirOf(rel)) + ' -run ' + q('^' + name + '$');\n    case 'pytest': return ['python -m pytest', cov ? '--cov' : '', scope !== 'all' ? q(rel + (scope === 'test' ? '::' + name : '')) : ''].filter(Boolean).join(' ');\n    case 'pest': return ['./vendor/bin/pest', scope !== 'all' ? q(rel) : '', scope === 'test' ? '--filter ' + q(name) : '', cov ? '--coverage' : ''].filter(Boolean).join(' ');\n    case 'phpunit': return ['./vendor/bin/phpunit', scope !== 'all' ? q(rel) : '', scope === 'test' ? '--filter ' + q(name) : ''].filter(Boolean).join(' ');\n    case 'maven': {\n      if (scope === 'all') return 'mvn test';\n      const cls = rel.split('/').pop().replace(/\\.\\w+$/, '');\n      return 'mvn test -Dtest=' + (scope === 'test' ? cls + '#' + name : cls);\n    }\n    case 'gradle': {\n      if (scope === 'all') return './gradlew test';\n      const cls = rel.split('/').pop().replace(/\\.\\w+$/, '');\n      return './gradlew test --tests ' + q(scope === 'test' ? '*' + cls + '.' + name : '*' + cls);\n    }\n    case 'rspec': return ['bundle exec rspec', scope !== 'all' ? q(rel) : ''].filter(Boolean).join(' ');\n    default: return null;\n  }\n}\n\nexports._test = { detect, nearestTest, command, familyOf, MARKERS };\n\nexports.activate = function (context) {\n  const reg = (id, fn) => context.subscriptions.push(ferrum.commands.register(id, fn));\n  const key = () => 'last:' + (ferrum.workspace.root || '');\n\n  async function project() {\n    if (!ferrum.workspace.root) { ferrum.window.showMessage('Open a folder first.', 'warn'); return null; }\n    const files = {};\n    for (const m of MARKERS) {\n      try { files[m] = await ferrum.workspace.readFile(m); } catch { files[m] = null; }\n    }\n    const found = detect(files);\n    if (!found) ferrum.window.showMessage('I could not tell which test tool this project uses. Looked for package.json, Cargo.toml, go.mod, composer.json, pyproject.toml, pom.xml, build.gradle and Gemfile.', 'warn');\n    return found;\n  }\n\n  const relPath = (p) => {\n    const root = (ferrum.workspace.root || '').replace(/\\\\/g, '/').replace(/\\/$/, '');\n    const f = String(p || '').replace(/\\\\/g, '/');\n    return root && f.startsWith(root + '/') ? f.slice(root.length + 1) : f;\n  };\n\n  async function run(scope, opts = {}) {\n    const found = await project();\n    if (!found) return;\n    let rel = '';\n    let test = null;\n    if (scope !== 'all') {\n      const doc = await ferrum.editor.active();\n      if (!doc || !doc.path) return ferrum.window.showMessage('Open and save a test file first.', 'warn');\n      rel = relPath(doc.path);\n      if (scope === 'test') {\n        const fam = familyOf(doc.path);\n        test = fam ? nearestTest(doc.text, doc.selection.start.line, fam) : null;\n        if (!test) return ferrum.window.showMessage('I could not find a test around the cursor. Put the cursor inside a test.', 'warn');\n        if (test.kind === 'suite' && found.tool !== 'vitest' && found.tool !== 'jest' && found.tool !== 'mocha' && found.tool !== 'pytest') scope = 'file';\n      }\n      if (doc.dirty) await ferrum.commands.execute('file.save');\n    }\n    const cmd = command(found.tool, scope, rel, test, opts);\n    if (!cmd) return ferrum.window.showMessage('Cannot build a safe command for that name or file.', 'warn');\n    await ferrum.storage.set(key(), cmd);\n    ferrum.window.showMessage(found.label + ': ' + (scope === 'all' ? 'all tests' : scope === 'file' ? rel : test.name));\n    await ferrum.terminal.run(cmd);\n  }\n\n  reg('testRunner.all', () => run('all'));\n  reg('testRunner.file', () => run('file'));\n  reg('testRunner.nearest', () => run('test'));\n  reg('testRunner.watch', () => run('file', { watch: true }));\n  reg('testRunner.coverage', () => run('all', { coverage: true }));\n  reg('testRunner.rerun', async () => {\n    const cmd = await ferrum.storage.get(key(), null);\n    if (!cmd) return ferrum.window.showMessage('Nothing to run again yet. Run a test first.');\n    await ferrum.terminal.run(cmd);\n  });\n  reg('testRunner.detect', async () => {\n    const found = await project();\n    if (found) ferrum.window.showMessage('This project uses ' + found.label + '.');\n  });\n};\n\nexports.deactivate = function () {};\n"},"README.md":{"encoding":"utf8","content":"# Test Runner\n\nThree shortcuts that save you typing test commands: **Run All Tests**, **Run Tests in This File** and **Run the Test Under Cursor**, plus **Run the Last Test Command Again** and, where the tool supports it, **Watch** and **Coverage**.\n\nIt looks at your project to choose the tool: `package.json` (Vitest, Jest, Mocha, `node --test` or the `test` script), `Cargo.toml` (cargo test), `go.mod` (go test), `composer.json` (Pest or PHPUnit), Python files (pytest), `pom.xml` (Maven), `build.gradle` (Gradle) and `Gemfile` (RSpec).\n\nThe test under the cursor is found by looking upward from the cursor for `it(` / `test(` / `describe(`, `def test_`, `#[test] fn`, `func TestX`, `function testX`, `@Test` or RSpec `it`. If no test is found it tells you instead of running everything.\n\nThe command is **typed in the terminal** where you see the result, and unsaved changes are saved first. Do not run it while a program that reads keys is active in that terminal.\n\n## Permissions\n\n**terminal**, and **workspace.read** to read the marker files (`package.json` and the like).\n"}}}