1
This commit is contained in:
@@ -135,7 +135,7 @@ describe('runtimeStoryService', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('filters disabled runtime options when rebuilding a story moment', () => {
|
||||
it('keeps disabled runtime options when rebuilding a story moment', () => {
|
||||
const story = buildStoryMomentFromRuntimeOptions({
|
||||
storyText: '服务端返回的新故事',
|
||||
options: [
|
||||
@@ -155,12 +155,16 @@ describe('runtimeStoryService', () => {
|
||||
});
|
||||
|
||||
expect(story.text).toBe('服务端返回的新故事');
|
||||
expect(story.options).toHaveLength(1);
|
||||
expect(story.options).toHaveLength(2);
|
||||
expect(story.options[0]?.functionId).toBe('npc_chat');
|
||||
expect(story.options[1]?.functionId).toBe('npc_recruit');
|
||||
expect(story.options[1]?.disabled).toBe(true);
|
||||
expect(story.options[1]?.disabledReason).toBe('队伍已满');
|
||||
});
|
||||
|
||||
it('recognizes server-runtime option pools for server-side legality checks', () => {
|
||||
expect(isTask5RuntimeFunctionId('npc_chat')).toBe(true);
|
||||
expect(isTask5RuntimeFunctionId('battle_attack_basic')).toBe(true);
|
||||
expect(isTask5RuntimeFunctionId('npc_trade')).toBe(false);
|
||||
expect(isServerRuntimeFunctionId('npc_trade')).toBe(true);
|
||||
expect(isServerRuntimeFunctionId('unknown_action')).toBe(false);
|
||||
|
||||
@@ -103,15 +103,11 @@ function createRuntimeStoryOption(
|
||||
option: RuntimeStoryOptionView,
|
||||
gameState?: Pick<GameState, 'currentEncounter'>,
|
||||
): StoryOption {
|
||||
const detailParts = [option.detailText, option.disabled ? option.reason : null]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
|
||||
return {
|
||||
functionId: option.functionId,
|
||||
actionText: option.actionText,
|
||||
text: option.actionText,
|
||||
detailText: detailParts || undefined,
|
||||
detailText: option.detailText,
|
||||
visuals: {
|
||||
playerAnimation: AnimationState.IDLE,
|
||||
playerMoveMeters: 0,
|
||||
@@ -121,6 +117,9 @@ function createRuntimeStoryOption(
|
||||
monsterChanges: [],
|
||||
},
|
||||
interaction: buildRuntimeOptionInteraction(option, gameState),
|
||||
runtimePayload: option.payload,
|
||||
disabled: option.disabled,
|
||||
disabledReason: option.reason,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -162,9 +161,9 @@ export function buildStoryMomentFromRuntimeOptions(params: {
|
||||
}) {
|
||||
return {
|
||||
text: params.storyText,
|
||||
options: params.options
|
||||
.filter((option) => !option.disabled)
|
||||
.map((option) => createRuntimeStoryOption(option, params.gameState)),
|
||||
options: params.options.map((option) =>
|
||||
createRuntimeStoryOption(option, params.gameState),
|
||||
),
|
||||
} satisfies StoryMoment;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ const { requestJsonMock } = vi.hoisted(() => ({
|
||||
import {
|
||||
clearProfileBrowseHistory,
|
||||
listProfileBrowseHistory,
|
||||
listProfileSaveArchives,
|
||||
resumeProfileSaveArchive,
|
||||
syncProfileBrowseHistory,
|
||||
upsertProfileBrowseHistory,
|
||||
} from './storageService';
|
||||
@@ -103,3 +105,54 @@ describe('storageService browse history routes', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('storageService save archive routes', () => {
|
||||
beforeEach(() => {
|
||||
requestJsonMock.mockReset();
|
||||
requestJsonMock.mockResolvedValue({ entries: [] });
|
||||
});
|
||||
|
||||
it('reads save archives from the runtime profile route', async () => {
|
||||
await listProfileSaveArchives();
|
||||
|
||||
expect(requestJsonMock).toHaveBeenCalledWith(
|
||||
'/api/runtime/profile/save-archives',
|
||||
expect.objectContaining({ method: 'GET' }),
|
||||
'读取存档列表失败',
|
||||
expect.objectContaining({
|
||||
retry: expect.objectContaining({ maxRetries: 1 }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('resumes a save archive through the runtime profile route', async () => {
|
||||
requestJsonMock.mockResolvedValueOnce({
|
||||
entry: {
|
||||
worldKey: 'custom:world-1',
|
||||
},
|
||||
snapshot: {
|
||||
version: 2,
|
||||
savedAt: '2026-04-19T10:15:00.000Z',
|
||||
bottomTab: 'adventure',
|
||||
currentStory: null,
|
||||
gameState: {
|
||||
worldType: 'CUSTOM',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await resumeProfileSaveArchive('custom:world-1');
|
||||
|
||||
expect(requestJsonMock).toHaveBeenCalledWith(
|
||||
'/api/runtime/profile/save-archives/custom%3Aworld-1',
|
||||
expect.objectContaining({ method: 'POST' }),
|
||||
'恢复存档失败',
|
||||
expect.objectContaining({
|
||||
retry: expect.objectContaining({
|
||||
maxRetries: 1,
|
||||
retryUnsafeMethods: true,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,9 @@ import type {
|
||||
PlatformBrowseHistoryResponse,
|
||||
PlatformBrowseHistoryWriteEntry,
|
||||
ProfileDashboardSummary,
|
||||
ProfileSaveArchiveListResponse,
|
||||
ProfileSaveArchiveResumeResponse,
|
||||
ProfileSaveArchiveSummary,
|
||||
ProfilePlayStatsResponse,
|
||||
ProfileWalletLedgerResponse,
|
||||
RuntimeSettings,
|
||||
@@ -137,6 +140,40 @@ export async function getProfilePlayStats(options: RuntimeRequestOptions = {}) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function listProfileSaveArchives(
|
||||
options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
const response = await requestRuntimeJson<ProfileSaveArchiveListResponse>(
|
||||
'/profile/save-archives',
|
||||
{ method: 'GET' },
|
||||
'读取存档列表失败',
|
||||
options,
|
||||
);
|
||||
|
||||
return Array.isArray(response?.entries) ? response.entries : [];
|
||||
}
|
||||
|
||||
export async function resumeProfileSaveArchive(
|
||||
worldKey: string,
|
||||
options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
const response = await requestRuntimeJson<
|
||||
ProfileSaveArchiveResumeResponse
|
||||
>(
|
||||
`/profile/save-archives/${encodeURIComponent(worldKey)}`,
|
||||
{ method: 'POST' },
|
||||
'恢复存档失败',
|
||||
options,
|
||||
);
|
||||
|
||||
return {
|
||||
entry: response.entry,
|
||||
snapshot: rehydrateSavedSnapshot(
|
||||
response.snapshot as HydratedSavedGameSnapshot,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export async function putSettings(
|
||||
settings: RuntimeSettings,
|
||||
options: RuntimeRequestOptions = {},
|
||||
@@ -363,6 +400,8 @@ export const runtimeStorageClient = {
|
||||
getProfileDashboard,
|
||||
getProfileWalletLedger,
|
||||
getProfilePlayStats,
|
||||
listProfileSaveArchives,
|
||||
resumeProfileSaveArchive,
|
||||
listCustomWorldLibrary,
|
||||
listCustomWorldWorks,
|
||||
upsertCustomWorldProfile,
|
||||
@@ -379,3 +418,4 @@ export const runtimeStorageClient = {
|
||||
|
||||
export type { CustomWorldLibraryEntry };
|
||||
export type { PlatformBrowseHistoryEntry };
|
||||
export type { ProfileSaveArchiveSummary };
|
||||
|
||||
Reference in New Issue
Block a user