| /** |
| * @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 './splash-page-action'; |
| import {SplashPageAction} from './splash-page-action'; |
| import {ChatModel, chatModelToken} from '../../models/chat/chat-model'; |
| import {Action} from '../../api/ai-code-review'; |
| import {testResolver} from '../../test/common-test-setup'; |
| import {pluginLoaderToken} from '../shared/gr-js-api-interface/gr-plugin-loader'; |
| import { |
| chatActions, |
| chatProvider, |
| createChange, |
| } from '../../test/test-data-generators'; |
| import {changeModelToken} from '../../models/change/change-model'; |
| import {ParsedChangeInfo} from '../../types/types'; |
| |
| suite('splash-page-action tests', () => { |
| let element: SplashPageAction; |
| 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); |
| |
| element = await fixture<SplashPageAction>( |
| html`<splash-page-action></splash-page-action>` |
| ); |
| }); |
| |
| test('renders with action', async () => { |
| const action: Action = { |
| id: 'test-action', |
| display_text: 'Test Action', |
| hover_text: 'Test hover', |
| subtext: 'Test subtext', |
| icon: 'test-icon', |
| initial_user_prompt: 'Test prompt', |
| }; |
| element.action = action; |
| await element.updateComplete; |
| assert.shadowDom.equal( |
| element, |
| /* HTML */ ` |
| <div class="container"> |
| <md-assist-chip class="action-chip" title="Test hover"> |
| <div class="chip-content"> |
| <gr-icon class="action-icon" icon="test-icon"></gr-icon> |
| <div class="action-text-container"> |
| <div class="main-action-text-container has-subtext"> |
| <span class="action-text">Test Action</span> |
| </div> |
| |
| <span class="action-subtext">Test subtext</span> |
| </div> |
| </div> |
| </md-assist-chip> |
| <gr-tooltip-content |
| class="info-button-container" |
| has-tooltip="" |
| title="Capability details" |
| > |
| <gr-button |
| aria-disabled="false" |
| class="info-button" |
| flatten="" |
| role="button" |
| tabindex="0" |
| > |
| <gr-icon icon="info"></gr-icon> |
| </gr-button> |
| </gr-tooltip-content> |
| </div> |
| <dialog id="detailsModal" tabindex="-1"> |
| <div role="dialog" aria-labelledby="detailsTitle"> |
| <h3 class="heading-3 modalHeader" id="detailsTitle">Test Action</h3> |
| <div class="detailsContent"> |
| <div class="modal-row instruction-row"> |
| <gr-icon icon="terminal"></gr-icon> |
| <div class="modal-row-content"> |
| <div class="modal-row-title">Instruction:</div> |
| <div class="modal-row-text instruction-text collapsed"> |
| Test prompt |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="modalActions"> |
| <gr-button |
| aria-disabled="false" |
| id="closeButton" |
| link="" |
| primary="" |
| role="button" |
| tabindex="0" |
| > |
| Close |
| </gr-button> |
| </div> |
| </div> |
| </dialog> |
| ` |
| ); |
| }); |
| |
| test('handles click with predefined prompt', async () => { |
| // Summarize action |
| element.action = chatActions.actions[1]; |
| await element.updateComplete; |
| |
| const chip = element.shadowRoot?.querySelector('md-assist-chip'); |
| assert.isOk(chip); |
| chip.click(); |
| |
| const turns = chatModel.getState().turns; |
| assert.lengthOf(turns, 1); |
| assert.equal(turns[0].userMessage.content, 'Summarize the change'); |
| }); |
| |
| test('handles click with user input', async () => { |
| // Freeform action |
| element.action = chatActions.actions[0]; |
| await element.updateComplete; |
| |
| const chip = element.shadowRoot?.querySelector('md-assist-chip'); |
| assert.isOk(chip); |
| chip.click(); |
| |
| const turns = chatModel.getState().turns; |
| assert.lengthOf(turns, 0); |
| }); |
| |
| test('opens details dialog on info button click', async () => { |
| element.action = { |
| id: 'test-action', |
| display_text: 'Test Action', |
| }; |
| await element.updateComplete; |
| |
| const infoButton = element.shadowRoot?.querySelector('.info-button'); |
| assert.isOk(infoButton); |
| |
| const dialog = element.shadowRoot?.querySelector( |
| '#detailsModal' |
| ) as HTMLDialogElement; |
| assert.isOk(dialog); |
| assert.isFalse(dialog.open); |
| |
| (infoButton as HTMLElement).click(); |
| await element.updateComplete; |
| |
| assert.isTrue(dialog.open); |
| |
| const turns = chatModel.getState().turns; |
| assert.lengthOf(turns, 0); |
| }); |
| test('renders capability definition link when URL is present', async () => { |
| const action: Action = { |
| id: 'test-action', |
| display_text: 'Test Action', |
| capability_definition_url: 'https://example.com', |
| }; |
| element.action = action; |
| await element.updateComplete; |
| |
| const modal = element.shadowRoot?.querySelector('#detailsModal'); |
| assert.isOk(modal); |
| |
| const sourceLink = modal?.querySelector( |
| '.modal-row-text a' |
| ) as HTMLAnchorElement; |
| assert.isOk(sourceLink); |
| assert.equal(sourceLink.getAttribute('href'), 'https://example.com'); |
| assert.equal(sourceLink.innerText.trim(), 'Capability Definition'); |
| }); |
| |
| test('does not render link when URL is absent', async () => { |
| const action: Action = { |
| id: 'test-action', |
| display_text: 'Test Action', |
| }; |
| element.action = action; |
| await element.updateComplete; |
| |
| const modal = element.shadowRoot?.querySelector('#detailsModal'); |
| assert.isOk(modal); |
| |
| const sourceLink = modal?.querySelector('.modal-row-text a'); |
| assert.isNull(sourceLink); |
| }); |
| |
| test('renders with matched_files', async () => { |
| const action: Action = { |
| id: 'test-action', |
| display_text: 'Test Action', |
| matched_files: ['file1.txt', 'file2.txt'], |
| }; |
| element.action = action; |
| await element.updateComplete; |
| |
| const modal = element.shadowRoot?.querySelector('#detailsModal'); |
| assert.isOk(modal); |
| |
| const fileItems = modal?.querySelectorAll('.file-item'); |
| assert.equal(fileItems?.length, 2); |
| assert.equal(fileItems?.[0].textContent?.trim(), 'file1.txt'); |
| assert.equal(fileItems?.[1].textContent?.trim(), 'file2.txt'); |
| |
| const expandButton = modal?.querySelector( |
| '.matched-files-row .expand-button' |
| ); |
| assert.isNotOk(expandButton); |
| }); |
| |
| test('renders with many matched_files and expands', async () => { |
| const action: Action = { |
| id: 'test-action', |
| display_text: 'Test Action', |
| matched_files: [ |
| 'file1.txt', |
| 'file2.txt', |
| 'file3.txt', |
| 'file4.txt', |
| 'file5.txt', |
| ], |
| }; |
| element.action = action; |
| await element.updateComplete; |
| |
| const modal = element.shadowRoot?.querySelector('#detailsModal'); |
| assert.isOk(modal); |
| |
| let fileItems = modal?.querySelectorAll('.file-item'); |
| assert.equal(fileItems?.length, 4); |
| |
| const expandButton = modal?.querySelector( |
| '.matched-files-row .expand-button' |
| ) as HTMLElement; |
| assert.isOk(expandButton); |
| assert.equal(expandButton.innerText.trim(), 'Show more'); |
| |
| expandButton.click(); |
| await element.updateComplete; |
| |
| fileItems = modal?.querySelectorAll('.file-item'); |
| assert.equal(fileItems?.length, 5); |
| assert.equal(expandButton.innerText.trim(), 'Show less'); |
| }); |
| }); |