Skip to content

Commit

Permalink
Stub out the MutationObserver.
Browse files Browse the repository at this point in the history
  • Loading branch information
crisbeto committed Dec 25, 2016
1 parent 6508f9a commit f91d2e8
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 18 deletions.
90 changes: 76 additions & 14 deletions src/lib/core/observe-content/observe-content.spec.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
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
* `MutationObserver` and needs to be investigated
*/

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();

Expand All @@ -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();

Expand All @@ -52,6 +50,62 @@ describe('Observe content', () => {
fixture.detectChanges();
});
});

describe('debounced', () => {
let fixture: ComponentFixture<ComponentWithDebouncedListener>;
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]);
}));
});
});


Expand All @@ -66,3 +120,11 @@ class ComponentWithChildTextContent {
text = '';
doSomething() {}
}

@Component({
template: `<div (cdkObserveContent)="spy($event)" [debounce]="debounce">{{text}}</div>`
})
class ComponentWithDebouncedListener {
debounce = 500;
spy = jasmine.createSpy('MutationObserver callback');
}
22 changes: 18 additions & 4 deletions src/lib/core/observe-content/observe-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
Expand All @@ -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,
Expand All @@ -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 {
Expand Down

0 comments on commit f91d2e8

Please sign in to comment.