1
- import { TestBed , async } from '@angular/core/testing' ;
1
+ import { TestBed , async , ComponentFixture } from '@angular/core/testing' ;
2
2
import { By } from '@angular/platform-browser' ;
3
3
import {
4
4
Component ,
5
5
ElementRef ,
6
6
EventEmitter ,
7
+ Input ,
7
8
Output ,
8
9
TemplateRef ,
9
10
ViewChild
@@ -18,6 +19,7 @@ import {
18
19
import { OverlayContainer } from '../core/overlay/overlay-container' ;
19
20
import { ViewportRuler } from '../core/overlay/position/viewport-ruler' ;
20
21
import { Dir , LayoutDirection } from '../core/rtl/dir' ;
22
+ import { extendObject } from '../core/util/object-extend' ;
21
23
22
24
describe ( 'MdMenu' , ( ) => {
23
25
let overlayContainerElement : HTMLElement ;
@@ -27,7 +29,7 @@ describe('MdMenu', () => {
27
29
dir = 'ltr' ;
28
30
TestBed . configureTestingModule ( {
29
31
imports : [ MdMenuModule . forRoot ( ) ] ,
30
- declarations : [ SimpleMenu , PositionedMenu , CustomMenuPanel , CustomMenu ] ,
32
+ declarations : [ SimpleMenu , PositionedMenu , OverlapMenu , CustomMenuPanel , CustomMenu ] ,
31
33
providers : [
32
34
{ provide : OverlayContainer , useFactory : ( ) => {
33
35
overlayContainerElement = document . createElement ( 'div' ) ;
@@ -256,6 +258,106 @@ describe('MdMenu', () => {
256
258
}
257
259
} ) ;
258
260
261
+ describe ( 'overlapping trigger' , ( ) => {
262
+ /**
263
+ * This test class is used to create components containing a menu.
264
+ * It provides helpers to reposition the trigger, open the menu,
265
+ * and access the trigger and overlay positions.
266
+ * Additionally it can take any inputs for the menu wrapper component.
267
+ *
268
+ * Basic usage:
269
+ * const subject = new OverlapSubject(MyComponent);
270
+ * subject.openMenu();
271
+ */
272
+ class OverlapSubject < T extends TestableMenu > {
273
+ private readonly fixture : ComponentFixture < T > ;
274
+ private readonly trigger : any ;
275
+
276
+ constructor ( ctor : { new ( ) : T ; } , inputs : { [ key : string ] : any } = { } ) {
277
+ this . fixture = TestBed . createComponent ( ctor ) ;
278
+ extendObject ( this . fixture . componentInstance , inputs ) ;
279
+ this . fixture . detectChanges ( ) ;
280
+ this . trigger = this . fixture . componentInstance . triggerEl . nativeElement ;
281
+ }
282
+
283
+ openMenu ( ) {
284
+ this . fixture . componentInstance . trigger . openMenu ( ) ;
285
+ this . fixture . detectChanges ( ) ;
286
+ }
287
+
288
+ updateTriggerStyle ( style : any ) {
289
+ return extendObject ( this . trigger . style , style ) ;
290
+ }
291
+
292
+ get overlayRect ( ) {
293
+ return this . overlayPane . getBoundingClientRect ( ) ;
294
+ }
295
+
296
+ get triggerRect ( ) {
297
+ return this . trigger . getBoundingClientRect ( ) ;
298
+ }
299
+
300
+ get menuPanel ( ) {
301
+ return overlayContainerElement . querySelector ( '.md-menu-panel' ) ;
302
+ }
303
+
304
+ private get overlayPane ( ) {
305
+ return overlayContainerElement . querySelector ( '.cdk-overlay-pane' ) as HTMLElement ;
306
+ }
307
+ }
308
+
309
+ let subject : OverlapSubject < OverlapMenu > ;
310
+ describe ( 'explicitly overlapping' , ( ) => {
311
+ beforeEach ( ( ) => {
312
+ subject = new OverlapSubject ( OverlapMenu , { overlapTrigger : true } ) ;
313
+ } ) ;
314
+
315
+ it ( 'positions the overlay below the trigger' , ( ) => {
316
+ subject . openMenu ( ) ;
317
+
318
+ // Since the menu is overlaying the trigger, the overlay top should be the trigger top.
319
+ expect ( Math . round ( subject . overlayRect . top ) )
320
+ . toBe ( Math . round ( subject . triggerRect . top ) ,
321
+ `Expected menu to open in default "below" position.` ) ;
322
+ } ) ;
323
+ } ) ;
324
+
325
+ describe ( 'not overlapping' , ( ) => {
326
+ beforeEach ( ( ) => {
327
+ subject = new OverlapSubject ( OverlapMenu , { overlapTrigger : false } ) ;
328
+ } ) ;
329
+
330
+ it ( 'positions the overlay below the trigger' , ( ) => {
331
+ subject . openMenu ( ) ;
332
+
333
+ // Since the menu is below the trigger, the overlay top should be the trigger bottom.
334
+ expect ( Math . round ( subject . overlayRect . top ) )
335
+ . toBe ( Math . round ( subject . triggerRect . bottom ) ,
336
+ `Expected menu to open directly below the trigger.` ) ;
337
+ } ) ;
338
+
339
+ it ( 'supports above position fall back' , ( ) => {
340
+ // Push trigger to the bottom part of viewport, so it doesn't have space to open
341
+ // in its default "below" position below the trigger.
342
+ subject . updateTriggerStyle ( { position : 'relative' , top : '650px' } ) ;
343
+ subject . openMenu ( ) ;
344
+
345
+ // Since the menu is above the trigger, the overlay bottom should be the trigger top.
346
+ expect ( Math . round ( subject . overlayRect . bottom ) )
347
+ . toBe ( Math . round ( subject . triggerRect . top ) ,
348
+ `Expected menu to open in "above" position if "below" position wouldn't fit.` ) ;
349
+ } ) ;
350
+
351
+ it ( 'repositions the origin to be below, so the menu opens from the trigger' , ( ) => {
352
+ subject . openMenu ( ) ;
353
+
354
+ expect ( subject . menuPanel . classList ) . toContain ( 'md-menu-below' ) ;
355
+ expect ( subject . menuPanel . classList ) . not . toContain ( 'md-menu-above' ) ;
356
+ } ) ;
357
+
358
+ } ) ;
359
+ } ) ;
360
+
259
361
describe ( 'animations' , ( ) => {
260
362
it ( 'should include the ripple on items by default' , ( ) => {
261
363
const fixture = TestBed . createComponent ( SimpleMenu ) ;
@@ -311,6 +413,23 @@ class PositionedMenu {
311
413
@ViewChild ( 'triggerEl' ) triggerEl : ElementRef ;
312
414
}
313
415
416
+ interface TestableMenu {
417
+ trigger : MdMenuTrigger ;
418
+ triggerEl : ElementRef ;
419
+ }
420
+ @Component ( {
421
+ template : `
422
+ <button [mdMenuTriggerFor]="menu" #triggerEl>Toggle menu</button>
423
+ <md-menu [overlapTrigger]="overlapTrigger" #menu="mdMenu">
424
+ <button md-menu-item> Not overlapped Content </button>
425
+ </md-menu>
426
+ `
427
+ } )
428
+ class OverlapMenu implements TestableMenu {
429
+ @Input ( ) overlapTrigger : boolean ;
430
+ @ViewChild ( MdMenuTrigger ) trigger : MdMenuTrigger ;
431
+ @ViewChild ( 'triggerEl' ) triggerEl : ElementRef ;
432
+ }
314
433
315
434
@Component ( {
316
435
selector : 'custom-menu' ,
@@ -325,6 +444,7 @@ class PositionedMenu {
325
444
class CustomMenuPanel implements MdMenuPanel {
326
445
positionX : MenuPositionX = 'after' ;
327
446
positionY : MenuPositionY = 'below' ;
447
+ overlapTrigger : true ;
328
448
329
449
@ViewChild ( TemplateRef ) templateRef : TemplateRef < any > ;
330
450
@Output ( ) close = new EventEmitter < void > ( ) ;
0 commit comments