diff --git a/extensions/sql-migration/src/wizard/skuRecommendationPage.ts b/extensions/sql-migration/src/wizard/skuRecommendationPage.ts index 8f425a1a86..4e10d7a3a4 100644 --- a/extensions/sql-migration/src/wizard/skuRecommendationPage.ts +++ b/extensions/sql-migration/src/wizard/skuRecommendationPage.ts @@ -98,11 +98,13 @@ export class SKURecommendationPage extends MigrationWizardPage { width: WIZARD_INPUT_COMPONENT_WIDTH }).component(); this._managedInstanceSubscriptionDropdown = view.modelBuilder.dropDown().withProps({ - width: WIZARD_INPUT_COMPONENT_WIDTH + width: WIZARD_INPUT_COMPONENT_WIDTH, + editable: true }).component(); this._managedInstanceSubscriptionDropdown.onValueChanged((e) => { - if (e.selected) { - this.migrationStateModel._targetSubscription = this.migrationStateModel.getSubscription(e.index); + if (e) { + const selectedIndex = (this._managedInstanceSubscriptionDropdown.values)?.findIndex(v => v.displayName === e); + this.migrationStateModel._targetSubscription = this.migrationStateModel.getSubscription(selectedIndex); this.migrationStateModel._targetServerInstance = undefined!; this.migrationStateModel._sqlMigrationService = undefined!; this.populateLocationAndResourceGroupDropdown(); @@ -393,6 +395,7 @@ export class SKURecommendationPage extends MigrationWizardPage { this._resourceDropdown.loading = true; try { this._managedInstanceSubscriptionDropdown.values = await this.migrationStateModel.getSubscriptionsDropdownValues(); + this._managedInstanceSubscriptionDropdown.value = this._managedInstanceSubscriptionDropdown.values[0]; } catch (e) { console.log(e); } finally { diff --git a/src/sql/base/parts/editableDropdown/browser/dropdown.ts b/src/sql/base/parts/editableDropdown/browser/dropdown.ts index a4902126ab..44204154e7 100644 --- a/src/sql/base/parts/editableDropdown/browser/dropdown.ts +++ b/src/sql/base/parts/editableDropdown/browser/dropdown.ts @@ -75,6 +75,7 @@ export class Dropdown extends Disposable implements IListVirtualDelegate private _options: IDropdownOptions; private _dataSource = new DropdownDataSource(); public fireOnTextChange?: boolean; + private _previousValue: string; private _onBlur = this._register(new Emitter()); public onBlur: Event = this._onBlur.event; @@ -228,7 +229,6 @@ export class Dropdown extends Disposable implements IListVirtualDelegate } if (this.fireOnTextChange) { this.value = e; - this._onValueChange.fire(e); } }); @@ -253,7 +253,7 @@ export class Dropdown extends Disposable implements IListVirtualDelegate return this._selectListContainer.classList.contains('visible'); } - private _setDropdownVisibility(visible: boolean): void { + public setDropdownVisibility(visible: boolean): void { if (visible) { this._selectListContainer.classList.add('visible'); } else { @@ -264,7 +264,6 @@ export class Dropdown extends Disposable implements IListVirtualDelegate private _updateSelection(newValue: string): void { this.value = newValue; - this._onValueChange.fire(newValue); this._input.focus(); this._hideList(); } @@ -277,12 +276,12 @@ export class Dropdown extends Disposable implements IListVirtualDelegate this.contextViewService.showContextView({ getAnchor: () => this._inputContainer, render: container => { - this._setDropdownVisibility(true); + this.setDropdownVisibility(true); DOM.append(container, this._selectListContainer); this._updateDropDownList(); return { dispose: () => { - this._setDropdownVisibility(false); + this.setDropdownVisibility(false); } }; } @@ -332,7 +331,11 @@ export class Dropdown extends Disposable implements IListVirtualDelegate } public set value(val: string) { - this._input.value = val; + if (this._previousValue !== val) { + this._input.value = val; + this._previousValue = val; + this._onValueChange.fire(val); + } } public get inputElement(): HTMLInputElement { @@ -384,4 +387,12 @@ export class Dropdown extends Disposable implements IListVirtualDelegate public set ariaLabel(val: string) { this._input.setAriaLabel(val); } + + public get input(): InputBox { + return this._input; + } + + public get selectList(): List { + return this._selectList; + } } diff --git a/src/sql/base/test/parts/editableDropdown/browser/dropdown.test.ts b/src/sql/base/test/parts/editableDropdown/browser/dropdown.test.ts new file mode 100644 index 0000000000..a454bbcca6 --- /dev/null +++ b/src/sql/base/test/parts/editableDropdown/browser/dropdown.test.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Dropdown, IDropdownOptions } from 'sql/base/parts/editableDropdown/browser/dropdown'; + +const options: IDropdownOptions = { + values: [ + 'foo1', + 'foo2', + 'foobar3', + 'foobar4' + ] +}; + +suite('Editable dropdown tests', () => { + let container: HTMLElement; + setup(() => { + container = document.createElement('div'); + container.style.position = 'absolute'; + container.style.width = `${200}px`; + container.style.height = `${200}px`; + }); + + test('default value for editable dropdown is empty', () => { + const dropdown = new Dropdown(container, undefined, options); + assert(dropdown.value === ''); + }); + + test('changing value through code fires onValueChange event', () => { + const dropdown = new Dropdown(container, undefined, options); + let count = 0; + dropdown.onValueChange((e) => { + count++; + }); + dropdown.value = options.values[0]; + + assert(count === 1, 'onValueChange event was not fired'); + dropdown.value = options.values[0]; + assert(count === 1, 'onValueChange event should not be fired for setting the same value again'); + dropdown.value = options.values[1]; + assert(count === 2, 'onValueChange event was not fired for setting a new value of the dropdown'); + }); + + test('changing value through input text fires onValue Change event', () => { + const dropdown = new Dropdown(container, undefined, options); + let count = 0; + dropdown.onValueChange((e) => { + count++; + }); + + dropdown.fireOnTextChange = true; + dropdown.setDropdownVisibility(true); + dropdown.input.value = options.values[0]; + assert(count === 1, 'onValueChange event was not fired for an option from the dropdown list'); + dropdown.input.value = 'foo'; + assert(count === 2, 'onValueChange event was not fired for a value not in dropdown list'); + assert(dropdown.selectList.length === 4, 'list does not have all the values that are matching the input box text'); + assert(dropdown.value = 'foo'); + dropdown.input.value = 'foobar'; + assert(count === 3, 'onValueChange event was not fired for a value not in dropdown list'); + assert(dropdown.selectList.length === 2, 'list does not have all the values that are matching the input box text'); + assert(dropdown.value = 'foobar'); + + dropdown.fireOnTextChange = false; + dropdown.input.value = options.values[0]; + assert(count === 3, 'onValueChange event was fired with input box value change even after setting the fireOnTextChange to false'); + }); +}); diff --git a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts index df9ee76202..98cffce995 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts @@ -466,6 +466,7 @@ suite('SQL QueryAction Tests', () => { connectionManagementService.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => { databaseName: databaseName }); + connectionManagementService.setup(x => x.changeDatabase(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => Promise.resolve(true)); // If I query without having initialized anything, state should be clear listItem = new ListDatabasesActionItem(editor.object, undefined, connectionManagementService.object, undefined, configurationService.object, undefined); @@ -497,6 +498,7 @@ suite('SQL QueryAction Tests', () => { let databaseName = 'foobar'; connectionManagementService.setup(x => x.onConnectionChanged).returns(() => dbChangedEmitter.event); connectionManagementService.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => { databaseName: databaseName }); + connectionManagementService.setup(x => x.changeDatabase(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => Promise.resolve(true)); // ... Create a database dropdown that has been connected let listItem = new ListDatabasesActionItem(editor.object, undefined, connectionManagementService.object, undefined, configurationService.object, undefined); @@ -519,6 +521,7 @@ suite('SQL QueryAction Tests', () => { let databaseName = 'foobar'; connectionManagementService.setup(x => x.onConnectionChanged).returns(() => dbChangedEmitter.event); connectionManagementService.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => { databaseName: databaseName }); + connectionManagementService.setup(x => x.changeDatabase(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => Promise.resolve(true)); // ... Create a database dropdown that has been connected let listItem = new ListDatabasesActionItem(editor.object, undefined, connectionManagementService.object, undefined, configurationService.object, undefined);