'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);
}