File size: 3,767 Bytes
e249c6d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import fs from "node:fs";
import * as os from "node:os";
import path from "node:path";
import { Text } from "@earendil-works/pi-tui";
import { describe, expect, it } from "vitest";
import {
  appendSessionToolTruncationWarning,
  reuseTextComponent,
  shortenPath,
  trimTrailingEmptyLines,
} from "./render-utils.js";

const theme = {
  fg: (color: string, text: string) => `<${color}>${text}</${color}>`,
};

describe("appendSessionToolTruncationWarning", () => {
  it("leaves output unchanged when no truncation metadata is present", () => {
    expect(appendSessionToolTruncationWarning("output", theme, {})).toBe("output");
  });

  it("combines limit, byte, and additional warnings in order", () => {
    expect(
      appendSessionToolTruncationWarning("output", theme, {
        limit: { count: 5, noun: "matches" },
        truncation: { truncated: true, maxBytes: 1024 },
        additionalWarnings: ["some lines truncated"],
      }),
    ).toBe(
      "output\n<warning>[Truncated: 5 matches limit, 1.0KB limit, some lines truncated]</warning>",
    );
  });
});

describe("trimTrailingEmptyLines", () => {
  it.each([
    {
      name: "removes multiple trailing empty lines",
      lines: ["first", "second", "", ""],
      expected: ["first", "second"],
    },
    { name: "removes all-empty input", lines: ["", ""], expected: [] },
    {
      name: "keeps leading and interior empty lines",
      lines: ["", "first", "", "second", ""],
      expected: ["", "first", "", "second"],
    },
    {
      name: "keeps whitespace-only trailing lines",
      lines: ["first", " ", "\t"],
      expected: ["first", " ", "\t"],
    },
  ])("$name", ({ lines, expected }) => {
    expect(trimTrailingEmptyLines(lines)).toEqual(expected);
  });

  it("does not mutate the caller-owned lines", () => {
    const lines = ["first", "", ""];
    const original = [...lines];

    trimTrailingEmptyLines(lines);

    expect(lines).toEqual(original);
  });
});

describe("reuseTextComponent", () => {
  it("creates a zero-padding Text component when no prior component exists", () => {
    const component = reuseTextComponent(undefined, "hello");

    expect(component).toBeInstanceOf(Text);
    expect(component.render(5)).toEqual(["hello"]);
  });

  it("reuses the prior Text component and invalidates its rendered content", () => {
    const component = new Text("before", 0, 0);
    expect(component.render(6)).toEqual(["before"]);

    const reused = reuseTextComponent(component, "after!");

    expect(reused).toBe(component);
    expect(reused.render(6)).toEqual(["after!"]);
  });
});

describe("shortenPath", () => {
  const home = os.homedir();

  it("shortens paths inside the home directory", () => {
    expect(shortenPath(`${home}/projects/app.ts`)).toBe("~/projects/app.ts");
  });

  it("collapses the home directory itself", () => {
    expect(shortenPath(home)).toBe("~");
  });

  it("leaves a sibling directory that merely shares the prefix untouched", () => {
    // `${home}extra` starts with `home` as a substring but is not under it,
    // so it must not be rewritten to `~extra`.
    expect(shortenPath(`${home}extra/app.ts`)).toBe(`${home}extra/app.ts`);
  });

  it("leaves unrelated paths untouched", () => {
    expect(shortenPath("/var/log/syslog")).toBe("/var/log/syslog");
  });

  it.skipIf(process.platform !== "win32")("shortens real Windows home casing aliases", () => {
    const homeAlias = home.toUpperCase();
    expect(fs.statSync(homeAlias).isDirectory()).toBe(true);

    expect(shortenPath(path.join(homeAlias, "projects", "app.ts"))).toBe(
      `~${path.sep}projects${path.sep}app.ts`,
    );
  });

  it("returns an empty string for non-string input", () => {
    expect(shortenPath(undefined)).toBe("");
  });
});