@ -18,12 +18,9 @@ export class BrowserAnimationBuilder extends AnimationBuilder {
|
||||
|
||||
constructor(rootRenderer: RendererFactory2, @Inject(DOCUMENT) doc: any) {
|
||||
super();
|
||||
const typeData = {
|
||||
id: '0',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
styles: [],
|
||||
data: {animation: []}
|
||||
} as RendererType2;
|
||||
const typeData =
|
||||
{id: '0', encapsulation: ViewEncapsulation.None, styles: [], data: {animation: []}} as
|
||||
RendererType2;
|
||||
this._renderer = rootRenderer.createRenderer(doc.body, typeData) as AnimationRenderer;
|
||||
}
|
||||
|
||||
@ -37,7 +34,9 @@ export class BrowserAnimationBuilder extends AnimationBuilder {
|
||||
}
|
||||
|
||||
export class BrowserAnimationFactory extends AnimationFactory {
|
||||
constructor(private _id: string, private _renderer: AnimationRenderer) { super(); }
|
||||
constructor(private _id: string, private _renderer: AnimationRenderer) {
|
||||
super();
|
||||
}
|
||||
|
||||
create(element: any, options?: AnimationOptions): AnimationPlayer {
|
||||
return new RendererAnimationPlayer(this._id, element, options || {}, this._renderer);
|
||||
@ -62,34 +61,58 @@ export class RendererAnimationPlayer implements AnimationPlayer {
|
||||
return issueAnimationCommand(this._renderer, this.element, this.id, command, args);
|
||||
}
|
||||
|
||||
onDone(fn: () => void): void { this._listen('done', fn); }
|
||||
onDone(fn: () => void): void {
|
||||
this._listen('done', fn);
|
||||
}
|
||||
|
||||
onStart(fn: () => void): void { this._listen('start', fn); }
|
||||
onStart(fn: () => void): void {
|
||||
this._listen('start', fn);
|
||||
}
|
||||
|
||||
onDestroy(fn: () => void): void { this._listen('destroy', fn); }
|
||||
onDestroy(fn: () => void): void {
|
||||
this._listen('destroy', fn);
|
||||
}
|
||||
|
||||
init(): void { this._command('init'); }
|
||||
init(): void {
|
||||
this._command('init');
|
||||
}
|
||||
|
||||
hasStarted(): boolean { return this._started; }
|
||||
hasStarted(): boolean {
|
||||
return this._started;
|
||||
}
|
||||
|
||||
play(): void {
|
||||
this._command('play');
|
||||
this._started = true;
|
||||
}
|
||||
|
||||
pause(): void { this._command('pause'); }
|
||||
pause(): void {
|
||||
this._command('pause');
|
||||
}
|
||||
|
||||
restart(): void { this._command('restart'); }
|
||||
restart(): void {
|
||||
this._command('restart');
|
||||
}
|
||||
|
||||
finish(): void { this._command('finish'); }
|
||||
finish(): void {
|
||||
this._command('finish');
|
||||
}
|
||||
|
||||
destroy(): void { this._command('destroy'); }
|
||||
destroy(): void {
|
||||
this._command('destroy');
|
||||
}
|
||||
|
||||
reset(): void { this._command('reset'); }
|
||||
reset(): void {
|
||||
this._command('reset');
|
||||
}
|
||||
|
||||
setPosition(p: number): void { this._command('setPosition', p); }
|
||||
setPosition(p: number): void {
|
||||
this._command('setPosition', p);
|
||||
}
|
||||
|
||||
getPosition(): number { return 0; }
|
||||
getPosition(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public totalTime = 0;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ const DISABLE_ANIMATIONS_FLAG = '@.disabled';
|
||||
// Define a recursive type to allow for nested arrays of `AnimationTriggerMetadata`. Note that an
|
||||
// interface declaration is used as TypeScript prior to 3.7 does not support recursive type
|
||||
// references, see https://github.com/microsoft/TypeScript/pull/33050 for details.
|
||||
type NestedAnimationTriggerMetadata = AnimationTriggerMetadata | RecursiveAnimationTriggerMetadata;
|
||||
type NestedAnimationTriggerMetadata = AnimationTriggerMetadata|RecursiveAnimationTriggerMetadata;
|
||||
interface RecursiveAnimationTriggerMetadata extends Array<NestedAnimationTriggerMetadata> {}
|
||||
|
||||
@Injectable()
|
||||
@ -84,7 +84,9 @@ export class AnimationRendererFactory implements RendererFactory2 {
|
||||
|
||||
private _scheduleCountTask() {
|
||||
// always use promise to schedule microtask instead of use Zone
|
||||
this.promise.then(() => { this._microtaskId++; });
|
||||
this.promise.then(() => {
|
||||
this._microtaskId++;
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@ -125,16 +127,20 @@ export class AnimationRendererFactory implements RendererFactory2 {
|
||||
}
|
||||
}
|
||||
|
||||
whenRenderingDone(): Promise<any> { return this.engine.whenRenderingDone(); }
|
||||
whenRenderingDone(): Promise<any> {
|
||||
return this.engine.whenRenderingDone();
|
||||
}
|
||||
}
|
||||
|
||||
export class BaseAnimationRenderer implements Renderer2 {
|
||||
constructor(
|
||||
protected namespaceId: string, public delegate: Renderer2, public engine: AnimationEngine) {
|
||||
this.destroyNode = this.delegate.destroyNode ? (n) => delegate.destroyNode !(n) : null;
|
||||
this.destroyNode = this.delegate.destroyNode ? (n) => delegate.destroyNode!(n) : null;
|
||||
}
|
||||
|
||||
get data() { return this.delegate.data; }
|
||||
get data() {
|
||||
return this.delegate.data;
|
||||
}
|
||||
|
||||
destroyNode: ((n: any) => void)|null;
|
||||
|
||||
@ -147,9 +153,13 @@ export class BaseAnimationRenderer implements Renderer2 {
|
||||
return this.delegate.createElement(name, namespace);
|
||||
}
|
||||
|
||||
createComment(value: string) { return this.delegate.createComment(value); }
|
||||
createComment(value: string) {
|
||||
return this.delegate.createComment(value);
|
||||
}
|
||||
|
||||
createText(value: string) { return this.delegate.createText(value); }
|
||||
createText(value: string) {
|
||||
return this.delegate.createText(value);
|
||||
}
|
||||
|
||||
appendChild(parent: any, newChild: any): void {
|
||||
this.delegate.appendChild(parent, newChild);
|
||||
@ -169,9 +179,13 @@ export class BaseAnimationRenderer implements Renderer2 {
|
||||
return this.delegate.selectRootElement(selectorOrNode, preserveContent);
|
||||
}
|
||||
|
||||
parentNode(node: any) { return this.delegate.parentNode(node); }
|
||||
parentNode(node: any) {
|
||||
return this.delegate.parentNode(node);
|
||||
}
|
||||
|
||||
nextSibling(node: any) { return this.delegate.nextSibling(node); }
|
||||
nextSibling(node: any) {
|
||||
return this.delegate.nextSibling(node);
|
||||
}
|
||||
|
||||
setAttribute(el: any, name: string, value: string, namespace?: string|null|undefined): void {
|
||||
this.delegate.setAttribute(el, name, value, namespace);
|
||||
@ -181,9 +195,13 @@ export class BaseAnimationRenderer implements Renderer2 {
|
||||
this.delegate.removeAttribute(el, name, namespace);
|
||||
}
|
||||
|
||||
addClass(el: any, name: string): void { this.delegate.addClass(el, name); }
|
||||
addClass(el: any, name: string): void {
|
||||
this.delegate.addClass(el, name);
|
||||
}
|
||||
|
||||
removeClass(el: any, name: string): void { this.delegate.removeClass(el, name); }
|
||||
removeClass(el: any, name: string): void {
|
||||
this.delegate.removeClass(el, name);
|
||||
}
|
||||
|
||||
setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2|undefined): void {
|
||||
this.delegate.setStyle(el, style, value, flags);
|
||||
@ -201,7 +219,9 @@ export class BaseAnimationRenderer implements Renderer2 {
|
||||
}
|
||||
}
|
||||
|
||||
setValue(node: any, value: string): void { this.delegate.setValue(node, value); }
|
||||
setValue(node: any, value: string): void {
|
||||
this.delegate.setValue(node, value);
|
||||
}
|
||||
|
||||
listen(target: any, eventName: string, callback: (event: any) => boolean | void): () => void {
|
||||
return this.delegate.listen(target, eventName, callback);
|
||||
@ -253,7 +273,7 @@ export class AnimationRenderer extends BaseAnimationRenderer implements Renderer
|
||||
}
|
||||
}
|
||||
|
||||
function resolveElementFromTarget(target: 'window' | 'document' | 'body' | any): any {
|
||||
function resolveElementFromTarget(target: 'window'|'document'|'body'|any): any {
|
||||
switch (target) {
|
||||
case 'body':
|
||||
return document.body;
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {AnimationBuilder} from '@angular/animations';
|
||||
import {AnimationDriver, ɵAnimationEngine as AnimationEngine, ɵAnimationStyleNormalizer as AnimationStyleNormalizer, ɵCssKeyframesDriver as CssKeyframesDriver, ɵNoopAnimationDriver as NoopAnimationDriver, ɵWebAnimationsDriver as WebAnimationsDriver, ɵWebAnimationsStyleNormalizer as WebAnimationsStyleNormalizer, ɵsupportsWebAnimations as supportsWebAnimations} from '@angular/animations/browser';
|
||||
import {AnimationDriver, ɵAnimationEngine as AnimationEngine, ɵAnimationStyleNormalizer as AnimationStyleNormalizer, ɵCssKeyframesDriver as CssKeyframesDriver, ɵNoopAnimationDriver as NoopAnimationDriver, ɵsupportsWebAnimations as supportsWebAnimations, ɵWebAnimationsDriver as WebAnimationsDriver, ɵWebAnimationsStyleNormalizer as WebAnimationsStyleNormalizer} from '@angular/animations/browser';
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {Inject, Injectable, InjectionToken, NgZone, Provider, RendererFactory2} from '@angular/core';
|
||||
import {ɵDomRendererFactory2 as DomRendererFactory2} from '@angular/platform-browser';
|
||||
|
@ -5,7 +5,7 @@
|
||||
* 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 {AnimationPlayer, AnimationTriggerMetadata, animate, state, style, transition, trigger} from '@angular/animations';
|
||||
import {animate, AnimationPlayer, AnimationTriggerMetadata, state, style, transition, trigger} from '@angular/animations';
|
||||
import {ɵAnimationEngine as AnimationEngine} from '@angular/animations/browser';
|
||||
import {Component, Injectable, NgZone, RendererFactory2, RendererType2, ViewChild} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
@ -15,289 +15,139 @@ import {DomRendererFactory2} from '@angular/platform-browser/src/dom/dom_rendere
|
||||
import {el} from '../../testing/src/browser_util';
|
||||
|
||||
(function() {
|
||||
if (isNode) return;
|
||||
describe('AnimationRenderer', () => {
|
||||
let element: any;
|
||||
beforeEach(() => {
|
||||
element = el('<div></div>');
|
||||
if (isNode) return;
|
||||
describe('AnimationRenderer', () => {
|
||||
let element: any;
|
||||
beforeEach(() => {
|
||||
element = el('<div></div>');
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: MockAnimationEngine}],
|
||||
imports: [BrowserAnimationsModule]
|
||||
});
|
||||
});
|
||||
|
||||
function makeRenderer(animationTriggers: any[] = []) {
|
||||
const type = <RendererType2>{
|
||||
id: 'id',
|
||||
encapsulation: null !,
|
||||
styles: [],
|
||||
data: {'animation': animationTriggers}
|
||||
};
|
||||
return (TestBed.inject(RendererFactory2) as AnimationRendererFactory)
|
||||
.createRenderer(element, type);
|
||||
}
|
||||
|
||||
it('should hook into the engine\'s insert operations when appending children', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
const container = el('<div></div>');
|
||||
|
||||
renderer.appendChild(container, element);
|
||||
expect(engine.captures['onInsert'].pop()).toEqual([element]);
|
||||
});
|
||||
|
||||
it('should hook into the engine\'s insert operations when inserting a child before another',
|
||||
() => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
const container = el('<div></div>');
|
||||
const element2 = el('<div></div>');
|
||||
container.appendChild(element2);
|
||||
|
||||
renderer.insertBefore(container, element, element2);
|
||||
expect(engine.captures['onInsert'].pop()).toEqual([element]);
|
||||
});
|
||||
|
||||
it('should hook into the engine\'s insert operations when removing children', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
const container = el('<div></div>');
|
||||
|
||||
renderer.removeChild(container, element);
|
||||
expect(engine.captures['onRemove'].pop()).toEqual([element]);
|
||||
});
|
||||
|
||||
it('should hook into the engine\'s setProperty call if the property begins with `@`', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
|
||||
renderer.setProperty(element, 'prop', 'value');
|
||||
expect(engine.captures['setProperty']).toBeFalsy();
|
||||
|
||||
renderer.setProperty(element, '@prop', 'value');
|
||||
expect(engine.captures['setProperty'].pop()).toEqual([element, 'prop', 'value']);
|
||||
});
|
||||
|
||||
// https://github.com/angular/angular/issues/32794
|
||||
it('should support nested animation triggers', () => {
|
||||
makeRenderer([[trigger('myAnimation', [])]]);
|
||||
|
||||
const {triggers} = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
|
||||
expect(triggers.length).toEqual(1);
|
||||
expect(triggers[0].name).toEqual('myAnimation');
|
||||
});
|
||||
|
||||
describe('listen', () => {
|
||||
it('should hook into the engine\'s listen call if the property begins with `@`', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
|
||||
const cb = (event: any): boolean => { return true; };
|
||||
|
||||
renderer.listen(element, 'event', cb);
|
||||
expect(engine.captures['listen']).toBeFalsy();
|
||||
|
||||
renderer.listen(element, '@event.phase', cb);
|
||||
expect(engine.captures['listen'].pop()).toEqual([element, 'event', 'phase']);
|
||||
});
|
||||
|
||||
it('should resolve the body|document|window nodes given their values as strings as input',
|
||||
() => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
|
||||
const cb = (event: any): boolean => { return true; };
|
||||
|
||||
renderer.listen('body', '@event', cb);
|
||||
expect(engine.captures['listen'].pop()[0]).toBe(document.body);
|
||||
|
||||
renderer.listen('document', '@event', cb);
|
||||
expect(engine.captures['listen'].pop()[0]).toBe(document);
|
||||
|
||||
renderer.listen('window', '@event', cb);
|
||||
expect(engine.captures['listen'].pop()[0]).toBe(window);
|
||||
});
|
||||
});
|
||||
|
||||
describe('registering animations', () => {
|
||||
it('should only create a trigger definition once even if the registered multiple times');
|
||||
});
|
||||
|
||||
describe('flushing animations', () => {
|
||||
// these tests are only mean't to be run within the DOM
|
||||
if (isNode) return;
|
||||
|
||||
it('should flush and fire callbacks when the zone becomes stable', (async) => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: '<div [@myAnimation]="exp" (@myAnimation.start)="onStart($event)"></div>',
|
||||
animations: [trigger(
|
||||
'myAnimation',
|
||||
[transition(
|
||||
'* => state',
|
||||
[style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])],
|
||||
})
|
||||
class Cmp {
|
||||
exp: any;
|
||||
event: any;
|
||||
onStart(event: any) { this.event = event; }
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const engine = TestBed.inject(AnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.exp = 'state';
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(cmp.event.triggerName).toEqual('myAnimation');
|
||||
expect(cmp.event.phaseName).toEqual('start');
|
||||
cmp.event = null;
|
||||
|
||||
engine.flush();
|
||||
expect(cmp.event).toBeFalsy();
|
||||
async();
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly insert/remove nodes through the animation renderer that do not contain animations',
|
||||
(async) => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: '<div #elm *ngIf="exp"></div>',
|
||||
animations: [trigger(
|
||||
'someAnimation',
|
||||
[transition(
|
||||
'* => *', [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])],
|
||||
})
|
||||
class Cmp {
|
||||
exp: any;
|
||||
@ViewChild('elm') public element: any;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
cmp.exp = false;
|
||||
const element = cmp.element;
|
||||
expect(element.nativeElement.parentNode).toBeTruthy();
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(element.nativeElement.parentNode).toBeFalsy();
|
||||
async();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only queue up dom removals if the element itself contains a valid leave animation',
|
||||
() => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: `
|
||||
<div #elm1 *ngIf="exp1"></div>
|
||||
<div #elm2 @animation1 *ngIf="exp2"></div>
|
||||
<div #elm3 @animation2 *ngIf="exp3"></div>
|
||||
`,
|
||||
animations: [
|
||||
trigger('animation1', [transition('a => b', [])]),
|
||||
trigger('animation2', [transition(':leave', [])]),
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
exp1: any = true;
|
||||
exp2: any = true;
|
||||
exp3: any = true;
|
||||
|
||||
@ViewChild('elm1') public elm1: any;
|
||||
|
||||
@ViewChild('elm2') public elm2: any;
|
||||
|
||||
@ViewChild('elm3') public elm3: any;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const engine = TestBed.inject(AnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
fixture.detectChanges();
|
||||
const elm1 = cmp.elm1;
|
||||
const elm2 = cmp.elm2;
|
||||
const elm3 = cmp.elm3;
|
||||
assertHasParent(elm1);
|
||||
assertHasParent(elm2);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
finishPlayers(engine.players);
|
||||
|
||||
cmp.exp1 = false;
|
||||
fixture.detectChanges();
|
||||
assertHasParent(elm1, false);
|
||||
assertHasParent(elm2);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
expect(engine.players.length).toEqual(0);
|
||||
|
||||
cmp.exp2 = false;
|
||||
fixture.detectChanges();
|
||||
assertHasParent(elm1, false);
|
||||
assertHasParent(elm2, false);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
expect(engine.players.length).toEqual(0);
|
||||
|
||||
cmp.exp3 = false;
|
||||
fixture.detectChanges();
|
||||
assertHasParent(elm1, false);
|
||||
assertHasParent(elm2, false);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
expect(engine.players.length).toEqual(1);
|
||||
});
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: MockAnimationEngine}],
|
||||
imports: [BrowserAnimationsModule]
|
||||
});
|
||||
});
|
||||
|
||||
describe('AnimationRendererFactory', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{
|
||||
provide: RendererFactory2,
|
||||
useClass: ExtendedAnimationRendererFactory,
|
||||
deps: [DomRendererFactory2, AnimationEngine, NgZone]
|
||||
}],
|
||||
imports: [BrowserAnimationsModule]
|
||||
});
|
||||
function makeRenderer(animationTriggers: any[] = []) {
|
||||
const type = <RendererType2>{
|
||||
id: 'id',
|
||||
encapsulation: null!,
|
||||
styles: [],
|
||||
data: {'animation': animationTriggers}
|
||||
};
|
||||
return (TestBed.inject(RendererFactory2) as AnimationRendererFactory)
|
||||
.createRenderer(element, type);
|
||||
}
|
||||
|
||||
it('should hook into the engine\'s insert operations when appending children', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
const container = el('<div></div>');
|
||||
|
||||
renderer.appendChild(container, element);
|
||||
expect(engine.captures['onInsert'].pop()).toEqual([element]);
|
||||
});
|
||||
|
||||
it('should hook into the engine\'s insert operations when inserting a child before another',
|
||||
() => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
const container = el('<div></div>');
|
||||
const element2 = el('<div></div>');
|
||||
container.appendChild(element2);
|
||||
|
||||
renderer.insertBefore(container, element, element2);
|
||||
expect(engine.captures['onInsert'].pop()).toEqual([element]);
|
||||
});
|
||||
|
||||
it('should hook into the engine\'s insert operations when removing children', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
const container = el('<div></div>');
|
||||
|
||||
renderer.removeChild(container, element);
|
||||
expect(engine.captures['onRemove'].pop()).toEqual([element]);
|
||||
});
|
||||
|
||||
it('should hook into the engine\'s setProperty call if the property begins with `@`', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
|
||||
renderer.setProperty(element, 'prop', 'value');
|
||||
expect(engine.captures['setProperty']).toBeFalsy();
|
||||
|
||||
renderer.setProperty(element, '@prop', 'value');
|
||||
expect(engine.captures['setProperty'].pop()).toEqual([element, 'prop', 'value']);
|
||||
});
|
||||
|
||||
// https://github.com/angular/angular/issues/32794
|
||||
it('should support nested animation triggers', () => {
|
||||
makeRenderer([[trigger('myAnimation', [])]]);
|
||||
|
||||
const {triggers} = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
|
||||
expect(triggers.length).toEqual(1);
|
||||
expect(triggers[0].name).toEqual('myAnimation');
|
||||
});
|
||||
|
||||
describe('listen', () => {
|
||||
it('should hook into the engine\'s listen call if the property begins with `@`', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
|
||||
const cb = (event: any): boolean => {
|
||||
return true;
|
||||
};
|
||||
|
||||
renderer.listen(element, 'event', cb);
|
||||
expect(engine.captures['listen']).toBeFalsy();
|
||||
|
||||
renderer.listen(element, '@event.phase', cb);
|
||||
expect(engine.captures['listen'].pop()).toEqual([element, 'event', 'phase']);
|
||||
});
|
||||
|
||||
it('should provide hooks at the start and end of change detection', () => {
|
||||
it('should resolve the body|document|window nodes given their values as strings as input',
|
||||
() => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
|
||||
|
||||
const cb = (event: any): boolean => {
|
||||
return true;
|
||||
};
|
||||
|
||||
renderer.listen('body', '@event', cb);
|
||||
expect(engine.captures['listen'].pop()[0]).toBe(document.body);
|
||||
|
||||
renderer.listen('document', '@event', cb);
|
||||
expect(engine.captures['listen'].pop()[0]).toBe(document);
|
||||
|
||||
renderer.listen('window', '@event', cb);
|
||||
expect(engine.captures['listen'].pop()[0]).toBe(window);
|
||||
});
|
||||
});
|
||||
|
||||
describe('registering animations', () => {
|
||||
it('should only create a trigger definition once even if the registered multiple times');
|
||||
});
|
||||
|
||||
describe('flushing animations', () => {
|
||||
// these tests are only mean't to be run within the DOM
|
||||
if (isNode) return;
|
||||
|
||||
it('should flush and fire callbacks when the zone becomes stable', (async) => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: `
|
||||
<div [@myAnimation]="exp"></div>
|
||||
`,
|
||||
animations: [trigger('myAnimation', [])]
|
||||
template: '<div [@myAnimation]="exp" (@myAnimation.start)="onStart($event)"></div>',
|
||||
animations: [trigger(
|
||||
'myAnimation',
|
||||
[transition(
|
||||
'* => state', [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])],
|
||||
})
|
||||
class Cmp {
|
||||
public exp: any;
|
||||
exp: any;
|
||||
event: any;
|
||||
onStart(event: any) {
|
||||
this.event = event;
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
@ -305,19 +155,174 @@ import {el} from '../../testing/src/browser_util';
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const renderer = TestBed.inject(RendererFactory2) as ExtendedAnimationRendererFactory;
|
||||
const engine = TestBed.inject(AnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
renderer.log = [];
|
||||
cmp.exp = 'state';
|
||||
fixture.detectChanges();
|
||||
expect(renderer.log).toEqual(['begin', 'end']);
|
||||
fixture.whenStable().then(() => {
|
||||
expect(cmp.event.triggerName).toEqual('myAnimation');
|
||||
expect(cmp.event.phaseName).toEqual('start');
|
||||
cmp.event = null;
|
||||
|
||||
renderer.log = [];
|
||||
fixture.detectChanges();
|
||||
expect(renderer.log).toEqual(['begin', 'end']);
|
||||
engine.flush();
|
||||
expect(cmp.event).toBeFalsy();
|
||||
async();
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly insert/remove nodes through the animation renderer that do not contain animations',
|
||||
(async) => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: '<div #elm *ngIf="exp"></div>',
|
||||
animations: [trigger(
|
||||
'someAnimation',
|
||||
[transition(
|
||||
'* => *', [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])],
|
||||
})
|
||||
class Cmp {
|
||||
exp: any;
|
||||
@ViewChild('elm') public element: any;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
cmp.exp = false;
|
||||
const element = cmp.element;
|
||||
expect(element.nativeElement.parentNode).toBeTruthy();
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(element.nativeElement.parentNode).toBeFalsy();
|
||||
async();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only queue up dom removals if the element itself contains a valid leave animation',
|
||||
() => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: `
|
||||
<div #elm1 *ngIf="exp1"></div>
|
||||
<div #elm2 @animation1 *ngIf="exp2"></div>
|
||||
<div #elm3 @animation2 *ngIf="exp3"></div>
|
||||
`,
|
||||
animations: [
|
||||
trigger('animation1', [transition('a => b', [])]),
|
||||
trigger('animation2', [transition(':leave', [])]),
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
exp1: any = true;
|
||||
exp2: any = true;
|
||||
exp3: any = true;
|
||||
|
||||
@ViewChild('elm1') public elm1: any;
|
||||
|
||||
@ViewChild('elm2') public elm2: any;
|
||||
|
||||
@ViewChild('elm3') public elm3: any;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const engine = TestBed.inject(AnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
fixture.detectChanges();
|
||||
const elm1 = cmp.elm1;
|
||||
const elm2 = cmp.elm2;
|
||||
const elm3 = cmp.elm3;
|
||||
assertHasParent(elm1);
|
||||
assertHasParent(elm2);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
finishPlayers(engine.players);
|
||||
|
||||
cmp.exp1 = false;
|
||||
fixture.detectChanges();
|
||||
assertHasParent(elm1, false);
|
||||
assertHasParent(elm2);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
expect(engine.players.length).toEqual(0);
|
||||
|
||||
cmp.exp2 = false;
|
||||
fixture.detectChanges();
|
||||
assertHasParent(elm1, false);
|
||||
assertHasParent(elm2, false);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
expect(engine.players.length).toEqual(0);
|
||||
|
||||
cmp.exp3 = false;
|
||||
fixture.detectChanges();
|
||||
assertHasParent(elm1, false);
|
||||
assertHasParent(elm2, false);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
expect(engine.players.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('AnimationRendererFactory', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{
|
||||
provide: RendererFactory2,
|
||||
useClass: ExtendedAnimationRendererFactory,
|
||||
deps: [DomRendererFactory2, AnimationEngine, NgZone]
|
||||
}],
|
||||
imports: [BrowserAnimationsModule]
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide hooks at the start and end of change detection', () => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: `
|
||||
<div [@myAnimation]="exp"></div>
|
||||
`,
|
||||
animations: [trigger('myAnimation', [])]
|
||||
})
|
||||
class Cmp {
|
||||
public exp: any;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: AnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const renderer = TestBed.inject(RendererFactory2) as ExtendedAnimationRendererFactory;
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
renderer.log = [];
|
||||
fixture.detectChanges();
|
||||
expect(renderer.log).toEqual(['begin', 'end']);
|
||||
|
||||
renderer.log = [];
|
||||
fixture.detectChanges();
|
||||
expect(renderer.log).toEqual(['begin', 'end']);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
@Injectable()
|
||||
@ -336,7 +341,9 @@ class MockAnimationEngine extends InjectableAnimationEngine {
|
||||
this.triggers.push(metadata);
|
||||
}
|
||||
|
||||
onInsert(namespaceId: string, element: any): void { this._capture('onInsert', [element]); }
|
||||
onInsert(namespaceId: string, element: any): void {
|
||||
this._capture('onInsert', [element]);
|
||||
}
|
||||
|
||||
onRemove(namespaceId: string, element: any, domFn: () => any): void {
|
||||
this._capture('onRemove', [element]);
|
||||
|
@ -5,11 +5,11 @@
|
||||
* 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 {AnimationBuilder, animate, style} from '@angular/animations';
|
||||
import {animate, AnimationBuilder, style} from '@angular/animations';
|
||||
import {AnimationDriver} from '@angular/animations/browser';
|
||||
import {MockAnimationDriver} from '@angular/animations/browser/testing';
|
||||
import {Component, ViewChild} from '@angular/core';
|
||||
import {TestBed, fakeAsync, flushMicrotasks} from '@angular/core/testing';
|
||||
import {fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing';
|
||||
import {NoopAnimationsModule, ɵBrowserAnimationBuilder as BrowserAnimationBuilder} from '@angular/platform-browser/animations';
|
||||
|
||||
import {el} from '../../testing/src/browser_util';
|
||||
|
@ -13,7 +13,9 @@ import {NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||||
|
||||
{
|
||||
describe('NoopAnimationsModule', () => {
|
||||
beforeEach(() => { TestBed.configureTestingModule({imports: [NoopAnimationsModule]}); });
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({imports: [NoopAnimationsModule]});
|
||||
});
|
||||
|
||||
it('should flush and fire callbacks when the zone becomes stable', (async) => {
|
||||
@Component({
|
||||
@ -29,8 +31,12 @@ import {NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||||
exp: any;
|
||||
startEvent: any;
|
||||
doneEvent: any;
|
||||
onStart(event: any) { this.startEvent = event; }
|
||||
onDone(event: any) { this.doneEvent = event; }
|
||||
onStart(event: any) {
|
||||
this.startEvent = event;
|
||||
}
|
||||
onDone(event: any) {
|
||||
this.doneEvent = event;
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
@ -63,8 +69,12 @@ import {NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||||
exp: any;
|
||||
startEvent: any;
|
||||
doneEvent: any;
|
||||
onStart(event: any) { this.startEvent = event; }
|
||||
onDone(event: any) { this.doneEvent = event; }
|
||||
onStart(event: any) {
|
||||
this.startEvent = event;
|
||||
}
|
||||
onDone(event: any) {
|
||||
this.doneEvent = event;
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
|
@ -7,7 +7,8 @@
|
||||
*/
|
||||
|
||||
import {CommonModule, DOCUMENT, ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID} from '@angular/common';
|
||||
import {APP_ID, ApplicationModule, ErrorHandler, Inject, ModuleWithProviders, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, RendererFactory2, Sanitizer, SkipSelf, StaticProvider, Testability, createPlatformFactory, platformCore, ɵConsole as Console, ɵINJECTOR_SCOPE as INJECTOR_SCOPE, ɵsetDocument} from '@angular/core';
|
||||
import {APP_ID, ApplicationModule, createPlatformFactory, ErrorHandler, Inject, ModuleWithProviders, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, platformCore, PlatformRef, RendererFactory2, Sanitizer, SkipSelf, StaticProvider, Testability, ɵConsole as Console, ɵINJECTOR_SCOPE as INJECTOR_SCOPE, ɵsetDocument} from '@angular/core';
|
||||
|
||||
import {BrowserDomAdapter} from './browser/browser_adapter';
|
||||
import {SERVER_TRANSITION_PROVIDERS, TRANSITION_ID} from './browser/server-transition';
|
||||
import {BrowserGetTestability} from './browser/testability';
|
||||
|
@ -29,8 +29,12 @@ const nodeContains: (this: Node, other: Node) => boolean = (() => {
|
||||
*/
|
||||
/* tslint:disable:requireParameterType no-console */
|
||||
export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||
static makeCurrent() { setRootDomAdapter(new BrowserDomAdapter()); }
|
||||
getProperty(el: Node, name: string): any { return (<any>el)[name]; }
|
||||
static makeCurrent() {
|
||||
setRootDomAdapter(new BrowserDomAdapter());
|
||||
}
|
||||
getProperty(el: Node, name: string): any {
|
||||
return (<any>el)[name];
|
||||
}
|
||||
|
||||
log(error: string): void {
|
||||
if (window.console) {
|
||||
@ -54,16 +58,22 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||
el.addEventListener(evt, listener, false);
|
||||
// Needed to follow Dart's subscription semantic, until fix of
|
||||
// https://code.google.com/p/dart/issues/detail?id=17406
|
||||
return () => { el.removeEventListener(evt, listener, false); };
|
||||
return () => {
|
||||
el.removeEventListener(evt, listener, false);
|
||||
};
|
||||
}
|
||||
dispatchEvent(el: Node, evt: any) {
|
||||
el.dispatchEvent(evt);
|
||||
}
|
||||
dispatchEvent(el: Node, evt: any) { el.dispatchEvent(evt); }
|
||||
remove(node: Node): Node {
|
||||
if (node.parentNode) {
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
getValue(el: any): string { return el.value; }
|
||||
getValue(el: any): string {
|
||||
return el.value;
|
||||
}
|
||||
createElement(tagName: string, doc?: Document): HTMLElement {
|
||||
doc = doc || this.getDefaultDocument();
|
||||
return doc.createElement(tagName);
|
||||
@ -71,11 +81,17 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||
createHtmlDocument(): HTMLDocument {
|
||||
return document.implementation.createHTMLDocument('fakeTitle');
|
||||
}
|
||||
getDefaultDocument(): Document { return document; }
|
||||
getDefaultDocument(): Document {
|
||||
return document;
|
||||
}
|
||||
|
||||
isElementNode(node: Node): boolean { return node.nodeType === Node.ELEMENT_NODE; }
|
||||
isElementNode(node: Node): boolean {
|
||||
return node.nodeType === Node.ELEMENT_NODE;
|
||||
}
|
||||
|
||||
isShadowRoot(node: any): boolean { return node instanceof DocumentFragment; }
|
||||
isShadowRoot(node: any): boolean {
|
||||
return node instanceof DocumentFragment;
|
||||
}
|
||||
|
||||
getGlobalEventTarget(doc: Document, target: string): EventTarget|null {
|
||||
if (target === 'window') {
|
||||
@ -89,14 +105,22 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
getHistory(): History { return window.history; }
|
||||
getLocation(): Location { return window.location; }
|
||||
getHistory(): History {
|
||||
return window.history;
|
||||
}
|
||||
getLocation(): Location {
|
||||
return window.location;
|
||||
}
|
||||
getBaseHref(doc: Document): string|null {
|
||||
const href = getBaseElementHref();
|
||||
return href == null ? null : relativePath(href);
|
||||
}
|
||||
resetBaseElement(): void { baseElement = null; }
|
||||
getUserAgent(): string { return window.navigator.userAgent; }
|
||||
resetBaseElement(): void {
|
||||
baseElement = null;
|
||||
}
|
||||
getUserAgent(): string {
|
||||
return window.navigator.userAgent;
|
||||
}
|
||||
performanceNow(): number {
|
||||
// performance.now() is not available in all browsers, see
|
||||
// http://caniuse.com/#search=performance.now
|
||||
@ -104,15 +128,19 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||
new Date().getTime();
|
||||
}
|
||||
|
||||
supportsCookies(): boolean { return true; }
|
||||
supportsCookies(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
getCookie(name: string): string|null { return parseCookieValue(document.cookie, name); }
|
||||
getCookie(name: string): string|null {
|
||||
return parseCookieValue(document.cookie, name);
|
||||
}
|
||||
}
|
||||
|
||||
let baseElement: HTMLElement|null = null;
|
||||
function getBaseElementHref(): string|null {
|
||||
if (!baseElement) {
|
||||
baseElement = document.querySelector('base') !;
|
||||
baseElement = document.querySelector('base')!;
|
||||
if (!baseElement) {
|
||||
return null;
|
||||
}
|
||||
|
@ -17,7 +17,11 @@ import {ɵDomAdapter as DomAdapter} from '@angular/common';
|
||||
* can introduce XSS risks.
|
||||
*/
|
||||
export abstract class GenericBrowserDomAdapter extends DomAdapter {
|
||||
constructor() { super(); }
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
supportsDOMEvents(): boolean { return true; }
|
||||
supportsDOMEvents(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -15,13 +15,16 @@ import {Inject, Injectable, ɵɵinject} from '@angular/core';
|
||||
* @publicApi
|
||||
*/
|
||||
export type MetaDefinition = {
|
||||
charset?: string; content?: string; httpEquiv?: string; id?: string; itemprop?: string;
|
||||
charset?: string;
|
||||
content?: string;
|
||||
httpEquiv?: string;
|
||||
id?: string;
|
||||
itemprop?: string;
|
||||
name?: string;
|
||||
property?: string;
|
||||
scheme?: string;
|
||||
url?: string;
|
||||
} &
|
||||
{
|
||||
}&{
|
||||
// TODO(IgorMinar): this type looks wrong
|
||||
[prop: string]: string;
|
||||
};
|
||||
@ -41,7 +44,9 @@ export function createMeta() {
|
||||
@Injectable({providedIn: 'root', useFactory: createMeta, deps: []})
|
||||
export class Meta {
|
||||
private _dom: DomAdapter;
|
||||
constructor(@Inject(DOCUMENT) private _doc: any) { this._dom = getDOM(); }
|
||||
constructor(@Inject(DOCUMENT) private _doc: any) {
|
||||
this._dom = getDOM();
|
||||
}
|
||||
|
||||
addTag(tag: MetaDefinition, forceCreation: boolean = false): HTMLMetaElement|null {
|
||||
if (!tag) return null;
|
||||
@ -72,14 +77,16 @@ export class Meta {
|
||||
updateTag(tag: MetaDefinition, selector?: string): HTMLMetaElement|null {
|
||||
if (!tag) return null;
|
||||
selector = selector || this._parseSelector(tag);
|
||||
const meta: HTMLMetaElement = this.getTag(selector) !;
|
||||
const meta: HTMLMetaElement = this.getTag(selector)!;
|
||||
if (meta) {
|
||||
return this._setMetaElementAttributes(tag, meta);
|
||||
}
|
||||
return this._getOrCreateElement(tag, true);
|
||||
}
|
||||
|
||||
removeTag(attrSelector: string): void { this.removeTagElement(this.getTag(attrSelector) !); }
|
||||
removeTag(attrSelector: string): void {
|
||||
this.removeTagElement(this.getTag(attrSelector)!);
|
||||
}
|
||||
|
||||
removeTagElement(meta: HTMLMetaElement): void {
|
||||
if (meta) {
|
||||
@ -91,7 +98,7 @@ export class Meta {
|
||||
HTMLMetaElement {
|
||||
if (!forceCreation) {
|
||||
const selector: string = this._parseSelector(meta);
|
||||
const elem: HTMLMetaElement = this.getTag(selector) !;
|
||||
const elem: HTMLMetaElement = this.getTag(selector)!;
|
||||
// It's allowed to have multiple elements with the same name so it's not enough to
|
||||
// just check that element with the same name already present on the page. We also need to
|
||||
// check if element has tag attributes
|
||||
|
@ -7,10 +7,12 @@
|
||||
*/
|
||||
|
||||
import {ɵgetDOM as getDOM} from '@angular/common';
|
||||
import {GetTestability, Testability, TestabilityRegistry, setTestabilityGetter, ɵglobal as global} from '@angular/core';
|
||||
import {GetTestability, setTestabilityGetter, Testability, TestabilityRegistry, ɵglobal as global} from '@angular/core';
|
||||
|
||||
export class BrowserGetTestability implements GetTestability {
|
||||
static init() { setTestabilityGetter(new BrowserGetTestability()); }
|
||||
static init() {
|
||||
setTestabilityGetter(new BrowserGetTestability());
|
||||
}
|
||||
|
||||
addToWindow(registry: TestabilityRegistry): void {
|
||||
global['getAngularTestability'] = (elem: any, findInAncestors: boolean = true) => {
|
||||
|
@ -33,11 +33,15 @@ export class Title {
|
||||
/**
|
||||
* Get the title of the current HTML document.
|
||||
*/
|
||||
getTitle(): string { return this._doc.title; }
|
||||
getTitle(): string {
|
||||
return this._doc.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the title of the current HTML document.
|
||||
* @param newTitle
|
||||
*/
|
||||
setTitle(newTitle: string) { this._doc.title = newTitle || ''; }
|
||||
setTitle(newTitle: string) {
|
||||
this._doc.title = newTitle || '';
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,9 @@ export class ChangeDetectionPerfRecord {
|
||||
export class AngularProfiler {
|
||||
appRef: ApplicationRef;
|
||||
|
||||
constructor(ref: ComponentRef<any>) { this.appRef = ref.injector.get(ApplicationRef); }
|
||||
constructor(ref: ComponentRef<any>) {
|
||||
this.appRef = ref.injector.get(ApplicationRef);
|
||||
}
|
||||
|
||||
// tslint:disable:no-console
|
||||
/**
|
||||
|
@ -45,7 +45,7 @@ export function unescapeHtml(text: string): string {
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export type StateKey<T> = string & {__not_a_string: never};
|
||||
export type StateKey<T> = string&{__not_a_string: never};
|
||||
|
||||
/**
|
||||
* Create a `StateKey<T>` that can be used to store value of type T with `TransferState`.
|
||||
@ -80,7 +80,7 @@ export function makeStateKey<T = void>(key: string): StateKey<T> {
|
||||
*/
|
||||
@Injectable()
|
||||
export class TransferState {
|
||||
private store: {[k: string]: {} | undefined} = {};
|
||||
private store: {[k: string]: {}|undefined} = {};
|
||||
private onSerializeCallbacks: {[k: string]: () => {} | undefined} = {};
|
||||
|
||||
/** @internal */
|
||||
@ -100,17 +100,23 @@ export class TransferState {
|
||||
/**
|
||||
* Set the value corresponding to a key.
|
||||
*/
|
||||
set<T>(key: StateKey<T>, value: T): void { this.store[key] = value; }
|
||||
set<T>(key: StateKey<T>, value: T): void {
|
||||
this.store[key] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a key from the store.
|
||||
*/
|
||||
remove<T>(key: StateKey<T>): void { delete this.store[key]; }
|
||||
remove<T>(key: StateKey<T>): void {
|
||||
delete this.store[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a key exists in the store.
|
||||
*/
|
||||
hasKey<T>(key: StateKey<T>) { return this.store.hasOwnProperty(key); }
|
||||
hasKey<T>(key: StateKey<T>) {
|
||||
return this.store.hasOwnProperty(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to provide the value for a key when `toJson` is called.
|
||||
|
@ -25,7 +25,9 @@ export class By {
|
||||
*
|
||||
* {@example platform-browser/dom/debug/ts/by/by.ts region='by_all'}
|
||||
*/
|
||||
static all(): Predicate<DebugNode> { return () => true; }
|
||||
static all(): Predicate<DebugNode> {
|
||||
return () => true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match elements by the given CSS selector.
|
||||
@ -52,7 +54,7 @@ export class By {
|
||||
* {@example platform-browser/dom/debug/ts/by/by.ts region='by_directive'}
|
||||
*/
|
||||
static directive(type: Type<any>): Predicate<DebugNode> {
|
||||
return (debugNode) => debugNode.providerTokens !.indexOf(type) !== -1;
|
||||
return (debugNode) => debugNode.providerTokens!.indexOf(type) !== -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,11 +137,17 @@ class DefaultDomRenderer2 implements Renderer2 {
|
||||
return document.createElement(name);
|
||||
}
|
||||
|
||||
createComment(value: string): any { return document.createComment(value); }
|
||||
createComment(value: string): any {
|
||||
return document.createComment(value);
|
||||
}
|
||||
|
||||
createText(value: string): any { return document.createTextNode(value); }
|
||||
createText(value: string): any {
|
||||
return document.createTextNode(value);
|
||||
}
|
||||
|
||||
appendChild(parent: any, newChild: any): void { parent.appendChild(newChild); }
|
||||
appendChild(parent: any, newChild: any): void {
|
||||
parent.appendChild(newChild);
|
||||
}
|
||||
|
||||
insertBefore(parent: any, newChild: any, refChild: any): void {
|
||||
if (parent) {
|
||||
@ -167,9 +173,13 @@ class DefaultDomRenderer2 implements Renderer2 {
|
||||
return el;
|
||||
}
|
||||
|
||||
parentNode(node: any): any { return node.parentNode; }
|
||||
parentNode(node: any): any {
|
||||
return node.parentNode;
|
||||
}
|
||||
|
||||
nextSibling(node: any): any { return node.nextSibling; }
|
||||
nextSibling(node: any): any {
|
||||
return node.nextSibling;
|
||||
}
|
||||
|
||||
setAttribute(el: any, name: string, value: string, namespace?: string): void {
|
||||
if (namespace) {
|
||||
@ -205,9 +215,13 @@ class DefaultDomRenderer2 implements Renderer2 {
|
||||
}
|
||||
}
|
||||
|
||||
addClass(el: any, name: string): void { el.classList.add(name); }
|
||||
addClass(el: any, name: string): void {
|
||||
el.classList.add(name);
|
||||
}
|
||||
|
||||
removeClass(el: any, name: string): void { el.classList.remove(name); }
|
||||
removeClass(el: any, name: string): void {
|
||||
el.classList.remove(name);
|
||||
}
|
||||
|
||||
setStyle(el: any, style: string, value: any, flags: RendererStyleFlags2): void {
|
||||
if (flags & RendererStyleFlags2.DashCase) {
|
||||
@ -233,7 +247,9 @@ class DefaultDomRenderer2 implements Renderer2 {
|
||||
el[name] = value;
|
||||
}
|
||||
|
||||
setValue(node: any, value: string): void { node.nodeValue = value; }
|
||||
setValue(node: any, value: string): void {
|
||||
node.nodeValue = value;
|
||||
}
|
||||
|
||||
listen(target: 'window'|'document'|'body'|any, event: string, callback: (event: any) => boolean):
|
||||
() => void {
|
||||
@ -243,15 +259,15 @@ class DefaultDomRenderer2 implements Renderer2 {
|
||||
target, event, decoratePreventDefault(callback));
|
||||
}
|
||||
return <() => void>this.eventManager.addEventListener(
|
||||
target, event, decoratePreventDefault(callback)) as() => void;
|
||||
target, event, decoratePreventDefault(callback)) as () => void;
|
||||
}
|
||||
}
|
||||
|
||||
const AT_CHARCODE = (() => '@'.charCodeAt(0))();
|
||||
function checkNoSyntheticProp(name: string, nameKind: string) {
|
||||
if (name.charCodeAt(0) === AT_CHARCODE) {
|
||||
throw new Error(
|
||||
`Found the synthetic ${nameKind} ${name}. Please include either "BrowserAnimationsModule" or "NoopAnimationsModule" in your application.`);
|
||||
throw new Error(`Found the synthetic ${nameKind} ${
|
||||
name}. Please include either "BrowserAnimationsModule" or "NoopAnimationsModule" in your application.`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,7 +286,9 @@ class EmulatedEncapsulationDomRenderer2 extends DefaultDomRenderer2 {
|
||||
this.hostAttr = shimHostAttribute(appId + '-' + component.id);
|
||||
}
|
||||
|
||||
applyToHost(element: any) { super.setAttribute(element, this.hostAttr, ''); }
|
||||
applyToHost(element: any) {
|
||||
super.setAttribute(element, this.hostAttr, '');
|
||||
}
|
||||
|
||||
createElement(parent: any, name: string): Element {
|
||||
const el = super.createElement(parent, name);
|
||||
@ -300,9 +318,13 @@ class ShadowDomRenderer extends DefaultDomRenderer2 {
|
||||
}
|
||||
}
|
||||
|
||||
private nodeOrShadowRoot(node: any): any { return node === this.hostEl ? this.shadowRoot : node; }
|
||||
private nodeOrShadowRoot(node: any): any {
|
||||
return node === this.hostEl ? this.shadowRoot : node;
|
||||
}
|
||||
|
||||
destroy() { this.sharedStylesHost.removeHost(this.shadowRoot); }
|
||||
destroy() {
|
||||
this.sharedStylesHost.removeHost(this.shadowRoot);
|
||||
}
|
||||
|
||||
appendChild(parent: any, newChild: any): void {
|
||||
return super.appendChild(this.nodeOrShadowRoot(parent), newChild);
|
||||
|
@ -13,11 +13,15 @@ import {EventManagerPlugin} from './event_manager';
|
||||
|
||||
@Injectable()
|
||||
export class DomEventsPlugin extends EventManagerPlugin {
|
||||
constructor(@Inject(DOCUMENT) doc: any) { super(doc); }
|
||||
constructor(@Inject(DOCUMENT) doc: any) {
|
||||
super(doc);
|
||||
}
|
||||
|
||||
// This plugin should come last in the list of plugins, because it accepts all
|
||||
// events.
|
||||
supports(eventName: string): boolean { return true; }
|
||||
supports(eventName: string): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
|
||||
element.addEventListener(eventName, handler as EventListener, false);
|
||||
|
@ -67,7 +67,9 @@ export class EventManager {
|
||||
/**
|
||||
* Retrieves the compilation zone in which event listeners are registered.
|
||||
*/
|
||||
getZone(): NgZone { return this._zone; }
|
||||
getZone(): NgZone {
|
||||
return this._zone;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_findPluginFor(eventName: string): EventManagerPlugin {
|
||||
@ -92,7 +94,7 @@ export abstract class EventManagerPlugin {
|
||||
constructor(private _doc: any) {}
|
||||
|
||||
// TODO(issue/24571): remove '!'.
|
||||
manager !: EventManager;
|
||||
manager!: EventManager;
|
||||
|
||||
abstract supports(eventName: string): boolean;
|
||||
|
||||
|
@ -99,21 +99,21 @@ export class HammerGestureConfig {
|
||||
events: string[] = [];
|
||||
|
||||
/**
|
||||
* Maps gesture event names to a set of configuration options
|
||||
* that specify overrides to the default values for specific properties.
|
||||
*
|
||||
* The key is a supported event name to be configured,
|
||||
* and the options object contains a set of properties, with override values
|
||||
* to be applied to the named recognizer event.
|
||||
* For example, to disable recognition of the rotate event, specify
|
||||
* `{"rotate": {"enable": false}}`.
|
||||
*
|
||||
* Properties that are not present take the HammerJS default values.
|
||||
* For information about which properties are supported for which events,
|
||||
* and their allowed and default values, see
|
||||
* [HammerJS documentation](http://hammerjs.github.io/).
|
||||
*
|
||||
*/
|
||||
* Maps gesture event names to a set of configuration options
|
||||
* that specify overrides to the default values for specific properties.
|
||||
*
|
||||
* The key is a supported event name to be configured,
|
||||
* and the options object contains a set of properties, with override values
|
||||
* to be applied to the named recognizer event.
|
||||
* For example, to disable recognition of the rotate event, specify
|
||||
* `{"rotate": {"enable": false}}`.
|
||||
*
|
||||
* Properties that are not present take the HammerJS default values.
|
||||
* For information about which properties are supported for which events,
|
||||
* and their allowed and default values, see
|
||||
* [HammerJS documentation](http://hammerjs.github.io/).
|
||||
*
|
||||
*/
|
||||
overrides: {[key: string]: Object} = {};
|
||||
|
||||
/**
|
||||
@ -124,7 +124,9 @@ export class HammerGestureConfig {
|
||||
* [HammerJS documentation](http://hammerjs.github.io/).
|
||||
*/
|
||||
options?: {
|
||||
cssProps?: any; domEvents?: boolean; enable?: boolean | ((manager: any) => boolean);
|
||||
cssProps?: any;
|
||||
domEvents?: boolean;
|
||||
enable?: boolean | ((manager: any) => boolean);
|
||||
preset?: any[];
|
||||
touchAction?: string;
|
||||
recognizers?: any[];
|
||||
@ -139,7 +141,7 @@ export class HammerGestureConfig {
|
||||
* @returns A HammerJS event-manager object.
|
||||
*/
|
||||
buildHammer(element: HTMLElement): HammerInstance {
|
||||
const mc = new Hammer !(element, this.options);
|
||||
const mc = new Hammer!(element, this.options);
|
||||
|
||||
mc.get('pinch').set({enable: true});
|
||||
mc.get('rotate').set({enable: true});
|
||||
@ -192,7 +194,9 @@ export class HammerGesturesPlugin extends EventManagerPlugin {
|
||||
// Until Hammer is loaded, the returned function needs to *cancel* the registration rather
|
||||
// than remove anything.
|
||||
let cancelRegistration = false;
|
||||
let deregister: Function = () => { cancelRegistration = true; };
|
||||
let deregister: Function = () => {
|
||||
cancelRegistration = true;
|
||||
};
|
||||
|
||||
this.loader()
|
||||
.then(() => {
|
||||
@ -220,14 +224,18 @@ export class HammerGesturesPlugin extends EventManagerPlugin {
|
||||
// Return a function that *executes* `deregister` (and not `deregister` itself) so that we
|
||||
// can change the behavior of `deregister` once the listener is added. Using a closure in
|
||||
// this way allows us to avoid any additional data structures to track listener removal.
|
||||
return () => { deregister(); };
|
||||
return () => {
|
||||
deregister();
|
||||
};
|
||||
}
|
||||
|
||||
return zone.runOutsideAngular(() => {
|
||||
// Creating the manager bind events, must be done outside of angular
|
||||
const mc = this._config.buildHammer(element);
|
||||
const callback = function(eventObj: HammerInput) {
|
||||
zone.runGuarded(function() { handler(eventObj); });
|
||||
zone.runGuarded(function() {
|
||||
handler(eventObj);
|
||||
});
|
||||
};
|
||||
mc.on(eventName, callback);
|
||||
return () => {
|
||||
@ -240,7 +248,9 @@ export class HammerGesturesPlugin extends EventManagerPlugin {
|
||||
});
|
||||
}
|
||||
|
||||
isCustomEvent(eventName: string): boolean { return this._config.events.indexOf(eventName) > -1; }
|
||||
isCustomEvent(eventName: string): boolean {
|
||||
return this._config.events.indexOf(eventName) > -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,14 +79,18 @@ export class KeyEventsPlugin extends EventManagerPlugin {
|
||||
* Initializes an instance of the browser plug-in.
|
||||
* @param doc The document in which key events will be detected.
|
||||
*/
|
||||
constructor(@Inject(DOCUMENT) doc: any) { super(doc); }
|
||||
constructor(@Inject(DOCUMENT) doc: any) {
|
||||
super(doc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports whether a named key event is supported.
|
||||
* @param eventName The event name to query.
|
||||
* @return True if the named key event is supported.
|
||||
* Reports whether a named key event is supported.
|
||||
* @param eventName The event name to query.
|
||||
* @return True if the named key event is supported.
|
||||
*/
|
||||
supports(eventName: string): boolean { return KeyEventsPlugin.parseEventName(eventName) != null; }
|
||||
supports(eventName: string): boolean {
|
||||
return KeyEventsPlugin.parseEventName(eventName) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a handler for a specific element and key event.
|
||||
@ -95,9 +99,9 @@ export class KeyEventsPlugin extends EventManagerPlugin {
|
||||
* @param handler A function to call when the notification occurs. Receives the
|
||||
* event object as an argument.
|
||||
* @returns The key event that was registered.
|
||||
*/
|
||||
*/
|
||||
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
|
||||
const parsedEvent = KeyEventsPlugin.parseEventName(eventName) !;
|
||||
const parsedEvent = KeyEventsPlugin.parseEventName(eventName)!;
|
||||
|
||||
const outsideHandler =
|
||||
KeyEventsPlugin.eventCallback(parsedEvent['fullKey'], handler, this.manager.getZone());
|
||||
@ -115,7 +119,7 @@ export class KeyEventsPlugin extends EventManagerPlugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
const key = KeyEventsPlugin._normalizeKey(parts.pop() !);
|
||||
const key = KeyEventsPlugin._normalizeKey(parts.pop()!);
|
||||
|
||||
let fullKey = '';
|
||||
MODIFIER_KEYS.forEach(modifierName => {
|
||||
|
@ -27,7 +27,9 @@ export class SharedStylesHost {
|
||||
|
||||
onStylesAdded(additions: Set<string>): void {}
|
||||
|
||||
getAllStyles(): string[] { return Array.from(this._stylesSet); }
|
||||
getAllStyles(): string[] {
|
||||
return Array.from(this._stylesSet);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@ -52,11 +54,15 @@ export class DomSharedStylesHost extends SharedStylesHost implements OnDestroy {
|
||||
this._hostNodes.add(hostNode);
|
||||
}
|
||||
|
||||
removeHost(hostNode: Node): void { this._hostNodes.delete(hostNode); }
|
||||
removeHost(hostNode: Node): void {
|
||||
this._hostNodes.delete(hostNode);
|
||||
}
|
||||
|
||||
onStylesAdded(additions: Set<string>): void {
|
||||
this._hostNodes.forEach(hostNode => this._addStylesToHost(additions, hostNode));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void { this._styleNodes.forEach(styleNode => getDOM().remove(styleNode)); }
|
||||
ngOnDestroy(): void {
|
||||
this._styleNodes.forEach(styleNode => getDOM().remove(styleNode));
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ export function exportNgVar(name: string, value: any): void {
|
||||
// - closure declares globals itself for minified names, which sometimes clobber our `ng` global
|
||||
// - we can't declare a closure extern as the namespace `ng` is already used within Google
|
||||
// for typings for angularJS (via `goog.provide('ng....')`).
|
||||
const ng = global['ng'] = (global['ng'] as{[key: string]: any} | undefined) || {};
|
||||
const ng = global['ng'] = (global['ng'] as {[key: string]: any} | undefined) || {};
|
||||
ng[name] = value;
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ export {BrowserModule, platformBrowser} from './browser';
|
||||
export {Meta, MetaDefinition} from './browser/meta';
|
||||
export {Title} from './browser/title';
|
||||
export {disableDebugTools, enableDebugTools} from './browser/tools/tools';
|
||||
export {BrowserTransferStateModule, StateKey, TransferState, makeStateKey} from './browser/transfer_state';
|
||||
export {BrowserTransferStateModule, makeStateKey, StateKey, TransferState} from './browser/transfer_state';
|
||||
export {By} from './dom/debug/by';
|
||||
export {EVENT_MANAGER_PLUGINS, EventManager} from './dom/events/event_manager';
|
||||
export {HAMMER_GESTURE_CONFIG, HAMMER_LOADER, HAMMER_PROVIDERS__POST_R3__ as ɵHAMMER_PROVIDERS__POST_R3__, HammerGestureConfig, HammerLoader, HammerModule} from './dom/events/hammer_gestures';
|
||||
|
@ -7,13 +7,13 @@
|
||||
*/
|
||||
|
||||
export {ɵgetDOM} from '@angular/common';
|
||||
export {BROWSER_SANITIZATION_PROVIDERS as ɵBROWSER_SANITIZATION_PROVIDERS, BROWSER_SANITIZATION_PROVIDERS__POST_R3__ as ɵBROWSER_SANITIZATION_PROVIDERS__POST_R3__, INTERNAL_BROWSER_PLATFORM_PROVIDERS as ɵINTERNAL_BROWSER_PLATFORM_PROVIDERS, initDomAdapter as ɵinitDomAdapter} from './browser';
|
||||
export {BROWSER_SANITIZATION_PROVIDERS as ɵBROWSER_SANITIZATION_PROVIDERS, BROWSER_SANITIZATION_PROVIDERS__POST_R3__ as ɵBROWSER_SANITIZATION_PROVIDERS__POST_R3__, initDomAdapter as ɵinitDomAdapter, INTERNAL_BROWSER_PLATFORM_PROVIDERS as ɵINTERNAL_BROWSER_PLATFORM_PROVIDERS} from './browser';
|
||||
export {BrowserDomAdapter as ɵBrowserDomAdapter} from './browser/browser_adapter';
|
||||
export {TRANSITION_ID as ɵTRANSITION_ID} from './browser/server-transition';
|
||||
export {BrowserGetTestability as ɵBrowserGetTestability} from './browser/testability';
|
||||
export {escapeHtml as ɵescapeHtml} from './browser/transfer_state';
|
||||
export {ELEMENT_PROBE_PROVIDERS as ɵELEMENT_PROBE_PROVIDERS} from './dom/debug/ng_probe';
|
||||
export {DomRendererFactory2 as ɵDomRendererFactory2, NAMESPACE_URIS as ɵNAMESPACE_URIS, flattenStyles as ɵflattenStyles, shimContentAttribute as ɵshimContentAttribute, shimHostAttribute as ɵshimHostAttribute} from './dom/dom_renderer';
|
||||
export {DomRendererFactory2 as ɵDomRendererFactory2, flattenStyles as ɵflattenStyles, NAMESPACE_URIS as ɵNAMESPACE_URIS, shimContentAttribute as ɵshimContentAttribute, shimHostAttribute as ɵshimHostAttribute} from './dom/dom_renderer';
|
||||
export {DomEventsPlugin as ɵDomEventsPlugin} from './dom/events/dom_events';
|
||||
export {HammerGesturesPlugin as ɵHammerGesturesPlugin} from './dom/events/hammer_gestures';
|
||||
export {KeyEventsPlugin as ɵKeyEventsPlugin} from './dom/events/key_events';
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {Inject, Injectable, Injector, Sanitizer, SecurityContext, forwardRef, ɵBypassType as BypassType, ɵ_sanitizeHtml as _sanitizeHtml, ɵ_sanitizeStyle as _sanitizeStyle, ɵ_sanitizeUrl as _sanitizeUrl, ɵallowSanitizationBypassAndThrow as allowSanitizationBypassOrThrow, ɵbypassSanitizationTrustHtml as bypassSanitizationTrustHtml, ɵbypassSanitizationTrustResourceUrl as bypassSanitizationTrustResourceUrl, ɵbypassSanitizationTrustScript as bypassSanitizationTrustScript, ɵbypassSanitizationTrustStyle as bypassSanitizationTrustStyle, ɵbypassSanitizationTrustUrl as bypassSanitizationTrustUrl, ɵgetSanitizationBypassType as getSanitizationBypassType, ɵunwrapSafeValue as unwrapSafeValue} from '@angular/core';
|
||||
import {forwardRef, Inject, Injectable, Injector, Sanitizer, SecurityContext, ɵ_sanitizeHtml as _sanitizeHtml, ɵ_sanitizeStyle as _sanitizeStyle, ɵ_sanitizeUrl as _sanitizeUrl, ɵallowSanitizationBypassAndThrow as allowSanitizationBypassOrThrow, ɵbypassSanitizationTrustHtml as bypassSanitizationTrustHtml, ɵbypassSanitizationTrustResourceUrl as bypassSanitizationTrustResourceUrl, ɵbypassSanitizationTrustScript as bypassSanitizationTrustScript, ɵbypassSanitizationTrustStyle as bypassSanitizationTrustStyle, ɵbypassSanitizationTrustUrl as bypassSanitizationTrustUrl, ɵBypassType as BypassType, ɵgetSanitizationBypassType as getSanitizationBypassType, ɵunwrapSafeValue as unwrapSafeValue} from '@angular/core';
|
||||
|
||||
export {SecurityContext};
|
||||
|
||||
@ -149,7 +149,9 @@ export function domSanitizerImplFactory(injector: Injector) {
|
||||
|
||||
@Injectable({providedIn: 'root', useFactory: domSanitizerImplFactory, deps: [Injector]})
|
||||
export class DomSanitizerImpl extends DomSanitizer {
|
||||
constructor(@Inject(DOCUMENT) private _doc: any) { super(); }
|
||||
constructor(@Inject(DOCUMENT) private _doc: any) {
|
||||
super();
|
||||
}
|
||||
|
||||
sanitize(ctx: SecurityContext, value: SafeValue|string|null): string|null {
|
||||
if (value == null) return null;
|
||||
@ -188,12 +190,18 @@ export class DomSanitizerImpl extends DomSanitizer {
|
||||
}
|
||||
}
|
||||
|
||||
bypassSecurityTrustHtml(value: string): SafeHtml { return bypassSanitizationTrustHtml(value); }
|
||||
bypassSecurityTrustStyle(value: string): SafeStyle { return bypassSanitizationTrustStyle(value); }
|
||||
bypassSecurityTrustHtml(value: string): SafeHtml {
|
||||
return bypassSanitizationTrustHtml(value);
|
||||
}
|
||||
bypassSecurityTrustStyle(value: string): SafeStyle {
|
||||
return bypassSanitizationTrustStyle(value);
|
||||
}
|
||||
bypassSecurityTrustScript(value: string): SafeScript {
|
||||
return bypassSanitizationTrustScript(value);
|
||||
}
|
||||
bypassSecurityTrustUrl(value: string): SafeUrl { return bypassSanitizationTrustUrl(value); }
|
||||
bypassSecurityTrustUrl(value: string): SafeUrl {
|
||||
return bypassSanitizationTrustUrl(value);
|
||||
}
|
||||
bypassSecurityTrustResourceUrl(value: string): SafeResourceUrl {
|
||||
return bypassSanitizationTrustResourceUrl(value);
|
||||
}
|
||||
|
@ -7,12 +7,12 @@
|
||||
*/
|
||||
|
||||
import {DOCUMENT, isPlatformBrowser, ɵgetDOM as getDOM} from '@angular/common';
|
||||
import {APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, Directive, ErrorHandler, Inject, Injector, Input, LOCALE_ID, NgModule, OnDestroy, PLATFORM_ID, PLATFORM_INITIALIZER, Pipe, Provider, Sanitizer, StaticProvider, Type, VERSION, createPlatformFactory} from '@angular/core';
|
||||
import {APP_INITIALIZER, Compiler, Component, createPlatformFactory, CUSTOM_ELEMENTS_SCHEMA, Directive, ErrorHandler, Inject, Injector, Input, LOCALE_ID, NgModule, OnDestroy, Pipe, PLATFORM_ID, PLATFORM_INITIALIZER, Provider, Sanitizer, StaticProvider, Type, VERSION} from '@angular/core';
|
||||
import {ApplicationRef, destroyPlatform} from '@angular/core/src/application_ref';
|
||||
import {Console} from '@angular/core/src/console';
|
||||
import {ComponentRef} from '@angular/core/src/linker/component_factory';
|
||||
import {Testability, TestabilityRegistry} from '@angular/core/src/testability/testability';
|
||||
import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, describe, inject, it} from '@angular/core/testing/src/testing_internal';
|
||||
import {afterEach, AsyncTestCompleter, beforeEach, beforeEachProviders, describe, inject, it, Log} from '@angular/core/testing/src/testing_internal';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
@ -25,7 +25,9 @@ class NonExistentComp {
|
||||
@Component({selector: 'hello-app', template: '{{greeting}} world!'})
|
||||
class HelloRootCmp {
|
||||
greeting: string;
|
||||
constructor() { this.greeting = 'hello'; }
|
||||
constructor() {
|
||||
this.greeting = 'hello';
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'hello-app', template: 'before: <ng-content></ng-content> after: done'})
|
||||
@ -36,7 +38,9 @@ class HelloRootCmpContent {
|
||||
@Component({selector: 'hello-app-2', template: '{{greeting}} world, again!'})
|
||||
class HelloRootCmp2 {
|
||||
greeting: string;
|
||||
constructor() { this.greeting = 'hello'; }
|
||||
constructor() {
|
||||
this.greeting = 'hello';
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'hello-app', template: ''})
|
||||
@ -52,7 +56,9 @@ class HelloRootCmp3 {
|
||||
class HelloRootCmp4 {
|
||||
appRef: any /** TODO #9100 */;
|
||||
|
||||
constructor(@Inject(ApplicationRef) appRef: ApplicationRef) { this.appRef = appRef; }
|
||||
constructor(@Inject(ApplicationRef) appRef: ApplicationRef) {
|
||||
this.appRef = appRef;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'hello-app'})
|
||||
@ -66,9 +72,13 @@ class HelloRootDirectiveIsNotCmp {
|
||||
@Component({selector: 'hello-app', template: ''})
|
||||
class HelloOnDestroyTickCmp implements OnDestroy {
|
||||
appRef: ApplicationRef;
|
||||
constructor(@Inject(ApplicationRef) appRef: ApplicationRef) { this.appRef = appRef; }
|
||||
constructor(@Inject(ApplicationRef) appRef: ApplicationRef) {
|
||||
this.appRef = appRef;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void { this.appRef.tick(); }
|
||||
ngOnDestroy(): void {
|
||||
this.appRef.tick();
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'hello-app', templateUrl: './sometemplate.html'})
|
||||
@ -79,13 +89,14 @@ class HelloUrlCmp {
|
||||
@Directive({selector: '[someDir]', host: {'[title]': 'someDir'}})
|
||||
class SomeDirective {
|
||||
// TODO(issue/24571): remove '!'.
|
||||
@Input()
|
||||
someDir !: string;
|
||||
@Input() someDir!: string;
|
||||
}
|
||||
|
||||
@Pipe({name: 'somePipe'})
|
||||
class SomePipe {
|
||||
transform(value: string): any { return `transformed ${value}`; }
|
||||
transform(value: string): any {
|
||||
return `transformed ${value}`;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'hello-app', template: `<div [someDir]="'someValue' | somePipe"></div>`})
|
||||
@ -99,7 +110,9 @@ class HelloCmpUsingCustomElement {
|
||||
|
||||
class MockConsole {
|
||||
res: any[][] = [];
|
||||
error(...s: any[]): void { this.res.push(s); }
|
||||
error(...s: any[]): void {
|
||||
this.res.push(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -107,7 +120,9 @@ class DummyConsole implements Console {
|
||||
public warnings: string[] = [];
|
||||
|
||||
log(message: string) {}
|
||||
warn(message: string) { this.warnings.push(message); }
|
||||
warn(message: string) {
|
||||
this.warnings.push(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -135,7 +150,9 @@ function bootstrap(
|
||||
if (isNode) return;
|
||||
let compilerConsole: DummyConsole;
|
||||
|
||||
beforeEachProviders(() => { return [Log]; });
|
||||
beforeEachProviders(() => {
|
||||
return [Log];
|
||||
});
|
||||
|
||||
beforeEach(inject([DOCUMENT], (doc: any) => {
|
||||
destroyPlatform();
|
||||
@ -419,7 +436,6 @@ function bootstrap(
|
||||
|
||||
it('should remove styles when transitioning from a server render',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
|
||||
@Component({
|
||||
selector: 'root',
|
||||
template: 'root',
|
||||
@ -449,8 +465,9 @@ function bootstrap(
|
||||
platform.bootstrapModule(TestModule).then(() => {
|
||||
const styles: HTMLElement[] =
|
||||
Array.prototype.slice.apply(document.getElementsByTagName('style') || []);
|
||||
styles.forEach(
|
||||
style => { expect(style.getAttribute('ng-transition')).not.toBe('my-app'); });
|
||||
styles.forEach(style => {
|
||||
expect(style.getAttribute('ng-transition')).not.toBe('my-app');
|
||||
});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
@ -487,7 +504,9 @@ function bootstrap(
|
||||
})
|
||||
class CompA {
|
||||
title: string = '';
|
||||
ngDoCheck() { log.push('CompA:ngDoCheck'); }
|
||||
ngDoCheck() {
|
||||
log.push('CompA:ngDoCheck');
|
||||
}
|
||||
onClick() {
|
||||
this.title = 'CompA';
|
||||
log.push('CompA:onClick');
|
||||
@ -500,7 +519,9 @@ function bootstrap(
|
||||
})
|
||||
class CompB {
|
||||
title: string = '';
|
||||
ngDoCheck() { log.push('CompB:ngDoCheck'); }
|
||||
ngDoCheck() {
|
||||
log.push('CompB:ngDoCheck');
|
||||
}
|
||||
onClick() {
|
||||
this.title = 'CompB';
|
||||
log.push('CompB:onClick');
|
||||
|
@ -30,14 +30,14 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
afterEach(() => getDOM().remove(defaultMeta));
|
||||
|
||||
it('should return meta tag matching selector', () => {
|
||||
const actual: HTMLMetaElement = metaService.getTag('property="fb:app_id"') !;
|
||||
const actual: HTMLMetaElement = metaService.getTag('property="fb:app_id"')!;
|
||||
expect(actual).not.toBeNull();
|
||||
expect(actual.getAttribute('content')).toEqual('123456789');
|
||||
});
|
||||
|
||||
it('should return all meta tags matching selector', () => {
|
||||
const tag1 = metaService.addTag({name: 'author', content: 'page author'}) !;
|
||||
const tag2 = metaService.addTag({name: 'author', content: 'another page author'}) !;
|
||||
const tag1 = metaService.addTag({name: 'author', content: 'page author'})!;
|
||||
const tag2 = metaService.addTag({name: 'author', content: 'another page author'})!;
|
||||
|
||||
const actual: HTMLMetaElement[] = metaService.getTags('name=author');
|
||||
expect(actual.length).toEqual(2);
|
||||
@ -50,7 +50,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
});
|
||||
|
||||
it('should return null if meta tag does not exist', () => {
|
||||
const actual: HTMLMetaElement = metaService.getTag('fake=fake') !;
|
||||
const actual: HTMLMetaElement = metaService.getTag('fake=fake')!;
|
||||
expect(actual).toBeNull();
|
||||
});
|
||||
|
||||
@ -73,7 +73,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
metaService.addTags([{name: 'keywords', content: 'meta test'}]);
|
||||
|
||||
const meta = metaService.getTag(selector) !;
|
||||
const meta = metaService.getTag(selector)!;
|
||||
expect(meta).not.toBeNull();
|
||||
|
||||
metaService.removeTagElement(meta);
|
||||
@ -87,7 +87,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
const actual = metaService.getTag(selector);
|
||||
expect(actual).not.toBeNull();
|
||||
expect(actual !.getAttribute('content')).toEqual('4321');
|
||||
expect(actual!.getAttribute('content')).toEqual('4321');
|
||||
});
|
||||
|
||||
it('should extract selector from the tag definition', () => {
|
||||
@ -96,7 +96,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
const actual = metaService.getTag(selector);
|
||||
expect(actual).not.toBeNull();
|
||||
expect(actual !.getAttribute('content')).toEqual('666');
|
||||
expect(actual!.getAttribute('content')).toEqual('666');
|
||||
});
|
||||
|
||||
it('should create meta tag if it does not exist', () => {
|
||||
@ -104,7 +104,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
metaService.updateTag({name: 'twitter:title', content: 'Content Title'}, selector);
|
||||
|
||||
const actual = metaService.getTag(selector) !;
|
||||
const actual = metaService.getTag(selector)!;
|
||||
expect(actual).not.toBeNull();
|
||||
expect(actual.getAttribute('content')).toEqual('Content Title');
|
||||
|
||||
@ -118,7 +118,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
metaService.addTag({name: 'og:title', content: 'Content Title'});
|
||||
|
||||
const actual = metaService.getTag(selector) !;
|
||||
const actual = metaService.getTag(selector)!;
|
||||
expect(actual).not.toBeNull();
|
||||
expect(actual.getAttribute('content')).toEqual('Content Title');
|
||||
|
||||
@ -136,8 +136,8 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
{name: 'twitter:title', content: 'Content Title'},
|
||||
{property: 'og:title', content: 'Content Title'}
|
||||
]);
|
||||
const twitterMeta = metaService.getTag(nameSelector) !;
|
||||
const fbMeta = metaService.getTag(propertySelector) !;
|
||||
const twitterMeta = metaService.getTag(nameSelector)!;
|
||||
const fbMeta = metaService.getTag(propertySelector)!;
|
||||
expect(twitterMeta).not.toBeNull();
|
||||
expect(fbMeta).not.toBeNull();
|
||||
|
||||
@ -160,7 +160,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
const selector = 'property="fb:app_id"';
|
||||
expect(metaService.getTags(selector).length).toEqual(1);
|
||||
|
||||
const meta = metaService.addTag({property: 'fb:app_id', content: '666'}) !;
|
||||
const meta = metaService.addTag({property: 'fb:app_id', content: '666'})!;
|
||||
|
||||
expect(metaService.getTags(selector).length).toEqual(2);
|
||||
|
||||
@ -172,18 +172,16 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
const selector = 'property="fb:app_id"';
|
||||
expect(metaService.getTags(selector).length).toEqual(1);
|
||||
|
||||
const meta = metaService.addTag({property: 'fb:app_id', content: '123456789'}, true) !;
|
||||
const meta = metaService.addTag({property: 'fb:app_id', content: '123456789'}, true)!;
|
||||
|
||||
expect(metaService.getTags(selector).length).toEqual(2);
|
||||
|
||||
// clean up
|
||||
metaService.removeTagElement(meta);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('integration test', () => {
|
||||
|
||||
@Injectable()
|
||||
class DependsOnMeta {
|
||||
constructor(public meta: Meta) {}
|
||||
|
@ -24,10 +24,13 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
titleService = new Title(doc);
|
||||
});
|
||||
|
||||
afterEach(() => { doc.title = initialTitle; });
|
||||
afterEach(() => {
|
||||
doc.title = initialTitle;
|
||||
});
|
||||
|
||||
it('should allow reading initial title',
|
||||
() => { expect(titleService.getTitle()).toEqual(initialTitle); });
|
||||
it('should allow reading initial title', () => {
|
||||
expect(titleService.getTitle()).toEqual(initialTitle);
|
||||
});
|
||||
|
||||
it('should set a title on the injected document', () => {
|
||||
titleService.setTitle('test title');
|
||||
@ -36,13 +39,12 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
});
|
||||
|
||||
it('should reset title to empty string if title not provided', () => {
|
||||
titleService.setTitle(null !);
|
||||
titleService.setTitle(null!);
|
||||
expect(doc.title).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration test', () => {
|
||||
|
||||
@Injectable()
|
||||
class DependsOnTitle {
|
||||
constructor(public title: Title) {}
|
||||
@ -55,7 +57,8 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
});
|
||||
});
|
||||
|
||||
it('should inject Title service when using BrowserModule',
|
||||
() => { expect(TestBed.inject(DependsOnTitle).title).toBeAnInstanceOf(Title); });
|
||||
it('should inject Title service when using BrowserModule', () => {
|
||||
expect(TestBed.inject(DependsOnTitle).title).toBeAnInstanceOf(Title);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -11,7 +11,9 @@ import {ApplicationRef} from '@angular/core/src/application_ref';
|
||||
import {SpyObject} from '@angular/core/testing/src/testing_internal';
|
||||
|
||||
export class SpyApplicationRef extends SpyObject {
|
||||
constructor() { super(ApplicationRef); }
|
||||
constructor() {
|
||||
super(ApplicationRef);
|
||||
}
|
||||
}
|
||||
|
||||
export class SpyComponentRef extends SpyObject {
|
||||
|
@ -8,18 +8,25 @@
|
||||
|
||||
import {disableDebugTools, enableDebugTools} from '@angular/platform-browser';
|
||||
|
||||
import {SpyComponentRef, callNgProfilerTimeChangeDetection} from './spies';
|
||||
import {callNgProfilerTimeChangeDetection, SpyComponentRef} from './spies';
|
||||
|
||||
{
|
||||
describe('profiler', () => {
|
||||
if (isNode) return;
|
||||
beforeEach(() => { enableDebugTools((<any>new SpyComponentRef())); });
|
||||
beforeEach(() => {
|
||||
enableDebugTools((<any>new SpyComponentRef()));
|
||||
});
|
||||
|
||||
afterEach(() => { disableDebugTools(); });
|
||||
afterEach(() => {
|
||||
disableDebugTools();
|
||||
});
|
||||
|
||||
it('should time change detection', () => { callNgProfilerTimeChangeDetection(); });
|
||||
it('should time change detection', () => {
|
||||
callNgProfilerTimeChangeDetection();
|
||||
});
|
||||
|
||||
it('should time change detection with recording',
|
||||
() => { callNgProfilerTimeChangeDetection({'record': true}); });
|
||||
it('should time change detection with recording', () => {
|
||||
callNgProfilerTimeChangeDetection({'record': true});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -9,124 +9,126 @@
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {BrowserModule, BrowserTransferStateModule, TransferState} from '@angular/platform-browser';
|
||||
import {StateKey, escapeHtml, makeStateKey, unescapeHtml} from '@angular/platform-browser/src/browser/transfer_state';
|
||||
import {escapeHtml, makeStateKey, StateKey, unescapeHtml} from '@angular/platform-browser/src/browser/transfer_state';
|
||||
|
||||
(function() {
|
||||
function removeScriptTag(doc: Document, id: string) {
|
||||
const existing = doc.getElementById(id);
|
||||
if (existing) {
|
||||
doc.body.removeChild(existing);
|
||||
}
|
||||
function removeScriptTag(doc: Document, id: string) {
|
||||
const existing = doc.getElementById(id);
|
||||
if (existing) {
|
||||
doc.body.removeChild(existing);
|
||||
}
|
||||
}
|
||||
|
||||
function addScriptTag(doc: Document, appId: string, data: {}) {
|
||||
const script = doc.createElement('script');
|
||||
const id = appId + '-state';
|
||||
script.id = id;
|
||||
script.setAttribute('type', 'application/json');
|
||||
script.textContent = escapeHtml(JSON.stringify(data));
|
||||
function addScriptTag(doc: Document, appId: string, data: {}) {
|
||||
const script = doc.createElement('script');
|
||||
const id = appId + '-state';
|
||||
script.id = id;
|
||||
script.setAttribute('type', 'application/json');
|
||||
script.textContent = escapeHtml(JSON.stringify(data));
|
||||
|
||||
// Remove any stale script tags.
|
||||
removeScriptTag(doc, id);
|
||||
// Remove any stale script tags.
|
||||
removeScriptTag(doc, id);
|
||||
|
||||
doc.body.appendChild(script);
|
||||
}
|
||||
doc.body.appendChild(script);
|
||||
}
|
||||
|
||||
describe('TransferState', () => {
|
||||
const APP_ID = 'test-app';
|
||||
let doc: Document;
|
||||
describe('TransferState', () => {
|
||||
const APP_ID = 'test-app';
|
||||
let doc: Document;
|
||||
|
||||
const TEST_KEY = makeStateKey<number>('test');
|
||||
const DELAYED_KEY = makeStateKey<string>('delayed');
|
||||
const TEST_KEY = makeStateKey<number>('test');
|
||||
const DELAYED_KEY = makeStateKey<string>('delayed');
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({appId: APP_ID}),
|
||||
BrowserTransferStateModule,
|
||||
]
|
||||
});
|
||||
doc = TestBed.inject(DOCUMENT);
|
||||
});
|
||||
|
||||
afterEach(() => { removeScriptTag(doc, APP_ID + '-state'); });
|
||||
|
||||
it('is initialized from script tag', () => {
|
||||
addScriptTag(doc, APP_ID, {test: 10});
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
expect(transferState.get(TEST_KEY, 0)).toBe(10);
|
||||
});
|
||||
|
||||
it('is initialized to empty state if script tag not found', () => {
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
expect(transferState.get(TEST_KEY, 0)).toBe(0);
|
||||
});
|
||||
|
||||
it('supports adding new keys using set', () => {
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
transferState.set(TEST_KEY, 20);
|
||||
expect(transferState.get(TEST_KEY, 0)).toBe(20);
|
||||
expect(transferState.hasKey(TEST_KEY)).toBe(true);
|
||||
});
|
||||
|
||||
it('supports setting and accessing value \'0\' via get', () => {
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
transferState.set(TEST_KEY, 0);
|
||||
expect(transferState.get(TEST_KEY, 20)).toBe(0);
|
||||
expect(transferState.hasKey(TEST_KEY)).toBe(true);
|
||||
});
|
||||
|
||||
it('supports setting and accessing value \'false\' via get', () => {
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
transferState.set(TEST_KEY, false);
|
||||
expect(transferState.get(TEST_KEY, true)).toBe(false);
|
||||
expect(transferState.hasKey(TEST_KEY)).toBe(true);
|
||||
});
|
||||
|
||||
it('supports setting and accessing value \'null\' via get', () => {
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
transferState.set(TEST_KEY, null);
|
||||
expect(transferState.get(TEST_KEY, 20 as any)).toBe(null);
|
||||
expect(transferState.hasKey(TEST_KEY)).toBe(true);
|
||||
});
|
||||
|
||||
it('supports removing keys', () => {
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
transferState.set(TEST_KEY, 20);
|
||||
transferState.remove(TEST_KEY);
|
||||
expect(transferState.get(TEST_KEY, 0)).toBe(0);
|
||||
expect(transferState.hasKey(TEST_KEY)).toBe(false);
|
||||
});
|
||||
|
||||
it('supports serialization using toJson()', () => {
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
transferState.set(TEST_KEY, 20);
|
||||
expect(transferState.toJson()).toBe('{"test":20}');
|
||||
});
|
||||
|
||||
it('calls onSerialize callbacks when calling toJson()', () => {
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
transferState.set(TEST_KEY, 20);
|
||||
|
||||
let value = 'initial';
|
||||
transferState.onSerialize(DELAYED_KEY, () => value);
|
||||
value = 'changed';
|
||||
|
||||
expect(transferState.toJson()).toBe('{"test":20,"delayed":"changed"}');
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({appId: APP_ID}),
|
||||
BrowserTransferStateModule,
|
||||
]
|
||||
});
|
||||
doc = TestBed.inject(DOCUMENT);
|
||||
});
|
||||
|
||||
describe('escape/unescape', () => {
|
||||
it('works with all escaped characters', () => {
|
||||
const testString = '</script><script>alert(\'Hello&\' + "World");';
|
||||
const testObj = {testString};
|
||||
const escaped = escapeHtml(JSON.stringify(testObj));
|
||||
expect(escaped).toBe(
|
||||
'{&q;testString&q;:&q;&l;/script&g;&l;script&g;' +
|
||||
'alert(&s;Hello&a;&s; + \\&q;World\\&q;);&q;}');
|
||||
|
||||
const unescapedObj = JSON.parse(unescapeHtml(escaped));
|
||||
expect(unescapedObj['testString']).toBe(testString);
|
||||
});
|
||||
afterEach(() => {
|
||||
removeScriptTag(doc, APP_ID + '-state');
|
||||
});
|
||||
|
||||
it('is initialized from script tag', () => {
|
||||
addScriptTag(doc, APP_ID, {test: 10});
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
expect(transferState.get(TEST_KEY, 0)).toBe(10);
|
||||
});
|
||||
|
||||
it('is initialized to empty state if script tag not found', () => {
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
expect(transferState.get(TEST_KEY, 0)).toBe(0);
|
||||
});
|
||||
|
||||
it('supports adding new keys using set', () => {
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
transferState.set(TEST_KEY, 20);
|
||||
expect(transferState.get(TEST_KEY, 0)).toBe(20);
|
||||
expect(transferState.hasKey(TEST_KEY)).toBe(true);
|
||||
});
|
||||
|
||||
it('supports setting and accessing value \'0\' via get', () => {
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
transferState.set(TEST_KEY, 0);
|
||||
expect(transferState.get(TEST_KEY, 20)).toBe(0);
|
||||
expect(transferState.hasKey(TEST_KEY)).toBe(true);
|
||||
});
|
||||
|
||||
it('supports setting and accessing value \'false\' via get', () => {
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
transferState.set(TEST_KEY, false);
|
||||
expect(transferState.get(TEST_KEY, true)).toBe(false);
|
||||
expect(transferState.hasKey(TEST_KEY)).toBe(true);
|
||||
});
|
||||
|
||||
it('supports setting and accessing value \'null\' via get', () => {
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
transferState.set(TEST_KEY, null);
|
||||
expect(transferState.get(TEST_KEY, 20 as any)).toBe(null);
|
||||
expect(transferState.hasKey(TEST_KEY)).toBe(true);
|
||||
});
|
||||
|
||||
it('supports removing keys', () => {
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
transferState.set(TEST_KEY, 20);
|
||||
transferState.remove(TEST_KEY);
|
||||
expect(transferState.get(TEST_KEY, 0)).toBe(0);
|
||||
expect(transferState.hasKey(TEST_KEY)).toBe(false);
|
||||
});
|
||||
|
||||
it('supports serialization using toJson()', () => {
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
transferState.set(TEST_KEY, 20);
|
||||
expect(transferState.toJson()).toBe('{"test":20}');
|
||||
});
|
||||
|
||||
it('calls onSerialize callbacks when calling toJson()', () => {
|
||||
const transferState: TransferState = TestBed.inject(TransferState);
|
||||
transferState.set(TEST_KEY, 20);
|
||||
|
||||
let value = 'initial';
|
||||
transferState.onSerialize(DELAYED_KEY, () => value);
|
||||
value = 'changed';
|
||||
|
||||
expect(transferState.toJson()).toBe('{"test":20,"delayed":"changed"}');
|
||||
});
|
||||
});
|
||||
|
||||
describe('escape/unescape', () => {
|
||||
it('works with all escaped characters', () => {
|
||||
const testString = '</script><script>alert(\'Hello&\' + "World");';
|
||||
const testObj = {testString};
|
||||
const escaped = escapeHtml(JSON.stringify(testObj));
|
||||
expect(escaped).toBe(
|
||||
'{&q;testString&q;:&q;&l;/script&g;&l;script&g;' +
|
||||
'alert(&s;Hello&a;&s; + \\&q;World\\&q;);&q;}');
|
||||
|
||||
const unescapedObj = JSON.parse(unescapeHtml(escaped));
|
||||
expect(unescapedObj['testString']).toBe(testString);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
@ -11,7 +11,6 @@ import {BrowserDetection} from '../testing/src/browser_util';
|
||||
|
||||
{
|
||||
describe('BrowserDetection', () => {
|
||||
|
||||
const browsers = [
|
||||
{
|
||||
name: 'Chrome',
|
||||
@ -224,7 +223,7 @@ import {BrowserDetection} from '../testing/src/browser_util';
|
||||
];
|
||||
|
||||
browsers.forEach((browser: {[key: string]: any}) => {
|
||||
it(`should detect ${browser[ 'name']}`, () => {
|
||||
it(`should detect ${browser['name']}`, () => {
|
||||
const bd = new BrowserDetection(<string>browser['ua']);
|
||||
expect(bd.isFirefox).toBe(browser['isFirefox']);
|
||||
expect(bd.isAndroid).toBe(browser['isAndroid']);
|
||||
|
@ -14,361 +14,393 @@ import {EventManager, EventManagerPlugin} from '@angular/platform-browser/src/do
|
||||
import {createMouseEvent, el} from '../../../testing/src/browser_util';
|
||||
|
||||
(function() {
|
||||
if (isNode) return;
|
||||
let domEventPlugin: DomEventsPlugin;
|
||||
let doc: any;
|
||||
let zone: NgZone;
|
||||
if (isNode) return;
|
||||
let domEventPlugin: DomEventsPlugin;
|
||||
let doc: any;
|
||||
let zone: NgZone;
|
||||
|
||||
describe('EventManager', () => {
|
||||
beforeEach(() => {
|
||||
doc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
|
||||
zone = new NgZone({});
|
||||
domEventPlugin = new DomEventsPlugin(doc);
|
||||
describe('EventManager', () => {
|
||||
beforeEach(() => {
|
||||
doc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
|
||||
zone = new NgZone({});
|
||||
domEventPlugin = new DomEventsPlugin(doc);
|
||||
});
|
||||
|
||||
it('should delegate event bindings to plugins that are passed in from the most generic one to the most specific one',
|
||||
() => {
|
||||
const element = el('<div></div>');
|
||||
const handler = (e: any /** TODO #9100 */) => e;
|
||||
const plugin = new FakeEventManagerPlugin(doc, ['click']);
|
||||
const manager = new EventManager([domEventPlugin, plugin], new FakeNgZone());
|
||||
manager.addEventListener(element, 'click', handler);
|
||||
expect(plugin.eventHandler['click']).toBe(handler);
|
||||
});
|
||||
|
||||
it('should delegate event bindings to the first plugin supporting the event', () => {
|
||||
const element = el('<div></div>');
|
||||
const clickHandler = (e: any /** TODO #9100 */) => e;
|
||||
const dblClickHandler = (e: any /** TODO #9100 */) => e;
|
||||
const plugin1 = new FakeEventManagerPlugin(doc, ['dblclick']);
|
||||
const plugin2 = new FakeEventManagerPlugin(doc, ['click', 'dblclick']);
|
||||
const manager = new EventManager([plugin2, plugin1], new FakeNgZone());
|
||||
manager.addEventListener(element, 'click', clickHandler);
|
||||
manager.addEventListener(element, 'dblclick', dblClickHandler);
|
||||
expect(plugin2.eventHandler['click']).toBe(clickHandler);
|
||||
expect(plugin1.eventHandler['dblclick']).toBe(dblClickHandler);
|
||||
});
|
||||
|
||||
it('should throw when no plugin can handle the event', () => {
|
||||
const element = el('<div></div>');
|
||||
const plugin = new FakeEventManagerPlugin(doc, ['dblclick']);
|
||||
const manager = new EventManager([plugin], new FakeNgZone());
|
||||
expect(() => manager.addEventListener(element, 'click', null!))
|
||||
.toThrowError('No event manager plugin found for event click');
|
||||
});
|
||||
|
||||
it('events are caught when fired from a child', () => {
|
||||
const element = el('<div><div></div></div>');
|
||||
// Workaround for https://bugs.webkit.org/show_bug.cgi?id=122755
|
||||
doc.body.appendChild(element);
|
||||
|
||||
const child = element.firstChild as Element;
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvent: any /** TODO #9100 */ = null;
|
||||
const handler = (e: any /** TODO #9100 */) => {
|
||||
receivedEvent = e;
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
manager.addEventListener(element, 'click', handler);
|
||||
getDOM().dispatchEvent(child, dispatchedEvent);
|
||||
|
||||
expect(receivedEvent).toBe(dispatchedEvent);
|
||||
});
|
||||
|
||||
it('should add and remove global event listeners', () => {
|
||||
const element = el('<div><div></div></div>');
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvent: any /** TODO #9100 */ = null;
|
||||
const handler = (e: any /** TODO #9100 */) => {
|
||||
receivedEvent = e;
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
|
||||
const remover = manager.addGlobalEventListener('document', 'click', handler);
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvent).toBe(dispatchedEvent);
|
||||
|
||||
receivedEvent = null;
|
||||
remover();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvent).toBe(null);
|
||||
});
|
||||
|
||||
it('should keep zone when addEventListener', () => {
|
||||
const Zone = (window as any)['Zone'];
|
||||
|
||||
const element = el('<div><div></div></div>');
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvent: any /** TODO #9100 */ = null;
|
||||
let receivedZone: any = null;
|
||||
const handler = (e: any /** TODO #9100 */) => {
|
||||
receivedEvent = e;
|
||||
receivedZone = Zone.current;
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
|
||||
let remover: any = null;
|
||||
Zone.root.run(() => {
|
||||
remover = manager.addEventListener(element, 'click', handler);
|
||||
});
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvent).toBe(dispatchedEvent);
|
||||
expect(receivedZone.name).toBe(Zone.root.name);
|
||||
|
||||
it('should delegate event bindings to plugins that are passed in from the most generic one to the most specific one',
|
||||
() => {
|
||||
const element = el('<div></div>');
|
||||
const handler = (e: any /** TODO #9100 */) => e;
|
||||
const plugin = new FakeEventManagerPlugin(doc, ['click']);
|
||||
const manager = new EventManager([domEventPlugin, plugin], new FakeNgZone());
|
||||
manager.addEventListener(element, 'click', handler);
|
||||
expect(plugin.eventHandler['click']).toBe(handler);
|
||||
});
|
||||
receivedEvent = null;
|
||||
remover && remover();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvent).toBe(null);
|
||||
});
|
||||
|
||||
it('should delegate event bindings to the first plugin supporting the event', () => {
|
||||
const element = el('<div></div>');
|
||||
const clickHandler = (e: any /** TODO #9100 */) => e;
|
||||
const dblClickHandler = (e: any /** TODO #9100 */) => e;
|
||||
const plugin1 = new FakeEventManagerPlugin(doc, ['dblclick']);
|
||||
const plugin2 = new FakeEventManagerPlugin(doc, ['click', 'dblclick']);
|
||||
const manager = new EventManager([plugin2, plugin1], new FakeNgZone());
|
||||
manager.addEventListener(element, 'click', clickHandler);
|
||||
manager.addEventListener(element, 'dblclick', dblClickHandler);
|
||||
expect(plugin2.eventHandler['click']).toBe(clickHandler);
|
||||
expect(plugin1.eventHandler['dblclick']).toBe(dblClickHandler);
|
||||
it('should keep zone when addEventListener multiple times', () => {
|
||||
const Zone = (window as any)['Zone'];
|
||||
|
||||
const element = el('<div><div></div></div>');
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvents: any[] /** TODO #9100 */ = [];
|
||||
let receivedZones: any[] = [];
|
||||
const handler1 = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
};
|
||||
const handler2 = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
|
||||
let remover1: any = null;
|
||||
let remover2: any = null;
|
||||
Zone.root.run(() => {
|
||||
remover1 = manager.addEventListener(element, 'click', handler1);
|
||||
});
|
||||
|
||||
it('should throw when no plugin can handle the event', () => {
|
||||
const element = el('<div></div>');
|
||||
const plugin = new FakeEventManagerPlugin(doc, ['dblclick']);
|
||||
const manager = new EventManager([plugin], new FakeNgZone());
|
||||
expect(() => manager.addEventListener(element, 'click', null !))
|
||||
.toThrowError('No event manager plugin found for event click');
|
||||
Zone.root.fork({name: 'test'}).run(() => {
|
||||
remover2 = manager.addEventListener(element, 'click', handler2);
|
||||
});
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([dispatchedEvent, dispatchedEvent]);
|
||||
expect(receivedZones).toEqual([Zone.root.name, 'test']);
|
||||
|
||||
it('events are caught when fired from a child', () => {
|
||||
const element = el('<div><div></div></div>');
|
||||
// Workaround for https://bugs.webkit.org/show_bug.cgi?id=122755
|
||||
doc.body.appendChild(element);
|
||||
receivedEvents = [];
|
||||
remover1 && remover1();
|
||||
remover2 && remover2();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([]);
|
||||
});
|
||||
|
||||
const child = element.firstChild as Element;
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvent: any /** TODO #9100 */ = null;
|
||||
const handler = (e: any /** TODO #9100 */) => { receivedEvent = e; };
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
manager.addEventListener(element, 'click', handler);
|
||||
getDOM().dispatchEvent(child, dispatchedEvent);
|
||||
it('should support event.stopImmediatePropagation', () => {
|
||||
const Zone = (window as any)['Zone'];
|
||||
|
||||
expect(receivedEvent).toBe(dispatchedEvent);
|
||||
const element = el('<div><div></div></div>');
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvents: any[] /** TODO #9100 */ = [];
|
||||
let receivedZones: any[] = [];
|
||||
const handler1 = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
e.stopImmediatePropagation();
|
||||
};
|
||||
const handler2 = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
|
||||
let remover1: any = null;
|
||||
let remover2: any = null;
|
||||
Zone.root.run(() => {
|
||||
remover1 = manager.addEventListener(element, 'click', handler1);
|
||||
});
|
||||
|
||||
it('should add and remove global event listeners', () => {
|
||||
const element = el('<div><div></div></div>');
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvent: any /** TODO #9100 */ = null;
|
||||
const handler = (e: any /** TODO #9100 */) => { receivedEvent = e; };
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
|
||||
const remover = manager.addGlobalEventListener('document', 'click', handler);
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvent).toBe(dispatchedEvent);
|
||||
|
||||
receivedEvent = null;
|
||||
remover();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvent).toBe(null);
|
||||
Zone.root.fork({name: 'test'}).run(() => {
|
||||
remover2 = manager.addEventListener(element, 'click', handler2);
|
||||
});
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([dispatchedEvent]);
|
||||
expect(receivedZones).toEqual([Zone.root.name]);
|
||||
|
||||
it('should keep zone when addEventListener', () => {
|
||||
const Zone = (window as any)['Zone'];
|
||||
receivedEvents = [];
|
||||
remover1 && remover1();
|
||||
remover2 && remover2();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([]);
|
||||
});
|
||||
|
||||
const element = el('<div><div></div></div>');
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvent: any /** TODO #9100 */ = null;
|
||||
let receivedZone: any = null;
|
||||
const handler = (e: any /** TODO #9100 */) => {
|
||||
receivedEvent = e;
|
||||
receivedZone = Zone.current;
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
it('should handle event correctly when one handler remove itself ', () => {
|
||||
const Zone = (window as any)['Zone'];
|
||||
|
||||
let remover: any = null;
|
||||
Zone.root.run(() => { remover = manager.addEventListener(element, 'click', handler); });
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvent).toBe(dispatchedEvent);
|
||||
expect(receivedZone.name).toBe(Zone.root.name);
|
||||
|
||||
receivedEvent = null;
|
||||
remover && remover();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvent).toBe(null);
|
||||
});
|
||||
|
||||
it('should keep zone when addEventListener multiple times', () => {
|
||||
const Zone = (window as any)['Zone'];
|
||||
|
||||
const element = el('<div><div></div></div>');
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvents: any[] /** TODO #9100 */ = [];
|
||||
let receivedZones: any[] = [];
|
||||
const handler1 = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
};
|
||||
const handler2 = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
|
||||
let remover1: any = null;
|
||||
let remover2: any = null;
|
||||
Zone.root.run(() => { remover1 = manager.addEventListener(element, 'click', handler1); });
|
||||
Zone.root.fork({name: 'test'}).run(() => {
|
||||
remover2 = manager.addEventListener(element, 'click', handler2);
|
||||
});
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([dispatchedEvent, dispatchedEvent]);
|
||||
expect(receivedZones).toEqual([Zone.root.name, 'test']);
|
||||
|
||||
receivedEvents = [];
|
||||
const element = el('<div><div></div></div>');
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvents: any[] /** TODO #9100 */ = [];
|
||||
let receivedZones: any[] = [];
|
||||
let remover1: any = null;
|
||||
let remover2: any = null;
|
||||
const handler1 = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
remover1 && remover1();
|
||||
remover2 && remover2();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([]);
|
||||
};
|
||||
const handler2 = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
|
||||
Zone.root.run(() => {
|
||||
remover1 = manager.addEventListener(element, 'click', handler1);
|
||||
});
|
||||
|
||||
it('should support event.stopImmediatePropagation', () => {
|
||||
const Zone = (window as any)['Zone'];
|
||||
|
||||
const element = el('<div><div></div></div>');
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvents: any[] /** TODO #9100 */ = [];
|
||||
let receivedZones: any[] = [];
|
||||
const handler1 = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
e.stopImmediatePropagation();
|
||||
};
|
||||
const handler2 = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
|
||||
let remover1: any = null;
|
||||
let remover2: any = null;
|
||||
Zone.root.run(() => { remover1 = manager.addEventListener(element, 'click', handler1); });
|
||||
Zone.root.fork({name: 'test'}).run(() => {
|
||||
remover2 = manager.addEventListener(element, 'click', handler2);
|
||||
});
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([dispatchedEvent]);
|
||||
expect(receivedZones).toEqual([Zone.root.name]);
|
||||
|
||||
receivedEvents = [];
|
||||
remover1 && remover1();
|
||||
remover2 && remover2();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([]);
|
||||
Zone.root.fork({name: 'test'}).run(() => {
|
||||
remover2 = manager.addEventListener(element, 'click', handler2);
|
||||
});
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([dispatchedEvent, dispatchedEvent]);
|
||||
expect(receivedZones).toEqual([Zone.root.name, 'test']);
|
||||
|
||||
it('should handle event correctly when one handler remove itself ', () => {
|
||||
const Zone = (window as any)['Zone'];
|
||||
receivedEvents = [];
|
||||
remover1 && remover1();
|
||||
remover2 && remover2();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([]);
|
||||
});
|
||||
|
||||
const element = el('<div><div></div></div>');
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvents: any[] /** TODO #9100 */ = [];
|
||||
let receivedZones: any[] = [];
|
||||
let remover1: any = null;
|
||||
let remover2: any = null;
|
||||
const handler1 = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
remover1 && remover1();
|
||||
};
|
||||
const handler2 = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
it('should only add same callback once when addEventListener', () => {
|
||||
const Zone = (window as any)['Zone'];
|
||||
|
||||
Zone.root.run(() => { remover1 = manager.addEventListener(element, 'click', handler1); });
|
||||
Zone.root.fork({name: 'test'}).run(() => {
|
||||
remover2 = manager.addEventListener(element, 'click', handler2);
|
||||
});
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([dispatchedEvent, dispatchedEvent]);
|
||||
expect(receivedZones).toEqual([Zone.root.name, 'test']);
|
||||
const element = el('<div><div></div></div>');
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvents: any[] /** TODO #9100 */ = [];
|
||||
let receivedZones: any[] = [];
|
||||
const handler = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
|
||||
receivedEvents = [];
|
||||
remover1 && remover1();
|
||||
remover2 && remover2();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([]);
|
||||
let remover1: any = null;
|
||||
let remover2: any = null;
|
||||
Zone.root.run(() => {
|
||||
remover1 = manager.addEventListener(element, 'click', handler);
|
||||
});
|
||||
|
||||
it('should only add same callback once when addEventListener', () => {
|
||||
const Zone = (window as any)['Zone'];
|
||||
|
||||
const element = el('<div><div></div></div>');
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvents: any[] /** TODO #9100 */ = [];
|
||||
let receivedZones: any[] = [];
|
||||
const handler = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
|
||||
let remover1: any = null;
|
||||
let remover2: any = null;
|
||||
Zone.root.run(() => { remover1 = manager.addEventListener(element, 'click', handler); });
|
||||
Zone.root.fork({name: 'test'}).run(() => {
|
||||
remover2 = manager.addEventListener(element, 'click', handler);
|
||||
});
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([dispatchedEvent]);
|
||||
expect(receivedZones).toEqual([Zone.root.name]);
|
||||
|
||||
receivedEvents = [];
|
||||
remover1 && remover1();
|
||||
remover2 && remover2();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([]);
|
||||
Zone.root.fork({name: 'test'}).run(() => {
|
||||
remover2 = manager.addEventListener(element, 'click', handler);
|
||||
});
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([dispatchedEvent]);
|
||||
expect(receivedZones).toEqual([Zone.root.name]);
|
||||
|
||||
it('should be able to remove event listener which was added inside of ngZone', () => {
|
||||
const Zone = (window as any)['Zone'];
|
||||
receivedEvents = [];
|
||||
remover1 && remover1();
|
||||
remover2 && remover2();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([]);
|
||||
});
|
||||
|
||||
const element = el('<div><div></div></div>');
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvents: any[] /** TODO #9100 */ = [];
|
||||
let receivedZones: any[] = [];
|
||||
const handler1 = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
};
|
||||
const handler2 = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
it('should be able to remove event listener which was added inside of ngZone', () => {
|
||||
const Zone = (window as any)['Zone'];
|
||||
|
||||
let remover1: any = null;
|
||||
let remover2: any = null;
|
||||
// handler1 is added in root zone
|
||||
Zone.root.run(() => { remover1 = manager.addEventListener(element, 'click', handler1); });
|
||||
// handler2 is added in 'angular' zone
|
||||
Zone.root.fork({name: 'fakeAngularZone', properties: {isAngularZone: true}}).run(() => {
|
||||
remover2 = manager.addEventListener(element, 'click', handler2);
|
||||
});
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([dispatchedEvent, dispatchedEvent]);
|
||||
expect(receivedZones).toEqual([Zone.root.name, 'fakeAngularZone']);
|
||||
const element = el('<div><div></div></div>');
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvents: any[] /** TODO #9100 */ = [];
|
||||
let receivedZones: any[] = [];
|
||||
const handler1 = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
};
|
||||
const handler2 = (e: any /** TODO #9100 */) => {
|
||||
receivedEvents.push(e);
|
||||
receivedZones.push(Zone.current.name);
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
|
||||
receivedEvents = [];
|
||||
remover1 && remover1();
|
||||
remover2 && remover2();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
// handler1 and handler2 are added in different zone
|
||||
// one is angular zone, the other is not
|
||||
// should still be able to remove them correctly
|
||||
expect(receivedEvents).toEqual([]);
|
||||
let remover1: any = null;
|
||||
let remover2: any = null;
|
||||
// handler1 is added in root zone
|
||||
Zone.root.run(() => {
|
||||
remover1 = manager.addEventListener(element, 'click', handler1);
|
||||
});
|
||||
|
||||
it('should run blackListedEvents handler outside of ngZone', () => {
|
||||
const Zone = (window as any)['Zone'];
|
||||
const element = el('<div><div></div></div>');
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('scroll');
|
||||
let receivedEvent: any /** TODO #9100 */ = null;
|
||||
let receivedZone: any = null;
|
||||
const handler = (e: any /** TODO #9100 */) => {
|
||||
receivedEvent = e;
|
||||
receivedZone = Zone.current;
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
|
||||
let remover = manager.addEventListener(element, 'scroll', handler);
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvent).toBe(dispatchedEvent);
|
||||
expect(receivedZone.name).not.toEqual('angular');
|
||||
|
||||
receivedEvent = null;
|
||||
remover && remover();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvent).toBe(null);
|
||||
// handler2 is added in 'angular' zone
|
||||
Zone.root.fork({name: 'fakeAngularZone', properties: {isAngularZone: true}}).run(() => {
|
||||
remover2 = manager.addEventListener(element, 'click', handler2);
|
||||
});
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvents).toEqual([dispatchedEvent, dispatchedEvent]);
|
||||
expect(receivedZones).toEqual([Zone.root.name, 'fakeAngularZone']);
|
||||
|
||||
it('should only trigger one Change detection when bubbling', (done: DoneFn) => {
|
||||
doc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
|
||||
zone = new NgZone({shouldCoalesceEventChangeDetection: true});
|
||||
domEventPlugin = new DomEventsPlugin(doc);
|
||||
const element = el('<div></div>');
|
||||
const child = el('<div></div>');
|
||||
element.appendChild(child);
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvents: any = [];
|
||||
let stables: any = [];
|
||||
const handler = (e: any) => { receivedEvents.push(e); };
|
||||
const manager = new EventManager([domEventPlugin], zone);
|
||||
let removerChild: any;
|
||||
let removerParent: any;
|
||||
receivedEvents = [];
|
||||
remover1 && remover1();
|
||||
remover2 && remover2();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
// handler1 and handler2 are added in different zone
|
||||
// one is angular zone, the other is not
|
||||
// should still be able to remove them correctly
|
||||
expect(receivedEvents).toEqual([]);
|
||||
});
|
||||
|
||||
zone.run(() => {
|
||||
removerChild = manager.addEventListener(child, 'click', handler);
|
||||
removerParent = manager.addEventListener(element, 'click', handler);
|
||||
});
|
||||
zone.onStable.subscribe((isStable: any) => { stables.push(isStable); });
|
||||
getDOM().dispatchEvent(child, dispatchedEvent);
|
||||
requestAnimationFrame(() => {
|
||||
expect(receivedEvents.length).toBe(2);
|
||||
expect(stables.length).toBe(1);
|
||||
it('should run blackListedEvents handler outside of ngZone', () => {
|
||||
const Zone = (window as any)['Zone'];
|
||||
const element = el('<div><div></div></div>');
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('scroll');
|
||||
let receivedEvent: any /** TODO #9100 */ = null;
|
||||
let receivedZone: any = null;
|
||||
const handler = (e: any /** TODO #9100 */) => {
|
||||
receivedEvent = e;
|
||||
receivedZone = Zone.current;
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
|
||||
removerChild && removerChild();
|
||||
removerParent && removerParent();
|
||||
done();
|
||||
});
|
||||
let remover = manager.addEventListener(element, 'scroll', handler);
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvent).toBe(dispatchedEvent);
|
||||
expect(receivedZone.name).not.toEqual('angular');
|
||||
|
||||
receivedEvent = null;
|
||||
remover && remover();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvent).toBe(null);
|
||||
});
|
||||
|
||||
it('should only trigger one Change detection when bubbling', (done: DoneFn) => {
|
||||
doc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
|
||||
zone = new NgZone({shouldCoalesceEventChangeDetection: true});
|
||||
domEventPlugin = new DomEventsPlugin(doc);
|
||||
const element = el('<div></div>');
|
||||
const child = el('<div></div>');
|
||||
element.appendChild(child);
|
||||
doc.body.appendChild(element);
|
||||
const dispatchedEvent = createMouseEvent('click');
|
||||
let receivedEvents: any = [];
|
||||
let stables: any = [];
|
||||
const handler = (e: any) => {
|
||||
receivedEvents.push(e);
|
||||
};
|
||||
const manager = new EventManager([domEventPlugin], zone);
|
||||
let removerChild: any;
|
||||
let removerParent: any;
|
||||
|
||||
zone.run(() => {
|
||||
removerChild = manager.addEventListener(child, 'click', handler);
|
||||
removerParent = manager.addEventListener(element, 'click', handler);
|
||||
});
|
||||
zone.onStable.subscribe((isStable: any) => {
|
||||
stables.push(isStable);
|
||||
});
|
||||
getDOM().dispatchEvent(child, dispatchedEvent);
|
||||
requestAnimationFrame(() => {
|
||||
expect(receivedEvents.length).toBe(2);
|
||||
expect(stables.length).toBe(1);
|
||||
|
||||
removerChild && removerChild();
|
||||
removerParent && removerParent();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
/** @internal */
|
||||
class FakeEventManagerPlugin extends EventManagerPlugin {
|
||||
eventHandler: {[event: string]: Function} = {};
|
||||
|
||||
constructor(doc: any, public supportedEvents: string[]) { super(doc); }
|
||||
constructor(doc: any, public supportedEvents: string[]) {
|
||||
super(doc);
|
||||
}
|
||||
|
||||
supports(eventName: string): boolean { return this.supportedEvents.indexOf(eventName) > -1; }
|
||||
supports(eventName: string): boolean {
|
||||
return this.supportedEvents.indexOf(eventName) > -1;
|
||||
}
|
||||
|
||||
addEventListener(element: any, eventName: string, handler: Function) {
|
||||
this.eventHandler[eventName] = handler;
|
||||
return () => { delete this.eventHandler[eventName]; };
|
||||
return () => {
|
||||
delete this.eventHandler[eventName];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class FakeNgZone extends NgZone {
|
||||
constructor() { super({enableLongStackTrace: false, shouldCoalesceEventChangeDetection: true}); }
|
||||
run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T { return fn(); }
|
||||
runOutsideAngular(fn: Function) { return fn(); }
|
||||
constructor() {
|
||||
super({enableLongStackTrace: false, shouldCoalesceEventChangeDetection: true});
|
||||
}
|
||||
run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T {
|
||||
return fn();
|
||||
}
|
||||
runOutsideAngular(fn: Function) {
|
||||
return fn();
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, EventEmitter, Injector, Input, NgModule, Output, Renderer2, ViewEncapsulation, destroyPlatform} from '@angular/core';
|
||||
import {Component, destroyPlatform, EventEmitter, Injector, Input, NgModule, Output, Renderer2, ViewEncapsulation} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
|
||||
@ -15,14 +15,15 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
if (browserDetection.supportsShadowDom) {
|
||||
describe('ShadowDOM Support', () => {
|
||||
|
||||
let testContainer: HTMLDivElement;
|
||||
|
||||
beforeEach(() => { TestBed.configureTestingModule({imports: [TestModule]}); });
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({imports: [TestModule]});
|
||||
});
|
||||
|
||||
it('should attach and use a shadowRoot when ViewEncapsulation.Native is set', () => {
|
||||
const compEl = TestBed.createComponent(ShadowComponent).nativeElement;
|
||||
expect(compEl.shadowRoot !.textContent).toEqual('Hello World');
|
||||
expect(compEl.shadowRoot!.textContent).toEqual('Hello World');
|
||||
});
|
||||
|
||||
it('should use the shadow root to encapsulate styles', () => {
|
||||
@ -40,10 +41,10 @@ if (browserDetection.supportsShadowDom) {
|
||||
const el = TestBed.createComponent(ShadowSlotComponent).nativeElement;
|
||||
const projectedContent = document.createTextNode('Hello Slot!');
|
||||
el.appendChild(projectedContent);
|
||||
const slot = el.shadowRoot !.querySelector('slot');
|
||||
const slot = el.shadowRoot!.querySelector('slot');
|
||||
|
||||
expect(slot !.assignedNodes().length).toBe(1);
|
||||
expect(slot !.assignedNodes()[0].textContent).toBe('Hello Slot!');
|
||||
expect(slot!.assignedNodes().length).toBe(1);
|
||||
expect(slot!.assignedNodes()[0].textContent).toBe('Hello Slot!');
|
||||
});
|
||||
|
||||
it('should allow the usage of named <slot> elements', () => {
|
||||
@ -65,16 +66,16 @@ if (browserDetection.supportsShadowDom) {
|
||||
el.appendChild(articleContent);
|
||||
el.appendChild(articleSubcontent);
|
||||
|
||||
const headerSlot = el.shadowRoot !.querySelector('slot[name=header]') as HTMLSlotElement;
|
||||
const articleSlot = el.shadowRoot !.querySelector('slot[name=article]') as HTMLSlotElement;
|
||||
const headerSlot = el.shadowRoot!.querySelector('slot[name=header]') as HTMLSlotElement;
|
||||
const articleSlot = el.shadowRoot!.querySelector('slot[name=article]') as HTMLSlotElement;
|
||||
|
||||
expect(headerSlot !.assignedNodes().length).toBe(1);
|
||||
expect(headerSlot !.assignedNodes()[0].textContent).toBe('Header Text!');
|
||||
expect(headerSlot!.assignedNodes().length).toBe(1);
|
||||
expect(headerSlot!.assignedNodes()[0].textContent).toBe('Header Text!');
|
||||
expect(headerContent.assignedSlot).toBe(headerSlot);
|
||||
|
||||
expect(articleSlot !.assignedNodes().length).toBe(2);
|
||||
expect(articleSlot !.assignedNodes()[0].textContent).toBe('Article Text!');
|
||||
expect(articleSlot !.assignedNodes()[1].textContent).toBe('Article Subtext!');
|
||||
expect(articleSlot!.assignedNodes().length).toBe(2);
|
||||
expect(articleSlot!.assignedNodes()[0].textContent).toBe('Article Text!');
|
||||
expect(articleSlot!.assignedNodes()[1].textContent).toBe('Article Subtext!');
|
||||
expect(articleContent.assignedSlot).toBe(articleSlot);
|
||||
expect(articleSubcontent.assignedSlot).toBe(articleSlot);
|
||||
});
|
||||
|
@ -7,8 +7,8 @@
|
||||
*/
|
||||
|
||||
import {CompilerConfig, ResourceLoader} from '@angular/compiler';
|
||||
import {CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, ComponentFactoryResolver, Directive, Inject, Injectable, InjectionToken, Injector, Input, NgModule, Optional, Pipe, SkipSelf, ɵstringify as stringify} from '@angular/core';
|
||||
import {TestBed, async, fakeAsync, getTestBed, inject, tick, withModule} from '@angular/core/testing';
|
||||
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 {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {ivyEnabled, modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing';
|
||||
|
||||
@ -18,7 +18,9 @@ import {ivyEnabled, modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/priv
|
||||
@Injectable()
|
||||
class ChildComp {
|
||||
childBinding: string;
|
||||
constructor() { this.childBinding = 'Child'; }
|
||||
constructor() {
|
||||
this.childBinding = 'Child';
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'child-comp', template: `<span>Mock</span>`})
|
||||
@ -52,12 +54,16 @@ class ChildChildComp {
|
||||
@Injectable()
|
||||
class ChildWithChildComp {
|
||||
childBinding: string;
|
||||
constructor() { this.childBinding = 'Child'; }
|
||||
constructor() {
|
||||
this.childBinding = 'Child';
|
||||
}
|
||||
}
|
||||
|
||||
class FancyService {
|
||||
value: string = 'real value';
|
||||
getAsyncValue() { return Promise.resolve('async value'); }
|
||||
getAsyncValue() {
|
||||
return Promise.resolve('async value');
|
||||
}
|
||||
getTimeoutValue() {
|
||||
return new Promise<string>((resolve, reject) => setTimeout(() => resolve('timeout value'), 10));
|
||||
}
|
||||
@ -88,13 +94,14 @@ class TestViewProvidersComp {
|
||||
@Directive({selector: '[someDir]', host: {'[title]': 'someDir'}})
|
||||
class SomeDirective {
|
||||
// TODO(issue/24571): remove '!'.
|
||||
@Input()
|
||||
someDir !: string;
|
||||
@Input() someDir!: string;
|
||||
}
|
||||
|
||||
@Pipe({name: 'somePipe'})
|
||||
class SomePipe {
|
||||
transform(value: string) { return `transformed ${value}`; }
|
||||
transform(value: string) {
|
||||
return `transformed ${value}`;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'comp', template: `<div [someDir]="'someValue' | somePipe"></div>`})
|
||||
@ -120,11 +127,17 @@ const bTok = new InjectionToken<string>('b');
|
||||
describe('using the async helper with context passing', () => {
|
||||
type TestContext = {actuallyDone: boolean};
|
||||
|
||||
beforeEach(function(this: TestContext) { this.actuallyDone = false; });
|
||||
beforeEach(function(this: TestContext) {
|
||||
this.actuallyDone = false;
|
||||
});
|
||||
|
||||
afterEach(function(this: TestContext) { expect(this.actuallyDone).toEqual(true); });
|
||||
afterEach(function(this: TestContext) {
|
||||
expect(this.actuallyDone).toEqual(true);
|
||||
});
|
||||
|
||||
it('should run normal tests', function(this: TestContext) { this.actuallyDone = true; });
|
||||
it('should run normal tests', function(this: TestContext) {
|
||||
this.actuallyDone = true;
|
||||
});
|
||||
|
||||
it('should run normal async tests', function(this: TestContext, done) {
|
||||
setTimeout(() => {
|
||||
@ -133,8 +146,9 @@ const bTok = new InjectionToken<string>('b');
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should run async tests with tasks',
|
||||
async(function(this: TestContext) { setTimeout(() => this.actuallyDone = true, 0); }));
|
||||
it('should run async tests with tasks', async(function(this: TestContext) {
|
||||
setTimeout(() => this.actuallyDone = true, 0);
|
||||
}));
|
||||
|
||||
it('should run async tests with promises', async(function(this: TestContext) {
|
||||
const p = new Promise((resolve, reject) => setTimeout(resolve, 10));
|
||||
@ -149,18 +163,26 @@ const bTok = new InjectionToken<string>('b');
|
||||
|
||||
type TestContext = {contextModified: boolean};
|
||||
|
||||
beforeEach(function(this: TestContext) { this.contextModified = false; });
|
||||
beforeEach(function(this: TestContext) {
|
||||
this.contextModified = false;
|
||||
});
|
||||
|
||||
afterEach(function(this: TestContext) { expect(this.contextModified).toEqual(true); });
|
||||
afterEach(function(this: TestContext) {
|
||||
expect(this.contextModified).toEqual(true);
|
||||
});
|
||||
|
||||
it('should pass context to inject helper',
|
||||
inject([], function(this: TestContext) { this.contextModified = true; }));
|
||||
it('should pass context to inject helper', inject([], function(this: TestContext) {
|
||||
this.contextModified = true;
|
||||
}));
|
||||
|
||||
it('should pass context to fakeAsync helper',
|
||||
fakeAsync(function(this: TestContext) { this.contextModified = true; }));
|
||||
it('should pass context to fakeAsync helper', fakeAsync(function(this: TestContext) {
|
||||
this.contextModified = true;
|
||||
}));
|
||||
|
||||
it('should pass context to withModule helper - simple',
|
||||
withModule(moduleConfig, function(this: TestContext) { this.contextModified = true; }));
|
||||
withModule(moduleConfig, function(this: TestContext) {
|
||||
this.contextModified = true;
|
||||
}));
|
||||
|
||||
it('should pass context to withModule helper - advanced',
|
||||
withModule(moduleConfig)
|
||||
@ -199,7 +221,7 @@ const bTok = new InjectionToken<string>('b');
|
||||
|
||||
it('should allow the use of fakeAsync',
|
||||
fakeAsync(inject([FancyService], (service: FancyService) => {
|
||||
let value: string = undefined !;
|
||||
let value: string = undefined!;
|
||||
service.getAsyncValue().then((val) => value = val);
|
||||
tick();
|
||||
expect(value).toEqual('async value');
|
||||
@ -329,7 +351,9 @@ const bTok = new InjectionToken<string>('b');
|
||||
describe('overwriting metadata', () => {
|
||||
@Pipe({name: 'undefined'})
|
||||
class SomePipe {
|
||||
transform(value: string): string { return `transformed ${value}`; }
|
||||
transform(value: string): string {
|
||||
return `transformed ${value}`;
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({selector: '[undefined]'})
|
||||
@ -418,7 +442,6 @@ const bTok = new InjectionToken<string>('b');
|
||||
});
|
||||
|
||||
describe('overriding providers', () => {
|
||||
|
||||
describe('in core', () => {
|
||||
it('ComponentFactoryResolver', () => {
|
||||
const componentFactoryMock =
|
||||
@ -429,7 +452,6 @@ const bTok = new InjectionToken<string>('b');
|
||||
});
|
||||
|
||||
describe('in NgModules', () => {
|
||||
|
||||
it('should support useValue', () => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
@ -501,7 +523,9 @@ const bTok = new InjectionToken<string>('b');
|
||||
|
||||
@NgModule()
|
||||
class SomeModule {
|
||||
constructor() { someModule = this; }
|
||||
constructor() {
|
||||
someModule = this;
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
@ -731,11 +755,12 @@ const bTok = new InjectionToken<string>('b');
|
||||
|
||||
@Directive({selector: '[test]'})
|
||||
class TestDir {
|
||||
constructor() { testDir = this; }
|
||||
constructor() {
|
||||
testDir = this;
|
||||
}
|
||||
|
||||
// TODO(issue/24571): remove '!'.
|
||||
@Input('test')
|
||||
test !: string;
|
||||
@Input('test') test!: string;
|
||||
}
|
||||
|
||||
TestBed.overrideTemplateUsingTestingModule(
|
||||
@ -746,7 +771,7 @@ const bTok = new InjectionToken<string>('b');
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('Hello world!');
|
||||
expect(testDir).toBeAnInstanceOf(TestDir);
|
||||
expect(testDir !.test).toBe('some prop');
|
||||
expect(testDir!.test).toBe('some prop');
|
||||
});
|
||||
|
||||
it('should throw if the TestBed is already created', () => {
|
||||
@ -776,9 +801,7 @@ const bTok = new InjectionToken<string>('b');
|
||||
});
|
||||
|
||||
describe('setting up the compiler', () => {
|
||||
|
||||
describe('providers', () => {
|
||||
|
||||
it('should use set up providers', fakeAsync(() => {
|
||||
// Keeping this component inside the test is needed to make sure it's not resolved
|
||||
// prior to this test, thus having ɵcmp and a reference in resource
|
||||
@ -869,8 +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', async(inject([], () => setTimeout(() => {
|
||||
throw barError;
|
||||
}, 0))));
|
||||
|
||||
itPromise.then(() => done.fail('Expected test to fail, but it did not'), (err) => {
|
||||
expect(err).toEqual(barError);
|
||||
@ -883,7 +907,7 @@ const bTok = new InjectionToken<string>('b');
|
||||
const itPromise = patchJasmineIt();
|
||||
|
||||
it('should fail with an error from a promise', async(inject([], () => {
|
||||
let reject: (error: any) => void = undefined !;
|
||||
let reject: (error: any) => void = undefined!;
|
||||
const promise = new Promise((_, rej) => reject = rej);
|
||||
const p = promise.then(() => expect(1).toEqual(2));
|
||||
|
||||
@ -919,21 +943,23 @@ const bTok = new InjectionToken<string>('b');
|
||||
}
|
||||
|
||||
expect(
|
||||
() => it(
|
||||
'should fail', withModule(
|
||||
{declarations: [InlineCompWithUrlTemplate]},
|
||||
() => TestBed.createComponent(InlineCompWithUrlTemplate))))
|
||||
() =>
|
||||
it('should fail',
|
||||
withModule(
|
||||
{declarations: [InlineCompWithUrlTemplate]},
|
||||
() => TestBed.createComponent(InlineCompWithUrlTemplate))))
|
||||
.toThrowError(
|
||||
ivyEnabled ?
|
||||
`Component 'InlineCompWithUrlTemplate' is not resolved:
|
||||
- templateUrl: /base/angular/packages/platform-browser/test/static_assets/test.html
|
||||
Did you run and wait for 'resolveComponentResources()'?` :
|
||||
`This test module uses the component ${stringify(InlineCompWithUrlTemplate)} which is using a "templateUrl" or "styleUrls", but they were never compiled. ` +
|
||||
`This test module uses the component ${
|
||||
stringify(
|
||||
InlineCompWithUrlTemplate)} which is using a "templateUrl" or "styleUrls", but they were never compiled. ` +
|
||||
`Please call "TestBed.compileComponents" before your test.`);
|
||||
|
||||
restoreJasmineIt();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@ -972,7 +998,6 @@ Did you run and wait for 'resolveComponentResources()'?` :
|
||||
});
|
||||
|
||||
describe('creating components', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
@ -1008,7 +1033,6 @@ Did you run and wait for 'resolveComponentResources()'?` :
|
||||
const componentFixture = TestBed.createComponent(ChildComp);
|
||||
componentFixture.detectChanges();
|
||||
expect(componentFixture.nativeElement).toHaveText('Mock');
|
||||
|
||||
}));
|
||||
|
||||
it('should override a provider', async(() => {
|
||||
|
@ -5,8 +5,9 @@
|
||||
* 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 {APP_ID, NgModule, NgZone, PLATFORM_INITIALIZER, PlatformRef, StaticProvider, createPlatformFactory, platformCore} from '@angular/core';
|
||||
import {APP_ID, createPlatformFactory, NgModule, NgZone, PLATFORM_INITIALIZER, platformCore, PlatformRef, StaticProvider} from '@angular/core';
|
||||
import {BrowserModule, ɵBrowserDomAdapter as BrowserDomAdapter, ɵELEMENT_PROBE_PROVIDERS as ELEMENT_PROBE_PROVIDERS} from '@angular/platform-browser';
|
||||
|
||||
import {BrowserDetection, createNgZone} from './browser_util';
|
||||
|
||||
function initBrowserTests() {
|
||||
|
@ -19,11 +19,17 @@ export class BrowserDetection {
|
||||
return getDOM() ? getDOM().getUserAgent() : '';
|
||||
}
|
||||
|
||||
static setup() { return new BrowserDetection(null); }
|
||||
static setup() {
|
||||
return new BrowserDetection(null);
|
||||
}
|
||||
|
||||
constructor(ua: string|null) { this._overrideUa = ua; }
|
||||
constructor(ua: string|null) {
|
||||
this._overrideUa = ua;
|
||||
}
|
||||
|
||||
get isFirefox(): boolean { return this._ua.indexOf('Firefox') > -1; }
|
||||
get isFirefox(): boolean {
|
||||
return this._ua.indexOf('Firefox') > -1;
|
||||
}
|
||||
|
||||
get isAndroid(): boolean {
|
||||
return this._ua.indexOf('Mozilla/5.0') > -1 && this._ua.indexOf('Android') > -1 &&
|
||||
@ -31,9 +37,13 @@ export class BrowserDetection {
|
||||
this._ua.indexOf('IEMobile') == -1;
|
||||
}
|
||||
|
||||
get isEdge(): boolean { return this._ua.indexOf('Edge') > -1; }
|
||||
get isEdge(): boolean {
|
||||
return this._ua.indexOf('Edge') > -1;
|
||||
}
|
||||
|
||||
get isIE(): boolean { return this._ua.indexOf('Trident') > -1; }
|
||||
get isIE(): boolean {
|
||||
return this._ua.indexOf('Trident') > -1;
|
||||
}
|
||||
|
||||
get isWebkit(): boolean {
|
||||
return this._ua.indexOf('AppleWebKit') > -1 && this._ua.indexOf('Edge') == -1 &&
|
||||
@ -45,7 +55,9 @@ export class BrowserDetection {
|
||||
this._ua.indexOf('IEMobile') == -1;
|
||||
}
|
||||
|
||||
get isSlow(): boolean { return this.isAndroid || this.isIE || this.isIOS7; }
|
||||
get isSlow(): boolean {
|
||||
return this.isAndroid || this.isIE || this.isIOS7;
|
||||
}
|
||||
|
||||
// The Intl API is only natively supported in Chrome, Firefox, IE11 and Edge.
|
||||
// This detector is needed in tests to make the difference between:
|
||||
@ -67,13 +79,17 @@ export class BrowserDetection {
|
||||
this._ua.indexOf('Edge') == -1;
|
||||
}
|
||||
|
||||
get supportsCustomElements() { return (typeof(<any>global).customElements !== 'undefined'); }
|
||||
|
||||
get supportsDeprecatedCustomCustomElementsV0() {
|
||||
return (typeof(document as any).registerElement !== 'undefined');
|
||||
get supportsCustomElements() {
|
||||
return (typeof (<any>global).customElements !== 'undefined');
|
||||
}
|
||||
|
||||
get supportsRegExUnicodeFlag(): boolean { return RegExp.prototype.hasOwnProperty('unicode'); }
|
||||
get supportsDeprecatedCustomCustomElementsV0() {
|
||||
return (typeof (document as any).registerElement !== 'undefined');
|
||||
}
|
||||
|
||||
get supportsRegExUnicodeFlag(): boolean {
|
||||
return RegExp.prototype.hasOwnProperty('unicode');
|
||||
}
|
||||
|
||||
get supportsShadowDom() {
|
||||
const testEl = document.createElement('div');
|
||||
@ -203,10 +219,10 @@ export function setCookie(name: string, value: string) {
|
||||
}
|
||||
|
||||
export function supportsWebAnimation(): boolean {
|
||||
return typeof(<any>Element).prototype['animate'] === 'function';
|
||||
return typeof (<any>Element).prototype['animate'] === 'function';
|
||||
}
|
||||
|
||||
export function hasStyle(element: any, styleName: string, styleValue?: string | null): boolean {
|
||||
export function hasStyle(element: any, styleName: string, styleValue?: string|null): boolean {
|
||||
const value = element.style[styleName] || '';
|
||||
return styleValue ? value == styleValue : value.length > 0;
|
||||
}
|
||||
|
@ -123,7 +123,9 @@ export const expect: <T = any>(actual: T) => NgMatchers<T> = _global.expect;
|
||||
return '' + m;
|
||||
}
|
||||
const res: any[] = [];
|
||||
m.forEach((v: any, k: any) => { res.push(`${String(k)}:${String(v)}`); });
|
||||
m.forEach((v: any, k: any) => {
|
||||
res.push(`${String(k)}:${String(v)}`);
|
||||
});
|
||||
return `{ ${res.join(',')} }`;
|
||||
};
|
||||
|
||||
@ -141,7 +143,7 @@ _global.beforeEach(function() {
|
||||
return pass;
|
||||
} else {
|
||||
// TODO(misko): we should change the return, but jasmine.d.ts is not null safe
|
||||
return undefined !;
|
||||
return undefined!;
|
||||
}
|
||||
});
|
||||
jasmine.addMatchers({
|
||||
@ -149,7 +151,12 @@ _global.beforeEach(function() {
|
||||
return {
|
||||
compare: function(actual: any) {
|
||||
const pass = typeof actual === 'object' && typeof actual.then === 'function';
|
||||
return {pass: pass, get message() { return 'Expected ' + actual + ' to be a promise'; }};
|
||||
return {
|
||||
pass: pass,
|
||||
get message() {
|
||||
return 'Expected ' + actual + ' to be a promise';
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
@ -174,7 +181,9 @@ _global.beforeEach(function() {
|
||||
const actualText = elementText(actual);
|
||||
return {
|
||||
pass: actualText == expectedText,
|
||||
get message() { return 'Expected ' + actualText + ' to be equal to ' + expectedText; }
|
||||
get message() {
|
||||
return 'Expected ' + actualText + ' to be equal to ' + expectedText;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -188,7 +197,8 @@ _global.beforeEach(function() {
|
||||
return {
|
||||
pass: hasClass(actual, className) == !isNot,
|
||||
get message() {
|
||||
return `Expected ${actual.outerHTML} ${isNot ? 'not ' : ''}to contain the CSS class "${className}"`;
|
||||
return `Expected ${actual.outerHTML} ${
|
||||
isNot ? 'not ' : ''}to contain the CSS class "${className}"`;
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -203,8 +213,9 @@ _global.beforeEach(function() {
|
||||
allPassed = hasStyle(actual, styles);
|
||||
} else {
|
||||
allPassed = Object.keys(styles).length !== 0;
|
||||
Object.keys(styles).forEach(
|
||||
prop => { allPassed = allPassed && hasStyle(actual, prop, styles[prop]); });
|
||||
Object.keys(styles).forEach(prop => {
|
||||
allPassed = allPassed && hasStyle(actual, prop, styles[prop]);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
@ -212,7 +223,8 @@ _global.beforeEach(function() {
|
||||
get message() {
|
||||
const expectedValueStr = typeof styles === 'string' ? styles : JSON.stringify(styles);
|
||||
return `Expected ${actual.outerHTML} ${!allPassed ? ' ' : 'not '}to contain the
|
||||
CSS ${typeof styles === 'string' ? 'property' : 'styles'} "${expectedValueStr}"`;
|
||||
CSS ${typeof styles === 'string' ? 'property' : 'styles'} "${
|
||||
expectedValueStr}"`;
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -225,7 +237,9 @@ _global.beforeEach(function() {
|
||||
const errorMessage = actual.toString();
|
||||
return {
|
||||
pass: errorMessage.indexOf(expectedText) > -1,
|
||||
get message() { return 'Expected ' + errorMessage + ' to contain ' + expectedText; }
|
||||
get message() {
|
||||
return 'Expected ' + errorMessage + ' to contain ' + expectedText;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -244,8 +258,8 @@ _global.beforeEach(function() {
|
||||
return {
|
||||
pass: missedMethods.length == 0,
|
||||
get message() {
|
||||
return 'Expected ' + actualObject + ' to have the following methods: ' +
|
||||
missedMethods.join(', ');
|
||||
return 'Expected ' + actualObject +
|
||||
' to have the following methods: ' + missedMethods.join(', ');
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -262,8 +276,8 @@ _global.beforeEach(function() {
|
||||
if (!(actualFixture instanceof ComponentFixture)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: msgFn(
|
||||
`Expected actual to be of type \'ComponentFixture\' [actual=${actualFixture.constructor.name}]`)
|
||||
message: msgFn(`Expected actual to be of type \'ComponentFixture\' [actual=${
|
||||
actualFixture.constructor.name}]`)
|
||||
};
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user