upstream: Merge remote-tracking branch 'upstream/master' into merge-upstream

# Conflicts:
#	CHANGELOG.md
#	aio/content/examples/testing/src/app/app.component.router.spec.ts
#	aio/content/examples/testing/src/app/dashboard/dashboard-hero.component.spec.ts
#	aio/content/examples/testing/src/app/dashboard/dashboard.component.spec.ts
#	aio/content/examples/testing/src/app/hero/hero-detail.component.spec.ts
#	aio/content/examples/testing/src/app/hero/hero-list.component.spec.ts
#	aio/content/examples/testing/src/app/twain/twain.component.spec.ts
#	goldens/public-api/core/testing/testing.d.ts
#	goldens/size-tracking/aio-payloads.json
#	package.json
#	packages/core/test/bundling/forms/bundle.golden_symbols.json
#	packages/forms/test/form_group_spec.ts
This commit is contained in:
Michael Prentice
2020-08-15 20:48:38 -04:00
159 changed files with 4962 additions and 2601 deletions

View File

@ -111,26 +111,10 @@ export class BrowserViewportScroller implements ViewportScroller {
*/
scrollToAnchor(anchor: string): void {
if (this.supportScrollRestoration()) {
// Escape anything passed to `querySelector` as it can throw errors and stop the application
// from working if invalid values are passed.
if (this.window.CSS && this.window.CSS.escape) {
anchor = this.window.CSS.escape(anchor);
} else {
anchor = anchor.replace(/(\"|\'\ |:|\.|\[|\]|,|=)/g, '\\$1');
}
try {
const elSelectedById = this.document.querySelector(`#${anchor}`);
if (elSelectedById) {
this.scrollToElement(elSelectedById);
return;
}
const elSelectedByName = this.document.querySelector(`[name='${anchor}']`);
if (elSelectedByName) {
this.scrollToElement(elSelectedByName);
return;
}
} catch (e) {
this.errorHandler.handleError(e);
const elSelected =
this.document.getElementById(anchor) || this.document.getElementsByName(anchor)[0];
if (elSelected) {
this.scrollToElement(elSelected);
}
}
}
@ -165,13 +149,25 @@ export class BrowserViewportScroller implements ViewportScroller {
*/
private supportScrollRestoration(): boolean {
try {
return !!this.window && !!this.window.scrollTo;
if (!this.window || !this.window.scrollTo) {
return false;
}
// The `scrollRestoration` property could be on the `history` instance or its prototype.
const scrollRestorationDescriptor = getScrollRestorationProperty(this.window.history) ||
getScrollRestorationProperty(Object.getPrototypeOf(this.window.history));
// We can write to the `scrollRestoration` property if it is a writable data field or it has a
// setter function.
return !!scrollRestorationDescriptor &&
!!(scrollRestorationDescriptor.writable || scrollRestorationDescriptor.set);
} catch {
return false;
}
}
}
function getScrollRestorationProperty(obj: any): PropertyDescriptor|undefined {
return Object.getOwnPropertyDescriptor(obj, 'scrollRestoration');
}
/**
* Provides an empty implementation of the viewport scroller. This will

View File

@ -7,7 +7,7 @@
*/
import {Component} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
{
describe('binding to CSS class list', () => {
@ -37,7 +37,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
});
});
it('should clean up when the directive is destroyed', async(() => {
it('should clean up when the directive is destroyed', waitForAsync(() => {
fixture = createTestComponent('<div *ngFor="let item of items" [ngClass]="item"></div>');
getComponent().items = [['0']];
@ -47,21 +47,22 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
}));
describe('expressions evaluating to objects', () => {
it('should add classes specified in an object literal', async(() => {
it('should add classes specified in an object literal', waitForAsync(() => {
fixture = createTestComponent('<div [ngClass]="{foo: true, bar: false}"></div>');
detectChangesAndExpectClassName('foo');
}));
it('should add classes specified in an object literal without change in class names',
async(() => {
waitForAsync(() => {
fixture =
createTestComponent(`<div [ngClass]="{'foo-bar': true, 'fooBar': true}"></div>`);
detectChangesAndExpectClassName('foo-bar fooBar');
}));
it('should add and remove classes based on changes in object literal values', async(() => {
it('should add and remove classes based on changes in object literal values',
waitForAsync(() => {
fixture =
createTestComponent('<div [ngClass]="{foo: condition, bar: !condition}"></div>');
@ -71,7 +72,8 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
detectChangesAndExpectClassName('bar');
}));
it('should add and remove classes based on changes to the expression object', async(() => {
it('should add and remove classes based on changes to the expression object',
waitForAsync(() => {
fixture = createTestComponent('<div [ngClass]="objExpr"></div>');
const objExpr = getComponent().objExpr;
@ -88,7 +90,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
}));
it('should add and remove classes based on reference changes to the expression object',
async(() => {
waitForAsync(() => {
fixture = createTestComponent('<div [ngClass]="objExpr"></div>');
detectChangesAndExpectClassName('foo');
@ -100,7 +102,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
detectChangesAndExpectClassName('baz');
}));
it('should remove active classes when expression evaluates to null', async(() => {
it('should remove active classes when expression evaluates to null', waitForAsync(() => {
fixture = createTestComponent('<div [ngClass]="objExpr"></div>');
detectChangesAndExpectClassName('foo');
@ -113,7 +115,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
}));
it('should allow multiple classes per expression', async(() => {
it('should allow multiple classes per expression', waitForAsync(() => {
fixture = createTestComponent('<div [ngClass]="objExpr"></div>');
getComponent().objExpr = {'bar baz': true, 'bar1 baz1': true};
@ -123,7 +125,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
detectChangesAndExpectClassName('bar1 baz1');
}));
it('should split by one or more spaces between classes', async(() => {
it('should split by one or more spaces between classes', waitForAsync(() => {
fixture = createTestComponent('<div [ngClass]="objExpr"></div>');
getComponent().objExpr = {'foo bar baz': true};
@ -132,14 +134,14 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
});
describe('expressions evaluating to lists', () => {
it('should add classes specified in a list literal', async(() => {
it('should add classes specified in a list literal', waitForAsync(() => {
fixture =
createTestComponent(`<div [ngClass]="['foo', 'bar', 'foo-bar', 'fooBar']"></div>`);
detectChangesAndExpectClassName('foo bar foo-bar fooBar');
}));
it('should add and remove classes based on changes to the expression', async(() => {
it('should add and remove classes based on changes to the expression', waitForAsync(() => {
fixture = createTestComponent('<div [ngClass]="arrExpr"></div>');
const arrExpr = getComponent().arrExpr;
detectChangesAndExpectClassName('foo');
@ -154,7 +156,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
detectChangesAndExpectClassName('foo');
}));
it('should add and remove classes when a reference changes', async(() => {
it('should add and remove classes when a reference changes', waitForAsync(() => {
fixture = createTestComponent('<div [ngClass]="arrExpr"></div>');
detectChangesAndExpectClassName('foo');
@ -162,7 +164,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
detectChangesAndExpectClassName('bar');
}));
it('should take initial classes into account when a reference changes', async(() => {
it('should take initial classes into account when a reference changes', waitForAsync(() => {
fixture = createTestComponent('<div class="foo" [ngClass]="arrExpr"></div>');
detectChangesAndExpectClassName('foo');
@ -170,13 +172,13 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
detectChangesAndExpectClassName('foo bar');
}));
it('should ignore empty or blank class names', async(() => {
it('should ignore empty or blank class names', waitForAsync(() => {
fixture = createTestComponent('<div class="foo" [ngClass]="arrExpr"></div>');
getComponent().arrExpr = ['', ' '];
detectChangesAndExpectClassName('foo');
}));
it('should trim blanks from class names', async(() => {
it('should trim blanks from class names', waitForAsync(() => {
fixture = createTestComponent('<div class="foo" [ngClass]="arrExpr"></div>');
getComponent().arrExpr = [' bar '];
@ -184,7 +186,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
}));
it('should allow multiple classes per item in arrays', async(() => {
it('should allow multiple classes per item in arrays', waitForAsync(() => {
fixture = createTestComponent('<div [ngClass]="arrExpr"></div>');
getComponent().arrExpr = ['foo bar baz', 'foo1 bar1 baz1'];
@ -203,7 +205,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
});
describe('expressions evaluating to sets', () => {
it('should add and remove classes if the set instance changed', async(() => {
it('should add and remove classes if the set instance changed', waitForAsync(() => {
fixture = createTestComponent('<div [ngClass]="setExpr"></div>');
let setExpr = new Set<string>();
setExpr.add('bar');
@ -218,12 +220,12 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
});
describe('expressions evaluating to string', () => {
it('should add classes specified in a string literal', async(() => {
it('should add classes specified in a string literal', waitForAsync(() => {
fixture = createTestComponent(`<div [ngClass]="'foo bar foo-bar fooBar'"></div>`);
detectChangesAndExpectClassName('foo bar foo-bar fooBar');
}));
it('should add and remove classes based on changes to the expression', async(() => {
it('should add and remove classes based on changes to the expression', waitForAsync(() => {
fixture = createTestComponent('<div [ngClass]="strExpr"></div>');
detectChangesAndExpectClassName('foo');
@ -235,7 +237,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
detectChangesAndExpectClassName('baz');
}));
it('should remove active classes when switching from string to null', async(() => {
it('should remove active classes when switching from string to null', waitForAsync(() => {
fixture = createTestComponent(`<div [ngClass]="strExpr"></div>`);
detectChangesAndExpectClassName('foo');
@ -244,7 +246,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
}));
it('should take initial classes into account when switching from string to null',
async(() => {
waitForAsync(() => {
fixture = createTestComponent(`<div class="foo" [ngClass]="strExpr"></div>`);
detectChangesAndExpectClassName('foo');
@ -252,7 +254,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
detectChangesAndExpectClassName('foo');
}));
it('should ignore empty and blank strings', async(() => {
it('should ignore empty and blank strings', waitForAsync(() => {
fixture = createTestComponent(`<div class="foo" [ngClass]="strExpr"></div>`);
getComponent().strExpr = '';
detectChangesAndExpectClassName('foo');
@ -260,7 +262,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
});
describe('cooperation with other class-changing constructs', () => {
it('should co-operate with the class attribute', async(() => {
it('should co-operate with the class attribute', waitForAsync(() => {
fixture = createTestComponent('<div [ngClass]="objExpr" class="init foo"></div>');
const objExpr = getComponent().objExpr;
@ -274,7 +276,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
detectChangesAndExpectClassName('init foo');
}));
it('should co-operate with the interpolated class attribute', async(() => {
it('should co-operate with the interpolated class attribute', waitForAsync(() => {
fixture = createTestComponent(`<div [ngClass]="objExpr" class="{{'init foo'}}"></div>`);
const objExpr = getComponent().objExpr;
@ -289,7 +291,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
}));
it('should co-operate with the interpolated class attribute when interpolation changes',
async(() => {
waitForAsync(() => {
fixture = createTestComponent(
`<div [ngClass]="{large: false, small: true}" class="{{strExpr}}"></div>`);
@ -299,7 +301,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
detectChangesAndExpectClassName(`bar small`);
}));
it('should co-operate with the class attribute and binding to it', async(() => {
it('should co-operate with the class attribute and binding to it', waitForAsync(() => {
fixture =
createTestComponent(`<div [ngClass]="objExpr" class="init" [class]="'foo'"></div>`);
const objExpr = getComponent().objExpr;
@ -314,7 +316,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
detectChangesAndExpectClassName(`init foo`);
}));
it('should co-operate with the class attribute and class.name binding', async(() => {
it('should co-operate with the class attribute and class.name binding', waitForAsync(() => {
const template =
'<div class="init foo" [ngClass]="objExpr" [class.baz]="condition"></div>';
fixture = createTestComponent(template);
@ -333,7 +335,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
}));
it('should co-operate with initial class and class attribute binding when binding changes',
async(() => {
waitForAsync(() => {
const template = '<div class="init" [ngClass]="objExpr" [class]="strExpr"></div>';
fixture = createTestComponent(template);
const cmp = getComponent();

View File

@ -9,7 +9,7 @@
import {CommonModule} from '@angular/common';
import {NgComponentOutlet} from '@angular/common/src/directives/ng_component_outlet';
import {Compiler, Component, ComponentRef, Inject, InjectionToken, Injector, NgModule, NgModuleFactory, NO_ERRORS_SCHEMA, Optional, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {async, TestBed} from '@angular/core/testing';
import {TestBed, waitForAsync} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
describe('insert/remove', () => {
@ -17,7 +17,7 @@ describe('insert/remove', () => {
TestBed.configureTestingModule({imports: [TestModule]});
});
it('should do nothing if component is null', async(() => {
it('should do nothing if component is null', waitForAsync(() => {
const template = `<ng-template *ngComponentOutlet="currentComponent"></ng-template>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
@ -28,7 +28,7 @@ describe('insert/remove', () => {
expect(fixture.nativeElement).toHaveText('');
}));
it('should insert content specified by a component', async(() => {
it('should insert content specified by a component', waitForAsync(() => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
@ -40,7 +40,7 @@ describe('insert/remove', () => {
expect(fixture.nativeElement).toHaveText('foo');
}));
it('should emit a ComponentRef once a component was created', async(() => {
it('should emit a ComponentRef once a component was created', waitForAsync(() => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
@ -56,7 +56,7 @@ describe('insert/remove', () => {
}));
it('should clear view if component becomes null', async(() => {
it('should clear view if component becomes null', waitForAsync(() => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
@ -74,7 +74,7 @@ describe('insert/remove', () => {
}));
it('should swap content if component changes', async(() => {
it('should swap content if component changes', waitForAsync(() => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
@ -91,7 +91,7 @@ describe('insert/remove', () => {
expect(fixture.nativeElement).toHaveText('bar');
}));
it('should use the injector, if one supplied', async(() => {
it('should use the injector, if one supplied', waitForAsync(() => {
let fixture = TestBed.createComponent(TestComponent);
const uniqueValue = {};
@ -107,7 +107,7 @@ describe('insert/remove', () => {
}));
it('should resolve with an injector', async(() => {
it('should resolve with an injector', waitForAsync(() => {
let fixture = TestBed.createComponent(TestComponent);
// We are accessing a ViewChild (ngComponentOutlet) before change detection has run
@ -120,7 +120,7 @@ describe('insert/remove', () => {
expect(cmpRef.instance.testToken).toBeNull();
}));
it('should render projectable nodes, if supplied', async(() => {
it('should render projectable nodes, if supplied', waitForAsync(() => {
const template = `<ng-template>projected foo</ng-template>${TEST_CMP_TEMPLATE}`;
TestBed.overrideComponent(TestComponent, {set: {template: template}})
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
@ -144,7 +144,7 @@ describe('insert/remove', () => {
expect(fixture.nativeElement).toHaveText('projected foo');
}));
it('should resolve components from other modules, if supplied', async(() => {
it('should resolve components from other modules, if supplied', waitForAsync(() => {
const compiler = TestBed.inject(Compiler);
let fixture = TestBed.createComponent(TestComponent);
@ -158,7 +158,7 @@ describe('insert/remove', () => {
expect(fixture.nativeElement).toHaveText('baz');
}));
it('should clean up moduleRef, if supplied', async(() => {
it('should clean up moduleRef, if supplied', waitForAsync(() => {
let destroyed = false;
const compiler = TestBed.inject(Compiler);
const fixture = TestBed.createComponent(TestComponent);
@ -174,7 +174,7 @@ describe('insert/remove', () => {
expect(moduleRef.destroy).toHaveBeenCalled();
}));
it('should not re-create moduleRef when it didn\'t actually change', async(() => {
it('should not re-create moduleRef when it didn\'t actually change', waitForAsync(() => {
const compiler = TestBed.inject(Compiler);
const fixture = TestBed.createComponent(TestComponent);
@ -191,7 +191,7 @@ describe('insert/remove', () => {
expect(moduleRef).toBe(fixture.componentInstance.ngComponentOutlet['_moduleRef']);
}));
it('should re-create moduleRef when changed', async(() => {
it('should re-create moduleRef when changed', waitForAsync(() => {
const compiler = TestBed.inject(Compiler);
const fixture = TestBed.createComponent(TestComponent);
fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);

View File

@ -8,7 +8,7 @@
import {CommonModule} from '@angular/common';
import {Component} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -38,27 +38,27 @@ let thisArg: any;
});
});
it('should reflect initial elements', async(() => {
it('should reflect initial elements', waitForAsync(() => {
fixture = createTestComponent();
detectChangesAndExpectText('1;2;');
}));
it('should reflect added elements', async(() => {
it('should reflect added elements', waitForAsync(() => {
fixture = createTestComponent();
fixture.detectChanges();
getComponent().items.push(3);
detectChangesAndExpectText('1;2;3;');
}));
it('should reflect removed elements', async(() => {
it('should reflect removed elements', waitForAsync(() => {
fixture = createTestComponent();
fixture.detectChanges();
getComponent().items.splice(1, 1);
detectChangesAndExpectText('1;');
}));
it('should reflect moved elements', async(() => {
it('should reflect moved elements', waitForAsync(() => {
fixture = createTestComponent();
fixture.detectChanges();
getComponent().items.splice(0, 1);
@ -66,7 +66,7 @@ let thisArg: any;
detectChangesAndExpectText('2;1;');
}));
it('should reflect a mix of all changes (additions/removals/moves)', async(() => {
it('should reflect a mix of all changes (additions/removals/moves)', waitForAsync(() => {
fixture = createTestComponent();
getComponent().items = [0, 1, 2, 3, 4, 5];
@ -77,7 +77,7 @@ let thisArg: any;
detectChangesAndExpectText('6;2;7;0;4;8;');
}));
it('should iterate over an array of objects', async(() => {
it('should iterate over an array of objects', waitForAsync(() => {
const template = '<ul><li *ngFor="let item of items">{{item["name"]}};</li></ul>';
fixture = createTestComponent(template);
@ -95,14 +95,14 @@ let thisArg: any;
detectChangesAndExpectText('shyam;');
}));
it('should gracefully handle nulls', async(() => {
it('should gracefully handle nulls', waitForAsync(() => {
const template = '<ul><li *ngFor="let item of null">{{item}};</li></ul>';
fixture = createTestComponent(template);
detectChangesAndExpectText('');
}));
it('should gracefully handle ref changing to null and back', async(() => {
it('should gracefully handle ref changing to null and back', waitForAsync(() => {
fixture = createTestComponent();
detectChangesAndExpectText('1;2;');
@ -114,7 +114,7 @@ let thisArg: any;
detectChangesAndExpectText('1;2;3;');
}));
it('should throw on non-iterable ref and suggest using an array', async(() => {
it('should throw on non-iterable ref and suggest using an array', waitForAsync(() => {
fixture = createTestComponent();
getComponent().items = <any>'whaaa';
@ -123,7 +123,7 @@ let thisArg: any;
/Cannot find a differ supporting object 'whaaa' of type 'string'. NgFor only supports binding to Iterables such as Arrays/);
}));
it('should throw on ref changing to string', async(() => {
it('should throw on ref changing to string', waitForAsync(() => {
fixture = createTestComponent();
detectChangesAndExpectText('1;2;');
@ -132,7 +132,7 @@ let thisArg: any;
expect(() => fixture.detectChanges()).toThrowError();
}));
it('should works with duplicates', async(() => {
it('should works with duplicates', waitForAsync(() => {
fixture = createTestComponent();
const a = new Foo();
@ -140,7 +140,7 @@ let thisArg: any;
detectChangesAndExpectText('foo;foo;');
}));
it('should repeat over nested arrays', async(() => {
it('should repeat over nested arrays', waitForAsync(() => {
const template = '<div *ngFor="let item of items">' +
'<div *ngFor="let subitem of item">{{subitem}}-{{item.length}};</div>|' +
'</div>';
@ -153,7 +153,7 @@ let thisArg: any;
detectChangesAndExpectText('e-1;|f-2;g-2;|');
}));
it('should repeat over nested arrays with no intermediate element', async(() => {
it('should repeat over nested arrays with no intermediate element', waitForAsync(() => {
const template = '<div *ngFor="let item of items">' +
'<div *ngFor="let subitem of item">{{subitem}}-{{item.length}};</div>' +
'</div>';
@ -166,7 +166,8 @@ let thisArg: any;
detectChangesAndExpectText('e-1;f-2;g-2;');
}));
it('should repeat over nested ngIf that are the last node in the ngFor template', async(() => {
it('should repeat over nested ngIf that are the last node in the ngFor template',
waitForAsync(() => {
const template = `<div *ngFor="let item of items; let i=index">` +
`<div>{{i}}|</div>` +
`<div *ngIf="i % 2 == 0">even|</div>` +
@ -185,7 +186,7 @@ let thisArg: any;
detectChangesAndExpectText('0|even|1|2|even|');
}));
it('should allow of saving the collection', async(() => {
it('should allow of saving the collection', waitForAsync(() => {
const template =
'<ul><li *ngFor="let item of items as collection; index as i">{{i}}/{{collection.length}} - {{item}};</li></ul>';
fixture = createTestComponent(template);
@ -196,7 +197,7 @@ let thisArg: any;
detectChangesAndExpectText('0/3 - 1;1/3 - 2;2/3 - 3;');
}));
it('should display indices correctly', async(() => {
it('should display indices correctly', waitForAsync(() => {
const template = '<span *ngFor ="let item of items; let i=index">{{i.toString()}}</span>';
fixture = createTestComponent(template);
@ -207,7 +208,7 @@ let thisArg: any;
detectChangesAndExpectText('0123456789');
}));
it('should display count correctly', async(() => {
it('should display count correctly', waitForAsync(() => {
const template = '<span *ngFor="let item of items; let len=count">{{len}}</span>';
fixture = createTestComponent(template);
@ -218,7 +219,7 @@ let thisArg: any;
detectChangesAndExpectText('666666');
}));
it('should display first item correctly', async(() => {
it('should display first item correctly', waitForAsync(() => {
const template =
'<span *ngFor="let item of items; let isFirst=first">{{isFirst.toString()}}</span>';
fixture = createTestComponent(template);
@ -230,7 +231,7 @@ let thisArg: any;
detectChangesAndExpectText('truefalse');
}));
it('should display last item correctly', async(() => {
it('should display last item correctly', waitForAsync(() => {
const template =
'<span *ngFor="let item of items; let isLast=last">{{isLast.toString()}}</span>';
fixture = createTestComponent(template);
@ -242,7 +243,7 @@ let thisArg: any;
detectChangesAndExpectText('falsetrue');
}));
it('should display even items correctly', async(() => {
it('should display even items correctly', waitForAsync(() => {
const template =
'<span *ngFor="let item of items; let isEven=even">{{isEven.toString()}}</span>';
fixture = createTestComponent(template);
@ -254,7 +255,7 @@ let thisArg: any;
detectChangesAndExpectText('truefalse');
}));
it('should display odd items correctly', async(() => {
it('should display odd items correctly', waitForAsync(() => {
const template =
'<span *ngFor="let item of items; let isOdd=odd">{{isOdd.toString()}}</span>';
fixture = createTestComponent(template);
@ -266,7 +267,7 @@ let thisArg: any;
detectChangesAndExpectText('falsetrue');
}));
it('should allow to use a custom template', async(() => {
it('should allow to use a custom template', waitForAsync(() => {
const template =
'<ng-container *ngFor="let item of items; template: tpl"></ng-container>' +
'<ng-template let-item let-i="index" #tpl><p>{{i}}: {{item}};</p></ng-template>';
@ -276,7 +277,7 @@ let thisArg: any;
detectChangesAndExpectText('0: a;1: b;2: c;');
}));
it('should use a default template if a custom one is null', async(() => {
it('should use a default template if a custom one is null', waitForAsync(() => {
const template =
`<ul><ng-container *ngFor="let item of items; template: null; let i=index">{{i}}: {{item}};</ng-container></ul>`;
fixture = createTestComponent(template);
@ -285,7 +286,8 @@ let thisArg: any;
detectChangesAndExpectText('0: a;1: b;2: c;');
}));
it('should use a custom template when both default and a custom one are present', async(() => {
it('should use a custom template when both default and a custom one are present',
waitForAsync(() => {
const template =
'<ng-container *ngFor="let item of items; template: tpl">{{i}};</ng-container>' +
'<ng-template let-item let-i="index" #tpl>{{i}}: {{item}};</ng-template>';
@ -296,7 +298,7 @@ let thisArg: any;
}));
describe('track by', () => {
it('should console.warn if trackBy is not a function', async(() => {
it('should console.warn if trackBy is not a function', waitForAsync(() => {
// TODO(vicb): expect a warning message when we have a proper log service
const template = `<p *ngFor="let item of items; trackBy: value"></p>`;
fixture = createTestComponent(template);
@ -304,7 +306,7 @@ let thisArg: any;
fixture.detectChanges();
}));
it('should track by identity when trackBy is to `null` or `undefined`', async(() => {
it('should track by identity when trackBy is to `null` or `undefined`', waitForAsync(() => {
// TODO(vicb): expect no warning message when we have a proper log service
const template = `<p *ngFor="let item of items; trackBy: value">{{ item }}</p>`;
fixture = createTestComponent(template);
@ -315,7 +317,7 @@ let thisArg: any;
detectChangesAndExpectText('abc');
}));
it('should set the context to the component instance', async(() => {
it('should set the context to the component instance', waitForAsync(() => {
const template =
`<p *ngFor="let item of items; trackBy: trackByContext.bind(this)"></p>`;
fixture = createTestComponent(template);
@ -325,7 +327,7 @@ let thisArg: any;
expect(thisArg).toBe(getComponent());
}));
it('should not replace tracked items', async(() => {
it('should not replace tracked items', waitForAsync(() => {
const template =
`<p *ngFor="let item of items; trackBy: trackById; let i=index">{{items[i]}}</p>`;
fixture = createTestComponent(template);
@ -341,7 +343,7 @@ let thisArg: any;
expect(finalP.nativeElement).toBe(firstP.nativeElement);
}));
it('should update implicit local variable on view', async(() => {
it('should update implicit local variable on view', waitForAsync(() => {
const template =
`<div *ngFor="let item of items; trackBy: trackById">{{item['color']}}</div>`;
fixture = createTestComponent(template);
@ -353,7 +355,7 @@ let thisArg: any;
detectChangesAndExpectText('red');
}));
it('should move items around and keep them updated ', async(() => {
it('should move items around and keep them updated ', waitForAsync(() => {
const template =
`<div *ngFor="let item of items; trackBy: trackById">{{item['color']}}</div>`;
fixture = createTestComponent(template);
@ -365,7 +367,8 @@ let thisArg: any;
detectChangesAndExpectText('orangered');
}));
it('should handle added and removed items properly when tracking by index', async(() => {
it('should handle added and removed items properly when tracking by index',
waitForAsync(() => {
const template = `<div *ngFor="let item of items; trackBy: trackByIndex">{{item}}</div>`;
fixture = createTestComponent(template);

View File

@ -8,7 +8,7 @@
import {CommonModule, ɵgetDOM as getDOM} from '@angular/common';
import {Component} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -31,7 +31,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
});
});
it('should work in a template attribute', async(() => {
it('should work in a template attribute', waitForAsync(() => {
const template = '<span *ngIf="booleanCondition">hello</span>';
fixture = createTestComponent(template);
fixture.detectChanges();
@ -39,14 +39,14 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
expect(fixture.nativeElement).toHaveText('hello');
}));
it('should work on a template element', async(() => {
it('should work on a template element', waitForAsync(() => {
const template = '<ng-template [ngIf]="booleanCondition">hello2</ng-template>';
fixture = createTestComponent(template);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('hello2');
}));
it('should toggle node when condition changes', async(() => {
it('should toggle node when condition changes', waitForAsync(() => {
const template = '<span *ngIf="booleanCondition">hello</span>';
fixture = createTestComponent(template);
getComponent().booleanCondition = false;
@ -65,7 +65,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
expect(fixture.nativeElement).toHaveText('');
}));
it('should handle nested if correctly', async(() => {
it('should handle nested if correctly', waitForAsync(() => {
const template =
'<div *ngIf="booleanCondition"><span *ngIf="nestedBooleanCondition">hello</span></div>';
@ -97,7 +97,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
expect(fixture.nativeElement).toHaveText('');
}));
it('should update several nodes with if', async(() => {
it('should update several nodes with if', waitForAsync(() => {
const template = '<span *ngIf="numberCondition + 1 >= 2">helloNumber</span>' +
'<span *ngIf="stringCondition == \'foo\'">helloString</span>' +
'<span *ngIf="functionCondition(stringCondition, numberCondition)">helloFunction</span>';
@ -120,7 +120,8 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
expect(fixture.nativeElement).toHaveText('helloNumber');
}));
it('should not add the element twice if the condition goes from truthy to truthy', async(() => {
it('should not add the element twice if the condition goes from truthy to truthy',
waitForAsync(() => {
const template = '<span *ngIf="numberCondition">hello</span>';
fixture = createTestComponent(template);
@ -141,7 +142,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
}));
describe('then/else templates', () => {
it('should support else', async(() => {
it('should support else', waitForAsync(() => {
const template = '<span *ngIf="booleanCondition; else elseBlock">TRUE</span>' +
'<ng-template #elseBlock>FALSE</ng-template>';
@ -155,7 +156,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
expect(fixture.nativeElement).toHaveText('FALSE');
}));
it('should support then and else', async(() => {
it('should support then and else', waitForAsync(() => {
const template =
'<span *ngIf="booleanCondition; then thenBlock; else elseBlock">IGNORE</span>' +
'<ng-template #thenBlock>THEN</ng-template>' +
@ -202,7 +203,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
expect(fixture.nativeElement).toHaveText('');
});
it('should support dynamic else', async(() => {
it('should support dynamic else', waitForAsync(() => {
const template =
'<span *ngIf="booleanCondition; else nestedBooleanCondition ? b1 : b2">TRUE</span>' +
'<ng-template #b1>FALSE1</ng-template>' +
@ -222,7 +223,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
expect(fixture.nativeElement).toHaveText('FALSE2');
}));
it('should support binding to variable using let', async(() => {
it('should support binding to variable using let', waitForAsync(() => {
const template = '<span *ngIf="booleanCondition; else elseBlock; let v">{{v}}</span>' +
'<ng-template #elseBlock let-v>{{v}}</ng-template>';
@ -236,7 +237,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
expect(fixture.nativeElement).toHaveText('false');
}));
it('should support binding to variable using as', async(() => {
it('should support binding to variable using as', waitForAsync(() => {
const template = '<span *ngIf="booleanCondition as v; else elseBlock">{{v}}</span>' +
'<ng-template #elseBlock let-v>{{v}}</ng-template>';
@ -252,7 +253,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
});
describe('Type guarding', () => {
it('should throw when then block is not template', async(() => {
it('should throw when then block is not template', waitForAsync(() => {
const template = '<span *ngIf="booleanCondition; then thenBlock">IGNORE</span>' +
'<div #thenBlock>THEN</div>';
@ -262,7 +263,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
.toThrowError(/ngIfThen must be a TemplateRef, but received/);
}));
it('should throw when else block is not template', async(() => {
it('should throw when else block is not template', waitForAsync(() => {
const template = '<span *ngIf="booleanCondition; else elseBlock">IGNORE</span>' +
'<div #elseBlock>ELSE</div>';

View File

@ -8,7 +8,7 @@
import {CommonModule, NgLocalization} from '@angular/common';
import {Component, Injectable} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
{
@ -36,7 +36,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
});
});
it('should display the template according to the exact value', async(() => {
it('should display the template according to the exact value', waitForAsync(() => {
const template = '<ul [ngPlural]="switchValue">' +
'<ng-template ngPluralCase="=0"><li>you have no messages.</li></ng-template>' +
'<ng-template ngPluralCase="=1"><li>you have one message.</li></ng-template>' +
@ -51,7 +51,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
detectChangesAndExpectText('you have one message.');
}));
it('should display the template according to the exact numeric value', async(() => {
it('should display the template according to the exact numeric value', waitForAsync(() => {
const template = '<div>' +
'<ul [ngPlural]="switchValue">' +
'<ng-template ngPluralCase="0"><li>you have no messages.</li></ng-template>' +
@ -69,7 +69,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
// https://github.com/angular/angular/issues/9868
// https://github.com/angular/angular/issues/9882
it('should not throw when ngPluralCase contains expressions', async(() => {
it('should not throw when ngPluralCase contains expressions', waitForAsync(() => {
const template = '<ul [ngPlural]="switchValue">' +
'<ng-template ngPluralCase="=0"><li>{{ switchValue }}</li></ng-template>' +
'</ul>';
@ -81,7 +81,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
}));
it('should be applicable to <ng-container> elements', async(() => {
it('should be applicable to <ng-container> elements', waitForAsync(() => {
const template = '<ng-container [ngPlural]="switchValue">' +
'<ng-template ngPluralCase="=0">you have no messages.</ng-template>' +
'<ng-template ngPluralCase="=1">you have one message.</ng-template>' +
@ -96,7 +96,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
detectChangesAndExpectText('you have one message.');
}));
it('should display the template according to the category', async(() => {
it('should display the template according to the category', waitForAsync(() => {
const template = '<ul [ngPlural]="switchValue">' +
'<ng-template ngPluralCase="few"><li>you have a few messages.</li></ng-template>' +
'<ng-template ngPluralCase="many"><li>you have many messages.</li></ng-template>' +
@ -111,7 +111,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
detectChangesAndExpectText('you have many messages.');
}));
it('should default to other when no matches are found', async(() => {
it('should default to other when no matches are found', waitForAsync(() => {
const template = '<ul [ngPlural]="switchValue">' +
'<ng-template ngPluralCase="few"><li>you have a few messages.</li></ng-template>' +
'<ng-template ngPluralCase="other"><li>default message.</li></ng-template>' +
@ -123,7 +123,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
detectChangesAndExpectText('default message.');
}));
it('should prioritize value matches over category matches', async(() => {
it('should prioritize value matches over category matches', waitForAsync(() => {
const template = '<ul [ngPlural]="switchValue">' +
'<ng-template ngPluralCase="few"><li>you have a few messages.</li></ng-template>' +
'<ng-template ngPluralCase="=2">you have two messages.</ng-template>' +

View File

@ -8,7 +8,7 @@
import {CommonModule} from '@angular/common';
import {Component} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
{
describe('NgStyle', () => {
@ -30,14 +30,14 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
TestBed.configureTestingModule({declarations: [TestComponent], imports: [CommonModule]});
});
it('should add styles specified in an object literal', async(() => {
it('should add styles specified in an object literal', waitForAsync(() => {
const template = `<div [ngStyle]="{'max-width': '40px'}"></div>`;
fixture = createTestComponent(template);
fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
}));
it('should add and change styles specified in an object expression', async(() => {
it('should add and change styles specified in an object expression', waitForAsync(() => {
const template = `<div [ngStyle]="expr"></div>`;
fixture = createTestComponent(template);
@ -51,7 +51,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'});
}));
it('should add and remove styles specified using style.unit notation', async(() => {
it('should add and remove styles specified using style.unit notation', waitForAsync(() => {
const template = `<div [ngStyle]="{'max-width.px': expr}"></div>`;
fixture = createTestComponent(template);
@ -66,7 +66,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
}));
// https://github.com/angular/angular/issues/21064
it('should add and remove styles which names are not dash-cased', async(() => {
it('should add and remove styles which names are not dash-cased', waitForAsync(() => {
fixture = createTestComponent(`<div [ngStyle]="{'color': expr}"></div>`);
getComponent().expr = 'green';
@ -78,7 +78,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
expectNativeEl(fixture).not.toHaveCssStyle('color');
}));
it('should update styles using style.unit notation when unit changes', async(() => {
it('should update styles using style.unit notation when unit changes', waitForAsync(() => {
const template = `<div [ngStyle]="expr"></div>`;
fixture = createTestComponent(template);
@ -93,7 +93,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
}));
// keyValueDiffer is sensitive to key order #9115
it('should change styles specified in an object expression', async(() => {
it('should change styles specified in an object expression', waitForAsync(() => {
const template = `<div [ngStyle]="expr"></div>`;
fixture = createTestComponent(template);
@ -117,7 +117,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
expectNativeEl(fixture).toHaveCssStyle({'height': '5px', 'width': '5px'});
}));
it('should remove styles when deleting a key in an object expression', async(() => {
it('should remove styles when deleting a key in an object expression', waitForAsync(() => {
const template = `<div [ngStyle]="expr"></div>`;
fixture = createTestComponent(template);
@ -131,7 +131,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
expectNativeEl(fixture).not.toHaveCssStyle('max-width');
}));
it('should co-operate with the style attribute', async(() => {
it('should co-operate with the style attribute', waitForAsync(() => {
const template = `<div style="font-size: 12px" [ngStyle]="expr"></div>`;
fixture = createTestComponent(template);
@ -147,7 +147,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
}));
it('should co-operate with the style.[styleName]="expr" special-case in the compiler',
async(() => {
waitForAsync(() => {
const template = `<div [style.font-size.px]="12" [ngStyle]="expr"></div>`;
fixture = createTestComponent(template);

View File

@ -8,7 +8,7 @@
import {CommonModule} from '@angular/common';
import {Component, ContentChildren, Directive, Injectable, NO_ERRORS_SCHEMA, OnDestroy, QueryList, TemplateRef} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
describe('NgTemplateOutlet', () => {
@ -36,7 +36,7 @@ describe('NgTemplateOutlet', () => {
});
// https://github.com/angular/angular/issues/14778
it('should accept the component as the context', async(() => {
it('should accept the component as the context', waitForAsync(() => {
const template = `<ng-container *ngTemplateOutlet="tpl; context: this"></ng-container>` +
`<ng-template #tpl>{{context.foo}}</ng-template>`;
@ -44,20 +44,20 @@ describe('NgTemplateOutlet', () => {
detectChangesAndExpectText('bar');
}));
it('should do nothing if templateRef is `null`', async(() => {
it('should do nothing if templateRef is `null`', waitForAsync(() => {
const template = `<ng-container [ngTemplateOutlet]="null"></ng-container>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('');
}));
it('should insert content specified by TemplateRef', async(() => {
it('should insert content specified by TemplateRef', waitForAsync(() => {
const template = `<ng-template #tpl>foo</ng-template>` +
`<ng-container [ngTemplateOutlet]="tpl"></ng-container>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('foo');
}));
it('should clear content if TemplateRef becomes `null`', async(() => {
it('should clear content if TemplateRef becomes `null`', waitForAsync(() => {
const template = `<tpl-refs #refs="tplRefs"><ng-template>foo</ng-template></tpl-refs>` +
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
fixture = createTestComponent(template);
@ -71,7 +71,7 @@ describe('NgTemplateOutlet', () => {
detectChangesAndExpectText('');
}));
it('should swap content if TemplateRef changes', async(() => {
it('should swap content if TemplateRef changes', waitForAsync(() => {
const template =
`<tpl-refs #refs="tplRefs"><ng-template>foo</ng-template><ng-template>bar</ng-template></tpl-refs>` +
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
@ -87,14 +87,14 @@ describe('NgTemplateOutlet', () => {
detectChangesAndExpectText('bar');
}));
it('should display template if context is `null`', async(() => {
it('should display template if context is `null`', waitForAsync(() => {
const template = `<ng-template #tpl>foo</ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; context: null"></ng-container>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('foo');
}));
it('should reflect initial context and changes', async(() => {
it('should reflect initial context and changes', waitForAsync(() => {
const template = `<ng-template let-foo="foo" #tpl>{{foo}}</ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; context: context"></ng-container>`;
fixture = createTestComponent(template);
@ -106,7 +106,7 @@ describe('NgTemplateOutlet', () => {
detectChangesAndExpectText('alter-bar');
}));
it('should reflect user defined `$implicit` property in the context', async(() => {
it('should reflect user defined `$implicit` property in the context', waitForAsync(() => {
const template = `<ng-template let-ctx #tpl>{{ctx.foo}}</ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; context: context"></ng-container>`;
fixture = createTestComponent(template);
@ -114,7 +114,7 @@ describe('NgTemplateOutlet', () => {
detectChangesAndExpectText('bra');
}));
it('should reflect context re-binding', async(() => {
it('should reflect context re-binding', waitForAsync(() => {
const template = `<ng-template let-shawshank="shawshank" #tpl>{{shawshank}}</ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; context: context"></ng-container>`;
fixture = createTestComponent(template);
@ -222,7 +222,8 @@ describe('NgTemplateOutlet', () => {
}).not.toThrow();
});
it('should not throw when switching from template to null and back to template', async(() => {
it('should not throw when switching from template to null and back to template',
waitForAsync(() => {
const template = `<tpl-refs #refs="tplRefs"><ng-template>foo</ng-template></tpl-refs>` +
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
fixture = createTestComponent(template);

View File

@ -9,7 +9,7 @@
import {ɵgetDOM as getDOM} from '@angular/common';
import {Component, Directive} from '@angular/core';
import {ElementRef} from '@angular/core/src/linker/element_ref';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {hasClass} from '@angular/platform-browser/testing/src/browser_util';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -21,7 +21,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
});
});
it('should not interpolate children', async(() => {
it('should not interpolate children', waitForAsync(() => {
const template = '<div>{{text}}<span ngNonBindable>{{text}}</span></div>';
const fixture = createTestComponent(template);
@ -29,7 +29,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
expect(fixture.nativeElement).toHaveText('foo{{text}}');
}));
it('should ignore directives on child nodes', async(() => {
it('should ignore directives on child nodes', waitForAsync(() => {
const template = '<div ngNonBindable><span id=child test-dec>{{text}}</span></div>';
const fixture = createTestComponent(template);
fixture.detectChanges();
@ -40,7 +40,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
expect(hasClass(span, 'compiled')).toBeFalsy();
}));
it('should trigger directives on the same node', async(() => {
it('should trigger directives on the same node', waitForAsync(() => {
const template = '<div><span id=child ngNonBindable test-dec>{{text}}</span></div>';
const fixture = createTestComponent(template);
fixture.detectChanges();

View File

@ -8,7 +8,7 @@
import {CommonModule, JsonPipe} from '@angular/common';
import {Component} from '@angular/core';
import {async, TestBed} from '@angular/core/testing';
import {TestBed, waitForAsync} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
{
@ -64,7 +64,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
TestBed.configureTestingModule({declarations: [TestComp], imports: [CommonModule]});
});
it('should work with mutable objects', async(() => {
it('should work with mutable objects', waitForAsync(() => {
const fixture = TestBed.createComponent(TestComp);
const mutable: number[] = [1];
fixture.componentInstance.data = mutable;

View File

@ -8,7 +8,7 @@
import {CommonModule, SlicePipe} from '@angular/common';
import {Component} from '@angular/core';
import {async, TestBed} from '@angular/core/testing';
import {TestBed, waitForAsync} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
{
@ -105,7 +105,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
TestBed.configureTestingModule({declarations: [TestComp], imports: [CommonModule]});
});
it('should work with mutable arrays', async(() => {
it('should work with mutable arrays', waitForAsync(() => {
const fixture = TestBed.createComponent(TestComp);
const mutable: number[] = [1, 2];
fixture.componentInstance.data = mutable;

View File

@ -6,35 +6,61 @@
* found in the LICENSE file at https://angular.io/license
*/
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
import {BrowserViewportScroller, ViewportScroller} from '../src/viewport_scroller';
{
describe('BrowserViewportScroller', () => {
let scroller: ViewportScroller;
let documentSpy: any;
beforeEach(() => {
documentSpy = jasmine.createSpyObj('document', ['querySelector']);
scroller = new BrowserViewportScroller(documentSpy, {scrollTo: 1}, null!);
});
it('escapes invalid characters selectors', () => {
const invalidSelectorChars = `"' :.[],=`;
// Double escaped to make sure we match the actual value passed to `querySelector`
const escapedInvalids = `\\"\\' \\:\\.\\[\\]\\,\\=`;
scroller.scrollToAnchor(`specials=${invalidSelectorChars}`);
expect(documentSpy.querySelector).toHaveBeenCalledWith(`#specials\\=${escapedInvalids}`);
expect(documentSpy.querySelector)
.toHaveBeenCalledWith(`[name='specials\\=${escapedInvalids}']`);
describe('BrowserViewportScroller', () => {
let scroller: ViewportScroller;
let documentSpy: any;
let windowSpy: any;
beforeEach(() => {
windowSpy = jasmine.createSpyObj('window', ['history']);
windowSpy.scrollTo = 1;
windowSpy.history.scrollRestoration = 'auto';
documentSpy = jasmine.createSpyObj('document', ['getElementById', 'getElementsByName']);
scroller = new BrowserViewportScroller(documentSpy, windowSpy, null!);
});
describe('setHistoryScrollRestoration', () => {
it('should not crash when scrollRestoration is not writable', () => {
Object.defineProperty(windowSpy.history, 'scrollRestoration', {
value: 'auto',
configurable: true,
});
expect(() => scroller.setHistoryScrollRestoration('manual')).not.toThrow();
});
});
}
describe('scrollToAnchor', () => {
const anchor = 'anchor';
const el = document.createElement('a');
it('should only call getElementById when an element is found by id', () => {
documentSpy.getElementById.and.returnValue(el);
spyOn<any>(scroller, 'scrollToElement');
scroller.scrollToAnchor(anchor);
expect(documentSpy.getElementById).toHaveBeenCalledWith(anchor);
expect(documentSpy.getElementsByName).not.toHaveBeenCalled();
expect((scroller as any).scrollToElement).toHaveBeenCalledWith(el);
});
it('should call getElementById and getElementsByName when an element is found by name', () => {
documentSpy.getElementsByName.and.returnValue([el]);
spyOn<any>(scroller, 'scrollToElement');
scroller.scrollToAnchor(anchor);
expect(documentSpy.getElementById).toHaveBeenCalledWith(anchor);
expect(documentSpy.getElementsByName).toHaveBeenCalledWith(anchor);
expect((scroller as any).scrollToElement).toHaveBeenCalledWith(el);
});
it('should not call scrollToElement when an element is not found by its id or its name', () => {
documentSpy.getElementsByName.and.returnValue([]);
spyOn<any>(scroller, 'scrollToElement');
scroller.scrollToAnchor(anchor);
expect(documentSpy.getElementById).toHaveBeenCalledWith(anchor);
expect(documentSpy.getElementsByName).toHaveBeenCalledWith(anchor);
expect((scroller as any).scrollToElement).not.toHaveBeenCalled();
});
});
});

View File

@ -307,7 +307,10 @@ export class NgModuleDecoratorHandler implements
});
if (this.factoryTracker !== null) {
this.factoryTracker.track(node.getSourceFile(), analysis.factorySymbolName);
this.factoryTracker.track(node.getSourceFile(), {
name: analysis.factorySymbolName,
hasId: analysis.id !== null,
});
}
this.injectableRegistry.registerInjectable(node);

View File

@ -65,6 +65,7 @@ const CORE_SUPPORTED_SYMBOLS = new Map<string, string>([
['ɵɵInjectorDef', 'ɵɵInjectorDef'],
['ɵɵNgModuleDefWithMeta', 'ɵɵNgModuleDefWithMeta'],
['ɵNgModuleFactory', 'NgModuleFactory'],
['ɵnoSideEffects', 'ɵnoSideEffects'],
]);
const CORE_MODULE = '@angular/core';

View File

@ -61,10 +61,15 @@ export interface PerFileShimGenerator {
export interface FactoryTracker {
readonly sourceInfo: Map<string, FactoryInfo>;
track(sf: ts.SourceFile, factorySymbolName: string): void;
track(sf: ts.SourceFile, moduleInfo: ModuleInfo): void;
}
export interface FactoryInfo {
sourceFilePath: string;
moduleSymbolNames: Set<string>;
}
moduleSymbols: Map<string, ModuleInfo>;
}
export interface ModuleInfo {
name: string;
hasId: boolean;
}

View File

@ -9,7 +9,7 @@ import * as ts from 'typescript';
import {absoluteFromSourceFile, AbsoluteFsPath, basename} from '../../file_system';
import {ImportRewriter} from '../../imports';
import {FactoryInfo, FactoryTracker, PerFileShimGenerator} from '../api';
import {FactoryInfo, FactoryTracker, ModuleInfo, PerFileShimGenerator} from '../api';
import {generatedModuleName} from './util';
@ -22,7 +22,7 @@ const STRIP_NG_FACTORY = /(.*)NgFactory$/;
*/
export class FactoryGenerator implements PerFileShimGenerator, FactoryTracker {
readonly sourceInfo = new Map<string, FactoryInfo>();
private sourceToFactorySymbols = new Map<string, Set<string>>();
private sourceToFactorySymbols = new Map<string, Map<string, ModuleInfo>>();
readonly shouldEmit = true;
readonly extensionPrefix = 'ngfactory';
@ -85,16 +85,19 @@ export class FactoryGenerator implements PerFileShimGenerator, FactoryTracker {
genFile.moduleName = generatedModuleName(sf.moduleName, sf.fileName, '.ngfactory');
}
const moduleSymbolNames = new Set<string>();
this.sourceToFactorySymbols.set(absoluteSfPath, moduleSymbolNames);
this.sourceInfo.set(genFilePath, {sourceFilePath: absoluteSfPath, moduleSymbolNames});
const moduleSymbols = new Map<string, ModuleInfo>();
this.sourceToFactorySymbols.set(absoluteSfPath, moduleSymbols);
this.sourceInfo.set(genFilePath, {
sourceFilePath: absoluteSfPath,
moduleSymbols,
});
return genFile;
}
track(sf: ts.SourceFile, factorySymbolName: string): void {
track(sf: ts.SourceFile, moduleInfo: ModuleInfo): void {
if (this.sourceToFactorySymbols.has(sf.fileName)) {
this.sourceToFactorySymbols.get(sf.fileName)!.add(factorySymbolName);
this.sourceToFactorySymbols.get(sf.fileName)!.set(moduleInfo.name, moduleInfo);
}
}
}
@ -123,7 +126,7 @@ function transformFactorySourceFile(
return file;
}
const {moduleSymbolNames, sourceFilePath} = factoryMap.get(file.fileName)!;
const {moduleSymbols, sourceFilePath} = factoryMap.get(file.fileName)!;
file = ts.getMutableClone(file);
@ -183,8 +186,24 @@ function transformFactorySourceFile(
// Otherwise, check if this export is a factory for a known NgModule, and retain it if so.
const match = STRIP_NG_FACTORY.exec(decl.name.text);
if (match !== null && moduleSymbolNames.has(match[1])) {
transformedStatements.push(stmt);
const module = match ? moduleSymbols.get(match[1]) : null;
if (module) {
// If the module can be tree shaken, then the factory should be wrapped in a
// `noSideEffects()` call which tells Closure to treat the expression as pure, allowing
// it to be removed if the result is not used.
//
// `NgModule`s with an `id` property will be lazy loaded. Google-internal lazy loading
// infra relies on a side effect from the `new NgModuleFactory()` call, which registers
// the module globally. Because of this, we **cannot** tree shake any module which has
// an `id` property. Doing so would cause lazy loaded modules to never be registered.
const moduleIsTreeShakable = !module.hasId;
const newStmt = !moduleIsTreeShakable ?
stmt :
updateInitializers(
stmt,
(init) => init ? wrapInNoSideEffects(init) : undefined,
);
transformedStatements.push(newStmt);
}
} else {
// Leave the statement alone, as it can't be understood.
@ -263,3 +282,62 @@ function getFileoverviewComment(sourceFile: ts.SourceFile): string|null {
return commentText;
}
/**
* Wraps the given expression in a call to `ɵnoSideEffects()`, which tells
* Closure we don't care about the side effects of this expression and it should
* be treated as "pure". Closure is free to tree shake this expression if its
* result is not used.
*
* Example: Takes `1 + 2` and returns `i0.ɵnoSideEffects(() => 1 + 2)`.
*/
function wrapInNoSideEffects(expr: ts.Expression): ts.Expression {
const noSideEffects = ts.createPropertyAccess(
ts.createIdentifier('i0'),
'ɵnoSideEffects',
);
return ts.createCall(
noSideEffects,
/* typeArguments */[],
/* arguments */
[
ts.createFunctionExpression(
/* modifiers */[],
/* asteriskToken */ undefined,
/* name */ undefined,
/* typeParameters */[],
/* parameters */[],
/* type */ undefined,
/* body */ ts.createBlock([
ts.createReturn(expr),
]),
),
],
);
}
/**
* Clones and updates the initializers for a given statement to use the new
* expression provided. Does not mutate the input statement.
*/
function updateInitializers(
stmt: ts.VariableStatement,
update: (initializer?: ts.Expression) => ts.Expression | undefined,
): ts.VariableStatement {
return ts.updateVariableStatement(
stmt,
stmt.modifiers,
ts.updateVariableDeclarationList(
stmt.declarationList,
stmt.declarationList.declarations.map(
(decl) => ts.updateVariableDeclaration(
decl,
decl.name,
decl.type,
update(decl.initializer),
),
),
),
);
}

View File

@ -79,7 +79,7 @@ export class NgTscPlugin implements TscPlugin {
}
wrapHost(
host: ts.CompilerHost&UnifiedModulesHost, inputFiles: readonly string[],
host: ts.CompilerHost&Partial<UnifiedModulesHost>, inputFiles: readonly string[],
options: ts.CompilerOptions): PluginCompilerHost {
// TODO(alxhub): Eventually the `wrapHost()` API will accept the old `ts.Program` (if one is
// available). When it does, its `ts.SourceFile`s need to be re-tagged to enable proper

View File

@ -166,8 +166,8 @@ function constructTypeCtorParameter(
if (coercedKeys.length > 0) {
const coercedLiteral = ts.createTypeLiteralNode(coercedKeys);
initType =
initType !== null ? ts.createUnionTypeNode([initType, coercedLiteral]) : coercedLiteral;
initType = initType !== null ? ts.createIntersectionTypeNode([initType, coercedLiteral]) :
coercedLiteral;
}
if (initType === null) {

View File

@ -179,7 +179,7 @@ TestClass.ngTypeCtor({value: 'test'});
const typeCtor = TestClassWithCtor.members.find(isTypeCtor)!;
const ctorText = typeCtor.getText().replace(/[ \r\n]+/g, ' ');
expect(ctorText).toContain(
'init: Pick<TestClass, "foo"> | { bar: typeof TestClass.ngAcceptInputType_bar; }');
'init: Pick<TestClass, "foo"> & { bar: typeof TestClass.ngAcceptInputType_bar; }');
});
});
});

View File

@ -3538,7 +3538,9 @@ runInEachFileSystem(os => {
expect(factoryContents).toContain(`import * as i0 from '@angular/core';`);
expect(factoryContents).toContain(`import { NotAModule, TestModule } from './test';`);
expect(factoryContents)
.toContain(`export var TestModuleNgFactory = new i0.\u0275NgModuleFactory(TestModule);`);
.toContain(
'export var TestModuleNgFactory = i0.\u0275noSideEffects(function () { ' +
'return new i0.\u0275NgModuleFactory(TestModule); });');
expect(factoryContents).not.toContain(`NotAModuleNgFactory`);
expect(factoryContents).not.toContain('\u0275NonEmptyModule');
@ -3677,11 +3679,32 @@ runInEachFileSystem(os => {
env.driveMain();
const factoryContents = env.getContents('test.ngfactory.js');
expect(normalize(factoryContents)).toBe(normalize(`
import * as i0 from "./r3_symbols";
import { TestModule } from './test';
export var TestModuleNgFactory = new i0.NgModuleFactory(TestModule);
`));
expect(factoryContents)
.toBe(
'import * as i0 from "./r3_symbols";\n' +
'import { TestModule } from \'./test\';\n' +
'export var TestModuleNgFactory = i0.\u0275noSideEffects(function () {' +
' return new i0.NgModuleFactory(TestModule); });\n');
});
it('should generate side effectful NgModuleFactory constructor when lazy loaded', () => {
env.tsconfig({'allowEmptyCodegenFiles': true});
env.write('test.ts', `
import {NgModule} from '@angular/core';
@NgModule({
id: 'test', // ID to use for lazy loading.
})
export class TestModule {}
`);
env.driveMain();
// Should **not** contain noSideEffects(), because the module is lazy loaded.
const factoryContents = env.getContents('test.ngfactory.js');
expect(factoryContents)
.toContain('export var TestModuleNgFactory = new i0.ɵNgModuleFactory(TestModule);');
});
describe('file-level comments', () => {

View File

@ -1498,6 +1498,39 @@ export declare class AnimationEvent {
expect(diags[0].messageText)
.toBe(`Type 'boolean' is not assignable to type 'string | number'.`);
});
it('should give an error for undefined bindings into regular inputs when coercion members are present',
() => {
env.tsconfig({strictTemplates: true});
env.write('test.ts', `
import {Component, Directive, NgModule, Input} from '@angular/core';
@Component({
selector: 'blah',
template: '<input dir [regular]="undefined" [coerced]="1">',
})
export class FooCmp {
invalidType = true;
}
@Directive({selector: '[dir]'})
export class CoercionDir {
@Input() regular: string;
@Input() coerced: boolean;
static ngAcceptInputType_coerced: boolean|number;
}
@NgModule({
declarations: [FooCmp, CoercionDir],
})
export class FooModule {}
`);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(1);
expect(diags[0].messageText)
.toBe(`Type 'undefined' is not assignable to type 'string'.`);
});
});
describe('legacy schema checking with the DOM schema', () => {

View File

@ -7,15 +7,15 @@
*/
import {Xliff2} from '@angular/compiler/src/i18n/serializers/xliff2';
import {async} from '@angular/core/testing';
import {waitForAsync} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {configureCompiler, createComponent, HTML, serializeTranslations, validateHtml} from './integration_common';
describe('i18n XLIFF integration spec', () => {
describe('(with LF line endings)', () => {
beforeEach(
async(() => configureCompiler(XLIFF2_TOMERGE + LF_LINE_ENDING_XLIFF2_TOMERGE, 'xlf2')));
beforeEach(waitForAsync(
() => configureCompiler(XLIFF2_TOMERGE + LF_LINE_ENDING_XLIFF2_TOMERGE, 'xlf2')));
it('should extract from templates', () => {
const serializer = new Xliff2();
@ -34,8 +34,8 @@ describe('i18n XLIFF integration spec', () => {
});
describe('(with CRLF line endings', () => {
beforeEach(
async(() => configureCompiler(XLIFF2_TOMERGE + CRLF_LINE_ENDING_XLIFF2_TOMERGE, 'xlf2')));
beforeEach(waitForAsync(
() => configureCompiler(XLIFF2_TOMERGE + CRLF_LINE_ENDING_XLIFF2_TOMERGE, 'xlf2')));
it('should extract from templates (with CRLF line endings)', () => {
const serializer = new Xliff2();

View File

@ -7,14 +7,15 @@
*/
import {Xliff} from '@angular/compiler/src/i18n/serializers/xliff';
import {async} from '@angular/core/testing';
import {waitForAsync} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {configureCompiler, createComponent, HTML, serializeTranslations, validateHtml} from './integration_common';
describe('i18n XLIFF integration spec', () => {
describe('(with LF line endings)', () => {
beforeEach(async(() => configureCompiler(XLIFF_TOMERGE + LF_LINE_ENDING_XLIFF_TOMERGE, 'xlf')));
beforeEach(
waitForAsync(() => configureCompiler(XLIFF_TOMERGE + LF_LINE_ENDING_XLIFF_TOMERGE, 'xlf')));
it('should extract from templates', () => {
const serializer = new Xliff();
@ -33,8 +34,8 @@ describe('i18n XLIFF integration spec', () => {
});
describe('(with CRLF line endings', () => {
beforeEach(
async(() => configureCompiler(XLIFF_TOMERGE + CRLF_LINE_ENDING_XLIFF_TOMERGE, 'xlf')));
beforeEach(waitForAsync(
() => configureCompiler(XLIFF_TOMERGE + CRLF_LINE_ENDING_XLIFF_TOMERGE, 'xlf')));
it('should extract from templates (with CRLF line endings)', () => {
const serializer = new Xliff();

View File

@ -7,14 +7,14 @@
*/
import {Xmb} from '@angular/compiler/src/i18n/serializers/xmb';
import {async} from '@angular/core/testing';
import {waitForAsync} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {configureCompiler, createComponent, HTML, serializeTranslations, validateHtml} from './integration_common';
describe('i18n XMB/XTB integration spec', () => {
describe('(with LF line endings)', () => {
beforeEach(async(() => configureCompiler(XTB + LF_LINE_ENDING_XTB, 'xtb')));
beforeEach(waitForAsync(() => configureCompiler(XTB + LF_LINE_ENDING_XTB, 'xtb')));
it('should extract from templates', () => {
const serializer = new Xmb();
@ -33,7 +33,7 @@ describe('i18n XMB/XTB integration spec', () => {
});
describe('(with CRLF line endings', () => {
beforeEach(async(() => configureCompiler(XTB + CRLF_LINE_ENDING_XTB, 'xtb')));
beforeEach(waitForAsync(() => configureCompiler(XTB + CRLF_LINE_ENDING_XTB, 'xtb')));
it('should extract from templates (with CRLF line endings)', () => {
const serializer = new Xmb();

View File

@ -7,7 +7,7 @@
*/
import {Component, Directive, Input} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -17,7 +17,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
let fixture: ComponentFixture<TestComponent>;
describe('directives', () => {
it('should support dotted selectors', async(() => {
it('should support dotted selectors', waitForAsync(() => {
@Directive({selector: '[dot.name]'})
class MyDir {
// TODO(issue/24571): remove '!'.
@ -41,7 +41,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
describe('ng-container', () => {
if (browserDetection.isChromeDesktop) {
it('should work regardless the namespace', async(() => {
it('should work regardless the namespace', waitForAsync(() => {
@Component({
selector: 'comp',
template:

View File

@ -8,7 +8,7 @@
import {LIFECYCLE_HOOKS_VALUES, LifecycleHooks} from '@angular/compiler/src/lifecycle_reflector';
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, Component, Directive, DoCheck, Injectable, NgModule, OnChanges, OnDestroy, OnInit, Pipe, SimpleChanges, ViewEncapsulation, ɵstringify as stringify} from '@angular/core';
import {async, inject, TestBed} from '@angular/core/testing';
import {inject, TestBed, waitForAsync} from '@angular/core/testing';
import {CompileDiDependencyMetadata, identifierName} from '../src/compile_metadata';
import {CompileMetadataResolver} from '../src/metadata_resolver';
@ -77,7 +77,7 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
}));
it('should read external metadata when sync=false',
async(inject(
waitForAsync(inject(
[CompileMetadataResolver, ResourceLoader],
(resolver: CompileMetadataResolver, resourceLoader: MockResourceLoader) => {
@NgModule({declarations: [ComponentWithExternalResources]})
@ -96,7 +96,7 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings';
})));
it('should use `./` as base url for templates during runtime compilation if no moduleId is given',
async(inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
waitForAsync(inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
@Component({selector: 'someComponent', templateUrl: 'someUrl'})
class ComponentWithoutModuleId {
}

View File

@ -8,7 +8,7 @@
import {DirectiveResolver, ResourceLoader} from '@angular/compiler';
import {Compiler, Component, Injector, NgModule, NgModuleFactory, ɵstringify as stringify} from '@angular/core';
import {async, fakeAsync, inject, TestBed, tick} from '@angular/core/testing';
import {fakeAsync, inject, TestBed, tick, waitForAsync} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {MockDirectiveResolver} from '../testing';
@ -42,7 +42,8 @@ class SomeCompWithUrlTemplate {
{providers: [{provide: ResourceLoader, useClass: StubResourceLoader, deps: []}]});
});
it('should throw when using a templateUrl that has not been compiled before', async(() => {
it('should throw when using a templateUrl that has not been compiled before',
waitForAsync(() => {
TestBed.configureTestingModule({declarations: [SomeCompWithUrlTemplate]});
TestBed.compileComponents().then(() => {
expect(() => TestBed.createComponent(SomeCompWithUrlTemplate))
@ -76,7 +77,8 @@ class SomeCompWithUrlTemplate {
{providers: [{provide: ResourceLoader, useClass: StubResourceLoader, deps: []}]});
});
it('should allow to use templateUrl components that have been loaded before', async(() => {
it('should allow to use templateUrl components that have been loaded before',
waitForAsync(() => {
TestBed.configureTestingModule({declarations: [SomeCompWithUrlTemplate]});
TestBed.compileComponents().then(() => {
const fixture = TestBed.createComponent(SomeCompWithUrlTemplate);

View File

@ -292,5 +292,8 @@ export {
ɵɵsanitizeUrl,
ɵɵsanitizeUrlOrResourceUrl,
} from './sanitization/sanitization';
export {
noSideEffects as ɵnoSideEffects,
} from './util/closure';
// clang-format on

View File

@ -28,6 +28,7 @@ export {ɵɵdefineNgModule} from './render3/definition';
export {ɵɵFactoryDef} from './render3/interfaces/definition';
export {setClassMetadata} from './render3/metadata';
export {NgModuleFactory} from './render3/ng_module_ref';
export {noSideEffects as ɵnoSideEffects} from './util/closure';

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import '../util/ng_i18n_closure_mode';
import '../util/ng_dev_mode';
import {DEFAULT_LOCALE_ID, getPluralCase} from '../i18n/localization';
import {getTemplateContent, SRCSET_ATTRS, URI_ATTRS, VALID_ATTRS, VALID_ELEMENTS} from '../sanitization/html_sanitizer';
@ -16,8 +17,8 @@ import {assertDataInRange, assertDefined, assertEqual} from '../util/assert';
import {bindingUpdated} from './bindings';
import {attachPatchData} from './context_discovery';
import {i18nMutateOpCodesToString, i18nUpdateOpCodesToString} from './i18n_debug';
import {setDelayProjection} from './instructions/all';
import {attachI18nOpCodesDebug} from './instructions/lview_debug';
import {allocExpando, elementAttributeInternal, elementPropertyInternal, getOrCreateTNode, setInputsForProperty, setNgReflectProperties, textBindingInternal} from './instructions/shared';
import {LContainer, NATIVE} from './interfaces/container';
import {getDocument} from './interfaces/document';
@ -29,6 +30,7 @@ import {isLContainer} from './interfaces/type_checks';
import {HEADER_OFFSET, LView, RENDERER, T_HOST, TVIEW, TView} from './interfaces/view';
import {appendChild, applyProjection, createTextNode, nativeRemoveNode} from './node_manipulation';
import {getBindingIndex, getIsParent, getLView, getPreviousOrParentTNode, getTView, nextBindingIndex, setIsNotParent, setPreviousOrParentTNode} from './state';
import {attachDebugGetter} from './util/debug_utils';
import {renderStringify} from './util/misc_utils';
import {getNativeByIndex, getNativeByTNode, getTNode, load} from './util/view_utils';
@ -267,6 +269,9 @@ function generateBindingUpdateOpCodes(
str: string, destinationNode: number, attrName?: string,
sanitizeFn: SanitizerFn|null = null): I18nUpdateOpCodes {
const updateOpCodes: I18nUpdateOpCodes = [null, null]; // Alloc space for mask and size
if (ngDevMode) {
attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString);
}
const textParts = str.split(BINDING_REGEXP);
let mask = 0;
@ -395,6 +400,9 @@ function i18nStartFirstPass(
let parentIndexPointer = 0;
parentIndexStack[parentIndexPointer] = parentIndex;
const createOpCodes: I18nMutateOpCodes = [];
if (ngDevMode) {
attachDebugGetter(createOpCodes, i18nMutateOpCodesToString);
}
// If the previous node wasn't the direct parent then we have a translation without top level
// element and we need to keep a reference of the previous element if there is one. We should also
// keep track whether an element was a parent node or not, so that the logic that consumes
@ -411,6 +419,9 @@ function i18nStartFirstPass(
createOpCodes.push(previousTNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select);
}
const updateOpCodes: I18nUpdateOpCodes = [];
if (ngDevMode) {
attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString);
}
const icuExpressions: TIcu[] = [];
if (message === '' && isRootTemplateMessage(subTemplateIndex)) {
@ -507,10 +518,6 @@ function i18nStartFirstPass(
allocExpando(tView, lView, i18nVarsCount);
}
ngDevMode &&
attachI18nOpCodesDebug(
createOpCodes, updateOpCodes, icuExpressions.length ? icuExpressions : null, lView);
// NOTE: local var needed to properly assert the type of `TI18n`.
const tI18n: TI18n = {
vars: i18nVarsCount,
@ -751,6 +758,7 @@ function createDynamicNodeAtIndex(
const previousOrParentTNode = getPreviousOrParentTNode();
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
lView[index + HEADER_OFFSET] = native;
// FIXME(misko): Why does this create A TNode??? I would not expect this to be here.
const tNode = getOrCreateTNode(tView, lView[T_HOST], index, type as any, name, null);
// We are creating a dynamic node, the previous tNode might not be pointing at this node.
@ -780,7 +788,7 @@ function readCreateOpCodes(
visitedNodes.push(textNodeIndex);
setIsNotParent();
} else if (typeof opCode == 'number') {
switch (opCode & I18nMutateOpCode.MASK_OPCODE) {
switch (opCode & I18nMutateOpCode.MASK_INSTRUCTION) {
case I18nMutateOpCode.AppendChild:
const destinationNodeIndex = opCode >>> I18nMutateOpCode.SHIFT_PARENT;
let destinationTNode: TNode;
@ -799,9 +807,10 @@ function readCreateOpCodes(
appendI18nNode(tView, currentTNode!, destinationTNode, previousTNode, lView);
break;
case I18nMutateOpCode.Select:
// Negative indicies indicate that a given TNode is a sibling node, not a parent node
// Negative indices indicate that a given TNode is a sibling node, not a parent node
// (see `i18nStartFirstPass` for additional information).
const isParent = opCode >= 0;
// FIXME(misko): This SHIFT_REF looks suspect as it does not have mask.
const nodeIndex = (isParent ? opCode : ~opCode) >>> I18nMutateOpCode.SHIFT_REF;
visitedNodes.push(nodeIndex);
previousTNode = currentTNode;
@ -874,7 +883,7 @@ function readCreateOpCodes(
function readUpdateOpCodes(
updateOpCodes: I18nUpdateOpCodes, icus: TIcu[]|null, bindingsStartIndex: number,
changeMask: number, tView: TView, lView: LView, bypassCheckBit = false) {
changeMask: number, tView: TView, lView: LView, bypassCheckBit: boolean) {
let caseCreated = false;
for (let i = 0; i < updateOpCodes.length; i++) {
// bit code to check if we should apply the next update
@ -890,13 +899,10 @@ function readUpdateOpCodes(
value += opCode;
} else if (typeof opCode == 'number') {
if (opCode < 0) {
// It's a binding index whose value is negative
// Negative opCode represent `i18nExp` values offset.
value += renderStringify(lView[bindingsStartIndex - opCode]);
} else {
const nodeIndex = opCode >>> I18nUpdateOpCode.SHIFT_REF;
let tIcuIndex: number;
let tIcu: TIcu;
let icuTNode: TIcuContainerNode;
switch (opCode & I18nUpdateOpCode.MASK_OPCODE) {
case I18nUpdateOpCode.Attr:
const propName = updateOpCodes[++j] as string;
@ -909,56 +915,13 @@ function readUpdateOpCodes(
textBindingInternal(lView, nodeIndex, value);
break;
case I18nUpdateOpCode.IcuSwitch:
tIcuIndex = updateOpCodes[++j] as number;
tIcu = icus![tIcuIndex];
icuTNode = getTNode(tView, nodeIndex) as TIcuContainerNode;
// If there is an active case, delete the old nodes
if (icuTNode.activeCaseIndex !== null) {
const removeCodes = tIcu.remove[icuTNode.activeCaseIndex];
for (let k = 0; k < removeCodes.length; k++) {
const removeOpCode = removeCodes[k] as number;
switch (removeOpCode & I18nMutateOpCode.MASK_OPCODE) {
case I18nMutateOpCode.Remove:
const nodeIndex = removeOpCode >>> I18nMutateOpCode.SHIFT_REF;
// Remove DOM element, but do *not* mark TNode as detached, since we are
// just switching ICU cases (while keeping the same TNode), so a DOM element
// representing a new ICU case will be re-created.
removeNode(tView, lView, nodeIndex, /* markAsDetached */ false);
break;
case I18nMutateOpCode.RemoveNestedIcu:
const nestedIcuNodeIndex =
removeCodes[k + 1] as number >>> I18nMutateOpCode.SHIFT_REF;
const nestedIcuTNode =
getTNode(tView, nestedIcuNodeIndex) as TIcuContainerNode;
const activeIndex = nestedIcuTNode.activeCaseIndex;
if (activeIndex !== null) {
const nestedIcuTIndex = removeOpCode >>> I18nMutateOpCode.SHIFT_REF;
const nestedTIcu = icus![nestedIcuTIndex];
addAllToArray(nestedTIcu.remove[activeIndex], removeCodes);
}
break;
}
}
}
// Update the active caseIndex
const caseIndex = getCaseIndex(tIcu, value);
icuTNode.activeCaseIndex = caseIndex !== -1 ? caseIndex : null;
if (caseIndex > -1) {
// Add the nodes for the new case
readCreateOpCodes(-1, tIcu.create[caseIndex], tView, lView);
caseCreated = true;
}
caseCreated = icuSwitchCase(
tView, updateOpCodes[++j] as number, nodeIndex, icus!, lView, value);
break;
case I18nUpdateOpCode.IcuUpdate:
tIcuIndex = updateOpCodes[++j] as number;
tIcu = icus![tIcuIndex];
icuTNode = getTNode(tView, nodeIndex) as TIcuContainerNode;
if (icuTNode.activeCaseIndex !== null) {
readUpdateOpCodes(
tIcu.update[icuTNode.activeCaseIndex], icus, bindingsStartIndex, changeMask,
tView, lView, caseCreated);
}
icuUpdateCase(
tView, lView, updateOpCodes[++j] as number, nodeIndex, bindingsStartIndex,
icus!, caseCreated);
break;
}
}
@ -969,6 +932,70 @@ function readUpdateOpCodes(
}
}
function icuUpdateCase(
tView: TView, lView: LView, tIcuIndex: number, nodeIndex: number, bindingsStartIndex: number,
tIcus: TIcu[], caseCreated: boolean) {
const tIcu = tIcus[tIcuIndex];
const icuTNode = getTNode(tView, nodeIndex) as TIcuContainerNode;
if (icuTNode.activeCaseIndex !== null) {
readUpdateOpCodes(
tIcu.update[icuTNode.activeCaseIndex], tIcus, bindingsStartIndex, changeMask, tView, lView,
caseCreated);
}
}
function icuSwitchCase(
tView: TView, tIcuIndex: number, nodeIndex: number, tIcus: TIcu[], lView: LView,
value: string): boolean {
const tIcu = tIcus[tIcuIndex];
const icuTNode = getTNode(tView, nodeIndex) as TIcuContainerNode;
let caseCreated = false;
// If there is an active case, delete the old nodes
if (icuTNode.activeCaseIndex !== null) {
const removeCodes = tIcu.remove[icuTNode.activeCaseIndex];
for (let k = 0; k < removeCodes.length; k++) {
const removeOpCode = removeCodes[k] as number;
const nodeOrIcuIndex = removeOpCode >>> I18nMutateOpCode.SHIFT_REF;
switch (removeOpCode & I18nMutateOpCode.MASK_INSTRUCTION) {
case I18nMutateOpCode.Remove:
// Remove DOM element, but do *not* mark TNode as detached, since we are
// just switching ICU cases (while keeping the same TNode), so a DOM element
// representing a new ICU case will be re-created.
removeNode(tView, lView, nodeOrIcuIndex, /* markAsDetached */ false);
break;
case I18nMutateOpCode.RemoveNestedIcu:
removeNestedIcu(
tView, tIcus, removeCodes, nodeOrIcuIndex,
removeCodes[k + 1] as number >>> I18nMutateOpCode.SHIFT_REF);
break;
}
}
}
// Update the active caseIndex
const caseIndex = getCaseIndex(tIcu, value);
icuTNode.activeCaseIndex = caseIndex !== -1 ? caseIndex : null;
if (caseIndex > -1) {
// Add the nodes for the new case
readCreateOpCodes(
-1 /* -1 means we don't have parent node */, tIcu.create[caseIndex], tView, lView);
caseCreated = true;
}
return caseCreated;
}
function removeNestedIcu(
tView: TView, tIcus: TIcu[], removeCodes: I18nMutateOpCodes, nodeIndex: number,
nestedIcuNodeIndex: number) {
const nestedIcuTNode = getTNode(tView, nestedIcuNodeIndex) as TIcuContainerNode;
const activeIndex = nestedIcuTNode.activeCaseIndex;
if (activeIndex !== null) {
const nestedTIcu = tIcus[nodeIndex];
// FIXME(misko): the fact that we are adding items to parent list looks very suspect!
addAllToArray(nestedTIcu.remove[activeIndex], removeCodes);
}
}
function removeNode(tView: TView, lView: LView, index: number, markAsDetached: boolean) {
const removedPhTNode = getTNode(tView, index);
const removedPhRNode = getNativeByIndex(index, lView);
@ -1044,6 +1071,9 @@ function i18nAttributesFirstPass(lView: LView, tView: TView, index: number, valu
const previousElement = getPreviousOrParentTNode();
const previousElementIndex = previousElement.index - HEADER_OFFSET;
const updateOpCodes: I18nUpdateOpCodes = [];
if (ngDevMode) {
attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString);
}
for (let i = 0; i < values.length; i += 2) {
const attrName = values[i];
const message = values[i + 1];
@ -1134,7 +1164,7 @@ export function ɵɵi18nApply(index: number) {
}
const bindingsStartIndex = getBindingIndex() - shiftsCounter - 1;
const lView = getLView();
readUpdateOpCodes(updateOpCodes, icus, bindingsStartIndex, changeMask, tView, lView);
readUpdateOpCodes(updateOpCodes, icus, bindingsStartIndex, changeMask, tView, lView, false);
// Reset changeMask & maskBit to default for the next update cycle
changeMask = 0b0;
@ -1180,9 +1210,9 @@ function getCaseIndex(icuExpression: TIcu, bindingValue: string): number {
function icuStart(
tIcus: TIcu[], icuExpression: IcuExpression, startIndex: number,
expandoStartIndex: number): void {
const createCodes = [];
const removeCodes = [];
const updateCodes = [];
const createCodes: I18nMutateOpCodes[] = [];
const removeCodes: I18nMutateOpCodes[] = [];
const updateCodes: I18nUpdateOpCodes[] = [];
const vars = [];
const childIcus: number[][] = [];
for (let i = 0; i < icuExpression.values.length; i++) {
@ -1240,6 +1270,11 @@ function parseIcuCase(
}
const wrapper = getTemplateContent(inertBodyElement!) as Element || inertBodyElement;
const opCodes: IcuCase = {vars: 0, childIcus: [], create: [], remove: [], update: []};
if (ngDevMode) {
attachDebugGetter(opCodes.create, i18nMutateOpCodesToString);
attachDebugGetter(opCodes.remove, i18nMutateOpCodesToString);
attachDebugGetter(opCodes.update, i18nUpdateOpCodesToString);
}
parseNodes(wrapper.firstChild, opCodes, parentIndex, nestedIcus, tIcus, expandoStartIndex);
return opCodes;
}
@ -1364,6 +1399,7 @@ function parseNodes(
3, // skip 3 opCodes if not changed
-1 - nestedIcu.mainBinding,
nestedIcuNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuSwitch,
// FIXME(misko): Index should be part of the opcode
nestTIcuIndex,
mask, // mask of all the bindings of this ICU expression
2, // skip 2 opCodes if not changed
@ -1371,6 +1407,7 @@ function parseNodes(
nestTIcuIndex);
icuCase.remove.push(
nestTIcuIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.RemoveNestedIcu,
// FIXME(misko): Index should be part of the opcode
nestedIcuNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove);
}
}

View File

@ -0,0 +1,186 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {assertNumber, assertString} from '../util/assert';
import {COMMENT_MARKER, ELEMENT_MARKER, getInstructionFromI18nMutateOpCode, getParentFromI18nMutateOpCode, getRefFromI18nMutateOpCode, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes} from './interfaces/i18n';
/**
* Converts `I18nUpdateOpCodes` array into a human readable format.
*
* This function is attached to the `I18nUpdateOpCodes.debug` property if `ngDevMode` is enabled.
* This function provides a human readable view of the opcodes. This is useful when debugging the
* application as well as writing more readable tests.
*
* @param this `I18nUpdateOpCodes` if attached as a method.
* @param opcodes `I18nUpdateOpCodes` if invoked as a function.
*/
export function i18nUpdateOpCodesToString(
this: I18nUpdateOpCodes|void, opcodes?: I18nUpdateOpCodes): string[] {
const parser = new OpCodeParser(opcodes || (Array.isArray(this) ? this : []));
let lines: string[] = [];
function consumeOpCode(value: number): string {
const ref = value >>> I18nUpdateOpCode.SHIFT_REF;
const opCode = value & I18nUpdateOpCode.MASK_OPCODE;
switch (opCode) {
case I18nUpdateOpCode.Text:
return `(lView[${ref}] as Text).textContent = $$$`;
case I18nUpdateOpCode.Attr:
const attrName = parser.consumeString();
const sanitizationFn = parser.consumeFunction();
const value = sanitizationFn ? `(${sanitizationFn})($$$)` : '$$$';
return `(lView[${ref}] as Element).setAttribute('${attrName}', ${value})`;
case I18nUpdateOpCode.IcuSwitch:
return `icuSwitchCase(lView[${ref}] as Comment, ${parser.consumeNumber()}, $$$)`;
case I18nUpdateOpCode.IcuUpdate:
return `icuUpdateCase(lView[${ref}] as Comment, ${parser.consumeNumber()})`;
}
throw new Error('unexpected OpCode');
}
while (parser.hasMore()) {
let mask = parser.consumeNumber();
let size = parser.consumeNumber();
const end = parser.i + size;
const statements: string[] = [];
let statement = '';
while (parser.i < end) {
let value = parser.consumeNumberOrString();
if (typeof value === 'string') {
statement += value;
} else if (value < 0) {
// Negative numbers are ref indexes
statement += '${lView[' + (0 - value) + ']}';
} else {
// Positive numbers are operations.
const opCodeText = consumeOpCode(value);
statements.push(opCodeText.replace('$$$', '`' + statement + '`') + ';');
statement = '';
}
}
lines.push(`if (mask & 0b${mask.toString(2)}) { ${statements.join(' ')} }`);
}
return lines;
}
/**
* Converts `I18nMutableOpCodes` array into a human readable format.
*
* This function is attached to the `I18nMutableOpCodes.debug` if `ngDevMode` is enabled. This
* function provides a human readable view of the opcodes. This is useful when debugging the
* application as well as writing more readable tests.
*
* @param this `I18nMutableOpCodes` if attached as a method.
* @param opcodes `I18nMutableOpCodes` if invoked as a function.
*/
export function i18nMutateOpCodesToString(
this: I18nMutateOpCodes|void, opcodes?: I18nMutateOpCodes): string[] {
const parser = new OpCodeParser(opcodes || (Array.isArray(this) ? this : []));
let lines: string[] = [];
function consumeOpCode(opCode: number): string {
const parent = getParentFromI18nMutateOpCode(opCode);
const ref = getRefFromI18nMutateOpCode(opCode);
switch (getInstructionFromI18nMutateOpCode(opCode)) {
case I18nMutateOpCode.Select:
lastRef = ref;
return '';
case I18nMutateOpCode.AppendChild:
return `(lView[${parent}] as Element).appendChild(lView[${lastRef}])`;
case I18nMutateOpCode.Remove:
return `(lView[${parent}] as Element).remove(lView[${ref}])`;
case I18nMutateOpCode.Attr:
return `(lView[${ref}] as Element).setAttribute("${parser.consumeString()}", "${
parser.consumeString()}")`;
case I18nMutateOpCode.ElementEnd:
return `setPreviousOrParentTNode(tView.data[${ref}] as TNode)`;
case I18nMutateOpCode.RemoveNestedIcu:
return `removeNestedICU(${ref})`;
}
throw new Error('Unexpected OpCode');
}
let lastRef = -1;
while (parser.hasMore()) {
let value = parser.consumeNumberStringOrMarker();
if (value === COMMENT_MARKER) {
const text = parser.consumeString();
lastRef = parser.consumeNumber();
lines.push(`lView[${lastRef}] = document.createComment("${text}")`);
} else if (value === ELEMENT_MARKER) {
const text = parser.consumeString();
lastRef = parser.consumeNumber();
lines.push(`lView[${lastRef}] = document.createElement("${text}")`);
} else if (typeof value === 'string') {
lastRef = parser.consumeNumber();
lines.push(`lView[${lastRef}] = document.createTextNode("${value}")`);
} else if (typeof value === 'number') {
const line = consumeOpCode(value);
line && lines.push(line);
} else {
throw new Error('Unexpected value');
}
}
return lines;
}
class OpCodeParser {
i: number = 0;
codes: any[];
constructor(codes: any[]) {
this.codes = codes;
}
hasMore() {
return this.i < this.codes.length;
}
consumeNumber(): number {
let value = this.codes[this.i++];
assertNumber(value, 'expecting number in OpCode');
return value;
}
consumeString(): string {
let value = this.codes[this.i++];
assertString(value, 'expecting string in OpCode');
return value;
}
consumeFunction(): Function|null {
let value = this.codes[this.i++];
if (value === null || typeof value === 'function') {
return value;
}
throw new Error('expecting function in OpCode');
}
consumeNumberOrString(): number|string {
let value = this.codes[this.i++];
if (typeof value === 'string') {
return value;
}
assertNumber(value, 'expecting number or string in OpCode');
return value;
}
consumeNumberStringOrMarker(): number|string|COMMENT_MARKER|ELEMENT_MARKER {
let value = this.codes[this.i++];
if (typeof value === 'string' || typeof value === 'number' || value == COMMENT_MARKER ||
value == ELEMENT_MARKER) {
return value;
}
assertNumber(value, 'expecting number, string, COMMENT_MARKER or ELEMENT_MARKER in OpCode');
return value;
}
}

View File

@ -15,15 +15,14 @@ import {createNamedArrayType} from '../../util/named_array_type';
import {initNgDevMode} from '../../util/ng_dev_mode';
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
import {DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, TIcu} from '../interfaces/i18n';
import {PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node';
import {PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TNodeTypeAsString, TViewNode} from '../interfaces/node';
import {SelectorFlags} from '../interfaces/projection';
import {LQueries, TQueries} from '../interfaces/query';
import {RComment, RElement, Renderer3, RendererFactory3, RNode} from '../interfaces/renderer';
import {getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, TStylingKey, TStylingRange} from '../interfaces/styling';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, DestroyHookData, ExpandoInstructions, FLAGS, HEADER_OFFSET, HookData, HOST, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TVIEW, TView as ITView, TView, TViewType} from '../interfaces/view';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DebugNode, DECLARATION_VIEW, DestroyHookData, ExpandoInstructions, FLAGS, HEADER_OFFSET, HookData, HOST, INJECTOR, LContainerDebug as ILContainerDebug, LView, LViewDebug as ILViewDebug, LViewDebugRange, LViewDebugRangeContent, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TView as ITView, TVIEW, TView, TViewType} from '../interfaces/view';
import {attachDebugObject} from '../util/debug_utils';
import {getTNode, unwrapRNode} from '../util/view_utils';
import {unwrapRNode} from '../util/view_utils';
const NG_DEV_MODE = ((typeof ngDevMode === 'undefined' || !!ngDevMode) && initNgDevMode());
@ -143,7 +142,10 @@ export const TViewConstructor = class TView implements ITView {
public firstChild: ITNode|null, //
public schemas: SchemaMetadata[]|null, //
public consts: TConstants|null, //
public incompleteFirstPass: boolean //
public incompleteFirstPass: boolean, //
public _decls: number, //
public _vars: number, //
) {}
get template_(): string {
@ -335,9 +337,9 @@ export function attachLContainerDebug(lContainer: LContainer) {
attachDebugObject(lContainer, new LContainerDebug(lContainer));
}
export function toDebug(obj: LView): LViewDebug;
export function toDebug(obj: LView|null): LViewDebug|null;
export function toDebug(obj: LView|LContainer|null): LViewDebug|LContainerDebug|null;
export function toDebug(obj: LView): ILViewDebug;
export function toDebug(obj: LView|null): ILViewDebug|null;
export function toDebug(obj: LView|LContainer|null): ILViewDebug|ILContainerDebug|null;
export function toDebug(obj: any): any {
if (obj) {
const debug = (obj as any).debug;
@ -375,7 +377,7 @@ function toHtml(value: any, includeChildren: boolean = false): string|null {
}
}
export class LViewDebug {
export class LViewDebug implements ILViewDebug {
constructor(private readonly _raw_lView: LView) {}
/**
@ -396,10 +398,10 @@ export class LViewDebug {
indexWithinInitPhase: flags >> LViewFlags.IndexWithinInitPhaseShift,
};
}
get parent(): LViewDebug|LContainerDebug|null {
get parent(): ILViewDebug|ILContainerDebug|null {
return toDebug(this._raw_lView[PARENT]);
}
get host(): string|null {
get hostHTML(): string|null {
return toHtml(this._raw_lView[HOST], true);
}
get html(): string {
@ -410,10 +412,9 @@ export class LViewDebug {
}
/**
* The tree of nodes associated with the current `LView`. The nodes have been normalized into
* a
* tree structure with relevant details pulled out for readability.
* a tree structure with relevant details pulled out for readability.
*/
get nodes(): DebugNode[]|null {
get nodes(): DebugNode[] {
const lView = this._raw_lView;
const tNode = lView[TVIEW].firstChild;
return toDebugNodes(tNode, lView);
@ -437,16 +438,16 @@ export class LViewDebug {
get sanitizer(): Sanitizer|null {
return this._raw_lView[SANITIZER];
}
get childHead(): LViewDebug|LContainerDebug|null {
get childHead(): ILViewDebug|ILContainerDebug|null {
return toDebug(this._raw_lView[CHILD_HEAD]);
}
get next(): LViewDebug|LContainerDebug|null {
get next(): ILViewDebug|ILContainerDebug|null {
return toDebug(this._raw_lView[NEXT]);
}
get childTail(): LViewDebug|LContainerDebug|null {
get childTail(): ILViewDebug|ILContainerDebug|null {
return toDebug(this._raw_lView[CHILD_TAIL]);
}
get declarationView(): LViewDebug|null {
get declarationView(): ILViewDebug|null {
return toDebug(this._raw_lView[DECLARATION_VIEW]);
}
get queries(): LQueries|null {
@ -456,11 +457,35 @@ export class LViewDebug {
return this._raw_lView[T_HOST];
}
get decls(): LViewDebugRange {
const tView = this.tView as any as {_decls: number, _vars: number};
const start = HEADER_OFFSET;
return toLViewRange(this.tView, this._raw_lView, start, start + tView._decls);
}
get vars(): LViewDebugRange {
const tView = this.tView as any as {_decls: number, _vars: number};
const start = HEADER_OFFSET + tView._decls;
return toLViewRange(this.tView, this._raw_lView, start, start + tView._vars);
}
get i18n(): LViewDebugRange {
const tView = this.tView as any as {_decls: number, _vars: number};
const start = HEADER_OFFSET + tView._decls + tView._vars;
return toLViewRange(this.tView, this._raw_lView, start, this.tView.expandoStartIndex);
}
get expando(): LViewDebugRange {
const tView = this.tView as any as {_decls: number, _vars: number};
return toLViewRange(
this.tView, this._raw_lView, this.tView.expandoStartIndex, this._raw_lView.length);
}
/**
* Normalized view of child views (and containers) attached at this location.
*/
get childViews(): Array<LViewDebug|LContainerDebug> {
const childViews: Array<LViewDebug|LContainerDebug> = [];
get childViews(): Array<ILViewDebug|ILContainerDebug> {
const childViews: Array<ILViewDebug|ILContainerDebug> = [];
let child = this.childHead;
while (child) {
childViews.push(child);
@ -470,11 +495,12 @@ export class LViewDebug {
}
}
export interface DebugNode {
html: string|null;
native: Node;
nodes: DebugNode[]|null;
component: LViewDebug|null;
function toLViewRange(tView: TView, lView: LView, start: number, end: number): LViewDebugRange {
let content: LViewDebugRangeContent[] = [];
for (let index = start; index < end; index++) {
content.push({index: index, t: tView.data[index], l: lView[index]});
}
return {start: start, end: end, length: end - start, content: content};
}
/**
@ -483,7 +509,7 @@ export interface DebugNode {
* @param tNode
* @param lView
*/
export function toDebugNodes(tNode: ITNode|null, lView: LView): DebugNode[]|null {
export function toDebugNodes(tNode: ITNode|null, lView: LView): DebugNode[] {
if (tNode) {
const debugNodes: DebugNode[] = [];
let tNodeCursor: ITNode|null = tNode;
@ -493,33 +519,32 @@ export function toDebugNodes(tNode: ITNode|null, lView: LView): DebugNode[]|null
}
return debugNodes;
} else {
return null;
return [];
}
}
export function buildDebugNode(tNode: ITNode, lView: LView, nodeIndex: number): DebugNode {
const rawValue = lView[nodeIndex];
const native = unwrapRNode(rawValue);
const componentLViewDebug = toDebug(readLViewValue(rawValue));
return {
html: toHtml(native),
type: TNodeTypeAsString[tNode.type],
native: native as any,
nodes: toDebugNodes(tNode.child, lView),
component: componentLViewDebug,
children: toDebugNodes(tNode.child, lView),
};
}
export class LContainerDebug {
export class LContainerDebug implements ILContainerDebug {
constructor(private readonly _raw_lContainer: LContainer) {}
get hasTransplantedViews(): boolean {
return this._raw_lContainer[HAS_TRANSPLANTED_VIEWS];
}
get views(): LViewDebug[] {
get views(): ILViewDebug[] {
return this._raw_lContainer.slice(CONTAINER_HEADER_OFFSET)
.map(toDebug as (l: LView) => LViewDebug);
.map(toDebug as (l: LView) => ILViewDebug);
}
get parent(): LViewDebug|LContainerDebug|null {
get parent(): ILViewDebug|null {
return toDebug(this._raw_lContainer[PARENT]);
}
get movedViews(): LView[]|null {
@ -550,206 +575,3 @@ export function readLViewValue(value: any): LView|null {
}
return null;
}
export class I18NDebugItem {
[key: string]: any;
get tNode() {
return getTNode(this._lView[TVIEW], this.nodeIndex);
}
constructor(
public __raw_opCode: any, private _lView: LView, public nodeIndex: number,
public type: string) {}
}
/**
* Turns a list of "Create" & "Update" OpCodes into a human-readable list of operations for
* debugging purposes.
* @param mutateOpCodes mutation opCodes to read
* @param updateOpCodes update opCodes to read
* @param icus list of ICU expressions
* @param lView The view the opCodes are acting on
*/
export function attachI18nOpCodesDebug(
mutateOpCodes: I18nMutateOpCodes, updateOpCodes: I18nUpdateOpCodes, icus: TIcu[]|null,
lView: LView) {
attachDebugObject(mutateOpCodes, new I18nMutateOpCodesDebug(mutateOpCodes, lView));
attachDebugObject(updateOpCodes, new I18nUpdateOpCodesDebug(updateOpCodes, icus, lView));
if (icus) {
icus.forEach(icu => {
icu.create.forEach(icuCase => {
attachDebugObject(icuCase, new I18nMutateOpCodesDebug(icuCase, lView));
});
icu.update.forEach(icuCase => {
attachDebugObject(icuCase, new I18nUpdateOpCodesDebug(icuCase, icus, lView));
});
});
}
}
export class I18nMutateOpCodesDebug implements I18nOpCodesDebug {
constructor(private readonly __raw_opCodes: I18nMutateOpCodes, private readonly __lView: LView) {}
/**
* A list of operation information about how the OpCodes will act on the view.
*/
get operations() {
const {__lView, __raw_opCodes} = this;
const results: any[] = [];
for (let i = 0; i < __raw_opCodes.length; i++) {
const opCode = __raw_opCodes[i];
let result: any;
if (typeof opCode === 'string') {
result = {
__raw_opCode: opCode,
type: 'Create Text Node',
nodeIndex: __raw_opCodes[++i],
text: opCode,
};
}
if (typeof opCode === 'number') {
switch (opCode & I18nMutateOpCode.MASK_OPCODE) {
case I18nMutateOpCode.AppendChild:
const destinationNodeIndex = opCode >>> I18nMutateOpCode.SHIFT_PARENT;
result = new I18NDebugItem(opCode, __lView, destinationNodeIndex, 'AppendChild');
break;
case I18nMutateOpCode.Select:
const nodeIndex = opCode >>> I18nMutateOpCode.SHIFT_REF;
result = new I18NDebugItem(opCode, __lView, nodeIndex, 'Select');
break;
case I18nMutateOpCode.ElementEnd:
let elementIndex = opCode >>> I18nMutateOpCode.SHIFT_REF;
result = new I18NDebugItem(opCode, __lView, elementIndex, 'ElementEnd');
break;
case I18nMutateOpCode.Attr:
elementIndex = opCode >>> I18nMutateOpCode.SHIFT_REF;
result = new I18NDebugItem(opCode, __lView, elementIndex, 'Attr');
result['attrName'] = __raw_opCodes[++i];
result['attrValue'] = __raw_opCodes[++i];
break;
}
}
if (!result) {
switch (opCode) {
case COMMENT_MARKER:
result = {
__raw_opCode: opCode,
type: 'COMMENT_MARKER',
commentValue: __raw_opCodes[++i],
nodeIndex: __raw_opCodes[++i],
};
break;
case ELEMENT_MARKER:
result = {
__raw_opCode: opCode,
type: 'ELEMENT_MARKER',
};
break;
}
}
if (!result) {
result = {
__raw_opCode: opCode,
type: 'Unknown Op Code',
code: opCode,
};
}
results.push(result);
}
return results;
}
}
export class I18nUpdateOpCodesDebug implements I18nOpCodesDebug {
constructor(
private readonly __raw_opCodes: I18nUpdateOpCodes, private readonly icus: TIcu[]|null,
private readonly __lView: LView) {}
/**
* A list of operation information about how the OpCodes will act on the view.
*/
get operations() {
const {__lView, __raw_opCodes, icus} = this;
const results: any[] = [];
for (let i = 0; i < __raw_opCodes.length; i++) {
// bit code to check if we should apply the next update
const checkBit = __raw_opCodes[i] as number;
// Number of opCodes to skip until next set of update codes
const skipCodes = __raw_opCodes[++i] as number;
let value = '';
for (let j = i + 1; j <= (i + skipCodes); j++) {
const opCode = __raw_opCodes[j];
if (typeof opCode === 'string') {
value += opCode;
} else if (typeof opCode == 'number') {
if (opCode < 0) {
// It's a binding index whose value is negative
// We cannot know the value of the binding so we only show the index
value += `<EFBFBD>${- opCode - 1}<EFBFBD>`;
} else {
const nodeIndex = opCode >>> I18nUpdateOpCode.SHIFT_REF;
let tIcuIndex: number;
let tIcu: TIcu;
switch (opCode & I18nUpdateOpCode.MASK_OPCODE) {
case I18nUpdateOpCode.Attr:
const attrName = __raw_opCodes[++j] as string;
const sanitizeFn = __raw_opCodes[++j];
results.push({
__raw_opCode: opCode,
checkBit,
type: 'Attr',
attrValue: value,
attrName,
sanitizeFn,
});
break;
case I18nUpdateOpCode.Text:
results.push({
__raw_opCode: opCode,
checkBit,
type: 'Text',
nodeIndex,
text: value,
});
break;
case I18nUpdateOpCode.IcuSwitch:
tIcuIndex = __raw_opCodes[++j] as number;
tIcu = icus![tIcuIndex];
let result = new I18NDebugItem(opCode, __lView, nodeIndex, 'IcuSwitch');
result['tIcuIndex'] = tIcuIndex;
result['checkBit'] = checkBit;
result['mainBinding'] = value;
result['tIcu'] = tIcu;
results.push(result);
break;
case I18nUpdateOpCode.IcuUpdate:
tIcuIndex = __raw_opCodes[++j] as number;
tIcu = icus![tIcuIndex];
result = new I18NDebugItem(opCode, __lView, nodeIndex, 'IcuUpdate');
result['tIcuIndex'] = tIcuIndex;
result['checkBit'] = checkBit;
result['tIcu'] = tIcu;
results.push(result);
break;
}
}
}
}
i += skipCodes;
}
return results;
}
}
export interface I18nOpCodesDebug {
operations: any[];
}

View File

@ -236,6 +236,13 @@ export function getOrCreateTNode(
const tNode = tView.data[adjustedIndex] as TNode ||
createTNodeAtIndex(tView, tHostNode, adjustedIndex, type, name, attrs);
setPreviousOrParentTNode(tNode, true);
if (ngDevMode) {
// For performance reasons it is important that the tNode retains the same shape during runtime.
// (To make sure that all of the code is monomorphic.) For this reason we seal the object to
// prevent class transitions.
// FIXME(misko): re-enable this once i18n code is compliant with this.
// Object.seal(tNode);
}
return tNode as TElementNode & TViewNode & TContainerNode & TElementContainerNode &
TProjectionNode & TIcuContainerNode;
}
@ -692,7 +699,9 @@ export function createTView(
null, // firstChild: TNode|null,
schemas, // schemas: SchemaMetadata[]|null,
consts, // consts: TConstants|null
false // incompleteFirstPass: boolean
false, // incompleteFirstPass: boolean
decls, // ngDevMode only: decls
vars, // ngDevMode only: vars
) :
{
type: type,

View File

@ -6,18 +6,31 @@
* found in the LICENSE file at https://angular.io/license
*/
import {SanitizerFn} from './sanitization';
/**
* `I18nMutateOpCode` defines OpCodes for `I18nMutateOpCodes` array.
*
* OpCodes are efficient operations which can be applied to the DOM to update it. (For example to
* update to a new ICU case requires that we clean up previous elements and create new ones.)
*
* OpCodes contain three parts:
* 1) Parent node index offset.
* 2) Reference node index offset.
* 3) The OpCode to execute.
* 1) Parent node index offset. (p)
* 2) Reference node index offset. (r)
* 3) The instruction to execute. (i)
*
* pppp pppp pppp pppp rrrr rrrr rrrr riii
* 3322 2222 2222 1111 1111 1110 0000 0000
* 1098 7654 3210 9876 5432 1098 7654 3210
*
* ```
* var parent = lView[opCode >>> SHIFT_PARENT];
* var refNode = lView[((opCode & MASK_REF) >>> SHIFT_REF)];
* var instruction = opCode & MASK_OPCODE;
* ```
*
* See: `I18nCreateOpCodes` for example of usage.
*/
import {SanitizerFn} from './sanitization';
export const enum I18nMutateOpCode {
/**
* Stores shift amount for bits 17-3 that contain reference index.
@ -30,36 +43,61 @@ export const enum I18nMutateOpCode {
/**
* Mask for OpCode
*/
MASK_OPCODE = 0b111,
MASK_INSTRUCTION = 0b111,
/**
* OpCode to select a node. (next OpCode will contain the operation.)
* Mask for the Reference node (bits 16-3)
*/
// FIXME(misko): Why is this not used?
MASK_REF = 0b11111111111111000,
// 11111110000000000
// 65432109876543210
/**
* Instruction to select a node. (next OpCode will contain the operation.)
*/
Select = 0b000,
/**
* OpCode to append the current node to `PARENT`.
* Instruction to append the current node to `PARENT`.
*/
AppendChild = 0b001,
/**
* OpCode to remove the `REF` node from `PARENT`.
* Instruction to remove the `REF` node from `PARENT`.
*/
Remove = 0b011,
/**
* OpCode to set the attribute of a node.
* Instruction to set the attribute of a node.
*/
Attr = 0b100,
/**
* OpCode to simulate elementEnd()
* Instruction to simulate elementEnd()
*/
ElementEnd = 0b101,
/**
* OpCode to read the remove OpCodes for the nested ICU
* Instruction to removed the nested ICU.
*/
RemoveNestedIcu = 0b110,
}
export function getParentFromI18nMutateOpCode(mergedCode: number): number {
return mergedCode >>> I18nMutateOpCode.SHIFT_PARENT;
}
export function getRefFromI18nMutateOpCode(mergedCode: number): number {
return (mergedCode & I18nMutateOpCode.MASK_REF) >>> I18nMutateOpCode.SHIFT_REF;
}
export function getInstructionFromI18nMutateOpCode(mergedCode: number): number {
return mergedCode & I18nMutateOpCode.MASK_INSTRUCTION;
}
/**
* Marks that the next string is for element.
* Marks that the next string is an element name.
*
* See `I18nMutateOpCodes` documentation.
*/
@ -71,7 +109,7 @@ export interface ELEMENT_MARKER {
}
/**
* Marks that the next string is for comment.
* Marks that the next string is comment text.
*
* See `I18nMutateOpCodes` documentation.
*/
@ -83,6 +121,18 @@ export interface COMMENT_MARKER {
marker: 'comment';
}
export interface I18nDebug {
/**
* Human readable representation of the OpCode arrays.
*
* NOTE: This property only exists if `ngDevMode` is set to `true` and it is not present in
* production. Its presence is purely to help debug issue in development, and should not be relied
* on in production application.
*/
debug?: string[];
}
/**
* Array storing OpCode for dynamically creating `i18n` blocks.
*
@ -92,50 +142,27 @@ export interface COMMENT_MARKER {
* // For adding text nodes
* // ---------------------
* // Equivalent to:
* // const node = lView[index++] = document.createTextNode('abc');
* // lView[1].insertBefore(node, lView[2]);
* 'abc', 1 << SHIFT_PARENT | 2 << SHIFT_REF | InsertBefore,
*
* // Equivalent to:
* // const node = lView[index++] = document.createTextNode('xyz');
* // lView[1].appendChild(node);
* 'xyz', 1 << SHIFT_PARENT | AppendChild,
* // lView[1].appendChild(lView[0] = document.createTextNode('xyz'));
* 'xyz', 0, 1 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
*
* // For adding element nodes
* // ---------------------
* // Equivalent to:
* // const node = lView[index++] = document.createElement('div');
* // lView[1].insertBefore(node, lView[2]);
* ELEMENT_MARKER, 'div', 1 << SHIFT_PARENT | 2 << SHIFT_REF | InsertBefore,
*
* // Equivalent to:
* // const node = lView[index++] = document.createElement('div');
* // lView[1].appendChild(node);
* ELEMENT_MARKER, 'div', 1 << SHIFT_PARENT | AppendChild,
* // lView[1].appendChild(lView[0] = document.createElement('div'));
* ELEMENT_MARKER, 'div', 0, 1 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
*
* // For adding comment nodes
* // ---------------------
* // Equivalent to:
* // const node = lView[index++] = document.createComment('');
* // lView[1].insertBefore(node, lView[2]);
* COMMENT_MARKER, '', 1 << SHIFT_PARENT | 2 << SHIFT_REF | InsertBefore,
*
* // Equivalent to:
* // const node = lView[index++] = document.createComment('');
* // lView[1].appendChild(node);
* COMMENT_MARKER, '', 1 << SHIFT_PARENT | AppendChild,
* // lView[1].appendChild(lView[0] = document.createComment(''));
* COMMENT_MARKER, '', 0, 1 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
*
* // For moving existing nodes to a different location
* // --------------------------------------------------
* // Equivalent to:
* // const node = lView[1];
* // lView[2].insertBefore(node, lView[3]);
* 1 << SHIFT_REF | Select, 2 << SHIFT_PARENT | 3 << SHIFT_REF | InsertBefore,
*
* // Equivalent to:
* // const node = lView[1];
* // lView[2].appendChild(node);
* 1 << SHIFT_REF | Select, 2 << SHIFT_PARENT | AppendChild,
* 1 << SHIFT_REF | Select, 2 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
*
* // For removing existing nodes
* // --------------------------------------------------
@ -147,18 +174,14 @@ export interface COMMENT_MARKER {
* // --------------------------------------------------
* // const node = lView[1];
* // node.setAttribute('attr', 'value');
* 1 << SHIFT_REF | Select, 'attr', 'value'
* // NOTE: Select followed by two string (vs select followed by OpCode)
* 1 << SHIFT_REF | Attr, 'attr', 'value'
* ];
* ```
* NOTE:
* - `index` is initial location where the extra nodes should be stored in the EXPANDO section of
* `LVIewData`.
*
* See: `applyI18nCreateOpCodes`;
*/
export interface I18nMutateOpCodes extends Array<number|string|ELEMENT_MARKER|COMMENT_MARKER|null> {
}
export interface I18nMutateOpCodes extends Array<number|string|ELEMENT_MARKER|COMMENT_MARKER|null>,
I18nDebug {}
export const enum I18nUpdateOpCode {
/**
@ -171,19 +194,19 @@ export const enum I18nUpdateOpCode {
MASK_OPCODE = 0b11,
/**
* OpCode to update a text node.
* Instruction to update a text node.
*/
Text = 0b00,
/**
* OpCode to update a attribute of a node.
* Instruction to update a attribute of a node.
*/
Attr = 0b01,
/**
* OpCode to switch the current ICU case.
* Instruction to switch the current ICU case.
*/
IcuSwitch = 0b10,
/**
* OpCode to update the current ICU case.
* Instruction to update the current ICU case.
*/
IcuUpdate = 0b11,
}
@ -197,6 +220,10 @@ export const enum I18nUpdateOpCode {
* higher.) The OpCodes then compare its own change mask against the expression change mask to
* determine if the OpCodes should execute.
*
* NOTE: 32nd bit is special as it says 32nd or higher. This way if we have more than 32 bindings
* the code still works, but with lower efficiency. (it is unlikely that a translation would have
* more than 32 bindings.)
*
* These OpCodes can be used by both the i18n block as well as ICU sub-block.
*
* ## Example
@ -220,8 +247,8 @@ export const enum I18nUpdateOpCode {
* // The following OpCodes represent: `<div i18n-title="pre{{exp1}}in{{exp2}}post">`
* // If `changeMask & 0b11`
* // has changed then execute update OpCodes.
* // has NOT changed then skip `7` values and start processing next OpCodes.
* 0b11, 7,
* // has NOT changed then skip `8` values and start processing next OpCodes.
* 0b11, 8,
* // Concatenate `newValue = 'pre'+lView[bindIndex-4]+'in'+lView[bindIndex-3]+'post';`.
* 'pre', -4, 'in', -3, 'post',
* // Update attribute: `elementAttribute(1, 'title', sanitizerFn(newValue));`
@ -240,8 +267,8 @@ export const enum I18nUpdateOpCode {
* // The following OpCodes represent: `<div i18n>{exp4, plural, ... }">`
* // If `changeMask & 0b1000`
* // has changed then execute update OpCodes.
* // has NOT changed then skip `4` values and start processing next OpCodes.
* 0b1000, 4,
* // has NOT changed then skip `2` values and start processing next OpCodes.
* 0b1000, 2,
* // Concatenate `newValue = lView[bindIndex -1];`.
* -1,
* // Switch ICU: `icuSwitchCase(lView[1], 0, newValue);`
@ -256,7 +283,7 @@ export const enum I18nUpdateOpCode {
* ```
*
*/
export interface I18nUpdateOpCodes extends Array<string|number|SanitizerFn|null> {}
export interface I18nUpdateOpCodes extends Array<string|number|SanitizerFn|null>, I18nDebug {}
/**
* Store information for the i18n translation block.

View File

@ -7,14 +7,11 @@
*/
import {KeyValueArray} from '../../util/array_utils';
import {TStylingRange} from '../interfaces/styling';
import {DirectiveDef} from './definition';
import {CssSelector} from './projection';
import {RNode} from './renderer';
import {LView, TView} from './view';
/**
* TNodeType corresponds to the {@link TNode} `type` property.
*/
@ -45,6 +42,20 @@ export const enum TNodeType {
IcuContainer = 5,
}
/**
* Converts `TNodeType` into human readable text.
* Make sure this matches with `TNodeType`
*/
export const TNodeTypeAsString = [
'Container', // 0
'Projection', // 1
'View', // 2
'Element', // 3
'ElementContainer', // 4
'IcuContainer' // 5
] as const;
/**
* Corresponds to the TNode.flags property.
*/
@ -701,7 +712,9 @@ export interface TIcuContainerNode extends TNode {
/**
* Indicates the current active case for an ICU expression.
* It is null when there is no active case.
*
*/
// FIXME(misko): This is at a wrong location as activeCase is `LView` (not `TView`) concern
activeCaseIndex: number|null;
}

View File

@ -15,10 +15,10 @@ import {Sanitizer} from '../../sanitization/sanitizer';
import {LContainer} from './container';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList, ViewQueriesFunction} from './definition';
import {I18nUpdateOpCodes, TI18n} from './i18n';
import {TConstants, TElementNode, TNode, TViewNode} from './node';
import {TConstants, TElementNode, TNode, TNodeTypeAsString, TViewNode} from './node';
import {PlayerHandler} from './player';
import {LQueries, TQueries} from './query';
import {RElement, Renderer3, RendererFactory3} from './renderer';
import {RComment, RElement, Renderer3, RendererFactory3} from './renderer';
import {TStylingKey, TStylingRange} from './styling';
@ -69,6 +69,15 @@ export interface OpaqueViewState {
* don't have to edit the data array based on which views are present.
*/
export interface LView extends Array<any> {
/**
* Human readable representation of the `LView`.
*
* NOTE: This property only exists if `ngDevMode` is set to `true` and it is not present in
* production. Its presence is purely to help debug issue in development, and should not be relied
* on in production application.
*/
debug?: LViewDebug;
/**
* The host node for this LView instance, if this is a component view.
* If this is an embedded view, HOST will be null.
@ -826,3 +835,190 @@ export type TData =
// Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types.
export const unusedValueExportToPlacateAjd = 1;
/**
* Human readable version of the `LView`.
*
* `LView` is a data structure used internally to keep track of views. The `LView` is designed for
* efficiency and so at times it is difficult to read or write tests which assert on its values. For
* this reason when `ngDevMode` is true we patch a `LView.debug` property which points to
* `LViewDebug` for easier debugging and test writing. It is the intent of `LViewDebug` to be used
* in tests.
*/
export interface LViewDebug {
/**
* Flags associated with the `LView` unpacked into a more readable state.
*
* See `LViewFlags` for the flag meanings.
*/
readonly flags: {
initPhaseState: number,
creationMode: boolean,
firstViewPass: boolean,
checkAlways: boolean,
dirty: boolean,
attached: boolean,
destroyed: boolean,
isRoot: boolean,
indexWithinInitPhase: number,
};
/**
* Parent view (or container)
*/
readonly parent: LViewDebug|LContainerDebug|null;
/**
* Next sibling to the `LView`.
*/
readonly next: LViewDebug|LContainerDebug|null;
/**
* The context used for evaluation of the `LView`
*
* (Usually the component)
*/
readonly context: {}|null;
/**
* Hierarchical tree of nodes.
*/
readonly nodes: DebugNode[];
/**
* HTML representation of the `LView`.
*
* This is only approximate to actual HTML as child `LView`s are removed.
*/
readonly html: string;
/**
* The host element to which this `LView` is attached.
*/
readonly hostHTML: string|null;
/**
* Child `LView`s
*/
readonly childViews: Array<LViewDebug|LContainerDebug>;
/**
* Sub range of `LView` containing decls (DOM elements).
*/
readonly decls: LViewDebugRange;
/**
* Sub range of `LView` containing vars (bindings).
*/
readonly vars: LViewDebugRange;
/**
* Sub range of `LView` containing i18n (translated DOM elements).
*/
readonly i18n: LViewDebugRange;
/**
* Sub range of `LView` containing expando (used by DI).
*/
readonly expando: LViewDebugRange;
}
/**
* Human readable version of the `LContainer`
*
* `LContainer` is a data structure used internally to keep track of child views. The `LContainer`
* is designed for efficiency and so at times it is difficult to read or write tests which assert on
* its values. For this reason when `ngDevMode` is true we patch a `LContainer.debug` property which
* points to `LContainerDebug` for easier debugging and test writing. It is the intent of
* `LContainerDebug` to be used in tests.
*/
export interface LContainerDebug {
readonly native: RComment;
/**
* Child `LView`s.
*/
readonly views: LViewDebug[];
readonly parent: LViewDebug|null;
readonly movedViews: LView[]|null;
readonly host: RElement|RComment|LView;
readonly next: LViewDebug|LContainerDebug|null;
readonly hasTransplantedViews: boolean;
}
/**
* `LView` is subdivided to ranges where the actual data is stored. Some of these ranges such as
* `decls` and `vars` are known at compile time. Other such as `i18n` and `expando` are runtime only
* concepts.
*/
export interface LViewDebugRange {
/**
* The starting index in `LView` where the range begins. (Inclusive)
*/
start: number;
/**
* The ending index in `LView` where the range ends. (Exclusive)
*/
end: number;
/**
* The length of the range
*/
length: number;
/**
* The merged content of the range. `t` contains data from `TView.data` and `l` contains `LView`
* data at an index.
*/
content: LViewDebugRangeContent[];
}
/**
* For convenience the static and instance portions of `TView` and `LView` are merged into a single
* object in `LViewRange`.
*/
export interface LViewDebugRangeContent {
/**
* Index into original `LView` or `TView.data`.
*/
index: number;
/**
* Value from the `TView.data[index]` location.
*/
t: any;
/**
* Value from the `LView[index]` location.
*/
l: any;
}
/**
* A logical node which comprise into `LView`s.
*
*/
export interface DebugNode {
/**
* HTML representation of the node.
*/
html: string|null;
/**
* Human readable node type.
*/
type: typeof TNodeTypeAsString[number];
/**
* DOM native node.
*/
native: Node;
/**
* Child nodes
*/
children: DebugNode[];
}

View File

@ -5,6 +5,37 @@
* 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
*/
export function attachDebugObject(obj: any, debug: any) {
Object.defineProperty(obj, 'debug', {value: debug, enumerable: false});
/**
* Patch a `debug` property on top of the existing object.
*
* NOTE: always call this method with `ngDevMode && attachDebugObject(...)`
*
* @param obj Object to patch
* @param debug Value to patch
*/
export function attachDebugObject(obj: any, debug: any): void {
if (ngDevMode) {
Object.defineProperty(obj, 'debug', {value: debug, enumerable: false});
} else {
throw new Error(
'This method should be guarded with `ngDevMode` so that it can be tree shaken in production!');
}
}
/**
* Patch a `debug` property getter on top of the existing object.
*
* NOTE: always call this method with `ngDevMode && attachDebugObject(...)`
*
* @param obj Object to patch
* @param debugGetter Getter returning a value to patch
*/
export function attachDebugGetter(obj: any, debugGetter: () => any): void {
if (ngDevMode) {
Object.defineProperty(obj, 'debug', {get: debugGetter, enumerable: false});
} else {
throw new Error(
'This method should be guarded with `ngDevMode` so that it can be tree shaken in production!');
}
}

View File

@ -10,12 +10,12 @@ import {Injector} from '../../di/injector';
import {assertLView} from '../assert';
import {discoverLocalRefs, getComponentAtNodeIndex, getDirectivesAtNodeIndex, getLContext} from '../context_discovery';
import {NodeInjector} from '../di';
import {buildDebugNode, DebugNode} from '../instructions/lview_debug';
import {buildDebugNode} from '../instructions/lview_debug';
import {LContext} from '../interfaces/context';
import {DirectiveDef} from '../interfaces/definition';
import {TElementNode, TNode, TNodeProviderIndexes} from '../interfaces/node';
import {isLView} from '../interfaces/type_checks';
import {CLEANUP, CONTEXT, FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, T_HOST, TVIEW} from '../interfaces/view';
import {CLEANUP, CONTEXT, DebugNode, FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, T_HOST, TVIEW} from '../interfaces/view';
import {stringifyForError} from './misc_utils';
import {getLViewParent, getRootContext} from './view_traversal_utils';

View File

@ -20,6 +20,7 @@ ts_library(
"//packages/compiler/testing",
"//packages/core",
"//packages/core/src/util",
"//packages/core/test/render3:matchers",
"//packages/core/testing",
"//packages/localize",
"//packages/localize/init",

View File

@ -8,13 +8,17 @@
import {Component} from '@angular/core';
import {getLContext} from '@angular/core/src/render3/context_discovery';
import {LViewDebug, toDebug} from '@angular/core/src/render3/instructions/lview_debug';
import {LViewDebug} from '@angular/core/src/render3/instructions/lview_debug';
import {TNodeType} from '@angular/core/src/render3/interfaces/node';
import {HEADER_OFFSET} from '@angular/core/src/render3/interfaces/view';
import {TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {onlyInIvy} from '@angular/private/testing';
describe('Debug Representation', () => {
onlyInIvy('Ivy specific').it('should generate a human readable version', () => {
import {matchDomElement, matchDomText, matchTI18n, matchTNode} from '../render3/matchers';
onlyInIvy('Ivy specific').describe('Debug Representation', () => {
it('should generate a human readable version', () => {
@Component({selector: 'my-comp', template: '<div id="123">Hello World</div>'})
class MyComponent {
}
@ -23,11 +27,56 @@ describe('Debug Representation', () => {
const fixture = TestBed.createComponent(MyComponent);
fixture.detectChanges();
const hostView = toDebug(getLContext(fixture.componentInstance)!.lView);
expect(hostView.host).toEqual(null);
const hostView = getLContext(fixture.componentInstance)!.lView.debug!;
expect(hostView.hostHTML).toEqual(null);
const myCompView = hostView.childViews[0] as LViewDebug;
expect(myCompView.host).toContain('<div id="123">Hello World</div>');
expect(myCompView.hostHTML).toContain('<div id="123">Hello World</div>');
expect(myCompView.nodes![0].html).toEqual('<div id="123">');
expect(myCompView.nodes![0].nodes![0].html).toEqual('Hello World');
expect(myCompView.nodes![0].children![0].html).toEqual('Hello World');
});
describe('LViewDebug', () => {
describe('range', () => {
it('should show ranges', () => {
@Component({selector: 'my-comp', template: '<div i18n>Hello {{name}}</div>'})
class MyComponent {
name = 'World';
}
TestBed.configureTestingModule({declarations: [MyComponent]});
const fixture = TestBed.createComponent(MyComponent);
fixture.detectChanges();
const hostView = getLContext(fixture.componentInstance)!.lView.debug!;
const myComponentView = hostView.childViews[0] as LViewDebug;
expect(myComponentView.decls).toEqual({
start: HEADER_OFFSET,
end: HEADER_OFFSET + 2,
length: 2,
content: [
{index: HEADER_OFFSET + 0, t: matchTNode({tagName: 'div'}), l: matchDomElement('div')},
{index: HEADER_OFFSET + 1, t: matchTI18n(), l: null},
]
});
expect(myComponentView.vars).toEqual({
start: HEADER_OFFSET + 2,
end: HEADER_OFFSET + 3,
length: 1,
content: [{index: HEADER_OFFSET + 2, t: null, l: 'World'}]
});
expect(myComponentView.i18n).toEqual({
start: HEADER_OFFSET + 3,
end: HEADER_OFFSET + 4,
length: 1,
content: [{
index: HEADER_OFFSET + 3,
t: matchTNode({type: TNodeType.Element, tagName: null}),
l: matchDomText('Hello World')
}]
});
expect(myComponentView.expando)
.toEqual({start: HEADER_OFFSET + 4, end: HEADER_OFFSET + 4, length: 0, content: []});
});
});
});
});

View File

@ -8,7 +8,7 @@
import {CommonModule} from '@angular/common';
import {Component, Directive, forwardRef, Inject, Injectable, InjectionToken, Injector, NgModule, Optional} from '@angular/core';
import {async, inject, TestBed} from '@angular/core/testing';
import {inject, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';
@ -603,7 +603,7 @@ describe('providers', () => {
});
it('should support injecting without bootstrapping',
async(inject([MyComp, MyService], (comp: MyComp, service: MyService) => {
waitForAsync(inject([MyComp, MyService], (comp: MyComp, service: MyService) => {
expect(comp.svc.value).toEqual('some value');
})));
});

View File

@ -8,19 +8,19 @@
import {Injector} from '@angular/core';
import {APP_INITIALIZER, ApplicationInitStatus} from '@angular/core/src/application_init';
import {async, inject, TestBed} from '../testing';
import {inject, TestBed, waitForAsync} from '../testing';
{
describe('ApplicationInitStatus', () => {
describe('no initializers', () => {
it('should return true for `done`',
async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
waitForAsync(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
(status as any).runInitializers();
expect(status.done).toBe(true);
})));
it('should return a promise that resolves immediately for `donePromise`',
async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
waitForAsync(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
(status as any).runInitializers();
status.donePromise.then(() => {
expect(status.done).toBe(true);
@ -58,7 +58,7 @@ import {async, inject, TestBed} from '../testing';
});
it('should update the status once all async initializers are done',
async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
waitForAsync(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
(status as any).runInitializers();
setTimeout(() => {

View File

@ -19,7 +19,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
import {onlyInIvy} from '@angular/private/testing';
import {NoopNgZone} from '../src/zone/ng_zone';
import {async, ComponentFixtureNoNgZone, inject, TestBed, withModule} from '../testing';
import {ComponentFixtureNoNgZone, inject, TestBed, waitForAsync, withModule} from '../testing';
@Component({selector: 'bootstrap-app', template: 'hello'})
class SomeComponent {
@ -79,62 +79,66 @@ class SomeComponent {
}
it('should bootstrap a component from a child module',
async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
@Component({
selector: 'bootstrap-app',
template: '',
})
class SomeComponent {
}
waitForAsync(
inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
@Component({
selector: 'bootstrap-app',
template: '',
})
class SomeComponent {
}
const helloToken = new InjectionToken<string>('hello');
const helloToken = new InjectionToken<string>('hello');
@NgModule({
providers: [{provide: helloToken, useValue: 'component'}],
declarations: [SomeComponent],
entryComponents: [SomeComponent],
})
class SomeModule {
}
@NgModule({
providers: [{provide: helloToken, useValue: 'component'}],
declarations: [SomeComponent],
entryComponents: [SomeComponent],
})
class SomeModule {
}
createRootEl();
const modFactory = compiler.compileModuleSync(SomeModule);
const module = modFactory.create(TestBed);
const cmpFactory = module.componentFactoryResolver.resolveComponentFactory(SomeComponent)!;
const component = app.bootstrap(cmpFactory);
createRootEl();
const modFactory = compiler.compileModuleSync(SomeModule);
const module = modFactory.create(TestBed);
const cmpFactory =
module.componentFactoryResolver.resolveComponentFactory(SomeComponent)!;
const component = app.bootstrap(cmpFactory);
// The component should see the child module providers
expect(component.injector.get(helloToken)).toEqual('component');
})));
// The component should see the child module providers
expect(component.injector.get(helloToken)).toEqual('component');
})));
it('should bootstrap a component with a custom selector',
async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
@Component({
selector: 'bootstrap-app',
template: '',
})
class SomeComponent {
}
waitForAsync(
inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
@Component({
selector: 'bootstrap-app',
template: '',
})
class SomeComponent {
}
const helloToken = new InjectionToken<string>('hello');
const helloToken = new InjectionToken<string>('hello');
@NgModule({
providers: [{provide: helloToken, useValue: 'component'}],
declarations: [SomeComponent],
entryComponents: [SomeComponent],
})
class SomeModule {
}
@NgModule({
providers: [{provide: helloToken, useValue: 'component'}],
declarations: [SomeComponent],
entryComponents: [SomeComponent],
})
class SomeModule {
}
createRootEl('custom-selector');
const modFactory = compiler.compileModuleSync(SomeModule);
const module = modFactory.create(TestBed);
const cmpFactory = module.componentFactoryResolver.resolveComponentFactory(SomeComponent)!;
const component = app.bootstrap(cmpFactory, 'custom-selector');
createRootEl('custom-selector');
const modFactory = compiler.compileModuleSync(SomeModule);
const module = modFactory.create(TestBed);
const cmpFactory =
module.componentFactoryResolver.resolveComponentFactory(SomeComponent)!;
const component = app.bootstrap(cmpFactory, 'custom-selector');
// The component should see the child module providers
expect(component.injector.get(helloToken)).toEqual('component');
})));
// The component should see the child module providers
expect(component.injector.get(helloToken)).toEqual('component');
})));
describe('ApplicationRef', () => {
beforeEach(() => {
@ -216,7 +220,7 @@ class SomeComponent {
defaultPlatform = _platform;
}));
it('should wait for asynchronous app initializers', async(() => {
it('should wait for asynchronous app initializers', waitForAsync(() => {
let resolve: (result: any) => void;
const promise: Promise<any> = new Promise((res) => {
resolve = res;
@ -235,7 +239,8 @@ class SomeComponent {
});
}));
it('should rethrow sync errors even if the exceptionHandler is not rethrowing', async(() => {
it('should rethrow sync errors even if the exceptionHandler is not rethrowing',
waitForAsync(() => {
defaultPlatform
.bootstrapModule(createModule([{
provide: APP_INITIALIZER,
@ -253,7 +258,7 @@ class SomeComponent {
}));
it('should rethrow promise errors even if the exceptionHandler is not rethrowing',
async(() => {
waitForAsync(() => {
defaultPlatform
.bootstrapModule(createModule([
{provide: APP_INITIALIZER, useValue: () => Promise.reject('Test'), multi: true}
@ -264,7 +269,7 @@ class SomeComponent {
});
}));
it('should throw useful error when ApplicationRef is not configured', async(() => {
it('should throw useful error when ApplicationRef is not configured', waitForAsync(() => {
@NgModule()
class EmptyModule {
}
@ -277,7 +282,7 @@ class SomeComponent {
}));
it('should call the `ngDoBootstrap` method with `ApplicationRef` on the main module',
async(() => {
waitForAsync(() => {
const ngDoBootstrap = jasmine.createSpy('ngDoBootstrap');
defaultPlatform.bootstrapModule(createModule({ngDoBootstrap: ngDoBootstrap}))
.then((moduleRef) => {
@ -286,7 +291,7 @@ class SomeComponent {
});
}));
it('should auto bootstrap components listed in @NgModule.bootstrap', async(() => {
it('should auto bootstrap components listed in @NgModule.bootstrap', waitForAsync(() => {
defaultPlatform.bootstrapModule(createModule({bootstrap: [SomeComponent]}))
.then((moduleRef) => {
const appRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
@ -295,7 +300,7 @@ class SomeComponent {
}));
it('should error if neither `ngDoBootstrap` nor @NgModule.bootstrap was specified',
async(() => {
waitForAsync(() => {
defaultPlatform.bootstrapModule(createModule({ngDoBootstrap: false}))
.then(() => expect(false).toBe(true), (e) => {
const expectedErrMsg =
@ -305,12 +310,12 @@ class SomeComponent {
});
}));
it('should add bootstrapped module into platform modules list', async(() => {
it('should add bootstrapped module into platform modules list', waitForAsync(() => {
defaultPlatform.bootstrapModule(createModule({bootstrap: [SomeComponent]}))
.then(module => expect((<any>defaultPlatform)._modules).toContain(module));
}));
it('should bootstrap with NoopNgZone', async(() => {
it('should bootstrap with NoopNgZone', waitForAsync(() => {
defaultPlatform
.bootstrapModule(createModule({bootstrap: [SomeComponent]}), {ngZone: 'noop'})
.then((module) => {
@ -376,7 +381,7 @@ class SomeComponent {
createRootEl();
defaultPlatform = _platform;
}));
it('should wait for asynchronous app initializers', async(() => {
it('should wait for asynchronous app initializers', waitForAsync(() => {
let resolve: (result: any) => void;
const promise: Promise<any> = new Promise((res) => {
resolve = res;
@ -396,7 +401,8 @@ class SomeComponent {
});
}));
it('should rethrow sync errors even if the exceptionHandler is not rethrowing', async(() => {
it('should rethrow sync errors even if the exceptionHandler is not rethrowing',
waitForAsync(() => {
const compilerFactory: CompilerFactory =
defaultPlatform.injector.get(CompilerFactory, null)!;
const moduleFactory = compilerFactory.createCompiler().compileModuleSync(createModule([{
@ -413,7 +419,7 @@ class SomeComponent {
}));
it('should rethrow promise errors even if the exceptionHandler is not rethrowing',
async(() => {
waitForAsync(() => {
const compilerFactory: CompilerFactory =
defaultPlatform.injector.get(CompilerFactory, null)!;
const moduleFactory = compilerFactory.createCompiler().compileModuleSync(createModule(
@ -623,25 +629,25 @@ class SomeComponent {
});
}
it('isStable should fire on synchronous component loading', async(() => {
it('isStable should fire on synchronous component loading', waitForAsync(() => {
expectStableTexts(SyncComp, ['1']);
}));
it('isStable should fire after a microtask on init is completed', async(() => {
it('isStable should fire after a microtask on init is completed', waitForAsync(() => {
expectStableTexts(MicroTaskComp, ['11']);
}));
it('isStable should fire after a macrotask on init is completed', async(() => {
it('isStable should fire after a macrotask on init is completed', waitForAsync(() => {
expectStableTexts(MacroTaskComp, ['11']);
}));
it('isStable should fire only after chain of micro and macrotasks on init are completed',
async(() => {
waitForAsync(() => {
expectStableTexts(MicroMacroTaskComp, ['111']);
}));
it('isStable should fire only after chain of macro and microtasks on init are completed',
async(() => {
waitForAsync(() => {
expectStableTexts(MacroMicroTaskComp, ['111']);
}));
@ -665,7 +671,7 @@ class SomeComponent {
});
}
it('should be fired after app becomes unstable', async(() => {
it('should be fired after app becomes unstable', waitForAsync(() => {
const fixture = TestBed.createComponent(ClickComp);
const appRef: ApplicationRef = TestBed.inject(ApplicationRef);
const zone: NgZone = TestBed.inject(NgZone);

View File

@ -701,9 +701,6 @@
{
"name": "_keyMap"
},
{
"name": "_mergeErrors"
},
{
"name": "_noControlError"
},
@ -914,6 +911,9 @@
{
"name": "executeTemplate"
},
{
"name": "executeValidators"
},
{
"name": "executeViewQueryFn"
},
@ -1346,6 +1346,9 @@
{
"name": "mergeAll"
},
{
"name": "mergeErrors"
},
{
"name": "mergeHostAttribute"
},
@ -1401,10 +1404,7 @@
"name": "noop"
},
{
"name": "normalizeAsyncValidator"
},
{
"name": "normalizeValidator"
"name": "normalizeValidators"
},
{
"name": "observable"

View File

@ -7,7 +7,7 @@
*/
import {Component, Injectable, Input} from '@angular/core';
import {async, ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, TestBed, withModule} from '@angular/core/testing';
import {ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, TestBed, waitForAsync, withModule} from '@angular/core/testing';
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -100,7 +100,7 @@ class NestedAsyncTimeoutComp {
{
describe('ComponentFixture', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [
AutoDetectComp, AsyncComp, AsyncTimeoutComp, NestedAsyncTimeoutComp, AsyncChangeComp,
@ -134,7 +134,7 @@ class NestedAsyncTimeoutComp {
}));
it('should signal through whenStable when the fixture is stable (autoDetectChanges)',
async(() => {
waitForAsync(() => {
const componentFixture = TestBed.createComponent(AsyncComp);
componentFixture.autoDetectChanges();
expect(componentFixture.nativeElement).toHaveText('1');
@ -153,7 +153,7 @@ class NestedAsyncTimeoutComp {
}));
it('should signal through isStable when the fixture is stable (no autoDetectChanges)',
async(() => {
waitForAsync(() => {
const componentFixture = TestBed.createComponent(AsyncComp);
componentFixture.detectChanges();
@ -174,7 +174,7 @@ class NestedAsyncTimeoutComp {
it('should wait for macroTask(setTimeout) while checking for whenStable ' +
'(autoDetectChanges)',
async(() => {
waitForAsync(() => {
const componentFixture = TestBed.createComponent(AsyncTimeoutComp);
componentFixture.autoDetectChanges();
expect(componentFixture.nativeElement).toHaveText('1');
@ -194,7 +194,7 @@ class NestedAsyncTimeoutComp {
it('should wait for macroTask(setTimeout) while checking for whenStable ' +
'(no autoDetectChanges)',
async(() => {
waitForAsync(() => {
const componentFixture = TestBed.createComponent(AsyncTimeoutComp);
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('1');
@ -215,7 +215,7 @@ class NestedAsyncTimeoutComp {
it('should wait for nested macroTasks(setTimeout) while checking for whenStable ' +
'(autoDetectChanges)',
async(() => {
waitForAsync(() => {
const componentFixture = TestBed.createComponent(NestedAsyncTimeoutComp);
componentFixture.autoDetectChanges();
@ -236,7 +236,7 @@ class NestedAsyncTimeoutComp {
it('should wait for nested macroTasks(setTimeout) while checking for whenStable ' +
'(no autoDetectChanges)',
async(() => {
waitForAsync(() => {
const componentFixture = TestBed.createComponent(NestedAsyncTimeoutComp);
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('1');
@ -255,7 +255,8 @@ class NestedAsyncTimeoutComp {
});
}));
it('should stabilize after async task in change detection (autoDetectChanges)', async(() => {
it('should stabilize after async task in change detection (autoDetectChanges)',
waitForAsync(() => {
const componentFixture = TestBed.createComponent(AsyncChangeComp);
componentFixture.autoDetectChanges();
@ -271,7 +272,8 @@ class NestedAsyncTimeoutComp {
});
}));
it('should stabilize after async task in change detection(no autoDetectChanges)', async(() => {
it('should stabilize after async task in change detection(no autoDetectChanges)',
waitForAsync(() => {
const componentFixture = TestBed.createComponent(AsyncChangeComp);
componentFixture.detectChanges();
componentFixture.whenStable().then((_) => {
@ -306,7 +308,7 @@ class NestedAsyncTimeoutComp {
}).toThrowError(/Cannot call autoDetectChanges when ComponentFixtureNoNgZone is set/);
});
it('should instantiate a component with valid DOM', async(() => {
it('should instantiate a component with valid DOM', waitForAsync(() => {
const componentFixture = TestBed.createComponent(SimpleComp);
expect(componentFixture.ngZone).toBeNull();
@ -314,7 +316,7 @@ class NestedAsyncTimeoutComp {
expect(componentFixture.nativeElement).toHaveText('Original Simple');
}));
it('should allow changing members of the component', async(() => {
it('should allow changing members of the component', waitForAsync(() => {
const componentFixture = TestBed.createComponent(MyIfComp);
componentFixture.detectChanges();

View File

@ -10,7 +10,7 @@
import {CommonModule, NgIfContext, ɵgetDOM as getDOM} from '@angular/common';
import {Component, DebugElement, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, OnInit, Output, Renderer2, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
import {NgZone} from '@angular/core/src/zone';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {createMouseEvent, hasClass} from '@angular/platform-browser/testing/src/browser_util';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -264,7 +264,7 @@ class TestCmptWithPropInterpolation {
describe('debug element', () => {
let fixture: ComponentFixture<any>;
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [
ChildComp,

View File

@ -17,7 +17,7 @@ import {TemplateRef} from '@angular/core/src/linker/template_ref';
import {ViewContainerRef} from '@angular/core/src/linker/view_container_ref';
import {EmbeddedViewRef} from '@angular/core/src/linker/view_ref';
import {Attribute, Component, ContentChildren, Directive, HostBinding, HostListener, Input, Output, Pipe} from '@angular/core/src/metadata';
import {async, fakeAsync, getTestBed, TestBed, tick} from '@angular/core/testing';
import {fakeAsync, getTestBed, TestBed, tick, waitForAsync} from '@angular/core/testing';
import {createMouseEvent, dispatchEvent, el, isCommentNode} from '@angular/platform-browser/testing/src/browser_util';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing';
@ -757,7 +757,7 @@ function declareTests(config?: {useJit: boolean}) {
expect(childComponent.myHost).toBeAnInstanceOf(SomeDirective);
});
it('should support events via EventEmitter on regular elements', async(() => {
it('should support events via EventEmitter on regular elements', waitForAsync(() => {
TestBed.configureTestingModule(
{declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]});
const template = '<div emitter listener></div>';
@ -787,7 +787,7 @@ function declareTests(config?: {useJit: boolean}) {
emitter.fireEvent('fired !');
}));
it('should support events via EventEmitter on template elements', async(() => {
it('should support events via EventEmitter on template elements', waitForAsync(() => {
const fixture =
TestBed
.configureTestingModule(
@ -819,7 +819,7 @@ function declareTests(config?: {useJit: boolean}) {
emitter.fireEvent('fired !');
}));
it('should support [()] syntax', async(() => {
it('should support [()] syntax', waitForAsync(() => {
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithTwoWayBinding]});
const template = '<div [(control)]="ctxProp" two-way></div>';
TestBed.overrideComponent(MyComp, {set: {template}});
@ -1072,7 +1072,7 @@ function declareTests(config?: {useJit: boolean}) {
});
describe('.createComponent', () => {
it('should allow to create a component at any bound location', async(() => {
it('should allow to create a component at any bound location', waitForAsync(() => {
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
.createComponent(MyComp);
const tc = fixture.debugElement.children[0].children[0];
@ -1083,7 +1083,7 @@ function declareTests(config?: {useJit: boolean}) {
.toHaveText('dynamic greet');
}));
it('should allow to create multiple components at a location', async(() => {
it('should allow to create multiple components at a location', waitForAsync(() => {
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
.createComponent(MyComp);
const tc = fixture.debugElement.children[0].children[0];
@ -1219,7 +1219,7 @@ function declareTests(config?: {useJit: boolean}) {
});
describe('.insert', () => {
it('should throw with destroyed views', async(() => {
it('should throw with destroyed views', waitForAsync(() => {
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
.createComponent(MyComp);
const tc = fixture.debugElement.children[0].children[0];
@ -1235,7 +1235,7 @@ function declareTests(config?: {useJit: boolean}) {
});
describe('.move', () => {
it('should throw with destroyed views', async(() => {
it('should throw with destroyed views', waitForAsync(() => {
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
.createComponent(MyComp);
const tc = fixture.debugElement.children[0].children[0];
@ -2001,7 +2001,7 @@ function declareTests(config?: {useJit: boolean}) {
});
describe('whitespaces in templates', () => {
it('should not remove whitespaces by default', async(() => {
it('should not remove whitespaces by default', waitForAsync(() => {
@Component({
selector: 'comp',
template: '<span>foo</span> <span>bar</span>',
@ -2015,7 +2015,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(f.nativeElement.childNodes.length).toBe(2);
}));
it('should not remove whitespaces when explicitly requested not to do so', async(() => {
it('should not remove whitespaces when explicitly requested not to do so',
waitForAsync(() => {
@Component({
selector: 'comp',
template: '<span>foo</span> <span>bar</span>',
@ -2030,7 +2031,7 @@ function declareTests(config?: {useJit: boolean}) {
expect(f.nativeElement.childNodes.length).toBe(3);
}));
it('should remove whitespaces when explicitly requested to do so', async(() => {
it('should remove whitespaces when explicitly requested to do so', waitForAsync(() => {
@Component({
selector: 'comp',
template: '<span>foo</span> <span>bar</span>',

View File

@ -10,7 +10,7 @@ import {ResourceLoader} from '@angular/compiler';
import {CompileMetadataResolver} from '@angular/compiler/src/metadata_resolver';
import {MockResourceLoader} from '@angular/compiler/testing/src/resource_loader_mock';
import {Component, Directive, Injectable, NgModule, OnDestroy, Pipe} from '@angular/core';
import {async, getTestBed, TestBed} from '@angular/core/testing';
import {getTestBed, TestBed, waitForAsync} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {obsoleteInIvy} from '@angular/private/testing';
@ -135,7 +135,7 @@ import {obsoleteInIvy} from '@angular/private/testing';
SomeService.annotations = [];
}
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
instances = new Map<any, any>();
createSummaries().then(s => summaries = s);
}));

View File

@ -8,7 +8,7 @@
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, asNativeElements, Component, ContentChild, ContentChildren, Directive, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {ElementRef} from '@angular/core/src/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ivyEnabled, modifiedInIvy, onlyInIvy} from '@angular/private/testing';
import {Subject} from 'rxjs';
@ -414,7 +414,7 @@ describe('Query API', () => {
});
describe('changes', () => {
it('should notify query on change', async(() => {
it('should notify query on change', waitForAsync(() => {
const template = '<needs-query #q>' +
'<div text="1"></div>' +
'<div *ngIf="shouldShow" text="2"></div>' +

View File

@ -8,7 +8,7 @@
import {Compiler, SystemJsNgModuleLoader} from '@angular/core';
import {global} from '@angular/core/src/util/global';
import {async} from '@angular/core/testing';
import {waitForAsync} from '@angular/core/testing';
import {afterEach, beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal';
import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';
@ -36,19 +36,19 @@ describe('SystemJsNgModuleLoader', () => {
global['System'] = oldSystem;
});
it('loads a default factory by appending the factory suffix', async(() => {
it('loads a default factory by appending the factory suffix', waitForAsync(() => {
const loader = new SystemJsNgModuleLoader(new Compiler());
loader.load('test').then(contents => {
expect(contents).toBe('test module factory' as any);
});
}));
it('loads a named factory by appending the factory suffix', async(() => {
it('loads a named factory by appending the factory suffix', waitForAsync(() => {
const loader = new SystemJsNgModuleLoader(new Compiler());
loader.load('test#Named').then(contents => {
expect(contents).toBe('test NamedNgFactory' as any);
});
}));
it('loads a named factory with a configured prefix and suffix', async(() => {
it('loads a named factory with a configured prefix and suffix', waitForAsync(() => {
const loader = new SystemJsNgModuleLoader(new Compiler(), {
factoryPathPrefix: 'prefixed/',
factoryPathSuffix: '/suffixed',
@ -70,13 +70,13 @@ describe('SystemJsNgModuleLoader', () => {
global['System'] = oldSystem;
});
it('loads a default module', async(() => {
it('loads a default module', waitForAsync(() => {
const loader = new SystemJsNgModuleLoader(new Compiler());
loader.load('test').then(contents => {
expect(contents.moduleType).toBe('test module' as any);
});
}));
it('loads a named module', async(() => {
it('loads a named module', waitForAsync(() => {
const loader = new SystemJsNgModuleLoader(new Compiler());
loader.load('test#NamedModule').then(contents => {
expect(contents.moduleType).toBe('test NamedModule' as any);

View File

@ -11,10 +11,13 @@ ts_library(
"**/*_perf.ts",
"domino.d.ts",
"load_domino.ts",
"is_shape_of.ts",
"jit_spec.ts",
"matchers.ts",
],
),
deps = [
":matchers",
"//packages:types",
"//packages/animations",
"//packages/animations/browser",
@ -34,6 +37,18 @@ ts_library(
],
)
ts_library(
name = "matchers",
testonly = True,
srcs = [
"is_shape_of.ts",
"matchers.ts",
],
deps = [
"//packages/core",
],
)
ts_library(
name = "domino",
testonly = True,

View File

@ -0,0 +1,140 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {i18nMutateOpCodesToString, i18nUpdateOpCodesToString} from '@angular/core/src/render3/i18n_debug';
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nUpdateOpCode} from '@angular/core/src/render3/interfaces/i18n';
describe('i18n debug', () => {
describe('i18nUpdateOpCodesToString', () => {
it('should print nothing', () => {
expect(i18nUpdateOpCodesToString([])).toEqual([]);
});
it('should print text opCode', () => {
expect(i18nUpdateOpCodesToString([
0b11,
4,
'pre ',
-4,
' post',
1 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text,
]))
.toEqual(
['if (mask & 0b11) { (lView[1] as Text).textContent = `pre ${lView[4]} post`; }']);
});
it('should print Attribute opCode', () => {
expect(i18nUpdateOpCodesToString([
0b01, 8,
'pre ', -4,
' in ', -3,
' post', 1 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr,
'title', null,
0b10, 8,
'pre ', -4,
' in ', -3,
' post', 1 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr,
'title', (v) => v,
]))
.toEqual([
'if (mask & 0b1) { (lView[1] as Element).setAttribute(\'title\', `pre ${lView[4]} in ${lView[3]} post`); }',
'if (mask & 0b10) { (lView[1] as Element).setAttribute(\'title\', (function (v) { return v; })(`pre ${lView[4]} in ${lView[3]} post`)); }'
]);
});
it('should print icuSwitch opCode', () => {
expect(i18nUpdateOpCodesToString([
0b100, 2, -5, 12 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuSwitch,
2 // FIXME(misko): Should be part of IcuSwitch
])).toEqual(['if (mask & 0b100) { icuSwitchCase(lView[12] as Comment, 2, `${lView[5]}`); }']);
});
it('should print icuUpdate opCode', () => {
expect(i18nUpdateOpCodesToString([
0b1000, 2, 13 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuUpdate,
3 // FIXME(misko): should be part of IcuUpdate
])).toEqual(['if (mask & 0b1000) { icuUpdateCase(lView[13] as Comment, 3); }']);
});
});
describe('i18nMutateOpCodesToString', () => {
it('should print nothing', () => {
expect(i18nMutateOpCodesToString([])).toEqual([]);
});
it('should print Move', () => {
expect(i18nMutateOpCodesToString([
1 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
2 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF |
I18nMutateOpCode.AppendChild,
])).toEqual(['(lView[2] as Element).appendChild(lView[1])']);
});
it('should print text AppendChild', () => {
expect(i18nMutateOpCodesToString([
'xyz', 0,
1 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF |
I18nMutateOpCode.AppendChild
]))
.toEqual([
'lView[0] = document.createTextNode("xyz")',
'(lView[1] as Element).appendChild(lView[0])'
]);
});
it('should print element AppendChild', () => {
expect(i18nMutateOpCodesToString([
ELEMENT_MARKER, 'xyz', 0,
1 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF |
I18nMutateOpCode.AppendChild
]))
.toEqual([
'lView[0] = document.createElement("xyz")',
'(lView[1] as Element).appendChild(lView[0])'
]);
});
it('should print comment AppendChild', () => {
expect(i18nMutateOpCodesToString([
COMMENT_MARKER, 'xyz', 0,
1 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF |
I18nMutateOpCode.AppendChild
]))
.toEqual([
'lView[0] = document.createComment("xyz")',
'(lView[1] as Element).appendChild(lView[0])'
]);
});
it('should print Remove', () => {
expect(i18nMutateOpCodesToString([
2 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF |
I18nMutateOpCode.Remove
])).toEqual(['(lView[2] as Element).remove(lView[0])']);
});
it('should print Attr', () => {
expect(i18nMutateOpCodesToString([
1 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Attr, 'attr', 'value'
])).toEqual(['(lView[1] as Element).setAttribute("attr", "value")']);
});
it('should print ElementEnd', () => {
expect(i18nMutateOpCodesToString([
1 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
])).toEqual(['setPreviousOrParentTNode(tView.data[1] as TNode)']);
});
it('should print RemoveNestedIcu', () => {
expect(i18nMutateOpCodesToString([
1 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.RemoveNestedIcu,
])).toEqual(['removeNestedICU(1)']);
});
});
});

View File

@ -9,10 +9,11 @@
import {noop} from '../../../compiler/src/render3/view/util';
import {getTranslationForTemplate, ɵɵi18nAttributes, ɵɵi18nPostprocess, ɵɵi18nStart} from '../../src/render3/i18n';
import {setDelayProjection, ɵɵelementEnd, ɵɵelementStart} from '../../src/render3/instructions/all';
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nUpdateOpCode, I18nUpdateOpCodes, TI18n} from '../../src/render3/interfaces/i18n';
import {I18nUpdateOpCodes, TI18n, TIcu} from '../../src/render3/interfaces/i18n';
import {HEADER_OFFSET, LView, TVIEW} from '../../src/render3/interfaces/view';
import {getNativeByIndex} from '../../src/render3/util/view_utils';
import {TemplateFixture} from './render_util';
import {debugMatch} from './utils';
describe('Runtime i18n', () => {
afterEach(() => {
@ -72,25 +73,15 @@ describe('Runtime i18n', () => {
const nbConsts = 1;
const index = 0;
const opCodes = getOpCodes(() => {
ɵɵi18nStart(index, MSG_DIV);
}, null, nbConsts, index);
// Check debug
const debugOps = (opCodes as any).create.debug!.operations;
expect(debugOps[0].__raw_opCode).toBe('simple text');
expect(debugOps[0].type).toBe('Create Text Node');
expect(debugOps[0].nodeIndex).toBe(1);
expect(debugOps[0].text).toBe('simple text');
expect(debugOps[1].__raw_opCode).toBe(1);
expect(debugOps[1].type).toBe('AppendChild');
expect(debugOps[1].nodeIndex).toBe(0);
ɵɵi18nStart(index, MSG_DIV);
}, null, nbConsts, index) as TI18n;
expect(opCodes).toEqual({
vars: 1,
create: [
'simple text', nbConsts,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
],
create: debugMatch([
'lView[1] = document.createTextNode("simple text")',
'(lView[0] as Element).appendChild(lView[1])'
]),
update: [],
icus: null
});
@ -102,37 +93,28 @@ describe('Runtime i18n', () => {
// 3 consts for the 2 divs and 1 span + 1 const for `i18nStart` = 4 consts
const nbConsts = 4;
const index = 1;
const elementIndex = 2;
const elementIndex2 = 3;
const opCodes = getOpCodes(() => {
ɵɵi18nStart(index, MSG_DIV);
}, null, nbConsts, index);
expect(opCodes).toEqual({
vars: 5,
create: [
'Hello ',
nbConsts,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
elementIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'world',
nbConsts + 1,
elementIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
elementIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
' and ',
nbConsts + 2,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
elementIndex2 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'universe',
nbConsts + 3,
elementIndex2 << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
elementIndex2 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
'!',
nbConsts + 4,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
],
create: debugMatch([
'lView[4] = document.createTextNode("Hello ")',
'(lView[1] as Element).appendChild(lView[4])',
'(lView[1] as Element).appendChild(lView[2])',
'lView[5] = document.createTextNode("world")',
'(lView[2] as Element).appendChild(lView[5])',
'setPreviousOrParentTNode(tView.data[2] as TNode)',
'lView[6] = document.createTextNode(" and ")',
'(lView[1] as Element).appendChild(lView[6])',
'(lView[1] as Element).appendChild(lView[3])',
'lView[7] = document.createTextNode("universe")',
'(lView[3] as Element).appendChild(lView[7])',
'setPreviousOrParentTNode(tView.data[3] as TNode)',
'lView[8] = document.createTextNode("!")',
'(lView[1] as Element).appendChild(lView[8])',
]),
update: [],
icus: null
});
@ -146,21 +128,18 @@ describe('Runtime i18n', () => {
ɵɵi18nStart(index, MSG_DIV);
}, null, nbConsts, index);
expect((opCodes as any).update.debug.operations).toEqual([
{__raw_opCode: 8, checkBit: 1, type: 'Text', nodeIndex: 2, text: 'Hello <20>0<EFBFBD>!'}
expect((opCodes as any).update.debug).toEqual([
'if (mask & 0b1) { (lView[2] as Text).textContent = `Hello ${lView[1]}!`; }'
]);
expect(opCodes).toEqual({
vars: 1,
create:
['', nbConsts, index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild],
update: [
0b1, // bindings mask
4, // if no update, skip 4
'Hello ',
-1, // binding index
'!', (index + 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text
],
create: debugMatch([
'lView[2] = document.createTextNode("")',
'(lView[1] as Element).appendChild(lView[2])',
]),
update: debugMatch(
['if (mask & 0b1) { (lView[2] as Text).textContent = `Hello ${lView[1]}!`; }']),
icus: null
});
});
@ -175,14 +154,12 @@ describe('Runtime i18n', () => {
expect(opCodes).toEqual({
vars: 1,
create:
['', nbConsts, index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild],
update: [
0b11, // bindings mask
8, // if no update, skip 8
'Hello ', -1, ' and ', -2, ', again ', -1, '!',
(index + 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text
],
create: debugMatch([
'lView[2] = document.createTextNode("")', '(lView[1] as Element).appendChild(lView[2])'
]),
update: debugMatch([
'if (mask & 0b11) { (lView[2] as Text).textContent = `Hello ${lView[1]} and ${lView[2]}, again ${lView[1]}!`; }'
]),
icus: null
});
});
@ -211,22 +188,14 @@ describe('Runtime i18n', () => {
expect(opCodes).toEqual({
vars: 2,
create: [
'',
nbConsts,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
~rootTemplate << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'!',
nbConsts + 1,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
],
update: [
0b1, // bindings mask
3, // if no update, skip 3
-1, // binding index
' is rendered as: ', firstTextNode << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text
],
create: debugMatch([
'lView[3] = document.createTextNode("")', '(lView[1] as Element).appendChild(lView[3])',
'(lView[1] as Element).appendChild(lView[16381])',
'lView[4] = document.createTextNode("!")', '(lView[1] as Element).appendChild(lView[4])'
]),
update: debugMatch([
'if (mask & 0b1) { (lView[3] as Text).textContent = `${lView[1]} is rendered as: `; }'
]),
icus: null
});
@ -243,19 +212,15 @@ describe('Runtime i18n', () => {
expect(opCodes).toEqual({
vars: 2,
create: [
spanElement << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'before',
nbConsts,
spanElement << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
~bElementSubTemplate << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
spanElement << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'after',
nbConsts + 1,
spanElement << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
spanElement << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
],
create: debugMatch([
'(lView[0] as Element).appendChild(lView[1])',
'lView[3] = document.createTextNode("before")',
'(lView[1] as Element).appendChild(lView[3])',
'(lView[1] as Element).appendChild(lView[16381])',
'lView[4] = document.createTextNode("after")',
'(lView[1] as Element).appendChild(lView[4])',
'setPreviousOrParentTNode(tView.data[1] as TNode)'
]),
update: [],
icus: null
});
@ -272,14 +237,12 @@ describe('Runtime i18n', () => {
expect(opCodes).toEqual({
vars: 1,
create: [
bElement << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'middle',
nbConsts,
bElement << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
bElement << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
],
create: debugMatch([
'(lView[0] as Element).appendChild(lView[1])',
'lView[2] = document.createTextNode("middle")',
'(lView[1] as Element).appendChild(lView[2])',
'setPreviousOrParentTNode(tView.data[1] as TNode)'
]),
update: [],
icus: null
});
@ -294,179 +257,76 @@ describe('Runtime i18n', () => {
const nbConsts = 1;
const index = 0;
const opCodes = getOpCodes(() => {
ɵɵi18nStart(index, MSG_DIV);
}, null, nbConsts, index);
const tIcuIndex = 0;
const icuCommentNodeIndex = index + 1;
const firstTextNodeIndex = index + 2;
const bElementNodeIndex = index + 3;
const iElementNodeIndex = index + 3;
const spanElementNodeIndex = index + 3;
const innerTextNode = index + 4;
const lastTextNode = index + 5;
const debugOps = (opCodes as any).update.debug.operations;
expect(debugOps[0].__raw_opCode).toBe(6);
expect(debugOps[0].checkBit).toBe(1);
expect(debugOps[0].type).toBe('IcuSwitch');
expect(debugOps[0].nodeIndex).toBe(1);
expect(debugOps[0].tIcuIndex).toBe(0);
expect(debugOps[0].mainBinding).toBe('<27>0<EFBFBD>');
expect(debugOps[1].__raw_opCode).toBe(7);
expect(debugOps[1].checkBit).toBe(3);
expect(debugOps[1].type).toBe('IcuUpdate');
expect(debugOps[1].nodeIndex).toBe(1);
expect(debugOps[1].tIcuIndex).toBe(0);
const icuDebugOps = (opCodes as any).icus[0].create[0].debug.operations;
let op: any;
let i = 0;
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe('no ');
expect(op.type).toBe('Create Text Node');
expect(op.nodeIndex).toBe(2);
expect(op.text).toBe('no ');
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe(131073);
expect(op.type).toBe('AppendChild');
expect(op.nodeIndex).toBe(1);
op = icuDebugOps[i++];
expect(op.__raw_opCode).toEqual({marker: 'element'});
expect(op.type).toBe('ELEMENT_MARKER');
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe('b');
expect(op.type).toBe('Create Text Node');
expect(op.nodeIndex).toBe(3);
expect(op.text).toBe('b');
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe(131073);
expect(op.type).toBe('AppendChild');
expect(op.nodeIndex).toBe(1);
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe(28);
expect(op.type).toBe('Attr');
expect(op.nodeIndex).toBe(3);
expect(op.attrName).toBe('title');
expect(op.attrValue).toBe('none');
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe('emails');
expect(op.type).toBe('Create Text Node');
expect(op.nodeIndex).toBe(4);
expect(op.text).toBe('emails');
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe(393217);
expect(op.type).toBe('AppendChild');
expect(op.nodeIndex).toBe(3);
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe('!');
expect(op.type).toBe('Create Text Node');
expect(op.nodeIndex).toBe(5);
expect(op.text).toBe('!');
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe(131073);
expect(op.type).toBe('AppendChild');
expect(op.nodeIndex).toBe(1);
ɵɵi18nStart(index, MSG_DIV);
}, null, nbConsts, index) as TI18n;
expect(opCodes).toEqual({
vars: 5,
create: [
COMMENT_MARKER, 'ICU 1', icuCommentNodeIndex,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
],
update: [
0b1, // mask for ICU main binding
3, // skip 3 if not changed
-1, // icu main binding
icuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuSwitch, tIcuIndex,
0b11, // mask for all ICU bindings
2, // skip 2 if not changed
icuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuUpdate, tIcuIndex
],
icus: [{
update: debugMatch([
'if (mask & 0b1) { icuSwitchCase(lView[1] as Comment, 0, `${lView[1]}`); }',
'if (mask & 0b11) { icuUpdateCase(lView[1] as Comment, 0); }',
]),
create: debugMatch([
'lView[1] = document.createComment("ICU 1")',
'(lView[0] as Element).appendChild(lView[1])',
]),
icus: [<TIcu>{
type: 1,
vars: [4, 3, 3],
childIcus: [[], [], []],
cases: ['0', '1', 'other'],
create: [
[
'no ',
firstTextNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
ELEMENT_MARKER,
'b',
bElementNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
bElementNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Attr,
'title',
'none',
'emails',
innerTextNode,
bElementNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'!',
lastTextNode,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
],
[
'one ', firstTextNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
ELEMENT_MARKER, 'i', iElementNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'email', innerTextNode,
iElementNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
],
[
'', firstTextNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
ELEMENT_MARKER, 'span', spanElementNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'emails', innerTextNode,
spanElementNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
]
debugMatch([
'lView[2] = document.createTextNode("no ")',
'(lView[1] as Element).appendChild(lView[2])',
'lView[3] = document.createElement("b")',
'(lView[1] as Element).appendChild(lView[3])',
'(lView[3] as Element).setAttribute("title", "none")',
'lView[4] = document.createTextNode("emails")',
'(lView[3] as Element).appendChild(lView[4])',
'lView[5] = document.createTextNode("!")',
'(lView[1] as Element).appendChild(lView[5])'
]),
debugMatch([
'lView[2] = document.createTextNode("one ")',
'(lView[1] as Element).appendChild(lView[2])',
'lView[3] = document.createElement("i")',
'(lView[1] as Element).appendChild(lView[3])',
'lView[4] = document.createTextNode("email")',
'(lView[3] as Element).appendChild(lView[4])'
]),
debugMatch([
'lView[2] = document.createTextNode("")',
'(lView[1] as Element).appendChild(lView[2])',
'lView[3] = document.createElement("span")',
'(lView[1] as Element).appendChild(lView[3])',
'lView[4] = document.createTextNode("emails")',
'(lView[3] as Element).appendChild(lView[4])'
])
],
remove: [
[
firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
innerTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
bElementNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
lastTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
],
[
firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
innerTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
iElementNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
],
[
firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
innerTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
spanElementNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
]
debugMatch([
'(lView[0] as Element).remove(lView[2])',
'(lView[0] as Element).remove(lView[4])',
'(lView[0] as Element).remove(lView[3])',
'(lView[0] as Element).remove(lView[5])',
]),
debugMatch([
'(lView[0] as Element).remove(lView[2])',
'(lView[0] as Element).remove(lView[4])',
'(lView[0] as Element).remove(lView[3])',
]),
debugMatch([
'(lView[0] as Element).remove(lView[2])',
'(lView[0] as Element).remove(lView[4])',
'(lView[0] as Element).remove(lView[3])',
])
],
update: [
[], [],
[
0b1, // mask for the first binding
3, // skip 3 if not changed
-1, // binding index
' ', // text string to concatenate to the binding value
firstTextNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text,
0b10, // mask for the title attribute binding
4, // skip 4 if not changed
-2, // binding index
bElementNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr,
'title', // attribute name
null // sanitize function
]
debugMatch([]), debugMatch([]), debugMatch([
'if (mask & 0b1) { (lView[2] as Text).textContent = `${lView[1]} `; }',
'if (mask & 0b10) { (lView[3] as Element).setAttribute(\'title\', `${lView[2]}`); }'
])
]
}]
});
@ -496,19 +356,14 @@ describe('Runtime i18n', () => {
expect(opCodes).toEqual({
vars: 6,
create: [
COMMENT_MARKER, 'ICU 1', icuCommentNodeIndex,
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
],
update: [
0b1, // mask for ICU main binding
3, // skip 3 if not changed
-1, // icu main binding
icuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuSwitch, tIcuIndex,
0b11, // mask for all ICU bindings
2, // skip 2 if not changed
icuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuUpdate, tIcuIndex
],
create: debugMatch([
'lView[1] = document.createComment("ICU 1")',
'(lView[0] as Element).appendChild(lView[1])'
]),
update: debugMatch([
'if (mask & 0b1) { icuSwitchCase(lView[1] as Comment, 1, `${lView[1]}`); }',
'if (mask & 0b11) { icuUpdateCase(lView[1] as Comment, 1); }'
]),
icus: [
{
type: 0,
@ -516,28 +371,29 @@ describe('Runtime i18n', () => {
childIcus: [[], [], []],
cases: ['cat', 'dog', 'other'],
create: [
[
'cats', nestedTextNodeIndex,
nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT |
I18nMutateOpCode.AppendChild
],
[
'dogs', nestedTextNodeIndex,
nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT |
I18nMutateOpCode.AppendChild
],
[
'animals', nestedTextNodeIndex,
nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT |
I18nMutateOpCode.AppendChild
]
debugMatch([
'lView[5] = document.createTextNode("cats")',
'(lView[3] as Element).appendChild(lView[5])'
]),
debugMatch([
'lView[5] = document.createTextNode("dogs")',
'(lView[3] as Element).appendChild(lView[5])'
]),
debugMatch([
'lView[5] = document.createTextNode("animals")',
'(lView[3] as Element).appendChild(lView[5])'
]),
],
remove: [
[nestedTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove],
[nestedTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove],
[nestedTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove]
debugMatch(['(lView[0] as Element).remove(lView[5])']),
debugMatch(['(lView[0] as Element).remove(lView[5])']),
debugMatch(['(lView[0] as Element).remove(lView[5])'])
],
update: [[], [], []]
update: [
debugMatch([]),
debugMatch([]),
debugMatch([]),
]
},
{
type: 1,
@ -545,48 +401,33 @@ describe('Runtime i18n', () => {
childIcus: [[], [0]],
cases: ['0', 'other'],
create: [
[
'zero', firstTextNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
],
[
'', firstTextNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
COMMENT_MARKER, 'nested ICU 0', nestedIcuCommentNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
'!', lastTextNodeIndex,
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
]
debugMatch([
'lView[2] = document.createTextNode("zero")',
'(lView[1] as Element).appendChild(lView[2])'
]),
debugMatch([
'lView[2] = document.createTextNode("")',
'(lView[1] as Element).appendChild(lView[2])',
'lView[3] = document.createComment("nested ICU 0")',
'(lView[1] as Element).appendChild(lView[3])',
'lView[4] = document.createTextNode("!")',
'(lView[1] as Element).appendChild(lView[4])'
]),
],
remove: [
[firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove],
[
firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
lastTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
0 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.RemoveNestedIcu,
nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
]
debugMatch(['(lView[0] as Element).remove(lView[2])']),
debugMatch([
'(lView[0] as Element).remove(lView[2])', '(lView[0] as Element).remove(lView[4])',
'removeNestedICU(0)', '(lView[0] as Element).remove(lView[3])'
]),
],
update: [
[],
[
0b1, // mask for ICU main binding
3, // skip 3 if not changed
-1, // binding index
' ', // text string to concatenate to the binding value
firstTextNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text,
0b10, // mask for inner ICU main binding
3, // skip 3 if not changed
-2, // inner ICU main binding
nestedIcuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF |
I18nUpdateOpCode.IcuSwitch,
nestedTIcuIndex,
0b10, // mask for all inner ICU bindings
2, // skip 2 if not changed
nestedIcuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF |
I18nUpdateOpCode.IcuUpdate,
nestedTIcuIndex
]
debugMatch([]),
debugMatch([
'if (mask & 0b1) { (lView[2] as Text).textContent = `${lView[1]} `; }',
'if (mask & 0b10) { icuSwitchCase(lView[3] as Comment, 0, `${lView[2]}`); }',
'if (mask & 0b10) { icuUpdateCase(lView[3] as Comment, 0); }'
]),
]
}
]
@ -623,13 +464,9 @@ describe('Runtime i18n', () => {
ɵɵi18nAttributes(index, MSG_div_attr);
}, null, nbConsts, index);
expect(opCodes).toEqual([
0b1, // bindings mask
6, // if no update, skip 4
'Hello ',
-1, // binding index
'!', (index - 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr, 'title', null
]);
expect(opCodes).toEqual(debugMatch([
'if (mask & 0b1) { (lView[0] as Element).setAttribute(\'title\', `Hello ${lView[1]}!`); }'
]));
});
it('for multiple bindings', () => {
@ -641,12 +478,9 @@ describe('Runtime i18n', () => {
ɵɵi18nAttributes(index, MSG_div_attr);
}, null, nbConsts, index);
expect(opCodes).toEqual([
0b11, // bindings mask
10, // size
'Hello ', -1, ' and ', -2, ', again ', -1, '!',
(index - 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr, 'title', null
]);
expect(opCodes).toEqual(debugMatch([
'if (mask & 0b11) { (lView[0] as Element).setAttribute(\'title\', `Hello ${lView[1]} and ${lView[2]}, again ${lView[1]}!`); }'
]));
});
it('for multiple attributes', () => {
@ -658,18 +492,10 @@ describe('Runtime i18n', () => {
ɵɵi18nAttributes(index, MSG_div_attr);
}, null, nbConsts, index);
expect(opCodes).toEqual([
0b1, // bindings mask
6, // if no update, skip 4
'Hello ',
-1, // binding index
'!', (index - 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr, 'title', null,
0b1, // bindings mask
6, // if no update, skip 4
'Hello ',
-1, // binding index
'!', (index - 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr, 'aria-label', null
]);
expect(opCodes).toEqual(debugMatch([
'if (mask & 0b1) { (lView[0] as Element).setAttribute(\'title\', `Hello ${lView[1]}!`); }',
'if (mask & 0b1) { (lView[0] as Element).setAttribute(\'aria-label\', `Hello ${lView[1]}!`); }'
]));
});
});

View File

@ -0,0 +1,22 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {TNodeType, TNodeTypeAsString} from '@angular/core/src/render3/interfaces/node';
describe('node interfaces', () => {
describe('TNodeType', () => {
it('should agree with TNodeTypeAsString', () => {
expect(TNodeTypeAsString[TNodeType.Container]).toEqual('Container');
expect(TNodeTypeAsString[TNodeType.Projection]).toEqual('Projection');
expect(TNodeTypeAsString[TNodeType.View]).toEqual('View');
expect(TNodeTypeAsString[TNodeType.Element]).toEqual('Element');
expect(TNodeTypeAsString[TNodeType.ElementContainer]).toEqual('ElementContainer');
expect(TNodeTypeAsString[TNodeType.IcuContainer]).toEqual('IcuContainer');
});
});
});

View File

@ -0,0 +1,186 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {TI18n} from '@angular/core/src/render3/interfaces/i18n';
import {TNode} from '@angular/core/src/render3/interfaces/node';
import {TView} from '@angular/core/src/render3/interfaces/view';
/**
* A type used to create a runtime representation of a shape of object which matches the declared
* interface at compile time.
*
* The purpose of this type is to ensure that the object must match all of the properties of a type.
* This is later used by `isShapeOf` method to ensure that a particular object has a particular
* shape.
*
* ```
* interface MyShape {
* foo: string,
* bar: number
* }
*
* const myShapeObj: {foo: '', bar: 0};
* const ExpectedPropertiesOfShape = {foo: true, bar: true};
*
* isShapeOf(myShapeObj, ExpectedPropertiesOfShape);
* ```
*
* The above code would verify that `myShapeObj` has `foo` and `bar` properties. However if later
* `MyShape` is refactored to change a set of properties we would like to have a compile time error
* that the `ExpectedPropertiesOfShape` also needs to be changed.
*
* ```
* const ExpectedPropertiesOfShape = <ShapeOf<MyShape>>{foo: true, bar: true};
* ```
* The above code will force through compile time checks that the `ExpectedPropertiesOfShape` match
* that of `MyShape`.
*
* See: `isShapeOf`
*
*/
export type ShapeOf<T> = {
[P in keyof T]: true;
};
/**
* Determines if a particular object is of a given shape (duck-type version of `instanceof`.)
*
* ```
* isShapeOf(someObj, {foo: true, bar: true});
* ```
*
* The above code will be true if the `someObj` has both `foo` and `bar` property
*
* @param obj Object to test for.
* @param shapeOf Desired shape.
*/
export function isShapeOf<T>(obj: any, shapeOf: ShapeOf<T>): obj is T {
if (typeof obj === 'object' && obj) {
return Object.keys(shapeOf).reduce(
(prev, key) => prev && obj.hasOwnProperty(key), true as boolean);
}
return false;
}
/**
* Determines if `obj` matches the shape `TI18n`.
* @param obj
*/
export function isTI18n(obj: any): obj is TI18n {
return isShapeOf<TI18n>(obj, ShapeOfTI18n);
}
const ShapeOfTI18n: ShapeOf<TI18n> = {
vars: true,
create: true,
update: true,
icus: true,
};
/**
* Determines if `obj` matches the shape `TView`.
* @param obj
*/
export function isTView(obj: any): obj is TView {
return isShapeOf<TView>(obj, ShapeOfTView);
}
const ShapeOfTView: ShapeOf<TView> = {
type: true,
id: true,
blueprint: true,
template: true,
viewQuery: true,
node: true,
firstCreatePass: true,
firstUpdatePass: true,
data: true,
bindingStartIndex: true,
expandoStartIndex: true,
staticViewQueries: true,
staticContentQueries: true,
firstChild: true,
expandoInstructions: true,
directiveRegistry: true,
pipeRegistry: true,
preOrderHooks: true,
preOrderCheckHooks: true,
contentHooks: true,
contentCheckHooks: true,
viewHooks: true,
viewCheckHooks: true,
destroyHooks: true,
cleanup: true,
components: true,
queries: true,
contentQueries: true,
schemas: true,
consts: true,
incompleteFirstPass: true,
};
/**
* Determines if `obj` matches the shape `TI18n`.
* @param obj
*/
export function isTNode(obj: any): obj is TNode {
return isShapeOf<TNode>(obj, ShapeOfTNode);
}
const ShapeOfTNode: ShapeOf<TNode> = {
type: true,
index: true,
injectorIndex: true,
directiveStart: true,
directiveEnd: true,
directiveStylingLast: true,
propertyBindings: true,
flags: true,
providerIndexes: true,
tagName: true,
attrs: true,
mergedAttrs: true,
localNames: true,
initialInputs: true,
inputs: true,
outputs: true,
tViews: true,
next: true,
projectionNext: true,
child: true,
parent: true,
projection: true,
styles: true,
stylesWithoutHost: true,
residualStyles: true,
classes: true,
classesWithoutHost: true,
residualClasses: true,
classBindings: true,
styleBindings: true,
};
/**
* Determines if `obj` is DOM `Node`.
*/
export function isDOMNode(obj: any): obj is Node {
return obj instanceof Node;
}
/**
* Determines if `obj` is DOM `Text`.
*/
export function isDOMElement(obj: any): obj is Element {
return obj instanceof Element;
}
/**
* Determines if `obj` is DOM `Text`.
*/
export function isDOMText(obj: any): obj is Text {
return obj instanceof Text;
}

View File

@ -0,0 +1,37 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {isShapeOf, ShapeOf} from './is_shape_of';
describe('isShapeOf', () => {
const ShapeOfEmptyObject: ShapeOf<{}> = {};
it('should not match for non objects', () => {
expect(isShapeOf(null, ShapeOfEmptyObject)).toBeFalse();
expect(isShapeOf(0, ShapeOfEmptyObject)).toBeFalse();
expect(isShapeOf(1, ShapeOfEmptyObject)).toBeFalse();
expect(isShapeOf(true, ShapeOfEmptyObject)).toBeFalse();
expect(isShapeOf(false, ShapeOfEmptyObject)).toBeFalse();
expect(isShapeOf(undefined, ShapeOfEmptyObject)).toBeFalse();
});
it('should match on empty object', () => {
expect(isShapeOf({}, ShapeOfEmptyObject)).toBeTrue();
expect(isShapeOf({extra: 'is ok'}, ShapeOfEmptyObject)).toBeTrue();
});
it('should match on shape', () => {
expect(isShapeOf({required: 1}, {required: true})).toBeTrue();
expect(isShapeOf({required: true, extra: 'is ok'}, {required: true})).toBeTrue();
});
it('should not match if missing property', () => {
expect(isShapeOf({required: 1}, {required: true, missing: true})).toBeFalse();
expect(isShapeOf({required: true, extra: 'is ok'}, {required: true, missing: true}))
.toBeFalse();
});
});

View File

@ -0,0 +1,218 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {TI18n} from '@angular/core/src/render3/interfaces/i18n';
import {TNode} from '@angular/core/src/render3/interfaces/node';
import {TView} from '@angular/core/src/render3/interfaces/view';
import {isDOMElement, isDOMText, isTI18n, isTNode, isTView} from './is_shape_of';
/**
* Generic matcher which asserts that an object is of a given shape (`shapePredicate`) and that it
* contains a subset of properties.
*
* @param name Name of `shapePredicate` to display when assertion fails.
* @param shapePredicate Predicate which verifies that the object is of correct shape.
* @param expected Expected set of properties to be found on the object.
*/
export function matchObjectShape<T>(
name: string, shapePredicate: (obj: any) => obj is T,
expected: Partial<T> = {}): jasmine.AsymmetricMatcher<T> {
const matcher = function() {};
let _actual: any = null;
matcher.asymmetricMatch = function(actual: any) {
_actual = actual;
if (!shapePredicate(actual)) return false;
for (const key in expected) {
if (expected.hasOwnProperty(key) && !jasmine.matchersUtil.equals(actual[key], expected[key]))
return false;
}
return true;
};
matcher.jasmineToString = function() {
return `${toString(_actual, false)} != ${toString(expected, true)})`;
};
function toString(obj: any, isExpected: boolean) {
if (isExpected || shapePredicate(obj)) {
const props =
Object.keys(expected).map(key => `${key}: ${JSON.stringify((obj as any)[key])}`);
if (isExpected === false) {
// Push something to let the user know that there may be other ignored properties in actual
props.push('...');
}
return `${name}({${props.length === 0 ? '' : '\n ' + props.join(',\n ') + '\n'}})`;
} else {
return JSON.stringify(obj);
}
}
return matcher;
}
/**
* Asymmetric matcher which matches a `TView` of a given shape.
*
* Expected usage:
* ```
* expect(tNode).toEqual(matchTView({type: TViewType.Root}));
* expect({
* node: tNode
* }).toEqual({
* node: matchTNode({type: TViewType.Root})
* });
* ```
*
* @param expected optional properties which the `TView` must contain.
*/
export function matchTView(expected?: Partial<TView>): jasmine.AsymmetricMatcher<TView> {
return matchObjectShape('TView', isTView, expected);
}
/**
* Asymmetric matcher which matches a `TNode` of a given shape.
*
* Expected usage:
* ```
* expect(tNode).toEqual(matchTNode({type: TNodeType.Element}));
* expect({
* node: tNode
* }).toEqual({
* node: matchTNode({type: TNodeType.Element})
* });
* ```
*
* @param expected optional properties which the `TNode` must contain.
*/
export function matchTNode(expected?: Partial<TNode>): jasmine.AsymmetricMatcher<TNode> {
return matchObjectShape('TNode', isTNode, expected);
}
/**
* Asymmetric matcher which matches a `T18n` of a given shape.
*
* Expected usage:
* ```
* expect(tNode).toEqual(matchT18n({vars: 0}));
* expect({
* node: tNode
* }).toEqual({
* node: matchT18n({vars: 0})
* });
* ```
*
* @param expected optional properties which the `TI18n` must contain.
*/
export function matchTI18n(expected?: Partial<TI18n>): jasmine.AsymmetricMatcher<TI18n> {
return matchObjectShape('TI18n', isTI18n, expected);
}
/**
* Asymmetric matcher which matches a DOM Element.
*
* Expected usage:
* ```
* expect(div).toEqual(matchT18n('div', {id: '123'}));
* expect({
* node: div
* }).toEqual({
* node: matchT18n('div', {id: '123'})
* });
* ```
*
* @param expectedTagName optional DOM tag name.
* @param expectedAttributes optional DOM element properties.
*/
export function matchDomElement(
expectedTagName: string|undefined = undefined,
expectedAttrs: {[key: string]: string|null} = {}): jasmine.AsymmetricMatcher<Element> {
const matcher = function() {};
let _actual: any = null;
matcher.asymmetricMatch = function(actual: any) {
_actual = actual;
if (!isDOMElement(actual)) return false;
if (expectedTagName && (expectedTagName.toUpperCase() !== actual.tagName.toUpperCase())) {
return false;
}
if (expectedAttrs) {
for (const attrName in expectedAttrs) {
if (expectedAttrs.hasOwnProperty(attrName)) {
const expectedAttrValue = expectedAttrs[attrName];
const actualAttrValue = actual.getAttribute(attrName);
if (expectedAttrValue !== actualAttrValue) {
return false;
}
}
}
}
return true;
};
matcher.jasmineToString = function() {
let actualStr = isDOMElement(_actual) ? `<${_actual.tagName}${toString(_actual.attributes)}>` :
JSON.stringify(_actual);
let expectedStr = `<${expectedTagName || '*'}${
Object.keys(expectedAttrs).map(key => ` ${key}=${JSON.stringify(expectedAttrs[key])}`)}>`;
return `[${actualStr} != ${expectedStr}]`;
};
function toString(attrs: NamedNodeMap) {
let text = '';
for (let i = 0; i < attrs.length; i++) {
const attr = attrs[i];
text += ` ${attr.name}=${JSON.stringify(attr.value)}`;
}
return text;
}
return matcher;
}
/**
* Asymmetric matcher which matches DOM text node.
*
* Expected usage:
* ```
* expect(div).toEqual(matchDomText('text'));
* expect({
* node: div
* }).toEqual({
* node: matchDomText('text')
* });
* ```
*
* @param expectedText optional DOM text.
*/
export function matchDomText(expectedText: string|undefined = undefined):
jasmine.AsymmetricMatcher<Text> {
const matcher = function() {};
let _actual: any = null;
matcher.asymmetricMatch = function(actual: any) {
_actual = actual;
if (!isDOMText(actual)) return false;
if (expectedText && (expectedText !== actual.textContent)) {
return false;
}
return true;
};
matcher.jasmineToString = function() {
let actualStr = isDOMText(_actual) ? `#TEXT: ${JSON.stringify(_actual.textContent)}` :
JSON.stringify(_actual);
let expectedStr = `#TEXT: ${JSON.stringify(expectedText)}`;
return `[${actualStr} != ${expectedStr}]`;
};
return matcher;
}

View File

@ -0,0 +1,101 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {createTNode, createTView} from '@angular/core/src/render3/instructions/shared';
import {TNodeType} from '@angular/core/src/render3/interfaces/node';
import {TViewType} from '@angular/core/src/render3/interfaces/view';
import {onlyInIvy} from '@angular/private/testing';
import {isShapeOf, ShapeOf} from './is_shape_of';
import {matchDomElement, matchDomText, matchObjectShape, matchTNode, matchTView} from './matchers';
import {dedent} from './utils';
describe('render3 matchers', () => {
describe('matchObjectShape', () => {
interface MyShape {
propA: any;
propB: any;
}
const myShape: MyShape = {propA: 'value', propB: 3};
function isMyShape(obj: any): obj is MyShape {
return isShapeOf<MyShape>(obj, ShapeOfMyShape);
}
const ShapeOfMyShape: ShapeOf<MyShape> = {propA: true, propB: true};
function matchMyShape(expected?: Partial<MyShape>): jasmine.AsymmetricMatcher<MyShape> {
return matchObjectShape('MyShape', isMyShape, expected);
}
it('should match', () => {
expect(isMyShape(myShape)).toBeTrue();
expect(myShape).toEqual(matchMyShape());
expect(myShape).toEqual(matchMyShape({propA: 'value'}));
expect({node: myShape}).toEqual({node: matchMyShape({propA: 'value'})});
});
it('should produce human readable errors', () => {
const matcher = matchMyShape({propA: 'different'});
expect(matcher.asymmetricMatch(myShape, [])).toEqual(false);
expect(matcher.jasmineToString!()).toEqual(dedent`
MyShape({
propA: "value",
...
}) != MyShape({
propA: "different"
}))`);
});
});
describe('matchTView', () => {
const tView = createTView(TViewType.Root, 1, null, 2, 3, null, null, null, null, null);
it('should match', () => {
expect(tView).toEqual(matchTView());
expect(tView).toEqual(matchTView({type: TViewType.Root}));
expect({node: tView}).toEqual({node: matchTView({type: TViewType.Root})});
});
});
describe('matchTNode', () => {
const tView = createTView(TViewType.Root, 1, null, 2, 3, null, null, null, null, null);
const tNode = createTNode(tView, null, TNodeType.Element, 1, 'tagName', []);
it('should match', () => {
expect(tNode).toEqual(matchTNode());
expect(tNode).toEqual(matchTNode({type: TNodeType.Element, tagName: 'tagName'}));
expect({node: tNode}).toEqual({node: matchTNode({type: TNodeType.Element})});
});
});
describe('matchDomElement', () => {
const div = document.createElement('div');
div.setAttribute('name', 'Name');
it('should match', () => {
expect(div).toEqual(matchDomElement());
expect(div).toEqual(matchDomElement('div', {name: 'Name'}));
});
it('should produce human readable error', () => {
const matcher = matchDomElement('div', {name: 'other'});
expect(matcher.asymmetricMatch(div, [])).toEqual(false);
expect(matcher.jasmineToString!()).toEqual(`[<DIV name="Name"> != <div name="other">]`);
});
});
describe('matchDomText', () => {
const text = document.createTextNode('myText');
it('should match', () => {
expect(text).toEqual(matchDomText());
expect(text).toEqual(matchDomText('myText'));
});
it('should produce human readable error', () => {
const matcher = matchDomText('other text');
expect(matcher.asymmetricMatch(text, [])).toEqual(false);
expect(matcher.jasmineToString!()).toEqual(`[#TEXT: "myText" != #TEXT: "other text"]`);
});
});
});

View File

@ -0,0 +1,73 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/** Template string function that can be used to strip indentation from a given string literal. */
export function dedent(strings: TemplateStringsArray, ...values: any[]) {
let joinedString = '';
for (let i = 0; i < values.length; i++) {
joinedString += `${strings[i]}${values[i]}`;
}
joinedString += strings[strings.length - 1];
const lines = joinedString.split('\n');
while (isBlank(lines[0])) {
lines.shift();
}
while (isBlank(lines[lines.length - 1])) {
lines.pop();
}
let minWhitespacePrefix = lines.reduce(
(min, line) => Math.min(min, numOfWhiteSpaceLeadingChars(line)), Number.MAX_SAFE_INTEGER);
return lines.map((line) => line.substring(minWhitespacePrefix)).join('\n');
}
/**
* Tests to see if the line is blank.
*
* A blank line is such which contains only whitespace.
* @param text string to test for blank-ness.
*/
function isBlank(text: string): boolean {
return /^\s*$/.test(text);
}
/**
* Returns number of whitespace leading characters.
*
* @param text
*/
function numOfWhiteSpaceLeadingChars(text: string): number {
return text.match(/^\s*/)![0].length;
}
/**
* Jasmine AsymmetricMatcher which can be used to assert `.debug` properties.
*
* ```
* expect(obj).toEqual({
* create: debugMatch('someValue')
* })
* ```
*
* In the above example it will assert that `obj.create.debug === 'someValue'`.
*
* @param expected Expected value.
*/
export function debugMatch<T>(expected: T): any {
const matcher = function() {};
let actual: any = null;
matcher.asymmetricMatch = function(objectWithDebug: any) {
return jasmine.matchersUtil.equals(actual = objectWithDebug.debug, expected);
};
matcher.jasmineToString = function() {
return `<${JSON.stringify(actual)} != ${JSON.stringify(expected)}>`;
};
return matcher;
}

View File

@ -10,7 +10,7 @@ import {EventEmitter} from '@angular/core';
import {Injectable} from '@angular/core/src/di';
import {PendingMacrotask, Testability, TestabilityRegistry} from '@angular/core/src/testability/testability';
import {NgZone} from '@angular/core/src/zone/ng_zone';
import {async, fakeAsync, flush, tick} from '@angular/core/testing';
import {fakeAsync, flush, tick, waitForAsync} from '@angular/core/testing';
import {beforeEach, describe, expect, it, SpyObject} from '@angular/core/testing/src/testing_internal';
import {scheduleMicroTask} from '../../src/util/microtask';
@ -55,7 +55,7 @@ class MockNgZone extends NgZone {
let updateCallback: any;
let ngZone: MockNgZone;
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
ngZone = new MockNgZone();
testability = new Testability(ngZone);
execute = new SpyObject().spy('execute');
@ -68,7 +68,7 @@ class MockNgZone extends NgZone {
expect(testability.getPendingRequestCount()).toEqual(0);
});
it('should fire whenstable callbacks if pending count is 0', async(() => {
it('should fire whenstable callbacks if pending count is 0', waitForAsync(() => {
testability.whenStable(execute);
microTask(() => {
@ -81,7 +81,7 @@ class MockNgZone extends NgZone {
expect(execute).not.toHaveBeenCalled();
});
it('should not call whenstable callbacks when there are pending counts', async(() => {
it('should not call whenstable callbacks when there are pending counts', waitForAsync(() => {
testability.increasePendingRequestCount();
testability.increasePendingRequestCount();
testability.whenStable(execute);
@ -96,7 +96,7 @@ class MockNgZone extends NgZone {
});
}));
it('should fire whenstable callbacks when pending drops to 0', async(() => {
it('should fire whenstable callbacks when pending drops to 0', waitForAsync(() => {
testability.increasePendingRequestCount();
testability.whenStable(execute);
@ -110,7 +110,8 @@ class MockNgZone extends NgZone {
});
}));
it('should not fire whenstable callbacks synchronously when pending drops to 0', async(() => {
it('should not fire whenstable callbacks synchronously when pending drops to 0',
waitForAsync(() => {
testability.increasePendingRequestCount();
testability.whenStable(execute);
testability.decreasePendingRequestCount();
@ -118,7 +119,7 @@ class MockNgZone extends NgZone {
expect(execute).not.toHaveBeenCalled();
}));
it('should fire whenstable callbacks with didWork if pending count is 0', async(() => {
it('should fire whenstable callbacks with didWork if pending count is 0', waitForAsync(() => {
microTask(() => {
testability.whenStable(execute);
@ -128,7 +129,8 @@ class MockNgZone extends NgZone {
});
}));
it('should fire whenstable callbacks with didWork when pending drops to 0', async(() => {
it('should fire whenstable callbacks with didWork when pending drops to 0',
waitForAsync(() => {
testability.increasePendingRequestCount();
testability.whenStable(execute);
@ -165,7 +167,7 @@ class MockNgZone extends NgZone {
clearTimeout(id);
}));
it('should fire if Angular is already stable', async(() => {
it('should fire if Angular is already stable', waitForAsync(() => {
testability.whenStable(execute, 200);
microTask(() => {
@ -363,7 +365,7 @@ class MockNgZone extends NgZone {
let registry: TestabilityRegistry;
let ngZone: MockNgZone;
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
ngZone = new MockNgZone();
testability1 = new Testability(ngZone);
testability2 = new Testability(ngZone);

View File

@ -7,7 +7,7 @@
*/
import {EventEmitter, NgZone} from '@angular/core';
import {async, fakeAsync, flushMicrotasks} from '@angular/core/testing';
import {fakeAsync, flushMicrotasks, waitForAsync} from '@angular/core/testing';
import {AsyncTestCompleter, beforeEach, describe, expect, inject, it, Log, xit} from '@angular/core/testing/src/testing_internal';
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
@ -914,7 +914,7 @@ function commonTests() {
asyncResult = null!;
});
it('should async even if the NgZone was created outside.', async(() => {
it('should async even if the NgZone was created outside.', waitForAsync(() => {
// try to escape the current async zone by using NgZone which was created outside.
ngZone.run(() => {
setTimeout(() => {

View File

@ -16,7 +16,7 @@ import {asyncFallback} from './async_fallback';
* Example:
*
* ```
* it('...', async(inject([AClass], (object) => {
* it('...', waitForAsync(inject([AClass], (object) => {
* object.doSomething.then(() => {
* expect(...);
* })
@ -25,12 +25,12 @@ import {asyncFallback} from './async_fallback';
*
* @publicApi
*/
export function async(fn: Function): (done: any) => any {
export function waitForAsync(fn: Function): (done: any) => any {
const _Zone: any = typeof Zone !== 'undefined' ? Zone : null;
if (!_Zone) {
return function() {
return Promise.reject(
'Zone is needed for the async() test helper but could not be found. ' +
'Zone is needed for the waitForAsync() test helper but could not be found. ' +
'Please make sure that your environment includes zone.js/dist/zone.js');
};
}
@ -43,3 +43,12 @@ export function async(fn: Function): (done: any) => any {
// newest version of zone.js(0.8.25)
return asyncFallback(fn);
}
/**
* @deprecated use `waitForAsync()`, (expected removal in v12)
* @see {@link waitForAsync}
* @publicApi
* */
export function async(fn: Function): (done: any) => any {
return waitForAsync(fn);
}

View File

@ -1,27 +0,0 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AbstractControl} from '../model';
import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators';
export function normalizeValidator(validator: ValidatorFn|Validator): ValidatorFn {
if (!!(<Validator>validator).validate) {
return (c: AbstractControl) => (<Validator>validator).validate(c);
} else {
return <ValidatorFn>validator;
}
}
export function normalizeAsyncValidator(validator: AsyncValidatorFn|
AsyncValidator): AsyncValidatorFn {
if (!!(<AsyncValidator>validator).validate) {
return (c: AbstractControl) => (<AsyncValidator>validator).validate(c);
} else {
return <AsyncValidatorFn>validator;
}
}

View File

@ -9,7 +9,8 @@
import {isDevMode} from '@angular/core';
import {FormArray, FormControl, FormGroup} from '../model';
import {Validators} from '../validators';
import {normalizeValidators, Validators} from '../validators';
import {AbstractControlDirective} from './abstract_control_directive';
import {AbstractFormGroupDirective} from './abstract_form_group_directive';
import {CheckboxControlValueAccessor} from './checkbox_value_accessor';
@ -17,7 +18,6 @@ import {ControlContainer} from './control_container';
import {ControlValueAccessor} from './control_value_accessor';
import {DefaultValueAccessor} from './default_value_accessor';
import {NgControl} from './ng_control';
import {normalizeAsyncValidator, normalizeValidator} from './normalize_validator';
import {NumberValueAccessor} from './number_value_accessor';
import {RadioControlValueAccessor} from './radio_control_value_accessor';
import {RangeValueAccessor} from './range_value_accessor';
@ -142,13 +142,15 @@ function _throwError(dir: AbstractControlDirective, message: string): void {
}
export function composeValidators(validators: Array<Validator|ValidatorFn>): ValidatorFn|null {
return validators != null ? Validators.compose(validators.map(normalizeValidator)) : null;
return validators != null ? Validators.compose(normalizeValidators<ValidatorFn>(validators)) :
null;
}
export function composeAsyncValidators(validators: Array<AsyncValidator|AsyncValidatorFn>):
AsyncValidatorFn|null {
return validators != null ? Validators.composeAsync(validators.map(normalizeAsyncValidator)) :
null;
return validators != null ?
Validators.composeAsync(normalizeValidators<AsyncValidatorFn>(validators)) :
null;
}
export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any): boolean {

View File

@ -10,7 +10,7 @@ import {InjectionToken, ɵisObservable as isObservable, ɵisPromise as isPromise
import {forkJoin, from, Observable} from 'rxjs';
import {map} from 'rxjs/operators';
import {AsyncValidatorFn, ValidationErrors, Validator, ValidatorFn} from './directives/validators';
import {AsyncValidator, AsyncValidatorFn, ValidationErrors, Validator, ValidatorFn} from './directives/validators';
import {AbstractControl} from './model';
function isEmptyInputValue(value: any): boolean {
@ -435,7 +435,7 @@ export class Validators {
if (presentValidators.length == 0) return null;
return function(control: AbstractControl) {
return _mergeErrors(_executeValidators(control, presentValidators));
return mergeErrors(executeValidators<ValidatorFn>(control, presentValidators));
};
}
@ -456,8 +456,9 @@ export class Validators {
if (presentValidators.length == 0) return null;
return function(control: AbstractControl) {
const observables = _executeAsyncValidators(control, presentValidators).map(toObservable);
return forkJoin(observables).pipe(map(_mergeErrors));
const observables =
executeValidators<AsyncValidatorFn>(control, presentValidators).map(toObservable);
return forkJoin(observables).pipe(map(mergeErrors));
};
}
}
@ -474,15 +475,7 @@ export function toObservable(r: any): Observable<any> {
return obs;
}
function _executeValidators(control: AbstractControl, validators: ValidatorFn[]): any[] {
return validators.map(v => v(control));
}
function _executeAsyncValidators(control: AbstractControl, validators: AsyncValidatorFn[]): any[] {
return validators.map(v => v(control));
}
function _mergeErrors(arrayOfErrors: ValidationErrors[]): ValidationErrors|null {
function mergeErrors(arrayOfErrors: (ValidationErrors|null)[]): ValidationErrors|null {
let res: {[key: string]: any} = {};
// Not using Array.reduce here due to a Chrome 80 bug
@ -493,3 +486,30 @@ function _mergeErrors(arrayOfErrors: ValidationErrors[]): ValidationErrors|null
return Object.keys(res).length === 0 ? null : res;
}
type GenericValidatorFn = (control: AbstractControl) => any;
function executeValidators<V extends GenericValidatorFn>(
control: AbstractControl, validators: V[]): ReturnType<V>[] {
return validators.map(validator => validator(control));
}
function isValidatorFn<V>(validator: V|Validator|AsyncValidator): validator is V {
return !(validator as Validator).validate;
}
/**
* Given the list of validators that may contain both functions as well as classes, return the list
* of validator functions (convert validator classes into validator functions). This is needed to
* have consistent structure in validators list before composing them.
*
* @param validators The set of validators that may contain validators both in plain function form
* as well as represented as a validator class.
*/
export function normalizeValidators<V>(validators: (V|Validator|AsyncValidator)[]): V[] {
return validators.map(validator => {
return isValidatorFn<V>(validator) ?
validator :
((c: AbstractControl) => validator.validate(c)) as unknown as V;
});
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {async, fakeAsync, tick} from '@angular/core/testing';
import {fakeAsync, tick, waitForAsync} from '@angular/core/testing';
import {AsyncTestCompleter, beforeEach, describe, inject, it} from '@angular/core/testing/src/testing_internal';
import {AbstractControl, FormArray, FormControl, FormGroup, ValidationErrors, Validators} from '@angular/forms';
import {of} from 'rxjs';
@ -726,7 +726,7 @@ describe('FormGroup', () => {
let control: FormControl;
let group: FormGroup;
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
control = new FormControl('', asyncValidatorReturningObservable);
group = new FormGroup({'one': control});
}));

View File

@ -8,7 +8,7 @@
import {ɵgetDOM as getDOM} from '@angular/common';
import {Component, Directive, forwardRef, Type} from '@angular/core';
import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';
import {AbstractControl, AsyncValidator, COMPOSITION_BUFFER_MODE, FormControl, FormsModule, NG_ASYNC_VALIDATORS, NgForm, NgModel} from '@angular/forms';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {dispatchEvent, sortedClassList} from '@angular/platform-browser/testing/src/browser_util';
@ -168,7 +168,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
expect(form.value).toEqual({});
}));
it('should set status classes with ngModel', async(() => {
it('should set status classes with ngModel', waitForAsync(() => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'aa';
fixture.detectChanges();
@ -212,7 +212,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
});
}));
it('should set status classes with ngModelGroup and ngForm', async(() => {
it('should set status classes with ngModelGroup and ngForm', waitForAsync(() => {
const fixture = initTest(NgModelGroupForm);
fixture.componentInstance.first = '';
fixture.detectChanges();
@ -1179,7 +1179,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
expect(input.nativeElement.disabled).toBe(true);
}));
it('should disable a custom control if disabled attr is added', async(() => {
it('should disable a custom control if disabled attr is added', waitForAsync(() => {
const fixture = initTest(NgModelCustomWrapper, NgModelCustomComp);
fixture.componentInstance.name = 'Nancy';
fixture.componentInstance.isDisabled = true;

View File

@ -8,12 +8,12 @@
import {fakeAsync, tick} from '@angular/core/testing';
import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
import {AbstractControl, AsyncValidatorFn, FormArray, FormControl, Validators} from '@angular/forms';
import {normalizeAsyncValidator} from '@angular/forms/src/directives/normalize_validator';
import {AsyncValidator, ValidationErrors, ValidatorFn} from '@angular/forms/src/directives/validators';
import {AbstractControl, AsyncValidator, AsyncValidatorFn, FormArray, FormControl, ValidationErrors, ValidatorFn, Validators} from '@angular/forms';
import {Observable, of, timer} from 'rxjs';
import {first, map} from 'rxjs/operators';
import {normalizeValidators} from '../src/validators';
(function() {
function validator(key: string, error: any): ValidatorFn {
return (c: AbstractControl) => {
@ -413,11 +413,12 @@ describe('Validators', () => {
}));
it('should normalize and evaluate async validator-directives correctly', fakeAsync(() => {
const v = Validators.composeAsync(
[normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))])!;
const normalizedValidators = normalizeValidators<AsyncValidatorFn>(
[new AsyncValidatorDirective('expected', {'one': true})]);
const validatorFn = Validators.composeAsync(normalizedValidators)!;
let errorMap: {[key: string]: any}|null = undefined!;
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
(validatorFn(new FormControl('invalid')) as Observable<ValidationErrors|null>)
.pipe(first())
.subscribe((errors: {[key: string]: any}|null) => errorMap = errors);
tick();
@ -475,11 +476,12 @@ describe('Validators', () => {
});
it('should normalize and evaluate async validator-directives correctly', () => {
const v = Validators.composeAsync(
[normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))])!;
const normalizedValidators = normalizeValidators<AsyncValidatorFn>(
[new AsyncValidatorDirective('expected', {'one': true})]);
const validatorFn = Validators.composeAsync(normalizedValidators)!;
let errorMap: {[key: string]: any}|null = undefined!;
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
(validatorFn(new FormControl('invalid')) as Observable<ValidationErrors|null>)
.pipe(first())
.subscribe((errors: {[key: string]: any}|null) => errorMap = errors)!;

View File

@ -7,7 +7,7 @@
*/
import {Component, Directive, EventEmitter, Input, Output, Type, ViewChild} from '@angular/core';
import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';
import {AbstractControl, ControlValueAccessor, FormControl, FormGroup, FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl, NgForm, NgModel, ReactiveFormsModule, Validators} from '@angular/forms';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
@ -1078,7 +1078,7 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
});
describe('in template-driven forms', () => {
it('should support standard writing to view and model', async(() => {
it('should support standard writing to view and model', waitForAsync(() => {
const fixture = initTest(NgModelCustomWrapper, NgModelCustomComp);
fixture.componentInstance.name = 'Nancy';
fixture.detectChanges();

View File

@ -10,7 +10,7 @@
import {HTTP_INTERCEPTORS, HttpBackend, HttpClient, HttpClientModule, HttpEvent, HttpEventType, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {async, TestBed} from '@angular/core/testing';
import {TestBed, waitForAsync} from '@angular/core/testing';
import {HttpClientBackendService, HttpClientInMemoryWebApiModule} from 'angular-in-memory-web-api';
import {Observable, zip} from 'rxjs';
import {concatMap, map, tap} from 'rxjs/operators';
@ -37,14 +37,14 @@ describe('HttpClient Backend Service', () => {
http = TestBed.get(HttpClient);
});
it('can get heroes', async(() => {
it('can get heroes', waitForAsync(() => {
http.get<Hero[]>('api/heroes')
.subscribe(
heroes => expect(heroes.length).toBeGreaterThan(0, 'should have heroes'),
failRequest);
}));
it('GET should be a "cold" observable', async(() => {
it('GET should be a "cold" observable', waitForAsync(() => {
const httpBackend = TestBed.get(HttpBackend);
const spy = spyOn(httpBackend, 'collectionHandler').and.callThrough();
const get$ = http.get<Hero[]>('api/heroes');
@ -58,7 +58,7 @@ describe('HttpClient Backend Service', () => {
}, failRequest);
}));
it('GET should wait until after delay to respond', async(() => {
it('GET should wait until after delay to respond', waitForAsync(() => {
// to make test fail, set `delay=0` above
let gotResponse = false;
@ -70,7 +70,7 @@ describe('HttpClient Backend Service', () => {
expect(gotResponse).toBe(false, 'should delay before response');
}));
it('Should only initialize the db once', async(() => {
it('Should only initialize the db once', waitForAsync(() => {
const httpBackend = TestBed.get(HttpBackend);
const spy = spyOn(httpBackend, 'resetDb').and.callThrough();
@ -86,13 +86,13 @@ describe('HttpClient Backend Service', () => {
expect(spy.calls.count()).toBe(1);
}));
it('can get heroes (w/ a different base path)', async(() => {
it('can get heroes (w/ a different base path)', waitForAsync(() => {
http.get<Hero[]>('some-base-path/heroes').subscribe(heroes => {
expect(heroes.length).toBeGreaterThan(0, 'should have heroes');
}, failRequest);
}));
it('should 404 when GET unknown collection (after delay)', async(() => {
it('should 404 when GET unknown collection (after delay)', waitForAsync(() => {
let gotError = false;
const url = 'api/unknown-collection';
http.get<Hero[]>(url).subscribe(
@ -104,46 +104,46 @@ describe('HttpClient Backend Service', () => {
expect(gotError).toBe(false, 'should not get error until after delay');
}));
it('should return the hero w/id=1 for GET app/heroes/1', async(() => {
it('should return the hero w/id=1 for GET app/heroes/1', waitForAsync(() => {
http.get<Hero>('api/heroes/1')
.subscribe(
hero => expect(hero).toBeDefined('should find hero with id=1'), failRequest);
}));
// test where id is string that looks like a number
it('should return the stringer w/id="10" for GET app/stringers/10', async(() => {
it('should return the stringer w/id="10" for GET app/stringers/10', waitForAsync(() => {
http.get<Hero>('api/stringers/10')
.subscribe(
hero => expect(hero).toBeDefined('should find string with id="10"'), failRequest);
}));
it('should return 1-item array for GET app/heroes/?id=1', async(() => {
it('should return 1-item array for GET app/heroes/?id=1', waitForAsync(() => {
http.get<Hero[]>('api/heroes/?id=1')
.subscribe(
heroes => expect(heroes.length).toBe(1, 'should find one hero w/id=1'),
failRequest);
}));
it('should return 1-item array for GET app/heroes?id=1', async(() => {
it('should return 1-item array for GET app/heroes?id=1', waitForAsync(() => {
http.get<Hero[]>('api/heroes?id=1')
.subscribe(
heroes => expect(heroes.length).toBe(1, 'should find one hero w/id=1'),
failRequest);
}));
it('should return undefined for GET app/heroes?id=not-found-id', async(() => {
it('should return undefined for GET app/heroes?id=not-found-id', waitForAsync(() => {
http.get<Hero[]>('api/heroes?id=123456')
.subscribe(heroes => expect(heroes.length).toBe(0), failRequest);
}));
it('should return 404 for GET app/heroes/not-found-id', async(() => {
it('should return 404 for GET app/heroes/not-found-id', waitForAsync(() => {
const url = 'api/heroes/123456';
http.get<Hero[]>(url).subscribe(
() => fail(`should not have found data for '${url}'`),
err => expect(err.status).toBe(404, 'should have 404 status'));
}));
it('can generate the id when add a hero with no id', async(() => {
it('can generate the id when add a hero with no id', waitForAsync(() => {
const hero = new Hero(undefined, 'SuperDooper');
http.post<Hero>('api/heroes', hero).subscribe(replyHero => {
expect(replyHero.id).toBeDefined('added hero should have an id');
@ -151,13 +151,13 @@ describe('HttpClient Backend Service', () => {
}, failRequest);
}));
it('can get nobodies (empty collection)', async(() => {
it('can get nobodies (empty collection)', waitForAsync(() => {
http.get<Hero[]>('api/nobodies').subscribe(nobodies => {
expect(nobodies.length).toBe(0, 'should have no nobodies');
}, failRequest);
}));
it('can add a nobody with an id to empty nobodies collection', async(() => {
it('can add a nobody with an id to empty nobodies collection', waitForAsync(() => {
const id = 'g-u-i-d';
http.post('api/nobodies', {id, name: 'Noman'})
@ -169,7 +169,8 @@ describe('HttpClient Backend Service', () => {
}, failRequest);
}));
it('should fail when add a nobody without an id to empty nobodies collection', async(() => {
it('should fail when add a nobody without an id to empty nobodies collection',
waitForAsync(() => {
http.post('api/nobodies', {name: 'Noman'})
.subscribe(
() => fail(`should not have been able to add 'Norman' to 'nobodies'`), err => {
@ -179,11 +180,11 @@ describe('HttpClient Backend Service', () => {
}));
describe('can reset the database', () => {
it('to empty (object db)', async(() => resetDatabaseTest('object')));
it('to empty (object db)', waitForAsync(() => resetDatabaseTest('object')));
it('to empty (observable db)', async(() => resetDatabaseTest('observable')));
it('to empty (observable db)', waitForAsync(() => resetDatabaseTest('observable')));
it('to empty (promise db)', async(() => resetDatabaseTest('promise')));
it('to empty (promise db)', waitForAsync(() => resetDatabaseTest('promise')));
function resetDatabaseTest(returnType: string) {
// Observable of the number of heroes and nobodies
@ -223,28 +224,29 @@ describe('HttpClient Backend Service', () => {
http = TestBed.get(HttpClient);
});
it('can get heroes', async(() => {
it('can get heroes', waitForAsync(() => {
http.get<Hero[]>('api/heroes')
.subscribe(
heroes => expect(heroes.length).toBeGreaterThan(0, 'should have heroes'),
failRequest);
}));
it('can translate `foo/heroes` to `heroes` via `parsedRequestUrl` override', async(() => {
it('can translate `foo/heroes` to `heroes` via `parsedRequestUrl` override',
waitForAsync(() => {
http.get<Hero[]>('api/foo/heroes')
.subscribe(
heroes => expect(heroes.length).toBeGreaterThan(0, 'should have heroes'),
failRequest);
}));
it('can get villains', async(() => {
it('can get villains', waitForAsync(() => {
http.get<Hero[]>('api/villains')
.subscribe(
villains => expect(villains.length).toBeGreaterThan(0, 'should have villains'),
failRequest);
}));
it('should 404 when POST to villains', async(() => {
it('should 404 when POST to villains', waitForAsync(() => {
const url = 'api/villains';
http.post<Hero[]>(url, {id: 42, name: 'Dr. Evil'})
.subscribe(
@ -252,14 +254,14 @@ describe('HttpClient Backend Service', () => {
err => expect(err.status).toBe(404, 'should have 404 status'));
}));
it('should 404 when GET unknown collection', async(() => {
it('should 404 when GET unknown collection', waitForAsync(() => {
const url = 'api/unknown-collection';
http.get<Hero[]>(url).subscribe(
() => fail(`should not have found data for '${url}'`),
err => expect(err.status).toBe(404, 'should have 404 status'));
}));
it('should use genId override to add new hero, "Maxinius"', async(() => {
it('should use genId override to add new hero, "Maxinius"', waitForAsync(() => {
http.post('api/heroes', {name: 'Maxinius'})
.pipe(concatMap(() => http.get<Hero[]>('api/heroes?name=Maxi')))
.subscribe(heroes => {
@ -269,7 +271,8 @@ describe('HttpClient Backend Service', () => {
}, failRequest);
}));
it('should use genId override guid generator for a new nobody without an id', async(() => {
it('should use genId override guid generator for a new nobody without an id',
waitForAsync(() => {
http.post('api/nobodies', {name: 'Noman'})
.pipe(concatMap(() => http.get<{id: string; name: string}[]>('api/nobodies')))
.subscribe(nobodies => {
@ -280,11 +283,11 @@ describe('HttpClient Backend Service', () => {
}));
describe('can reset the database', () => {
it('to empty (object db)', async(() => resetDatabaseTest('object')));
it('to empty (object db)', waitForAsync(() => resetDatabaseTest('object')));
it('to empty (observable db)', async(() => resetDatabaseTest('observable')));
it('to empty (observable db)', waitForAsync(() => resetDatabaseTest('observable')));
it('to empty (promise db)', async(() => resetDatabaseTest('promise')));
it('to empty (promise db)', waitForAsync(() => resetDatabaseTest('promise')));
function resetDatabaseTest(returnType: string) {
// Observable of the number of heroes, nobodies and villains
@ -331,19 +334,19 @@ describe('HttpClient Backend Service', () => {
heroService = TestBed.get(HeroService);
});
it('can get heroes', async(() => {
it('can get heroes', waitForAsync(() => {
heroService.getHeroes().subscribe(heroes => {
expect(heroes.length).toBeGreaterThan(0, 'should have heroes');
}, failRequest);
}));
it('can get hero w/ id=1', async(() => {
it('can get hero w/ id=1', waitForAsync(() => {
heroService.getHero(1).subscribe(hero => {
expect(hero.name).toBe('Windstorm');
}, () => fail('getHero failed'));
}));
it('should 404 when hero id not found', async(() => {
it('should 404 when hero id not found', waitForAsync(() => {
const id = 123456;
heroService.getHero(id).subscribe(
() => fail(`should not have found hero for id='${id}'`), err => {
@ -351,7 +354,7 @@ describe('HttpClient Backend Service', () => {
});
}));
it('can add a hero', async(() => {
it('can add a hero', waitForAsync(() => {
heroService.addHero('FunkyBob')
.pipe(
tap(hero => expect(hero.name).toBe('FunkyBob')),
@ -363,23 +366,23 @@ describe('HttpClient Backend Service', () => {
}),
10000);
it('can delete a hero', async(() => {
it('can delete a hero', waitForAsync(() => {
const id = 1;
heroService.deleteHero(id).subscribe((_: {}) => expect(_).toBeDefined(), failRequest);
}));
it('should allow delete of non-existent hero', async(() => {
it('should allow delete of non-existent hero', waitForAsync(() => {
const id = 123456;
heroService.deleteHero(id).subscribe((_: {}) => expect(_).toBeDefined(), failRequest);
}));
it('can search for heroes by name containing "a"', async(() => {
it('can search for heroes by name containing "a"', waitForAsync(() => {
heroService.searchHeroes('a').subscribe((heroes: Hero[]) => {
expect(heroes.length).toBe(3, 'should find 3 heroes with letter "a"');
}, failRequest);
}));
it('can update existing hero', async(() => {
it('can update existing hero', waitForAsync(() => {
const id = 1;
heroService.getHero(id)
.pipe(
@ -394,7 +397,7 @@ describe('HttpClient Backend Service', () => {
}),
10000);
it('should create new hero when try to update non-existent hero', async(() => {
it('should create new hero when try to update non-existent hero', waitForAsync(() => {
const falseHero = new Hero(12321, 'DryMan');
heroService.updateHero(falseHero).subscribe(
hero => expect(hero.name).toBe(falseHero.name), failRequest);
@ -446,7 +449,7 @@ describe('HttpClient Backend Service', () => {
expect(ti).toBeDefined();
});
it('should have GET request header from test interceptor', async(() => {
it('should have GET request header from test interceptor', waitForAsync(() => {
const handle = spyOn(httpBackend, 'handle').and.callThrough();
http.get<Hero[]>('api/heroes').subscribe(heroes => {
@ -459,7 +462,7 @@ describe('HttpClient Backend Service', () => {
}, failRequest);
}));
it('should have GET response header from test interceptor', async(() => {
it('should have GET response header from test interceptor', waitForAsync(() => {
let gotResponse = false;
const req = new HttpRequest<any>('GET', 'api/heroes');
http.request<Hero[]>(req).subscribe(event => {
@ -503,7 +506,7 @@ describe('HttpClient Backend Service', () => {
jasmine.Ajax.uninstall();
});
it('can get heroes (no passthru)', async(() => {
it('can get heroes (no passthru)', waitForAsync(() => {
http.get<Hero[]>('api/heroes').subscribe(heroes => {
expect(createPassThruBackend).not.toHaveBeenCalled();
expect(heroes.length).toBeGreaterThan(0, 'should have heroes');
@ -513,7 +516,7 @@ describe('HttpClient Backend Service', () => {
// `passthru` is NOT a collection in the data store
// so requests for it should pass thru to the "real" server
it('can GET passthru', async(() => {
it('can GET passthru', waitForAsync(() => {
jasmine.Ajax.stubRequest('api/passthru').andReturn({
'status': 200,
'contentType': 'application/json',
@ -525,7 +528,7 @@ describe('HttpClient Backend Service', () => {
}, failRequest);
}));
it('can ADD to passthru', async(() => {
it('can ADD to passthru', waitForAsync(() => {
jasmine.Ajax.stubRequest('api/passthru').andReturn({
'status': 200,
'contentType': 'application/json',
@ -554,7 +557,7 @@ describe('HttpClient Backend Service', () => {
http = TestBed.get(HttpClient);
});
it('can get heroes (encapsulated)', async(() => {
it('can get heroes (encapsulated)', waitForAsync(() => {
http.get<{data: any}>('api/heroes')
.pipe(map(data => data.data as Hero[]))
.subscribe(

View File

@ -8,7 +8,7 @@
import {ResourceLoader, UrlResolver} from '@angular/compiler';
import {Component} from '@angular/core';
import {async, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';
import {CachedResourceLoader} from '@angular/platform-browser-dynamic/src/resource_loader/resource_loader_cache';
import {setTemplateCache} from '@angular/platform-browser-dynamic/test/resource_loader/resource_loader_cache_setter';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -29,14 +29,14 @@ if (isBrowser) {
}).toThrowError('CachedResourceLoader: Template cache was not found in $templateCache.');
});
it('should resolve the Promise with the cached file content on success', async(() => {
it('should resolve the Promise with the cached file content on success', waitForAsync(() => {
resourceLoader = createCachedResourceLoader();
resourceLoader.get('test.html').then((text) => {
expect(text).toBe('<div>Hello</div>');
});
}));
it('should reject the Promise on failure', async(() => {
it('should reject the Promise on failure', waitForAsync(() => {
resourceLoader = createCachedResourceLoader();
resourceLoader.get('unknown.html').then(() => {
throw new Error('Not expected to succeed.');

View File

@ -8,7 +8,7 @@
import {ResourceLoader} from '@angular/compiler';
import {Compiler, Component, NgModule} from '@angular/core';
import {async, fakeAsync, inject, TestBed, tick} from '@angular/core/testing';
import {fakeAsync, inject, TestBed, tick, waitForAsync} from '@angular/core/testing';
import {ResourceLoaderImpl} from '@angular/platform-browser-dynamic/src/resource_loader/resource_loader_impl';
@ -54,7 +54,7 @@ if (isBrowser) {
expect(actuallyDone).toEqual(true);
});
it('should run async tests with ResourceLoaders', async(() => {
it('should run async tests with ResourceLoaders', waitForAsync(() => {
const resourceLoader = new ResourceLoaderImpl();
resourceLoader
.get('/base/angular/packages/platform-browser/test/static_assets/test.html')
@ -132,7 +132,7 @@ if (isBrowser) {
it('should fail when an ResourceLoader fails', done => {
const itPromise = patchJasmineIt();
it('should fail with an error from a promise', async(() => {
it('should fail with an error from a promise', waitForAsync(() => {
TestBed.configureTestingModule({declarations: [BadTemplateUrl]});
TestBed.compileComponents();
}));
@ -151,7 +151,7 @@ if (isBrowser) {
});
describe('TestBed createComponent', function() {
it('should allow an external templateUrl', async(() => {
it('should allow an external templateUrl', waitForAsync(() => {
TestBed.configureTestingModule({declarations: [ExternalTemplateComp]});
TestBed.compileComponents().then(() => {
const componentFixture = TestBed.createComponent(ExternalTemplateComp);

View File

@ -111,7 +111,7 @@ export class KeyEventsPlugin extends EventManagerPlugin {
});
}
static parseEventName(eventName: string): {[key: string]: string}|null {
static parseEventName(eventName: string): {fullKey: string, domEventName: string}|null {
const parts: string[] = eventName.toLowerCase().split('.');
const domEventName = parts.shift();
@ -136,7 +136,10 @@ export class KeyEventsPlugin extends EventManagerPlugin {
return null;
}
const result: {[k: string]: string} = {};
// NOTE: Please don't rewrite this as so, as it will break JSCompiler property renaming.
// The code must remain in the `result['domEventName']` form.
// return {domEventName, fullKey};
const result: {fullKey: string, domEventName: string} = {} as any;
result['domEventName'] = domEventName;
result['fullKey'] = fullKey;
return result;

View File

@ -8,7 +8,7 @@
import {CompilerConfig, ResourceLoader} from '@angular/compiler';
import {Compiler, Component, ComponentFactoryResolver, CUSTOM_ELEMENTS_SCHEMA, Directive, Inject, Injectable, InjectionToken, Injector, Input, NgModule, Optional, Pipe, SkipSelf, ɵstringify as stringify} from '@angular/core';
import {async, fakeAsync, getTestBed, inject, TestBed, tick, withModule} from '@angular/core/testing';
import {fakeAsync, getTestBed, inject, TestBed, tick, waitForAsync, withModule} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ivyEnabled, modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing';
@ -146,11 +146,11 @@ const bTok = new InjectionToken<string>('b');
}, 0);
});
it('should run async tests with tasks', async(function(this: TestContext) {
it('should run async tests with tasks', waitForAsync(function(this: TestContext) {
setTimeout(() => this.actuallyDone = true, 0);
}));
it('should run async tests with promises', async(function(this: TestContext) {
it('should run async tests with promises', waitForAsync(function(this: TestContext) {
const p = new Promise((resolve, reject) => setTimeout(resolve, 10));
p.then(() => this.actuallyDone = true);
}));
@ -192,7 +192,7 @@ const bTok = new InjectionToken<string>('b');
}));
it('should preserve context when async and inject helpers are combined',
async(inject([], function(this: TestContext) {
waitForAsync(inject([], function(this: TestContext) {
setTimeout(() => this.contextModified = true, 0);
})));
@ -214,7 +214,7 @@ const bTok = new InjectionToken<string>('b');
}));
it('should wait until returned promises',
async(inject([FancyService], (service: FancyService) => {
waitForAsync(inject([FancyService], (service: FancyService) => {
service.getAsyncValue().then((value) => expect(value).toEqual('async value'));
service.getTimeoutValue().then((value) => expect(value).toEqual('timeout value'));
})));
@ -251,7 +251,7 @@ const bTok = new InjectionToken<string>('b');
});
describe('using async beforeEach', () => {
beforeEach(async(inject([FancyService], (service: FancyService) => {
beforeEach(waitForAsync(inject([FancyService], (service: FancyService) => {
service.getAsyncValue().then((value) => service.value = value);
})));
@ -335,7 +335,7 @@ const bTok = new InjectionToken<string>('b');
});
describe('components with template url', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({declarations: [CompWithUrlTemplate]});
TestBed.compileComponents();
}));
@ -892,9 +892,9 @@ const bTok = new InjectionToken<string>('b');
const itPromise = patchJasmineIt();
const barError = new Error('bar');
it('throws an async error', async(inject([], () => setTimeout(() => {
throw barError;
}, 0))));
it('throws an async error', waitForAsync(inject([], () => setTimeout(() => {
throw barError;
}, 0))));
itPromise.then(() => done.fail('Expected test to fail, but it did not'), (err) => {
expect(err).toEqual(barError);
@ -906,7 +906,7 @@ const bTok = new InjectionToken<string>('b');
it('should fail when a returned promise is rejected', (done) => {
const itPromise = patchJasmineIt();
it('should fail with an error from a promise', async(inject([], () => {
it('should fail with an error from a promise', waitForAsync(inject([], () => {
let reject: (error: any) => void = undefined!;
const promise = new Promise((_, rej) => reject = rej);
const p = promise.then(() => expect(1).toEqual(2));
@ -1011,14 +1011,14 @@ Did you run and wait for 'resolveComponentResources()'?` :
});
});
it('should instantiate a component with valid DOM', async(() => {
it('should instantiate a component with valid DOM', waitForAsync(() => {
const fixture = TestBed.createComponent(ChildComp);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('Original Child');
}));
it('should allow changing members of the component', async(() => {
it('should allow changing members of the component', waitForAsync(() => {
const componentFixture = TestBed.createComponent(MyIfComp);
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('MyIf()');
@ -1028,14 +1028,14 @@ Did you run and wait for 'resolveComponentResources()'?` :
expect(componentFixture.nativeElement).toHaveText('MyIf(More)');
}));
it('should override a template', async(() => {
it('should override a template', waitForAsync(() => {
TestBed.overrideComponent(ChildComp, {set: {template: '<span>Mock</span>'}});
const componentFixture = TestBed.createComponent(ChildComp);
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('Mock');
}));
it('should override a provider', async(() => {
it('should override a provider', waitForAsync(() => {
TestBed.overrideComponent(
TestProvidersComp,
{set: {providers: [{provide: FancyService, useClass: MockFancyService}]}});
@ -1045,7 +1045,7 @@ Did you run and wait for 'resolveComponentResources()'?` :
}));
it('should override a viewProvider', async(() => {
it('should override a viewProvider', waitForAsync(() => {
TestBed.overrideComponent(
TestViewProvidersComp,
{set: {viewProviders: [{provide: FancyService, useClass: MockFancyService}]}});
@ -1065,7 +1065,7 @@ Did you run and wait for 'resolveComponentResources()'?` :
});
});
it('should override component dependencies', async(() => {
it('should override component dependencies', waitForAsync(() => {
const componentFixture = TestBed.createComponent(ParentComp);
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('Parent(Mock)');

View File

@ -11,7 +11,7 @@ import {DOCUMENT, isPlatformServer, PlatformLocation, ɵgetDOM as getDOM} from '
import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {ApplicationRef, CompilerFactory, Component, destroyPlatform, getPlatform, HostListener, Inject, Injectable, Input, NgModule, NgZone, PLATFORM_ID, PlatformRef, ViewEncapsulation} from '@angular/core';
import {async, inject} from '@angular/core/testing';
import {inject, waitForAsync} from '@angular/core/testing';
import {BrowserModule, makeStateKey, Title, TransferState} from '@angular/platform-browser';
import {BEFORE_APP_SERIALIZED, INITIAL_CONFIG, platformDynamicServer, PlatformState, renderModule, renderModuleFactory, ServerModule, ServerTransferStateModule} from '@angular/platform-server';
import {ivyEnabled, modifiedInIvy} from '@angular/private/testing';
@ -409,7 +409,7 @@ describe('platform-server integration', () => {
if (getPlatform()) destroyPlatform();
});
it('should bootstrap', async(() => {
it('should bootstrap', waitForAsync(() => {
const platform =
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
@ -426,7 +426,7 @@ describe('platform-server integration', () => {
});
}));
it('should allow multiple platform instances', async(() => {
it('should allow multiple platform instances', waitForAsync(() => {
const platform =
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
@ -447,7 +447,7 @@ describe('platform-server integration', () => {
});
}));
it('adds title to the document using Title service', async(() => {
it('adds title to the document using Title service', waitForAsync(() => {
const platform = platformDynamicServer([{
provide: INITIAL_CONFIG,
useValue: {document: '<html><head><title></title></head><body><app></app></body></html>'}
@ -461,7 +461,7 @@ describe('platform-server integration', () => {
});
}));
it('should get base href from document', async(() => {
it('should get base href from document', waitForAsync(() => {
const platform = platformDynamicServer([{
provide: INITIAL_CONFIG,
useValue: {document: '<html><head><base href="/"></head><body><app></app></body></html>'}
@ -473,7 +473,7 @@ describe('platform-server integration', () => {
});
}));
it('adds styles with ng-transition attribute', async(() => {
it('adds styles with ng-transition attribute', waitForAsync(() => {
const platform = platformDynamicServer([{
provide: INITIAL_CONFIG,
useValue: {document: '<html><head></head><body><app></app></body></html>'}
@ -488,7 +488,7 @@ describe('platform-server integration', () => {
});
}));
it('copies known properties to attributes', async(() => {
it('copies known properties to attributes', waitForAsync(() => {
const platform =
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
platform.bootstrapModule(ImageExampleModule).then(ref => {
@ -500,7 +500,7 @@ describe('platform-server integration', () => {
}));
describe('PlatformLocation', () => {
it('is injectable', async(() => {
it('is injectable', waitForAsync(() => {
const platform = platformDynamicServer(
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
platform.bootstrapModule(ExampleModule).then(appRef => {
@ -551,7 +551,7 @@ describe('platform-server integration', () => {
expect(location.hash).toBe('');
});
});
it('pushState causes the URL to update', async(() => {
it('pushState causes the URL to update', waitForAsync(() => {
const platform = platformDynamicServer(
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
platform.bootstrapModule(ExampleModule).then(appRef => {
@ -601,7 +601,7 @@ describe('platform-server integration', () => {
expect(called).toBe(true);
});
it('using long form should work', async(() => {
it('using long form should work', waitForAsync(() => {
const platform =
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: doc}}]);
@ -618,7 +618,7 @@ describe('platform-server integration', () => {
});
}));
it('using renderModule should work', async(() => {
it('using renderModule should work', waitForAsync(() => {
renderModule(AsyncServerModule, {document: doc}).then(output => {
expect(output).toBe(expectedOutput);
called = true;
@ -626,7 +626,7 @@ describe('platform-server integration', () => {
}));
modifiedInIvy('Will not support binding to innerText in Ivy since domino does not')
.it('should support binding to innerText', async(() => {
.it('should support binding to innerText', waitForAsync(() => {
renderModule(InnerTextModule, {document: doc}).then(output => {
expect(output).toBe(
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER"><div innertext="Some text">Some text</div></app></body></html>');
@ -635,7 +635,7 @@ describe('platform-server integration', () => {
}));
it('using renderModuleFactory should work',
async(inject([PlatformRef], (defaultPlatform: PlatformRef) => {
waitForAsync(inject([PlatformRef], (defaultPlatform: PlatformRef) => {
const compilerFactory: CompilerFactory =
defaultPlatform.injector.get(CompilerFactory, null)!;
const moduleFactory =
@ -646,7 +646,7 @@ describe('platform-server integration', () => {
});
})));
it('works with SVG elements', async(() => {
it('works with SVG elements', waitForAsync(() => {
renderModule(SVGServerModule, {document: doc}).then(output => {
expect(output).toBe(
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
@ -655,7 +655,7 @@ describe('platform-server integration', () => {
});
}));
it('works with animation', async(() => {
it('works with animation', waitForAsync(() => {
renderModule(AnimationServerModule, {document: doc}).then(output => {
expect(output).toContain('Works!');
expect(output).toContain('ng-trigger-myAnimation');
@ -666,7 +666,7 @@ describe('platform-server integration', () => {
});
}));
it('should handle ViewEncapsulation.Native', async(() => {
it('should handle ViewEncapsulation.Native', waitForAsync(() => {
renderModule(NativeExampleModule, {document: doc}).then(output => {
expect(output).not.toBe('');
expect(output).toContain('color: red');
@ -675,7 +675,7 @@ describe('platform-server integration', () => {
}));
it('sets a prefix for the _nghost and _ngcontent attributes', async(() => {
it('sets a prefix for the _nghost and _ngcontent attributes', waitForAsync(() => {
renderModule(ExampleStylesModule, {document: doc}).then(output => {
expect(output).toMatch(
/<html><head><style ng-transition="example-styles">div\[_ngcontent-sc\d+\] {color: blue; } \[_nghost-sc\d+\] { color: red; }<\/style><\/head><body><app _nghost-sc\d+="" ng-version="0.0.0-PLACEHOLDER"><div _ngcontent-sc\d+="">Works!<\/div><\/app><\/body><\/html>/);
@ -683,7 +683,7 @@ describe('platform-server integration', () => {
});
}));
it('should handle false values on attributes', async(() => {
it('should handle false values on attributes', waitForAsync(() => {
renderModule(FalseAttributesModule, {document: doc}).then(output => {
expect(output).toBe(
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
@ -692,7 +692,7 @@ describe('platform-server integration', () => {
});
}));
it('should handle element property "name"', async(() => {
it('should handle element property "name"', waitForAsync(() => {
renderModule(NameModule, {document: doc}).then(output => {
expect(output).toBe(
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
@ -701,7 +701,7 @@ describe('platform-server integration', () => {
});
}));
it('should work with sanitizer to handle "innerHTML"', async(() => {
it('should work with sanitizer to handle "innerHTML"', waitForAsync(() => {
// Clear out any global states. These should be set when platform-server
// is initialized.
(global as any).Node = undefined;
@ -714,7 +714,7 @@ describe('platform-server integration', () => {
});
}));
it('should handle element property "hidden"', async(() => {
it('should handle element property "hidden"', waitForAsync(() => {
renderModule(HiddenModule, {document: doc}).then(output => {
expect(output).toBe(
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
@ -723,7 +723,7 @@ describe('platform-server integration', () => {
});
}));
it('should call render hook', async(() => {
it('should call render hook', waitForAsync(() => {
renderModule(RenderHookModule, {document: doc}).then(output => {
// title should be added by the render hook.
expect(output).toBe(
@ -733,7 +733,7 @@ describe('platform-server integration', () => {
});
}));
it('should call multiple render hooks', async(() => {
it('should call multiple render hooks', waitForAsync(() => {
const consoleSpy = spyOn(console, 'warn');
renderModule(MultiRenderHookModule, {document: doc}).then(output => {
// title should be added by the render hook.
@ -745,7 +745,7 @@ describe('platform-server integration', () => {
});
}));
it('should call async render hooks', async(() => {
it('should call async render hooks', waitForAsync(() => {
renderModule(AsyncRenderHookModule, {document: doc}).then(output => {
// title should be added by the render hook.
expect(output).toBe(
@ -755,7 +755,7 @@ describe('platform-server integration', () => {
});
}));
it('should call multiple async and sync render hooks', async(() => {
it('should call multiple async and sync render hooks', waitForAsync(() => {
const consoleSpy = spyOn(console, 'warn');
renderModule(AsyncMultiRenderHookModule, {document: doc}).then(output => {
// title should be added by the render hook.
@ -769,7 +769,7 @@ describe('platform-server integration', () => {
});
describe('HttpClient', () => {
it('can inject HttpClient', async(() => {
it('can inject HttpClient', waitForAsync(() => {
const platform = platformDynamicServer(
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
platform.bootstrapModule(HttpClientExampleModule).then(ref => {
@ -777,7 +777,7 @@ describe('platform-server integration', () => {
});
}));
it('can make HttpClient requests', async(() => {
it('can make HttpClient requests', waitForAsync(() => {
const platform = platformDynamicServer(
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
platform.bootstrapModule(HttpClientExampleModule).then(ref => {
@ -938,7 +938,7 @@ describe('platform-server integration', () => {
});
});
it('requests are macrotasks', async(() => {
it('requests are macrotasks', waitForAsync(() => {
const platform = platformDynamicServer(
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
platform.bootstrapModule(HttpClientExampleModule).then(ref => {
@ -984,7 +984,7 @@ describe('platform-server integration', () => {
expect(called).toBe(true);
});
it('adds transfer script tag when using renderModule', async(() => {
it('adds transfer script tag when using renderModule', waitForAsync(() => {
renderModule(TransferStoreModule, {document: '<app></app>'}).then(output => {
expect(output).toBe(defaultExpectedOutput);
called = true;
@ -992,7 +992,7 @@ describe('platform-server integration', () => {
}));
it('adds transfer script tag when using renderModuleFactory',
async(inject([PlatformRef], (defaultPlatform: PlatformRef) => {
waitForAsync(inject([PlatformRef], (defaultPlatform: PlatformRef) => {
const compilerFactory: CompilerFactory =
defaultPlatform.injector.get(CompilerFactory, null)!;
const moduleFactory =
@ -1003,7 +1003,7 @@ describe('platform-server integration', () => {
});
})));
it('cannot break out of <script> tag in serialized output', async(() => {
it('cannot break out of <script> tag in serialized output', waitForAsync(() => {
renderModule(EscapedTransferStoreModule, {
document: '<esc-app></esc-app>'
}).then(output => {

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {isDevMode} from '@angular/core';
import {MonoTypeOperatorFunction} from 'rxjs';
import {map} from 'rxjs/operators';
@ -185,6 +186,10 @@ export class ActivateRoutes {
// Activate the outlet when it has already been instantiated
// Otherwise it will get activated from its `ngOnInit` when instantiated
context.outlet.activateWith(future, cmpFactoryResolver);
} else if (isDevMode() && console && console.warn) {
console.warn(
`A router outlet has not been instantiated during routes activation. URL Segment: '${
future.snapshot._urlSegment}'`);
}
this.activateChildRoutes(futureNode, null, context.children);

View File

@ -74,8 +74,7 @@ export class NoPreloading implements PreloadingStrategy {
@Injectable()
export class RouterPreloader implements OnDestroy {
private loader: RouterConfigLoader;
// TODO(issue/24571): remove '!'.
private subscription!: Subscription;
private subscription?: Subscription;
constructor(
private router: Router, moduleLoader: NgModuleFactoryLoader, compiler: Compiler,
@ -98,11 +97,10 @@ export class RouterPreloader implements OnDestroy {
return this.processRoutes(ngModule, this.router.config);
}
// TODO(jasonaden): This class relies on code external to the class to call setUpPreloading. If
// this hasn't been done, ngOnDestroy will fail as this.subscription will be undefined. This
// should be refactored.
ngOnDestroy(): void {
this.subscription.unsubscribe();
if (this.subscription) {
this.subscription.unsubscribe();
}
}
private processRoutes(ngModule: NgModuleRef<any>, routes: Routes): Observable<void> {

View File

@ -19,6 +19,23 @@ describe('RouterPreloader', () => {
class LazyLoadedCmp {
}
describe('should properly handle', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes(
[{path: 'lazy', loadChildren: 'expected', canLoad: ['someGuard']}])],
providers: [{provide: PreloadingStrategy, useExisting: PreloadAllModules}]
});
});
it('being destroyed before expected', () => {
const preloader: RouterPreloader = TestBed.get(RouterPreloader);
// Calling the RouterPreloader's ngOnDestroy method is done to simulate what would happen if
// the containing NgModule is destroyed.
expect(() => preloader.ngOnDestroy()).not.toThrow();
});
});
describe('should not load configurations with canLoad guard', () => {
@NgModule({
declarations: [LazyLoadedCmp],

View File

@ -720,7 +720,11 @@ export class Driver implements Debuggable, UpdateSource {
private async deleteAllCaches(): Promise<void> {
await (await this.scope.caches.keys())
.filter(key => key.startsWith(`${this.adapter.cacheNamePrefix}:`))
// The Chrome debugger is not able to render the syntax properly when the
// code contains backticks. This is a known issue in Chrome and they have an
// open [issue](https://bugs.chromium.org/p/chromium/issues/detail?id=659515) for that.
// As a work-around for the time being, we can use \\ ` at the end of the line.
.filter(key => key.startsWith(`${this.adapter.cacheNamePrefix}:`)) // `
.reduce(async (previous, key) => {
await Promise.all([
previous,

View File

@ -7,7 +7,7 @@
*/
import {ChangeDetectorRef, Component, destroyPlatform, EventEmitter, forwardRef, Input, NgModule, NgModuleFactory, NgZone, NO_ERRORS_SCHEMA, OnChanges, OnDestroy, Output, SimpleChange, SimpleChanges, Testability} from '@angular/core';
import {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
import {fakeAsync, flushMicrotasks, tick, waitForAsync} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
@ -30,7 +30,7 @@ withEachNg1Version(() => {
it('should have AngularJS loaded',
() => expect(angular.getAngularJSGlobal().version.major).toBe(1));
it('should instantiate ng2 in ng1 template and project content', async(() => {
it('should instantiate ng2 in ng1 template and project content', waitForAsync(() => {
const ng1Module = angular.module_('ng1', []);
@Component({
@ -55,7 +55,7 @@ withEachNg1Version(() => {
});
}));
it('should instantiate ng1 in ng2 template and project content', async(() => {
it('should instantiate ng1 in ng2 template and project content', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
@ -86,7 +86,7 @@ withEachNg1Version(() => {
});
}));
it('supports the compilerOptions argument', async(() => {
it('supports the compilerOptions argument', waitForAsync(() => {
const platformRef = platformBrowserDynamic();
spyOn(platformRef, 'bootstrapModule').and.callThrough();
spyOn(platformRef, 'bootstrapModuleFactory').and.callThrough();
@ -170,7 +170,7 @@ withEachNg1Version(() => {
});
describe('change-detection', () => {
it('should not break if a $digest is already in progress', async(() => {
it('should not break if a $digest is already in progress', waitForAsync(() => {
@Component({selector: 'my-app', template: ''})
class AppComponent {
}
@ -209,7 +209,7 @@ withEachNg1Version(() => {
});
}));
it('should interleave scope and component expressions', async(() => {
it('should interleave scope and component expressions', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
const log: string[] = [];
@ -256,7 +256,8 @@ withEachNg1Version(() => {
});
}));
it('should propagate changes to a downgraded component inside the ngZone', async(() => {
it('should propagate changes to a downgraded component inside the ngZone',
waitForAsync(() => {
let appComponent: AppComponent;
let upgradeRef: UpgradeAdapterRef;
@ -342,7 +343,7 @@ withEachNg1Version(() => {
});
describe('downgrade ng2 component', () => {
it('should allow non-element selectors for downgraded components', async(() => {
it('should allow non-element selectors for downgraded components', waitForAsync(() => {
@Component({selector: '[itWorks]', template: 'It works'})
class WorksComponent {
}
@ -361,7 +362,7 @@ withEachNg1Version(() => {
});
}));
it('should bind properties, events', async(() => {
it('should bind properties, events', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []).value($EXCEPTION_HANDLER, (err: any) => {
throw err;
@ -483,7 +484,7 @@ withEachNg1Version(() => {
});
}));
it('should support two-way binding and event listener', async(() => {
it('should support two-way binding and event listener', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const listenerSpy = jasmine.createSpy('$rootScope.listener');
const ng1Module = angular.module_('ng1', []).run(($rootScope: angular.IScope) => {
@ -532,7 +533,7 @@ withEachNg1Version(() => {
});
}));
it('should initialize inputs in time for `ngOnChanges`', async(() => {
it('should initialize inputs in time for `ngOnChanges`', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
@Component({
@ -591,7 +592,7 @@ withEachNg1Version(() => {
});
}));
it('should bind to ng-model', async(() => {
it('should bind to ng-model', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
@ -661,7 +662,7 @@ withEachNg1Version(() => {
});
}));
it('should properly run cleanup when ng1 directive is destroyed', async(() => {
it('should properly run cleanup when ng1 directive is destroyed', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
const onDestroyed: EventEmitter<string> = new EventEmitter<string>();
@ -700,7 +701,7 @@ withEachNg1Version(() => {
});
}));
it('should properly run cleanup with multiple levels of nesting', async(() => {
it('should properly run cleanup with multiple levels of nesting', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
let destroyed = false;
@ -745,7 +746,8 @@ withEachNg1Version(() => {
});
}));
it('should fallback to the root ng2.injector when compiled outside the dom', async(() => {
it('should fallback to the root ng2.injector when compiled outside the dom',
waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
@ -781,7 +783,7 @@ withEachNg1Version(() => {
});
}));
it('should support multi-slot projection', async(() => {
it('should support multi-slot projection', waitForAsync(() => {
const ng1Module = angular.module_('ng1', []);
@Component({
@ -810,7 +812,7 @@ withEachNg1Version(() => {
});
}));
it('should correctly project structural directives', async(() => {
it('should correctly project structural directives', waitForAsync(() => {
@Component({selector: 'ng2', template: 'ng2-{{ itemId }}(<ng-content></ng-content>)'})
class Ng2Component {
// TODO(issue/24571): remove '!'.
@ -844,7 +846,7 @@ withEachNg1Version(() => {
});
}));
it('should allow attribute selectors for components in ng2', async(() => {
it('should allow attribute selectors for components in ng2', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => MyNg2Module));
const ng1Module = angular.module_('myExample', []);
@ -1123,7 +1125,7 @@ withEachNg1Version(() => {
});
}));
it('should bind properties, events', async(() => {
it('should bind properties, events', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
@ -1181,7 +1183,7 @@ withEachNg1Version(() => {
});
}));
it('should bind optional properties', async(() => {
it('should bind optional properties', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
@ -1226,7 +1228,7 @@ withEachNg1Version(() => {
}));
it('should bind properties, events in controller when bindToController is not used',
async(() => {
waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
@ -1272,7 +1274,7 @@ withEachNg1Version(() => {
});
}));
it('should bind properties, events in link function', async(() => {
it('should bind properties, events in link function', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
@ -1318,7 +1320,7 @@ withEachNg1Version(() => {
});
}));
it('should support templateUrl fetched from $httpBackend', async(() => {
it('should support templateUrl fetched from $httpBackend', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
ng1Module.value(
@ -1349,7 +1351,7 @@ withEachNg1Version(() => {
});
}));
it('should support templateUrl as a function', async(() => {
it('should support templateUrl as a function', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
ng1Module.value(
@ -1384,7 +1386,7 @@ withEachNg1Version(() => {
});
}));
it('should support empty template', async(() => {
it('should support empty template', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
@ -1412,7 +1414,7 @@ withEachNg1Version(() => {
});
}));
it('should support template as a function', async(() => {
it('should support template as a function', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
@ -1444,7 +1446,7 @@ withEachNg1Version(() => {
});
}));
it('should support templateUrl fetched from $templateCache', async(() => {
it('should support templateUrl fetched from $templateCache', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
ng1Module.run(($templateCache: any) => $templateCache.put('url.html', 'WORKS'));
@ -1473,7 +1475,7 @@ withEachNg1Version(() => {
});
}));
it('should support controller with controllerAs', async(() => {
it('should support controller with controllerAs', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
@ -1524,7 +1526,7 @@ withEachNg1Version(() => {
});
}));
it('should support bindToController', async(() => {
it('should support bindToController', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
@ -1558,7 +1560,7 @@ withEachNg1Version(() => {
});
}));
it('should support bindToController with bindings', async(() => {
it('should support bindToController with bindings', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
@ -1592,7 +1594,7 @@ withEachNg1Version(() => {
});
}));
it('should support single require in linking fn', async(() => {
it('should support single require in linking fn', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
@ -1635,7 +1637,7 @@ withEachNg1Version(() => {
});
}));
it('should support array require in linking fn', async(() => {
it('should support array require in linking fn', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
@ -1688,7 +1690,7 @@ withEachNg1Version(() => {
}));
describe('with life-cycle hooks', () => {
it('should call `$onInit()` on controller', async(() => {
it('should call `$onInit()` on controller', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $onInitSpyA = jasmine.createSpy('$onInitA');
const $onInitSpyB = jasmine.createSpy('$onInitB');
@ -1739,7 +1741,7 @@ withEachNg1Version(() => {
});
}));
it('should not call `$onInit()` on scope', async(() => {
it('should not call `$onInit()` on scope', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $onInitSpy = jasmine.createSpy('$onInit');
@ -1785,7 +1787,7 @@ withEachNg1Version(() => {
});
}));
it('should call `$doCheck()` on controller', async(() => {
it('should call `$doCheck()` on controller', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $doCheckSpyA = jasmine.createSpy('$doCheckA');
const $doCheckSpyB = jasmine.createSpy('$doCheckB');
@ -1847,7 +1849,7 @@ withEachNg1Version(() => {
});
}));
it('should not call `$doCheck()` on scope', async(() => {
it('should not call `$doCheck()` on scope', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $doCheckSpyA = jasmine.createSpy('$doCheckA');
const $doCheckSpyB = jasmine.createSpy('$doCheckB');
@ -1904,7 +1906,7 @@ withEachNg1Version(() => {
});
}));
it('should call `$postLink()` on controller', async(() => {
it('should call `$postLink()` on controller', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $postLinkSpyA = jasmine.createSpy('$postLinkA');
const $postLinkSpyB = jasmine.createSpy('$postLinkB');
@ -1955,7 +1957,7 @@ withEachNg1Version(() => {
});
}));
it('should not call `$postLink()` on scope', async(() => {
it('should not call `$postLink()` on scope', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $postLinkSpy = jasmine.createSpy('$postLink');
@ -2520,7 +2522,7 @@ withEachNg1Version(() => {
});
describe('linking', () => {
it('should run the pre-linking after instantiating the controller', async(() => {
it('should run the pre-linking after instantiating the controller', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const log: string[] = [];
@ -2561,7 +2563,7 @@ withEachNg1Version(() => {
});
}));
it('should run the pre-linking function before linking', async(() => {
it('should run the pre-linking function before linking', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const log: string[] = [];
@ -2601,7 +2603,7 @@ withEachNg1Version(() => {
});
}));
it('should run the post-linking function after linking (link: object)', async(() => {
it('should run the post-linking function after linking (link: object)', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const log: string[] = [];
@ -2641,7 +2643,8 @@ withEachNg1Version(() => {
});
}));
it('should run the post-linking function after linking (link: function)', async(() => {
it('should run the post-linking function after linking (link: function)',
waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const log: string[] = [];
@ -2681,7 +2684,7 @@ withEachNg1Version(() => {
});
}));
it('should run the post-linking function before `$postLink`', async(() => {
it('should run the post-linking function before `$postLink`', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const log: string[] = [];
@ -2724,7 +2727,7 @@ withEachNg1Version(() => {
});
describe('transclusion', () => {
it('should support single-slot transclusion', async(() => {
it('should support single-slot transclusion', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
let ng2ComponentAInstance: Ng2ComponentA;
let ng2ComponentBInstance: Ng2ComponentB;
@ -2788,7 +2791,7 @@ withEachNg1Version(() => {
});
}));
it('should support single-slot transclusion with fallback content', async(() => {
it('should support single-slot transclusion with fallback content', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
let ng1ControllerInstances: any[] = [];
let ng2ComponentInstance: Ng2Component;
@ -2854,7 +2857,7 @@ withEachNg1Version(() => {
});
}));
it('should support multi-slot transclusion', async(() => {
it('should support multi-slot transclusion', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
let ng2ComponentInstance: Ng2Component;
@ -2916,7 +2919,7 @@ withEachNg1Version(() => {
});
}));
it('should support default slot (with fallback content)', async(() => {
it('should support default slot (with fallback content)', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
let ng1ControllerInstances: any[] = [];
let ng2ComponentInstance: Ng2Component;
@ -2999,7 +3002,8 @@ withEachNg1Version(() => {
});
}));
it('should support optional transclusion slots (with fallback content)', async(() => {
it('should support optional transclusion slots (with fallback content)',
waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
let ng1ControllerInstances: any[] = [];
let ng2ComponentInstance: Ng2Component;
@ -3072,7 +3076,7 @@ withEachNg1Version(() => {
});
}));
it('should throw if a non-optional slot is not filled', async(() => {
it('should throw if a non-optional slot is not filled', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
let errorMessage: string;
@ -3111,7 +3115,7 @@ withEachNg1Version(() => {
});
}));
it('should support structural directives in transcluded content', async(() => {
it('should support structural directives in transcluded content', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
let ng2ComponentInstance: Ng2Component;
@ -3179,7 +3183,7 @@ withEachNg1Version(() => {
}));
});
it('should bind input properties (<) of components', async(() => {
it('should bind input properties (<) of components', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
@ -3211,7 +3215,7 @@ withEachNg1Version(() => {
});
}));
it('should support ng2 > ng1 > ng2', async(() => {
it('should support ng2 > ng1 > ng2', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module_('ng1', []);
@ -3247,7 +3251,7 @@ withEachNg1Version(() => {
describe('injection', () => {
function SomeToken() {}
it('should export ng2 instance to ng1', async(() => {
it('should export ng2 instance to ng1', waitForAsync(() => {
@NgModule({
providers: [{provide: SomeToken, useValue: 'correct_value'}],
imports: [BrowserModule],
@ -3264,7 +3268,7 @@ withEachNg1Version(() => {
});
}));
it('should export ng1 instance to ng2', async(() => {
it('should export ng1 instance to ng2', waitForAsync(() => {
@NgModule({imports: [BrowserModule]})
class MyNg2Module {
}
@ -3283,7 +3287,7 @@ withEachNg1Version(() => {
});
}));
it('should respect hierarchical dependency injection for ng2', async(() => {
it('should respect hierarchical dependency injection for ng2', waitForAsync(() => {
const ng1Module = angular.module_('ng1', []);
@Component({selector: 'ng2-parent', template: `ng2-parent(<ng-content></ng-content>)`})
@ -3311,7 +3315,7 @@ withEachNg1Version(() => {
});
describe('testability', () => {
it('should handle deferred bootstrap', async(() => {
it('should handle deferred bootstrap', waitForAsync(() => {
@NgModule({imports: [BrowserModule]})
class MyNg2Module {
}
@ -3362,7 +3366,7 @@ withEachNg1Version(() => {
expect(value).toBe(a1Injector);
}));
it('should wait for ng2 testability', async(() => {
it('should wait for ng2 testability', waitForAsync(() => {
@NgModule({imports: [BrowserModule]})
class MyNg2Module {
}
@ -3389,7 +3393,7 @@ withEachNg1Version(() => {
});
describe('examples', () => {
it('should verify UpgradeAdapter example', async(() => {
it('should verify UpgradeAdapter example', waitForAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const module = angular.module_('myExample', []);
@ -3461,7 +3465,7 @@ withEachNg1Version(() => {
});
});
it('should be able to test ng1 components that use ng2 components', async(() => {
it('should be able to test ng1 components that use ng2 components', waitForAsync(() => {
upgradeAdapterRef.ready(() => {
const element = $compile('<ng2></ng2>')($rootScope);
$rootScope.$digest();

View File

@ -7,7 +7,7 @@
*/
import {Component, destroyPlatform, Directive, ElementRef, Injector, Input, NgModule, NgZone, SimpleChanges} from '@angular/core';
import {async} from '@angular/core/testing';
import {waitForAsync} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {downgradeComponent, UpgradeComponent, UpgradeModule} from '@angular/upgrade/static';
@ -22,7 +22,7 @@ withEachNg1Version(() => {
beforeEach(() => destroyPlatform());
afterEach(() => destroyPlatform());
it('should not break if a $digest is already in progress', async(() => {
it('should not break if a $digest is already in progress', waitForAsync(() => {
const element = html('<my-app></my-app>');
@Component({selector: 'my-app', template: ''})
@ -77,7 +77,7 @@ withEachNg1Version(() => {
});
}));
it('should interleave scope and component expressions', async(() => {
it('should interleave scope and component expressions', waitForAsync(() => {
const log: string[] = [];
const l = (value: string) => {
log.push(value);
@ -132,7 +132,7 @@ withEachNg1Version(() => {
});
}));
it('should propagate changes to a downgraded component inside the ngZone', async(() => {
it('should propagate changes to a downgraded component inside the ngZone', waitForAsync(() => {
const element = html('<my-app></my-app>');
let appComponent: AppComponent;

View File

@ -7,7 +7,7 @@
*/
import {Component, destroyPlatform, Directive, ElementRef, Injector, Input, NgModule} from '@angular/core';
import {async} from '@angular/core/testing';
import {waitForAsync} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {downgradeComponent, UpgradeComponent, UpgradeModule} from '@angular/upgrade/static';
@ -22,7 +22,7 @@ withEachNg1Version(() => {
beforeEach(() => destroyPlatform());
afterEach(() => destroyPlatform());
it('should instantiate ng2 in ng1 template and project content', async(() => {
it('should instantiate ng2 in ng1 template and project content', waitForAsync(() => {
// the ng2 component that will be used in ng1 (downgraded)
@Component({selector: 'ng2', template: `{{ prop }}(<ng-content></ng-content>)`})
class Ng2Component {
@ -57,7 +57,7 @@ withEachNg1Version(() => {
});
}));
it('should correctly project structural directives', async(() => {
it('should correctly project structural directives', waitForAsync(() => {
@Component({selector: 'ng2', template: 'ng2-{{ itemId }}(<ng-content></ng-content>)'})
class Ng2Component {
// TODO(issue/24571): remove '!'.
@ -94,7 +94,7 @@ withEachNg1Version(() => {
});
}));
it('should instantiate ng1 in ng2 template and project content', async(() => {
it('should instantiate ng1 in ng2 template and project content', waitForAsync(() => {
@Component({
selector: 'ng2',
template: `{{ 'ng2(' }}<ng1>{{ transclude }}</ng1>{{ ')' }}`,
@ -139,7 +139,7 @@ withEachNg1Version(() => {
});
}));
it('should support multi-slot projection', async(() => {
it('should support multi-slot projection', waitForAsync(() => {
@Component({
selector: 'ng2',
template: '2a(<ng-content select=".ng1a"></ng-content>)' +

View File

@ -7,7 +7,7 @@
*/
import {ChangeDetectionStrategy, Compiler, Component, destroyPlatform, Directive, ElementRef, EventEmitter, Injector, Input, NgModule, NgModuleRef, OnChanges, OnDestroy, Output, SimpleChanges} from '@angular/core';
import {async, fakeAsync, tick} from '@angular/core/testing';
import {fakeAsync, tick, waitForAsync} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {downgradeComponent, UpgradeComponent, UpgradeModule} from '@angular/upgrade/static';
@ -22,7 +22,7 @@ withEachNg1Version(() => {
beforeEach(() => destroyPlatform());
afterEach(() => destroyPlatform());
it('should bind properties, events', async(() => {
it('should bind properties, events', waitForAsync(() => {
const ng1Module = angular.module_('ng1', []).run(($rootScope: angular.IScope) => {
$rootScope['name'] = 'world';
$rootScope['dataA'] = 'A';
@ -146,7 +146,7 @@ withEachNg1Version(() => {
});
}));
it('should bind properties to onpush components', async(() => {
it('should bind properties to onpush components', waitForAsync(() => {
const ng1Module = angular.module_('ng1', []).run(($rootScope: angular.IScope) => {
$rootScope['dataB'] = 'B';
});
@ -188,7 +188,7 @@ withEachNg1Version(() => {
});
}));
it('should support two-way binding and event listener', async(() => {
it('should support two-way binding and event listener', waitForAsync(() => {
const listenerSpy = jasmine.createSpy('$rootScope.listener');
const ng1Module = angular.module_('ng1', []).run(($rootScope: angular.IScope) => {
$rootScope['value'] = 'world';
@ -240,7 +240,7 @@ withEachNg1Version(() => {
});
}));
it('should run change-detection on every digest (by default)', async(() => {
it('should run change-detection on every digest (by default)', waitForAsync(() => {
let ng2Component: Ng2Component;
@Component({selector: 'ng2', template: '{{ value1 }} | {{ value2 }}'})
@ -305,7 +305,7 @@ withEachNg1Version(() => {
});
}));
it('should not run change-detection on every digest when opted out', async(() => {
it('should not run change-detection on every digest when opted out', waitForAsync(() => {
let ng2Component: Ng2Component;
@Component({selector: 'ng2', template: '{{ value1 }} | {{ value2 }}'})
@ -408,7 +408,7 @@ withEachNg1Version(() => {
});
}));
it('should initialize inputs in time for `ngOnChanges`', async(() => {
it('should initialize inputs in time for `ngOnChanges`', waitForAsync(() => {
@Component({
selector: 'ng2',
template: `
@ -468,7 +468,7 @@ withEachNg1Version(() => {
});
}));
it('should bind to ng-model', async(() => {
it('should bind to ng-model', waitForAsync(() => {
const ng1Module = angular.module_('ng1', []).run(($rootScope: angular.IScope) => {
$rootScope['modelA'] = 'A';
});
@ -534,7 +534,7 @@ withEachNg1Version(() => {
});
}));
it('should properly run cleanup when ng1 directive is destroyed', async(() => {
it('should properly run cleanup when ng1 directive is destroyed', waitForAsync(() => {
let destroyed = false;
@Component({selector: 'ng2', template: 'test'})
class Ng2Component implements OnDestroy {
@ -574,7 +574,7 @@ withEachNg1Version(() => {
});
}));
it('should properly run cleanup with multiple levels of nesting', async(() => {
it('should properly run cleanup with multiple levels of nesting', waitForAsync(() => {
let destroyed = false;
@Component({
@ -628,7 +628,7 @@ withEachNg1Version(() => {
}));
it('should work when compiled outside the dom (by fallback to the root ng2.injector)',
async(() => {
waitForAsync(() => {
@Component({selector: 'ng2', template: 'test'})
class Ng2Component {
}
@ -674,7 +674,7 @@ withEachNg1Version(() => {
});
}));
it('should allow attribute selectors for downgraded components', async(() => {
it('should allow attribute selectors for downgraded components', waitForAsync(() => {
@Component({selector: '[itWorks]', template: 'It works'})
class WorksComponent {
}
@ -698,7 +698,7 @@ withEachNg1Version(() => {
});
}));
it('should allow attribute selectors for components in ng2', async(() => {
it('should allow attribute selectors for components in ng2', waitForAsync(() => {
@Component({selector: '[itWorks]', template: 'It works'})
class WorksComponent {
}
@ -726,7 +726,7 @@ withEachNg1Version(() => {
});
}));
it('should respect hierarchical dependency injection for ng2', async(() => {
it('should respect hierarchical dependency injection for ng2', waitForAsync(() => {
@Component({selector: 'parent', template: 'parent(<ng-content></ng-content>)'})
class ParentComponent {
}
@ -757,7 +757,7 @@ withEachNg1Version(() => {
});
}));
it('should be compiled synchronously, if possible', async(() => {
it('should be compiled synchronously, if possible', waitForAsync(() => {
@Component({selector: 'ng2A', template: '<ng-content></ng-content>'})
class Ng2ComponentA {
}
@ -786,7 +786,7 @@ withEachNg1Version(() => {
});
}));
it('should work with ng2 lazy loaded components', async(() => {
it('should work with ng2 lazy loaded components', waitForAsync(() => {
let componentInjector: Injector;
@Component({selector: 'ng2', template: ''})
@ -836,7 +836,7 @@ withEachNg1Version(() => {
});
}));
it('should throw if `downgradedModule` is specified', async(() => {
it('should throw if `downgradedModule` is specified', waitForAsync(() => {
@Component({selector: 'ng2', template: ''})
class Ng2Component {
}

View File

@ -7,7 +7,7 @@
*/
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ApplicationRef, Compiler, Component, destroyPlatform, Directive, DoCheck, ElementRef, getPlatform, Inject, Injectable, Injector, Input, NgModule, NgZone, OnChanges, OnDestroy, OnInit, StaticProvider, Type, ViewRef} from '@angular/core';
import {async, fakeAsync, tick} from '@angular/core/testing';
import {fakeAsync, tick, waitForAsync} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
@ -26,7 +26,7 @@ withEachNg1Version(() => {
beforeEach(() => destroyPlatform());
afterEach(() => destroyPlatform());
it('should support multiple downgraded modules', async(() => {
it('should support multiple downgraded modules', waitForAsync(() => {
@Component({selector: 'ng2A', template: 'a'})
class Ng2ComponentA {
}
@ -80,7 +80,7 @@ withEachNg1Version(() => {
setTimeout(() => expect(element.textContent).toBe('a | b'));
}));
it('should support nesting components from different downgraded modules', async(() => {
it('should support nesting components from different downgraded modules', waitForAsync(() => {
@Directive({selector: 'ng1A'})
class Ng1ComponentA extends UpgradeComponent {
constructor(elementRef: ElementRef, injector: Injector) {
@ -163,7 +163,7 @@ withEachNg1Version(() => {
}));
it('should support nesting components from different downgraded modules (via projection)',
async(() => {
waitForAsync(() => {
@Component({
selector: 'ng2A',
template: 'ng2A(<ng-content></ng-content>)',
@ -336,7 +336,8 @@ withEachNg1Version(() => {
expect(multiTrim(element.children[1].textContent)).toBe('Counter:1');
}));
it('should correctly traverse the injector tree of downgraded components', async(() => {
it('should correctly traverse the injector tree of downgraded components',
waitForAsync(() => {
@Component({
selector: 'ng2A',
template: 'ng2A(<ng-content></ng-content>)',
@ -423,7 +424,7 @@ withEachNg1Version(() => {
}));
it('should correctly traverse the injector tree of downgraded components (from different modules)',
async(() => {
waitForAsync(() => {
@Component({
selector: 'ng2A',
template: 'ng2A(<ng-content></ng-content>)',
@ -544,7 +545,7 @@ withEachNg1Version(() => {
});
}));
it('should support downgrading a component and propagate inputs', async(() => {
it('should support downgrading a component and propagate inputs', waitForAsync(() => {
@Component(
{selector: 'ng2A', template: 'a({{ value }}) | <ng2B [value]="value"></ng2B>'})
class Ng2AComponent {
@ -598,7 +599,7 @@ withEachNg1Version(() => {
});
}));
it('should support using an upgraded service', async(() => {
it('should support using an upgraded service', waitForAsync(() => {
@Injectable()
class Ng2Service {
constructor(@Inject('ng1Value') private ng1Value: string) {}
@ -658,7 +659,7 @@ withEachNg1Version(() => {
});
}));
it('should create components inside the Angular zone', async(() => {
it('should create components inside the Angular zone', waitForAsync(() => {
@Component({selector: 'ng2', template: 'In the zone: {{ inTheZone }}'})
class Ng2Component {
private inTheZone = false;
@ -694,7 +695,7 @@ withEachNg1Version(() => {
});
}));
it('should destroy components inside the Angular zone', async(() => {
it('should destroy components inside the Angular zone', waitForAsync(() => {
let destroyedInTheZone = false;
@Component({selector: 'ng2', template: ''})
@ -732,7 +733,7 @@ withEachNg1Version(() => {
});
}));
it('should propagate input changes inside the Angular zone', async(() => {
it('should propagate input changes inside the Angular zone', waitForAsync(() => {
let ng2Component: Ng2Component;
@Component({selector: 'ng2', template: ''})
@ -793,7 +794,7 @@ withEachNg1Version(() => {
}));
it('should create and destroy nested, asynchronously instantiated components inside the Angular zone',
async(() => {
waitForAsync(() => {
let createdInTheZone = false;
let destroyedInTheZone = false;
@ -858,7 +859,7 @@ withEachNg1Version(() => {
});
}));
it('should wire up the component for change detection', async(() => {
it('should wire up the component for change detection', waitForAsync(() => {
@Component(
{selector: 'ng2', template: '{{ count }}<button (click)="increment()"></button>'})
class Ng2Component {
@ -903,7 +904,7 @@ withEachNg1Version(() => {
}));
it('should wire up nested, asynchronously instantiated components for change detection',
async(() => {
waitForAsync(() => {
@Component(
{selector: 'test', template: '{{ count }}<button (click)="increment()"></button>'})
class TestComponent {
@ -961,7 +962,7 @@ withEachNg1Version(() => {
});
}));
it('should run the lifecycle hooks in the correct order', async(() => {
it('should run the lifecycle hooks in the correct order', waitForAsync(() => {
const logs: string[] = [];
let rootScope: angular.IRootScopeService;
@ -1138,7 +1139,7 @@ withEachNg1Version(() => {
});
}));
it('should detach hostViews from the ApplicationRef once destroyed', async(() => {
it('should detach hostViews from the ApplicationRef once destroyed', waitForAsync(() => {
let ng2Component: Ng2Component;
@Component({selector: 'ng2', template: ''})
@ -1253,7 +1254,8 @@ withEachNg1Version(() => {
$rootScope.$destroy();
}));
it('should give access to both injectors in the Angular module\'s constructor', async(() => {
it('should give access to both injectors in the Angular module\'s constructor',
waitForAsync(() => {
let $injectorFromNg2: angular.IInjectorService|null = null;
@Component({selector: 'ng2', template: ''})
@ -1341,7 +1343,7 @@ withEachNg1Version(() => {
// Explicitly clean up after each test to prevent that.
afterEach(() => setTempInjectorRef(null!));
it('should throw if no downgraded module is included', async(() => {
it('should throw if no downgraded module is included', waitForAsync(() => {
const ng1Module = angular.module_('ng1', [])
.value($EXCEPTION_HANDLER, errorSpy)
.directive('ng2A', downgradeComponent({
@ -1374,7 +1376,8 @@ withEachNg1Version(() => {
'<ng2-b>');
}));
it('should throw if the corresponding downgraded module is not included', async(() => {
it('should throw if the corresponding downgraded module is not included',
waitForAsync(() => {
const ng1Module = angular.module_('ng1', [downModA])
.value($EXCEPTION_HANDLER, errorSpy)
.directive('ng2A', downgradeComponent({
@ -1402,7 +1405,7 @@ withEachNg1Version(() => {
}));
it('should throw if `downgradedModule` is not specified and there are multiple downgraded modules',
async(() => {
waitForAsync(() => {
const ng1Module = angular.module_('ng1', [downModA, downModB])
.value($EXCEPTION_HANDLER, errorSpy)
.directive('ng2A', downgradeComponent({

View File

@ -7,7 +7,7 @@
*/
import {Component, destroyPlatform, Directive, ElementRef, Injector, Input, NgModule} from '@angular/core';
import {async} from '@angular/core/testing';
import {waitForAsync} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {downgradeComponent, UpgradeComponent, UpgradeModule} from '@angular/upgrade/static';
@ -25,7 +25,7 @@ withEachNg1Version(() => {
it('should have AngularJS loaded',
() => expect(angular.getAngularJSGlobal().version.major).toBe(1));
it('should verify UpgradeAdapter example', async(() => {
it('should verify UpgradeAdapter example', waitForAsync(() => {
// This is wrapping (upgrading) an AngularJS component to be used in an Angular
// component
@Directive({selector: 'ng1'})

View File

@ -7,7 +7,7 @@
*/
import {destroyPlatform, InjectionToken, Injector, NgModule} from '@angular/core';
import {async} from '@angular/core/testing';
import {waitForAsync} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
@ -23,7 +23,7 @@ withEachNg1Version(() => {
beforeEach(() => destroyPlatform());
afterEach(() => destroyPlatform());
it('should downgrade ng2 service to ng1', async(() => {
it('should downgrade ng2 service to ng1', waitForAsync(() => {
// Tokens used in ng2 to identify services
const Ng2Service = new InjectionToken('ng2-service');
@ -49,7 +49,7 @@ withEachNg1Version(() => {
});
}));
it('should upgrade ng1 service to ng2', async(() => {
it('should upgrade ng1 service to ng2', waitForAsync(() => {
// Tokens used in ng2 to identify services
const Ng1Service = new InjectionToken('ng1-service');
@ -81,7 +81,7 @@ withEachNg1Version(() => {
}));
it('should initialize the upgraded injector before application run blocks are executed',
async(() => {
waitForAsync(() => {
let runBlockTriggered = false;
const ng1Module = angular.module_('ng1Module', []).run([
@ -102,7 +102,7 @@ withEachNg1Version(() => {
});
}));
it('should allow resetting angular at runtime', async(() => {
it('should allow resetting angular at runtime', waitForAsync(() => {
let wrappedBootstrapCalled = false;
const n: any = getAngularJSGlobal();

View File

@ -7,7 +7,7 @@
*/
import {Component, destroyPlatform, Directive, ElementRef, EventEmitter, Inject, Injector, Input, NgModule, NO_ERRORS_SCHEMA, Output, SimpleChanges} from '@angular/core';
import {async, fakeAsync, tick} from '@angular/core/testing';
import {fakeAsync, tick, waitForAsync} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
@ -25,7 +25,7 @@ withEachNg1Version(() => {
afterEach(() => destroyPlatform());
describe('template/templateUrl', () => {
it('should support `template` (string)', async(() => {
it('should support `template` (string)', waitForAsync(() => {
// Define `ng1Component`
const ng1Component: angular.IComponent = {template: 'Hello, Angular!'};
@ -65,7 +65,7 @@ withEachNg1Version(() => {
});
}));
it('should support `template` (function)', async(() => {
it('should support `template` (function)', waitForAsync(() => {
// Define `ng1Component`
const ng1Component: angular.IComponent = {template: () => 'Hello, Angular!'};
@ -105,7 +105,7 @@ withEachNg1Version(() => {
});
}));
it('should pass $element to `template` function and not $attrs', async(() => {
it('should pass $element to `template` function and not $attrs', waitForAsync(() => {
// Define `ng1Component`
const ng1Component: angular.IComponent = {
template: ($attrs: angular.IAttributes, $element: angular.IAugmentedJQuery) => {
@ -152,7 +152,7 @@ withEachNg1Version(() => {
});
}));
it('should support `templateUrl` (string) fetched from `$templateCache`', async(() => {
it('should support `templateUrl` (string) fetched from `$templateCache`', waitForAsync(() => {
// Define `ng1Component`
const ng1Component: angular.IComponent = {templateUrl: 'ng1.component.html'};
@ -196,7 +196,8 @@ withEachNg1Version(() => {
});
}));
it('should support `templateUrl` (function) fetched from `$templateCache`', async(() => {
it('should support `templateUrl` (function) fetched from `$templateCache`',
waitForAsync(() => {
// Define `ng1Component`
const ng1Component: angular.IComponent = {templateUrl: () => 'ng1.component.html'};
@ -240,7 +241,7 @@ withEachNg1Version(() => {
});
}));
it('should pass $element to `templateUrl` function and not $attrs', async(() => {
it('should pass $element to `templateUrl` function and not $attrs', waitForAsync(() => {
// Define `ng1Component`
const ng1Component: angular.IComponent = {
templateUrl: ($attrs: angular.IAttributes, $element: angular.IAugmentedJQuery) => {
@ -393,7 +394,7 @@ withEachNg1Version(() => {
});
}));
it('should support empty templates', async(() => {
it('should support empty templates', waitForAsync(() => {
// Define `ng1Component`s
const ng1ComponentA: angular.IComponent = {template: ''};
const ng1ComponentB: angular.IComponent = {template: () => ''};
@ -1063,7 +1064,7 @@ withEachNg1Version(() => {
});
describe('compiling', () => {
it('should compile the ng1 template in the correct DOM context', async(() => {
it('should compile the ng1 template in the correct DOM context', waitForAsync(() => {
let grandParentNodeName: string;
// Define `ng1Component`
@ -1114,7 +1115,7 @@ withEachNg1Version(() => {
});
describe('linking', () => {
it('should run the pre-linking after instantiating the controller', async(() => {
it('should run the pre-linking after instantiating the controller', waitForAsync(() => {
const log: string[] = [];
// Define `ng1Directive`
@ -1164,7 +1165,7 @@ withEachNg1Version(() => {
});
}));
it('should run the pre-linking function before linking', async(() => {
it('should run the pre-linking function before linking', waitForAsync(() => {
const log: string[] = [];
// Define `ng1Directive`
@ -1212,7 +1213,7 @@ withEachNg1Version(() => {
});
}));
it('should run the post-linking function after linking (link: object)', async(() => {
it('should run the post-linking function after linking (link: object)', waitForAsync(() => {
const log: string[] = [];
// Define `ng1Directive`
@ -1260,7 +1261,7 @@ withEachNg1Version(() => {
});
}));
it('should run the post-linking function after linking (link: function)', async(() => {
it('should run the post-linking function after linking (link: function)', waitForAsync(() => {
const log: string[] = [];
// Define `ng1Directive`
@ -1308,7 +1309,7 @@ withEachNg1Version(() => {
});
}));
it('should run the post-linking function before `$postLink`', async(() => {
it('should run the post-linking function before `$postLink`', waitForAsync(() => {
const log: string[] = [];
// Define `ng1Directive`
@ -1360,7 +1361,7 @@ withEachNg1Version(() => {
});
describe('controller', () => {
it('should support `controllerAs`', async(() => {
it('should support `controllerAs`', waitForAsync(() => {
// Define `ng1Directive`
const ng1Directive: angular.IDirective = {
template:
@ -1425,7 +1426,7 @@ withEachNg1Version(() => {
});
}));
it('should support `bindToController` (boolean)', async(() => {
it('should support `bindToController` (boolean)', waitForAsync(() => {
// Define `ng1Directive`
const ng1DirectiveA: angular.IDirective = {
template: 'Scope: {{ title }}; Controller: {{ $ctrl.title }}',
@ -1501,7 +1502,7 @@ withEachNg1Version(() => {
});
}));
it('should support `bindToController` (object)', async(() => {
it('should support `bindToController` (object)', waitForAsync(() => {
// Define `ng1Directive`
const ng1Directive: angular.IDirective = {
template: '{{ $ctrl.title }}',
@ -1552,7 +1553,7 @@ withEachNg1Version(() => {
});
}));
it('should support `controller` as string', async(() => {
it('should support `controller` as string', waitForAsync(() => {
// Define `ng1Directive`
const ng1Directive: angular.IDirective = {
template: '{{ $ctrl.title }} {{ $ctrl.text }}',
@ -1605,7 +1606,8 @@ withEachNg1Version(() => {
});
}));
it('should insert the compiled content before instantiating the controller', async(() => {
it('should insert the compiled content before instantiating the controller',
waitForAsync(() => {
let compiledContent: string;
let getCurrentContent: () => string;
@ -1663,7 +1665,7 @@ withEachNg1Version(() => {
describe('require', () => {
// NOT YET SUPPORTED
xdescribe('in pre-/post-link', () => {
it('should resolve to its own controller if falsy', async(() => {
it('should resolve to its own controller if falsy', waitForAsync(() => {
// Define `ng1Directive`
const ng1Directive: angular.IDirective = {
template: 'Pre: {{ pre }} | Post: {{ post }}',
@ -1720,7 +1722,7 @@ withEachNg1Version(() => {
});
describe('in controller', () => {
it('should be available to children', async(() => {
it('should be available to children', waitForAsync(() => {
// Define `ng1Component`
const ng1ComponentA: angular.IComponent = {
template: '<ng1-b></ng1-b>',
@ -1771,7 +1773,7 @@ withEachNg1Version(() => {
});
}));
it('should throw if required controller cannot be found', async(() => {
it('should throw if required controller cannot be found', waitForAsync(() => {
// Define `ng1Component`
const ng1ComponentA: angular.IComponent = {require: {foo: 'iDoNotExist'}};
const ng1ComponentB: angular.IComponent = {require: {foo: '^iDoNotExist'}};
@ -1861,7 +1863,7 @@ withEachNg1Version(() => {
});
}));
it('should not throw if missing required controller is optional', async(() => {
it('should not throw if missing required controller is optional', waitForAsync(() => {
// Define `ng1Component`
const ng1Component: angular.IComponent = {
require: {
@ -1910,7 +1912,7 @@ withEachNg1Version(() => {
}));
it('should assign resolved values to the controller instance (if `require` is not object)',
async(() => {
waitForAsync(() => {
// Define `ng1Component`
const ng1ComponentA: angular.IComponent = {
template: 'ng1A(<div><ng2></ng2></div>)',
@ -1988,7 +1990,7 @@ withEachNg1Version(() => {
}));
it('should assign resolved values to the controller instance (if `require` is object)',
async(() => {
waitForAsync(() => {
// Define `ng1Component`
const ng1ComponentA: angular.IComponent = {
template: 'ng1A(<div><ng2></ng2></div>)',
@ -2058,7 +2060,7 @@ withEachNg1Version(() => {
});
}));
it('should assign to controller before calling `$onInit()`', async(() => {
it('should assign to controller before calling `$onInit()`', waitForAsync(() => {
// Define `ng1Component`
const ng1ComponentA: angular.IComponent = {
template: '<ng2></ng2>',
@ -2115,7 +2117,8 @@ withEachNg1Version(() => {
});
}));
it('should use the key as name if the required controller name is omitted', async(() => {
it('should use the key as name if the required controller name is omitted',
waitForAsync(() => {
// Define `ng1Component`
const ng1ComponentA: angular.IComponent = {
template: '<ng1-b></ng1-b>',
@ -2185,7 +2188,7 @@ withEachNg1Version(() => {
});
describe('transclusion', () => {
it('should support single-slot transclusion', async(() => {
it('should support single-slot transclusion', waitForAsync(() => {
let ng2ComponentAInstance: Ng2ComponentA;
let ng2ComponentBInstance: Ng2ComponentB;
@ -2256,7 +2259,7 @@ withEachNg1Version(() => {
});
}));
it('should support single-slot transclusion with fallback content', async(() => {
it('should support single-slot transclusion with fallback content', waitForAsync(() => {
let ng1ControllerInstances: any[] = [];
let ng2ComponentInstance: Ng2Component;
@ -2318,7 +2321,7 @@ withEachNg1Version(() => {
});
}));
it('should support multi-slot transclusion', async(() => {
it('should support multi-slot transclusion', waitForAsync(() => {
let ng2ComponentInstance: Ng2Component;
// Define `ng1Component`
@ -2387,7 +2390,7 @@ withEachNg1Version(() => {
});
}));
it('should support default slot (with fallback content)', async(() => {
it('should support default slot (with fallback content)', waitForAsync(() => {
let ng1ControllerInstances: any[] = [];
let ng2ComponentInstance: Ng2Component;
@ -2471,7 +2474,7 @@ withEachNg1Version(() => {
});
}));
it('should support optional transclusion slots (with fallback content)', async(() => {
it('should support optional transclusion slots (with fallback content)', waitForAsync(() => {
let ng1ControllerInstances: any[] = [];
let ng2ComponentInstance: Ng2Component;
@ -2553,7 +2556,7 @@ withEachNg1Version(() => {
});
}));
it('should throw if a non-optional slot is not filled', async(() => {
it('should throw if a non-optional slot is not filled', waitForAsync(() => {
let errorMessage: string;
// Define `ng1Component`
@ -2601,7 +2604,7 @@ withEachNg1Version(() => {
});
}));
it('should support structural directives in transcluded content', async(() => {
it('should support structural directives in transcluded content', waitForAsync(() => {
let ng2ComponentInstance: Ng2Component;
// Define `ng1Component`
@ -2984,7 +2987,7 @@ withEachNg1Version(() => {
});
}));
it('should call `$onInit()` on controller', async(() => {
it('should call `$onInit()` on controller', waitForAsync(() => {
// Define `ng1Directive`
const ng1DirectiveA: angular.IDirective = {
template: 'Called: {{ called }}',
@ -3055,7 +3058,7 @@ withEachNg1Version(() => {
});
}));
it('should not call `$onInit()` on scope', async(() => {
it('should not call `$onInit()` on scope', waitForAsync(() => {
// Define `ng1Directive`
const ng1DirectiveA: angular.IDirective = {
template: 'Called: {{ called }}',
@ -3125,7 +3128,7 @@ withEachNg1Version(() => {
});
}));
it('should call `$postLink()` on controller', async(() => {
it('should call `$postLink()` on controller', waitForAsync(() => {
// Define `ng1Directive`
const ng1DirectiveA: angular.IDirective = {
template: 'Called: {{ called }}',
@ -3196,7 +3199,7 @@ withEachNg1Version(() => {
});
}));
it('should not call `$postLink()` on scope', async(() => {
it('should not call `$postLink()` on scope', waitForAsync(() => {
// Define `ng1Directive`
const ng1DirectiveA: angular.IDirective = {
template: 'Called: {{ called }}',
@ -3267,7 +3270,7 @@ withEachNg1Version(() => {
}));
it('should call `$doCheck()` on controller', async(() => {
it('should call `$doCheck()` on controller', waitForAsync(() => {
const controllerDoCheckA = jasmine.createSpy('controllerDoCheckA');
const controllerDoCheckB = jasmine.createSpy('controllerDoCheckB');
@ -3354,7 +3357,7 @@ withEachNg1Version(() => {
});
}));
it('should not call `$doCheck()` on scope', async(() => {
it('should not call `$doCheck()` on scope', waitForAsync(() => {
const scopeDoCheck = jasmine.createSpy('scopeDoCheck');
// Define `ng1Directive`
@ -3432,7 +3435,7 @@ withEachNg1Version(() => {
}));
it('should call `$onDestroy()` on controller', async(() => {
it('should call `$onDestroy()` on controller', waitForAsync(() => {
const controllerOnDestroyA = jasmine.createSpy('controllerOnDestroyA');
const controllerOnDestroyB = jasmine.createSpy('controllerOnDestroyB');
@ -3532,7 +3535,7 @@ withEachNg1Version(() => {
});
}));
it('should not call `$onDestroy()` on scope', async(() => {
it('should not call `$onDestroy()` on scope', waitForAsync(() => {
const scopeOnDestroy = jasmine.createSpy('scopeOnDestroy');
// Define `ng1Directive`
@ -3628,7 +3631,7 @@ withEachNg1Version(() => {
}));
it('should be called in order `$onChanges()` > `$onInit()` > `$doCheck()` > `$postLink()`',
async(() => {
waitForAsync(() => {
// Define `ng1Component`
const ng1Component: angular.IComponent = {
// `$doCheck()` will keep getting called as long as the interpolated value keeps
@ -3697,7 +3700,7 @@ withEachNg1Version(() => {
});
describe('destroying the upgraded component', () => {
it('should destroy `$componentScope`', async(() => {
it('should destroy `$componentScope`', waitForAsync(() => {
const scopeDestroyListener = jasmine.createSpy('scopeDestroyListener');
let ng2ComponentAInstance: Ng2ComponentA;
@ -3760,7 +3763,7 @@ withEachNg1Version(() => {
});
}));
it('should emit `$destroy` on `$element` and descendants', async(() => {
it('should emit `$destroy` on `$element` and descendants', waitForAsync(() => {
const elementDestroyListener = jasmine.createSpy('elementDestroyListener');
const descendantDestroyListener = jasmine.createSpy('descendantDestroyListener');
let ng2ComponentAInstance: Ng2ComponentA;
@ -3828,7 +3831,7 @@ withEachNg1Version(() => {
});
}));
it('should clear data on `$element` and descendants`', async(() => {
it('should clear data on `$element` and descendants`', waitForAsync(() => {
let ng1ComponentElement: angular.IAugmentedJQuery;
let ng2ComponentAInstance: Ng2ComponentA;
@ -3897,7 +3900,7 @@ withEachNg1Version(() => {
});
}));
it('should clear dom listeners on `$element` and descendants`', async(() => {
it('should clear dom listeners on `$element` and descendants`', waitForAsync(() => {
const elementClickListener = jasmine.createSpy('elementClickListener');
const descendantClickListener = jasmine.createSpy('descendantClickListener');
let ng1DescendantElement: angular.IAugmentedJQuery;
@ -3970,7 +3973,7 @@ withEachNg1Version(() => {
});
}));
it('should clean up `$doCheck()` watchers from the parent scope', async(() => {
it('should clean up `$doCheck()` watchers from the parent scope', waitForAsync(() => {
let ng2Component: Ng2Component;
// Define `ng1Component`
@ -4041,7 +4044,7 @@ withEachNg1Version(() => {
}));
});
it('should support ng2 > ng1 > ng2 (no inputs/outputs)', async(() => {
it('should support ng2 > ng1 > ng2 (no inputs/outputs)', waitForAsync(() => {
// Define `ng1Component`
const ng1Component: angular.IComponent = {template: 'ng1X(<ng2-b></ng2-b>)'};
@ -4283,7 +4286,7 @@ withEachNg1Version(() => {
});
}));
it('should support ng2 > ng1 > ng2 > ng1 (with `require`)', async(() => {
it('should support ng2 > ng1 > ng2 > ng1 (with `require`)', waitForAsync(() => {
// Define `ng1Component`
const ng1ComponentA: angular.IComponent = {
template: 'ng1A(<ng2-b></ng2-b>)',