Files
YG_FT/frontend/scripts/regression-dataset-preview.mjs
caoxiaozhu 0d82930181 feat: 数据集版本管理
新增数据集版本创建、切换、激活与历史追溯能力,Mock 通过 localStorage 持久化版本状态并接入版本接口路由,预览页支持版本对比与内容回滚,列表补充版本入口,回归脚本扩充断言。
2026-07-13 10:31:20 +08:00

123 lines
8.3 KiB
JavaScript

import fs from 'node:fs'
import path from 'node:path'
import ts from 'typescript'
const root = process.cwd()
const previewPath = path.join(root, 'src/views/dataset/DatasetPreviewView.vue')
const apiPath = path.join(root, 'src/api/modules/dataset.ts')
const adapterPath = path.join(root, 'src/mock/adapter.ts')
const mockPath = path.join(root, 'src/mock/data.ts')
const recordsPath = path.join(root, 'src/views/dataset/datasetRecords.ts')
const versionsPath = path.join(root, 'src/mock/datasetVersions.ts')
const source = fs.readFileSync(previewPath, 'utf8')
const apiSource = fs.readFileSync(apiPath, 'utf8')
const adapterSource = fs.readFileSync(adapterPath, 'utf8')
const mockSource = fs.readFileSync(mockPath, 'utf8')
const recordsSource = fs.readFileSync(recordsPath, 'utf8')
const versionsSource = fs.readFileSync(versionsPath, 'utf8')
const recordsModuleCode = ts.transpileModule(recordsSource, {
compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 },
}).outputText
const { parseDatasetRecords, updateDatasetRecord } = await import(
`data:text/javascript;base64,${Buffer.from(recordsModuleCode).toString('base64')}`
)
const versionsModuleCode = ts.transpileModule(versionsSource, {
compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 },
}).outputText
const {
activateDatasetVersion,
appendDatasetVersion,
createInitialVersionState,
getActiveDatasetVersion,
} = await import(`data:text/javascript;base64,${Buffer.from(versionsModuleCode).toString('base64')}`)
function expect(condition, message) {
if (!condition) throw new Error(message)
}
expect(source.includes('class="preview-workspace"'), '详情页应提供数据文件工作区')
expect(!source.includes('class="file-pane"'), '单文件数据集详情不应展示文件选择侧栏')
expect(source.includes('class="code-line"'), '内容查看器应提供逐行展示')
expect(source.includes('class="line-number"'), '内容查看器应显示行号')
expect(source.includes('previewLoading'), '切换文件时应提供独立加载状态')
expect(source.includes('previewError'), '内容加载失败时应提供错误状态')
expect(source.includes('class="records-viewer"'), '详情页应将结构化文件展示为样本列表')
expect(source.includes('class="record-table"'), '样本数据应使用企业级表格展示')
expect(source.includes('fixed="right"'), '逐条编辑操作列应固定在表格右侧')
expect(source.includes('v-for="fieldKey in tableFieldKeys"'), '企业表格应根据数据结构动态生成字段列')
expect(source.includes('openRecordEditor(asDatasetRecord(row))'), '每条样本应提供独立编辑入口')
expect(source.includes('暂存修改'), '单条编辑应先暂存到当前页面')
expect(source.includes('hasPendingVersionChanges && isViewingActiveVersion'), '存在暂存修改时才应显示保存版本按钮')
expect(source.includes('保存版本'), '文件工具栏应提供保存版本按钮')
expect(source.includes('baseVersionContent'), '页面应区分已保存版本与待保存工作副本')
expect(source.includes('class="version-control-bar"'), '详情页应提供独立版本控制区')
expect(source.includes('设为当前版本'), '历史版本应支持显式切换为当前版本')
expect(source.includes('历史版本(只读)'), '历史版本应明确展示只读状态')
expect(source.includes('loadedVersionId.value'), '下载和展示应绑定正在查看的具体版本')
expect(source.includes('versionRequestId'), '快速切换历史版本时应防止旧响应覆盖新内容')
expect(source.includes('v-model:current-page="currentPage"'), '样本列表应支持分页浏览')
expect(!source.includes('class="content-editor"'), '详情页不应继续提供整文件编辑器')
expect(source.includes('saveRecord'), '逐条编辑器应提供单条保存动作')
expect(source.includes('hasUnsavedChanges'), '在线编辑器应跟踪未保存修改')
expect(source.includes('onBeforeRouteLeave'), '离开页面时应保护未保存修改')
expect(source.includes("event.key.toLowerCase() === 's'"), '在线编辑器应支持快捷键保存')
expect(source.includes('loadVersions(selectedFile)'), '版本内容加载失败后应支持重新加载')
expect(!source.includes('handleDownloadAll'), '页面不应保留整包下载逻辑')
expect(!source.includes('handleDelete'), '页面不应保留删除逻辑')
expect(!source.includes('router.back()'), '页面不应保留页头返回逻辑')
expect(mockSource.includes('files: ['), 'Mock 数据集应包含可验收的文件列表')
expect(mockSource.includes('mockDatasetPreviews'), 'Mock 数据应包含文件预览内容')
expect(mockSource.includes("'mock-jsonl'"), 'Mock 数据应为通用数据集提供模拟样本')
expect(mockSource.includes("'mock-readme'"), 'Mock 数据应为通用数据集提供模拟说明')
expect(mockSource.includes('.map((dataset)'), '所有 Mock 数据集都应自动补齐预览文件')
expect(mockSource.includes(': [{ id: `dataset-${dataset.id}-samples`'), '每个 Mock 数据集应只对应一个数据文件')
expect(apiSource.includes('createDatasetFileVersion'), '数据集 API 应提供创建新版本方法')
expect(apiSource.includes('activateDatasetFileVersion'), '数据集 API 应提供切换当前版本方法')
expect(apiSource.includes('getDatasetFileVersionContent'), '数据集 API 应支持读取历史版本内容')
expect(apiSource.includes('expected_current_version_id'), '创建和激活版本应携带乐观锁版本指针')
expect(!apiSource.includes('updateDatasetFileContent'), '数据编辑不应继续覆盖历史版本内容')
expect(adapterSource.includes('mock:dataset-versions:'), 'Mock 版本历史应支持刷新持久化')
expect(adapterSource.includes('当前版本已被其他用户更新'), 'Mock 应拒绝基于过期版本的并发覆盖')
expect(adapterSource.includes('历史版本不可覆盖'), '旧的覆盖保存接口应明确拒绝修改历史版本')
expect(adapterSource.includes('Mock 存储空间不足'), 'Mock 存储失败时应返回明确错误且保留编辑草稿')
const jsonl = [
'{"instruction":"第一条","output":"A"}',
'',
'{"instruction":"第二条","output":["B","C"]}',
].join('\n')
const parsed = parseDatasetRecords(jsonl, 'samples.jsonl')
expect(parsed.supported && parsed.records.length === 2, 'JSONL 应跳过空行并解析为逐条样本')
expect(parsed.records[1].sourceIndex === 2, '样本应保留原文件物理行位置')
const updated = updateDatasetRecord(jsonl, 'samples.jsonl', 2, {
instruction: '第二条已修改',
output: ['B', 'C'],
})
const beforeLines = jsonl.split('\n')
const afterLines = updated.split('\n')
expect(afterLines[0] === beforeLines[0] && afterLines[1] === '', '单条保存不得改写其他样本或空行')
expect(JSON.parse(afterLines[2]).instruction === '第二条已修改', '单条保存应准确更新目标样本')
expect(Array.isArray(JSON.parse(afterLines[2]).output), '单条保存不应丢失数组等非字符串字段类型')
const invalid = parseDatasetRecords('{invalid json}', 'samples.jsonl')
expect(invalid.records[0].kind === 'invalid' && invalid.records[0].error, '非法 JSON 行应被单独标记并给出错误')
const repairedJson = updateDatasetRecord('{invalid json}', 'samples.json', 0, { repaired: true })
expect(JSON.parse(repairedJson).repaired === true, '非法 JSON 文件应支持按单条方式修复')
const v1State = createInitialVersionState('A', '2026-07-13T00:00:00Z')
const v2State = appendDatasetVersion(v1State, 'B', '2026-07-13T01:00:00Z', '修改第一条')
expect(v2State !== v1State, '创建版本应返回新的不可变状态')
expect(v1State.versions.length === 1 && v1State.versions[0].content === 'A', '创建新版本不得修改历史版本')
expect(v2State.versions[1].version === 2 && v2State.active_version_id === 'v2', '新版本编号应递增并自动激活')
const switchedToV1 = activateDatasetVersion(v2State, 'v1')
expect(getActiveDatasetVersion(switchedToV1).content === 'A', '切换当前版本后应读取对应历史内容')
expect(v2State.active_version_id === 'v2' && v2State.versions[1].content === 'B', '切换版本不得污染原状态或历史快照')
let invalidVersionRejected = false
try {
activateDatasetVersion(v2State, 'missing')
} catch {
invalidVersionRejected = true
}
expect(invalidVersionRejected, '不存在的版本不得切换为当前版本')
console.log('dataset preview regression checks passed')