Path: blob/main/src/vs/workbench/test/browser/componentFixtures/chat/chatArtifacts.fixture.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Event } from '../../../../../base/common/event.js';6import { observableValue } from '../../../../../base/common/observable.js';7import { URI } from '../../../../../base/common/uri.js';8import { mock } from '../../../../../base/test/common/mock.js';9import { IFileDialogService } from '../../../../../platform/dialogs/common/dialogs.js';10import { IFileService } from '../../../../../platform/files/common/files.js';11import { IListService, ListService } from '../../../../../platform/list/browser/listService.js';12import { IContextViewService } from '../../../../../platform/contextview/browser/contextView.js';13import { ChatArtifactsWidget } from '../../../../contrib/chat/browser/widget/chatArtifactsWidget.js';14import { IChatImageCarouselService } from '../../../../contrib/chat/browser/chatImageCarouselService.js';15import { IChatArtifact, IChatArtifacts, IChatArtifactsService, IArtifactSourceGroup } from '../../../../contrib/chat/common/tools/chatArtifactsService.js';16import { ComponentFixtureContext, createEditorServices, defineComponentFixture, defineThemedFixtureGroup } from '../fixtureUtils.js';1718import '../../../../contrib/chat/browser/widget/media/chat.css';1920function createMockArtifactsFromGroups(groups: IArtifactSourceGroup[]): IChatArtifacts {21const artifactGroups = observableValue<readonly IArtifactSourceGroup[]>('artifactGroups', groups);22return new class extends mock<IChatArtifacts>() {23override readonly artifactGroups = artifactGroups;24override setAgentArtifacts(a: IChatArtifact[]) { artifactGroups.set(a.length > 0 ? [{ source: { kind: 'agent' }, artifacts: a }] : [], undefined); }25override clearAgentArtifacts() { artifactGroups.set([], undefined); }26override clearSubagentArtifacts() { }27override migrate() { }28}();29}3031function createMockArtifacts(artifacts: IChatArtifact[]): IChatArtifacts {32return createMockArtifactsFromGroups(artifacts.length > 0 ? [{ source: { kind: 'agent' }, artifacts }] : []);33}3435function createMockArtifactsService(artifacts: IChatArtifact[]): IChatArtifactsService {36const instance = createMockArtifacts(artifacts);37return new class extends mock<IChatArtifactsService>() {38override getArtifacts() { return instance; }39}();40}4142function createMockArtifactsServiceFromGroups(groups: IArtifactSourceGroup[]): IChatArtifactsService {43const instance = createMockArtifactsFromGroups(groups);44return new class extends mock<IChatArtifactsService>() {45override getArtifacts() { return instance; }46}();47}4849function renderArtifactsWidget(context: ComponentFixtureContext, artifacts: IChatArtifact[]): void {50const { container, disposableStore } = context;5152const instantiationService = createEditorServices(disposableStore, {53colorTheme: context.theme,54additionalServices: (reg) => {55reg.define(IListService, ListService);56reg.defineInstance(IContextViewService, new class extends mock<IContextViewService>() { }());57reg.defineInstance(IChatArtifactsService, createMockArtifactsService(artifacts));58reg.defineInstance(IChatImageCarouselService, new class extends mock<IChatImageCarouselService>() { }());59reg.defineInstance(IFileService, new class extends mock<IFileService>() { override onDidFilesChange = Event.None; override onDidRunOperation = Event.None; }());60reg.defineInstance(IFileDialogService, new class extends mock<IFileDialogService>() { }());61},62});6364const widget = disposableStore.add(instantiationService.createInstance(ChatArtifactsWidget));65widget.setSessionResource(URI.parse('chat-session:test-session'));6667container.style.width = '400px';68container.style.padding = '8px';69container.appendChild(widget.domNode);70}7172function renderArtifactsWidgetFromGroups(context: ComponentFixtureContext, groups: IArtifactSourceGroup[]): void {73const { container, disposableStore } = context;7475const instantiationService = createEditorServices(disposableStore, {76colorTheme: context.theme,77additionalServices: (reg) => {78reg.define(IListService, ListService);79reg.defineInstance(IContextViewService, new class extends mock<IContextViewService>() { }());80reg.defineInstance(IChatArtifactsService, createMockArtifactsServiceFromGroups(groups));81reg.defineInstance(IChatImageCarouselService, new class extends mock<IChatImageCarouselService>() { }());82reg.defineInstance(IFileService, new class extends mock<IFileService>() { override onDidFilesChange = Event.None; override onDidRunOperation = Event.None; }());83reg.defineInstance(IFileDialogService, new class extends mock<IFileDialogService>() { }());84},85});8687const widget = disposableStore.add(instantiationService.createInstance(ChatArtifactsWidget));88widget.setSessionResource(URI.parse('chat-session:test-session'));8990container.style.width = '400px';91container.style.padding = '8px';92container.appendChild(widget.domNode);93}9495// ============================================================================96// Sample artifacts97// ============================================================================9899const singleArtifact: IChatArtifact[] = [100{ label: 'Dev Server', uri: 'http://localhost:3000', type: 'devServer' },101];102103const multipleArtifacts: IChatArtifact[] = [104{ label: 'Dev Server', uri: 'http://localhost:3000', type: 'devServer' },105{ label: 'Screenshot of login page', uri: 'file:///tmp/screenshot.png', type: 'screenshot' },106{ label: 'Implementation Plan', uri: 'file:///tmp/plan.md', type: 'plan' },107];108109const multiSourceGroups: IArtifactSourceGroup[] = [110{111source: { kind: 'rules' },112artifacts: [113{ label: 'Implementation Plan', uri: 'file:///tmp/plan.md', type: 'plan', groupName: 'Plans' },114{ label: 'Verification Plan', uri: 'file:///tmp/verify-plan.md', type: 'plan', groupName: 'Plans' },115{ label: 'Screenshot 1', uri: 'file:///tmp/s1.png', type: 'screenshot', groupName: 'Screenshots', onlyShowGroup: true },116{ label: 'Screenshot 2', uri: 'file:///tmp/s2.png', type: 'screenshot', groupName: 'Screenshots', onlyShowGroup: true },117{ label: 'Screenshot 3', uri: 'file:///tmp/s3.png', type: 'screenshot', groupName: 'Screenshots', onlyShowGroup: true },118],119},120{121source: { kind: 'agent' },122artifacts: [123{ label: 'Specification (v2 - reviewed)', uri: 'file:///tmp/spec.md', type: 'plan' },124{ label: 'Dev Server', uri: 'http://localhost:5173', type: 'devServer' },125],126},127{128source: { kind: 'subagent', invocationId: 'sub-1', name: 'Explore' },129artifacts: [130{ label: 'Architecture Notes', uri: 'file:///tmp/arch.md', type: 'plan' },131],132},133];134135// ============================================================================136// Fixtures137// ============================================================================138139export default defineThemedFixtureGroup({ path: 'chat/artifacts/' }, {140SingleArtifact: defineComponentFixture({141render: context => renderArtifactsWidget(context, singleArtifact),142}),143144MultipleArtifacts: defineComponentFixture({145render: context => renderArtifactsWidget(context, multipleArtifacts),146}),147148MultipleArtifactsCollapsed: defineComponentFixture({149render: context => {150renderArtifactsWidget(context, multipleArtifacts);151const expandButton = context.container.querySelector<HTMLElement>('.chat-artifacts-expand .monaco-button');152expandButton?.click();153},154}),155156MultiSourceExpanded: defineComponentFixture({157render: context => renderArtifactsWidgetFromGroups(context, multiSourceGroups),158}),159160MultiSourceHoveredRow: defineComponentFixture({161render: context => {162renderArtifactsWidgetFromGroups(context, multiSourceGroups);163// Force hover on a rules-sourced leaf (save only, no clear)164const rows = context.container.querySelectorAll<HTMLElement>('.chat-artifacts-list-row');165for (const row of rows) {166const label = row.querySelector('.chat-artifacts-list-label');167if (label?.textContent === 'Implementation Plan') {168row.classList.add('force-hover');169break;170}171}172},173}),174});175176177