import { describe, expect, it } from 'vitest' import { store } from './store' import type { StreamEvent, SurfaceSnapshot } from './contracts' // U4/R5:surface.updated 投影的 revision 守卫——旧修订/重复事件不得覆盖新修订; // 服务端快照是唯一权威(KTD8 单一状态所有者)。 function surfEvent(seq: number, surfaceId: string, revision: number, title: string): StreamEvent { return { seq, conversationId: 'c1', ts: new Date().toISOString(), v: 1, type: 'surface.updated', payload: { identity: { surfaceId, owner: 'alice', pluginId: 'generative-ui', conversationId: 'c1' }, compat: { schemaVersion: 1, catalogVersion: 'ui-v1' }, revision, activationEpoch: 1, spec: { title, nodes: [{ type: 'markdown', id: 'n1', markdown: 'x' }] }, }, } } describe('surface.updated projection', () => { it('新事件入库;旧修订与重复事件不覆盖', () => { store.applyEvent(surfEvent(1, 's1', 1, '初版')) store.applyEvent(surfEvent(2, 's1', 2, '修订二')) store.applyEvent(surfEvent(3, 's1', 1, '迟到的旧修订')) store.applyEvent(surfEvent(4, 's1', 2, '重复事件')) const list = store.getState().surfaces expect(list).toHaveLength(1) expect(list[0].revision).toBe(2) expect(list[0].spec.title).toBe('修订二') }) it('多页面并存,互不覆盖', () => { store.applyEvent(surfEvent(5, 's2', 1, '页面二')) const list = store.getState().surfaces expect(list.filter(x => String(x.identity.surfaceId) === 's1')).toHaveLength(1) expect(list.filter(x => String(x.identity.surfaceId) === 's2')).toHaveLength(1) }) })