import { describe, expect, test } from 'vitest'; import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle'; import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary'; import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject'; import type { JumpHopGalleryCardResponse } from '../../../packages/shared/src/contracts/jumpHop'; import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks'; import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary'; import type { CustomWorldLibraryEntry } from '../../../packages/shared/src/contracts/runtime'; import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks'; import type { VisualNovelWorkSummary } from '../../../packages/shared/src/contracts/visualNovel'; import type { WoodenFishGalleryCardResponse } from '../../../packages/shared/src/contracts/woodenFish'; import { buildBarkBattlePublicWorkCode, buildBigFishPublicWorkCode, buildJumpHopPublicWorkCode, buildMatch3DPublicWorkCode, buildPuzzlePublicWorkCode, buildSquareHolePublicWorkCode, buildVisualNovelPublicWorkCode, buildWoodenFishPublicWorkCode, } from '../../services/publicWorkCode'; import type { CustomWorldProfile } from '../../types'; import { mapRpgPublicCodeSearchDetailToGalleryCard, type PlatformPublicCodeSearchStep, resolveBabyObjectMatchPublicCodeSearchMatch, resolveBarkBattlePublicCodeSearchMatch, resolveBigFishPublicCodeSearchMatch, resolveJumpHopPublicCodeSearchMatch, resolveMatch3DPublicCodeSearchMatch, resolvePlatformPublicCodeSearchPlan, resolvePuzzlePublicCodeSearchMatch, resolveSquareHolePublicCodeSearchMatch, resolveVisualNovelPublicCodeSearchMatch, resolveWoodenFishPublicCodeSearchMatch, } from './platformPublicCodeSearchModel'; function expectSearchSteps( keyword: string, steps: readonly PlatformPublicCodeSearchStep[], ) { expect(resolvePlatformPublicCodeSearchPlan(keyword)?.steps).toEqual(steps); } describe('platformPublicCodeSearchModel', () => { test('ignores empty public code search input', () => { expect(resolvePlatformPublicCodeSearchPlan(' ')).toBeNull(); }); test('normalizes public code search keyword before planning', () => { expect(resolvePlatformPublicCodeSearchPlan(' PZ-00000001 ')).toEqual({ normalizedKeyword: 'PZ-00000001', steps: ['puzzle-work'], }); }); test('searches internal user ids directly without work fallback', () => { expectSearchSteps('user_00000001', ['user-id']); expectSearchSteps('USER-profile-1', ['user-id']); }); test('routes known public work prefixes to their play-specific lookup', () => { const cases: Array< [keyword: string, step: PlatformPublicCodeSearchStep] > = [ ['PZ-EPUBLIC1', 'puzzle-work'], ['BF-NPUBLIC1', 'big-fish-work'], ['JH-EPUBLIC1', 'jump-hop-work'], ['WF-EPUBLIC1', 'wooden-fish-work'], ['BO-EPUBLIC1', 'baby-object-match-work'], ['M3-EPUBLIC1', 'match3d-work'], ['M3D-LEGACY1', 'match3d-work'], ['SH-EPUBLIC1', 'square-hole-work'], ['VN-EPUBLIC1', 'visual-novel-work'], ['BB-EPUBLIC1', 'bark-battle-work'], ]; for (const [keyword, step] of cases) { expectSearchSteps(keyword, [step]); } }); test('searches RPG public works before public user codes for CW and numeric codes', () => { expectSearchSteps('CW-00000001', ['rpg-work', 'public-user-code']); expectSearchSteps('12345678', ['rpg-work', 'public-user-code']); }); test('keeps legacy user-code-first fallback for SY and ordinary keywords', () => { const legacyFallbackSteps = [ 'public-user-code', 'rpg-work', 'bark-battle-work', 'public-user-code', ] as const; expectSearchSteps('SY-00000001', legacyFallbackSteps); expectSearchSteps('月井守望', legacyFallbackSteps); }); test('maps RPG detail responses to gallery cards with count defaults', () => { expect( mapRpgPublicCodeSearchDetailToGalleryCard( buildRpgDetailEntry({ playCount: undefined, remixCount: undefined, likeCount: undefined, }), ), ).toMatchObject({ profileId: 'rpg-profile-1', visibility: 'published', worldName: '潮雾世界', playCount: 0, remixCount: 0, likeCount: 0, }); }); test('resolves public code matches for every play-specific gallery type', () => { const puzzle = buildPuzzleWork({ profileId: 'puzzle-profile-12345678' }); const bigFish = buildBigFishWork({ sourceSessionId: 'big-fish-session-12345678', }); const jumpHop = buildJumpHopCard({ profileId: 'jump-hop-profile-12345678' }); const woodenFish = buildWoodenFishCard({ profileId: 'wooden-fish-profile-12345678', }); const babyObjectMatch = buildBabyObjectMatchDraft({ profileId: 'baby-object-profile-12345678', }); const match3d = buildMatch3DWork({ profileId: 'match3d-profile-12345678' }); const squareHole = buildSquareHoleWork({ profileId: 'square-hole-profile-12345678', }); const visualNovel = buildVisualNovelWork({ profileId: 'visual-novel-profile-12345678', }); const barkBattle = buildBarkBattleWork({ workId: 'bark-battle-work-12345678', }); expect( resolvePuzzlePublicCodeSearchMatch( [puzzle], buildPuzzlePublicWorkCode(puzzle.profileId), )?.detail, ).toMatchObject({ sourceType: 'puzzle' }); expect( resolveBigFishPublicCodeSearchMatch( [bigFish], buildBigFishPublicWorkCode(bigFish.sourceSessionId), )?.detail, ).toMatchObject({ sourceType: 'big-fish' }); expect( resolveJumpHopPublicCodeSearchMatch( [jumpHop], buildJumpHopPublicWorkCode(jumpHop.profileId), )?.detail, ).toMatchObject({ sourceType: 'jump-hop' }); expect( resolveWoodenFishPublicCodeSearchMatch( [woodenFish], buildWoodenFishPublicWorkCode(woodenFish.profileId), )?.detail, ).toMatchObject({ sourceType: 'wooden-fish' }); expect( resolveBabyObjectMatchPublicCodeSearchMatch( [babyObjectMatch], `BO-${babyObjectMatch.profileId.slice(-8)}`, )?.detail, ).toMatchObject({ sourceType: 'edutainment' }); expect( resolveMatch3DPublicCodeSearchMatch( [match3d], buildMatch3DPublicWorkCode(match3d.profileId), )?.detail, ).toMatchObject({ sourceType: 'match3d' }); expect( resolveSquareHolePublicCodeSearchMatch( [squareHole], buildSquareHolePublicWorkCode(squareHole.profileId), )?.detail, ).toMatchObject({ sourceType: 'square-hole' }); expect( resolveVisualNovelPublicCodeSearchMatch( [visualNovel], buildVisualNovelPublicWorkCode(visualNovel.profileId), )?.detail, ).toMatchObject({ sourceType: 'visual-novel' }); expect( resolveBarkBattlePublicCodeSearchMatch( [barkBattle], buildBarkBattlePublicWorkCode(barkBattle.workId), )?.detail, ).toMatchObject({ sourceType: 'bark-battle' }); }); test('public code search matchers skip entries hidden by visibility policy', () => { const hiddenPuzzle = buildPuzzleWork({ profileId: 'hidden-profile-12345678', }); expect( resolvePuzzlePublicCodeSearchMatch( [hiddenPuzzle], buildPuzzlePublicWorkCode(hiddenPuzzle.profileId), () => false, ), ).toBeNull(); }); }); function buildRpgDetailEntry( overrides: Partial> = {}, ): CustomWorldLibraryEntry { return { ownerUserId: 'rpg-owner-1', profileId: 'rpg-profile-1', publicWorkCode: 'CW-00000001', authorPublicUserCode: 'SY-00000001', profile: {} as CustomWorldProfile, visibility: 'published', publishedAt: '2026-06-04T00:00:00.000Z', updatedAt: '2026-06-04T00:00:00.000Z', authorDisplayName: '测试作者', worldName: '潮雾世界', subtitle: '潮雾港', summaryText: '潮雾世界说明。', coverImageSrc: null, themeMode: 'tide', playableNpcCount: 1, landmarkCount: 1, playCount: 1, remixCount: 1, likeCount: 1, ...overrides, }; } function buildPuzzleWork( overrides: Partial = {}, ): PuzzleWorkSummary { return { workId: 'puzzle-work-1', profileId: 'puzzle-profile-1', ownerUserId: 'user-1', sourceSessionId: 'puzzle-session-1', authorDisplayName: '测试作者', workTitle: '潮雾拼图', workDescription: '潮雾拼图说明。', levelName: '潮雾拼图', summary: '潮雾拼图说明。', themeTags: [], coverImageSrc: null, coverAssetId: null, publicationStatus: 'published', updatedAt: '2026-06-04T00:00:00.000Z', publishedAt: '2026-06-04T00:00:00.000Z', playCount: 0, remixCount: 0, likeCount: 0, publishReady: true, levels: [], ...overrides, }; } function buildBigFishWork( overrides: Partial = {}, ): BigFishWorkSummary { return { workId: 'big-fish-work-1', sourceSessionId: 'big-fish-session-1', ownerUserId: 'user-1', authorDisplayName: '测试作者', title: '潮雾大鱼', subtitle: '潮雾港', summary: '潮雾大鱼说明。', coverImageSrc: null, status: 'published', updatedAt: '2026-06-04T00:00:00.000Z', publishedAt: '2026-06-04T00:00:00.000Z', playCount: 0, remixCount: 0, likeCount: 0, publishReady: true, levelCount: 1, levelMainImageReadyCount: 1, levelMotionReadyCount: 1, backgroundReady: true, ...overrides, }; } function buildJumpHopCard( overrides: Partial = {}, ): JumpHopGalleryCardResponse { const profileId = overrides.profileId ?? 'jump-hop-profile-1'; return { publicWorkCode: buildJumpHopPublicWorkCode(profileId), workId: 'jump-hop-work-1', profileId, ownerUserId: 'user-1', authorDisplayName: '测试作者', workTitle: '潮雾跳一跳', workDescription: '潮雾跳一跳说明。', coverImageSrc: null, themeTags: [], difficulty: 'standard', stylePreset: 'minimal-blocks', publicationStatus: 'published', playCount: 0, updatedAt: '2026-06-04T00:00:00.000Z', publishedAt: '2026-06-04T00:00:00.000Z', generationStatus: 'ready', ...overrides, }; } function buildWoodenFishCard( overrides: Partial = {}, ): WoodenFishGalleryCardResponse { const profileId = overrides.profileId ?? 'wooden-fish-profile-1'; return { publicWorkCode: buildWoodenFishPublicWorkCode(profileId), workId: 'wooden-fish-work-1', profileId, ownerUserId: 'user-1', authorDisplayName: '测试作者', workTitle: '潮雾木鱼', workDescription: '潮雾木鱼说明。', coverImageSrc: null, themeTags: ['敲木鱼'], publicationStatus: 'published', playCount: 0, updatedAt: '2026-06-04T00:00:00.000Z', publishedAt: '2026-06-04T00:00:00.000Z', generationStatus: 'ready', ...overrides, }; } function buildBabyObjectMatchDraft( overrides: Partial = {}, ): BabyObjectMatchDraft { return { draftId: 'baby-draft-1', profileId: 'baby-object-profile-1', templateId: 'baby-object-match', templateName: '宝贝识物', workTitle: '潮雾识物', workDescription: '潮雾识物说明。', itemNames: ['苹果', '香蕉'], itemAssets: [ buildBabyObjectMatchItemAsset('item-a', '苹果'), buildBabyObjectMatchItemAsset('item-b', '香蕉'), ], visualPackage: null, themeTags: ['寓教于乐'], publicationStatus: 'published', createdAt: '2026-06-04T00:00:00.000Z', updatedAt: '2026-06-04T00:00:00.000Z', publishedAt: '2026-06-04T00:00:00.000Z', ...overrides, }; } function buildBabyObjectMatchItemAsset(itemId: string, itemName: string) { return { itemId, itemName, imageSrc: `/media/${itemId}.png`, assetObjectId: null, generationProvider: 'placeholder' as const, prompt: itemName, }; } function buildMatch3DWork( overrides: Partial = {}, ): Match3DWorkSummary { return { workId: 'match3d-work-1', profileId: 'match3d-profile-1', ownerUserId: 'user-1', sourceSessionId: 'match3d-session-1', gameName: '潮雾抓大鹅', themeText: '潮雾港', summary: '潮雾抓大鹅说明。', tags: [], coverImageSrc: null, referenceImageSrc: null, clearCount: 0, difficulty: 1, publicationStatus: 'published', playCount: 0, updatedAt: '2026-06-04T00:00:00.000Z', publishedAt: '2026-06-04T00:00:00.000Z', publishReady: true, generationStatus: 'ready', generatedItemAssets: [], ...overrides, }; } function buildSquareHoleWork( overrides: Partial = {}, ): SquareHoleWorkSummary { return { workId: 'square-hole-work-1', profileId: 'square-hole-profile-1', ownerUserId: 'user-1', sourceSessionId: 'square-hole-session-1', gameName: '潮雾方洞', themeText: '潮雾港', twistRule: '避开雾门', summary: '潮雾方洞说明。', tags: [], coverImageSrc: null, backgroundPrompt: '潮雾港', backgroundImageSrc: null, shapeOptions: [], holeOptions: [], shapeCount: 1, difficulty: 1, publicationStatus: 'published', playCount: 0, updatedAt: '2026-06-04T00:00:00.000Z', publishedAt: '2026-06-04T00:00:00.000Z', publishReady: true, ...overrides, }; } function buildVisualNovelWork( overrides: Partial = {}, ): VisualNovelWorkSummary { return { runtimeKind: 'visual-novel', profileId: 'visual-novel-profile-1', ownerUserId: 'user-1', title: '潮雾视觉小说', description: '潮雾视觉小说说明。', coverImageSrc: null, tags: [], publishStatus: 'published', publishReady: true, playCount: 0, updatedAt: '2026-06-04T00:00:00.000Z', publishedAt: '2026-06-04T00:00:00.000Z', ...overrides, }; } function buildBarkBattleWork( overrides: Partial = {}, ): BarkBattleWorkSummary { return { workId: 'bark-battle-work-1', draftId: 'bark-battle-draft-1', ownerUserId: 'user-1', authorDisplayName: '测试作者', title: '潮雾声浪', summary: '潮雾声浪说明。', themeDescription: '潮雾港', playerImageDescription: '小狗', opponentImageDescription: '对手', onomatopoeia: ['汪'], playerCharacterImageSrc: null, opponentCharacterImageSrc: null, uiBackgroundImageSrc: null, difficultyPreset: 'normal', status: 'published', generationStatus: 'ready', publishReady: true, playCount: 0, updatedAt: '2026-06-04T00:00:00.000Z', publishedAt: '2026-06-04T00:00:00.000Z', ...overrides, }; }