diff --git a/packages/elements/__tests__/prompt-input.test.tsx b/packages/elements/__tests__/prompt-input.test.tsx index d961a3ba..035e4203 100644 --- a/packages/elements/__tests__/prompt-input.test.tsx +++ b/packages/elements/__tests__/prompt-input.test.tsx @@ -1054,6 +1054,114 @@ describe("promptInputProvider", () => { expect(screen.getByTestId("count")).toHaveTextContent("1"); }); + + it("keeps attachment references stable while typing", async () => { + setupPromptInputTests(); + const { PromptInputProvider } = await import("../src/prompt-input"); + const user = userEvent.setup(); + const onSubmit = vi.fn(); + const file = new File(["test"], "test.txt", { type: "text/plain" }); + + const fileRefs: AttachmentData[] = []; + const AttachmentConsumer = () => { + const attachments = usePromptInputAttachments(); + if (attachments.files.length > 0) { + fileRefs.push(attachments.files[0]); + } + return ( + + ); + }; + + render( + + + + + + + + + ); + + await user.click(screen.getByTestId("add-file")); + expect(fileRefs.length).toBeGreaterThan(0); + + await user.type(screen.getByRole("textbox"), "hello"); + + expect(new Set(fileRefs).size).toBe(1); + }); + + it("does not re-render memoized attachment rows on text input", async () => { + setupPromptInputTests(); + const { PromptInputProvider } = await import("../src/prompt-input"); + const user = userEvent.setup(); + const onSubmit = vi.fn(); + const file = new File(["test"], "test.txt", { type: "text/plain" }); + const otherFile = new File(["other"], "other.txt", { + type: "text/plain", + }); + const chipRenders: string[] = []; + + const Chip = React.memo(({ data }: { data: AttachmentData }) => { + chipRenders.push(data.id); + return {data.filename}; + }); + Chip.displayName = "Chip"; + + const ChipList = () => { + const attachments = usePromptInputAttachments(); + return ( + <> + + + {attachments.files.map((f) => ( + + ))} + + ); + }; + + render( + + + + + + + + + ); + + await user.click(screen.getByTestId("add-file")); + const rendersAfterAdd = chipRenders.length; + expect(rendersAfterAdd).toBeGreaterThan(0); + + await user.type(screen.getByRole("textbox"), "hello"); + expect(chipRenders.length).toBe(rendersAfterAdd); + + await user.click(screen.getByTestId("add-other-file")); + expect(chipRenders.length).toBeGreaterThan(rendersAfterAdd); + expect(screen.getAllByTestId(/^chip-/)).toHaveLength(2); + }); }); describe("file validation", () => { diff --git a/packages/elements/src/prompt-input.tsx b/packages/elements/src/prompt-input.tsx index 412c846d..0c384e83 100644 --- a/packages/elements/src/prompt-input.tsx +++ b/packages/elements/src/prompt-input.tsx @@ -816,7 +816,7 @@ export const PromptInput = ({ add, clear: clearAttachments, fileInputRef: inputRef, - files: files.map((item) => ({ ...item, id: item.id })), + files, openFileDialog, remove, }),