import type { AskThreadProps } from './AskThread' import type { Ask } from './qa' import type { QaEntryView } from './types' import { renderToStaticMarkup } from 'react-dom/server' import { describe, expect, it } from 'vitest' import { AskThread } from './AskThread' const NOW = new Date(2026, 8, 26, 15, 0).getTime() const HISTORY: QaEntryView[] = [ { id: 'e1', question: '为什么普通用户不能看别人的资料?', answer: '这是**权限控制**:不是管理员、又不是查自己时抛 `Forbidden`。', createdAt: new Date(2026, 8, 23, 10, 0).toISOString() }, { id: 'e2', question: 'UserRole 还有哪些角色?', answer: '只有 ADMIN 和 USER。', createdAt: new Date(2026, 8, 25, 10, 0).toISOString() }, ] function render(props: Partial): string { return renderToStaticMarkup( {}} onStop={() => {}} onDelete={() => {}} onClose={() => {}} {...props} />) } const text = (html: string): string => html.replace(/<[^>]*>/g, '') function ask(overrides: Partial): Ask { return { askId: 'a1', anchor: 'x', question: 'Forbidden 抛出去以后前端收到什么?', text: '', status: 'running', ...overrides } } describe('块下面的问答区', () => { it('没问过、没展开:什么都不画', () => { expect(render({})).toBe('') }) it('问过、没展开:只露一行「问过 N 次」,点开才看历史', () => { const html = render({ history: HISTORY }) expect(html).toContain('data-collapsed') expect(text(html)).toBe('问过 2 次') expect(html).toContain('aria-expanded="false"') expect(html).not.toContain(' { const html = render({ open: true, history: HISTORY, asks: [ask({ text: '`Forbidden` 定义在 **core**', activity: '读取 exceptions.py' })] }) expect(html.match(/data-history=""/g)).toHaveLength(2) expect(text(html)).toContain('为什么普通用户不能看别人的资料?3 天前') expect(text(html)).toContain('UserRole 还有哪些角色?昨天') expect(html.match(/title="已理解,删除" aria-label="已理解,删除"/g)).toHaveLength(2) expect(text(html)).toContain('这是权限控制:不是管理员、又不是查自己时抛 Forbidden。') expect(html).toContain('
') expect(html).toContain('data-current=""') expect(html).toContain('data-streaming="">

Forbidden 定义在 core

') expect(text(html)).toContain('读取 exceptions.py') expect(html).toContain('placeholder="继续问… Enter 发送,Esc 收起"') expect(html).toContain('title="停止" aria-label="停止"') expect(html).not.toContain('aria-label="发送"') }) it('还没问过:输入框提示问这块代码,发送按钮在没字时不能点;答完的回答按 Markdown 排、不再闪光标', () => { expect(render({ open: true })).toContain('placeholder="问这块代码… Enter 发送,Esc 收起"') expect(render({ open: true })).toMatch(/aria-label="发送" disabled=""/) const done = render({ open: true, asks: [ask({ status: 'done', text: '答完了' })] }) expect(done).toContain('

答完了

') expect(done).not.toContain('data-streaming') expect(done).not.toContain('role="status"') }) it('出错、停止写在这一问下面', () => { const failed = render({ open: true, asks: [ask({ status: 'failed', error: 'Claude Code 没登录或登录过期了' })] }) expect(failed).toContain('role="alert"') expect(text(failed)).toContain('Claude Code 没登录或登录过期了') expect(text(render({ open: true, asks: [ask({ status: 'stopped', text: '一半' })] }))).toContain('已停止,这次没存。') expect(text(render({ open: true, asks: [ask({})] }))).toContain('正在想…') }) })