diff --git a/src/cdk/layout/breakpoints-observer.spec.ts b/src/cdk/layout/breakpoints-observer.spec.ts index 4bb2d9ab5049..e78e2dc24721 100644 --- a/src/cdk/layout/breakpoints-observer.spec.ts +++ b/src/cdk/layout/breakpoints-observer.spec.ts @@ -5,17 +5,18 @@ * 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 {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}] @@ -33,12 +34,12 @@ describe('BreakpointObserver', () => { mediaMatcher.clear(); }); - it('retrieves the whether a query is currently matched', () => { + it('retrieves the whether a query is currently matched', fakeAsync(() => { const 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); @@ -48,9 +49,9 @@ 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); @@ -58,69 +59,78 @@ describe('BreakpointObserver', () => { 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(() => { const 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(() => { const firstTest = jasmine.createSpy('test1'); breakpointManager.observe('test1').subscribe(undefined, undefined, firstTest); const 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(() => { const query = '(width: 999px)'; let queryMatchState = false; breakpointManager.observe(query).subscribe((state: BreakpointState) => { queryMatchState = state.matches; }); + flush(); expect(queryMatchState).toBeTruthy(); mediaMatcher.setMatchesQuery(query, false); + flush(); expect(queryMatchState).toBeFalsy(); - }); - - it('emits an event on the observable with the matching state of all queries provided', () => { - const queryOne = '(width: 999px)'; - const queryTwo = '(width: 700px)'; - let state: BreakpointState = {matches: false, breakpoints: {}}; - breakpointManager.observe([queryOne, queryTwo]).subscribe((breakpoint: BreakpointState) => { - state = breakpoint; - }); - - mediaMatcher.setMatchesQuery(queryOne, false); - mediaMatcher.setMatchesQuery(queryTwo, false); - expect(state.breakpoints).toEqual({[queryOne]: false, [queryTwo]: false}); + })); - mediaMatcher.setMatchesQuery(queryOne, true); - mediaMatcher.setMatchesQuery(queryTwo, false); - expect(state.breakpoints).toEqual({[queryOne]: true, [queryTwo]: false}); - }); + it('emits an event on the observable with the matching state of all queries provided', + fakeAsync(() => { + const queryOne = '(width: 999px)'; + const queryTwo = '(width: 700px)'; + let state: BreakpointState = {matches: false, breakpoints: {}}; + breakpointManager.observe([queryOne, queryTwo]).subscribe((breakpoint: BreakpointState) => { + state = breakpoint; + }); + + mediaMatcher.setMatchesQuery(queryOne, false); + mediaMatcher.setMatchesQuery(queryTwo, false); + flush(); + expect(state.breakpoints).toEqual({[queryOne]: false, [queryTwo]: false}); + + mediaMatcher.setMatchesQuery(queryOne, true); + mediaMatcher.setMatchesQuery(queryTwo, false); + flush(); + expect(state.breakpoints).toEqual({[queryOne]: true, [queryTwo]: false}); + })); - it('emits a true matches state when the query is matched', () => { + it('emits a true matches state when the query is matched', fakeAsync(() => { const 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(() => { const 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 { @@ -170,6 +180,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.'); } } } diff --git a/src/cdk/layout/breakpoints-observer.ts b/src/cdk/layout/breakpoints-observer.ts index 1e476c0d63ff..41ba8c98f7e2 100644 --- a/src/cdk/layout/breakpoints-observer.ts +++ b/src/cdk/layout/breakpoints-observer.ts @@ -5,10 +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 {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'; @@ -74,17 +75,19 @@ 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: InternalBreakpointState[]) => { - const response: BreakpointState = { - matches: false, - breakpoints: {}, - }; - breakpointStates.forEach((state: InternalBreakpointState) => { - response.matches = response.matches || state.matches; - response.breakpoints[state.query] = state.matches; - }); - return response; - })); + return combineLatest(observables).pipe( + debounceTime(0, asapScheduler), + map((breakpointStates: InternalBreakpointState[]) => { + const response: BreakpointState = { + matches: false, + breakpoints: {}, + }; + breakpointStates.forEach((state: InternalBreakpointState) => { + response.matches = response.matches || state.matches; + response.breakpoints[state.query] = state.matches; + }); + return response; + })); } /** Registers a specific query to be listened for. */ diff --git a/src/lib/tooltip/tooltip.html b/src/lib/tooltip/tooltip.html index b21865b8b1b2..feaaaf53352f 100644 --- a/src/lib/tooltip/tooltip.html +++ b/src/lib/tooltip/tooltip.html @@ -1,6 +1,6 @@
{{message}}