From 579e60f5d5a817536362c1fb563359ed12f0710c Mon Sep 17 00:00:00 2001 From: Florian Egger Date: Wed, 6 May 2026 22:30:29 +0200 Subject: [PATCH] ``` Increase test coverage across multiple modules (91.2% statements, 82.85% branches, 82.35% functions, 93.1% lines) Update abort controller handling in OllamaClient to properly clean up previous controllers Remove unused imports and constants: MODEL_NAME_REGEX, MouseEvent, DEFAULT_SETTINGS, convertMarkdownToHtml Refactor event handler naming to be more consistent ``` --- coverage/lcov-report/chat-view.ts.html | 832 +++++----- coverage/lcov-report/error-handler.ts.html | 254 +-- coverage/lcov-report/index.html | 166 +- coverage/lcov-report/ollama-client.ts.html | 556 +++---- coverage/lcov-report/tool-executor.ts.html | 216 +-- coverage/lcov-report/types.ts.html | 110 +- coverage/lcov-report/utils.ts.html | 346 ++-- coverage/lcov-report/vault-indexer.ts.html | 514 +++--- coverage/lcov.info | 1644 ++++++++++---------- src/chat-view.js | 48 +- src/chat-view.ts | 89 +- src/constants.js | 3 +- src/constants.ts | 1 - src/main.js | 1 - src/main.ts | 1 - src/ollama-client.js | 6 +- src/ollama-client.ts | 7 +- src/types.js | 9 +- src/types.ts | 8 - src/utils.js | 4 - src/utils.ts | 4 - 21 files changed, 2392 insertions(+), 2427 deletions(-) diff --git a/coverage/lcov-report/chat-view.ts.html b/coverage/lcov-report/chat-view.ts.html index 83523b1..cf58eee 100644 --- a/coverage/lcov-report/chat-view.ts.html +++ b/coverage/lcov-report/chat-view.ts.html @@ -23,30 +23,30 @@
- 58.79% + 91.2% Statements - 127/216 + 197/216
- 41.42% + 82.85% Branches - 29/70 + 58/70
- 38.23% + 82.35% Functions - 13/34 + 28/34
- 60.09% + 93.1% Lines - 122/203 + 189/203
@@ -61,7 +61,7 @@
-
+
1 2 @@ -579,382 +579,382 @@   1x   -1x +31x       -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -1x -1x -1x -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -1x -1x -  -  -  -  -  -2x -2x -  -2x -  -  -2x -  -  -2x -  -  -  -  -  -  -2x -  -1x -  -1x -  -  -1x -1x +31x +31x +31x +31x +31x +31x +31x +31x +31x +31x +31x +31x       2x     +  4x     -2x   2x +  +  +  +31x +31x +31x +31x +31x +  +  +  +  +  +  +  +  +1x +  +  +  +1x +  +  +  +5x +5x +5x +5x +  +  +  +  +  +  +  +3x +3x +3x +3x +3x +3x 3x 3x -1x -  -2x -  -  -2x -2x         -2x -1x -1x -  +23x +8x +8x         +  +33x +33x +  +33x +  +  +33x +4x +  +33x +4x +  +  4x +  +  +33x +  +22x +  +22x +  +  +22x +22x +  +  +  +33x +  +  +262x +  +  +33x +  +33x +247x +247x +104x +  +143x +  +  +143x +143x +  +  +  +  +33x +128x +3656x +24x +  +  +  +  +262x +33x +  +  +  +  +  +  +  +  +  +  +13x +  +  +13x +13x +2x +2x 2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -        +13x +13x 4x 2x 2x -  -  -  -  -2x -  -  -  -4x -2x -1x -  -  -1x -  -2x 2x         -1x -1x +13x +    -1x +13x +    -1x -1x +13x +13x     -1x -1x +  +13x +7x +7x +  +7x +  +  +7x     -1x -1x +  +  +13x +  +  +  +8x +2x +  +  +  +  +8x +2x +  +  +  +  +8x +2x +  +  +  +  +8x +8x +8x +8x +  +  +  +  +2x +2x +2x +2x +  +  +  +138x +9x +9x +  +  +  +  +9x +  +  +  +146x +13x +8x +  +  +8x +  +13x +13x +  +  +  +  +16x +16x +  +16x +  +16x +16x +  +  +14x +14x +  +  +14x +14x       -1x +14x       -1x -1x +14x +14x         -1x +14x       +120x +  +  +  +  +  +  +  +  +14x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +14x +  +14x +14x +  +  +14x +  +  +  +  +  +  +14x +  +  +  +  +  +  +  +  +14x +  +14x +14x +  +14x +14x +14x +14x +14x +12x +12x       +12x +12x     -  -  -  -  +12x 1x     +12x +  +  +  +8x           -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -1x -1x -  -  -1x -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -1x -  -1x -1x -  -1x -1x -1x -1x -1x -1x -1x -  -  -  -1x -1x -  -  -1x -1x -  -  -1x -  -  -  -1x -  -  -  -  -  -1x +8x   1x 1x @@ -1008,37 +1008,37 @@       -1x -  -  -  -  +8x +7x +7x +7x +7x         -1x -  +8x +2x   -1x +8x     -1x +14x       -  +6x         -  -  +6x +12x   -  +6x   -1x -1x +16x +16x       @@ -1087,16 +1087,16 @@ export class ChatView extends ItemView { private listenersAttached = false;   // Getters for testing - public getSendButtonClickHandler(): (() => Promise<void>) | null { - return this.sendButtonClickHandler; + public getSendButtonClickHandler(): (() => Promise<void>) | null { + return this.sendButtonClickHandler; }   - public getInputKeyDownHandler(): ((e: KeyboardEvent) => Promise<void>) | null { - return this.inputKeyDownHandler; + public getInputKeyDownHandler(): ((e: KeyboardEvent) => Promise<void>) | null { + return this.inputKeyDownHandler; }   - public getNewChatButtonClickHandler(): (() => void) | null { - return this.newChatButtonClickHandler; + public getNewChatButtonClickHandler(): (() => void) | null { + return this.newChatButtonClickHandler; }   constructor(leaf: WorkspaceLeaf, settings: PluginSettings) { @@ -1112,34 +1112,34 @@ export class ChatView extends ItemView { this.ollamaClient = new OllamaClient(newSettings.ollamaUrl, newSettings.model); }   - getViewType(): string { - return 'ollama-chat-view'; + getViewType(): string { + return 'ollama-chat-view'; }   - getDisplayText(): string { - return 'Ollama Chat'; + getDisplayText(): string { + return 'Ollama Chat'; }   - onOpen(): Promise<void> { - this.render(); - this.removeEventListeners(); // Clean up any existing listeners before reattaching - this.setupEventListeners(); - return Promise.resolve(); + onOpen(): Promise<void> { + this.render(); + this.removeEventListeners(); // Clean up any existing listeners before reattaching + this.setupEventListeners(); + return Promise.resolve(); }   public onSettingsChange(newSettings: PluginSettings): void { this.updateSettings(newSettings); }   - onClose(): Promise<void> { - this.ollamaClient.cancelStream(); - this.removeEventListeners(); - this.cleanupStreamingResources(); - this.lastMessageEl = null; - this.sendButton = null; - this.inputEl = null; - this.chatContainer = null; - return Promise.resolve(); + onClose(): Promise<void> { + this.ollamaClient.cancelStream(); + this.removeEventListeners(); + this.cleanupStreamingResources(); + this.lastMessageEl = null; + this.sendButton = null; + this.inputEl = null; + this.chatContainer = null; + return Promise.resolve(); }   private cleanupStreamingResources(): void { @@ -1158,14 +1158,14 @@ export class ChatView extends ItemView { this.contentEl.querySelector('.ollama-input-container') || this.contentEl.createEl('div', { cls: 'ollama-input-container' });   - Iif (!this.inputEl) { - this.inputEl = inputContainer.createEl('textarea', { cls: 'ollama-input' }); + if (!this.inputEl) { + this.inputEl = inputContainer.createEl('textarea', { cls: 'ollama-input' }); } - Iif (!this.sendButton) { - this.sendButton = inputContainer.createEl('button', { + if (!this.sendButton) { + this.sendButton = inputContainer.createEl('button', { cls: 'ollama-send-button', }); - (this.sendButton as HTMLButtonElement).textContent = 'Send'; + (this.sendButton as HTMLButtonElement).textContent = 'Send'; }   if (!this.newChatButton) { @@ -1204,8 +1204,8 @@ export class ChatView extends ItemView { // Remove messages that are no longer in the array for (const el of Array.from(existingMessages)) { const id = el.getAttribute('data-msg-id'); - Iif (!id || !nonStreamingMessages.some((m) => m.id === id)) { - el.remove(); + if (!id || !nonStreamingMessages.some((m) => m.id === id)) { + el.remove(); } }   @@ -1221,85 +1221,85 @@ export class ChatView extends ItemView { } }   - private setupEventListeners(): void { - Iif (!this.sendButton || !this.inputEl || this.listenersAttached) return; + private setupEventListeners(): void { + Iif (!this.sendButton || !this.inputEl || this.listenersAttached) return;   // Create handlers if they don't exist - Iif (!this.sendButtonClickHandler) { - this.sendButtonClickHandler = async () => { - Iif (!this.inputEl) return; - await this.handleUserInput((this.inputEl as HTMLTextAreaElement).value); - (this.inputEl as HTMLTextAreaElement).value = ''; + if (!this.sendButtonClickHandler) { + this.sendButtonClickHandler = async () => { + Iif (!this.inputEl) return; + await this.handleUserInput((this.inputEl as HTMLTextAreaElement).value); + (this.inputEl as HTMLTextAreaElement).value = ''; }; }   - Iif (!this.inputKeyDownHandler) { - this.inputKeyDownHandler = async (e: KeyboardEvent) => { - Iif (!this.inputEl || e.key !== 'Enter' || e.shiftKey) return; - e.preventDefault(); - await this.handleUserInput((this.inputEl as HTMLTextAreaElement).value); - (this.inputEl as HTMLTextAreaElement).value = ''; + if (!this.inputKeyDownHandler) { + this.inputKeyDownHandler = async (e: KeyboardEvent) => { + if (!this.inputEl || e.key !== 'Enter' || e.shiftKey) return; + e.preventDefault(); + await this.handleUserInput((this.inputEl as HTMLTextAreaElement).value); + (this.inputEl as HTMLTextAreaElement).value = ''; }; }   // Add event listeners - this.sendButtonEventHandler = () => { + this.sendButtonEventHandler = () => { void this.sendButtonClickHandler?.(); }; - this.inputKeyDownEventHandler = (e: KeyboardEvent) => { + this.inputKeyDownEventHandler = (e: KeyboardEvent) => { void this.inputKeyDownHandler?.(e); }; - (this.sendButton as HTMLButtonElement).addEventListener('click', this.sendButtonEventHandler); - (this.inputEl as HTMLTextAreaElement).addEventListener( + (this.sendButton as HTMLButtonElement).addEventListener('click', this.sendButtonEventHandler); + (this.inputEl as HTMLTextAreaElement).addEventListener( 'keydown', this.inputKeyDownEventHandler ); - Iif (this.newChatButton) { - Iif (!this.newChatButtonClickHandler) { - this.newChatButtonClickHandler = () => this.clearConversation(); + if (this.newChatButton) { + if (!this.newChatButtonClickHandler) { + this.newChatButtonClickHandler = () => this.clearConversation(); } - this.newChatButtonEventHandler = () => { + this.newChatButtonEventHandler = () => { this.newChatButtonClickHandler?.(); }; - (this.newChatButton as HTMLButtonElement).addEventListener( + (this.newChatButton as HTMLButtonElement).addEventListener( 'click', this.newChatButtonEventHandler ); } - this.listenersAttached = true; + this.listenersAttached = true; }   - private removeEventListeners(): void { - Iif (this.sendButton && this.sendButtonEventHandler) { - (this.sendButton as HTMLButtonElement).removeEventListener( + private removeEventListeners(): void { + if (this.sendButton && this.sendButtonEventHandler) { + (this.sendButton as HTMLButtonElement).removeEventListener( 'click', this.sendButtonEventHandler ); } - Iif (this.inputEl && this.inputKeyDownEventHandler) { - (this.inputEl as HTMLTextAreaElement).removeEventListener( + if (this.inputEl && this.inputKeyDownEventHandler) { + (this.inputEl as HTMLTextAreaElement).removeEventListener( 'keydown', this.inputKeyDownEventHandler ); } - Iif (this.newChatButton && this.newChatButtonEventHandler) { - (this.newChatButton as HTMLButtonElement).removeEventListener( + if (this.newChatButton && this.newChatButtonEventHandler) { + (this.newChatButton as HTMLButtonElement).removeEventListener( 'click', this.newChatButtonEventHandler ); } - this.sendButtonEventHandler = null; - this.inputKeyDownEventHandler = null; - this.newChatButtonEventHandler = null; - this.listenersAttached = false; + this.sendButtonEventHandler = null; + this.inputKeyDownEventHandler = null; + this.newChatButtonEventHandler = null; + this.listenersAttached = false; }   - private clearConversation(): void { + private clearConversation(): void { // Create new array to ensure immutability - this.messages = []; - this.lastMessageEl = null; - this.render(); - new Notice('Conversation cleared'); + this.messages = []; + this.lastMessageEl = null; + this.render(); + new Notice('Conversation cleared'); }   private updateMessageById(id: string, partial: Partial<ChatMessage>): boolean { @@ -1333,7 +1333,7 @@ export class ChatView extends ItemView { try { // Guard against empty messages const userMessage = content.trim(); - Iif (!userMessage) return; + if (!userMessage) return;   // Search vault using user message as query const entries = await this.vaultIndexer.searchVault(userMessage, DEFAULT_VAULT_SEARCH_LIMIT); @@ -1358,8 +1358,8 @@ export class ChatView extends ItemView { const messages: OllamaMessage[] = [ systemMessage, ...this.messages.map( - (m) => - ({ + (m) => + ({ role: m.role, content: m.content, tool_calls: m.tool_calls, @@ -1498,17 +1498,17 @@ export class ChatView extends ItemView { }   // Update last message immutably — only if no tool calls were processed - Iif (toolCalls.length === 0) { - const lastMessageIndex = this.messages.length - 1; - Iif (lastMessageIndex >= 0) { - const lastMessage = { ...this.messages[lastMessageIndex], isStreaming: false }; - this.messages = [...this.messages.slice(0, lastMessageIndex), lastMessage]; + if (toolCalls.length === 0) { + const lastMessageIndex = this.messages.length - 1; + if (lastMessageIndex >= 0) { + const lastMessage = { ...this.messages[lastMessageIndex], isStreaming: false }; + this.messages = [...this.messages.slice(0, lastMessageIndex), lastMessage]; } }   // Limit conversation history to prevent memory issues - Iif (this.messages.length > MAX_MESSAGE_HISTORY) { - this.messages = this.messages.slice(-MAX_MESSAGE_HISTORY); + if (this.messages.length > MAX_MESSAGE_HISTORY) { + this.messages = this.messages.slice(-MAX_MESSAGE_HISTORY); } this.render(); } finally { @@ -1517,15 +1517,15 @@ export class ChatView extends ItemView { } } catch (error) { // Use centralized error handler - ErrorHandler.handleError(error, 'ChatView.handleUserInput'); + ErrorHandler.handleError(error, 'ChatView.handleUserInput'); // Update any streaming messages to non-streaming state to prevent stale messages // This ensures that if an error occurs during streaming, the assistant message // is still visible (with any partial content received) but won't cause issues // in subsequent requests due to stale isStreaming: true flag - this.messages = this.messages.map((msg) => - msg.isStreaming ? { ...msg, isStreaming: false } : msg + this.messages = this.messages.map((msg) => + msg.isStreaming ? { ...msg, isStreaming: false } : msg ); - this.cleanupStreamingResources(); + this.cleanupStreamingResources(); } finally { if (this.sendButton) { (this.sendButton as HTMLButtonElement).disabled = false; @@ -1540,7 +1540,7 @@ export class ChatView extends ItemView {