fix(frontend): auto-scroll not working in Planner tab when plan content updates (#13355)

This commit is contained in:
Hiep Le
2026-03-13 23:47:03 +07:00
committed by GitHub
parent cd2d0ee9a5
commit b4f00379b8
2 changed files with 94 additions and 5 deletions

View File

@@ -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,
);
}
}
});
});
});

View File

@@ -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();