fix(web-shell): require answers before submitting questions (#11341)

Co-authored-by: 钉萁 <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
This commit is contained in:
authored and GitHub committed 2026-09-08 12:00:57 +00:00
1 parent 4fa1476c18
commit 0fde526891
2 files changed
+21 -24

No files matched your search

@@ -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);
});
});
@@ -180,15 +180,24 @@ export function AskUserQuestion({
const handleSubmit = useCallback(
(submittedAnswers?: Record<string, string>) => {
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({
<button
type="button"
className={`${styles.button} ${styles.submitButton}`}
disabled={submitting}
disabled={submitting || !allQuestionsAnswered}
aria-busy={submitting}
aria-keyshortcuts="Control+Enter Meta+Enter"
data-shortcut={submitShortcutLabel}