fix: tighten public work type routing

This commit is contained in:
2026-06-07 14:49:36 +08:00
parent 8f460feb41
commit 8131894bb5
21 changed files with 611 additions and 228 deletions

View File

@@ -42,7 +42,10 @@ import type {
CustomWorldGalleryCard,
CustomWorldLibraryEntry,
} from '../../../packages/shared/src/contracts/runtime';
import type { WoodenFishWorkSummaryResponse } from '../../../packages/shared/src/contracts/woodenFish';
import type {
WoodenFishGalleryCardResponse,
WoodenFishWorkSummaryResponse,
} from '../../../packages/shared/src/contracts/woodenFish';
import { normalizeCustomWorldProfileRecord } from '../../data/customWorldLibrary';
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
import {
@@ -155,6 +158,7 @@ import {
deleteRpgEntryWorldProfile,
getRpgEntryWorldGalleryDetail as getRpgEntryWorldGalleryDetailFromClient,
getRpgEntryWorldGalleryDetailByCode,
likeRpgEntryWorldGallery,
recordRpgEntryWorldGalleryPlay,
remixRpgEntryWorldGallery,
} from '../../services/rpg-entry/rpgEntryLibraryClient';
@@ -538,6 +542,7 @@ const rpgEntryLibraryServiceMocks = vi.hoisted(() => ({
getRpgEntryWorldGalleryDetail: vi.fn(),
getRpgEntryWorldGalleryDetailByCode: vi.fn(),
getRpgEntryWorldLibraryDetail: vi.fn(),
likeRpgEntryWorldGallery: vi.fn(),
listRpgEntryWorldGallery: vi.fn(),
listRpgEntryWorldLibrary: vi.fn(),
publishRpgEntryWorldProfile: vi.fn(),
@@ -7365,6 +7370,75 @@ test('home recommendation share opens publish share modal', async () => {
.toBeTruthy();
});
test('home recommendation wooden fish like does not call RPG gallery like', async () => {
const user = userEvent.setup();
const publishedWoodenFishWork: WoodenFishGalleryCardResponse = {
publicWorkCode: 'WF-3A9EC89B',
workId: 'wooden-fish-work-like-1',
profileId: 'wooden-fish-profile-like-1',
ownerUserId: 'wooden-fish-user-1',
authorDisplayName: '木鱼作者',
workTitle: '莲台木鱼',
workDescription: '推荐页里的敲木鱼作品。',
coverImageSrc: null,
themeTags: ['敲木鱼'],
publicationStatus: 'published',
playCount: 0,
updatedAt: '2026-04-25T09:00:00.000Z',
publishedAt: '2026-04-25T09:00:00.000Z',
generationStatus: 'ready',
};
vi.mocked(woodenFishClient.listGallery).mockResolvedValue({
items: [publishedWoodenFishWork],
hasMore: false,
nextCursor: null,
});
vi.mocked(woodenFishClient.startRun).mockResolvedValue({
run: {
runId: 'wooden-fish-run-like-1',
profileId: publishedWoodenFishWork.profileId,
ownerUserId: publishedWoodenFishWork.ownerUserId,
status: 'playing',
totalTapCount: 0,
wordCounters: [],
startedAtMs: 1,
updatedAtMs: 1,
finishedAtMs: null,
},
});
vi.mocked(likeRpgEntryWorldGallery).mockResolvedValue(
buildMockRpgGalleryDetail({
ownerUserId: 'custom-world-user-1',
profileId: 'custom-world-profile-1',
publicWorkCode: 'CW-00000001',
authorPublicUserCode: 'SY-00000001',
visibility: 'published',
publishedAt: '2026-04-25T09:00:00.000Z',
updatedAt: '2026-04-25T09:00:00.000Z',
authorDisplayName: 'RPG 作者',
worldName: '不应被点赞的 RPG',
subtitle: '错误分流',
summaryText: 'WF 点赞不应进入这里。',
coverImageSrc: null,
themeMode: 'mythic',
playableNpcCount: 0,
landmarkCount: 0,
likeCount: 1,
}),
);
render(<TestWrapper withAuth />);
const meta = await screen.findByLabelText('莲台木鱼 作品信息');
await user.click(within(meta).getByRole('button', { name: '点赞 0' }));
expect(likeRpgEntryWorldGallery).not.toHaveBeenCalled();
expect(
await screen.findByText('作品类型 wooden-fish 暂不支持点赞。'),
).toBeTruthy();
});
test('home recommendation keeps logged-in puzzle start on default auth instead of guest token', async () => {
const publishedPuzzleWork = {
workId: 'puzzle-work-public-2',
@@ -7467,12 +7541,6 @@ test('logged out home recommendation next starts the next puzzle work', async ()
/>,
);
const recommendNavButton = document.querySelector<HTMLButtonElement>(
'.platform-bottom-nav [aria-label="推荐"]',
);
expect(recommendNavButton).toBeTruthy();
await user.click(recommendNavButton!);
await waitFor(() => {
expect(startPuzzleRun).toHaveBeenCalledWith(
{

View File

@@ -144,6 +144,7 @@ import {
formatPlatformWorldTime,
isBarkBattleGalleryEntry,
isBigFishGalleryEntry,
isCustomWorldGalleryEntry,
isEdutainmentGalleryEntry,
isJumpHopGalleryEntry,
isMatch3DGalleryEntry,
@@ -373,11 +374,15 @@ type PlatformRankingTab = 'hot' | 'remix' | 'new' | 'like';
type PlatformCategoryKindFilter =
| 'all'
| 'puzzle'
| 'puzzle-clear'
| 'jump-hop'
| 'wooden-fish'
| 'match3d'
| 'square-hole'
| 'visual-novel'
| 'bark-battle'
| 'big-fish'
| 'edutainment'
| 'custom-world';
type PlatformCategorySortMode = 'composite' | 'latest' | 'play' | 'like';
@@ -413,11 +418,15 @@ const PLATFORM_CATEGORY_KIND_FILTERS: Array<{
}> = [
{ id: 'all', label: '全部' },
{ id: 'puzzle', label: '拼图' },
{ id: 'puzzle-clear', label: '拼消' },
{ id: 'jump-hop', label: '跳一跳' },
{ id: 'wooden-fish', label: '木鱼' },
{ id: 'match3d', label: '抓鹅' },
{ id: 'square-hole', label: '方洞' },
{ id: 'visual-novel', label: '视觉' },
{ id: 'bark-battle', label: '汪汪' },
{ id: 'big-fish', label: '大鱼' },
{ id: 'edutainment', label: EDUTAINMENT_WORK_TAG },
{ id: 'custom-world', label: 'RPG' },
];
const PLATFORM_CATEGORY_SORT_OPTIONS: Array<{
@@ -2192,6 +2201,18 @@ function getPlatformCategoryKindFilter(entry: PlatformPublicGalleryCard) {
return 'puzzle';
}
if (isPuzzleClearGalleryEntry(entry)) {
return 'puzzle-clear';
}
if (isJumpHopGalleryEntry(entry)) {
return 'jump-hop';
}
if (isWoodenFishGalleryEntry(entry)) {
return 'wooden-fish';
}
if (isMatch3DGalleryEntry(entry)) {
return 'match3d';
}
@@ -2212,7 +2233,15 @@ function getPlatformCategoryKindFilter(entry: PlatformPublicGalleryCard) {
return 'big-fish';
}
return 'custom-world';
if (isEdutainmentGalleryEntry(entry)) {
return 'edutainment';
}
if (isCustomWorldGalleryEntry(entry)) {
return 'custom-world';
}
throw new Error('未知公开作品类型。');
}
function matchesPlatformCategoryKindFilter(

View File

@@ -23,6 +23,7 @@ import type {
CustomWorldGalleryCard,
CustomWorldLibraryEntry,
} from '../../../packages/shared/src/contracts/runtime';
import type { PublicWorkSourceType } from '../../../packages/shared/src/contracts/playTypes';
import type {
SquareHoleHoleOption,
SquareHoleShapeOption,
@@ -55,8 +56,12 @@ export const PLATFORM_WORK_TAG_DISPLAY_LIMIT = 4;
export const EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_ID = 'baby-object-match';
export const EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_NAME = '宝贝识物';
export type PlatformCustomWorldGalleryCard = CustomWorldGalleryCard & {
sourceType?: 'custom-world';
};
export type PlatformWorldCardLike =
| CustomWorldGalleryCard
| PlatformCustomWorldGalleryCard
| CustomWorldLibraryEntry<CustomWorldProfile>
| PlatformBigFishGalleryCard
| PlatformMatch3DGalleryCard
@@ -319,7 +324,7 @@ export type PlatformBarkBattleGalleryCard = {
};
export type PlatformPublicGalleryCard =
| CustomWorldGalleryCard
| PlatformCustomWorldGalleryCard
| PlatformBigFishGalleryCard
| PlatformMatch3DGalleryCard
| PlatformSquareHoleGalleryCard
@@ -337,6 +342,14 @@ export function isLibraryWorldEntry(
return 'profile' in entry;
}
export function isCustomWorldGalleryEntry(
entry: PlatformWorldCardLike,
): entry is PlatformCustomWorldGalleryCard {
return !isLibraryWorldEntry(entry) && !('sourceType' in entry)
? true
: 'sourceType' in entry && entry.sourceType === 'custom-world';
}
export function isPuzzleGalleryEntry(
entry: PlatformWorldCardLike,
): entry is PlatformPuzzleGalleryCard {
@@ -397,28 +410,62 @@ export function isBarkBattleGalleryEntry(
return 'sourceType' in entry && entry.sourceType === 'bark-battle';
}
export function resolvePlatformPublicWorkSourceType(
entry: PlatformPublicGalleryCard,
): PublicWorkSourceType {
if (isCustomWorldGalleryEntry(entry)) {
return 'custom-world';
}
if (isBigFishGalleryEntry(entry)) {
return 'big-fish';
}
if (isPuzzleGalleryEntry(entry)) {
return 'puzzle';
}
if (isPuzzleClearGalleryEntry(entry)) {
return 'puzzle-clear';
}
if (isJumpHopGalleryEntry(entry)) {
return 'jump-hop';
}
if (isWoodenFishGalleryEntry(entry)) {
return 'wooden-fish';
}
if (isMatch3DGalleryEntry(entry)) {
return 'match3d';
}
if (isSquareHoleGalleryEntry(entry)) {
return 'square-hole';
}
if (isVisualNovelGalleryEntry(entry)) {
return 'visual-novel';
}
if (isBarkBattleGalleryEntry(entry)) {
return 'bark-battle';
}
if (isEdutainmentGalleryEntry(entry)) {
return 'edutainment';
}
throw new Error('未知公开作品类型。');
}
export function buildPlatformPublicGalleryCardKey(
entry: PlatformPublicGalleryCard,
) {
const kind = isBigFishGalleryEntry(entry)
? 'big-fish'
: isPuzzleGalleryEntry(entry)
? 'puzzle'
: isJumpHopGalleryEntry(entry)
? 'jump-hop'
: isWoodenFishGalleryEntry(entry)
? 'wooden-fish'
: isMatch3DGalleryEntry(entry)
? 'match3d'
: isSquareHoleGalleryEntry(entry)
? 'square-hole'
: isVisualNovelGalleryEntry(entry)
? 'visual-novel'
: isBarkBattleGalleryEntry(entry)
? 'bark-battle'
: isEdutainmentGalleryEntry(entry)
? `edutainment:${entry.templateId}`
: 'rpg';
const kind = isEdutainmentGalleryEntry(entry)
? `edutainment:${entry.templateId}`
: resolvePlatformPublicWorkSourceType(entry);
return `${kind}:${entry.ownerUserId}:${entry.profileId}`;
}
@@ -868,7 +915,11 @@ export function resolvePlatformWorldFallbackCoverImage(
return '/creation-type-references/bark-battle.webp';
}
return '/creation-type-references/rpg.webp';
if (isCustomWorldGalleryEntry(entry) || isLibraryWorldEntry(entry)) {
return '/creation-type-references/rpg.webp';
}
throw new Error('未知公开作品类型。');
}
export function resolvePlatformWorldCoverSlides(