import { afterEach, describe, expect, it, vi } from 'vitest';
import { postWorkbenchMessage } from '@/lib/workbench/session-store';
import type { ElementRef } from '@/lib/workbench/element-refs';
import { useElementRefsStore } from '@/lib/store/element-refs';
import { settleSentElementRefs } from '@/lib/workbench/element-ref-send-result';
const material = {
materialId: 'mat_00000000000000000000000000',
name: '讲义.pdf',
bytes: 5,
mimeType: 'application/pdf',
extractionStatus: 'idle' as const,
};
const elementRef: ElementRef = {
kind: 'slide-element',
stageId: 'stage-1',
sceneId: 'scene-1',
elementId: 'title-1',
elementType: 'text',
label: '文本 · 折射定律',
snapshotText: '折射定律',
};
function captureFetch(responseBody?: unknown) {
const calls: Array<{ url: string; init: RequestInit }> = [];
const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
calls.push({ url: String(input), init: init ?? {} });
return responseBody === undefined
? new Response(null, { status: 202 })
: Response.json(responseBody, { status: 202 });
});
vi.stubGlobal('fetch', fetchMock);
return calls;
}
afterEach(() => {
vi.unstubAllGlobals();
useElementRefsStore.setState({
ownerSessionId: null,
refs: [],
hovered: null,
nextGeneration: 1,
});
});
describe('postWorkbenchMessage element refs', () => {
* The zero-regression pin. The control plane rejects unknown fields and no
* message sends refs today, so an unconditional `elementRefs: []` would break
* every existing send. These two assertions are byte-exact on purpose: a
* reordered or always-present key is exactly the failure they exist to catch.
*/
it('sends a body identical to the pre-refs shape when nothing is staged', async () => {
const calls = captureFetch();
await postWorkbenchMessage('sess-1', '第三页换个例子');
expect(calls[0].init.body).toBe(JSON.stringify({ text: '第三页换个例子' }));
});
it('keeps the materials-only body byte-identical too', async () => {
const calls = captureFetch();
await postWorkbenchMessage('sess-1', '读这份 PDF', [material]);
expect(calls[0].init.body).toBe(
JSON.stringify({ text: '读这份 PDF', materialIds: [material.materialId] }),
);
});
it('omits the field for an explicitly empty ref list', async () => {
const calls = captureFetch();
await postWorkbenchMessage('sess-1', 'hi', [], []);
expect(calls[0].init.body).toBe(JSON.stringify({ text: 'hi' }));
expect(String(calls[0].init.body)).not.toContain('elementRefs');
});
it('carries the staged refs verbatim, after materialIds', async () => {
const calls = captureFetch({ elementRefsAccepted: true });
const result = await postWorkbenchMessage('sess-1', '把标题改短', [material], [elementRef]);
expect(calls[0].init.body).toBe(
JSON.stringify({
text: '把标题改短',
materialIds: [material.materialId],
elementRefs: [elementRef],
}),
);
expect(calls[0].url).toBe('/api/agent/sessions/sess-1/messages');
expect(result).toEqual({ elementRefsAccepted: true, courseRefsAccepted: false });
});
it('warns and retains refs when an old route returns 202 without a capability receipt', async () => {
captureFetch();
const store = useElementRefsStore.getState();
store.attachOwner('sess-1');
store.add(elementRef);
const sent = useElementRefsStore.getState().refs;
const warnUnsupported = vi.fn();
const result = await postWorkbenchMessage('sess-1', '把标题改短', [], sent);
settleSentElementRefs({
sessionId: 'sess-1',
sent,
elementRefsAccepted: result.elementRefsAccepted,
warnUnsupported,
});
expect(warnUnsupported).toHaveBeenCalledOnce();
expect(useElementRefsStore.getState().refs).toHaveLength(1);
});
it('clears the sent refs normally when the route returns the capability receipt', async () => {
captureFetch({ elementRefsAccepted: true });
const store = useElementRefsStore.getState();
store.attachOwner('sess-1');
store.add(elementRef);
const sent = useElementRefsStore.getState().refs;
const warnUnsupported = vi.fn();
const result = await postWorkbenchMessage('sess-1', '把标题改短', [], sent);
settleSentElementRefs({
sessionId: 'sess-1',
sent,
elementRefsAccepted: result.elementRefsAccepted,
warnUnsupported,
});
expect(warnUnsupported).not.toHaveBeenCalled();
expect(useElementRefsStore.getState().refs).toEqual([]);
});
});