import { describe, expect, it } from 'vitest'; import { buildGameAssetReviewBatchMessage, buildGameAssetReviewActionMessage, buildGameAssetReviewMarker, buildGameAssetConfirmationMessage, findGameAssetRoundConfirmation, GAME_ASSET_SELECTION_MARKER, hasGameAssetReviewContent, hasGameAssetSelectionMarker, parseGameAssetReviewAction, parseGameAssetReviewActions, parseGameAssetReviewInvocation, stripGameAssetSelectionMarker, } from '@/lib/game-asset-selection-message'; describe('conversation-guided game asset selection', () => { it('detects and hides the assistant selection marker', () => { const message = { role: 'assistant' as const, content: `候选准备好了。\n${GAME_ASSET_SELECTION_MARKER}` }; expect(hasGameAssetSelectionMarker(message)).toBe(true); expect(stripGameAssetSelectionMarker(String(message.content))).toBe('候选准备好了。'); }); it('round-trips an Agent-filled review marker and user action', () => { const assistant = { role: 'assistant' as const, content: `候选准备好了。\n${buildGameAssetReviewMarker('review-1', ['hero', 'jump'])}` }; expect(hasGameAssetSelectionMarker(assistant)).toBe(true); expect(parseGameAssetReviewInvocation(assistant)).toEqual({ invocationId: 'review-1', candidateIds: ['hero', 'jump'] }); expect(stripGameAssetSelectionMarker(String(assistant.content))).toBe('候选准备好了。'); const user = { role: 'user' as const, content: buildGameAssetReviewActionMessage({ invocationId: 'review-1', asset: { id: 'hero', name: '主角' }, action: 'approve' }) }; expect(parseGameAssetReviewAction(user)).toEqual({ invocationId: 'review-1', assetId: 'hero', action: 'approve' }); }); it('round-trips one grouped user message for the complete review batch', () => { const message = { role: 'user' as const, content: buildGameAssetReviewBatchMessage({ invocationId: 'review-batch', decisions: [ { asset: { id: 'hero', name: '主角' }, action: 'approve' }, { asset: { id: 'jump', name: '跳跃音效' }, action: 'discard' }, { asset: { id: 'background', name: '背景' }, action: 'replace' }, ], }), }; expect(message.content).toContain('游戏素材审核结果(本轮一次性提交)'); expect(message.content).toContain('纳入开发:'); expect(message.content).toContain('舍弃:'); expect(message.content).toContain('不合适,重新寻找:'); expect(parseGameAssetReviewActions(message)).toEqual({ invocationId: 'review-batch', decisions: [ { assetId: 'hero', action: 'approve' }, { assetId: 'jump', action: 'discard' }, { assetId: 'background', action: 'replace' }, ], }); }); it('detects a concrete preview list when the Agent omitted the review marker', () => { const assistant = { role: 'assistant' as const, content: '已经找到 7 个素材候选。\n\n| 预览文件 | 是什么 |\n| --- | --- |\n| C-001-flappy-bird.png | 小鸟和管子 |', }; expect(hasGameAssetReviewContent(assistant)).toBe(true); expect(hasGameAssetReviewContent({ role: 'assistant', content: '我正在整理素材方向,暂时没有候选。' })).toBe(false); }); it('builds a real user confirmation and associates it with the latest round', () => { const confirmation = buildGameAssetConfirmationMessage([ { id: 'hero', name: '主角' }, { id: 'jump', name: '跳跃音效' }, ]); const messages = [ { role: 'assistant' as const, content: GAME_ASSET_SELECTION_MARKER }, { role: 'user' as const, content: confirmation }, ]; expect(confirmation).toContain('「主角」(hero)、「跳跃音效」(jump)'); expect(findGameAssetRoundConfirmation(messages, 0)).toBe(messages[1]); }); it('does not let a later round confirmation collapse an older unanswered round', () => { const messages = [ { role: 'assistant' as const, content: GAME_ASSET_SELECTION_MARKER }, { role: 'user' as const, content: '换一批' }, { role: 'assistant' as const, content: GAME_ASSET_SELECTION_MARKER }, { role: 'user' as const, content: buildGameAssetConfirmationMessage([{ id: 'new', name: '新素材' }]) }, ]; expect(findGameAssetRoundConfirmation(messages, 0)).toBeNull(); expect(findGameAssetRoundConfirmation(messages, 2)).toBe(messages[3]); }); });