Files
Genarrative/src/components/platform-entry/platformRecommendation.test.ts
T

179 lines
4.9 KiB
TypeScript

import { describe, expect, test } from 'vitest';
import type { PlatformPublicGalleryCard } from '../rpg-entry/rpgEntryWorldPresentation';
import { buildPlatformRecommendedEntries } from './platformRecommendation';
const NOW_MS = Date.parse('2026-06-07T12:00:00.000Z');
type PublicCardTestParams = {
id: string;
sourceType?: 'puzzle' | 'match3d' | 'jump-hop';
subtitle?: string;
summaryText?: string;
coverImageSrc?: string | null;
themeTags?: string[];
playCount?: number;
remixCount?: number;
likeCount?: number;
recentPlayCount7d?: number;
publishedAt?: string | null;
updatedAt?: string;
};
function buildPublicCard(
params: PublicCardTestParams,
): PlatformPublicGalleryCard {
const sourceType = params.sourceType ?? 'puzzle';
return {
sourceType,
workId: `${sourceType}-work-${params.id}`,
profileId: `${sourceType}-profile-${params.id}`,
publicWorkCode: `${sourceType.toUpperCase()}-${params.id}`,
ownerUserId: `user-${params.id}`,
authorDisplayName: `${params.id} 作者`,
worldName: `${params.id} 作品`,
subtitle: params.subtitle ?? '公开作品',
summaryText: params.summaryText ?? '公开作品摘要。',
coverImageSrc: params.coverImageSrc ?? `${params.id}.png`,
themeTags: params.themeTags ?? ['推荐'],
playCount: params.playCount ?? 0,
remixCount: params.remixCount ?? 0,
likeCount: params.likeCount ?? 0,
recentPlayCount7d: params.recentPlayCount7d ?? 0,
visibility: 'published',
publishedAt: params.publishedAt ?? '2026-06-01T12:00:00.000Z',
updatedAt:
params.updatedAt ?? params.publishedAt ?? '2026-06-01T12:00:00.000Z',
} satisfies PlatformPublicGalleryCard;
}
describe('buildPlatformRecommendedEntries', () => {
test('combines heat, freshness and featured boost after de-duplicating works', () => {
const coldEntry = buildPublicCard({
id: 'cold',
playCount: 1,
publishedAt: '2026-04-01T12:00:00.000Z',
});
const hotRecentEntry = buildPublicCard({
id: 'hot',
playCount: 8,
likeCount: 4,
recentPlayCount7d: 16,
publishedAt: '2026-06-06T12:00:00.000Z',
});
const curatedEntry = buildPublicCard({
id: 'curated',
playCount: 0,
likeCount: 0,
publishedAt: '2026-05-10T12:00:00.000Z',
});
const entries = buildPlatformRecommendedEntries(
{
featuredEntries: [curatedEntry],
latestEntries: [coldEntry, hotRecentEntry, curatedEntry],
},
{ nowMs: NOW_MS },
);
expect(entries.map((entry) => entry.profileId)).toEqual([
hotRecentEntry.profileId,
curatedEntry.profileId,
coldEntry.profileId,
]);
});
test('interleaves close-score works from different play types', () => {
const firstPuzzle = buildPublicCard({
id: 'puzzle-a',
sourceType: 'puzzle',
likeCount: 2,
});
const secondPuzzle = buildPublicCard({
id: 'puzzle-b',
sourceType: 'puzzle',
likeCount: 2,
});
const match3d = buildPublicCard({
id: 'match3d-a',
sourceType: 'match3d',
likeCount: 2,
});
const entries = buildPlatformRecommendedEntries(
{
featuredEntries: [],
latestEntries: [firstPuzzle, secondPuzzle, match3d],
},
{ nowMs: NOW_MS },
);
expect(entries.map((entry) => entry.profileId)).toEqual([
firstPuzzle.profileId,
match3d.profileId,
secondPuzzle.profileId,
]);
});
test('separates same-type candidates while alternatives remain', () => {
const hotPuzzle = buildPublicCard({
id: 'hot-puzzle',
sourceType: 'puzzle',
recentPlayCount7d: 50,
likeCount: 20,
});
const warmPuzzle = buildPublicCard({
id: 'warm-puzzle',
sourceType: 'puzzle',
recentPlayCount7d: 32,
likeCount: 12,
});
const coldMatch3d = buildPublicCard({
id: 'cold-match3d',
sourceType: 'match3d',
publishedAt: '2026-04-01T12:00:00.000Z',
});
const entries = buildPlatformRecommendedEntries(
{
featuredEntries: [],
latestEntries: [hotPuzzle, warmPuzzle, coldMatch3d],
},
{ nowMs: NOW_MS },
);
expect(entries.map((entry) => entry.profileId)).toEqual([
hotPuzzle.profileId,
coldMatch3d.profileId,
warmPuzzle.profileId,
]);
});
test('falls back to same-type adjacency when no other type remains', () => {
const firstPuzzle = buildPublicCard({
id: 'only-puzzle-a',
sourceType: 'puzzle',
recentPlayCount7d: 8,
});
const secondPuzzle = buildPublicCard({
id: 'only-puzzle-b',
sourceType: 'puzzle',
recentPlayCount7d: 4,
});
const entries = buildPlatformRecommendedEntries(
{
featuredEntries: [],
latestEntries: [firstPuzzle, secondPuzzle],
},
{ nowMs: NOW_MS },
);
expect(entries.map((entry) => entry.profileId)).toEqual([
firstPuzzle.profileId,
secondPuzzle.profileId,
]);
});
});