diff --git a/src/components/chat/client.test.tsx b/src/components/chat/client.test.tsx index 09c1b23..625e34e 100644 --- a/src/components/chat/client.test.tsx +++ b/src/components/chat/client.test.tsx @@ -111,6 +111,27 @@ describe("ChatWidgetClient", () => { ); }); + it("should focus the input textarea after a suggested question is triggered", () => { + mockUseChatWidgetResult.isOpen = true; + mockUseChatWidgetResult.messages = []; + + const { getByText, getByLabelText } = render(); + const textarea = getByLabelText("Ask a question to Miro"); + const focusSpy = vi.spyOn(textarea, "focus"); + + mockHandleSuggestedQuestion.mockImplementation(() => { + textarea.focus(); + }); + + const suggestedQuestion = getByText("What was his PhD research about?"); + + act(() => { + suggestedQuestion.click(); + }); + + expect(focusSpy).toHaveBeenCalled(); + }); + it("should render messages list, typing indicator and stop button when loading", () => { mockUseChatWidgetResult.isOpen = true; mockUseChatWidgetResult.isLoading = true; diff --git a/src/components/chat/use-chat-widget.ts b/src/components/chat/use-chat-widget.ts index 1a6557e..e512050 100644 --- a/src/components/chat/use-chat-widget.ts +++ b/src/components/chat/use-chat-widget.ts @@ -102,6 +102,9 @@ export function useChatWidget() { } try { await sendMessage({ text: currentInput }); + if (inputRef.current) { + inputRef.current.focus(); + } } catch (err) { console.error("Failed to send message:", err); } @@ -114,6 +117,9 @@ export function useChatWidget() { if (isLoading) return; try { await sendMessage({ text: question }); + if (inputRef.current) { + inputRef.current.focus(); + } } catch (err) { console.error("Failed to send suggested question:", err); } diff --git a/src/components/components.test.tsx b/src/components/components.test.tsx index 89683b0..8357621 100644 --- a/src/components/components.test.tsx +++ b/src/components/components.test.tsx @@ -109,15 +109,14 @@ describe("UI Components", () => { }); describe("ScrollToTopButton (upArrow)", () => { - it("should show/hide on scroll and trigger window.scrollTo on click", () => { - const { container } = render(); + it("should show/hide on scroll and trigger window.scrollTo on click, shifting focus to main-content or body", () => { + // Create a dummy main-content div in the test DOM + const mainContent = document.createElement("div"); + mainContent.id = "main-content"; + const focusSpy = vi.spyOn(mainContent, "focus"); + document.body.appendChild(mainContent); - // Initially not visible (window.scrollY is 0) - window.scrollY = 0; - act(() => { - window.dispatchEvent(new Event("scroll")); - }); - expect(container.querySelector(".scroll-button")).not.toBeInTheDocument(); + const { container } = render(); // Scroll down window.scrollY = 400; @@ -137,6 +136,31 @@ describe("UI Components", () => { top: 0, behavior: "smooth", }); + + // Verify that focus was shifted to main-content + expect(focusSpy).toHaveBeenCalledWith({ preventScroll: true }); + + // Clean up + document.body.removeChild(mainContent); + }); + + it("should fallback to document.body.focus if main-content is not found", () => { + const bodyFocusSpy = vi.spyOn(document.body, "focus"); + + const { container } = render(); + + // Scroll down + window.scrollY = 400; + act(() => { + window.dispatchEvent(new Event("scroll")); + }); + + const btn = container.querySelector(".scroll-button"); + act(() => { + (btn as HTMLButtonElement).click(); + }); + + expect(bodyFocusSpy).toHaveBeenCalledWith({ preventScroll: true }); }); }); diff --git a/src/components/upArrow.tsx b/src/components/upArrow.tsx index b89efc3..d1c8cd7 100644 --- a/src/components/upArrow.tsx +++ b/src/components/upArrow.tsx @@ -30,6 +30,12 @@ const ScrollToTopButton = () => { top: 0, behavior: "smooth", }); + const mainContent = document.getElementById("main-content"); + if (mainContent) { + mainContent.focus({ preventScroll: true }); + } else { + document.body.focus({ preventScroll: true }); + } }; return (