Files
PlantAI-rag-new/ui/tests/r3debug/skipReveal.test.js
adminandClaude Fable 5 53069d4940 fix(ui): 复测缺陷 r3-07-01/r3-52-02 + b3 测试债
- r3-07-01 「跳过」无效:补齐事件转发链 MessageList(新增
  skip-reveal emit)→ ChatPanel(@skip-reveal 监听)→ store.skipReveal();
  新增 skipReveal.test.js 三层断言(点击定格终态/组件转发/ChatPanel 监听)
- r3-52-02 终态文案残留:docgen 轮询 tick 的 done 分支把 generation 步
  desc 还原 DOCGEN_STEPS 默认文案「按大纲逐节生成正文」;docgenPoll
  done 用例改两阶段 mock(running 进度文案 → done 还原)复现原缺陷
- 测试债:b3 bench.store.test.js 删除已下线 sessionSourceItems 引用,
  按 #27 修复后语义改断言(后端去重结果原样透出)

自测:r3debug 32 项 + 全套 373 项 + private_docs 23 项全绿;
playwright 实测(mock SSE/docgen 202):跳过按钮点击后全文定格、
skip-btn/cursor 消失;docgen done 后第 4 步 desc 还原默认文案;无 JS 错误。
未 push。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-10 18:01:01 +08:00

152 lines
5.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @vitest-environment jsdom
// tests/r3debug/skipReveal.test.js —— r3-07-01「跳过」按钮事件链全链路
// 断言:AssistantMessage skip-btn 点击 → MessageList 转发 skip-reveal →
// ChatPanel 落 store.skipReveal() → 消息定格终态(stream 块换 text 块)。
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mount, flushPromises } from '@vue/test-utils';
import { setActivePinia, createPinia } from 'pinia';
// jsdom 无 scrollTo:polyfill(MessageList 挂载滚动用)
if (typeof window !== 'undefined' && typeof window.Element !== 'undefined' && !window.Element.prototype.scrollTo) {
window.Element.prototype.scrollTo = vi.fn();
}
vi.mock('../../src/stores/health.js', () => ({
useHealthStore: vi.fn(() => ({ data: null, qaOk: true })),
}));
vi.mock('../../src/api/sessions.js', () => ({
listSessions: vi.fn(async () => []),
createSession: vi.fn(async () => ({ id: 's1' })),
getSession: vi.fn(async () => ({ id: 's1', status: 'done', meta: {}, messages: [] })),
updateSession: vi.fn(async (id, b) => ({ id, ...b })),
deleteSession: vi.fn(async () => {}),
createMessage: vi.fn(async () => ({ id: 'm1' })),
updateMessage: vi.fn(async () => ({ updated_at: new Date().toISOString() })),
}));
vi.mock('../../src/api/qa.js', () => ({
chatStream: vi.fn(async () => {}),
chat: vi.fn(),
search: vi.fn(),
info: vi.fn(),
}));
vi.mock('../../src/api/private.js', () => ({
askPrivate: vi.fn(async () => ({ hit: false })),
}));
vi.mock('../../src/api/docgen.js', () => ({
generate: vi.fn(),
getTask: vi.fn(),
listTasks: vi.fn(),
getArtifactContent: vi.fn(async () => ({ markdown: '' })),
getArtifactHtml: vi.fn(async () => ({ ok: true, html: '', css: '' })),
saveArtifactContent: vi.fn(async () => ({})),
editArtifact: vi.fn(async () => ({})),
exportArtifact: vi.fn(async () => ({})),
uploadLibrary: vi.fn(),
listLibrary: vi.fn(async () => ({ items: [] })),
removeLibrary: vi.fn(async () => ({ ok: true })),
}));
import ChatPanel from '../../src/components/chat/ChatPanel.vue';
import MessageList from '../../src/components/chat/MessageList.vue';
import AssistantMessage from '../../src/components/chat/AssistantMessage.vue';
import { useChatStore } from '../../src/stores/chat.js';
import { useSessionsStore } from '../../src/stores/sessions.js';
/** 构造揭示中的会话态:aMsg 带 stream 块 + ctx.reveal */
function seedRevealing(chat) {
const msgList = [
{ id: 'u1', role: 'user', status: 'done', text: '问题', blocks: [{ type: 'text', html: '问题' }] },
{
id: 'a1',
role: 'assistant',
kind: 'qa',
status: 'streaming',
streamErr: null,
revealId: 1, // revealText 正常流程会写入;skipReveal 据此定位目标消息
blocks: [
{ type: 'pipeline', status: 'running', steps: [] },
{ type: 'stream', text: '部分内容' },
],
},
];
chat.sessionId = 's1';
chat.messages = msgList;
chat.sessionMsgs['s1'] = msgList;
const aMsg = msgList[1];
chat.streamCtx['s1'] = {
sessionId: 's1',
msgList,
aMsg,
controller: null,
completedOnce: false,
events: [],
checkpointTimer: null,
lastCheckpointAt: 0,
reveal: { text: '全文内容', aMsg },
abortAnn: null,
};
return aMsg;
}
async function mountPanel() {
const w = mount(ChatPanel, { global: { stubs: { teleport: true } } });
await flushPromises();
return w;
}
describe('r3-07-01 跳过按钮事件链', () => {
beforeEach(() => {
setActivePinia(createPinia());
const sessions = useSessionsStore();
sessions.items = [{ id: 's1', title: 't', status: 'done', created_at: '', updated_at: '' }];
});
it('全链路:skip-btn 点击 → MessageList 转发 → ChatPanel → store.skipReveal 定格终态', async () => {
const chat = useChatStore();
seedRevealing(chat);
const w = await mountPanel();
// 揭示中:skip-btn 存在
const skipBtn = w.find('.skip-btn');
expect(skipBtn.exists()).toBe(true);
await skipBtn.trigger('click');
await flushPromises();
// store 效果:stream 块移除、text 块定格、状态 done、reveal 清空
const aMsg = chat.messages[1];
expect(aMsg.status).toBe('done');
expect(aMsg.blocks.some((b) => b.type === 'stream')).toBe(false);
expect(aMsg.blocks.some((b) => b.type === 'text')).toBe(true);
expect(chat.reveal).toBeNull();
});
it('MessageList 层:AssistantMessage skip-reveal 事件被转发为同名 emit', async () => {
const chat = useChatStore();
seedRevealing(chat);
const w = mount(MessageList, {
props: { messages: chat.messages, busy: false, reveal: chat.reveal, scrollSeq: 0 },
global: { stubs: { transition: false } },
});
const am = w.findComponent(AssistantMessage);
expect(am.exists()).toBe(true);
await am.vm.$emit('skip-reveal');
expect(w.emitted('skip-reveal')).toBeTruthy();
expect(w.emitted('skip-reveal')).toHaveLength(1);
});
it('ChatPanel 层:MessageList skip-reveal 事件被监听(组件契约存在)', async () => {
const chat = useChatStore();
seedRevealing(chat);
const w = await mountPanel();
const ml = w.findComponent(MessageList);
expect(ml.exists()).toBe(true);
// 直接对 MessageList 发事件:ChatPanel 的 @skip-reveal 监听应落 store
await ml.vm.$emit('skip-reveal');
await flushPromises();
const aMsg = chat.messages[1];
expect(aMsg.status).toBe('done');
expect(aMsg.blocks.some((b) => b.type === 'text')).toBe(true);
});
});