mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 08:24:46 +00:00
87 lines
3.0 KiB
TypeScript
87 lines
3.0 KiB
TypeScript
import { expect, test } from "vitest";
|
|
import { DiffStream } from "./stream.js";
|
|
import { DiffOpcode, type DiffBegin } from "@getpaseo/protocol/binary-frames/index";
|
|
import type { DiffState } from "./index.js";
|
|
const entry = {
|
|
path: "a.ts",
|
|
oldPath: null,
|
|
change: "modified" as const,
|
|
content: "text" as const,
|
|
additions: 1,
|
|
deletions: 0,
|
|
body: true,
|
|
};
|
|
const begin = (revision: number, baseRevision = 0): DiffBegin => ({
|
|
opcode: DiffOpcode.Begin,
|
|
slot: 0,
|
|
revision,
|
|
baseRevision,
|
|
baseSha: "a",
|
|
headSha: "b",
|
|
mergeBaseSha: "",
|
|
entries: [entry],
|
|
});
|
|
const end = (revision: number) =>
|
|
({
|
|
opcode: DiffOpcode.End,
|
|
slot: 0,
|
|
revision,
|
|
diffTooLarge: false,
|
|
omitted: [],
|
|
error: null,
|
|
}) as const;
|
|
test("publishes the initial tree, commits on End and buffers refreshes with stable equal files", () => {
|
|
const states: DiffState[] = [];
|
|
const stream = new DiffStream((state) => states.push(state));
|
|
stream.receive(begin(1));
|
|
expect(states[0]).toMatchObject({ phase: "loading", entries: [{ path: "a.ts" }] });
|
|
stream.receive({
|
|
opcode: DiffOpcode.Bodies,
|
|
slot: 0,
|
|
revision: 1,
|
|
files: [{ path: "a.ts", hunks: [] }],
|
|
});
|
|
expect(states).toHaveLength(1);
|
|
stream.receive(end(1));
|
|
const ready = states[1];
|
|
if (ready.phase !== "ready") throw new Error("Expected ready");
|
|
stream.receive(begin(2, 1));
|
|
stream.receive({
|
|
opcode: DiffOpcode.Bodies,
|
|
slot: 0,
|
|
revision: 2,
|
|
files: [{ path: "a.ts", hunks: [] }],
|
|
});
|
|
expect(states).toHaveLength(2);
|
|
stream.receive(end(2));
|
|
const refreshed = states[2];
|
|
if (refreshed.phase !== "ready") throw new Error("Expected ready");
|
|
expect(refreshed.files[0]).toBe(ready.files[0]);
|
|
});
|
|
test("superseded and stale frames cannot leave the client loading; End is authoritative", () => {
|
|
const states: DiffState[] = [];
|
|
const stream = new DiffStream((s) => states.push(s));
|
|
stream.receive(begin(1));
|
|
stream.receive(begin(2));
|
|
stream.receive(end(1));
|
|
expect(() => stream.receive(end(2))).toThrow(/Missing/);
|
|
stream.receive(begin(3));
|
|
stream.receive({ ...end(3), error: { code: "REF_NOT_FOUND", message: "gone" } });
|
|
expect(states.at(-1)).toMatchObject({ phase: "error", error: { code: "REF_NOT_FOUND" } });
|
|
stream.receive({ ...begin(4), entries: [] });
|
|
stream.receive(end(4));
|
|
expect(states.at(-1)).toMatchObject({ phase: "ready", files: [] });
|
|
});
|
|
test("unchanged entries retain hunks, omissions override cache, removed paths disappear", () => {
|
|
const states: DiffState[] = [];
|
|
const stream = new DiffStream((s) => states.push(s));
|
|
stream.receive(begin(1));
|
|
stream.receive({ ...end(1), omitted: ["a.ts"], diffTooLarge: true });
|
|
stream.receive({ ...begin(2, 1), entries: [{ ...entry, body: false }] });
|
|
stream.receive(end(2));
|
|
expect(states.at(-1)).toMatchObject({ phase: "ready", files: [{ status: "too_large" }] });
|
|
stream.receive({ ...begin(3, 2), entries: [] });
|
|
stream.receive(end(3));
|
|
expect(states.at(-1)).toMatchObject({ phase: "ready", files: [] });
|
|
});
|