From 0688f258cde8602f1f463d69592e2efb9faa209d Mon Sep 17 00:00:00 2001 From: Para <62068271+murd0cc@users.noreply.github.com> Date: Mon, 10 Aug 2026 02:01:45 +0300 Subject: [PATCH 1/4] issue #685 - point 8 --- .../src/components/chat/FollowUpSuggest.tsx | 5 ++-- .../chat/__tests__/FollowUpSuggest.spec.tsx | 23 +++++++++++++++++++ .../settings/AutoApproveSettings.tsx | 8 +++++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/webview-ui/src/components/chat/FollowUpSuggest.tsx b/webview-ui/src/components/chat/FollowUpSuggest.tsx index 42b41bacfa..b9e1c9eecd 100644 --- a/webview-ui/src/components/chat/FollowUpSuggest.tsx +++ b/webview-ui/src/components/chat/FollowUpSuggest.tsx @@ -36,14 +36,15 @@ export const FollowUpSuggest = ({ // Start countdown timer when auto-approval is enabled for follow-up questions useEffect(() => { // Only start countdown if auto-approval is enabled for follow-up questions and no suggestion has been selected - // Also stop countdown if the question has been answered or auto-approval is paused (user is typing) + // Also stop countdown if the question has been answered or auto-approval is paused (user is typing) or timer is disabled (set to 0) if ( autoApprovalEnabled && alwaysAllowFollowupQuestions && suggestions.length > 0 && !suggestionSelected && !isAnswered && - !isFollowUpAutoApprovalPaused + !isFollowUpAutoApprovalPaused && + (followupAutoApproveTimeoutMs ?? DEFAULT_FOLLOWUP_TIMEOUT_MS) > 0 ) { // Start with the configured timeout in seconds const timeoutMs = diff --git a/webview-ui/src/components/chat/__tests__/FollowUpSuggest.spec.tsx b/webview-ui/src/components/chat/__tests__/FollowUpSuggest.spec.tsx index a46df75b80..2672e47306 100644 --- a/webview-ui/src/components/chat/__tests__/FollowUpSuggest.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/FollowUpSuggest.spec.tsx @@ -74,6 +74,13 @@ describe("FollowUpSuggest", () => { followupAutoApproveTimeoutMs: 3000, // 3 seconds for testing } + // Test state with timeout disabled (0) + const disabledTimeoutState: TestExtensionState = { + autoApprovalEnabled: true, + alwaysAllowFollowupQuestions: true, + followupAutoApproveTimeoutMs: 0, // Disabled + } + beforeEach(() => { vi.clearAllMocks() vi.useFakeTimers() @@ -218,6 +225,22 @@ describe("FollowUpSuggest", () => { expect(screen.queryByText(/\d+s/)).not.toBeInTheDocument() }) + // Should not show countdown when timeout is disabled (set to 0) + it("should not show countdown when timeout is disabled (set to 0)", () => { + renderWithTestProviders( + , + disabledTimeoutState, + ) + + // Should not show countdown when timeout is disabled + expect(screen.queryByText(/\d+s/)).not.toBeInTheDocument() + }) + it("should not render when no suggestions are provided", () => { const { container } = renderWithTestProviders(
- {followupAutoApproveTimeoutMs / 1000}s + + {followupAutoApproveTimeoutMs === 0 + ? "Disabled" + : `${followupAutoApproveTimeoutMs / 1000}s`} +
{t("settings:autoApprove.followupQuestions.timeoutLabel")} From 04aa5fad937595e2100c935d182aee0a67f737af Mon Sep 17 00:00:00 2001 From: Para <62068271+murd0cc@users.noreply.github.com> Date: Tue, 11 Aug 2026 23:59:03 +0300 Subject: [PATCH 2/4] PR requested changes implemented --- .../chat/__tests__/FollowUpSuggest.spec.tsx | 19 +++++++++ .../settings/AutoApproveSettings.tsx | 2 +- .../__tests__/AutoApproveSettings.spec.tsx | 14 +++++++ .../AutoApproveSettings.visual.fixture.tsx | 36 ++++++++++++++++++ .../__tests__/AutoApproveSettings.visual.tsx | 18 +++++++++ ...approve-followup-timeout-disabled-dark.png | Bin 0 -> 205 bytes webview-ui/src/i18n/locales/ca/settings.json | 3 +- webview-ui/src/i18n/locales/de/settings.json | 3 +- webview-ui/src/i18n/locales/en/settings.json | 3 +- webview-ui/src/i18n/locales/es/settings.json | 3 +- webview-ui/src/i18n/locales/fr/settings.json | 3 +- webview-ui/src/i18n/locales/hi/settings.json | 3 +- webview-ui/src/i18n/locales/id/settings.json | 3 +- webview-ui/src/i18n/locales/it/settings.json | 3 +- webview-ui/src/i18n/locales/ja/settings.json | 3 +- webview-ui/src/i18n/locales/ko/settings.json | 3 +- webview-ui/src/i18n/locales/nl/settings.json | 3 +- webview-ui/src/i18n/locales/pl/settings.json | 3 +- .../src/i18n/locales/pt-BR/settings.json | 3 +- webview-ui/src/i18n/locales/ru/settings.json | 3 +- webview-ui/src/i18n/locales/tr/settings.json | 3 +- webview-ui/src/i18n/locales/vi/settings.json | 3 +- .../src/i18n/locales/zh-CN/settings.json | 3 +- .../src/i18n/locales/zh-TW/settings.json | 3 +- 24 files changed, 124 insertions(+), 19 deletions(-) create mode 100644 webview-ui/src/components/settings/__tests__/AutoApproveSettings.visual.fixture.tsx create mode 100644 webview-ui/src/components/settings/__tests__/AutoApproveSettings.visual.tsx create mode 100644 webview-ui/src/components/settings/__tests__/__screenshots__/auto-approve-followup-timeout-disabled-dark.png diff --git a/webview-ui/src/components/chat/__tests__/FollowUpSuggest.spec.tsx b/webview-ui/src/components/chat/__tests__/FollowUpSuggest.spec.tsx index 2672e47306..169df1aee4 100644 --- a/webview-ui/src/components/chat/__tests__/FollowUpSuggest.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/FollowUpSuggest.spec.tsx @@ -241,6 +241,25 @@ describe("FollowUpSuggest", () => { expect(screen.queryByText(/\d+s/)).not.toBeInTheDocument() }) + it("should not show countdown when timeout is negative", () => { + const negativeTimeoutState: TestExtensionState = { + ...defaultTestState, + followupAutoApproveTimeoutMs: -1000, + } + + renderWithTestProviders( + , + negativeTimeoutState, + ) + + expect(screen.queryByText(/\d+s/)).not.toBeInTheDocument() + }) + it("should not render when no suggestions are provided", () => { const { container } = renderWithTestProviders( {followupAutoApproveTimeoutMs === 0 - ? "Disabled" + ? t("settings:autoApprove.followupQuestions.timeoutDisabled") : `${followupAutoApproveTimeoutMs / 1000}s`}
diff --git a/webview-ui/src/components/settings/__tests__/AutoApproveSettings.spec.tsx b/webview-ui/src/components/settings/__tests__/AutoApproveSettings.spec.tsx index c798b7a4a9..5ee6026ed8 100644 --- a/webview-ui/src/components/settings/__tests__/AutoApproveSettings.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/AutoApproveSettings.spec.tsx @@ -161,4 +161,18 @@ describe("AutoApproveSettings - Save/Discard contract", () => { expect(screen.getByTestId("allowed-commands-heading")).toBeInTheDocument() expect(screen.getByTestId("denied-commands-heading")).toBeInTheDocument() }) + + it("renders disabled timeout label when follow-up auto-approve timeout is 0", () => { + renderSettings({ alwaysAllowFollowupQuestions: true, followupAutoApproveTimeoutMs: 0 }) + + expect(screen.getByTestId("followup-timeout-slider")).toBeInTheDocument() + expect(screen.getByText("settings:autoApprove.followupQuestions.timeoutDisabled")).toBeInTheDocument() + }) + + it("renders timeout in seconds when follow-up auto-approve timeout is non-zero", () => { + renderSettings({ alwaysAllowFollowupQuestions: true, followupAutoApproveTimeoutMs: 5000 }) + + expect(screen.getByTestId("followup-timeout-slider")).toBeInTheDocument() + expect(screen.getByText("5s")).toBeInTheDocument() + }) }) diff --git a/webview-ui/src/components/settings/__tests__/AutoApproveSettings.visual.fixture.tsx b/webview-ui/src/components/settings/__tests__/AutoApproveSettings.visual.fixture.tsx new file mode 100644 index 0000000000..6f4e4707e3 --- /dev/null +++ b/webview-ui/src/components/settings/__tests__/AutoApproveSettings.visual.fixture.tsx @@ -0,0 +1,36 @@ +/* v8 ignore file -- Playwright component fixture is covered by the visual test. */ +import React from "react" + +import { TranslationContext } from "@/i18n/TranslationContext" +import { ExtensionStateContext } from "@/context/ExtensionStateContext" +import { AutoApproveSettings } from "../AutoApproveSettings" + +const extensionStateValue = { + autoApprovalEnabled: false, + setAutoApprovalEnabled: () => {}, + alwaysAllowReadOnly: false, + alwaysAllowWrite: false, + alwaysAllowExecute: false, + alwaysAllowMcp: false, + alwaysAllowModeSwitch: false, + alwaysAllowSubtasks: false, + alwaysAllowFollowupQuestions: true, +} as any + +export const AutoApproveSettingsFixture = () => ( + key, + i18n: null as unknown as typeof import("../../../i18n/setup").default, + }}> + +
+ {}} + /> +
+
+
+) diff --git a/webview-ui/src/components/settings/__tests__/AutoApproveSettings.visual.tsx b/webview-ui/src/components/settings/__tests__/AutoApproveSettings.visual.tsx new file mode 100644 index 0000000000..9fea71d4e0 --- /dev/null +++ b/webview-ui/src/components/settings/__tests__/AutoApproveSettings.visual.tsx @@ -0,0 +1,18 @@ +import React from "react" + +import { expect, test } from "../../../../playwright/coverage-fixture" +import { AutoApproveSettingsFixture } from "./AutoApproveSettings.visual.fixture" + +test("renders follow-up timeout row with disabled state label in the VS Code dark theme", async ({ mount, page }) => { + // The full settings bundle can leave a bare Zod reference after CT tree-shaking. + await page.evaluate(() => Object.assign(globalThis, { z: undefined })) + + const component = await mount() + + await component.evaluate(async () => { + await document.fonts.ready + await new Promise((resolve) => requestAnimationFrame(() => resolve())) + }) + + await expect(component).toHaveScreenshot("auto-approve-followup-timeout-disabled-dark.png") +}) diff --git a/webview-ui/src/components/settings/__tests__/__screenshots__/auto-approve-followup-timeout-disabled-dark.png b/webview-ui/src/components/settings/__tests__/__screenshots__/auto-approve-followup-timeout-disabled-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..db87f68911f60194136207b64222fe42540967af GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0y~yVB!F>6*!oH* k|8@~+mw~Qf1G$Eofo~1NT#m%>1wau7Pgg&ebxsLQ04Fjnga7~l literal 0 HcmV?d00001 diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 73827b1ec1..c8efd130c4 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -323,7 +323,8 @@ "followupQuestions": { "label": "Pregunta", "description": "Seleccionar automàticament la primera resposta suggerida per a preguntes de seguiment després del temps d'espera configurat", - "timeoutLabel": "Temps d'espera abans de seleccionar automàticament la primera resposta" + "timeoutLabel": "Temps d'espera abans de seleccionar automàticament la primera resposta", + "timeoutDisabled": "Desactivat" }, "execute": { "label": "Executar", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 8078037525..b650941fc5 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -323,7 +323,8 @@ "followupQuestions": { "label": "Frage", "description": "Automatisch die erste vorgeschlagene Antwort für Folgefragen nach der konfigurierten Zeitüberschreitung auswählen", - "timeoutLabel": "Wartezeit vor der automatischen Auswahl der ersten Antwort" + "timeoutLabel": "Wartezeit vor der automatischen Auswahl der ersten Antwort", + "timeoutDisabled": "Deaktiviert" }, "execute": { "label": "Ausführen", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 14a7476a75..6f4ac738f0 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -401,7 +401,8 @@ "followupQuestions": { "label": "Question", "description": "Automatically select the first suggested answer for follow-up questions after the configured timeout", - "timeoutLabel": "Time to wait before auto-selecting the first answer" + "timeoutLabel": "Time to wait before auto-selecting the first answer", + "timeoutDisabled": "Disabled" }, "execute": { "label": "Execute", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index e629e43b50..529d5f7fad 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -323,7 +323,8 @@ "followupQuestions": { "label": "Pregunta", "description": "Seleccionar automáticamente la primera respuesta sugerida para preguntas de seguimiento después del tiempo de espera configurado", - "timeoutLabel": "Tiempo de espera antes de seleccionar automáticamente la primera respuesta" + "timeoutLabel": "Tiempo de espera antes de seleccionar automáticamente la primera respuesta", + "timeoutDisabled": "Deshabilitado" }, "execute": { "label": "Ejecutar", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 6048e2274c..9b2358bae8 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -324,7 +324,8 @@ "followupQuestions": { "label": "Question", "description": "Sélectionner automatiquement la première réponse suggérée pour les questions de suivi après le délai configuré", - "timeoutLabel": "Temps d'attente avant la sélection automatique de la première réponse" + "timeoutLabel": "Temps d'attente avant la sélection automatique de la première réponse", + "timeoutDisabled": "Désactivé" }, "execute": { "label": "Exécuter", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 28d0b8699b..2c03328615 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -323,7 +323,8 @@ "followupQuestions": { "label": "प्रश्न", "description": "कॉन्फ़िगर किए गए टाइमआउट के बाद अनुवर्ती प्रश्नों के लिए पहले सुझाए गए उत्तर को स्वचालित रूप से चुनें", - "timeoutLabel": "पहले उत्तर को स्वचालित रूप से चुनने से पहले प्रतीक्षा करने का समय" + "timeoutLabel": "पहले उत्तर को स्वचालित रूप से चुनने से पहले प्रतीक्षा करने का समय", + "timeoutDisabled": "अक्षम" }, "execute": { "label": "निष्पादित करें", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index bf049395c4..81c317214a 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -323,7 +323,8 @@ "followupQuestions": { "label": "Pertanyaan", "description": "Secara otomatis memilih jawaban pertama yang disarankan untuk pertanyaan lanjutan setelah batas waktu yang dikonfigurasi", - "timeoutLabel": "Waktu tunggu sebelum otomatis memilih jawaban pertama" + "timeoutLabel": "Waktu tunggu sebelum otomatis memilih jawaban pertama", + "timeoutDisabled": "Dinonaktifkan" }, "execute": { "label": "Eksekusi", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 577a74a77a..35259f3382 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -323,7 +323,8 @@ "followupQuestions": { "label": "Domanda", "description": "Seleziona automaticamente la prima risposta suggerita per le domande di follow-up dopo il timeout configurato", - "timeoutLabel": "Tempo di attesa prima di selezionare automaticamente la prima risposta" + "timeoutLabel": "Tempo di attesa prima di selezionare automaticamente la prima risposta", + "timeoutDisabled": "Disabilitato" }, "execute": { "label": "Esegui", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 1113ac32a6..169eb60858 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -323,7 +323,8 @@ "followupQuestions": { "label": "質問", "description": "設定された時間が経過すると、フォローアップ質問の最初の提案回答を自動的に選択します", - "timeoutLabel": "最初の回答を自動選択するまでの待機時間" + "timeoutLabel": "最初の回答を自動選択するまでの待機時間", + "timeoutDisabled": "無効" }, "execute": { "label": "実行", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 27e8493bd4..39e780c3c7 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -323,7 +323,8 @@ "followupQuestions": { "label": "질문", "description": "설정된 시간이 지나면 후속 질문에 대한 첫 번째 제안 답변을 자동으로 선택합니다", - "timeoutLabel": "첫 번째 답변을 자동 선택하기 전 대기 시간" + "timeoutLabel": "첫 번째 답변을 자동 선택하기 전 대기 시간", + "timeoutDisabled": "사용 안 함" }, "execute": { "label": "실행", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 394cdd48f2..27f6ebccfb 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -323,7 +323,8 @@ "followupQuestions": { "label": "Vraag", "description": "Selecteer automatisch het eerste voorgestelde antwoord voor vervolgvragen na de geconfigureerde time-out", - "timeoutLabel": "Wachttijd voordat het eerste antwoord automatisch wordt geselecteerd" + "timeoutLabel": "Wachttijd voordat het eerste antwoord automatisch wordt geselecteerd", + "timeoutDisabled": "Uitgeschakeld" }, "execute": { "label": "Uitvoeren", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 864e4ffde1..1e9fb88c1b 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -323,7 +323,8 @@ "followupQuestions": { "label": "Pytanie", "description": "Automatycznie wybierz pierwszą sugerowaną odpowiedź na pytania uzupełniające po skonfigurowanym limicie czasu", - "timeoutLabel": "Czas oczekiwania przed automatycznym wybraniem pierwszej odpowiedzi" + "timeoutLabel": "Czas oczekiwania przed automatycznym wybraniem pierwszej odpowiedzi", + "timeoutDisabled": "Wyłączone" }, "execute": { "label": "Wykonaj", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index a1948a0218..078b78e815 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -323,7 +323,8 @@ "followupQuestions": { "label": "Pergunta", "description": "Selecionar automaticamente a primeira resposta sugerida para perguntas de acompanhamento após o tempo limite configurado", - "timeoutLabel": "Tempo de espera antes de selecionar automaticamente a primeira resposta" + "timeoutLabel": "Tempo de espera antes de selecionar automaticamente a primeira resposta", + "timeoutDisabled": "Desativado" }, "execute": { "label": "Executar", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index f36fe62539..bb807c930f 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -323,7 +323,8 @@ "followupQuestions": { "label": "Вопрос", "description": "Автоматически выбирать первый предложенный ответ на дополнительные вопросы после настроенного тайм-аута", - "timeoutLabel": "Время ожидания перед автоматическим выбором первого ответа" + "timeoutLabel": "Время ожидания перед автоматическим выбором первого ответа", + "timeoutDisabled": "Отключено" }, "execute": { "label": "Выполнение", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 9099677679..638fd6c8b2 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -323,7 +323,8 @@ "followupQuestions": { "label": "Soru", "description": "Yapılandırılan zaman aşımından sonra takip sorularına ilişkin ilk önerilen yanıtı otomatik olarak seç", - "timeoutLabel": "İlk yanıtı otomatik olarak seçmeden önce beklenecek süre" + "timeoutLabel": "İlk yanıtı otomatik olarak seçmeden önce beklenecek süre", + "timeoutDisabled": "Devre dışı" }, "execute": { "label": "Yürüt", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index c66b236165..11d368ea1e 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -323,7 +323,8 @@ "followupQuestions": { "label": "Câu hỏi", "description": "Tự động chọn câu trả lời đầu tiên được đề xuất cho các câu hỏi tiếp theo sau thời gian chờ đã cấu hình", - "timeoutLabel": "Thời gian chờ trước khi tự động chọn câu trả lời đầu tiên" + "timeoutLabel": "Thời gian chờ trước khi tự động chọn câu trả lời đầu tiên", + "timeoutDisabled": "Đã tắt" }, "execute": { "label": "Thực thi", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 22742e0e0e..fec2653574 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -323,7 +323,8 @@ "followupQuestions": { "label": "问题", "description": "在配置的超时时间后自动选择后续问题的第一个建议答案", - "timeoutLabel": "自动选择第一个答案前的等待时间" + "timeoutLabel": "自动选择第一个答案前的等待时间", + "timeoutDisabled": "已禁用" }, "execute": { "label": "执行", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 4255a2e697..80a7a94dbd 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -348,7 +348,8 @@ "followupQuestions": { "label": "後續提問", "description": "在設定的逾時時間過後,自動選擇後續問題的第一個建議答案", - "timeoutLabel": "自動選擇第一個答案前的等待時間" + "timeoutLabel": "自動選擇第一個答案前的等待時間", + "timeoutDisabled": "已停用" }, "execute": { "label": "執行命令", From 9411e41853f512b970473d7cc0bbccc30a53a009 Mon Sep 17 00:00:00 2001 From: Para <62068271+murd0cc@users.noreply.github.com> Date: Wed, 12 Aug 2026 00:13:19 +0300 Subject: [PATCH 3/4] Maintainability & Code Quality --- webview-ui/eslint-suppressions.json | 1 + .../__tests__/AutoApproveSettings.spec.tsx | 83 +++++++++++++++++-- .../AutoApproveSettings.visual.fixture.tsx | 24 ++---- 3 files changed, 85 insertions(+), 23 deletions(-) create mode 100644 webview-ui/eslint-suppressions.json diff --git a/webview-ui/eslint-suppressions.json b/webview-ui/eslint-suppressions.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/webview-ui/eslint-suppressions.json @@ -0,0 +1 @@ +{} diff --git a/webview-ui/src/components/settings/__tests__/AutoApproveSettings.spec.tsx b/webview-ui/src/components/settings/__tests__/AutoApproveSettings.spec.tsx index 5ee6026ed8..37fa40024d 100644 --- a/webview-ui/src/components/settings/__tests__/AutoApproveSettings.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/AutoApproveSettings.spec.tsx @@ -33,6 +33,24 @@ vi.mock("@/hooks/useAutoApprovalState", () => ({ useAutoApprovalState: () => ({ effectiveAutoApprovalEnabled: false, hasEnabledOptions: false }), })) +vi.mock("@/components/ui", async (importOriginal) => { + const actual = await importOriginal() + + return { + ...actual, + Button: ({ children, ...props }: any) => , + Input: (props: any) => , + Slider: ({ value, onValueChange, ...props }: any) => ( + onValueChange?.([Number((event.target as HTMLInputElement).value)])} + {...props} + /> + ), + } +}) + const renderSettings = (overrides = {}) => { const setCachedStateField = vi.fn() const props = { @@ -163,16 +181,69 @@ describe("AutoApproveSettings - Save/Discard contract", () => { }) it("renders disabled timeout label when follow-up auto-approve timeout is 0", () => { - renderSettings({ alwaysAllowFollowupQuestions: true, followupAutoApproveTimeoutMs: 0 }) - - expect(screen.getByTestId("followup-timeout-slider")).toBeInTheDocument() + const { setCachedStateField } = renderSettings({ + alwaysAllowFollowupQuestions: true, + followupAutoApproveTimeoutMs: 0, + }) + + const slider = screen.getByTestId("followup-timeout-slider") as HTMLInputElement + expect(slider).toBeInTheDocument() + expect(slider.value).toBe("0") expect(screen.getByText("settings:autoApprove.followupQuestions.timeoutDisabled")).toBeInTheDocument() + + fireEvent.change(slider, { target: { value: "4000" } }) + + expect(setCachedStateField).toHaveBeenCalledWith("followupAutoApproveTimeoutMs", 4000) + expectNoImmediateUpdateSettings() }) it("renders timeout in seconds when follow-up auto-approve timeout is non-zero", () => { - renderSettings({ alwaysAllowFollowupQuestions: true, followupAutoApproveTimeoutMs: 5000 }) - - expect(screen.getByTestId("followup-timeout-slider")).toBeInTheDocument() + const { setCachedStateField } = renderSettings({ + alwaysAllowFollowupQuestions: true, + followupAutoApproveTimeoutMs: 5000, + }) + + const slider = screen.getByTestId("followup-timeout-slider") as HTMLInputElement + expect(slider).toBeInTheDocument() + expect(slider.value).toBe("5000") expect(screen.getByText("5s")).toBeInTheDocument() + + fireEvent.change(slider, { target: { value: "0" } }) + + expect(setCachedStateField).toHaveBeenCalledWith("followupAutoApproveTimeoutMs", 0) + expectNoImmediateUpdateSettings() + }) + + it("uses the default timeout value when timeout is unset and follow-up auto-approve is enabled", () => { + renderSettings({ alwaysAllowFollowupQuestions: true }) + + const slider = screen.getByTestId("followup-timeout-slider") as HTMLInputElement + expect(slider.value).toBe("60000") + expect(screen.getByText("60s")).toBeInTheDocument() + }) + + it("does not render the follow-up timeout controls when follow-up auto-approve is disabled or unset", () => { + const { rerender } = render( + , + ) + + expect(screen.queryByTestId("followup-timeout-slider")).not.toBeInTheDocument() + + rerender( + , + ) + + expect(screen.queryByTestId("followup-timeout-slider")).not.toBeInTheDocument() }) }) diff --git a/webview-ui/src/components/settings/__tests__/AutoApproveSettings.visual.fixture.tsx b/webview-ui/src/components/settings/__tests__/AutoApproveSettings.visual.fixture.tsx index 6f4e4707e3..0002607348 100644 --- a/webview-ui/src/components/settings/__tests__/AutoApproveSettings.visual.fixture.tsx +++ b/webview-ui/src/components/settings/__tests__/AutoApproveSettings.visual.fixture.tsx @@ -2,28 +2,18 @@ import React from "react" import { TranslationContext } from "@/i18n/TranslationContext" -import { ExtensionStateContext } from "@/context/ExtensionStateContext" +import i18next from "@/i18n/setup" +import { ExtensionStateContextProvider } from "@/context/ExtensionStateContext" import { AutoApproveSettings } from "../AutoApproveSettings" -const extensionStateValue = { - autoApprovalEnabled: false, - setAutoApprovalEnabled: () => {}, - alwaysAllowReadOnly: false, - alwaysAllowWrite: false, - alwaysAllowExecute: false, - alwaysAllowMcp: false, - alwaysAllowModeSwitch: false, - alwaysAllowSubtasks: false, - alwaysAllowFollowupQuestions: true, -} as any - export const AutoApproveSettingsFixture = () => ( key, - i18n: null as unknown as typeof import("../../../i18n/setup").default, + t: (key) => (key === "settings:autoApprove.followupQuestions.timeoutDisabled" ? "Disabled" : key), + i18n: i18next, }}> - +
( setCachedStateField={() => {}} />
-
+
) From ca2ed956a63a6389e27c5031e9e38d3f0a65880e Mon Sep 17 00:00:00 2001 From: Para <62068271+murd0cc@users.noreply.github.com> Date: Wed, 12 Aug 2026 00:38:51 +0300 Subject: [PATCH 4/4] coverage improvements --- .../chat/__tests__/FollowUpSuggest.spec.tsx | 94 ++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/webview-ui/src/components/chat/__tests__/FollowUpSuggest.spec.tsx b/webview-ui/src/components/chat/__tests__/FollowUpSuggest.spec.tsx index 169df1aee4..e6d623b389 100644 --- a/webview-ui/src/components/chat/__tests__/FollowUpSuggest.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/FollowUpSuggest.spec.tsx @@ -1,5 +1,5 @@ import React, { createContext, useContext } from "react" -import { render, screen, act } from "@testing-library/react" +import { render, screen, act, fireEvent } from "@testing-library/react" import { TooltipProvider } from "@radix-ui/react-tooltip" import { FollowUpSuggest } from "../FollowUpSuggest" @@ -28,7 +28,7 @@ vi.mock("@src/i18n/TranslationContext", () => ({ interface TestExtensionState { autoApprovalEnabled: boolean alwaysAllowFollowupQuestions: boolean - followupAutoApproveTimeoutMs: number + followupAutoApproveTimeoutMs?: number } const TestExtensionStateContext = createContext(undefined) @@ -749,4 +749,94 @@ describe("FollowUpSuggest", () => { expect(mockOnCancelAutoApproval).toHaveBeenCalled() }) }) + + describe("suggestion interactions", () => { + it("cancels countdown and forwards click when user clicks a suggestion", () => { + renderWithTestProviders( + , + defaultTestState, + ) + + fireEvent.click(screen.getByText("First suggestion")) + + expect(mockOnSuggestionClick).toHaveBeenCalledWith( + expect.objectContaining({ answer: "First suggestion" }), + expect.objectContaining({ shiftKey: false }), + ) + expect(mockOnCancelAutoApproval).toHaveBeenCalled() + expect(screen.queryByText(/Selecting in \d+s/)).not.toBeInTheDocument() + }) + + it("keeps countdown when shift-clicking a suggestion", () => { + renderWithTestProviders( + , + defaultTestState, + ) + + mockOnCancelAutoApproval.mockClear() + fireEvent.click(screen.getByText("First suggestion"), { shiftKey: true }) + + expect(mockOnSuggestionClick).toHaveBeenCalledWith( + expect.objectContaining({ answer: "First suggestion" }), + expect.objectContaining({ shiftKey: true }), + ) + expect(mockOnCancelAutoApproval).not.toHaveBeenCalled() + expect(screen.getByText(/Selecting in 3s/)).toBeInTheDocument() + }) + + it("copies suggestion into input when the copy affordance is clicked", () => { + const { container } = renderWithTestProviders( + , + defaultTestState, + ) + + const copyAffordance = container.querySelector( + ".absolute.cursor-pointer.top-1\\.5.right-1\\.5", + ) as HTMLElement + + expect(copyAffordance).toBeTruthy() + fireEvent.click(copyAffordance) + + expect(mockOnSuggestionClick).toHaveBeenCalledWith( + expect.objectContaining({ answer: "First suggestion" }), + expect.objectContaining({ shiftKey: true }), + ) + expect(mockOnCancelAutoApproval).toHaveBeenCalled() + expect(screen.queryByText(/Selecting in \d+s/)).not.toBeInTheDocument() + }) + + it("uses default timeout when extension state timeout is undefined", () => { + const stateWithUndefinedTimeout = { + ...defaultTestState, + followupAutoApproveTimeoutMs: undefined, + } + + renderWithTestProviders( + , + stateWithUndefinedTimeout, + ) + + expect(screen.getByText(/Selecting in 60s/)).toBeInTheDocument() + }) + }) })