mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
Handle unhandled rejections more gracefully. (#4417)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
This commit is contained in:
@@ -5,127 +5,105 @@
|
||||
*/
|
||||
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { useConsoleMessages } from './useConsoleMessages.js';
|
||||
import { ConsoleMessageItem } from '../types.js';
|
||||
|
||||
// Mock setTimeout and clearTimeout
|
||||
vi.useFakeTimers();
|
||||
import { vi } from 'vitest';
|
||||
import { useConsoleMessages } from './useConsoleMessages';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
describe('useConsoleMessages', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.runOnlyPendingTimers();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
const useTestableConsoleMessages = () => {
|
||||
const { handleNewMessage, ...rest } = useConsoleMessages();
|
||||
const log = useCallback(
|
||||
(content: string) => handleNewMessage({ type: 'log', content, count: 1 }),
|
||||
[handleNewMessage],
|
||||
);
|
||||
const error = useCallback(
|
||||
(content: string) =>
|
||||
handleNewMessage({ type: 'error', content, count: 1 }),
|
||||
[handleNewMessage],
|
||||
);
|
||||
return {
|
||||
...rest,
|
||||
log,
|
||||
error,
|
||||
clearConsoleMessages: rest.clearConsoleMessages,
|
||||
};
|
||||
};
|
||||
|
||||
it('should initialize with an empty array of console messages', () => {
|
||||
const { result } = renderHook(() => useConsoleMessages());
|
||||
const { result } = renderHook(() => useTestableConsoleMessages());
|
||||
expect(result.current.consoleMessages).toEqual([]);
|
||||
});
|
||||
|
||||
it('should add a new message', () => {
|
||||
const { result } = renderHook(() => useConsoleMessages());
|
||||
const message: ConsoleMessageItem = {
|
||||
type: 'log',
|
||||
content: 'Test message',
|
||||
count: 1,
|
||||
};
|
||||
it('should add a new message when log is called', async () => {
|
||||
const { result } = renderHook(() => useTestableConsoleMessages());
|
||||
|
||||
act(() => {
|
||||
result.current.handleNewMessage(message);
|
||||
result.current.log('Test message');
|
||||
});
|
||||
|
||||
act(() => {
|
||||
vi.runAllTimers(); // Process the queue
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toEqual([{ ...message, count: 1 }]);
|
||||
});
|
||||
|
||||
it('should consolidate identical consecutive messages', () => {
|
||||
const { result } = renderHook(() => useConsoleMessages());
|
||||
const message: ConsoleMessageItem = {
|
||||
type: 'log',
|
||||
content: 'Test message',
|
||||
count: 1,
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.handleNewMessage(message);
|
||||
result.current.handleNewMessage(message);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
vi.runAllTimers();
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toEqual([{ ...message, count: 2 }]);
|
||||
});
|
||||
|
||||
it('should not consolidate different messages', () => {
|
||||
const { result } = renderHook(() => useConsoleMessages());
|
||||
const message1: ConsoleMessageItem = {
|
||||
type: 'log',
|
||||
content: 'Test message 1',
|
||||
count: 1,
|
||||
};
|
||||
const message2: ConsoleMessageItem = {
|
||||
type: 'error',
|
||||
content: 'Test message 2',
|
||||
count: 1,
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.handleNewMessage(message1);
|
||||
result.current.handleNewMessage(message2);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
vi.runAllTimers();
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(20);
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toEqual([
|
||||
{ ...message1, count: 1 },
|
||||
{ ...message2, count: 1 },
|
||||
{ type: 'log', content: 'Test message', count: 1 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not consolidate messages if type is different', () => {
|
||||
const { result } = renderHook(() => useConsoleMessages());
|
||||
const message1: ConsoleMessageItem = {
|
||||
type: 'log',
|
||||
content: 'Test message',
|
||||
count: 1,
|
||||
};
|
||||
const message2: ConsoleMessageItem = {
|
||||
type: 'error',
|
||||
content: 'Test message',
|
||||
count: 1,
|
||||
};
|
||||
it('should batch and count identical consecutive messages', async () => {
|
||||
const { result } = renderHook(() => useTestableConsoleMessages());
|
||||
|
||||
act(() => {
|
||||
result.current.handleNewMessage(message1);
|
||||
result.current.handleNewMessage(message2);
|
||||
result.current.log('Test message');
|
||||
result.current.log('Test message');
|
||||
result.current.log('Test message');
|
||||
});
|
||||
|
||||
act(() => {
|
||||
vi.runAllTimers();
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(20);
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toEqual([
|
||||
{ ...message1, count: 1 },
|
||||
{ ...message2, count: 1 },
|
||||
{ type: 'log', content: 'Test message', count: 3 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should clear console messages', () => {
|
||||
const { result } = renderHook(() => useConsoleMessages());
|
||||
const message: ConsoleMessageItem = {
|
||||
type: 'log',
|
||||
content: 'Test message',
|
||||
count: 1,
|
||||
};
|
||||
it('should not batch different messages', async () => {
|
||||
const { result } = renderHook(() => useTestableConsoleMessages());
|
||||
|
||||
act(() => {
|
||||
result.current.handleNewMessage(message);
|
||||
result.current.log('First message');
|
||||
result.current.error('Second message');
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(20);
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toEqual([
|
||||
{ type: 'log', content: 'First message', count: 1 },
|
||||
{ type: 'error', content: 'Second message', count: 1 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should clear all messages when clearConsoleMessages is called', async () => {
|
||||
const { result } = renderHook(() => useTestableConsoleMessages());
|
||||
|
||||
act(() => {
|
||||
vi.runAllTimers();
|
||||
result.current.log('A message');
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(20);
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toHaveLength(1);
|
||||
@@ -134,79 +112,36 @@ describe('useConsoleMessages', () => {
|
||||
result.current.clearConsoleMessages();
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toEqual([]);
|
||||
expect(result.current.consoleMessages).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should clear pending timeout on clearConsoleMessages', () => {
|
||||
const { result } = renderHook(() => useConsoleMessages());
|
||||
const message: ConsoleMessageItem = {
|
||||
type: 'log',
|
||||
content: 'Test message',
|
||||
count: 1,
|
||||
};
|
||||
it('should clear the pending timeout when clearConsoleMessages is called', () => {
|
||||
const { result } = renderHook(() => useTestableConsoleMessages());
|
||||
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
|
||||
|
||||
act(() => {
|
||||
result.current.handleNewMessage(message); // This schedules a timeout
|
||||
result.current.log('A message');
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.clearConsoleMessages();
|
||||
});
|
||||
|
||||
// Ensure the queue is empty and no more messages are processed
|
||||
act(() => {
|
||||
vi.runAllTimers(); // If timeout wasn't cleared, this would process the queue
|
||||
});
|
||||
|
||||
expect(result.current.consoleMessages).toEqual([]);
|
||||
expect(clearTimeoutSpy).toHaveBeenCalled();
|
||||
clearTimeoutSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should clear message queue on clearConsoleMessages', () => {
|
||||
const { result } = renderHook(() => useConsoleMessages());
|
||||
const message: ConsoleMessageItem = {
|
||||
type: 'log',
|
||||
content: 'Test message',
|
||||
count: 1,
|
||||
};
|
||||
it('should clean up the timeout on unmount', () => {
|
||||
const { result, unmount } = renderHook(() => useTestableConsoleMessages());
|
||||
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
|
||||
|
||||
act(() => {
|
||||
// Add a message but don't process the queue yet
|
||||
result.current.handleNewMessage(message);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.clearConsoleMessages();
|
||||
});
|
||||
|
||||
// Process any pending timeouts (should be none related to message queue)
|
||||
act(() => {
|
||||
vi.runAllTimers();
|
||||
});
|
||||
|
||||
// The consoleMessages should be empty because the queue was cleared before processing
|
||||
expect(result.current.consoleMessages).toEqual([]);
|
||||
});
|
||||
|
||||
it('should cleanup timeout on unmount', () => {
|
||||
const { result, unmount } = renderHook(() => useConsoleMessages());
|
||||
const message: ConsoleMessageItem = {
|
||||
type: 'log',
|
||||
content: 'Test message',
|
||||
count: 1,
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.handleNewMessage(message);
|
||||
result.current.log('A message');
|
||||
});
|
||||
|
||||
unmount();
|
||||
|
||||
// This is a bit indirect. We check that clearTimeout was called.
|
||||
// If clearTimeout was not called, and we run timers, an error might occur
|
||||
// or the state might change, which it shouldn't after unmount.
|
||||
// Vitest's vi.clearAllTimers() or specific checks for clearTimeout calls
|
||||
// would be more direct if available and easy to set up here.
|
||||
// For now, we rely on the useEffect cleanup pattern.
|
||||
expect(vi.getTimerCount()).toBe(0); // Check if all timers are cleared
|
||||
expect(clearTimeoutSpy).toHaveBeenCalled();
|
||||
clearTimeoutSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user