1
This commit is contained in:
@@ -25,7 +25,10 @@ import {
|
||||
getBigFishCreationSession,
|
||||
} from '../../services/big-fish-creation';
|
||||
import { listBigFishGallery } from '../../services/big-fish-gallery';
|
||||
import { startLocalBigFishRuntimeRun } from '../../services/big-fish-runtime';
|
||||
import {
|
||||
recordBigFishPlay,
|
||||
startLocalBigFishRuntimeRun,
|
||||
} from '../../services/big-fish-runtime';
|
||||
import { listBigFishWorks } from '../../services/big-fish-works';
|
||||
import {
|
||||
createPuzzleAgentSession,
|
||||
@@ -34,12 +37,16 @@ import {
|
||||
import {
|
||||
getPuzzleGalleryDetail,
|
||||
listPuzzleGallery,
|
||||
remixPuzzleGalleryWork,
|
||||
} from '../../services/puzzle-gallery';
|
||||
import {
|
||||
advanceLocalPuzzleNextLevel,
|
||||
advancePuzzleNextLevel,
|
||||
getPuzzleRun,
|
||||
startPuzzleRun,
|
||||
submitPuzzleLeaderboard,
|
||||
updatePuzzleRunPause,
|
||||
usePuzzleRuntimeProp,
|
||||
} from '../../services/puzzle-runtime';
|
||||
import {
|
||||
dragLocalPuzzlePiece,
|
||||
@@ -78,6 +85,7 @@ import {
|
||||
AuthUiContext,
|
||||
type PlatformSettingsSection,
|
||||
} from '../auth/AuthUiContext';
|
||||
import { type CustomWorldProfile, WorldType } from '../../types';
|
||||
import {
|
||||
RpgEntryFlowShell,
|
||||
type RpgEntryFlowShellProps,
|
||||
@@ -199,6 +207,7 @@ vi.mock('../../services/puzzle-works', () => ({
|
||||
vi.mock('../../services/puzzle-gallery', () => ({
|
||||
getPuzzleGalleryDetail: vi.fn(),
|
||||
listPuzzleGallery: vi.fn(),
|
||||
remixPuzzleGalleryWork: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../services/puzzle-runtime', () => ({
|
||||
@@ -233,6 +242,7 @@ vi.mock('../../services/big-fish-gallery', () => ({
|
||||
|
||||
vi.mock('../../services/big-fish-runtime', () => ({
|
||||
advanceLocalBigFishRuntimeRun: vi.fn((run) => run),
|
||||
recordBigFishPlay: vi.fn(),
|
||||
startLocalBigFishRuntimeRun: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -591,22 +601,38 @@ function buildClearedPuzzleRun(params: {
|
||||
|
||||
function buildMockRpgGalleryDetail(
|
||||
entry: CustomWorldGalleryCard,
|
||||
): CustomWorldLibraryEntry {
|
||||
): CustomWorldLibraryEntry<CustomWorldProfile> {
|
||||
return {
|
||||
...entry,
|
||||
profile: {
|
||||
id: entry.profileId,
|
||||
settingText: entry.summaryText,
|
||||
name: entry.worldName,
|
||||
subtitle: entry.subtitle,
|
||||
summary: entry.summaryText,
|
||||
tone: '压抑、潮湿、悬疑',
|
||||
playerGoal: '查清旧案。',
|
||||
templateWorldType: WorldType.WUXIA,
|
||||
attributeSchema: {
|
||||
id: `${entry.profileId}-attribute-schema`,
|
||||
worldId: entry.profileId,
|
||||
schemaVersion: 1,
|
||||
generatedFrom: {
|
||||
worldType: WorldType.CUSTOM,
|
||||
worldName: entry.worldName,
|
||||
settingSummary: entry.summaryText,
|
||||
tone: '压抑、潮湿、悬疑',
|
||||
conflictCore: '雾潮正在逼近港口',
|
||||
},
|
||||
slots: [],
|
||||
},
|
||||
majorFactions: ['守灯会'],
|
||||
coreConflicts: ['雾潮正在逼近港口'],
|
||||
playableNpcs: [],
|
||||
storyNpcs: [],
|
||||
items: [],
|
||||
landmarks: [],
|
||||
} as never,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1474,6 +1500,9 @@ beforeEach(() => {
|
||||
vi.mocked(listBigFishGallery).mockResolvedValue({
|
||||
items: [],
|
||||
});
|
||||
vi.mocked(recordBigFishPlay).mockResolvedValue({
|
||||
session: {} as never,
|
||||
});
|
||||
vi.mocked(startLocalBigFishRuntimeRun).mockReturnValue({
|
||||
runId: 'big-fish-run-1',
|
||||
sessionId: 'big-fish-session-public-1',
|
||||
@@ -1503,9 +1532,21 @@ beforeEach(() => {
|
||||
vi.mocked(listPuzzleGallery).mockResolvedValue({
|
||||
items: [],
|
||||
});
|
||||
vi.mocked(remixPuzzleGalleryWork).mockRejectedValue(
|
||||
new Error('未启用拼图 remix'),
|
||||
);
|
||||
vi.mocked(advancePuzzleNextLevel).mockImplementation(async (runId) => ({
|
||||
run: buildMockPuzzleRun(`${runId}-next-profile`, '后端推荐下一关'),
|
||||
}));
|
||||
vi.mocked(getPuzzleRun).mockImplementation(async (runId) => ({
|
||||
run: buildMockPuzzleRun(`${runId}-profile`, '后端同步关卡'),
|
||||
}));
|
||||
vi.mocked(updatePuzzleRunPause).mockImplementation(async (runId) => ({
|
||||
run: buildMockPuzzleRun(`${runId}-profile`, '后端同步关卡'),
|
||||
}));
|
||||
vi.mocked(usePuzzleRuntimeProp).mockImplementation(async (runId) => ({
|
||||
run: buildMockPuzzleRun(`${runId}-profile`, '后端同步关卡'),
|
||||
}));
|
||||
vi.mocked(submitPuzzleLeaderboard).mockImplementation(
|
||||
async (runId, payload) => ({
|
||||
run: {
|
||||
@@ -1921,6 +1962,114 @@ test('clicking a public work while logged out opens public detail without starti
|
||||
expect(recordRpgEntryWorldGalleryPlay).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('logged out public detail gates puzzle start and remix before real actions', async () => {
|
||||
const user = userEvent.setup();
|
||||
const requireAuth = vi.fn();
|
||||
const publishedPuzzleWork = {
|
||||
workId: 'puzzle-work-public-1',
|
||||
profileId: 'puzzle-profile-public-1',
|
||||
ownerUserId: 'user-2',
|
||||
sourceSessionId: null,
|
||||
authorDisplayName: '拼图作者',
|
||||
levelName: '星桥机关',
|
||||
summary: '旋转碎片并接通星桥机关。',
|
||||
themeTags: ['机关', '星桥'],
|
||||
coverImageSrc: null,
|
||||
coverAssetId: null,
|
||||
publicationStatus: 'published',
|
||||
updatedAt: '2026-04-25T09:00:00.000Z',
|
||||
publishedAt: '2026-04-25T09:00:00.000Z',
|
||||
playCount: 3,
|
||||
remixCount: 0,
|
||||
likeCount: 0,
|
||||
publishReady: true,
|
||||
} satisfies PuzzleWorkSummary;
|
||||
|
||||
vi.mocked(listPuzzleGallery).mockResolvedValue({
|
||||
items: [publishedPuzzleWork],
|
||||
});
|
||||
vi.mocked(getPuzzleGalleryDetail).mockResolvedValue({
|
||||
item: publishedPuzzleWork,
|
||||
});
|
||||
|
||||
render(
|
||||
<TestWrapper
|
||||
authValue={createAuthValue({
|
||||
user: null,
|
||||
canAccessProtectedData: false,
|
||||
openLoginModal: () => {},
|
||||
requireAuth,
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText('星桥机关').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
const workCards = screen.getAllByRole('button', { name: /星桥机关/u });
|
||||
await user.click(workCards[0]!);
|
||||
expect(await screen.findByText('详情')).toBeTruthy();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '启动' }));
|
||||
expect(requireAuth).toHaveBeenCalledTimes(1);
|
||||
expect(startPuzzleRun).not.toHaveBeenCalled();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '作品改造' }));
|
||||
expect(requireAuth).toHaveBeenCalledTimes(2);
|
||||
expect(remixPuzzleGalleryWork).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('logged out public detail gates big fish start before local runtime', async () => {
|
||||
const user = userEvent.setup();
|
||||
const requireAuth = vi.fn();
|
||||
const bigFishWork: BigFishWorkSummary = {
|
||||
workId: 'big-fish-work-public-1',
|
||||
sourceSessionId: 'big-fish-session-public-1',
|
||||
ownerUserId: 'user-2',
|
||||
authorDisplayName: '大鱼作者',
|
||||
title: '机械深海 大鱼吃小鱼',
|
||||
subtitle: '机械微生物吞并进化',
|
||||
summary: '从微光孢子一路吞并成长到深海巨鲲。',
|
||||
coverImageSrc: null,
|
||||
status: 'published',
|
||||
updatedAt: '2026-04-25T10:30:00.000Z',
|
||||
publishReady: true,
|
||||
levelCount: 8,
|
||||
levelMainImageReadyCount: 8,
|
||||
levelMotionReadyCount: 16,
|
||||
backgroundReady: true,
|
||||
};
|
||||
|
||||
vi.mocked(listBigFishGallery).mockResolvedValue({
|
||||
items: [bigFishWork],
|
||||
});
|
||||
|
||||
render(
|
||||
<TestWrapper
|
||||
authValue={createAuthValue({
|
||||
user: null,
|
||||
canAccessProtectedData: false,
|
||||
openLoginModal: () => {},
|
||||
requireAuth,
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
|
||||
const searchInput = await screen.findByPlaceholderText(
|
||||
'输入 SY / CW / BF / PZ 编号',
|
||||
);
|
||||
await user.type(searchInput, 'BF-NPUBLIC1');
|
||||
await user.click(screen.getByRole('button', { name: '搜索' }));
|
||||
|
||||
expect(await screen.findByText('详情')).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: '启动' }));
|
||||
|
||||
expect(requireAuth).toHaveBeenCalledTimes(1);
|
||||
expect(startLocalBigFishRuntimeRun).not.toHaveBeenCalled();
|
||||
expect(recordBigFishPlay).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('creation hub clears all private work shelves immediately after logout state', async () => {
|
||||
const user = userEvent.setup();
|
||||
const loggedInAuth = createAuthValue();
|
||||
@@ -2593,6 +2742,7 @@ test('formal puzzle next level uses backend run and leaderboard keeps frontend l
|
||||
await user.click(await screen.findByRole('button', { name: '启动' }));
|
||||
await waitFor(() => {
|
||||
expect(startPuzzleRun).toHaveBeenCalledWith({
|
||||
levelId: null,
|
||||
profileId: 'puzzle-profile-public-1',
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user