修复创作者主页返回循环
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m7s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m47s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m16s
Project CI / Frontend tests (pull_request) Successful in 3m28s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m7s
Project CI / Repository checks (pull_request) Successful in 6m49s
Project CI / Backend tests (pull_request) Successful in 8m37s
Project CI / Native shell tests (pull_request) Successful in 10m15s

返回兜底改为替换历史,关系列表回所属主页,主页回游戏广场
主页资料行改为静态展示,保留详情和关系列表的作者链接
补充导航与共享组件回归测试,并同步产品规则和排障记忆
This commit is contained in:
2026-10-05 12:30:26 +08:00
parent cadde8f8c5
commit bdcf67cb5f
9 changed files with 202 additions and 20 deletions
-2
View File
@@ -8,7 +8,6 @@ import type { CreatorProfile } from '../../../packages/shared/src/contracts/crea
import {
connectionsPath,
type CreatorConnectionKind,
creatorPath,
getCreator,
} from '../../services/creatorProfileClient';
import {
@@ -128,7 +127,6 @@ function CreatorProfileContent({
name={profile.user.displayName}
avatarUrl={profile.user.avatarUrl}
subtitle={`ID ${profile.user.publicUserCode}`}
href={creatorPath(id)}
actions={<CreatorRelationshipControl target={id} />}
/>
<nav className="creator-counts" aria-label="关注与粉丝">
+28 -1
View File
@@ -304,6 +304,33 @@ it('同账号切换主页,迟到资料不覆盖新目标;关系读取失败
followerCount: 2,
});
});
expect(screen.getByRole('link', { name: /新的创作者/ })).toBeTruthy();
expect(screen.getByText('新的创作者')).toBeTruthy();
expect(screen.queryByText('旧作者')).toBeNull();
});
it.each([null, 'a', 'b'])(
'主页 %s 顶部资料不自跳转,关系入口仍可用',
async (id) => {
const target = id ?? 'a';
api.profile.mockResolvedValue({
user: { ...user, id: target },
followingCount: 1,
followerCount: 2,
});
render(
<CreatorPage
id={id}
onOpenConnections={callbacks.onOpenConnections}
onOpenGame={vi.fn()}
onBack={vi.fn()}
/>,
);
const name = await screen.findByText('用户乙');
expect(name.closest('a, button, [tabindex]')).toBeNull();
fireEvent.click(screen.getByRole('link', { name: '1 关注' }));
expect(callbacks.onOpenConnections).toHaveBeenCalledWith(
target,
'following',
);
},
);
@@ -12,7 +12,10 @@ import { type ReactElement, type ReactNode, useState } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { AuthUser } from '../../../packages/shared/src/contracts/auth';
import { pushAppHistoryPath } from '../../routing/activeAppPageRoutes';
import {
hasAppHistoryBackEntry,
pushAppHistoryPath,
} from '../../routing/activeAppPageRoutes';
import {
usePlatformWalletLifecycle,
usePlatformWalletStore,
@@ -63,6 +66,16 @@ const gameDistributionMock = vi.hoisted(() => ({
vi.mock('../../services/gameDistributionClient', () => gameDistributionMock);
const creatorClientMock = vi.hoisted(() => ({
getCreator: vi.fn(),
getCreatorConnections: vi.fn(),
getCreatorRelationship: vi.fn(),
}));
vi.mock('../../services/creatorProfileClient', async (original) => ({
...(await original<typeof import('../../services/creatorProfileClient')>()),
...creatorClientMock,
}));
const frontendRuntimeConfigMock = vi.hoisted(() => ({
loadFrontendRuntimeConfig: vi.fn(),
}));
@@ -944,6 +957,112 @@ describe('游戏发布灰度入口', () => {
});
});
describe('创作者页面返回', () => {
beforeEach(() => {
Object.defineProperty(Element.prototype, 'scrollTo', {
configurable: true,
value: vi.fn(),
});
authUiMock.value.user = {
id: 'user-1',
publicUserCode: '100001',
displayName: '测试用户',
avatarUrl: null,
phoneNumberMasked: null,
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
};
authUiMock.value.canAccessProtectedData = true;
gameDistributionMock.listGames.mockResolvedValue([]);
creatorClientMock.getCreator.mockResolvedValue({
user: authUiMock.value.user,
followingCount: 0,
followerCount: 0,
});
creatorClientMock.getCreatorConnections.mockResolvedValue({
items: [],
nextCursor: null,
total: 0,
});
creatorClientMock.getCreatorRelationship.mockResolvedValue({
relationship: { isSelf: false, isFollowing: false, isFollowedBy: false },
});
});
it.each(['/creators', '/creators?id=user-1', '/creators?id=user-2'])(
'直接打开 %s 后返回替换为游戏广场,不增加历史',
async (path) => {
window.history.replaceState(null, '', path);
const historyLength = window.history.length;
const setSelectionStage = vi.fn();
render(
<PlatformEntryFlowShellImpl
selectionStage="creator-home"
setSelectionStage={setSelectionStage}
/>,
);
fireEvent.click(await screen.findByRole('button', { name: '返回' }));
expect(window.location.pathname).toBe('/games');
expect(window.location.search).toBe('');
expect(window.history.length).toBe(historyLength);
expect(hasAppHistoryBackEntry()).toBe(false);
expect(setSelectionStage).toHaveBeenCalledWith('games', {
path: '/games',
});
},
);
it.each(['following', 'followers'])(
'直接打开 %s 列表连续返回:所属主页、游戏广场,均不增加历史',
async (tab) => {
window.history.replaceState(
null,
'',
`/creators/connections?id=user-2&tab=${tab}`,
);
const historyLength = window.history.length;
render(
<StatefulPlatformEntryFlowShell initialStage="creator-connections" />,
);
fireEvent.click(await screen.findByRole('button', { name: '返回' }));
await screen.findByRole('heading', { name: '创作者主页' });
expect(window.location.pathname + window.location.search).toBe(
'/creators?id=user-2',
);
expect(hasAppHistoryBackEntry()).toBe(false);
fireEvent.click(screen.getByRole('button', { name: '返回' }));
expect(window.location.pathname).toBe('/games');
expect(window.history.length).toBe(historyLength);
expect(hasAppHistoryBackEntry()).toBe(false);
},
);
it.each([
'/games/detail?id=game-1',
'/creators/connections?id=user-1&tab=following',
])('从 %s 进入主页后使用原生返回,不覆盖来源', async (source) => {
window.history.replaceState(null, '', source);
pushAppHistoryPath('/creators?id=user-2');
const back = vi.spyOn(window.history, 'back').mockImplementation(() => {});
try {
const setSelectionStage = vi.fn();
render(
<PlatformEntryFlowShellImpl
selectionStage="creator-home"
setSelectionStage={setSelectionStage}
/>,
);
fireEvent.click(await screen.findByRole('button', { name: '返回' }));
expect(back).toHaveBeenCalledTimes(1);
expect(setSelectionStage).not.toHaveBeenCalled();
expect(window.location.search).toBe('?id=user-2');
} finally {
back.mockRestore();
}
});
});
describe('游戏详情返回', () => {
function mockDetailGame() {
gameDistributionMock.getGame.mockResolvedValue({
@@ -466,8 +466,11 @@ export function PlatformEntryFlowShellImpl({
selectionStage === 'creator-connections'
? new URLSearchParams(window.location.search).get('id')
: null;
openCreator(owner ?? undefined);
}, [openCreator, selectionStage]);
// 兜底返回替换当前条目,避免主页深链与本人主页互相新增历史。
const path = owner ? creatorPath(owner) : '/games';
replaceAppHistoryPath(path);
setSelectionStage(owner ? 'creator-home' : 'games', { path });
}, [selectionStage, setSelectionStage]);
const openGames = useCallback(() => {
setSelectionStage('games', { path: '/games' });
}, [setSelectionStage]);