1import { describe, expect, it } from "vite-plus/test";
2
3import {
4 decodeEventStream,
5 encodeEventStream,
6 Root,
7 type StreamNode,
8 type StreamEvent,
9} from "./progress.ts";
10
11describe("progress transport", () => {
12 it("reconstructs a late snapshot with nested nodes and empty labels", async () => {
13 const source = new Root();
14 const parent = source.start("parent", { total: 8, value: 3 });
15 parent.start("", { total: 4, value: 2 });
16 const target = new Root();
17 const input = encodeEventStream(source, { throttleMs: 0 });
18 const decoding = decodeEventStream(input, target);
19 await new Promise<void>((resolve) =>
20 target.on("node-start", () => {
21 if (target.active[0]?.children.length) resolve();
22 }),
23 );
24 expect(target.active[0]?.text).toBe("parent");
25 expect(target.active[0]?.value).toBe(3);
26 expect(target.active[0]?.children[0]?.text).toBe("");
27 expect(target.active[0]?.children[0]?.value).toBe(2);
28 decoding.cancel();
29 expect(target.active).toHaveLength(0);
30 });
31
32 it("applies zero values, cleared text, and a cleared estimate", async () => {
33 const target = new Root();
34 let controller: ReadableStreamDefaultController<StreamEvent>;
35 const input = new ReadableStream<StreamEvent>({
36 start(value) {
37 controller = value;
38 },
39 });
40 const decoding = decodeEventStream<void, { checkpoint: [] }>(input, target);
41 const checkpoint = () => new Promise<void>((resolve) => decoding.on("checkpoint", resolve));
42 let ready = checkpoint();
43 controller!.enqueue([
44 -1,
45 { k: 1 as StreamNode["k"], t: "task", v: 5, e: 10, E: 2000 },
46 [1],
47 ["checkpoint"],
48 ]);
49 await ready;
50 ready = checkpoint();
51 controller!.enqueue([
52 0,
53 { k: 1 as StreamNode["k"], t: "", v: 0, e: 0, E: null },
54 ["checkpoint"],
55 ]);
56 await ready;
57 expect(target.active[0]).toMatchObject({ text: "", value: 0, total: 0, estimatedTime: null });
58 controller!.enqueue([0, ["end", null]]);
59 controller!.close();
60 await decoding;
61 expect(target.active).toHaveLength(0);
62 });
63
64 it("cancels a pending read and removes managed nodes without ending the transport", async () => {
65 const target = new Root();
66 const cancelled = Promise.withResolvers<void>();
67 let controller: ReadableStreamDefaultController<StreamEvent>;
68 const input = new ReadableStream<StreamEvent>({
69 start(value) {
70 controller = value;
71 },
72 cancel() {
73 cancelled.resolve();
74 },
75 });
76 const decoding = decodeEventStream<void, { checkpoint: [] }>(input, target);
77 const ready = new Promise<void>((resolve) => decoding.on("checkpoint", resolve));
78 controller!.enqueue([-1, { k: 1 as StreamNode["k"], t: "interrupted" }, [1], ["checkpoint"]]);
79 await ready;
80 decoding.cancel();
81 await cancelled.promise;
82 expect(target.active).toHaveLength(0);
83 expect(input.locked).toBe(false);
84 });
85});