diff --git a/src/lib/core/observe-content/observe-content.spec.ts b/src/lib/core/observe-content/observe-content.spec.ts index 888a37391a0e..f33246370bfb 100644 --- a/src/lib/core/observe-content/observe-content.spec.ts +++ b/src/lib/core/observe-content/observe-content.spec.ts @@ -1,6 +1,6 @@ import {Component} from '@angular/core'; -import {async, TestBed} from '@angular/core/testing'; -import {ObserveContentModule} from './observe-content'; +import {async, TestBed, ComponentFixture, fakeAsync, tick} from '@angular/core/testing'; +import {ObserveContentModule, MutationObserverFactory} from './observe-content'; /** * TODO(elad): `ProxyZone` doesn't seem to capture the events raised by @@ -8,17 +8,17 @@ import {ObserveContentModule} from './observe-content'; */ describe('Observe content', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ObserveContentModule], - declarations: [ComponentWithTextContent, ComponentWithChildTextContent], - }); + describe('basic usage', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ObserveContentModule], + declarations: [ComponentWithTextContent, ComponentWithChildTextContent] + }); - TestBed.compileComponents(); - })); + TestBed.compileComponents(); + })); - describe('text content change', () => { - it('should call the registered for changes function', done => { + it('should trigger the callback when the content of the element changes', done => { let fixture = TestBed.createComponent(ComponentWithTextContent); fixture.detectChanges(); @@ -33,10 +33,8 @@ describe('Observe content', () => { fixture.componentInstance.text = 'text'; fixture.detectChanges(); }); - }); - describe('child text content change', () => { - it('should call the registered for changes function', done => { + it('should trigger the callback when the content of the children changes', done => { let fixture = TestBed.createComponent(ComponentWithChildTextContent); fixture.detectChanges(); @@ -52,6 +50,62 @@ describe('Observe content', () => { fixture.detectChanges(); }); }); + + describe('debounced', () => { + let fixture: ComponentFixture; + let instance: ComponentWithDebouncedListener; + let callbacks: Function[]; + let fakeMutationObserver: { + create(callback: MutationCallback): any; + invokeCallbacks: (args: any[]) => void; + }; + + beforeEach(async(() => { + callbacks = []; + + fakeMutationObserver = { + invokeCallbacks: (args?: any[]) => callbacks.forEach(callback => callback(args)), + create: (callback: MutationCallback) => { + callbacks.push(callback); + + return { + observe: () => {}, + disconnect: () => {} + }; + } + }; + + TestBed.configureTestingModule({ + imports: [ObserveContentModule], + declarations: [ComponentWithDebouncedListener], + providers: [{ provide: MutationObserverFactory, useValue: fakeMutationObserver }] + }); + + TestBed.compileComponents(); + + fixture = TestBed.createComponent(ComponentWithDebouncedListener); + instance = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should debounce the content changes', fakeAsync(() => { + fakeMutationObserver.invokeCallbacks([1]); + fakeMutationObserver.invokeCallbacks([2]); + fakeMutationObserver.invokeCallbacks([3]); + + tick(500); + expect(instance.spy).toHaveBeenCalledTimes(1); + })); + + it('should should keep track of all of the mutation records', fakeAsync(() => { + fakeMutationObserver.invokeCallbacks([1]); + fakeMutationObserver.invokeCallbacks([2]); + fakeMutationObserver.invokeCallbacks([3]); + + tick(500); + expect(instance.spy).toHaveBeenCalledWith([1, 2, 3]); + })); + }); }); @@ -66,3 +120,11 @@ class ComponentWithChildTextContent { text = ''; doSomething() {} } + +@Component({ + template: `
{{text}}
` +}) +class ComponentWithDebouncedListener { + debounce = 500; + spy = jasmine.createSpy('MutationObserver callback'); +} diff --git a/src/lib/core/observe-content/observe-content.ts b/src/lib/core/observe-content/observe-content.ts index ac0cba5e695f..12070af7d096 100644 --- a/src/lib/core/observe-content/observe-content.ts +++ b/src/lib/core/observe-content/observe-content.ts @@ -7,11 +7,22 @@ import { Input, EventEmitter, OnDestroy, - AfterContentInit + AfterContentInit, } from '@angular/core'; import {debounce} from '../util/debounce'; +/** + * Factory class to create MutationObserver instances. Useful for being able to + * stub out the MutationObserver for unit tests. + * @docs-private + */ +export class MutationObserverFactory { + create(callback: MutationCallback): MutationObserver { + return new MutationObserver(callback); + } +} + /** * Directive that triggers a callback whenever the content of * its associated element has changed. @@ -31,7 +42,9 @@ export class ObserveContent implements AfterContentInit, OnDestroy { /** Debounce interval for emitting the changes. */ @Input() debounce: number; - constructor(private _elementRef: ElementRef) {} + constructor( + private _elementRef: ElementRef, + private _observerFactory: MutationObserverFactory) { } ngAfterContentInit() { let callback: MutationCallback; @@ -51,7 +64,7 @@ export class ObserveContent implements AfterContentInit, OnDestroy { callback = (mutations: MutationRecord[]) => this.event.emit(mutations); } - this._observer = new MutationObserver(callback); + this._observer = this._observerFactory.create(callback); this._observer.observe(this._elementRef.nativeElement, { characterData: true, @@ -70,7 +83,8 @@ export class ObserveContent implements AfterContentInit, OnDestroy { @NgModule({ exports: [ObserveContent], - declarations: [ObserveContent] + declarations: [ObserveContent], + providers: [MutationObserverFactory] }) export class ObserveContentModule { static forRoot(): ModuleWithProviders {