eigent/test/unit/components/ChatBox/BottomBox/InputBox.test.tsx
Douglas Lai 8f4f575db9
feat: add native menus, shortcuts, and window chrome (#1858)
Co-authored-by: 4pmtong <web_chentong@163.com>
2026-08-21 20:39:55 +08:00

163 lines
4.9 KiB
TypeScript

// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
import { Inputbox } from '@/components/ChatBox/BottomBox/InputBox';
import { fireEvent, render } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
vi.mock('@/components/ChatBox/BottomBox/RichChatInput', async () => {
const { forwardRef } = await import('react');
return {
RichChatInput: forwardRef<
HTMLDivElement,
{
value?: string;
onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
}
>(({ value, onKeyDown }, ref) => (
<div ref={ref} data-testid="rich-chat-input" onKeyDown={onKeyDown}>
{value}
</div>
)),
};
});
function primaryAction(container: HTMLElement) {
return container.querySelector<HTMLButtonElement>(
'[data-composer-primary-action]'
)!;
}
describe('Inputbox primary action', () => {
it('rotates the default arrow upward when text becomes sendable', () => {
const onSend = vi.fn();
const { container, rerender } = render(
<Inputbox value="" onSend={onSend} />
);
expect(primaryAction(container)).toHaveAttribute(
'data-composer-primary-action',
'idle'
);
expect(primaryAction(container)).toBeDisabled();
expect(
container.querySelector('[data-composer-primary-icon="arrow"]')
).toHaveClass('rotate-0', 'motion-reduce:transition-none');
rerender(<Inputbox value="Queue this next" onSend={onSend} />);
expect(primaryAction(container)).toHaveAttribute(
'data-composer-primary-action',
'send'
);
expect(primaryAction(container)).not.toBeDisabled();
expect(
container.querySelector('[data-composer-primary-icon="arrow"]')
).toHaveClass('-rotate-90');
fireEvent.click(primaryAction(container));
expect(onSend).toHaveBeenCalledOnce();
});
it('uses pause and resume while an empty composer controls a live task', () => {
const onPauseTask = vi.fn();
const onResumeTask = vi.fn();
const { container, rerender } = render(
<Inputbox
taskControlState="running"
onPauseTask={onPauseTask}
onResumeTask={onResumeTask}
/>
);
expect(primaryAction(container)).toHaveAttribute(
'data-composer-primary-action',
'pause'
);
fireEvent.click(primaryAction(container));
expect(onPauseTask).toHaveBeenCalledOnce();
rerender(
<Inputbox
taskControlState="paused"
onPauseTask={onPauseTask}
onResumeTask={onResumeTask}
/>
);
expect(primaryAction(container)).toHaveAttribute(
'data-composer-primary-action',
'resume'
);
fireEvent.click(primaryAction(container));
expect(onResumeTask).toHaveBeenCalledOnce();
});
it('prioritizes sending text or files over pause and resume', () => {
const onSend = vi.fn();
const onPauseTask = vi.fn();
const { container, rerender } = render(
<Inputbox
value="A follow-up"
taskControlState="running"
onPauseTask={onPauseTask}
onSend={onSend}
/>
);
expect(primaryAction(container)).toHaveAttribute(
'data-composer-primary-action',
'send'
);
fireEvent.click(primaryAction(container));
expect(onSend).toHaveBeenCalledOnce();
expect(onPauseTask).not.toHaveBeenCalled();
rerender(
<Inputbox
files={[{ fileName: 'brief.pdf', filePath: '/tmp/brief.pdf' }]}
taskControlState="running"
onPauseTask={onPauseTask}
onSend={onSend}
/>
);
expect(primaryAction(container)).toHaveAttribute(
'data-composer-primary-action',
'send'
);
fireEvent.click(primaryAction(container));
expect(onSend).toHaveBeenCalledTimes(2);
expect(onPauseTask).not.toHaveBeenCalled();
});
it('sends a running-task follow-up with Return but not Shift+Return', () => {
const onSend = vi.fn();
const { getByTestId } = render(
<Inputbox
value="A follow-up"
taskControlState="running"
onSend={onSend}
/>
);
fireEvent.keyDown(getByTestId('rich-chat-input'), {
key: 'Enter',
shiftKey: true,
});
expect(onSend).not.toHaveBeenCalled();
fireEvent.keyDown(getByTestId('rich-chat-input'), { key: 'Enter' });
expect(onSend).toHaveBeenCalledOnce();
});
});