'use strict'; // 新手闯关引导静态 smoke test // 无浏览器依赖:校验资源文件、引入顺序、关键锚点与 localStorage 键。 // 运行:node scripts/onboarding-guide-smoke-test.js const assert = require('assert/strict'); const fs = require('fs'); const path = require('path'); const { spawnSync } = require('child_process'); const ROOT = path.resolve(__dirname, '..'); const DASH = path.join(ROOT, 'mcp', 'src', 'dashboard'); function read(file) { return fs.readFileSync(path.join(DASH, file), 'utf8'); } function testStaticAssetsExist() { ['driver.min.js', 'driver.css', 'onboarding.css', 'onboarding-guide.js'].forEach((name) => { const stat = fs.statSync(path.join(DASH, name)); assert(stat.size > 0, `${name} 应为非空文件`); }); } function testIndexHtmlReferences() { const html = read('index.html'); // 资源必须被引入,版本号随改动变化,只断言路径前缀 assert.match(html, /\/dashboard\/driver\.css\?v=/); assert.match(html, /\/dashboard\/onboarding\.css\?v=/); assert.match(html, /\/dashboard\/driver\.min\.js\?v=/); assert.match(html, /\/dashboard\/onboarding-guide\.js\?v=/); // 加载顺序:driver.min.js 必须先于 onboarding-guide.js const driverIdx = html.indexOf('driver.min.js'); const guideIdx = html.indexOf('onboarding-guide.js'); assert(driverIdx > -1 && guideIdx > driverIdx, 'driver.min.js 应加载于 onboarding-guide.js 之前'); } function testSidebarEntry() { const html = read('index.html'); const start = html.indexOf(' -1, '侧边栏应包含 id="onboarding-nav-entry" 的入口'); const end = html.indexOf('', start); assert(end > start, '入口 应闭合'); const entry = html.slice(start, end + 4); assert(entry.includes('id="onboarding-nav-entry"'), '入口应带 id'); assert(entry.includes('onboarding-progress-badge'), '入口应包含进度徽标'); // 入口绝不能带 data-page(避免被 renderPage 当路由 / setActiveNav 匹配) assert(!/data-page\s*=/.test(entry), '新手闯关入口不得包含 data-page,否则会与 hash 路由冲突'); } function testGuideSourceContainsKeyMarkers() { const src = read('onboarding-guide.js'); ['qiwei-onboarding-v1', '/api/status', '/api/agent/status', '/api/agent/voice/status', '#btn-subscribe-action', '[data-agent-action="manage-allowlist"]', '#allowlist-contact-list', '#allowlist-save', '[data-agent-action="activate-onboarding"]', '#voice-profile-body', '[data-voice-save]', "ENTRY_ID = 'onboarding-nav-entry'", "'activate'"].forEach((marker) => { assert(src.includes(marker), `onboarding-guide.js 应包含关键标记:${marker}`); }); // 关卡完整性:含首次登录后的监听与自动回复启用步骤。 ['welcome', 'subscribe', 'account', 'allowlist', 'activate', 'voice'].forEach((id) => { assert(new RegExp(`id: '${id}'`).test(src), `应定义关卡:${id}`); }); // 不再存在 tour 关卡 assert(!new RegExp(`id: 'tour'`).test(src), '不应再有关卡 tour(已改为每页引导)'); // 每页引导:注册表 + 引擎 + 自动触发标记 ['PAGE_GUIDES', 'pageSeen', 'runPageGuide', 'maybeAutoPageGuide', 'openPageGuideFromOverview'].forEach((marker) => { assert(src.includes(marker), `onboarding-guide.js 应包含每页引导标记:${marker}`); }); // 覆盖全部页面 ['overview', 'analytics', 'agent', 'knowledge', 'skills', 'customer-ops', 'portraits', 'transfers', 'group-ops', 'groups', 'status'].forEach((pg) => { assert(new RegExp(`['"]?${pg}['"]?: \\{`).test(src), `PAGE_GUIDES 应覆盖页面:${pg}`); }); } function testGuideSyntax() { const result = spawnSync(process.execPath, ['--check', path.join(DASH, 'onboarding-guide.js')], { encoding: 'utf8', windowsHide: true, }); assert.equal(result.status, 0, result.stderr || 'onboarding-guide.js 语法错误'); } function testPackageJsonScript() { const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')); assert.equal(typeof pkg.scripts['onboarding:smoke'], 'string', 'package.json 应有 onboarding:smoke 脚本'); } function main() { testStaticAssetsExist(); testIndexHtmlReferences(); testSidebarEntry(); testGuideSourceContainsKeyMarkers(); testGuideSyntax(); testPackageJsonScript(); process.stdout.write(`${JSON.stringify({ status: 'ok', checks: 6 }, null, 2)}\n`); } try { main(); } catch (error) { process.stderr.write(`${error.message}\n`); process.exit(1); }