@gitlab/ui
Version:
GitLab UI Components
829 lines (712 loc) • 31 kB
JavaScript
import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
import GlEmptyState from '../../../regions/empty_state/empty_state.vue';
import GlExperimentBadge from '../../experiment_badge/experiment_badge.vue';
import GlCard from '../../../base/card/card.vue';
import GlDropdownItem from '../../../base/dropdown/dropdown_item.vue';
import DuoChatLoader from './components/duo_chat_loader/duo_chat_loader.vue';
import DuoChatPredefinedPrompts from './components/duo_chat_predefined_prompts/duo_chat_predefined_prompts.vue';
import DuoChatConversation from './components/duo_chat_conversation/duo_chat_conversation.vue';
import GlDuoChat from './duo_chat.vue';
import {
MOCK_RESPONSE_MESSAGE,
MOCK_USER_PROMPT_MESSAGE,
SLASH_COMMANDS as slashCommands,
} from './mock_data';
import { MESSAGE_MODEL_ROLES, CHAT_RESET_MESSAGE } from './constants';
const invalidSlashCommands = [
{
name: '/foo',
},
{
description: '/bar',
},
{
shouldSubmit: true,
},
];
const generatePartialSlashCommands = () => {
const res = [];
slashCommands.forEach((command) => {
res.push(command.name.slice(0, command.name.length - 1));
});
return res;
};
describe('GlDuoChat', () => {
let wrapper;
const createComponent = ({ propsData = {}, slots = {} } = {}) => {
jest.spyOn(DuoChatLoader.methods, 'computeTransitionWidth').mockImplementation();
wrapper = shallowMount(GlDuoChat, {
propsData,
slots,
stubs: {
DuoChatLoader,
},
});
};
const findChatComponent = () => wrapper.find('[data-testid="chat-component"]');
const findChatHistoryComponent = () => wrapper.find('[data-testid="chat-history"]');
const findCloseButton = () => wrapper.find('[data-testid="chat-close-button"]');
const findChatConversations = () => wrapper.findAllComponents(DuoChatConversation);
const findCustomLoader = () => wrapper.findComponent(DuoChatLoader);
const findError = () => wrapper.find('[data-testid="chat-error"]');
const findHeader = () => wrapper.find('[data-testid="chat-header"]');
const findFooter = () => wrapper.find('[data-testid="chat-footer"]');
const findPromptForm = () => wrapper.find('[data-testid="chat-prompt-form"]');
const findGeneratedByAI = () => wrapper.find('[data-testid="chat-legal-warning"]');
const findBadge = () => wrapper.findComponent(GlExperimentBadge);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findPredefined = () => wrapper.findComponent(DuoChatPredefinedPrompts);
const findChatInput = () => wrapper.find('[data-testid="chat-prompt-input"]');
const findCloseChatButton = () => wrapper.find('[data-testid="chat-close-button"]');
const findLegalDisclaimer = () => wrapper.find('[data-testid="chat-legal-disclaimer"]');
const findSlashCommandsCard = () => wrapper.findComponent(GlCard);
const findSlashCommands = () => wrapper.findAllComponents(GlDropdownItem);
const findSelectedSlashCommand = () => wrapper.find('.active-command');
const setPromptInput = (val) => findChatInput().vm.$emit('input', val);
beforeEach(() => {
createComponent();
});
const promptStr = 'foo';
const messages = [
{
role: MESSAGE_MODEL_ROLES.user,
content: promptStr,
},
];
describe('rendering', () => {
it('does not fail if no messages are passed', () => {
createComponent({
propsData: { messages: null },
});
expect(findChatConversations()).toHaveLength(0);
expect(findEmptyState().exists()).toBe(true);
});
it.each`
desc | component | shouldRender
${'renders root component'} | ${findChatComponent} | ${true}
${'renders experimental label'} | ${findBadge} | ${true}
${'renders empty state'} | ${findEmptyState} | ${true}
${'renders predefined prompts'} | ${findPredefined} | ${true}
${'does not render loading skeleton'} | ${findCustomLoader} | ${false}
${'does not render chat error'} | ${findError} | ${false}
${'does render chat input'} | ${findChatInput} | ${true}
${'renders a generated by AI note'} | ${findGeneratedByAI} | ${true}
`('$desc', ({ component, shouldRender }) => {
expect(component().exists()).toBe(shouldRender);
});
it('does not render the experimental label if it is explicitely disabled', () => {
createComponent({
propsData: {
badgeType: null,
},
});
expect(findBadge().exists()).toBe(false);
});
describe('when messages exist', () => {
it('scrolls to the bottom on load', async () => {
const scrollIntoViewMock = jest.fn();
window.HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
createComponent({ propsData: { messages } });
await nextTick();
expect(scrollIntoViewMock).toHaveBeenCalledTimes(1);
window.HTMLElement.prototype.scrollIntoView = undefined;
});
});
describe('conversations', () => {
it('renders one conversation when no reset message is present', () => {
const newMessages = [
{
role: MESSAGE_MODEL_ROLES.user,
content: 'How are you?',
},
{
role: MESSAGE_MODEL_ROLES.assistant,
content: 'Great!',
},
];
createComponent({ propsData: { messages: newMessages } });
expect(findChatConversations().length).toEqual(1);
expect(findChatConversations().at(0).props('showDelimiter')).toEqual(false);
});
it('does not render conversations when no message is present', () => {
createComponent({ propsData: { messages: [] } });
expect(findChatConversations().length).toEqual(0);
});
it('splits it up into multiple conversations when reset message is present', () => {
const newMessages = [
{
role: MESSAGE_MODEL_ROLES.user,
content: 'Message 1',
},
{
role: MESSAGE_MODEL_ROLES.assistant,
content: 'Great!',
},
{
role: MESSAGE_MODEL_ROLES.user,
content: CHAT_RESET_MESSAGE,
},
];
createComponent({ propsData: { messages: newMessages } });
expect(findChatConversations().length).toEqual(2);
expect(findChatConversations().at(0).props('showDelimiter')).toEqual(false);
expect(findChatConversations().at(1).props('showDelimiter')).toEqual(true);
});
});
describe('slots', () => {
const slotContent = 'As Gregor Samsa awoke one morning from uneasy dreams';
it.each`
slot | content | isChatAvailable
${'subheader'} | ${slotContent} | ${false}
${'subheader'} | ${slotContent} | ${true}
`(
'renders the $content passed to the $slot slot when isChatAvailable is $isChatAvailable',
({ slot, content, isChatAvailable }) => {
createComponent({
propsData: { isChatAvailable },
slots: { [slot]: content },
});
expect(wrapper.text()).toContain(content);
}
);
});
it('sets correct props on the Experiment badge', () => {
const badgeHelpPageUrl = 'https://foo.bar';
const containerId = 'chat-component';
createComponent({ propsData: { badgeHelpPageUrl } });
expect(findBadge().props('helpPageUrl')).toBe(badgeHelpPageUrl);
expect(findBadge().attributes('container-id')).toBe(containerId);
});
it.each`
badgeType | expectedProp
${'experiment'} | ${'experiment'}
${'beta'} | ${'beta'}
${undefined} | ${'experiment'}
`(
'sets correct props on the Experiment badge when badgeType is "$badgeType"',
({ badgeType, expectedProp }) => {
createComponent({ propsData: { badgeType } });
expect(findBadge().props('type')).toBe(expectedProp);
}
);
describe('showHeader', () => {
it.each`
desc | showHeader | shouldRender
${'renders'} | ${undefined} | ${true}
${'does not render'} | ${false} | ${false}
${'renders'} | ${true} | ${true}
`('$desc the header when showHeader is "$showHeader"', ({ showHeader, shouldRender }) => {
createComponent({ propsData: { showHeader } });
expect(findHeader().exists()).toBe(shouldRender);
});
});
describe('emptyStateTitle', () => {
it.each`
emptyStateTitle | expectedTitle
${undefined} | ${'Ask a question'}
${''} | ${''}
${'custom title'} | ${'custom title'}
`(
'displays "$expectedTitle" when emptyStateTitle is "$emptyStateTitle"',
({ emptyStateTitle, expectedTitle }) => {
createComponent({ propsData: { emptyStateTitle } });
expect(findEmptyState().props('title')).toBe(expectedTitle);
}
);
});
describe('emptyStateDescription', () => {
it.each`
emptyStateDescription | expectedDescription
${undefined} | ${'AI generated explanations will appear here.'}
${''} | ${''}
${'custom description'} | ${'custom description'}
`(
'displays "$expectedDescription" when emptyStateDescription is "$emptyStateDescription"',
({ emptyStateDescription, expectedDescription }) => {
createComponent({ propsData: { emptyStateDescription } });
expect(findEmptyState().props('description')).toBe(expectedDescription);
}
);
});
describe('prompt placeholder', () => {
it.each`
chatPromptPlaceholder | commands | expectedPlaceholder
${undefined} | ${undefined} | ${'GitLab Duo Chat'}
${''} | ${undefined} | ${'GitLab Duo Chat'}
${'custom placeholder'} | ${undefined} | ${'custom placeholder'}
${undefined} | ${[]} | ${'GitLab Duo Chat'}
${''} | ${[]} | ${'GitLab Duo Chat'}
${'custom placeholder'} | ${[]} | ${'custom placeholder'}
${undefined} | ${slashCommands} | ${'Type "/" for slash commands'}
${''} | ${slashCommands} | ${'Type "/" for slash commands'}
${'custom placeholder'} | ${slashCommands} | ${'custom placeholder'}
`(
'displays "$expectedPlaceholder" when chatPromptPlaceholder is "$chatPromptPlaceholder", and slashCommands are "$commands"',
({ chatPromptPlaceholder, commands, expectedPlaceholder }) => {
createComponent({ propsData: { chatPromptPlaceholder, slashCommands: commands } });
expect(findChatInput().attributes('placeholder')).toBe(expectedPlaceholder);
}
);
});
});
describe('chat', () => {
const clickSubmit = () =>
findPromptForm().vm.$emit('submit', {
preventDefault: jest.fn(),
stopPropagation: jest.fn(),
});
it('does render the prompt input by default', () => {
createComponent({ propsData: { messages } });
expect(findChatInput().exists()).toBe(true);
});
it('does not render the prompt input if `isChatAvailable` prop is `false`', () => {
createComponent({ propsData: { messages, isChatAvailable: false } });
expect(findChatInput().exists()).toBe(false);
});
it('renders the legal disclaimer if `isChatAvailable` prop is `true', () => {
createComponent({ propsData: { messages, isChatAvailable: true } });
expect(findLegalDisclaimer().exists()).toBe(true);
});
describe('submit', () => {
const ENTER = 'Enter';
it('trims the prompt', () => {
const question = ' foo bar ';
const expectedPrompt = 'foo bar';
createComponent({
propsData: { isChatAvailable: true, messages: [] },
});
setPromptInput(question);
clickSubmit();
expect(wrapper.emitted('send-chat-prompt')).toEqual([[expectedPrompt]]);
});
it.each`
trigger | event | action | expectEmitted
${() => clickSubmit()} | ${'Submit button click'} | ${'submit'} | ${[[promptStr]]}
${() => findChatInput().trigger('keyup', { key: ENTER })} | ${`Clicking ${ENTER}`} | ${'submit'} | ${[[promptStr]]}
${() => findChatInput().trigger('keyup', { key: ENTER, metaKey: true })} | ${`Clicking ${ENTER} + ⌘`} | ${'not submit'} | ${undefined}
${() => findChatInput().trigger('keyup', { key: ENTER, altKey: true })} | ${`Clicking ${ENTER} + ⎇`} | ${'not submit'} | ${undefined}
${() => findChatInput().trigger('keyup', { key: ENTER, shiftKey: true })} | ${`Clicking ${ENTER} + ⬆︎`} | ${'not submit'} | ${undefined}
${() => findChatInput().trigger('keyup', { key: ENTER, ctrlKey: true })} | ${`Clicking ${ENTER} + CTRL`} | ${'not submit'} | ${undefined}
`('$event should $action the prompt form', ({ trigger, expectEmitted } = {}) => {
createComponent({
propsData: { messages: [], isChatAvailable: true },
});
setPromptInput(promptStr);
trigger();
expect(wrapper.emitted('send-chat-prompt')).toEqual(expectEmitted);
});
it.each`
desc | msgs
${''} | ${[]}
${'with just a user message'} | ${[MOCK_USER_PROMPT_MESSAGE]}
${'with a user message, and a complete response'} | ${[MOCK_USER_PROMPT_MESSAGE, MOCK_RESPONSE_MESSAGE]}
`('prevents submission when loading $desc', ({ msgs } = {}) => {
createComponent({
propsData: { isChatAvailable: true, isLoading: true, messages: msgs },
});
setPromptInput(promptStr);
clickSubmit();
expect(wrapper.emitted('send-chat-prompt')).toBe(undefined);
});
it.each([
[[{ ...MOCK_RESPONSE_MESSAGE, content: undefined, chunks: [''] }]],
[
[
MOCK_USER_PROMPT_MESSAGE,
{ ...MOCK_RESPONSE_MESSAGE, content: undefined, chunks: [''] },
],
],
[[{ ...MOCK_RESPONSE_MESSAGE, chunkId: 1 }]],
])('prevents submission when streaming (messages = "%o")', (msgs = []) => {
createComponent({
propsData: { isChatAvailable: true, messages: msgs },
});
setPromptInput(promptStr);
clickSubmit();
expect(wrapper.emitted('send-chat-prompt')).toBe(undefined);
});
it('resets the prompt after form submission', async () => {
const prompt = 'foo';
createComponent();
await setPromptInput(prompt);
expect(findChatInput().props('value')).toBe(prompt);
clickSubmit();
await nextTick();
expect(findChatInput().props('value')).toBe('');
});
it('focuses on prompt after form submission', async () => {
const focusSpy = jest.fn();
jest.spyOn(HTMLElement.prototype, 'focus').mockImplementation(function focusMockImpl() {
focusSpy(this);
});
createComponent();
await setPromptInput('TEST!');
clickSubmit();
await nextTick();
expect(focusSpy).toHaveBeenCalledWith(findChatInput().element);
});
});
describe('reset', () => {
it('emits the event with the reset prompt', () => {
createComponent({
propsData: { messages, isChatAvailable: true },
});
setPromptInput(CHAT_RESET_MESSAGE);
clickSubmit();
expect(wrapper.emitted('send-chat-prompt')).toEqual([[CHAT_RESET_MESSAGE]]);
expect(findChatConversations().length).toEqual(1);
});
it('reset does nothing when chat is loading', () => {
createComponent({
propsData: { messages, isChatAvailable: true, isLoading: true },
});
setPromptInput(CHAT_RESET_MESSAGE);
clickSubmit();
expect(wrapper.emitted('send-chat-prompt')).toBeUndefined();
expect(findChatConversations().length).toEqual(1);
});
it('reset does nothing when there are no messages', () => {
createComponent({
propsData: { messages: [], isChatAvailable: true },
});
setPromptInput(CHAT_RESET_MESSAGE);
clickSubmit();
expect(wrapper.emitted('send-chat-prompt')).toBeUndefined();
expect(findChatConversations().length).toEqual(0);
});
it('reset does nothing when last message was a reset message', () => {
const existingMessages = [
...messages,
{
role: MESSAGE_MODEL_ROLES.user,
content: CHAT_RESET_MESSAGE,
},
];
createComponent({
propsData: {
isLoading: false,
messages: existingMessages,
isChatAvailable: true,
},
});
setPromptInput(CHAT_RESET_MESSAGE);
clickSubmit();
expect(wrapper.emitted('send-chat-prompt')).toBeUndefined();
expect(findChatConversations().length).toEqual(2);
expect(findChatConversations().at(0).props('messages')).toEqual(messages);
expect(findChatConversations().at(1).props('messages')).toEqual([]);
});
});
});
describe('when closed', () => {
beforeEach(async () => {
findCloseButton().vm.$emit('click');
await nextTick();
});
it('is hidden', () => {
expect(findChatComponent().exists()).toBe(false);
});
it('when starts loading, resets hidden status', async () => {
// setProps is justified here because we are testing the component's
// reactive behavior which consistutes an exception
// See https://docs.gitlab.com/ee/development/fe_guide/style/vue.html#setting-component-state
wrapper.setProps({
isLoading: true,
});
await nextTick();
expect(findChatComponent().exists()).toBe(true);
});
});
describe('interaction', () => {
it('renders custom loader when isLoading', () => {
createComponent({ propsData: { isLoading: true } });
expect(findCustomLoader().exists()).toBe(true);
});
it('renders alert if error', () => {
const errorMessage = 'Something went Wrong';
createComponent({ propsData: { error: errorMessage } });
expect(findError().text()).toBe(errorMessage);
});
it('hides the chat on button click and emits an event', async () => {
createComponent({ propsData: { messages } });
expect(findChatComponent().exists()).toBe(true);
findCloseChatButton().vm.$emit('click');
await nextTick();
expect(findChatComponent().exists()).toBe(false);
expect(wrapper.emitted('chat-hidden')).toBeDefined();
});
it('does not render the empty state when there are messages available', () => {
createComponent({ propsData: { messages } });
expect(findEmptyState().exists()).toBe(false);
});
describe('scrolling', () => {
let element;
beforeEach(() => {
createComponent({ propsData: { messages, isChatAvailable: true } });
element = findChatHistoryComponent().element;
});
it('when scrolling to the bottom it removes the scrim class', async () => {
jest.spyOn(element, 'scrollTop', 'get').mockReturnValue(100);
jest.spyOn(element, 'offsetHeight', 'get').mockReturnValue(100);
jest.spyOn(element, 'scrollHeight', 'get').mockReturnValue(200);
findChatHistoryComponent().trigger('scroll');
await nextTick();
expect(findFooter().classes()).not.toContain('duo-chat-drawer-body-scrim-on-footer');
});
it('when scrolling up it adds the scrim class', async () => {
jest.spyOn(element, 'scrollTop', 'get').mockReturnValue(50);
jest.spyOn(element, 'offsetHeight', 'get').mockReturnValue(100);
jest.spyOn(element, 'scrollHeight', 'get').mockReturnValue(200);
findChatHistoryComponent().trigger('scroll');
await nextTick();
expect(findFooter().classes()).toContain('duo-chat-drawer-body-scrim-on-footer');
});
});
describe('predefined prompts', () => {
const prompts = ['what is a fork'];
beforeEach(() => {
createComponent({ propsData: { predefinedPrompts: prompts } });
});
it('passes on predefined prompts', () => {
expect(findPredefined().props().prompts).toEqual(prompts);
});
it('listens to the click event and sends the predefined prompt', async () => {
findPredefined().vm.$emit('click', prompts[0]);
await nextTick();
expect(wrapper.emitted('send-chat-prompt')).toEqual([[prompts[0]]]);
});
});
});
describe('slash commands', () => {
const slashCommandsNames = slashCommands.map((command) => command.name);
const slashCommandsOnly = (commands = []) =>
slashCommandsNames.filter((name) => commands.includes(name));
describe('rendering', () => {
describe('without slash commands', () => {
it('does not render slash commands by default', () => {
createComponent();
expect(findSlashCommandsCard().exists()).toBe(false);
});
it('does not render slash commands when prompt is "/"', async () => {
createComponent();
setPromptInput('/');
await nextTick();
expect(findSlashCommandsCard().exists()).toBe(false);
});
});
describe('with slash commands', () => {
it('does not render slash commands by default', async () => {
createComponent({
propsData: {
slashCommands,
},
});
await nextTick();
expect(findSlashCommandsCard().exists()).toBe(false);
});
it('renders all slash commands when prompt is "/"', async () => {
createComponent({
propsData: {
slashCommands,
},
});
setPromptInput('/');
await nextTick();
expect(findSlashCommandsCard().exists()).toBe(true);
expect(findSlashCommands()).toHaveLength(slashCommands.length);
slashCommands.forEach((command, index) => {
expect(findSlashCommands().at(index).text()).toContain(command.name);
expect(findSlashCommands().at(index).text()).toContain(command.description);
});
});
it('prevents passing down invalid slash commands', () => {
expect(() => {
wrapper = shallowMount(GlDuoChat, {
propsData: {
slashCommands: [...slashCommands, ...invalidSlashCommands],
},
});
}).toHaveLength(0);
});
describe('when the prompt includes the "/" character or no characters', () => {
it.each(['', '//', '\\', 'foo', '/foo'])(
'does not render the slash commands if prompt is "$prompt"',
async (prompt) => {
createComponent({
propsData: {
slashCommands,
},
});
setPromptInput(prompt);
await nextTick();
expect(findSlashCommandsCard().exists()).toBe(false);
}
);
});
describe('when prompt presents a partial match to an existing slash command', () => {
it.each(generatePartialSlashCommands())(
'renders the slash commands when prompt is "%s" and is a partial match',
async (prompt) => {
createComponent({
propsData: {
slashCommands,
},
});
setPromptInput(prompt);
await nextTick();
expect(findSlashCommandsCard().exists()).toBe(true);
}
);
});
describe('when the prompt matches a complete slash command', () => {
it.each(slashCommands.map((command) => command.name))(
'does not render the slash commands when prompt is "%s"',
async (prompt) => {
createComponent({
propsData: {
slashCommands,
},
});
setPromptInput(prompt);
await nextTick();
expect(findSlashCommandsCard().exists()).toBe(false);
}
);
});
});
});
describe('interaction', () => {
describe('filtering when user types in partial slash command', () => {
it.each`
prompt | expectedCommands
${'/'} | ${slashCommandsNames}
${'/t'} | ${slashCommandsOnly(['/tests'])}
${'/tes'} | ${slashCommandsOnly(['/tests'])}
${'/test'} | ${slashCommandsOnly(['/tests'])}
${'/e'} | ${slashCommandsOnly(['/explain'])}
${'/explai'} | ${slashCommandsOnly(['/explain'])}
${'/r'} | ${slashCommandsOnly(['/reset', '/refactor'])}
${'/re'} | ${slashCommandsOnly(['/reset', '/refactor'])}
${'/res'} | ${slashCommandsOnly(['/reset'])}
${'/ref'} | ${slashCommandsOnly(['/refactor'])}
${'/foo'} | ${[]}
`(
'shows $expectedCommands when prompt is $prompt',
async ({ prompt, expectedCommands } = {}) => {
createComponent({
propsData: {
slashCommands,
},
});
setPromptInput(prompt);
await nextTick();
expect(findSlashCommands()).toHaveLength(expectedCommands.length);
expectedCommands.forEach((command) => {
expect(findSlashCommandsCard().text()).toContain(command);
});
}
);
});
describe('keyboard navigation', () => {
beforeEach(() => {
createComponent({
propsData: {
slashCommands,
messages,
},
});
setPromptInput('/');
});
it('toggles through commands on ArrowDown', async () => {
for (const command of slashCommandsNames) {
expect(findSelectedSlashCommand().text()).toContain(command);
findChatInput().trigger('keyup', { key: 'ArrowDown' });
// eslint-disable-next-line no-await-in-loop
await nextTick();
}
});
it('toggles through commands on ArrowUp', async () => {
const arr = [...slashCommandsNames].reverse();
arr.unshift(slashCommandsNames[0]); // it still has the top most command selected on the first run
for (const command of arr) {
expect(findSelectedSlashCommand().text()).toContain(command);
findChatInput().trigger('keyup', { key: 'ArrowUp' });
// eslint-disable-next-line no-await-in-loop
await nextTick();
}
});
describe('on Enter', () => {
const navigateToCommand = async (index) => {
const command = slashCommandsNames[index];
if (index) {
for (let i = 0; i < index; i += 1) {
findChatInput().trigger('keyup', { key: 'ArrowDown' });
}
}
await nextTick();
return command;
};
it('selects correct command and updates input if command should not submit right away', async () => {
const commandIndex = slashCommands.findIndex((cmd) => !cmd.shouldSubmit);
const command = await navigateToCommand(commandIndex);
expect(findSelectedSlashCommand().text()).toContain(command);
findChatInput().trigger('keyup', { key: 'Enter' });
await nextTick();
expect(findChatInput().props('value')).toBe(`${command} `);
expect(wrapper.emitted('send-chat-prompt')).toBe(undefined);
});
it('selects correct command and submits the prompt if command should submit right away', async () => {
const commandIndex = slashCommands.findIndex((cmd) => cmd.shouldSubmit);
const command = await navigateToCommand(commandIndex);
expect(findSelectedSlashCommand().text()).toContain(command);
findChatInput().trigger('keyup', { key: 'Enter' });
await nextTick();
expect(wrapper.emitted('send-chat-prompt')).toEqual([[command]]);
});
});
});
describe('mouse navigation', () => {
beforeEach(() => {
createComponent({
propsData: {
slashCommands,
messages,
},
});
setPromptInput('/');
});
it('updates the selected command when hovering over it', async () => {
expect(findSelectedSlashCommand().text()).toContain(slashCommandsNames[0]);
findSlashCommands().at(2).trigger('mouseenter');
await nextTick();
expect(findSelectedSlashCommand().text()).toContain(slashCommandsNames[2]);
expect(findSelectedSlashCommand().text()).not.toContain(slashCommandsNames[0]);
});
describe('click', () => {
it('selects correct command and updates input if command should not submit right away', async () => {
const commandIndex = slashCommands.findIndex((cmd) => !cmd.shouldSubmit);
findSlashCommands().at(commandIndex).trigger('mouseenter');
await nextTick();
expect(findSelectedSlashCommand().text()).toContain(slashCommandsNames[commandIndex]);
findSelectedSlashCommand().vm.$emit('click');
await nextTick();
expect(findChatInput().props('value')).toBe(`${slashCommandsNames[commandIndex]} `);
expect(wrapper.emitted('send-chat-prompt')).toBe(undefined);
});
it('selects correct command and submits the prompt if command should submit right away', async () => {
const commandIndex = slashCommands.findIndex((cmd) => cmd.shouldSubmit);
findSlashCommands().at(commandIndex).trigger('mouseenter');
await nextTick();
expect(findSelectedSlashCommand().text()).toContain(slashCommandsNames[commandIndex]);
findSelectedSlashCommand().vm.$emit('click');
await nextTick();
expect(wrapper.emitted('send-chat-prompt')).toEqual([
[slashCommandsNames[commandIndex]],
]);
});
});
});
});
});
});