diff --git a/webview-ui/src/components/chat/MultiQuestionHandler.tsx b/webview-ui/src/components/chat/MultiQuestionHandler.tsx
index 34bf5e3ca7..38eb4fbf70 100644
--- a/webview-ui/src/components/chat/MultiQuestionHandler.tsx
+++ b/webview-ui/src/components/chat/MultiQuestionHandler.tsx
@@ -24,12 +24,6 @@ export const MultiQuestionHandler = ({ questions, onSendResponse }: MultiQuestio
if (currentQuestionIndex < questions.length - 1) {
setCurrentQuestionIndex(currentQuestionIndex + 1)
- } else {
- // Finish
- const combined = questions
- .map((q, i) => `Question: ${q}\nAnswer: ${newAnswers[i] || "(skipped)"}`)
- .join("\n\n")
- onSendResponse(combined)
}
}
@@ -43,6 +37,15 @@ export const MultiQuestionHandler = ({ questions, onSendResponse }: MultiQuestio
}
}
+ const handleFinish = () => {
+ const newAnswers = [...answers]
+ newAnswers[currentQuestionIndex] = inputValue
+ setAnswers(newAnswers)
+
+ const combined = questions.map((q, i) => `Question: ${q}\nAnswer: ${newAnswers[i] || "(skipped)"}`).join("\n\n")
+ onSendResponse(combined)
+ }
+
return (
@@ -65,11 +68,15 @@ export const MultiQuestionHandler = ({ questions, onSendResponse }: MultiQuestio
{t("chat:questions.previous")}
)}
-
+ {currentQuestionIndex < questions.length - 1 ? (
+
+ ) : (
+
+ )}
)
diff --git a/webview-ui/src/components/chat/__tests__/MultiQuestionHandler.spec.tsx b/webview-ui/src/components/chat/__tests__/MultiQuestionHandler.spec.tsx
new file mode 100644
index 0000000000..942d5fc368
--- /dev/null
+++ b/webview-ui/src/components/chat/__tests__/MultiQuestionHandler.spec.tsx
@@ -0,0 +1,195 @@
+import { render, screen, fireEvent } from "@/utils/test-utils"
+import { vi, describe, it, expect, beforeEach } from "vitest"
+import TranslationProvider from "@src/i18n/TranslationContext"
+import { MultiQuestionHandler } from "../MultiQuestionHandler"
+
+// Mock ExtensionStateContext
+vi.mock("@src/context/ExtensionStateContext", () => ({
+ useExtensionState: () => ({
+ language: "en",
+ }),
+}))
+
+// Mock react-i18next
+vi.mock("react-i18next", () => ({
+ useTranslation: () => ({
+ i18n: {
+ t: (key: string, options?: Record) => {
+ // Mock specific translations used in tests
+ if (key === "chat:questions.questionNumberOfTotal" && options) {
+ return `Question ${options.current} of ${options.total}`
+ }
+ if (key === "chat:questions.typeAnswer") return "Type your answer..."
+ if (key === "chat:questions.previous") return "Previous"
+ if (key === "chat:questions.next") return "Next"
+ if (key === "chat:questions.finish") return "Finish"
+ return key
+ },
+ changeLanguage: vi.fn(),
+ },
+ }),
+}))
+
+// Mock translations
+vi.mock("@src/i18n/setup", () => ({
+ default: {
+ t: (key: string, options?: Record) => {
+ // Mock specific translations used in tests
+ if (key === "chat:questions.questionNumberOfTotal" && options) {
+ return `Question ${options.current} of ${options.total}`
+ }
+ if (key === "chat:questions.typeAnswer") return "Type your answer..."
+ if (key === "chat:questions.previous") return "Previous"
+ if (key === "chat:questions.next") return "Next"
+ if (key === "chat:questions.finish") return "Finish"
+ return key
+ },
+ changeLanguage: vi.fn(),
+ },
+ loadTranslations: vi.fn(),
+}))
+
+const TestWrapper = ({ children }: { children: React.ReactNode }) => (
+ {children}
+)
+
+describe("MultiQuestionHandler", () => {
+ const mockOnSendResponse = vi.fn()
+
+ beforeEach(() => {
+ mockOnSendResponse.mockClear()
+ })
+
+ it("should render single question correctly", () => {
+ const questions = ["What is your name?"]
+ render(
+
+
+ ,
+ )
+
+ expect(screen.getByText("Question 1 of 1")).toBeInTheDocument()
+ expect(screen.getByText("What is your name?")).toBeInTheDocument()
+ expect(screen.getByText("Finish")).toBeInTheDocument()
+ })
+
+ it("should render multiple questions with navigation", () => {
+ const questions = ["What is your name?", "What is your age?"]
+ render(
+
+
+ ,
+ )
+
+ expect(screen.getByText("Question 1 of 2")).toBeInTheDocument()
+ expect(screen.getByText("What is your name?")).toBeInTheDocument()
+ expect(screen.getByText("Next")).toBeInTheDocument()
+ expect(screen.queryByText("Previous")).not.toBeInTheDocument()
+ expect(screen.queryByText("Finish")).not.toBeInTheDocument()
+ })
+
+ it("should navigate between questions without sending responses", () => {
+ const questions = ["What is your name?", "What is your age?"]
+ render(
+
+
+ ,
+ )
+
+ const textarea = screen.getByPlaceholderText("Type your answer...")
+ fireEvent.change(textarea, { target: { value: "John" } })
+
+ const nextButton = screen.getByText("Next")
+ fireEvent.click(nextButton)
+
+ expect(screen.getByText("Question 2 of 2")).toBeInTheDocument()
+ expect(screen.getByText("What is your age?")).toBeInTheDocument()
+ expect(mockOnSendResponse).not.toHaveBeenCalled()
+
+ const previousButton = screen.getByText("Previous")
+ fireEvent.click(previousButton)
+
+ expect(screen.getByText("Question 1 of 2")).toBeInTheDocument()
+ expect(screen.getByText("What is your name?")).toBeInTheDocument()
+ expect(textarea).toHaveValue("John")
+ expect(mockOnSendResponse).not.toHaveBeenCalled()
+ })
+
+ it("should only send response when Finish is clicked", () => {
+ const questions = ["What is your name?", "What is your age?"]
+ render(
+
+
+ ,
+ )
+
+ const textarea = screen.getByPlaceholderText("Type your answer...")
+ fireEvent.change(textarea, { target: { value: "John" } })
+
+ const nextButton = screen.getByText("Next")
+ fireEvent.click(nextButton)
+
+ fireEvent.change(textarea, { target: { value: "25" } })
+
+ const finishButton = screen.getByText("Finish")
+ fireEvent.click(finishButton)
+
+ expect(mockOnSendResponse).toHaveBeenCalledTimes(1)
+ expect(mockOnSendResponse).toHaveBeenCalledWith(
+ "Question: What is your name?\nAnswer: John\n\nQuestion: What is your age?\nAnswer: 25",
+ )
+ })
+
+ it("should handle skipped questions", () => {
+ const questions = ["What is your name?", "What is your age?"]
+ render(
+
+
+ ,
+ )
+
+ const nextButton = screen.getByText("Next")
+ fireEvent.click(nextButton) // Skip first question
+
+ const textarea = screen.getByPlaceholderText("Type your answer...")
+ fireEvent.change(textarea, { target: { value: "25" } })
+
+ const finishButton = screen.getByText("Finish")
+ fireEvent.click(finishButton)
+
+ expect(mockOnSendResponse).toHaveBeenCalledWith(
+ "Question: What is your name?\nAnswer: (skipped)\n\nQuestion: What is your age?\nAnswer: 25",
+ )
+ })
+
+ it("should preserve answers when navigating back and forth", () => {
+ const questions = ["Q1", "Q2", "Q3"]
+ render(
+
+
+ ,
+ )
+
+ const textarea = screen.getByPlaceholderText("Type your answer...")
+
+ // Answer Q1
+ fireEvent.change(textarea, { target: { value: "A1" } })
+ fireEvent.click(screen.getByText("Next"))
+
+ // Answer Q2
+ fireEvent.change(textarea, { target: { value: "A2" } })
+ fireEvent.click(screen.getByText("Next"))
+
+ // Go back to Q1
+ fireEvent.click(screen.getByText("Previous"))
+ fireEvent.click(screen.getByText("Previous"))
+
+ expect(textarea).toHaveValue("A1")
+
+ // Go to Q2
+ fireEvent.click(screen.getByText("Next"))
+ expect(textarea).toHaveValue("A2")
+
+ expect(mockOnSendResponse).not.toHaveBeenCalled()
+ })
+})