onboarding-guide-smoke-test.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. 'use strict';
  2. // 新手闯关引导静态 smoke test
  3. // 无浏览器依赖:校验资源文件、引入顺序、关键锚点与 localStorage 键。
  4. // 运行:node scripts/onboarding-guide-smoke-test.js
  5. const assert = require('assert/strict');
  6. const fs = require('fs');
  7. const path = require('path');
  8. const { spawnSync } = require('child_process');
  9. const ROOT = path.resolve(__dirname, '..');
  10. const DASH = path.join(ROOT, 'mcp', 'src', 'dashboard');
  11. function read(file) {
  12. return fs.readFileSync(path.join(DASH, file), 'utf8');
  13. }
  14. function testStaticAssetsExist() {
  15. ['driver.min.js', 'driver.css', 'onboarding.css', 'onboarding-guide.js'].forEach((name) => {
  16. const stat = fs.statSync(path.join(DASH, name));
  17. assert(stat.size > 0, `${name} 应为非空文件`);
  18. });
  19. }
  20. function testIndexHtmlReferences() {
  21. const html = read('index.html');
  22. // 资源必须被引入,版本号随改动变化,只断言路径前缀
  23. assert.match(html, /\/dashboard\/driver\.css\?v=/);
  24. assert.match(html, /\/dashboard\/onboarding\.css\?v=/);
  25. assert.match(html, /\/dashboard\/driver\.min\.js\?v=/);
  26. assert.match(html, /\/dashboard\/onboarding-guide\.js\?v=/);
  27. // 加载顺序:driver.min.js 必须先于 onboarding-guide.js
  28. const driverIdx = html.indexOf('driver.min.js');
  29. const guideIdx = html.indexOf('onboarding-guide.js');
  30. assert(driverIdx > -1 && guideIdx > driverIdx, 'driver.min.js 应加载于 onboarding-guide.js 之前');
  31. }
  32. function testSidebarEntry() {
  33. const html = read('index.html');
  34. const start = html.indexOf('<a class="nav-item onboarding-nav-entry"');
  35. assert(start > -1, '侧边栏应包含 id="onboarding-nav-entry" 的入口');
  36. const end = html.indexOf('</a>', start);
  37. assert(end > start, '入口 <a> 应闭合');
  38. const entry = html.slice(start, end + 4);
  39. assert(entry.includes('id="onboarding-nav-entry"'), '入口应带 id');
  40. assert(entry.includes('onboarding-progress-badge'), '入口应包含进度徽标');
  41. // 入口绝不能带 data-page(避免被 renderPage 当路由 / setActiveNav 匹配)
  42. assert(!/data-page\s*=/.test(entry), '新手闯关入口不得包含 data-page,否则会与 hash 路由冲突');
  43. }
  44. function testGuideSourceContainsKeyMarkers() {
  45. const src = read('onboarding-guide.js');
  46. ['qiwei-onboarding-v1', '/api/status', '/api/agent/status', '/api/agent/voice/status',
  47. '#btn-subscribe-action', '[data-agent-action="manage-allowlist"]', '#allowlist-contact-list',
  48. '#allowlist-save', '#voice-profile-body', '[data-voice-save]', "ENTRY_ID = 'onboarding-nav-entry'"].forEach((marker) => {
  49. assert(src.includes(marker), `onboarding-guide.js 应包含关键标记:${marker}`);
  50. });
  51. // 关卡完整性:5 个设置关卡(巡览关已改为每页引导)
  52. ['welcome', 'subscribe', 'account', 'allowlist', 'voice'].forEach((id) => {
  53. assert(new RegExp(`id: '${id}'`).test(src), `应定义关卡:${id}`);
  54. });
  55. // 不再存在 tour 关卡
  56. assert(!new RegExp(`id: 'tour'`).test(src), '不应再有关卡 tour(已改为每页引导)');
  57. // 每页引导:注册表 + 引擎 + 自动触发标记
  58. ['PAGE_GUIDES', 'pageSeen', 'runPageGuide', 'maybeAutoPageGuide', 'openPageGuideFromOverview'].forEach((marker) => {
  59. assert(src.includes(marker), `onboarding-guide.js 应包含每页引导标记:${marker}`);
  60. });
  61. // 覆盖全部页面
  62. ['overview', 'analytics', 'agent', 'knowledge', 'skills', 'customer-ops', 'portraits', 'transfers', 'group-ops', 'groups', 'status'].forEach((pg) => {
  63. assert(new RegExp(`['"]?${pg}['"]?: \\{`).test(src), `PAGE_GUIDES 应覆盖页面:${pg}`);
  64. });
  65. }
  66. function testGuideSyntax() {
  67. const result = spawnSync(process.execPath, ['--check', path.join(DASH, 'onboarding-guide.js')], {
  68. encoding: 'utf8',
  69. windowsHide: true,
  70. });
  71. assert.equal(result.status, 0, result.stderr || 'onboarding-guide.js 语法错误');
  72. }
  73. function testPackageJsonScript() {
  74. const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
  75. assert.equal(typeof pkg.scripts['onboarding:smoke'], 'string', 'package.json 应有 onboarding:smoke 脚本');
  76. }
  77. function main() {
  78. testStaticAssetsExist();
  79. testIndexHtmlReferences();
  80. testSidebarEntry();
  81. testGuideSourceContainsKeyMarkers();
  82. testGuideSyntax();
  83. testPackageJsonScript();
  84. process.stdout.write(`${JSON.stringify({ status: 'ok', checks: 6 }, null, 2)}\n`);
  85. }
  86. try { main(); }
  87. catch (error) {
  88. process.stderr.write(`${error.message}\n`);
  89. process.exit(1);
  90. }