From 0fde52689184858ad953c36a2db3d773d28d8dff Mon Sep 17 00:00:00 2001 From: ytahdn <1294726970@qq.com> Date: Tue, 8 Sep 2026 12:00:57 +0000 Subject: [PATCH] fix(web-shell): require answers before submitting questions (#11341) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 钉萁 Co-authored-by: Shaojin Wen --- .../messages/AskUserQuestion.test.tsx | 30 ++++++------------- .../components/messages/AskUserQuestion.tsx | 15 ++++++++-- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/packages/web-shell/client/components/messages/AskUserQuestion.test.tsx b/packages/web-shell/client/components/messages/AskUserQuestion.test.tsx index 364058e739e..8b66d6fbc41 100644 --- a/packages/web-shell/client/components/messages/AskUserQuestion.test.tsx +++ b/packages/web-shell/client/components/messages/AskUserQuestion.test.tsx @@ -948,10 +948,7 @@ describe('AskUserQuestion multiple questions', () => { ctrlKey: true, cancelable: true, }); - expect(onConfirm).toHaveBeenCalledWith('req-multiple', 'submit', { - '0': '', - '1': 'Small', - }); + expect(onConfirm).not.toHaveBeenCalled(); }); it('submits directly when Enter is pressed on the last question', () => { @@ -977,7 +974,7 @@ describe('AskUserQuestion multiple questions', () => { }); }); - it('submits incomplete answers with Command/Ctrl+Enter', () => { + it('does not submit incomplete answers with Command/Ctrl+Enter', () => { render(undefined, multipleQuestionsRequest); const event = pressKey(optionButtons()[0]!, 'Enter', { @@ -986,17 +983,14 @@ describe('AskUserQuestion multiple questions', () => { }); expect(event.defaultPrevented).toBe(true); - expect(onConfirm).toHaveBeenCalledWith('req-multiple', 'submit', { - '0': 'Red', - '1': '', - }); + expect(onConfirm).not.toHaveBeenCalled(); }); it.each([ ['Control', { ctrlKey: true }], ['Command', { metaKey: true }], ] as const)( - 'submits from an intermediate custom input with %s+Enter', + 'does not submit incomplete answers from custom input with %s+Enter', (_modifier, modifierInit) => { render(undefined, multipleQuestionsRequest); act(() => optionButtons()[2]!.click()); @@ -1015,10 +1009,7 @@ describe('AskUserQuestion multiple questions', () => { }); expect(event.defaultPrevented).toBe(true); - expect(onConfirm).toHaveBeenCalledWith('req-multiple', 'submit', { - '0': 'Purple', - '1': '', - }); + expect(onConfirm).not.toHaveBeenCalled(); }, ); @@ -1266,7 +1257,7 @@ describe('AskUserQuestion multi-select', () => { const opts = optionButtons(); expect(opts[0]!.getAttribute('aria-pressed')).toBe('false'); expect(opts[0]!.tabIndex).toBe(0); - expect(submitButton()!.disabled).toBe(false); + expect(submitButton()!.disabled).toBe(true); expect(opts[0]!.hasAttribute('aria-checked')).toBe(false); expect(opts[0]!.getAttribute('role')).not.toBe('radio'); }); @@ -1292,7 +1283,7 @@ describe('AskUserQuestion multi-select', () => { const opts = optionButtons(); expect(opts[0]!.getAttribute('aria-pressed')).toBe('false'); expect(opts[0]!.tabIndex).toBe(0); - expect(submitButton()!.disabled).toBe(false); + expect(submitButton()!.disabled).toBe(true); }); it('toggles options and submits the joined selection', () => { @@ -1459,7 +1450,7 @@ describe('AskUserQuestion multi-select', () => { expect(optionButtons()[0]!.getAttribute('aria-pressed')).toBe('true'); }); - it('submits from the final multi-select question when an earlier answer is missing', () => { + it('does not submit from the final question when an earlier answer is missing', () => { const requestWithMultiFinal: PermissionRequest = { ...multipleQuestionsRequest, rawInput: { @@ -1484,10 +1475,7 @@ describe('AskUserQuestion multi-select', () => { pressKey(optionButtons()[0]!, 'Enter'); expect(optionButtons()[0]!.getAttribute('aria-pressed')).toBe('true'); - expect(onConfirm).toHaveBeenCalledWith('req-multiple', 'submit', { - '0': '', - '1': 'Option A', - }); + expect(onConfirm).not.toHaveBeenCalled(); expect(submitButton()!.disabled).toBe(true); }); }); diff --git a/packages/web-shell/client/components/messages/AskUserQuestion.tsx b/packages/web-shell/client/components/messages/AskUserQuestion.tsx index 4544030ba2a..985edfc5e71 100644 --- a/packages/web-shell/client/components/messages/AskUserQuestion.tsx +++ b/packages/web-shell/client/components/messages/AskUserQuestion.tsx @@ -180,15 +180,24 @@ export function AskUserQuestion({ const handleSubmit = useCallback( (submittedAnswers?: Record) => { if (submittedRef.current) return; + const result = submittedAnswers ?? buildResult(); + if (!questions.every((_, idx) => hasCustomAnswer(result[String(idx)]))) { + return; + } const submitOption = request.options.find((o) => o.kind === 'allow_once'); if (!submitOption) { const message = t('askUser.submitOptionUnavailable'); onError(new Error(message), message); return; } - void submitDecision(submitOption.id, submittedAnswers ?? buildResult()); + void submitDecision(submitOption.id, result); }, - [buildResult, onError, request.options, submitDecision, t], + [buildResult, onError, questions, request.options, submitDecision, t], + ); + + const currentAnswers = buildResult(); + const allQuestionsAnswered = questions.every((_, idx) => + hasCustomAnswer(currentAnswers[String(idx)]), ); const handleCancel = useCallback(() => { @@ -935,7 +944,7 @@ export function AskUserQuestion({