mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-02 17:23:40 -05:00
add existing project to workspace feature (#12249)
* add existing project to workspace feature * update file name * new test and use URI * handle workspace with no folder * add more validation * and more tests * use forward slash
This commit is contained in:
@@ -4,7 +4,8 @@
|
||||
"relativeCoverageDir": "../../coverage",
|
||||
"ignorePatterns": [
|
||||
"**/node_modules/**",
|
||||
"**/test/**"
|
||||
"**/test/**",
|
||||
"main.js"
|
||||
],
|
||||
"reports": [
|
||||
"cobertura",
|
||||
|
||||
@@ -97,11 +97,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/sinon": "^9.0.4",
|
||||
"mocha": "^5.2.0",
|
||||
"mocha-junit-reporter": "^1.17.0",
|
||||
"mocha-multi-reporters": "^1.1.7",
|
||||
"typemoq": "^2.1.0",
|
||||
"vscodetestcover": "^1.1.0",
|
||||
"should": "^13.2.3"
|
||||
"should": "^13.2.3",
|
||||
"sinon": "^9.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
14
extensions/data-workspace/src/common/constants.ts
Normal file
14
extensions/data-workspace/src/common/constants.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EOL } from 'os';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export const ExtensionActivationErrorMessage = (extensionId: string, err: any): string => { return localize('activateExtensionFailed', "Failed to load the project provider extension '{0}'. Error message: {1}", extensionId, err.message ?? err); };
|
||||
export const UnknownProjectsErrorMessage = (projectFiles: string[]): string => { return localize('UnknownProjectsError', "No provider was found for the following projects: {0}", projectFiles.join(EOL)); };
|
||||
|
||||
export const SelectProjectFileActionName = localize('SelectProjectFileActionName', "Select");
|
||||
|
||||
@@ -45,13 +45,19 @@ export interface IWorkspaceService {
|
||||
/**
|
||||
* Gets the project files in current workspace
|
||||
*/
|
||||
getProjectsInWorkspace(): Promise<string[]>;
|
||||
getProjectsInWorkspace(): Promise<vscode.Uri[]>;
|
||||
|
||||
/**
|
||||
* Gets the project provider by project file
|
||||
* @param projectFilePath The full path of the project file
|
||||
* @param projectFileUri The Uri of the project file
|
||||
*/
|
||||
getProjectProvider(projectFilePath: string): Promise<IProjectProvider | undefined>;
|
||||
getProjectProvider(projectFileUri: vscode.Uri): Promise<IProjectProvider | undefined>;
|
||||
|
||||
/**
|
||||
* Adds the projects to workspace, if a project is not in the workspace folder, its containing folder will be added to the workspace
|
||||
* @param projectFiles the list of project files to be added, the project file should be absolute path.
|
||||
*/
|
||||
addProjectsToWorkspace(projectFiles: vscode.Uri[]): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { IWorkspaceService, WorkspaceTreeItem as WorkspaceTreeItem } from './interfaces';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { EOL } from 'os';
|
||||
const localize = nls.loadMessageBundle();
|
||||
import { UnknownProjectsErrorMessage } from './constants';
|
||||
|
||||
/**
|
||||
* Tree data provider for the workspace main view
|
||||
@@ -36,11 +34,10 @@ export class WorkspaceTreeDataProvider implements vscode.TreeDataProvider<Worksp
|
||||
const projects = await this._workspaceService.getProjectsInWorkspace();
|
||||
const unknownProjects: string[] = [];
|
||||
const treeItems: WorkspaceTreeItem[] = [];
|
||||
let project: string;
|
||||
for (project of projects) {
|
||||
for (const project of projects) {
|
||||
const projectProvider = await this._workspaceService.getProjectProvider(project);
|
||||
if (projectProvider === undefined) {
|
||||
unknownProjects.push(project);
|
||||
unknownProjects.push(project.path);
|
||||
continue;
|
||||
}
|
||||
const treeDataProvider = await projectProvider.getProjectTreeDataProvider(project);
|
||||
@@ -58,7 +55,7 @@ export class WorkspaceTreeDataProvider implements vscode.TreeDataProvider<Worksp
|
||||
});
|
||||
}
|
||||
if (unknownProjects.length > 0) {
|
||||
vscode.window.showErrorMessage(localize('UnknownProjectsError', "No provider was found for the following projects: {0}", unknownProjects.join(EOL)));
|
||||
vscode.window.showErrorMessage(UnknownProjectsErrorMessage(unknownProjects));
|
||||
}
|
||||
return treeItems;
|
||||
}
|
||||
|
||||
@@ -27,9 +27,9 @@ declare module 'dataworkspace' {
|
||||
export interface IProjectProvider {
|
||||
/**
|
||||
* Gets the tree data provider for the given project file
|
||||
* @param projectFilePath The full path of the project file
|
||||
* @param projectFile The Uri of the project file
|
||||
*/
|
||||
getProjectTreeDataProvider(projectFilePath: string): Promise<vscode.TreeDataProvider<any>>;
|
||||
getProjectTreeDataProvider(projectFile: vscode.Uri): Promise<vscode.TreeDataProvider<any>>;
|
||||
|
||||
/**
|
||||
* Gets the supported project types
|
||||
|
||||
@@ -4,16 +4,40 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as dataworkspace from 'dataworkspace';
|
||||
import { WorkspaceTreeDataProvider } from './common/workspaceTreeDataProvider';
|
||||
import { WorkspaceService } from './services/workspaceService';
|
||||
import { DataWorkspaceExtension } from './dataWorkspaceExtension';
|
||||
import { SelectProjectFileActionName } from './common/constants';
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext): Promise<dataworkspace.IExtension> {
|
||||
const workspaceService = new WorkspaceService();
|
||||
const workspaceTreeDataProvider = new WorkspaceTreeDataProvider(workspaceService);
|
||||
context.subscriptions.push(vscode.window.registerTreeDataProvider('dataworkspace.views.main', workspaceTreeDataProvider));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('projects.addProject', () => {
|
||||
context.subscriptions.push(vscode.commands.registerCommand('projects.addProject', async () => {
|
||||
// To Sakshi - You can replace the implementation with your complete dialog implementation
|
||||
// but all the code here should be reusable by you
|
||||
if (vscode.workspace.workspaceFile) {
|
||||
const filter: { [name: string]: string[] } = {};
|
||||
const projectTypes = await workspaceService.getAllProjectTypes();
|
||||
projectTypes.forEach(type => {
|
||||
filter[type.displayName] = projectTypes.map(projectType => projectType.projectFileExtension);
|
||||
});
|
||||
let fileUris = await vscode.window.showOpenDialog({
|
||||
canSelectFiles: true,
|
||||
canSelectFolders: false,
|
||||
canSelectMany: false,
|
||||
defaultUri: vscode.Uri.file(path.dirname(vscode.workspace.workspaceFile.path)),
|
||||
openLabel: SelectProjectFileActionName,
|
||||
filters: filter
|
||||
});
|
||||
if (!fileUris || fileUris.length === 0) {
|
||||
return;
|
||||
}
|
||||
await workspaceService.addProjectsToWorkspace(fileUris);
|
||||
workspaceTreeDataProvider.refresh();
|
||||
}
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('dataworkspace.refresh', () => {
|
||||
|
||||
@@ -8,14 +8,44 @@ import * as dataworkspace from 'dataworkspace';
|
||||
import * as path from 'path';
|
||||
import { IWorkspaceService } from '../common/interfaces';
|
||||
import { ProjectProviderRegistry } from '../common/projectProviderRegistry';
|
||||
import * as nls from 'vscode-nls';
|
||||
import Logger from '../common/logger';
|
||||
import { ExtensionActivationErrorMessage } from '../common/constants';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
const WorkspaceConfigurationName = 'dataworkspace';
|
||||
const ProjectsConfigurationName = 'projects';
|
||||
|
||||
export class WorkspaceService implements IWorkspaceService {
|
||||
async addProjectsToWorkspace(projectFiles: vscode.Uri[]): Promise<void> {
|
||||
if (vscode.workspace.workspaceFile) {
|
||||
const currentProjects: vscode.Uri[] = await this.getProjectsInWorkspace();
|
||||
const newWorkspaceFolders: string[] = [];
|
||||
let newProjectFileAdded = false;
|
||||
for (const projectFile of projectFiles) {
|
||||
if (currentProjects.findIndex((p: vscode.Uri) => p.fsPath === projectFile.fsPath) === -1) {
|
||||
currentProjects.push(projectFile);
|
||||
newProjectFileAdded = true;
|
||||
|
||||
// if the relativePath and the original path is the same, that means the project file is not under
|
||||
// any workspace folders, we should add the parent folder of the project file to the workspace
|
||||
const relativePath = vscode.workspace.asRelativePath(projectFile, false);
|
||||
if (vscode.Uri.file(relativePath).fsPath === projectFile.fsPath) {
|
||||
newWorkspaceFolders.push(path.dirname(projectFile.path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newProjectFileAdded) {
|
||||
// Save the new set of projects to the workspace configuration.
|
||||
await this.setWorkspaceConfigurationValue(ProjectsConfigurationName, currentProjects.map(project => this.toRelativePath(project)));
|
||||
}
|
||||
|
||||
if (newWorkspaceFolders.length > 0) {
|
||||
// second parameter is null means don't remove any workspace folders
|
||||
vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders!.length, null, ...(newWorkspaceFolders.map(folder => ({ uri: vscode.Uri.file(folder) }))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getAllProjectTypes(): Promise<dataworkspace.IProjectType[]> {
|
||||
await this.ensureProviderExtensionLoaded();
|
||||
const projectTypes: dataworkspace.IProjectType[] = [];
|
||||
@@ -25,16 +55,12 @@ export class WorkspaceService implements IWorkspaceService {
|
||||
return projectTypes;
|
||||
}
|
||||
|
||||
async getProjectsInWorkspace(): Promise<string[]> {
|
||||
if (vscode.workspace.workspaceFile) {
|
||||
const projects = <string[]>vscode.workspace.getConfiguration(WorkspaceConfigurationName).get(ProjectsConfigurationName);
|
||||
return projects.map(project => path.isAbsolute(project) ? project : path.join(vscode.workspace.rootPath!, project));
|
||||
}
|
||||
return [];
|
||||
async getProjectsInWorkspace(): Promise<vscode.Uri[]> {
|
||||
return vscode.workspace.workspaceFile ? this.getWorkspaceConfigurationValue<string[]>(ProjectsConfigurationName).map(project => this.toUri(project)) : [];
|
||||
}
|
||||
|
||||
async getProjectProvider(projectFilePath: string): Promise<dataworkspace.IProjectProvider | undefined> {
|
||||
const projectType = path.extname(projectFilePath).replace(/\./g, '');
|
||||
async getProjectProvider(projectFile: vscode.Uri): Promise<dataworkspace.IProjectProvider | undefined> {
|
||||
const projectType = path.extname(projectFile.path).replace(/\./g, '');
|
||||
let provider = ProjectProviderRegistry.getProviderByProjectType(projectType);
|
||||
if (!provider) {
|
||||
await this.ensureProviderExtensionLoaded(projectType);
|
||||
@@ -70,7 +96,32 @@ export class WorkspaceService implements IWorkspaceService {
|
||||
try {
|
||||
await extension.activate();
|
||||
} catch (err) {
|
||||
Logger.error(localize('activateExtensionFailed', "Failed to load the project provider extension '{0}'. Error message: {1}", extension.id, err.message ?? err));
|
||||
Logger.error(ExtensionActivationErrorMessage(extension.id, err));
|
||||
}
|
||||
}
|
||||
|
||||
getWorkspaceConfigurationValue<T>(configurationName: string): T {
|
||||
return vscode.workspace.getConfiguration(WorkspaceConfigurationName).get(configurationName) as T;
|
||||
}
|
||||
|
||||
async setWorkspaceConfigurationValue(configurationName: string, value: any): Promise<void> {
|
||||
await vscode.workspace.getConfiguration(WorkspaceConfigurationName).update(configurationName, value, vscode.ConfigurationTarget.Workspace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the relative path to the workspace file
|
||||
* @param filePath the absolute path
|
||||
*/
|
||||
private toRelativePath(filePath: vscode.Uri): string {
|
||||
return path.relative(path.dirname(vscode.workspace.workspaceFile!.path!), filePath.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Uri of the given relative path
|
||||
* @param relativePath the relative path
|
||||
*/
|
||||
private toUri(relativePath: string): vscode.Uri {
|
||||
const fullPath = path.join(path.dirname(vscode.workspace.workspaceFile!.path!), relativePath);
|
||||
return vscode.Uri.file(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export function createProjectProvider(projectTypes: IProjectType[]): IProjectPro
|
||||
const treeDataProvider = new MockTreeDataProvider();
|
||||
const projectProvider: IProjectProvider = {
|
||||
supportedProjectTypes: projectTypes,
|
||||
getProjectTreeDataProvider: (projectFile: string): Promise<vscode.TreeDataProvider<any>> => {
|
||||
getProjectTreeDataProvider: (projectFile: vscode.Uri): Promise<vscode.TreeDataProvider<any>> => {
|
||||
return Promise.resolve(treeDataProvider);
|
||||
}
|
||||
};
|
||||
|
||||
211
extensions/data-workspace/src/test/workspaceService.test.ts
Normal file
211
extensions/data-workspace/src/test/workspaceService.test.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
import * as sinon from 'sinon';
|
||||
import * as should from 'should';
|
||||
import * as path from 'path';
|
||||
import { WorkspaceService } from '../services/workspaceService';
|
||||
import { ProjectProviderRegistry } from '../common/projectProviderRegistry';
|
||||
import { createProjectProvider } from './projectProviderRegistry.test';
|
||||
|
||||
const DefaultWorkspaceFilePath = '/test/folder/ws.code-workspace';
|
||||
|
||||
/**
|
||||
* Create a stub for vscode.workspace.workspaceFile
|
||||
* @param workspaceFilePath The workspace file to return
|
||||
*/
|
||||
function stubWorkspaceFile(workspaceFilePath: string | undefined): sinon.SinonStub {
|
||||
return sinon.stub(vscode.workspace, 'workspaceFile').value(workspaceFilePath ? vscode.Uri.file(workspaceFilePath) : undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stub for vscode.workspace.getConfiguration
|
||||
* @param returnValue the configuration value to return
|
||||
*/
|
||||
function stubGetConfigurationValue(getStub?: sinon.SinonStub, updateStub?: sinon.SinonStub): sinon.SinonStub {
|
||||
return sinon.stub(vscode.workspace, 'getConfiguration').returns({
|
||||
get: (configurationName: string) => {
|
||||
return getStub!(configurationName);
|
||||
},
|
||||
update: (section: string, value: any, configurationTarget?: vscode.ConfigurationTarget | boolean, overrideInLanguage?: boolean) => {
|
||||
updateStub!(section, value, configurationTarget);
|
||||
}
|
||||
} as vscode.WorkspaceConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stub for vscode.extensions.all
|
||||
* @param extensions extensions to return
|
||||
*/
|
||||
function stubAllExtensions(extensions: vscode.Extension<any>[]): sinon.SinonStub {
|
||||
return sinon.stub(vscode.extensions, 'all').value(extensions);
|
||||
}
|
||||
|
||||
function createMockExtension(id: string, isActive: boolean, projectTypes: string[] | undefined): { extension: vscode.Extension<any>, activationStub: sinon.SinonStub } {
|
||||
const activationStub = sinon.stub();
|
||||
const extension: vscode.Extension<any> = {
|
||||
id: id,
|
||||
isActive: isActive,
|
||||
packageJSON: {},
|
||||
activate: () => { return activationStub(); }
|
||||
} as vscode.Extension<any>;
|
||||
extension.packageJSON.contributes = projectTypes === undefined ? undefined : { projects: projectTypes };
|
||||
return {
|
||||
extension: extension,
|
||||
activationStub: activationStub
|
||||
};
|
||||
}
|
||||
|
||||
suite('WorkspaceService Tests', function (): void {
|
||||
const service = new WorkspaceService();
|
||||
|
||||
this.afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
test('test getProjectsInWorkspace', async () => {
|
||||
// No workspace is loaded
|
||||
stubWorkspaceFile(undefined);
|
||||
let projects = await service.getProjectsInWorkspace();
|
||||
should.strictEqual(projects.length, 0, 'no projects should be returned when no workspace is loaded');
|
||||
|
||||
// from this point on, workspace is loaded
|
||||
stubWorkspaceFile(DefaultWorkspaceFilePath);
|
||||
|
||||
// No projects are present in the workspace file
|
||||
const getConfigurationStub = stubGetConfigurationValue(sinon.stub().returns([]));
|
||||
projects = await service.getProjectsInWorkspace();
|
||||
should.strictEqual(projects.length, 0, 'no projects should be returned when projects are present in the workspace file');
|
||||
getConfigurationStub.restore();
|
||||
|
||||
// Projects are present
|
||||
stubGetConfigurationValue(sinon.stub().returns(['abc.sqlproj', 'folder1/abc1.sqlproj', 'folder2/abc2.sqlproj']));
|
||||
projects = await service.getProjectsInWorkspace();
|
||||
should.strictEqual(projects.length, 3, 'there should be 2 projects');
|
||||
const project1 = vscode.Uri.file('/test/folder/abc.sqlproj');
|
||||
const project2 = vscode.Uri.file('/test/folder/folder1/abc1.sqlproj');
|
||||
const project3 = vscode.Uri.file('/test/folder/folder2/abc2.sqlproj');
|
||||
should.strictEqual(projects[0].path, project1.path);
|
||||
should.strictEqual(projects[1].path, project2.path);
|
||||
should.strictEqual(projects[2].path, project3.path);
|
||||
});
|
||||
|
||||
test('test getAllProjectTypes', async () => {
|
||||
// extensions that are already activated
|
||||
const extension1 = createMockExtension('ext1', true, ['csproj']); // with projects contribution
|
||||
const extension2 = createMockExtension('ext2', true, []); // with empty projects contribution
|
||||
const extension3 = createMockExtension('ext3', true, undefined); // with no contributes in packageJSON
|
||||
|
||||
// extensions that are still not activated
|
||||
const extension4 = createMockExtension('ext4', false, ['sqlproj']); // with projects contribution
|
||||
const extension5 = createMockExtension('ext5', false, ['dbproj']); // with projects contribution but activate() will throw error
|
||||
extension5.activationStub.throws(); // extension activation failure shouldn't cause the getAllProjectTypes() call to fail
|
||||
const extension6 = createMockExtension('ext6', false, undefined); // with no contributes in packageJSON
|
||||
const extension7 = createMockExtension('ext7', false, []); // with empty projects contribution
|
||||
|
||||
stubAllExtensions([extension1, extension2, extension3, extension4, extension5, extension6, extension7].map(ext => ext.extension));
|
||||
|
||||
const provider1 = createProjectProvider([
|
||||
{
|
||||
projectFileExtension: 'testproj',
|
||||
icon: '',
|
||||
displayName: 'test project'
|
||||
}, {
|
||||
projectFileExtension: 'testproj1',
|
||||
icon: '',
|
||||
displayName: 'test project 1'
|
||||
}
|
||||
]);
|
||||
const provider2 = createProjectProvider([
|
||||
{
|
||||
projectFileExtension: 'sqlproj',
|
||||
icon: '',
|
||||
displayName: 'sql project'
|
||||
}
|
||||
]);
|
||||
sinon.stub(ProjectProviderRegistry, 'providers').value([provider1, provider2]);
|
||||
const consoleErrorStub = sinon.stub(console, 'error');
|
||||
const projectTypes = await service.getAllProjectTypes();
|
||||
should.strictEqual(projectTypes.length, 3);
|
||||
should.strictEqual(projectTypes[0].projectFileExtension, 'testproj');
|
||||
should.strictEqual(projectTypes[1].projectFileExtension, 'testproj1');
|
||||
should.strictEqual(projectTypes[2].projectFileExtension, 'sqlproj');
|
||||
should.strictEqual(extension1.activationStub.notCalled, true, 'extension1.activate() should not have been called');
|
||||
should.strictEqual(extension2.activationStub.notCalled, true, 'extension2.activate() should not have been called');
|
||||
should.strictEqual(extension3.activationStub.notCalled, true, 'extension3.activate() should not have been called');
|
||||
should.strictEqual(extension4.activationStub.calledOnce, true, 'extension4.activate() should have been called');
|
||||
should.strictEqual(extension5.activationStub.called, true, 'extension5.activate() should have been called');
|
||||
should.strictEqual(extension6.activationStub.notCalled, true, 'extension6.activate() should not have been called');
|
||||
should.strictEqual(extension7.activationStub.notCalled, true, 'extension7.activate() should not have been called');
|
||||
should.strictEqual(consoleErrorStub.calledOnce, true, 'Logger.error should be called once');
|
||||
});
|
||||
|
||||
test('test getProjectProvider', async () => {
|
||||
const extension1 = createMockExtension('ext1', true, ['csproj']);
|
||||
const extension2 = createMockExtension('ext2', false, ['sqlproj']);
|
||||
const extension3 = createMockExtension('ext3', false, ['dbproj']);
|
||||
stubAllExtensions([extension1, extension2, extension3].map(ext => ext.extension));
|
||||
const getProviderByProjectTypeStub = sinon.stub(ProjectProviderRegistry, 'getProviderByProjectType');
|
||||
getProviderByProjectTypeStub.onFirstCall().returns(undefined);
|
||||
getProviderByProjectTypeStub.onSecondCall().returns(createProjectProvider([
|
||||
{
|
||||
projectFileExtension: 'sqlproj',
|
||||
icon: '',
|
||||
displayName: 'test project'
|
||||
}
|
||||
]));
|
||||
let provider = await service.getProjectProvider(vscode.Uri.file('abc.sqlproj'));
|
||||
should.notStrictEqual(provider, undefined, 'Provider should be returned for sqlproj');
|
||||
should.strictEqual(provider!.supportedProjectTypes[0].projectFileExtension, 'sqlproj');
|
||||
should.strictEqual(extension1.activationStub.notCalled, true, 'the ext1.activate() should not have been called for sqlproj');
|
||||
should.strictEqual(extension2.activationStub.calledOnce, true, 'the ext2.activate() should have been called once after requesting sqlproj provider');
|
||||
should.strictEqual(extension3.activationStub.notCalled, true, 'the ext3.activate() should not have been called for sqlproj');
|
||||
|
||||
getProviderByProjectTypeStub.reset();
|
||||
getProviderByProjectTypeStub.returns(createProjectProvider([{
|
||||
projectFileExtension: 'csproj',
|
||||
icon: '',
|
||||
displayName: 'test cs project'
|
||||
}]));
|
||||
provider = await service.getProjectProvider(vscode.Uri.file('abc.csproj'));
|
||||
should.notStrictEqual(provider, undefined, 'Provider should be returned for csproj');
|
||||
should.strictEqual(provider!.supportedProjectTypes[0].projectFileExtension, 'csproj');
|
||||
should.strictEqual(extension1.activationStub.notCalled, true, 'the ext1.activate() should not have been called for csproj');
|
||||
should.strictEqual(extension2.activationStub.calledOnce, true, 'the ext2.activate() should still have been called once');
|
||||
should.strictEqual(extension3.activationStub.notCalled, true, 'the ext3.activate() should not have been called for csproj');
|
||||
});
|
||||
|
||||
test('test addProjectsToWorkspace', async () => {
|
||||
const processPath = (original: string): string => {
|
||||
return original.replace(/\//g, path.sep);
|
||||
};
|
||||
stubWorkspaceFile(DefaultWorkspaceFilePath);
|
||||
const updateConfigurationStub = sinon.stub();
|
||||
const getConfigurationStub = sinon.stub().returns([processPath('folder1/proj2.sqlproj')]);
|
||||
stubGetConfigurationValue(getConfigurationStub, updateConfigurationStub);
|
||||
const asRelativeStub = sinon.stub(vscode.workspace, 'asRelativePath');
|
||||
sinon.stub(vscode.workspace, 'workspaceFolders').value(['.']);
|
||||
asRelativeStub.onFirstCall().returns(`proj1.sqlproj`);
|
||||
asRelativeStub.onSecondCall().returns(processPath('/test/other/proj3.sqlproj'));
|
||||
const updateWorkspaceFoldersStub = sinon.stub(vscode.workspace, 'updateWorkspaceFolders');
|
||||
await service.addProjectsToWorkspace([
|
||||
vscode.Uri.file('/test/folder/proj1.sqlproj'), // within the workspace folder
|
||||
vscode.Uri.file('/test/folder/folder1/proj2.sqlproj'), //already exists
|
||||
vscode.Uri.file('/test/other/proj3.sqlproj') // outside of workspace folder
|
||||
]);
|
||||
should.strictEqual(updateConfigurationStub.calledOnce, true, 'update configuration should have been called once');
|
||||
should.strictEqual(updateWorkspaceFoldersStub.calledOnce, true, 'updateWorkspaceFolders should have been called once');
|
||||
should.strictEqual(updateConfigurationStub.calledWith('projects', sinon.match.array.deepEquals([
|
||||
processPath('folder1/proj2.sqlproj'),
|
||||
processPath('proj1.sqlproj'),
|
||||
processPath('../other/proj3.sqlproj')
|
||||
]), vscode.ConfigurationTarget.Workspace), true, 'updateConfiguration parameters does not match expectation');
|
||||
should.strictEqual(updateWorkspaceFoldersStub.calledWith(1, null, sinon.match((arg) => {
|
||||
return arg.uri.path === '/test/other';
|
||||
})), true, 'updateWorkspaceFolder parameters does not match expectation');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sinon from 'sinon';
|
||||
import * as vscode from 'vscode';
|
||||
import * as should from 'should';
|
||||
import { WorkspaceTreeDataProvider } from '../common/workspaceTreeDataProvider';
|
||||
import { WorkspaceService } from '../services/workspaceService';
|
||||
import { WorkspaceTreeItem } from '../common/interfaces';
|
||||
import { IProjectProvider } from 'dataworkspace';
|
||||
import { MockTreeDataProvider } from './projectProviderRegistry.test';
|
||||
|
||||
suite('workspaceTreeDataProvider Tests', function (): void {
|
||||
const workspaceService = new WorkspaceService();
|
||||
const treeProvider = new WorkspaceTreeDataProvider(workspaceService);
|
||||
|
||||
this.afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
test('test refresh()', () => {
|
||||
const treeDataChangeHandler = sinon.stub();
|
||||
treeProvider.onDidChangeTreeData!((e) => {
|
||||
treeDataChangeHandler(e);
|
||||
});
|
||||
treeProvider.refresh();
|
||||
should.strictEqual(treeDataChangeHandler.calledOnce, true);
|
||||
});
|
||||
|
||||
test('test getTreeItem()', () => {
|
||||
const getTreeItemStub = sinon.stub();
|
||||
treeProvider.getTreeItem(({
|
||||
treeDataProvider: ({
|
||||
getTreeItem: (arg: WorkspaceTreeItem) => {
|
||||
return getTreeItemStub(arg);
|
||||
}
|
||||
}) as vscode.TreeDataProvider<any>
|
||||
}) as WorkspaceTreeItem);
|
||||
should.strictEqual(getTreeItemStub.calledOnce, true);
|
||||
});
|
||||
|
||||
test('test getChildren() for non-root element', async () => {
|
||||
const getChildrenStub = sinon.stub().resolves([]);
|
||||
const element = {
|
||||
treeDataProvider: ({
|
||||
getChildren: (arg: any) => {
|
||||
return getChildrenStub(arg);
|
||||
}
|
||||
}) as vscode.TreeDataProvider<any>,
|
||||
element: 'obj1'
|
||||
};
|
||||
const children = await treeProvider.getChildren(element);
|
||||
should.strictEqual(children.length, 0, 'children count should be 0');
|
||||
should.strictEqual(getChildrenStub.calledWithExactly('obj1'), true, 'getChildren parameter should be obj1')
|
||||
});
|
||||
|
||||
test('test getChildren() for root element', async () => {
|
||||
const getProjectsInWorkspaceStub = sinon.stub(workspaceService, 'getProjectsInWorkspace').resolves(
|
||||
[
|
||||
vscode.Uri.file('test/proj1/proj1.sqlproj'),
|
||||
vscode.Uri.file('test/proj2/proj2.csproj')
|
||||
]
|
||||
);
|
||||
const treeDataProvider = new MockTreeDataProvider();
|
||||
const projectProvider: IProjectProvider = {
|
||||
supportedProjectTypes: [{
|
||||
projectFileExtension: 'sqlproj',
|
||||
icon: '',
|
||||
displayName: 'sql project'
|
||||
}],
|
||||
getProjectTreeDataProvider: (projectFile: vscode.Uri): Promise<vscode.TreeDataProvider<any>> => {
|
||||
return Promise.resolve(treeDataProvider);
|
||||
}
|
||||
};
|
||||
const getProjectProviderStub = sinon.stub(workspaceService, 'getProjectProvider');
|
||||
getProjectProviderStub.onFirstCall().resolves(undefined);
|
||||
getProjectProviderStub.onSecondCall().resolves(projectProvider);
|
||||
sinon.stub(treeDataProvider, 'getChildren').resolves(['treeitem1']);
|
||||
const showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage');
|
||||
const children = await treeProvider.getChildren(undefined);
|
||||
should.strictEqual(children.length, 1, 'there should be 1 tree item returned');
|
||||
should.strictEqual(children[0].element, 'treeitem1');
|
||||
should.strictEqual(getProjectsInWorkspaceStub.calledOnce, true, 'getProjectsInWorkspaceStub should be called');
|
||||
should.strictEqual(getProjectProviderStub.calledTwice, true, 'getProjectProvider should be called twice');
|
||||
should.strictEqual(showErrorMessageStub.calledOnce, true, 'showErrorMessage should be called once');
|
||||
});
|
||||
});
|
||||
@@ -182,11 +182,59 @@
|
||||
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
|
||||
integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==
|
||||
|
||||
"@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.7.2":
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217"
|
||||
integrity sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==
|
||||
dependencies:
|
||||
type-detect "4.0.8"
|
||||
|
||||
"@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40"
|
||||
integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
"@sinonjs/formatio@^5.0.1":
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-5.0.1.tgz#f13e713cb3313b1ab965901b01b0828ea6b77089"
|
||||
integrity sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1"
|
||||
"@sinonjs/samsam" "^5.0.2"
|
||||
|
||||
"@sinonjs/samsam@^5.0.2", "@sinonjs/samsam@^5.1.0":
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.1.0.tgz#3afe719232b541bb6cf3411a4c399a188de21ec0"
|
||||
integrity sha512-42nyaQOVunX5Pm6GRJobmzbS7iLI+fhERITnETXzzwDZh+TtDr/Au3yAvXVjFmZ4wEUaE4Y3NFZfKv0bV0cbtg==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.6.0"
|
||||
lodash.get "^4.4.2"
|
||||
type-detect "^4.0.8"
|
||||
|
||||
"@sinonjs/text-encoding@^0.7.1":
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
|
||||
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
|
||||
|
||||
"@types/mocha@^5.2.5":
|
||||
version "5.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea"
|
||||
integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==
|
||||
|
||||
"@types/sinon@^9.0.4":
|
||||
version "9.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.5.tgz#56b2a12662dd8c7d081cdc511af5f872cb37377f"
|
||||
integrity sha512-4CnkGdM/5/FXDGqL32JQ1ttVrGvhOoesLLF7VnTh4KdjK5N5VQOtxaylFqqTjnHx55MnD9O02Nbk5c1ELC8wlQ==
|
||||
dependencies:
|
||||
"@types/sinonjs__fake-timers" "*"
|
||||
|
||||
"@types/sinonjs__fake-timers@*":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz#681df970358c82836b42f989188d133e218c458e"
|
||||
integrity sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==
|
||||
|
||||
ansi-regex@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
|
||||
@@ -329,6 +377,11 @@ diff@3.5.0:
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
|
||||
|
||||
diff@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||
|
||||
escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
@@ -428,6 +481,11 @@ is-buffer@~1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
|
||||
|
||||
istanbul-lib-coverage@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49"
|
||||
@@ -503,6 +561,16 @@ json5@^2.1.2:
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
just-extend@^4.0.2:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.0.tgz#7278a4027d889601640ee0ce0e5a00b992467da4"
|
||||
integrity sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==
|
||||
|
||||
lodash.get@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
|
||||
|
||||
lodash@^4.16.4, lodash@^4.17.13, lodash@^4.17.4:
|
||||
version "4.17.19"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
||||
@@ -602,6 +670,17 @@ ms@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
||||
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
||||
|
||||
nise@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/nise/-/nise-4.0.4.tgz#d73dea3e5731e6561992b8f570be9e363c4512dd"
|
||||
integrity sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
"@sinonjs/fake-timers" "^6.0.0"
|
||||
"@sinonjs/text-encoding" "^0.7.1"
|
||||
just-extend "^4.0.2"
|
||||
path-to-regexp "^1.7.0"
|
||||
|
||||
once@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
@@ -619,6 +698,13 @@ path-parse@^1.0.6:
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
||||
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
|
||||
|
||||
path-to-regexp@^1.7.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
|
||||
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
|
||||
dependencies:
|
||||
isarray "0.0.1"
|
||||
|
||||
pify@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
|
||||
@@ -702,6 +788,19 @@ should@^13.2.3:
|
||||
should-type-adaptors "^1.0.1"
|
||||
should-util "^1.0.0"
|
||||
|
||||
sinon@^9.0.2:
|
||||
version "9.0.3"
|
||||
resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.3.tgz#bffc3ec28c936332cd2a41833547b9eed201ecff"
|
||||
integrity sha512-IKo9MIM111+smz9JGwLmw5U1075n1YXeAq8YeSFlndCLhAL5KGn6bLgu7b/4AYHTV/LcEMcRm2wU2YiL55/6Pg==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.2"
|
||||
"@sinonjs/fake-timers" "^6.0.1"
|
||||
"@sinonjs/formatio" "^5.0.1"
|
||||
"@sinonjs/samsam" "^5.1.0"
|
||||
diff "^4.0.2"
|
||||
nise "^4.0.4"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
source-map@^0.5.0:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
@@ -750,6 +849,11 @@ to-fast-properties@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
|
||||
integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
|
||||
|
||||
type-detect@4.0.8, type-detect@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
|
||||
|
||||
typemoq@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/typemoq/-/typemoq-2.1.0.tgz#4452ce360d92cf2a1a180f0c29de2803f87af1e8"
|
||||
|
||||
@@ -15,11 +15,11 @@ export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvide
|
||||
|
||||
/**
|
||||
* Gets the project tree data provider
|
||||
* @param projectFilePath The project file path
|
||||
* @param projectFile The project file Uri
|
||||
*/
|
||||
async getProjectTreeDataProvider(projectFilePath: string): Promise<vscode.TreeDataProvider<BaseProjectTreeItem>> {
|
||||
async getProjectTreeDataProvider(projectFilePath: vscode.Uri): Promise<vscode.TreeDataProvider<BaseProjectTreeItem>> {
|
||||
const provider = new SqlDatabaseProjectTreeViewProvider();
|
||||
const project = await Project.openProject(projectFilePath);
|
||||
const project = await Project.openProject(projectFilePath.fsPath);
|
||||
provider.load([project]);
|
||||
return provider;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user