test(ivy): update root causes for @angular/core TestBed failures (#27627)

PR Close #27627
This commit is contained in:
Pawel Kozlowski 2018-12-12 16:21:58 +01:00 committed by Alex Rickabaugh
parent fc6dc78fe9
commit 28ceca0163
3 changed files with 219 additions and 213 deletions

View File

@ -996,32 +996,33 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
expect(directiveLog.filter(['ngAfterViewInit'])).toEqual([]); expect(directiveLog.filter(['ngAfterViewInit'])).toEqual([]);
})); }));
fixmeIvy('unknown').it( fixmeIvy(
'should not call ngAfterViewInit again if it throws', fakeAsync(() => { 'FW-830: Exception thrown in ngAfterViewInit triggers ngAfterViewInit re-execution')
const ctx = .it('should not call ngAfterViewInit again if it throws', fakeAsync(() => {
createCompFixture('<div testDirective="dir" throwOn="ngAfterViewInit"></div>'); const ctx = createCompFixture(
'<div testDirective="dir" throwOn="ngAfterViewInit"></div>');
let errored = false; let errored = false;
// First pass fails, but ngAfterViewInit should be called. // First pass fails, but ngAfterViewInit should be called.
try { try {
ctx.detectChanges(false); ctx.detectChanges(false);
} catch (e) { } catch (e) {
errored = true; errored = true;
} }
expect(errored).toBe(true); expect(errored).toBe(true);
expect(directiveLog.filter(['ngAfterViewInit'])).toEqual(['dir.ngAfterViewInit']); expect(directiveLog.filter(['ngAfterViewInit'])).toEqual(['dir.ngAfterViewInit']);
directiveLog.clear(); directiveLog.clear();
// Second change detection also fails, but this time ngAfterViewInit should not be // Second change detection also fails, but this time ngAfterViewInit should not be
// called. // called.
try { try {
ctx.detectChanges(false); ctx.detectChanges(false);
} catch (e) { } catch (e) {
throw new Error('Second detectChanges() should not have run detection.'); throw new Error('Second detectChanges() should not have run detection.');
} }
expect(directiveLog.filter(['ngAfterViewInit'])).toEqual([]); expect(directiveLog.filter(['ngAfterViewInit'])).toEqual([]);
})); }));
}); });
describe('ngAfterViewChecked', () => { describe('ngAfterViewChecked', () => {
@ -1185,14 +1186,14 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
/Previous value: 'changed: undefined'\. Current value: 'changed: 1'/g); /Previous value: 'changed: undefined'\. Current value: 'changed: 1'/g);
})); }));
fixmeIvy('unknown').it( fixmeIvy('FW-831: Views created in a cd hooks throw in view engine')
'should warn when the view has been created in a cd hook', fakeAsync(() => { .it('should warn when the view has been created in a cd hook', fakeAsync(() => {
const ctx = createCompFixture('<div *gh9882>{{ a }}</div>', TestData); const ctx = createCompFixture('<div *gh9882>{{ a }}</div>', TestData);
ctx.componentInstance.a = 1; ctx.componentInstance.a = 1;
expect(() => ctx.detectChanges()) expect(() => ctx.detectChanges())
.toThrowError( .toThrowError(
/It seems like the view has been created after its parent and its children have been dirty checked/); /It seems like the view has been created after its parent and its children have been dirty checked/);
})); }));
it('should not throw when two arrays are structurally the same', fakeAsync(() => { it('should not throw when two arrays are structurally the same', fakeAsync(() => {
const ctx = _bindSimpleValue('a', TestData); const ctx = _bindSimpleValue('a', TestData);
@ -1537,110 +1538,111 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
childThrows: LifetimeMethods; childThrows: LifetimeMethods;
} }
fixmeIvy('unknown').describe('calling init', () => { fixmeIvy('FW-832: View engine supports recursive detectChanges() calls')
function initialize(options: Options) { .describe('calling init', () => {
@Component({selector: 'my-child', template: ''}) function initialize(options: Options) {
class MyChild { @Component({selector: 'my-child', template: ''})
private thrown = LifetimeMethods.None; class MyChild {
private thrown = LifetimeMethods.None;
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@Input() inp !: boolean; @Input() inp !: boolean;
@Output() outp = new EventEmitter<any>(); @Output() outp = new EventEmitter<any>();
constructor() {} constructor() {}
ngDoCheck() { this.check(LifetimeMethods.ngDoCheck); } ngDoCheck() { this.check(LifetimeMethods.ngDoCheck); }
ngOnInit() { this.check(LifetimeMethods.ngOnInit); } ngOnInit() { this.check(LifetimeMethods.ngOnInit); }
ngOnChanges() { this.check(LifetimeMethods.ngOnChanges); } ngOnChanges() { this.check(LifetimeMethods.ngOnChanges); }
ngAfterViewInit() { this.check(LifetimeMethods.ngAfterViewInit); } ngAfterViewInit() { this.check(LifetimeMethods.ngAfterViewInit); }
ngAfterContentInit() { this.check(LifetimeMethods.ngAfterContentInit); } ngAfterContentInit() { this.check(LifetimeMethods.ngAfterContentInit); }
private check(method: LifetimeMethods) { private check(method: LifetimeMethods) {
log(`MyChild::${LifetimeMethods[method]}()`); log(`MyChild::${LifetimeMethods[method]}()`);
if ((options.childRecursion & method) !== 0) { if ((options.childRecursion & method) !== 0) {
if (logged.length < 20) { if (logged.length < 20) {
this.outp.emit(null); this.outp.emit(null);
} else { } else {
fail(`Unexpected MyChild::${LifetimeMethods[method]} recursion`); fail(`Unexpected MyChild::${LifetimeMethods[method]} recursion`);
}
}
if ((options.childThrows & method) !== 0) {
if ((this.thrown & method) === 0) {
this.thrown |= method;
log(`<THROW from MyChild::${LifetimeMethods[method]}>()`);
throw new Error(`Throw from MyChild::${LifetimeMethods[method]}`);
}
}
} }
} }
if ((options.childThrows & method) !== 0) {
if ((this.thrown & method) === 0) { @Component({
this.thrown |= method; selector: 'my-component',
log(`<THROW from MyChild::${LifetimeMethods[method]}>()`); template: `<my-child [inp]='true' (outp)='onOutp()'></my-child>`
throw new Error(`Throw from MyChild::${LifetimeMethods[method]}`); })
class MyComponent {
constructor(private changeDetectionRef: ChangeDetectorRef) {}
ngDoCheck() { this.check(LifetimeMethods.ngDoCheck); }
ngOnInit() { this.check(LifetimeMethods.ngOnInit); }
ngAfterViewInit() { this.check(LifetimeMethods.ngAfterViewInit); }
ngAfterContentInit() { this.check(LifetimeMethods.ngAfterContentInit); }
onOutp() {
log('<RECURSION START>');
this.changeDetectionRef.detectChanges();
log('<RECURSION DONE>');
}
private check(method: LifetimeMethods) {
log(`MyComponent::${LifetimeMethods[method]}()`);
} }
} }
}
}
@Component({ TestBed.configureTestingModule({declarations: [MyChild, MyComponent]});
selector: 'my-component',
template: `<my-child [inp]='true' (outp)='onOutp()'></my-child>` return createCompFixture(`<my-component></my-component>`);
})
class MyComponent {
constructor(private changeDetectionRef: ChangeDetectorRef) {}
ngDoCheck() { this.check(LifetimeMethods.ngDoCheck); }
ngOnInit() { this.check(LifetimeMethods.ngOnInit); }
ngAfterViewInit() { this.check(LifetimeMethods.ngAfterViewInit); }
ngAfterContentInit() { this.check(LifetimeMethods.ngAfterContentInit); }
onOutp() {
log('<RECURSION START>');
this.changeDetectionRef.detectChanges();
log('<RECURSION DONE>');
} }
private check(method: LifetimeMethods) { function ensureOneInit(options: Options) {
log(`MyComponent::${LifetimeMethods[method]}()`); const ctx = initialize(options);
}
}
TestBed.configureTestingModule({declarations: [MyChild, MyComponent]});
return createCompFixture(`<my-component></my-component>`);
}
function ensureOneInit(options: Options) {
const ctx = initialize(options);
const throws = options.childThrows != LifetimeMethods.None; const throws = options.childThrows != LifetimeMethods.None;
if (throws) { if (throws) {
log(`<CYCLE 0 START>`); log(`<CYCLE 0 START>`);
expect(() => { expect(() => {
// Expect child to throw. // Expect child to throw.
ctx.detectChanges();
}).toThrow();
log(`<CYCLE 0 END>`);
log(`<CYCLE 1 START>`);
}
ctx.detectChanges(); ctx.detectChanges();
}).toThrow(); if (throws) log(`<CYCLE 1 DONE>`);
log(`<CYCLE 0 END>`); expectOnceAndOnlyOnce('MyComponent::ngOnInit()');
log(`<CYCLE 1 START>`); expectOnceAndOnlyOnce('MyChild::ngOnInit()');
} expectOnceAndOnlyOnce('MyComponent::ngAfterViewInit()');
ctx.detectChanges(); expectOnceAndOnlyOnce('MyComponent::ngAfterContentInit()');
if (throws) log(`<CYCLE 1 DONE>`); expectOnceAndOnlyOnce('MyChild::ngAfterViewInit()');
expectOnceAndOnlyOnce('MyComponent::ngOnInit()'); expectOnceAndOnlyOnce('MyChild::ngAfterContentInit()');
expectOnceAndOnlyOnce('MyChild::ngOnInit()'); }
expectOnceAndOnlyOnce('MyComponent::ngAfterViewInit()');
expectOnceAndOnlyOnce('MyComponent::ngAfterContentInit()');
expectOnceAndOnlyOnce('MyChild::ngAfterViewInit()');
expectOnceAndOnlyOnce('MyChild::ngAfterContentInit()');
}
forEachMethod(LifetimeMethods.InitMethodsAndChanges, method => { forEachMethod(LifetimeMethods.InitMethodsAndChanges, method => {
it(`should ensure that init hooks are called once an only once with recursion in ${LifetimeMethods[method]} `, it(`should ensure that init hooks are called once an only once with recursion in ${LifetimeMethods[method]} `,
() => { () => {
// Ensure all the init methods are called once. // Ensure all the init methods are called once.
ensureOneInit({childRecursion: method, childThrows: LifetimeMethods.None}); ensureOneInit({childRecursion: method, childThrows: LifetimeMethods.None});
}); });
}); });
forEachMethod(LifetimeMethods.All, method => { forEachMethod(LifetimeMethods.All, method => {
it(`should ensure that init hooks are called once an only once with a throw in ${LifetimeMethods[method]} `, it(`should ensure that init hooks are called once an only once with a throw in ${LifetimeMethods[method]} `,
() => { () => {
// Ensure all the init methods are called once. // Ensure all the init methods are called once.
// the first cycle throws but the next cycle should complete the inits. // the first cycle throws but the next cycle should complete the inits.
ensureOneInit({childRecursion: LifetimeMethods.None, childThrows: method}); ensureOneInit({childRecursion: LifetimeMethods.None, childThrows: method});
}); });
}); });
}); });
}); });
}); });
})(); })();

View File

@ -699,33 +699,32 @@ function declareTests(config?: {useJit: boolean}) {
expect(cmp.prop).toEqual('two'); expect(cmp.prop).toEqual('two');
}); });
if (getDOM().supportsDOMEvents()) { fixmeIvy(
fixmeIvy('unknown').it( 'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template')
'should be checked when an async pipe requests a check', fakeAsync(() => { .it('should be checked when an async pipe requests a check', fakeAsync(() => {
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]}); {declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]});
const template = '<push-cmp-with-async #cmp></push-cmp-with-async>'; const template = '<push-cmp-with-async #cmp></push-cmp-with-async>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp); const fixture = TestBed.createComponent(MyComp);
tick(); tick();
const cmp: PushCmpWithAsyncPipe = const cmp: PushCmpWithAsyncPipe =
fixture.debugElement.children[0].references !['cmp']; fixture.debugElement.children[0].references !['cmp'];
fixture.detectChanges(); fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(1); expect(cmp.numberOfChecks).toEqual(1);
fixture.detectChanges(); fixture.detectChanges();
fixture.detectChanges(); fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(1); expect(cmp.numberOfChecks).toEqual(1);
cmp.resolve(2); cmp.resolve(2);
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(2); expect(cmp.numberOfChecks).toEqual(2);
})); }));
}
}); });
it('should create a component that injects an @Host', () => { it('should create a component that injects an @Host', () => {
@ -1871,79 +1870,83 @@ function declareTests(config?: {useJit: boolean}) {
if (getDOM().supportsDOMEvents()) { if (getDOM().supportsDOMEvents()) {
describe('svg', () => { describe('svg', () => {
fixmeIvy('unknown').it('should support svg elements', () => { fixmeIvy('FW-672: SVG attribute xlink:href is output as :xlink:href (extra ":")')
TestBed.configureTestingModule({declarations: [MyComp]}); .it('should support svg elements', () => {
const template = '<svg><use xlink:href="Port" /></svg>'; TestBed.configureTestingModule({declarations: [MyComp]});
TestBed.overrideComponent(MyComp, {set: {template}}); const template = '<svg><use xlink:href="Port" /></svg>';
const fixture = TestBed.createComponent(MyComp); TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
const el = fixture.nativeElement; const el = fixture.nativeElement;
const svg = getDOM().childNodes(el)[0]; const svg = getDOM().childNodes(el)[0];
const use = getDOM().childNodes(svg)[0]; const use = getDOM().childNodes(svg)[0];
expect(getDOM().getProperty(<Element>svg, 'namespaceURI')) expect(getDOM().getProperty(<Element>svg, 'namespaceURI'))
.toEqual('http://www.w3.org/2000/svg'); .toEqual('http://www.w3.org/2000/svg');
expect(getDOM().getProperty(<Element>use, 'namespaceURI')) expect(getDOM().getProperty(<Element>use, 'namespaceURI'))
.toEqual('http://www.w3.org/2000/svg'); .toEqual('http://www.w3.org/2000/svg');
const firstAttribute = getDOM().getProperty(<Element>use, 'attributes')[0]; const firstAttribute = getDOM().getProperty(<Element>use, 'attributes')[0];
expect(firstAttribute.name).toEqual('xlink:href'); expect(firstAttribute.name).toEqual('xlink:href');
expect(firstAttribute.namespaceURI).toEqual('http://www.w3.org/1999/xlink'); expect(firstAttribute.namespaceURI).toEqual('http://www.w3.org/1999/xlink');
}); });
fixmeIvy('unknown').it('should support foreignObjects with document fragments', () => { fixmeIvy('FW-811: Align HTML namespaces between Ivy and Render2')
TestBed.configureTestingModule({declarations: [MyComp]}); .it('should support foreignObjects with document fragments', () => {
const template = TestBed.configureTestingModule({declarations: [MyComp]});
'<svg><foreignObject><xhtml:div><p>Test</p></xhtml:div></foreignObject></svg>'; const template =
TestBed.overrideComponent(MyComp, {set: {template}}); '<svg><foreignObject><xhtml:div><p>Test</p></xhtml:div></foreignObject></svg>';
const fixture = TestBed.createComponent(MyComp); TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
const el = fixture.nativeElement; const el = fixture.nativeElement;
const svg = getDOM().childNodes(el)[0]; const svg = getDOM().childNodes(el)[0];
const foreignObject = getDOM().childNodes(svg)[0]; const foreignObject = getDOM().childNodes(svg)[0];
const p = getDOM().childNodes(foreignObject)[0]; const p = getDOM().childNodes(foreignObject)[0];
expect(getDOM().getProperty(<Element>svg, 'namespaceURI')) expect(getDOM().getProperty(<Element>svg, 'namespaceURI'))
.toEqual('http://www.w3.org/2000/svg'); .toEqual('http://www.w3.org/2000/svg');
expect(getDOM().getProperty(<Element>foreignObject, 'namespaceURI')) expect(getDOM().getProperty(<Element>foreignObject, 'namespaceURI'))
.toEqual('http://www.w3.org/2000/svg'); .toEqual('http://www.w3.org/2000/svg');
expect(getDOM().getProperty(<Element>p, 'namespaceURI')) expect(getDOM().getProperty(<Element>p, 'namespaceURI'))
.toEqual('http://www.w3.org/1999/xhtml'); .toEqual('http://www.w3.org/1999/xhtml');
}); });
}); });
describe('attributes', () => { describe('attributes', () => {
fixmeIvy('unknown').it('should support attributes with namespace', () => { fixmeIvy('FW-672: SVG attribute xlink:href is output as :xlink:href (extra ":")')
TestBed.configureTestingModule({declarations: [MyComp, SomeCmp]}); .it('should support attributes with namespace', () => {
const template = '<svg:use xlink:href="#id" />'; TestBed.configureTestingModule({declarations: [MyComp, SomeCmp]});
TestBed.overrideComponent(SomeCmp, {set: {template}}); const template = '<svg:use xlink:href="#id" />';
const fixture = TestBed.createComponent(SomeCmp); TestBed.overrideComponent(SomeCmp, {set: {template}});
const fixture = TestBed.createComponent(SomeCmp);
const useEl = getDOM().firstChild(fixture.nativeElement); const useEl = getDOM().firstChild(fixture.nativeElement);
expect(getDOM().getAttributeNS(useEl, 'http://www.w3.org/1999/xlink', 'href')) expect(getDOM().getAttributeNS(useEl, 'http://www.w3.org/1999/xlink', 'href'))
.toEqual('#id'); .toEqual('#id');
}); });
fixmeIvy('unknown').it('should support binding to attributes with namespace', () => { fixmeIvy('FW-672: SVG attribute xlink:href is output as :xlink:href (extra ":")')
TestBed.configureTestingModule({declarations: [MyComp, SomeCmp]}); .it('should support binding to attributes with namespace', () => {
const template = '<svg:use [attr.xlink:href]="value" />'; TestBed.configureTestingModule({declarations: [MyComp, SomeCmp]});
TestBed.overrideComponent(SomeCmp, {set: {template}}); const template = '<svg:use [attr.xlink:href]="value" />';
const fixture = TestBed.createComponent(SomeCmp); TestBed.overrideComponent(SomeCmp, {set: {template}});
const fixture = TestBed.createComponent(SomeCmp);
const cmp = fixture.componentInstance; const cmp = fixture.componentInstance;
const useEl = getDOM().firstChild(fixture.nativeElement); const useEl = getDOM().firstChild(fixture.nativeElement);
cmp.value = '#id'; cmp.value = '#id';
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().getAttributeNS(useEl, 'http://www.w3.org/1999/xlink', 'href')) expect(getDOM().getAttributeNS(useEl, 'http://www.w3.org/1999/xlink', 'href'))
.toEqual('#id'); .toEqual('#id');
cmp.value = null; cmp.value = null;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().hasAttributeNS(useEl, 'http://www.w3.org/1999/xlink', 'href')) expect(getDOM().hasAttributeNS(useEl, 'http://www.w3.org/1999/xlink', 'href'))
.toEqual(false); .toEqual(false);
}); });
}); });
} }
}); });

View File

@ -81,21 +81,22 @@ describe('projection', () => {
expect(main.nativeElement).toHaveText(''); expect(main.nativeElement).toHaveText('');
}); });
fixmeIvy('unknown').it('should support multiple content tags', () => { fixmeIvy('FW-833: Directive / projected node matching against class name')
TestBed.configureTestingModule({declarations: [MultipleContentTagsComponent]}); .it('should support multiple content tags', () => {
TestBed.overrideComponent(MainComp, { TestBed.configureTestingModule({declarations: [MultipleContentTagsComponent]});
set: { TestBed.overrideComponent(MainComp, {
template: '<multiple-content-tags>' + set: {
'<div>B</div>' + template: '<multiple-content-tags>' +
'<div>C</div>' + '<div>B</div>' +
'<div class="left">A</div>' + '<div>C</div>' +
'</multiple-content-tags>' '<div class="left">A</div>' +
} '</multiple-content-tags>'
}); }
const main = TestBed.createComponent(MainComp); });
const main = TestBed.createComponent(MainComp);
expect(main.nativeElement).toHaveText('(A, BC)'); expect(main.nativeElement).toHaveText('(A, BC)');
}); });
it('should redistribute only direct children', () => { it('should redistribute only direct children', () => {
TestBed.configureTestingModule({declarations: [MultipleContentTagsComponent]}); TestBed.configureTestingModule({declarations: [MultipleContentTagsComponent]});