diff --git a/jest/package.json b/jest/package.json index 55e2805..0882000 100644 --- a/jest/package.json +++ b/jest/package.json @@ -1,6 +1,6 @@ { "name": "testsolar-oss-jest", - "version": "0.2.7", + "version": "0.2.10", "description": "TestSolar support for jest tool", "main": "main.js", "scripts": { diff --git a/jest/src/jestx/parser.ts b/jest/src/jestx/parser.ts index 2e1b25f..41c4665 100644 --- a/jest/src/jestx/parser.ts +++ b/jest/src/jestx/parser.ts @@ -45,9 +45,19 @@ export async function collectTestCases( const fileContent = fs.readFileSync(filePath, "utf-8"); const testData = JSON.parse(fileContent); - // 解析所有用例 - const loadCaseResult = parseTestcase(projPath, testData); - log.info("Jest testtool parse all testcases: \n", loadCaseResult); + // 检查是否为文件模式 + const runMode = process.env.TESTSOLAR_TTP_RUN_MODE; + let loadCaseResult: string[]; + + if (runMode === 'file') { + // 文件模式:只返回文件路径 + loadCaseResult = testData.map((filePath: string) => path.relative(projPath, filePath)); + log.info("Jest testtool file mode - returning files only: \n", loadCaseResult); + } else { + // 正常模式:解析所有用例 + loadCaseResult = parseTestcase(projPath, testData); + log.info("Jest testtool parse all testcases: \n", loadCaseResult); + } // 过滤用例 let filterResult; @@ -72,9 +82,15 @@ export async function collectTestCases( // 提取用例数据 filterResult.forEach((filteredTestCase: string) => { - const [path, descAndName] = filteredTestCase.split("?"); - const test = new TestCase(`${path}?${descAndName}`, {}); - result.Tests.push(test); + if (runMode === 'file') { + // 文件模式:直接使用文件路径作为测试用例名称 + const test = new TestCase(filteredTestCase, {}); + result.Tests.push(test); + } else { + const [path, descAndName] = filteredTestCase.split("?"); + const test = new TestCase(`${path}?${descAndName}`, {}); + result.Tests.push(test); + } }); } catch (error: unknown) { // 直接抛出异常并退出 diff --git a/jest/src/jestx/utils.ts b/jest/src/jestx/utils.ts index ddf5ef2..aaced8a 100644 --- a/jest/src/jestx/utils.ts +++ b/jest/src/jestx/utils.ts @@ -138,79 +138,72 @@ export const parseTestcase = ( ): string[] => { const testcases: string[] = []; - // 遍历所有文件 for (const filePath of fileData) { const relativePath = path.relative(projPath, filePath); - // 读取文件内容 const fileContent = fs.readFileSync(filePath, "utf-8"); + const fileCases = parseFileTestcasesByTokens(fileContent, relativePath); + testcases.push(...fileCases); + } - // 将文件内容按行分割 - const lines = fileContent.split("\n"); - - // 使用栈来跟踪多层 describe 嵌套 - const describeStack: string[] = []; - let braceCount = 0; - const braceStack: number[] = []; - - // 遍历每一行 - for (const line of lines) { - // 计算当前行的缩进层级 - const indentLevel = (line.match(/^\s*/)?.[0].length || 0) / 4; // 假设使用4个空格缩进 - - // 匹配 describe 标签 - const describeMatch = line.match(/describe\(['"](.*?)['"],/); - if (describeMatch) { - // 根据缩进层级调整 describe 栈 - while (describeStack.length > indentLevel) { - describeStack.pop(); - braceStack.pop(); - } - // 添加新的 describe 到栈中 - describeStack.push(describeMatch[1]); - braceStack.push(braceCount); - } - - // 匹配大括号来跟踪作用域 - const openBraces = (line.match(/\{/g) || []).length; - const closeBraces = (line.match(/\}/g) || []).length; - braceCount += openBraces - closeBraces; + return Array.from(new Set(testcases)); +}; - // 当遇到闭合大括号时,检查是否需要弹出 describe - if (closeBraces > 0) { - while (braceStack.length > 0 && braceCount <= braceStack[braceStack.length - 1]) { - describeStack.pop(); - braceStack.pop(); - } - } - // 扫描只有it或者test标签用例,无describe - const singleItMatch = line.match(/^(it|test)\(['"](.*?)['"],/); - if (singleItMatch) { - const testcase = `${relativePath.replace(projPath, "")}?${singleItMatch[2]}`; - testcases.push(testcase); - continue; +const parseFileTestcasesByTokens = (content: string, relativePath: string): string[] => { + const testcases: string[] = []; + const lines = content.split('\n'); + + // 状态管理 + const describeStack: string[] = []; + const indentStack: number[] = []; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmedLine = line.trim(); + + // 跳过空行和注释 + if (!trimmedLine || trimmedLine.startsWith('//')) { + continue; + } + + // 计算缩进(空格数) + const indent = line.search(/\S/); + + // 处理 describe + const describeMatch = line.match(/describe\(['"](.*?)['"],/); + if (describeMatch) { + // 移除缩进更大或相等的 describe(同级或子级) + while (indentStack.length > 0 && indentStack[indentStack.length - 1] >= indent) { + indentStack.pop(); + describeStack.pop(); } - - // 匹配describe下的 it 或 test 标签 - const itMatch = line.match(/\s+(it|test)\(['"](.*?)['"],/); - if (itMatch) { - if (describeStack.length > 0) { - // 构建完整的测试用例路径,包含所有层级的 describe - const fullDescribePath = describeStack.join(' '); - const testcase = `${relativePath.replace(projPath, "")}?${fullDescribePath} ${itMatch[2]}`; - testcases.push(testcase); - } else { - // 如果没有 describe 包围,直接使用测试名称 - const testcase = `${relativePath.replace(projPath, "")}?${itMatch[2]}`; - testcases.push(testcase); - } + + // 添加新的 describe + describeStack.push(describeMatch[1]); + indentStack.push(indent); + continue; + } + + // 处理 it/test + const testMatch = line.match(/^\s*(it|test)\(['"](.*?)['"],/); + if (testMatch) { + // 移除缩进更大或相等的 describe + while (indentStack.length > 0 && indentStack[indentStack.length - 1] >= indent) { + indentStack.pop(); + describeStack.pop(); } + + // 构建测试用例名称 + const fullTestName = [...describeStack, testMatch[2]].join(' '); + const testcase = `${relativePath}?${fullTestName}`; + testcases.push(testcase); } } - - return Array.from(new Set(testcases)); + + return testcases; }; + /// 生成运行测试用例的命令 export function generateCommands( path: string, diff --git a/jest/testtool.yaml b/jest/testtool.yaml index c5a5d29..33bdeba 100644 --- a/jest/testtool.yaml +++ b/jest/testtool.yaml @@ -2,7 +2,7 @@ schemaVersion: 1.0 name: jest nameZh: Jest自动化测试 lang: javascript -version: '0.2.7' +version: '0.2.10' defaultBaseImage: node:18 langType: INTERPRETED description: |-