Files
dbx/apps/desktop/src/lib/__tests__/aiMessageEdit.spec.ts
T
Abeautifulsnow fe4908321e feat(ai): support editing sent user messages
* feat(ai): support editing sent user messages

Adds inline edit support for user messages in the AI chat panel.
Hover over a user message to reveal the edit button; confirming the
edit truncates all subsequent history and re-sends with the new content.

* fix(ai): fix IME composition and ref-focus regression in message edit

- Handle IME composition events in onEditKeydown to prevent accidental
  submission on Enter during CJK candidate selection
- Replace per-render :ref focus callback with nextTick + data attribute
  to avoid cursor jumping on every keystroke
- Extract visibleToActualIndex into aiMessageEdit.ts for testability
- Add 8 unit tests covering index mapping with contextSummary messages

* fix(ai): prevent data loss and mention pollution in message edit

- Guard connection/config before truncating messages to prevent silent data
  loss when send() bails early
- Clear selectedMentions before resend to prevent stale chips from polluting
  edited message content
2026-07-03 15:18:56 +08:00

49 lines
2.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { visibleToActualIndex } from "@/lib/aiMessageEdit";
describe("visibleToActualIndex", () => {
it("maps visible index 0 to first non-summary message", () => {
const messages = [{ kind: undefined }, { kind: undefined }, { kind: undefined }];
expect(visibleToActualIndex(messages, 0)).toBe(0);
});
it("maps visible index 1 correctly with no summaries", () => {
const messages = [{ kind: undefined }, { kind: undefined }, { kind: undefined }];
expect(visibleToActualIndex(messages, 1)).toBe(1);
});
it("skips contextSummary messages when computing visible index", () => {
const messages = [{ kind: undefined }, { kind: "contextSummary" }, { kind: undefined }, { kind: undefined }];
// visible[0] → actual[0], visible[1] → actual[2], visible[2] → actual[3]
expect(visibleToActualIndex(messages, 0)).toBe(0);
expect(visibleToActualIndex(messages, 1)).toBe(2);
expect(visibleToActualIndex(messages, 2)).toBe(3);
});
it("skips multiple consecutive contextSummary messages", () => {
const messages = [{ kind: "contextSummary" }, { kind: "contextSummary" }, { kind: undefined }, { kind: undefined }];
expect(visibleToActualIndex(messages, 0)).toBe(2);
expect(visibleToActualIndex(messages, 1)).toBe(3);
});
it("returns -1 when visibleIndex is out of range", () => {
const messages = [{ kind: undefined }, { kind: undefined }];
expect(visibleToActualIndex(messages, 5)).toBe(-1);
});
it("returns -1 for empty messages array", () => {
expect(visibleToActualIndex([], 0)).toBe(-1);
});
it("returns -1 when all messages are contextSummary", () => {
const messages = [{ kind: "contextSummary" }, { kind: "contextSummary" }];
expect(visibleToActualIndex(messages, 0)).toBe(-1);
});
it("handles last visible message correctly", () => {
const messages = [{ kind: undefined }, { kind: "contextSummary" }, { kind: undefined }];
expect(visibleToActualIndex(messages, 1)).toBe(2);
expect(visibleToActualIndex(messages, 2)).toBe(-1);
});
});