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'); }); });