Skip to content

Commit 796f895

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/playwright/test-1.56.1
2 parents 5d58db2 + 63eb5c4 commit 796f895

17 files changed

+401
-413
lines changed

package-lock.json

Lines changed: 117 additions & 114 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cli/arguments.test.ts

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test'
22
import { parse_arguments, validate_arguments } from './arguments'
33

44
test.describe('--coverage-dir', () => {
5-
let cov = '--min-line-coverage=1'
5+
let cov = '--min-coverage=1'
66

77
test('missing --coverage-dir', () => {
88
expect(() => validate_arguments(parse_arguments([cov]))).toThrowError()
@@ -18,51 +18,51 @@ test.describe('--coverage-dir', () => {
1818
})
1919
})
2020

21-
test.describe('--min-line-coverage', () => {
21+
test.describe('--min-coverage', () => {
2222
let dir = '--coverage-dir=coverage'
2323

24-
test('missing --min-line-coverage', () => {
24+
test('missing --min-coverage', () => {
2525
expect(() => validate_arguments(parse_arguments([dir]))).toThrowError()
2626
})
2727

28-
test('empty --min-line-coverage', () => {
29-
expect(() => validate_arguments(parse_arguments([dir, '--min-line-coverage']))).toThrowError()
28+
test('empty --min-coverage', () => {
29+
expect(() => validate_arguments(parse_arguments([dir, '--min-coverage']))).toThrowError()
3030
})
3131

32-
test('invalid --min-line-coverage=-1', () => {
33-
expect(() => validate_arguments(parse_arguments([dir, '--min-line-coverage=-1']))).toThrowError()
32+
test('invalid --min-coverage=-1', () => {
33+
expect(() => validate_arguments(parse_arguments([dir, '--min-coverage=-1']))).toThrowError()
3434
})
3535

36-
test('valid --min-line-coverage=.8', () => {
37-
let result = validate_arguments(parse_arguments([dir, '--min-line-coverage=.8']))
38-
expect(result['min-line-coverage']).toEqual(0.8)
36+
test('valid --min-coverage=.8', () => {
37+
let result = validate_arguments(parse_arguments([dir, '--min-coverage=.8']))
38+
expect(result['min-coverage']).toEqual(0.8)
3939
})
4040
})
4141

42-
test.describe('--min-file-line-coverage', () => {
43-
let args = ['--coverage-dir=coverage', '--min-line-coverage=1']
42+
test.describe('--min-file-coverage', () => {
43+
let args = ['--coverage-dir=coverage', '--min-coverage=1']
4444

45-
test('missing --min-file-line-coverage', () => {
45+
test('missing --min-file-coverage', () => {
4646
expect.soft(() => validate_arguments(parse_arguments([...args]))).not.toThrowError()
47-
expect.soft(validate_arguments(parse_arguments([...args]))['min-file-line-coverage']).toEqual(0)
47+
expect.soft(validate_arguments(parse_arguments([...args]))['min-file-coverage']).toEqual(0)
4848
})
4949

50-
test('empty --min-file-line-coverage', () => {
51-
expect.soft(() => validate_arguments(parse_arguments([...args, '--min-file-line-coverage']))).toThrowError()
50+
test('empty --min-file-coverage', () => {
51+
expect.soft(() => validate_arguments(parse_arguments([...args, '--min-file-coverage']))).toThrowError()
5252
})
5353

54-
test('invalid --min-file-line-coverage=-1', () => {
55-
expect(() => validate_arguments(parse_arguments([...args, '--min-file-line-coverage=-1']))).toThrowError()
54+
test('invalid --min-file-coverage=-1', () => {
55+
expect(() => validate_arguments(parse_arguments([...args, '--min-file-coverage=-1']))).toThrowError()
5656
})
5757

58-
test('valid --min-file-line-coverage=.8', () => {
59-
let result = validate_arguments(parse_arguments([...args, '--min-file-line-coverage=.8']))
60-
expect(result['min-file-line-coverage']).toEqual(0.8)
58+
test('valid --min-file-coverage=.8', () => {
59+
let result = validate_arguments(parse_arguments([...args, '--min-file-coverage=.8']))
60+
expect(result['min-file-coverage']).toEqual(0.8)
6161
})
6262
})
6363

6464
test.describe('--reporter', () => {
65-
let args = ['--coverage-dir=coverage', '--min-line-coverage=1']
65+
let args = ['--coverage-dir=coverage', '--min-coverage=1']
6666

6767
test('missing --reporter', () => {
6868
expect.soft(() => validate_arguments(parse_arguments([...args]))).not.toThrowError()
@@ -89,7 +89,7 @@ test.describe('--reporter', () => {
8989
})
9090

9191
test.describe('--show-uncovered', () => {
92-
let args = ['--coverage-dir=coverage', '--min-line-coverage=1']
92+
let args = ['--coverage-dir=coverage', '--min-coverage=1']
9393

9494
test('missing --show-uncovered', () => {
9595
expect.soft(() => validate_arguments(parse_arguments([...args]))).not.toThrowError()

src/cli/arguments.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,16 @@ let ReporterSchema = v.pipe(v.string(), v.enum(reporters))
2121

2222
let CliArgumentsSchema = v.object({
2323
'coverage-dir': CoverageDirSchema,
24-
'min-line-coverage': RatioPercentageSchema,
25-
'min-file-line-coverage': v.optional(RatioPercentageSchema),
24+
'min-coverage': RatioPercentageSchema,
25+
'min-file-coverage': v.optional(RatioPercentageSchema),
2626
'show-uncovered': v.optional(ShowUncoveredSchema, show_uncovered_options.violations),
2727
reporter: v.optional(ReporterSchema, reporters.pretty),
2828
})
2929

3030
export type CliArguments = {
3131
'coverage-dir': string
32-
'min-line-coverage': number
33-
'min-file-line-coverage'?: number
32+
'min-coverage': number
33+
'min-file-coverage'?: number
3434
'show-uncovered': keyof typeof show_uncovered_options
3535
reporter: keyof typeof reporters
3636
}
@@ -64,15 +64,14 @@ export function validate_arguments(args: ReturnType<typeof parse_arguments>): Cl
6464
export function parse_arguments(args: string[]) {
6565
let { values } = parseArgs({
6666
args,
67-
allowPositionals: true,
6867
options: {
6968
'coverage-dir': {
7069
type: 'string',
7170
},
72-
'min-line-coverage': {
71+
'min-coverage': {
7372
type: 'string',
7473
},
75-
'min-file-line-coverage': {
74+
'min-file-coverage': {
7675
type: 'string',
7776
default: '0',
7877
},

src/cli/cli.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@ import { program } from './program.js'
55
import { read } from './file-reader.js'
66
import { print as pretty } from './reporters/pretty.js'
77
import { print as tap } from './reporters/tap.js'
8+
import { help } from './help.js'
89

910
async function cli(cli_args: string[]) {
11+
if (!cli_args || cli_args.length === 0 || cli_args.includes('--help') || cli_args.includes('-h')) {
12+
return console.log(help())
13+
}
14+
1015
let params = validate_arguments(parse_arguments(cli_args))
1116
let coverage_data = await read(params['coverage-dir'])
1217
let report = program(
1318
{
14-
min_file_coverage: params['min-line-coverage'],
15-
min_file_line_coverage: params['min-file-line-coverage'],
19+
min_coverage: params['min-coverage'],
20+
min_file_coverage: params['min-file-coverage'],
1621
},
1722
coverage_data,
1823
)

src/cli/help.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { styleText } from 'node:util'
2+
3+
export function help() {
4+
return `
5+
${styleText(['bold'], 'USAGE')}
6+
$ css-coverage --coverage-dir=<dir> --min-coverage=<number> [options]
7+
8+
${styleText('bold', 'OPTIONS')}
9+
Required:
10+
--coverage-dir Where your Coverage JSON files are
11+
--min-coverage Minimum overall CSS coverage [0-1]
12+
13+
Optional:
14+
--min-file-coverage Minimal coverage per file [0-1]
15+
16+
--show-uncovered Which files to show when not meeting
17+
the --min-file-line-coverage threshold
18+
• violations [default] ${styleText('dim', 'show under-threshold files')}
19+
• all ${styleText('dim', 'show partially covered files')}
20+
• none ${styleText('dim', 'do not show files')}
21+
22+
--reporter How to show the results
23+
• pretty [default]
24+
• tap
25+
• json
26+
27+
${styleText('bold', 'EXAMPLES')}
28+
${styleText('dim', '# analyze all .json files in ./coverage; require 80% overall coverage')}
29+
$ css-coverage --coverage-dir=./coverage --min-coverage=0.8
30+
31+
${styleText('dim', '# Require 50% coverage per file')}
32+
$ css-coverage --coverage-dir=./coverage --min-coverage=0.8 --min-file-coverage=0.5
33+
34+
${styleText('dim', 'Report JSON')}
35+
$ css-coverage --coverage-dir=./coverage --min-coverage=0.8 --reporter=json
36+
`.trim()
37+
}

src/cli/program.test.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,31 @@ let coverage = [
1212
] satisfies Coverage[]
1313

1414
test('returns context', () => {
15-
let result = program({ min_file_coverage: 0.5 }, coverage)
15+
let result = program({ min_coverage: 0.5 }, coverage)
1616
expect.soft(result.context.coverage.covered_lines).toEqual(4)
1717
expect.soft(result.context.coverage.total_files_found).toEqual(1)
1818
})
1919

20-
test.describe('--min-file-coverage', () => {
21-
test('Success: --min-file-coverage=0.5', () => {
22-
let result = program({ min_file_coverage: 0.5 }, coverage)
20+
test.describe('--min-coverage', () => {
21+
test('Success: --min-coverage=0.5', () => {
22+
let result = program({ min_coverage: 0.5 }, coverage)
2323
expect(result.report.ok).toBeTruthy()
2424
})
2525

26-
test('Failure: --min-file-coverage=1', () => {
27-
let result = program({ min_file_coverage: 1 }, coverage)
26+
test('Failure: --min-coverage=1', () => {
27+
let result = program({ min_coverage: 1 }, coverage)
2828
expect(result.report.ok).toBeFalsy()
2929
})
3030
})
3131

32-
test.describe('--min-file-line-coverage', () => {
33-
test('Success: --min-file-line-coverage=0.5', () => {
34-
let result = program({ min_file_coverage: 0, min_file_line_coverage: 0.5 }, coverage)
32+
test.describe('--min-file-coverage', () => {
33+
test('Success: --min-file-coverage=0.5', () => {
34+
let result = program({ min_coverage: 0, min_file_coverage: 0.5 }, coverage)
3535
expect(result.report.ok).toBeTruthy()
3636
})
3737

38-
test('Failure: --min-file-line-coverage=1', () => {
39-
let result = program({ min_file_coverage: 0, min_file_line_coverage: 1 }, coverage)
38+
test('Failure: --min-file-coverage=1', () => {
39+
let result = program({ min_coverage: 0, min_file_coverage: 1 }, coverage)
4040
expect(result.report.ok).toBeFalsy()
4141
})
4242
})

src/cli/program.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
import { calculate_coverage, type Coverage, type CoverageResult } from '../lib/index.js'
22

3-
class MissingDataError extends Error {
4-
constructor() {
5-
super('No data to analyze')
6-
}
7-
}
8-
93
export type Report = {
104
context: {
115
coverage: CoverageResult
@@ -51,29 +45,29 @@ function validate_min_file_line_coverage(actual: number, expected: number | unde
5145

5246
export function program(
5347
{
48+
min_coverage,
5449
min_file_coverage,
55-
min_file_line_coverage,
5650
}: {
57-
min_file_coverage: number
58-
min_file_line_coverage?: number
51+
min_coverage: number
52+
min_file_coverage?: number
5953
},
6054
coverage_data: Coverage[],
6155
) {
6256
let coverage = calculate_coverage(coverage_data)
63-
let min_line_coverage_result = validate_min_line_coverage(coverage.line_coverage_ratio, min_file_coverage)
64-
let min_file_line_coverage_result = validate_min_file_line_coverage(
57+
let min_coverage_result = validate_min_line_coverage(coverage.line_coverage_ratio, min_coverage)
58+
let min_file_coverage_result = validate_min_file_line_coverage(
6559
Math.min(...coverage.coverage_per_stylesheet.map((sheet) => sheet.line_coverage_ratio)),
66-
min_file_line_coverage,
60+
min_file_coverage,
6761
)
6862

6963
let result: Report = {
7064
context: {
7165
coverage,
7266
},
7367
report: {
74-
ok: min_line_coverage_result.ok && min_file_line_coverage_result.ok,
75-
min_line_coverage: min_line_coverage_result,
76-
min_file_line_coverage: min_file_line_coverage_result,
68+
ok: min_coverage_result.ok && min_file_coverage_result.ok,
69+
min_line_coverage: min_coverage_result,
70+
min_file_line_coverage: min_file_coverage_result,
7771
},
7872
}
7973

src/cli/reporters/pretty.test.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,15 +164,23 @@ test.describe('only --min-line-coverage', () => {
164164

165165
test('failure', () => {
166166
const report = {
167-
...context_empty,
167+
context: {
168+
coverage: {
169+
total_lines: 10_000,
170+
covered_lines: 5022,
171+
} as CoverageResult,
172+
},
168173
report: {
169174
ok: false,
170175
...min_line_coverage_failure,
171176
...min_file_line_coverage_unset,
172177
},
173178
} satisfies Report
174179
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'])
180+
expect(result).toEqual([
181+
'Failed: line coverage is 50.22%% which is lower than the threshold of 1',
182+
'Tip: cover 4978 more lines to meet the threshold of 100%',
183+
])
176184
})
177185
})
178186

src/cli/reporters/pretty.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,15 @@ export function print_lines({ report, context }: Report, params: CliArguments, {
2929
if (report.min_line_coverage.ok) {
3030
output.push(`${styleText(['bold', 'green'], 'Success')}: total line coverage is ${percentage(report.min_line_coverage.actual)}`)
3131
} else {
32+
let { actual, expected } = report.min_line_coverage
3233
output.push(
33-
`${styleText(['bold', 'red'], 'Failed')}: line coverage is ${percentage(
34-
report.min_line_coverage.actual,
35-
)}% which is lower than the threshold of ${report.min_line_coverage.expected}`,
34+
`${styleText(['bold', 'red'], 'Failed')}: line coverage is ${percentage(actual)}% which is lower than the threshold of ${expected}`,
35+
)
36+
let lines_to_cover = expected * context.coverage.total_lines - context.coverage.covered_lines
37+
output.push(
38+
`Tip: cover ${Math.ceil(lines_to_cover)} more ${lines_to_cover === 1 ? 'line' : 'lines'} to meet the threshold of ${percentage(
39+
expected,
40+
)}`,
3641
)
3742
}
3843

@@ -60,6 +65,9 @@ export function print_lines({ report, context }: Report, params: CliArguments, {
6065
print_width = print_width ?? 80
6166
let min_file_line_coverage = report.min_file_line_coverage.expected
6267

68+
// Show empty line between report header and chunks output
69+
output.push()
70+
6371
for (let sheet of context.coverage.coverage_per_stylesheet.sort((a, b) => a.line_coverage_ratio - b.line_coverage_ratio)) {
6472
if (
6573
(sheet.line_coverage_ratio !== 1 && params['show-uncovered'] === 'all') ||

src/lib/chunkify.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export type ChunkedCoverage = Omit<Coverage, 'ranges'> & {
1010
chunks: Chunk[]
1111
}
1212

13+
const WHITESPACE_ONLY_REGEX = /^\s+$/
14+
1315
function merge(stylesheet: ChunkedCoverage): ChunkedCoverage {
1416
let new_chunks: Chunk[] = []
1517
let previous_chunk: Chunk | undefined
@@ -18,7 +20,7 @@ function merge(stylesheet: ChunkedCoverage): ChunkedCoverage {
1820
let chunk = stylesheet.chunks.at(i)!
1921

2022
// If the current chunk is only whitespace or empty, ignore it
21-
if (/^\s+$/.test(stylesheet.text.slice(chunk.start_offset, chunk.end_offset))) {
23+
if (WHITESPACE_ONLY_REGEX.test(stylesheet.text.slice(chunk.start_offset, chunk.end_offset))) {
2224
continue
2325
}
2426

@@ -32,7 +34,10 @@ function merge(stylesheet: ChunkedCoverage): ChunkedCoverage {
3234
continue
3335
}
3436
// If the current chunk is only whitespace or empty, add it to the previous
35-
else if (/^\s+$/.test(stylesheet.text.slice(chunk.start_offset, chunk.end_offset)) || chunk.end_offset === chunk.start_offset) {
37+
else if (
38+
WHITESPACE_ONLY_REGEX.test(stylesheet.text.slice(chunk.start_offset, chunk.end_offset)) ||
39+
chunk.end_offset === chunk.start_offset
40+
) {
3641
latest_chunk.end_offset = chunk.end_offset
3742
// do not update previous_chunk
3843
continue

0 commit comments

Comments
 (0)