1import { afterEach, expect, it, vi } from "vite-plus/test";
2
3import { createUpdateProgress } from "./updateProgress.ts";
4
5afterEach(() => {
6 vi.unstubAllEnvs();
7 vi.restoreAllMocks();
8});
9
10function terminal(isTTY = true, columns = 80) {
11 vi.stubEnv("TERM", "xterm");
12 vi.stubEnv("NO_COLOR", "1");
13 let text = "";
14 const progress = createUpdateProgress({
15 isTTY,
16 columns,
17 write(chunk: string | Uint8Array) {
18 text += chunk;
19 return true;
20 },
21 });
22 return { ...progress, text: () => text };
23}
24
25it("keeps redirected output readable without per-chunk updates or escapes", () => {
26 const output = terminal(false);
27 for (let received = 0; received <= 100; received++) {
28 output.report({ stage: "download", received, total: 100 });
29 }
30 output.report({ stage: "verify" });
31 output.report({ stage: "extract" });
32 output.report({ stage: "validate" });
33 output.finish();
34 expect(output.text()).toBe(
35 " Downloading...\n Verifying the download...\n Extracting T3 Code...\n Checking the new executable...\n",
36 );
37});
38
39it("throttles redraws, shows the final byte count, and closes the line once", () => {
40 const now = vi.spyOn(performance, "now").mockReturnValue(0);
41 const output = terminal();
42 output.report({ stage: "download", received: 0, total: 1024 ** 2 });
43 const initial = output.text();
44 for (let received = 1; received < 100; received++) {
45 output.report({ stage: "download", received, total: 1024 ** 2 });
46 }
47 expect(output.text()).toBe(initial);
48 now.mockReturnValue(100);
49 output.report({ stage: "download", received: 524288, total: 1024 ** 2 });
50 output.report({ stage: "download", received: 1024 ** 2, total: 1024 ** 2 });
51 output.finish();
52 output.finish();
53 expect(output.text().match(/100%/g)).toHaveLength(1);
54 expect(output.text()).toContain("50% 0.5 / 1.0 MB");
55 expect(output.text()).toMatch(/100% 1.0 \/ 1.0 MB\n$/);
56});
57
58it("shows bytes for an unknown size and leaves a clean line on interruption", () => {
59 const output = terminal();
60 output.report({ stage: "download", received: 524288, total: undefined });
61 output.finish();
62 expect(output.text()).toMatch(/0.5 MB\n$/);
63 expect(output.text()).not.toContain("%");
64});
65
66it.each([1, 2, 7, 8, 9, 30])("fits a %i-column terminal without wrapping", (columns) => {
67 const output = terminal(true, columns);
68 output.report({ stage: "download", received: 50, total: 100 });
69 for (const line of output.text().split("\r\x1b[2K")) {
70 expect(line.length).toBeLessThan(columns);
71 }
72});
73
74it("draws the final size of a fast chunked download even inside the throttle window", () => {
75 vi.spyOn(performance, "now").mockReturnValue(0);
76 const output = terminal();
77 output.report({ stage: "download", received: 0, total: undefined });
78 output.report({ stage: "download", received: 524288, total: undefined });
79 output.report({ stage: "download", received: 524288, total: 524288 });
80 output.report({ stage: "verify" });
81 expect(output.text()).toContain("100% 0.5 / 0.5 MB\n\r\x1b[2K Verifying");
82});
83
84it("uses color only in capable terminals that have not requested NO_COLOR", () => {
85 const output = terminal();
86 // Create a new renderer after changing the color preference.
87 vi.stubEnv("NO_COLOR", undefined);
88 const chunks: string[] = [];
89 const progress = createUpdateProgress({
90 isTTY: true,
91 columns: 80,
92 write(chunk) {
93 chunks.push(String(chunk));
94 return true;
95 },
96 });
97 progress.report({ stage: "download", received: 50, total: 100 });
98 expect(chunks.join("")).toContain("\x1b[94m");
99 output.report({ stage: "download", received: 50, total: 100 });
100 expect(output.text()).not.toContain("\x1b[94m");
101});