fix(webui): open threads at latest message (#5142)
This commit is contained in:
@@ -1637,6 +1637,149 @@ describe("ThreadViewport", () => {
|
||||
await waitFor(() => expect(scroller.scrollTop).toBe(1800));
|
||||
});
|
||||
|
||||
it("pins an opened conversation to late layout growth without animating", async () => {
|
||||
const resizeObserver = stubResizeObserver();
|
||||
const jumpTo = vi.spyOn(ThreadCameraController.prototype, "jumpTo");
|
||||
const followTo = vi.spyOn(ThreadCameraController.prototype, "followTo");
|
||||
|
||||
try {
|
||||
const { container, rerender } = render(
|
||||
<ThreadViewport
|
||||
messages={messages}
|
||||
isStreaming={false}
|
||||
composer={<div />}
|
||||
conversationKey="chat-a"
|
||||
/>,
|
||||
);
|
||||
const scroller = getScroller(container);
|
||||
Object.defineProperties(scroller, {
|
||||
scrollHeight: { configurable: true, value: 2400 },
|
||||
clientHeight: { configurable: true, value: 600 },
|
||||
scrollTop: { configurable: true, writable: true, value: 300 },
|
||||
});
|
||||
act(() => {
|
||||
dispatchUserScroll(scroller);
|
||||
});
|
||||
|
||||
rerender(
|
||||
<ThreadViewport
|
||||
messages={messages}
|
||||
isStreaming={false}
|
||||
composer={<div />}
|
||||
conversationKey="chat-b"
|
||||
/>,
|
||||
);
|
||||
await waitFor(() => expect(scroller.scrollTop).toBe(1800));
|
||||
jumpTo.mockClear();
|
||||
followTo.mockClear();
|
||||
|
||||
const messageRegion = screen.getByTestId("thread-message-region");
|
||||
const messageContent = messageRegion.firstElementChild;
|
||||
expect(messageContent).not.toBeNull();
|
||||
const contentObserver = resizeObserver.observers.find(
|
||||
(observer) => observer.elements.includes(messageContent!),
|
||||
);
|
||||
expect(contentObserver).toBeDefined();
|
||||
|
||||
Object.defineProperty(scroller, "scrollHeight", {
|
||||
configurable: true,
|
||||
value: 3000,
|
||||
});
|
||||
act(() => {
|
||||
contentObserver!.callback([], contentObserver as unknown as ResizeObserver);
|
||||
});
|
||||
await flushAnimationFrame();
|
||||
|
||||
expect(jumpTo).toHaveBeenCalledWith(2400);
|
||||
expect(scroller.scrollTop).toBe(2400);
|
||||
expect(followTo).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
jumpTo.mockRestore();
|
||||
followTo.mockRestore();
|
||||
resizeObserver.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("animates the bottom button target and pins later layout growth", async () => {
|
||||
const resizeObserver = stubResizeObserver();
|
||||
const navigateLatestTo = vi.spyOn(
|
||||
ThreadMotionCoordinator.prototype,
|
||||
"navigateLatestTo",
|
||||
);
|
||||
const jumpTo = vi.spyOn(ThreadCameraController.prototype, "jumpTo");
|
||||
const followTo = vi.spyOn(ThreadCameraController.prototype, "followTo");
|
||||
|
||||
try {
|
||||
const { container } = render(
|
||||
<ThreadViewport
|
||||
messages={messages}
|
||||
isStreaming={false}
|
||||
composer={<div />}
|
||||
conversationKey="chat-a"
|
||||
/>,
|
||||
);
|
||||
const scroller = getScroller(container);
|
||||
Object.defineProperties(scroller, {
|
||||
scrollHeight: { configurable: true, value: 2400 },
|
||||
clientHeight: { configurable: true, value: 600 },
|
||||
scrollTop: { configurable: true, writable: true, value: 300 },
|
||||
});
|
||||
act(() => {
|
||||
dispatchUserScroll(scroller);
|
||||
});
|
||||
|
||||
navigateLatestTo.mockClear();
|
||||
jumpTo.mockClear();
|
||||
followTo.mockClear();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Scroll to bottom" }));
|
||||
expect(navigateLatestTo).toHaveBeenCalledWith(1800);
|
||||
expect(scroller.scrollTop).toBe(300);
|
||||
|
||||
const messageRegion = screen.getByTestId("thread-message-region");
|
||||
const messageContent = messageRegion.firstElementChild;
|
||||
expect(messageContent).not.toBeNull();
|
||||
const contentObserver = resizeObserver.observers.find(
|
||||
(observer) => observer.elements.includes(messageContent!),
|
||||
);
|
||||
expect(contentObserver).toBeDefined();
|
||||
|
||||
Object.defineProperty(scroller, "scrollHeight", {
|
||||
configurable: true,
|
||||
value: 3000,
|
||||
});
|
||||
act(() => {
|
||||
contentObserver!.callback([], contentObserver as unknown as ResizeObserver);
|
||||
});
|
||||
await waitFor(() => expect(scroller.scrollTop).toBe(2400));
|
||||
|
||||
expect(jumpTo).not.toHaveBeenCalled();
|
||||
expect(followTo).not.toHaveBeenCalled();
|
||||
|
||||
act(() => {
|
||||
scroller.dispatchEvent(new Event("scroll"));
|
||||
});
|
||||
await flushAnimationFrame();
|
||||
jumpTo.mockClear();
|
||||
|
||||
Object.defineProperty(scroller, "scrollHeight", {
|
||||
configurable: true,
|
||||
value: 3400,
|
||||
});
|
||||
act(() => {
|
||||
contentObserver!.callback([], contentObserver as unknown as ResizeObserver);
|
||||
});
|
||||
await flushAnimationFrame();
|
||||
|
||||
expect(jumpTo).toHaveBeenCalledWith(2800);
|
||||
expect(scroller.scrollTop).toBe(2800);
|
||||
} finally {
|
||||
navigateLatestTo.mockRestore();
|
||||
jumpTo.mockRestore();
|
||||
followTo.mockRestore();
|
||||
resizeObserver.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("waits for the next conversation's transcript before restoring its bottom", async () => {
|
||||
const jumpTo = vi.spyOn(ThreadCameraController.prototype, "jumpTo");
|
||||
const followTo = vi.spyOn(ThreadCameraController.prototype, "followTo");
|
||||
|
||||
Reference in New Issue
Block a user