


















@@ -109,6 +109,101 @@ describe("commitments command", () => {
109109);
110110});
111111112+it("keeps fixed-width columns aligned when an id or scope is truncated", async () => {
113+// An id longer than the 16-char ID column and a scope longer than the
114+// 28-char Scope column, so truncate() fires for both cells.
115+mocks.listCommitments.mockResolvedValue([
116+commitment({
117+id: "cm_abcdefghijklmnopqrstuvwxyz", // 29 chars > 16
118+agentId: "averylongagentidentifier",
119+channel: "telegram",
120+to: "+15551234567890", // agentId/channel/to joined > 28 chars
121+}),
122+]);
123+const { runtime, logs } = createRuntime();
124+125+await commitmentsListCommand({}, runtime);
126+127+const lines = logs.map(stripAnsi);
128+const header = lines.find((line) => line.startsWith("ID"));
129+const row = lines.find((line) => line.startsWith("cm_"));
130+expect(header).toBeDefined();
131+expect(row).toBeDefined();
132+133+// The truncated ID cell must stay within its 16-char column: 15 chars of
134+// content plus a single-character ellipsis, not a 3-char "..." that overflows.
135+expect(row?.slice(0, 16)).toBe("cm_abcdefghijkl…");
136+137+// With each truncated cell at its intended width, the following columns line
138+// up with the header. A 3-char "..." pushes every column after a truncated
139+// cell 2 chars right of its header label.
140+expect(row?.indexOf("pending")).toBe(header?.indexOf("Status"));
141+expect(row?.indexOf("event_check_in")).toBe(header?.indexOf("Kind"));
142+});
143+144+it("keeps the Scope column aligned when only the scope is truncated", async () => {
145+// Short id (untouched) but a scope longer than its 28-char column, so only
146+// the scope cell is truncated. Isolates the second truncation site.
147+mocks.listCommitments.mockResolvedValue([
148+commitment({
149+id: "cm_short", // 8 chars, fits the ID column untouched
150+agentId: "averylongagentidentifier",
151+channel: "telegram",
152+to: "+15551234567890", // joined scope exceeds 28 chars
153+}),
154+]);
155+const { runtime, logs } = createRuntime();
156+157+await commitmentsListCommand({}, runtime);
158+159+const lines = logs.map(stripAnsi);
160+const header = lines.find((line) => line.startsWith("ID"));
161+const row = lines.find((line) => line.startsWith("cm_"));
162+expect(header).toBeDefined();
163+expect(row).toBeDefined();
164+165+// The short id is rendered in full (no ellipsis).
166+expect(row?.slice(0, 16)).toBe("cm_short ");
167+168+// The 28-char Scope cell ends in a single-char ellipsis and holds its width,
169+// so the trailing Suggested text column still begins under its header label.
170+const scopeCell = row?.slice(70, 98);
171+expect(scopeCell?.length).toBe(28);
172+expect(scopeCell?.endsWith("…")).toBe(true);
173+expect(row?.indexOf("How did it go?")).toBe(header?.indexOf("Suggested text"));
174+});
175+176+it("does not truncate an id that exactly fills the ID column", async () => {
177+// 16 chars == maxChars, so value.length <= maxChars and the id passes through
178+// whole with no ellipsis. Guards the boundary so we never over-truncate.
179+mocks.listCommitments.mockResolvedValue([commitment({ id: "cm_exactly16char" })]);
180+const { runtime, logs } = createRuntime();
181+182+await commitmentsListCommand({}, runtime);
183+184+const lines = logs.map(stripAnsi);
185+const header = lines.find((line) => line.startsWith("ID"));
186+const row = lines.find((line) => line.startsWith("cm_"));
187+expect(row?.slice(0, 16)).toBe("cm_exactly16char");
188+expect(row).not.toContain("…");
189+expect(row?.indexOf("pending")).toBe(header?.indexOf("Status"));
190+});
191+192+it("truncates an id one character past the column width to a single ellipsis", async () => {
193+// 17 chars == maxChars + 1, so truncate fires: 15 chars of content plus one
194+// ellipsis == 16, holding the column (the old "..." produced 18 and overflowed).
195+mocks.listCommitments.mockResolvedValue([commitment({ id: "cm_0123456789abcd" })]);
196+const { runtime, logs } = createRuntime();
197+198+await commitmentsListCommand({}, runtime);
199+200+const lines = logs.map(stripAnsi);
201+const header = lines.find((line) => line.startsWith("ID"));
202+const row = lines.find((line) => line.startsWith("cm_"));
203+expect(row?.slice(0, 16)).toBe("cm_0123456789ab…");
204+expect(row?.indexOf("pending")).toBe(header?.indexOf("Status"));
205+});
206+112207it("writes list JSON to runtime stdout instead of log output", async () => {
113208const { runtime, logs, stdout } = createRuntime();
114209此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。