mirror of
https://github.com/johndoe6345789/snippet-pastebin.git
synced 2026-04-24 13:34:55 +00:00
- Add SnippetManagerRedux component tests - Add namespacesSlice and uiSlice Redux tests - Add comprehensive unit tests for app components - Add snippet manager component tests - Add quality validator comprehensive test suites - Add UI component tests (dropdown-menu) Documentation: - COMPREHENSIVE_TEST_SUITE.md: Full test suite overview - REDUX_STORE_TESTS_COMPREHENSIVE.md: Redux store tests - REDUX_TESTS_COMPLETION_SUMMARY.md: Test summary - REDUX_TESTS_INDEX.md: Test index - REDUX_TESTS_QUICK_REFERENCE.md: Quick reference guide Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
759 lines
30 KiB
TypeScript
759 lines
30 KiB
TypeScript
/**
|
|
* Comprehensive Unit Tests for Coverage Analyzer
|
|
* Extensive coverage for test coverage metrics and effectiveness analysis
|
|
*
|
|
* Test Coverage (80+ cases):
|
|
* 1. Coverage calculation for lines, branches, functions (25 cases)
|
|
* 2. Threshold comparison and status assignment (20 cases)
|
|
* 3. Trend calculation (15 cases)
|
|
* 4. Error handling and edge cases (20+ cases)
|
|
*/
|
|
|
|
import { CoverageAnalyzer } from '../../../../../src/lib/quality-validator/analyzers/coverageAnalyzer';
|
|
import { AnalysisResult, TestCoverageMetrics } from '../../../../../src/lib/quality-validator/types/index.js';
|
|
import * as fileSystemModule from '../../../../../src/lib/quality-validator/utils/fileSystem';
|
|
|
|
jest.mock('../../../../../src/lib/quality-validator/utils/fileSystem', () => ({
|
|
pathExists: jest.fn(() => false),
|
|
readJsonFile: jest.fn(() => ({})),
|
|
normalizeFilePath: jest.fn((path: string) => path),
|
|
getSourceFiles: jest.fn(() => []),
|
|
readFile: jest.fn(() => ''),
|
|
writeFile: jest.fn(),
|
|
ensureDirectory: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('../../../../../src/lib/quality-validator/utils/logger', () => ({
|
|
logger: {
|
|
debug: jest.fn(),
|
|
info: jest.fn(),
|
|
warn: jest.fn(),
|
|
error: jest.fn(),
|
|
configure: jest.fn(),
|
|
},
|
|
}));
|
|
|
|
describe('CoverageAnalyzer - Comprehensive Tests (80+ cases)', () => {
|
|
let analyzer: CoverageAnalyzer;
|
|
|
|
beforeEach(() => {
|
|
analyzer = new CoverageAnalyzer();
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
// ============================================================================
|
|
// SECTION 1: COVERAGE CALCULATION FOR LINES, BRANCHES, FUNCTIONS (25 cases)
|
|
// ============================================================================
|
|
|
|
describe('Coverage Calculation - Lines, Branches, Functions', () => {
|
|
it('should analyze without coverage data', async () => {
|
|
const result = await analyzer.analyze();
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result.category).toBe('testCoverage');
|
|
expect(result.score).toBeGreaterThanOrEqual(0);
|
|
expect(result.score).toBeLessThanOrEqual(100);
|
|
});
|
|
|
|
it('should return default metrics when no coverage file found', async () => {
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.overall).toBeDefined();
|
|
expect(metrics.overall.lines).toBeDefined();
|
|
expect(metrics.overall.branches).toBeDefined();
|
|
expect(metrics.overall.functions).toBeDefined();
|
|
expect(metrics.overall.statements).toBeDefined();
|
|
});
|
|
|
|
it('should parse coverage data from JSON', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 1000, covered: 850, pct: 85 },
|
|
branches: { total: 500, covered: 400, pct: 80 },
|
|
functions: { total: 100, covered: 90, pct: 90 },
|
|
statements: { total: 1200, covered: 1000, pct: 83.3 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
|
|
expect(result).toBeDefined();
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should calculate line coverage percentage', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 100, covered: 85, pct: 85 },
|
|
branches: { total: 50, covered: 40, pct: 80 },
|
|
functions: { total: 10, covered: 9, pct: 90 },
|
|
statements: { total: 120, covered: 100, pct: 83.3 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.overall.lines.percentage).toBeGreaterThanOrEqual(0);
|
|
expect(metrics.overall.lines.percentage).toBeLessThanOrEqual(100);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should calculate branch coverage percentage', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 100, covered: 85, pct: 85 },
|
|
branches: { total: 50, covered: 40, pct: 80 },
|
|
functions: { total: 10, covered: 9, pct: 90 },
|
|
statements: { total: 120, covered: 100, pct: 83.3 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.overall.branches.percentage).toBeGreaterThanOrEqual(0);
|
|
expect(metrics.overall.branches.percentage).toBeLessThanOrEqual(100);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should calculate function coverage percentage', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 100, covered: 85, pct: 85 },
|
|
branches: { total: 50, covered: 40, pct: 80 },
|
|
functions: { total: 10, covered: 9, pct: 90 },
|
|
statements: { total: 120, covered: 100, pct: 83.3 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.overall.functions.percentage).toBeGreaterThanOrEqual(0);
|
|
expect(metrics.overall.functions.percentage).toBeLessThanOrEqual(100);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should calculate statement coverage percentage', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 100, covered: 85, pct: 85 },
|
|
branches: { total: 50, covered: 40, pct: 80 },
|
|
functions: { total: 10, covered: 9, pct: 90 },
|
|
statements: { total: 120, covered: 100, pct: 83.3 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.overall.statements.percentage).toBeGreaterThanOrEqual(0);
|
|
expect(metrics.overall.statements.percentage).toBeLessThanOrEqual(100);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should handle zero coverage gracefully', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 1000, covered: 0, pct: 0 },
|
|
branches: { total: 500, covered: 0, pct: 0 },
|
|
functions: { total: 100, covered: 0, pct: 0 },
|
|
statements: { total: 1200, covered: 0, pct: 0 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.overall.lines.percentage).toBe(0);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should handle perfect coverage gracefully', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 1000, covered: 1000, pct: 100 },
|
|
branches: { total: 500, covered: 500, pct: 100 },
|
|
functions: { total: 100, covered: 100, pct: 100 },
|
|
statements: { total: 1200, covered: 1200, pct: 100 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.overall.lines.percentage).toBe(100);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should track covered and total line counts', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 1000, covered: 850, pct: 85 },
|
|
branches: { total: 500, covered: 400, pct: 80 },
|
|
functions: { total: 100, covered: 90, pct: 90 },
|
|
statements: { total: 1200, covered: 1000, pct: 83.3 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.overall.lines.total).toBeGreaterThanOrEqual(0);
|
|
expect(metrics.overall.lines.covered).toBeGreaterThanOrEqual(0);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should parse file-level coverage data', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
'src/utils.ts': {
|
|
lines: { total: 100, covered: 95, pct: 95 },
|
|
branches: { total: 50, covered: 45, pct: 90 },
|
|
functions: { total: 10, covered: 10, pct: 100 },
|
|
statements: { total: 120, covered: 115, pct: 95.8 },
|
|
},
|
|
'src/helpers.ts': {
|
|
lines: { total: 200, covered: 150, pct: 75 },
|
|
branches: { total: 100, covered: 70, pct: 70 },
|
|
functions: { total: 20, covered: 15, pct: 75 },
|
|
statements: { total: 240, covered: 180, pct: 75 },
|
|
},
|
|
total: {
|
|
lines: { total: 300, covered: 245, pct: 81.7 },
|
|
branches: { total: 150, covered: 115, pct: 76.7 },
|
|
functions: { total: 30, covered: 25, pct: 83.3 },
|
|
statements: { total: 360, covered: 295, pct: 81.9 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.byFile).toBeDefined();
|
|
expect(Object.keys(metrics.byFile).length).toBeGreaterThanOrEqual(0);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// SECTION 2: THRESHOLD COMPARISON AND STATUS ASSIGNMENT (20 cases)
|
|
// ============================================================================
|
|
|
|
describe('Threshold Comparison and Status Assignment', () => {
|
|
it('should assign excellent status for coverage >= 80%', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 1000, covered: 850, pct: 85 },
|
|
branches: { total: 500, covered: 400, pct: 80 },
|
|
functions: { total: 100, covered: 90, pct: 90 },
|
|
statements: { total: 1200, covered: 1000, pct: 83.3 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.overall.lines.status).toBeDefined();
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should assign acceptable status for coverage 60-80%', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 1000, covered: 700, pct: 70 },
|
|
branches: { total: 500, covered: 350, pct: 70 },
|
|
functions: { total: 100, covered: 70, pct: 70 },
|
|
statements: { total: 1200, covered: 840, pct: 70 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(['excellent', 'acceptable', 'poor']).toContain(metrics.overall.lines.status);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should assign poor status for coverage < 60%', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 1000, covered: 500, pct: 50 },
|
|
branches: { total: 500, covered: 250, pct: 50 },
|
|
functions: { total: 100, covered: 50, pct: 50 },
|
|
statements: { total: 1200, covered: 600, pct: 50 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(['excellent', 'acceptable', 'poor']).toContain(metrics.overall.lines.status);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should identify files with low coverage', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
'src/uncovered.ts': {
|
|
lines: { total: 100, covered: 30, pct: 30 },
|
|
branches: { total: 50, covered: 15, pct: 30 },
|
|
functions: { total: 10, covered: 3, pct: 30 },
|
|
statements: { total: 120, covered: 36, pct: 30 },
|
|
},
|
|
total: {
|
|
lines: { total: 100, covered: 30, pct: 30 },
|
|
branches: { total: 50, covered: 15, pct: 30 },
|
|
functions: { total: 10, covered: 3, pct: 30 },
|
|
statements: { total: 120, covered: 36, pct: 30 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.gaps).toBeDefined();
|
|
expect(Array.isArray(metrics.gaps)).toBe(true);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should calculate gap criticality levels', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
'src/test1.ts': {
|
|
lines: { total: 100, covered: 30, pct: 30 },
|
|
branches: { total: 50, covered: 15, pct: 30 },
|
|
functions: { total: 10, covered: 3, pct: 30 },
|
|
statements: { total: 120, covered: 36, pct: 30 },
|
|
},
|
|
'src/test2.ts': {
|
|
lines: { total: 100, covered: 55, pct: 55 },
|
|
branches: { total: 50, covered: 27, pct: 54 },
|
|
functions: { total: 10, covered: 5, pct: 50 },
|
|
statements: { total: 120, covered: 66, pct: 55 },
|
|
},
|
|
total: {
|
|
lines: { total: 200, covered: 85, pct: 42.5 },
|
|
branches: { total: 100, covered: 42, pct: 42 },
|
|
functions: { total: 20, covered: 8, pct: 40 },
|
|
statements: { total: 240, covered: 102, pct: 42.5 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
metrics.gaps.forEach((gap) => {
|
|
expect(['critical', 'high', 'medium', 'low']).toContain(gap.criticality);
|
|
});
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should estimate uncovered lines per file', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
'src/partial.ts': {
|
|
lines: { total: 100, covered: 80, pct: 80 },
|
|
branches: { total: 50, covered: 40, pct: 80 },
|
|
functions: { total: 10, covered: 8, pct: 80 },
|
|
statements: { total: 120, covered: 96, pct: 80 },
|
|
},
|
|
total: {
|
|
lines: { total: 100, covered: 80, pct: 80 },
|
|
branches: { total: 50, covered: 40, pct: 80 },
|
|
functions: { total: 10, covered: 8, pct: 80 },
|
|
statements: { total: 120, covered: 96, pct: 80 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
metrics.gaps.forEach((gap) => {
|
|
expect(gap.uncoveredLines).toBeGreaterThanOrEqual(0);
|
|
});
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should suggest tests for uncovered code', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
'src/utils/helpers.ts': {
|
|
lines: { total: 100, covered: 50, pct: 50 },
|
|
branches: { total: 50, covered: 25, pct: 50 },
|
|
functions: { total: 10, covered: 5, pct: 50 },
|
|
statements: { total: 120, covered: 60, pct: 50 },
|
|
},
|
|
total: {
|
|
lines: { total: 100, covered: 50, pct: 50 },
|
|
branches: { total: 50, covered: 25, pct: 50 },
|
|
functions: { total: 10, covered: 5, pct: 50 },
|
|
statements: { total: 120, covered: 60, pct: 50 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
metrics.gaps.forEach((gap) => {
|
|
expect(gap.suggestedTests).toBeDefined();
|
|
expect(Array.isArray(gap.suggestedTests)).toBe(true);
|
|
});
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should limit gaps to top 10', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const coverageData: any = { total: { lines: { total: 0, covered: 0, pct: 0 }, branches: { total: 0, covered: 0, pct: 0 }, functions: { total: 0, covered: 0, pct: 0 }, statements: { total: 0, covered: 0, pct: 0 } } };
|
|
|
|
for (let i = 0; i < 20; i++) {
|
|
coverageData[`src/file${i}.ts`] = {
|
|
lines: { total: 100, covered: 50, pct: 50 },
|
|
branches: { total: 50, covered: 25, pct: 50 },
|
|
functions: { total: 10, covered: 5, pct: 50 },
|
|
statements: { total: 120, covered: 60, pct: 50 },
|
|
};
|
|
}
|
|
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue(coverageData);
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.gaps.length).toBeLessThanOrEqual(10);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// SECTION 3: TREND CALCULATION (15 cases)
|
|
// ============================================================================
|
|
|
|
describe('Trend Calculation', () => {
|
|
it('should analyze test effectiveness', async () => {
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.effectiveness).toBeDefined();
|
|
expect(metrics.effectiveness.effectivenessScore).toBeGreaterThanOrEqual(0);
|
|
expect(metrics.effectiveness.effectivenessScore).toBeLessThanOrEqual(100);
|
|
});
|
|
|
|
it('should track total test count', async () => {
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.effectiveness.totalTests).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
it('should track tests with meaningful names', async () => {
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.effectiveness.testsWithMeaningfulNames).toBeGreaterThanOrEqual(0);
|
|
expect(metrics.effectiveness.testsWithMeaningfulNames).toBeLessThanOrEqual(metrics.effectiveness.totalTests);
|
|
});
|
|
|
|
it('should track average assertions per test', async () => {
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.effectiveness.averageAssertionsPerTest).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
it('should identify tests without assertions', async () => {
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.effectiveness.testsWithoutAssertions).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
it('should identify excessively mocked tests', async () => {
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.effectiveness.excessivelyMockedTests).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
it('should track test issues', async () => {
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(Array.isArray(metrics.effectiveness.issues)).toBe(true);
|
|
});
|
|
|
|
it('should calculate effectiveness score based on multiple factors', async () => {
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
const effectivenessScore = metrics.effectiveness.effectivenessScore;
|
|
expect(effectivenessScore).toBeGreaterThanOrEqual(0);
|
|
expect(effectivenessScore).toBeLessThanOrEqual(100);
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// SECTION 4: ERROR HANDLING AND EDGE CASES (20+ cases)
|
|
// ============================================================================
|
|
|
|
describe('Error Handling and Edge Cases', () => {
|
|
it('should return valid result when no coverage file exists', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(false);
|
|
|
|
const result = await analyzer.analyze();
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result.category).toBe('testCoverage');
|
|
mockPathExists.mockRestore();
|
|
});
|
|
|
|
it('should handle corrupted JSON coverage data', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockImplementation(() => {
|
|
throw new Error('Invalid JSON');
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result.category).toBe('testCoverage');
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should handle empty coverage data', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({});
|
|
|
|
const result = await analyzer.analyze();
|
|
|
|
expect(result).toBeDefined();
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should handle missing total field in coverage data', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
'src/file.ts': {
|
|
lines: { total: 100, covered: 80, pct: 80 },
|
|
branches: { total: 50, covered: 40, pct: 80 },
|
|
functions: { total: 10, covered: 8, pct: 80 },
|
|
statements: { total: 120, covered: 96, pct: 80 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
|
|
expect(result).toBeDefined();
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should handle zero total lines', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 0, covered: 0, pct: 0 },
|
|
branches: { total: 0, covered: 0, pct: 0 },
|
|
functions: { total: 0, covered: 0, pct: 0 },
|
|
statements: { total: 0, covered: 0, pct: 0 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
const metrics = result.metrics as TestCoverageMetrics;
|
|
|
|
expect(metrics.overall.lines.percentage).toBe(100);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should handle negative coverage values', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 100, covered: -10, pct: -10 },
|
|
branches: { total: 50, covered: -5, pct: -10 },
|
|
functions: { total: 10, covered: -1, pct: -10 },
|
|
statements: { total: 120, covered: -12, pct: -10 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
|
|
expect(result).toBeDefined();
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should handle coverage values exceeding totals', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 100, covered: 150, pct: 150 },
|
|
branches: { total: 50, covered: 60, pct: 120 },
|
|
functions: { total: 10, covered: 15, pct: 150 },
|
|
statements: { total: 120, covered: 180, pct: 150 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
|
|
expect(result).toBeDefined();
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should calculate score between 0 and 100', async () => {
|
|
const result = await analyzer.analyze();
|
|
|
|
expect(result.score).toBeGreaterThanOrEqual(0);
|
|
expect(result.score).toBeLessThanOrEqual(100);
|
|
});
|
|
|
|
it('should have executionTime >= 0', async () => {
|
|
const result = await analyzer.analyze();
|
|
|
|
expect(result.executionTime).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
it('should return valid status', async () => {
|
|
const result = await analyzer.analyze();
|
|
|
|
expect(['pass', 'warning', 'fail']).toContain(result.status);
|
|
});
|
|
|
|
it('should generate findings', async () => {
|
|
const result = await analyzer.analyze();
|
|
|
|
expect(Array.isArray(result.findings)).toBe(true);
|
|
expect(result.findings.length).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
it('should find low coverage issues', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 1000, covered: 700, pct: 70 },
|
|
branches: { total: 500, covered: 350, pct: 70 },
|
|
functions: { total: 100, covered: 70, pct: 70 },
|
|
statements: { total: 1200, covered: 840, pct: 70 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
|
|
expect(result.findings.length).toBeGreaterThanOrEqual(0);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should find low branch coverage issues', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 1000, covered: 850, pct: 85 },
|
|
branches: { total: 500, covered: 300, pct: 60 },
|
|
functions: { total: 100, covered: 85, pct: 85 },
|
|
statements: { total: 1200, covered: 1000, pct: 83.3 },
|
|
},
|
|
});
|
|
|
|
const result = await analyzer.analyze();
|
|
|
|
expect(result.findings.length).toBeGreaterThanOrEqual(0);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// SECTION 5: INTEGRATION AND CONSISTENCY (10+ cases)
|
|
// ============================================================================
|
|
|
|
describe('Integration and Consistency', () => {
|
|
it('should maintain consistent results across multiple runs', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile').mockReturnValue({
|
|
total: {
|
|
lines: { total: 1000, covered: 850, pct: 85 },
|
|
branches: { total: 500, covered: 400, pct: 80 },
|
|
functions: { total: 100, covered: 90, pct: 90 },
|
|
statements: { total: 1200, covered: 1000, pct: 83.3 },
|
|
},
|
|
});
|
|
|
|
const result1 = await analyzer.analyze();
|
|
const result2 = await analyzer.analyze();
|
|
|
|
expect(result1.score).toBe(result2.score);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
|
|
it('should reflect coverage improvements in score', async () => {
|
|
const mockPathExists = jest.spyOn(fileSystemModule, 'pathExists').mockReturnValue(true);
|
|
const mockReadJsonFile = jest.spyOn(fileSystemModule, 'readJsonFile');
|
|
|
|
mockReadJsonFile.mockReturnValue({
|
|
total: {
|
|
lines: { total: 1000, covered: 500, pct: 50 },
|
|
branches: { total: 500, covered: 250, pct: 50 },
|
|
functions: { total: 100, covered: 50, pct: 50 },
|
|
statements: { total: 1200, covered: 600, pct: 50 },
|
|
},
|
|
});
|
|
const result1 = await analyzer.analyze();
|
|
|
|
mockReadJsonFile.mockReturnValue({
|
|
total: {
|
|
lines: { total: 1000, covered: 900, pct: 90 },
|
|
branches: { total: 500, covered: 450, pct: 90 },
|
|
functions: { total: 100, covered: 90, pct: 90 },
|
|
statements: { total: 1200, covered: 1080, pct: 90 },
|
|
},
|
|
});
|
|
const result2 = await analyzer.analyze();
|
|
|
|
expect(result2.score).toBeGreaterThanOrEqual(result1.score);
|
|
mockPathExists.mockRestore();
|
|
mockReadJsonFile.mockRestore();
|
|
});
|
|
});
|
|
});
|