Skip to content

Commit 03fe831

Browse files
committed
refactor(cli): simplify download progress rendering and tests
1 parent 6bfe6c4 commit 03fe831

2 files changed

Lines changed: 63 additions & 50 deletions

File tree

‎crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/download_progress/download.mjs‎

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ import { tmpdir } from 'node:os';
88
import { join } from 'node:path';
99
import { gzipSync } from 'node:zlib';
1010

11-
// Small, valid archives keep this test offline. The installed files are never run.
12-
function archive(files) {
11+
/**
12+
* Small, valid archives keep this test offline. The installed files are never run.
13+
* @param {Record<string, string>} files
14+
* @returns {Buffer}
15+
*/
16+
function createArchive(files) {
1317
const blocks = [];
1418
for (const [name, contents] of Object.entries(files)) {
1519
const body = Buffer.from(contents);
@@ -30,8 +34,8 @@ const version = '99.0.0';
3034
const musl = process.platform === 'linux' && !process.report.getReport().header.glibcVersionRuntime;
3135
const platform = `${process.platform}-${process.arch}${musl ? '-musl' : ''}`;
3236
const nodeRoot = `node-v${version}-${platform}`;
33-
const nodeArchive = archive({ [`${nodeRoot}/bin/node`]: '#!/bin/sh\nexit 0\n' });
34-
const npmArchive = archive({
37+
const nodeArchive = createArchive({ [`${nodeRoot}/bin/node`]: '#!/bin/sh\nexit 0\n' });
38+
const npmArchive = createArchive({
3539
'package/package.json': JSON.stringify({
3640
name: 'npm',
3741
version,
@@ -41,7 +45,7 @@ const npmArchive = archive({
4145
'package/bin/npx-cli.js': '// fixture\n',
4246
});
4347
let downloads = 0;
44-
const server = createServer((request, response) => {
48+
const server = createServer(function handleRequest(request, response) {
4549
if (request.url.endsWith('/SHASUMS256.txt.asc')) {
4650
response.writeHead(404).end();
4751
return;
@@ -50,25 +54,30 @@ const server = createServer((request, response) => {
5054
response.end(`${createHash('sha256').update(nodeArchive).digest('hex')} ${nodeRoot}.tar.gz\n`);
5155
return;
5256
}
53-
const body = request.url.endsWith(`/${nodeRoot}.tar.gz`)
54-
? nodeArchive
55-
: request.url === `/npm/-/npm-${version}.tgz`
56-
? npmArchive
57-
: undefined;
58-
if (!body) {
57+
let body;
58+
if (request.url.endsWith(`/${nodeRoot}.tar.gz`)) {
59+
body = nodeArchive;
60+
} else if (request.url === `/npm/-/npm-${version}.tgz`) {
61+
body = npmArchive;
62+
} else {
5963
response.writeHead(404).end();
6064
return;
6165
}
6266
downloads++;
63-
if (process.argv[2] === 'known') response.setHeader('Content-Length', body.length);
67+
if (process.argv[2] === 'known') {
68+
response.setHeader('Content-Length', body.length);
69+
}
6470
response.flushHeaders();
6571
// Exercise multiple progress redraws; only the completed screen is snapshotted.
72+
const chunkSize = Math.ceil(body.length / 8);
6673
let offset = 0;
67-
const timer = setInterval(() => {
68-
const end = Math.min(offset + Math.ceil(body.length / 8), body.length);
74+
const timer = setInterval(function sendChunk() {
75+
const end = Math.min(offset + chunkSize, body.length);
6976
response.write(body.subarray(offset, end));
7077
offset = end;
71-
if (offset === body.length) response.end();
78+
if (offset === body.length) {
79+
response.end();
80+
}
7281
}, 50);
7382
response.on('close', () => clearInterval(timer));
7483
});

‎crates/vp_shared/src/download_progress.rs‎

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -30,48 +30,50 @@ fn style_with_width(
3030
}
3131

3232
fn download_row(state: &ProgressState, message: &str, available: usize) -> Str {
33+
let has_total = state.len().is_some();
34+
let message_width = measure_text_width(message);
3335
let bytes = HumanBytes(state.pos());
34-
let counts = match state.len() {
36+
let mut compact_stats = match state.len() {
3537
Some(total) => format!("{bytes}/{}", HumanBytes(total)),
3638
None => format!("{bytes}"),
3739
};
3840
let speed = HumanBytes(state.per_sec() as u64);
39-
let details = match state.len() {
40-
Some(_) => format!("{counts} ({speed}/s, {:#})", HumanDuration(state.eta())),
41-
None => format!("{counts} ({speed}/s)"),
41+
let detailed_stats = if has_total {
42+
format!("{compact_stats} ({speed}/s, {:#})", HumanDuration(state.eta()))
43+
} else {
44+
format!("{compact_stats} ({speed}/s)")
4245
};
43-
let fixed_width = measure_text_width(message) + measure_text_width(&details) + 2;
44-
if state.len().is_some() {
45-
// Keep a useful bar only when the full message and statistics fit.
46-
// The leading space, brackets, and trailing space use three columns
47-
// in addition to the two spaces included in `fixed_width`.
48-
let bar_width = available.saturating_sub(fixed_width + 3);
46+
let text_width = message_width + measure_text_width(&detailed_stats);
47+
if has_total {
48+
// Reserve three spaces and two brackets, plus at least four bar columns.
49+
let bar_width = available.saturating_sub(text_width + 5);
4950
if bar_width >= 4 {
5051
let filled = (state.fraction() * bar_width as f32) as usize;
5152
let remaining = bar_width - filled;
5253
let bar = format!("{}{}", "#".repeat(filled), if remaining > 0 { ">" } else { "" });
5354
return format!(
54-
" {message} [{}{}] {details}",
55+
" {message} [{}{}] {detailed_stats}",
5556
style(bar).blue(),
5657
style("-".repeat(remaining.saturating_sub(1))).white(),
5758
);
5859
}
59-
} else if fixed_width <= available {
60-
return format!(" {message} {details}");
60+
} else if text_width + 2 <= available {
61+
return format!(" {message} {detailed_stats}");
6162
}
6263

6364
// On narrow terminals, omit the bar, speed, and ETA before shortening the
6465
// message. For very small panes, prefer a percentage to two byte counts.
65-
let counts = if state.len().is_some()
66-
&& measure_text_width(&counts) + 2 + measure_text_width(message).min(8) > available
67-
{
68-
format!("{:.0}%", state.fraction() * 100.0)
66+
let compact_width = measure_text_width(&compact_stats) + 2 + message_width.min(8);
67+
if has_total && compact_width > available {
68+
compact_stats = format!("{:.0}%", state.fraction() * 100.0);
69+
}
70+
let available_message_width = available.saturating_sub(measure_text_width(&compact_stats) + 2);
71+
let message = truncate_str(message, available_message_width, "");
72+
if message.is_empty() {
73+
format!(" {compact_stats}")
6974
} else {
70-
counts
71-
};
72-
let message_width = available.saturating_sub(measure_text_width(&counts) + 2);
73-
let message = truncate_str(message, message_width, "");
74-
if message.is_empty() { format!(" {counts}") } else { format!(" {message} {counts}") }
75+
format!(" {message} {compact_stats}")
76+
}
7577
}
7678

7779
#[cfg(test)]
@@ -114,19 +116,9 @@ mod tests {
114116
for position in [0, 5 * 1024 * 1024, 25 * 1024 * 1024, total] {
115117
progress.set_elapsed(Duration::from_secs(2));
116118
progress.set_position(position);
117-
for reset_speed in [false, true] {
118-
if reset_speed {
119-
progress.reset_eta();
120-
}
121-
progress.force_draw();
122-
let screen = term.contents();
123-
let lines: Vec<_> = screen.lines().collect();
124-
assert_eq!(lines.len(), 2, "width {width}: {screen}");
125-
assert_eq!(lines[0], earlier);
126-
assert!(measure_text_width(lines[1]) <= usize::from(width));
127-
let moves = term.moves_since_last_check();
128-
assert!(!moves.contains("Up("), "width {width}: {moves}");
129-
}
119+
assert_progress_row(&progress, &term, earlier);
120+
progress.reset_eta();
121+
assert_progress_row(&progress, &term, earlier);
130122
}
131123
}
132124

@@ -140,6 +132,18 @@ mod tests {
140132
}
141133
}
142134

135+
fn assert_progress_row(progress: &ProgressBar, term: &InMemoryTerm, earlier: &str) {
136+
progress.force_draw();
137+
let width = term.width();
138+
let screen = term.contents();
139+
let lines: Vec<_> = screen.lines().collect();
140+
assert_eq!(lines.len(), 2, "width {width}: {screen}");
141+
assert_eq!(lines[0], earlier);
142+
assert!(measure_text_width(lines[1]) <= usize::from(width));
143+
let moves = term.moves_since_last_check();
144+
assert!(!moves.contains("Up("), "width {width}: {moves}");
145+
}
146+
143147
#[test]
144148
fn compact_layout_preserves_download_counts() {
145149
for (width, has_bar) in [(40, false), (60, false), (80, true), (138, true)] {

0 commit comments

Comments
 (0)