diff --git a/frontend/scripts/regression-back-navigation.mjs b/frontend/scripts/regression-back-navigation.mjs new file mode 100644 index 0000000..fdac1eb --- /dev/null +++ b/frontend/scripts/regression-back-navigation.mjs @@ -0,0 +1,35 @@ +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const root = resolve(fileURLToPath(new URL('..', import.meta.url))) + +function read(relativePath) { + return readFileSync(resolve(root, relativePath), 'utf8') +} + +function assert(condition, message) { + if (!condition) { + throw new Error(message) + } +} + +const appHeader = read('src/components/AppHeader.vue') +const trainingLog = read('src/views/system/TrainingLogView.vue') + +assert( + appHeader.includes('showBackButton'), + 'AppHeader should gate the global back button behind showBackButton', +) + +assert( + appHeader.includes('v-if="showBackButton"'), + 'AppHeader should hide 返回上一页 when the current route is not a detail/sub page', +) + +assert( + !trainingLog.includes('返回列表'), + 'TrainingLogView should rely on the global 返回上一页 button instead of rendering 返回列表', +) + +console.log('back-navigation regression checks passed') diff --git a/frontend/scripts/regression-data-process-wizard.mjs b/frontend/scripts/regression-data-process-wizard.mjs new file mode 100644 index 0000000..e7c9fe3 --- /dev/null +++ b/frontend/scripts/regression-data-process-wizard.mjs @@ -0,0 +1,411 @@ +import assert from 'node:assert/strict' +import { existsSync } from 'node:fs' +import { readFile } from 'node:fs/promises' +import { fileURLToPath } from 'node:url' +import path from 'node:path' +import { parse as parseSfc } from '@vue/compiler-sfc' + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)) +const viewPath = path.resolve(scriptDir, '../src/views/data-process/DataProcessCreateView.vue') +const createDir = path.resolve(scriptDir, '../src/views/data-process/create') +const layoutPath = path.resolve(scriptDir, '../src/layouts/MainLayout.vue') +const viewSource = await readFile(viewPath, 'utf8') +const layoutSource = await readFile(layoutPath, 'utf8') + +assert.match(viewSource, /const WIZARD_STEPS = \[/, '向导步骤尚未改为固定常量') +for (const title of ['创建任务', '数据预览', '开始生成', '结果编辑与保存']) { + assert.ok(viewSource.includes(`title: '${title}'`), `缺少固定步骤:${title}`) +} +assert.doesNotMatch(viewSource, /steps\s*=\s*computed|all\.filter/, '步骤仍根据处理类型动态增减') +assert.match(viewSource, /localStorage\.setItem\(DRAFT_STORAGE_KEY/, '草稿没有持久化') +assert.match(viewSource, /localStorage\.getItem\(DRAFT_STORAGE_KEY\)/, '草稿没有恢复读取') +assert.match(viewSource, /restoreDraft\(\)/, '页面没有恢复草稿') + +const expectedComponents = [ + 'TaskSetupStep.vue', + 'PreviewCompareStep.vue', + 'GenerationStep.vue', + 'ResultEditorStep.vue', +] +for (const component of expectedComponents) { + assert.ok(existsSync(path.join(createDir, component)), `缺少步骤组件:${component}`) + assert.ok(viewSource.includes(component.replace('.vue', '')), `父页面未使用:${component}`) +} + +const typesPath = path.join(createDir, 'types.ts') +const modelPath = path.join(createDir, 'previewModel.ts') +assert.ok(existsSync(typesPath), '缺少向导类型定义') +assert.ok(existsSync(modelPath), '缺少来源映射模型') + +const [typesSource, modelSource, previewSource] = await Promise.all([ + readFile(typesPath, 'utf8'), + readFile(modelPath, 'utf8'), + readFile(path.join(createDir, 'PreviewCompareStep.vue'), 'utf8'), +]) + +for (const field of ['sourceStart', 'sourceEnd', 'originalContent', 'editedContent']) { + assert.ok(typesSource.includes(field), `PreviewItem 缺少字段:${field}`) +} +assert.match(typesSource, /sourceFileId/, 'PreviewItem 缺少来源文件标识') +assert.match(modelSource, /export function buildPreviewItems/, '缺少切片来源映射生成函数') +assert.match(modelSource, /export function sourceLines/, '缺少源文件行偏移生成函数') +assert.match(modelSource, /sourceFileId/, '切片生成没有写入来源文件标识') +assert.match(viewSource, /selectedPreviewFileId/, '父页面缺少当前预览文件状态') +assert.match( + viewSource, + /buildPreviewItems\(file\.content, processType\.value, String\(file\.uid\)\)/, + '预览没有按文件分别生成', +) + +for (const marker of [ + 'preview-workspace', + 'source-viewer', + 'source-line', + 'is-highlighted', + 'preview-item', + 'preview-editor', + 'scrollIntoView', +]) { + assert.ok(previewSource.includes(marker), `第二步缺少结构或行为:${marker}`) +} +assert.match(previewSource, /sourceStart/, '第二步未使用来源起始偏移') +assert.match(previewSource, /sourceEnd/, '第二步未使用来源结束偏移') +assert.match(previewSource, /filterable/, '文件选择器必须可搜索') +assert.match(previewSource, /当前文件/, '预览缺少当前文件切换器') +assert.doesNotMatch(previewSource, /located-badge|sync-label|已定位到/, '源文件栏不应显示冗余定位提示') +assert.match(previewSource, /const PREVIEW_PAGE_SIZE = 6/, '切片列表必须限制每页展示数量') +assert.match(previewSource, /const pagedItems = computed/, '切片列表缺少分页数据') +assert.match(previewSource, /v-for="item in pagedItems"/, '切片列表没有使用分页数据') +assert.match(previewSource, /\(null\)/, '缺少切片编辑模式状态') +assert.match(previewSource, /const editorDraft = ref\(''\)/, '缺少编辑临时草稿') +assert.match(previewSource, /function openEditor\(item: PreviewItem\)/, '列表缺少打开切片编辑器的动作') +assert.match(previewSource, /function closeEditor\(\)/, '编辑器缺少返回列表的动作') +assert.match(previewSource, /function saveEditor\(\)/, '编辑器缺少保存动作') +assert.match(previewSource, /