修复推荐页切作品误报无法进入
区分推荐运行态启动 pending 与真实失败 切作品时保持封面遮罩等待自动重试 补充推荐页 pending 切换回归测试 更新玩法链路文档的失败态判断口径
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -12794,6 +12794,54 @@ export function PlatformEntryFlowShellImpl({
|
|||||||
setPuzzleError(null);
|
setPuzzleError(null);
|
||||||
}
|
}
|
||||||
}, [activeRecommendRuntimeKind, setPuzzleError]);
|
}, [activeRecommendRuntimeKind, setPuzzleError]);
|
||||||
|
const isRecommendRuntimeStartPending = useCallback(
|
||||||
|
(runtimeKind: RecommendRuntimeKind) => {
|
||||||
|
if (runtimeKind === 'big-fish') {
|
||||||
|
return isBigFishBusy;
|
||||||
|
}
|
||||||
|
if (runtimeKind === 'match3d') {
|
||||||
|
return isMatch3DBusy;
|
||||||
|
}
|
||||||
|
if (runtimeKind === 'puzzle') {
|
||||||
|
return isPuzzleBusy || puzzleStartInFlightKeyRef.current !== null;
|
||||||
|
}
|
||||||
|
if (runtimeKind === 'jump-hop') {
|
||||||
|
return isJumpHopBusy;
|
||||||
|
}
|
||||||
|
if (runtimeKind === 'puzzle-clear') {
|
||||||
|
return isPuzzleClearBusy;
|
||||||
|
}
|
||||||
|
if (runtimeKind === 'wooden-fish') {
|
||||||
|
return isWoodenFishBusy;
|
||||||
|
}
|
||||||
|
if (runtimeKind === 'square-hole') {
|
||||||
|
return isSquareHoleBusy;
|
||||||
|
}
|
||||||
|
if (runtimeKind === 'visual-novel') {
|
||||||
|
return isVisualNovelBusy;
|
||||||
|
}
|
||||||
|
if (runtimeKind === 'bark-battle') {
|
||||||
|
return isBarkBattleBusy;
|
||||||
|
}
|
||||||
|
if (runtimeKind === 'edutainment') {
|
||||||
|
return isBabyObjectMatchBusy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[
|
||||||
|
isBabyObjectMatchBusy,
|
||||||
|
isBarkBattleBusy,
|
||||||
|
isBigFishBusy,
|
||||||
|
isJumpHopBusy,
|
||||||
|
isMatch3DBusy,
|
||||||
|
isPuzzleBusy,
|
||||||
|
isPuzzleClearBusy,
|
||||||
|
isSquareHoleBusy,
|
||||||
|
isVisualNovelBusy,
|
||||||
|
isWoodenFishBusy,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const leaveAgentWorkspace = useCallback(() => {
|
const leaveAgentWorkspace = useCallback(() => {
|
||||||
sessionController.resetSessionViewState();
|
sessionController.resetSessionViewState();
|
||||||
@@ -15843,6 +15891,11 @@ export function PlatformEntryFlowShellImpl({
|
|||||||
if (started) {
|
if (started) {
|
||||||
setActiveRecommendRuntimeKind(runtimeKind);
|
setActiveRecommendRuntimeKind(runtimeKind);
|
||||||
setActiveRecommendRuntimeError(null);
|
setActiveRecommendRuntimeError(null);
|
||||||
|
} else if (isRecommendRuntimeStartPending(runtimeKind)) {
|
||||||
|
// 中文注释:切换推荐作品时,旧作品启动请求或退出收口可能仍在进行;
|
||||||
|
// 这类中间态继续保留封面遮罩,不能误报成作品不可进入。
|
||||||
|
setActiveRecommendRuntimeKind(runtimeKind);
|
||||||
|
setActiveRecommendRuntimeError(null);
|
||||||
} else {
|
} else {
|
||||||
setActiveRecommendRuntimeKind(null);
|
setActiveRecommendRuntimeKind(null);
|
||||||
setActiveRecommendRuntimeError('作品暂时无法进入,请稍后再试。');
|
setActiveRecommendRuntimeError('作品暂时无法进入,请稍后再试。');
|
||||||
@@ -15863,6 +15916,7 @@ export function PlatformEntryFlowShellImpl({
|
|||||||
[
|
[
|
||||||
activeRecommendEntryKey,
|
activeRecommendEntryKey,
|
||||||
barkBattleGalleryEntries,
|
barkBattleGalleryEntries,
|
||||||
|
isRecommendRuntimeStartPending,
|
||||||
saveAndExitRecommendPuzzleRuntime,
|
saveAndExitRecommendPuzzleRuntime,
|
||||||
selectedPuzzleDetail,
|
selectedPuzzleDetail,
|
||||||
setBarkBattleError,
|
setBarkBattleError,
|
||||||
@@ -16394,7 +16448,9 @@ export function PlatformEntryFlowShellImpl({
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
(activeRecommendEntry !== null && isActiveRecommendRuntimeReady) ||
|
(activeRecommendEntry !== null && isActiveRecommendRuntimeReady) ||
|
||||||
isStartingRecommendEntry
|
isStartingRecommendEntry ||
|
||||||
|
(activeRecommendRuntimeKind !== null &&
|
||||||
|
isRecommendRuntimeStartPending(activeRecommendRuntimeKind))
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -16413,6 +16469,7 @@ export function PlatformEntryFlowShellImpl({
|
|||||||
bigFishRun,
|
bigFishRun,
|
||||||
jumpHopRun,
|
jumpHopRun,
|
||||||
isActiveRecommendRuntimeReady,
|
isActiveRecommendRuntimeReady,
|
||||||
|
isRecommendRuntimeStartPending,
|
||||||
isStartingRecommendEntry,
|
isStartingRecommendEntry,
|
||||||
match3dRun,
|
match3dRun,
|
||||||
platformBootstrap.isLoadingPlatform,
|
platformBootstrap.isLoadingPlatform,
|
||||||
|
|||||||
@@ -7658,6 +7658,113 @@ test('logged out home recommendation next starts the next puzzle work', async ()
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('home recommendation keeps cover while switching during a pending puzzle start', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const firstWork = {
|
||||||
|
workId: 'puzzle-work-pending-next-1',
|
||||||
|
profileId: 'puzzle-profile-pending-next-1',
|
||||||
|
ownerUserId: 'user-2',
|
||||||
|
sourceSessionId: 'puzzle-session-pending-next-1',
|
||||||
|
authorDisplayName: '拼图作者',
|
||||||
|
levelName: '雨港电路',
|
||||||
|
summary: '第一张公开拼图仍在启动。',
|
||||||
|
themeTags: ['雨港', '拼图'],
|
||||||
|
coverImageSrc: null,
|
||||||
|
coverAssetId: null,
|
||||||
|
publicationStatus: 'published',
|
||||||
|
updatedAt: '2026-04-25T10:00:00.000Z',
|
||||||
|
publishedAt: '2026-04-25T10:00:00.000Z',
|
||||||
|
playCount: 47,
|
||||||
|
likeCount: 1,
|
||||||
|
publishReady: true,
|
||||||
|
} satisfies PuzzleWorkSummary;
|
||||||
|
const secondWork = {
|
||||||
|
...firstWork,
|
||||||
|
workId: 'puzzle-work-pending-next-2',
|
||||||
|
profileId: 'puzzle-profile-pending-next-2',
|
||||||
|
ownerUserId: 'user-3',
|
||||||
|
sourceSessionId: 'puzzle-session-pending-next-2',
|
||||||
|
authorDisplayName: '贝壳作者',
|
||||||
|
levelName: '贝壳潮汐',
|
||||||
|
summary: '第二张公开拼图。',
|
||||||
|
themeTags: ['贝壳', '拼图'],
|
||||||
|
playCount: 1,
|
||||||
|
likeCount: 0,
|
||||||
|
updatedAt: '2026-04-25T09:00:00.000Z',
|
||||||
|
publishedAt: '2026-04-25T09:00:00.000Z',
|
||||||
|
} satisfies PuzzleWorkSummary;
|
||||||
|
let resolveFirstRun!: (value: { run: PuzzleRunSnapshot }) => void;
|
||||||
|
|
||||||
|
vi.mocked(listPuzzleGallery).mockResolvedValue({
|
||||||
|
items: [firstWork, secondWork],
|
||||||
|
});
|
||||||
|
vi.mocked(getPuzzleGalleryDetail).mockImplementation(async (profileId) => ({
|
||||||
|
item: profileId === secondWork.profileId ? secondWork : firstWork,
|
||||||
|
}));
|
||||||
|
vi.mocked(startPuzzleRun).mockImplementationOnce(
|
||||||
|
(async () =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
resolveFirstRun = resolve;
|
||||||
|
})) as typeof startPuzzleRun,
|
||||||
|
);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TestWrapper
|
||||||
|
authValue={createAuthValue({
|
||||||
|
user: null,
|
||||||
|
canAccessProtectedData: false,
|
||||||
|
openLoginModal: () => {},
|
||||||
|
requireAuth: (action) => action(),
|
||||||
|
})}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(startPuzzleRun).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
profileId: firstWork.profileId,
|
||||||
|
levelId: null,
|
||||||
|
},
|
||||||
|
expect.objectContaining({
|
||||||
|
runtimeGuestToken: 'runtime-guest-token',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.click(await screen.findByRole('button', { name: '下一个' }));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByText('作品暂时无法进入,请稍后再试。'),
|
||||||
|
).toBeNull();
|
||||||
|
expect(
|
||||||
|
await screen.findByLabelText('贝壳潮汐 作品信息', undefined, {
|
||||||
|
timeout: 3000,
|
||||||
|
}),
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(startPuzzleRun).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
resolveFirstRun({
|
||||||
|
run: buildMockPuzzleRun(firstWork.profileId, '后端拼图关卡'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(startPuzzleRun).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
profileId: secondWork.profileId,
|
||||||
|
levelId: null,
|
||||||
|
},
|
||||||
|
expect.objectContaining({
|
||||||
|
runtimeGuestToken: 'runtime-guest-token',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
screen.queryByText('作品暂时无法进入,请稍后再试。'),
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
test('home recommendation puzzle next level uses unified recommend switching', async () => {
|
test('home recommendation puzzle next level uses unified recommend switching', async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const entryWork = {
|
const entryWork = {
|
||||||
|
|||||||
Reference in New Issue
Block a user