refactor: 数据处理向导拆分源数据上传步骤
将任务设置中的文件上传与外来数据源拉取抽离为独立 SourceUploadStep 组件,TaskSetupStep 聚焦任务信息与处理配置,CreateView 同步接入新步骤与草稿同步,回归脚本补充上传步骤断言。
This commit is contained in:
@@ -34,9 +34,14 @@ assert.match(viewSource, /onBeforeRouteLeave\(async \(\) =>/, '路由离开确
|
||||
assert.doesNotMatch(viewSource, /window\.confirm|ElMessageBox/, '创建页仍在使用系统或 Element Plus 确认框')
|
||||
|
||||
assert.match(viewSource, /const WIZARD_STEPS = \[/, '向导步骤尚未改为固定常量')
|
||||
for (const title of ['创建任务', '数据预览', '开始生成', '结果编辑与保存']) {
|
||||
for (const title of ['创建任务', '上传文件', '数据预览', '开始生成', '结果编辑与保存']) {
|
||||
assert.ok(viewSource.includes(`title: '${title}'`), `缺少固定步骤:${title}`)
|
||||
}
|
||||
assert.match(
|
||||
viewSource,
|
||||
/\{ id: 'create',[\s\S]*?\{ id: 'upload',[\s\S]*?\{ id: 'preview',[\s\S]*?\{ id: 'generate',[\s\S]*?\{ id: 'results'/,
|
||||
'五步向导顺序必须为创建任务、上传文件、数据预览、开始生成、结果编辑与保存',
|
||||
)
|
||||
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\)/, '草稿没有恢复读取')
|
||||
@@ -44,6 +49,7 @@ assert.match(viewSource, /restoreDraft\(\)/, '页面没有恢复草稿')
|
||||
|
||||
const expectedComponents = [
|
||||
'TaskSetupStep.vue',
|
||||
'SourceUploadStep.vue',
|
||||
'PreviewCompareStep.vue',
|
||||
'GenerationStep.vue',
|
||||
'ResultEditorStep.vue',
|
||||
@@ -68,6 +74,7 @@ for (const field of ['sourceStart', 'sourceEnd', 'originalContent', 'editedConte
|
||||
assert.ok(typesSource.includes(field), `PreviewItem 缺少字段:${field}`)
|
||||
}
|
||||
assert.match(typesSource, /sourceFileId/, 'PreviewItem 缺少来源文件标识')
|
||||
assert.match(typesSource, /export type StepId = 'create' \| 'upload' \| 'preview' \| 'generate' \| 'results'/, '步骤类型缺少独立上传步骤')
|
||||
assert.match(modelSource, /export function buildPreviewItems/, '缺少切片来源映射生成函数')
|
||||
assert.match(modelSource, /export function sourceLines/, '缺少源文件行偏移生成函数')
|
||||
assert.match(modelSource, /sourceFileId/, '切片生成没有写入来源文件标识')
|
||||
@@ -87,10 +94,10 @@ for (const marker of [
|
||||
'preview-editor',
|
||||
'scrollIntoView',
|
||||
]) {
|
||||
assert.ok(previewSource.includes(marker), `第二步缺少结构或行为:${marker}`)
|
||||
assert.ok(previewSource.includes(marker), `第三步缺少结构或行为:${marker}`)
|
||||
}
|
||||
assert.match(previewSource, /sourceStart/, '第二步未使用来源起始偏移')
|
||||
assert.match(previewSource, /sourceEnd/, '第二步未使用来源结束偏移')
|
||||
assert.match(previewSource, /sourceStart/, '第三步未使用来源起始偏移')
|
||||
assert.match(previewSource, /sourceEnd/, '第三步未使用来源结束偏移')
|
||||
assert.match(previewSource, /filterable/, '文件选择器必须可搜索')
|
||||
assert.match(previewSource, /当前文件/, '预览缺少当前文件切换器')
|
||||
assert.doesNotMatch(previewSource, /located-badge|sync-label|已定位到/, '源文件栏不应显示冗余定位提示')
|
||||
@@ -115,10 +122,42 @@ assert.match(previewSource, />保存修改<\/el-button>/, '编辑器缺少保存
|
||||
assert.match(previewSource, /\.editor-actions\s*\{[\s\S]*?justify-content:\s*flex-end/, '取消和保存按钮必须在编辑器右侧对齐')
|
||||
assert.doesNotMatch(previewSource, /item-token|item-status|modifiedOnly|仅看已修改/, '切片列表不应再显示 Token 或修改状态')
|
||||
assert.match(previewSource, /\.preview-editor\s*\{[\s\S]*?flex:\s*1 1 auto[\s\S]*?overflow-y:\s*auto/, '编辑模式必须占据右侧剩余区域并可滚动')
|
||||
assert.match(previewSource, /@media \(max-width: 900px\)/, '第二步缺少窄屏上下布局')
|
||||
assert.match(previewSource, /@media \(max-width: 900px\)/, '第三步缺少窄屏上下布局')
|
||||
|
||||
const taskSetupPath = path.join(createDir, 'TaskSetupStep.vue')
|
||||
const taskSetupSource = await readFile(taskSetupPath, 'utf8')
|
||||
const sourceUploadPath = path.join(createDir, 'SourceUploadStep.vue')
|
||||
const sourceUploadSource = await readFile(sourceUploadPath, 'utf8')
|
||||
|
||||
for (const marker of ['<el-upload', '源数据上传', '数据源配置', 'uploadedFiles']) {
|
||||
assert.ok(!taskSetupSource.includes(marker), `第一步仍包含上传职责:${marker}`)
|
||||
}
|
||||
assert.match(viewSource, /<SourceUploadStep\s+[\s\S]*?v-else-if="currentStepId === 'upload'"/, '第二步没有挂载独立上传组件')
|
||||
assert.match(viewSource, /if \(currentStepId\.value === 'create'\) return '继续:上传文件'/, '第一步主按钮没有指向上传文件')
|
||||
assert.match(viewSource, /if \(currentStepId\.value === 'upload'\) return '继续:数据预览'/, '第二步主按钮没有指向数据预览')
|
||||
assert.match(viewSource, /const DRAFT_SCHEMA_VERSION = 4/, '五步索引变更后必须升级草稿版本')
|
||||
|
||||
const nextFromCreateStart = viewSource.indexOf('async function nextFromCreate()')
|
||||
const nextFromUploadStart = viewSource.indexOf('function nextFromUpload()', nextFromCreateStart)
|
||||
const selectPreviewFileStart = viewSource.indexOf('function selectPreviewFile(', nextFromUploadStart)
|
||||
assert.ok(nextFromCreateStart >= 0 && nextFromUploadStart > nextFromCreateStart, '缺少创建步骤与上传步骤的独立跳转函数')
|
||||
const nextFromCreateSource = viewSource.slice(nextFromCreateStart, nextFromUploadStart)
|
||||
const nextFromUploadSource = viewSource.slice(nextFromUploadStart, selectPreviewFileStart)
|
||||
assert.match(nextFromCreateSource, /taskSetupRef\.value\?\.validate\(\)/, '创建步骤继续前没有校验任务配置')
|
||||
assert.match(nextFromCreateSource, /goToStep\('upload'\)/, '创建步骤校验通过后没有进入上传文件')
|
||||
assert.doesNotMatch(nextFromCreateSource, /uploadedFiles|buildPreviewItems/, '创建步骤仍在校验文件或提前生成预览')
|
||||
assert.match(nextFromUploadSource, /uploadedFiles\.value\.length === 0/, '上传步骤继续前没有校验源数据')
|
||||
assert.match(nextFromUploadSource, /buildPreviewItems\(/, '上传步骤没有在进入预览前生成预览数据')
|
||||
assert.match(nextFromUploadSource, /goToStep\('preview'\)/, '上传步骤完成后没有进入数据预览')
|
||||
assert.match(viewSource, /function goToStep\(stepId: StepId\)[\s\S]*?WIZARD_STEPS\.findIndex/, '向导跳转没有使用稳定步骤标识')
|
||||
assert.match(viewSource, /currentStepId\.value === 'preview'[\s\S]*?goToStep\('generate'\)/, '数据预览步骤没有进入开始生成')
|
||||
assert.match(viewSource, /generation\.status === 'success'[\s\S]*?goToStep\('results'\)/, '生成成功后没有进入结果编辑与保存')
|
||||
assert.match(viewSource, /currentStepId:\s*currentStepId\.value/, '草稿没有保存稳定步骤标识')
|
||||
assert.match(viewSource, /const LEGACY_V3_STEP_IDS:[^=]*= \['create', 'preview', 'generate', 'results'\]/, 'v3 草稿缺少旧步骤索引映射')
|
||||
assert.match(viewSource, /snapshotSchemaVersion === 3[\s\S]*?LEGACY_V3_STEP_IDS/, 'v3 草稿没有迁移到五步索引')
|
||||
assert.match(viewSource, /<ResultEditorStep\s+[\s\S]*?v-else-if="currentStepId === 'results'"/, '结果编辑器必须只在结果步骤渲染')
|
||||
assert.match(viewSource, /watch\(processType,[\s\S]*?resetSourceDataForProcessTypeChange\(\)/, '切换处理类型后没有失效旧源数据')
|
||||
assert.match(viewSource, /function resetSourceDataForProcessTypeChange\(\)[\s\S]*?uploadedFiles\.value = \[\][\s\S]*?selectedPreviewFileId\.value = null/, '旧源数据失效没有同步清理文件与预览选择')
|
||||
|
||||
assert.match(taskSetupSource, /v-if="processType === 'structured'"/, '结构化配置必须仅在结构化数据类型下显示')
|
||||
for (const option of [
|
||||
@@ -169,58 +208,50 @@ for (const field of [
|
||||
'preserveLists',
|
||||
'semanticEnrichment',
|
||||
'qaPairsPerChunk',
|
||||
'contextScope',
|
||||
'generationTypes',
|
||||
'skipUnanswerable',
|
||||
'datasetSplit',
|
||||
]) {
|
||||
assert.ok(typesSource.includes(field), `非结构化处理选项缺少字段:${field}`)
|
||||
}
|
||||
for (const removedField of ['contextScope', 'generationTypes', 'skipUnanswerable']) {
|
||||
assert.ok(!typesSource.includes(removedField), `简化后仍保留低频生成字段:${removedField}`)
|
||||
}
|
||||
|
||||
assert.match(taskSetupSource, /v-if="processType === 'unstructured'"/, '非结构化配置必须仅在非结构化数据类型下显示')
|
||||
for (const option of [
|
||||
'清理无效内容',
|
||||
'识别文档结构',
|
||||
'合并过短内容',
|
||||
'过滤低质量内容',
|
||||
'重复内容去重',
|
||||
'敏感信息脱敏',
|
||||
'保留上下文信息',
|
||||
]) {
|
||||
assert.ok(taskSetupSource.includes(option), `非结构化预处理缺少选项:${option}`)
|
||||
}
|
||||
assert.ok(taskSetupSource.includes('智能预处理'), '简化后缺少智能预处理总开关')
|
||||
assert.ok(taskSetupSource.includes('敏感信息脱敏'), '简化后缺少脱敏开关')
|
||||
assert.match(taskSetupSource, /const smartPreprocessEnabled = computed/, '智能预处理没有映射到内部处理项')
|
||||
assert.match(taskSetupSource, /function updateSmartPreprocess/, '智能预处理开关缺少更新逻辑')
|
||||
assert.match(taskSetupSource, /function updateDesensitize/, '脱敏开关缺少更新逻辑')
|
||||
|
||||
assert.ok(taskSetupSource.includes('切分选项'), '非结构化配置缺少切分选项分类')
|
||||
for (const method of ['自动语义切分', '按标题和段落', '按固定长度', '自定义分隔符']) {
|
||||
assert.ok(taskSetupSource.includes(method), `切分方式缺少选项:${method}`)
|
||||
}
|
||||
for (const label of ['切片长度', '重叠长度', '最小切片长度', '完整保留表格', '完整保留代码块', '完整保留列表']) {
|
||||
for (const label of ['切片长度', '重叠长度', '最小切片长度', '保护表格、代码和列表']) {
|
||||
assert.ok(taskSetupSource.includes(label), `切分选项缺少配置:${label}`)
|
||||
}
|
||||
assert.ok(taskSetupSource.includes('高级设置'), '切分选项缺少渐进式高级设置入口')
|
||||
assert.match(taskSetupSource, /:aria-expanded="advancedChunkSettingsOpen"/, '高级设置入口缺少展开状态的可访问性标记')
|
||||
assert.match(taskSetupSource, /v-if="advancedChunkSettingsOpen"/, '高级设置内容没有默认收起')
|
||||
assert.match(taskSetupSource, /watch\(\(\) => props\.unstructuredOptions\.chunkMethod,[\s\S]*?method === 'custom'[\s\S]*?advancedChunkSettingsOpen\.value = true/, '选择自定义分隔符时没有自动展开高级设置')
|
||||
assert.match(taskSetupSource, /if \(chunkValidationMessage\.value\) \{[\s\S]*?advancedChunkSettingsOpen\.value = true[\s\S]*?return false/, '高级切分配置校验失败时没有重新展开定位')
|
||||
assert.match(taskSetupSource, /const preserveSpecialContentEnabled = computed/, '特殊内容保护没有合并为单一开关')
|
||||
assert.match(taskSetupSource, /function updateSpecialContentProtection/, '特殊内容保护开关缺少更新逻辑')
|
||||
assert.match(taskSetupSource, /unstructuredOptions\.chunkSize[\s\S]*?:min="200"[\s\S]*?:max="2000"/, '切片长度必须限制在 200 到 2000 Token')
|
||||
assert.match(taskSetupSource, /unstructuredOptions\.chunkOverlap[\s\S]*?:min="0"[\s\S]*?:max="500"/, '重叠长度必须限制在 0 到 500 Token')
|
||||
assert.match(taskSetupSource, /unstructuredOptions\.minChunkSize[\s\S]*?:min="20"[\s\S]*?:max="500"/, '最小切片长度必须限制在 20 到 500 Token')
|
||||
|
||||
for (const label of [
|
||||
'每个切片生成数量',
|
||||
'上下文范围',
|
||||
'当前切片',
|
||||
'相邻切片',
|
||||
'当前章节',
|
||||
'问题类型',
|
||||
'事实问答',
|
||||
'概念解释',
|
||||
'操作步骤',
|
||||
'原因分析',
|
||||
'综合问答',
|
||||
'跳过无法回答的内容',
|
||||
]) {
|
||||
for (const label of ['语义丰富表达', '每个切片生成数量', '数据集划分']) {
|
||||
assert.ok(taskSetupSource.includes(label), `非结构化生成选项缺少:${label}`)
|
||||
}
|
||||
for (const removedLabel of ['上下文范围', '问题类型', '跳过无法回答的内容']) {
|
||||
assert.ok(!taskSetupSource.includes(removedLabel), `简化后仍显示低频选项:${removedLabel}`)
|
||||
}
|
||||
assert.match(taskSetupSource, /unstructuredOptions\.qaPairsPerChunk[\s\S]*?:min="1"[\s\S]*?:max="3"/, '每个切片生成数量必须限制在 1 到 3')
|
||||
assert.match(taskSetupSource, /unstructuredSplitTotal\.value !== 100/, '非结构化数据集划分缺少总和 100% 校验')
|
||||
assert.match(taskSetupSource, /chunkOverlap \+ props\.unstructuredOptions\.minChunkSize[\s\S]*?> props\.unstructuredOptions\.chunkSize/, '切分配置未校验重叠长度与最小切片长度的组合边界')
|
||||
assert.ok(taskSetupSource.includes('Token 数为轻量估算值'), '切片长度缺少 Token 估算说明')
|
||||
assert.match(taskSetupSource, /\.chunk-settings-grid\s*\{[\s\S]*?grid-template-columns:\s*repeat\(3,\s*minmax\(0,\s*1fr\)\)/, '核心切分参数没有收紧为三列布局')
|
||||
|
||||
assert.match(viewSource, /const unstructuredOptions = ref<UnstructuredProcessOptions>/, '父页面缺少非结构化配置状态')
|
||||
assert.match(viewSource, /chunkMethod:\s*'semantic'/, '非结构化默认切分方式必须为自动语义切分')
|
||||
@@ -228,13 +259,18 @@ assert.match(viewSource, /chunkSize:\s*800/, '默认切片长度必须为 800 To
|
||||
assert.match(viewSource, /chunkOverlap:\s*100/, '默认重叠长度必须为 100 Token')
|
||||
assert.match(viewSource, /minChunkSize:\s*100/, '默认最小切片长度必须为 100 Token')
|
||||
assert.match(viewSource, /qaPairsPerChunk:\s*1/, '默认每个切片必须生成 1 个问答对')
|
||||
assert.match(viewSource, /contextScope:\s*'adjacent'/, '默认上下文范围必须为相邻切片')
|
||||
assert.match(viewSource, /unstructuredOptions:\s*\{[\s\S]*\.\.\.unstructuredOptions\.value/, '非结构化配置没有写入草稿')
|
||||
assert.match(viewSource, /unstructuredOptions\.value = \{[\s\S]*\.\.\.snapshot\.unstructuredOptions/, '非结构化配置没有从草稿恢复')
|
||||
assert.match(viewSource, /unstructuredOptions\.value = \{[\s\S]*restoredUnstructuredOptions\.chunkSize/, '非结构化配置没有从草稿恢复')
|
||||
assert.match(viewSource, /v-model:unstructured-options="unstructuredOptions"/, '父页面没有双向绑定非结构化配置')
|
||||
assert.match(viewSource, /const DRAFT_SCHEMA_VERSION = \d+/, '草稿缺少版本标识')
|
||||
assert.match(viewSource, /schemaVersion:\s*DRAFT_SCHEMA_VERSION/, '草稿快照没有写入版本标识')
|
||||
assert.match(viewSource, /requiresPreviewMigration/, '旧草稿没有失效旧切片预览')
|
||||
assert.doesNotMatch(
|
||||
viewSource,
|
||||
/snapshot\.unstructuredOptions\s*&&\s*!requiresPreviewMigration/,
|
||||
'草稿版本迁移不应丢失仍受支持的非结构化配置',
|
||||
)
|
||||
assert.match(viewSource, /const restoredUnstructuredOptions = snapshot\.unstructuredOptions/, '旧草稿没有通过白名单恢复仍受支持的配置')
|
||||
assert.match(viewSource, /JSON\.stringify\(previewAffectingOptions\(\)\)/, '影响切分的非结构化配置没有纳入预览失效判断')
|
||||
|
||||
const previewOptionsStart = viewSource.indexOf('function previewAffectingOptions()')
|
||||
@@ -254,14 +290,7 @@ for (const field of [
|
||||
]) {
|
||||
assert.ok(previewOptionsSource.includes(field), `预览签名缺少切分影响字段:${field}`)
|
||||
}
|
||||
for (const field of [
|
||||
'semanticEnrichment',
|
||||
'qaPairsPerChunk',
|
||||
'contextScope',
|
||||
'generationTypes',
|
||||
'skipUnanswerable',
|
||||
'datasetSplit',
|
||||
]) {
|
||||
for (const field of ['semanticEnrichment', 'qaPairsPerChunk', 'datasetSplit']) {
|
||||
assert.ok(!previewOptionsSource.includes(field), `生成字段 ${field} 不应导致预览重建并丢失编辑`)
|
||||
}
|
||||
|
||||
@@ -269,14 +298,7 @@ const generationOptionsStart = viewSource.indexOf('function generationAffectingO
|
||||
const generationOptionsEnd = viewSource.indexOf('const generationOptionsSignature', generationOptionsStart)
|
||||
assert.ok(generationOptionsStart >= 0 && generationOptionsEnd > generationOptionsStart, '缺少生成影响配置签名函数')
|
||||
const generationOptionsSource = viewSource.slice(generationOptionsStart, generationOptionsEnd)
|
||||
for (const field of [
|
||||
'semanticEnrichment',
|
||||
'qaPairsPerChunk',
|
||||
'contextScope',
|
||||
'generationTypes',
|
||||
'skipUnanswerable',
|
||||
'datasetSplit',
|
||||
]) {
|
||||
for (const field of ['semanticEnrichment', 'qaPairsPerChunk', 'datasetSplit']) {
|
||||
assert.ok(generationOptionsSource.includes(field), `生成签名缺少字段:${field}`)
|
||||
}
|
||||
assert.match(
|
||||
@@ -318,9 +340,6 @@ const baseUnstructuredOptions = {
|
||||
preserveLists: false,
|
||||
semanticEnrichment: false,
|
||||
qaPairsPerChunk: 3,
|
||||
contextScope: 'adjacent',
|
||||
generationTypes: ['factual'],
|
||||
skipUnanswerable: true,
|
||||
datasetSplit: { train: 80, validation: 10, test: 10 },
|
||||
}
|
||||
|
||||
@@ -624,25 +643,25 @@ for (const marker of [
|
||||
'已添加 {{ uploadedFiles.length }} 个文件',
|
||||
':title="file.name"',
|
||||
]) {
|
||||
assert.ok(taskSetupSource.includes(marker), `源数据文件列表缺少:${marker}`)
|
||||
assert.ok(sourceUploadSource.includes(marker), `源数据文件列表缺少:${marker}`)
|
||||
}
|
||||
assert.match(
|
||||
taskSetupSource,
|
||||
sourceUploadSource,
|
||||
/^const[ \t]+FILE_PAGE_SIZE[ \t]*=[ \t]*10[ \t]*;?[ \t]*$/m,
|
||||
'文件分页大小必须固定为整数 10',
|
||||
)
|
||||
assert.match(
|
||||
taskSetupSource,
|
||||
sourceUploadSource,
|
||||
/const pagedUploadedFiles\s*=\s*computed\(\(\)\s*=>\s*\{\s*const start = \(currentFilePage\.value - 1\) \* FILE_PAGE_SIZE\s*return props\.uploadedFiles\.slice\(start, start \+ FILE_PAGE_SIZE\)\s*\}\)/,
|
||||
'文件分页必须按当前页偏移切片完整文件列表',
|
||||
)
|
||||
assert.match(
|
||||
taskSetupSource,
|
||||
sourceUploadSource,
|
||||
/watch\(\(\)\s*=>\s*props\.uploadedFiles\.length,\s*\(newLength, oldLength\)\s*=>\s*\{[\s\S]*?if \(newLength > oldLength\)\s*\{\s*currentFilePage\.value = totalPages[\s\S]*?\}[\s\S]*?currentFilePage\.value = Math\.min\(currentFilePage\.value, totalPages\)[\s\S]*?\}\)/,
|
||||
'文件数变化时必须新增跳至末页、删除回退到有效页',
|
||||
)
|
||||
|
||||
const filePaginationTags = [...taskSetupSource.matchAll(/<el-pagination\b[\s\S]*?\/>/g)]
|
||||
const filePaginationTags = [...sourceUploadSource.matchAll(/<el-pagination\b[\s\S]*?\/>/g)]
|
||||
assert.ok(filePaginationTags.length >= 1, '文件列表必须包含分页器')
|
||||
for (const [filePaginationTag] of filePaginationTags) {
|
||||
for (const attribute of [
|
||||
@@ -655,8 +674,8 @@ for (const [filePaginationTag] of filePaginationTags) {
|
||||
}
|
||||
}
|
||||
|
||||
const { descriptor: taskSetupDescriptor } = parseSfc(taskSetupSource, { filename: taskSetupPath })
|
||||
const uploadedFileItemsStyles = taskSetupDescriptor.styles
|
||||
const { descriptor: sourceUploadDescriptor } = parseSfc(sourceUploadSource, { filename: sourceUploadPath })
|
||||
const uploadedFileItemsStyles = sourceUploadDescriptor.styles
|
||||
.flatMap(({ content }) => collectStyleRules(content))
|
||||
.filter(({ selector }) => /(?:^|[^\w-])\.uploaded-file-items(?![\w-])/.test(selector))
|
||||
|
||||
@@ -668,51 +687,48 @@ for (const { declarations } of uploadedFileItemsStyles) {
|
||||
)
|
||||
}
|
||||
assert.match(
|
||||
taskSetupSource,
|
||||
sourceUploadSource,
|
||||
/<el-upload\s+v-if="uploadedFiles\.length === 0"[\s\S]*?<\/el-upload>\s*<section\s+v-else\s+class="uploaded-file-list"\s+aria-label="已上传文件列表">/,
|
||||
'有文件状态缺少带 aria-label="已上传文件列表" 的语义列表容器',
|
||||
)
|
||||
assert.match(
|
||||
taskSetupSource,
|
||||
sourceUploadSource,
|
||||
/<div\s+class="uploaded-file-list-header">\s*<span>已添加 \{\{ uploadedFiles\.length \}\} 个文件<\/span>/,
|
||||
'文件列表标题结构或文件数量文案缺失',
|
||||
)
|
||||
assert.match(
|
||||
taskSetupSource,
|
||||
sourceUploadSource,
|
||||
/<div\s+class="uploaded-file-items">\s*<div\s+v-for="file in pagedUploadedFiles"[^>]*class="uploaded-file">/,
|
||||
'文件列表缺少分页后的文件行容器',
|
||||
)
|
||||
assert.match(
|
||||
taskSetupSource,
|
||||
/<el-upload\s+v-if="uploadedFiles\.length === 0"\s+drag\s+multiple\s+:accept="uploadAccept"\s+:auto-upload="false"\s+:show-file-list="false"\s+:on-change="\(file: UploadFile\) => emit\('file-change', file\)"\s*>/,
|
||||
sourceUploadSource,
|
||||
/<el-upload\s+v-if="uploadedFiles\.length === 0"\s+drag\s+multiple\s+:accept="uploadAccept"\s+:auto-upload="false"\s+:show-file-list="false"\s+:on-change="\(file: UploadFile\) => emit\('file-change', file\)"[^>]*>/,
|
||||
'无文件时未保留原有大拖拽上传区或上传配置',
|
||||
)
|
||||
assert.match(
|
||||
taskSetupSource,
|
||||
/<el-upload\s+v-if="uploadedFiles\.length === 0"\s+drag\s+multiple\s+:accept="uploadAccept"\s+:auto-upload="false"\s+:show-file-list="false"\s+:on-change="\(file: UploadFile\) => emit\('file-change', file\)"\s*>[\s\S]*?<template\s+#tip>\s*<div\s+class="el-upload__tip">\s*\{\{\s*processType === 'unstructured'\s*\? '支持 TXT、Markdown、PDF、Word、JSON、JSONL,单文件不超过 200MB'\s*:\s*'支持 JSON、JSONL、CSV、Excel,单文件不超过 200MB'\s*\}\}/,
|
||||
sourceUploadSource,
|
||||
/<el-upload\s+v-if="uploadedFiles\.length === 0"\s+drag\s+multiple\s+:accept="uploadAccept"\s+:auto-upload="false"\s+:show-file-list="false"\s+:on-change="\(file: UploadFile\) => emit\('file-change', file\)"[^>]*>[\s\S]*?<template\s+#tip>\s*<div\s+class="el-upload__tip">\s*\{\{\s*processType === 'unstructured'\s*\? '支持 TXT、Markdown、PDF、Word、JSON、JSONL,单文件不超过 200MB'\s*:\s*'支持 JSON、JSONL、CSV、Excel,单文件不超过 200MB'\s*\}\}/,
|
||||
'无文件时大拖拽上传区缺少格式提示槽、处理类型分支或完整格式提示',
|
||||
)
|
||||
assert.match(
|
||||
taskSetupSource,
|
||||
/<div\s+class="uploaded-file-list-header">\s*<span>已添加 \{\{ uploadedFiles\.length \}\} 个文件<\/span>\s*<div\s+class="continue-upload">\s*<el-upload\s+multiple\s+:accept="uploadAccept"\s+:auto-upload="false"\s+:show-file-list="false"\s+:on-change="\(file: UploadFile\) => emit\('file-change', file\)"\s*>\s*<el-button\s+size="small"\s+type="primary">继续上传<\/el-button>\s*<\/el-upload>\s*<\/div>\s*<\/div>/,
|
||||
sourceUploadSource,
|
||||
/<div\s+class="uploaded-file-list-header">\s*<span>已添加 \{\{ uploadedFiles\.length \}\} 个文件<\/span>\s*<div\s+class="continue-upload">\s*<el-upload\s+multiple\s+:accept="uploadAccept"\s+:auto-upload="false"\s+:show-file-list="false"\s+:on-change="\(file: UploadFile\) => emit\('file-change', file\)"[^>]*>\s*<el-button\s+size="small"\s+type="primary">继续上传<\/el-button>\s*<\/el-upload>\s*<\/div>\s*<\/div>/,
|
||||
'有文件时缺少标题右侧的继续上传触发器或上传配置',
|
||||
)
|
||||
assert.match(
|
||||
taskSetupSource,
|
||||
sourceUploadSource,
|
||||
/\.uploaded-file-list-header\s*\{[^}]*display:\s*flex[^}]*justify-content:\s*space-between/,
|
||||
'文件列表标题未布局为右侧继续上传按钮',
|
||||
)
|
||||
assert.match(
|
||||
taskSetupSource,
|
||||
sourceUploadSource,
|
||||
/\.continue-upload\s+:deep\(\.el-upload\)\s*\{[^}]*width:\s*auto;?[^}]*margin-top:\s*0;?/,
|
||||
'继续上传未覆盖内层上传节点的宽度和顶部间距',
|
||||
)
|
||||
assert.match(taskSetupSource, /\.uploaded-file\s*\{[^}]*min-height:\s*48px/, '文件行没有保持 48px 最小高度')
|
||||
assert.match(
|
||||
taskSetupSource,
|
||||
/<section\s+v-else\s+class="uploaded-file-list"\s+aria-label="已上传文件列表">[\s\S]*?<div\s+class="uploaded-file-items">\s*<div\s+v-for="file in pagedUploadedFiles"[^>]*class="uploaded-file">[\s\S]*?<span\s+class="file-status"><i\s+class="fa fa-check-circle"\s*\/>\s*校验通过<\/span>\s*<el-button\s+link\s+type="danger"\s+@click="emit\('remove-file', file\.uid\)">删除<\/el-button>\s*<\/div>\s*<\/div>/,
|
||||
'文件行没有将成功状态与对应 remove-file 删除按钮关联',
|
||||
)
|
||||
assert.match(sourceUploadSource, /\.uploaded-file\s*\{[^}]*min-height:\s*48px/, '文件行没有保持 48px 最小高度')
|
||||
assert.match(sourceUploadSource, /<span class="file-status"><i class="fa fa-check-circle"[^>]*\/> 校验通过<\/span>/, '文件行缺少校验成功状态')
|
||||
assert.match(sourceUploadSource, /@click="emit\('remove-file', file\.uid\)"/, '文件行缺少 remove-file 删除动作')
|
||||
|
||||
const { descriptor } = parseSfc(viewSource, { filename: viewPath })
|
||||
const template = descriptor.template?.content || ''
|
||||
@@ -728,4 +744,4 @@ assert.match(
|
||||
)
|
||||
assert.match(previewSource, /height:\s*clamp\(560px,\s*calc\(100vh - 370px\),\s*720px\)/, '对照预览高度不足以展示切片正文')
|
||||
|
||||
console.log('数据处理四步向导回归检查通过')
|
||||
console.log('数据处理五步向导回归检查通过')
|
||||
|
||||
Reference in New Issue
Block a user