diff --git a/apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx b/apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx
index 27f8b7c1a..25b1372f8 100644
--- a/apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx
+++ b/apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx
@@ -400,3 +400,73 @@ test('reuses a single in-flight catalog request', async () => {
await waitFor(() => expect(loadClientLlmModels).toHaveBeenCalledTimes(2));
expect(loadClientLlmModels).toHaveBeenCalledTimes(2);
});
+
+test('keeps refreshing when the server omits the catalog revision', async () => {
+ vi.mocked(loadClientLlmModels).mockResolvedValue({
+ defaultModelId: 'quality',
+ models: [
+ { id: 'quality', displayName: '高质量' },
+ { id: 'fast', displayName: '快速' },
+ ],
+ revision: undefined as unknown as number,
+ });
+ const onReady = vi.fn();
+ render();
+ await waitFor(() => expect(onReady).toHaveBeenLastCalledWith(true));
+
+ // 旧服务端不返回 revision 时,两次响应的 revision 都是 undefined,
+ // 不能因此判定「未变化」而停止刷新。
+ vi.mocked(loadClientLlmModels).mockResolvedValue({
+ defaultModelId: 'quality',
+ models: [
+ { id: 'quality', displayName: '高质量' },
+ { id: 'vision', displayName: '视觉' },
+ ],
+ revision: undefined as unknown as number,
+ });
+ fireEvent(window, new Event('focus'));
+ await waitFor(() => expect(loadClientLlmModels).toHaveBeenCalledTimes(2));
+ fireEvent.click(screen.getByRole('button', { name: '对话模型' }));
+ expect(await screen.findByRole('option', { name: '视觉' })).not.toBeNull();
+});
+
+test('applies a catalog when the revision goes backwards', async () => {
+ const onReady = vi.fn();
+ render();
+ await waitFor(() => expect(onReady).toHaveBeenLastCalledWith(true));
+
+ // 服务端目录重建后 revision 可能回退,仍要按「已变化」处理。
+ vi.mocked(loadClientLlmModels).mockResolvedValue({
+ defaultModelId: 'quality',
+ models: [
+ { id: 'quality', displayName: '高质量' },
+ { id: 'vision', displayName: '视觉' },
+ ],
+ revision: 0,
+ });
+ fireEvent(window, new Event('focus'));
+ await waitFor(() => expect(loadClientLlmModels).toHaveBeenCalledTimes(2));
+ fireEvent.click(screen.getByRole('button', { name: '对话模型' }));
+ expect(await screen.findByRole('option', { name: '视觉' })).not.toBeNull();
+});
+
+test('keeps the applied catalog when the revision is unchanged', async () => {
+ const onReady = vi.fn();
+ render();
+ await waitFor(() => expect(onReady).toHaveBeenLastCalledWith(true));
+
+ // revision 未变化时不更新界面,沿用已应用的目录。
+ vi.mocked(loadClientLlmModels).mockResolvedValue({
+ defaultModelId: 'quality',
+ models: [
+ { id: 'quality', displayName: '高质量' },
+ { id: 'vision', displayName: '视觉' },
+ ],
+ revision: 1,
+ });
+ fireEvent(window, new Event('focus'));
+ await waitFor(() => expect(loadClientLlmModels).toHaveBeenCalledTimes(2));
+ fireEvent.click(screen.getByRole('button', { name: '对话模型' }));
+ expect(screen.queryByRole('option', { name: '视觉' })).toBeNull();
+ expect(screen.getByRole('option', { name: '快速' })).not.toBeNull();
+});