qwen-code/packages/cli/src/ui/components/messages/AskUserQuestionDialog.test.tsx
tanzhenxin 4e5fccf82e test(cli): remove flaky keyboard interaction tests on Windows
Remove tests that rely on arrow key and keyboard input timing which are
unreliable on Windows CI due to terminal emulation differences.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-13 16:13:31 +08:00

331 lines
9.1 KiB
TypeScript

/**
* @license
* Copyright 2025 Qwen
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi } from 'vitest';
import { AskUserQuestionDialog } from './AskUserQuestionDialog.js';
import type { ToolAskUserQuestionConfirmationDetails } from '@qwen-code/qwen-code-core';
import { ToolConfirmationOutcome } from '@qwen-code/qwen-code-core';
import { renderWithProviders } from '../../../test-utils/render.js';
const wait = (ms = 50) => new Promise((resolve) => setTimeout(resolve, ms));
const createSingleQuestion = (
overrides: Partial<
ToolAskUserQuestionConfirmationDetails['questions'][0]
> = {},
): ToolAskUserQuestionConfirmationDetails['questions'][0] => ({
question: 'What is your favorite color?',
header: 'Color',
options: [
{ label: 'Red', description: 'A warm color' },
{ label: 'Blue', description: 'A cool color' },
{ label: 'Green', description: '' },
],
multiSelect: false,
...overrides,
});
const createConfirmationDetails = (
overrides: Partial<ToolAskUserQuestionConfirmationDetails> = {},
): ToolAskUserQuestionConfirmationDetails => ({
type: 'ask_user_question',
title: 'Question',
questions: [createSingleQuestion()],
onConfirm: vi.fn(),
...overrides,
});
describe('<AskUserQuestionDialog />', () => {
describe('rendering', () => {
it('renders single question with options', () => {
const details = createConfirmationDetails();
const onConfirm = vi.fn();
const { lastFrame } = renderWithProviders(
<AskUserQuestionDialog
confirmationDetails={details}
onConfirm={onConfirm}
/>,
);
const output = lastFrame();
expect(output).toContain('What is your favorite color?');
expect(output).toContain('Red');
expect(output).toContain('Blue');
expect(output).toContain('Green');
expect(output).toContain('A warm color');
expect(output).toContain('A cool color');
});
it('renders header for single question', () => {
const details = createConfirmationDetails();
const onConfirm = vi.fn();
const { lastFrame } = renderWithProviders(
<AskUserQuestionDialog
confirmationDetails={details}
onConfirm={onConfirm}
/>,
);
expect(lastFrame()).toContain('Color');
});
it('renders "Type something..." custom input option', () => {
const details = createConfirmationDetails();
const onConfirm = vi.fn();
const { lastFrame } = renderWithProviders(
<AskUserQuestionDialog
confirmationDetails={details}
onConfirm={onConfirm}
/>,
);
expect(lastFrame()).toContain('Type something...');
});
it('renders help text for single select', () => {
const details = createConfirmationDetails();
const onConfirm = vi.fn();
const { lastFrame } = renderWithProviders(
<AskUserQuestionDialog
confirmationDetails={details}
onConfirm={onConfirm}
/>,
);
expect(lastFrame()).toContain('Enter: Select');
expect(lastFrame()).toContain('Esc: Cancel');
expect(lastFrame()).not.toContain('Switch tabs');
});
it('renders tabs for multiple questions', () => {
const details = createConfirmationDetails({
questions: [
createSingleQuestion({ header: 'Q1' }),
createSingleQuestion({
header: 'Q2',
question: 'Second question?',
}),
],
});
const onConfirm = vi.fn();
const { lastFrame } = renderWithProviders(
<AskUserQuestionDialog
confirmationDetails={details}
onConfirm={onConfirm}
/>,
);
const output = lastFrame();
expect(output).toContain('Q1');
expect(output).toContain('Q2');
expect(output).toContain('Submit');
expect(output).toContain('Switch tabs');
});
it('renders multi-select with checkboxes', () => {
const details = createConfirmationDetails({
questions: [createSingleQuestion({ multiSelect: true })],
});
const onConfirm = vi.fn();
const { lastFrame } = renderWithProviders(
<AskUserQuestionDialog
confirmationDetails={details}
onConfirm={onConfirm}
/>,
);
const output = lastFrame();
expect(output).toContain('[ ]');
expect(output).toContain('Space: Toggle');
expect(output).toContain('Enter: Confirm');
});
});
describe('single-select interaction', () => {
it('selects an option with Enter and submits immediately for single question', async () => {
const onConfirm = vi.fn();
const details = createConfirmationDetails();
const { stdin, unmount } = renderWithProviders(
<AskUserQuestionDialog
confirmationDetails={details}
onConfirm={onConfirm}
/>,
);
await wait();
// Press Enter to select the first option (Red)
stdin.write('\r');
await wait();
expect(onConfirm).toHaveBeenCalledWith(
ToolConfirmationOutcome.ProceedOnce,
{ answers: { 0: 'Red' } },
);
unmount();
});
it('navigates with number keys', async () => {
const onConfirm = vi.fn();
const details = createConfirmationDetails();
const { stdin, unmount } = renderWithProviders(
<AskUserQuestionDialog
confirmationDetails={details}
onConfirm={onConfirm}
/>,
);
await wait();
// Press '2' to select Blue
stdin.write('2');
await wait();
// Press Enter
stdin.write('\r');
await wait();
expect(onConfirm).toHaveBeenCalledWith(
ToolConfirmationOutcome.ProceedOnce,
{ answers: { 0: 'Blue' } },
);
unmount();
});
it('cancels with Escape', async () => {
const onConfirm = vi.fn();
const details = createConfirmationDetails();
const { stdin, unmount } = renderWithProviders(
<AskUserQuestionDialog
confirmationDetails={details}
onConfirm={onConfirm}
/>,
);
await wait();
stdin.write('\u001B'); // Escape
await wait();
expect(onConfirm).toHaveBeenCalledWith(ToolConfirmationOutcome.Cancel);
unmount();
});
});
describe('multi-select interaction', () => {
it('toggles options with Space', async () => {
const onConfirm = vi.fn();
const details = createConfirmationDetails({
questions: [createSingleQuestion({ multiSelect: true })],
});
const { stdin, lastFrame, unmount } = renderWithProviders(
<AskUserQuestionDialog
confirmationDetails={details}
onConfirm={onConfirm}
/>,
);
await wait();
// Space to toggle first option
stdin.write(' ');
await wait();
// Should show checked state
expect(lastFrame()).toContain('[✓]');
unmount();
});
});
describe('multiple questions', () => {
it('shows Submit tab for multiple questions', async () => {
const onConfirm = vi.fn();
const details = createConfirmationDetails({
questions: [
createSingleQuestion({ header: 'Q1' }),
createSingleQuestion({ header: 'Q2' }),
],
});
const { stdin, lastFrame, unmount } = renderWithProviders(
<AskUserQuestionDialog
confirmationDetails={details}
onConfirm={onConfirm}
/>,
);
await wait();
// Navigate to submit tab (right arrow twice: Q1 -> Q2 -> Submit)
stdin.write('\u001B[C'); // Right
await wait();
stdin.write('\u001B[C'); // Right
await wait();
const output = lastFrame();
expect(output).toContain('Submit answers');
expect(output).toContain('Cancel');
expect(output).toContain('Your answers');
unmount();
});
it('shows unanswered questions as (not answered) in Submit tab', async () => {
const onConfirm = vi.fn();
const details = createConfirmationDetails({
questions: [
createSingleQuestion({ header: 'Q1' }),
createSingleQuestion({ header: 'Q2' }),
],
});
const { stdin, lastFrame, unmount } = renderWithProviders(
<AskUserQuestionDialog
confirmationDetails={details}
onConfirm={onConfirm}
/>,
);
await wait();
// Navigate directly to submit tab without answering anything
stdin.write('\u001B[C'); // Right
await wait();
stdin.write('\u001B[C'); // Right
await wait();
expect(lastFrame()).toContain('(not answered)');
unmount();
});
});
describe('focus behavior', () => {
it('does not respond to keys when isFocused is false', async () => {
const onConfirm = vi.fn();
const details = createConfirmationDetails();
const { stdin, unmount } = renderWithProviders(
<AskUserQuestionDialog
confirmationDetails={details}
isFocused={false}
onConfirm={onConfirm}
/>,
);
await wait();
stdin.write('\r'); // Enter
await wait();
stdin.write('\u001B'); // Escape
await wait();
expect(onConfirm).not.toHaveBeenCalled();
unmount();
});
});
});