import '@testing-library/jest-dom';
import {
DropdownGroup,
DropdownItem,
DropdownList,
MenuSearchInputProps,
MenuSearchProps
} from '@patternfly/react-core';
import { BellIcon, CalendarAltIcon, ClipboardIcon, CodeIcon } from '@patternfly/react-icons';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { createRef } from 'react';
import SourceDetailsMenuItem from '../SourceDetailsMenuItem';
import { MessageBar } from './MessageBar';
const ATTACH_MENU_ITEMS = [
}
name="auth-operator"
type="Pod"
/>
,
}>
Alerts
}>
Events
}>
Logs
}>
YAML - Status
}>
YAML - All contents
];
const originalSpeechRecognition = window.SpeechRecognition;
const mockSpeechRecognition = () => {
const MockSpeechRecognition = jest.fn().mockImplementation(() => ({
start: jest.fn(),
stop: jest.fn()
}));
(MockSpeechRecognition as any).prototype = {};
window.SpeechRecognition = MockSpeechRecognition as any;
};
describe('Message bar', () => {
afterAll(() => {
window.SpeechRecognition = originalSpeechRecognition;
});
it('should render correctly', () => {
render();
expect(screen.getByRole('button', { name: 'Attach' })).toBeTruthy();
expect(screen.queryByRole('button', { name: 'Send' })).toBeFalsy();
expect(screen.queryByRole('button', { name: 'Use microphone' })).toBeFalsy();
expect(screen.getByRole('textbox', { name: /Send a message.../i })).toBeTruthy();
});
it('can send via enter key', async () => {
const spy = jest.fn();
render();
const input = screen.getByRole('textbox', { name: /Send a message.../i });
await userEvent.type(input, 'Hello world');
expect(input).toHaveTextContent('Hello world');
await userEvent.type(input, '[Enter]');
expect(spy).toHaveBeenCalledTimes(1);
});
it('calls onChange callback appropriately', async () => {
const spy = jest.fn();
render();
const input = screen.getByRole('textbox', { name: /Send a message.../i });
await userEvent.type(input, 'A');
expect(input).toHaveTextContent('A');
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(expect.any(Object), 'A');
});
it('can use specified placeholder text', async () => {
render();
const input = screen.getByRole('textbox', { name: /test placeholder/i });
await userEvent.type(input, 'Hello world');
expect(input).toHaveTextContent('Hello world');
});
// Send button
// --------------------------------------------------------------------------
it('shows send button when text is input', async () => {
render();
const input = screen.getByRole('textbox', { name: /Send a message.../i });
await userEvent.type(input, 'Hello world');
expect(input).toHaveTextContent('Hello world');
expect(screen.getByRole('button', { name: 'Send' })).toBeTruthy();
});
it('can disable send button shown when text is input', async () => {
render();
const input = screen.getByRole('textbox', { name: /Send a message.../i });
await userEvent.type(input, 'Hello world');
expect(input).toHaveTextContent('Hello world');
expect(screen.getByRole('button', { name: 'Send' })).toBeTruthy();
expect(screen.getByRole('button', { name: 'Send' })).toBeDisabled();
});
it('can click send button', async () => {
const spy = jest.fn();
render();
const input = screen.getByRole('textbox', { name: /Send a message.../i });
await userEvent.type(input, 'Hello world');
expect(input).toHaveTextContent('Hello world');
const sendButton = screen.getByRole('button', { name: 'Send' });
expect(sendButton).toBeTruthy();
await userEvent.click(sendButton);
expect(spy).toHaveBeenCalledTimes(1);
});
it('can always show send button', () => {
render();
expect(screen.getByRole('button', { name: 'Send' })).toBeTruthy();
expect(screen.getByRole('button', { name: 'Send' })).toBeEnabled();
});
it('can disable send button if always showing', () => {
render();
expect(screen.getByRole('button', { name: 'Send' })).toBeTruthy();
expect(screen.getByRole('button', { name: 'Send' })).toBeDisabled();
});
it('can handle buttonProps tooltipContent appropriately for send', async () => {
render(
);
await userEvent.click(screen.getByRole('button', { name: 'Send' }));
expect(screen.getByRole('tooltip', { name: 'Test' })).toBeTruthy();
});
it('can handle buttonProps tooltipProps appropriately for send', () => {
render(
);
// isVisible, so no need for click
expect(screen.getByRole('tooltip', { name: 'Send' })).toBeTruthy();
});
it('can handle buttonProps props appropriately for send', async () => {
render(
);
await userEvent.click(screen.getByRole('button', { name: 'Test' }));
});
// Attach button
// --------------------------------------------------------------------------
it('can show attach menu', async () => {
render(
);
expect(screen.getByRole('textbox', { name: /Filter menu items/i })).toBeTruthy();
expect(screen.getByRole('menuitem', { name: /auth-operator/i })).toBeTruthy();
expect(screen.getByRole('menuitem', { name: /Alerts/i })).toBeTruthy();
expect(screen.getByRole('menuitem', { name: /Events/i })).toBeTruthy();
expect(screen.getByRole('menuitem', { name: /Logs/i })).toBeTruthy();
expect(screen.getByRole('menuitem', { name: /YAML - Status/i })).toBeTruthy();
expect(screen.getByRole('menuitem', { name: /YAML - All contents/i })).toBeTruthy();
});
it('can toggle attach menu', async () => {
const attachToggleClickSpy = jest.fn();
render(
);
expect(screen.queryByRole('textbox', { name: /Filter menu items/i })).toBeFalsy();
expect(screen.queryByRole('menuitem', { name: /auth-operator/i })).toBeFalsy();
expect(screen.queryByRole('menuitem', { name: /Alerts/i })).toBeFalsy();
expect(screen.queryByRole('menuitem', { name: /Events/i })).toBeFalsy();
expect(screen.queryByRole('menuitem', { name: /Logs/i })).toBeFalsy();
expect(screen.queryByRole('menuitem', { name: /YAML - Status/i })).toBeFalsy();
expect(screen.queryByRole('menuitem', { name: /YAML - All contents/i })).toBeFalsy();
const attachButton = screen.getByRole('button', { name: 'Attach' });
await userEvent.click(attachButton);
expect(attachToggleClickSpy).toHaveBeenCalledTimes(1);
});
it('can pass searchInputProps to search input in AttachMenu', () => {
render(
);
expect(screen.getByRole('textbox', { name: /Filter menu items/i })).toBeDisabled();
});
it('can pass menuSearchProps to search input in AttachMenu', () => {
render(
);
expect(screen.getByTestId('menu-search')).toBeTruthy();
});
it('can pass menuSearchInputProps to search input in AttachMenu', () => {
render(
);
expect(screen.getByTestId('menu-search-input')).toBeTruthy();
});
it('can remove input from attach menu', async () => {
render(
);
expect(screen.queryByRole('textbox', { name: /Filter menu items/i })).not.toBeInTheDocument();
});
it('can hide attach button', () => {
render();
expect(screen.queryByRole('button', { name: 'Attach' })).toBeFalsy();
});
// Based on this because I had no idea how to do this and was looking around: https://stackoverflow.com/a/75562651
// See also https://developer.mozilla.org/en-US/docs/Web/API/File/File for what that file variable is doing
it('can handle handleAttach', async () => {
const spy = jest.fn();
render(
);
expect(screen.getByRole('button', { name: 'Attach' })).toBeTruthy();
await userEvent.click(screen.getByRole('button', { name: 'Attach' }));
const file = new File(['test'], 'test.json');
const input = screen.getByTestId('input') as HTMLInputElement;
await userEvent.upload(input, file);
expect(input.files).toHaveLength(1);
expect(spy).toHaveBeenCalledTimes(1);
});
it('can handle buttonProps tooltipContent appropriately for attach', async () => {
render();
await userEvent.click(screen.getByRole('button', { name: 'Attach' }));
expect(screen.getByRole('tooltip', { name: 'Test' })).toBeTruthy();
});
it('can handle buttonProps tooltipProps appropriately for attach', () => {
render(
);
// isVisible, so no need for click
expect(screen.getByRole('tooltip', { name: 'Attach' })).toBeTruthy();
});
it('can handle buttonProps props appropriately for attach', async () => {
render(
);
await userEvent.click(screen.getByRole('button', { name: 'Test' }));
});
it('can change attach button icon', () => {
render(
}
}}
/>
);
expect(screen.getByRole('img')).toBeVisible();
});
// Stop button
// --------------------------------------------------------------------------
it('can show stop button', () => {
render();
expect(screen.getByRole('button', { name: 'Stop' })).toBeTruthy();
});
it('can call handleStopButton', async () => {
const spy = jest.fn();
render();
await userEvent.click(screen.getByRole('button', { name: 'Stop' }));
expect(spy).toHaveBeenCalledTimes(1);
});
it('can handle buttonProps tooltipContent appropriately for stop', async () => {
render(
);
await userEvent.click(screen.getByRole('button', { name: 'Stop' }));
expect(screen.getByRole('tooltip', { name: 'Test' })).toBeTruthy();
});
it('can handle buttonProps tooltipProps appropriately for stop', () => {
render(
);
// isVisible, so no need for click
expect(screen.getByRole('tooltip', { name: 'Stop' })).toBeTruthy();
});
it('can handle buttonProps props appropriately for stop', async () => {
render(
);
await userEvent.click(screen.getByRole('button', { name: 'Test' }));
});
// Microphone button
// --------------------------------------------------------------------------
it('can hide microphone button when window.SpeechRecognition is not there', () => {
render();
expect(screen.queryByRole('button', { name: 'Use microphone' })).toBeFalsy();
});
it('can show microphone button', () => {
mockSpeechRecognition();
render();
expect(screen.getByRole('button', { name: 'Use microphone' })).toBeTruthy();
});
it('can handle buttonProps appropriately for microphone', async () => {
mockSpeechRecognition();
render(
);
await userEvent.click(screen.getByRole('button', { name: 'Use microphone' }));
expect(screen.getByRole('tooltip', { name: 'Currently listening' })).toBeTruthy();
await userEvent.click(screen.getByRole('button', { name: 'Stop listening' }));
expect(screen.getByRole('tooltip', { name: 'Not currently listening' })).toBeTruthy();
});
it('can customize the listening placeholder', async () => {
mockSpeechRecognition();
render();
await userEvent.click(screen.getByRole('button', { name: 'Use microphone' }));
const input = screen.getByRole('textbox', { name: /I am listening/i });
expect(input).toBeTruthy();
});
it('can handle buttonProps tooltipProps appropriately for microphone', () => {
render(
);
// isVisible, so no need for click
expect(screen.getByRole('tooltip', { name: 'Use microphone' })).toBeTruthy();
});
it('can handle buttonProps props appropriately for microphone', async () => {
mockSpeechRecognition();
render(
);
await userEvent.click(screen.getByRole('button', { name: 'Test' }));
});
it('can be controlled', () => {
render();
expect(screen.getByRole('button', { name: 'Attach' })).toBeTruthy();
expect(screen.getByRole('button', { name: 'Send' })).toBeTruthy();
expect(screen.queryByRole('button', { name: 'Use microphone' })).toBeFalsy();
expect(screen.getByRole('textbox', { name: /Send a message.../i })).toBeTruthy();
expect(screen.getByRole('textbox', { name: /Send a message.../i })).toHaveValue('test');
});
it('should focus textarea when using a custom ref', () => {
const ref = createRef();
render();
ref.current?.focus();
expect(document.activeElement).toBe(screen.getByRole('textbox'));
});
it('should handle isPrimary', () => {
const { container } = render();
expect(container.querySelector('.pf-m-primary')).toBeTruthy();
});
it('Renders with class pf-v6-m-ai-indicator when hasAiIndicator is true', () => {
render();
expect(screen.getByRole('textbox').closest('.pf-chatbot__message-bar')).toHaveClass('pf-v6-m-ai-indicator');
});
it('Renders with class pf-v6-m-thinking when isThinking is true', () => {
render();
expect(screen.getByRole('textbox').closest('.pf-chatbot__message-bar')).toHaveClass('pf-v6-m-thinking');
});
});