Skip to content

Commit 1f7d3b9

Browse files
karaalxhub
authored andcommitted
fix(ivy): TestBed should use annotation for the last match rather than the first (angular#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 angular#28195
1 parent 8a08ff1 commit 1f7d3b9

File tree

3 files changed

+25
-3
lines changed

3 files changed

+25
-3
lines changed

packages/core/src/render3/metadata.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function setClassMetadata(
2828
propDecorators: {[field: string]: any} | null): void {
2929
const clazz = type as TypeWithMetadata;
3030
if (decorators !== null) {
31-
if (clazz.decorators !== undefined) {
31+
if (clazz.hasOwnProperty('decorators') && clazz.decorators !== undefined) {
3232
clazz.decorators.push(...decorators);
3333
} else {
3434
clazz.decorators = decorators;

packages/core/test/test_bed_spec.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,21 @@ export class SimpleCmp {
4848
export class WithRefsCmp {
4949
}
5050

51+
@Component({selector: 'inherited-cmp', template: 'inherited'})
52+
export class InheritedCmp extends SimpleCmp {
53+
}
54+
55+
@Component({
56+
selector: 'simple-app',
57+
template: `
58+
<simple-cmp></simple-cmp> - <inherited-cmp></inherited-cmp>
59+
`
60+
})
61+
export class SimpleApp {
62+
}
63+
5164
@NgModule({
52-
declarations: [HelloWorld, SimpleCmp, WithRefsCmp],
65+
declarations: [HelloWorld, SimpleCmp, WithRefsCmp, InheritedCmp, SimpleApp],
5366
imports: [GreetingModule],
5467
providers: [
5568
{provide: NAME, useValue: 'World!'},
@@ -174,6 +187,13 @@ describe('TestBed', () => {
174187
expect(hello.nativeElement).toHaveText('Hello injected World !');
175188
});
176189

190+
it('should resolve components that are extended by other components', () => {
191+
// SimpleApp uses SimpleCmp in its template, which is extended by InheritedCmp
192+
const simpleApp = TestBed.createComponent(SimpleApp);
193+
simpleApp.detectChanges();
194+
expect(simpleApp.nativeElement).toHaveText('simple - inherited');
195+
});
196+
177197
onlyInIvy('patched ng defs should be removed after resetting TestingModule')
178198
.it('make sure we restore ng defs to their initial states', () => {
179199
@Pipe({name: 'somePipe', pure: true})

packages/core/testing/src/resolvers.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ abstract class OverrideResolver<T> implements Resolver<T> {
3737
}
3838

3939
getAnnotation(type: Type<any>): T|null {
40-
return reflection.annotations(type).find(a => a instanceof this.type) || null;
40+
// We should always return the last match from filter(), or we may return superclass data by
41+
// mistake.
42+
return reflection.annotations(type).filter(a => a instanceof this.type).pop() || null;
4143
}
4244

4345
resolve(type: Type<any>): T|null {

0 commit comments

Comments
 (0)