test: fix memory leak when running test campaign (#11072)

This commit is contained in:
Marc Laval 2016-08-25 23:37:46 +02:00 committed by Victor Berchet
parent 566d4361e2
commit d7c82f5c0f
6 changed files with 92 additions and 67 deletions

View File

@ -57,6 +57,7 @@ export class ComponentFixture<T> {
private _autoDetect: boolean; private _autoDetect: boolean;
private _isStable: boolean = true; private _isStable: boolean = true;
private _isDestroyed: boolean = false;
private _resolve: (result: any) => void; private _resolve: (result: any) => void;
private _promise: Promise<any> = null; private _promise: Promise<any> = null;
private _onUnstableSubscription: any /** TODO #9100 */ = null; private _onUnstableSubscription: any /** TODO #9100 */ = null;
@ -178,6 +179,7 @@ export class ComponentFixture<T> {
* Trigger component destruction. * Trigger component destruction.
*/ */
destroy(): void { destroy(): void {
if (!this._isDestroyed) {
this.componentRef.destroy(); this.componentRef.destroy();
if (this._onUnstableSubscription != null) { if (this._onUnstableSubscription != null) {
this._onUnstableSubscription.unsubscribe(); this._onUnstableSubscription.unsubscribe();
@ -195,5 +197,7 @@ export class ComponentFixture<T> {
this._onErrorSubscription.unsubscribe(); this._onErrorSubscription.unsubscribe();
this._onErrorSubscription = null; this._onErrorSubscription = null;
} }
this._isDestroyed = true;
}
} }
} }

View File

@ -11,6 +11,7 @@ import {ListWrapper} from '../src/facade/collection';
import {BaseException} from '../src/facade/exceptions'; import {BaseException} from '../src/facade/exceptions';
import {FunctionWrapper, stringify} from '../src/facade/lang'; import {FunctionWrapper, stringify} from '../src/facade/lang';
import {Type} from '../src/type'; import {Type} from '../src/type';
import {AsyncTestCompleter} from './async_test_completer'; import {AsyncTestCompleter} from './async_test_completer';
import {ComponentFixture} from './component_fixture'; import {ComponentFixture} from './component_fixture';
import {MetadataOverride} from './metadata_override'; import {MetadataOverride} from './metadata_override';
@ -155,6 +156,7 @@ export class TestBed implements Injector {
private _declarations: Array<Type<any>|any[]|any> = []; private _declarations: Array<Type<any>|any[]|any> = [];
private _imports: Array<Type<any>|any[]|any> = []; private _imports: Array<Type<any>|any[]|any> = [];
private _schemas: Array<SchemaMetadata|any[]> = []; private _schemas: Array<SchemaMetadata|any[]> = [];
private _activeFixtures: ComponentFixture<any>[] = [];
/** /**
* Initialize the environment for testing with a compiler factory, a PlatformRef, and an * Initialize the environment for testing with a compiler factory, a PlatformRef, and an
@ -203,6 +205,8 @@ export class TestBed implements Injector {
this._imports = []; this._imports = [];
this._schemas = []; this._schemas = [];
this._instantiated = false; this._instantiated = false;
this._activeFixtures.forEach((fixture) => fixture.destroy());
this._activeFixtures = [];
} }
platform: PlatformRef = null; platform: PlatformRef = null;
@ -355,7 +359,9 @@ export class TestBed implements Injector {
return new ComponentFixture<T>(componentRef, ngZone, autoDetect); return new ComponentFixture<T>(componentRef, ngZone, autoDetect);
}; };
return ngZone == null ? initComponent() : ngZone.run(initComponent); const fixture = ngZone == null ? initComponent() : ngZone.run(initComponent);
this._activeFixtures.push(fixture);
return fixture;
} }
} }

View File

@ -37,7 +37,11 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn
this.formDirective.addFormGroup(this); this.formDirective.addFormGroup(this);
} }
ngOnDestroy(): void { this.formDirective.removeFormGroup(this); } ngOnDestroy(): void {
if (this.formDirective) {
this.formDirective.removeFormGroup(this);
}
}
/** /**
* Get the {@link FormGroup} backing this binding. * Get the {@link FormGroup} backing this binding.
@ -52,7 +56,7 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn
/** /**
* Get the {@link Form} to which this group belongs. * Get the {@link Form} to which this group belongs.
*/ */
get formDirective(): Form { return this._parent.formDirective; } get formDirective(): Form { return this._parent ? this._parent.formDirective : null; }
get validator(): ValidatorFn { return composeValidators(this._validators); } get validator(): ValidatorFn { return composeValidators(this._validators); }

View File

@ -105,7 +105,6 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
// TODO(kara): Replace ngModel with reactive API // TODO(kara): Replace ngModel with reactive API
@Input('ngModel') model: any; @Input('ngModel') model: any;
@Output('ngModelChange') update = new EventEmitter(); @Output('ngModelChange') update = new EventEmitter();
@Input('disabled') @Input('disabled')
set disabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); } set disabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); }
@ -133,7 +132,11 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
} }
} }
ngOnDestroy(): void { this.formDirective.removeControl(this); } ngOnDestroy(): void {
if (this.formDirective) {
this.formDirective.removeControl(this);
}
}
viewToModelUpdate(newValue: any): void { viewToModelUpdate(newValue: any): void {
this.viewModel = newValue; this.viewModel = newValue;
@ -142,7 +145,7 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
get path(): string[] { return controlPath(this.name, this._parent); } get path(): string[] { return controlPath(this.name, this._parent); }
get formDirective(): any { return this._parent.formDirective; } get formDirective(): any { return this._parent ? this._parent.formDirective : null; }
get validator(): ValidatorFn { return composeValidators(this._validators); } get validator(): ValidatorFn { return composeValidators(this._validators); }

View File

@ -161,11 +161,17 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy
this.formDirective.addFormArray(this); this.formDirective.addFormArray(this);
} }
ngOnDestroy(): void { this.formDirective.removeFormArray(this); } ngOnDestroy(): void {
if (this.formDirective) {
this.formDirective.removeFormArray(this);
}
}
get control(): FormArray { return this.formDirective.getFormArray(this); } get control(): FormArray { return this.formDirective.getFormArray(this); }
get formDirective(): FormGroupDirective { return <FormGroupDirective>this._parent.formDirective; } get formDirective(): FormGroupDirective {
return this._parent ? <FormGroupDirective>this._parent.formDirective : null;
}
get path(): string[] { return controlPath(this.name, this._parent); } get path(): string[] { return controlPath(this.name, this._parent); }

View File

@ -135,7 +135,7 @@ export function main() {
expect(form.value).toEqual({}); expect(form.value).toEqual({});
})); }));
it('should set status classes with ngModel', () => { it('should set status classes with ngModel', async(() => {
const fixture = TestBed.createComponent(NgModelForm); const fixture = TestBed.createComponent(NgModelForm);
fixture.debugElement.componentInstance.name = 'aa'; fixture.debugElement.componentInstance.name = 'aa';
fixture.detectChanges(); fixture.detectChanges();
@ -156,9 +156,9 @@ export function main() {
fixture.detectChanges(); fixture.detectChanges();
expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
}); });
}); }));
it('should set status classes with ngModelGroup and ngForm', () => { it('should set status classes with ngModelGroup and ngForm', async(() => {
const fixture = TestBed.createComponent(NgModelGroupForm); const fixture = TestBed.createComponent(NgModelGroupForm);
fixture.debugElement.componentInstance.first = ''; fixture.debugElement.componentInstance.first = '';
fixture.detectChanges(); fixture.detectChanges();
@ -179,7 +179,9 @@ export function main() {
dispatchEvent(input, 'blur'); dispatchEvent(input, 'blur');
fixture.detectChanges(); fixture.detectChanges();
expect(sortedClassList(modelGroup)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']); expect(sortedClassList(modelGroup)).toEqual([
'ng-invalid', 'ng-pristine', 'ng-touched'
]);
expect(sortedClassList(form)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']); expect(sortedClassList(form)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']);
input.value = 'updatedValue'; input.value = 'updatedValue';
@ -189,7 +191,7 @@ export function main() {
expect(sortedClassList(modelGroup)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); expect(sortedClassList(modelGroup)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
expect(sortedClassList(form)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); expect(sortedClassList(form)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
}); });
}); }));
it('should not create a template-driven form when ngNoForm is used', () => { it('should not create a template-driven form when ngNoForm is used', () => {
const fixture = TestBed.createComponent(NgNoFormComp); const fixture = TestBed.createComponent(NgNoFormComp);