diff --git a/src/download/progress.test.ts b/src/download/progress.test.ts new file mode 100644 index 000000000..441004e46 --- /dev/null +++ b/src/download/progress.test.ts @@ -0,0 +1,32 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { createProgressBar } from './progress.js'; + +describe('download progress display', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('clamps percentages above 100 to keep the progress bar renderable', () => { + const write = vi.spyOn(process.stderr, 'write').mockImplementation(() => true); + const progress = createProgressBar('file.bin', 0, 1); + + expect(() => progress.update(150, 100)).not.toThrow(); + expect(write).toHaveBeenCalledWith(expect.stringContaining('100%')); + }); + + it('clamps negative percentages to zero', () => { + const write = vi.spyOn(process.stderr, 'write').mockImplementation(() => true); + const progress = createProgressBar('file.bin', 0, 1); + + expect(() => progress.update(-10, 100)).not.toThrow(); + expect(write).toHaveBeenCalledWith(expect.stringContaining('0%')); + }); + + it('renders zero percent when the total size is unknown', () => { + const write = vi.spyOn(process.stderr, 'write').mockImplementation(() => true); + const progress = createProgressBar('file.bin', 0, 1); + + expect(() => progress.update(50, 0)).not.toThrow(); + expect(write).toHaveBeenCalledWith(expect.stringContaining('0%')); + }); +}); diff --git a/src/download/progress.ts b/src/download/progress.ts index 972e4d836..352bfe284 100644 --- a/src/download/progress.ts +++ b/src/download/progress.ts @@ -47,7 +47,7 @@ export function createProgressBar(filename: string, index: number, total: number return { update(current: number, totalBytes: number, label?: string) { - const percent = totalBytes > 0 ? Math.round((current / totalBytes) * 100) : 0; + const percent = clampPercent(totalBytes > 0 ? Math.round((current / totalBytes) * 100) : 0); const bar = createBar(percent); const size = totalBytes > 0 ? formatBytes(totalBytes) : ''; const extra = label ? ` ${label}` : ''; @@ -64,6 +64,11 @@ export function createProgressBar(filename: string, index: number, total: number }; } +function clampPercent(percent: number): number { + if (!Number.isFinite(percent)) return 0; + return Math.max(0, Math.min(100, percent)); +} + /** * Create a progress bar string. */