Skip to content

Commit 1118f36

Browse files
authored
coverage: provide overall statistics about coverage (#2184)
1 parent db38f97 commit 1118f36

File tree

8 files changed

+217
-35
lines changed

8 files changed

+217
-35
lines changed

Diff for: .changeset/neat-pillows-eat.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-inspector/coverage-command': patch
3+
---
4+
5+
coverage: overall statistics about coverage provided

Diff for: assets/coverage.jpg

18.5 KB
Loading

Diff for: example/coverage.json

+87-32
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,126 @@
11
{
22
"sources": [
33
{
4-
"body": "query post {\n post {\n ... on Post {\n id\n title\n }\n createdAtSomePoint\n }\n}\n",
5-
"name": "./documents/post.graphql",
4+
"body": "query depthPost {\n post {\n id\n title @client\n author {\n id\n name\n }\n }\n}",
5+
"name": "/home/velias/Devel/projects/github/graphql-inspector/example/documents/depth-post.graphql",
6+
"locationOffset": {
7+
"line": 1,
8+
"column": 1
9+
}
10+
},
11+
{
12+
"body": "query post {\n post {\n ... on Post {\n id\n title\n }\n createdAtSomePoint\n }\n}",
13+
"name": "/home/velias/Devel/projects/github/graphql-inspector/example/documents/post.graphql",
614
"locationOffset": {
715
"line": 1,
816
"column": 1
917
}
1018
}
1119
],
1220
"types": {
13-
"Query": {
14-
"hits": 1,
15-
"type": "Query",
16-
"children": {
17-
"post": {
18-
"hits": 1,
19-
"locations": {
20-
"./documents/post.graphql": [
21-
{
22-
"start": 15,
23-
"end": 93
24-
}
25-
]
26-
}
27-
},
28-
"posts": {
29-
"hits": 0,
30-
"locations": {}
31-
}
32-
}
33-
},
3421
"Post": {
35-
"hits": 2,
22+
"hits": 4,
23+
"fieldsCount": 4,
24+
"fieldsCountCovered": 2,
3625
"type": "Post",
3726
"children": {
3827
"id": {
39-
"hits": 1,
28+
"hits": 2,
29+
"fieldsCount": 0,
30+
"fieldsCountCovered": 0,
4031
"locations": {
41-
"./documents/post.graphql": [
32+
"/home/velias/Devel/projects/github/graphql-inspector/example/documents/depth-post.graphql": [
33+
{
34+
"start": 31,
35+
"end": 33
36+
}
37+
],
38+
"/home/velias/Devel/projects/github/graphql-inspector/example/documents/post.graphql": [
4239
{
4340
"start": 46,
4441
"end": 48
4542
}
4643
]
47-
}
44+
},
45+
"children": {}
4846
},
4947
"title": {
50-
"hits": 1,
48+
"hits": 2,
49+
"fieldsCount": 0,
50+
"fieldsCountCovered": 0,
5151
"locations": {
52-
"./documents/post.graphql": [
52+
"/home/velias/Devel/projects/github/graphql-inspector/example/documents/depth-post.graphql": [
53+
{
54+
"start": 38,
55+
"end": 51
56+
}
57+
],
58+
"/home/velias/Devel/projects/github/graphql-inspector/example/documents/post.graphql": [
5359
{
5460
"start": 55,
5561
"end": 60
5662
}
5763
]
58-
}
64+
},
65+
"children": {}
5966
},
6067
"createdAt": {
6168
"hits": 0,
62-
"locations": {}
69+
"fieldsCount": 0,
70+
"fieldsCountCovered": 0,
71+
"locations": {},
72+
"children": {}
6373
},
6474
"modifiedAt": {
6575
"hits": 0,
66-
"locations": {}
76+
"fieldsCount": 0,
77+
"fieldsCountCovered": 0,
78+
"locations": {},
79+
"children": {}
80+
}
81+
}
82+
},
83+
"Query": {
84+
"hits": 2,
85+
"fieldsCount": 2,
86+
"fieldsCountCovered": 1,
87+
"type": "Query",
88+
"children": {
89+
"post": {
90+
"hits": 2,
91+
"fieldsCount": 0,
92+
"fieldsCountCovered": 0,
93+
"locations": {
94+
"/home/velias/Devel/projects/github/graphql-inspector/example/documents/depth-post.graphql": [
95+
{
96+
"start": 20,
97+
"end": 94
98+
}
99+
],
100+
"/home/velias/Devel/projects/github/graphql-inspector/example/documents/post.graphql": [
101+
{
102+
"start": 15,
103+
"end": 93
104+
}
105+
]
106+
},
107+
"children": {}
108+
},
109+
"posts": {
110+
"hits": 0,
111+
"fieldsCount": 0,
112+
"fieldsCountCovered": 0,
113+
"locations": {},
114+
"children": {}
67115
}
68116
}
69117
}
118+
},
119+
"stats": {
120+
"numTypes": 2,
121+
"numTypesCoveredFully": 0,
122+
"numTypesCovered": 2,
123+
"numFields": 6,
124+
"numFiledsCovered": 3
70125
}
71126
}

Diff for: packages/commands/coverage/src/index.ts

+23
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,29 @@ function renderCoverage(coverage: SchemaCoverage) {
162162
Logger.log(chalk.grey('}\n'));
163163
}
164164
}
165+
166+
Logger.log(
167+
`Types covered: ${
168+
coverage.stats.numTypes > 0
169+
? ((coverage.stats.numTypesCovered / coverage.stats.numTypes) * 100).toFixed(1)
170+
: 'N/A'
171+
}%`,
172+
);
173+
Logger.log(
174+
`Types covered fully: ${
175+
coverage.stats.numTypes > 0
176+
? ((coverage.stats.numTypesCoveredFully / coverage.stats.numTypes) * 100).toFixed(1)
177+
: 'N/A'
178+
}%`,
179+
);
180+
Logger.log(
181+
`Fields covered: ${
182+
coverage.stats.numFields > 0
183+
? ((coverage.stats.numFiledsCovered / coverage.stats.numFields) * 100).toFixed(1)
184+
: 'N/A'
185+
}%`,
186+
);
187+
Logger.log(``);
165188
}
166189

167190
function indent(line: string, space: number): string {

Diff for: packages/core/__tests__/coverage/coverage.ts

+43-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('coverage', () => {
1818
type Query {
1919
post: Post
2020
posts: [Post!]
21-
objectById(id: ID!): Identifiable
21+
objectById(id: ID!, unused: String): Identifiable
2222
}
2323
2424
type Mutation {
@@ -66,6 +66,7 @@ describe('coverage', () => {
6666
expect(results.types.Query.children.post.hits).toEqual(1);
6767
expect(results.types.Query.children.objectById.hits).toEqual(1);
6868
expect(results.types.Query.children.objectById.children.id.hits).toEqual(1);
69+
expect(results.types.Query.children.objectById.children.unused.hits).toEqual(0);
6970
// Post
7071
expect(results.types.Post.hits).toEqual(5);
7172
expect(results.types.Post.children.id.hits).toEqual(2);
@@ -81,6 +82,47 @@ describe('coverage', () => {
8182
expect(results.types.Mutation.children.submitPost.hits).toEqual(1);
8283
expect(results.types.Mutation.children.submitPost.children.title.hits).toEqual(1);
8384
expect(results.types.Mutation.children.submitPost.children.author.hits).toEqual(1);
85+
86+
// stats
87+
expect(results.stats.numTypes).toEqual(4);
88+
expect(results.stats.numTypesCovered).toEqual(4);
89+
expect(results.stats.numTypesCoveredFully).toEqual(1);
90+
expect(results.stats.numFields).toEqual(14);
91+
expect(results.stats.numFiledsCovered).toEqual(10);
92+
});
93+
94+
test('no coverage', () => {
95+
const results = coverage(schema, []);
96+
97+
// Query
98+
expect(results.types.Query.hits).toEqual(0);
99+
expect(results.types.Query.children.posts.hits).toEqual(0);
100+
expect(results.types.Query.children.post.hits).toEqual(0);
101+
expect(results.types.Query.children.objectById.hits).toEqual(0);
102+
expect(results.types.Query.children.objectById.children.id.hits).toEqual(0);
103+
expect(results.types.Query.children.objectById.children.unused.hits).toEqual(0);
104+
// Post
105+
expect(results.types.Post.hits).toEqual(0);
106+
expect(results.types.Post.children.id.hits).toEqual(0);
107+
expect(results.types.Post.children.title.hits).toEqual(0);
108+
expect(results.types.Post.children.author.hits).toEqual(0);
109+
expect(results.types.Post.children.createdAt.hits).toEqual(0);
110+
// Identifiable
111+
expect(results.types.Identifiable.hits).toEqual(0);
112+
expect(results.types.Identifiable.children.id.hits).toEqual(0);
113+
expect(results.types.Identifiable.children.createdAt.hits).toEqual(0);
114+
// Mutation
115+
expect(results.types.Mutation.hits).toEqual(0);
116+
expect(results.types.Mutation.children.submitPost.hits).toEqual(0);
117+
expect(results.types.Mutation.children.submitPost.children.title.hits).toEqual(0);
118+
expect(results.types.Mutation.children.submitPost.children.author.hits).toEqual(0);
119+
120+
// stats
121+
expect(results.stats.numTypes).toEqual(4);
122+
expect(results.stats.numTypesCovered).toEqual(0);
123+
expect(results.stats.numTypesCoveredFully).toEqual(0);
124+
expect(results.stats.numFields).toEqual(14);
125+
expect(results.stats.numFiledsCovered).toEqual(0);
84126
});
85127

86128
test('introspection', () => {

Diff for: packages/core/src/coverage/index.ts

+57
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,17 @@ export interface Location {
2121

2222
export interface ArgumentCoverage {
2323
hits: number;
24+
fieldsCount: number;
25+
fieldsCountCovered: number;
2426
locations: {
2527
[name: string]: Array<Location>;
2628
};
2729
}
2830

2931
export interface TypeChildCoverage {
3032
hits: number;
33+
fieldsCount: number;
34+
fieldsCountCovered: number;
3135
locations: {
3236
[name: string]: Array<Location>;
3337
};
@@ -38,6 +42,8 @@ export interface TypeChildCoverage {
3842

3943
export interface TypeCoverage {
4044
hits: number;
45+
fieldsCount: number;
46+
fieldsCountCovered: number;
4147
type: GraphQLNamedType;
4248
children: {
4349
[name: string]: TypeChildCoverage;
@@ -49,6 +55,13 @@ export interface SchemaCoverage {
4955
types: {
5056
[typename: string]: TypeCoverage;
5157
};
58+
stats: {
59+
numTypes: number;
60+
numTypesCoveredFully: number;
61+
numTypesCovered: number;
62+
numFields: number;
63+
numFiledsCovered: number;
64+
};
5265
}
5366

5467
export interface InvalidDocument {
@@ -60,6 +73,13 @@ export function coverage(schema: GraphQLSchema, sources: Source[]): SchemaCovera
6073
const coverage: SchemaCoverage = {
6174
sources,
6275
types: {},
76+
stats: {
77+
numTypes: 0,
78+
numTypesCoveredFully: 0,
79+
numTypesCovered: 0,
80+
numFields: 0,
81+
numFiledsCovered: 0,
82+
},
6383
};
6484
const typeMap = schema.getTypeMap();
6585
const typeInfo = new TypeInfo(schema);
@@ -112,6 +132,8 @@ export function coverage(schema: GraphQLSchema, sources: Source[]): SchemaCovera
112132
if (isObjectType(type) || isInterfaceType(type)) {
113133
const typeCoverage: TypeCoverage = {
114134
hits: 0,
135+
fieldsCount: 0,
136+
fieldsCountCovered: 0,
115137
type,
116138
children: {},
117139
};
@@ -122,13 +144,17 @@ export function coverage(schema: GraphQLSchema, sources: Source[]): SchemaCovera
122144

123145
typeCoverage.children[field.name] = {
124146
hits: 0,
147+
fieldsCount: 0,
148+
fieldsCountCovered: 0,
125149
locations: {},
126150
children: {},
127151
};
128152

129153
for (const arg of field.args) {
130154
typeCoverage.children[field.name].children[arg.name] = {
131155
hits: 0,
156+
fieldsCount: 0,
157+
fieldsCountCovered: 0,
132158
locations: {},
133159
};
134160
}
@@ -151,5 +177,36 @@ export function coverage(schema: GraphQLSchema, sources: Source[]): SchemaCovera
151177
});
152178
});
153179

180+
for (const key in coverage.types) {
181+
const me = coverage.types[key];
182+
processStats(me);
183+
184+
coverage.stats.numTypes++;
185+
if (me.fieldsCountCovered > 0) coverage.stats.numTypesCovered++;
186+
if (me.fieldsCount == me.fieldsCountCovered) coverage.stats.numTypesCoveredFully++;
187+
coverage.stats.numFields += me.fieldsCount;
188+
coverage.stats.numFiledsCovered += me.fieldsCountCovered;
189+
}
190+
154191
return coverage;
155192
}
193+
194+
function processStats(me: TypeCoverage | TypeChildCoverage) {
195+
const children = me.children;
196+
if (children) {
197+
for (const k in children) {
198+
const ch = children[k];
199+
200+
if ((ch as TypeChildCoverage).children !== undefined) {
201+
processStats(ch as TypeChildCoverage);
202+
me.fieldsCount += ch.fieldsCount;
203+
me.fieldsCountCovered += ch.fieldsCountCovered;
204+
}
205+
206+
me.fieldsCount++;
207+
if (ch.hits > 0) {
208+
me.fieldsCountCovered++;
209+
}
210+
}
211+
}
212+
}

Diff for: website/public/assets/img/cli/coverage.jpg

18.5 KB
Loading

Diff for: website/src/pages/docs/essentials/coverage.mdx

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@ graphql-inspector coverage DOCUMENTS SCHEMA
3232

3333
### Output
3434

35-
Depending on enabled flags, a printed GraphQL Schema with stats per each field and a JSON file with
36-
data.
35+
Depending on enabled flags, a printed GraphQL Schema with stats per each field and overall stats, a
36+
JSON file with data.

0 commit comments

Comments
 (0)