|
- import fs from 'node:fs'
- import path from 'node:path'
- import { fileURLToPath } from 'node:url'
- import ts from 'typescript'
- import { sceneGroups } from './scene-editor-feature-catalog.mjs'
- import { sceneEvidenceBindings, sceneUnitPlan } from './scene-editor-evidence-bindings.mjs'
-
- // Static test-plan extraction, never executes a business test or accesses API.
- const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
- const report = path.join(root, 'reports/scene-editor-closure-20260906')
- const catalog = sceneGroups.flatMap(([group, rows]) => rows.map(([id, name]) => ({ id, name, group })))
- const browser = []
- const literal = node => node && (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) ? node.text : null
- function property(node, key) {
- return node && ts.isObjectLiteralExpression(node)
- ? node.properties.find(p => ts.isPropertyAssignment(p) && p.name.getText().replace(/['"]/g, '') === key)?.initializer : null
- }
- for (const basename of fs.readdirSync(path.join(root, 'tests')).filter(f => /^scene-editor-.*closure\.spec\.ts$/.test(f))) {
- const file = `tests/${basename}`, text = fs.readFileSync(path.join(root, file), 'utf8')
- const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true)
- function visit(node, title = '') {
- if (ts.isCallExpression(node) && node.expression.getText(source) === 'test') title = literal(node.arguments[0]) || node.arguments[0].getText(source)
- if (ts.isCallExpression(node) && node.expression.getText(source) === 'evidence') {
- const name = literal(node.arguments[0]), data = node.arguments[1]
- const idsNode = property(data, 'featureIds')
- const explicitIds = idsNode && ts.isArrayLiteralExpression(idsNode) ? idsNode.elements.map(literal).filter(Boolean) : []
- const binding = name && sceneEvidenceBindings[name]
- const ids = explicitIds.length ? explicitIds : binding?.featureIds || []
- const line = source.getLineAndCharacterOfPosition(node.getStart()).line + 1
- if (name) {
- const featureIds = name === 'basic-assets'
- ? [...ids, ...['diagnostic','equipment','workbench','shelf','safety-zone','camera','worker','light'].map(v => `asset.${v}`)] : ids
- browser.push({ file, line, evidenceName: name, testTitle: title, kind: 'browser', featureIds: [...new Set(featureIds)], scope: literal(property(data, 'scope')) || binding?.scope || '以源测试实际断言为准;计划关联不代表最终通过。' })
- } else if (basename === 'scene-editor-templates-closure.spec.ts') {
- for (const item of catalog.filter(item => item.id.startsWith('template.') && item.id !== 'template.switch-cancel')) {
- browser.push({ file, line, evidenceName: item.id.replace('template.', 'template-'), testTitle: `场景模板实际加载、保存重开:${item.name}`, kind: 'browser', featureIds: [item.id], scope: '循环参数化模板用例,每种独立副本、真实点击、保存、刷新与截图。' })
- }
- } else if (basename === 'scene-editor-downstream-closure.spec.ts') {
- for (const key of ['fox','truck']) browser.push({ file, line, evidenceName: `downstream-${key}`, testTitle: `${key}:场景发布后在真实训练编排选择、保存并重开静态模型`, kind: 'browser', featureIds: ids, scope: '真实训练编排静态还原;不计来源模型动画/蓝图自动执行。' })
- }
- }
- ts.forEachChild(node, child => visit(child, title))
- }
- visit(source)
- }
- const unit = sceneUnitPlan.map(([file, testTitle, featureIds]) => ({ file, testTitle, featureIds, kind: 'unit', scope: '聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。' }))
- const rows = catalog.map(item => ({ ...item, verification: 'PENDING', plannedEvidence: [...browser, ...unit].filter(proof => proof.featureIds.includes(item.id)) }))
- const unknownIds = [...new Set([...browser, ...unit].flatMap(p => p.featureIds).filter(id => !catalog.some(row => row.id === id)))]
- const data = { generatedAt: new Date().toISOString(), status: 'PENDING', scope: '只读解析当前spec与明确unit计划;不执行测试,不推断最终PASS。',
- count: { total: rows.length, withBrowserPlan: rows.filter(r => r.plannedEvidence.some(p => p.kind === 'browser')).length,
- onlyUnitPlan: rows.filter(r => r.plannedEvidence.length && r.plannedEvidence.every(p => p.kind === 'unit')).length,
- unmapped: rows.filter(r => !r.plannedEvidence.length).length },
- unknownIds, gaps: rows.filter(r => !r.plannedEvidence.length).map(({ id, name }) => ({ id, name })), features: rows }
- fs.mkdirSync(path.join(report, 'data'), { recursive: true })
- fs.writeFileSync(path.join(report, 'data/coverage-test-plan.json'), JSON.stringify(data, null, 2))
- console.log(JSON.stringify({ count: data.count, gaps: data.gaps, unknownIds }, null, 2))
|