diff --git a/extensions/dacpac/src/test/DacFxTestConfigPages.ts b/extensions/dacpac/src/test/DacFxTestConfigPages.ts new file mode 100644 index 0000000000..a9d0cf4937 --- /dev/null +++ b/extensions/dacpac/src/test/DacFxTestConfigPages.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { DacFxDataModel } from '../wizard/api/models'; +import { DeployConfigPage } from '../wizard/pages/deployConfigPage'; +import { ExtractConfigPage } from '../wizard/pages/extractConfigPage'; +import { DataTierApplicationWizard } from '../wizard/dataTierApplicationWizard'; + +export class TestDeployConfigPage extends DeployConfigPage { + + constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) { + super(instance, wizardPage, model, view); + } + + get Model(): DacFxDataModel { + return this.model; + } + + SetDatabaseDropDown(): void { + this.databaseDropdown.value = { name: 'DummyDatabase', displayName: 'DummyDatabase' }; + } + + SetFileName(): void { + this.fileTextBox.value = 'DummyDacpac'; + } +} + +export class TestExtractConfigPage extends ExtractConfigPage { + + constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) { + super(instance, wizardPage, model, view); + } + + get Model(): DacFxDataModel { + return this.model; + } + +} diff --git a/extensions/dacpac/src/test/configPages.test.ts b/extensions/dacpac/src/test/configPages.test.ts new file mode 100644 index 0000000000..26598f16ed --- /dev/null +++ b/extensions/dacpac/src/test/configPages.test.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import * as should from 'should'; +import { DataTierApplicationWizard, PageName } from '../wizard/dataTierApplicationWizard'; +import { DacFxDataModel } from '../wizard/api/models'; +import { TestContext, createContext } from './testContext'; +import { TestDeployConfigPage, TestExtractConfigPage } from './DacFxTestConfigPages'; + +let wizard: DataTierApplicationWizard; +let testContext: TestContext; + +describe('Dacfx Wizard Pages', function (): void { + beforeEach(async function (): Promise { + wizard = new DataTierApplicationWizard(); + wizard.model = {}; + wizard.model.server = undefined; + }); + + it('Should open and edit deploy config page correctly', async () => { + testContext = createContext(); + wizard.setPages(); + + let deployConfigPage = new TestDeployConfigPage(wizard, wizard.pages.get(PageName.deployConfig).wizardPage, wizard.model, testContext.viewContext.view); + await deployConfigPage.start(); + + // Validate state after start + should.equal(deployConfigPage.Model.upgradeExisting, true); + should.equal(deployConfigPage.Model.filePath, undefined); + should.equal(deployConfigPage.Model.database, undefined); + + // Adding file name in test box should update model path and db + deployConfigPage.SetFileName(); + testContext.viewContext.onTextChanged.fire(undefined); + should.equal(deployConfigPage.Model.filePath, 'DummyDacpac'); + should.equal(deployConfigPage.Model.database, 'DummyDacpac'); + + // Choosing database should update model db but not path + deployConfigPage.SetDatabaseDropDown(); + testContext.viewContext.onValueChanged.fire(undefined); + should.equal(deployConfigPage.Model.filePath, 'DummyDacpac'); + should.equal(deployConfigPage.Model.database, 'DummyDatabase'); + + // Changing radio buttons should affect model correctly + testContext.viewContext.newDatabaseRadioOnClick.fire(undefined); + should.equal(deployConfigPage.Model.upgradeExisting, false); + testContext.viewContext.updateExistingRadioOnClick.fire(undefined); + should.equal(deployConfigPage.Model.upgradeExisting, true); + }); + + it('Should open and edit extract config page correctly', async () => { + testContext = createContext(); + wizard.setPages(); + + let extractConfigPage = new TestExtractConfigPage(wizard, wizard.pages.get(PageName.deployConfig).wizardPage, wizard.model, testContext.viewContext.view); + await extractConfigPage.start(); + + // Validate state after start + should.equal(extractConfigPage.Model.version, '1.0.0.0'); + should.equal(extractConfigPage.Model.filePath, ''); + should.equal(extractConfigPage.Model.database, undefined); + + // Note : no need to test above function from deploy page since they are from common base class + // on text change event value in text box should overwrite model value + extractConfigPage.Model.version = 'dummy'; + testContext.viewContext.onTextChanged.fire(undefined); + should.equal(extractConfigPage.Model.version, '1.0.0.0'); + + // Should autocorrect file name to correct extension type + extractConfigPage.Model.filePath = 'DummyPath'; + await extractConfigPage.onPageLeave(); + should.equal(extractConfigPage.Model.filePath, 'DummyPath.dacpac'); + }); +}); diff --git a/extensions/dacpac/src/test/testContext.ts b/extensions/dacpac/src/test/testContext.ts index 4f0285aee6..03e5321bea 100644 --- a/extensions/dacpac/src/test/testContext.ts +++ b/extensions/dacpac/src/test/testContext.ts @@ -5,14 +5,17 @@ import * as vscode from 'vscode'; import * as path from 'path'; +import * as azdata from 'azdata'; +import * as loc from '../localizedConstants'; export interface TestContext { context: vscode.ExtensionContext; + viewContext: ViewTestContext; } export function createContext(): TestContext { let extensionPath = path.join(__dirname, '..', '..'); - + let viewContext = createViewContext(); return { context: { subscriptions: [], @@ -32,6 +35,235 @@ export function createContext(): TestContext { extensionUri: vscode.Uri.parse(''), environmentVariableCollection: undefined as any, extensionMode: undefined as any - } + }, + viewContext: viewContext + }; +} + +export interface ViewTestContext { + view: azdata.ModelView; + onClick: vscode.EventEmitter; + onTextChanged: vscode.EventEmitter; + onValueChanged: vscode.EventEmitter; + newDatabaseRadioOnClick: vscode.EventEmitter; + updateExistingRadioOnClick: vscode.EventEmitter; +} + +export function createViewContext(): ViewTestContext { + let onClick: vscode.EventEmitter = new vscode.EventEmitter(); + let onTextChanged: vscode.EventEmitter = new vscode.EventEmitter(); + let onValueChanged: vscode.EventEmitter = new vscode.EventEmitter(); + let newDatabaseRadioOnClick: vscode.EventEmitter = new vscode.EventEmitter(); + let updateExistingRadioOnClick: vscode.EventEmitter = new vscode.EventEmitter(); + + let componentBase: azdata.Component = { + id: '', + updateProperties: () => Promise.resolve(), + updateProperty: () => Promise.resolve(), + updateCssStyles: undefined!, + onValidityChanged: undefined!, + valid: true, + validate: undefined!, + focus: () => Promise.resolve() + }; + let button: azdata.ButtonComponent = Object.assign({}, componentBase, { + onDidClick: onClick.event + }); + let radioButton = () => { + let button: azdata.RadioButtonComponent = Object.assign({}, componentBase, { + name: '', + label: '', + checked: false, + onDidClick: onClick.event, + }); + return button; + }; + let checkbox: azdata.CheckBoxComponent = Object.assign({}, componentBase, { + checked: true, + onChanged: onClick.event + }); + let container = { + clearItems: () => { }, + addItems: () => { }, + addItem: () => { }, + removeItem: () => true, + insertItem: () => { }, + items: [] as any[], + setLayout: () => { }, + setItemLayout: () => { } + }; + let form: azdata.FormContainer = Object.assign({}, componentBase, container, { + }); + let flex: azdata.FlexContainer = Object.assign({}, componentBase, container, { + }); + let div: azdata.DivContainer = Object.assign({}, componentBase, container, { + onDidClick: onClick.event + }); + + let buttonBuilder: azdata.ComponentBuilder = { + component: () => button, + withProperties: () => buttonBuilder, + withValidation: () => buttonBuilder + }; + + let radioButtonBuilder = () => { + let button = radioButton(); + let builder: azdata.ComponentBuilder = { + component: () => button, + withProperties: (properties) => { + if ((properties as any).label === loc.newDatabase) { + button.label = loc.newDatabase; + button.onDidClick = newDatabaseRadioOnClick.event; + } + else if ((properties as any).label === loc.upgradeExistingDatabase) { + button.label = loc.upgradeExistingDatabase; + button.onDidClick = updateExistingRadioOnClick.event; + } + return builder; + }, + withValidation: () => builder + }; + return builder; + }; + + let checkBoxBuilder: azdata.ComponentBuilder = { + component: () => checkbox, + withProperties: () => checkBoxBuilder, + withValidation: () => checkBoxBuilder + }; + let inputBox: () => azdata.InputBoxComponent = () => Object.assign({}, componentBase, { + onTextChanged: onTextChanged.event, + onEnterKeyPressed: onClick.event, + value: '' + }); + let dropdown: () => azdata.DropDownComponent = () => Object.assign({}, componentBase, { + onValueChanged: onValueChanged.event, + value: { + name: '', + displayName: '' + }, + values: [] + }); + + let table: () => azdata.TableComponent = () => Object.assign({}, componentBase, { + data: [] as any[][], + columns: [] as string[], + onRowSelected: onClick.event, + }); + + let loadingComponent: () => azdata.LoadingComponent = () => Object.assign({}, componentBase, { + loading: false, + component: undefined! + }); + + let tableBuilder: azdata.ComponentBuilder = { + component: () => table(), + withProperties: () => tableBuilder, + withValidation: () => tableBuilder + }; + + let loadingBuilder: azdata.LoadingComponentBuilder = { + component: () => loadingComponent(), + withProperties: () => loadingBuilder, + withValidation: () => loadingBuilder, + withItem: () => loadingBuilder + }; + + let formBuilder: azdata.FormBuilder = Object.assign({}, { + component: () => form, + addFormItem: () => { }, + insertFormItem: () => { }, + removeFormItem: () => true, + addFormItems: () => { }, + withFormItems: () => formBuilder, + withProperties: () => formBuilder, + withValidation: () => formBuilder, + withItems: () => formBuilder, + withLayout: () => formBuilder + }); + + let flexBuilder: azdata.FlexBuilder = Object.assign({}, { + component: () => flex, + withProperties: () => flexBuilder, + withValidation: () => flexBuilder, + withItems: () => flexBuilder, + withLayout: () => flexBuilder + }); + let divBuilder: azdata.DivBuilder = Object.assign({}, { + component: () => div, + withProperties: () => divBuilder, + withValidation: () => divBuilder, + withItems: () => divBuilder, + withLayout: () => divBuilder + }); + + let inputBoxBuilder: azdata.ComponentBuilder = { + component: () => { + let r = inputBox(); + return r; + }, + withProperties: () => inputBoxBuilder, + withValidation: () => inputBoxBuilder + }; + + let dropdownBuilder: azdata.ComponentBuilder = { + component: () => { + let r = dropdown(); + return r; + }, + withProperties: () => dropdownBuilder, + withValidation: () => dropdownBuilder + }; + + let view: azdata.ModelView = { + onClosed: undefined!, + connection: undefined!, + serverInfo: undefined!, + valid: true, + onValidityChanged: undefined!, + validate: undefined!, + initializeModel: () => { return Promise.resolve(); }, + modelBuilder: { + radioCardGroup: undefined!, + navContainer: undefined!, + divContainer: () => divBuilder, + flexContainer: () => flexBuilder, + splitViewContainer: undefined!, + dom: undefined!, + card: () => undefined!, + inputBox: () => inputBoxBuilder, + checkBox: () => checkBoxBuilder!, + radioButton: () => radioButtonBuilder(), + webView: undefined!, + editor: undefined!, + diffeditor: undefined!, + text: () => inputBoxBuilder, + image: () => undefined!, + button: () => buttonBuilder, + dropDown: () => dropdownBuilder, + tree: undefined!, + listBox: undefined!, + table: () => tableBuilder, + declarativeTable: () => undefined!, + dashboardWidget: undefined!, + dashboardWebview: undefined!, + formContainer: () => formBuilder, + groupContainer: () => undefined!, + toolbarContainer: undefined!, + loadingComponent: () => loadingBuilder, + fileBrowserTree: undefined!, + hyperlink: () => undefined!, + tabbedPanel: undefined!, + separator: undefined!, + propertiesContainer: undefined! + } + }; + return { + view: view, + onClick: onClick, + onTextChanged: onTextChanged, + onValueChanged: onValueChanged, + newDatabaseRadioOnClick: newDatabaseRadioOnClick, + updateExistingRadioOnClick: updateExistingRadioOnClick, }; } diff --git a/extensions/dacpac/src/test/wizard.test.ts b/extensions/dacpac/src/test/wizard.test.ts index 96bb694208..1aaafc6a9d 100644 --- a/extensions/dacpac/src/test/wizard.test.ts +++ b/extensions/dacpac/src/test/wizard.test.ts @@ -6,10 +6,21 @@ import 'mocha'; import * as should from 'should'; import * as loc from '../localizedConstants'; -import { DataTierApplicationWizard, Operation } from '../wizard/dataTierApplicationWizard'; +import { DataTierApplicationWizard, Operation, PageName } from '../wizard/dataTierApplicationWizard'; import { DacFxDataModel } from '../wizard/api/models'; +import { TestContext, createContext } from './testContext'; +import { DeployConfigPage } from '../wizard/pages/deployConfigPage'; +import { ExportConfigPage } from '../wizard/pages/exportConfigPage'; +import { ExtractConfigPage } from '../wizard/pages/extractConfigPage'; +import { ImportConfigPage } from '../wizard/pages/importConfigPage'; +import { DacFxSummaryPage } from '../wizard/pages/dacFxSummaryPage'; +import { SelectOperationPage } from '../wizard/pages/selectOperationpage'; +import { DeployPlanPage } from '../wizard/pages/deployPlanPage'; +import { BasePage } from '../wizard/api/basePage'; let wizard: DataTierApplicationWizard; +let testContext: TestContext; + describe('Dacfx wizard', function (): void { beforeEach(async function (): Promise { wizard = new DataTierApplicationWizard(); @@ -24,6 +35,32 @@ describe('Dacfx wizard', function (): void { should.notEqual(wizard.pages, undefined); should.equal(wizard.pages.size, 7); should.equal(wizard.wizard.pages.length, 4); + + should.equal(wizard.pages.get(PageName.selectOperation).wizardPage.title, loc.selectOperationPageName); + }); + + it('Should initialize wizard with correct page order', async () => { + wizard.setPages(); + + wizard.selectedOperation = Operation.deploy; + should.equal(wizard.getPage(1), wizard.pages.get(PageName.deployConfig)); + wizard.model.upgradeExisting = false; + should.equal(wizard.getPage(2), wizard.pages.get(PageName.summary)); + wizard.model.upgradeExisting = true; + should.equal(wizard.getPage(2), wizard.pages.get(PageName.deployPlan)); + should.equal(wizard.getPage(3), wizard.pages.get(PageName.summary)); + + wizard.selectedOperation = Operation.export; + should.equal(wizard.getPage(1), wizard.pages.get(PageName.exportConfig)); + should.equal(wizard.getPage(2), wizard.pages.get(PageName.summary)); + + wizard.selectedOperation = Operation.extract; + should.equal(wizard.getPage(1), wizard.pages.get(PageName.extractConfig)); + should.equal(wizard.getPage(2), wizard.pages.get(PageName.summary)); + + wizard.selectedOperation = Operation.import; + should.equal(wizard.getPage(1), wizard.pages.get(PageName.importConfig)); + should.equal(wizard.getPage(2), wizard.pages.get(PageName.summary)); }); it('Should determine summary page correctly', async () => { @@ -70,4 +107,42 @@ describe('Dacfx wizard', function (): void { wizard.setDoneButton(Operation.export); should.equal(wizard.selectedOperation, Operation.export); }); + + it('Should start all pages - Set 1', async () => { + testContext = createContext(); + wizard.setPages(); + + let selectOperationPage = new SelectOperationPage(wizard, wizard.pages.get(PageName.selectOperation).wizardPage, wizard.model, testContext.viewContext.view); + await validateStartPage(selectOperationPage); + + let deployConfigPage = new DeployConfigPage(wizard, wizard.pages.get(PageName.deployConfig).wizardPage, wizard.model, testContext.viewContext.view); + await validateStartPage(deployConfigPage); + + let deployPlanPage = new DeployPlanPage(wizard, wizard.pages.get(PageName.deployPlan).wizardPage, wizard.model, testContext.viewContext.view); + await validateStartPage(deployPlanPage); + + let dacFxSummaryPage = new DacFxSummaryPage(wizard, wizard.pages.get(PageName.summary).wizardPage, wizard.model, testContext.viewContext.view); + await validateStartPage(dacFxSummaryPage); + + }); + + it('Should start all pages - Set 2', async () => { + testContext = createContext(); + wizard.setPages(); + + let exportConfigPage = new ExportConfigPage(wizard, wizard.pages.get(PageName.exportConfig).wizardPage, wizard.model, testContext.viewContext.view); + await validateStartPage(exportConfigPage); + + let extractConfigPage = new ExtractConfigPage(wizard, wizard.pages.get(PageName.exportConfig).wizardPage, wizard.model, testContext.viewContext.view); + await validateStartPage(extractConfigPage); + + let importConfigPage = new ImportConfigPage(wizard, wizard.pages.get(PageName.importConfig).wizardPage, wizard.model, testContext.viewContext.view); + await validateStartPage(importConfigPage); + }); + + async function validateStartPage(page: BasePage) : Promise { + let result = false; + result = await page.start(); + should.equal(result, true); + } }); diff --git a/extensions/dacpac/src/wizard/dataTierApplicationWizard.ts b/extensions/dacpac/src/wizard/dataTierApplicationWizard.ts index f34728b637..6d083c0d12 100644 --- a/extensions/dacpac/src/wizard/dataTierApplicationWizard.ts +++ b/extensions/dacpac/src/wizard/dataTierApplicationWizard.ts @@ -306,7 +306,7 @@ export class DataTierApplicationWizard { await service.generateDeployScript(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.script); } - private getPage(idx: number): Page { + public getPage(idx: number): Page { let page: Page; if (idx === 1) { diff --git a/extensions/dacpac/src/wizard/pages/deployConfigPage.ts b/extensions/dacpac/src/wizard/pages/deployConfigPage.ts index 28d5f77903..30afebc48b 100644 --- a/extensions/dacpac/src/wizard/pages/deployConfigPage.ts +++ b/extensions/dacpac/src/wizard/pages/deployConfigPage.ts @@ -104,13 +104,13 @@ export class DeployConfigPage extends DacFxConfigPage { private async createRadiobuttons(): Promise { let upgradeRadioButton = this.view.modelBuilder.radioButton() .withProperties({ - name: 'updateExisting', + name: 'updateExistingOrCreateNew', label: loc.upgradeExistingDatabase, }).component(); let newRadioButton = this.view.modelBuilder.radioButton() .withProperties({ - name: 'updateExisting', + name: 'updateExistingOrCreateNew', label: loc.newDatabase, }).component();