qwen-code/packages/cli/src/ui/components/messages/AskUserQuestionDialog.test.tsx
tanzhenxin e0aeee5414
chore: remove outdated pr-review skill (#3028)
* chore: remove outdated pr-review skill

The .qwen/skills/pr-review skill is outdated and no longer needed.
Also removes the dangling reference from terminal-capture skill.

* chore: add token-stats script to e2e-testing skill

* fix(test): remove flaky AskUserQuestionDialog Submit tab test

The "shows Submit tab for multiple questions" test fails on macOS CI
due to terminal width truncation causing "Submit answers" to render
as just "Submit".
2026-04-09 10:54:10 +08:00

273 lines
7.6 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('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 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();
});
});
});