import { expect, test } from 'vitest'; import type { ProfileTaskCenterResponse, ProfileTaskItem, } from '../../../packages/shared/src/contracts/runtime'; import { buildProfileTaskCardSummary, buildProfileTaskProgressLabel, getProfileTaskClaimButtonLabel, getProfileTaskStatusLabel, selectProfileTaskCardTask, selectProfileTaskCenterTasks, } from './rpgEntryProfileTaskViewModel'; function buildTask( overrides: Partial = {}, ): ProfileTaskItem { return { taskId: 'task-1', title: '游玩一次', description: '完成一次游戏', eventKey: 'work_play_start', cycle: 'daily', threshold: 1, progressCount: 0, rewardPoints: 10, status: 'incomplete', dayKey: 20260603, claimedAt: null, updatedAt: '2026-06-03T00:00:00.000Z', ...overrides, }; } function buildCenter( tasks: ProfileTaskItem[], ): ProfileTaskCenterResponse { return { dayKey: 20260603, walletBalance: 12, tasks, updatedAt: '2026-06-03T00:00:00.000Z', }; } test('profile task ViewModel selects one actionable task by status priority and original order', () => { const firstIncomplete = buildTask({ taskId: 'incomplete-1', status: 'incomplete', }); const secondIncomplete = buildTask({ taskId: 'incomplete-2', status: 'incomplete', }); const claimable = buildTask({ taskId: 'claimable-1', status: 'claimable', }); expect( selectProfileTaskCenterTasks([ firstIncomplete, secondIncomplete, claimable, ]), ).toEqual([claimable]); expect(selectProfileTaskCenterTasks([firstIncomplete, secondIncomplete])).toEqual( [firstIncomplete], ); }); test('profile task ViewModel falls back from card task to claimed and enabled tasks', () => { const claimed = buildTask({ taskId: 'claimed-1', status: 'claimed' }); const disabled = buildTask({ taskId: 'disabled-1', status: 'disabled' }); const incomplete = buildTask({ taskId: 'incomplete-1', status: 'incomplete', }); expect(selectProfileTaskCardTask([disabled, claimed])).toBe(claimed); expect(selectProfileTaskCardTask([disabled, incomplete])).toBe(incomplete); expect(selectProfileTaskCardTask([disabled])).toBeNull(); }); test('profile task ViewModel builds card summary with reward fallback and clamped progress', () => { expect(buildProfileTaskCardSummary(null)).toEqual({ actionLabel: '去完成', progressCount: 0, progressPercent: 0, rewardPoints: 10, threshold: 1, }); expect( buildProfileTaskCardSummary( buildCenter([ buildTask({ progressCount: 5, rewardPoints: 25, status: 'claimable', threshold: 3, }), ]), ), ).toEqual({ actionLabel: '领取', progressCount: 3, progressPercent: 100, rewardPoints: 25, threshold: 3, }); }); test('profile task ViewModel exposes task labels for the modal', () => { const task = buildTask({ progressCount: -1, threshold: 0 }); expect(getProfileTaskStatusLabel('claimable')).toBe('可领取'); expect(buildProfileTaskProgressLabel(task)).toBe('0/1'); expect(getProfileTaskClaimButtonLabel(task, true)).toBe('领取中'); expect(getProfileTaskClaimButtonLabel(buildTask({ status: 'claimed' }), false)).toBe( '已领取', ); expect( getProfileTaskClaimButtonLabel(buildTask({ status: 'claimable' }), false), ).toBe('领取'); });