ask.test.js
1 import { beforeEach, describe, expect, it, vi } from 'vitest'; 2 const baseline = { 3 turns: [{ Role: 'Assistant', Text: '旧回答' }], 4 transcriptLines: ['baseline'], 5 composerHasText: true, 6 isGenerating: false, 7 structuredTurnsTrusted: true, 8 }; 9 const submission = { 10 snapshot: { 11 turns: [ 12 { Role: 'Assistant', Text: '旧回答' }, 13 { Role: 'User', Text: '请只回复:OK' }, 14 ], 15 transcriptLines: ['baseline', '请只回复:OK'], 16 composerHasText: false, 17 isGenerating: true, 18 structuredTurnsTrusted: true, 19 }, 20 preSendAssistantCount: 1, 21 userAnchorTurn: { Role: 'User', Text: '请只回复:OK' }, 22 reason: 'user_turn', 23 }; 24 const mocks = vi.hoisted(() => ({ 25 readGeminiSnapshot: vi.fn(), 26 sendGeminiMessage: vi.fn(), 27 startNewGeminiChat: vi.fn(), 28 waitForGeminiSubmission: vi.fn(), 29 waitForGeminiResponse: vi.fn(), 30 })); 31 vi.mock('./utils.js', async () => { 32 const actual = await vi.importActual('./utils.js'); 33 return { 34 ...actual, 35 readGeminiSnapshot: mocks.readGeminiSnapshot, 36 sendGeminiMessage: mocks.sendGeminiMessage, 37 startNewGeminiChat: mocks.startNewGeminiChat, 38 waitForGeminiSubmission: mocks.waitForGeminiSubmission, 39 waitForGeminiResponse: mocks.waitForGeminiResponse, 40 }; 41 }); 42 import { askCommand } from './ask.js'; 43 function createPageMock() { 44 return { 45 goto: vi.fn().mockResolvedValue(undefined), 46 evaluate: vi.fn(), 47 getCookies: vi.fn().mockResolvedValue([]), 48 snapshot: vi.fn().mockResolvedValue(undefined), 49 click: vi.fn().mockResolvedValue(undefined), 50 typeText: vi.fn().mockResolvedValue(undefined), 51 pressKey: vi.fn().mockResolvedValue(undefined), 52 scrollTo: vi.fn().mockResolvedValue(undefined), 53 getFormState: vi.fn().mockResolvedValue({}), 54 wait: vi.fn().mockResolvedValue(undefined), 55 tabs: vi.fn().mockResolvedValue([]), 56 selectTab: vi.fn().mockResolvedValue(undefined), 57 networkRequests: vi.fn().mockResolvedValue([]), 58 consoleMessages: vi.fn().mockResolvedValue([]), 59 scroll: vi.fn().mockResolvedValue(undefined), 60 autoScroll: vi.fn().mockResolvedValue(undefined), 61 installInterceptor: vi.fn().mockResolvedValue(undefined), 62 getInterceptedRequests: vi.fn().mockResolvedValue([]), 63 waitForCapture: vi.fn().mockResolvedValue(undefined), 64 screenshot: vi.fn().mockResolvedValue(''), 65 nativeType: vi.fn().mockResolvedValue(undefined), 66 nativeKeyPress: vi.fn().mockResolvedValue(undefined), 67 }; 68 } 69 describe('gemini ask orchestration', () => { 70 beforeEach(() => { 71 vi.clearAllMocks(); 72 }); 73 it('captures baseline, sends, waits for confirmed submission, then waits with the remaining timeout', async () => { 74 vi.spyOn(Date, 'now') 75 .mockReturnValueOnce(0) 76 .mockReturnValueOnce(2000); 77 const page = createPageMock(); 78 mocks.readGeminiSnapshot.mockResolvedValueOnce(baseline); 79 mocks.sendGeminiMessage.mockResolvedValueOnce('button'); 80 mocks.waitForGeminiSubmission.mockResolvedValueOnce(submission); 81 mocks.waitForGeminiResponse.mockResolvedValueOnce('OK'); 82 const result = await askCommand.func(page, { prompt: '请只回复:OK', timeout: '20', new: 'false' }); 83 expect(mocks.readGeminiSnapshot).toHaveBeenCalledWith(page); 84 expect(mocks.waitForGeminiSubmission).toHaveBeenCalledWith(page, baseline, 20); 85 expect(mocks.waitForGeminiResponse).toHaveBeenCalledWith(page, submission, '请只回复:OK', 18); 86 expect(result).toEqual([{ response: '💬 OK' }]); 87 }); 88 it('does not spend extra response wait time after submission has already consumed the full timeout budget', async () => { 89 vi.spyOn(Date, 'now') 90 .mockReturnValueOnce(0) 91 .mockReturnValueOnce(20000); 92 const page = createPageMock(); 93 mocks.readGeminiSnapshot.mockResolvedValueOnce(baseline); 94 mocks.sendGeminiMessage.mockResolvedValueOnce('button'); 95 mocks.waitForGeminiSubmission.mockResolvedValueOnce(submission); 96 mocks.waitForGeminiResponse.mockResolvedValueOnce(''); 97 await askCommand.func(page, { prompt: '请只回复:OK', timeout: '20', new: 'false' }); 98 expect(mocks.waitForGeminiResponse).toHaveBeenCalledWith(page, submission, '请只回复:OK', 0); 99 }); 100 });