Skip to content

Commit

Permalink
fix(breakpoints): emit only one event for adjacent breakpoint changes.
Browse files Browse the repository at this point in the history
  • Loading branch information
josephperrott committed Jul 13, 2018
1 parent 3255cf3 commit de30ce1
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 27 deletions.
46 changes: 27 additions & 19 deletions src/cdk/layout/breakpoints-observer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
import {LayoutModule} from './layout-module';
import {BreakpointObserver, BreakpointState} from './breakpoints-observer';
import {MediaMatcher} from './media-matcher';
import {async, TestBed, inject} from '@angular/core/testing';
import {fakeAsync, TestBed, inject, flush} from '@angular/core/testing';
import {Injectable} from '@angular/core';

describe('BreakpointObserver', () => {
let breakpointManager: BreakpointObserver;
let mediaMatcher: FakeMediaMatcher;

beforeEach(async(() => {
beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
imports: [LayoutModule],
providers: [{provide: MediaMatcher, useClass: FakeMediaMatcher}]
Expand All @@ -33,12 +33,12 @@ describe('BreakpointObserver', () => {
mediaMatcher.clear();
});

it('retrieves the whether a query is currently matched', () => {
it('retrieves the whether a query is currently matched', fakeAsync(() => {
let query = 'everything starts as true in the FakeMediaMatcher';
expect(breakpointManager.isMatched(query)).toBeTruthy();
});
}));

it('reuses the same MediaQueryList for matching queries', () => {
it('reuses the same MediaQueryList for matching queries', fakeAsync(() => {
expect(mediaMatcher.queryCount).toBe(0);
breakpointManager.observe('query1');
expect(mediaMatcher.queryCount).toBe(1);
Expand All @@ -48,62 +48,68 @@ describe('BreakpointObserver', () => {
expect(mediaMatcher.queryCount).toBe(2);
breakpointManager.observe('query1');
expect(mediaMatcher.queryCount).toBe(2);
});
}));

it('splits combined query strings into individual matchMedia listeners', () => {
it('splits combined query strings into individual matchMedia listeners', fakeAsync(() => {
expect(mediaMatcher.queryCount).toBe(0);
breakpointManager.observe('query1, query2');
expect(mediaMatcher.queryCount).toBe(2);
breakpointManager.observe('query1');
expect(mediaMatcher.queryCount).toBe(2);
breakpointManager.observe('query2, query3');
expect(mediaMatcher.queryCount).toBe(3);
});
}));

it('accepts an array of queries', () => {
it('accepts an array of queries', fakeAsync(() => {
let queries = ['1 query', '2 query', 'red query', 'blue query'];
breakpointManager.observe(queries);
expect(mediaMatcher.queryCount).toBe(queries.length);
});
}));

it('completes all events when the breakpoint manager is destroyed', () => {
it('completes all events when the breakpoint manager is destroyed', fakeAsync(() => {
let firstTest = jasmine.createSpy('test1');
breakpointManager.observe('test1').subscribe(undefined, undefined, firstTest);
let secondTest = jasmine.createSpy('test2');
breakpointManager.observe('test2').subscribe(undefined, undefined, secondTest);

flush();
expect(firstTest).not.toHaveBeenCalled();
expect(secondTest).not.toHaveBeenCalled();

breakpointManager.ngOnDestroy();
flush();

expect(firstTest).toHaveBeenCalled();
expect(secondTest).toHaveBeenCalled();
});
}));

it('emits an event on the observable when values change', () => {
it('emits an event on the observable when values change', fakeAsync(() => {
let query = '(width: 999px)';
let queryMatchState: boolean = false;
breakpointManager.observe(query).subscribe((state: BreakpointState) => {
queryMatchState = state.matches;
});

flush();
expect(queryMatchState).toBeTruthy();
mediaMatcher.setMatchesQuery(query, false);
flush();
expect(queryMatchState).toBeFalsy();
});
}));

it('emits a true matches state when the query is matched', () => {
it('emits a true matches state when the query is matched', fakeAsync(() => {
let query = '(width: 999px)';
breakpointManager.observe(query).subscribe();
mediaMatcher.setMatchesQuery(query, true);
expect(breakpointManager.isMatched(query)).toBeTruthy();
});
}));

it('emits a false matches state when the query is not matched', () => {
it('emits a false matches state when the query is not matched', fakeAsync(() => {
let query = '(width: 999px)';
breakpointManager.observe(query).subscribe();
mediaMatcher.setMatchesQuery(query, false);
expect(breakpointManager.isMatched(query)).toBeTruthy();
});
expect(breakpointManager.isMatched(query)).toBeFalsy();
}));
});

export class FakeMediaQueryList implements MediaQueryList {
Expand Down Expand Up @@ -153,6 +159,8 @@ export class FakeMediaMatcher {
setMatchesQuery(query: string, matches: boolean) {
if (this.queries.has(query)) {
this.queries.get(query)!.setMatches(matches);
} else {
throw Error('This query is not being observed.');
}
}
}
16 changes: 9 additions & 7 deletions src/cdk/layout/breakpoints-observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
*/
import {Injectable, NgZone, OnDestroy} from '@angular/core';
import {MediaMatcher} from './media-matcher';
import {combineLatest, fromEventPattern, Observable, Subject} from 'rxjs';
import {map, startWith, takeUntil} from 'rxjs/operators';
import {asapScheduler, combineLatest, fromEventPattern, Observable, Subject} from 'rxjs';
import {debounceTime, map, startWith, takeUntil} from 'rxjs/operators';
import {coerceArray} from '@angular/cdk/coercion';


Expand Down Expand Up @@ -59,11 +59,13 @@ export class BreakpointObserver implements OnDestroy {
const queries = splitQueries(coerceArray(value));
const observables = queries.map(query => this._registerQuery(query).observable);

return combineLatest(observables).pipe(map((breakpointStates: BreakpointState[]) => {
return {
matches: breakpointStates.some(state => state && state.matches)
};
}));
return combineLatest(observables).pipe(
debounceTime(0, asapScheduler),
map((breakpointStates: BreakpointState[]) => {
return {
matches: breakpointStates.some(state => state && state.matches)
};
}));
}

/** Registers a specific query to be listened for. */
Expand Down
2 changes: 1 addition & 1 deletion src/lib/tooltip/tooltip.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="mat-tooltip"
[ngClass]="tooltipClass"
[class.mat-tooltip-handset]="(_isHandset | async)!.matches"
[class.mat-tooltip-handset]="(_isHandset | async)?.matches"
[@state]="_visibility"
(@state.start)="_animationStart()"
(@state.done)="_animationDone($event)">{{message}}</div>

0 comments on commit de30ce1

Please sign in to comment.