Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions packages/elements/__tests__/prompt-input.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<button
data-testid="add-file"
onClick={() => attachments.add([file])}
type="button"
>
Add
</button>
);
};

render(
<PromptInputProvider>
<PromptInput onSubmit={onSubmit}>
<PromptInputBody>
<AttachmentConsumer />
<PromptInputTextarea />
</PromptInputBody>
</PromptInput>
</PromptInputProvider>
);

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 <span data-testid={`chip-${data.id}`}>{data.filename}</span>;
});
Chip.displayName = "Chip";

const ChipList = () => {
const attachments = usePromptInputAttachments();
return (
<>
<button
data-testid="add-file"
onClick={() => attachments.add([file])}
type="button"
>
Add
</button>
<button
data-testid="add-other-file"
onClick={() => attachments.add([otherFile])}
type="button"
>
Add other
</button>
{attachments.files.map((f) => (
<Chip data={f} key={f.id} />
))}
</>
);
};

render(
<PromptInputProvider>
<PromptInput onSubmit={onSubmit}>
<PromptInputBody>
<ChipList />
<PromptInputTextarea />
</PromptInputBody>
</PromptInput>
</PromptInputProvider>
);

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", () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/elements/src/prompt-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ export const PromptInput = ({
add,
clear: clearAttachments,
fileInputRef: inputRef,
files: files.map((item) => ({ ...item, id: item.id })),
files,
openFileDialog,
remove,
}),
Expand Down