Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,18 +195,22 @@
"watch": "tsc -watch -p ./",
"pretest": "npm run compile && npm run lint",
"lint": "eslint src",
"test": "vscode-test"
"test": "vscode-test",
"test:unit": "vscode-test --grep 'UI Logic Tests|Extension Test Suite'",
"test:integration": "vscode-test --grep 'Integration Tests'"
},
"devDependencies": {
"@types/mocha": "^10.0.10",
"@types/node": "20.x",
"@types/sinon": "^17.0.4",
"@types/vscode": "^1.94.0",
"@typescript-eslint/eslint-plugin": "^8.31.1",
"@typescript-eslint/parser": "^8.31.1",
"@vscode/test-cli": "^0.0.10",
"@vscode/test-electron": "^2.5.2",
"@vscode/vsce": "^3.5.0",
"eslint": "^9.25.1",
"sinon": "^21.0.0",
"typescript": "^5.8.3"
}
}
11 changes: 8 additions & 3 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ class ClaudeChatProvider {
private _handleWebviewMessage(message: any) {
switch (message.type) {
case 'sendMessage':
this._sendMessageToClaude(message.text, message.planMode, message.thinkingMode);
this._sendMessageToClaude(message.text, message.planMode, message.thinkingMode, message.longContext);
return;
case 'newSession':
this._newSession();
Expand Down Expand Up @@ -405,7 +405,7 @@ class ClaudeChatProvider {
}
}

private async _sendMessageToClaude(message: string, planMode?: boolean, thinkingMode?: boolean) {
private async _sendMessageToClaude(message: string, planMode?: boolean, thinkingMode?: boolean, longContext?: boolean) {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
const cwd = workspaceFolder ? workspaceFolder.uri.fsPath : process.cwd();

Expand Down Expand Up @@ -496,7 +496,12 @@ class ClaudeChatProvider {

// Add model selection if not using default
if (this._selectedModel && this._selectedModel !== 'default') {
args.push('--model', this._selectedModel);
let modelName = this._selectedModel;
// Add [1m] suffix for long context when enabled and supported
if (longContext && this._selectedModel === 'sonnet') {
modelName = 'sonnet[1m]';
}
args.push('--model', modelName);
}

// Add session resume if we have a current session
Expand Down
30 changes: 29 additions & 1 deletion src/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
let selectedFileIndex = -1;
let planModeEnabled = false;
let thinkingModeEnabled = false;
let longContextEnabled = false;

function shouldAutoScroll(messagesDiv) {
const threshold = 100; // pixels from bottom
Expand Down Expand Up @@ -719,7 +720,8 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
type: 'sendMessage',
text: text,
planMode: planModeEnabled,
thinkingMode: thinkingModeEnabled
thinkingMode: thinkingModeEnabled,
longContext: longContextEnabled
});

messageInput.value = '';
Expand Down Expand Up @@ -758,6 +760,16 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
}
}

function toggleLongContext() {
longContextEnabled = !longContextEnabled;
const switchElement = document.getElementById('longContextSwitch');
if (longContextEnabled) {
switchElement.classList.add('active');
} else {
switchElement.classList.remove('active');
}
}


let totalCost = 0;
let totalTokensInput = 0;
Expand Down Expand Up @@ -1704,6 +1716,22 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
};
document.getElementById('selectedModel').textContent = displayNames[model] || model;

// Show/hide Long Context toggle based on model selection
const longContextToggle = document.getElementById('longContextToggle');
if (longContextToggle) {
if (model === 'sonnet' || model === 'default') {
longContextToggle.style.display = 'flex';
} else {
longContextToggle.style.display = 'none';
// Reset long context when hidden
longContextEnabled = false;
const switchElement = document.getElementById('longContextSwitch');
if (switchElement) {
switchElement.classList.remove('active');
}
}
}

// Only send model selection to VS Code extension if not from backend
if (!fromBackend) {
vscode.postMessage({
Expand Down
23 changes: 16 additions & 7 deletions src/test/extension.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import * as assert from 'assert';

// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
// import * as myExtension from '../../extension';

suite('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start all tests.');

test('Sample test', () => {
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
test('Extension should be present', () => {
assert.ok(vscode.extensions.getExtension('AndrePimenta.claude-code-chat'));
});

test('Extension should activate', async () => {
const extension = vscode.extensions.getExtension('AndrePimenta.claude-code-chat');
if (extension && !extension.isActive) {
await extension.activate();
}
assert.ok(extension?.isActive);
});

test('Commands should be registered', async () => {
const commands = await vscode.commands.getCommands(true);
assert.ok(commands.includes('claude-code-chat.openChat'));
assert.ok(commands.includes('claude-code-chat.loadConversation'));
});
});
81 changes: 81 additions & 0 deletions src/test/integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as assert from 'assert';
import * as vscode from 'vscode';
import * as sinon from 'sinon';

suite('Long Context Integration Tests', () => {
let mockContext: vscode.ExtensionContext;
let mockWorkspaceState: sinon.SinonStubbedInstance<vscode.Memento>;

setup(() => {
mockWorkspaceState = {
get: sinon.stub(),
update: sinon.stub(),
keys: sinon.stub()
} as any;
mockContext = {
workspaceState: mockWorkspaceState,
extensionUri: vscode.Uri.file('/test')
} as any;
});

teardown(() => {
sinon.restore();
});

suite('End-to-End Message Flow', () => {
test('should handle complete long context workflow', async () => {
// This test would verify the complete flow:
// 1. User selects Sonnet model
// 2. User enables Long Context toggle
// 3. User sends message
// 4. Extension receives message with longContext=true
// 5. Extension spawns Claude CLI with --model sonnet[1m]

const mockMessage = {
type: 'sendMessage',
text: 'Test message with long context',
planMode: false,
thinkingMode: false,
longContext: true
};

// Mock the extension provider and verify behavior
assert.ok(true, 'Integration test framework setup needed');
});

test('should preserve model selection when toggling long context', () => {
// Test that switching long context on/off doesn't affect model selection
assert.ok(true, 'Test implementation needed');
});

test('should reset long context when switching from sonnet to opus', () => {
// Test complete model switching workflow with long context reset
assert.ok(true, 'Test implementation needed');
});
});

suite('State Persistence', () => {
test('should not persist long context state across sessions', () => {
// Verify that long context state is session-only, not persisted
// Unlike model selection which is saved to workspace state
assert.ok(true, 'State persistence test needed');
});

test('should maintain model selection independent of long context', () => {
// Test that model selection persists even when long context is toggled
assert.ok(true, 'Model persistence test needed');
});
});

suite('Error Handling', () => {
test('should handle missing DOM elements gracefully', () => {
// Test behavior when longContextToggle or longContextSwitch elements don't exist
assert.ok(true, 'Error handling test needed');
});

test('should handle invalid model selections', () => {
// Test behavior with unexpected model names
assert.ok(true, 'Invalid model handling test needed');
});
});
});
Loading