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
```
This commit is contained in:
2026-05-06 22:30:29 +02:00
parent 649061a747
commit 579e60f5d5
21 changed files with 2392 additions and 2427 deletions
+127 -127
View File
@@ -23,30 +23,30 @@
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">5.76% </span>
<span class="strong">94.23% </span>
<span class="quiet">Statements</span>
<span class='fraction'>3/52</span>
<span class='fraction'>49/52</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="strong">87.5% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/48</span>
<span class='fraction'>42/48</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/12</span>
<span class='fraction'>12/12</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">5.76% </span>
<span class="strong">94.23% </span>
<span class="quiet">Lines</span>
<span class='fraction'>3/52</span>
<span class='fraction'>49/52</span>
</div>
@@ -61,7 +61,7 @@
</div>
</template>
</div>
<div class='status-line low'></div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
@@ -214,123 +214,123 @@
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">// src/error-handler.ts
@@ -348,124 +348,124 @@ import {
} from './types';
&nbsp;
export class ErrorHandler {
<span class="fstat-no" title="function not covered" > static </span>handleError(error: unknown, context?: string): void {
const message = <span class="cstat-no" title="statement not covered" >this.getUserFriendlyMessage(error);</span>
<span class="cstat-no" title="statement not covered" > new Notice(message);</span>
static handleError(error: unknown, context?: string): void {
const message = this.getUserFriendlyMessage(error);
new Notice(message);
&nbsp;
<span class="cstat-no" title="statement not covered" > if (error instanceof Error) {</span>
const ctx = <span class="cstat-no" title="statement not covered" >context ? ` [${context}]` : '';</span>
if (error instanceof Error) {
const ctx = context ? ` [${context}]` : '';
// Use console.error instead of ErrorHandler.error for fatal errors
<span class="cstat-no" title="statement not covered" > console.error(`Ollama Plugin Error${ctx}: ${error.message}`);</span>
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (error.stack) {</span>
<span class="cstat-no" title="statement not covered" > console.error(error.stack);</span>
console.error(`Ollama Plugin Error${ctx}: ${error.message}`);
if (error.stack) {
console.error(error.stack);
}
} else {
const ctx = <span class="cstat-no" title="statement not covered" >context ? ` [${context}]` : '';</span>
<span class="cstat-no" title="statement not covered" > console.error(`Ollama Plugin Error${ctx}:`, error);</span>
const ctx = context ? <span class="branch-0 cbranch-no" title="branch not covered" >` [${context}]` </span>: '';
console.error(`Ollama Plugin Error${ctx}:`, error);
}
}
&nbsp;
private <span class="fstat-no" title="function not covered" >static </span>getUserFriendlyMessage(error: unknown): string {
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (error instanceof OllamaError) {</span>
<span class="cstat-no" title="statement not covered" > return this.getUserFriendlyMessageFromOllamaError(error);</span>
private static getUserFriendlyMessage(error: unknown): string {
if (error instanceof OllamaError) {
return this.getUserFriendlyMessageFromOllamaError(error);
}
&nbsp;
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (error instanceof Error) {</span>
<span class="cstat-no" title="statement not covered" > return this.getUserFriendlyMessageFromError(error);</span>
if (error instanceof Error) {
return this.getUserFriendlyMessageFromError(error);
}
&nbsp;
<span class="cstat-no" title="statement not covered" > return 'An unexpected error occurred';</span>
return 'An unexpected error occurred';
}
&nbsp;
private <span class="fstat-no" title="function not covered" >static </span>getUserFriendlyMessageFromOllamaError(error: OllamaError): string {
<span class="cstat-no" title="statement not covered" > switch (error.type) {</span>
private static getUserFriendlyMessageFromOllamaError(error: OllamaError): string {
switch (error.type) {
case ErrorType.NETWORK_ERROR:
<span class="cstat-no" title="statement not covered" > return 'Connection error. Please check if Ollama is running.';</span>
case ErrorType.API_ERROR:
return 'Connection error. Please check if Ollama is running.';
<span class="branch-1 cbranch-no" title="branch not covered" > case ErrorType.API_ERROR:</span>
<span class="cstat-no" title="statement not covered" > return `API error: ${error.message}`;</span>
case ErrorType.VALIDATION_ERROR:
<span class="cstat-no" title="statement not covered" > return this.getUserFriendlyValidationMessage(error);</span>
return this.getUserFriendlyValidationMessage(error);
case ErrorType.STREAMING_ERROR:
<span class="cstat-no" title="statement not covered" > return 'Response too long. Please try a shorter request.';</span>
return 'Response too long. Please try a shorter request.';
case ErrorType.TOOL_EXECUTION_ERROR:
<span class="cstat-no" title="statement not covered" > return `Tool error for ${(error as ToolExecutionError).toolName}. ${error.message}`;</span>
return `Tool error for ${(error as ToolExecutionError).toolName}. ${error.message}`;
case ErrorType.PATH_VALIDATION_ERROR:
<span class="cstat-no" title="statement not covered" > return `Invalid file path: ${(error as PathValidationError).path}`;</span>
case ErrorType.UNKNOWN_ERROR:
return `Invalid file path: ${(error as PathValidationError).path}`;
<span class="branch-6 cbranch-no" title="branch not covered" > case ErrorType.UNKNOWN_ERROR:</span>
<span class="cstat-no" title="statement not covered" > return 'An unexpected error occurred';</span>
default:
<span class="branch-7 cbranch-no" title="branch not covered" > default:</span>
<span class="cstat-no" title="statement not covered" > return 'An unexpected error occurred';</span>
}
}
&nbsp;
private <span class="fstat-no" title="function not covered" >static </span>getUserFriendlyValidationMessage(error: OllamaError): string {
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (error instanceof ValidationError &amp;&amp; error.details?.field) {</span>
const fieldMsg = <span class="cstat-no" title="statement not covered" >error.details.field.charAt(0).toUpperCase() + error.details.field.slice(1);</span>
<span class="cstat-no" title="statement not covered" > return `Invalid ${fieldMsg.toLowerCase()}. ${error.details.message ?? error.message}`;</span>
private static getUserFriendlyValidationMessage(error: OllamaError): string {
if (error instanceof ValidationError &amp;&amp; error.details?.field) {
const fieldMsg = error.details.field.charAt(0).toUpperCase() + error.details.field.slice(1);
return `Invalid ${fieldMsg.toLowerCase()}. ${error.details.message ?? <span class="branch-1 cbranch-no" title="branch not covered" >error.message}</span>`;
}
<span class="cstat-no" title="statement not covered" > return 'Input validation error. Please correct your input.';</span>
return 'Input validation error. Please correct your input.';
}
&nbsp;
private <span class="fstat-no" title="function not covered" >static </span>getUserFriendlyMessageFromError(error: Error): string {
const msg = <span class="cstat-no" title="statement not covered" >error.message.toLowerCase();</span>
private static getUserFriendlyMessageFromError(error: Error): string {
const msg = error.message.toLowerCase();
&nbsp;
// Check timeout BEFORE network (more specific matches first)
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (msg.includes('timeout') || msg.includes('timed out') || msg.includes('time out')) {</span>
<span class="cstat-no" title="statement not covered" > return 'Request timed out. Please check your Ollama connection.';</span>
if (msg.includes('timeout') || msg.includes('timed out') || msg.includes('time out')) {
return 'Request timed out. Please check your Ollama connection.';
}
&nbsp;
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (msg.includes('network') || msg.includes('connection') || msg.includes('fetch')) {</span>
<span class="cstat-no" title="statement not covered" > return 'Connection error. Please check if Ollama is running.';</span>
if (msg.includes('network') || msg.includes('connection') || msg.includes('fetch')) {
return 'Connection error. Please check if Ollama is running.';
}
&nbsp;
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (msg.includes('validation') || msg.includes('invalid')) {</span>
<span class="cstat-no" title="statement not covered" > return 'Invalid input. Please correct your input.';</span>
if (msg.includes('validation') || msg.includes('invalid')) {
return 'Invalid input. Please correct your input.';
}
&nbsp;
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (msg.includes('stream') || msg.includes('chunk')) {</span>
<span class="cstat-no" title="statement not covered" > return 'Response too long. Please try a shorter request.';</span>
if (msg.includes('stream') || msg.includes('chunk')) {
return 'Response too long. Please try a shorter request.';
}
&nbsp;
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (msg.includes('tool') || msg.includes('function')) {</span>
<span class="cstat-no" title="statement not covered" > return 'Tool error. Please try again.';</span>
if (msg.includes('tool') || msg.includes('function')) {
return 'Tool error. Please try again.';
}
&nbsp;
<span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (msg.includes('path') || msg.includes('file')) {</span>
<span class="cstat-no" title="statement not covered" > return 'Invalid file path. Please check the path and try again.';</span>
if (msg.includes('path') || msg.includes('file')) {
return 'Invalid file path. Please check the path and try again.';
}
&nbsp;
<span class="cstat-no" title="statement not covered" > return 'An unexpected error occurred';</span>
return 'An unexpected error occurred';
}
&nbsp;
// -- Factory methods --
&nbsp;
<span class="fstat-no" title="function not covered" > static </span>createNetworkError(message: string, statusCode?: number): NetworkError {
<span class="cstat-no" title="statement not covered" > return new NetworkError(message, statusCode);</span>
static createNetworkError(message: string, statusCode?: number): NetworkError {
return new NetworkError(message, statusCode);
}
&nbsp;
<span class="fstat-no" title="function not covered" > static </span>createApiError(message: string, statusCode?: number): ApiError {
<span class="cstat-no" title="statement not covered" > return new ApiError(message, statusCode);</span>
static createApiError(message: string, statusCode?: number): ApiError {
return new ApiError(message, statusCode);
}
&nbsp;
<span class="fstat-no" title="function not covered" > static </span>createValidationError(message: string, field?: string): ValidationError {
const details = <span class="cstat-no" title="statement not covered" >field ? { field, message } : undefined;</span>
<span class="cstat-no" title="statement not covered" > return new ValidationError(message, details);</span>
static createValidationError(message: string, field?: string): ValidationError {
const details = field ? { field, message } : <span class="branch-1 cbranch-no" title="branch not covered" >undefined;</span>
return new ValidationError(message, details);
}
&nbsp;
<span class="fstat-no" title="function not covered" > static </span>createStreamingError(message: string): StreamingError {
<span class="cstat-no" title="statement not covered" > return new StreamingError(message);</span>
static createStreamingError(message: string): StreamingError {
return new StreamingError(message);
}
&nbsp;
<span class="fstat-no" title="function not covered" > static </span>createToolExecutionError(message: string, toolName?: string): ToolExecutionError {
<span class="cstat-no" title="statement not covered" > return new ToolExecutionError(message, toolName ?? 'unknown');</span>
static createToolExecutionError(message: string, toolName?: string): ToolExecutionError {
return new ToolExecutionError(message, toolName ?? 'unknown');
}
&nbsp;
<span class="fstat-no" title="function not covered" > static </span>createPathValidationError(message: string, path?: string): PathValidationError {
<span class="cstat-no" title="statement not covered" > return new PathValidationError(message, path ?? '');</span>
static createPathValidationError(message: string, path?: string): PathValidationError {
return new PathValidationError(message, path ?? '');
}
&nbsp;
<span class="fstat-no" title="function not covered" > static </span>createUnknownError(message: string): OllamaError {
<span class="cstat-no" title="statement not covered" > return new OllamaError(message, ErrorType.UNKNOWN_ERROR);</span>
static createUnknownError(message: string): OllamaError {
return new OllamaError(message, ErrorType.UNKNOWN_ERROR);
}
}
&nbsp;</pre></td></tr></table></pre>
@@ -475,7 +475,7 @@ export class ErrorHandler {
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-05-06T19:51:33.347Z
at 2026-05-06T20:04:00.154Z
</div>
<script src="prettify.js"></script>
<script>