fix(ivy): TestBed should use annotation for the last match rather than the first (#28195)

When we look for matching annotations in TestBed, we should always take the last
matching annotation. Otherwise, we will return superclass data for subclasses,
which would have unintended consequences like directives matching the wrong selectors.

PR Close #28195
This commit is contained in:
Kara Erickson 2019-01-16 16:28:04 -08:00 committed by Alex Rickabaugh
parent 8a08ff1571
commit 1f7d3b9a57
3 changed files with 25 additions and 3 deletions

View File

@ -28,7 +28,7 @@ export function setClassMetadata(
propDecorators: {[field: string]: any} | null): void {
const clazz = type as TypeWithMetadata;
if (decorators !== null) {
if (clazz.decorators !== undefined) {
if (clazz.hasOwnProperty('decorators') && clazz.decorators !== undefined) {
clazz.decorators.push(...decorators);
} else {
clazz.decorators = decorators;

View File

@ -48,8 +48,21 @@ export class SimpleCmp {
export class WithRefsCmp {
}
@Component({selector: 'inherited-cmp', template: 'inherited'})
export class InheritedCmp extends SimpleCmp {
}
@Component({
selector: 'simple-app',
template: `
<simple-cmp></simple-cmp> - <inherited-cmp></inherited-cmp>
`
})
export class SimpleApp {
}
@NgModule({
declarations: [HelloWorld, SimpleCmp, WithRefsCmp],
declarations: [HelloWorld, SimpleCmp, WithRefsCmp, InheritedCmp, SimpleApp],
imports: [GreetingModule],
providers: [
{provide: NAME, useValue: 'World!'},
@ -174,6 +187,13 @@ describe('TestBed', () => {
expect(hello.nativeElement).toHaveText('Hello injected World !');
});
it('should resolve components that are extended by other components', () => {
// SimpleApp uses SimpleCmp in its template, which is extended by InheritedCmp
const simpleApp = TestBed.createComponent(SimpleApp);
simpleApp.detectChanges();
expect(simpleApp.nativeElement).toHaveText('simple - inherited');
});
onlyInIvy('patched ng defs should be removed after resetting TestingModule')
.it('make sure we restore ng defs to their initial states', () => {
@Pipe({name: 'somePipe', pure: true})

View File

@ -37,7 +37,9 @@ abstract class OverrideResolver<T> implements Resolver<T> {
}
getAnnotation(type: Type<any>): T|null {
return reflection.annotations(type).find(a => a instanceof this.type) || null;
// We should always return the last match from filter(), or we may return superclass data by
// mistake.
return reflection.annotations(type).filter(a => a instanceof this.type).pop() || null;
}
resolve(type: Type<any>): T|null {