refactor: 深化前端入口作品流与作品架模块
This commit is contained in:
204
src/components/platform-entry/platformPublicGalleryFlow.test.ts
Normal file
204
src/components/platform-entry/platformPublicGalleryFlow.test.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
import { expect, test } from 'vitest';
|
||||
|
||||
import type { CustomWorldGalleryCard } from '../../../packages/shared/src/contracts/runtime';
|
||||
import {
|
||||
EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_ID,
|
||||
EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_NAME,
|
||||
type PlatformPublicGalleryCard,
|
||||
} from '../rpg-entry/rpgEntryWorldPresentation';
|
||||
import {
|
||||
getPlatformPublicGalleryEntryKey,
|
||||
getPlatformPublicGalleryEntryTime,
|
||||
getPlatformRecommendRuntimeKind,
|
||||
isSamePlatformPublicGalleryEntry,
|
||||
mergePlatformPublicGalleryEntries,
|
||||
type RecommendRuntimeKind,
|
||||
} from './platformPublicGalleryFlow';
|
||||
|
||||
type TypedPlatformPublicGalleryCard = Extract<
|
||||
PlatformPublicGalleryCard,
|
||||
{ sourceType: string }
|
||||
>;
|
||||
type PlatformGallerySourceType = TypedPlatformPublicGalleryCard['sourceType'];
|
||||
type TypedPlatformPublicGalleryCardOverrides = Partial<
|
||||
Omit<TypedPlatformPublicGalleryCard, 'sourceType'>
|
||||
>;
|
||||
|
||||
function buildRpgEntry(
|
||||
overrides: Partial<CustomWorldGalleryCard> = {},
|
||||
): CustomWorldGalleryCard {
|
||||
return {
|
||||
ownerUserId: 'user-1',
|
||||
profileId: 'rpg-profile',
|
||||
publicWorkCode: 'CW-RPG',
|
||||
authorPublicUserCode: null,
|
||||
visibility: 'published',
|
||||
publishedAt: '2026-06-01T00:00:00.000Z',
|
||||
updatedAt: '2026-06-01T01:00:00.000Z',
|
||||
authorDisplayName: '玩家',
|
||||
worldName: 'RPG 世界',
|
||||
subtitle: '公开作品',
|
||||
summaryText: '公开作品摘要',
|
||||
coverImageSrc: null,
|
||||
themeMode: 'martial',
|
||||
playableNpcCount: 1,
|
||||
landmarkCount: 1,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function buildTypedEntry(
|
||||
sourceType: PlatformGallerySourceType,
|
||||
overrides: TypedPlatformPublicGalleryCardOverrides = {},
|
||||
): PlatformPublicGalleryCard {
|
||||
const common = {
|
||||
workId: `${sourceType}-work`,
|
||||
profileId: `${sourceType}-profile`,
|
||||
publicWorkCode: `${sourceType}-code`,
|
||||
ownerUserId: 'user-1',
|
||||
authorDisplayName: '玩家',
|
||||
worldName: `${sourceType} 作品`,
|
||||
subtitle: '公开作品',
|
||||
summaryText: '公开作品摘要',
|
||||
coverImageSrc: null,
|
||||
themeTags: [sourceType],
|
||||
visibility: 'published' as const,
|
||||
publishedAt: '2026-06-01T00:00:00.000Z',
|
||||
updatedAt: '2026-06-01T01:00:00.000Z',
|
||||
};
|
||||
|
||||
switch (sourceType) {
|
||||
case 'puzzle':
|
||||
return { ...common, ...overrides, sourceType };
|
||||
case 'big-fish':
|
||||
return { ...common, ...overrides, sourceType };
|
||||
case 'match3d':
|
||||
return { ...common, ...overrides, sourceType };
|
||||
case 'square-hole':
|
||||
return { ...common, ...overrides, sourceType };
|
||||
case 'visual-novel':
|
||||
return { ...common, ...overrides, sourceType };
|
||||
case 'jump-hop':
|
||||
return { ...common, ...overrides, sourceType };
|
||||
case 'wooden-fish':
|
||||
return { ...common, ...overrides, sourceType };
|
||||
case 'edutainment':
|
||||
return {
|
||||
...common,
|
||||
...overrides,
|
||||
sourceType,
|
||||
templateId: EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_ID,
|
||||
templateName: EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_NAME,
|
||||
};
|
||||
case 'bark-battle':
|
||||
return {
|
||||
...common,
|
||||
...overrides,
|
||||
sourceType,
|
||||
authorPublicUserCode: null,
|
||||
coverRenderMode: 'image',
|
||||
coverCharacterImageSrcs: [],
|
||||
themeMode: 'martial',
|
||||
playableNpcCount: 1,
|
||||
landmarkCount: 1,
|
||||
};
|
||||
default: {
|
||||
const exhaustive: never = sourceType;
|
||||
return exhaustive;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test('platform public gallery flow resolves stable key and runtime kind for every play kind', () => {
|
||||
const cases: Array<
|
||||
[sourceType: PlatformGallerySourceType, keyKind: string, kind: RecommendRuntimeKind]
|
||||
> = [
|
||||
['big-fish', 'big-fish', 'big-fish'],
|
||||
['puzzle', 'puzzle', 'puzzle'],
|
||||
['jump-hop', 'jump-hop', 'jump-hop'],
|
||||
['wooden-fish', 'wooden-fish', 'wooden-fish'],
|
||||
['match3d', 'match3d', 'match3d'],
|
||||
['square-hole', 'square-hole', 'square-hole'],
|
||||
['visual-novel', 'visual-novel', 'visual-novel'],
|
||||
['bark-battle', 'bark-battle', 'bark-battle'],
|
||||
[
|
||||
'edutainment',
|
||||
`edutainment:${EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_ID}`,
|
||||
'edutainment',
|
||||
],
|
||||
];
|
||||
|
||||
cases.forEach(([sourceType, keyKind, kind]) => {
|
||||
const entry = buildTypedEntry(sourceType);
|
||||
|
||||
expect(getPlatformPublicGalleryEntryKey(entry)).toBe(
|
||||
`${keyKind}:user-1:${sourceType}-profile`,
|
||||
);
|
||||
expect(getPlatformRecommendRuntimeKind(entry)).toBe(kind);
|
||||
});
|
||||
|
||||
const rpgEntry = buildRpgEntry();
|
||||
|
||||
expect(getPlatformPublicGalleryEntryKey(rpgEntry)).toBe(
|
||||
'rpg:user-1:rpg-profile',
|
||||
);
|
||||
expect(getPlatformRecommendRuntimeKind(rpgEntry)).toBe('rpg');
|
||||
});
|
||||
|
||||
test('platform public gallery flow compares entries by resolved identity', () => {
|
||||
const left = buildTypedEntry('puzzle');
|
||||
const sameIdentity = buildTypedEntry('puzzle', {
|
||||
workId: 'other-work',
|
||||
worldName: '新标题',
|
||||
});
|
||||
const otherKind = buildTypedEntry('match3d', {
|
||||
ownerUserId: left.ownerUserId,
|
||||
profileId: left.profileId,
|
||||
});
|
||||
|
||||
expect(isSamePlatformPublicGalleryEntry(left, sameIdentity)).toBe(true);
|
||||
expect(isSamePlatformPublicGalleryEntry(left, otherKind)).toBe(false);
|
||||
});
|
||||
|
||||
test('platform public gallery flow merges duplicate identities and sorts newest first', () => {
|
||||
const staleRpgEntry = buildRpgEntry({
|
||||
profileId: 'shared-rpg',
|
||||
worldName: '旧版 RPG',
|
||||
publishedAt: '2026-06-01T00:00:00.000Z',
|
||||
});
|
||||
const freshRpgEntry = buildRpgEntry({
|
||||
profileId: 'shared-rpg',
|
||||
worldName: '新版 RPG',
|
||||
publishedAt: '2026-06-04T00:00:00.000Z',
|
||||
});
|
||||
const middleRpgEntry = buildRpgEntry({
|
||||
profileId: 'middle-rpg',
|
||||
worldName: '中间 RPG',
|
||||
publishedAt: '2026-06-02T00:00:00.000Z',
|
||||
});
|
||||
const updatedOnlyEntry = buildTypedEntry('big-fish', {
|
||||
profileId: 'updated-only',
|
||||
publishedAt: null,
|
||||
updatedAt: '2026-06-03T00:00:00.000Z',
|
||||
});
|
||||
const invalidTimeEntry = buildTypedEntry('puzzle', {
|
||||
profileId: 'invalid-time',
|
||||
publishedAt: 'not-a-date',
|
||||
updatedAt: 'still-not-a-date',
|
||||
});
|
||||
|
||||
const merged = mergePlatformPublicGalleryEntries(
|
||||
[staleRpgEntry, middleRpgEntry],
|
||||
[invalidTimeEntry, updatedOnlyEntry, freshRpgEntry],
|
||||
);
|
||||
|
||||
expect(merged).toHaveLength(4);
|
||||
expect(merged.map((entry) => entry.profileId)).toEqual([
|
||||
'shared-rpg',
|
||||
'updated-only',
|
||||
'middle-rpg',
|
||||
'invalid-time',
|
||||
]);
|
||||
expect(merged[0]?.worldName).toBe('新版 RPG');
|
||||
expect(getPlatformPublicGalleryEntryTime(invalidTimeEntry)).toBe(0);
|
||||
});
|
||||
Reference in New Issue
Block a user