import { pollTaskDetailUploadJob } from '@/lib/task-detail-upload-status' describe('task-detail-upload-status', () => { const originalFetch = global.fetch beforeEach(() => { jest.useFakeTimers() }) afterEach(() => { jest.useRealTimers() global.fetch = originalFetch jest.restoreAllMocks() }) test('正常等待结束时移除 abort listener,不在长轮询中累积', async () => { const controller = new AbortController() const addListenerSpy = jest.spyOn(controller.signal, 'addEventListener') const removeListenerSpy = jest.spyOn(controller.signal, 'removeEventListener') global.fetch = jest.fn() .mockResolvedValueOnce({ ok: true, json: async () => ({ jobId: 'job-1', status: 'processing' }), }) .mockResolvedValueOnce({ ok: true, json: async () => ({ jobId: 'job-1', status: 'processing' }), }) .mockResolvedValueOnce({ ok: true, json: async () => ({ jobId: 'job-1', status: 'processing' }), }) .mockResolvedValueOnce({ ok: true, json: async () => ({ jobId: 'job-1', status: 'completed' }), }) as unknown as typeof fetch const resultPromise = pollTaskDetailUploadJob('job-1', { signal: controller.signal }) await jest.advanceTimersByTimeAsync(800 + 1_000 + 1_250) await expect(resultPromise).resolves.toMatchObject({ status: 'completed' }) expect(addListenerSpy).toHaveBeenCalledTimes(3) expect(removeListenerSpy).toHaveBeenCalledTimes(3) addListenerSpy.mock.calls.forEach((call, index) => { expect(removeListenerSpy.mock.calls[index][1]).toBe(call[1]) }) expect(jest.getTimerCount()).toBe(0) }) })