Skip to content

Commit 1649556

Browse files
committed
Add test
1 parent 9e8f8be commit 1649556

File tree

1 file changed

+129
-1
lines changed

1 file changed

+129
-1
lines changed

src/core/__tests__/Cline.test.ts

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Cline } from '../Cline';
22
import { ClineProvider } from '../webview/ClineProvider';
3-
import { ApiConfiguration } from '../../shared/api';
3+
import { ApiConfiguration, ModelInfo } from '../../shared/api';
44
import { ApiStreamChunk } from '../../api/transform/stream';
5+
import { Anthropic } from '@anthropic-ai/sdk';
56
import * as vscode from 'vscode';
67

78
// Mock all MCP-related modules
@@ -498,6 +499,133 @@ describe('Cline', () => {
498499
expect(passedMessage).not.toHaveProperty('ts');
499500
expect(passedMessage).not.toHaveProperty('extraProp');
500501
});
502+
503+
it('should handle image blocks based on model capabilities', async () => {
504+
// Create two configurations - one with image support, one without
505+
const configWithImages = {
506+
...mockApiConfig,
507+
apiModelId: 'claude-3-sonnet'
508+
};
509+
const configWithoutImages = {
510+
...mockApiConfig,
511+
apiModelId: 'gpt-3.5-turbo'
512+
};
513+
514+
// Create test conversation history with mixed content
515+
const conversationHistory: (Anthropic.MessageParam & { ts?: number })[] = [
516+
{
517+
role: 'user' as const,
518+
content: [
519+
{
520+
type: 'text' as const,
521+
text: 'Here is an image'
522+
} satisfies Anthropic.TextBlockParam,
523+
{
524+
type: 'image' as const,
525+
source: {
526+
type: 'base64' as const,
527+
media_type: 'image/jpeg',
528+
data: 'base64data'
529+
}
530+
} satisfies Anthropic.ImageBlockParam
531+
]
532+
},
533+
{
534+
role: 'assistant' as const,
535+
content: [{
536+
type: 'text' as const,
537+
text: 'I see the image'
538+
} satisfies Anthropic.TextBlockParam]
539+
}
540+
];
541+
542+
// Test with model that supports images
543+
const clineWithImages = new Cline(
544+
mockProvider,
545+
configWithImages,
546+
undefined,
547+
false,
548+
undefined,
549+
'test task'
550+
);
551+
// Mock the model info to indicate image support
552+
jest.spyOn(clineWithImages.api, 'getModel').mockReturnValue({
553+
id: 'claude-3-sonnet',
554+
info: {
555+
supportsImages: true,
556+
supportsPromptCache: true,
557+
supportsComputerUse: true,
558+
contextWindow: 200000,
559+
maxTokens: 4096,
560+
inputPrice: 0.25,
561+
outputPrice: 0.75
562+
} as ModelInfo
563+
});
564+
clineWithImages.apiConversationHistory = conversationHistory;
565+
566+
// Test with model that doesn't support images
567+
const clineWithoutImages = new Cline(
568+
mockProvider,
569+
configWithoutImages,
570+
undefined,
571+
false,
572+
undefined,
573+
'test task'
574+
);
575+
// Mock the model info to indicate no image support
576+
jest.spyOn(clineWithoutImages.api, 'getModel').mockReturnValue({
577+
id: 'gpt-3.5-turbo',
578+
info: {
579+
supportsImages: false,
580+
supportsPromptCache: false,
581+
supportsComputerUse: false,
582+
contextWindow: 16000,
583+
maxTokens: 2048,
584+
inputPrice: 0.1,
585+
outputPrice: 0.2
586+
} as ModelInfo
587+
});
588+
clineWithoutImages.apiConversationHistory = conversationHistory;
589+
590+
// Create message spy for both instances
591+
const createMessageSpyWithImages = jest.fn();
592+
const createMessageSpyWithoutImages = jest.fn();
593+
const mockStream = {
594+
async *[Symbol.asyncIterator]() {
595+
yield { type: 'text', text: '' };
596+
}
597+
} as AsyncGenerator<ApiStreamChunk>;
598+
599+
jest.spyOn(clineWithImages.api, 'createMessage').mockImplementation((...args) => {
600+
createMessageSpyWithImages(...args);
601+
return mockStream;
602+
});
603+
jest.spyOn(clineWithoutImages.api, 'createMessage').mockImplementation((...args) => {
604+
createMessageSpyWithoutImages(...args);
605+
return mockStream;
606+
});
607+
608+
// Trigger API requests for both instances
609+
await clineWithImages.recursivelyMakeClineRequests([{ type: 'text', text: 'test' }]);
610+
await clineWithoutImages.recursivelyMakeClineRequests([{ type: 'text', text: 'test' }]);
611+
612+
// Verify model with image support preserves image blocks
613+
const callsWithImages = createMessageSpyWithImages.mock.calls;
614+
const historyWithImages = callsWithImages[0][1][0];
615+
expect(historyWithImages.content).toHaveLength(2);
616+
expect(historyWithImages.content[0]).toEqual({ type: 'text', text: 'Here is an image' });
617+
expect(historyWithImages.content[1]).toHaveProperty('type', 'image');
618+
619+
// Verify model without image support converts image blocks to text
620+
const callsWithoutImages = createMessageSpyWithoutImages.mock.calls;
621+
const historyWithoutImages = callsWithoutImages[0][1][0];
622+
expect(historyWithoutImages.content).toHaveLength(2);
623+
expect(historyWithoutImages.content[0]).toEqual({ type: 'text', text: 'Here is an image' });
624+
expect(historyWithoutImages.content[1]).toEqual({
625+
type: 'text',
626+
text: '[Referenced image in conversation]'
627+
});
628+
});
501629
});
502630
});
503631
});

0 commit comments

Comments
 (0)