Path: blob/main/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts
5222 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 * as nls from '../../../../nls.js';6import assert from 'assert';7import { mock } from '../../../../base/test/common/mock.js';8import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';9import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';10import { TestInstantiationService } from '../../../../platform/instantiation/test/common/instantiationServiceMock.js';11import { NullLogService } from '../../../../platform/log/common/log.js';12import { TestNotificationService } from '../../../../platform/notification/test/common/testNotificationService.js';13import { Registry } from '../../../../platform/registry/common/platform.js';14import { NullTelemetryService } from '../../../../platform/telemetry/common/telemetryUtils.js';15import { MainThreadTreeViews } from '../../browser/mainThreadTreeViews.js';16import { DataTransferDTO, ExtHostTreeViewsShape } from '../../common/extHost.protocol.js';17import { CustomTreeView } from '../../../browser/parts/views/treeView.js';18import { Extensions, ITreeItem, ITreeView, ITreeViewDescriptor, IViewContainersRegistry, IViewDescriptorService, IViewsRegistry, TreeItemCollapsibleState, ViewContainer, ViewContainerLocation } from '../../../common/views.js';19import { IExtHostContext } from '../../../services/extensions/common/extHostCustomers.js';20import { ExtensionHostKind } from '../../../services/extensions/common/extensionHostKind.js';21import { ViewDescriptorService } from '../../../services/views/browser/viewDescriptorService.js';22import { TestViewsService, workbenchInstantiationService } from '../../../test/browser/workbenchTestServices.js';23import { TestExtensionService } from '../../../test/common/workbenchTestServices.js';24import { CancellationToken } from '../../../../base/common/cancellation.js';25import { Mimes } from '../../../../base/common/mime.js';26import { URI } from '../../../../base/common/uri.js';2728suite('MainThreadHostTreeView', function () {29const testTreeViewId = 'testTreeView';30const customValue = 'customValue';31const ViewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);3233interface CustomTreeItem extends ITreeItem {34customProp: string;35}3637class MockExtHostTreeViewsShape extends mock<ExtHostTreeViewsShape>() {38override async $getChildren(treeViewId: string, treeItemHandle?: string[]): Promise<(number | ITreeItem)[][]> {39return [[0, <CustomTreeItem>{ handle: 'testItem1', collapsibleState: TreeItemCollapsibleState.Expanded, customProp: customValue }]];40}4142override async $hasResolve(): Promise<boolean> {43return false;44}4546override $setVisible(): void { }47}4849let container: ViewContainer;50let mainThreadTreeViews: MainThreadTreeViews;51let extHostTreeViewsShape: MockExtHostTreeViewsShape;52let instantiationService: TestInstantiationService;5354teardown(() => {55ViewsRegistry.deregisterViews(ViewsRegistry.getViews(container), container);56});5758const disposables = ensureNoDisposablesAreLeakedInTestSuite();5960setup(async () => {61instantiationService = workbenchInstantiationService(undefined, disposables);62const viewDescriptorService = disposables.add(instantiationService.createInstance(ViewDescriptorService));63instantiationService.stub(IViewDescriptorService, viewDescriptorService);64// eslint-disable-next-line local/code-no-any-casts65container = Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).registerViewContainer({ id: 'testContainer', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);66const viewDescriptor: ITreeViewDescriptor = {67id: testTreeViewId,68ctorDescriptor: null!,69name: nls.localize2('Test View 1', 'Test View 1'),70treeView: disposables.add(instantiationService.createInstance(CustomTreeView, 'testTree', 'Test Title', 'extension.id')),71};72ViewsRegistry.registerViews([viewDescriptor], container);7374const testExtensionService = new TestExtensionService();75extHostTreeViewsShape = new MockExtHostTreeViewsShape();76mainThreadTreeViews = disposables.add(new MainThreadTreeViews(77new class implements IExtHostContext {78remoteAuthority = '';79extensionHostKind = ExtensionHostKind.LocalProcess;80dispose() { }81assertRegistered() { }82set(v: any): any { return null; }83getProxy(): any {84return extHostTreeViewsShape;85}86drain(): any { return null; }87}, new TestViewsService(), new TestNotificationService(), testExtensionService, new NullLogService(), NullTelemetryService));88mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false, dropMimeTypes: [], dragMimeTypes: [], hasHandleDrag: false, hasHandleDrop: false, manuallyManageCheckboxes: false });89await testExtensionService.whenInstalledExtensionsRegistered();90});9192test('getChildren keeps custom properties', async () => {93const treeView: ITreeView = (<ITreeViewDescriptor>ViewsRegistry.getView(testTreeViewId)).treeView;94const children = await treeView.dataProvider?.getChildren({ handle: 'root', collapsibleState: TreeItemCollapsibleState.Expanded });95assert(children!.length === 1, 'Exactly one child should be returned');96assert((<CustomTreeItem>children![0]).customProp === customValue, 'Tree Items should keep custom properties');97});9899test('handleDrag reconstructs URI list from uriListData', async () => {100const testTreeViewIdWithDrag = 'testTreeViewWithDrag';101102// Create a mock that returns URI list data103const mockExtHostWithDrag = new class extends mock<ExtHostTreeViewsShape>() {104override async $getChildren(treeViewId: string, treeItemHandle?: string[]): Promise<(number | ITreeItem)[][]> {105return [[0, { handle: 'item1', collapsibleState: TreeItemCollapsibleState.None }]];106}107108override async $hasResolve(): Promise<boolean> {109return false;110}111112override $setVisible(): void { }113114override async $handleDrag(_sourceViewId: string, _sourceTreeItemHandles: string[], _operationUuid: string, _token: CancellationToken): Promise<DataTransferDTO | undefined> {115// Return a DataTransferDTO with text/uri-list containing uriListData116// This simulates what the extension host sends after URI transformation117return {118items: [119[Mimes.uriList, {120id: 'test-id',121// This is the original (untransformed) string - should NOT be used122asString: 'file:///original/untransformed/path.txt',123fileData: undefined,124// This is the transformed URI data - should be used125uriListData: [126{ scheme: 'file', authority: '', path: '/transformed/correct/path.txt', query: '', fragment: '' }127]128}]129]130};131}132}();133134// Register a view with drag support135const viewDescriptorWithDrag: ITreeViewDescriptor = {136id: testTreeViewIdWithDrag,137ctorDescriptor: null!,138name: nls.localize2('Test View 2', 'Test View 2'),139treeView: disposables.add(instantiationService.createInstance(CustomTreeView, 'testTree2', 'Test Title 2', 'extension.id')),140};141ViewsRegistry.registerViews([viewDescriptorWithDrag], container);142143const dragTestExtensionService = new TestExtensionService();144const dragTestMainThreadTreeViews = disposables.add(new MainThreadTreeViews(145new class implements IExtHostContext {146remoteAuthority = '';147extensionHostKind = ExtensionHostKind.LocalProcess;148dispose() { }149assertRegistered() { }150set(v: any): any { return null; }151getProxy(): any {152return mockExtHostWithDrag;153}154drain(): any { return null; }155}, new TestViewsService(), new TestNotificationService(), dragTestExtensionService, new NullLogService(), NullTelemetryService));156dragTestMainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewIdWithDrag, {157showCollapseAll: false,158canSelectMany: false,159dropMimeTypes: [],160dragMimeTypes: [Mimes.uriList],161hasHandleDrag: true,162hasHandleDrop: false,163manuallyManageCheckboxes: false164});165await dragTestExtensionService.whenInstalledExtensionsRegistered();166167// Get the tree view and its drag controller168const dragTestTreeView: ITreeView = (<ITreeViewDescriptor>ViewsRegistry.getView(testTreeViewIdWithDrag)).treeView;169const dragController = dragTestTreeView.dragAndDropController;170assert(dragController, 'Drag controller should exist');171172// Call handleDrag173const result = await dragController.handleDrag(['item1'], 'test-operation-uuid', CancellationToken.None);174assert(result, 'Result should not be undefined');175176// Verify that the URI list was reconstructed from uriListData, not asString177const uriListItem = result.get(Mimes.uriList);178assert(uriListItem, 'URI list item should exist');179180const uriListValue = await uriListItem.asString();181// The value should be the transformed URI, not the original untransformed one182assert.strictEqual(uriListValue, URI.from({ scheme: 'file', authority: '', path: '/transformed/correct/path.txt', query: '', fragment: '' }).toString());183});184185186});187188189