@@ -7,6 +7,7 @@ import {MdSelect} from './select';
77import { MdOption } from './option' ;
88import { Dir } from '../core/rtl/dir' ;
99import { FormControl , FormsModule , ReactiveFormsModule } from '@angular/forms' ;
10+ import { ViewportRuler } from '../core/overlay/position/viewport-ruler' ;
1011
1112describe ( 'MdSelect' , ( ) => {
1213 let overlayContainerElement : HTMLElement ;
@@ -19,17 +20,31 @@ describe('MdSelect', () => {
1920 providers : [
2021 { provide : OverlayContainer , useFactory : ( ) => {
2122 overlayContainerElement = document . createElement ( 'div' ) ;
23+ overlayContainerElement . style . position = 'fixed' ;
24+ overlayContainerElement . style . top = '0' ;
25+ overlayContainerElement . style . left = '0' ;
26+ document . body . appendChild ( overlayContainerElement ) ;
27+
28+ // remove body padding to keep consistent cross-browser
29+ document . body . style . padding = '0' ;
30+ document . body . style . margin = '0' ;
31+
2232 return { getContainerElement : ( ) => overlayContainerElement } ;
2333 } } ,
2434 { provide : Dir , useFactory : ( ) => {
2535 return dir = { value : 'ltr' } ;
26- } }
36+ } } ,
37+ { provide : ViewportRuler , useClass : FakeViewportRuler }
2738 ]
2839 } ) ;
2940
3041 TestBed . compileComponents ( ) ;
3142 } ) ) ;
3243
44+ afterEach ( ( ) => {
45+ document . body . removeChild ( overlayContainerElement ) ;
46+ } ) ;
47+
3348 describe ( 'overlay panel' , ( ) => {
3449 let fixture : ComponentFixture < BasicSelect > ;
3550 let trigger : HTMLElement ;
@@ -457,19 +472,78 @@ describe('MdSelect', () => {
457472
458473 trigger . click ( ) ;
459474 fixture . detectChanges ( ) ;
460- expect ( fixture . componentInstance . select . _getPanelState ( ) ) . toEqual ( 'showing -ltr' ) ;
475+ expect ( fixture . componentInstance . select . _getPanelState ( ) ) . toEqual ( 'top -ltr' ) ;
461476 } ) ;
462477
463478 it ( 'should use the rtl panel state when the dir is rtl' , ( ) => {
464479 dir . value = 'rtl' ;
465480
466481 trigger . click ( ) ;
467482 fixture . detectChanges ( ) ;
468- expect ( fixture . componentInstance . select . _getPanelState ( ) ) . toEqual ( 'showing -rtl' ) ;
483+ expect ( fixture . componentInstance . select . _getPanelState ( ) ) . toEqual ( 'top -rtl' ) ;
469484 } ) ;
470485
471486 } ) ;
472487
488+ describe ( 'positioning' , ( ) => {
489+ let fixture : ComponentFixture < BasicSelect > ;
490+ let trigger : HTMLElement ;
491+
492+ beforeEach ( ( ) => {
493+ fixture = TestBed . createComponent ( BasicSelect ) ;
494+ fixture . detectChanges ( ) ;
495+ trigger = fixture . debugElement . query ( By . css ( '.md-select-trigger' ) ) . nativeElement ;
496+ } ) ;
497+
498+ it ( 'should open below the trigger if the panel will fit' , ( ) => {
499+ trigger . click ( ) ;
500+ fixture . detectChanges ( ) ;
501+
502+ const overlayPane = overlayContainerElement . children [ 0 ] as HTMLElement ;
503+ const overlayRect = overlayPane . getBoundingClientRect ( ) ;
504+ const triggerRect = trigger . getBoundingClientRect ( ) ;
505+
506+ // when the select panel opens below the trigger, the tops of the trigger and the overlay
507+ // should be aligned.
508+ expect ( overlayRect . top . toFixed ( 2 ) )
509+ . toEqual ( triggerRect . top . toFixed ( 2 ) , `Expected panel to open below by default.` ) ;
510+
511+ // animation should match the position
512+ expect ( fixture . componentInstance . select . _getPanelState ( ) )
513+ . toEqual ( 'top-ltr' , `Expected panel animation values to match the position.` ) ;
514+ expect ( fixture . componentInstance . select . _transformOrigin )
515+ . toBe ( 'top' , `Expected panel animation to originate at the top.` ) ;
516+ } ) ;
517+
518+ it ( 'should open above the trigger if there is not space below for the panel' , ( ) => {
519+ // Push trigger to the bottom part of viewport, so it doesn't have space to open
520+ // in its default position below the trigger.
521+ trigger . style . position = 'relative' ;
522+ trigger . style . top = '650px' ;
523+
524+ trigger . click ( ) ;
525+ fixture . detectChanges ( ) ;
526+
527+ const overlayPane = overlayContainerElement . children [ 0 ] as HTMLElement ;
528+ const overlayRect = overlayPane . getBoundingClientRect ( ) ;
529+ const triggerRect = trigger . getBoundingClientRect ( ) ;
530+
531+ // In "above" position, the bottom edges of the overlay and the origin are aligned.
532+ // To find the overlay top, subtract the panel height from the origin's bottom edge.
533+ const expectedTop = triggerRect . bottom - overlayRect . height ;
534+ expect ( overlayRect . top . toFixed ( 2 ) )
535+ . toEqual ( expectedTop . toFixed ( 2 ) ,
536+ `Expected panel to open above the trigger if below wouldn't fit.` ) ;
537+
538+ // animation should match the position
539+ expect ( fixture . componentInstance . select . _getPanelState ( ) )
540+ . toEqual ( 'bottom-ltr' , `Expected panel animation values to match the position.` ) ;
541+ expect ( fixture . componentInstance . select . _transformOrigin )
542+ . toBe ( 'bottom' , `Expected panel animation to originate at the bottom.` ) ;
543+ } ) ;
544+
545+ } ) ;
546+
473547 describe ( 'accessibility' , ( ) => {
474548 let fixture : ComponentFixture < BasicSelect > ;
475549
@@ -658,3 +732,15 @@ function dispatchEvent(eventName: string, element: HTMLElement): void {
658732 event . initEvent ( eventName , true , true ) ;
659733 element . dispatchEvent ( event ) ;
660734}
735+
736+ class FakeViewportRuler {
737+ getViewportRect ( ) {
738+ return {
739+ left : 0 , top : 0 , width : 1014 , height : 686 , bottom : 686 , right : 1014
740+ } ;
741+ }
742+
743+ getViewportScrollPosition ( ) {
744+ return { top : 0 , left : 0 } ;
745+ }
746+ }
0 commit comments