diff --git a/packages/core/test/acceptance/integration_spec.ts b/packages/core/test/acceptance/integration_spec.ts
index 900a5a2bb2..f211d2ce18 100644
--- a/packages/core/test/acceptance/integration_spec.ts
+++ b/packages/core/test/acceptance/integration_spec.ts
@@ -5,12 +5,1380 @@
* 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 {Component, ContentChild, Directive, EventEmitter, HostBinding, HostListener, Input, Output, QueryList, TemplateRef, ViewChildren} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {Component, ContentChild, Directive, ElementRef, EventEmitter, HostBinding, HostListener, Input, OnInit, Output, QueryList, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
+import {onlyInIvy} from '@angular/private/testing';
describe('acceptance integration tests', () => {
+ function stripHtmlComments(str: string) { return str.replace(//g, ''); }
+
+ describe('render', () => {
+
+ it('should render basic template', () => {
+ @Component({template: 'Greetings '})
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+
+ expect(fixture.nativeElement.innerHTML).toEqual('Greetings ');
+ });
+
+ it('should render and update basic "Hello, World" template', () => {
+ @Component({template: '
Hello, {{name}}! '})
+ class App {
+ name = '';
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+
+ fixture.componentInstance.name = 'World';
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('Hello, World! ');
+
+ fixture.componentInstance.name = 'New World';
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('Hello, New World! ');
+ });
+ });
+
+ describe('ng-container', () => {
+
+ it('should insert as a child of a regular element', () => {
+ @Component(
+ {template: 'before|Greetings |after
'})
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+
+ // Strip comments since VE and Ivy put them in different places.
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML))
+ .toBe('before|Greetings |after
');
+ });
+
+ it('should add and remove DOM nodes when ng-container is a child of a regular element', () => {
+ @Component({
+ template:
+ 'content
'
+ })
+ class App {
+ render = false;
+ }
+
+ TestBed.configureTestingModule({declarations: [App], imports: [CommonModule]});
+ const fixture = TestBed.createComponent(App);
+
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('');
+
+ fixture.componentInstance.render = true;
+ fixture.detectChanges();
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('content
');
+
+ fixture.componentInstance.render = false;
+ fixture.detectChanges();
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('');
+ });
+
+ it('should add and remove DOM nodes when ng-container is a child of an embedded view', () => {
+
+ @Component({template: 'content '})
+ class App {
+ render = false;
+ }
+
+ TestBed.configureTestingModule({declarations: [App], imports: [CommonModule]});
+ const fixture = TestBed.createComponent(App);
+
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('');
+
+ fixture.componentInstance.render = true;
+ fixture.detectChanges();
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('content');
+
+ fixture.componentInstance.render = false;
+ fixture.detectChanges();
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('');
+ });
+
+ // https://stackblitz.com/edit/angular-tfhcz1?file=src%2Fapp%2Fapp.component.ts
+ it('should add and remove DOM nodes when ng-container is a child of a delayed embedded view',
+ () => {
+
+ @Directive({selector: '[testDirective]'})
+ class TestDirective {
+ constructor(private _tplRef: TemplateRef, private _vcRef: ViewContainerRef) {}
+
+ createAndInsert() { this._vcRef.insert(this._tplRef.createEmbeddedView({})); }
+
+ clear() { this._vcRef.clear(); }
+ }
+
+ @Component({
+ template: 'content '
+ })
+ class App {
+ @ViewChild(TestDirective) testDirective !: TestDirective;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, TestDirective]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toBe('');
+
+ fixture.componentInstance.testDirective.createAndInsert();
+ fixture.detectChanges();
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toBe('content');
+
+ fixture.componentInstance.testDirective.clear();
+ fixture.detectChanges();
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toBe('');
+ });
+
+ it('should render at the component view root', () => {
+ @Component(
+ {selector: 'test-cmpt', template: 'component template '})
+ class TestCmpt {
+ }
+
+ @Component({template: ' '})
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App, TestCmpt]});
+ const fixture = TestBed.createComponent(App);
+
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML))
+ .toBe('component template ');
+ });
+
+ it('should render inside another ng-container', () => {
+ @Component({
+ selector: 'test-cmpt',
+ template:
+ 'content '
+ })
+ class TestCmpt {
+ }
+
+ @Component({template: ' '})
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App, TestCmpt]});
+ const fixture = TestBed.createComponent(App);
+
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML))
+ .toBe('content ');
+ });
+
+ it('should render inside another ng-container at the root of a delayed view', () => {
+ @Directive({selector: '[testDirective]'})
+ class TestDirective {
+ constructor(private _tplRef: TemplateRef, private _vcRef: ViewContainerRef) {}
+
+ createAndInsert() { this._vcRef.insert(this._tplRef.createEmbeddedView({})); }
+
+ clear() { this._vcRef.clear(); }
+ }
+
+ @Component({
+ template:
+ 'content '
+ })
+ class App {
+ @ViewChild(TestDirective) testDirective !: TestDirective;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, TestDirective]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toBe('');
+
+ fixture.componentInstance.testDirective.createAndInsert();
+ fixture.detectChanges();
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toBe('content');
+
+ fixture.componentInstance.testDirective.createAndInsert();
+ fixture.detectChanges();
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toBe('contentcontent');
+
+ fixture.componentInstance.testDirective.clear();
+ fixture.detectChanges();
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toBe('');
+ });
+
+ it('should support directives and inject ElementRef', () => {
+ @Directive({selector: '[dir]'})
+ class TestDirective {
+ constructor(public elRef: ElementRef) {}
+ }
+
+ @Component({template: '
'})
+ class App {
+ @ViewChild(TestDirective) testDirective !: TestDirective;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, TestDirective]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('
');
+ expect(fixture.componentInstance.testDirective.elRef.nativeElement.nodeType)
+ .toBe(Node.COMMENT_NODE);
+ });
+
+ it('should support ViewContainerRef when ng-container is at the root of a view', () => {
+ @Directive({selector: '[dir]'})
+ class TestDirective {
+ @Input()
+ contentTpl: TemplateRef<{}>|null = null;
+
+ constructor(private _vcRef: ViewContainerRef) {}
+
+ insertView() { this._vcRef.createEmbeddedView(this.contentTpl as TemplateRef<{}>); }
+
+ clear() { this._vcRef.clear(); }
+ }
+
+ @Component({
+ template:
+ 'Content '
+ })
+ class App {
+ @ViewChild(TestDirective) testDirective !: TestDirective;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, TestDirective]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('');
+
+ fixture.componentInstance.testDirective.insertView();
+ fixture.detectChanges();
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('Content');
+
+ fixture.componentInstance.testDirective.clear();
+ fixture.detectChanges();
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('');
+ });
+
+ it('should support ViewContainerRef on inside ', () => {
+ @Directive({selector: '[dir]'})
+ class TestDirective {
+ constructor(private _tplRef: TemplateRef<{}>, private _vcRef: ViewContainerRef) {}
+
+ insertView() { this._vcRef.createEmbeddedView(this._tplRef); }
+
+ clear() { this._vcRef.clear(); }
+ }
+
+ @Component({template: 'Content '})
+ class App {
+ @ViewChild(TestDirective) testDirective !: TestDirective;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, TestDirective]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('');
+
+ fixture.componentInstance.testDirective.insertView();
+ fixture.detectChanges();
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('Content');
+
+ fixture.componentInstance.testDirective.clear();
+ fixture.detectChanges();
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('');
+ });
+
+ it('should not set any attributes', () => {
+ @Component({template: '
'})
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(stripHtmlComments(fixture.nativeElement.innerHTML)).toEqual('
');
+ });
+
+ });
+
+ describe('text bindings', () => {
+ it('should render "undefined" as ""', () => {
+ @Component({template: '{{name}}'})
+ class App {
+ name: string|undefined = 'benoit';
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('benoit');
+
+ fixture.componentInstance.name = undefined;
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('');
+ });
+
+ it('should render "null" as ""', () => {
+ @Component({template: '{{name}}'})
+ class App {
+ name: string|null = 'benoit';
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('benoit');
+
+ fixture.componentInstance.name = null;
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('');
+ });
+
+ });
+
+ describe('ngNonBindable handling', () => {
+ function stripNgNonBindable(str: string) { return str.replace(/ ngnonbindable=""/i, ''); }
+
+ it('should keep local ref for host element', () => {
+ @Component({
+ template: `
+
+ Hello {{ name }}!
+
+ {{ myRef.id }}
+ `
+ })
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(stripNgNonBindable(fixture.nativeElement.innerHTML))
+ .toEqual('Hello {{ name }}! my-id ');
+ });
+
+ it('should invoke directives for host element', () => {
+ let directiveInvoked: boolean = false;
+
+ @Directive({selector: '[directive]'})
+ class TestDirective implements OnInit {
+ ngOnInit() { directiveInvoked = true; }
+ }
+
+ @Component({
+ template: `
+
+ Hello {{ name }}!
+
+ `
+ })
+ class App {
+ name = 'World';
+ }
+
+ TestBed.configureTestingModule({declarations: [App, TestDirective]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(stripNgNonBindable(fixture.nativeElement.innerHTML))
+ .toEqual('Hello {{ name }}! ');
+ expect(directiveInvoked).toEqual(true);
+ });
+
+ it('should not invoke directives for nested elements', () => {
+ let directiveInvoked: boolean = false;
+
+ @Directive({selector: '[directive]'})
+ class TestDirective implements OnInit {
+ ngOnInit() { directiveInvoked = true; }
+ }
+
+ @Component({
+ template: `
+
+ Hello {{ name }}!
+
+ `
+ })
+ class App {
+ name = 'World';
+ }
+
+ TestBed.configureTestingModule({declarations: [App, TestDirective]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(stripNgNonBindable(fixture.nativeElement.innerHTML))
+ .toEqual('Hello {{ name }}! ');
+ expect(directiveInvoked).toEqual(false);
+ });
+ });
+
+ describe('Siblings update', () => {
+ it('should handle a flat list of static/bound text nodes', () => {
+ @Component({template: 'Hello {{name}}!'})
+ class App {
+ name = '';
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+
+ fixture.componentInstance.name = 'world';
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('Hello world!');
+
+ fixture.componentInstance.name = 'monde';
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('Hello monde!');
+ });
+
+ it('should handle a list of static/bound text nodes as element children', () => {
+ @Component({template: 'Hello {{name}}! '})
+ class App {
+ name = '';
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+
+ fixture.componentInstance.name = 'world';
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('Hello world! ');
+
+ fixture.componentInstance.name = 'mundo';
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('Hello mundo! ');
+ });
+
+ it('should render/update text node as a child of a deep list of elements', () => {
+ @Component({template: 'Hello {{name}}! '})
+ class App {
+ name = '';
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+
+ fixture.componentInstance.name = 'world';
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('Hello world! ');
+
+ fixture.componentInstance.name = 'mundo';
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('Hello mundo! ');
+ });
+
+ it('should update 2 sibling elements', () => {
+ @Component({template: ' '})
+ class App {
+ id = '';
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+
+ fixture.componentInstance.id = 'foo';
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(' ');
+
+ fixture.componentInstance.id = 'bar';
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(' ');
+ });
+
+ it('should handle sibling text node after element with child text node', () => {
+ @Component({template: 'hello
{{name}}'})
+ class App {
+ name = '';
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+
+ fixture.componentInstance.name = 'world';
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('hello
world');
+
+ fixture.componentInstance.name = 'mundo';
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('hello
mundo');
+ });
+ });
+
+ describe('basic components', () => {
+ @Component({selector: 'todo', template: 'Todo{{value}}
'})
+ class TodoComponent {
+ value = ' one';
+ }
+
+ it('should support a basic component template', () => {
+ @Component({template: ' '})
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App, TodoComponent]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('Todo one
');
+ });
+
+ it('should support a component template with sibling', () => {
+ @Component({template: ' two'})
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App, TodoComponent]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('Todo one
two');
+ });
+
+ it('should support a component template with component sibling', () => {
+ @Component({template: ' '})
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App, TodoComponent]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual('Todo one
Todo one
');
+ });
+
+ it('should support a component with binding on host element', () => {
+ @Component({selector: 'todo', template: '{{title}}'})
+ class TodoComponentHostBinding {
+ @HostBinding()
+ title = 'one';
+ }
+
+ @Component({template: ' '})
+ class App {
+ @ViewChild(TodoComponentHostBinding) todoComponentHostBinding !: TodoComponentHostBinding;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, TodoComponentHostBinding]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('one ');
+
+ fixture.componentInstance.todoComponentHostBinding.title = 'two';
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('two ');
+ });
+
+ it('should support root component with host attribute', () => {
+ @Component({selector: 'host-attr-comp', template: '', host: {'role': 'button'}})
+ class HostAttributeComp {
+ }
+
+ TestBed.configureTestingModule({declarations: [HostAttributeComp]});
+ const fixture = TestBed.createComponent(HostAttributeComp);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.getAttribute('role')).toEqual('button');
+ });
+
+ it('should support component with bindings in template', () => {
+ @Component({selector: 'comp', template: '{{ name }}
'})
+ class MyComp {
+ name = 'Bess';
+ }
+
+ @Component({template: ' '})
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App, MyComp]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.innerHTML).toEqual('Bess
');
+ });
+
+ it('should support a component with sub-views', () => {
+ @Component({selector: 'comp', template: 'text
'})
+ class MyComp {
+ @Input()
+ condition !: boolean;
+ }
+
+ @Component({template: ' '})
+ class App {
+ condition = false;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, MyComp], imports: [CommonModule]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+ const compElement = fixture.nativeElement.querySelector('comp');
+
+ fixture.componentInstance.condition = true;
+ fixture.detectChanges();
+ expect(stripHtmlComments(compElement.innerHTML)).toEqual('text
');
+
+ fixture.componentInstance.condition = false;
+ fixture.detectChanges();
+ expect(stripHtmlComments(compElement.innerHTML)).toEqual('');
+ });
+
+ });
+
+ describe('element bindings', () => {
+ describe('elementAttribute', () => {
+ it('should support attribute bindings', () => {
+ @Component({template: ' '})
+ class App {
+ title: string|null = '';
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+ fixture.componentInstance.title = 'Hello';
+ fixture.detectChanges();
+ // initial binding
+ expect(fixture.nativeElement.innerHTML).toEqual(' ');
+
+ // update binding
+ fixture.componentInstance.title = 'Hi!';
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML).toEqual(' ');
+
+ // remove attribute
+ fixture.componentInstance.title = null;
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML).toEqual(' ');
+ });
+
+ it('should stringify values used attribute bindings', () => {
+ @Component({template: ' '})
+ class App {
+ title: any;
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+ fixture.componentInstance.title = NaN;
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML).toEqual(' ');
+
+ fixture.componentInstance.title = {toString: () => 'Custom toString'};
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(' ');
+ });
+
+ it('should update bindings', () => {
+ @Component({
+ template: [
+ 'a:{{c[0]}}{{c[1]}}{{c[2]}}{{c[3]}}{{c[4]}}{{c[5]}}{{c[6]}}{{c[7]}}{{c[8]}}{{c[9]}}{{c[10]}}{{c[11]}}{{c[12]}}{{c[13]}}{{c[14]}}{{c[15]}}{{c[16]}}',
+ 'a0:{{c[1]}}',
+ 'a1:{{c[0]}}{{c[1]}}{{c[16]}}',
+ 'a2:{{c[0]}}{{c[1]}}{{c[2]}}{{c[3]}}{{c[16]}}',
+ 'a3:{{c[0]}}{{c[1]}}{{c[2]}}{{c[3]}}{{c[4]}}{{c[5]}}{{c[16]}}',
+ 'a4:{{c[0]}}{{c[1]}}{{c[2]}}{{c[3]}}{{c[4]}}{{c[5]}}{{c[6]}}{{c[7]}}{{c[16]}}',
+ 'a5:{{c[0]}}{{c[1]}}{{c[2]}}{{c[3]}}{{c[4]}}{{c[5]}}{{c[6]}}{{c[7]}}{{c[8]}}{{c[9]}}{{c[16]}}',
+ 'a6:{{c[0]}}{{c[1]}}{{c[2]}}{{c[3]}}{{c[4]}}{{c[5]}}{{c[6]}}{{c[7]}}{{c[8]}}{{c[9]}}{{c[10]}}{{c[11]}}{{c[16]}}',
+ 'a7:{{c[0]}}{{c[1]}}{{c[2]}}{{c[3]}}{{c[4]}}{{c[5]}}{{c[6]}}{{c[7]}}{{c[8]}}{{c[9]}}{{c[10]}}{{c[11]}}{{c[12]}}{{c[13]}}{{c[16]}}',
+ 'a8:{{c[0]}}{{c[1]}}{{c[2]}}{{c[3]}}{{c[4]}}{{c[5]}}{{c[6]}}{{c[7]}}{{c[8]}}{{c[9]}}{{c[10]}}{{c[11]}}{{c[12]}}{{c[13]}}{{c[14]}}{{c[15]}}{{c[16]}}',
+ ].join('\n')
+ })
+ class App {
+ c = ['(', 0, 'a', 1, 'b', 2, 'c', 3, 'd', 4, 'e', 5, 'f', 6, 'g', 7, ')'];
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.textContent.trim()).toEqual([
+ 'a:(0a1b2c3d4e5f6g7)',
+ 'a0:0',
+ 'a1:(0)',
+ 'a2:(0a1)',
+ 'a3:(0a1b2)',
+ 'a4:(0a1b2c3)',
+ 'a5:(0a1b2c3d4)',
+ 'a6:(0a1b2c3d4e5)',
+ 'a7:(0a1b2c3d4e5f6)',
+ 'a8:(0a1b2c3d4e5f6g7)',
+ ].join('\n'));
+
+ fixture.componentInstance.c.reverse();
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.textContent.trim()).toEqual([
+ 'a:)7g6f5e4d3c2b1a0(',
+ 'a0:7',
+ 'a1:)7(',
+ 'a2:)7g6(',
+ 'a3:)7g6f5(',
+ 'a4:)7g6f5e4(',
+ 'a5:)7g6f5e4d3(',
+ 'a6:)7g6f5e4d3c2(',
+ 'a7:)7g6f5e4d3c2b1(',
+ 'a8:)7g6f5e4d3c2b1a0(',
+ ].join('\n'));
+
+ fixture.componentInstance.c.reverse();
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.textContent.trim()).toEqual([
+ 'a:(0a1b2c3d4e5f6g7)',
+ 'a0:0',
+ 'a1:(0)',
+ 'a2:(0a1)',
+ 'a3:(0a1b2)',
+ 'a4:(0a1b2c3)',
+ 'a5:(0a1b2c3d4)',
+ 'a6:(0a1b2c3d4e5)',
+ 'a7:(0a1b2c3d4e5f6)',
+ 'a8:(0a1b2c3d4e5f6g7)',
+ ].join('\n'));
+ });
+
+ it('should not update DOM if context has not changed', () => {
+ @Component({
+ template: `
+
+
+
+ `
+ })
+ class App {
+ title: string|null = '';
+ shouldRender = true;
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ const span: HTMLSpanElement = fixture.nativeElement.querySelector('span');
+ const bold: HTMLElement = span.querySelector('b') !;
+
+ fixture.componentInstance.title = 'Hello';
+ fixture.detectChanges();
+
+ // initial binding
+ expect(span.getAttribute('title')).toBe('Hello');
+ expect(bold.getAttribute('title')).toBe('Hello');
+
+ // update DOM manually
+ bold.setAttribute('title', 'Goodbye');
+
+ // refresh with same binding
+ fixture.detectChanges();
+ expect(span.getAttribute('title')).toBe('Hello');
+ expect(bold.getAttribute('title')).toBe('Goodbye');
+
+ // refresh again with same binding
+ fixture.detectChanges();
+ expect(span.getAttribute('title')).toBe('Hello');
+ expect(bold.getAttribute('title')).toBe('Goodbye');
+ });
+
+ it('should support host attribute bindings', () => {
+ @Directive({selector: '[hostBindingDir]'})
+ class HostBindingDir {
+ @HostBinding('attr.aria-label')
+ label = 'some label';
+ }
+
+ @Component({template: '
'})
+ class App {
+ @ViewChild(HostBindingDir) hostBindingDir !: HostBindingDir;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, HostBindingDir]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+ const hostBindingEl = fixture.nativeElement.querySelector('div');
+
+ // Needs `toLowerCase`, because different browsers produce
+ // attributes either in camel case or lower case.
+ expect(hostBindingEl.getAttribute('aria-label')).toBe('some label');
+
+ fixture.componentInstance.hostBindingDir.label = 'other label';
+ fixture.detectChanges();
+
+ expect(hostBindingEl.getAttribute('aria-label')).toBe('other label');
+ });
+ });
+
+ describe('elementStyle', () => {
+ it('should support binding to styles', () => {
+ @Component({template: ' '})
+ class App {
+ size: string|null = '';
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+ fixture.componentInstance.size = '10px';
+ fixture.detectChanges();
+ const span: HTMLElement = fixture.nativeElement.querySelector('span');
+
+ expect(span.style.fontSize).toBe('10px');
+
+ fixture.componentInstance.size = '16px';
+ fixture.detectChanges();
+ expect(span.style.fontSize).toBe('16px');
+
+ fixture.componentInstance.size = null;
+ fixture.detectChanges();
+ expect(span.style.fontSize).toBeFalsy();
+ });
+
+ it('should support binding to styles with suffix', () => {
+ @Component({template: ' '})
+ class App {
+ size: string|number|null = '';
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+ fixture.componentInstance.size = '100';
+ fixture.detectChanges();
+ const span: HTMLElement = fixture.nativeElement.querySelector('span');
+
+ expect(span.style.fontSize).toEqual('100px');
+
+ fixture.componentInstance.size = 200;
+ fixture.detectChanges();
+ expect(span.style.fontSize).toEqual('200px');
+
+ fixture.componentInstance.size = 0;
+ fixture.detectChanges();
+ expect(span.style.fontSize).toEqual('0px');
+
+ fixture.componentInstance.size = null;
+ fixture.detectChanges();
+ expect(span.style.fontSize).toBeFalsy();
+ });
+ });
+
+ describe('class-based styling', () => {
+ it('should support CSS class toggle', () => {
+ @Component({template: ' '})
+ class App {
+ value: any;
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+ fixture.componentInstance.value = true;
+ fixture.detectChanges();
+ const span = fixture.nativeElement.querySelector('span');
+
+ expect(span.getAttribute('class')).toEqual('active');
+
+ fixture.componentInstance.value = false;
+ fixture.detectChanges();
+ expect(span.getAttribute('class')).toBeFalsy();
+
+ // truthy values
+ fixture.componentInstance.value = 'a_string';
+ fixture.detectChanges();
+ expect(span.getAttribute('class')).toEqual('active');
+
+ fixture.componentInstance.value = 10;
+ fixture.detectChanges();
+ expect(span.getAttribute('class')).toEqual('active');
+
+ // falsy values
+ fixture.componentInstance.value = '';
+ fixture.detectChanges();
+ expect(span.getAttribute('class')).toBeFalsy();
+
+ fixture.componentInstance.value = 0;
+ fixture.detectChanges();
+ expect(span.getAttribute('class')).toBeFalsy();
+ });
+
+ it('should work correctly with existing static classes', () => {
+ @Component({template: ' '})
+ class App {
+ value: any;
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+ fixture.componentInstance.value = true;
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML).toEqual(' ');
+
+ fixture.componentInstance.value = false;
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML).toEqual(' ');
+ });
+
+ it('should apply classes properly when nodes are components', () => {
+ @Component({selector: 'my-comp', template: 'Comp Content'})
+ class MyComp {
+ }
+
+ @Component({template: ' '})
+ class App {
+ value: any;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, MyComp]});
+ const fixture = TestBed.createComponent(App);
+ fixture.componentInstance.value = true;
+ fixture.detectChanges();
+ const compElement = fixture.nativeElement.querySelector('my-comp');
+
+ expect(fixture.nativeElement.textContent).toContain('Comp Content');
+ expect(compElement.getAttribute('class')).toBe('active');
+
+ fixture.componentInstance.value = false;
+ fixture.detectChanges();
+ expect(compElement.getAttribute('class')).toBeFalsy();
+ });
+
+ it('should apply classes properly when nodes have containers', () => {
+ @Component({selector: 'structural-comp', template: 'Comp Content'})
+ class StructuralComp {
+ @Input()
+ tmp !: TemplateRef;
+
+ constructor(public vcr: ViewContainerRef) {}
+
+ create() { this.vcr.createEmbeddedView(this.tmp); }
+ }
+
+ @Component({
+ template: `
+ Temp Content
+
+ `
+ })
+ class App {
+ @ViewChild(StructuralComp) structuralComp !: StructuralComp;
+ value: any;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, StructuralComp]});
+ const fixture = TestBed.createComponent(App);
+ fixture.componentInstance.value = true;
+ fixture.detectChanges();
+ const structuralCompEl = fixture.nativeElement.querySelector('structural-comp');
+
+ expect(structuralCompEl.getAttribute('class')).toEqual('active');
+
+ fixture.componentInstance.structuralComp.create();
+ fixture.detectChanges();
+ expect(structuralCompEl.getAttribute('class')).toEqual('active');
+
+ fixture.componentInstance.value = false;
+ fixture.detectChanges();
+ expect(structuralCompEl.getAttribute('class')).toEqual('');
+ });
+
+ @Directive({selector: '[DirWithClass]'})
+ class DirWithClassDirective {
+ public classesVal: string = '';
+
+ @Input('class')
+ set klass(value: string) { this.classesVal = value; }
+ }
+
+ @Directive({selector: '[DirWithStyle]'})
+ class DirWithStyleDirective {
+ public stylesVal: string = '';
+
+ @Input()
+ set style(value: string) { this.stylesVal = value; }
+ }
+
+ it('should delegate initial classes to a [class] input binding if present on a directive on the same element',
+ () => {
+ @Component({template: '
'})
+ class App {
+ @ViewChild(DirWithClassDirective) mockClassDirective !: DirWithClassDirective;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, DirWithClassDirective]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.mockClassDirective.classesVal)
+ .toEqual('apple orange banana');
+ });
+
+ it('should delegate initial styles to a [style] input binding if present on a directive on the same element',
+ () => {
+ @Component({template: '
'})
+ class App {
+ @ViewChild(DirWithStyleDirective) mockStyleDirective !: DirWithStyleDirective;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, DirWithStyleDirective]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ const styles = fixture.componentInstance.mockStyleDirective.stylesVal;
+
+ // Use `toContain` since Ivy and ViewEngine have some slight differences in formatting.
+ expect(styles).toContain('width:100px');
+ expect(styles).toContain('height:200px');
+ });
+
+ it('should update `[class]` and bindings in the provided directive if the input is matched',
+ () => {
+ @Component({template: '
'})
+ class App {
+ @ViewChild(DirWithClassDirective) mockClassDirective !: DirWithClassDirective;
+ value = '';
+ }
+
+ TestBed.configureTestingModule({declarations: [App, DirWithClassDirective]});
+ const fixture = TestBed.createComponent(App);
+ fixture.componentInstance.value = 'cucumber grape';
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.mockClassDirective.classesVal)
+ .toEqual('cucumber grape');
+ });
+
+ onlyInIvy('Passing an object into [style] works differently')
+ .it('should update `[style]` and bindings in the provided directive if the input is matched',
+ () => {
+ @Component({template: '
'})
+ class App {
+ @ViewChild(DirWithStyleDirective) mockStyleDirective !: DirWithStyleDirective;
+ value !: {[key: string]: string};
+ }
+
+ TestBed.configureTestingModule({declarations: [App, DirWithStyleDirective]});
+ const fixture = TestBed.createComponent(App);
+ fixture.componentInstance.value = {width: '200px', height: '500px'};
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.mockStyleDirective.stylesVal)
+ .toEqual('width:200px;height:500px');
+ });
+
+ onlyInIvy('Style binding merging works differently in Ivy')
+ .it('should apply initial styling to the element that contains the directive with host styling',
+ () => {
+ @Directive({
+ selector: '[DirWithInitialStyling]',
+ host: {
+ 'title': 'foo',
+ 'class': 'heavy golden',
+ 'style': 'color: purple',
+ '[style.font-weight]': '"bold"'
+ }
+ })
+ class DirWithInitialStyling {
+ }
+
+ @Component({
+ template: `
+
+ `
+ })
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App, DirWithInitialStyling]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ const target: HTMLDivElement = fixture.nativeElement.querySelector('div');
+ const classes = target.getAttribute('class') !.split(/\s+/).sort();
+ expect(classes).toEqual(['big', 'golden', 'heavy']);
+
+ expect(target.getAttribute('title')).toEqual('foo');
+ expect(target.style.getPropertyValue('color')).toEqual('black');
+ expect(target.style.getPropertyValue('font-size')).toEqual('200px');
+ expect(target.style.getPropertyValue('font-weight')).toEqual('bold');
+ });
+
+ onlyInIvy('Style binding merging works differently in Ivy')
+ .it('should apply single styling bindings present within a directive onto the same element and defer the element\'s initial styling values when missing',
+ () => {
+ @Directive({
+ selector: '[DirWithSingleStylingBindings]',
+ host: {
+ 'class': 'def',
+ '[class.xyz]': 'activateXYZClass',
+ '[style.width]': 'width',
+ '[style.height]': 'height'
+ }
+ })
+ class DirWithSingleStylingBindings {
+ width: null|string = null;
+ height: null|string = null;
+ activateXYZClass: boolean = false;
+ }
+
+ @Component({
+ template: `
+
+ `
+ })
+ class App {
+ @ViewChild(DirWithSingleStylingBindings)
+ dirInstance !: DirWithSingleStylingBindings;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, DirWithSingleStylingBindings]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+ const dirInstance = fixture.componentInstance.dirInstance;
+ const target: HTMLDivElement = fixture.nativeElement.querySelector('div');
+ expect(target.style.getPropertyValue('width')).toEqual('100px');
+ expect(target.style.getPropertyValue('height')).toEqual('200px');
+ expect(target.classList.contains('abc')).toBeTruthy();
+ expect(target.classList.contains('def')).toBeTruthy();
+ expect(target.classList.contains('xyz')).toBeFalsy();
+
+ dirInstance.width = '444px';
+ dirInstance.height = '999px';
+ dirInstance.activateXYZClass = true;
+ fixture.detectChanges();
+
+ expect(target.style.getPropertyValue('width')).toEqual('444px');
+ expect(target.style.getPropertyValue('height')).toEqual('999px');
+ expect(target.classList.contains('abc')).toBeTruthy();
+ expect(target.classList.contains('def')).toBeTruthy();
+ expect(target.classList.contains('xyz')).toBeTruthy();
+
+ dirInstance.width = null;
+ dirInstance.height = null;
+ fixture.detectChanges();
+
+ expect(target.style.getPropertyValue('width')).toEqual('100px');
+ expect(target.style.getPropertyValue('height')).toEqual('200px');
+ expect(target.classList.contains('abc')).toBeTruthy();
+ expect(target.classList.contains('def')).toBeTruthy();
+ expect(target.classList.contains('xyz')).toBeTruthy();
+ });
+
+ onlyInIvy('Style binding merging works differently in Ivy')
+ .it('should properly prioritize single style binding collisions when they exist on multiple directives',
+ () => {
+ @Directive({selector: '[Dir1WithStyle]', host: {'[style.width]': 'width'}})
+ class Dir1WithStyle {
+ width: null|string = null;
+ }
+
+ @Directive({
+ selector: '[Dir2WithStyle]',
+ host: {'style': 'width: 111px', '[style.width]': 'width'}
+ })
+ class Dir2WithStyle {
+ width: null|string = null;
+ }
+
+ @Component(
+ {template: '
'})
+ class App {
+ @ViewChild(Dir1WithStyle) dir1Instance !: Dir1WithStyle;
+ @ViewChild(Dir2WithStyle) dir2Instance !: Dir2WithStyle;
+ width: string|null = null;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, Dir1WithStyle, Dir2WithStyle]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+ const {dir1Instance, dir2Instance} = fixture.componentInstance;
+
+ const target: HTMLDivElement = fixture.nativeElement.querySelector('div');
+ expect(target.style.getPropertyValue('width')).toEqual('111px');
+
+ fixture.componentInstance.width = '999px';
+ dir1Instance.width = '222px';
+ dir2Instance.width = '333px';
+ fixture.detectChanges();
+ expect(target.style.getPropertyValue('width')).toEqual('999px');
+
+ fixture.componentInstance.width = null;
+ fixture.detectChanges();
+ expect(target.style.getPropertyValue('width')).toEqual('222px');
+
+ dir1Instance.width = null;
+ fixture.detectChanges();
+ expect(target.style.getPropertyValue('width')).toEqual('333px');
+
+ dir2Instance.width = null;
+ fixture.detectChanges();
+ expect(target.style.getPropertyValue('width')).toEqual('111px');
+
+ dir1Instance.width = '666px';
+ fixture.detectChanges();
+ expect(target.style.getPropertyValue('width')).toEqual('666px');
+
+ fixture.componentInstance.width = '777px';
+ fixture.detectChanges();
+ expect(target.style.getPropertyValue('width')).toEqual('777px');
+ });
+
+ onlyInIvy('Style binding merging works differently in Ivy')
+ .it('should properly prioritize multi style binding collisions when they exist on multiple directives',
+ () => {
+ @Directive({
+ selector: '[Dir1WithStyling]',
+ host: {'[style]': 'stylesExp', '[class]': 'classesExp'}
+ })
+ class Dir1WithStyling {
+ classesExp: any = {};
+ stylesExp: any = {};
+ }
+
+ @Directive({
+ selector: '[Dir2WithStyling]',
+ host: {'style': 'width: 111px', '[style]': 'stylesExp'}
+ })
+ class Dir2WithStyling {
+ stylesExp: any = {};
+ }
+
+ @Component({
+ template:
+ '
'
+ })
+ class App {
+ @ViewChild(Dir1WithStyling) dir1Instance !: Dir1WithStyling;
+ @ViewChild(Dir2WithStyling) dir2Instance !: Dir2WithStyling;
+ stylesExp: any = {};
+ classesExp: any = {};
+ }
+
+ TestBed.configureTestingModule(
+ {declarations: [App, Dir1WithStyling, Dir2WithStyling]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+ const {dir1Instance, dir2Instance} = fixture.componentInstance;
+
+ const target = fixture.nativeElement.querySelector('div') !;
+ expect(target.style.getPropertyValue('width')).toEqual('111px');
+
+ const compInstance = fixture.componentInstance;
+ compInstance.stylesExp = {width: '999px', height: null};
+ compInstance.classesExp = {one: true, two: false};
+ dir1Instance.stylesExp = {width: '222px'};
+ dir1Instance.classesExp = {two: true, three: false};
+ dir2Instance.stylesExp = {width: '333px', height: '100px'};
+ fixture.detectChanges();
+ expect(target.style.getPropertyValue('width')).toEqual('999px');
+ expect(target.style.getPropertyValue('height')).toEqual('100px');
+ expect(target.classList.contains('one')).toBeTruthy();
+ expect(target.classList.contains('two')).toBeFalsy();
+ expect(target.classList.contains('three')).toBeFalsy();
+
+ compInstance.stylesExp = {};
+ compInstance.classesExp = {};
+ dir1Instance.stylesExp = {width: '222px', height: '200px'};
+ fixture.detectChanges();
+ expect(target.style.getPropertyValue('width')).toEqual('222px');
+ expect(target.style.getPropertyValue('height')).toEqual('200px');
+ expect(target.classList.contains('one')).toBeFalsy();
+ expect(target.classList.contains('two')).toBeTruthy();
+ expect(target.classList.contains('three')).toBeFalsy();
+
+ dir1Instance.stylesExp = {};
+ dir1Instance.classesExp = {};
+ fixture.detectChanges();
+ expect(target.style.getPropertyValue('width')).toEqual('333px');
+ expect(target.style.getPropertyValue('height')).toEqual('100px');
+ expect(target.classList.contains('one')).toBeFalsy();
+ expect(target.classList.contains('two')).toBeFalsy();
+ expect(target.classList.contains('three')).toBeFalsy();
+
+ dir2Instance.stylesExp = {};
+ compInstance.stylesExp = {height: '900px'};
+ fixture.detectChanges();
+ expect(target.style.getPropertyValue('width')).toEqual('111px');
+ expect(target.style.getPropertyValue('height')).toEqual('900px');
+
+ dir1Instance.stylesExp = {width: '666px', height: '600px'};
+ dir1Instance.classesExp = {four: true, one: true};
+ fixture.detectChanges();
+ expect(target.style.getPropertyValue('width')).toEqual('666px');
+ expect(target.style.getPropertyValue('height')).toEqual('900px');
+ expect(target.classList.contains('one')).toBeTruthy();
+ expect(target.classList.contains('two')).toBeFalsy();
+ expect(target.classList.contains('three')).toBeFalsy();
+ expect(target.classList.contains('four')).toBeTruthy();
+
+ compInstance.stylesExp = {width: '777px'};
+ compInstance.classesExp = {four: false};
+ fixture.detectChanges();
+ expect(target.style.getPropertyValue('width')).toEqual('777px');
+ expect(target.style.getPropertyValue('height')).toEqual('600px');
+ expect(target.classList.contains('one')).toBeTruthy();
+ expect(target.classList.contains('two')).toBeFalsy();
+ expect(target.classList.contains('three')).toBeFalsy();
+ expect(target.classList.contains('four')).toBeFalsy();
+ });
+ });
+
+ it('should properly handle and render interpolation for class attribute bindings', () => {
+ @Component({template: '
'})
+ class App {
+ name = '';
+ age = '';
+ }
+
+ TestBed.configureTestingModule({declarations: [App]});
+ const fixture = TestBed.createComponent(App);
+ const target = fixture.nativeElement.querySelector('div') !;
+
+ expect(target.classList.contains('-fred-36-')).toBeFalsy();
+
+ fixture.componentInstance.name = 'fred';
+ fixture.componentInstance.age = '36';
+ fixture.detectChanges();
+
+ expect(target.classList.contains('-fred-36-')).toBeTruthy();
+ });
+ });
+
it('should only call inherited host listeners once', () => {
let clicks = 0;
diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts
index 9c0118f69d..9af14ac2b4 100644
--- a/packages/core/test/render3/integration_spec.ts
+++ b/packages/core/test/render3/integration_spec.ts
@@ -6,1080 +6,42 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
-
import {RendererType2} from '../../src/render/api';
import {getLContext} from '../../src/render3/context_discovery';
-import {AttributeMarker, ΔclassMap, ΔdefineComponent, ΔdefineDirective, ΔstyleMap, ΔtemplateRefExtractor} from '../../src/render3/index';
-import {ΔallocHostVars, Δbind, ΔclassProp, Δcontainer, ΔcontainerRefreshEnd, ΔcontainerRefreshStart, ΔdirectiveInject, Δelement, ΔelementAttribute, ΔelementContainerEnd, ΔelementContainerStart, ΔelementEnd, ΔelementHostAttrs, ΔelementProperty, ΔelementStart, ΔembeddedViewEnd, ΔembeddedViewStart, Δinterpolation1, Δinterpolation2, Δinterpolation3, Δinterpolation4, Δinterpolation5, Δinterpolation6, Δinterpolation7, Δinterpolation8, ΔinterpolationV, Δprojection, ΔprojectionDef, Δreference, Δselect, ΔstyleProp, Δstyling, ΔstylingApply, Δtemplate, Δtext, ΔtextBinding} from '../../src/render3/instructions/all';
+import {AttributeMarker, ΔdefineComponent, ΔdefineDirective} from '../../src/render3/index';
+import {ΔallocHostVars, Δbind, Δcontainer, ΔcontainerRefreshEnd, ΔcontainerRefreshStart, Δelement, ΔelementAttribute, ΔelementEnd, ΔelementProperty, ΔelementStart, ΔembeddedViewEnd, ΔembeddedViewStart, Δprojection, ΔprojectionDef, Δselect, Δstyling, ΔstylingApply, Δtemplate, Δtext, ΔtextBinding} from '../../src/render3/instructions/all';
import {MONKEY_PATCH_KEY_NAME} from '../../src/render3/interfaces/context';
import {RenderFlags} from '../../src/render3/interfaces/definition';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer';
import {StylingIndex} from '../../src/render3/interfaces/styling';
import {CONTEXT, HEADER_OFFSET} from '../../src/render3/interfaces/view';
-import {ΔdisableBindings, ΔenableBindings} from '../../src/render3/state';
import {ΔsanitizeUrl} from '../../src/sanitization/sanitization';
import {Sanitizer, SecurityContext} from '../../src/sanitization/security';
import {NgIf} from './common_with_def';
-import {ComponentFixture, MockRendererFactory, TemplateFixture, createComponent, renderToHtml} from './render_util';
+import {ComponentFixture, MockRendererFactory, renderToHtml} from './render_util';
describe('render3 integration test', () => {
describe('render', () => {
-
- it('should render basic template', () => {
- expect(renderToHtml(Template, {}, 2)).toEqual('Greetings ');
-
- function Template(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'span', ['title', 'Hello']);
- { Δtext(1, 'Greetings'); }
- ΔelementEnd();
- }
- }
- expect(ngDevMode).toHaveProperties({
- firstTemplatePass: 1,
- tNode: 3, // 1 for div, 1 for text, 1 for host element
- tView: 2, // 1 for root view, 1 for template
- rendererCreateElement: 1,
- });
- });
-
- it('should render and update basic "Hello, World" template', () => {
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'h1');
- { Δtext(1); }
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ΔtextBinding(1, Δinterpolation1('Hello, ', ctx.name, '!'));
- }
- }, 2, 1);
-
- const fixture = new ComponentFixture(App);
- fixture.component.name = 'World';
- fixture.update();
- expect(fixture.html).toEqual('Hello, World! ');
-
- fixture.component.name = 'New World';
- fixture.update();
- expect(fixture.html).toEqual('Hello, New World! ');
- });
- });
-
- describe('text bindings', () => {
- it('should render "undefined" as "" when used with `bind()`', () => {
- function Template(rf: RenderFlags, name: string) {
- if (rf & RenderFlags.Create) {
- Δtext(0);
- }
- if (rf & RenderFlags.Update) {
- ΔtextBinding(0, Δbind(name));
- }
- }
-
- expect(renderToHtml(Template, 'benoit', 1, 1)).toEqual('benoit');
- expect(renderToHtml(Template, undefined, 1, 1)).toEqual('');
- expect(ngDevMode).toHaveProperties({
- firstTemplatePass: 0,
- tNode: 2,
- tView: 2, // 1 for root view, 1 for template
- rendererSetText: 2,
- });
- });
-
- it('should render "null" as "" when used with `bind()`', () => {
- function Template(rf: RenderFlags, name: string) {
- if (rf & RenderFlags.Create) {
- Δtext(0);
- }
- if (rf & RenderFlags.Update) {
- ΔtextBinding(0, Δbind(name));
- }
- }
-
- expect(renderToHtml(Template, 'benoit', 1, 1)).toEqual('benoit');
- expect(renderToHtml(Template, null, 1, 1)).toEqual('');
- expect(ngDevMode).toHaveProperties({
- firstTemplatePass: 0,
- tNode: 2,
- tView: 2, // 1 for root view, 1 for template
- rendererSetText: 2,
- });
- });
-
- it('should support creation-time values in text nodes', () => {
- function Template(rf: RenderFlags, value: string) {
- if (rf & RenderFlags.Create) {
- Δtext(0);
- ΔtextBinding(0, value);
- }
- }
- expect(renderToHtml(Template, 'once', 1, 1)).toEqual('once');
- expect(renderToHtml(Template, 'twice', 1, 1)).toEqual('once');
- expect(ngDevMode).toHaveProperties({
- firstTemplatePass: 0,
- tNode: 2,
- tView: 2, // 1 for root view, 1 for template
- rendererSetText: 1,
- });
- });
-
- });
-
-
- describe('ngNonBindable handling', () => {
- it('should keep local ref for host element', () => {
- /**
- *
- * Hello {{ name }}!
- *
- * {{ myRef.id }}
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'b', ['id', 'my-id'], ['myRef', '']);
- ΔdisableBindings();
- ΔelementStart(2, 'i');
- Δtext(3, 'Hello {{ name }}!');
- ΔelementEnd();
- ΔenableBindings();
- ΔelementEnd();
- Δtext(4);
- }
- if (rf & RenderFlags.Update) {
- const ref = Δreference(1) as any;
- ΔtextBinding(4, Δinterpolation1(' ', ref.id, ' '));
- }
- }, 5, 1);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual('Hello {{ name }}! my-id ');
- });
-
- it('should invoke directives for host element', () => {
- let directiveInvoked: boolean = false;
-
- class TestDirective {
- ngOnInit() { directiveInvoked = true; }
-
- static ngDirectiveDef = ΔdefineDirective({
- type: TestDirective,
- selectors: [['', 'directive', '']],
- factory: () => new TestDirective()
- });
- }
-
- /**
- *
- * Hello {{ name }}!
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'b', ['directive', '']);
- ΔdisableBindings();
- ΔelementStart(1, 'i');
- Δtext(2, 'Hello {{ name }}!');
- ΔelementEnd();
- ΔenableBindings();
- ΔelementEnd();
- }
- }, 3, 0, [TestDirective]);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual('Hello {{ name }}! ');
- expect(directiveInvoked).toEqual(true);
- });
-
- it('should not invoke directives for nested elements', () => {
- let directiveInvoked: boolean = false;
-
- class TestDirective {
- ngOnInit() { directiveInvoked = true; }
-
- static ngDirectiveDef = ΔdefineDirective({
- type: TestDirective,
- selectors: [['', 'directive', '']],
- factory: () => new TestDirective()
- });
- }
-
- /**
- *
- * Hello {{ name }}!
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'b');
- ΔdisableBindings();
- ΔelementStart(1, 'i', ['directive', '']);
- Δtext(2, 'Hello {{ name }}!');
- ΔelementEnd();
- ΔenableBindings();
- ΔelementEnd();
- }
- }, 3, 0, [TestDirective]);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual('Hello {{ name }}! ');
- expect(directiveInvoked).toEqual(false);
- });
- });
-
- describe('Siblings update', () => {
- it('should handle a flat list of static/bound text nodes', () => {
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δtext(0, 'Hello ');
- Δtext(1);
- Δtext(2, '!');
- }
- if (rf & RenderFlags.Update) {
- ΔtextBinding(1, Δbind(ctx.name));
- }
- }, 3, 1);
-
- const fixture = new ComponentFixture(App);
- fixture.component.name = 'world';
- fixture.update();
- expect(fixture.html).toEqual('Hello world!');
-
- fixture.component.name = 'monde';
- fixture.update();
- expect(fixture.html).toEqual('Hello monde!');
- });
-
- it('should handle a list of static/bound text nodes as element children', () => {
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'b');
- {
- Δtext(1, 'Hello ');
- Δtext(2);
- Δtext(3, '!');
- }
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ΔtextBinding(2, Δbind(ctx.name));
- }
- }, 4, 1);
-
- const fixture = new ComponentFixture(App);
- fixture.component.name = 'world';
- fixture.update();
- expect(fixture.html).toEqual('Hello world! ');
-
- fixture.component.name = 'mundo';
- fixture.update();
- expect(fixture.html).toEqual('Hello mundo! ');
- });
-
- it('should render/update text node as a child of a deep list of elements', () => {
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'b');
- {
- ΔelementStart(1, 'b');
- {
- ΔelementStart(2, 'b');
- {
- ΔelementStart(3, 'b');
- { Δtext(4); }
- ΔelementEnd();
- }
- ΔelementEnd();
- }
- ΔelementEnd();
- }
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ΔtextBinding(4, Δinterpolation1('Hello ', ctx.name, '!'));
- }
- }, 5, 1);
-
- const fixture = new ComponentFixture(App);
- fixture.component.name = 'world';
- fixture.update();
- expect(fixture.html).toEqual('Hello world! ');
-
- fixture.component.name = 'mundo';
- fixture.update();
- expect(fixture.html).toEqual('Hello mundo! ');
- });
-
- it('should update 2 sibling elements', () => {
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'b');
- {
- Δelement(1, 'span');
- ΔelementStart(2, 'span', ['class', 'foo']);
- {}
- ΔelementEnd();
- }
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ΔelementAttribute(2, 'id', Δbind(ctx.id));
- }
- }, 3, 1);
-
- const fixture = new ComponentFixture(App);
- fixture.component.id = 'foo';
- fixture.update();
- expect(fixture.html).toEqual(' ');
-
- fixture.component.id = 'bar';
- fixture.update();
- expect(fixture.html).toEqual(' ');
- });
-
- it('should handle sibling text node after element with child text node', () => {
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'p');
- { Δtext(1, 'hello'); }
- ΔelementEnd();
- Δtext(2, 'world');
- }
- }, 3);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual('hello
world');
- });
- });
-
- describe('basic components', () => {
-
- class TodoComponent {
- value = ' one';
-
- static ngComponentDef = ΔdefineComponent({
- type: TodoComponent,
- selectors: [['todo']],
- consts: 3,
- vars: 1,
- template: function TodoTemplate(rf: RenderFlags, ctx: any) {
+ describe('text bindings', () => {
+ it('should support creation-time values in text nodes', () => {
+ function Template(rf: RenderFlags, value: string) {
if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'p');
- {
- Δtext(1, 'Todo');
- Δtext(2);
- }
- ΔelementEnd();
+ Δtext(0);
+ ΔtextBinding(0, value);
}
- if (rf & RenderFlags.Update) {
- ΔtextBinding(2, Δbind(ctx.value));
- }
- },
- factory: () => new TodoComponent
+ }
+ expect(renderToHtml(Template, 'once', 1, 1)).toEqual('once');
+ expect(renderToHtml(Template, 'twice', 1, 1)).toEqual('once');
+ expect(ngDevMode).toHaveProperties({
+ firstTemplatePass: 0,
+ tNode: 2,
+ tView: 2, // 1 for root view, 1 for template
+ rendererSetText: 1,
+ });
});
- }
-
- const defs = [TodoComponent];
-
- it('should support a basic component template', () => {
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'todo');
- }
- }, 1, 0, defs);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual('Todo one
');
});
-
- it('should support a component template with sibling', () => {
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'todo');
- Δtext(1, 'two');
- }
- }, 2, 0, defs);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual('Todo one
two');
- });
-
- it('should support a component template with component sibling', () => {
- /**
- *
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'todo');
- Δelement(1, 'todo');
- }
- }, 2, 0, defs);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual('Todo one
Todo one
');
- });
-
- it('should support a component with binding on host element', () => {
- let cmptInstance: TodoComponentHostBinding|null;
-
- class TodoComponentHostBinding {
- title = 'one';
- static ngComponentDef = ΔdefineComponent({
- type: TodoComponentHostBinding,
- selectors: [['todo']],
- consts: 1,
- vars: 1,
- template: function TodoComponentHostBindingTemplate(
- rf: RenderFlags, ctx: TodoComponentHostBinding) {
- if (rf & RenderFlags.Create) {
- Δtext(0);
- }
- if (rf & RenderFlags.Update) {
- ΔtextBinding(0, Δbind(ctx.title));
- }
- },
- factory: () => cmptInstance = new TodoComponentHostBinding,
- hostBindings: function(rf: RenderFlags, ctx: any, elementIndex: number): void {
- if (rf & RenderFlags.Create) {
- ΔallocHostVars(1);
- }
- if (rf & RenderFlags.Update) {
- // host bindings
- ΔelementProperty(elementIndex, 'title', Δbind(ctx.title));
- }
- }
- });
- }
-
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'todo');
- }
- }, 1, 0, [TodoComponentHostBinding]);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual('one ');
-
- cmptInstance !.title = 'two';
- fixture.update();
- expect(fixture.html).toEqual('two ');
- });
-
- it('should support root component with host attribute', () => {
- class HostAttributeComp {
- static ngComponentDef = ΔdefineComponent({
- type: HostAttributeComp,
- selectors: [['host-attr-comp']],
- factory: () => new HostAttributeComp(),
- consts: 0,
- vars: 0,
- hostBindings: function(rf, ctx, elIndex) {
- if (rf & RenderFlags.Create) {
- ΔelementHostAttrs(['role', 'button']);
- }
- },
- template: (rf: RenderFlags, ctx: HostAttributeComp) => {},
- });
- }
-
- const fixture = new ComponentFixture(HostAttributeComp);
- expect(fixture.hostElement.getAttribute('role')).toEqual('button');
- });
-
- it('should support component with bindings in template', () => {
- /** {{ name }}
*/
- class MyComp {
- name = 'Bess';
- static ngComponentDef = ΔdefineComponent({
- type: MyComp,
- selectors: [['comp']],
- consts: 2,
- vars: 1,
- template: function MyCompTemplate(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'p');
- { Δtext(1); }
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ΔtextBinding(1, Δbind(ctx.name));
- }
- },
- factory: () => new MyComp
- });
- }
-
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'comp');
- }
- }, 1, 0, [MyComp]);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual('Bess
');
- });
-
- it('should support a component with sub-views', () => {
- /**
- * % if (condition) {
- * text
- * % }
- */
- class MyComp {
- // TODO(issue/24571): remove '!'.
- condition !: boolean;
- static ngComponentDef = ΔdefineComponent({
- type: MyComp,
- selectors: [['comp']],
- consts: 1,
- vars: 0,
- template: function MyCompTemplate(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δcontainer(0);
- }
- if (rf & RenderFlags.Update) {
- ΔcontainerRefreshStart(0);
- {
- if (ctx.condition) {
- let rf1 = ΔembeddedViewStart(0, 2, 0);
- if (rf1 & RenderFlags.Create) {
- ΔelementStart(0, 'div');
- { Δtext(1, 'text'); }
- ΔelementEnd();
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- },
- factory: () => new MyComp,
- inputs: {condition: 'condition'}
- });
- }
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'comp');
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'condition', Δbind(ctx.condition));
- }
- }, 1, 1, [MyComp]);
-
- const fixture = new ComponentFixture(App);
- fixture.component.condition = true;
- fixture.update();
- expect(fixture.html).toEqual('text
');
-
- fixture.component.condition = false;
- fixture.update();
- expect(fixture.html).toEqual(' ');
- });
-
- });
-
- describe('ng-container', () => {
-
- it('should insert as a child of a regular element', () => {
- /**
- * before|Greetings |after
- */
- function Template() {
- ΔelementStart(0, 'div');
- {
- Δtext(1, 'before|');
- ΔelementContainerStart(2);
- {
- Δtext(3, 'Greetings');
- Δelement(4, 'span');
- }
- ΔelementContainerEnd();
- Δtext(5, '|after');
- }
- ΔelementEnd();
- }
-
- const fixture = new TemplateFixture(Template, () => {}, 6);
- expect(fixture.html).toEqual('before|Greetings |after
');
- });
-
- it('should add and remove DOM nodes when ng-container is a child of a regular element', () => {
- /**
- * {% if (value) { %}
- *
- * content
- *
- * {% } %}
- */
- const TestCmpt = createComponent('test-cmpt', function(rf: RenderFlags, ctx: {value: any}) {
- if (rf & RenderFlags.Create) {
- Δcontainer(0);
- }
- if (rf & RenderFlags.Update) {
- ΔcontainerRefreshStart(0);
- if (ctx.value) {
- let rf1 = ΔembeddedViewStart(0, 3, 0);
- {
- if (rf1 & RenderFlags.Create) {
- ΔelementStart(0, 'div');
- {
- ΔelementContainerStart(1);
- { Δtext(2, 'content'); }
- ΔelementContainerEnd();
- }
- ΔelementEnd();
- }
- }
- ΔembeddedViewEnd();
- }
- ΔcontainerRefreshEnd();
- }
- }, 1);
-
- const fixture = new ComponentFixture(TestCmpt);
- expect(fixture.html).toEqual('');
-
- fixture.component.value = true;
- fixture.update();
- expect(fixture.html).toEqual('content
');
-
- fixture.component.value = false;
- fixture.update();
- expect(fixture.html).toEqual('');
- });
-
- it('should add and remove DOM nodes when ng-container is a child of an embedded view (JS block)',
- () => {
- /**
- * {% if (value) { %}
- * content
- * {% } %}
- */
- const TestCmpt =
- createComponent('test-cmpt', function(rf: RenderFlags, ctx: {value: any}) {
- if (rf & RenderFlags.Create) {
- Δcontainer(0);
- }
- if (rf & RenderFlags.Update) {
- ΔcontainerRefreshStart(0);
- if (ctx.value) {
- let rf1 = ΔembeddedViewStart(0, 2, 0);
- {
- if (rf1 & RenderFlags.Create) {
- ΔelementContainerStart(0);
- { Δtext(1, 'content'); }
- ΔelementContainerEnd();
- }
- }
- ΔembeddedViewEnd();
- }
- ΔcontainerRefreshEnd();
- }
- }, 1);
-
- const fixture = new ComponentFixture(TestCmpt);
- expect(fixture.html).toEqual('');
-
- fixture.component.value = true;
- fixture.update();
- expect(fixture.html).toEqual('content');
-
- fixture.component.value = false;
- fixture.update();
- expect(fixture.html).toEqual('');
- });
-
- it('should add and remove DOM nodes when ng-container is a child of an embedded view (ViewContainerRef)',
- () => {
-
- function ngIfTemplate(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementContainerStart(0);
- { Δtext(1, 'content'); }
- ΔelementContainerEnd();
- }
- }
-
- /**
- * content
- */
- // equivalent to:
- /**
- *
- *
- * content
- *
- *
- */
- const TestCmpt =
- createComponent('test-cmpt', function(rf: RenderFlags, ctx: {value: any}) {
- if (rf & RenderFlags.Create) {
- Δtemplate(
- 0, ngIfTemplate, 2, 0, 'ng-template', [AttributeMarker.Bindings, 'ngIf']);
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'ngIf', Δbind(ctx.value));
- }
- }, 1, 1, [NgIf]);
-
- const fixture = new ComponentFixture(TestCmpt);
- expect(fixture.html).toEqual('');
-
- fixture.component.value = true;
- fixture.update();
- expect(fixture.html).toEqual('content');
-
- fixture.component.value = false;
- fixture.update();
- expect(fixture.html).toEqual('');
- });
-
- // https://stackblitz.com/edit/angular-tfhcz1?file=src%2Fapp%2Fapp.component.ts
- it('should add and remove DOM nodes when ng-container is a child of a delayed embedded view',
- () => {
-
- class TestDirective {
- constructor(private _tplRef: TemplateRef, private _vcRef: ViewContainerRef) {}
-
- createAndInsert() { this._vcRef.insert(this._tplRef.createEmbeddedView({})); }
-
- clear() { this._vcRef.clear(); }
-
- static ngDirectiveDef = ΔdefineDirective({
- type: TestDirective,
- selectors: [['', 'testDirective', '']],
- factory: () => testDirective = new TestDirective(
- ΔdirectiveInject(TemplateRef as any),
- ΔdirectiveInject(ViewContainerRef as any)),
- });
- }
-
-
- function embeddedTemplate(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementContainerStart(0);
- { Δtext(1, 'content'); }
- ΔelementContainerEnd();
- }
- }
-
- let testDirective: TestDirective;
-
-
- `
-
- content
-
- `;
- const TestCmpt = createComponent('test-cmpt', function(rf: RenderFlags) {
- if (rf & RenderFlags.Create) {
- Δtemplate(
- 0, embeddedTemplate, 2, 0, 'ng-template',
- [AttributeMarker.Bindings, 'testDirective']);
- }
- }, 1, 0, [TestDirective]);
-
- const fixture = new ComponentFixture(TestCmpt);
- expect(fixture.html).toEqual('');
-
- testDirective !.createAndInsert();
- fixture.update();
- expect(fixture.html).toEqual('content');
-
- testDirective !.clear();
- fixture.update();
- expect(fixture.html).toEqual('');
- });
-
- it('should render at the component view root', () => {
- /**
- * component template
- */
- const TestCmpt = createComponent('test-cmpt', function(rf: RenderFlags) {
- if (rf & RenderFlags.Create) {
- ΔelementContainerStart(0);
- { Δtext(1, 'component template'); }
- ΔelementContainerEnd();
- }
- }, 2);
-
- function App() { Δelement(0, 'test-cmpt'); }
-
- const fixture = new TemplateFixture(App, () => {}, 1, 0, [TestCmpt]);
- expect(fixture.html).toEqual('component template ');
- });
-
- it('should render inside another ng-container', () => {
- /**
- *
- *
- *
- * content
- *
- *
- *
- */
- const TestCmpt = createComponent('test-cmpt', function(rf: RenderFlags) {
- if (rf & RenderFlags.Create) {
- ΔelementContainerStart(0);
- {
- ΔelementContainerStart(1);
- {
- ΔelementContainerStart(2);
- { Δtext(3, 'content'); }
- ΔelementContainerEnd();
- }
- ΔelementContainerEnd();
- }
- ΔelementContainerEnd();
- }
- }, 4);
-
- function App() { Δelement(0, 'test-cmpt'); }
-
- const fixture = new TemplateFixture(App, () => {}, 1, 0, [TestCmpt]);
- expect(fixture.html).toEqual('content ');
- });
-
- it('should render inside another ng-container at the root of a delayed view', () => {
- let testDirective: TestDirective;
-
- class TestDirective {
- constructor(private _tplRef: TemplateRef, private _vcRef: ViewContainerRef) {}
-
- createAndInsert() { this._vcRef.insert(this._tplRef.createEmbeddedView({})); }
-
- clear() { this._vcRef.clear(); }
-
- static ngDirectiveDef = ΔdefineDirective({
- type: TestDirective,
- selectors: [['', 'testDirective', '']],
- factory:
- () => testDirective = new TestDirective(
- ΔdirectiveInject(TemplateRef as any), ΔdirectiveInject(ViewContainerRef as any)),
- });
- }
-
-
- function embeddedTemplate(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementContainerStart(0);
- {
- ΔelementContainerStart(1);
- {
- ΔelementContainerStart(2);
- { Δtext(3, 'content'); }
- ΔelementContainerEnd();
- }
- ΔelementContainerEnd();
- }
- ΔelementContainerEnd();
- }
- }
-
- /**
- *
- *
- *
- *
- * content
- *
- *
- *
- *
- */
- const TestCmpt = createComponent('test-cmpt', function(rf: RenderFlags) {
- if (rf & RenderFlags.Create) {
- Δtemplate(
- 0, embeddedTemplate, 4, 0, 'ng-template',
- [AttributeMarker.Bindings, 'testDirective']);
- }
- }, 1, 0, [TestDirective]);
-
- function App() { Δelement(0, 'test-cmpt'); }
-
- const fixture = new ComponentFixture(TestCmpt);
- expect(fixture.html).toEqual('');
-
- testDirective !.createAndInsert();
- fixture.update();
- expect(fixture.html).toEqual('content');
-
- testDirective !.createAndInsert();
- fixture.update();
- expect(fixture.html).toEqual('contentcontent');
-
- testDirective !.clear();
- fixture.update();
- expect(fixture.html).toEqual('');
- });
-
- it('should support directives and inject ElementRef', () => {
-
- class Directive {
- constructor(public elRef: ElementRef) {}
-
- static ngDirectiveDef = ΔdefineDirective({
- type: Directive,
- selectors: [['', 'dir', '']],
- factory: () => directive = new Directive(ΔdirectiveInject(ElementRef)),
- });
- }
-
- let directive: Directive;
-
- /**
- *
- */
- function Template() {
- ΔelementStart(0, 'div');
- {
- ΔelementContainerStart(1, [AttributeMarker.Bindings, 'dir']);
- ΔelementContainerEnd();
- }
- ΔelementEnd();
- }
-
- const fixture = new TemplateFixture(Template, () => {}, 2, 0, [Directive]);
- expect(fixture.html).toEqual('
');
- expect(directive !.elRef.nativeElement.nodeType).toBe(Node.COMMENT_NODE);
- });
-
- it('should support ViewContainerRef when ng-container is at the root of a view', () => {
-
- function ContentTemplate(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δtext(0, 'Content');
- }
- }
-
- class Directive {
- contentTpl: TemplateRef<{}>|null = null;
-
- constructor(private _vcRef: ViewContainerRef) {}
-
- insertView() { this._vcRef.createEmbeddedView(this.contentTpl as TemplateRef<{}>); }
-
- clear() { this._vcRef.clear(); }
-
- static ngDirectiveDef = ΔdefineDirective({
- type: Directive,
- selectors: [['', 'dir', '']],
- factory: () => directive = new Directive(ΔdirectiveInject(ViewContainerRef as any)),
- inputs: {contentTpl: 'contentTpl'},
- });
- }
-
- let directive: Directive;
-
- /**
- *
- * Content
- *
- */
- const App = createComponent('app', function(rf: RenderFlags) {
- if (rf & RenderFlags.Create) {
- ΔelementContainerStart(0, [AttributeMarker.Bindings, 'dir']);
- Δtemplate(
- 1, ContentTemplate, 1, 0, 'ng-template', null, ['content', ''],
- ΔtemplateRefExtractor);
- ΔelementContainerEnd();
- }
- if (rf & RenderFlags.Update) {
- const content = Δreference(2) as any;
- ΔelementProperty(0, 'contentTpl', Δbind(content));
- }
- }, 3, 1, [Directive]);
-
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual('');
-
- directive !.insertView();
- fixture.update();
- expect(fixture.html).toEqual('Content');
-
- directive !.clear();
- fixture.update();
- expect(fixture.html).toEqual('');
- });
-
- it('should support ViewContainerRef on inside ', () => {
- function ContentTemplate(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δtext(0, 'Content');
- }
- }
-
- class Directive {
- constructor(private _tplRef: TemplateRef<{}>, private _vcRef: ViewContainerRef) {}
-
- insertView() { this._vcRef.createEmbeddedView(this._tplRef); }
-
- clear() { this._vcRef.clear(); }
-
- static ngDirectiveDef = ΔdefineDirective({
- type: Directive,
- selectors: [['', 'dir', '']],
- factory:
- () => directive = new Directive(
- ΔdirectiveInject(TemplateRef as any), ΔdirectiveInject(ViewContainerRef as any)),
- });
- }
-
- let directive: Directive;
-
- /**
- *
- * Content
- *
- */
- const App = createComponent('app', function(rf: RenderFlags) {
- if (rf & RenderFlags.Create) {
- ΔelementContainerStart(0);
- Δtemplate(
- 1, ContentTemplate, 1, 0, 'ng-template', [AttributeMarker.Bindings, 'dir'], [],
- ΔtemplateRefExtractor);
- ΔelementContainerEnd();
- }
- }, 2, 0, [Directive]);
-
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual('');
-
- directive !.insertView();
- fixture.update();
- expect(fixture.html).toEqual('Content');
-
- directive !.clear();
- fixture.update();
- expect(fixture.html).toEqual('');
- });
-
- it('should not set any attributes', () => {
- /**
- *
- */
- function Template() {
- ΔelementStart(0, 'div');
- {
- ΔelementContainerStart(1, ['id', 'foo']);
- ΔelementContainerEnd();
- }
- ΔelementEnd();
- }
-
- const fixture = new TemplateFixture(Template, () => {}, 2);
- expect(fixture.html).toEqual('
');
- });
-
});
describe('tree', () => {
@@ -1232,920 +194,6 @@ describe('render3 integration test', () => {
});
- describe('element bindings', () => {
-
- describe('elementAttribute', () => {
- it('should support attribute bindings', () => {
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'span');
- }
- if (rf & RenderFlags.Update) {
- ΔelementAttribute(0, 'title', Δbind(ctx.title));
- }
- }, 1, 1);
-
- const fixture = new ComponentFixture(App);
- fixture.component.title = 'Hello';
- fixture.update();
- // initial binding
- expect(fixture.html).toEqual(' ');
-
- // update binding
- fixture.component.title = 'Hi!';
- fixture.update();
- expect(fixture.html).toEqual(' ');
-
- // remove attribute
- fixture.component.title = null;
- fixture.update();
- expect(fixture.html).toEqual(' ');
- });
-
- it('should stringify values used attribute bindings', () => {
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'span');
- }
- if (rf & RenderFlags.Update) {
- ΔelementAttribute(0, 'title', Δbind(ctx.title));
- }
- }, 1, 1);
-
- const fixture = new ComponentFixture(App);
- fixture.component.title = NaN;
- fixture.update();
- expect(fixture.html).toEqual(' ');
-
- fixture.component.title = {toString: () => 'Custom toString'};
- fixture.update();
- expect(fixture.html).toEqual(' ');
- });
-
- it('should update bindings', () => {
- function Template(rf: RenderFlags, c: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'b');
- }
- if (rf & RenderFlags.Update) {
- ΔelementAttribute(0, 'a', ΔinterpolationV(c));
- ΔelementAttribute(0, 'a0', Δbind(c[1]));
- ΔelementAttribute(0, 'a1', Δinterpolation1(c[0], c[1], c[16]));
- ΔelementAttribute(0, 'a2', Δinterpolation2(c[0], c[1], c[2], c[3], c[16]));
- ΔelementAttribute(0, 'a3', Δinterpolation3(c[0], c[1], c[2], c[3], c[4], c[5], c[16]));
- ΔelementAttribute(
- 0, 'a4', Δinterpolation4(c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[16]));
- ΔelementAttribute(
- 0, 'a5',
- Δinterpolation5(c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[16]));
- ΔelementAttribute(
- 0, 'a6', Δinterpolation6(
- c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[10],
- c[11], c[16]));
- ΔelementAttribute(
- 0, 'a7', Δinterpolation7(
- c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[10],
- c[11], c[12], c[13], c[16]));
- ΔelementAttribute(
- 0, 'a8', Δinterpolation8(
- c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[10],
- c[11], c[12], c[13], c[14], c[15], c[16]));
- }
- }
- let args = ['(', 0, 'a', 1, 'b', 2, 'c', 3, 'd', 4, 'e', 5, 'f', 6, 'g', 7, ')'];
- expect(renderToHtml(Template, args, 1, 54))
- .toEqual(
- ' ');
- args = args.reverse();
- expect(renderToHtml(Template, args, 1, 54))
- .toEqual(
- ' ');
- args = args.reverse();
- expect(renderToHtml(Template, args, 1, 54))
- .toEqual(
- ' ');
- });
-
- it('should not update DOM if context has not changed', () => {
- const ctx: {title: string | null} = {title: 'Hello'};
-
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'span');
- Δcontainer(1);
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ΔelementAttribute(0, 'title', Δbind(ctx.title));
- ΔcontainerRefreshStart(1);
- {
- if (true) {
- let rf1 = ΔembeddedViewStart(1, 1, 1);
- {
- if (rf1 & RenderFlags.Create) {
- ΔelementStart(0, 'b');
- {}
- ΔelementEnd();
- }
- if (rf1 & RenderFlags.Update) {
- ΔelementAttribute(0, 'title', Δbind(ctx.title));
- }
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- }, 2, 1);
-
- const fixture = new ComponentFixture(App);
- fixture.component.title = 'Hello';
- fixture.update();
- // initial binding
- expect(fixture.html).toEqual(' ');
- // update DOM manually
- fixture.hostElement.querySelector('b') !.setAttribute('title', 'Goodbye');
-
- // refresh with same binding
- fixture.update();
- expect(fixture.html).toEqual(' ');
-
- // refresh again with same binding
- fixture.update();
- expect(fixture.html).toEqual(' ');
- });
-
- it('should support host attribute bindings', () => {
- let hostBindingDir: HostBindingDir;
-
- class HostBindingDir {
- /* @HostBinding('attr.aria-label') */
- label = 'some label';
-
- static ngDirectiveDef = ΔdefineDirective({
- type: HostBindingDir,
- selectors: [['', 'hostBindingDir', '']],
- factory: function HostBindingDir_Factory() {
- return hostBindingDir = new HostBindingDir();
- },
- hostBindings: function HostBindingDir_HostBindings(
- rf: RenderFlags, ctx: any, elIndex: number) {
- if (rf & RenderFlags.Create) {
- ΔallocHostVars(1);
- }
- if (rf & RenderFlags.Update) {
- ΔelementAttribute(elIndex, 'aria-label', Δbind(ctx.label));
- }
- }
- });
- }
-
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'div', ['hostBindingDir', '']);
- }
- }, 1, 0, [HostBindingDir]);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual(`
`);
-
- hostBindingDir !.label = 'other label';
- fixture.update();
- expect(fixture.html).toEqual(`
`);
- });
- });
-
- describe('elementStyle', () => {
- it('should support binding to styles', () => {
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'span');
- Δstyling(null, ['border-color']);
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- Δselect(0);
- ΔstyleProp(0, ctx.color);
- ΔstylingApply();
- }
- }, 1);
-
- const fixture = new ComponentFixture(App);
- fixture.component.color = 'red';
- fixture.update();
- expect(fixture.html).toEqual(' ');
-
- fixture.component.color = 'green';
- fixture.update();
- expect(fixture.html).toEqual(' ');
-
- fixture.component.color = null;
- fixture.update();
- expect(fixture.html).toEqual(' ');
- });
-
- it('should support binding to styles with suffix', () => {
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'span');
- Δstyling(null, ['font-size']);
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- Δselect(0);
- ΔstyleProp(0, ctx.time, 'px');
- ΔstylingApply();
- }
- }, 1);
-
- const fixture = new ComponentFixture(App);
- fixture.component.time = '100';
- fixture.update();
- expect(fixture.html).toEqual(' ');
-
- fixture.component.time = 200;
- fixture.update();
- expect(fixture.html).toEqual(' ');
-
- fixture.component.time = 0;
- fixture.update();
- expect(fixture.html).toEqual(' ');
-
- fixture.component.time = null;
- fixture.update();
- expect(fixture.html).toEqual(' ');
- });
- });
-
- describe('class-based styling', () => {
- it('should support CSS class toggle', () => {
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'span');
- Δstyling(['active']);
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- Δselect(0);
- ΔclassProp(0, ctx.class);
- ΔstylingApply();
- }
- }, 1);
-
- const fixture = new ComponentFixture(App);
- fixture.component.class = true;
- fixture.update();
- expect(fixture.html).toEqual(' ');
-
- fixture.component.class = false;
- fixture.update();
- expect(fixture.html).toEqual(' ');
-
- // truthy values
- fixture.component.class = 'a_string';
- fixture.update();
- expect(fixture.html).toEqual(' ');
-
- fixture.component.class = 10;
- fixture.update();
- expect(fixture.html).toEqual(' ');
-
- // falsy values
- fixture.component.class = '';
- fixture.update();
- expect(fixture.html).toEqual(' ');
-
- fixture.component.class = 0;
- fixture.update();
- expect(fixture.html).toEqual(' ');
- });
-
- it('should work correctly with existing static classes', () => {
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'span', [AttributeMarker.Classes, 'existing']);
- Δstyling(['existing', 'active']);
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- Δselect(0);
- ΔclassProp(1, ctx.class);
- ΔstylingApply();
- }
- }, 1);
-
- const fixture = new ComponentFixture(App);
- fixture.component.class = true;
- fixture.update();
- expect(fixture.html).toEqual(' ');
-
- fixture.component.class = false;
- fixture.update();
- expect(fixture.html).toEqual(' ');
- });
-
- it('should apply classes properly when nodes are components', () => {
- const MyComp = createComponent('my-comp', (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- Δtext(0, 'Comp Content');
- }
- }, 1, 0, []);
-
- /**
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'my-comp');
- Δstyling(['active']);
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- Δselect(0);
- ΔclassProp(0, ctx.class);
- ΔstylingApply();
- }
- }, 1, 0, [MyComp]);
-
- const fixture = new ComponentFixture(App);
- fixture.component.class = true;
- fixture.update();
- expect(fixture.html).toEqual('Comp Content ');
-
- fixture.component.class = false;
- fixture.update();
- expect(fixture.html).toEqual('Comp Content ');
- });
-
- it('should apply classes properly when nodes have LContainers', () => {
- let structuralComp !: StructuralComp;
-
- class StructuralComp {
- tmp !: TemplateRef;
-
- constructor(public vcr: ViewContainerRef) {}
-
- create() { this.vcr.createEmbeddedView(this.tmp); }
-
- static ngComponentDef = ΔdefineComponent({
- type: StructuralComp,
- selectors: [['structural-comp']],
- factory: () => structuralComp =
- new StructuralComp(ΔdirectiveInject(ViewContainerRef as any)),
- inputs: {tmp: 'tmp'},
- consts: 1,
- vars: 0,
- template: (rf: RenderFlags, ctx: StructuralComp) => {
- if (rf & RenderFlags.Create) {
- Δtext(0, 'Comp Content');
- }
- }
- });
- }
-
- function FooTemplate(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δtext(0, 'Temp Content');
- }
- }
-
- /**
- *
- * Temp Content
- *
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δtemplate(
- 0, FooTemplate, 1, 0, 'ng-template', null, ['foo', ''], ΔtemplateRefExtractor);
- ΔelementStart(2, 'structural-comp');
- Δstyling(['active']);
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- const foo = Δreference(1) as any;
- Δselect(2);
- ΔclassProp(0, ctx.class);
- ΔstylingApply();
- ΔelementProperty(2, 'tmp', Δbind(foo));
- }
- }, 3, 1, [StructuralComp]);
-
- const fixture = new ComponentFixture(App);
- fixture.component.class = true;
- fixture.update();
- expect(fixture.html)
- .toEqual('Comp Content ');
-
- structuralComp.create();
- fixture.update();
- expect(fixture.html)
- .toEqual('Comp Content Temp Content');
-
- fixture.component.class = false;
- fixture.update();
- expect(fixture.html)
- .toEqual('Comp Content Temp Content');
- });
-
- let mockClassDirective: DirWithClassDirective;
- class DirWithClassDirective {
- static ngDirectiveDef = ΔdefineDirective({
- type: DirWithClassDirective,
- selectors: [['', 'DirWithClass', '']],
- factory: () => mockClassDirective = new DirWithClassDirective(),
- inputs: {'klass': 'class'}
- });
-
- public classesVal: string = '';
- set klass(value: string) { this.classesVal = value; }
- }
-
- let mockStyleDirective: DirWithStyleDirective;
- class DirWithStyleDirective {
- static ngDirectiveDef = ΔdefineDirective({
- type: DirWithStyleDirective,
- selectors: [['', 'DirWithStyle', '']],
- factory: () => mockStyleDirective = new DirWithStyleDirective(),
- inputs: {'style': 'style'}
- });
-
- public stylesVal: string = '';
- set style(value: string) { this.stylesVal = value; }
- }
-
- it('should delegate initial classes to a [class] input binding if present on a directive on the same element',
- () => {
- /**
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(
- 0, 'div',
- ['DirWithClass', '', AttributeMarker.Classes, 'apple', 'orange', 'banana']);
- Δstyling();
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- Δselect(0);
- ΔstylingApply();
- }
- }, 1, 0, [DirWithClassDirective]);
-
- const fixture = new ComponentFixture(App);
- expect(mockClassDirective !.classesVal).toEqual('apple orange banana');
- });
-
- it('should delegate initial styles to a [style] input binding if present on a directive on the same element',
- () => {
- /**
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'div', [
- 'DirWithStyle', '', AttributeMarker.Styles, 'width', '100px', 'height', '200px'
- ]);
- Δstyling();
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- Δselect(0);
- ΔstylingApply();
- }
- }, 1, 0, [DirWithStyleDirective]);
-
- const fixture = new ComponentFixture(App);
- expect(mockStyleDirective !.stylesVal).toEqual('width:100px;height:200px');
- });
-
- it('should update `[class]` and bindings in the provided directive if the input is matched',
- () => {
- /**
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'div', ['DirWithClass']);
- Δstyling();
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- Δselect(0);
- ΔclassMap('cucumber grape');
- ΔstylingApply();
- }
- }, 1, 0, [DirWithClassDirective]);
-
- const fixture = new ComponentFixture(App);
- expect(mockClassDirective !.classesVal).toEqual('cucumber grape');
- });
-
- it('should update `[style]` and bindings in the provided directive if the input is matched',
- () => {
- /**
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'div', ['DirWithStyle']);
- Δstyling();
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- Δselect(0);
- ΔstyleMap({width: '200px', height: '500px'});
- ΔstylingApply();
- }
- }, 1, 0, [DirWithStyleDirective]);
-
- const fixture = new ComponentFixture(App);
- expect(mockStyleDirective !.stylesVal).toEqual('width:200px;height:500px');
- });
-
- it('should apply initial styling to the element that contains the directive with host styling',
- () => {
- class DirWithInitialStyling {
- static ngDirectiveDef = ΔdefineDirective({
- type: DirWithInitialStyling,
- selectors: [['', 'DirWithInitialStyling', '']],
- factory: () => new DirWithInitialStyling(),
- hostBindings: function(
- rf: RenderFlags, ctx: DirWithInitialStyling, elementIndex: number) {
- if (rf & RenderFlags.Create) {
- ΔelementHostAttrs([
- 'title', 'foo', AttributeMarker.Classes, 'heavy', 'golden',
- AttributeMarker.Styles, 'color', 'purple', 'font-weight', 'bold'
- ]);
- }
- }
- });
-
- public classesVal: string = '';
- }
-
- /**
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'div', [
- 'DirWithInitialStyling', '', AttributeMarker.Classes, 'big',
- AttributeMarker.Styles, 'color', 'black', 'font-size', '200px'
- ]);
- }
- }, 1, 0, [DirWithInitialStyling]);
-
- const fixture = new ComponentFixture(App);
- const target = fixture.hostElement.querySelector('div') !;
- const classes = target.getAttribute('class') !.split(/\s+/).sort();
- expect(classes).toEqual(['big', 'golden', 'heavy']);
-
- expect(target.getAttribute('title')).toEqual('foo');
- expect(target.style.getPropertyValue('color')).toEqual('black');
- expect(target.style.getPropertyValue('font-size')).toEqual('200px');
- expect(target.style.getPropertyValue('font-weight')).toEqual('bold');
- });
-
- it('should apply single styling bindings present within a directive onto the same element and defer the element\'s initial styling values when missing',
- () => {
- let dirInstance: DirWithSingleStylingBindings;
- /**
- *
- */
- class DirWithSingleStylingBindings {
- static ngDirectiveDef = ΔdefineDirective({
- type: DirWithSingleStylingBindings,
- selectors: [['', 'DirWithSingleStylingBindings', '']],
- factory: () => dirInstance = new DirWithSingleStylingBindings(),
- hostBindings: function(
- rf: RenderFlags, ctx: DirWithSingleStylingBindings, elementIndex: number) {
- if (rf & RenderFlags.Create) {
- ΔelementHostAttrs(
- [AttributeMarker.Classes, 'def', AttributeMarker.Styles, 'width', '555px']);
- Δstyling(['xyz'], ['width', 'height']);
- }
- if (rf & RenderFlags.Update) {
- ΔstyleProp(0, ctx.width);
- ΔstyleProp(1, ctx.height);
- ΔclassProp(0, ctx.activateXYZClass);
- ΔstylingApply();
- }
- }
- });
-
- width: null|string = null;
- height: null|string = null;
- activateXYZClass: boolean = false;
- }
-
- /**
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'div', [
- 'DirWithSingleStylingBindings', '', AttributeMarker.Classes, 'abc',
- AttributeMarker.Styles, 'width', '100px', 'height', '200px'
- ]);
- }
- }, 1, 0, [DirWithSingleStylingBindings]);
-
- const fixture = new ComponentFixture(App);
- const target = fixture.hostElement.querySelector('div') !;
- expect(target.style.getPropertyValue('width')).toEqual('100px');
- expect(target.style.getPropertyValue('height')).toEqual('200px');
- expect(target.classList.contains('abc')).toBeTruthy();
- expect(target.classList.contains('def')).toBeTruthy();
- expect(target.classList.contains('xyz')).toBeFalsy();
-
- dirInstance !.width = '444px';
- dirInstance !.height = '999px';
- dirInstance !.activateXYZClass = true;
- fixture.update();
-
- expect(target.style.getPropertyValue('width')).toEqual('444px');
- expect(target.style.getPropertyValue('height')).toEqual('999px');
- expect(target.classList.contains('abc')).toBeTruthy();
- expect(target.classList.contains('def')).toBeTruthy();
- expect(target.classList.contains('xyz')).toBeTruthy();
-
- dirInstance !.width = null;
- dirInstance !.height = null;
- fixture.update();
-
- expect(target.style.getPropertyValue('width')).toEqual('100px');
- expect(target.style.getPropertyValue('height')).toEqual('200px');
- expect(target.classList.contains('abc')).toBeTruthy();
- expect(target.classList.contains('def')).toBeTruthy();
- expect(target.classList.contains('xyz')).toBeTruthy();
- });
-
- it('should properly prioritize single style binding collisions when they exist on multiple directives',
- () => {
- let dir1Instance: Dir1WithStyle;
- /**
- * Directive with host props:
- * [style.width]
- */
- class Dir1WithStyle {
- static ngDirectiveDef = ΔdefineDirective({
- type: Dir1WithStyle,
- selectors: [['', 'Dir1WithStyle', '']],
- factory: () => dir1Instance = new Dir1WithStyle(),
- hostBindings: function(rf: RenderFlags, ctx: Dir1WithStyle, elementIndex: number) {
- if (rf & RenderFlags.Create) {
- Δstyling(null, ['width']);
- }
- if (rf & RenderFlags.Update) {
- ΔstyleProp(0, ctx.width);
- ΔstylingApply();
- }
- }
- });
- width: null|string = null;
- }
-
- let dir2Instance: Dir2WithStyle;
- /**
- * Directive with host props:
- * [style.width]
- * style="width:111px"
- */
- class Dir2WithStyle {
- static ngDirectiveDef = ΔdefineDirective({
- type: Dir2WithStyle,
- selectors: [['', 'Dir2WithStyle', '']],
- factory: () => dir2Instance = new Dir2WithStyle(),
- hostBindings: function(rf: RenderFlags, ctx: Dir2WithStyle, elementIndex: number) {
- if (rf & RenderFlags.Create) {
- ΔelementHostAttrs([AttributeMarker.Styles, 'width', '111px']);
- Δstyling(null, ['width']);
- }
- if (rf & RenderFlags.Update) {
- ΔstyleProp(0, ctx.width);
- ΔstylingApply();
- }
- }
- });
- width: null|string = null;
- }
-
- /**
- * Component with the following template:
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'div', ['Dir1WithStyle', '', 'Dir2WithStyle', '']);
- Δstyling(null, ['width']);
- }
- if (rf & RenderFlags.Update) {
- Δselect(0);
- ΔstyleProp(0, ctx.width);
- ΔstylingApply();
- }
- }, 1, 0, [Dir1WithStyle, Dir2WithStyle]);
-
- const fixture = new ComponentFixture(App);
- const target = fixture.hostElement.querySelector('div') !;
- expect(target.style.getPropertyValue('width')).toEqual('111px');
-
- fixture.component.width = '999px';
- dir1Instance !.width = '222px';
- dir2Instance !.width = '333px';
- fixture.update();
- expect(target.style.getPropertyValue('width')).toEqual('999px');
-
- fixture.component.width = null;
- fixture.update();
- expect(target.style.getPropertyValue('width')).toEqual('222px');
-
- dir1Instance !.width = null;
- fixture.update();
- expect(target.style.getPropertyValue('width')).toEqual('333px');
-
- dir2Instance !.width = null;
- fixture.update();
- expect(target.style.getPropertyValue('width')).toEqual('111px');
-
- dir1Instance !.width = '666px';
- fixture.update();
- expect(target.style.getPropertyValue('width')).toEqual('666px');
-
- fixture.component.width = '777px';
- fixture.update();
- expect(target.style.getPropertyValue('width')).toEqual('777px');
- });
-
- it('should properly prioritize multi style binding collisions when they exist on multiple directives',
- () => {
- let dir1Instance: Dir1WithStyling;
- /**
- * Directive with host props:
- * [style]
- * [class]
- */
- class Dir1WithStyling {
- static ngDirectiveDef = ΔdefineDirective({
- type: Dir1WithStyling,
- selectors: [['', 'Dir1WithStyling', '']],
- factory: () => dir1Instance = new Dir1WithStyling(),
- hostBindings: function(rf: RenderFlags, ctx: Dir1WithStyling, elementIndex: number) {
- if (rf & RenderFlags.Create) {
- Δstyling();
- }
- if (rf & RenderFlags.Update) {
- ΔstyleMap(ctx.stylesExp);
- ΔclassMap(ctx.classesExp);
- ΔstylingApply();
- }
- }
- });
-
- classesExp: any = {};
- stylesExp: any = {};
- }
-
- let dir2Instance: Dir2WithStyling;
- /**
- * Directive with host props:
- * [style]
- * style="width:111px"
- */
- class Dir2WithStyling {
- static ngDirectiveDef = ΔdefineDirective({
- type: Dir2WithStyling,
- selectors: [['', 'Dir2WithStyling', '']],
- factory: () => dir2Instance = new Dir2WithStyling(),
- hostBindings: function(rf: RenderFlags, ctx: Dir2WithStyling, elementIndex: number) {
- if (rf & RenderFlags.Create) {
- ΔelementHostAttrs([AttributeMarker.Styles, 'width', '111px']);
- Δstyling();
- }
- if (rf & RenderFlags.Update) {
- ΔstyleMap(ctx.stylesExp);
- ΔstylingApply();
- }
- }
- });
-
- stylesExp: any = {};
- }
-
- /**
- * Component with the following template:
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'div', ['Dir1WithStyling', '', 'Dir2WithStyling', '']);
- Δstyling();
- }
- if (rf & RenderFlags.Update) {
- Δselect(0);
- ΔstyleMap(ctx.stylesExp);
- ΔclassMap(ctx.classesExp);
- ΔstylingApply();
- }
- }, 1, 0, [Dir1WithStyling, Dir2WithStyling]);
-
- const fixture = new ComponentFixture(App);
- const target = fixture.hostElement.querySelector('div') !;
- expect(target.style.getPropertyValue('width')).toEqual('111px');
-
- const compInstance = fixture.component;
- compInstance.stylesExp = {width: '999px', height: null};
- compInstance.classesExp = {one: true, two: false};
- dir1Instance !.stylesExp = {width: '222px'};
- dir1Instance !.classesExp = {two: true, three: false};
- dir2Instance !.stylesExp = {width: '333px', height: '100px'};
- fixture.update();
- expect(target.style.getPropertyValue('width')).toEqual('999px');
- expect(target.style.getPropertyValue('height')).toEqual('100px');
- expect(target.classList.contains('one')).toBeTruthy();
- expect(target.classList.contains('two')).toBeFalsy();
- expect(target.classList.contains('three')).toBeFalsy();
-
- compInstance.stylesExp = {};
- compInstance !.classesExp = {};
- dir1Instance !.stylesExp = {width: '222px', height: '200px'};
- fixture.update();
- expect(target.style.getPropertyValue('width')).toEqual('222px');
- expect(target.style.getPropertyValue('height')).toEqual('200px');
- expect(target.classList.contains('one')).toBeFalsy();
- expect(target.classList.contains('two')).toBeTruthy();
- expect(target.classList.contains('three')).toBeFalsy();
-
- dir1Instance !.stylesExp = {};
- dir1Instance !.classesExp = {};
- fixture.update();
- expect(target.style.getPropertyValue('width')).toEqual('333px');
- expect(target.style.getPropertyValue('height')).toEqual('100px');
- expect(target.classList.contains('one')).toBeFalsy();
- expect(target.classList.contains('two')).toBeFalsy();
- expect(target.classList.contains('three')).toBeFalsy();
-
- dir2Instance !.stylesExp = {};
- compInstance.stylesExp = {height: '900px'};
- fixture.update();
- expect(target.style.getPropertyValue('width')).toEqual('111px');
- expect(target.style.getPropertyValue('height')).toEqual('900px');
-
- dir1Instance !.stylesExp = {width: '666px', height: '600px'};
- dir1Instance !.classesExp = {four: true, one: true};
- fixture.update();
- expect(target.style.getPropertyValue('width')).toEqual('666px');
- expect(target.style.getPropertyValue('height')).toEqual('900px');
- expect(target.classList.contains('one')).toBeTruthy();
- expect(target.classList.contains('two')).toBeFalsy();
- expect(target.classList.contains('three')).toBeFalsy();
- expect(target.classList.contains('four')).toBeTruthy();
-
- compInstance.stylesExp = {width: '777px'};
- compInstance.classesExp = {four: false};
- fixture.update();
- expect(target.style.getPropertyValue('width')).toEqual('777px');
- expect(target.style.getPropertyValue('height')).toEqual('600px');
- expect(target.classList.contains('one')).toBeTruthy();
- expect(target.classList.contains('two')).toBeFalsy();
- expect(target.classList.contains('three')).toBeFalsy();
- expect(target.classList.contains('four')).toBeFalsy();
- });
- });
-
- it('should properly handle and render interpolation for class attribute bindings', () => {
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'div');
- Δstyling();
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- Δselect(0);
- ΔclassMap(Δinterpolation2('-', ctx.name, '-', ctx.age, '-'));
- ΔstylingApply();
- }
- }, 1, 2);
-
- const fixture = new ComponentFixture(App);
- const target = fixture.hostElement.querySelector('div') !;
- expect(target.classList.contains('-fred-36-')).toBeFalsy();
-
- fixture.component.name = 'fred';
- fixture.component.age = '36';
- fixture.update();
-
- expect(target.classList.contains('-fred-36-')).toBeTruthy();
- });
- });
});
describe('template data', () => {