mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
a8e0db73ba
Currently expressions `$event.foo()` and `this.$event.foo()`, as well as `$any(foo)` and `this.$any(foo)`, are treated as the same expression by the compiler, because `this` is considered the same implicit receiver as when the receiver is omitted. This introduces the following issues: 1. Any time something called `$any` is used, it'll be stripped away, leaving only the first parameter. 2. If something called `$event` is used anywhere in a template, it'll be preserved as `$event`, rather than being rewritten to `ctx.$event`, causing the value to undefined at runtime. This applies to listener, property and text bindings. These changes resolve the first issue and part of the second one by preserving anything that is accessed through `this`, even if it's one of the "special" ones like `$any` or `$event`. Furthermore, these changes only expose the `$event` global variable inside event listeners, whereas previously it was available everywhere. Fixes #30278. PR Close #39323
248 lines
6.5 KiB
TypeScript
248 lines
6.5 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {MockDirectory, setup} from '@angular/compiler/test/aot/test_util';
|
|
import {compile, expectEmit} from './mock_compile';
|
|
|
|
describe('r3_view_compiler', () => {
|
|
const angularFiles = setup({
|
|
compileAngular: false,
|
|
compileFakeCore: true,
|
|
compileAnimations: false,
|
|
});
|
|
|
|
describe('hello world', () => {
|
|
it('should be able to generate the hello world component', () => {
|
|
const files: MockDirectory = {
|
|
app: {
|
|
'hello.ts': `
|
|
import {Component, NgModule} from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'hello-world',
|
|
template: 'Hello, world!'
|
|
})
|
|
export class HelloWorldComponent {
|
|
|
|
}
|
|
|
|
@NgModule({
|
|
declarations: [HelloWorldComponent]
|
|
})
|
|
export class HelloWorldModule {}
|
|
`
|
|
}
|
|
};
|
|
compile(files, angularFiles);
|
|
});
|
|
});
|
|
|
|
it('should be able to generate the example', () => {
|
|
const files: MockDirectory = {
|
|
app: {
|
|
'example.ts': `
|
|
import {Component, OnInit, OnDestroy, ElementRef, Input, NgModule} from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'my-app',
|
|
template: '<todo [data]="list"></todo>'
|
|
})
|
|
export class MyApp implements OnInit {
|
|
|
|
list: any[] = [];
|
|
|
|
constructor(public elementRef: ElementRef) {}
|
|
|
|
ngOnInit(): void {
|
|
}
|
|
}
|
|
|
|
@Component({
|
|
selector: 'todo',
|
|
template: '<ul class="list" [title]="myTitle"><li *ngFor="let item of data">{{data}}</li></ul>'
|
|
})
|
|
export class TodoComponent implements OnInit, OnDestroy {
|
|
|
|
@Input()
|
|
data: any[] = [];
|
|
|
|
myTitle: string;
|
|
|
|
constructor(public elementRef: ElementRef) {}
|
|
|
|
ngOnInit(): void {}
|
|
|
|
ngOnDestroy(): void {}
|
|
}
|
|
|
|
@NgModule({
|
|
declarations: [TodoComponent, MyApp],
|
|
})
|
|
export class TodoModule{}
|
|
`
|
|
}
|
|
};
|
|
const result = compile(files, angularFiles);
|
|
expect(result.source).toContain('@angular/core');
|
|
});
|
|
|
|
describe('interpolations', () => {
|
|
// Regression #21927
|
|
it('should generate a correct call to textInterpolateV with more than 8 interpolations', () => {
|
|
const files: MockDirectory = {
|
|
app: {
|
|
'example.ts': `
|
|
import {Component, NgModule} from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'my-app',
|
|
template: ' {{list[0]}} {{list[1]}} {{list[2]}} {{list[3]}} {{list[4]}} {{list[5]}} {{list[6]}} {{list[7]}} {{list[8]}} '
|
|
})
|
|
export class MyApp {
|
|
list: any[] = [];
|
|
}
|
|
|
|
@NgModule({declarations: [MyApp]})
|
|
export class MyModule {}`
|
|
}
|
|
};
|
|
|
|
const bV_call = `
|
|
…
|
|
function MyApp_Template(rf, ctx) {
|
|
if (rf & 1) {
|
|
$i0$.ɵɵtext(0);
|
|
}
|
|
if (rf & 2) {
|
|
$i0$.ɵɵtextInterpolateV([" ", ctx.list[0], " ", ctx.list[1], " ", ctx.list[2], " ", ctx.list[3], " ", ctx.list[4], " ", ctx.list[5], " ", ctx.list[6], " ", ctx.list[7], " ", ctx.list[8], " "]);
|
|
}
|
|
}
|
|
…
|
|
`;
|
|
const result = compile(files, angularFiles);
|
|
expectEmit(result.source, bV_call, 'Incorrect bV call');
|
|
});
|
|
});
|
|
|
|
describe('animations', () => {
|
|
it('should not register any @attr attributes as static attributes', () => {
|
|
const files: MockDirectory = {
|
|
app: {
|
|
'example.ts': `
|
|
import {Component, NgModule} from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'my-app',
|
|
template: '<div @attr [@binding]="exp"></div>'
|
|
})
|
|
export class MyApp {
|
|
}
|
|
|
|
@NgModule({declarations: [MyApp]})
|
|
export class MyModule {}`
|
|
}
|
|
};
|
|
|
|
const template = `
|
|
template: function MyApp_Template(rf, ctx) {
|
|
if (rf & 1) {
|
|
$i0$.ɵɵelement(0, "div");
|
|
}
|
|
if (rf & 2) {
|
|
$i0$.ɵɵproperty("@attr", …)("@binding", …);
|
|
}
|
|
}`;
|
|
const result = compile(files, angularFiles);
|
|
expectEmit(result.source, template, 'Incorrect initialization attributes');
|
|
});
|
|
|
|
it('should dedup multiple [@event] listeners', () => {
|
|
const files: MockDirectory = {
|
|
app: {
|
|
'example.ts': `
|
|
import {Component, NgModule} from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'my-app',
|
|
template: '<div (@mySelector.start)="false" (@mySelector.done)="false" [@mySelector]="0"></div>'
|
|
})
|
|
export class MyApp {
|
|
}
|
|
|
|
@NgModule({declarations: [MyApp]})
|
|
export class MyModule {}`
|
|
}
|
|
};
|
|
|
|
const template = `
|
|
template: function MyApp_Template(rf, ctx) {
|
|
if (rf & 1) {
|
|
$i0$.ɵɵelementStart(0, "div");
|
|
…
|
|
$i0$.ɵɵproperty("@mySelector", …);
|
|
}
|
|
}`;
|
|
const result = compile(files, angularFiles);
|
|
expectEmit(result.source, template, 'Incorrect initialization attributes');
|
|
});
|
|
});
|
|
|
|
describe('$any', () => {
|
|
it('should strip out $any wrappers', () => {
|
|
const files = {
|
|
app: {
|
|
'spec.ts': `
|
|
import {Component} from '@angular/core';
|
|
|
|
@Component({
|
|
template: '<div [tabIndex]="$any(10)"></div>'
|
|
})
|
|
class Comp {
|
|
}
|
|
`
|
|
}
|
|
};
|
|
|
|
const template = `
|
|
…
|
|
i0.ɵɵproperty("tabIndex", 10);
|
|
`;
|
|
|
|
const result = compile(files, angularFiles);
|
|
expectEmit(result.source, template, 'Incorrect template');
|
|
});
|
|
|
|
it('should preserve $any if it is accessed through `this`', () => {
|
|
const files = {
|
|
app: {
|
|
'spec.ts': `
|
|
import {Component} from '@angular/core';
|
|
|
|
@Component({
|
|
template: '<div [tabIndex]="this.$any(null)"></div>'
|
|
})
|
|
class Comp {
|
|
$any(value: null): any {
|
|
return value as any;
|
|
}
|
|
}
|
|
`
|
|
}
|
|
};
|
|
|
|
const template = `
|
|
…
|
|
i0.ɵɵproperty("tabIndex", ctx.$any(null));
|
|
`;
|
|
|
|
const result = compile(files, angularFiles);
|
|
expectEmit(result.source, template, 'Incorrect template');
|
|
});
|
|
});
|
|
});
|