blob: 00059b1696c5b74c96a92cd25eb517bdd8cf0fed [file] [log] [blame]
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import '../../test/common-test-setup';
import {assert, fixture, html} from '@open-wc/testing';
import './prompt-box';
import {PromptBox} from './prompt-box';
import {
ChatModel,
chatModelToken,
ChatState,
} from '../../models/chat/chat-model';
import {testResolver} from '../../test/common-test-setup';
import {pluginLoaderToken} from '../shared/gr-js-api-interface/gr-plugin-loader';
import {chatProvider, createChange} from '../../test/test-data-generators';
import {changeModelToken} from '../../models/change/change-model';
import {ParsedChangeInfo} from '../../types/types';
suite('prompt-box tests', () => {
let element: PromptBox;
let chatModel: ChatModel;
setup(async () => {
const pluginLoader = testResolver(pluginLoaderToken);
pluginLoader.pluginsModel.aiCodeReviewRegister({
pluginName: 'test-plugin',
provider: chatProvider,
});
const changeModel = testResolver(changeModelToken);
changeModel.updateState({
change: createChange() as ParsedChangeInfo,
});
chatModel = testResolver(chatModelToken);
chatModel.updateState({turns: []});
element = await fixture<PromptBox>(html`<prompt-box></prompt-box>`);
await element.updateComplete;
});
test('renders', () => {
assert.shadowDom.equal(
element,
/* HTML */ `
<div class="prompt-box-inner-container">
<div class="prompt-input-container">
<textarea
id="promptInput"
rows="1"
class="prompt-input"
name="search"
role="searchbox"
autocomplete="off"
spellcheck="false"
aria-label="Ask Gemini"
placeholder="Enter a prompt here..."
style="height: 18px;"
></textarea>
</div>
</div>
<md-chip-set class="context-chip-set">
<context-input-chip> </context-input-chip>
</md-chip-set>
`
);
});
test('chatInputDisabledText when model loading error', async () => {
chatModel.updateState({
...chatModel.getState(),
modelsLoadingError: 'Error loading models',
});
await element.updateComplete;
assert.equal(
element.chatInputDisabledText,
'Failed to load models. Please reload the page.'
);
});
test('updates userInput on input', async () => {
const promptInput = element.shadowRoot?.querySelector('#promptInput');
assert.isOk(promptInput);
(promptInput as HTMLTextAreaElement).value = 'test input';
promptInput?.dispatchEvent(new Event('input'));
await element.updateComplete;
assert.equal(element.userInput, 'test input');
});
test('dispatches user-input-change event on input', async () => {
let eventFired = false;
let eventDetail = null;
element.addEventListener('user-input-change', (e: Event) => {
eventFired = true;
eventDetail = (e as CustomEvent).detail;
});
const promptInput = element.shadowRoot?.querySelector('#promptInput');
assert.isOk(promptInput);
(promptInput as HTMLTextAreaElement).value = 'test input';
promptInput?.dispatchEvent(new Event('input'));
await element.updateComplete;
assert.isTrue(eventFired);
assert.deepEqual(eventDetail, {value: 'test input'});
});
test('sends message on Enter', async () => {
const initialTurns = chatModel.getState().turns.length;
const promptInput = element.shadowRoot?.querySelector('#promptInput');
assert.isOk(promptInput);
element.userInput = 'test input';
await element.updateComplete;
promptInput?.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
await element.updateComplete;
const turns = chatModel.getState().turns;
assert.equal(turns.length, initialTurns + 1);
assert.equal(turns[turns.length - 1].userMessage.content, 'test input');
});
test('renders context items', async () => {
chatModel.updateState({
...chatModel.getState(),
draftUserMessage: {
...chatModel.getState().draftUserMessage,
contextItems: [
{type_id: 'file', title: 'test.ts', link: 'link1'},
{type_id: 'file', title: 'test2.ts', link: 'link2'},
],
},
});
await element.updateComplete;
const contextChips = element.shadowRoot?.querySelectorAll('context-chip');
assert.isOk(contextChips);
assert.equal(contextChips?.length, 2);
});
test('renders suggested context items', async () => {
element.dynamicContextItemsSuggestions = [
{type_id: 'file', title: 'suggested.ts', link: 'link3'},
];
await element.updateComplete;
const suggestedChips = element.shadowRoot?.querySelectorAll(
'.suggestion-context'
);
assert.isOk(suggestedChips);
assert.equal(suggestedChips?.length, 1);
});
test('shows context toggle when too many items', async () => {
chatModel.updateState({
...chatModel.getState(),
draftUserMessage: {
...chatModel.getState().draftUserMessage,
contextItems: [
{type_id: 'file', title: 'test.ts', link: 'link1'},
{type_id: 'file', title: 'test2.ts', link: 'link2'},
{type_id: 'file', title: 'test3.ts', link: 'link3'},
{type_id: 'file', title: 'test4.ts', link: 'link4'},
],
},
});
await element.updateComplete;
const toggleChip = element.shadowRoot?.querySelector(
'.context-toggle-chip'
);
assert.isOk(toggleChip);
});
test('toggles showAllContextItems', async () => {
chatModel.updateState({
...chatModel.getState(),
draftUserMessage: {
...chatModel.getState().draftUserMessage,
contextItems: [
{type_id: 'file', title: 'test.ts', link: 'link1'},
{type_id: 'file', title: 'test2.ts', link: 'link2'},
{type_id: 'file', title: 'test3.ts', link: 'link3'},
{type_id: 'file', title: 'test4.ts', link: 'link4'},
],
},
});
await element.updateComplete;
assert.isFalse(element.showAllContextItems);
const toggleChip = element.shadowRoot?.querySelector(
'.context-toggle-chip'
);
(toggleChip as HTMLElement).click();
await element.updateComplete;
assert.isTrue(element.showAllContextItems);
});
test('chatInputDisabledText when message is processing', async () => {
chatModel.updateState({
...chatModel.getState(),
models: {
models: [
{
model_id: 'gemini-pro',
short_text: 'Gemini Pro',
full_display_text: 'Gemini Pro',
},
],
default_model_id: 'gemini-pro',
},
turns: [
{
userMessage: {
content: 'test',
userType: 0,
contextItems: [],
},
geminiMessage: {
responseComplete: false,
userType: 1,
responseParts: [],
regenerationIndex: 0,
references: [],
citations: [],
},
},
],
} as Partial<ChatState>);
await element.updateComplete;
assert.equal(element.chatInputDisabledText, 'Thinking ...');
});
test('grows on input', async () => {
const promptInput = element.shadowRoot?.querySelector('#promptInput');
assert.isOk(promptInput);
const textarea = promptInput as HTMLTextAreaElement;
// Mock scrollHeight to simulate content growth
Object.defineProperty(textarea, 'scrollHeight', {
value: 50,
configurable: true,
});
textarea.value = 'line 1\nline 2';
textarea.dispatchEvent(new Event('input'));
await element.updateComplete;
assert.equal(textarea.style.height, '50px');
// Mock scrollHeight to simulate further growth
Object.defineProperty(textarea, 'scrollHeight', {
value: 100,
configurable: true,
});
textarea.value = 'line 1\nline 2\nline 3\nline 4';
textarea.dispatchEvent(new Event('input'));
await element.updateComplete;
assert.equal(textarea.style.height, '90px');
});
});