落地输入合规、探索题库、报告日/时辰刷新、账号头像、OEJTS 量表,并补齐 H5 埋点与 Admin 漏斗;同步 ESS 工件、切至自建 Git、清理 GitHub Actions。 Co-authored-by: Cursor <cursoragent@cursor.com>
103 lines
3.6 KiB
TypeScript
103 lines
3.6 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import { mount, flushPromises } from '@vue/test-utils'
|
|
import { defineComponent, h } from 'vue'
|
|
import AskPage from './AskPage.vue'
|
|
|
|
const { api } = vi.hoisted(() => ({
|
|
api: {
|
|
authMe: vi.fn(),
|
|
listProfiles: vi.fn(),
|
|
getAskQuota: vi.fn(),
|
|
createAskThread: vi.fn(),
|
|
sendAskMessage: vi.fn(),
|
|
sendAskMessageStream: vi.fn(),
|
|
},
|
|
}))
|
|
|
|
vi.mock('../api/client', () => ({ api }))
|
|
|
|
vi.mock('vue-router', () => ({
|
|
useRoute: () => ({ query: {}, fullPath: '/ask' }),
|
|
useRouter: () => ({ back: vi.fn(), push: vi.fn(), replace: vi.fn() }),
|
|
}))
|
|
|
|
const RouterLinkStub = defineComponent({
|
|
props: ['to'],
|
|
setup(props, { slots }) {
|
|
return () => h('a', { href: String(props.to) }, slots.default?.())
|
|
},
|
|
})
|
|
|
|
describe('AskPage', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
api.authMe.mockResolvedValue({ id: 'u1' })
|
|
})
|
|
|
|
it('shows empty state when no profiles', async () => {
|
|
api.listProfiles.mockResolvedValue({ items: [] })
|
|
api.getAskQuota.mockResolvedValue({ remaining: 3, free_limit: 3, active_membership: false, source: 'free' })
|
|
const w = mount(AskPage, { global: { stubs: { RouterLink: RouterLinkStub } } })
|
|
await flushPromises()
|
|
expect(w.text()).toContain('问答')
|
|
expect(w.text()).toContain('AI 成长助手')
|
|
expect(w.text()).toContain('先建立你的个人档案')
|
|
expect(w.text()).toContain('去愈心解码')
|
|
})
|
|
|
|
it('loads profiles and sends a message', async () => {
|
|
api.listProfiles.mockResolvedValue({
|
|
items: [{ id: 'p1', relation: 'self', display_name: '我', birth_date: '1990-01-01' }],
|
|
})
|
|
api.getAskQuota.mockResolvedValue({ remaining: 3, free_limit: 3, active_membership: false, source: 'free' })
|
|
api.createAskThread.mockResolvedValue({ id: 't1', profile_id: 'p1' })
|
|
api.sendAskMessageStream.mockImplementation(
|
|
async (
|
|
_tid: string,
|
|
_content: string,
|
|
handlers: {
|
|
onMeta?: (m: unknown) => void
|
|
onDelta?: (t: string) => void
|
|
onDone?: (p: unknown) => void
|
|
},
|
|
) => {
|
|
handlers.onMeta?.({ id: 'm1', role: 'user', content: '你好', thread_id: 't1', created_at: '' })
|
|
handlers.onDelta?.('结合档案的回复')
|
|
handlers.onDone?.({
|
|
assistant_message: {
|
|
id: 'm2',
|
|
role: 'assistant',
|
|
content: '结合档案的回复',
|
|
thread_id: 't1',
|
|
created_at: '',
|
|
},
|
|
quota: { remaining: 2, free_limit: 3, active_membership: false, source: 'free' },
|
|
})
|
|
},
|
|
)
|
|
|
|
const w = mount(AskPage, { global: { stubs: { RouterLink: RouterLinkStub } } })
|
|
await flushPromises()
|
|
expect(w.text()).toContain('还可聊 3 次')
|
|
await w.find('textarea').setValue('你好')
|
|
await w.find('form').trigger('submit')
|
|
await flushPromises()
|
|
expect(api.sendAskMessageStream).toHaveBeenCalled()
|
|
expect(w.text()).toContain('结合档案的回复')
|
|
expect(w.text()).toContain('还可聊 2 次')
|
|
})
|
|
|
|
it('shows boot error with retry', async () => {
|
|
api.listProfiles.mockRejectedValueOnce(new Error('网络错误'))
|
|
api.getAskQuota.mockRejectedValueOnce(new Error('网络错误'))
|
|
const w = mount(AskPage, { global: { stubs: { RouterLink: RouterLinkStub } } })
|
|
await flushPromises()
|
|
expect(w.text()).toContain('网络错误')
|
|
api.listProfiles.mockResolvedValue({ items: [] })
|
|
api.getAskQuota.mockResolvedValue({ remaining: 3, free_limit: 3, active_membership: false, source: 'free' })
|
|
await w.find('button.retry').trigger('click')
|
|
await flushPromises()
|
|
expect(w.text()).toContain('先建立你的个人档案')
|
|
})
|
|
})
|