diff --git a/src/sql/workbench/contrib/objectExplorer/test/browser/serverTreeView.test.ts b/src/sql/workbench/contrib/objectExplorer/test/browser/serverTreeView.test.ts index 33d794847f..677aecef50 100644 --- a/src/sql/workbench/contrib/objectExplorer/test/browser/serverTreeView.test.ts +++ b/src/sql/workbench/contrib/objectExplorer/test/browser/serverTreeView.test.ts @@ -7,17 +7,18 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { ServerTreeView } from 'sql/workbench/contrib/objectExplorer/browser/serverTreeView'; import { ConnectionManagementService } from 'sql/workbench/services/connection/browser/connectionManagementService'; -import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { TestStorageService } from 'vs/workbench/test/workbenchTestServices'; import * as TypeMoq from 'typemoq'; import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService'; +import { ITree } from 'vs/base/parts/tree/browser/tree'; +import { TestTree } from 'sql/workbench/contrib/objectExplorer/test/browser/treeMock'; suite('ServerTreeView onAddConnectionProfile handler tests', () => { let serverTreeView: ServerTreeView; - let mockTree: TypeMoq.Mock; + let mockTree: TypeMoq.Mock; let mockRefreshTreeMethod: TypeMoq.Mock; let capabilitiesService = new TestCapabilitiesService(); @@ -27,13 +28,7 @@ suite('ServerTreeView onAddConnectionProfile handler tests', () => { mockConnectionManagementService.setup(x => x.getConnectionGroups()).returns(x => []); mockConnectionManagementService.setup(x => x.hasRegisteredServers()).returns(() => true); serverTreeView = new ServerTreeView(mockConnectionManagementService.object, instantiationService, undefined, undefined, undefined, undefined, capabilitiesService); - let tree = { - clearSelection() { }, - getSelection() { }, - select(selection) { }, - reveal(reveal) { } - }; - mockTree = TypeMoq.Mock.ofInstance(tree); + mockTree = TypeMoq.Mock.ofType(TestTree); (serverTreeView as any)._tree = mockTree.object; mockRefreshTreeMethod = TypeMoq.Mock.ofType(Function); mockRefreshTreeMethod.setup(x => x()).returns(() => Promise.resolve()); diff --git a/src/sql/workbench/contrib/objectExplorer/test/browser/treeMock.ts b/src/sql/workbench/contrib/objectExplorer/test/browser/treeMock.ts new file mode 100644 index 0000000000..cb89477b3b --- /dev/null +++ b/src/sql/workbench/contrib/objectExplorer/test/browser/treeMock.ts @@ -0,0 +1,119 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { Event } from 'vs/base/common/event'; +import { INavigator } from 'vs/base/common/iterator'; +import { ITree, IHighlightEvent, ISelectionEvent, IFocusEvent, ITreeStyles } from 'vs/base/parts/tree/browser/tree'; +import { IItemExpandEvent, IItemCollapseEvent } from 'vs/base/parts/tree/browser/treeModel'; + +/** + * A basic implementation of ITree to use for testing + */ +export class TestTree implements ITree { + + readonly onDidChangeFocus: Event; + readonly onDidChangeSelection: Event; + readonly onDidChangeHighlight: Event; + readonly onDidExpandItem: Event; + readonly onDidCollapseItem: Event; + readonly onDidDispose: Event; + + constructor() { } + + public style(styles: ITreeStyles): void { } + + get onDidFocus(): Event { return undefined; } + + get onDidBlur(): Event { return undefined; } + + get onDidScroll(): Event { return undefined; } + + public getHTMLElement(): HTMLElement { return undefined; } + + public layout(height?: number, width?: number): void { } + + public domFocus(): void { } + + public isDOMFocused(): boolean { return true; } + + public domBlur(): void { } + + public onVisible(): void { } + + public onHidden(): void { } + + public setInput(element: any): Promise { return Promise.resolve(true); } + + public getInput(): any { return undefined; } + + public refresh(element: any = null, recursive = true): Promise { return Promise.resolve(true); } + + public expand(element: any): Promise { return Promise.resolve(true); } + + public expandAll(elements: any[]): Promise { return Promise.resolve(true); } + + public collapse(element: any, recursive: boolean = false): Promise { return Promise.resolve(true); } + + public collapseAll(elements: any[] | null = null, recursive: boolean = false): Promise { return Promise.resolve(true); } + + public toggleExpansion(element: any, recursive: boolean = false): Promise { return Promise.resolve(true); } + + public isExpanded(element: any): boolean { return true; } + + public reveal(element: any, relativeTop: number | null = null): Promise { return Promise.resolve(true); } + + public getExpandedElements(): any[] { return []; } + + public getScrollPosition(): number { return 0; } + + public setScrollPosition(pos: number): void { } + + getContentHeight(): number { return 0; } + + public getHighlight(): any { } + + public clearHighlight(eventPayload?: any): void { } + + public setSelection(elements: any[], eventPayload?: any): void { } + + public getSelection(): any[] { return []; } + + public clearSelection(eventPayload?: any): void { } + + public setFocus(element?: any, eventPayload?: any): void { } + + public getFocus(): any { } + + public focusNext(count?: number, eventPayload?: any): void { } + + public focusPrevious(count?: number, eventPayload?: any): void { } + + public focusParent(eventPayload?: any): void { } + + public focusFirstChild(eventPayload?: any): void { } + + public focusFirst(eventPayload?: any, from?: any): void { } + + public focusNth(index: number, eventPayload?: any): void { } + + public focusLast(eventPayload?: any, from?: any): void { } + + public focusNextPage(eventPayload?: any): void { } + + public focusPreviousPage(eventPayload?: any): void { } + + public clearFocus(eventPayload?: any): void { } + + public addTraits(trait: string, elements: any[]): void { } + + public removeTraits(trait: string, elements: any[]): void { } + + public select(element: any, eventPayload?: any): void { } + + public deselect(element: any, eventPayload?: any): void { } + + getNavigator(fromElement?: any, subTreeOnly?: boolean): INavigator { return undefined; } + + public dispose(): void { } +} diff --git a/src/sql/workbench/contrib/scripting/browser/scriptingActions.ts b/src/sql/workbench/contrib/scripting/browser/scriptingActions.ts index 1bc5ad8897..90b2f974c2 100644 --- a/src/sql/workbench/contrib/scripting/browser/scriptingActions.ts +++ b/src/sql/workbench/contrib/scripting/browser/scriptingActions.ts @@ -26,6 +26,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { getErrorMessage } from 'vs/base/common/errors'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; //#region -- Data Explorer export const SCRIPT_AS_CREATE_COMMAND_ID = 'dataExplorer.scriptAsCreate'; @@ -310,22 +311,24 @@ CommandsRegistry.registerCommand({ // Refresh Action for Scriptable objects CommandsRegistry.registerCommand({ id: OE_REFRESH_COMMAND_ID, - handler: async (accessor, args: ObjectExplorerActionsContext): Promise => { - const objectExplorerService = accessor.get(IObjectExplorerService); - const logService = accessor.get(ILogService); - const notificationService = accessor.get(INotificationService); - const treeNode = await getTreeNode(args, objectExplorerService); - const tree = objectExplorerService.getServerTreeView().tree; - try { - await objectExplorerService.refreshTreeNode(treeNode.getSession(), treeNode); - await tree.refresh(treeNode); - } catch (err) { - // Display message to the user but also log the entire error to the console for the stack trace - notificationService.error(localize('refreshError', "An error occurred refreshing node '{0}': {1}", args.nodeInfo.label, getErrorMessage(err))); - logService.error(err); - } - } + handler: handleOeRefreshCommand }); + +export async function handleOeRefreshCommand(accessor: ServicesAccessor, args: ObjectExplorerActionsContext): Promise { + const objectExplorerService = accessor.get(IObjectExplorerService); + const logService = accessor.get(ILogService); + const notificationService = accessor.get(INotificationService); + const treeNode = await getTreeNode(args, objectExplorerService); + const tree = objectExplorerService.getServerTreeView().tree; + try { + await objectExplorerService.refreshTreeNode(treeNode.getSession(), treeNode); + await tree.refresh(treeNode); + } catch (err) { + // Display message to the user but also log the entire error to the console for the stack trace + notificationService.error(localize('refreshError', "An error occurred refreshing node '{0}': {1}", args.nodeInfo.label, getErrorMessage(err))); + logService.error(err); + } +} //#endregion //#region -- explorer widget diff --git a/src/sql/workbench/contrib/scripting/test/browser/scriptingActions.test.ts b/src/sql/workbench/contrib/scripting/test/browser/scriptingActions.test.ts new file mode 100644 index 0000000000..d7175de989 --- /dev/null +++ b/src/sql/workbench/contrib/scripting/test/browser/scriptingActions.test.ts @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as azdata from 'azdata'; +import * as TypeMoq from 'typemoq'; +import { handleOeRefreshCommand } from 'sql/workbench/contrib/scripting/browser/scriptingActions'; +import { ObjectExplorerActionsContext } from 'sql/workbench/contrib/objectExplorer/browser/objectExplorerActions'; +import { mssqlProviderName } from 'sql/platform/connection/common/constants'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; +import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService'; +import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService'; +import { TreeNode } from 'sql/workbench/contrib/objectExplorer/common/treeNode'; +import { ILogService, NullLogService } from 'vs/platform/log/common/log'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { NodeType } from 'sql/workbench/contrib/objectExplorer/common/nodeType'; +import { ServerTreeView } from 'sql/workbench/contrib/objectExplorer/browser/serverTreeView'; +import { createObjectExplorerServiceMock } from 'sql/workbench/services/objectExplorer/test/browser/testObjectExplorerService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ITree } from 'vs/base/parts/tree/browser/tree'; +import { TestTree } from 'sql/workbench/contrib/objectExplorer/test/browser/treeMock'; +import { TestConnectionManagementService } from 'sql/platform/connection/test/common/testConnectionManagementService'; + +const connection: azdata.IConnectionProfile = { + options: [], + connectionName: '', + serverName: 'server1', + databaseName: 'database', + userName: 'user', + password: 'password', + authenticationType: '', + providerName: mssqlProviderName, + groupId: '', + groupFullName: '', + savePassword: true, + saveProfile: true, + id: 'server1' +}; + +const nodeInfo: azdata.NodeInfo = { + nodePath: 'MyServer', + nodeStatus: '', + nodeSubType: '', + nodeType: 'Server', + isLeaf: false, + label: 'MyServer', + metadata: undefined, + errorMessage: '' +}; + +const treeNode = new TreeNode(NodeType.Database, 'db node', false, '', '', '', undefined, undefined, undefined, undefined); +const oeActionArgs: ObjectExplorerActionsContext = { connectionProfile: connection, isConnectionNode: false, nodeInfo: nodeInfo }; + +let instantiationService: IInstantiationService; +let logServiceMock: TypeMoq.Mock; +let treeMock: TypeMoq.Mock; + +suite('Scripting Actions', () => { + + setup(() => { + const collection = new ServiceCollection(); + instantiationService = new InstantiationService(collection); + const capabilitiesService = new TestCapabilitiesService(); + const connectionManagementServiceMock = TypeMoq.Mock.ofType(TestConnectionManagementService, TypeMoq.MockBehavior.Loose); + const serverTreeViewMock = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Loose, connectionManagementServiceMock.object, instantiationService, undefined, undefined, undefined, undefined, capabilitiesService); + treeMock = TypeMoq.Mock.ofType(TestTree); + serverTreeViewMock.setup(x => x.tree).returns(() => treeMock.object); + collection.set(IObjectExplorerService, createObjectExplorerServiceMock({ serverTreeView: serverTreeViewMock.object, treeNode: treeNode }).object); + logServiceMock = TypeMoq.Mock.ofInstance(new NullLogService()); + collection.set(ILogService, logServiceMock.object); + collection.set(INotificationService, new TestNotificationService()); + }); + + suite('objectExplorer.refreshNode', () => { + + test('refresh should be called when action is invoked', async () => { + await instantiationService.invokeFunction(handleOeRefreshCommand, oeActionArgs); + treeMock.verify(x => x.refresh(TypeMoq.It.isAny()), TypeMoq.Times.once()); + }); + + test('errors should be logged when refresh throws', async () => { + treeMock.setup(x => x.refresh(TypeMoq.It.isAny())).throws(new Error()); + await instantiationService.invokeFunction(handleOeRefreshCommand, oeActionArgs); + treeMock.verify(x => x.refresh(TypeMoq.It.isAny()), TypeMoq.Times.once()); + logServiceMock.verify(x => x.error(TypeMoq.It.isAny()), TypeMoq.Times.once()); + }); + }); +}); diff --git a/src/sql/workbench/services/objectExplorer/test/browser/testObjectExplorerService.ts b/src/sql/workbench/services/objectExplorer/test/browser/testObjectExplorerService.ts new file mode 100644 index 0000000000..c03ab77a7a --- /dev/null +++ b/src/sql/workbench/services/objectExplorer/test/browser/testObjectExplorerService.ts @@ -0,0 +1,108 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TreeNode } from 'sql/workbench/contrib/objectExplorer/common/treeNode'; +import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; +import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; +import { Event } from 'vs/base/common/event'; +import { ServerTreeView } from 'sql/workbench/contrib/objectExplorer/browser/serverTreeView'; +import { ObjectExplorerNodeEventArgs, IObjectExplorerService, NodeExpandInfoWithProviderId } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService'; +import * as azdata from 'azdata'; +import * as TypeMoq from 'typemoq'; + +export type ObjectExplorerServiceMockOptions = { + /** + * Return value for getServerTreeView + */ + serverTreeView?: ServerTreeView; + /** + * Return value for getTreeNode + */ + treeNode?: TreeNode; +}; + +/** + * + * @param options Options to use for setting up functions on the mock to return various values + */ +export function createObjectExplorerServiceMock(options: ObjectExplorerServiceMockOptions): TypeMoq.Mock { + const objectExplorerService = TypeMoq.Mock.ofType(TestObjectExplorerService); + + if (options.treeNode) { + objectExplorerService.setup(x => x.getTreeNode(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(options.treeNode)); + } + + if (options.serverTreeView) { + objectExplorerService.setup(x => x.getServerTreeView()).returns(() => options.serverTreeView); + } + + return objectExplorerService; +} + +/** + * A basic implementation of IObjectExplorerService to use for testing + */ +export class TestObjectExplorerService implements IObjectExplorerService { + + public _serviceBrand: undefined; + + constructor() { } + + public getSession(sessionId: string): azdata.ObjectExplorerSession { return undefined; } + + public providerRegistered(providerId: string): boolean { return true; } + + public get onUpdateObjectExplorerNodes(): Event { return undefined; } + + public get onSelectionOrFocusChange(): Event { return undefined; } + + public updateObjectExplorerNodes(connection: IConnectionProfile): Promise { return Promise.resolve(); } + + public deleteObjectExplorerNode(connection: IConnectionProfile): Thenable { return Promise.resolve(); } + + public onNodeExpanded(expandResponse: NodeExpandInfoWithProviderId) { } + + public onSessionCreated(handle: number, session: azdata.ObjectExplorerSession): void { } + + public onSessionDisconnected(handle: number, session: azdata.ObjectExplorerSession) { } + + public getObjectExplorerNode(connection: IConnectionProfile): TreeNode { return undefined; } + + public async createNewSession(providerId: string, connection: ConnectionProfile): Promise { return undefined; } + + public expandNode(providerId: string, session: azdata.ObjectExplorerSession, nodePath: string): Thenable { return Promise.resolve(undefined); } + + public refreshNode(providerId: string, session: azdata.ObjectExplorerSession, nodePath: string): Thenable { return Promise.resolve(undefined); } + + public closeSession(providerId: string, session: azdata.ObjectExplorerSession): Thenable { return Promise.resolve(undefined); } + + public registerProvider(providerId: string, provider: azdata.ObjectExplorerProvider): void { } + + public registerNodeProvider(nodeProvider: azdata.ObjectExplorerNodeProvider): void { } + + public resolveTreeNodeChildren(session: azdata.ObjectExplorerSession, parentTree: TreeNode): Thenable { return Promise.resolve(undefined); } + + public refreshTreeNode(session: azdata.ObjectExplorerSession, parentTree: TreeNode): Thenable { return Promise.resolve(undefined); } + + public registerServerTreeView(view: ServerTreeView): void { } + + public getSelectedProfileAndDatabase(): { profile: ConnectionProfile, databaseName: string } { return undefined; } + + public isFocused(): boolean { return true; } + + public getServerTreeView(): ServerTreeView { return undefined; } + + public findNodes(connectionId: string, type: string, schema: string, name: string, database: string, parentObjectNames?: string[]): Thenable { return Promise.resolve(undefined); } + + public getActiveConnectionNodes(): TreeNode[] { return undefined; } + + public getNodeActions(connectionId: string, nodePath: string): Thenable { return Promise.resolve(undefined); } + + public async refreshNodeInView(connectionId: string, nodePath: string): Promise { return Promise.resolve(undefined); } + + public getSessionConnectionProfile(sessionId: string): azdata.IConnectionProfile { return undefined; } + + public async getTreeNode(connectionId: string, nodePath: string): Promise { return Promise.resolve(undefined); } +}