qat-cli 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1047,12 +1047,36 @@ import_handlebars.default.registerHelper("propTestValue", (prop) => {
1047
1047
  };
1048
1048
  return map[prop.type] || "'test-value'";
1049
1049
  });
1050
+ function resolveImportPath(testType, targetPath) {
1051
+ if (!targetPath) return targetPath;
1052
+ const testDirMap = {
1053
+ unit: "tests/unit",
1054
+ component: "tests/component",
1055
+ e2e: "tests/e2e",
1056
+ api: "tests/api",
1057
+ visual: "tests/visual",
1058
+ performance: "tests/e2e"
1059
+ };
1060
+ const testDir = testDirMap[testType];
1061
+ if (!testDir) return targetPath;
1062
+ if (targetPath.startsWith("../") || targetPath.startsWith("./../")) {
1063
+ return targetPath;
1064
+ }
1065
+ const depth = testDir.split("/").length;
1066
+ const prefix = "../".repeat(depth);
1067
+ let cleanPath = targetPath.replace(/^\.\//, "");
1068
+ if (!cleanPath.endsWith(".vue")) {
1069
+ cleanPath = cleanPath.replace(/\.(ts|js|tsx|jsx)$/, "");
1070
+ }
1071
+ return `${prefix}${cleanPath}`;
1072
+ }
1050
1073
  function registerTemplate(type, templateContent) {
1051
1074
  customTemplates.set(type, templateContent);
1052
1075
  }
1053
1076
  function renderTemplate(type, context) {
1054
1077
  const templateContent = loadTemplate(type);
1055
1078
  const template = import_handlebars.default.compile(templateContent);
1079
+ const resolvedTarget = resolveImportPath(type, context.target);
1056
1080
  const fullContext = {
1057
1081
  vueVersion: 3,
1058
1082
  typescript: true,
@@ -1073,6 +1097,8 @@ function renderTemplate(type, context) {
1073
1097
  requiredProps: [],
1074
1098
  optionalProps: [],
1075
1099
  ...context,
1100
+ // 使用计算后的正确路径
1101
+ target: resolvedTarget,
1076
1102
  framework: context.framework || "vue",
1077
1103
  camelName: context.camelName || toCamelCase(context.name),
1078
1104
  pascalName: context.pascalName || toPascalCase(context.name)
@@ -1715,12 +1741,23 @@ function generateHTMLReport(data) {
1715
1741
  // src/runners/vitest-runner.ts
1716
1742
  var import_node_child_process = require("child_process");
1717
1743
  var import_node_path6 = __toESM(require("path"), 1);
1744
+ var import_node_fs6 = __toESM(require("fs"), 1);
1745
+ var import_node_os = __toESM(require("os"), 1);
1746
+ var isVerbose = () => process.env.QAT_VERBOSE === "true";
1747
+ function debug(label, ...args) {
1748
+ if (isVerbose()) {
1749
+ console.log(`\x1B[90m [debug:${label}]\x1B[0m`, ...args);
1750
+ }
1751
+ }
1718
1752
  async function runVitest(options) {
1719
1753
  const startTime = Date.now();
1720
1754
  const args = buildVitestArgs(options);
1755
+ debug("vitest", "\u547D\u4EE4\u53C2\u6570:", args.join(" "));
1721
1756
  try {
1722
1757
  const result = await execVitest(args);
1723
1758
  const endTime = Date.now();
1759
+ debug("vitest", `\u89E3\u6790\u7ED3\u679C: ${result.suites.length} \u4E2A\u5957\u4EF6, ${result.suites.reduce((s, su) => s + su.tests.length, 0)} \u4E2A\u7528\u4F8B`);
1760
+ debug("vitest", "\u89E3\u6790\u65B9\u5F0F:", result.parseMethod);
1724
1761
  return {
1725
1762
  type: options.type,
1726
1763
  status: result.success ? "passed" : "failed",
@@ -1763,14 +1800,14 @@ function buildVitestArgs(options) {
1763
1800
  if (options.files && options.files.length > 0) {
1764
1801
  args.push(...options.files);
1765
1802
  } else {
1766
- const includeMap = {
1767
- unit: ["tests/unit"],
1768
- component: ["tests/component"],
1769
- api: ["tests/api"]
1803
+ const pathMap = {
1804
+ unit: "tests/unit/**/*.test.ts",
1805
+ component: "tests/component/**/*.test.ts",
1806
+ api: "tests/api/**/*.test.ts"
1770
1807
  };
1771
- const includes = includeMap[options.type];
1772
- if (includes) {
1773
- args.push("--include", includes.map((d) => `${d}/**/*.test.ts`).join(","));
1808
+ const testPattern = pathMap[options.type];
1809
+ if (testPattern) {
1810
+ args.push(testPattern);
1774
1811
  }
1775
1812
  }
1776
1813
  if (options.coverage) {
@@ -1785,12 +1822,11 @@ function buildVitestArgs(options) {
1785
1822
  return args;
1786
1823
  }
1787
1824
  async function execVitest(args) {
1788
- const os = await import("os");
1789
- const fs9 = await import("fs");
1790
- const tmpFile = import_node_path6.default.join(os.tmpdir(), `qat-vitest-result-${Date.now()}.json`);
1825
+ const tmpFile = import_node_path6.default.join(import_node_os.default.tmpdir(), `qat-vitest-result-${Date.now()}.json`);
1791
1826
  const argsWithOutput = [...args, "--outputFile", tmpFile];
1792
1827
  return new Promise((resolve, reject) => {
1793
1828
  const npx = process.platform === "win32" ? "npx.cmd" : "npx";
1829
+ debug("vitest", "\u6267\u884C\u547D\u4EE4:", npx, argsWithOutput.join(" "));
1794
1830
  const child = (0, import_node_child_process.execFile)(npx, argsWithOutput, {
1795
1831
  cwd: process.cwd(),
1796
1832
  env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
@@ -1798,85 +1834,113 @@ async function execVitest(args) {
1798
1834
  shell: true
1799
1835
  }, (error, stdout, stderr) => {
1800
1836
  const rawOutput = stdout || stderr || "";
1837
+ const exitCode = error && "code" in error ? error.code : 0;
1838
+ debug("vitest", `\u9000\u51FA\u7801: ${exitCode}`);
1839
+ debug("vitest", `stdout \u957F\u5EA6: ${stdout?.length || 0}, stderr \u957F\u5EA6: ${stderr?.length || 0}`);
1801
1840
  let jsonResult = null;
1802
1841
  try {
1803
- if (fs9.existsSync(tmpFile)) {
1804
- jsonResult = fs9.readFileSync(tmpFile, "utf-8");
1805
- fs9.unlinkSync(tmpFile);
1842
+ if (import_node_fs6.default.existsSync(tmpFile)) {
1843
+ jsonResult = import_node_fs6.default.readFileSync(tmpFile, "utf-8");
1844
+ debug("vitest", `\u4ECE\u4E34\u65F6\u6587\u4EF6\u8BFB\u53D6\u5230 JSON (${jsonResult.length} \u5B57\u7B26)`);
1845
+ debug("vitest", "JSON \u524D 500 \u5B57\u7B26:", jsonResult.substring(0, 500));
1846
+ import_node_fs6.default.unlinkSync(tmpFile);
1847
+ } else {
1848
+ debug("vitest", "\u4E34\u65F6\u6587\u4EF6\u4E0D\u5B58\u5728:", tmpFile);
1806
1849
  }
1807
- } catch {
1850
+ } catch (e) {
1851
+ debug("vitest", "\u4E34\u65F6\u6587\u4EF6\u8BFB\u53D6\u5931\u8D25:", e instanceof Error ? e.message : String(e));
1808
1852
  }
1809
1853
  if (jsonResult) {
1810
1854
  try {
1811
- const parsed = parseVitestJSONResult(jsonResult);
1812
- resolve({ ...parsed, rawOutput });
1855
+ const parsed = parseVitestJSON(jsonResult);
1856
+ debug("vitest", "\u4ECE\u4E34\u65F6\u6587\u4EF6\u89E3\u6790\u6210\u529F:", parsed.suites.length, "\u4E2A\u5957\u4EF6");
1857
+ resolve({ ...parsed, rawOutput, parseMethod: "outputFile-JSON" });
1813
1858
  return;
1814
- } catch {
1859
+ } catch (e) {
1860
+ debug("vitest", "\u4E34\u65F6\u6587\u4EF6 JSON \u89E3\u6790\u5931\u8D25:", e instanceof Error ? e.message : String(e));
1815
1861
  }
1816
1862
  }
1863
+ debug("vitest", "\u5C1D\u8BD5\u4ECE stdout \u63D0\u53D6 JSON...");
1817
1864
  try {
1818
- const parsed = parseVitestJSONOutput(rawOutput);
1819
- resolve({ ...parsed, rawOutput });
1820
- } catch {
1821
- if (rawOutput) {
1822
- resolve({ ...parseVitestTextOutput(rawOutput, !!error), rawOutput });
1823
- } else if (error && error.message.includes("ENOENT")) {
1824
- reject(new Error("\u672A\u627E\u5230 vitest\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5: npm install -D vitest"));
1825
- } else {
1826
- resolve({ success: !error, suites: [], rawOutput });
1827
- }
1865
+ const parsed = parseFromStdout(rawOutput);
1866
+ debug("vitest", "\u4ECE stdout \u89E3\u6790\u6210\u529F:", parsed.suites.length, "\u4E2A\u5957\u4EF6");
1867
+ resolve({ ...parsed, rawOutput, parseMethod: "stdout-JSON" });
1868
+ return;
1869
+ } catch (e) {
1870
+ debug("vitest", "stdout JSON \u89E3\u6790\u5931\u8D25:", e instanceof Error ? e.message : String(e));
1871
+ }
1872
+ debug("vitest", "\u5C1D\u8BD5\u4ECE\u6587\u672C\u8F93\u51FA\u89E3\u6790...");
1873
+ if (rawOutput) {
1874
+ const parsed = parseVitestTextOutput(rawOutput, !!error);
1875
+ debug("vitest", "\u6587\u672C\u89E3\u6790\u7ED3\u679C:", parsed.suites.length, "\u4E2A\u5957\u4EF6");
1876
+ resolve({ ...parsed, rawOutput, parseMethod: "text-fallback" });
1877
+ return;
1828
1878
  }
1879
+ if (error && error.message.includes("ENOENT")) {
1880
+ reject(new Error("\u672A\u627E\u5230 vitest\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5: npm install -D vitest"));
1881
+ return;
1882
+ }
1883
+ debug("vitest", "\u6240\u6709\u89E3\u6790\u65B9\u5F0F\u5747\u5931\u8D25\uFF0C\u8FD4\u56DE\u7A7A\u7ED3\u679C");
1884
+ resolve({ success: !error, suites: [], rawOutput, parseMethod: "none" });
1829
1885
  });
1830
1886
  child.on("error", (err) => {
1831
1887
  try {
1832
- fs9.unlinkSync(tmpFile);
1888
+ import_node_fs6.default.unlinkSync(tmpFile);
1833
1889
  } catch {
1834
1890
  }
1835
1891
  reject(new Error(`Vitest \u6267\u884C\u5931\u8D25: ${err.message}`));
1836
1892
  });
1837
1893
  });
1838
1894
  }
1839
- function parseVitestJSONResult(jsonStr) {
1895
+ function parseVitestJSON(jsonStr) {
1840
1896
  const data = JSON.parse(jsonStr);
1841
1897
  const suites = [];
1898
+ debug("vitest-json", "JSON \u9876\u5C42\u5B57\u6BB5:", Object.keys(data).join(", "));
1842
1899
  if (data.testResults && Array.isArray(data.testResults)) {
1900
+ debug("vitest-json", `testResults \u6570\u91CF: ${data.testResults.length}`);
1843
1901
  for (const fileResult of data.testResults) {
1902
+ const suiteTests = parseTestResults(fileResult);
1903
+ suites.push({
1904
+ name: import_node_path6.default.basename(fileResult.name || "unknown"),
1905
+ file: fileResult.name || "unknown",
1906
+ type: "unit",
1907
+ status: mapVitestStatus(fileResult.status),
1908
+ duration: fileResult.duration || 0,
1909
+ tests: suiteTests
1910
+ });
1911
+ }
1912
+ }
1913
+ if (suites.length === 0 && data.numTotalTests !== void 0) {
1914
+ debug("vitest-json", `\u4F7F\u7528\u6C47\u603B\u683C\u5F0F: total=${data.numTotalTests}, passed=${data.numPassedTests}`);
1915
+ suites.push({
1916
+ name: "Vitest Results",
1917
+ file: "unknown",
1918
+ type: "unit",
1919
+ status: data.numFailedTests > 0 ? "failed" : "passed",
1920
+ duration: 0,
1921
+ tests: buildTestsFromSummary(data)
1922
+ });
1923
+ }
1924
+ if (suites.length === 0 && data.suites && Array.isArray(data.suites)) {
1925
+ debug("vitest-json", `suites \u6570\u91CF: ${data.suites.length}`);
1926
+ for (const suiteData of data.suites) {
1844
1927
  const suiteTests = [];
1845
- const assertions = fileResult.assertionResults || fileResult.tests || [];
1846
- for (const assertion of assertions) {
1928
+ for (const test of suiteData.tests || []) {
1847
1929
  suiteTests.push({
1848
- name: assertion.title || assertion.fullName || assertion.name || "unknown",
1849
- file: fileResult.name || "unknown",
1850
- status: mapVitestStatus(assertion.status),
1851
- duration: assertion.duration || 0,
1852
- error: assertion.failureMessages?.length ? { message: assertion.failureMessages[0] } : assertion.failureMessage ? { message: assertion.failureMessage } : void 0,
1930
+ name: test.name || test.title || "unknown",
1931
+ file: test.file || suiteData.file || "unknown",
1932
+ status: mapVitestStatus(test.status || test.result?.status),
1933
+ duration: test.duration || test.result?.duration || 0,
1934
+ error: test.result?.errors?.[0] ? { message: test.result.errors[0].message || String(test.result.errors[0]) } : void 0,
1853
1935
  retries: 0
1854
1936
  });
1855
1937
  }
1856
- if (suiteTests.length === 0 && fileResult.numPassingTests !== void 0) {
1857
- const counts = [
1858
- { n: fileResult.numPassingTests || 0, s: "passed" },
1859
- { n: fileResult.numFailingTests || 0, s: "failed" },
1860
- { n: fileResult.numPendingTests || 0, s: "skipped" }
1861
- ];
1862
- for (const { n, s } of counts) {
1863
- for (let i = 0; i < n; i++) {
1864
- suiteTests.push({
1865
- name: `${s} test ${i + 1}`,
1866
- file: fileResult.name || "unknown",
1867
- status: s,
1868
- duration: 0,
1869
- retries: 0
1870
- });
1871
- }
1872
- }
1873
- }
1874
1938
  suites.push({
1875
- name: import_node_path6.default.basename(fileResult.name || "unknown"),
1876
- file: fileResult.name || "unknown",
1939
+ name: suiteData.name || "unknown",
1940
+ file: suiteData.file || "unknown",
1877
1941
  type: "unit",
1878
- status: mapVitestStatus(fileResult.status),
1879
- duration: fileResult.duration || 0,
1942
+ status: suiteTests.some((t) => t.status === "failed") ? "failed" : "passed",
1943
+ duration: 0,
1880
1944
  tests: suiteTests
1881
1945
  });
1882
1946
  }
@@ -1885,58 +1949,101 @@ function parseVitestJSONResult(jsonStr) {
1885
1949
  if (data.coverageMap) {
1886
1950
  coverage = extractCoverage(data.coverageMap);
1887
1951
  }
1888
- const success = data.success !== false && data.numFailedTests === void 0 ? suites.every((s) => s.status !== "failed") : (data.numFailedTests || 0) === 0;
1952
+ if (!coverage && data.coverage && typeof data.coverage === "object") {
1953
+ const cov = data.coverage;
1954
+ const totals = cov.totals || cov;
1955
+ const getVal = (key) => {
1956
+ const v = totals[key];
1957
+ return typeof v === "number" ? v : typeof v === "object" && v !== null && "pct" in v ? v.pct / 100 : 0;
1958
+ };
1959
+ coverage = {
1960
+ lines: getVal("lines"),
1961
+ statements: getVal("statements"),
1962
+ functions: getVal("functions"),
1963
+ branches: getVal("branches")
1964
+ };
1965
+ }
1966
+ const success = data.success !== false ? data.numFailedTests !== void 0 ? data.numFailedTests === 0 : suites.every((s) => s.status !== "failed") : false;
1889
1967
  return { success, suites, coverage };
1890
1968
  }
1891
- function parseVitestJSONOutput(output) {
1892
- const jsonMatch = output.match(/\{[\s\S]*"testResults"[\s\S]*\}/);
1893
- if (!jsonMatch) {
1894
- return parseVitestTextOutput(output, false);
1969
+ function parseTestResults(fileResult) {
1970
+ const tests = [];
1971
+ const assertions = fileResult.assertionResults || fileResult.tests || [];
1972
+ for (const assertion of assertions) {
1973
+ tests.push({
1974
+ name: assertion.title || assertion.fullName || assertion.name || "unknown",
1975
+ file: fileResult.name || "unknown",
1976
+ status: mapVitestStatus(assertion.status),
1977
+ duration: assertion.duration || 0,
1978
+ error: assertion.failureMessages?.length ? { message: assertion.failureMessages[0] } : assertion.failureMessage ? { message: assertion.failureMessage } : void 0,
1979
+ retries: 0
1980
+ });
1895
1981
  }
1896
- try {
1982
+ if (tests.length === 0 && fileResult.numPassingTests !== void 0) {
1983
+ tests.push(...buildTestsFromSummary(fileResult));
1984
+ }
1985
+ return tests;
1986
+ }
1987
+ function buildTestsFromSummary(data) {
1988
+ const tests = [];
1989
+ const counts = [
1990
+ [data.numPassedTests || 0, "passed"],
1991
+ [data.numFailedTests || 0, "failed"],
1992
+ [data.numPendingTests || 0, "skipped"]
1993
+ ];
1994
+ for (const [n, s] of counts) {
1995
+ for (let i = 0; i < n; i++) {
1996
+ tests.push({
1997
+ name: `${s} test ${i + 1}`,
1998
+ file: data.name || "unknown",
1999
+ status: s,
2000
+ duration: 0,
2001
+ retries: 0
2002
+ });
2003
+ }
2004
+ }
2005
+ return tests;
2006
+ }
2007
+ function parseFromStdout(output) {
2008
+ const jsonMatch = output.match(/\{[\s\S]*"testResults"[\s\S]*\}/);
2009
+ if (jsonMatch) {
1897
2010
  const data = JSON.parse(jsonMatch[0]);
1898
2011
  const suites = [];
1899
2012
  if (data.testResults && Array.isArray(data.testResults)) {
1900
2013
  for (const fileResult of data.testResults) {
1901
- const suite = {
1902
- name: import_node_path6.default.basename(fileResult.name || fileResult.assertionResults?.[0]?.ancestorTitles?.[0] || "unknown"),
2014
+ suites.push({
2015
+ name: import_node_path6.default.basename(fileResult.name || "unknown"),
1903
2016
  file: fileResult.name || "unknown",
1904
2017
  type: "unit",
1905
2018
  status: mapVitestStatus(fileResult.status),
1906
2019
  duration: fileResult.duration || 0,
1907
- tests: (fileResult.assertionResults || []).map((assertion) => ({
1908
- name: assertion.title || assertion.fullName || "unknown",
1909
- file: fileResult.name || "unknown",
1910
- status: mapVitestStatus(assertion.status),
1911
- duration: assertion.duration || 0,
1912
- error: assertion.failureMessages?.length ? { message: assertion.failureMessages[0] } : void 0,
1913
- retries: 0
1914
- }))
1915
- };
1916
- suites.push(suite);
2020
+ tests: parseTestResults(fileResult)
2021
+ });
1917
2022
  }
1918
2023
  }
1919
- let coverage;
1920
- if (data.coverageMap) {
1921
- coverage = extractCoverage(data.coverageMap);
1922
- }
1923
2024
  const success = data.success !== false && suites.every((s) => s.status !== "failed");
2025
+ let coverage;
2026
+ if (data.coverageMap) coverage = extractCoverage(data.coverageMap);
1924
2027
  return { success, suites, coverage };
1925
- } catch {
1926
- return parseVitestTextOutput(output, false);
1927
2028
  }
2029
+ const anyJsonMatch = output.match(/\{[\s\S]*"numTotalTests"[\s\S]*\}/);
2030
+ if (anyJsonMatch) {
2031
+ return parseVitestJSON(anyJsonMatch[0]);
2032
+ }
2033
+ throw new Error("stdout \u4E2D\u672A\u627E\u5230\u6709\u6548 JSON");
1928
2034
  }
1929
2035
  function parseVitestTextOutput(output, hasError) {
1930
2036
  const suites = [];
2037
+ debug("vitest-text", "\u6587\u672C\u8F93\u51FA\u524D 1000 \u5B57\u7B26:", output.substring(0, 1e3));
2038
+ const suiteRegex = /[✓✗×✕]\s+(.+\.test\.(ts|js)|.+\.spec\.(ts|js))\s*\((\d+)[^)]*\)/i;
2039
+ const lines = output.split("\n");
1931
2040
  let totalPassed = 0;
1932
2041
  let totalFailed = 0;
1933
- const suiteRegex = /[✓✗×]\s+(.+\.test\.ts|.+\.spec\.ts)\s*\((\d+)\s+test/i;
1934
- const lines = output.split("\n");
1935
2042
  for (const line of lines) {
1936
2043
  const match = line.match(suiteRegex);
1937
2044
  if (match) {
1938
2045
  const file = match[1];
1939
- const testCount = parseInt(match[2], 10);
2046
+ const testCount = parseInt(match[4], 10);
1940
2047
  const isPassed = line.includes("\u2713");
1941
2048
  if (isPassed) totalPassed += testCount;
1942
2049
  else totalFailed += testCount;
@@ -1956,15 +2063,39 @@ function parseVitestTextOutput(output, hasError) {
1956
2063
  });
1957
2064
  }
1958
2065
  }
2066
+ if (suites.length === 0) {
2067
+ const summaryMatch = output.match(/Tests\s+(\d+)\s+(passed|failed)/i);
2068
+ if (summaryMatch) {
2069
+ const count = parseInt(summaryMatch[1], 10);
2070
+ const status = summaryMatch[2].toLowerCase() === "passed" ? "passed" : "failed";
2071
+ suites.push({
2072
+ name: "Vitest Summary",
2073
+ file: "unknown",
2074
+ type: "unit",
2075
+ status,
2076
+ duration: 0,
2077
+ tests: Array.from({ length: count }, (_, i) => ({
2078
+ name: `test ${i + 1}`,
2079
+ file: "unknown",
2080
+ status,
2081
+ duration: 0,
2082
+ retries: 0
2083
+ }))
2084
+ });
2085
+ }
2086
+ }
2087
+ debug("vitest-text", `\u89E3\u6790\u5230 ${suites.length} \u4E2A\u5957\u4EF6, ${totalPassed} \u901A\u8FC7, ${totalFailed} \u5931\u8D25`);
1959
2088
  return {
1960
2089
  success: !hasError || totalFailed === 0,
1961
2090
  suites
1962
2091
  };
1963
2092
  }
1964
2093
  function mapVitestStatus(status) {
2094
+ if (!status) return "pending";
1965
2095
  switch (status) {
1966
2096
  case "passed":
1967
2097
  case "pass":
2098
+ case "done":
1968
2099
  return "passed";
1969
2100
  case "failed":
1970
2101
  case "fail":
@@ -1972,6 +2103,7 @@ function mapVitestStatus(status) {
1972
2103
  case "skipped":
1973
2104
  case "skip":
1974
2105
  case "pending":
2106
+ case "todo":
1975
2107
  return "skipped";
1976
2108
  default:
1977
2109
  return "pending";
@@ -2658,8 +2790,13 @@ ${error.actual ? `\u5B9E\u9645\u503C: ${error.actual}` : ""}`;
2658
2790
  6. \u5982\u679C\u6709 props/emits \u4FE1\u606F\uFF0C\u52A1\u5FC5\u9488\u5BF9\u6BCF\u4E2A prop \u548C emit \u751F\u6210\u6D4B\u8BD5`;
2659
2791
  }
2660
2792
  buildGenerateTestUserPrompt(req) {
2793
+ const importPath = this.computeTestImportPath(req.type, req.target);
2661
2794
  let prompt = `\u8BF7\u4E3A\u4EE5\u4E0B\u6587\u4EF6\u751F\u6210${req.type}\u6D4B\u8BD5\u4EE3\u7801:
2662
2795
  \u76EE\u6807\u6587\u4EF6: ${req.target}
2796
+ \u6D4B\u8BD5\u6587\u4EF6\u5C06\u653E\u5728: ${this.getTestOutputDir(req.type)}/
2797
+ \u6B63\u786E\u7684 import \u8DEF\u5F84: ${importPath}
2798
+
2799
+ \u91CD\u8981\uFF1Aimport \u8BED\u53E5\u4E2D\u5FC5\u987B\u4F7F\u7528\u4E0A\u8FF0\u6B63\u786E\u7684\u76F8\u5BF9\u8DEF\u5F84 ${importPath}\uFF0C\u4E0D\u8981\u4F7F\u7528 ${req.target} \u6216\u5176\u4ED6\u8DEF\u5F84\uFF01
2663
2800
  `;
2664
2801
  if (req.analysis) {
2665
2802
  prompt += "\n\u6E90\u7801\u5206\u6790\u7ED3\u679C:\n";
@@ -2709,6 +2846,46 @@ ${req.context}
2709
2846
  }
2710
2847
  return prompt;
2711
2848
  }
2849
+ /**
2850
+ * 根据测试类型和源文件路径,计算从测试文件到源文件的正确相对导入路径
2851
+ */
2852
+ computeTestImportPath(testType, targetPath) {
2853
+ if (!targetPath) return targetPath;
2854
+ const testDirMap = {
2855
+ unit: "tests/unit",
2856
+ component: "tests/component",
2857
+ e2e: "tests/e2e",
2858
+ api: "tests/api",
2859
+ visual: "tests/visual",
2860
+ performance: "tests/e2e"
2861
+ };
2862
+ const testDir = testDirMap[testType];
2863
+ if (!testDir) return targetPath;
2864
+ if (targetPath.startsWith("../") || targetPath.startsWith("./../")) {
2865
+ return targetPath;
2866
+ }
2867
+ const depth = testDir.split("/").length;
2868
+ const prefix = "../".repeat(depth);
2869
+ let cleanPath = targetPath.replace(/^\.\//, "");
2870
+ if (!cleanPath.endsWith(".vue")) {
2871
+ cleanPath = cleanPath.replace(/\.(ts|js|tsx|jsx)$/, "");
2872
+ }
2873
+ return `${prefix}${cleanPath}`;
2874
+ }
2875
+ /**
2876
+ * 获取测试输出目录
2877
+ */
2878
+ getTestOutputDir(testType) {
2879
+ const dirMap = {
2880
+ unit: "tests/unit",
2881
+ component: "tests/component",
2882
+ e2e: "tests/e2e",
2883
+ api: "tests/api",
2884
+ visual: "tests/visual",
2885
+ performance: "tests/e2e"
2886
+ };
2887
+ return dirMap[testType] || "tests/unit";
2888
+ }
2712
2889
  parseGenerateTestResponse(content) {
2713
2890
  const codeBlockMatch = content.match(/```(?:typescript|ts|javascript|js)?\s*\n([\s\S]*?)```/);
2714
2891
  const code = codeBlockMatch ? codeBlockMatch[1].trim() : content.replace(/^(?:```[\s\S]*?\n)?/, "").replace(/\n?```$/, "").trim();
@@ -2737,10 +2914,12 @@ ISSUES: \u95EE\u9898\u5217\u8868\uFF08\u6BCF\u884C\u4E00\u4E2A\uFF0C\u683C\u5F0F
2737
2914
  SUGGESTIONS: \u6539\u8FDB\u5EFA\u8BAE\u5217\u8868\uFF08\u6BCF\u884C\u4E00\u4E2A\uFF0C\u683C\u5F0F "- \u5EFA\u8BAE\u63CF\u8FF0"\uFF09`;
2738
2915
  }
2739
2916
  buildReviewTestUserPrompt(req) {
2917
+ const importPath = this.computeTestImportPath(req.testType, req.target);
2740
2918
  let prompt = `\u8BF7\u5BA1\u67E5\u4EE5\u4E0B\u6D4B\u8BD5\u7528\u4F8B\u662F\u5426\u4E0E\u6E90\u7801\u8D34\u5207\u4E14\u51C6\u786E\u3002
2741
2919
 
2742
2920
  \u88AB\u6D4B\u6587\u4EF6: ${req.target}
2743
2921
  \u6D4B\u8BD5\u7C7B\u578B: ${req.testType}
2922
+ \u6B63\u786E\u7684 import \u8DEF\u5F84\u5E94\u4E3A: ${importPath}\uFF08\u6D4B\u8BD5\u6587\u4EF6\u4F4D\u4E8E ${this.getTestOutputDir(req.testType)}/\uFF09
2744
2923
 
2745
2924
  --- \u6E90\u7801\u5185\u5BB9 ---
2746
2925
  \`\`\`typescript
@@ -2908,7 +3087,7 @@ async function testAIConnection(config) {
2908
3087
  }
2909
3088
 
2910
3089
  // src/services/mock-server.ts
2911
- var import_node_fs6 = __toESM(require("fs"), 1);
3090
+ var import_node_fs7 = __toESM(require("fs"), 1);
2912
3091
  var import_node_path8 = __toESM(require("path"), 1);
2913
3092
  var serverState = {
2914
3093
  running: false,
@@ -2921,18 +3100,18 @@ function getMockServerState() {
2921
3100
  }
2922
3101
  async function loadMockRoutes(routesDir) {
2923
3102
  const absDir = import_node_path8.default.resolve(process.cwd(), routesDir);
2924
- if (!import_node_fs6.default.existsSync(absDir)) {
3103
+ if (!import_node_fs7.default.existsSync(absDir)) {
2925
3104
  return [];
2926
3105
  }
2927
3106
  const routes = [];
2928
- const entries = import_node_fs6.default.readdirSync(absDir, { withFileTypes: true });
3107
+ const entries = import_node_fs7.default.readdirSync(absDir, { withFileTypes: true });
2929
3108
  for (const entry of entries) {
2930
3109
  if (!entry.isFile()) continue;
2931
3110
  const filePath = import_node_path8.default.join(absDir, entry.name);
2932
3111
  const ext = import_node_path8.default.extname(entry.name);
2933
3112
  try {
2934
3113
  if (ext === ".json") {
2935
- const content = import_node_fs6.default.readFileSync(filePath, "utf-8");
3114
+ const content = import_node_fs7.default.readFileSync(filePath, "utf-8");
2936
3115
  const parsed = JSON.parse(content);
2937
3116
  if (Array.isArray(parsed)) {
2938
3117
  routes.push(...parsed);
@@ -3124,11 +3303,11 @@ function generateMockRouteTemplate(name) {
3124
3303
  }
3125
3304
  function initMockRoutesDir(routesDir) {
3126
3305
  const absDir = import_node_path8.default.resolve(process.cwd(), routesDir);
3127
- if (!import_node_fs6.default.existsSync(absDir)) {
3128
- import_node_fs6.default.mkdirSync(absDir, { recursive: true });
3306
+ if (!import_node_fs7.default.existsSync(absDir)) {
3307
+ import_node_fs7.default.mkdirSync(absDir, { recursive: true });
3129
3308
  }
3130
3309
  const examplePath = import_node_path8.default.join(absDir, "example.json");
3131
- if (!import_node_fs6.default.existsSync(examplePath)) {
3310
+ if (!import_node_fs7.default.existsSync(examplePath)) {
3132
3311
  const exampleRoutes = [
3133
3312
  {
3134
3313
  method: "GET",
@@ -3152,24 +3331,24 @@ function initMockRoutesDir(routesDir) {
3152
3331
  }
3153
3332
  }
3154
3333
  ];
3155
- import_node_fs6.default.writeFileSync(examplePath, JSON.stringify(exampleRoutes, null, 2), "utf-8");
3334
+ import_node_fs7.default.writeFileSync(examplePath, JSON.stringify(exampleRoutes, null, 2), "utf-8");
3156
3335
  }
3157
3336
  }
3158
3337
 
3159
3338
  // src/services/visual.ts
3160
- var import_node_fs7 = __toESM(require("fs"), 1);
3339
+ var import_node_fs8 = __toESM(require("fs"), 1);
3161
3340
  var import_node_path9 = __toESM(require("path"), 1);
3162
3341
  var import_pixelmatch = __toESM(require("pixelmatch"), 1);
3163
3342
  var import_pngjs = require("pngjs");
3164
3343
  function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.1) {
3165
- if (!import_node_fs7.default.existsSync(baselinePath)) {
3344
+ if (!import_node_fs8.default.existsSync(baselinePath)) {
3166
3345
  throw new Error(`\u57FA\u7EBF\u56FE\u7247\u4E0D\u5B58\u5728: ${baselinePath}`);
3167
3346
  }
3168
- if (!import_node_fs7.default.existsSync(currentPath)) {
3347
+ if (!import_node_fs8.default.existsSync(currentPath)) {
3169
3348
  throw new Error(`\u5F53\u524D\u56FE\u7247\u4E0D\u5B58\u5728: ${currentPath}`);
3170
3349
  }
3171
- const baseline = import_pngjs.PNG.sync.read(import_node_fs7.default.readFileSync(baselinePath));
3172
- const current = import_pngjs.PNG.sync.read(import_node_fs7.default.readFileSync(currentPath));
3350
+ const baseline = import_pngjs.PNG.sync.read(import_node_fs8.default.readFileSync(baselinePath));
3351
+ const current = import_pngjs.PNG.sync.read(import_node_fs8.default.readFileSync(currentPath));
3173
3352
  if (baseline.width !== current.width || baseline.height !== current.height) {
3174
3353
  return {
3175
3354
  passed: false,
@@ -3198,10 +3377,10 @@ function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.
3198
3377
  let diffPath;
3199
3378
  if (diffPixels > 0) {
3200
3379
  const diffDir = import_node_path9.default.dirname(diffOutputPath);
3201
- if (!import_node_fs7.default.existsSync(diffDir)) {
3202
- import_node_fs7.default.mkdirSync(diffDir, { recursive: true });
3380
+ if (!import_node_fs8.default.existsSync(diffDir)) {
3381
+ import_node_fs8.default.mkdirSync(diffDir, { recursive: true });
3203
3382
  }
3204
- import_node_fs7.default.writeFileSync(diffOutputPath, import_pngjs.PNG.sync.write(diff));
3383
+ import_node_fs8.default.writeFileSync(diffOutputPath, import_pngjs.PNG.sync.write(diff));
3205
3384
  diffPath = diffOutputPath;
3206
3385
  }
3207
3386
  return {
@@ -3215,14 +3394,14 @@ function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.
3215
3394
  };
3216
3395
  }
3217
3396
  function createBaseline(currentPath, baselinePath) {
3218
- if (!import_node_fs7.default.existsSync(currentPath)) {
3397
+ if (!import_node_fs8.default.existsSync(currentPath)) {
3219
3398
  throw new Error(`\u5F53\u524D\u622A\u56FE\u4E0D\u5B58\u5728: ${currentPath}`);
3220
3399
  }
3221
3400
  const baselineDir = import_node_path9.default.dirname(baselinePath);
3222
- if (!import_node_fs7.default.existsSync(baselineDir)) {
3223
- import_node_fs7.default.mkdirSync(baselineDir, { recursive: true });
3401
+ if (!import_node_fs8.default.existsSync(baselineDir)) {
3402
+ import_node_fs8.default.mkdirSync(baselineDir, { recursive: true });
3224
3403
  }
3225
- import_node_fs7.default.copyFileSync(currentPath, baselinePath);
3404
+ import_node_fs8.default.copyFileSync(currentPath, baselinePath);
3226
3405
  return baselinePath;
3227
3406
  }
3228
3407
  function updateBaseline(currentPath, baselinePath) {
@@ -3230,69 +3409,69 @@ function updateBaseline(currentPath, baselinePath) {
3230
3409
  }
3231
3410
  function updateAllBaselines(currentDir, baselineDir) {
3232
3411
  const updated = [];
3233
- if (!import_node_fs7.default.existsSync(currentDir)) {
3412
+ if (!import_node_fs8.default.existsSync(currentDir)) {
3234
3413
  return updated;
3235
3414
  }
3236
- const files = import_node_fs7.default.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
3415
+ const files = import_node_fs8.default.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
3237
3416
  for (const file of files) {
3238
3417
  const currentPath = import_node_path9.default.join(currentDir, file);
3239
3418
  const baselinePath = import_node_path9.default.join(baselineDir, file);
3240
3419
  const baselineDirAbs = import_node_path9.default.dirname(baselinePath);
3241
- if (!import_node_fs7.default.existsSync(baselineDirAbs)) {
3242
- import_node_fs7.default.mkdirSync(baselineDirAbs, { recursive: true });
3420
+ if (!import_node_fs8.default.existsSync(baselineDirAbs)) {
3421
+ import_node_fs8.default.mkdirSync(baselineDirAbs, { recursive: true });
3243
3422
  }
3244
- import_node_fs7.default.copyFileSync(currentPath, baselinePath);
3423
+ import_node_fs8.default.copyFileSync(currentPath, baselinePath);
3245
3424
  updated.push(file);
3246
3425
  }
3247
3426
  return updated;
3248
3427
  }
3249
3428
  function cleanBaselines(baselineDir) {
3250
- if (!import_node_fs7.default.existsSync(baselineDir)) {
3429
+ if (!import_node_fs8.default.existsSync(baselineDir)) {
3251
3430
  return 0;
3252
3431
  }
3253
- const files = import_node_fs7.default.readdirSync(baselineDir).filter((f) => f.endsWith(".png"));
3432
+ const files = import_node_fs8.default.readdirSync(baselineDir).filter((f) => f.endsWith(".png"));
3254
3433
  let count = 0;
3255
3434
  for (const file of files) {
3256
- import_node_fs7.default.unlinkSync(import_node_path9.default.join(baselineDir, file));
3435
+ import_node_fs8.default.unlinkSync(import_node_path9.default.join(baselineDir, file));
3257
3436
  count++;
3258
3437
  }
3259
3438
  return count;
3260
3439
  }
3261
3440
  function cleanDiffs(diffDir) {
3262
- if (!import_node_fs7.default.existsSync(diffDir)) {
3441
+ if (!import_node_fs8.default.existsSync(diffDir)) {
3263
3442
  return 0;
3264
3443
  }
3265
- const files = import_node_fs7.default.readdirSync(diffDir).filter((f) => f.endsWith(".png"));
3444
+ const files = import_node_fs8.default.readdirSync(diffDir).filter((f) => f.endsWith(".png"));
3266
3445
  let count = 0;
3267
3446
  for (const file of files) {
3268
- import_node_fs7.default.unlinkSync(import_node_path9.default.join(diffDir, file));
3447
+ import_node_fs8.default.unlinkSync(import_node_path9.default.join(diffDir, file));
3269
3448
  count++;
3270
3449
  }
3271
3450
  return count;
3272
3451
  }
3273
3452
  function listBaselines(baselineDir) {
3274
- if (!import_node_fs7.default.existsSync(baselineDir)) {
3453
+ if (!import_node_fs8.default.existsSync(baselineDir)) {
3275
3454
  return [];
3276
3455
  }
3277
- return import_node_fs7.default.readdirSync(baselineDir).filter((f) => f.endsWith(".png")).sort();
3456
+ return import_node_fs8.default.readdirSync(baselineDir).filter((f) => f.endsWith(".png")).sort();
3278
3457
  }
3279
3458
  function listDiffs(diffDir) {
3280
- if (!import_node_fs7.default.existsSync(diffDir)) {
3459
+ if (!import_node_fs8.default.existsSync(diffDir)) {
3281
3460
  return [];
3282
3461
  }
3283
- return import_node_fs7.default.readdirSync(diffDir).filter((f) => f.endsWith(".png")).sort();
3462
+ return import_node_fs8.default.readdirSync(diffDir).filter((f) => f.endsWith(".png")).sort();
3284
3463
  }
3285
3464
  function compareDirectories(baselineDir, currentDir, diffDir, threshold = 0.1) {
3286
3465
  const results = [];
3287
- if (!import_node_fs7.default.existsSync(currentDir)) {
3466
+ if (!import_node_fs8.default.existsSync(currentDir)) {
3288
3467
  return results;
3289
3468
  }
3290
- const currentFiles = import_node_fs7.default.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
3469
+ const currentFiles = import_node_fs8.default.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
3291
3470
  for (const file of currentFiles) {
3292
3471
  const currentPath = import_node_path9.default.join(currentDir, file);
3293
3472
  const baselinePath = import_node_path9.default.join(baselineDir, file);
3294
3473
  const diffPath = import_node_path9.default.join(diffDir, file);
3295
- if (!import_node_fs7.default.existsSync(baselinePath)) {
3474
+ if (!import_node_fs8.default.existsSync(baselinePath)) {
3296
3475
  createBaseline(currentPath, baselinePath);
3297
3476
  results.push({
3298
3477
  passed: true,
@@ -3324,14 +3503,14 @@ function compareDirectories(baselineDir, currentDir, diffDir, threshold = 0.1) {
3324
3503
  }
3325
3504
 
3326
3505
  // src/services/source-analyzer.ts
3327
- var import_node_fs8 = __toESM(require("fs"), 1);
3506
+ var import_node_fs9 = __toESM(require("fs"), 1);
3328
3507
  var import_node_path10 = __toESM(require("path"), 1);
3329
3508
  function analyzeFile(filePath) {
3330
3509
  const absolutePath = import_node_path10.default.resolve(process.cwd(), filePath);
3331
- if (!import_node_fs8.default.existsSync(absolutePath)) {
3510
+ if (!import_node_fs9.default.existsSync(absolutePath)) {
3332
3511
  return { filePath, exports: [], apiCalls: [] };
3333
3512
  }
3334
- const content = import_node_fs8.default.readFileSync(absolutePath, "utf-8");
3513
+ const content = import_node_fs9.default.readFileSync(absolutePath, "utf-8");
3335
3514
  const ext = import_node_path10.default.extname(filePath);
3336
3515
  const result = {
3337
3516
  filePath,
@@ -3686,13 +3865,13 @@ function cleanTemplateUrl(url) {
3686
3865
  }
3687
3866
  function scanAPICalls(srcDir) {
3688
3867
  const absDir = import_node_path10.default.resolve(process.cwd(), srcDir);
3689
- if (!import_node_fs8.default.existsSync(absDir)) {
3868
+ if (!import_node_fs9.default.existsSync(absDir)) {
3690
3869
  return [];
3691
3870
  }
3692
3871
  const allCalls = [];
3693
3872
  const seen = /* @__PURE__ */ new Set();
3694
3873
  function walk(dir) {
3695
- const entries = import_node_fs8.default.readdirSync(dir, { withFileTypes: true });
3874
+ const entries = import_node_fs9.default.readdirSync(dir, { withFileTypes: true });
3696
3875
  for (const entry of entries) {
3697
3876
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") continue;
3698
3877
  const fullPath = import_node_path10.default.join(dir, entry.name);
@@ -3700,7 +3879,7 @@ function scanAPICalls(srcDir) {
3700
3879
  walk(fullPath);
3701
3880
  } else if (entry.isFile() && /\.(ts|js|vue|mjs)$/.test(entry.name)) {
3702
3881
  try {
3703
- const content = import_node_fs8.default.readFileSync(fullPath, "utf-8");
3882
+ const content = import_node_fs9.default.readFileSync(fullPath, "utf-8");
3704
3883
  const calls = extractAPICalls(content, import_node_path10.default.relative(process.cwd(), fullPath));
3705
3884
  for (const call of calls) {
3706
3885
  const key = `${call.method}:${call.url}`;