diff --git a/src/sql/workbench/api/node/extHostModelViewDialog.ts b/src/sql/workbench/api/node/extHostModelViewDialog.ts index 552e7ecf9b..87e48b3c37 100644 --- a/src/sql/workbench/api/node/extHostModelViewDialog.ts +++ b/src/sql/workbench/api/node/extHostModelViewDialog.ts @@ -194,25 +194,22 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape { public createDialog(title: string): sqlops.window.modelviewdialog.Dialog { let dialog = new DialogImpl(this); dialog.title = title; - let handle = ExtHostModelViewDialog.getNewHandle(); - this._dialogHandles.set(dialog, handle); + this.getDialogHandle(dialog); return dialog; } public createTab(title: string): sqlops.window.modelviewdialog.DialogTab { let tab = new TabImpl(this); tab.title = title; - let handle = ExtHostModelViewDialog.getNewHandle(); - this._tabHandles.set(tab, handle); + this.getTabHandle(tab); return tab; } public createButton(label: string): sqlops.window.modelviewdialog.Button { let button = new ButtonImpl(this); - button.label = label; - let handle = ExtHostModelViewDialog.getNewHandle(); - this._buttonHandles.set(button, handle); + this.getButtonHandle(button); this.registerOnClickCallback(button, button.getOnClickCallback()); + button.label = label; return button; } } \ No newline at end of file diff --git a/src/sqltest/platform/dialog/dialogPane.test.ts b/src/sqltest/platform/dialog/dialogPane.test.ts index 12ac7a868b..db6b0e1b37 100644 --- a/src/sqltest/platform/dialog/dialogPane.test.ts +++ b/src/sqltest/platform/dialog/dialogPane.test.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { Dialog, DialogTab } from 'sql/platform/dialog/dialogTypes'; +import { Dialog, DialogTab } from 'sql/platform/dialog/dialogTypes'; import { Mock, It, Times } from 'typemoq'; import { IBootstrapService } from 'sql/services/bootstrap/bootstrapService'; import { DialogPane } from 'sql/platform/dialog/dialogPane'; diff --git a/src/sqltest/workbench/api/extHostModelViewDialog.test.ts b/src/sqltest/workbench/api/extHostModelViewDialog.test.ts new file mode 100644 index 0000000000..ef492f2dc1 --- /dev/null +++ b/src/sqltest/workbench/api/extHostModelViewDialog.test.ts @@ -0,0 +1,121 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Mock, It, Times } from 'typemoq'; +import { ExtHostModelViewDialog } from 'sql/workbench/api/node/extHostModelViewDialog'; +import { MainThreadModelViewDialogShape } from 'sql/workbench/api/node/sqlExtHost.protocol'; +import { IMainContext } from 'vs/workbench/api/node/extHost.protocol'; + +'use strict'; + +suite('ExtHostModelViewDialog Tests', () => { + let extHostModelViewDialog: ExtHostModelViewDialog; + let mockProxy: Mock; + + setup(() => { + mockProxy = Mock.ofInstance({ + $open: handle => undefined, + $close: handle => undefined, + $setDialogDetails: (handle, details) => undefined, + $setTabDetails: (handle, details) => undefined, + $setButtonDetails: (handle, details) => undefined + }); + let mainContext = { + getProxy: proxyType => mockProxy.object + }; + extHostModelViewDialog = new ExtHostModelViewDialog(mainContext); + }); + + test('Creating a dialog returns a dialog with initialized ok and cancel buttons and the given title', () => { + let title = 'dialog_title'; + let dialog = extHostModelViewDialog.createDialog(title); + + assert.equal(dialog.title, title); + assert.equal(dialog.okButton.enabled, true); + assert.equal(dialog.cancelButton.enabled, true); + }); + + test('Creating a tab returns a tab with the given title', () => { + let title = 'tab_title'; + let tab = extHostModelViewDialog.createTab(title); + + assert.equal(tab.title, title); + }); + + test('Creating a button returns an enabled button with the given label', () => { + let label = 'button_label'; + let button = extHostModelViewDialog.createButton(label); + + assert.equal(button.label, label); + assert.equal(button.enabled, true); + }); + + test('Opening a dialog updates its tabs and buttons on the main thread', () => { + mockProxy.setup(x => x.$open(It.isAny())); + mockProxy.setup(x => x.$setDialogDetails(It.isAny(), It.isAny())); + mockProxy.setup(x => x.$setTabDetails(It.isAny(), It.isAny())); + mockProxy.setup(x => x.$setButtonDetails(It.isAny(), It.isAny())); + + // Create a dialog with 2 tabs and 2 custom buttons + let dialogTitle = 'dialog_title'; + let dialog = extHostModelViewDialog.createDialog(dialogTitle); + let tab1Title = 'tab_1'; + let tab1 = extHostModelViewDialog.createTab(tab1Title); + let tab2Title = 'tab_2'; + let tab2 = extHostModelViewDialog.createTab(tab2Title); + dialog.content = [tab1, tab2]; + let button1Label = 'button_1'; + let button1 = extHostModelViewDialog.createButton(button1Label); + button1.enabled = false; + let button2Label = 'button_2'; + let button2 = extHostModelViewDialog.createButton(button2Label); + + // Open the dialog and verify that the correct main thread methods were called + extHostModelViewDialog.open(dialog); + mockProxy.verify(x => x.$setButtonDetails(It.isAny(), It.is(details => { + return details.enabled === false && details.label === button1Label; + })), Times.once()); + mockProxy.verify(x => x.$setButtonDetails(It.isAny(), It.is(details => { + return details.enabled === true && details.label === button2Label; + })), Times.once()); + mockProxy.verify(x => x.$setTabDetails(It.isAny(), It.is(details => { + return details.title === tab1Title; + })), Times.once()); + mockProxy.verify(x => x.$setTabDetails(It.isAny(), It.is(details => { + return details.title === tab2Title; + })), Times.once()); + mockProxy.verify(x => x.$setDialogDetails(It.isAny(), It.is(details => { + return details.title === dialogTitle; + })), Times.once()); + mockProxy.verify(x => x.$open(It.isAny()), Times.once()); + }); + + test('Button clicks are forwarded to the correct button', () => { + // Set up the proxy to record button handles + let handles = []; + mockProxy.setup(x => x.$setButtonDetails(It.isAny(), It.isAny())).callback((handle, details) => handles.push(handle)); + + // Set up the buttons to record click events + let label1 = 'button_1'; + let label2 = 'button_2'; + let button1 = extHostModelViewDialog.createButton(label1); + let button2 = extHostModelViewDialog.createButton(label2); + let clickEvents = []; + button1.onClick(() => clickEvents.push(1)); + button2.onClick(() => clickEvents.push(2)); + extHostModelViewDialog.updateButton(button1); + extHostModelViewDialog.updateButton(button2); + + // If the main thread sends some notifications that the buttons have been clicked + extHostModelViewDialog.$onButtonClick(handles[0]); + extHostModelViewDialog.$onButtonClick(handles[1]); + extHostModelViewDialog.$onButtonClick(handles[1]); + extHostModelViewDialog.$onButtonClick(handles[0]); + + // Then the clicks should have been handled by the expected handlers + assert.deepEqual(clickEvents, [1, 2, 2, 1]); + }); +}); \ No newline at end of file diff --git a/src/sqltest/workbench/api/mainThreadModelViewDialog.test.ts b/src/sqltest/workbench/api/mainThreadModelViewDialog.test.ts new file mode 100644 index 0000000000..6d4128ab29 --- /dev/null +++ b/src/sqltest/workbench/api/mainThreadModelViewDialog.test.ts @@ -0,0 +1,153 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Mock, It, Times } from 'typemoq'; +import { MainThreadModelViewDialog } from 'sql/workbench/api/node/mainThreadModelViewDialog'; +import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol'; +import { IModelViewButtonDetails, IModelViewTabDetails, IModelViewDialogDetails } from 'sql/workbench/api/common/sqlExtHostTypes'; +import { CustomDialogService } from 'sql/platform/dialog/customDialogService'; +import { Dialog, DialogTab } from 'sql/platform/dialog/dialogTypes'; +import { ExtHostModelViewDialogShape } from 'sql/workbench/api/node/sqlExtHost.protocol'; +import { Emitter } from 'vs/base/common/event'; + +'use strict'; + +suite('MainThreadModelViewDialog Tests', () => { + let mainThreadModelViewDialog: MainThreadModelViewDialog; + let mockExtHostModelViewDialog: Mock; + let mockDialogService: Mock; + let openedDialog: Dialog; + + // Dialog details + let button1Details: IModelViewButtonDetails; + let button2Details: IModelViewButtonDetails; + let okButtonDetails: IModelViewButtonDetails; + let cancelButtonDetails: IModelViewButtonDetails; + let tab1Details: IModelViewTabDetails; + let tab2Details: IModelViewTabDetails; + let dialogDetails: IModelViewDialogDetails; + let button1Handle = 1; + let button2Handle = 2; + let okButtonHandle = 3; + let cancelButtonHandle = 4; + let tab1Handle = 5; + let tab2Handle = 6; + let dialogHandle = 7; + + setup(() => { + mockExtHostModelViewDialog = Mock.ofInstance({ + $onButtonClick: handle => undefined + }); + let extHostContext = { + getProxy: proxyType => mockExtHostModelViewDialog.object + }; + mainThreadModelViewDialog = new MainThreadModelViewDialog(extHostContext, undefined); + + // Set up the mock dialog service + mockDialogService = Mock.ofType(CustomDialogService, undefined, undefined); + openedDialog = undefined; + mockDialogService.setup(x => x.showDialog(It.isAny())).callback(dialog => openedDialog = dialog); + (mainThreadModelViewDialog as any)._dialogService = mockDialogService.object; + + // Set up the dialog details + button1Details = { + label: 'button1', + enabled: false + }; + button2Details = { + label: 'button2', + enabled: true + }; + okButtonDetails = { + label: 'ok_label', + enabled: true + }; + cancelButtonDetails = { + label: 'cancel_label', + enabled: true + }; + tab1Details = { + title: 'tab1', + content: 'content1' + }; + tab2Details = { + title: 'tab2', + content: 'content2' + }; + dialogDetails = { + title: 'dialog1', + content: [tab1Handle, tab2Handle], + okButton: okButtonHandle, + cancelButton: cancelButtonHandle, + customButtons: [button1Handle, button2Handle] + }; + + // Register the buttons, tabs, and dialog + mainThreadModelViewDialog.$setButtonDetails(button1Handle, button1Details); + mainThreadModelViewDialog.$setButtonDetails(button2Handle, button2Details); + mainThreadModelViewDialog.$setButtonDetails(okButtonHandle, okButtonDetails); + mainThreadModelViewDialog.$setButtonDetails(cancelButtonHandle, cancelButtonDetails); + mainThreadModelViewDialog.$setTabDetails(tab1Handle, tab1Details); + mainThreadModelViewDialog.$setTabDetails(tab2Handle, tab2Details); + mainThreadModelViewDialog.$setDialogDetails(dialogHandle, dialogDetails); + }); + + test('Creating a dialog and calling open on it causes a dialog with correct content and buttons to open', () => { + // If I open the dialog + mainThreadModelViewDialog.$open(dialogHandle); + + // Then the opened dialog's content and buttons match what was set + mockDialogService.verify(x => x.showDialog(It.isAny()), Times.once()); + assert.notEqual(openedDialog, undefined); + assert.equal(openedDialog.title, dialogDetails.title); + assert.equal(openedDialog.okButton.label, okButtonDetails.label); + assert.equal(openedDialog.okButton.enabled, okButtonDetails.enabled); + assert.equal(openedDialog.cancelButton.label, cancelButtonDetails.label); + assert.equal(openedDialog.cancelButton.enabled, cancelButtonDetails.enabled); + assert.equal(openedDialog.customButtons.length, 2); + assert.equal(openedDialog.customButtons[0].label, button1Details.label); + assert.equal(openedDialog.customButtons[0].enabled, button1Details.enabled); + assert.equal(openedDialog.customButtons[1].label, button2Details.label); + assert.equal(openedDialog.customButtons[1].enabled, button2Details.enabled); + assert.equal(openedDialog.content.length, 2); + assert.equal((openedDialog.content[0] as DialogTab).content, tab1Details.content); + assert.equal((openedDialog.content[0] as DialogTab).title, tab1Details.title); + assert.equal((openedDialog.content[1] as DialogTab).content, tab2Details.content); + assert.equal((openedDialog.content[1] as DialogTab).title, tab2Details.title); + }); + + test('Button presses are forwarded to the extension host', () => { + // Set up the mock proxy to capture button presses + let pressedHandles = []; + mockExtHostModelViewDialog.setup(x => x.$onButtonClick(It.isAny())).callback(handle => pressedHandles.push(handle)); + + // Open the dialog so that its buttons can be accessed + mainThreadModelViewDialog.$open(dialogHandle); + + // Set up click emitters for each button + let okEmitter = new Emitter(); + let cancelEmitter = new Emitter(); + let button1Emitter = new Emitter(); + let button2Emitter = new Emitter(); + openedDialog.okButton.registerClickEvent(okEmitter.event); + openedDialog.cancelButton.registerClickEvent(cancelEmitter.event); + openedDialog.customButtons[0].registerClickEvent(button1Emitter.event); + openedDialog.customButtons[1].registerClickEvent(button2Emitter.event); + + // Click the buttons + button1Emitter.fire(); + button2Emitter.fire(); + okEmitter.fire(); + cancelEmitter.fire(); + button2Emitter.fire(); + cancelEmitter.fire(); + button1Emitter.fire(); + okEmitter.fire(); + + // Verify that the correct button click notifications were sent to the proxy + assert.deepEqual(pressedHandles, [button1Handle, button2Handle, okButtonHandle, cancelButtonHandle, button2Handle, cancelButtonHandle, button1Handle, okButtonHandle]); + }); +}); \ No newline at end of file