@@ -7,6 +7,7 @@ import {MdSelect} from './select';
7
7
import { MdOption } from './option' ;
8
8
import { Dir } from '../core/rtl/dir' ;
9
9
import { FormControl , FormsModule , ReactiveFormsModule } from '@angular/forms' ;
10
+ import { ViewportRuler } from '../core/overlay/position/viewport-ruler' ;
10
11
11
12
describe ( 'MdSelect' , ( ) => {
12
13
let overlayContainerElement : HTMLElement ;
@@ -19,17 +20,33 @@ describe('MdSelect', () => {
19
20
providers : [
20
21
{ provide : OverlayContainer , useFactory : ( ) => {
21
22
overlayContainerElement = document . createElement ( 'div' ) ;
23
+
24
+ // add fixed positioning to match real overlay container styles
25
+ overlayContainerElement . style . position = 'fixed' ;
26
+ overlayContainerElement . style . top = '0' ;
27
+ overlayContainerElement . style . left = '0' ;
28
+ document . body . appendChild ( overlayContainerElement ) ;
29
+
30
+ // remove body padding to keep consistent cross-browser
31
+ document . body . style . padding = '0' ;
32
+ document . body . style . margin = '0' ;
33
+
22
34
return { getContainerElement : ( ) => overlayContainerElement } ;
23
35
} } ,
24
36
{ provide : Dir , useFactory : ( ) => {
25
37
return dir = { value : 'ltr' } ;
26
- } }
38
+ } } ,
39
+ { provide : ViewportRuler , useClass : FakeViewportRuler }
27
40
]
28
41
} ) ;
29
42
30
43
TestBed . compileComponents ( ) ;
31
44
} ) ) ;
32
45
46
+ afterEach ( ( ) => {
47
+ document . body . removeChild ( overlayContainerElement ) ;
48
+ } ) ;
49
+
33
50
describe ( 'overlay panel' , ( ) => {
34
51
let fixture : ComponentFixture < BasicSelect > ;
35
52
let trigger : HTMLElement ;
@@ -457,19 +474,78 @@ describe('MdSelect', () => {
457
474
458
475
trigger . click ( ) ;
459
476
fixture . detectChanges ( ) ;
460
- expect ( fixture . componentInstance . select . _getPanelState ( ) ) . toEqual ( 'showing -ltr' ) ;
477
+ expect ( fixture . componentInstance . select . _getPanelState ( ) ) . toEqual ( 'top -ltr' ) ;
461
478
} ) ;
462
479
463
480
it ( 'should use the rtl panel state when the dir is rtl' , ( ) => {
464
481
dir . value = 'rtl' ;
465
482
466
483
trigger . click ( ) ;
467
484
fixture . detectChanges ( ) ;
468
- expect ( fixture . componentInstance . select . _getPanelState ( ) ) . toEqual ( 'showing -rtl' ) ;
485
+ expect ( fixture . componentInstance . select . _getPanelState ( ) ) . toEqual ( 'top -rtl' ) ;
469
486
} ) ;
470
487
471
488
} ) ;
472
489
490
+ describe ( 'positioning' , ( ) => {
491
+ let fixture : ComponentFixture < BasicSelect > ;
492
+ let trigger : HTMLElement ;
493
+
494
+ beforeEach ( ( ) => {
495
+ fixture = TestBed . createComponent ( BasicSelect ) ;
496
+ fixture . detectChanges ( ) ;
497
+ trigger = fixture . debugElement . query ( By . css ( '.md-select-trigger' ) ) . nativeElement ;
498
+ } ) ;
499
+
500
+ it ( 'should open below the trigger if the panel will fit' , ( ) => {
501
+ trigger . click ( ) ;
502
+ fixture . detectChanges ( ) ;
503
+
504
+ const overlayPane = overlayContainerElement . children [ 0 ] as HTMLElement ;
505
+ const overlayRect = overlayPane . getBoundingClientRect ( ) ;
506
+ const triggerRect = trigger . getBoundingClientRect ( ) ;
507
+
508
+ // when the select panel opens below the trigger, the tops of the trigger and the overlay
509
+ // should be aligned.
510
+ expect ( overlayRect . top . toFixed ( 2 ) )
511
+ . toEqual ( triggerRect . top . toFixed ( 2 ) , `Expected panel to open below by default.` ) ;
512
+
513
+ // animation should match the position
514
+ expect ( fixture . componentInstance . select . _getPanelState ( ) )
515
+ . toEqual ( 'top-ltr' , `Expected panel animation values to match the position.` ) ;
516
+ expect ( fixture . componentInstance . select . _transformOrigin )
517
+ . toBe ( 'top' , `Expected panel animation to originate at the top.` ) ;
518
+ } ) ;
519
+
520
+ it ( 'should open above the trigger if there is not space below for the panel' , ( ) => {
521
+ // Push trigger to the bottom part of viewport, so it doesn't have space to open
522
+ // in its default position below the trigger.
523
+ trigger . style . position = 'relative' ;
524
+ trigger . style . top = '650px' ;
525
+
526
+ trigger . click ( ) ;
527
+ fixture . detectChanges ( ) ;
528
+
529
+ const overlayPane = overlayContainerElement . children [ 0 ] as HTMLElement ;
530
+ const overlayRect = overlayPane . getBoundingClientRect ( ) ;
531
+ const triggerRect = trigger . getBoundingClientRect ( ) ;
532
+
533
+ // In "above" position, the bottom edges of the overlay and the origin are aligned.
534
+ // To find the overlay top, subtract the panel height from the origin's bottom edge.
535
+ const expectedTop = triggerRect . bottom - overlayRect . height ;
536
+ expect ( overlayRect . top . toFixed ( 2 ) )
537
+ . toEqual ( expectedTop . toFixed ( 2 ) ,
538
+ `Expected panel to open above the trigger if below wouldn't fit.` ) ;
539
+
540
+ // animation should match the position
541
+ expect ( fixture . componentInstance . select . _getPanelState ( ) )
542
+ . toEqual ( 'bottom-ltr' , `Expected panel animation values to match the position.` ) ;
543
+ expect ( fixture . componentInstance . select . _transformOrigin )
544
+ . toBe ( 'bottom' , `Expected panel animation to originate at the bottom.` ) ;
545
+ } ) ;
546
+
547
+ } ) ;
548
+
473
549
describe ( 'accessibility' , ( ) => {
474
550
let fixture : ComponentFixture < BasicSelect > ;
475
551
@@ -658,3 +734,15 @@ function dispatchEvent(eventName: string, element: HTMLElement): void {
658
734
event . initEvent ( eventName , true , true ) ;
659
735
element . dispatchEvent ( event ) ;
660
736
}
737
+
738
+ class FakeViewportRuler {
739
+ getViewportRect ( ) {
740
+ return {
741
+ left : 0 , top : 0 , width : 1014 , height : 686 , bottom : 686 , right : 1014
742
+ } ;
743
+ }
744
+
745
+ getViewportScrollPosition ( ) {
746
+ return { top : 0 , left : 0 } ;
747
+ }
748
+ }
0 commit comments