agent-workspace-ui-smoke-test.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. const assert = require('assert/strict');
  2. const fs = require('fs');
  3. const path = require('path');
  4. const root = path.resolve(__dirname, '..');
  5. const app = fs.readFileSync(path.join(root, 'mcp', 'src', 'dashboard', 'app.js'), 'utf8');
  6. const styles = fs.readFileSync(path.join(root, 'mcp', 'src', 'dashboard', 'styles.css'), 'utf8');
  7. const results = [];
  8. function check(name, fn) {
  9. fn();
  10. results.push({ name, status: 'passed' });
  11. }
  12. function functionSource(name, nextName) {
  13. const start = app.indexOf(`function ${name}`);
  14. const end = app.indexOf(`function ${nextName}`, start + 1);
  15. assert.ok(start >= 0 && end > start, `missing function source: ${name}`);
  16. return app.slice(start, end);
  17. }
  18. check('active Agent view uses a compact command bar and drawers', () => {
  19. const source = functionSource('renderAgentWorkspaceV2', 'responseMonitorRenderSignature');
  20. assert.match(source, /agent-commandbar/);
  21. assert.match(source, /agent-drawer settings/);
  22. assert.match(source, /agent-drawer reminders/);
  23. assert.doesNotMatch(source, /agent-hero|agent-modebar/);
  24. });
  25. check('skills workspace does not receive Agent-only drawers', () => {
  26. const source = functionSource('renderSkillsWorkspace', 'renderSkillsPage');
  27. assert.doesNotMatch(source, /agent-drawer|renderAgentIntakeSettings|renderResponseMonitor/);
  28. });
  29. check('workspace reserves viewport height and internal scrolling', () => {
  30. assert.match(styles, /\.agent-page\s*\{[\s\S]*?height:\s*calc\(100dvh\s*-\s*144px\)/);
  31. assert.match(styles, /\.agent-workspace\s*\{[\s\S]*?min-height:\s*0;[\s\S]*?overflow:\s*hidden;/);
  32. assert.match(styles, /\.agent-list-scroll\s*\{[^}]*overflow-y:\s*auto;/);
  33. assert.match(styles, /\.agent-message-stream\s*\{[\s\S]*?overflow-y:\s*auto;/);
  34. assert.match(styles, /\.agent-insight-panel\s*\{[\s\S]*?overflow-y:\s*auto;/);
  35. });
  36. check('short desktop viewports keep the chat column bounded and usable', () => {
  37. assert.match(styles, /\.agent-chat-panel\s*>\s*\*\s*\{\s*min-width:\s*0;/);
  38. assert.match(styles, /@media\s*\(min-width:\s*761px\)\s*and\s*\(max-height:\s*760px\)/);
  39. assert.match(styles, /\.agent-composer-actions\s*\{\s*align-items:\s*stretch;\s*flex-direction:\s*column;/);
  40. assert.match(styles, /\.agent-review-actions\s*\{\s*flex-wrap:\s*nowrap;/);
  41. });
  42. check('mobile layout is bounded and keeps controls reachable', () => {
  43. const mobile = styles.slice(styles.indexOf('@media (max-width: 760px)'));
  44. assert.match(mobile, /\.agent-workspace\s*\{[^}]*grid-template-columns:\s*1fr;/);
  45. assert.match(mobile, /\.agent-command-actions\s*\{[^}]*overflow-x:\s*auto;/);
  46. assert.match(mobile, /\.agent-drawer\s*\{[^}]*width:\s*100vw;/);
  47. assert.match(mobile, /\.agent-chat-panel\s*\{[^}]*min-height:\s*720px;[^}]*minmax\(160px,\s*1fr\)/);
  48. assert.match(styles, /\.page\.agent-page\s*\{\s*animation:\s*none;\s*transform:\s*none;/);
  49. });
  50. check('closed Agent drawers do not leave a gray page overlay', () => {
  51. assert.match(styles, /\.content\s*>\s*\.agent-page\s*>\s*\*\s*\{\s*animation:\s*none;/);
  52. assert.match(styles, /\.agent-drawer-backdrop\s*\{[^}]*opacity:\s*0;[^}]*pointer-events:\s*none;[^}]*transition:\s*none;/);
  53. assert.match(styles, /\.agent-drawer\s*\{[^}]*visibility:\s*hidden;[^}]*transition:\s*none;/);
  54. assert.match(styles, /\.agent-drawer-backdrop\.open\s*\{[^}]*opacity:\s*1;[^}]*pointer-events:\s*auto;[^}]*transition:\s*none;/);
  55. assert.match(styles, /\.agent-drawer\.open\s*\{[^}]*visibility:\s*visible;[^}]*transition:\s*none;/);
  56. });
  57. check('conversation ordering has explicit invalid-time and stable-tie behavior', () => {
  58. assert.match(app, /Number\.NEGATIVE_INFINITY/);
  59. assert.match(app, /b\.timestamp\s*-\s*a\.timestamp\s*\|\|\s*a\.index\s*-\s*b\.index/);
  60. assert.match(app, /sortAgentItemsByLatest\(conversations\.filter/);
  61. });
  62. check('activity timeline uses existing trace and audit without raw JSON rendering', () => {
  63. const source = functionSource('renderAgentActivityTimeline', 'scrollAgentMessagesToLatest');
  64. assert.match(source, /Plan/);
  65. assert.match(source, /Act/);
  66. assert.match(source, /Observe/);
  67. assert.match(source, /Evaluate/);
  68. assert.doesNotMatch(source, /JSON\.stringify/);
  69. assert.doesNotMatch(app, /JSON\.stringify\(viewModel\.toolTrace\[0\]/);
  70. });
  71. check('Agent theme is semantic and tenant-overridable', () => {
  72. for (const token of ['brand', 'page-bg', 'panel', 'ink', 'line', 'focus', 'success', 'warning', 'error']) {
  73. assert.match(styles, new RegExp(`--agent-${token}:`));
  74. }
  75. assert.match(styles, /\.agent-page\[data-theme="xiaoniu"\]/);
  76. assert.match(app, /page\.dataset\.theme/);
  77. });
  78. check('intake settings use the fixed API contract and explicit confirmation', () => {
  79. assert.match(app, /\/api\/agent\/intake-policy/);
  80. assert.match(app, /welcomeEnabled/);
  81. assert.match(app, /welcomeText/);
  82. assert.match(app, /welcomeSendMode/);
  83. assert.match(app, /ENABLE_AUTO_ENROLL_AUTOPILOT/);
  84. });
  85. console.log(JSON.stringify({ status: 'ok', passed: results.length, results }, null, 2));