合并 master 并修复架构分支回归
合入 master 最新的认证、玩法契约与推荐页改动。 修复拼图草稿生成、推荐页下一关和公开详情访客试玩回归。 修复抓大鹅草稿试玩鉴权与首屏推荐详情测试入口。 补齐相关测试夹具、文档与团队记忆更新。
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import type { PlatformCreationTypeId } from '../../packages/shared/src/contracts/playTypes';
|
||||
import { requestJson } from './apiClient';
|
||||
|
||||
/** 后端下发的单个创作类型入口配置,前端只据此展示和分流。 */
|
||||
export type CreationEntryTypeConfig = {
|
||||
id: string;
|
||||
id: PlatformCreationTypeId;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
badge: string;
|
||||
@@ -27,7 +28,7 @@ export type UnifiedCreationField = {
|
||||
|
||||
/** 统一创作工作台契约,把入口类型映射到工作台、生成页和结果页阶段。 */
|
||||
export type UnifiedCreationSpec = {
|
||||
playId: string;
|
||||
playId: PlatformCreationTypeId;
|
||||
title: string;
|
||||
workspaceStage: string;
|
||||
generationStage: string;
|
||||
|
||||
@@ -188,9 +188,16 @@ export function executeJumpHopCreationAction(
|
||||
.then(normalizeJumpHopActionResponse);
|
||||
}
|
||||
|
||||
export async function getJumpHopWorkDetail(profileId: string) {
|
||||
export async function getJumpHopWorkDetail(
|
||||
profileId: string,
|
||||
options: { audience?: 'creation' | 'runtime' } = {},
|
||||
) {
|
||||
const base =
|
||||
options.audience === 'creation'
|
||||
? JUMP_HOP_WORKS_API_BASE
|
||||
: `${JUMP_HOP_RUNTIME_API_BASE}/works`;
|
||||
const response = await requestJson<JumpHopWorkDetailResponse>(
|
||||
`${JUMP_HOP_RUNTIME_API_BASE}/works/${encodeURIComponent(profileId)}`,
|
||||
`${base}/${encodeURIComponent(profileId)}`,
|
||||
{ method: 'GET' },
|
||||
'读取跳一跳作品详情失败',
|
||||
);
|
||||
|
||||
@@ -30,6 +30,7 @@ const leaderboardResponse: JumpHopLeaderboardResponse = {
|
||||
{
|
||||
rank: 1,
|
||||
playerId: 'player-1',
|
||||
displayName: '玩家一号',
|
||||
successfulJumpCount: 10,
|
||||
durationMs: 3210,
|
||||
updatedAt: '2026-05-27T00:00:00Z',
|
||||
|
||||
@@ -530,6 +530,44 @@ describe('miniGameDraftGenerationProgress', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('jump hop generation anchors hide unused style preset fallback', () => {
|
||||
const entries = buildJumpHopGenerationAnchorEntries({
|
||||
sessionId: 'jump-hop-session-style-hidden',
|
||||
ownerUserId: 'user-1',
|
||||
status: 'generating',
|
||||
draft: {
|
||||
templateId: 'jump-hop',
|
||||
templateName: '跳一跳',
|
||||
profileId: 'jump-hop-profile-style-hidden',
|
||||
themeText: '水果',
|
||||
workTitle: '水果跳一跳',
|
||||
workDescription: '水果主题跳一跳。',
|
||||
themeTags: ['水果'],
|
||||
difficulty: 'standard',
|
||||
stylePreset: 'minimal-blocks',
|
||||
characterPrompt: '内置默认 3D 角色',
|
||||
tilePrompt: '',
|
||||
endMoodPrompt: null,
|
||||
characterAsset: null,
|
||||
tileAtlasAsset: null,
|
||||
tileAssets: [],
|
||||
path: null,
|
||||
coverComposite: null,
|
||||
generationStatus: 'generating',
|
||||
},
|
||||
createdAt: '2026-06-06T10:00:00.000Z',
|
||||
updatedAt: '2026-06-06T10:00:00.000Z',
|
||||
});
|
||||
|
||||
expect(entries).toEqual([
|
||||
{
|
||||
id: 'jump-hop-theme',
|
||||
label: '主题',
|
||||
value: '水果',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('wooden fish draft generation exposes hit object, background and back button pipeline', () => {
|
||||
const state = createMiniGameDraftGenerationState('wooden-fish');
|
||||
|
||||
|
||||
@@ -1163,7 +1163,7 @@ export function buildJumpHopGenerationAnchorEntries(
|
||||
workTitle?: string;
|
||||
themeText?: string;
|
||||
characterPrompt?: string;
|
||||
stylePreset?: string;
|
||||
tilePrompt?: string;
|
||||
} | null;
|
||||
}
|
||||
| null
|
||||
@@ -1187,7 +1187,7 @@ export function buildJumpHopGenerationAnchorEntries(
|
||||
value:
|
||||
formPayload?.tilePrompt?.trim() ||
|
||||
config?.tilePrompt?.trim() ||
|
||||
draft?.stylePreset?.trim() ||
|
||||
draft?.tilePrompt?.trim() ||
|
||||
'',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -93,7 +93,7 @@ export async function dragPuzzlePieceOrGroup(
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入推荐出的下一关。
|
||||
* 进入当前 run 的下一关。
|
||||
*/
|
||||
export async function advancePuzzleNextLevel(
|
||||
runId: string,
|
||||
@@ -101,10 +101,8 @@ export async function advancePuzzleNextLevel(
|
||||
options: PuzzleRuntimeRequestOptions = {},
|
||||
) {
|
||||
const targetProfileId = payload.targetProfileId?.trim() ?? '';
|
||||
const preferSimilarWork = payload.preferSimilarWork === true;
|
||||
const requestPayload = {
|
||||
...(targetProfileId ? { targetProfileId } : {}),
|
||||
...(preferSimilarWork ? { preferSimilarWork: true } : {}),
|
||||
};
|
||||
const hasRequestPayload = Object.keys(requestPayload).length > 0;
|
||||
return requestRuntimeJson<PuzzleRunResponse>({
|
||||
|
||||
@@ -130,10 +130,10 @@ describe('recommended runtime guest launch clients', () => {
|
||||
},
|
||||
);
|
||||
|
||||
it('puzzle next level can carry preferSimilarWork through the runtime guest request', async () => {
|
||||
it('puzzle next level keeps the default current-run handoff without a request body', async () => {
|
||||
await advancePuzzleNextLevel(
|
||||
'run-puzzle-1',
|
||||
{ preferSimilarWork: true },
|
||||
{},
|
||||
{ runtimeGuestToken: 'runtime-guest-token' },
|
||||
);
|
||||
|
||||
@@ -144,11 +144,10 @@ describe('recommended runtime guest launch clients', () => {
|
||||
method: 'POST',
|
||||
headers: expect.objectContaining({
|
||||
Authorization: 'Bearer runtime-guest-token',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: JSON.stringify({ preferSimilarWork: true }),
|
||||
}),
|
||||
);
|
||||
expect(init.body).toBeUndefined();
|
||||
expect(options).toEqual(
|
||||
expect.objectContaining({
|
||||
skipAuth: true,
|
||||
|
||||
Reference in New Issue
Block a user