65 lines
3.0 KiB
TypeScript
65 lines
3.0 KiB
TypeScript
// ps-frontend-no-project-surfaces-r2:openConversation 在 state.currentProjectId 为空时
|
||
// 不再触发 /api/projects/<id>/surfaces 请求(避免被移除项目成员打开历史会话时的 404 噪音)。
|
||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||
import { api } from '../src/api'
|
||
import { store } from '../src/store'
|
||
import type { Conversation, Message, SurfaceMeta } from '../src/contracts'
|
||
|
||
function detail(id: string) {
|
||
return {
|
||
conversation: { id, projectId: '', title: id, createdAt: '', updatedAt: '' } as Conversation,
|
||
messages: [] as Message[], runs: [], activeHitl: [], lastSeq: 0, resumeSeq: 0,
|
||
}
|
||
}
|
||
class FakeEventSource {
|
||
onopen = null
|
||
onerror = null
|
||
onmessage = null
|
||
addEventListener() {}
|
||
close() {}
|
||
}
|
||
|
||
let projectSurfacesSpy: ReturnType<typeof vi.spyOn>
|
||
|
||
beforeEach(async () => {
|
||
vi.stubGlobal('EventSource', FakeEventSource)
|
||
vi.spyOn(api, 'conversationDetail').mockImplementation(async id => detail(id))
|
||
vi.spyOn(api, 'getPlan').mockResolvedValue(null)
|
||
vi.spyOn(api, 'getTodos').mockResolvedValue(null)
|
||
vi.spyOn(api, 'listCards').mockResolvedValue([])
|
||
vi.spyOn(api, 'listTasks').mockResolvedValue([])
|
||
vi.spyOn(api, 'listArtifacts').mockResolvedValue([])
|
||
vi.spyOn(api, 'surfaceList').mockResolvedValue([])
|
||
projectSurfacesSpy = vi.spyOn(api, 'projectSurfaces').mockResolvedValue([])
|
||
vi.spyOn(api, 'listProjectCards').mockResolvedValue([])
|
||
// 还原至 currentProjectId = null 的干净投影,避免别的套件残留影响本次断言。
|
||
store.resetIdentity()
|
||
})
|
||
afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks(); vi.unstubAllGlobals() })
|
||
|
||
describe('openConversation projectSurfaces gating', () => {
|
||
it('skips the /api/projects/.../surfaces request and keeps projectSurfaces empty when no project is current', async () => {
|
||
expect(store.getState().currentProjectId).toBeNull()
|
||
await store.openConversation('cnv_no_project')
|
||
// 让微任务(包含 surfaceList/Stream 等链)落定,但不改变当前结论。
|
||
await new Promise(r => setTimeout(r, 0))
|
||
expect(projectSurfacesSpy).not.toHaveBeenCalled()
|
||
expect(store.getState().projectSurfaces).toEqual([])
|
||
expect(store.getState().currentProjectId).toBeNull()
|
||
})
|
||
|
||
it('still requests /api/projects/<id>/surfaces when a project is current', async () => {
|
||
const metas: SurfaceMeta[] = [{ surfaceId: 'surf_1', title: '页面一', pluginId: 'plg_1', revision: 1, updatedAt: '2026-10-12T00:00:00Z' }]
|
||
projectSurfacesSpy.mockResolvedValueOnce(metas)
|
||
await store.openProject('prj_with_surfaces')
|
||
expect(store.getState().currentProjectId).toBe('prj_with_surfaces')
|
||
projectSurfacesSpy.mockClear()
|
||
await store.openConversation('cnv_with_project')
|
||
expect(projectSurfacesSpy).toHaveBeenCalledTimes(1)
|
||
expect(projectSurfacesSpy.mock.calls[0][0]).toBe('prj_with_surfaces')
|
||
// 微任务处理完 .then 后,projectSurfaces 投影被填进服务端的元数据列表。
|
||
await new Promise(r => setTimeout(r, 0))
|
||
expect(store.getState().projectSurfaces).toEqual(metas)
|
||
})
|
||
})
|