mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 05:37:20 +08:00
fix(frontend): auto-scroll not working in Planner tab when plan content updates (#13355)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
import { screen, act } from "@testing-library/react";
|
||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||
import PlannerTab from "#/routes/planner-tab";
|
||||
import { renderWithProviders } from "../../test-utils";
|
||||
import { useConversationStore } from "#/stores/conversation-store";
|
||||
@@ -12,8 +12,15 @@ vi.mock("#/hooks/use-handle-plan-click", () => ({
|
||||
}));
|
||||
|
||||
describe("PlannerTab", () => {
|
||||
const originalRAF = global.requestAnimationFrame;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// Make requestAnimationFrame execute synchronously for testing
|
||||
global.requestAnimationFrame = (cb: FrameRequestCallback) => {
|
||||
cb(0);
|
||||
return 0;
|
||||
};
|
||||
// Reset store state to defaults
|
||||
useConversationStore.setState({
|
||||
planContent: null,
|
||||
@@ -21,6 +28,10 @@ describe("PlannerTab", () => {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.requestAnimationFrame = originalRAF;
|
||||
});
|
||||
|
||||
describe("Create a plan button", () => {
|
||||
it("should be enabled when conversation mode is 'code'", () => {
|
||||
// Arrange
|
||||
@@ -52,4 +63,71 @@ describe("PlannerTab", () => {
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Auto-scroll behavior", () => {
|
||||
it("should scroll to bottom when plan content is updated", () => {
|
||||
// Arrange
|
||||
const scrollTopSetter = vi.fn();
|
||||
const mockScrollHeight = 500;
|
||||
|
||||
// Mock scroll properties on HTMLElement prototype
|
||||
const originalScrollHeightDescriptor = Object.getOwnPropertyDescriptor(
|
||||
HTMLElement.prototype,
|
||||
"scrollHeight",
|
||||
);
|
||||
const originalScrollTopDescriptor = Object.getOwnPropertyDescriptor(
|
||||
HTMLElement.prototype,
|
||||
"scrollTop",
|
||||
);
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, "scrollHeight", {
|
||||
get: () => mockScrollHeight,
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(HTMLElement.prototype, "scrollTop", {
|
||||
get: () => 0,
|
||||
set: scrollTopSetter,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
try {
|
||||
// Render with initial plan content
|
||||
useConversationStore.setState({
|
||||
planContent: "# Initial Plan",
|
||||
conversationMode: "plan",
|
||||
});
|
||||
|
||||
renderWithProviders(<PlannerTab />);
|
||||
|
||||
// Clear calls from initial render
|
||||
scrollTopSetter.mockClear();
|
||||
|
||||
// Act - Update plan content which should trigger auto-scroll
|
||||
act(() => {
|
||||
useConversationStore.setState({
|
||||
planContent: "# Updated Plan\n\nMore content added here.",
|
||||
});
|
||||
});
|
||||
|
||||
// Assert - scrollTop should be set to scrollHeight
|
||||
expect(scrollTopSetter).toHaveBeenCalledWith(mockScrollHeight);
|
||||
} finally {
|
||||
// Restore original descriptors
|
||||
if (originalScrollHeightDescriptor) {
|
||||
Object.defineProperty(
|
||||
HTMLElement.prototype,
|
||||
"scrollHeight",
|
||||
originalScrollHeightDescriptor,
|
||||
);
|
||||
}
|
||||
if (originalScrollTopDescriptor) {
|
||||
Object.defineProperty(
|
||||
HTMLElement.prototype,
|
||||
"scrollTop",
|
||||
originalScrollTopDescriptor,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,11 +11,22 @@ import { cn } from "#/utils/utils";
|
||||
|
||||
function PlannerTab() {
|
||||
const { t } = useTranslation();
|
||||
const { scrollRef: scrollContainerRef, onChatBodyScroll } = useScrollToBottom(
|
||||
React.useRef<HTMLDivElement>(null),
|
||||
);
|
||||
const scrollRef = React.useRef<HTMLDivElement>(null);
|
||||
const {
|
||||
scrollRef: scrollContainerRef,
|
||||
onChatBodyScroll,
|
||||
autoScroll,
|
||||
scrollDomToBottom,
|
||||
} = useScrollToBottom(scrollRef);
|
||||
|
||||
const { planContent, conversationMode } = useConversationStore();
|
||||
|
||||
// Auto-scroll to bottom when plan content changes
|
||||
React.useEffect(() => {
|
||||
if (autoScroll) {
|
||||
scrollDomToBottom();
|
||||
}
|
||||
}, [planContent, autoScroll, scrollDomToBottom]);
|
||||
const isPlanMode = conversationMode === "plan";
|
||||
const { handlePlanClick } = useHandlePlanClick();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user