Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
101 lines
3.5 KiB
TypeScript
101 lines
3.5 KiB
TypeScript
import { execFileSync } from "node:child_process";
|
|
import { fileURLToPath } from "node:url";
|
|
import { describe, expect, it } from "vitest";
|
|
import { formatSize, truncateHead } from "../src/truncate.ts";
|
|
|
|
const encoder = new TextEncoder();
|
|
|
|
function byteLength(content: string): number {
|
|
return encoder.encode(content).length;
|
|
}
|
|
|
|
describe("truncate utilities", () => {
|
|
it("reports UTF-8 byte counts in truncation results", () => {
|
|
const content = "aé🙂\nb";
|
|
const result = truncateHead(content, { maxBytes: 100, maxLines: 10 });
|
|
|
|
expect(result.truncated).toBe(false);
|
|
expect(result.totalBytes).toBe(byteLength(content));
|
|
expect(result.outputBytes).toBe(byteLength(content));
|
|
expect(result.totalBytes).toBe(9);
|
|
});
|
|
|
|
it("counts UTF-8 bytes and truncates correctly in a runtime without Buffer", () => {
|
|
const inputs = ["", "ascii", "é", "中", "🙂", "\ud83d", "\ude42", "a\ud83d\ude42b", "\u07ff\u0800\uffff"];
|
|
const output = execFileSync(
|
|
process.execPath,
|
|
[
|
|
// Workspace packages from source, as the test runner resolves them, not from a possibly stale build.
|
|
"--conditions=source",
|
|
"--import",
|
|
new URL("./fixtures/delete-buffer.ts", import.meta.url).href,
|
|
fileURLToPath(new URL("./fixtures/utf8-byte-length-without-buffer.ts", import.meta.url)),
|
|
JSON.stringify(inputs),
|
|
],
|
|
{ encoding: "utf8" },
|
|
);
|
|
const result = JSON.parse(output) as {
|
|
bufferAvailable: boolean;
|
|
lengths: number[];
|
|
head: { content: string; outputBytes: number; truncatedBy: string | null };
|
|
};
|
|
expect(result.bufferAvailable).toBe(false);
|
|
expect(result.lengths).toEqual(inputs.map(byteLength));
|
|
expect(result.head).toMatchObject({ content: "aé🙂", outputBytes: 7, truncatedBy: "bytes" });
|
|
});
|
|
|
|
it("does not count a trailing newline as an extra line", () => {
|
|
const content = `${Array.from({ length: 3 }, () => "line").join("\n")}\n`;
|
|
const head = truncateHead(content, { maxBytes: 100, maxLines: 3 });
|
|
|
|
expect(head).toMatchObject({ truncated: false, totalLines: 3, outputLines: 3 });
|
|
});
|
|
|
|
it("truncates head by line limits", () => {
|
|
const content = "one\ntwo\nthree\nfour";
|
|
expect(truncateHead(content, { maxBytes: 100, maxLines: 2 })).toMatchObject({
|
|
content: "one\ntwo",
|
|
truncated: true,
|
|
truncatedBy: "lines",
|
|
totalLines: 4,
|
|
outputLines: 2,
|
|
});
|
|
});
|
|
|
|
it("reports bytes when only a trailing newline exceeds limits at the line cap", () => {
|
|
expect(truncateHead("hello\nworld\n", { maxBytes: 11, maxLines: 2 })).toMatchObject({
|
|
content: "hello\nworld",
|
|
truncated: true,
|
|
truncatedBy: "bytes",
|
|
totalLines: 2,
|
|
outputLines: 2,
|
|
});
|
|
});
|
|
|
|
it("truncates head on UTF-8 byte limits without partial lines", () => {
|
|
const content = "éé\nabc";
|
|
const result = truncateHead(content, { maxBytes: 4, maxLines: 10 });
|
|
|
|
expect(result.content).toBe("éé");
|
|
expect(result.truncated).toBe(true);
|
|
expect(result.truncatedBy).toBe("bytes");
|
|
expect(result.outputBytes).toBe(4);
|
|
expect(result.firstLineExceedsLimit).toBe(false);
|
|
});
|
|
|
|
it("reports head truncation when the first line exceeds the byte limit", () => {
|
|
const result = truncateHead("éé\nabc", { maxBytes: 3, maxLines: 10 });
|
|
|
|
expect(result.content).toBe("");
|
|
expect(result.truncated).toBe(true);
|
|
expect(result.truncatedBy).toBe("bytes");
|
|
expect(result.firstLineExceedsLimit).toBe(true);
|
|
});
|
|
|
|
it("formats sizes", () => {
|
|
expect(formatSize(1023)).toBe("1023B");
|
|
expect(formatSize(1536)).toBe("1.5KB");
|
|
expect(formatSize(3 * 1024 * 1024)).toBe("3.0MB");
|
|
});
|
|
});
|