/**
* @license
* Copyright Google Inc. 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 {AttributeMarker} from '@angular/compiler/src/core';
import {setup} from '@angular/compiler/test/aot/test_util';
import {compile, expectEmit} from './mock_compile';
/**
* These tests are codified version of the tests in compiler_canonical_spec.ts. Every
* test in compiler_canonical_spec.ts should have a corresponding test here.
*/
describe('compiler compliance', () => {
const angularFiles = setup({
compileAngular: false,
compileAnimations: false,
compileFakeCore: true,
});
describe('elements', () => {
it('should handle SVG', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
test
\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
// The factory should look like this:
const factory =
'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }';
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
const $c1$ = ["title", "Hello", ${AttributeMarker.Classes}, "my-app"];
const $c2$ = ["cx", "20", "cy", "30", "r", "50"];
…
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ΔelementStart(0, "div", $c1$);
$r3$.ΔnamespaceSVG();
$r3$.ΔelementStart(1, "svg");
$r3$.Δelement(2, "circle", $c2$);
$r3$.ΔelementEnd();
$r3$.ΔnamespaceHTML();
$r3$.ΔelementStart(3, "p");
$r3$.Δtext(4, "test");
$r3$.ΔelementEnd();
$r3$.ΔelementEnd();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory');
expectEmit(result.source, template, 'Incorrect template');
});
it('should handle MathML', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
test
\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
// The factory should look like this:
const factory =
'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }';
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
const $c1$ = ["title", "Hello", ${AttributeMarker.Classes}, "my-app"];
…
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ΔelementStart(0, "div", $c1$);
$r3$.ΔnamespaceMathML();
$r3$.ΔelementStart(1, "math");
$r3$.Δelement(2, "infinity");
$r3$.ΔelementEnd();
$r3$.ΔnamespaceHTML();
$r3$.ΔelementStart(3, "p");
$r3$.Δtext(4, "test");
$r3$.ΔelementEnd();
$r3$.ΔelementEnd();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory');
expectEmit(result.source, template, 'Incorrect template');
});
it('should translate DOM structure', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
Hello World!
\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
// The factory should look like this:
const factory =
'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }';
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
const $c1$ = ["title", "Hello", ${AttributeMarker.Classes}, "my-app"];
…
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ΔelementStart(0, "div", $c1$);
$r3$.Δtext(1, "Hello ");
$r3$.ΔelementStart(2, "b");
$r3$.Δtext(3, "World");
$r3$.ΔelementEnd();
$r3$.Δtext(4, "!");
$r3$.ΔelementEnd();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory');
expectEmit(result.source, template, 'Incorrect template');
});
// TODO(https://github.com/angular/angular/issues/24426): We need to support the parser actually
// building the proper attributes based off of xmlns attributes.
xit('should support namspaced attributes', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
Hello World!
\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
// The factory should look like this:
const factory =
'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }';
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
const $e0_attrs$ = ["class", "my-app", 0, "http://someuri/foo", "foo:bar", "baz", "title", "Hello", 0, "http://someuri/foo", "foo:qux", "quacks"];
…
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ΔelementStart(0, "div", $e0_attrs$);
$r3$.Δtext(1, "Hello ");
$r3$.ΔelementStart(2, "b");
$r3$.Δtext(3, "World");
$r3$.ΔelementEnd();
$r3$.Δtext(4, "!");
$r3$.ΔelementEnd();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory');
expectEmit(result.source, template, 'Incorrect template');
});
it('should support ', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`in a container\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
…
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
i0.ΔelementContainerStart(0);
i0.ΔelementStart(1, "span");
i0.Δtext(2, "in a ");
i0.ΔelementEnd();
i0.Δtext(3, "container");
i0.ΔelementContainerEnd();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should generate elementContainerStart/End instructions for empty ', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
…
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
i0.ΔelementContainerStart(0);
i0.ΔelementContainerEnd();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should bind to element properties', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`\`
})
export class MyComponent {
id = 'one';
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const factory =
'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }';
const template = `
const $e0_attrs$ = [${AttributeMarker.Bindings}, "id"];
…
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.Δelement(0, "div", $e0_attrs$);
}
if (rf & 2) {
$r3$.Δselect(0);
$r3$.ΔelementProperty(0, "id", $r3$.Δbind(ctx.id));
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory');
expectEmit(result.source, template, 'Incorrect template');
});
it('should reserve slots for pure functions', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`\`
})
export class MyComponent {
id = 'one';
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const $e0_attrs$ = [];
const factory =
'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }';
const template = `
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.Δelement(0, "div", $e0_attrs$);
$r3$.Δpipe(1,"pipe");
}
if (rf & 2) {
$r3$.Δselect(0);
$r3$.ΔelementProperty(0, "ternary", $r3$.Δbind((ctx.cond ? $r3$.ΔpureFunction1(8, $c0$, ctx.a): $c1$)));
$r3$.ΔelementProperty(0, "pipe", $r3$.Δbind($r3$.ΔpipeBind3(1, 4, ctx.value, 1, 2)));
$r3$.ΔelementProperty(0, "and", $r3$.Δbind((ctx.cond && $r3$.ΔpureFunction1(10, $c0$, ctx.b))));
$r3$.ΔelementProperty(0, "or", $r3$.Δbind((ctx.cond || $r3$.ΔpureFunction1(12, $c0$, ctx.c))));
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory');
expectEmit(result.source, template, 'Incorrect template');
});
it('should reserve slots for pure functions in host binding function', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule, Input} from '@angular/core';
@Component({
selector: 'my-component',
template: '...',
host: {
'[@expansionHeight]': \`{
value: getExpandedState(),
params: {
collapsedHeight: collapsedHeight,
expandedHeight: expandedHeight
}
}\`,
'[@expansionWidth]': \`{
value: getExpandedState(),
params: {
collapsedWidth: collapsedWidth,
expandedWidth: expandedWidth
}
}\`
}
})
export class MyComponent {
@Input() expandedHeight: string;
@Input() collapsedHeight: string;
@Input() expandedWidth: string;
@Input() collapsedWidth: string;
getExpandedState() {
return 'expanded';
}
}
@NgModule({
declarations: [MyComponent]
})
export class MyModule {}
`
}
};
const hostBindingsDef = `
const $_c0$ = function (a0, a1) { return { collapsedHeight: a0, expandedHeight: a1 }; };
const $_c1$ = function (a0, a1) { return { value: a0, params: a1 }; };
const $_c2$ = function (a0, a1) { return { collapsedWidth: a0, expandedWidth: a1 }; };
…
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
if (rf & 1) {
$r3$.ΔallocHostVars(14);
}
if (rf & 2) {
$r3$.ΔcomponentHostSyntheticProperty(elIndex, "@expansionHeight",
$r3$.Δbind(
$r3$.ΔpureFunction2(5, $_c1$, ctx.getExpandedState(),
$r3$.ΔpureFunction2(2, $_c0$, ctx.collapsedHeight, ctx.expandedHeight)
)
), null, true
);
$r3$.ΔcomponentHostSyntheticProperty(elIndex, "@expansionWidth",
$r3$.Δbind(
$r3$.ΔpureFunction2(11, $_c1$, ctx.getExpandedState(),
$r3$.ΔpureFunction2(8, $_c2$, ctx.collapsedWidth, ctx.expandedWidth)
)
), null, true
);
}
},
…
`;
const result = compile(files, angularFiles);
expectEmit(result.source, hostBindingsDef, 'Incorrect "hostBindings" function');
});
it('should bind to class and style names', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`\`
})
export class MyComponent {
error = true;
color = 'red';
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const factory =
'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }';
const template = `
const $e0_classBindings$ = ["error"];
const $e0_styleBindings$ = ["background-color"];
…
MyComponent.ngComponentDef = i0.ΔdefineComponent({type:MyComponent,selectors:[["my-component"]],
factory: function MyComponent_Factory(t){
return new (t || MyComponent)();
},
consts: 1,
vars: 0,
template: function MyComponent_Template(rf,ctx){
if (rf & 1) {
$r3$.ΔelementStart(0, "div");
$r3$.ΔelementStyling($e0_classBindings$, $e0_styleBindings$);
$r3$.ΔelementEnd();
}
if (rf & 2) {
$r3$.ΔelementStyleProp(0, 0, ctx.color);
$r3$.ΔelementClassProp(0, 0, ctx.error);
$r3$.ΔelementStylingApply(0);
}
},
encapsulation: 2
});
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory');
expectEmit(result.source, template, 'Incorrect template');
});
});
describe('components & directives', () => {
it('should instantiate directives', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule} from '@angular/core';
@Component({selector: 'child', template: 'child-view'})
export class ChildComponent {}
@Directive({selector: '[some-directive]'})
export class SomeDirective {}
@Component({selector: 'my-component', template: '!'})
export class MyComponent {}
@NgModule({declarations: [ChildComponent, SomeDirective, MyComponent]})
export class MyModule{}
`
}
};
// ChildComponent definition should be:
const ChildComponentDefinition = `
ChildComponent.ngComponentDef = $r3$.ΔdefineComponent({
type: ChildComponent,
selectors: [["child"]],
factory: function ChildComponent_Factory(t) { return new (t || ChildComponent)(); },
consts: 1,
vars: 0,
template: function ChildComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.Δtext(0, "child-view");
}
},
encapsulation: 2
});`;
// SomeDirective definition should be:
const SomeDirectiveDefinition = `
SomeDirective.ngDirectiveDef = $r3$.ΔdefineDirective({
type: SomeDirective,
selectors: [["", "some-directive", ""]],
factory: function SomeDirective_Factory(t) {return new (t || SomeDirective)(); }
});
`;
// MyComponent definition should be:
const MyComponentDefinition = `
const $c1$ = ["some-directive", ""];
…
MyComponent.ngComponentDef = $r3$.ΔdefineComponent({
type: MyComponent,
selectors: [["my-component"]],
factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); },
consts: 2,
vars: 0,
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.Δelement(0, "child", $c1$);
$r3$.Δtext(1, "!");
}
},
directives: [ChildComponent, SomeDirective],
encapsulation: 2
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, ChildComponentDefinition, 'Incorrect ChildComponent.ngComponentDef');
expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ngDirectiveDef');
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ngComponentDef');
});
it('should support complex selectors', () => {
const files = {
app: {
'spec.ts': `
import {Directive, NgModule} from '@angular/core';
@Directive({selector: 'div.foo[some-directive]:not([title]):not(.baz)'})
export class SomeDirective {}
@Directive({selector: ':not(span[title]):not(.baz)'})
export class OtherDirective {}
@NgModule({declarations: [SomeDirective, OtherDirective]})
export class MyModule{}
`
}
};
// SomeDirective definition should be:
const SomeDirectiveDefinition = `
SomeDirective.ngDirectiveDef = $r3$.ΔdefineDirective({
type: SomeDirective,
selectors: [["div", "some-directive", "", 8, "foo", 3, "title", "", 9, "baz"]],
factory: function SomeDirective_Factory(t) {return new (t || SomeDirective)(); }
});
`;
// OtherDirective definition should be:
const OtherDirectiveDefinition = `
OtherDirective.ngDirectiveDef = $r3$.ΔdefineDirective({
type: OtherDirective,
selectors: [["", 5, "span", "title", "", 9, "baz"]],
factory: function OtherDirective_Factory(t) {return new (t || OtherDirective)(); }
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ngDirectiveDef');
expectEmit(source, OtherDirectiveDefinition, 'Incorrect OtherDirective.ngDirectiveDef');
});
it('should support components without selector', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule} from '@angular/core';
@Component({template: ''})
export class EmptyOutletComponent {}
@NgModule({declarations: [EmptyOutletComponent]})
export class MyModule{}
`
}
};
// EmptyOutletComponent definition should be:
const EmptyOutletComponentDefinition = `
…
EmptyOutletComponent.ngComponentDef = $r3$.ΔdefineComponent({
type: EmptyOutletComponent,
selectors: [["ng-component"]],
factory: function EmptyOutletComponent_Factory(t) { return new (t || EmptyOutletComponent)(); },
consts: 1,
vars: 0,
template: function EmptyOutletComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.Δelement(0, "router-outlet");
}
},
encapsulation: 2
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(
source, EmptyOutletComponentDefinition, 'Incorrect EmptyOutletComponent.ngComponentDef');
});
it('should not support directives without selector', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule} from '@angular/core';
@Directive({})
export class EmptyOutletDirective {}
@NgModule({declarations: [EmptyOutletDirective]})
export class MyModule{}
`
}
};
expect(() => compile(files, angularFiles))
.toThrowError('Directive EmptyOutletDirective has no selector, please add it!');
});
it('should not support directives with empty selector', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule} from '@angular/core';
@Directive({selector: ''})
export class EmptyOutletDirective {}
@NgModule({declarations: [EmptyOutletDirective]})
export class MyModule{}
`
}
};
expect(() => compile(files, angularFiles))
.toThrowError('Directive EmptyOutletDirective has no selector, please add it!');
});
it('should not treat ElementRef, ViewContainerRef, or ChangeDetectorRef specially when injecting',
() => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule, ElementRef, ChangeDetectorRef, ViewContainerRef} from '@angular/core';
@Component({
selector: 'my-component',
template: ''
})
export class MyComponent {
constructor(public el: ElementRef, public vcr: ViewContainerRef, public cdr: ChangeDetectorRef) {}
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const MyComponentDefinition = `
…
MyComponent.ngComponentDef = $r3$.ΔdefineComponent({
type: MyComponent,
selectors: [["my-component"]],
factory: function MyComponent_Factory(t) {
return new (t || MyComponent)(
$r3$.ΔdirectiveInject($i$.ElementRef), $r3$.ΔdirectiveInject($i$.ViewContainerRef),
$r3$.ΔdirectiveInject($i$.ChangeDetectorRef));
},
consts: 0,
vars: 0,
template: function MyComponent_Template(rf, ctx) {},
encapsulation: 2
});`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
});
it('should support structural directives', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
@Directive({selector: '[if]'})
export class IfDirective {
constructor(template: TemplateRef) { }
}
@Component({
selector: 'my-component',
template: '