From 1d5094f0288526e4e215712a2f9cdc1763acebf7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 10:19:48 +0000 Subject: [PATCH] Refactor: Split tests into unit and integration suites Decouples pure logic from VS Code dependencies to allow fast unit testing without launching a VS Code instance. Changes: - Extracted `src/core/types.ts`, `src/core/constants.ts`, and `src/core/logic.ts` from `src/core/config.ts`. - Refactored `src/ai/registry.ts` and `src/ai/selector.ts` to use type-only imports for `vscode`. - Organized tests into `src/test/unit` and `src/test/integration`. - Added `npm run test:unit` script (Mocha) and `npm run test:integration` (`@vscode/test-cli`). - Updated `lint-staged` and GitHub CI to run unit tests separately. --- .github/workflows/ci.yml | 7 +++-- .vscode-test.mjs | 2 +- package.json | 7 +++-- src/ai/registry.ts | 4 +-- src/ai/selector.ts | 4 +-- src/commands.ts | 3 +- src/core/config.ts | 29 ++------------------ src/core/constants.ts | 8 ++++++ src/core/logic.ts | 9 ++++++ src/core/types.ts | 9 ++++++ src/providers/local/index.ts | 2 +- src/test/{ => integration}/config.test.ts | 5 +++- src/test/{ => integration}/extension.test.ts | 6 ++-- src/test/{ => unit}/ai.test.ts | 8 +++--- src/test/{ => unit}/http_error.test.ts | 4 +-- src/test/{ => unit}/utils.test.ts | 6 ++-- src/utils/truncate.ts | 2 +- 17 files changed, 63 insertions(+), 52 deletions(-) create mode 100644 src/core/constants.ts create mode 100644 src/core/logic.ts create mode 100644 src/core/types.ts rename src/test/{ => integration}/config.test.ts (88%) rename src/test/{ => integration}/extension.test.ts (83%) rename src/test/{ => unit}/ai.test.ts (92%) rename src/test/{ => unit}/http_error.test.ts (93%) rename src/test/{ => unit}/utils.test.ts (96%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92b1591..5b688b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,9 @@ jobs: - name: Compile run: npm run compile - - name: Run Tests + - name: Run Unit Tests + run: npm run test:unit + + - name: Run Integration Tests # VS Code tests require a display server on Linux - run: xvfb-run -a npm test + run: xvfb-run -a npm run test:integration diff --git a/.vscode-test.mjs b/.vscode-test.mjs index b62ba25..f9c67eb 100644 --- a/.vscode-test.mjs +++ b/.vscode-test.mjs @@ -1,5 +1,5 @@ import { defineConfig } from '@vscode/test-cli'; export default defineConfig({ - files: 'out/test/**/*.test.js', + files: 'out/test/integration/**/*.test.js', }); diff --git a/package.json b/package.json index fb2c832..2ed00b3 100644 --- a/package.json +++ b/package.json @@ -113,12 +113,15 @@ "pretest": "npm run compile-tests && npm run compile && npm run lint", "check-types": "node ./node_modules/typescript/bin/tsc --noEmit", "lint": "node ./node_modules/eslint/bin/eslint.js src", - "test": "node ./node_modules/@vscode/test-cli/out/bin.mjs", + "test": "npm run test:unit && npm run test:integration", + "test:unit": "npm run compile-tests && npx mocha out/test/unit/**/*.test.js --ui tdd", + "test:integration": "node ./node_modules/@vscode/test-cli/out/bin.mjs", "prepare": "husky" }, "lint-staged": { "*.ts": [ - "eslint --fix" + "eslint --fix", + "bash -c 'npm run test:unit'" ] }, "devDependencies": { diff --git a/src/ai/registry.ts b/src/ai/registry.ts index 2affe81..ff06509 100644 --- a/src/ai/registry.ts +++ b/src/ai/registry.ts @@ -1,5 +1,5 @@ -import * as vscode from 'vscode'; -import type { PredicteCommitConfig } from '../core/config'; +import type * as vscode from 'vscode'; +import type { PredicteCommitConfig } from '../core/types'; import type { ProviderClient } from './types'; export interface ProviderDefinition { diff --git a/src/ai/selector.ts b/src/ai/selector.ts index 98214ca..8760a5c 100644 --- a/src/ai/selector.ts +++ b/src/ai/selector.ts @@ -1,6 +1,6 @@ import type * as vscode from 'vscode'; -import type { PredicteCommitConfig } from '../core/config'; -import { getEffectiveProviderId } from '../core/config'; +import type { PredicteCommitConfig } from '../core/types'; +import { getEffectiveProviderId } from '../core/logic'; import type { ProviderClient } from './types'; import { getProviderDefinition } from './registry'; diff --git a/src/commands.ts b/src/commands.ts index 1dafd1c..b95cf1e 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; -import { getConfig, getEffectiveProviderId } from './core/config'; +import { getConfig } from './core/config'; +import { getEffectiveProviderId } from './core/logic'; import { getGitExtension } from './modules/git/vscode'; import { getTargetRepository } from './modules/git/repo'; import { toRepoRelativePosixPath } from './utils/paths'; diff --git a/src/core/config.ts b/src/core/config.ts index 42d7607..2782f97 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -1,16 +1,6 @@ import * as vscode from 'vscode'; - -export type PredicteCommitConfig = { - provider: string; - models: string[]; - ignoredFiles: string[]; - useLocal: boolean; - localBaseUrl: string; - localModel: string; - debugLogging: boolean; -}; - -export const DEFAULT_LOCAL_URL = ''; +import { PredicteCommitConfig } from './types'; +import { DEFAULT_LOCAL_URL } from './constants'; export function getConfig(): PredicteCommitConfig { const cfg = vscode.workspace.getConfiguration('predicteCommit'); @@ -26,18 +16,3 @@ export function getConfig(): PredicteCommitConfig { debugLogging: cfg.get('debugLogging', false), }; } - -export const DIFF_CAPS: { maxCharsPerFile: number; maxCharsTotal: number } = { - maxCharsPerFile: 8000, - maxCharsTotal: 32000, -}; - -export const TRUNCATION_MARKER = '...TRUNCATED...'; - -export function getEffectiveProviderId(cfg: PredicteCommitConfig): string { - // Backwards compatibility: existing users may rely on useLocal. - if (cfg.useLocal) { - return 'ollama'; - } - return cfg.provider; -} diff --git a/src/core/constants.ts b/src/core/constants.ts new file mode 100644 index 0000000..7e65d52 --- /dev/null +++ b/src/core/constants.ts @@ -0,0 +1,8 @@ +export const DEFAULT_LOCAL_URL = ''; + +export const DIFF_CAPS: { maxCharsPerFile: number; maxCharsTotal: number } = { + maxCharsPerFile: 8000, + maxCharsTotal: 32000, +}; + +export const TRUNCATION_MARKER = '...TRUNCATED...'; diff --git a/src/core/logic.ts b/src/core/logic.ts new file mode 100644 index 0000000..ba3afe1 --- /dev/null +++ b/src/core/logic.ts @@ -0,0 +1,9 @@ +import { PredicteCommitConfig } from './types'; + +export function getEffectiveProviderId(cfg: PredicteCommitConfig): string { + // Backwards compatibility: existing users may rely on useLocal. + if (cfg.useLocal) { + return 'ollama'; + } + return cfg.provider; +} diff --git a/src/core/types.ts b/src/core/types.ts new file mode 100644 index 0000000..561bc4a --- /dev/null +++ b/src/core/types.ts @@ -0,0 +1,9 @@ +export type PredicteCommitConfig = { + provider: string; + models: string[]; + ignoredFiles: string[]; + useLocal: boolean; + localBaseUrl: string; + localModel: string; + debugLogging: boolean; +}; diff --git a/src/providers/local/index.ts b/src/providers/local/index.ts index bfe3426..7767521 100644 --- a/src/providers/local/index.ts +++ b/src/providers/local/index.ts @@ -1,7 +1,7 @@ import { postChatCompletion } from '../../ai/http'; import type { GenerateRequest, GenerateResult, ProviderClient } from '../../ai/types'; import { registerProvider } from '../../ai/registry'; -import { DEFAULT_LOCAL_URL } from '../../core/config'; +import { DEFAULT_LOCAL_URL } from '../../core/constants'; export class LocalProvider implements ProviderClient { constructor( diff --git a/src/test/config.test.ts b/src/test/integration/config.test.ts similarity index 88% rename from src/test/config.test.ts rename to src/test/integration/config.test.ts index 8f0e58e..59d95e3 100644 --- a/src/test/config.test.ts +++ b/src/test/integration/config.test.ts @@ -1,5 +1,8 @@ import * as assert from 'assert'; -import { getEffectiveProviderId, getConfig, PredicteCommitConfig, DEFAULT_LOCAL_URL } from '../core/config'; +import { getConfig } from '../../core/config'; +import { PredicteCommitConfig } from '../../core/types'; +import { getEffectiveProviderId } from '../../core/logic'; +import { DEFAULT_LOCAL_URL } from '../../core/constants'; suite('Config Test Suite', () => { test('getEffectiveProviderId', () => { diff --git a/src/test/extension.test.ts b/src/test/integration/extension.test.ts similarity index 83% rename from src/test/extension.test.ts rename to src/test/integration/extension.test.ts index 3147783..b433856 100644 --- a/src/test/extension.test.ts +++ b/src/test/integration/extension.test.ts @@ -3,10 +3,10 @@ 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'; +// import * as myExtension from '../../../extension'; -import { truncateWithMarker } from '../utils/truncate'; -import { isTransientStatus, isAuthOrConfigStatus } from '../ai/errors'; +import { truncateWithMarker } from '../../utils/truncate'; +import { isTransientStatus, isAuthOrConfigStatus } from '../../ai/errors'; suite('Extension Test Suite', () => { vscode.window.showInformationMessage('Start all tests.'); diff --git a/src/test/ai.test.ts b/src/test/unit/ai.test.ts similarity index 92% rename from src/test/ai.test.ts rename to src/test/unit/ai.test.ts index ae903a1..f83d7b2 100644 --- a/src/test/ai.test.ts +++ b/src/test/unit/ai.test.ts @@ -1,8 +1,8 @@ import * as assert from 'assert'; -import { selectProvider } from '../ai/selector'; -import { registerProvider } from '../ai/registry'; -import { PredicteCommitConfig } from '../core/config'; -import { ProviderClient, GenerateRequest, GenerateResult } from '../ai/types'; +import { selectProvider } from '../../ai/selector'; +import { registerProvider } from '../../ai/registry'; +import { PredicteCommitConfig } from '../../core/types'; +import { ProviderClient, GenerateRequest, GenerateResult } from '../../ai/types'; suite('AI Selector Test Suite', () => { test('selectProvider selects correct provider', async () => { diff --git a/src/test/http_error.test.ts b/src/test/unit/http_error.test.ts similarity index 93% rename from src/test/http_error.test.ts rename to src/test/unit/http_error.test.ts index 6a4d22c..f63e9a8 100644 --- a/src/test/http_error.test.ts +++ b/src/test/unit/http_error.test.ts @@ -1,6 +1,6 @@ import * as assert from 'assert'; -import { postChatCompletion } from '../ai/http'; -import { ProviderError } from '../ai/errors'; +import { postChatCompletion } from '../../ai/http'; +import { ProviderError } from '../../ai/errors'; suite('HTTP Error Handling', () => { test('ECONNREFUSED returns friendly message', async () => { diff --git a/src/test/utils.test.ts b/src/test/unit/utils.test.ts similarity index 96% rename from src/test/utils.test.ts rename to src/test/unit/utils.test.ts index fa6c65d..83e46c8 100644 --- a/src/test/utils.test.ts +++ b/src/test/unit/utils.test.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; -import { isIgnored } from '../utils/ignore'; -import { truncateWithMarker, capDiffsByFileAndTotal } from '../utils/truncate'; -import { DIFF_CAPS, TRUNCATION_MARKER } from '../core/config'; +import { isIgnored } from '../../utils/ignore'; +import { truncateWithMarker, capDiffsByFileAndTotal } from '../../utils/truncate'; +import { DIFF_CAPS, TRUNCATION_MARKER } from '../../core/constants'; suite('Utils Test Suite', () => { test('isIgnored', () => { diff --git a/src/utils/truncate.ts b/src/utils/truncate.ts index 38dcde6..0af0aa8 100644 --- a/src/utils/truncate.ts +++ b/src/utils/truncate.ts @@ -1,4 +1,4 @@ -import { DIFF_CAPS, TRUNCATION_MARKER } from '../core/config'; +import { DIFF_CAPS, TRUNCATION_MARKER } from '../core/constants'; export function truncateWithMarker(input: string, maxChars: number): string { if (input.length <= maxChars) {