import { expect, test } from 'vitest'; import type { BarkBattleDraftConfig, BarkBattlePublishedConfig, BarkBattleWorkSummary, } from '../../../packages/shared/src/contracts/barkBattle'; import { buildBarkBattlePublishedConfigFromDraft, buildBarkBattlePublishedConfigFromWork, buildBarkBattlePublishSnapshot, mergeBarkBattlePublishedConfigAssets, mergeBarkBattleWorksByWorkId, mergeBarkBattleWorkSummary, resolveBarkBattleDraftGenerationStatus, shouldPreserveLocalBarkBattleWorkOnRefresh, } from './barkBattleWorkCache'; function buildBarkBattleWork( overrides: Partial = {}, ): BarkBattleWorkSummary { return { workId: 'BB-cache-race-12345678', draftId: 'bark-battle-draft-1', ownerUserId: 'user-1', authorDisplayName: '测试玩家', title: '汪汪测试杯', summary: '测试声浪赛', themeDescription: '阳光草坪声浪竞技场', playerImageDescription: '戴红色围巾的柯基选手', opponentImageDescription: '蓝色护目镜哈士奇对手', playerCharacterImageSrc: '/generated-bark-battle/player.png', opponentCharacterImageSrc: '/generated-bark-battle/opponent.png', uiBackgroundImageSrc: '/generated-bark-battle/background.png', difficultyPreset: 'normal', status: 'draft', generationStatus: 'ready', publishReady: true, playCount: 0, updatedAt: '2026-05-21T10:00:00.000Z', publishedAt: null, ...overrides, }; } function buildBarkBattleDraft( overrides: Partial = {}, ): BarkBattleDraftConfig { return { draftId: 'bark-battle-draft-1', workId: 'BB-cache-race-12345678', configVersion: 2, rulesetVersion: 'bark-battle-ruleset-v2', title: '汪汪测试杯', description: '测试声浪赛', themeDescription: '阳光草坪声浪竞技场', playerImageDescription: '戴红色围巾的柯基选手', opponentImageDescription: '蓝色护目镜哈士奇对手', onomatopoeia: ['汪', '破阵'], playerCharacterImageSrc: '/generated-bark-battle/player.png', opponentCharacterImageSrc: '/generated-bark-battle/opponent.png', uiBackgroundImageSrc: '/generated-bark-battle/background.png', difficultyPreset: 'normal', updatedAt: '2026-05-21T10:00:00.000Z', ...overrides, }; } test('preserves local published bark battle when refresh only returns same work draft', () => { const published = buildBarkBattleWork({ status: 'published', playCount: 3, updatedAt: '2026-05-21T10:02:00.000Z', publishedAt: '2026-05-21T10:02:00.000Z', }); const refreshedDraft = buildBarkBattleWork({ status: 'draft', playCount: 0, updatedAt: '2026-05-21T10:01:00.000Z', publishedAt: null, }); expect(shouldPreserveLocalBarkBattleWorkOnRefresh(published, [refreshedDraft])).toBe( true, ); const [merged] = mergeBarkBattleWorksByWorkId([refreshedDraft, published]); expect(merged?.status).toBe('published'); expect(merged?.publishedAt).toBe('2026-05-21T10:02:00.000Z'); expect(merged?.playCount).toBe(3); }); test('does not let later draft cache updates downgrade an existing published bark battle', () => { const published = buildBarkBattleWork({ status: 'published', playCount: 4, updatedAt: '2026-05-21T10:03:00.000Z', publishedAt: '2026-05-21T10:03:00.000Z', }); const staleDraft = buildBarkBattleWork({ title: '旧草稿标题', status: 'draft', playCount: 0, updatedAt: '2026-05-21T10:01:00.000Z', publishedAt: null, }); const merged = mergeBarkBattleWorkSummary(published, staleDraft); expect(merged.status).toBe('published'); expect(merged.title).toBe('汪汪测试杯'); expect(merged.playCount).toBe(4); expect(merged.publishedAt).toBe('2026-05-21T10:03:00.000Z'); }); test('preserves local ready bark battle draft when refresh has not returned it yet', () => { const readyDraft = buildBarkBattleWork({ status: 'draft', generationStatus: 'ready', publishReady: true, playerCharacterImageSrc: '/generated-bark-battle/player-ready.png', opponentCharacterImageSrc: '/generated-bark-battle/opponent-ready.png', uiBackgroundImageSrc: '/generated-bark-battle/background-ready.png', }); expect(shouldPreserveLocalBarkBattleWorkOnRefresh(readyDraft, [])).toBe(true); const merged = mergeBarkBattleWorksByWorkId([ ...[], ...(shouldPreserveLocalBarkBattleWorkOnRefresh(readyDraft, []) ? [readyDraft] : []), ]); expect(merged).toHaveLength(1); expect(merged[0]?.workId).toBe('BB-cache-race-12345678'); expect(merged[0]?.generationStatus).toBe('ready'); }); test('resolves bark battle draft generation status from required images', () => { expect( resolveBarkBattleDraftGenerationStatus( buildBarkBattleDraft({ uiBackgroundImageSrc: undefined }), false, ), ).toBe('pending_assets'); expect( resolveBarkBattleDraftGenerationStatus( buildBarkBattleDraft({ opponentCharacterImageSrc: '' }), true, ), ).toBe('partial_failed'); expect(resolveBarkBattleDraftGenerationStatus(buildBarkBattleDraft(), true)).toBe( 'ready', ); }); test('builds draft runtime config with stable defaults', () => { const config = buildBarkBattlePublishedConfigFromDraft( buildBarkBattleDraft({ workId: undefined, configVersion: undefined, rulesetVersion: undefined, }), ); expect(config.workId).toBe('bark-battle-draft-1'); expect(config.draftId).toBe('bark-battle-draft-1'); expect(config.configVersion).toBe(1); expect(config.rulesetVersion).toBe('bark-battle-ruleset-v1'); expect(config.playTypeId).toBe('bark-battle'); expect(config.publishedAt).toBe('2026-05-21T10:00:00.000Z'); }); test('builds work runtime config with publishedAt fallback', () => { const config = buildBarkBattlePublishedConfigFromWork( buildBarkBattleWork({ publishedAt: null }), ); expect(config.workId).toBe('BB-cache-race-12345678'); expect(config.description).toBe('测试声浪赛'); expect(config.publishedAt).toBe('2026-05-21T10:00:00.000Z'); expect(config.playerCharacterImageSrc).toBe('/generated-bark-battle/player.png'); }); test('builds publish snapshot without empty asset fields', () => { const snapshot = buildBarkBattlePublishSnapshot( buildBarkBattleDraft({ playerCharacterImageSrc: '', opponentCharacterImageSrc: undefined, }), ); expect(snapshot).not.toHaveProperty('playerCharacterImageSrc'); expect(snapshot).not.toHaveProperty('opponentCharacterImageSrc'); expect(snapshot.uiBackgroundImageSrc).toBe( '/generated-bark-battle/background.png', ); }); test('merges draft assets into published config when publish response omits them', () => { const draft = buildBarkBattleDraft(); const published: BarkBattlePublishedConfig = { workId: 'BB-cache-race-12345678', draftId: 'bark-battle-draft-1', configVersion: 2, rulesetVersion: 'bark-battle-ruleset-v2', playTypeId: 'bark-battle', title: '汪汪测试杯', description: '测试声浪赛', themeDescription: '阳光草坪声浪竞技场', playerImageDescription: '戴红色围巾的柯基选手', opponentImageDescription: '蓝色护目镜哈士奇对手', onomatopoeia: ['汪', '破阵'], difficultyPreset: 'normal', updatedAt: '2026-05-21T10:01:00.000Z', publishedAt: '2026-05-21T10:01:00.000Z', }; const merged = mergeBarkBattlePublishedConfigAssets(published, draft); expect(merged.playerCharacterImageSrc).toBe( '/generated-bark-battle/player.png', ); expect(merged.opponentCharacterImageSrc).toBe( '/generated-bark-battle/opponent.png', ); expect(merged.uiBackgroundImageSrc).toBe( '/generated-bark-battle/background.png', ); });