Skip to content

Commit 30a4120

Browse files
committed
add tests for pretty printer
1 parent 96765e3 commit 30a4120

File tree

3 files changed

+309
-15
lines changed

3 files changed

+309
-15
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"files.trimTrailingWhitespaceInRegexAndStrings": false
3+
}

src/cli/reporters/pretty.test.ts

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
import { test, expect } from '@playwright/test'
2+
import { print_lines as print, PrintLinesDependencies, type StyleTextFn, TextStyle } from './pretty'
3+
import { Report } from '../program'
4+
import { CoverageResult } from '../../lib'
5+
import { CliArguments } from '../arguments'
6+
import { PrettifiedChunk } from '../../lib/prettify'
7+
8+
// test matrix
9+
// ------------------------------
10+
// overall line coverage success
11+
// overall line coverage fail
12+
// per-file line coverage success
13+
// per-file line coverage fail
14+
// show all
15+
// show violations
16+
// show none
17+
18+
function style_text_fn(_style: TextStyle | TextStyle[], text: string): string {
19+
return text
20+
}
21+
22+
const min_line_coverage_failure = {
23+
min_line_coverage: {
24+
ok: false,
25+
expected: 1,
26+
actual: 0.502222222222,
27+
} satisfies Report['report']['min_line_coverage'],
28+
}
29+
30+
const min_line_coverage_success = {
31+
min_line_coverage: {
32+
ok: true,
33+
expected: 0,
34+
actual: 0.5022222222,
35+
} satisfies Report['report']['min_line_coverage'],
36+
}
37+
38+
const min_file_line_coverage_unset = {
39+
min_file_line_coverage: {
40+
ok: true,
41+
actual: 0.5,
42+
} satisfies Report['report']['min_file_line_coverage'],
43+
}
44+
45+
const min_file_line_coverage_success = {
46+
min_file_line_coverage: {
47+
ok: true,
48+
actual: 1,
49+
expected: 0.5,
50+
} satisfies Report['report']['min_file_line_coverage'],
51+
}
52+
53+
const min_file_line_coverage_failure = {
54+
min_file_line_coverage: {
55+
ok: false,
56+
actual: 0.5,
57+
expected: 1,
58+
} satisfies Report['report']['min_file_line_coverage'],
59+
}
60+
61+
const show_none = { 'show-uncovered': 'none' } as CliArguments
62+
const show_all = { 'show-uncovered': 'all' } as CliArguments
63+
const show_violations = { 'show-uncovered': 'violations' } as CliArguments
64+
65+
const context_empty = {
66+
context: {
67+
coverage: {} as CoverageResult,
68+
},
69+
}
70+
71+
const context_with_failures = {
72+
context: {
73+
coverage: {
74+
line_coverage_ratio: 0.4022222,
75+
covered_lines: 10,
76+
total_lines: 11,
77+
coverage_per_stylesheet: [
78+
{
79+
url: 'example.com',
80+
line_coverage_ratio: 1,
81+
text: `z {
82+
color: yellow
83+
}`,
84+
chunks: [
85+
{
86+
start_line: 1,
87+
end_line: 3,
88+
is_covered: true,
89+
},
90+
],
91+
},
92+
{
93+
url: 'example.com',
94+
line_coverage_ratio: 8 / 19,
95+
covered_lines: 8,
96+
total_lines: 19,
97+
text: `a {
98+
color: red;
99+
}
100+
101+
a1 {
102+
color: blue;
103+
}
104+
105+
b {
106+
color: red;
107+
}
108+
109+
b1 {
110+
color: blue;
111+
}
112+
113+
c {
114+
color: red;
115+
}`,
116+
chunks: [
117+
{
118+
start_line: 1,
119+
end_line: 3,
120+
is_covered: false,
121+
},
122+
{
123+
start_line: 4,
124+
end_line: 8,
125+
is_covered: true,
126+
},
127+
{
128+
start_line: 9,
129+
end_line: 11,
130+
is_covered: false,
131+
},
132+
{
133+
start_line: 12,
134+
end_line: 16,
135+
is_covered: true,
136+
},
137+
{
138+
start_line: 17,
139+
end_line: 19,
140+
is_covered: false,
141+
},
142+
],
143+
},
144+
],
145+
} as CoverageResult,
146+
},
147+
}
148+
149+
const dependencies = { styleText: style_text_fn, print_width: 60 } satisfies PrintLinesDependencies
150+
151+
test.describe('only --min-line-coverage', () => {
152+
test('success', () => {
153+
const report = {
154+
...context_empty,
155+
report: {
156+
ok: true,
157+
...min_line_coverage_success,
158+
...min_file_line_coverage_unset,
159+
},
160+
} satisfies Report
161+
let result = print(report, show_none, dependencies)
162+
expect(result).toEqual(['Success: total line coverage is 50.22%'])
163+
})
164+
165+
test('failure', () => {
166+
const report = {
167+
...context_empty,
168+
report: {
169+
ok: false,
170+
...min_line_coverage_failure,
171+
...min_file_line_coverage_unset,
172+
},
173+
} satisfies Report
174+
let result = print(report, show_none, dependencies)
175+
expect(result).toEqual(['Failed: line coverage is 50.22%% which is lower than the threshold of 1'])
176+
})
177+
})
178+
179+
test.describe('with --min-file-line-coverage', () => {
180+
test('--min-line-coverage: success; --min-file-line-coverage: success; --show: none', () => {
181+
let report = {
182+
...context_empty,
183+
report: {
184+
ok: true,
185+
...min_line_coverage_success,
186+
...min_file_line_coverage_success,
187+
},
188+
} satisfies Report
189+
let result = print(report, show_none, dependencies)
190+
expect(result).toEqual(['Success: total line coverage is 50.22%', 'Success: all files pass minimum line coverage of 50.00%'])
191+
})
192+
193+
test.describe('--min-line-coverage: success; --min-file-line-coverage: failure; --show: none', () => {
194+
let report = {
195+
...context_with_failures,
196+
report: {
197+
ok: false,
198+
...min_line_coverage_success,
199+
...min_file_line_coverage_failure,
200+
},
201+
} satisfies Report
202+
let result = print(report, show_none, dependencies)
203+
204+
test('coverage: pass', () => {
205+
expect(result[0]).toEqual('Success: total line coverage is 50.22%')
206+
})
207+
test('file-coverage: fail', () => {
208+
expect(result[1]).toEqual('Failed: 1 file does not meet the minimum line coverage of 100% (minimum coverage was 50.00%)')
209+
})
210+
test('shows hint to --show=violations', () => {
211+
expect(result[2]).toEqual(" Hint: set --show-uncovered=violations to see which files didn't pass")
212+
})
213+
test('no files shown', () => {
214+
expect(result).toHaveLength(3)
215+
})
216+
})
217+
218+
test.describe('--min-line-coverage: success; --min-file-line-coverage: failure; --show: violations', () => {
219+
let report = {
220+
...context_with_failures,
221+
report: {
222+
ok: false,
223+
...min_line_coverage_success,
224+
...min_file_line_coverage_failure,
225+
},
226+
} satisfies Report
227+
let result = print(report, show_violations, dependencies)
228+
229+
test('coverage: pass', () => {
230+
expect(result[0]).toEqual('Success: total line coverage is 50.22%')
231+
})
232+
test('file-coverage: fail', () => {
233+
expect(result[1]).toEqual('Failed: 1 file does not meet the minimum line coverage of 100% (minimum coverage was 50.00%)')
234+
})
235+
test('does not show hint to --show=violations', () => {
236+
expect(result[2]).not.toEqual(" Hint: set --show-uncovered=violations to see which files didn't pass")
237+
})
238+
test.describe('shows file details', () => {
239+
let lines = result.slice(2)
240+
241+
test('shows header block', () => {
242+
expect(lines[0]).toEqual('─'.repeat(60))
243+
expect(lines[4]).toEqual(lines[0])
244+
})
245+
test('shows file name', () => {
246+
expect(lines[1]).toEqual('example.com')
247+
})
248+
test('shows coverage info', () => {
249+
expect(lines[2]).toEqual('Coverage: 42.11%, 8/19 lines covered')
250+
})
251+
test('shows hint with how many lines to cover to reach threshold', () => {
252+
expect(lines[3]).toEqual('Tip: cover 11 more lines to meet the file threshold of 100%')
253+
})
254+
test('result snapshot', () => {
255+
let snapshot = lines.join('\n')
256+
expect(snapshot).toEqual(
257+
`
258+
────────────────────────────────────────────────────────────
259+
example.com
260+
Coverage: 42.11%, 8/19 lines covered
261+
Tip: cover 11 more lines to meet the file threshold of 100%
262+
────────────────────────────────────────────────────────────
263+
1 ━ a {
264+
2 ━ color: red;
265+
3 ━ }
266+
4 │
267+
5 │ a1 {
268+
6 │ color: blue;
269+
7 │ }
270+
8 │
271+
9 ━ b {
272+
10 ━ color: red;
273+
11 ━ }
274+
12 │
275+
13 │ b1 {
276+
14 │ color: blue;
277+
15 │ }
278+
16 │
279+
17 ━ c {
280+
18 ━ color: red;
281+
19 ━ }`.trim(),
282+
)
283+
})
284+
})
285+
})
286+
})

src/cli/reporters/pretty.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,26 @@ function indent(line?: string): string {
88
return (line || '').replace(/^\t+/, (tabs) => ' '.repeat(tabs.length * 4))
99
}
1010

11-
function percentage(ratio: number): string {
12-
return `${(ratio * 100).toFixed(2)}%`
11+
let line_number = (num: number, covered: boolean = true) => `${num.toString().padStart(5, ' ')} ${covered ? '│' : '━'} `
12+
13+
function percentage(ratio: number, decimals: number = 2): string {
14+
return `${(ratio * 100).toFixed(ratio === 1 ? 0 : decimals)}%`
1315
}
1416

15-
type TextStyle = 'bold' | 'red' | 'dim' | 'green'
16-
type StyleTextFn = (style: TextStyle | TextStyle[], input: string) => string
17+
export type TextStyle = 'bold' | 'red' | 'dim' | 'green'
18+
19+
export type StyleTextFn = (style: TextStyle | TextStyle[], input: string) => string
1720

18-
export function print_lines(
19-
{ report, context }: Report,
20-
params: CliArguments,
21-
{ styleText, print_width }: { styleText: StyleTextFn; print_width?: number },
22-
) {
21+
export type PrintLinesDependencies = {
22+
styleText: StyleTextFn
23+
print_width?: number
24+
}
25+
26+
export function print_lines({ report, context }: Report, params: CliArguments, { styleText, print_width }: PrintLinesDependencies) {
2327
let output: (string | undefined)[] = []
2428

2529
if (report.min_line_coverage.ok) {
26-
output.push(`${styleText(['bold', 'green'], 'Success')}: total line coverage is ${percentage(report.min_line_coverage.actual)}%`)
30+
output.push(`${styleText(['bold', 'green'], 'Success')}: total line coverage is ${percentage(report.min_line_coverage.actual)}`)
2731
} else {
2832
output.push(
2933
`${styleText(['bold', 'red'], 'Failed')}: line coverage is ${percentage(
@@ -35,7 +39,7 @@ export function print_lines(
3539
if (report.min_file_line_coverage.expected !== undefined) {
3640
let { expected, actual, ok } = report.min_file_line_coverage
3741
if (ok) {
38-
output.push(`${styleText(['bold', 'green'], 'Success')}: all files pass minimum line coverage of ${percentage(expected)}%`)
42+
output.push(`${styleText(['bold', 'green'], 'Success')}: all files pass minimum line coverage of ${percentage(expected)}`)
3943
} else {
4044
let num_files_failed = context.coverage.coverage_per_stylesheet.filter((sheet) => sheet.line_coverage_ratio < expected!).length
4145
output.push(
@@ -54,7 +58,6 @@ export function print_lines(
5458
const NUM_LEADING_LINES = 3
5559
const NUM_TRAILING_LINES = NUM_LEADING_LINES
5660
print_width = print_width ?? 80
57-
let line_number = (num: number, covered: boolean = true) => `${num.toString().padStart(5, ' ')} ${covered ? '│' : '━'} `
5861
let min_file_line_coverage = report.min_file_line_coverage.expected
5962

6063
for (let sheet of context.coverage.coverage_per_stylesheet.sort((a, b) => a.line_coverage_ratio - b.line_coverage_ratio)) {
@@ -82,15 +85,15 @@ export function print_lines(
8285

8386
for (let chunk of sheet.chunks.filter((chunk) => !chunk.is_covered)) {
8487
// Render N leading lines
85-
for (let x = Math.max(chunk.start_line - NUM_LEADING_LINES, 0); x < chunk.start_line; x++) {
88+
for (let x = Math.max(chunk.start_line - NUM_LEADING_LINES, 1); x < chunk.start_line; x++) {
8689
output.push([styleText('dim', line_number(x)), styleText('dim', indent(lines[x - 1]))].join(''))
8790
}
8891
// Render the uncovered chunk
8992
for (let i = chunk.start_line; i <= chunk.end_line; i++) {
9093
output.push([styleText('red', line_number(i, false)), indent(lines[i - 1])].join(''))
9194
}
9295
// Render N trailing lines
93-
for (let y = chunk.end_line; y < Math.min(chunk.end_line + NUM_TRAILING_LINES, lines.length); y++) {
96+
for (let y = chunk.end_line + 1; y < Math.min(chunk.end_line + NUM_TRAILING_LINES, lines.length); y++) {
9497
output.push([styleText('dim', line_number(y)), styleText('dim', indent(lines[y - 1]))].join(''))
9598
}
9699
// Show empty line between blocks
@@ -104,5 +107,7 @@ export function print_lines(
104107
}
105108

106109
export function print(report: Report, params: CliArguments): void {
107-
print_lines(report, params, { styleText, print_width: process.stdout.columns }).map((line) => console.log(line))
110+
for (let line of print_lines(report, params, { styleText, print_width: process.stdout.columns })) {
111+
console.log(line)
112+
}
108113
}

0 commit comments

Comments
 (0)