Skip to content

Commit 40c8daa

Browse files
committed
test(plugin-doc-coverage): finish remaining tests, add format to coverage
1 parent 3fe6e3b commit 40c8daa

File tree

8 files changed

+259
-60
lines changed

8 files changed

+259
-60
lines changed

packages/plugin-doc-coverage/src/lib/config.unit.test.ts

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,80 @@ import {
55
} from './config.js';
66

77
describe('docCoveragePluginConfigSchema', () => {
8-
it('accepts a valid source glob pattern', () => {
9-
expect(() =>
10-
docCoveragePluginConfigSchema.parse({
11-
sourceGlob: ['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts'],
12-
} satisfies DocCoveragePluginConfig),
13-
).not.toThrow();
8+
describe('sourceGlob', () => {
9+
it('accepts a valid source glob pattern', () => {
10+
expect(() =>
11+
docCoveragePluginConfigSchema.parse({
12+
sourceGlob: ['src/**/*.{ts,tsx}', '!**/*.spec.ts', '!**/*.test.ts'],
13+
} satisfies DocCoveragePluginConfig),
14+
).not.toThrow();
15+
});
16+
17+
it('uses default value for missing sourceGlob', () => {
18+
const result = docCoveragePluginConfigSchema.parse({});
19+
expect(result.sourceGlob).toEqual([
20+
'src/**/*.{ts,tsx}',
21+
'!**/*.spec.ts',
22+
'!**/*.test.ts',
23+
]);
24+
});
25+
26+
it('throws for invalid sourceGlob type', () => {
27+
expect(() =>
28+
docCoveragePluginConfigSchema.parse({
29+
sourceGlob: 123,
30+
}),
31+
).toThrow('Expected array');
32+
});
1433
});
1534

16-
it('not throws for missing sourceGlob', () => {
17-
expect(() => docCoveragePluginConfigSchema.parse({})).not.toThrow();
35+
describe('onlyAudits', () => {
36+
it('accepts valid audit slugs array', () => {
37+
expect(() =>
38+
docCoveragePluginConfigSchema.parse({
39+
onlyAudits: ['functions-coverage', 'classes-coverage'],
40+
sourceGlob: ['src/**/*.ts'],
41+
}),
42+
).not.toThrow();
43+
});
44+
45+
it('accepts empty array for onlyAudits', () => {
46+
expect(() =>
47+
docCoveragePluginConfigSchema.parse({
48+
onlyAudits: [],
49+
sourceGlob: ['src/**/*.ts'],
50+
}),
51+
).not.toThrow();
52+
});
53+
54+
it('allows onlyAudits to be undefined', () => {
55+
const result = docCoveragePluginConfigSchema.parse({});
56+
expect(result.onlyAudits).toBeUndefined();
57+
});
58+
59+
it('throws for invalid onlyAudits type', () => {
60+
expect(() =>
61+
docCoveragePluginConfigSchema.parse({
62+
onlyAudits: 'functions-coverage',
63+
}),
64+
).toThrow('Expected array');
65+
});
66+
67+
it('throws for array with non-string elements', () => {
68+
expect(() =>
69+
docCoveragePluginConfigSchema.parse({
70+
onlyAudits: [123, true],
71+
}),
72+
).toThrow('Expected string');
73+
});
1874
});
1975

20-
it('throws for invalid sourceGlob type', () => {
76+
it('accepts a complete valid configuration', () => {
2177
expect(() =>
2278
docCoveragePluginConfigSchema.parse({
23-
sourceGlob: 123,
24-
}),
25-
).toThrow('Expected string');
79+
sourceGlob: ['src/**/*.ts'],
80+
onlyAudits: ['functions-coverage'],
81+
} satisfies DocCoveragePluginConfig),
82+
).not.toThrow();
2683
});
2784
});

packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import {
1010
filterGroupsByOnlyAudits,
1111
} from './utils.js';
1212

13-
const PLUGIN_TITLE = 'Documentation coverage';
13+
export const PLUGIN_TITLE = 'Documentation coverage';
1414

15-
const PLUGIN_DESCRIPTION =
15+
export const PLUGIN_DESCRIPTION =
1616
'Official Code PushUp documentation coverage plugin.';
1717

18-
const PLUGIN_DOCS_URL =
18+
export const PLUGIN_DOCS_URL =
1919
'https://www.npmjs.com/package/@code-pushup/doc-coverage-plugin/';
2020

2121
/**

packages/plugin-doc-coverage/src/lib/doc-coverage-plugin.unit.test.ts

Lines changed: 14 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { describe, expect, it } from 'vitest';
22
import type { RunnerConfig } from '@code-pushup/models';
3-
import { docCoveragePlugin } from './doc-coverage-plugin.js';
3+
import { PLUGIN_SLUG } from './constants.js';
4+
import {
5+
PLUGIN_DESCRIPTION,
6+
PLUGIN_DOCS_URL,
7+
PLUGIN_TITLE,
8+
docCoveragePlugin,
9+
} from './doc-coverage-plugin.js';
410

511
vi.mock('./runner/index.ts', () => ({
612
createRunnerConfig: vi.fn().mockReturnValue({
@@ -17,46 +23,14 @@ describe('docCoveragePlugin', () => {
1723
}),
1824
).resolves.toStrictEqual(
1925
expect.objectContaining({
20-
slug: 'doc-coverage',
21-
title: 'Documentation coverage',
22-
audits: expect.any(Array),
23-
runner: expect.any(Object),
24-
}),
25-
);
26-
});
27-
28-
it('should generate percentage coverage audit', async () => {
29-
await expect(
30-
docCoveragePlugin({
31-
sourceGlob: ['src/**/*.ts', '!**/*.spec.ts', '!**/*.test.ts'],
32-
}),
33-
).resolves.toStrictEqual(
34-
expect.objectContaining({
35-
audits: [
36-
{
37-
slug: 'percentage-coverage',
38-
title: 'Percentage of codebase with documentation',
39-
description: expect.stringContaining(
40-
'how many % of the codebase have documentation',
41-
),
42-
},
43-
],
44-
}),
45-
);
46-
});
47-
48-
it('should include package metadata', async () => {
49-
await expect(
50-
docCoveragePlugin({
51-
sourceGlob: ['src/**/*.ts', '!**/*.spec.ts', '!**/*.test.ts'],
52-
}),
53-
).resolves.toStrictEqual(
54-
expect.objectContaining({
26+
slug: PLUGIN_SLUG,
27+
title: PLUGIN_TITLE,
5528
icon: 'folder-src',
56-
description: expect.stringContaining('documentation coverage plugin'),
57-
docsUrl: expect.stringContaining('npmjs.com'),
58-
packageName: expect.any(String),
59-
version: expect.any(String),
29+
description: PLUGIN_DESCRIPTION,
30+
docsUrl: PLUGIN_DOCS_URL,
31+
groups: expect.any(Array),
32+
audits: expect.any(Array),
33+
runner: expect.any(Function),
6034
}),
6135
);
6236
});

packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.integration.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ exports[`processDocCoverage > should succesfully get the right number of ts file
1313
"nodesCount": 0,
1414
},
1515
"functions": {
16-
"coverage": 33.333333333333336,
16+
"coverage": 33.33,
1717
"issues": [
1818
{
1919
"file": "/home/alejandro/dev/code-pushup-cli/packages/plugin-doc-coverage/mocks/fixtures/angular/app.component.spec.ts",

packages/plugin-doc-coverage/src/lib/runner/__snapshots__/doc-processer.unit.test.ts.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
exports[`getUnprocessedCoverageReport > should produce a full report 1`] = `
44
{
55
"classes": {
6-
"coverage": 33.333333333333336,
6+
"coverage": 33.33,
77
"issues": [
88
{
99
"file": "test.ts",
@@ -21,7 +21,7 @@ exports[`getUnprocessedCoverageReport > should produce a full report 1`] = `
2121
"nodesCount": 3,
2222
},
2323
"enums": {
24-
"coverage": 33.333333333333336,
24+
"coverage": 33.33,
2525
"issues": [
2626
{
2727
"file": "test.ts",
@@ -44,7 +44,7 @@ exports[`getUnprocessedCoverageReport > should produce a full report 1`] = `
4444
"nodesCount": 3,
4545
},
4646
"interfaces": {
47-
"coverage": 66.66666666666667,
47+
"coverage": 66.67,
4848
"issues": [
4949
{
5050
"file": "test.ts",

packages/plugin-doc-coverage/src/lib/runner/utils.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ export function calculateCoverage(result: UnprocessedCoverageResult) {
2828
coverage:
2929
value.nodesCount === 0
3030
? 100
31-
: (1 - value.issues.length / value.nodesCount) * 100,
31+
: Number(
32+
((1 - value.issues.length / value.nodesCount) * 100).toFixed(
33+
2,
34+
),
35+
),
3236
issues: value.issues,
3337
nodesCount: value.nodesCount,
3438
},
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { SyntaxKind } from 'ts-morph';
2+
import type { UnprocessedCoverageResult } from './models';
3+
import {
4+
calculateCoverage,
5+
createEmptyUnprocessedCoverageReport,
6+
getCoverageTypeFromKind,
7+
} from './utils';
8+
9+
describe('createEmptyUnprocessedCoverageReport', () => {
10+
it('should create an empty report with all categories initialized', () => {
11+
const result = createEmptyUnprocessedCoverageReport();
12+
13+
expect(result).toStrictEqual({
14+
enums: { nodesCount: 0, issues: [] },
15+
interfaces: { nodesCount: 0, issues: [] },
16+
types: { nodesCount: 0, issues: [] },
17+
functions: { nodesCount: 0, issues: [] },
18+
variables: { nodesCount: 0, issues: [] },
19+
classes: { nodesCount: 0, issues: [] },
20+
methods: { nodesCount: 0, issues: [] },
21+
properties: { nodesCount: 0, issues: [] },
22+
});
23+
});
24+
});
25+
26+
describe('calculateCoverage', () => {
27+
it('should calculate 100% coverage when there are no nodes', () => {
28+
const input: UnprocessedCoverageResult =
29+
createEmptyUnprocessedCoverageReport();
30+
const result = calculateCoverage(input);
31+
32+
Object.values(result).forEach(category => {
33+
expect(category.coverage).toBe(100);
34+
expect(category.nodesCount).toBe(0);
35+
expect(category.issues).toEqual([]);
36+
});
37+
});
38+
39+
it('should calculate correct coverage percentage with issues', () => {
40+
const input: UnprocessedCoverageResult = {
41+
...createEmptyUnprocessedCoverageReport(),
42+
functions: {
43+
nodesCount: 4,
44+
issues: [
45+
{ type: 'functions', line: 1, file: 'test.ts', name: 'fn1' },
46+
{ type: 'functions', line: 2, file: 'test.ts', name: 'fn2' },
47+
],
48+
},
49+
classes: {
50+
nodesCount: 4,
51+
issues: [
52+
{ type: 'classes', line: 1, file: 'test.ts', name: 'Class1' },
53+
{ type: 'classes', line: 2, file: 'test.ts', name: 'Class2' },
54+
{ type: 'classes', line: 3, file: 'test.ts', name: 'Class3' },
55+
],
56+
},
57+
};
58+
59+
const result = calculateCoverage(input);
60+
61+
expect(result.functions.coverage).toBe(50);
62+
expect(result.classes.coverage).toBe(25);
63+
});
64+
});
65+
66+
describe('getCoverageTypeFromKind', () => {
67+
it.each([
68+
[SyntaxKind.ClassDeclaration, 'classes'],
69+
[SyntaxKind.MethodDeclaration, 'methods'],
70+
[SyntaxKind.FunctionDeclaration, 'functions'],
71+
[SyntaxKind.InterfaceDeclaration, 'interfaces'],
72+
[SyntaxKind.EnumDeclaration, 'enums'],
73+
[SyntaxKind.VariableDeclaration, 'variables'],
74+
[SyntaxKind.PropertyDeclaration, 'properties'],
75+
[SyntaxKind.TypeAliasDeclaration, 'types'],
76+
])('should return %s for SyntaxKind.%s', (kind, expectedType) => {
77+
expect(getCoverageTypeFromKind(kind)).toBe(expectedType);
78+
});
79+
80+
it('should throw error for unsupported syntax kind', () => {
81+
expect(() => getCoverageTypeFromKind(SyntaxKind.Unknown)).toThrow(
82+
'Unsupported syntax kind',
83+
);
84+
});
85+
});
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import type { Group } from '@code-pushup/models';
2+
import { AUDITS_MAP } from './constants';
3+
import { filterAuditsByPluginConfig, filterGroupsByOnlyAudits } from './utils';
4+
5+
describe('filterAuditsByPluginConfig', () => {
6+
it('should return all audits when onlyAudits is not provided', () => {
7+
const result = filterAuditsByPluginConfig({});
8+
expect(result).toStrictEqual(Object.values(AUDITS_MAP));
9+
});
10+
11+
it('should return all audits when onlyAudits is empty array', () => {
12+
const result = filterAuditsByPluginConfig({ onlyAudits: [] });
13+
expect(result).toStrictEqual(Object.values(AUDITS_MAP));
14+
});
15+
16+
it('should return only specified audits when onlyAudits is provided', () => {
17+
const onlyAudits = ['functions-coverage', 'classes-coverage'];
18+
const result = filterAuditsByPluginConfig({ onlyAudits });
19+
20+
expect(result).toStrictEqual(
21+
Object.values(AUDITS_MAP).filter(audit =>
22+
onlyAudits.includes(audit.slug),
23+
),
24+
);
25+
});
26+
});
27+
28+
describe('filterGroupsByOnlyAudits', () => {
29+
const mockGroups: Group[] = [
30+
{
31+
title: 'Group 1',
32+
slug: 'group-1',
33+
refs: [
34+
{ slug: 'functions-coverage', weight: 1 },
35+
{ slug: 'classes-coverage', weight: 1 },
36+
],
37+
},
38+
{
39+
title: 'Group 2',
40+
slug: 'group-2',
41+
refs: [
42+
{ slug: 'types-coverage', weight: 1 },
43+
{ slug: 'interfaces-coverage', weight: 1 },
44+
],
45+
},
46+
];
47+
48+
it('should return all groups when onlyAudits is not provided', () => {
49+
const result = filterGroupsByOnlyAudits(mockGroups, {});
50+
expect(result).toStrictEqual(mockGroups);
51+
});
52+
53+
it('should return all groups when onlyAudits is empty array', () => {
54+
const result = filterGroupsByOnlyAudits(mockGroups, { onlyAudits: [] });
55+
expect(result).toStrictEqual(mockGroups);
56+
});
57+
58+
it('should filter groups based on specified audits', () => {
59+
const result = filterGroupsByOnlyAudits(mockGroups, {
60+
onlyAudits: ['functions-coverage'],
61+
});
62+
63+
expect(result).toStrictEqual([
64+
{
65+
title: 'Group 1',
66+
slug: 'group-1',
67+
refs: [{ slug: 'functions-coverage', weight: 1 }],
68+
},
69+
]);
70+
});
71+
72+
it('should remove groups with no matching refs', () => {
73+
const result = filterGroupsByOnlyAudits(mockGroups, {
74+
onlyAudits: ['enums-coverage'],
75+
});
76+
77+
expect(result).toStrictEqual([]);
78+
});
79+
});

0 commit comments

Comments
 (0)