fix: settle puzzle failures and profile tasks
This commit is contained in:
@@ -702,14 +702,22 @@ function mockNarrowMobileLayout() {
|
||||
});
|
||||
}
|
||||
|
||||
function renderProfileView(
|
||||
function ProfileHomeViewHarness({
|
||||
onRechargeSuccess = vi.fn(),
|
||||
profileDashboardOverrides: Partial<
|
||||
profileDashboardOverrides = {},
|
||||
userOverrides = {},
|
||||
activeTab = 'profile',
|
||||
profileTaskRefreshKey = 0,
|
||||
}: {
|
||||
onRechargeSuccess?: () => void | Promise<void>;
|
||||
profileDashboardOverrides?: Partial<
|
||||
NonNullable<RpgEntryHomeViewProps['profileDashboard']>
|
||||
> = {},
|
||||
userOverrides: Partial<AuthUser> = {},
|
||||
) {
|
||||
return render(
|
||||
>;
|
||||
userOverrides?: Partial<AuthUser>;
|
||||
activeTab?: RpgEntryHomeViewProps['activeTab'];
|
||||
profileTaskRefreshKey?: number;
|
||||
}) {
|
||||
return (
|
||||
<AuthUiContext.Provider
|
||||
value={{
|
||||
user: {
|
||||
@@ -742,7 +750,7 @@ function renderProfileView(
|
||||
}}
|
||||
>
|
||||
<RpgEntryHomeView
|
||||
activeTab="profile"
|
||||
activeTab={activeTab}
|
||||
onTabChange={vi.fn()}
|
||||
hasSavedGame={false}
|
||||
savedSnapshot={null}
|
||||
@@ -772,8 +780,27 @@ function renderProfileView(
|
||||
onOpenLibraryDetail={vi.fn()}
|
||||
onSearchPublicCode={vi.fn()}
|
||||
onRechargeSuccess={onRechargeSuccess}
|
||||
profileTaskRefreshKey={profileTaskRefreshKey}
|
||||
/>
|
||||
</AuthUiContext.Provider>,
|
||||
</AuthUiContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function renderProfileView(
|
||||
onRechargeSuccess = vi.fn(),
|
||||
profileDashboardOverrides: Partial<
|
||||
NonNullable<RpgEntryHomeViewProps['profileDashboard']>
|
||||
> = {},
|
||||
userOverrides: Partial<AuthUser> = {},
|
||||
profileTaskRefreshKey = 0,
|
||||
) {
|
||||
return render(
|
||||
<ProfileHomeViewHarness
|
||||
onRechargeSuccess={onRechargeSuccess}
|
||||
profileDashboardOverrides={profileDashboardOverrides}
|
||||
userOverrides={userOverrides}
|
||||
profileTaskRefreshKey={profileTaskRefreshKey}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2026,7 +2053,7 @@ test('mobile profile page matches the reference layout sections', async () => {
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
shortcutRegion.querySelectorAll('.platform-profile-shortcut-button'),
|
||||
).toHaveLength(5);
|
||||
).toHaveLength(4);
|
||||
expect(
|
||||
shortcutRegion
|
||||
.querySelector('.platform-profile-shortcut-grid')
|
||||
@@ -2034,7 +2061,6 @@ test('mobile profile page matches the reference layout sections', async () => {
|
||||
).toBe(true);
|
||||
for (const label of [
|
||||
'泥点充值',
|
||||
'邀请好友',
|
||||
'兑换码',
|
||||
'玩家社区',
|
||||
'反馈与建议',
|
||||
@@ -2172,28 +2198,25 @@ test('wallet ledger modal shows empty and error states', async () => {
|
||||
expect(screen.getByText('重新加载')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('profile invite shortcut shows reward subtitle and invited users', async () => {
|
||||
test('profile community shortcut shows reward subtitle and invited users', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
renderProfileView(vi.fn(), {}, { createdAt: buildFreshProfileCreatedAt() });
|
||||
|
||||
const inviteButton = screen.getByRole('button', { name: /邀请好友/u });
|
||||
expect(within(inviteButton).getByText('双方得 30 泥点')).toBeTruthy();
|
||||
expect(screen.queryByRole('button', { name: /邀请好友/u })).toBeNull();
|
||||
|
||||
const communityButton = screen.getByRole('button', { name: /玩家社区/u });
|
||||
expect(within(communityButton).getByText('交流心得 领取福利')).toBeTruthy();
|
||||
|
||||
await user.click(inviteButton);
|
||||
await user.click(communityButton);
|
||||
|
||||
expect(mockGetRpgProfileReferralInviteCenter).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
await screen.findByText('邀请一个用户注册,双方都可以获得30泥点。'),
|
||||
).toBeTruthy();
|
||||
expect(screen.getByText('每日最多获得十次邀请奖励。')).toBeTruthy();
|
||||
expect(screen.getByText('成功邀请')).toBeTruthy();
|
||||
expect(screen.getByText('被邀请玩家')).toBeTruthy();
|
||||
expect(screen.queryByText('已奖')).toBeNull();
|
||||
expect(screen.queryByText('今日')).toBeNull();
|
||||
expect(screen.getByAltText('玩家社区微信群二维码')).toBeTruthy();
|
||||
expect(screen.getByAltText('玩家社区 QQ 群二维码')).toBeTruthy();
|
||||
expect(screen.getByText('微信群')).toBeTruthy();
|
||||
expect(screen.getByText('QQ群')).toBeTruthy();
|
||||
expect(screen.queryByText('成功邀请')).toBeNull();
|
||||
expect(screen.queryByText('被邀请玩家')).toBeNull();
|
||||
});
|
||||
|
||||
test('profile page hides legacy redeem invite secondary shortcut for fresh accounts', async () => {
|
||||
@@ -2203,50 +2226,55 @@ test('profile page hides legacy redeem invite secondary shortcut for fresh accou
|
||||
{ createdAt: buildFreshProfileCreatedAt() },
|
||||
);
|
||||
|
||||
const inviteButton = screen.getByRole('button', { name: /邀请好友/u });
|
||||
const communityButton = screen.getByRole('button', { name: /玩家社区/u });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockGetRpgProfileReferralInviteCenter).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
expect(inviteButton).toBeTruthy();
|
||||
expect(screen.queryByRole('button', { name: /邀请好友/u })).toBeNull();
|
||||
expect(communityButton).toBeTruthy();
|
||||
expect(screen.queryByRole('region', { name: '次级入口' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: /填邀请码/u })).toBeNull();
|
||||
});
|
||||
|
||||
test('profile redeem invite shortcut hides after redeemed or one day old', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
mockGetRpgProfileReferralInviteCenter.mockResolvedValueOnce(
|
||||
mockBuildReferralCenter({
|
||||
invitedUsers: [],
|
||||
hasRedeemedCode: true,
|
||||
boundInviterUserId: 'user-2',
|
||||
boundAt: '2026-05-01T08:00:00Z',
|
||||
}),
|
||||
);
|
||||
const { unmount } = renderProfileView();
|
||||
await user.click(screen.getByRole('button', { name: /邀请好友/u }));
|
||||
await screen.findByText('成功邀请');
|
||||
const firstShortcutRegion = screen.getByRole('region', { name: '常用功能' });
|
||||
expect(
|
||||
within(firstShortcutRegion).queryByRole('button', { name: /邀请好友/u }),
|
||||
).toBeNull();
|
||||
expect(
|
||||
within(firstShortcutRegion).queryByRole('button', { name: /填邀请码/u }),
|
||||
).toBeNull();
|
||||
await screen.findByText('1 / 1');
|
||||
await waitFor(() => {
|
||||
expect(mockGetRpgProfileTasks).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
unmount();
|
||||
|
||||
renderProfileView(vi.fn(), {}, { createdAt: '2026-04-01T00:00:00.000Z' });
|
||||
const expiredShortcutRegion = screen.getByRole('region', {
|
||||
name: '常用功能',
|
||||
});
|
||||
expect(
|
||||
within(expiredShortcutRegion).queryByRole('button', {
|
||||
name: /邀请好友/u,
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(
|
||||
within(expiredShortcutRegion).queryByRole('button', {
|
||||
name: /填邀请码/u,
|
||||
}),
|
||||
).toBeNull();
|
||||
await screen.findByText('1 / 1');
|
||||
await waitFor(() => {
|
||||
expect(mockGetRpgProfileTasks).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
test('invite query opens login modal for logged out users', async () => {
|
||||
@@ -2303,6 +2331,22 @@ test('profile redeem invite query modal submits code after login', async () => {
|
||||
expect(screen.queryByRole('region', { name: '次级入口' })).toBeNull();
|
||||
});
|
||||
|
||||
test('profile task center reloads when refresh key changes', async () => {
|
||||
const { rerender } = renderProfileView();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockGetRpgProfileTasks).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
rerender(
|
||||
<ProfileHomeViewHarness profileTaskRefreshKey={1} />,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockGetRpgProfileTasks).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
test('opens reward code modal from profile action on mobile', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -2330,8 +2374,8 @@ test('profile page shows legal entries and hides archive shortcuts', async () =>
|
||||
?.classList.contains('platform-profile-shortcut-grid'),
|
||||
).toBe(true);
|
||||
expect(
|
||||
within(shortcutRegion).getByRole('button', { name: /邀请好友/u }),
|
||||
).toBeTruthy();
|
||||
within(shortcutRegion).queryByRole('button', { name: /邀请好友/u }),
|
||||
).toBeNull();
|
||||
expect(
|
||||
within(shortcutRegion).getByRole('button', { name: /玩家社区/u }),
|
||||
).toBeTruthy();
|
||||
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
Star,
|
||||
ThumbsUp,
|
||||
Ticket,
|
||||
UserPlus,
|
||||
UserRound,
|
||||
XCircle,
|
||||
} from 'lucide-react';
|
||||
@@ -50,7 +49,6 @@ import profileClockImage from '../../../media/profile/_Image (1).png';
|
||||
import profileGamepadImage from '../../../media/profile/_Image (2).png';
|
||||
import profileStillLifeImage from '../../../media/profile/_Image (3).png';
|
||||
import profileCoinsImage from '../../../media/profile/_Image (4).png';
|
||||
import profileInviteImage from '../../../media/profile/_Image (5).png';
|
||||
import profileGiftImage from '../../../media/profile/_Image (6).png';
|
||||
import profileCommunityImage from '../../../media/profile/_Image (7).png';
|
||||
import profileFeedbackImage from '../../../media/profile/_Image (8).png';
|
||||
@@ -4026,6 +4024,7 @@ export function RpgEntryHomeView({
|
||||
useState<ProfileTaskCenterResponse | null>(null);
|
||||
const [taskCenterError, setTaskCenterError] = useState<string | null>(null);
|
||||
const [isLoadingTaskCenter, setIsLoadingTaskCenter] = useState(false);
|
||||
const taskCenterRequestIdRef = useRef(0);
|
||||
const [claimingTaskId, setClaimingTaskId] = useState<string | null>(null);
|
||||
const [taskClaimSuccess, setTaskClaimSuccess] = useState<string | null>(null);
|
||||
const [isQrScannerOpen, setIsQrScannerOpen] = useState(false);
|
||||
@@ -4045,6 +4044,7 @@ export function RpgEntryHomeView({
|
||||
: readProfileInviteCodeFromLocationSearch(window.location.search),
|
||||
[],
|
||||
);
|
||||
const promptedLoginForInviteQueryRef = useRef(false);
|
||||
const autoOpenedInviteQueryRef = useRef(false);
|
||||
const [referralRedeemCode, setReferralRedeemCode] = useState(
|
||||
pendingProfileInviteCode,
|
||||
@@ -4375,12 +4375,15 @@ export function RpgEntryHomeView({
|
||||
return;
|
||||
}
|
||||
|
||||
autoOpenedInviteQueryRef.current = true;
|
||||
if (!authUi?.user) {
|
||||
authUi?.openLoginModal();
|
||||
if (!promptedLoginForInviteQueryRef.current) {
|
||||
promptedLoginForInviteQueryRef.current = true;
|
||||
authUi?.openLoginModal();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
autoOpenedInviteQueryRef.current = true;
|
||||
setReferralRedeemCode(pendingProfileInviteCode);
|
||||
setReferralError(null);
|
||||
setReferralSuccess(null);
|
||||
@@ -4779,21 +4782,34 @@ export function RpgEntryHomeView({
|
||||
};
|
||||
}, [handleWechatPayResult]);
|
||||
const loadTaskCenter = useCallback(() => {
|
||||
const requestId = ++taskCenterRequestIdRef.current;
|
||||
setTaskCenterError(null);
|
||||
setIsLoadingTaskCenter(true);
|
||||
void getRpgProfileTasks()
|
||||
.then(setTaskCenter)
|
||||
.then((center) => {
|
||||
if (requestId === taskCenterRequestIdRef.current) {
|
||||
setTaskCenter(center);
|
||||
}
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
if (requestId !== taskCenterRequestIdRef.current) {
|
||||
return;
|
||||
}
|
||||
setTaskCenter(null);
|
||||
setTaskCenterError(
|
||||
error instanceof Error ? error.message : '读取每日任务失败',
|
||||
);
|
||||
})
|
||||
.finally(() => setIsLoadingTaskCenter(false));
|
||||
.finally(() => {
|
||||
if (requestId === taskCenterRequestIdRef.current) {
|
||||
setIsLoadingTaskCenter(false);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab !== 'profile' || !isAuthenticated) {
|
||||
taskCenterRequestIdRef.current += 1;
|
||||
setTaskCenter(null);
|
||||
setTaskCenterError(null);
|
||||
return;
|
||||
@@ -6243,13 +6259,6 @@ export function RpgEntryHomeView({
|
||||
imageSrc={profileCoinsImage}
|
||||
onClick={openRechargeOrRewardCodeModal}
|
||||
/>
|
||||
<ProfileShortcutButton
|
||||
label="邀请好友"
|
||||
subLabel="双方得 30 泥点"
|
||||
icon={UserPlus}
|
||||
imageSrc={profileInviteImage}
|
||||
onClick={() => openProfilePopupPanel('invite')}
|
||||
/>
|
||||
<ProfileShortcutButton
|
||||
label="兑换码"
|
||||
subLabel="领取福利"
|
||||
|
||||
Reference in New Issue
Block a user