Skip to main content
Glama
analyze-coverage-thresholds.test.ts10.4 kB
import { describe, it, expect, vi, beforeEach } from "vitest"; import { handleAnalyzeCoverage } from "../analyze-coverage.js"; import * as configLoader from "../../config/config-loader.js"; import * as fileUtils from "../../utils/file-utils.js"; import * as versionChecker from "../../utils/version-checker.js"; import * as projectContext from "../../context/project-context.js"; import * as vitestConfigReader from "../../utils/vitest-config-reader.js"; import { spawn } from "child_process"; import type { ChildProcess } from "child_process"; import { EventEmitter } from "events"; vi.mock("../../config/config-loader"); vi.mock("../../utils/file-utils"); vi.mock("../../utils/version-checker"); vi.mock("../../context/project-context"); vi.mock("../../utils/vitest-config-reader"); vi.mock("child_process"); describe("analyze-coverage threshold detection", () => { const mockProjectRoot = "/test/project"; beforeEach(() => { vi.clearAllMocks(); // Mock project context vi.spyOn(projectContext.projectContext, "getProjectRoot").mockReturnValue( mockProjectRoot ); // Mock vitest config reader - default to no thresholds configured vi.mocked(vitestConfigReader.getVitestCoverageThresholds).mockResolvedValue(null); vi.mocked(vitestConfigReader.checkThresholdsMet).mockReturnValue(true); vi.mocked(vitestConfigReader.getThresholdViolations).mockReturnValue([]); // Mock config vi.mocked(configLoader.getConfig).mockResolvedValue({ testDefaults: { format: "summary" as const, timeout: 30000, watchMode: false, }, coverageDefaults: { format: "summary" as const, exclude: [], }, discovery: { testPatterns: ["**/*.{test,spec}.{js,ts,jsx,tsx}"], excludePatterns: ["node_modules", "dist", "coverage", ".git"], maxDepth: 10, }, server: { verbose: false, validatePaths: true, allowRootExecution: false, workingDirectory: mockProjectRoot, }, safety: { maxFiles: 100, requireConfirmation: true, allowedRunners: ["vitest"], allowedPaths: undefined!, }, }); // Mock version check vi.mocked(versionChecker.checkAllVersions).mockResolvedValue({ vitest: { installed: true, version: "1.0.0", compatible: true }, node: { installed: true, version: "18.0.0", compatible: true }, coverageProvider: { installed: true, version: "1.0.0", compatible: true }, errors: [], warnings: [], }); // Mock file exists vi.mocked(fileUtils.fileExists).mockResolvedValue(true); vi.mocked(fileUtils.isDirectory).mockResolvedValue(false); }); it("should detect when Vitest thresholds pass (exit code 0)", async () => { // Mock thresholds configured vi.mocked(vitestConfigReader.getVitestCoverageThresholds).mockResolvedValue({ lines: 80, functions: 80, branches: 80, statements: 80 }); vi.mocked(vitestConfigReader.checkThresholdsMet).mockReturnValue(true); vi.mocked(vitestConfigReader.getThresholdViolations).mockReturnValue([]); // Mock spawn to simulate successful threshold check const mockProcess = new EventEmitter() as ChildProcess; const mockStdout = new EventEmitter(); const mockStderr = new EventEmitter(); mockProcess.stdout = mockStdout as NodeJS.ReadableStream; mockProcess.stderr = mockStderr as NodeJS.ReadableStream; vi.mocked(spawn).mockReturnValue(mockProcess); // Simulate Vitest output with coverage data const coverageOutput = JSON.stringify({ coverageMap: { "/test/project/src/utils.ts": { path: "/test/project/src/utils.ts", statementMap: { "0": {}, "1": {} }, fnMap: { "0": {}, "1": {} }, branchMap: { "0": {} }, s: { "0": 1, "1": 1 }, // 100% statement coverage f: { "0": 1, "1": 1 }, // 100% function coverage b: { "0": [1, 1] }, // 100% branch coverage }, }, }); setTimeout(() => { mockStdout.emit("data", coverageOutput); mockProcess.emit("close", 0); // Exit code 0 - thresholds passed }, 10); const result = await handleAnalyzeCoverage({ target: "./src/utils.ts", format: "summary", }); expect(result.success).toBe(true); expect(result.meetsThreshold).toBe(true); expect(result.coverage.lines).toBe(100); expect(result.coverage.functions).toBe(100); }); it("should detect when Vitest thresholds fail (exit code 1)", async () => { // Mock thresholds configured with failing coverage vi.mocked(vitestConfigReader.getVitestCoverageThresholds).mockResolvedValue({ lines: 80, functions: 80, branches: 80, statements: 80 }); vi.mocked(vitestConfigReader.checkThresholdsMet).mockReturnValue(false); vi.mocked(vitestConfigReader.getThresholdViolations).mockReturnValue([ "Line coverage (50%) is below threshold (80%)", "Function coverage (50%) is below threshold (80%)" ]); // Mock spawn to simulate failed threshold check const mockProcess = new EventEmitter() as ChildProcess; const mockStdout = new EventEmitter(); const mockStderr = new EventEmitter(); mockProcess.stdout = mockStdout as NodeJS.ReadableStream; mockProcess.stderr = mockStderr as NodeJS.ReadableStream; vi.mocked(spawn).mockReturnValue(mockProcess); // Simulate Vitest output with low coverage const coverageOutput = JSON.stringify({ coverageMap: { "/test/project/src/utils.ts": { path: "/test/project/src/utils.ts", statementMap: { "0": {}, "1": {}, "2": {}, "3": {} }, fnMap: { "0": {}, "1": {} }, branchMap: { "0": {} }, s: { "0": 1, "1": 1, "2": 0, "3": 0 }, // 50% statement coverage f: { "0": 1, "1": 0 }, // 50% function coverage b: { "0": [1, 0] }, // 50% branch coverage }, }, }); setTimeout(() => { mockStdout.emit("data", coverageOutput); mockProcess.emit("close", 0); // Exit code 0 - Vitest doesn't fail on threshold violations }, 10); const result = await handleAnalyzeCoverage({ target: "./src/utils.ts", format: "summary", }); expect(result.success).toBe(true); // Coverage ran successfully expect(result.meetsThreshold).toBe(false); // But thresholds were not met expect(result.thresholdViolations).toEqual([ "Line coverage (50%) is below threshold (80%)", "Function coverage (50%) is below threshold (80%)" ]); expect(result.coverage.lines).toBe(50); expect(result.coverage.functions).toBe(50); }); it("should handle when Vitest has no thresholds configured (exit code 0)", async () => { // Mock no thresholds configured (default from beforeEach) // Mock spawn to simulate no thresholds configured const mockProcess = new EventEmitter() as ChildProcess; const mockStdout = new EventEmitter(); const mockStderr = new EventEmitter(); mockProcess.stdout = mockStdout as NodeJS.ReadableStream; mockProcess.stderr = mockStderr as NodeJS.ReadableStream; vi.mocked(spawn).mockReturnValue(mockProcess); const coverageOutput = JSON.stringify({ coverageMap: { "/test/project/src/utils.ts": { path: "/test/project/src/utils.ts", statementMap: { "0": {}, "1": {} }, fnMap: { "0": {} }, branchMap: {}, s: { "0": 1, "1": 0 }, // 50% coverage f: { "0": 0 }, // 0% function coverage b: {}, }, }, }); setTimeout(() => { mockStdout.emit("data", coverageOutput); // No threshold errors because none are configured mockProcess.emit("close", 0); // Exit code 0 - no thresholds to fail }, 10); const result = await handleAnalyzeCoverage({ target: "./src/utils.ts", format: "summary", }); expect(result.success).toBe(true); expect(result.meetsThreshold).toBeUndefined(); // No threshold info when none configured expect(result.thresholdViolations).toBeUndefined(); // No violations when none configured expect(result.coverage.lines).toBe(50); expect(result.coverage.functions).toBe(0); }); it("should provide detailed format with threshold status", async () => { // Mock thresholds configured with failing coverage vi.mocked(vitestConfigReader.getVitestCoverageThresholds).mockResolvedValue({ lines: 80, functions: 80, branches: 80, statements: 80 }); vi.mocked(vitestConfigReader.checkThresholdsMet).mockReturnValue(false); vi.mocked(vitestConfigReader.getThresholdViolations).mockReturnValue([ "Line coverage (33%) is below threshold (80%)" ]); const mockProcess = new EventEmitter() as ChildProcess; const mockStdout = new EventEmitter(); const mockStderr = new EventEmitter(); mockProcess.stdout = mockStdout as NodeJS.ReadableStream; mockProcess.stderr = mockStderr as NodeJS.ReadableStream; vi.mocked(spawn).mockReturnValue(mockProcess); const coverageOutput = JSON.stringify({ coverageMap: { "/test/project/src/utils.ts": { path: "/test/project/src/utils.ts", statementMap: { "0": { start: { line: 1 } }, "1": { start: { line: 2 } }, "2": { start: { line: 3 } }, }, fnMap: { "0": { name: "testFunc", decl: { start: { line: 1 } } }, }, branchMap: {}, s: { "0": 1, "1": 0, "2": 0 }, // Line 1 covered, lines 2-3 not covered f: { "0": 0 }, // Function not covered b: {}, }, }, }); setTimeout(() => { mockStdout.emit("data", coverageOutput); mockProcess.emit("close", 0); // Vitest doesn't fail the process }, 10); const result = await handleAnalyzeCoverage({ target: "./src/utils.ts", format: "detailed", }); expect(result.success).toBe(true); expect(result.meetsThreshold).toBe(false); expect(result.thresholdViolations).toEqual([ "Line coverage (33%) is below threshold (80%)" ]); // In detailed format, uncovered items would be populated if the file was accessible // The key point is that meetsThreshold correctly reflects the threshold comparison }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/djankies/vitest-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server