1
This commit is contained in:
@@ -1035,6 +1035,15 @@ afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.unstubAllEnvs();
|
||||
vi.unstubAllGlobals();
|
||||
Object.defineProperty(HTMLMediaElement.prototype, 'play', {
|
||||
configurable: true,
|
||||
value: vi.fn(async () => undefined),
|
||||
});
|
||||
Object.defineProperty(navigator, 'mediaDevices', {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
Reflect.deleteProperty(globalThis as Record<string, unknown>, 'BarcodeDetector');
|
||||
window.wx = undefined;
|
||||
document
|
||||
.querySelectorAll(
|
||||
@@ -1832,10 +1841,68 @@ test('profile daily task shortcut opens task center and claims reward', async ()
|
||||
});
|
||||
expect(onRechargeSuccess).toHaveBeenCalledTimes(1);
|
||||
expect(await screen.findByText('已领取 10 泥点')).toBeTruthy();
|
||||
expect(
|
||||
(screen.getByRole('button', { name: '已领取' }) as HTMLButtonElement)
|
||||
.disabled,
|
||||
).toBe(true);
|
||||
expect(screen.queryByRole('button', { name: '已领取' })).toBeNull();
|
||||
expect(screen.getByText('暂无任务')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('profile task center keeps only the highest priority actionable task', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
mockGetRpgProfileTasks.mockResolvedValueOnce(
|
||||
mockBuildTaskCenter({
|
||||
tasks: [
|
||||
{
|
||||
taskId: 'claimed_low',
|
||||
title: '低优先级已完成',
|
||||
description: '',
|
||||
eventKey: 'profile.task.claimed_low',
|
||||
cycle: 'daily',
|
||||
threshold: 1,
|
||||
progressCount: 1,
|
||||
rewardPoints: 5,
|
||||
status: 'claimed',
|
||||
dayKey: 20260503,
|
||||
claimedAt: '2026-05-03T08:01:00Z',
|
||||
updatedAt: '2026-05-03T08:01:00Z',
|
||||
},
|
||||
{
|
||||
taskId: 'claimable_mid',
|
||||
title: '中优先级可领取',
|
||||
description: '',
|
||||
eventKey: 'profile.task.claimable_mid',
|
||||
cycle: 'daily',
|
||||
threshold: 2,
|
||||
progressCount: 2,
|
||||
rewardPoints: 10,
|
||||
status: 'claimable',
|
||||
dayKey: 20260503,
|
||||
claimedAt: null,
|
||||
updatedAt: '2026-05-03T08:01:00Z',
|
||||
},
|
||||
{
|
||||
taskId: 'incomplete_high',
|
||||
title: '高优先级未完成',
|
||||
description: '',
|
||||
eventKey: 'profile.task.incomplete_high',
|
||||
cycle: 'daily',
|
||||
threshold: 3,
|
||||
progressCount: 1,
|
||||
rewardPoints: 20,
|
||||
status: 'incomplete',
|
||||
dayKey: 20260503,
|
||||
claimedAt: null,
|
||||
updatedAt: '2026-05-03T08:01:00Z',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
renderProfileView();
|
||||
await user.click(screen.getByRole('button', { name: /每日任务/u }));
|
||||
|
||||
expect(await screen.findByText('中优先级可领取')).toBeTruthy();
|
||||
expect(screen.queryByText('高优先级未完成')).toBeNull();
|
||||
expect(screen.queryByText('低优先级已完成')).toBeNull();
|
||||
});
|
||||
|
||||
test('profile total play time card always uses hours', () => {
|
||||
@@ -1882,21 +1949,35 @@ test('profile stats cards are centered without update timestamp', () => {
|
||||
});
|
||||
|
||||
test('mobile profile page matches the reference layout sections', async () => {
|
||||
mockWechatMobileLayout();
|
||||
mockNarrowMobileLayout();
|
||||
|
||||
const { container } = renderProfileView(vi.fn(), {
|
||||
walletBalance: 70,
|
||||
totalPlayTimeMs: 0,
|
||||
playedWorldCount: 0,
|
||||
}, { createdAt: buildFreshProfileCreatedAt() });
|
||||
});
|
||||
|
||||
const profilePage = container.querySelector('.platform-profile-page');
|
||||
expect(profilePage).toBeTruthy();
|
||||
expect(profilePage?.classList.contains('platform-page-stage')).toBe(true);
|
||||
expect(profilePage?.classList.contains('platform-page-stage')).toBe(false);
|
||||
expect(profilePage?.querySelector('.platform-profile-scene-decor')).toBeTruthy();
|
||||
expect(profilePage?.classList.contains('platform-profile-page')).toBe(true);
|
||||
expect(profilePage?.getAttribute('style') ?? '').not.toContain('overflow: hidden');
|
||||
|
||||
const topbar = container.querySelector('.platform-mobile-topbar');
|
||||
expect(topbar).toBeTruthy();
|
||||
expect(
|
||||
within(topbar as HTMLElement).getByRole('button', { name: '扫码' }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
within(topbar as HTMLElement).getByRole('button', { name: '打开设置' }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
within(topbar as HTMLElement).queryByRole('button', {
|
||||
name: /充值/u,
|
||||
}),
|
||||
).toBeNull();
|
||||
|
||||
const membershipCard = screen.getByRole('button', { name: '查看权益' });
|
||||
expect(membershipCard.className).toContain('platform-profile-membership-card');
|
||||
expect(
|
||||
@@ -1914,6 +1995,7 @@ test('mobile profile page matches the reference layout sections', async () => {
|
||||
expect(
|
||||
within(statPanel).getByRole('button', { name: /泥点余额\s*70/u }).className,
|
||||
).toContain('platform-profile-stat-card');
|
||||
expect(statPanel.querySelectorAll('.platform-profile-stat-card__icon')).toHaveLength(3);
|
||||
|
||||
const dailyTask = screen.getByRole('button', { name: /每日任务/u });
|
||||
expect(dailyTask.className).toContain('platform-profile-daily-task-card');
|
||||
@@ -1953,18 +2035,11 @@ test('mobile profile page matches the reference layout sections', async () => {
|
||||
within(settingsRegion).getByRole('button', { name: new RegExp(label, 'u') }),
|
||||
).toBeTruthy();
|
||||
}
|
||||
expect(
|
||||
within(settingsRegion).queryByRole('button', { name: /存档/u }),
|
||||
).toBeNull();
|
||||
|
||||
const secondaryShortcuts = screen.getByRole('region', {
|
||||
name: '次级入口',
|
||||
});
|
||||
expect(
|
||||
within(secondaryShortcuts).getByRole('button', { name: /存档/u }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
await within(secondaryShortcuts).findByRole('button', {
|
||||
name: /填邀请码/u,
|
||||
}),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByRole('region', { name: '次级入口' })).toBeNull();
|
||||
|
||||
const profileHeader = profilePage?.querySelector('.platform-profile-header');
|
||||
expect(profileHeader).toBeTruthy();
|
||||
@@ -1982,6 +2057,46 @@ test('mobile profile page matches the reference layout sections', async () => {
|
||||
expect(legalRegion.querySelector('.platform-profile-legal-strip__link')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('profile scan action opens camera scanner instead of recharge panel', async () => {
|
||||
const user = userEvent.setup();
|
||||
const stopTrack = vi.fn();
|
||||
const stream = {
|
||||
getTracks: () => [{ stop: stopTrack }],
|
||||
} as unknown as MediaStream;
|
||||
const getUserMedia = vi.fn(async () => stream);
|
||||
|
||||
mockNarrowMobileLayout();
|
||||
Object.defineProperty(globalThis, 'BarcodeDetector', {
|
||||
configurable: true,
|
||||
value: class {
|
||||
async detect() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
});
|
||||
Object.defineProperty(navigator, 'mediaDevices', {
|
||||
configurable: true,
|
||||
value: { getUserMedia },
|
||||
});
|
||||
|
||||
renderProfileView();
|
||||
const topbar = document.querySelector('.platform-mobile-topbar');
|
||||
expect(topbar).toBeTruthy();
|
||||
|
||||
await user.click(
|
||||
within(topbar as HTMLElement).getByRole('button', { name: '扫码' }),
|
||||
);
|
||||
|
||||
expect(await screen.findByRole('dialog', { name: '扫码' })).toBeTruthy();
|
||||
await waitFor(() => {
|
||||
expect(getUserMedia).toHaveBeenCalledWith({
|
||||
audio: false,
|
||||
video: { facingMode: { ideal: 'environment' } },
|
||||
});
|
||||
});
|
||||
expect(mockGetRpgProfileRechargeCenter).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('desktop account entry uses saved avatar image when available', () => {
|
||||
mockDesktopLayout();
|
||||
const avatarUrl = 'data:image/png;base64,AAAA';
|
||||
@@ -2195,7 +2310,7 @@ test('opens reward code modal from profile action on mobile', async () => {
|
||||
expect(screen.getByLabelText('关闭兑换码')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('profile page shows legal entries and ICP record link', async () => {
|
||||
test('profile page shows legal entries and hides archive shortcuts', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
renderProfileView();
|
||||
@@ -2221,18 +2336,9 @@ test('profile page shows legal entries and ICP record link', async () => {
|
||||
|
||||
const settingsRegion = screen.getByRole('region', { name: '设置入口' });
|
||||
expect(
|
||||
within(settingsRegion).getByRole('button', { name: /存档/u }),
|
||||
).toBeTruthy();
|
||||
|
||||
const secondaryShortcuts = screen.getByRole('region', {
|
||||
name: '次级入口',
|
||||
});
|
||||
expect(
|
||||
within(secondaryShortcuts).getByRole('button', { name: /存档/u }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
within(secondaryShortcuts).queryByRole('button', { name: /填邀请码/u }),
|
||||
within(settingsRegion).queryByRole('button', { name: /存档/u }),
|
||||
).toBeNull();
|
||||
expect(screen.queryByRole('region', { name: '次级入口' })).toBeNull();
|
||||
|
||||
const legalRegion = screen.getByRole('region', { name: '法律信息' });
|
||||
expect(
|
||||
@@ -2697,7 +2803,7 @@ test('logged out mobile shell defaults to discover tab', () => {
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
test('logged out recommend tab opens login modal and shows cover only', async () => {
|
||||
test('logged out recommend tab opens recommend runtime directly', async () => {
|
||||
const user = userEvent.setup();
|
||||
const { container, openLoginModal } = renderStatefulLoggedOutHomeView({
|
||||
latestEntries: [puzzlePublicEntry],
|
||||
@@ -2715,17 +2821,17 @@ test('logged out recommend tab opens login modal and shows cover only', async ()
|
||||
expect(openLoginModal).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
container.querySelector('.platform-recommend-cover-only'),
|
||||
).toBeTruthy();
|
||||
).toBeNull();
|
||||
expect(container.querySelector('.platform-mobile-topbar')).toBeNull();
|
||||
expect(
|
||||
container.querySelector('.platform-mobile-entry-shell--recommend'),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByTestId('recommend-runtime')).toBeNull();
|
||||
expect(screen.queryByLabelText('奇幻拼图 作品信息')).toBeNull();
|
||||
expect(screen.getByTestId('recommend-runtime')).toBeTruthy();
|
||||
expect(screen.getByLabelText('奇幻拼图 作品信息')).toBeTruthy();
|
||||
expect(screen.getAllByText('奇幻拼图').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('logged out recommend cover opens login modal again', async () => {
|
||||
test('logged out recommend meta keeps gallery detail gated', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onOpenGalleryDetail = vi.fn();
|
||||
const { openLoginModal } = renderStatefulLoggedOutHomeView({
|
||||
@@ -2741,12 +2847,9 @@ test('logged out recommend cover opens login modal again', async () => {
|
||||
await user.click(
|
||||
within(bottomNav as HTMLElement).getByRole('button', { name: '推荐' }),
|
||||
);
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /登录后游玩 奇幻拼图/u }),
|
||||
);
|
||||
await user.click(screen.getByLabelText('奇幻拼图 作品信息'));
|
||||
|
||||
expect(openLoginModal).toHaveBeenCalledTimes(2);
|
||||
expect(openLoginModal).toHaveBeenLastCalledWith();
|
||||
expect(openLoginModal).toHaveBeenCalledTimes(1);
|
||||
expect(onOpenGalleryDetail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -3082,7 +3185,7 @@ test('mobile recommend meta loads real author avatar from public user summary',
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
document
|
||||
.querySelector('.platform-recommend-cover-only__author img')
|
||||
.querySelector('.platform-recommend-work-meta__avatar img')
|
||||
?.getAttribute('src'),
|
||||
).toBe('data:image/png;base64,AUTHOR');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user