mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
chore(test): install and configure vitest eslint plugin (#3228)
Co-authored-by: N. Taylor Mullen <ntaylormullen@google.com>
This commit is contained in:
@@ -720,19 +720,23 @@ describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => {
|
||||
// readability, and content based on paths derived from the mocked os.homedir().
|
||||
// 3. Spies on console functions (for logger output) are correctly set up if needed.
|
||||
// Example of a previously failing test structure:
|
||||
/*
|
||||
it('should correctly use mocked homedir for global path', async () => {
|
||||
it.skip('should correctly use mocked homedir for global path', async () => {
|
||||
const MOCK_GEMINI_DIR_LOCAL = path.join('/mock/home/user', '.gemini');
|
||||
const MOCK_GLOBAL_PATH_LOCAL = path.join(MOCK_GEMINI_DIR_LOCAL, 'GEMINI.md');
|
||||
const MOCK_GLOBAL_PATH_LOCAL = path.join(
|
||||
MOCK_GEMINI_DIR_LOCAL,
|
||||
'GEMINI.md',
|
||||
);
|
||||
mockFs({
|
||||
[MOCK_GLOBAL_PATH_LOCAL]: { type: 'file', content: 'GlobalContentOnly' }
|
||||
[MOCK_GLOBAL_PATH_LOCAL]: { type: 'file', content: 'GlobalContentOnly' },
|
||||
});
|
||||
const memory = await loadHierarchicalGeminiMemory("/some/other/cwd", false);
|
||||
const memory = await loadHierarchicalGeminiMemory('/some/other/cwd', false);
|
||||
expect(memory).toBe('GlobalContentOnly');
|
||||
expect(vi.mocked(os.homedir)).toHaveBeenCalled();
|
||||
expect(fsPromises.readFile).toHaveBeenCalledWith(MOCK_GLOBAL_PATH_LOCAL, 'utf-8');
|
||||
expect(fsPromises.readFile).toHaveBeenCalledWith(
|
||||
MOCK_GLOBAL_PATH_LOCAL,
|
||||
'utf-8',
|
||||
);
|
||||
});
|
||||
*/
|
||||
});
|
||||
|
||||
describe('mergeMcpServers', () => {
|
||||
@@ -1294,7 +1298,7 @@ describe('loadCliConfig with allowed-mcp-server-names', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should override allowMCPServers with excludeMCPServers if overlapping ', async () => {
|
||||
it('should override allowMCPServers with excludeMCPServers if overlapping', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const argv = await parseArguments();
|
||||
const settings: Settings = {
|
||||
@@ -1308,7 +1312,7 @@ describe('loadCliConfig with allowed-mcp-server-names', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should prioritize mcp server flag if set ', async () => {
|
||||
it('should prioritize mcp server flag if set', async () => {
|
||||
process.argv = [
|
||||
'node',
|
||||
'script.js',
|
||||
|
||||
@@ -893,7 +893,7 @@ describe('useTextBuffer', () => {
|
||||
expect(getBufferState(result).cursor).toEqual([0, 2]);
|
||||
});
|
||||
|
||||
it('should handle inserts that contain delete characters ', () => {
|
||||
it('should handle inserts that contain delete characters', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
initialText: 'abcde',
|
||||
@@ -911,7 +911,7 @@ describe('useTextBuffer', () => {
|
||||
expect(getBufferState(result).cursor).toEqual([0, 2]);
|
||||
});
|
||||
|
||||
it('should handle inserts with a mix of regular and delete characters ', () => {
|
||||
it('should handle inserts with a mix of regular and delete characters', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
initialText: 'abcde',
|
||||
|
||||
@@ -1464,6 +1464,64 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should process @include commands, adding user turn after processing to prevent race conditions', async () => {
|
||||
const rawQuery = '@include file.txt Summarize this.';
|
||||
const processedQueryParts = [
|
||||
{ text: 'Summarize this with content from @file.txt' },
|
||||
{ text: 'File content...' },
|
||||
];
|
||||
const userMessageTimestamp = Date.now();
|
||||
vi.spyOn(Date, 'now').mockReturnValue(userMessageTimestamp);
|
||||
|
||||
handleAtCommandSpy.mockResolvedValue({
|
||||
processedQuery: processedQueryParts,
|
||||
shouldProceed: true,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useGeminiStream(
|
||||
mockConfig.getGeminiClient() as GeminiClient,
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockOnDebugMessage,
|
||||
mockHandleSlashCommand,
|
||||
false, // shellModeActive
|
||||
vi.fn(), // getPreferredEditor
|
||||
vi.fn(), // onAuthError
|
||||
vi.fn(), // performMemoryRefresh
|
||||
false, // modelSwitched
|
||||
vi.fn(), // setModelSwitched
|
||||
vi.fn(), // onEditorClose
|
||||
vi.fn(), // onCancelSubmit
|
||||
),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitQuery(rawQuery);
|
||||
});
|
||||
|
||||
expect(handleAtCommandSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
query: rawQuery,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
{
|
||||
type: MessageType.USER,
|
||||
text: rawQuery,
|
||||
},
|
||||
userMessageTimestamp,
|
||||
);
|
||||
|
||||
// FIX: The expectation now matches the actual call signature.
|
||||
expect(mockSendMessageStream).toHaveBeenCalledWith(
|
||||
processedQueryParts, // Argument 1: The parts array directly
|
||||
expect.any(AbortSignal), // Argument 2: An AbortSignal
|
||||
expect.any(String), // Argument 3: The prompt_id string
|
||||
);
|
||||
});
|
||||
describe('Thought Reset', () => {
|
||||
it('should reset thought to null when starting a new prompt', async () => {
|
||||
// First, simulate a response with a thought
|
||||
@@ -1660,301 +1718,4 @@ describe('useGeminiStream', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should process @include commands, adding user turn after processing to prevent race conditions', async () => {
|
||||
const rawQuery = '@include file.txt Summarize this.';
|
||||
const processedQueryParts = [
|
||||
{ text: 'Summarize this with content from @file.txt' },
|
||||
{ text: 'File content...' },
|
||||
];
|
||||
const userMessageTimestamp = Date.now();
|
||||
vi.spyOn(Date, 'now').mockReturnValue(userMessageTimestamp);
|
||||
|
||||
handleAtCommandSpy.mockResolvedValue({
|
||||
processedQuery: processedQueryParts,
|
||||
shouldProceed: true,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useGeminiStream(
|
||||
mockConfig.getGeminiClient() as GeminiClient,
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockOnDebugMessage,
|
||||
mockHandleSlashCommand,
|
||||
false, // shellModeActive
|
||||
vi.fn(), // getPreferredEditor
|
||||
vi.fn(), // onAuthError
|
||||
vi.fn(), // performMemoryRefresh
|
||||
false, // modelSwitched
|
||||
vi.fn(), // setModelSwitched
|
||||
vi.fn(), // onEditorClose
|
||||
vi.fn(), // onCancelSubmit
|
||||
),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitQuery(rawQuery);
|
||||
});
|
||||
|
||||
expect(handleAtCommandSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
query: rawQuery,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
{
|
||||
type: MessageType.USER,
|
||||
text: rawQuery,
|
||||
},
|
||||
userMessageTimestamp,
|
||||
);
|
||||
|
||||
// FIX: The expectation now matches the actual call signature.
|
||||
expect(mockSendMessageStream).toHaveBeenCalledWith(
|
||||
processedQueryParts, // Argument 1: The parts array directly
|
||||
expect.any(AbortSignal), // Argument 2: An AbortSignal
|
||||
expect.any(String), // Argument 3: The prompt_id string
|
||||
);
|
||||
});
|
||||
describe('Thought Reset', () => {
|
||||
it('should reset thought to null when starting a new prompt', async () => {
|
||||
mockSendMessageStream.mockReturnValue(
|
||||
(async function* () {
|
||||
yield {
|
||||
type: ServerGeminiEventType.Thought,
|
||||
value: {
|
||||
subject: 'Previous thought',
|
||||
description: 'Old description',
|
||||
},
|
||||
};
|
||||
yield {
|
||||
type: ServerGeminiEventType.Content,
|
||||
value: 'Some response content',
|
||||
};
|
||||
yield { type: ServerGeminiEventType.Finished, value: 'STOP' };
|
||||
})(),
|
||||
);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useGeminiStream(
|
||||
new MockedGeminiClientClass(mockConfig),
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockOnDebugMessage,
|
||||
mockHandleSlashCommand,
|
||||
false,
|
||||
() => 'vscode' as EditorType,
|
||||
() => {},
|
||||
() => Promise.resolve(),
|
||||
false,
|
||||
() => {},
|
||||
() => {},
|
||||
() => {},
|
||||
),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitQuery('First query');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'gemini',
|
||||
text: 'Some response content',
|
||||
}),
|
||||
expect.any(Number),
|
||||
);
|
||||
});
|
||||
|
||||
mockSendMessageStream.mockReturnValue(
|
||||
(async function* () {
|
||||
yield {
|
||||
type: ServerGeminiEventType.Content,
|
||||
value: 'New response content',
|
||||
};
|
||||
yield { type: ServerGeminiEventType.Finished, value: 'STOP' };
|
||||
})(),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitQuery('Second query');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'gemini',
|
||||
text: 'New response content',
|
||||
}),
|
||||
expect.any(Number),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should reset thought to null when user cancels', async () => {
|
||||
mockSendMessageStream.mockReturnValue(
|
||||
(async function* () {
|
||||
yield {
|
||||
type: ServerGeminiEventType.Thought,
|
||||
value: { subject: 'Some thought', description: 'Description' },
|
||||
};
|
||||
yield { type: ServerGeminiEventType.UserCancelled };
|
||||
})(),
|
||||
);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useGeminiStream(
|
||||
new MockedGeminiClientClass(mockConfig),
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockOnDebugMessage,
|
||||
mockHandleSlashCommand,
|
||||
false,
|
||||
() => 'vscode' as EditorType,
|
||||
() => {},
|
||||
() => Promise.resolve(),
|
||||
false,
|
||||
() => {},
|
||||
() => {},
|
||||
() => {},
|
||||
),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitQuery('Test query');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'info',
|
||||
text: 'User cancelled the request.',
|
||||
}),
|
||||
expect.any(Number),
|
||||
);
|
||||
});
|
||||
|
||||
expect(result.current.streamingState).toBe(StreamingState.Idle);
|
||||
});
|
||||
|
||||
it('should reset thought to null when there is an error', async () => {
|
||||
mockSendMessageStream.mockReturnValue(
|
||||
(async function* () {
|
||||
yield {
|
||||
type: ServerGeminiEventType.Thought,
|
||||
value: { subject: 'Some thought', description: 'Description' },
|
||||
};
|
||||
yield {
|
||||
type: ServerGeminiEventType.Error,
|
||||
value: { error: { message: 'Test error' } },
|
||||
};
|
||||
})(),
|
||||
);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useGeminiStream(
|
||||
new MockedGeminiClientClass(mockConfig),
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockOnDebugMessage,
|
||||
mockHandleSlashCommand,
|
||||
false,
|
||||
() => 'vscode' as EditorType,
|
||||
() => {},
|
||||
() => Promise.resolve(),
|
||||
false,
|
||||
() => {},
|
||||
() => {},
|
||||
() => {},
|
||||
),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitQuery('Test query');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'error',
|
||||
}),
|
||||
expect.any(Number),
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockParseAndFormatApiError).toHaveBeenCalledWith(
|
||||
{ message: 'Test error' },
|
||||
expect.any(String),
|
||||
undefined,
|
||||
'gemini-2.5-pro',
|
||||
'gemini-2.5-flash',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should process @include commands, adding user turn after processing to prevent race conditions', async () => {
|
||||
const rawQuery = '@include file.txt Summarize this.';
|
||||
const processedQueryParts = [
|
||||
{ text: 'Summarize this with content from @file.txt' },
|
||||
{ text: 'File content...' },
|
||||
];
|
||||
const userMessageTimestamp = Date.now();
|
||||
vi.spyOn(Date, 'now').mockReturnValue(userMessageTimestamp);
|
||||
|
||||
handleAtCommandSpy.mockResolvedValue({
|
||||
processedQuery: processedQueryParts,
|
||||
shouldProceed: true,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useGeminiStream(
|
||||
mockConfig.getGeminiClient() as GeminiClient,
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockOnDebugMessage,
|
||||
mockHandleSlashCommand,
|
||||
false,
|
||||
vi.fn(),
|
||||
vi.fn(),
|
||||
vi.fn(),
|
||||
false,
|
||||
vi.fn(),
|
||||
vi.fn(),
|
||||
vi.fn(),
|
||||
),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitQuery(rawQuery);
|
||||
});
|
||||
|
||||
expect(handleAtCommandSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
query: rawQuery,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
{
|
||||
type: MessageType.USER,
|
||||
text: rawQuery,
|
||||
},
|
||||
userMessageTimestamp,
|
||||
);
|
||||
|
||||
// FIX: This expectation now correctly matches the actual function call signature.
|
||||
expect(mockSendMessageStream).toHaveBeenCalledWith(
|
||||
processedQueryParts, // Argument 1: The parts array directly
|
||||
expect.any(AbortSignal), // Argument 2: An AbortSignal
|
||||
expect.any(String), // Argument 3: The prompt_id string
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -120,7 +120,7 @@ describe('getLatestRelease', async () => {
|
||||
|
||||
it('throws an error if the fetch fails', async () => {
|
||||
global.fetch = vi.fn(() => Promise.reject('nope'));
|
||||
expect(getLatestGitHubRelease()).rejects.toThrowError(
|
||||
await expect(getLatestGitHubRelease()).rejects.toThrowError(
|
||||
/Unable to determine the latest/,
|
||||
);
|
||||
});
|
||||
@@ -132,7 +132,7 @@ describe('getLatestRelease', async () => {
|
||||
json: () => Promise.resolve({ foo: 'bar' }),
|
||||
} as Response),
|
||||
);
|
||||
expect(getLatestGitHubRelease()).rejects.toThrowError(
|
||||
await expect(getLatestGitHubRelease()).rejects.toThrowError(
|
||||
/Unable to determine the latest/,
|
||||
);
|
||||
});
|
||||
@@ -144,6 +144,6 @@ describe('getLatestRelease', async () => {
|
||||
json: () => Promise.resolve({ tag_name: 'v1.2.3' }),
|
||||
} as Response),
|
||||
);
|
||||
expect(getLatestGitHubRelease()).resolves.toBe('v1.2.3');
|
||||
await expect(getLatestGitHubRelease()).resolves.toBe('v1.2.3');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user