import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' import { fileURLToPath } from 'node:url' import path from 'node:path' const scriptDir = path.dirname(fileURLToPath(import.meta.url)) const sourceRoot = path.resolve(scriptDir, '../src') const publicRoot = path.resolve(scriptDir, '../public') const [headerSource, routerSource, guideSource, guideContentSource] = await Promise.all([ readFile(path.join(sourceRoot, 'components/AppHeader.vue'), 'utf8'), readFile(path.join(sourceRoot, 'router/index.ts'), 'utf8'), readFile(path.join(sourceRoot, 'views/guide/GuideView.vue'), 'utf8'), readFile(path.join(sourceRoot, 'views/guide/guideContent.ts'), 'utf8'), ]) assert.match(headerSource, /fa-book/, '顶部栏缺少书状使用文档图标') assert.match(headerSource, /window\.open\([\s\S]*?['"]_blank['"]/, '使用文档应在浏览器新标签页打开') assert.match(headerSource, /aria-label=["']打开使用文档["']/, '使用文档图标缺少无障碍名称') assert.match(routerSource, /path:\s*['"]\/guide['"][\s\S]*?GuideView\.vue/, '缺少独立使用文档路由') assert.match(routerSource, /name:\s*['"]guide['"][\s\S]*?skipPermission:\s*true/, '使用文档应允许所有已登录用户访问') assert.ok(routerSource.indexOf("path: '/guide'") < routerSource.indexOf("path: '/'"), '使用文档不应嵌入主应用布局') assert.match(guideSource, /class="docs-shell"/, '使用文档缺少独立文档站布局') assert.match(guideSource, /class="docs-sidebar"/, '使用文档缺少左侧分级导航') assert.match(guideSource, /class="article-toc"/, '使用文档缺少右侧页内目录') assert.match(guideSource, /v-model="searchQuery"/, '使用文档缺少全局搜索能力') assert.match(guideSource, /scrollIntoView/, '使用文档目录缺少锚点导航能力') assert.match(guideSource, /class="architecture-flow"/, '使用文档缺少架构或流程图') assert.match(guideSource, /articlePresentation\.stepsTitle/, '文档步骤标题应根据页面类型动态展示') assert.match(guideSource, /查看型页面/, '文档缺少查看型页面说明') assert.match(guideSource, /排查型页面/, '文档缺少排查型页面说明') assert.match(guideSource, /class="step-screenshot"/, '使用文档缺少步骤内联页面截图') assert.match(guideSource, /loading="lazy"/, '页面截图应使用懒加载') assert.match(guideSource, /class="image-preview-overlay"/, '页面截图缺少大图预览') assert.match(guideSource, /aria-modal="true"/, '截图预览缺少无障碍对话框语义') assert.match(guideSource, /:inert="previewImage \? true : undefined"/, '截图预览打开时应禁用背景交互') assert.match(guideSource, /event\.key === 'Tab'/, '截图预览缺少键盘焦点循环') assert.match(guideSource, /:width="step\.screenshot\.width"/, '页面截图应使用真实固有尺寸') for (const moduleName of [ '服务看板', '模型训练', '模型评测', '模型推理', '模型管理', '数据集管理', '数据处理', '数据类型转换', '用户设置', '平台性能', '查看日志', ]) { assert.ok(guideContentSource.includes(moduleName), `使用文档缺少模块:${moduleName}`) } assert.match(guideSource, /操作步骤/, '使用文档缺少分步骤教程') assert.match(guideContentSource, /title:\s*'快速开始'/, '使用文档缺少完整快速开始教程') assert.match(guideContentSource, /architecture:/, '使用文档内容缺少架构流程数据') assert.match(guideContentSource, /steps:/, '使用文档内容缺少模块操作步骤') assert.match(guideContentSource, /id:\s*'dashboard'[\s\S]*?mode:\s*'overview'/, '服务看板应标记为查看型页面') assert.match(guideContentSource, /id:\s*'hardware'[\s\S]*?mode:\s*'overview'/, '平台性能应标记为查看型页面') assert.match(guideContentSource, /id:\s*'logs'[\s\S]*?mode:\s*'troubleshooting'/, '查看日志应标记为排查型页面') assert.match(guideContentSource, /screenshot:/, '使用文档步骤缺少真实页面截图引用') assert.match(guideContentSource, /status:\s*'建设中'/, '使用文档应如实标记未开放功能') assert.match(guideSource, /@media \(max-width: 900px\)/, '使用文档缺少窄屏响应式布局') for (const screenshotName of [ 'service-dashboard.png', 'fine-tune-create.jpg', 'training-detail.jpg', 'model-edit.jpg', 'data-process-create.jpg', 'data-process-preview.jpg', ]) { const screenshotPath = path.join(publicRoot, 'guide/screenshots', screenshotName) const screenshot = await readFile(screenshotPath) assert.ok(screenshot.length > 10_000, `页面截图不存在或内容异常:${screenshotName}`) const isValidSignature = screenshotName.endsWith('.png') ? screenshot.subarray(1, 4).toString() === 'PNG' : screenshot[0] === 0xff && screenshot[1] === 0xd8 && screenshot[2] === 0xff assert.ok(isValidSignature, `页面截图扩展名与文件格式不一致:${screenshotName}`) assert.ok( guideContentSource.includes(`/guide/screenshots/${screenshotName}`), `使用文档未引用页面截图:${screenshotName}`, ) } console.log('使用文档回归检查通过')