From ca19f085822f86c39962e647b97ec104dd5ef471 Mon Sep 17 00:00:00 2001 From: Kim Santiago <31145923+kisantia@users.noreply.github.com> Date: Fri, 26 Mar 2021 15:30:29 -0700 Subject: [PATCH] Add git clone option for opening existing workspace (#14828) * added git option to dialog * Add validation for cloning workspace and hide radio buttons for project * add test * cleanup --- .../data-workspace/src/common/constants.ts | 7 + .../data-workspace/src/common/telemetry.ts | 3 +- .../src/dialogs/openExistingDialog.ts | 204 +++++++++++++++--- .../test/dialogs/openExistingDialog.test.ts | 52 +++-- 4 files changed, 215 insertions(+), 51 deletions(-) diff --git a/extensions/data-workspace/src/common/constants.ts b/extensions/data-workspace/src/common/constants.ts index 754f370cb7..0be62279c2 100644 --- a/extensions/data-workspace/src/common/constants.ts +++ b/extensions/data-workspace/src/common/constants.ts @@ -53,12 +53,19 @@ export const WorkspaceFileAlreadyExistsError = (file: string): string => { retur //Open Existing Dialog export const OpenExistingDialogTitle = localize('dataworkspace.openExistingDialogTitle', "Open existing"); export const FileNotExistError = (fileType: string, filePath: string): string => { return localize('dataworkspace.fileNotExistError', "The selected {0} file '{1}' does not exist or is not a file.", fileType, filePath); }; +export const CloneParentDirectoryNotExistError = (location: string): string => { return localize('dataworkspace.cloneParentDirectoryNotExistError', "The selected clone path '{0}' does not exist or is not a directory.", location); }; export const Project = localize('dataworkspace.project', "Project"); export const Workspace = localize('dataworkspace.workspace', "Workspace"); export const LocationSelectorTitle = localize('dataworkspace.locationSelectorTitle', "Location"); export const ProjectFilePlaceholder = localize('dataworkspace.projectFilePlaceholder', "Select project (.sqlproj) file"); export const WorkspacePlaceholder = localize('dataworkspace.workspacePlaceholder', "Select workspace ({0}) file", WorkspaceFileExtension); export const ProjectAlreadyOpened = (path: string): string => { return localize('dataworkspace.projectAlreadyOpened', "Project '{0}' is already opened.", path); }; +export const Local = localize('dataworksapce.local', 'Local'); +export const RemoteGitRepo = localize('dataworkspace.remoteGitRepo', "Remote git repository"); +export const GitRepoUrlTitle = localize('dataworkspace.gitRepoUrlTitle', "Git repository URL"); +export const GitRepoUrlPlaceholder = localize('dataworkspace.gitRepoUrlPlaceholder', "Enter remote git repository URL"); +export const LocalClonePathTitle = localize('dataworkspace.localClonePathTitle', "Local clone path"); +export const LocalClonePathPlaceholder = localize('dataworkspace.localClonePathPlaceholder', "Select location to clone repository locally"); // Workspace settings for saving new projects export const ProjectConfigurationKey = 'projects'; diff --git a/extensions/data-workspace/src/common/telemetry.ts b/extensions/data-workspace/src/common/telemetry.ts index 2837d5a516..9126154d35 100644 --- a/extensions/data-workspace/src/common/telemetry.ts +++ b/extensions/data-workspace/src/common/telemetry.ts @@ -52,5 +52,6 @@ export enum TelemetryActions { NewProjectDialogLaunched = 'NewProjectDialogLaunched', OpeningWorkspace = 'OpeningWorkspace', OpenExistingDialogLaunched = 'OpenExistingDialogLaunched', - NewProjectDialogCompleted = 'NewProjectDialogCompleted' + NewProjectDialogCompleted = 'NewProjectDialogCompleted', + GitClone = 'GitClone' } diff --git a/extensions/data-workspace/src/dialogs/openExistingDialog.ts b/extensions/data-workspace/src/dialogs/openExistingDialog.ts index eab062d1c6..10053346db 100644 --- a/extensions/data-workspace/src/dialogs/openExistingDialog.ts +++ b/extensions/data-workspace/src/dialogs/openExistingDialog.ts @@ -9,13 +9,21 @@ import * as path from 'path'; import { DialogBase } from './dialogBase'; import * as constants from '../common/constants'; import { IWorkspaceService } from '../common/interfaces'; -import { fileExist } from '../common/utils'; +import { directoryExist, fileExist } from '../common/utils'; import { IconPathHelper } from '../common/iconHelper'; import { calculateRelativity, TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry'; +import { defaultProjectSaveLocation } from '../common/projectLocationHelper'; export class OpenExistingDialog extends DialogBase { - public _targetTypeRadioCardGroup: azdata.RadioCardGroupComponent | undefined; - public _filePathTextBox: azdata.InputBoxComponent | undefined; + public targetTypeRadioCardGroup: azdata.RadioCardGroupComponent | undefined; + public filePathTextBox: azdata.InputBoxComponent | undefined; + public filePathAndButtonComponent: azdata.FormComponent | undefined; + public gitRepoTextBoxComponent: azdata.FormComponent | undefined; + public localClonePathComponent: azdata.FormComponent | undefined; + public localClonePathTextBox: azdata.InputBoxComponent | undefined; + public localRadioButton: azdata.RadioButtonComponent | undefined; + public remoteGitRepoRadioButton: azdata.RadioButtonComponent | undefined; + public locationRadioButtonFormComponent: azdata.FormComponent | undefined; public formBuilder: azdata.FormBuilder | undefined; private _targetTypes = [ @@ -40,14 +48,20 @@ export class OpenExistingDialog extends DialogBase { async validate(): Promise { try { // the selected location should be an existing directory - if (this._targetTypeRadioCardGroup?.selectedCardId === constants.Project) { - await this.validateFile(this._filePathTextBox!.value!, constants.Project.toLowerCase()); + if (this.targetTypeRadioCardGroup?.selectedCardId === constants.Project) { + await this.validateFile(this.filePathTextBox!.value!, constants.Project.toLowerCase()); if (this.workspaceInputBox!.enabled) { await this.validateNewWorkspace(false); } - } else if (this._targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) { - await this.validateFile(this._filePathTextBox!.value!, constants.Workspace.toLowerCase()); + } else if (this.targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) { + if (this.localRadioButton?.checked) { + await this.validateFile(this.filePathTextBox!.value!, constants.Workspace.toLowerCase()); + } else { + // validate clone location + // check if parent folder exists + await this.validateClonePath(this.localClonePathTextBox!.value); + } } return true; @@ -65,15 +79,33 @@ export class OpenExistingDialog extends DialogBase { } } + public async validateClonePath(location: string): Promise { + // only need to check if parent directory exists + // if the same repo has been cloned before, the git clone will append the next number to the folder + const parentDirectoryExists = await directoryExist(location); + if (!parentDirectoryExists) { + throw new Error(constants.CloneParentDirectoryNotExistError(location)); + } + } + async onComplete(): Promise { try { - if (this._targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) { + if (this.targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) { // capture that workspace was selected, also if there's already an open workspace that's being replaced TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.OpeningWorkspace) .withAdditionalProperties({ hasWorkspaceOpen: (vscode.workspace.workspaceFile !== undefined).toString() }) .send(); - await this.workspaceService.enterWorkspace(vscode.Uri.file(this._filePathTextBox!.value!)); + if (this.remoteGitRepoRadioButton!.checked) { + TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.GitClone) + .withAdditionalProperties({ selectedTarget: 'workspace' }) + .send(); + + // after this executes, the git extension will show a popup asking if you want to enter the workspace + await vscode.commands.executeCommand('git.clone', (this.gitRepoTextBoxComponent?.component).value, this.localClonePathTextBox!.value); + } else { + await this.workspaceService.enterWorkspace(vscode.Uri.file(this.filePathTextBox!.value!)); + } } else { // save datapoint now because it'll get set to new value during validateWorkspace() const telemetryProps: any = { hasWorkspaceOpen: (vscode.workspace.workspaceFile !== undefined).toString() }; @@ -82,13 +114,13 @@ export class OpenExistingDialog extends DialogBase { let addProjectsPromise: Promise; if (validateWorkspace) { - telemetryProps.workspaceProjectRelativity = calculateRelativity(this._filePathTextBox!.value!, this.workspaceInputBox!.value!); + telemetryProps.workspaceProjectRelativity = calculateRelativity(this.filePathTextBox!.value!, this.workspaceInputBox!.value!); telemetryProps.cancelled = 'false'; - addProjectsPromise = this.workspaceService.addProjectsToWorkspace([vscode.Uri.file(this._filePathTextBox!.value!)], vscode.Uri.file(this.workspaceInputBox!.value!)); + addProjectsPromise = this.workspaceService.addProjectsToWorkspace([vscode.Uri.file(this.filePathTextBox!.value!)], vscode.Uri.file(this.workspaceInputBox!.value!)); } else { telemetryProps.workspaceProjectRelativity = 'none'; telemetryProps.cancelled = 'true'; - addProjectsPromise = this.workspaceService.addProjectsToWorkspace([vscode.Uri.file(this._filePathTextBox!.value!)], vscode.Uri.file(this.workspaceInputBox!.value!)); + addProjectsPromise = this.workspaceService.addProjectsToWorkspace([vscode.Uri.file(this.filePathTextBox!.value!)], vscode.Uri.file(this.workspaceInputBox!.value!)); } TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.OpeningProject) @@ -104,7 +136,7 @@ export class OpenExistingDialog extends DialogBase { } protected async initialize(view: azdata.ModelView): Promise { - this._targetTypeRadioCardGroup = view.modelBuilder.radioCardGroup().withProperties({ + this.targetTypeRadioCardGroup = view.modelBuilder.radioCardGroup().withProperties({ cards: this._targetTypes.map((targetType) => { return { id: targetType.name, @@ -130,44 +162,148 @@ export class OpenExistingDialog extends DialogBase { selectedCardId: constants.Project }).component(); - this._filePathTextBox = view.modelBuilder.inputBox().withProperties({ - ariaLabel: constants.LocationSelectorTitle, - placeHolder: constants.ProjectFilePlaceholder, + this.localRadioButton = view.modelBuilder.radioButton().withProperties({ + name: 'location', + label: constants.Local, + checked: true + }).component(); + + this.register(this.localRadioButton.onDidChangeCheckedState(checked => { + if (checked) { + this.formBuilder?.removeFormItem(this.gitRepoTextBoxComponent); + this.formBuilder?.removeFormItem(this.localClonePathComponent); + this.formBuilder?.insertFormItem(this.filePathAndButtonComponent, 2); + } + })); + + this.remoteGitRepoRadioButton = view.modelBuilder.radioButton().withProperties({ + name: 'location', + label: constants.RemoteGitRepo + }).component(); + + this.locationRadioButtonFormComponent = { + title: constants.LocationSelectorTitle, + required: true, + component: view.modelBuilder.flexContainer() + .withItems([this.localRadioButton, this.remoteGitRepoRadioButton], { flex: '0 0 auto', CSSStyles: { 'margin-right': '15px' } }) + .withProperties({ ariaRole: 'radiogroup' }) + .component() + }; + + this.register(this.remoteGitRepoRadioButton.onDidChangeCheckedState(checked => { + if (checked) { + this.formBuilder?.removeFormItem(this.filePathAndButtonComponent); + this.formBuilder?.insertFormItem(this.gitRepoTextBoxComponent, 2); + this.formBuilder?.insertFormItem(this.localClonePathComponent, 3); + } + })); + + this.gitRepoTextBoxComponent = { + title: constants.GitRepoUrlTitle, + component: view.modelBuilder.inputBox().withProperties({ + ariaLabel: constants.GitRepoUrlTitle, + placeHolder: constants.GitRepoUrlPlaceholder, + required: true, + width: constants.DefaultInputWidth + }).component() + }; + + this.localClonePathTextBox = view.modelBuilder.inputBox().withProperties({ + ariaLabel: constants.LocalClonePathTitle, + placeHolder: constants.LocalClonePathPlaceholder, required: true, width: constants.DefaultInputWidth }).component(); - this.register(this._filePathTextBox.onTextChanged(() => { - this._filePathTextBox!.updateProperty('title', this._filePathTextBox!.value!); - this.updateWorkspaceInputbox(path.dirname(this._filePathTextBox!.value!), path.basename(this._filePathTextBox!.value!, path.extname(this._filePathTextBox!.value!))); - })); - const browseFolderButton = view.modelBuilder.button().withProperties({ + const localClonePathBrowseFolderButton = view.modelBuilder.button().withProperties({ ariaLabel: constants.BrowseButtonText, iconPath: IconPathHelper.folder, width: '18px', height: '16px', }).component(); - this.register(browseFolderButton.onDidClick(async () => { - if (this._targetTypeRadioCardGroup?.selectedCardId === constants.Project) { + + this.register(localClonePathBrowseFolderButton.onDidClick(async () => { + const folderUris = await vscode.window.showOpenDialog({ + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + defaultUri: defaultProjectSaveLocation() + }); + if (!folderUris || folderUris.length === 0) { + return; + } + + const selectedFolder = folderUris[0].fsPath; + this.localClonePathTextBox!.value = selectedFolder; + })); + + this.localClonePathComponent = { + title: constants.LocalClonePathTitle, + component: this.createHorizontalContainer(view, [this.localClonePathTextBox, localClonePathBrowseFolderButton]), + required: true + }; + + this.filePathTextBox = view.modelBuilder.inputBox().withProperties({ + ariaLabel: constants.LocationSelectorTitle, + placeHolder: constants.ProjectFilePlaceholder, + required: true, + width: constants.DefaultInputWidth + }).component(); + + this.register(this.filePathTextBox.onTextChanged(() => { + this.filePathTextBox!.updateProperty('title', this.filePathTextBox!.value!); + this.updateWorkspaceInputbox(path.dirname(this.filePathTextBox!.value!), path.basename(this.filePathTextBox!.value!, path.extname(this.filePathTextBox!.value!))); + })); + + const localProjectBrowseFolderButton = view.modelBuilder.button().withProperties({ + ariaLabel: constants.BrowseButtonText, + iconPath: IconPathHelper.folder, + width: '18px', + height: '16px' + }).component(); + this.register(localProjectBrowseFolderButton.onDidClick(async () => { + if (this.targetTypeRadioCardGroup?.selectedCardId === constants.Project) { await this.projectBrowse(); - } else if (this._targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) { + } else if (this.targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) { await this.workspaceBrowse(); } })); - this.register(this._targetTypeRadioCardGroup.onSelectionChanged(({ cardId }) => { + const flexContainer = this.createHorizontalContainer(view, [this.filePathTextBox, localProjectBrowseFolderButton]); + flexContainer.updateCssStyles({ 'margin-top': '-10px' }); + this.filePathAndButtonComponent = { + component: flexContainer + }; + + this.register(this.targetTypeRadioCardGroup.onSelectionChanged(({ cardId }) => { if (cardId === constants.Project) { - this._filePathTextBox!.placeHolder = constants.ProjectFilePlaceholder; + this.filePathTextBox!.placeHolder = constants.ProjectFilePlaceholder; + // hide these two radio buttons for now since git clone is just for workspaces + this.localRadioButton?.updateCssStyles({ 'display': 'none' }); + this.remoteGitRepoRadioButton?.updateCssStyles({ 'display': 'none' }); + this.formBuilder?.removeFormItem(this.gitRepoTextBoxComponent); + this.formBuilder?.removeFormItem(this.localClonePathComponent); + + this.formBuilder?.addFormItem(this.filePathAndButtonComponent!); this.formBuilder?.addFormItem(this.workspaceDescriptionFormComponent!); this.formBuilder?.addFormItem(this.workspaceInputFormComponent!); } else if (cardId === constants.Workspace) { - this._filePathTextBox!.placeHolder = constants.WorkspacePlaceholder; + this.filePathTextBox!.placeHolder = constants.WorkspacePlaceholder; + this.localRadioButton?.updateCssStyles({ 'display': 'block' }); + this.remoteGitRepoRadioButton?.updateCssStyles({ 'display': 'block' }); + this.formBuilder?.removeFormItem(this.workspaceDescriptionFormComponent!); this.formBuilder?.removeFormItem(this.workspaceInputFormComponent!); + + if (this.remoteGitRepoRadioButton!.checked) { + this.formBuilder?.removeFormItem(this.filePathAndButtonComponent); + this.formBuilder?.insertFormItem(this.gitRepoTextBoxComponent, 2); + this.formBuilder?.insertFormItem(this.localClonePathComponent, 3); + } } // clear selected file textbox - this._filePathTextBox!.value = ''; + this.filePathTextBox!.value = ''; })); this.createWorkspaceContainer(view); @@ -176,12 +312,10 @@ export class OpenExistingDialog extends DialogBase { { title: constants.TypeTitle, required: true, - component: this._targetTypeRadioCardGroup, - }, { - title: constants.LocationSelectorTitle, - required: true, - component: this.createHorizontalContainer(view, [this._filePathTextBox, browseFolderButton]) + component: this.targetTypeRadioCardGroup, }, + this.locationRadioButtonFormComponent, + this.filePathAndButtonComponent, this.workspaceDescriptionFormComponent!, this.workspaceInputFormComponent! ]); @@ -204,7 +338,7 @@ export class OpenExistingDialog extends DialogBase { } const workspaceFilePath = fileUris[0].fsPath; - this._filePathTextBox!.value = workspaceFilePath; + this.filePathTextBox!.value = workspaceFilePath; } public async projectBrowse(): Promise { @@ -228,6 +362,6 @@ export class OpenExistingDialog extends DialogBase { } const projectFilePath = fileUris[0].fsPath; - this._filePathTextBox!.value = projectFilePath; + this.filePathTextBox!.value = projectFilePath; } } diff --git a/extensions/data-workspace/src/test/dialogs/openExistingDialog.test.ts b/extensions/data-workspace/src/test/dialogs/openExistingDialog.test.ts index 505aab60bf..0b16c61297 100644 --- a/extensions/data-workspace/src/test/dialogs/openExistingDialog.test.ts +++ b/extensions/data-workspace/src/test/dialogs/openExistingDialog.test.ts @@ -7,6 +7,7 @@ import * as should from 'should'; import * as TypeMoq from 'typemoq'; import * as sinon from 'sinon'; import * as vscode from 'vscode'; +import * as os from 'os'; import * as constants from '../../common/constants'; import * as utils from '../../common/utils'; import { WorkspaceService } from '../../services/workspaceService'; @@ -25,8 +26,8 @@ suite('Open Existing Dialog', function (): void { const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object); await dialog.open(); - dialog._targetTypeRadioCardGroup?.updateProperty( 'selectedCardId', constants.Project); - dialog._filePathTextBox!.value = 'nonExistentProjectFile'; + dialog.targetTypeRadioCardGroup?.updateProperty( 'selectedCardId', constants.Project); + dialog.filePathTextBox!.value = 'nonExistentProjectFile'; dialog.workspaceInputBox!.value = 'test.code-workspace'; const validateResult = await dialog.validate(); @@ -36,7 +37,7 @@ suite('Open Existing Dialog', function (): void { should.equal(validateResult, false, 'Validation should fail because project file does not exist, but passed'); // create a project file - dialog._filePathTextBox!.value = await createProjectFile('testproj'); + dialog.filePathTextBox!.value = await createProjectFile('testproj'); should.equal(await dialog.validate(), true, `Validation should pass because project file exists, but failed with: ${dialog.dialogObject.message.text}`); }); @@ -45,8 +46,8 @@ suite('Open Existing Dialog', function (): void { const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object); await dialog.open(); - dialog._targetTypeRadioCardGroup?.updateProperty( 'selectedCardId', constants.Workspace); - dialog._filePathTextBox!.value = 'nonExistentWorkspaceFile'; + dialog.targetTypeRadioCardGroup?.updateProperty( 'selectedCardId', constants.Workspace); + dialog.filePathTextBox!.value = 'nonExistentWorkspaceFile'; const fileExistStub = sinon.stub(utils, 'fileExist').resolves(false); const validateResult = await dialog.validate(); @@ -55,11 +56,32 @@ suite('Open Existing Dialog', function (): void { should.equal(validateResult, false, 'Validation should fail because workspace file does not exist, but passed'); // validation should pass if workspace file exists - dialog._filePathTextBox!.value = generateUniqueWorkspaceFilePath(); + dialog.filePathTextBox!.value = generateUniqueWorkspaceFilePath(); fileExistStub.resolves(true); should.equal(await dialog.validate(), true, `Validation should pass because workspace file exists, but failed with: ${dialog.dialogObject.message.text}`); }); + test('Should validate workspace git clone location', async function (): Promise { + const workspaceServiceMock = TypeMoq.Mock.ofType(); + const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object); + await dialog.open(); + + dialog.targetTypeRadioCardGroup?.updateProperty( 'selectedCardId', constants.Workspace); + dialog.localRadioButton!.checked = false; + dialog.remoteGitRepoRadioButton!.checked = true; + dialog.localClonePathTextBox!.value = 'invalidLocation'; + const folderExistStub = sinon.stub(utils, 'directoryExist').resolves(false); + + const validateResult = await dialog.validate(); + const msg = constants.CloneParentDirectoryNotExistError(dialog.localClonePathTextBox!.value); + should.equal(dialog.dialogObject.message.text, msg); + should.equal(validateResult, false, 'Validation should fail because clone directory does not exist, but passed'); + + // validation should pass if directory exists + dialog.localClonePathTextBox!.value = os.tmpdir(); + folderExistStub.resolves(true); + should.equal(await dialog.validate(), true, `Validation should pass because clone directory exists, but failed with: ${dialog.dialogObject.message.text}`); + }); test('Should validate workspace in onComplete when opening project', async function (): Promise { const workspaceServiceMock = TypeMoq.Mock.ofType(); @@ -69,7 +91,7 @@ suite('Open Existing Dialog', function (): void { const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object); await dialog.open(); - dialog._filePathTextBox!.value = generateUniqueProjectFilePath('testproj'); + dialog.filePathTextBox!.value = generateUniqueProjectFilePath('testproj'); should.doesNotThrow(async () => await dialog.onComplete()); workspaceServiceMock.setup(x => x.validateWorkspace()).throws(new Error('test error')); @@ -84,16 +106,16 @@ suite('Open Existing Dialog', function (): void { const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object); await dialog.open(); - should.equal(dialog._filePathTextBox!.value, ''); + should.equal(dialog.filePathTextBox!.value, ''); await dialog.workspaceBrowse(); - should.equal(dialog._filePathTextBox!.value, '', 'Workspace file should not be set when no file is selected'); + should.equal(dialog.filePathTextBox!.value, '', 'Workspace file should not be set when no file is selected'); sinon.restore(); const workspaceFile = vscode.Uri.file(generateUniqueWorkspaceFilePath()); sinon.stub(vscode.window, 'showOpenDialog').returns(Promise.resolve([workspaceFile])); await dialog.workspaceBrowse(); - should.equal(dialog._filePathTextBox!.value, workspaceFile.fsPath, 'Workspace file should get set'); - should.equal(dialog._filePathTextBox?.value, workspaceFile.fsPath); + should.equal(dialog.filePathTextBox!.value, workspaceFile.fsPath, 'Workspace file should get set'); + should.equal(dialog.filePathTextBox?.value, workspaceFile.fsPath); }); test('project browse', async function (): Promise { @@ -103,16 +125,16 @@ suite('Open Existing Dialog', function (): void { const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object); await dialog.open(); - should.equal(dialog._filePathTextBox!.value, ''); + should.equal(dialog.filePathTextBox!.value, ''); await dialog.projectBrowse(); - should.equal(dialog._filePathTextBox!.value, '', 'Project file should not be set when no file is selected'); + should.equal(dialog.filePathTextBox!.value, '', 'Project file should not be set when no file is selected'); sinon.restore(); const projectFile = vscode.Uri.file(generateUniqueProjectFilePath('testproj')); sinon.stub(vscode.window, 'showOpenDialog').returns(Promise.resolve([projectFile])); await dialog.projectBrowse(); - should.equal(dialog._filePathTextBox!.value, projectFile.fsPath, 'Project file should be set'); - should.equal(dialog._filePathTextBox?.value, projectFile.fsPath); + should.equal(dialog.filePathTextBox!.value, projectFile.fsPath, 'Project file should be set'); + should.equal(dialog.filePathTextBox?.value, projectFile.fsPath); }); });