Bootstrap Service Abstract injection (#1534)

* change to generic injection

* formatting

* fixed missed merge

* change to keep a record of the services per selector

* formatting

* adding back in tests

* apply back tests

* remove fundamentally broken test
This commit is contained in:
Anthony Dresser
2018-06-07 16:19:26 -07:00
committed by GitHub
parent 0a839c7321
commit 63fb4e2827
10 changed files with 66 additions and 159 deletions

View File

@@ -98,7 +98,7 @@ export class CreateLoginEditor extends BaseEditor {
connection: input.getConnectionProfile(),
ownerUri: input.getUri()
};
let uniqueSelector = this.instantiationService.invokeFunction(bootstrapAngular,
let uniqueSelector = bootstrapAngular(this.instantiationService,
CreateLoginModule,
this.getContainer(),
CREATELOGIN_SELECTOR,

View File

@@ -122,7 +122,7 @@ export class DashboardEditor extends BaseEditor {
input.hasBootstrapped = true;
let uniqueSelector = this.instantiationService.invokeFunction(bootstrapAngular,
let uniqueSelector = bootstrapAngular(this.instantiationService,
DashboardModule,
this._dashboardContainer,
DASHBOARD_SELECTOR,

View File

@@ -54,7 +54,7 @@ export class BackupDialog extends Modal {
* Get the bootstrap params and perform the bootstrap
*/
private bootstrapAngular(bodyContainer: HTMLElement) {
this._uniqueSelector = this._instantiationService.invokeFunction(bootstrapAngular,
this._uniqueSelector = bootstrapAngular(this._instantiationService,
BackupModule,
bodyContainer,
BACKUP_SELECTOR,

View File

@@ -110,7 +110,7 @@ export class EditDataResultsEditor extends BaseEditor {
// to events from the backing data service
const parent = input.container;
let params: IEditDataComponentParams = { dataService: dataService };
this._instantiationService.invokeFunction(bootstrapAngular,
bootstrapAngular(this._instantiationService,
EditDataModule,
parent,
EDITDATA_SELECTOR,

View File

@@ -170,7 +170,7 @@ export class QueryResultsEditor extends BaseEditor {
// Otherwise many components will be left around and be subscribed
// to events from the backing data service
let params: IQueryComponentParams = { dataService: dataService };
this._instantiationService.invokeFunction(bootstrapAngular,
bootstrapAngular(this._instantiationService,
QueryOutputModule,
this.getContainer(),
QUERY_OUTPUT_SELECTOR,

View File

@@ -111,7 +111,7 @@ export class QueryPlanEditor extends BaseEditor {
planXml: input.planXml
};
let uniqueSelector = this.instantiationService.invokeFunction(bootstrapAngular,
let uniqueSelector = bootstrapAngular(this.instantiationService,
QueryPlanModule,
this.getContainer(),
QUERYPLAN_SELECTOR,

View File

@@ -69,7 +69,7 @@ export class TaskDialogEditor extends BaseEditor {
private revealElementWithTagName(tagName: string, parent: HTMLElement): void {
let elementToReveal: HTMLElement;
for(let i = 0; i < parent.children.length; i++) {
for (let i = 0; i < parent.children.length; i++) {
let child: HTMLElement = <HTMLElement>parent.children[i];
if (child.tagName && child.tagName.toLowerCase() === tagName && !elementToReveal) {
elementToReveal = child;
@@ -92,7 +92,7 @@ export class TaskDialogEditor extends BaseEditor {
let params: ITaskDialogComponentParams = {
ownerUri: input.getUri()
};
let uniqueSelector = this.instantiationService.invokeFunction(bootstrapAngular,
let uniqueSelector = bootstrapAngular(this.instantiationService,
TaskDialogModule,
this.getContainer(),
TASKDIALOG_SELECTOR,

View File

@@ -90,7 +90,7 @@ export class DialogPane extends Disposable implements IThemable {
* Bootstrap angular for the dialog's model view controller with the given model view ID
*/
private initializeModelViewContainer(bodyContainer: HTMLElement, modelViewId: string, tab?: DialogTab) {
this._instantiationService.invokeFunction(bootstrapAngular,
bootstrapAngular(this._instantiationService,
DialogModule,
bodyContainer,
'dialog-modelview-container',

View File

@@ -3,51 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { NgModuleRef, enableProdMode, InjectionToken, ReflectiveInjector, Type, PlatformRef } from '@angular/core';
import { NgModuleRef, enableProdMode, InjectionToken, ReflectiveInjector, Type, PlatformRef, Provider } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { IConnectionManagementService, IConnectionDialogService, IErrorMessageService }
from 'sql/parts/connection/common/connectionManagement';
import { IMetadataService } from 'sql/services/metadata/metadataService';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { IScriptingService } from 'sql/services/scripting/scriptingService';
import { IQueryManagementService } from 'sql/parts/query/common/queryManagement';
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
import { IAdminService } from 'sql/parts/admin/common/adminService';
import { IRestoreDialogController, IRestoreService } from 'sql/parts/disasterRecovery/restore/common/restoreService';
import { IBackupService, IBackupUiService } from 'sql/parts/disasterRecovery/backup/common/backupService';
import { IAngularEventingService } from 'sql/services/angularEventing/angularEventingService';
import { IInsightsDialogService } from 'sql/parts/insights/common/interfaces';
import { ISqlOAuthService } from 'sql/common/sqlOAuthService';
import { IFileBrowserService, IFileBrowserDialogController } from 'sql/parts/fileBrowser/common/interfaces';
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { IDashboardViewService } from 'sql/services/dashboard/common/dashboardViewService';
import { IModelViewService } from 'sql/services/modelComponents/modelViewService';
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { IEditorInput } from 'vs/platform/editor/common/editor';
import { IInstantiationService, ServicesAccessor, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IAccountManagementService } from 'sql/services/accountManagement/interfaces';
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IClipboardService as vsIClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { IInstantiationService, _util } from 'vs/platform/instantiation/common/instantiation';
const selectorCounter = new Map<string, number>();
const serviceMap = new Map<string, IInstantiationService>();
export const IBootstrapParams = new InjectionToken('bootstrap_params');
export interface IBootstrapParams {
@@ -68,63 +31,26 @@ function createUniqueSelector(selector: string): string {
let platform: PlatformRef;
export function bootstrapAngular<T>(collection: ServicesAccessor, moduleType: IModuleFactory<T>, container: HTMLElement, selectorString: string, params: IBootstrapParams, input?: IEditorInput, callbackSetModule?: (value: NgModuleRef<T>) => void): string {
export function bootstrapAngular<T>(service: IInstantiationService, moduleType: IModuleFactory<T>, container: HTMLElement, selectorString: string, params: IBootstrapParams, input?: IEditorInput, callbackSetModule?: (value: NgModuleRef<T>) => void): string {
// Create the uniqueSelectorString
let uniqueSelectorString = createUniqueSelector(selectorString);
let selector = document.createElement(uniqueSelectorString);
container.appendChild(selector);
serviceMap.set(uniqueSelectorString, service);
if (!platform) {
// Perform the bootsrap
const providers: { provide: ServiceIdentifier<any> | InjectionToken<any>, useValue: any }[] = [
// sql services
{ provide: IConnectionManagementService, useValue: collection.get(IConnectionManagementService) },
{ provide: IConnectionDialogService, useValue: collection.get(IConnectionDialogService) },
{ provide: IErrorMessageService, useValue: collection.get(IErrorMessageService) },
{ provide: IMetadataService, useValue: collection.get(IMetadataService) },
{ provide: IObjectExplorerService, useValue: collection.get(IObjectExplorerService) },
{ provide: IQueryEditorService, useValue: collection.get(IQueryEditorService) },
{ provide: IScriptingService, useValue: collection.get(IScriptingService) },
{ provide: IQueryManagementService, useValue: collection.get(IQueryManagementService) },
{ provide: IQueryModelService, useValue: collection.get(IQueryModelService) },
{ provide: IAdminService, useValue: collection.get(IAdminService) },
{ provide: IRestoreDialogController, useValue: collection.get(IRestoreDialogController) },
{ provide: IRestoreService, useValue: collection.get(IRestoreService) },
{ provide: IBackupService, useValue: collection.get(IBackupService) },
{ provide: IBackupUiService, useValue: collection.get(IBackupUiService) },
{ provide: IAngularEventingService, useValue: collection.get(IAngularEventingService) },
{ provide: IInsightsDialogService, useValue: collection.get(IInsightsDialogService) },
{ provide: ISqlOAuthService, useValue: collection.get(ISqlOAuthService) },
{ provide: IFileBrowserService, useValue: collection.get(IFileBrowserService) },
{ provide: IFileBrowserDialogController, useValue: collection.get(IFileBrowserDialogController) },
{ provide: IClipboardService, useValue: collection.get(IClipboardService) },
{ provide: ICapabilitiesService, useValue: collection.get(ICapabilitiesService) },
{ provide: IDashboardViewService, useValue: collection.get(IDashboardViewService) },
{ provide: IModelViewService, useValue: collection.get(IModelViewService) },
// vscode services
{ provide: vsIClipboardService, useValue: collection.get(vsIClipboardService) },
{ provide: IKeybindingService, useValue: collection.get(IKeybindingService) },
{ provide: IContextKeyService, useValue: collection.get(IContextKeyService) },
{ provide: IContextMenuService, useValue: collection.get(IContextMenuService) },
{ provide: IContextViewService, useValue: collection.get(IContextViewService) },
{ provide: IWorkbenchEditorService, useValue: collection.get(IWorkbenchEditorService) },
{ provide: IPartService, useValue: collection.get(IPartService) },
{ provide: IInstantiationService, useValue: collection.get(IInstantiationService) },
{ provide: IConfigurationService, useValue: collection.get(IConfigurationService) },
{ provide: IWorkspaceContextService, useValue: collection.get(IWorkspaceContextService) },
{ provide: IAccountManagementService, useValue: collection.get(IAccountManagementService) },
{ provide: IWindowsService, useValue: collection.get(IWindowsService) },
{ provide: IWindowService, useValue: collection.get(IWindowService) },
{ provide: ITelemetryService, useValue: collection.get(ITelemetryService) },
{ provide: IStorageService, useValue: collection.get(IStorageService) },
{ provide: ICommandService, useValue: collection.get(ICommandService) },
{ provide: IJobManagementService, useValue: collection.get(IJobManagementService) },
{ provide: IEnvironmentService, useValue: collection.get(IEnvironmentService) },
{ provide: INotificationService, useValue: collection.get(INotificationService) },
{ provide: IWorkbenchThemeService, useValue: collection.get(IWorkbenchThemeService) },
{ provide: IProgressService, useValue: collection.get(IProgressService) }
];
const providers: Provider = [];
_util.serviceIds.forEach(id => {
providers.push({
provide: id, useFactory: () => {
return (<any>serviceMap.get(uniqueSelectorString))._getOrCreateServiceInstance(id);
}
});
});
platform = platformBrowserDynamic(providers);
}
@@ -132,6 +58,10 @@ export function bootstrapAngular<T>(collection: ServicesAccessor, moduleType: IM
platform.bootstrapModule(moduleType(params, uniqueSelectorString)).then(moduleRef => {
if (input) {
input.onDispose(() => {
serviceMap.delete(uniqueSelectorString);
moduleRef.onDestroy(() => {
serviceMap.delete(uniqueSelectorString);
});
moduleRef.destroy();
});
}

View File

@@ -4,124 +4,101 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Mock, It, Times } from 'typemoq';
import { Mock, It, Times, GlobalMock } from 'typemoq';
import { Dialog, DialogTab } from 'sql/platform/dialog/dialogTypes';
import { DialogPane } from 'sql/platform/dialog/dialogPane';
import { DialogComponentParams } from 'sql/platform/dialog/dialogContainer.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService';
'use strict';
interface BootstrapAngular {
(collection, moduleType, container, selectorString, params: DialogComponentParams, input, callbackSetModule): void;
}
suite('Dialog Pane Tests', () => {
let dialog: Dialog;
let mockInstantiationService: Mock<IInstantiationService>;
let container: HTMLElement;
let bootstrapSave: BootstrapAngular;
function setupBootstrap(fn: BootstrapAngular): void {
(<any>bootstrapAngular) = fn;
}
setup(() => {
dialog = new Dialog('test_dialog');
mockInstantiationService = Mock.ofInstance({
invokeFunction: () => undefined
} as any);
mockInstantiationService.setup(x => x.invokeFunction(It.isAny(), It.isAny(), It.isAny(), It.isAny(), It.isAny(), undefined, It.isAny()));
container = document.createElement('div');
bootstrapSave = bootstrapAngular;
});
test('Creating a pane from content without tabs initializes the model view content correctly', () => {
// If I fill in a dialog's content with the ID of a specific model view provider and then render the dialog
let modelViewId = 'test_content';
let bootstrapCalls = 0;
setupBootstrap((collection, moduleType, container, selectorString, params: DialogComponentParams, input, callbackSetModule) => {
assert.equal(params.modelViewId, modelViewId);
bootstrapCalls++;
});
dialog.content = modelViewId;
let dialogPane = new DialogPane(dialog.title, dialog.content, () => undefined, mockInstantiationService.object);
let dialogPane = new DialogPane(dialog.title, dialog.content, () => undefined, undefined);
dialogPane.createBody(container);
// Then a single dialog-modelview-container element is added directly to the dialog pane
mockInstantiationService.verify(x => x.invokeFunction(
It.isAny(),
It.isAny(),
It.isAny(),
It.isAny(),
It.is((x: DialogComponentParams) => x.modelViewId === modelViewId),
undefined,
It.isAny()), Times.once());
assert.equal(bootstrapCalls, 1);
});
test('Creating a pane from content with a single tab initializes without showing tabs', () => {
// If I fill in a dialog's content with a single tab and then render the dialog
let modelViewId = 'test_content';
let bootstrapCalls = 0;
setupBootstrap((collection, moduleType, container, selectorString, params: DialogComponentParams, input, callbackSetModule) => {
assert.equal(params.modelViewId, modelViewId);
bootstrapCalls++;
});
dialog.content = [new DialogTab('', modelViewId)];
let dialogPane = new DialogPane(dialog.title, dialog.content, () => undefined, mockInstantiationService.object);
let dialogPane = new DialogPane(dialog.title, dialog.content, () => undefined, undefined);
dialogPane.createBody(container);
// Then a single dialog-modelview-container element is added directly to the dialog pane
mockInstantiationService.verify(x => x.invokeFunction(
It.isAny(),
It.isAny(),
It.isAny(),
It.isAny(),
It.is((x: DialogComponentParams) => x.modelViewId === modelViewId),
undefined,
It.isAny()), Times.once());
});
test('Creating a pane from content with multiple tabs initializes multiple model view tabs', () => {
// If I fill in a dialog's content with a single tab and then render the dialog
let modelViewId1 = 'test_content_1';
let modelViewId2 = 'test_content_2';
dialog.content = [new DialogTab('tab1', modelViewId1), new DialogTab('tab2', modelViewId2)];
let dialogPane = new DialogPane(dialog.title, dialog.content, () => undefined, mockInstantiationService.object);
dialogPane.createBody(container);
// Then a dialog-modelview-container element is added for the first tab (subsequent ones get added when the tab is actually clicked)
mockInstantiationService.verify(x => x.invokeFunction(
It.isAny(),
It.isAny(),
It.isAny(),
It.isAny(),
It.is((x: DialogComponentParams) => x.modelViewId === modelViewId1),
undefined,
It.isAny()), Times.once());
assert.equal(bootstrapCalls, 1);
});
test('Dialog validation gets set based on the validity of the model view content', () => {
// Set up the mock bootstrap service to intercept validation callbacks
let validationCallbacks: ((valid: boolean) => void)[] = [];
mockInstantiationService.reset();
mockInstantiationService.setup(x => x.invokeFunction(It.isAny(), It.isAny(), It.isAny(), It.isAny(), It.isAny(), undefined, It.isAny())).callback(
(collection, moduleType, container, selectorString, params: DialogComponentParams, input, callbackSetModule) => {
validationCallbacks.push(params.validityChangedCallback);
});
setupBootstrap((collection, moduleType, container, selectorString, params: DialogComponentParams, input, callbackSetModule) => {
validationCallbacks.push(params.validityChangedCallback);
});
let modelViewId1 = 'test_content_1';
let modelViewId2 = 'test_content_2';
dialog.content = [new DialogTab('tab1', modelViewId1), new DialogTab('tab2', modelViewId2)];
let dialogPane = new DialogPane(dialog.title, dialog.content, valid => dialog.notifyValidityChanged(valid), mockInstantiationService.object);
let dialogPane = new DialogPane(dialog.title, dialog.content, valid => dialog.notifyValidityChanged(valid), undefined);
dialogPane.createBody(container);
let validityChanges: boolean[] = [];
dialog.onValidityChanged(valid => validityChanges.push(valid));
dialog.onValidityChanged(valid => validityChanges.push(valid));
// If I set tab 2's validation to false
validationCallbacks[1](false);
// Then the whole dialog's validation is false
assert.equal(dialog.valid, false);
assert.equal(validityChanges.length, 1);
assert.equal(validityChanges[0], false);
// If I then set it back to true
validationCallbacks[1](true);
// Then the whole dialog's validation is true
assert.equal(dialog.valid, true);
assert.equal(validityChanges.length, 2);
assert.equal(validityChanges[1], true);
// If I set tab 1's validation to false
validationCallbacks[0](false);
// Then the whole dialog's validation is false
assert.equal(dialog.valid, false);
assert.equal(validityChanges.length, 3);
assert.equal(validityChanges[2], false);
});
});
teardown(() => {
(<any>bootstrapAngular) = bootstrapSave;
});
});