/** * @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, ViewEncapsulation} from '@angular/compiler/src/core'; import {setup} from '@angular/compiler/test/aot/test_util'; import {compile, expectEmit} from './mock_compile'; describe('compiler compliance: styling', () => { const angularFiles = setup({ compileAngular: false, compileFakeCore: true, compileAnimations: false, }); describe('@Component.styles', () => { it('should pass in the component metadata styles into the component definition and shim them using style encapsulation', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: "my-component", styles: ["div.foo { color: red; }", ":host p:nth-child(even) { --webkit-transition: 1s linear all; }"], template: "..." }) export class MyComponent { } @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; const template = 'styles: ["div.foo[_ngcontent-%COMP%] { color: red; }", "[_nghost-%COMP%] p[_ngcontent-%COMP%]:nth-child(even) { --webkit-transition: 1s linear all; }"]'; const result = compile(files, angularFiles); expectEmit(result.source, template, 'Incorrect template'); }); it('should pass in styles, but skip shimming the styles if the view encapsulation signals not to', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, ViewEncapsulation} from '@angular/core'; @Component({ selector: "my-component", encapsulation: ViewEncapsulation.None, styles: ["div.tall { height: 123px; }", ":host.small p { height:5px; }"], template: "..." }) export class MyComponent { } @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; const template = 'div.tall { height: 123px; }", ":host.small p { height:5px; }'; const result = compile(files, angularFiles); expectEmit(result.source, template, 'Incorrect template'); }); it('should pass in the component metadata styles into the component definition but skip shimming when style encapsulation is set to native', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule, ViewEncapsulation} from '@angular/core'; @Component({ encapsulation: ViewEncapsulation.Native, selector: "my-component", styles: ["div.cool { color: blue; }", ":host.nice p { color: gold; }"], template: "..." }) export class MyComponent { } @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; const template = ` MyComponent.ngComponentDef = $r3$.ɵɵdefineComponent({ … styles: ["div.cool { color: blue; }", ":host.nice p { color: gold; }"], encapsulation: 1 }) `; const result = compile(files, angularFiles); expectEmit(result.source, template, 'Incorrect template'); }); }); describe('@Component.animations', () => { it('should pass in the component metadata animations into the component definition', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: "my-component", animations: [{name: 'foo123'}, {name: 'trigger123'}], template: "" }) export class MyComponent { } @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; const template = ` MyComponent.ngComponentDef = $r3$.ɵɵdefineComponent({ type: MyComponent, selectors:[["my-component"]], factory:function MyComponent_Factory(t){ return new (t || MyComponent)(); }, consts: 0, vars: 0, template: function MyComponent_Template(rf, $ctx$) { }, encapsulation: 2, data: { animation: [{name: 'foo123'}, {name: 'trigger123'}] } }); `; const result = compile(files, angularFiles); expectEmit(result.source, template, 'Incorrect template'); }); it('should include animations even if the provided array is empty', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: "my-component", animations: [], template: "" }) export class MyComponent { } @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; const template = ` MyComponent.ngComponentDef = $r3$.ɵɵdefineComponent({ type: MyComponent, selectors:[["my-component"]], factory:function MyComponent_Factory(t){ return new (t || MyComponent)(); }, consts: 0, vars: 0, template: function MyComponent_Template(rf, $ctx$) { }, encapsulation: 2, data: { animation: [] } }); `; const result = compile(files, angularFiles); expectEmit(result.source, template, 'Incorrect template'); }); it('should generate any animation triggers into the component template', () => { 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 {} ` } }; const template = ` … MyComponent.ngComponentDef = $r3$.ɵɵdefineComponent({ … consts: 3, vars: 3, template: function MyComponent_Template(rf, $ctx$) { if (rf & 1) { $r3$.ɵɵelement(0, "div"); $r3$.ɵɵelement(1, "div"); $r3$.ɵɵelement(2, "div"); } if (rf & 2) { $r3$.ɵɵselect(0); $r3$.ɵɵproperty("@foo", ctx.exp); $r3$.ɵɵselect(1); $r3$.ɵɵproperty("@bar", undefined); $r3$.ɵɵselect(2); $r3$.ɵɵproperty("@baz", undefined); } }, encapsulation: 2 }); `; const result = compile(files, angularFiles); expectEmit(result.source, template, 'Incorrect template'); }); it('should generate animation listeners', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-cmp', template: \` \`, animations: [trigger( 'myAnimation', [transition( '* => state', [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])], }) class MyComponent { exp: any; startEvent: any; doneEvent: any; onStart(event: any) { this.startEvent = event; } onDone(event: any) { this.doneEvent = event; } } @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; const template = ` … MyComponent.ngComponentDef = $r3$.ɵɵdefineComponent({ … consts: 1, vars: 1, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵelementStart(0, "div"); $r3$.ɵɵlistener("@myAnimation.start", function MyComponent_Template_div_animation_myAnimation_start_0_listener($event) { return ctx.onStart($event); }); $r3$.ɵɵlistener("@myAnimation.done", function MyComponent_Template_div_animation_myAnimation_done_0_listener($event) { return ctx.onDone($event); }); $r3$.ɵɵelementEnd(); } if (rf & 2) { $r3$.ɵɵselect(0); $r3$.ɵɵproperty("@myAnimation", ctx.exp); } }, encapsulation: 2, … }); `; const result = compile(files, angularFiles); expectEmit(result.source, template, 'Incorrect template'); }); it('should generate animation host binding and listener code for directives', () => { const files = { app: { 'spec.ts': ` import {Directive, Component, NgModule} from '@angular/core'; @Directive({ selector: '[my-anim-dir]', animations: [ {name: 'myAnim'} ], host: { '[@myAnim]': 'myAnimState', '(@myAnim.start)': 'onStart()', '(@myAnim.done)': 'onDone()' } }) class MyAnimDir { onStart() {} onDone() {} myAnimState = '123'; } @Component({ selector: 'my-cmp', template: \` \` }) class MyComponent { } @NgModule({declarations: [MyComponent, MyAnimDir]}) export class MyModule {} ` } }; const template = ` MyAnimDir.ngDirectiveDef = $r3$.ɵɵdefineDirective({ … hostBindings: function MyAnimDir_HostBindings(rf, ctx, elIndex) { if (rf & 1) { $r3$.ɵɵallocHostVars(1); $r3$.ɵɵcomponentHostSyntheticListener("@myAnim.start", function MyAnimDir_animation_myAnim_start_HostBindingHandler($event) { return ctx.onStart(); }); $r3$.ɵɵcomponentHostSyntheticListener("@myAnim.done", function MyAnimDir_animation_myAnim_done_HostBindingHandler($event) { return ctx.onDone(); }); } if (rf & 2) { $r3$.ɵɵcomponentHostSyntheticProperty(elIndex, "@myAnim", $r3$.ɵɵbind(ctx.myAnimState), null, true); } } … }); `; const result = compile(files, angularFiles); expectEmit(result.source, template, 'Incorrect template'); }); }); describe('[style] and [style.prop]', () => { it('should create style instructions on the element', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-component', template: \`\` }) export class MyComponent { myStyleExp = [{color:'red'}, {color:'blue', duration:1000}] } @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; const template = ` template: function MyComponent_Template(rf, $ctx$) { if (rf & 1) { $r3$.ɵɵelementStart(0, "div"); $r3$.ɵɵelementStyling(null, null, $r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵelementEnd(); } if (rf & 2) { $r3$.ɵɵselect(0); $r3$.ɵɵelementStyleMap($ctx$.myStyleExp); $r3$.ɵɵelementStylingApply(); } } `; const result = compile(files, angularFiles); expectEmit(result.source, template, 'Incorrect template'); }); it('should correctly count the total slots required when style/class bindings include interpolation', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-component-with-interpolation', template: \` \` }) export class MyComponentWithInterpolation { fooId = '123'; } @Component({ selector: 'my-component-with-muchos-interpolation', template: \` \` }) export class MyComponentWithMuchosInterpolation { fooId = '123'; fooUsername = 'superfoo'; } @Component({ selector: 'my-component-without-interpolation', template: \` \` }) export class MyComponentWithoutInterpolation { exp = 'bar'; } @NgModule({declarations: [MyComponentWithInterpolation, MyComponentWithMuchosInterpolation, MyComponentWithoutInterpolation]}) export class MyModule {} ` } }; const template = ` … consts: 1, vars: 1, template: function MyComponentWithInterpolation_Template(rf, $ctx$) { if (rf & 1) { $r3$.ɵɵelementStart(0, "div"); $r3$.ɵɵelementStyling(); $r3$.ɵɵelementEnd(); } if (rf & 2) { $r3$.ɵɵselect(0); $r3$.ɵɵelementClassMap($r3$.ɵɵinterpolation1("foo foo-", $ctx$.fooId, "")); $r3$.ɵɵelementStylingApply(); } } … consts: 1, vars: 2, template: function MyComponentWithMuchosInterpolation_Template(rf, $ctx$) { if (rf & 1) { $r3$.ɵɵelementStart(0, "div"); $r3$.ɵɵelementStyling(); $r3$.ɵɵelementEnd(); } if (rf & 2) { $r3$.ɵɵselect(0); $r3$.ɵɵelementClassMap($r3$.ɵɵinterpolation2("foo foo-", $ctx$.fooId, "-", $ctx$.fooUsername, "")); $r3$.ɵɵelementStylingApply(); } } … consts: 1, vars: 0, template: function MyComponentWithoutInterpolation_Template(rf, $ctx$) { if (rf & 1) { $r3$.ɵɵelementStart(0, "div"); $r3$.ɵɵelementStyling(); $r3$.ɵɵelementEnd(); } if (rf & 2) { $r3$.ɵɵselect(0); $r3$.ɵɵelementClassMap($ctx$.exp); $r3$.ɵɵelementStylingApply(); } } `; const result = compile(files, angularFiles); expectEmit(result.source, template, 'Incorrect template'); }); it('should place initial, multi, singular and application followed by attribute style instructions in the template code in that order', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-component', template: \`\` }) export class MyComponent { myStyleExp = [{color:'red'}, {color:'blue', duration:1000}] myWidth = '100px'; myHeight = '100px'; } @NgModule({declarations: [MyComponent]}) export class MyModule {} ` } }; const template = ` const $_c0$ = [${AttributeMarker.Styles}, "opacity", "1", ${AttributeMarker.Bindings}, "style"]; const $_c1$ = ["width", "height"]; … MyComponent.ngComponentDef = $r3$.ɵɵdefineComponent({ type: MyComponent, selectors:[["my-component"]], factory:function MyComponent_Factory(t){ return new (t || MyComponent)(); }, consts: 1, vars: 1, template: function MyComponent_Template(rf, $ctx$) { if (rf & 1) { $r3$.ɵɵelementStart(0, "div", $_c0$); $r3$.ɵɵelementStyling(null, $_c1$, $r3$.ɵɵdefaultStyleSanitizer); $r3$.ɵɵelementEnd(); } if (rf & 2) { $r3$.ɵɵselect(0); $r3$.ɵɵelementStyleMap($ctx$.myStyleExp); $r3$.ɵɵelementStyleProp(0, $ctx$.myWidth); $r3$.ɵɵelementStyleProp(1, $ctx$.myHeight); $r3$.ɵɵelementStylingApply(); $r3$.ɵɵelementAttribute(0, "style", $r3$.ɵɵbind("border-width: 10px"), $r3$.ɵɵsanitizeStyle); } }, encapsulation: 2 }); `; const result = compile(files, angularFiles); expectEmit(result.source, template, 'Incorrect template'); }); it('should assign a sanitizer instance to the element style allocation instruction if any url-based properties are detected', () => { const files = { app: { 'spec.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ selector: 'my-component', template: \`