Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/cli/arguments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ test.describe('--min-coverage', () => {
test.describe('--min-file-coverage', () => {
let args = ['coverage', '--min-coverage=1']

test('missing --min-file-coverage defaults to 0', () => {
test('missing --min-file-coverage defaults to undefined', () => {
let result = parse_arguments([...args])
expect(result['min-file-coverage']).toEqual(0)
expect(result['min-file-coverage']).toBeUndefined()
})

test('empty --min-file-coverage', () => {
Expand Down
14 changes: 9 additions & 5 deletions src/cli/arguments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type Reporter = (typeof REPORTERS)[number]
export type CliArguments = {
'coverage-dir': string
'min-coverage': number
'min-file-coverage': number
'min-file-coverage'?: number
'show-uncovered': ShowUncovered
reporter: Reporter
}
Expand All @@ -21,7 +21,7 @@ export function parse_arguments(args: string[]): CliArguments {
allowPositionals: true,
options: {
'min-coverage': { type: 'string' },
'min-file-coverage': { type: 'string', default: '0' },
'min-file-coverage': { type: 'string' },
'show-uncovered': { type: 'string', default: 'violations' },
reporter: { type: 'string', default: 'pretty' },
},
Expand Down Expand Up @@ -50,9 +50,13 @@ export function parse_arguments(args: string[]): CliArguments {
issues.push('--min-coverage must be a number between 0 and 1')
}

let min_file_coverage = Number(values['min-file-coverage'])
if (isNaN(min_file_coverage) || min_file_coverage < 0 || min_file_coverage > 1) {
issues.push('--min-file-coverage must be a number between 0 and 1')
let min_file_coverage
if (values['min-file-coverage'] !== undefined) {
min_file_coverage = Number(values['min-file-coverage'])

if (isNaN(min_file_coverage) || min_file_coverage < 0 || min_file_coverage > 1) {
issues.push('--min-file-coverage must be a number between 0 and 1')
}
}

let show_uncovered = values['show-uncovered'] as ShowUncovered
Expand Down
2 changes: 2 additions & 0 deletions src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { print as json } from './reporters/json.js'
import { help } from './help.js'

async function cli(cli_args: string[]) {
let start_time = performance.now()
if (
!cli_args ||
cli_args.length === 0 ||
Expand All @@ -24,6 +25,7 @@ async function cli(cli_args: string[]) {
{
min_coverage: params['min-coverage'],
min_file_coverage: params['min-file-coverage'],
start_time,
},
coverage_data,
)
Expand Down
5 changes: 5 additions & 0 deletions src/cli/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
export type Report = {
context: {
coverage: CoverageResult
duration: number
}
report: {
ok: boolean
Expand Down Expand Up @@ -54,9 +55,11 @@ export function program(
{
min_coverage,
min_file_coverage,
start_time = 0,
}: {
min_coverage: number
min_file_coverage?: number
start_time?: number
},
coverage_data: Coverage[],
) {
Expand All @@ -66,10 +69,12 @@ export function program(
Math.min(...coverage.coverage_per_stylesheet.map((sheet) => sheet.line_coverage_ratio)),
min_file_coverage,
)
let end_time = performance.now()

let result: Report = {
context: {
coverage,
duration: end_time - start_time,
},
report: {
ok: min_coverage_result.ok && min_file_coverage_result.ok,
Expand Down
48 changes: 36 additions & 12 deletions src/cli/reporters/pretty.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,24 @@ const show_violations = { 'show-uncovered': 'violations' } as CliArguments

const context_empty = {
context: {
coverage: {} as CoverageResult,
coverage: {
total_files_found: 7,
total_stylesheets: 8,
total_lines: 9999,
} as CoverageResult,
duration: 2,
},
}

const context_with_failures = {
context: {
duration: 2,
coverage: {
line_coverage_ratio: 0.4022222,
covered_lines: 10,
total_lines: 11,
total_files_found: 7,
total_stylesheets: 9,
coverage_per_stylesheet: [
{
url: 'example.com',
Expand Down Expand Up @@ -157,7 +165,10 @@ test.describe('only --min-line-coverage', () => {
},
} satisfies Report
let result = print(report, show_none, dependencies)
expect(result).toEqual(['Success: total line coverage is 50.22%'])
expect(result).toEqual([
'Finished in 2ms on 7 JSON files containing 8 stylesheets with 9,999 lines of CSS in total.',
'Success: total line coverage is 50.22%',
])
})

test('failure', () => {
Expand All @@ -166,7 +177,10 @@ test.describe('only --min-line-coverage', () => {
coverage: {
total_lines: 10_000,
covered_lines: 5022,
total_files_found: 7,
total_stylesheets: 8,
} as CoverageResult,
duration: 2,
},
report: {
ok: false,
Expand All @@ -176,8 +190,9 @@ test.describe('only --min-line-coverage', () => {
} satisfies Report
let result = print(report, show_none, dependencies)
expect(result).toEqual([
'Finished in 2ms on 7 JSON files containing 8 stylesheets with 10,000 lines of CSS in total.',
'Failed: line coverage is 50.22%% which is lower than the threshold of 1',
'Tip: cover 4978 more lines to meet the threshold of 100%',
'Tip: cover 4,978 more lines to meet the threshold of 100%',
])
})
})
Expand All @@ -194,6 +209,7 @@ test.describe('with --min-file-line-coverage', () => {
} satisfies Report
let result = print(report, show_none, dependencies)
expect(result).toEqual([
'Finished in 2ms on 7 JSON files containing 8 stylesheets with 9,999 lines of CSS in total.',
'Success: total line coverage is 50.22%',
'Success: all files pass minimum line coverage of 50.00%',
])
Expand All @@ -210,21 +226,26 @@ test.describe('with --min-file-line-coverage', () => {
} satisfies Report
let result = print(report, show_none, dependencies)

test('metadata', () => {
expect(result[0]).toEqual(
'Finished in 2ms on 7 JSON files containing 9 stylesheets with 11 lines of CSS in total.',
)
})
test('coverage: pass', () => {
expect(result[0]).toEqual('Success: total line coverage is 50.22%')
expect(result[1]).toEqual('Success: total line coverage is 50.22%')
})
test('file-coverage: fail', () => {
expect(result[1]).toEqual(
expect(result[2]).toEqual(
'Failed: 1 file does not meet the minimum line coverage of 100% (minimum coverage was 50.00%)',
)
})
test('shows hint to --show=violations', () => {
expect(result[2]).toEqual(
expect(result[3]).toEqual(
" Hint: set --show-uncovered=violations to see which files didn't pass",
)
})
test('no files shown', () => {
expect(result).toHaveLength(3)
expect(result).toHaveLength(4)
})
})

Expand All @@ -240,20 +261,20 @@ test.describe('with --min-file-line-coverage', () => {
let result = print(report, show_violations, dependencies)

test('coverage: pass', () => {
expect(result[0]).toEqual('Success: total line coverage is 50.22%')
expect(result.at(-2)).toEqual('Success: total line coverage is 50.22%')
})
test('file-coverage: fail', () => {
expect(result[1]).toEqual(
expect(result.at(-1)).toEqual(
'Failed: 1 file does not meet the minimum line coverage of 100% (minimum coverage was 50.00%)',
)
})
test('does not show hint to --show=violations', () => {
expect(result[2]).not.toEqual(
expect(result.at(-1)).not.toEqual(
" Hint: set --show-uncovered=violations to see which files didn't pass",
)
})
test.describe('shows file details', () => {
let lines = result.slice(2)
let lines = result

test('shows header block', () => {
expect(lines[0]).toEqual('─'.repeat(60))
Expand Down Expand Up @@ -299,7 +320,10 @@ Tip: cover 11 more lines to meet the file threshold of 100%
▌ 17 │ c {
▌ 18 │ color: red;
▌ 19 │ }
`,

Finished in 2ms on 7 JSON files containing 9 stylesheets with 11 lines of CSS in total.
Success: total line coverage is 50.22%
Failed: 1 file does not meet the minimum line coverage of 100% (minimum coverage was 50.00%)`,
)
})
})
Expand Down
90 changes: 49 additions & 41 deletions src/cli/reporters/pretty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ function percentage(ratio: number, decimals: number = 2): string {
return `${(ratio * 100).toFixed(ratio === 1 ? 0 : decimals)}%`
}

function number(num: number): string {
return new Intl.NumberFormat().format(num)
}

export type TextStyle =
| 'bold'
| 'red'
Expand Down Expand Up @@ -90,54 +94,13 @@ export function print_lines(
) {
let output: (string | undefined)[] = []

if (report.min_line_coverage.ok) {
output.push(
`${styleText(['bold', 'green'], 'Success')}: total line coverage is ${percentage(report.min_line_coverage.actual)}`,
)
} else {
let { actual, expected } = report.min_line_coverage
output.push(
`${styleText(['bold', 'red'], 'Failed')}: line coverage is ${percentage(actual)}% which is lower than the threshold of ${expected}`,
)
let lines_to_cover = expected * context.coverage.total_lines - context.coverage.covered_lines
output.push(
`Tip: cover ${Math.ceil(lines_to_cover)} more ${lines_to_cover === 1 ? 'line' : 'lines'} to meet the threshold of ${percentage(
expected,
)}`,
)
}

if (report.min_file_line_coverage.expected !== undefined) {
let { expected, actual, ok } = report.min_file_line_coverage
if (ok) {
output.push(
`${styleText(['bold', 'green'], 'Success')}: all files pass minimum line coverage of ${percentage(expected)}`,
)
} else {
let num_files_failed = context.coverage.coverage_per_stylesheet.filter(
(sheet) => sheet.line_coverage_ratio < expected!,
).length
output.push(
`${styleText(['bold', 'red'], 'Failed')}: ${num_files_failed} ${
num_files_failed === 1 ? 'file does' : 'files do'
} not meet the minimum line coverage of ${percentage(expected)} (minimum coverage was ${percentage(actual)})`,
)
if (params['show-uncovered'] === 'none') {
output.push(` Hint: set --show-uncovered=violations to see which files didn't pass`)
}
}
}

// Show un-covered chunks
if (params['show-uncovered'] !== 'none') {
const NUM_LEADING_LINES = 3
const NUM_TRAILING_LINES = NUM_LEADING_LINES
print_width = print_width ?? 80
let min_file_line_coverage = report.min_file_line_coverage.expected

// Show empty line between report header and chunks output
output.push()

for (let sheet of context.coverage.coverage_per_stylesheet.sort(
(a, b) => a.line_coverage_ratio - b.line_coverage_ratio,
)) {
Expand Down Expand Up @@ -213,6 +176,51 @@ export function print_lines(
}
}

// Show empty line between report summary and chunks output
output.push()

output.push(
`Finished in ${number(Math.round(context.duration))}ms on ${number(context.coverage.total_files_found)} JSON files containing ${number(context.coverage.total_stylesheets)} stylesheets with ${number(context.coverage.total_lines)} lines of CSS in total.`,
)

if (report.min_line_coverage.ok) {
output.push(
`${styleText(['bold', 'green'], 'Success')}: total line coverage is ${percentage(report.min_line_coverage.actual)}`,
)
} else {
let { actual, expected } = report.min_line_coverage
output.push(
`${styleText(['bold', 'red'], 'Failed')}: line coverage is ${percentage(actual)}% which is lower than the threshold of ${expected}`,
)
let lines_to_cover = expected * context.coverage.total_lines - context.coverage.covered_lines
output.push(
`Tip: cover ${number(Math.ceil(lines_to_cover))} more ${lines_to_cover === 1 ? 'line' : 'lines'} to meet the threshold of ${percentage(
expected,
)}`,
)
}

if (report.min_file_line_coverage.expected !== undefined) {
let { expected, actual, ok } = report.min_file_line_coverage
if (ok) {
output.push(
`${styleText(['bold', 'green'], 'Success')}: all files pass minimum line coverage of ${percentage(expected)}`,
)
} else {
let num_files_failed = context.coverage.coverage_per_stylesheet.filter(
(sheet) => sheet.line_coverage_ratio < expected,
).length
output.push(
`${styleText(['bold', 'red'], 'Failed')}: ${number(num_files_failed)} ${
num_files_failed === 1 ? 'file does' : 'files do'
} not meet the minimum line coverage of ${percentage(expected)} (minimum coverage was ${percentage(actual)})`,
)
if (params['show-uncovered'] === 'none') {
output.push(` Hint: set --show-uncovered=violations to see which files didn't pass`)
}
}
}

return output
}

Expand Down
Loading