test(note): add rendered-output regression test for copy-sensitive token preservation

Add a focused test in table.test.ts that verifies clack's note() rendering
does not re-break copy-sensitive tokens (session lock paths) after
wrapNoteMessage preserves them. Uses resolveNoteOutputColumns to widen
the virtual output stream, matching the fix in note.ts.

Refs #94730
This commit is contained in:
Dirk
2026-06-19 02:04:50 +00:00
parent 90da1f07f0
commit b17a4ff571
2 changed files with 39 additions and 8 deletions

View File

@@ -186,6 +186,13 @@ export function resolveNoteColumns(columns: number | undefined): number {
return columns;
}
export function resolveNoteOutputColumns(message: string, columns: number): number {
const widestLine = message
.split("\n")
.reduce((max, line) => Math.max(max, visibleWidth(line)), 0);
return Math.max(columns, widestLine + 6);
}
function createNoteOutput(columns: number): NodeJS.WriteStream {
if (process.stdout.columns === columns) {
return process.stdout;
@@ -207,13 +214,9 @@ export function note(message: unknown, title?: string) {
return;
}
const columns = resolveNoteColumns(process.stdout.columns);
const wrapped = wrapNoteMessage(message, { columns });
// Use a wide virtual stream so clack's internal wrap (which runs after our
// format callback) does not re-break copy-sensitive tokens that
// wrapNoteMessage intentionally kept intact.
const wideOutput = createNoteOutput(Math.max(columns, visibleWidth(wrapped) + 12));
clackNote(wrapped, stylePromptTitle(title), {
output: wideOutput,
const wrappedMessage = wrapNoteMessage(message, { columns });
clackNote(wrappedMessage, stylePromptTitle(title), {
output: createNoteOutput(resolveNoteOutputColumns(wrappedMessage, columns)),
format: (line) => line,
});
}

View File

@@ -1,7 +1,8 @@
import { note as clackNote } from "@clack/prompts";
// Terminal Core tests cover table behavior.
import { afterEach, describe, expect, it, vi } from "vitest";
import { visibleWidth } from "./ansi.js";
import { resolveNoteColumns, wrapNoteMessage } from "./note.js";
import { resolveNoteColumns, resolveNoteOutputColumns, wrapNoteMessage } from "./note.js";
import { renderTable } from "./table.js";
function mockProcessPlatform(platform: NodeJS.Platform): void {
@@ -348,6 +349,33 @@ describe("wrapNoteMessage", () => {
expect(resolveNoteColumns(120)).toBe(120);
});
it("widens note output columns so clack does not re-wrap copy-sensitive lines", () => {
const wrapped = wrapNoteMessage(
[
"- Found 1 session lock file.",
"- ~/.openclaw/agents/main/sessions/9c2acae5-841f-4aea-936b-fdb513b60202.jsonl.lock pid=86519 (alive) age=2m47s stale=no",
].join("\n"),
{ columns: 80 },
);
const writes: string[] = [];
const output = {
columns: resolveNoteOutputColumns(wrapped, 80),
write(chunk: string) {
writes.push(chunk);
return true;
},
} as unknown as NodeJS.WriteStream;
clackNote(wrapped, "Session locks", { output, format: (line) => line });
const rendered = writes.join("");
expect(rendered).toContain(".jsonl.lock");
expect(rendered).not.toContain(".js\n");
expect(rendered).toContain(
"- ~/.openclaw/agents/main/sessions/9c2acae5-841f-4aea-936b-fdb513b60202.jsonl.lock",
);
});
it("coerces nullish and non-string note messages before wrapping", () => {
expect(wrapNoteMessage(undefined, { maxWidth: 20, columns: 80 })).toBe("");
expect(wrapNoteMessage(null, { maxWidth: 20, columns: 80 })).toBe("");