char-slop/berry

multipurpose self-modifiable agentic harness

git clone https://git.t4t.associates/char-slop/berry

Charlotte Somtest and palette cleanups: one source for the 16-color terminal palette, no change-detector testsb3a4fb7

main
5.1 KiB143 linesraw
1import { assertEquals } from "@std/assert";
2import { TERMINAL_PALETTE } from "@berry/util/palette.ts";
3import { ansiParser, type AnsiRun, ansiRuns } from "./ansi.ts";
4
5Deno.test("ANSI output preserves literal text, whitespace, and newlines", () => {
6  const text = '  <script>alert("&")</script>\n\tend\n';
7  assertEquals(ansiRuns(text), [{ text, style: {} }]);
8  assertEquals(ansiRuns(""), []);
9});
10
11Deno.test("ANSI colours and emphasis persist across lines until reset", () => {
12  assertEquals(ansiRuns("before\x1b[1;31mred\nline\x1b[22m normal\x1b[39m after"), [
13    { text: "before", style: {} },
14    { text: "red\nline", style: { color: TERMINAL_PALETTE[1], "font-weight": "bold" } },
15    { text: " normal", style: { color: TERMINAL_PALETTE[1] } },
16    { text: " after", style: {} },
17  ]);
18  assertEquals(ansiRuns("\x1b[2;94;103mdim\x1b[mplain"), [
19    {
20      text: "dim",
21      style: {
22        color: TERMINAL_PALETTE[12],
23        "background-color": TERMINAL_PALETTE[11],
24        opacity: "0.5",
25      },
26    },
27    { text: "plain", style: {} },
28  ]);
29});
30
31Deno.test("ANSI extended colours support the palette, cube, grayscale, and RGB", () => {
32  assertEquals(
33    ansiRuns("\x1b[38;5;2mpalette\x1b[38;5;196mcube\x1b[48;5;244mgray\x1b[38;2;12;34;56mrgb"),
34    [
35      { text: "palette", style: { color: TERMINAL_PALETTE[2] } },
36      { text: "cube", style: { color: "rgb(255, 0, 0)" } },
37      {
38        text: "gray",
39        style: { color: "rgb(255, 0, 0)", "background-color": "rgb(128, 128, 128)" },
40      },
41      {
42        text: "rgb",
43        style: { color: "rgb(12, 34, 56)", "background-color": "rgb(128, 128, 128)" },
44      },
45    ],
46  );
47});
48
49Deno.test("ANSI resets foreground and background independently and clears decorations", () => {
50  assertEquals(ansiRuns("\x1b[3;4;9;32;41mstyled\x1b[39mbackground\x1b[49;23;24;29mplain"), [
51    {
52      text: "styled",
53      style: {
54        color: TERMINAL_PALETTE[2],
55        "background-color": TERMINAL_PALETTE[1],
56        "font-style": "italic",
57        "text-decoration": "underline line-through",
58      },
59    },
60    {
61      text: "background",
62      style: {
63        "background-color": TERMINAL_PALETTE[1],
64        "font-style": "italic",
65        "text-decoration": "underline line-through",
66      },
67    },
68    { text: "plain", style: {} },
69  ]);
70});
71
72Deno.test("ANSI reverse video uses defaults and can be turned off", () => {
73  assertEquals(ansiRuns("\x1b[7mreverse\x1b[27mplain"), [
74    { text: "reverse", style: { color: "var(--panel)", "background-color": "var(--text)" } },
75    { text: "plain", style: {} },
76  ]);
77});
78
79Deno.test("unsupported terminal controls and OSC links keep their visible text", () => {
80  const text =
81    "\x1b[?25l\x1b[2K\x1b[H\x1b]0;title\x07\x1b]8;;https://example.com\x1b\\link\x1b]8;;\x07\x1b[999m end";
82  assertEquals(ansiRuns(text).map((run) => run.text).join(""), "link end");
83});
84
85Deno.test("incomplete streaming escapes stay hidden and render once complete", () => {
86  const partial = "start\x1b[38;2;12;";
87  assertEquals(ansiRuns(partial), [{ text: "start", style: {} }]);
88  assertEquals(ansiRuns(partial + "34;56mend"), [
89    { text: "start", style: {} },
90    { text: "end", style: { color: "rgb(12, 34, 56)" } },
91  ]);
92  assertEquals(ansiRuns("text\x1b]unfinished title"), [{ text: "text", style: {} }]);
93});
94
95Deno.test("malformed extended colours do not consume their components as SGR styles", () => {
96  assertEquals(ansiRuns("\x1b[38;2;999;1;2mtext\x1b[48;5;999mend"), [
97    { text: "text", style: {} },
98    { text: "end", style: {} },
99  ]);
100});
101
102Deno.test("ANSI streaming preserves styles and hides escapes at every chunk boundary", () => {
103  const sources = [
104    "before\x1b[1;31mred\nline\x1b[22m normal\x1b[39m after",
105    "\x1b[38;2;12;34;56mrgb\x1b[48;5;244mgray\x1b[0mplain",
106    "\x1b]8;;https://example.com\x1b\\link\x1b]8;;\x07 end",
107    "\x1b[?25l\x1b[2K\x1b[H\x1b]0;title\x07text\x1b(B end",
108    "text\x1b[38;2;1;",
109  ];
110  const characters = (runs: AnsiRun[]) =>
111    runs.flatMap(({ text, style }) => [...text].map((text) => ({ text, style })));
112  for (const source of sources) {
113    const expected = characters(ansiRuns(source));
114    for (let split = 0; split <= source.length; split++) {
115      const parser = ansiParser();
116      assertEquals(
117        characters([
118          ...parser.write(source.slice(0, split)),
119          ...parser.write(source.slice(split)),
120        ]),
121        expected,
122        `split at ${split}`,
123      );
124    }
125    const parser = ansiParser();
126    assertEquals(characters([...source].flatMap((char) => parser.write(char))), expected);
127  }
128});
129
130Deno.test("ANSI streaming discards long control payloads without leaking them or losing later styles", () => {
131  const parser = ansiParser();
132  assertEquals(parser.write("\x1b]0;"), []);
133  for (let i = 0; i < 100; i++) assertEquals(parser.write("x".repeat(10000)), []);
134  assertEquals(parser.write("\x1b"), []);
135  assertEquals(parser.write("\\\x1b[32mgreen"), [
136    { text: "green", style: { color: TERMINAL_PALETTE[2] } },
137  ]);
138  assertEquals(parser.write("\x1b[" + "1;".repeat(10000)), []);
139  assertEquals(parser.write("mstill green\x1b[mplain"), [
140    { text: "still green", style: { color: TERMINAL_PALETTE[2] } },
141    { text: "plain", style: {} },
142  ]);
143});