2017-11-20 13:21:17 -05:00
|
|
|
/**
|
|
|
|
* @license
|
2020-05-19 15:08:49 -04:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2017-11-20 13:21:17 -05:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2018-07-12 18:10:55 -04:00
|
|
|
import {MockDirectory, setup} from '@angular/compiler/test/aot/test_util';
|
2018-01-25 11:52:10 -05:00
|
|
|
import {compile, expectEmit} from './mock_compile';
|
2017-11-20 13:21:17 -05:00
|
|
|
|
|
|
|
describe('r3_view_compiler', () => {
|
|
|
|
const angularFiles = setup({
|
2018-08-01 03:52:34 -04:00
|
|
|
compileAngular: false,
|
|
|
|
compileFakeCore: true,
|
2017-11-20 13:21:17 -05:00
|
|
|
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');
|
|
|
|
});
|
2018-01-11 18:37:56 -05:00
|
|
|
|
2018-01-31 16:11:07 -05:00
|
|
|
describe('interpolations', () => {
|
|
|
|
// Regression #21927
|
2019-04-23 23:40:05 -04:00
|
|
|
it('should generate a correct call to textInterpolateV with more than 8 interpolations', () => {
|
2018-01-31 16:11:07 -05:00
|
|
|
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]}} '
|
|
|
|
})
|
2018-01-25 11:52:10 -05:00
|
|
|
export class MyApp {
|
2018-01-31 16:11:07 -05:00
|
|
|
list: any[] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({declarations: [MyApp]})
|
|
|
|
export class MyModule {}`
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-04-23 23:40:05 -04:00
|
|
|
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], " "]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
…
|
|
|
|
`;
|
2018-01-31 16:11:07 -05:00
|
|
|
const result = compile(files, angularFiles);
|
|
|
|
expectEmit(result.source, bV_call, 'Incorrect bV call');
|
|
|
|
});
|
|
|
|
});
|
2018-12-21 14:53:18 -05:00
|
|
|
|
|
|
|
describe('animations', () => {
|
2019-01-15 16:46:15 -05:00
|
|
|
it('should not register any @attr attributes as static attributes', () => {
|
2018-12-21 14:53:18 -05:00
|
|
|
const files: MockDirectory = {
|
|
|
|
app: {
|
|
|
|
'example.ts': `
|
|
|
|
import {Component, NgModule} from '@angular/core';
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'my-app',
|
2019-01-15 16:46:15 -05:00
|
|
|
template: '<div @attr [@binding]="exp"></div>'
|
2018-12-21 14:53:18 -05:00
|
|
|
})
|
|
|
|
export class MyApp {
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({declarations: [MyApp]})
|
|
|
|
export class MyModule {}`
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const template = `
|
|
|
|
template: function MyApp_Template(rf, ctx) {
|
|
|
|
if (rf & 1) {
|
2019-05-17 21:49:21 -04:00
|
|
|
$i0$.ɵɵelement(0, "div");
|
2019-01-15 16:46:15 -05:00
|
|
|
}
|
|
|
|
if (rf & 2) {
|
2019-06-19 13:50:42 -04:00
|
|
|
$i0$.ɵɵproperty("@attr", …)("@binding", …);
|
2018-12-21 14:53:18 -05:00
|
|
|
}
|
|
|
|
}`;
|
|
|
|
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) {
|
2019-05-17 21:49:21 -04:00
|
|
|
$i0$.ɵɵelementStart(0, "div");
|
2019-01-15 16:46:15 -05:00
|
|
|
…
|
2019-05-17 21:49:21 -04:00
|
|
|
$i0$.ɵɵproperty("@mySelector", …);
|
2018-12-21 14:53:18 -05:00
|
|
|
}
|
|
|
|
}`;
|
|
|
|
const result = compile(files, angularFiles);
|
|
|
|
expectEmit(result.source, template, 'Incorrect initialization attributes');
|
|
|
|
});
|
|
|
|
});
|
fix(compiler): preserve this.$event and this.$any accesses in expressions (#39323)
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
2020-10-18 11:41:29 -04:00
|
|
|
|
|
|
|
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');
|
|
|
|
});
|
|
|
|
});
|
2017-11-20 13:21:17 -05:00
|
|
|
});
|