@xynogen/pix-pretty 1.18.4 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,382 +0,0 @@
1
- import { describe, expect, jest, test } from "bun:test";
2
- import { type OverlayUI, showOverlay } from "./gate-overlay.ts";
3
- import { modalOverlayOptions } from "./modal-frame.ts";
4
-
5
- // ── Mock host ─────────────────────────────────────────────────────────────────
6
- //
7
- // Drive showOverlay deterministically without a real TUI. The mock invokes the
8
- // builder callback (so components initialise + wire their handlers), captures
9
- // the rendered lines, then hands a `drive(comp, done)` hook to the test which
10
- // triggers selectList.onSelect / maskedInput.onSubmit / etc via the real
11
- // component instances — exactly what real keyboard input would do.
12
-
13
- const theme = {
14
- fg: (_c: string, t: string) => t,
15
- bg: (_c: string, t: string) => t,
16
- bold: (t: string) => t,
17
- };
18
-
19
- interface Wired {
20
- render(w: number): string[];
21
- invalidate(): void;
22
- handleInput(d: string): void;
23
- }
24
-
25
- /**
26
- * Build a mock UI. `drive` receives the rendered lines and a `feed` fn that
27
- * pushes raw input strings into the component. To trigger a selection we feed
28
- * the SelectList keys; simpler: we expose the live component so the test can
29
- * call its handlers. We do the latter via the captured component ref.
30
- */
31
- function makeUI(
32
- onReady: (comp: Wired, finish: (v: unknown) => void) => void,
33
- onOptions?: (options: unknown) => void,
34
- renderTheme = theme,
35
- ): OverlayUI {
36
- return {
37
- custom: async <T>(
38
- cb: (
39
- tui: { requestRender(): void },
40
- th: typeof theme,
41
- kb: unknown,
42
- done: (v: T) => void,
43
- ) => Wired,
44
- options?: unknown,
45
- ): Promise<T | undefined> => {
46
- onOptions?.(options);
47
- let resolved: T | undefined;
48
- const done = (v: T) => {
49
- resolved = v;
50
- };
51
- const comp = cb({ requestRender: () => {} }, renderTheme, undefined, done);
52
- comp.render(80); // initialise render path
53
- onReady(comp, done as (v: unknown) => void);
54
- return resolved;
55
- },
56
- };
57
- }
58
-
59
- // SelectList handles "\r" (enter) to select the highlighted item, and arrow
60
- // keys to move. The first item is highlighted by default.
61
- const ENTER = "\r";
62
- const DOWN = "\x1b[B";
63
-
64
- describe("showOverlay — confirm mode", () => {
65
- test("uses configured global overlay bounds", async () => {
66
- let options: unknown;
67
- await showOverlay(
68
- makeUI(
69
- (comp) => comp.handleInput(ENTER),
70
- (value) => {
71
- options = value;
72
- },
73
- ),
74
- { mode: "confirm", title: "T" },
75
- );
76
- expect(options).toEqual({ overlay: true, overlayOptions: modalOverlayOptions() });
77
- });
78
-
79
- test("selecting the approve choice (first) returns approved", async () => {
80
- const result = await showOverlay(
81
- makeUI((comp) => {
82
- comp.handleInput(ENTER); // select item 0 = "yes"
83
- }),
84
- { mode: "confirm", title: "T", timeoutMs: 0 },
85
- );
86
- expect(result.action).toBe("approved");
87
- expect(result.password).toBeUndefined();
88
- });
89
-
90
- test("selecting the deny choice (second) returns denied", async () => {
91
- const result = await showOverlay(
92
- makeUI((comp) => {
93
- comp.handleInput(DOWN); // move to item 1 = "no"
94
- comp.handleInput(ENTER);
95
- }),
96
- { mode: "confirm", title: "T", timeoutMs: 0 },
97
- );
98
- expect(result.action).toBe("denied");
99
- });
100
-
101
- test("deny-first ordering: item 0 is the deny choice when configured so", async () => {
102
- const result = await showOverlay(
103
- makeUI((comp) => {
104
- comp.handleInput(ENTER); // select item 0
105
- }),
106
- {
107
- mode: "confirm",
108
- title: "Critical",
109
- timeoutMs: 0,
110
- approveValue: "yes",
111
- choices: [
112
- { value: "no", label: "Block", description: "deny" },
113
- { value: "yes", label: "Allow", description: "approve" },
114
- ],
115
- },
116
- );
117
- // item 0 = "no" => not the approveValue => denied
118
- expect(result.action).toBe("denied");
119
- });
120
-
121
- test("renders title and body lines", async () => {
122
- let captured: string[] = [];
123
- await showOverlay(
124
- makeUI((comp) => {
125
- captured = comp.render(80);
126
- comp.handleInput(ENTER);
127
- }),
128
- {
129
- mode: "confirm",
130
- title: "MY TITLE",
131
- body: ["body-line-x"],
132
- timeoutMs: 0,
133
- },
134
- );
135
- const joined = captured.join("\n");
136
- expect(joined).toContain("MY TITLE");
137
- expect(joined).toContain("body-line-x");
138
- });
139
-
140
- test("uses semantic colors for transfer details", async () => {
141
- const coloredTheme = {
142
- fg: (color: string, text: string) => `<${color}>${text}</${color}>`,
143
- bg: (_color: string, text: string) => text,
144
- bold: (text: string) => text,
145
- };
146
- let captured: string[] = [];
147
- await showOverlay(
148
- makeUI(
149
- (comp) => {
150
- captured = comp.render(100);
151
- comp.handleInput(ENTER);
152
- },
153
- undefined,
154
- coloredTheme,
155
- ),
156
- {
157
- mode: "confirm",
158
- title: "SSH FILE TRANSFER",
159
- body: [
160
- "Intent: Copy a release artifact",
161
- "Command: scp app.tar.gz host:/tmp",
162
- "Host: deploy@example.com",
163
- "Direction: Download",
164
- "From: /srv/releases/app.tar.gz",
165
- "To: /tmp/app.tar.gz",
166
- "Mode: Single item",
167
- "Warning: existing destination may be overwritten",
168
- "Auth: SSH key (no password)",
169
- ],
170
- timeoutMs: 0,
171
- },
172
- );
173
- const joined = captured.join("\n");
174
- expect(joined).toContain("<dim>Intent:</dim> <text>Copy a release artifact</text>");
175
- expect(joined).toContain("<dim>Command:</dim> <dim>scp app.tar.gz host:/tmp</dim>");
176
- expect(joined).toContain("<dim>Host:</dim> <accent>deploy@example.com</accent>");
177
- expect(joined).toContain("<dim>Direction:</dim> <warning>Download</warning>");
178
- expect(joined).toContain("<dim>From:</dim> <text>/srv/releases/app.tar.gz</text>");
179
- expect(joined).toContain("<dim>To:</dim> <accent>/tmp/app.tar.gz</accent>");
180
- expect(joined).toContain("<warning>Warning: existing destination may be overwritten</warning>");
181
- expect(joined).toContain("<dim>Auth:</dim> <success>SSH key (no password)</success>");
182
- });
183
-
184
- // Regression guard (readability fix, pix-pretty 1.18.4): Intent:/Command:
185
- // lines must go through the label/value colour map — a <dim> label plus a
186
- // semantic value — never a bare bright value with the label stripped off.
187
- // Pre-1.18.4 they were sliced and dumped as bright <text> with no label,
188
- // which was hard to read on the modal background. Command value must be <dim>.
189
- test("Intent/Command body lines keep a dim label and never drop it", async () => {
190
- const coloredTheme = {
191
- fg: (color: string, text: string) => `<${color}>${text}</${color}>`,
192
- bg: (_color: string, text: string) => text,
193
- bold: (text: string) => text,
194
- };
195
- let captured: string[] = [];
196
- await showOverlay(
197
- makeUI(
198
- (comp) => {
199
- captured = comp.render(100);
200
- comp.handleInput(ENTER);
201
- },
202
- undefined,
203
- coloredTheme,
204
- ),
205
- {
206
- mode: "sudo",
207
- title: "ROOT COMMAND REQUEST",
208
- accent: "error",
209
- body: ["Intent: install a package", "Command: apt install foo"],
210
- timeoutMs: 0,
211
- },
212
- );
213
- const joined = captured.join("\n");
214
- // Command label + value both dim; intent value stays readable text but is
215
- // always prefixed by a dim label (never a bare label-less value).
216
- expect(joined).toContain("<dim>Command:</dim> <dim>apt install foo</dim>");
217
- expect(joined).toContain("<dim>Intent:</dim> <text>install a package</text>");
218
- // The pre-1.18.4 bug: label stripped, value dumped bright with no dim label.
219
- expect(joined).not.toContain("<text>apt install foo</text>"); // command value is dim, not text
220
- });
221
-
222
- test("wraps a long body command instead of truncating it", async () => {
223
- // A command far wider than any modal width — must survive in full, wrapped.
224
- const longCmd = `echo ${"pix-gate-installed-or-linked ".repeat(8)}done`;
225
- let captured: string[] = [];
226
- await showOverlay(
227
- makeUI((comp) => {
228
- captured = comp.render(80);
229
- comp.handleInput(ENTER);
230
- }),
231
- { mode: "confirm", title: "T", body: [longCmd], timeoutMs: 0 },
232
- );
233
- // Every whitespace-delimited token of the command appears somewhere in the
234
- // frame — nothing was dropped by truncation.
235
- const joined = captured.join("\n");
236
- for (const tok of longCmd.split(" ")) expect(joined).toContain(tok);
237
- });
238
- });
239
-
240
- describe("showOverlay — sudo mode", () => {
241
- test("approve then submit password returns approved + real password", async () => {
242
- const result = await showOverlay(
243
- makeUI((comp) => {
244
- comp.handleInput(ENTER); // select item 0 = "yes" => switch to password stage
245
- comp.handleInput("s3cret"); // type into MaskedInput
246
- comp.handleInput(ENTER); // submit
247
- }),
248
- { mode: "sudo", title: "ROOT", timeoutMs: 0 },
249
- );
250
- expect(result.action).toBe("approved");
251
- expect(result.password).toBe("s3cret");
252
- });
253
-
254
- test("deny at select stage returns denied, never reaches password", async () => {
255
- const result = await showOverlay(
256
- makeUI((comp) => {
257
- comp.handleInput(DOWN); // item 1 = "no"
258
- comp.handleInput(ENTER);
259
- }),
260
- { mode: "sudo", title: "ROOT", timeoutMs: 0 },
261
- );
262
- expect(result.action).toBe("denied");
263
- expect(result.password).toBeUndefined();
264
- });
265
-
266
- test("wrong password retries inside the same overlay", async () => {
267
- let component: Wired | undefined;
268
- let overlayCount = 0;
269
- const attempts: string[] = [];
270
- const ui: OverlayUI = {
271
- custom: <T>(cb: Parameters<OverlayUI["custom"]>[0]): Promise<T | undefined> => {
272
- overlayCount += 1;
273
- return new Promise((resolve) => {
274
- component = cb({ requestRender: () => {} }, theme, undefined, (value) =>
275
- resolve(value as T),
276
- );
277
- });
278
- },
279
- };
280
-
281
- const pending = showOverlay(ui, {
282
- mode: "sudo",
283
- title: "ROOT",
284
- timeoutMs: 0,
285
- maxPasswordAttempts: 3,
286
- validatePassword: async (password) => {
287
- attempts.push(password);
288
- return password === "correct";
289
- },
290
- });
291
- component?.handleInput(ENTER);
292
- component?.handleInput("wrong");
293
- component?.handleInput(ENTER);
294
- await new Promise((resolve) => setTimeout(resolve, 0));
295
- expect(component?.render(80).join("\n")).toContain("Incorrect password — attempt 1 of 3");
296
- component?.handleInput("correct");
297
- component?.handleInput(ENTER);
298
-
299
- expect(await pending).toEqual({ action: "approved", password: "correct" });
300
- expect(attempts).toEqual(["wrong", "correct"]);
301
- expect(overlayCount).toBe(1);
302
- });
303
-
304
- test("password is masked in render (● not plaintext)", async () => {
305
- let pwFrame: string[] = [];
306
- await showOverlay(
307
- makeUI((comp) => {
308
- comp.handleInput(ENTER); // to password stage
309
- comp.handleInput("abc");
310
- pwFrame = comp.render(80);
311
- comp.handleInput(ENTER); // submit so the promise resolves
312
- }),
313
- { mode: "sudo", title: "ROOT", timeoutMs: 0 },
314
- );
315
- const joined = pwFrame.join("\n");
316
- expect(joined).not.toContain("abc");
317
- expect(joined).toContain("●");
318
- });
319
- });
320
-
321
- // ── Auto-deny timer (dead-man's switch) ───────────────────────────────────────
322
- //
323
- // Timer-aware mock: unlike makeUI, this keeps the promise pending and resolves
324
- // only when `done` fires — so a real setInterval expiry can drive the result.
325
- // `onReady` gets the live component to optionally feed input before expiry.
326
- function makeTimerUI(onReady?: (comp: Wired) => void): OverlayUI {
327
- return {
328
- custom: <T>(
329
- cb: (
330
- tui: { requestRender(): void },
331
- th: typeof theme,
332
- kb: unknown,
333
- done: (v: T) => void,
334
- ) => Wired,
335
- ): Promise<T | undefined> =>
336
- new Promise((resolve) => {
337
- const comp = cb({ requestRender: () => {} }, theme, undefined, (v) => resolve(v));
338
- comp.render(80);
339
- onReady?.(comp);
340
- }),
341
- };
342
- }
343
-
344
- describe("showOverlay — auto-deny timer", () => {
345
- test("expires to timeout when left untouched", async () => {
346
- jest.useFakeTimers();
347
- try {
348
- const pending = showOverlay(makeTimerUI(), {
349
- mode: "confirm",
350
- title: "T",
351
- timeoutMs: 1000, // ceil → 1s, fires on first tick
352
- });
353
- jest.advanceTimersByTime(1000); // fire the auto-deny tick without a real wait
354
- const result = await pending;
355
- expect(result.action).toBe("timeout");
356
- } finally {
357
- jest.useRealTimers();
358
- }
359
- });
360
-
361
- test("first keypress cancels the timer (no auto-deny)", async () => {
362
- jest.useFakeTimers();
363
- try {
364
- let live: Wired | undefined;
365
- const pending = showOverlay(
366
- makeTimerUI((comp) => {
367
- live = comp;
368
- comp.handleInput(DOWN); // any key — cancels the dead-man's switch
369
- }),
370
- { mode: "confirm", title: "T", timeoutMs: 1000 },
371
- );
372
- // Advance well past the 1s window. A live timer would have resolved
373
- // "timeout"; the keypress cancelled it, so the promise stays pending.
374
- jest.advanceTimersByTime(1300);
375
- live?.handleInput(ENTER); // now deny explicitly
376
- const result = await pending;
377
- expect(result.action).toBe("denied");
378
- } finally {
379
- jest.useRealTimers();
380
- }
381
- });
382
- });
@@ -1,36 +0,0 @@
1
- import { beforeEach, describe, expect, test } from "bun:test";
2
- import { _cache, clearHighlightCache, hlBlock } from "./highlight.ts";
3
-
4
- function theme(color: string) {
5
- return {
6
- fg: (key: string, text: string) => `\x1b[38;2;${color}m${key}:${text}\x1b[0m`,
7
- getFgAnsi: (key: string) => `\x1b[38;2;${color}m:${key}`,
8
- };
9
- }
10
-
11
- describe("active-theme syntax highlighting", () => {
12
- beforeEach(() => clearHighlightCache());
13
-
14
- test("maps JSON scopes to semantic Pi syntax roles", async () => {
15
- const out = (await hlBlock('{"name":"pix","count":2}', "json", theme("10;20;30"))).join("\n");
16
- expect(out).toContain("syntaxVariable");
17
- expect(out).toContain("syntaxString");
18
- expect(out).toContain("syntaxNumber");
19
- });
20
-
21
- test("separates cached output by active theme colors", async () => {
22
- await hlBlock("const value = 1", "typescript", theme("10;20;30"));
23
- await hlBlock("const value = 1", "typescript", theme("30;40;50"));
24
- expect(_cache.size).toBe(2);
25
- });
26
-
27
- test("bails to plain (never highlights) when any line exceeds the per-line guard", async () => {
28
- // Regression: a single multi-KB JSON string value made cli-highlight's
29
- // tokenizer backtrack and froze the render thread. The guard returns the
30
- // block unhighlighted (no ANSI, not cached) instead of tokenizing it.
31
- const mega = JSON.stringify({ blurb: "x".repeat(5000) });
32
- const out = await hlBlock(mega, "json", theme("10;20;30"));
33
- expect(out.join("\n")).toBe(mega); // untouched, no ANSI escapes injected
34
- expect(_cache.size).toBe(0); // not cached — it never went through highlight()
35
- });
36
- });
@@ -1,97 +0,0 @@
1
- import { afterEach, describe, expect, it } from "bun:test";
2
- import {
3
- getIconMode,
4
- ICON_KEYS,
5
- ICON_MODES,
6
- icon,
7
- iconFor,
8
- onIconModeChange,
9
- setIconMode,
10
- } from "./icon-catalog.ts";
11
-
12
- describe("icon-catalog", () => {
13
- afterEach(() => setIconMode("nerd")); // restore default for other suites
14
-
15
- it("exposes nerd/unicode/ascii in cycle order", () => {
16
- expect([...ICON_MODES]).toEqual(["nerd", "unicode", "ascii"]);
17
- });
18
-
19
- it("resolves a key against the active mode", () => {
20
- setIconMode("ascii");
21
- expect(icon("cwd")).toBe("~");
22
- setIconMode("unicode");
23
- expect(icon("cwd")).toBe("\u2302\uFE0E");
24
- setIconMode("nerd");
25
- expect(icon("cwd")).toBe("\u{F024B}");
26
- });
27
-
28
- it("iconFor resolves without touching the active mode", () => {
29
- setIconMode("nerd");
30
- expect(iconFor("opt.caveman", "ascii")).toBe("Cv");
31
- expect(getIconMode()).toBe("nerd"); // unchanged
32
- });
33
-
34
- it("provides AFK keyboard fallbacks for every icon mode", () => {
35
- expect(iconFor("afk", "nerd")).toBe("\u{F0310}");
36
- expect(iconFor("afk", "unicode")).toBe("\u2328\uFE0E");
37
- expect(iconFor("afk", "ascii")).toBe("kbd");
38
- });
39
-
40
- it("every catalog key has a non-empty glyph in every mode", () => {
41
- for (const mode of ICON_MODES) {
42
- for (const key of ICON_KEYS) {
43
- expect(iconFor(key, mode).length).toBeGreaterThan(0);
44
- }
45
- }
46
- });
47
-
48
- it("status family keeps historical nerd glyphs and gains ascii tokens", () => {
49
- // nerd mode must equal the pre-catalog literals so mixed-glyph rows and
50
- // existing snapshot assertions stay aligned.
51
- expect(iconFor("status.ok", "nerd")).toBe("\u2713");
52
- expect(iconFor("status.error", "nerd")).toBe("\u2717");
53
- expect(iconFor("status.warn", "nerd")).toBe("\u26A0");
54
- expect(iconFor("status.pending", "nerd")).toBe("\u25CB");
55
- expect(iconFor("status.running", "nerd")).toBe("\u25D0");
56
- expect(iconFor("status.active", "nerd")).toBe("\u25CF");
57
- expect(iconFor("status.done", "nerd")).toBe("\u25CF");
58
- expect(iconFor("status.blocked", "nerd")).toBe("\u2298");
59
- // ascii mode must be tofu-free (letters/punctuation only).
60
- for (const key of [
61
- "status.ok",
62
- "status.error",
63
- "status.warn",
64
- "status.pending",
65
- "status.running",
66
- "status.active",
67
- "status.done",
68
- "status.blocked",
69
- ] as const) {
70
- expect(iconFor(key, "ascii")).toMatch(/^[\x20-\x7e]+$/);
71
- }
72
- });
73
-
74
- it("unknown key fails soft to empty string", () => {
75
- // @ts-expect-error exercising the runtime guard
76
- expect(icon("does.not.exist")).toBe("");
77
- });
78
-
79
- it("setIconMode ignores an invalid mode", () => {
80
- setIconMode("unicode");
81
- // @ts-expect-error invalid mode must be rejected, leaving prior value
82
- setIconMode("bogus");
83
- expect(getIconMode()).toBe("unicode");
84
- });
85
-
86
- it("notifies subscribers on an actual change, not on no-ops", () => {
87
- setIconMode("nerd");
88
- const seen: string[] = [];
89
- const off = onIconModeChange((m) => seen.push(m));
90
- setIconMode("nerd"); // no-op — must NOT fire
91
- setIconMode("ascii"); // change — fires
92
- setIconMode("ascii"); // no-op — must NOT fire
93
- off();
94
- setIconMode("unicode"); // after unsubscribe — must NOT fire
95
- expect(seen).toEqual(["ascii"]);
96
- });
97
- });
@@ -1,59 +0,0 @@
1
- import { afterAll, afterEach, beforeAll, describe, expect, it } from "bun:test";
2
- import { mkdtempSync, rmSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import { reloadConfig } from "@xynogen/pix-runtime/config";
6
- import { getIconMode, setIconMode } from "./icon-catalog.ts";
7
- import { initIconMode, loadIconMode, saveIconMode } from "./icon-persist.ts";
8
-
9
- let tmpAgentDir: string;
10
- let origHome: string | undefined;
11
-
12
- beforeAll(async () => {
13
- tmpAgentDir = mkdtempSync(join(tmpdir(), "pretty-persist-test-"));
14
- origHome = process.env.HOME;
15
- // Point HOME at the temp dir so the runtime reads from there, not the real ~/.pi/agent/pix.json
16
- process.env.HOME = tmpAgentDir;
17
- process.env.PI_CODING_AGENT_DIR = tmpAgentDir;
18
- // Drop any singleton created by earlier test files (it is bound to the old
19
- // agent dir); the next accessor call lazily recreates it under the temp HOME.
20
- delete (globalThis as Record<symbol, unknown>)[Symbol.for("@xynogen/pix-runtime")];
21
- await reloadConfig();
22
- });
23
-
24
- afterAll(() => {
25
- process.env.HOME = origHome;
26
- delete process.env.PI_CODING_AGENT_DIR;
27
- // Drop the temp-HOME-bound singleton so later test files get a fresh one.
28
- delete (globalThis as Record<symbol, unknown>)[Symbol.for("@xynogen/pix-runtime")];
29
- try {
30
- rmSync(tmpAgentDir, { recursive: true });
31
- } catch {
32
- // already gone — ignore
33
- }
34
- });
35
-
36
- describe("icon-persist", () => {
37
- afterEach(() => setIconMode("nerd"));
38
-
39
- it("returns default (nerd) in a fresh config", () => {
40
- expect(loadIconMode()).toBe("nerd");
41
- });
42
-
43
- it("round-trips a mode across save/load (new-session sim)", async () => {
44
- await saveIconMode("unicode");
45
- expect(loadIconMode()).toBe("unicode");
46
- });
47
-
48
- it("rejects an invalid persisted mode", async () => {
49
- await saveIconMode("ascii");
50
- expect(loadIconMode()).toBe("ascii");
51
- });
52
-
53
- it("initIconMode applies the persisted choice to the catalog", async () => {
54
- await saveIconMode("ascii");
55
- setIconMode("nerd"); // pretend env default
56
- initIconMode();
57
- expect(getIconMode()).toBe("ascii");
58
- });
59
- });
package/src/icons.test.ts DELETED
@@ -1,35 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { dirIcon, fileColor, fileIcon } from "./icons.ts";
3
-
4
- const theme = {
5
- fg: (key: string, text: string) => `<${key}>${text}</${key}>`,
6
- };
7
-
8
- describe("theme-derived file icons", () => {
9
- test("uses semantic theme roles instead of embedded ANSI colors", () => {
10
- expect(fileIcon("example.ts", theme)).toContain("<syntaxType>");
11
- expect(fileIcon("data.json", theme)).toContain("<syntaxNumber>");
12
- expect(fileIcon("unknown.zzz", theme)).toContain("<muted>");
13
- });
14
-
15
- test("themes directory icons with the active accent", () => {
16
- expect(dirIcon(theme)).toContain("<accent>");
17
- });
18
- });
19
-
20
- describe("theme-derived file name color", () => {
21
- test("colors a filename with the same role as its icon", () => {
22
- expect(fileColor("example.ts", "example.ts", theme)).toBe(
23
- "<syntaxType>example.ts</syntaxType>",
24
- );
25
- expect(fileColor("package.json", "package.json", theme)).toContain("<syntaxString>");
26
- });
27
-
28
- test("falls back to the text role for an unknown extension", () => {
29
- expect(fileColor("notes.zzz", "notes.zzz", theme)).toBe("<text>notes.zzz</text>");
30
- });
31
-
32
- test("passes the name through unchanged when no theme is supplied", () => {
33
- expect(fileColor("example.ts", "example.ts")).toBe("example.ts");
34
- });
35
- });
package/src/index.test.ts DELETED
@@ -1,13 +0,0 @@
1
- /**
2
- * Smoke tests for pix-pretty (pure lib).
3
- * UI extension tests moved to pix-display.
4
- */
5
-
6
- import { describe, expect, it } from "bun:test";
7
-
8
- describe("pix-pretty", () => {
9
- it("main module exports a function", async () => {
10
- const mod = await import("./index");
11
- expect(mod.default).toBeFunction();
12
- });
13
- });