11import { TestBed , async , ComponentFixture } from '@angular/core/testing' ;
2- import { Component , OnDestroy , ViewChild } from '@angular/core' ;
2+ import { Component , OnDestroy , QueryList , ViewChild , ViewChildren } from '@angular/core' ;
33import { By } from '@angular/platform-browser' ;
44import { MdAutocompleteModule , MdAutocompleteTrigger } from './index' ;
55import { OverlayContainer } from '../core/overlay/overlay-container' ;
66import { MdInputModule } from '../input/index' ;
77import { Dir , LayoutDirection } from '../core/rtl/dir' ;
88import { FormControl , ReactiveFormsModule } from '@angular/forms' ;
99import { Subscription } from 'rxjs/Subscription' ;
10+ import { ENTER , DOWN_ARROW , SPACE } from '../core/keyboard/keycodes' ;
11+ import { MdOption } from '../core/option/option' ;
1012
1113describe ( 'MdAutocomplete' , ( ) => {
1214 let overlayContainerElement : HTMLElement ;
@@ -205,7 +207,7 @@ describe('MdAutocomplete', () => {
205207 fixture . detectChanges ( ) ;
206208
207209 expect ( input . value )
208- . toContain ( 'California' , `Expected text field to be filled with selected value.` ) ;
210+ . toContain ( 'California' , `Expected text field to fill with selected value.` ) ;
209211 } ) ;
210212
211213 it ( 'should mark the autocomplete control as dirty when an option is selected' , ( ) => {
@@ -236,6 +238,159 @@ describe('MdAutocomplete', () => {
236238
237239 } ) ;
238240
241+ describe ( 'keyboard events' , ( ) => {
242+ let fixture : ComponentFixture < SimpleAutocomplete > ;
243+ let input : HTMLInputElement ;
244+ let DOWN_ARROW_EVENT : KeyboardEvent ;
245+ let ENTER_EVENT : KeyboardEvent ;
246+
247+ beforeEach ( ( ) => {
248+ fixture = TestBed . createComponent ( SimpleAutocomplete ) ;
249+ fixture . detectChanges ( ) ;
250+
251+ input = fixture . debugElement . query ( By . css ( 'input' ) ) . nativeElement ;
252+ DOWN_ARROW_EVENT = new FakeKeyboardEvent ( DOWN_ARROW ) as KeyboardEvent ;
253+ ENTER_EVENT = new FakeKeyboardEvent ( ENTER ) as KeyboardEvent ;
254+ } ) ;
255+
256+ it ( 'should should not focus the option when DOWN key is pressed' , ( ) => {
257+ fixture . componentInstance . trigger . openPanel ( ) ;
258+ fixture . detectChanges ( ) ;
259+
260+ spyOn ( fixture . componentInstance . options . first , 'focus' ) ;
261+
262+ fixture . componentInstance . trigger . _handleKeydown ( DOWN_ARROW_EVENT ) ;
263+ expect ( fixture . componentInstance . options . first . focus ) . not . toHaveBeenCalled ( ) ;
264+ } ) ;
265+
266+ it ( 'should set the active item to the first option when DOWN key is pressed' , async ( ( ) => {
267+ fixture . componentInstance . trigger . openPanel ( ) ;
268+ fixture . detectChanges ( ) ;
269+
270+ const optionEls =
271+ overlayContainerElement . querySelectorAll ( 'md-option' ) as NodeListOf < HTMLElement > ;
272+
273+ fixture . componentInstance . trigger . _handleKeydown ( DOWN_ARROW_EVENT ) ;
274+
275+ fixture . whenStable ( ) . then ( ( ) => {
276+ fixture . detectChanges ( ) ;
277+ expect ( fixture . componentInstance . trigger . activeOption )
278+ . toBe ( fixture . componentInstance . options . first , 'Expected first option to be active.' ) ;
279+ expect ( optionEls [ 0 ] . classList ) . toContain ( 'md-active' ) ;
280+ expect ( optionEls [ 1 ] . classList ) . not . toContain ( 'md-active' ) ;
281+
282+ fixture . componentInstance . trigger . _handleKeydown ( DOWN_ARROW_EVENT ) ;
283+
284+ fixture . whenStable ( ) . then ( ( ) => {
285+ fixture . detectChanges ( ) ;
286+ expect ( fixture . componentInstance . trigger . activeOption )
287+ . toBe ( fixture . componentInstance . options . toArray ( ) [ 1 ] ,
288+ 'Expected second option to be active.' ) ;
289+ expect ( optionEls [ 0 ] . classList ) . not . toContain ( 'md-active' ) ;
290+ expect ( optionEls [ 1 ] . classList ) . toContain ( 'md-active' ) ;
291+ } ) ;
292+ } ) ;
293+ } ) ) ;
294+
295+ it ( 'should set the active item properly after filtering' , async ( ( ) => {
296+ fixture . componentInstance . trigger . openPanel ( ) ;
297+ fixture . detectChanges ( ) ;
298+
299+ fixture . componentInstance . trigger . _handleKeydown ( DOWN_ARROW_EVENT ) ;
300+
301+ fixture . whenStable ( ) . then ( ( ) => {
302+ fixture . detectChanges ( ) ;
303+ input . value = 'o' ;
304+ dispatchEvent ( 'input' , input ) ;
305+ fixture . detectChanges ( ) ;
306+
307+ const optionEls =
308+ overlayContainerElement . querySelectorAll ( 'md-option' ) as NodeListOf < HTMLElement > ;
309+
310+ fixture . componentInstance . trigger . _handleKeydown ( DOWN_ARROW_EVENT ) ;
311+
312+ fixture . whenStable ( ) . then ( ( ) => {
313+ fixture . detectChanges ( ) ;
314+ expect ( fixture . componentInstance . trigger . activeOption )
315+ . toBe ( fixture . componentInstance . options . first , 'Expected first option to be active.' ) ;
316+ expect ( optionEls [ 0 ] . classList ) . toContain ( 'md-active' ) ;
317+ expect ( optionEls [ 1 ] . classList ) . not . toContain ( 'md-active' ) ;
318+ } ) ;
319+ } ) ;
320+ } ) ) ;
321+
322+ it ( 'should fill the text field when an option is selected with ENTER' , ( ) => {
323+ fixture . componentInstance . trigger . openPanel ( ) ;
324+ fixture . detectChanges ( ) ;
325+
326+ fixture . componentInstance . trigger . _handleKeydown ( DOWN_ARROW_EVENT ) ;
327+ fixture . componentInstance . trigger . _handleKeydown ( ENTER_EVENT ) ;
328+ fixture . detectChanges ( ) ;
329+
330+ expect ( input . value )
331+ . toContain ( 'Alabama' , `Expected text field to fill with selected value on ENTER.` ) ;
332+ } ) ;
333+
334+ it ( 'should fill the text field, not select an option, when SPACE is entered' , ( ) => {
335+ fixture . componentInstance . trigger . openPanel ( ) ;
336+ fixture . detectChanges ( ) ;
337+
338+ input . value = 'New' ;
339+ dispatchEvent ( 'input' , input ) ;
340+ fixture . detectChanges ( ) ;
341+
342+ const SPACE_EVENT = new FakeKeyboardEvent ( SPACE ) as KeyboardEvent ;
343+ fixture . componentInstance . trigger . _handleKeydown ( DOWN_ARROW_EVENT ) ;
344+ fixture . componentInstance . trigger . _handleKeydown ( SPACE_EVENT ) ;
345+ fixture . detectChanges ( ) ;
346+
347+ expect ( input . value )
348+ . not . toContain ( 'New York' , `Expected option not to be selected on SPACE.` ) ;
349+ } ) ;
350+
351+ it ( 'should mark the control as dirty when an option is selected from the keyboard' , ( ) => {
352+ fixture . componentInstance . trigger . openPanel ( ) ;
353+ fixture . detectChanges ( ) ;
354+
355+ expect ( fixture . componentInstance . stateCtrl . dirty )
356+ . toBe ( false , `Expected control to start out pristine.` ) ;
357+
358+ fixture . componentInstance . trigger . _handleKeydown ( DOWN_ARROW_EVENT ) ;
359+ fixture . componentInstance . trigger . _handleKeydown ( ENTER_EVENT ) ;
360+ fixture . detectChanges ( ) ;
361+
362+ expect ( fixture . componentInstance . stateCtrl . dirty )
363+ . toBe ( true , `Expected control to become dirty when option was selected by ENTER.` ) ;
364+ } ) ;
365+
366+ it ( 'should open the panel again when typing after making a selection' , async ( ( ) => {
367+ fixture . componentInstance . trigger . openPanel ( ) ;
368+ fixture . detectChanges ( ) ;
369+
370+ fixture . componentInstance . trigger . _handleKeydown ( DOWN_ARROW_EVENT ) ;
371+ fixture . componentInstance . trigger . _handleKeydown ( ENTER_EVENT ) ;
372+ fixture . detectChanges ( ) ;
373+
374+ fixture . whenStable ( ) . then ( ( ) => {
375+ expect ( fixture . componentInstance . trigger . panelOpen )
376+ . toBe ( false , `Expected panel state to read closed after ENTER key.` ) ;
377+ expect ( overlayContainerElement . textContent )
378+ . toEqual ( '' , `Expected panel to close after ENTER key.` ) ;
379+
380+ // 65 is the keycode for "a"
381+ const A_KEY = new FakeKeyboardEvent ( 65 ) as KeyboardEvent ;
382+ fixture . componentInstance . trigger . _handleKeydown ( A_KEY ) ;
383+ fixture . detectChanges ( ) ;
384+
385+ expect ( fixture . componentInstance . trigger . panelOpen )
386+ . toBe ( true , `Expected panel state to read open when typing in input.` ) ;
387+ expect ( overlayContainerElement . textContent )
388+ . toContain ( 'Alabama' , `Expected panel to display when typing in input.` ) ;
389+ } ) ;
390+ } ) ) ;
391+
392+ } ) ;
393+
239394} ) ;
240395
241396@Component ( {
@@ -257,6 +412,7 @@ class SimpleAutocomplete implements OnDestroy {
257412 valueSub : Subscription ;
258413
259414 @ViewChild ( MdAutocompleteTrigger ) trigger : MdAutocompleteTrigger ;
415+ @ViewChildren ( MdOption ) options : QueryList < MdOption > ;
260416
261417 states = [
262418 { code : 'AL' , name : 'Alabama' } ,
@@ -301,4 +457,11 @@ function dispatchEvent(eventName: string, element: HTMLElement): void {
301457 element . dispatchEvent ( event ) ;
302458}
303459
460+ /** This is a mock keyboard event to test keyboard events in the autocomplete. */
461+ class FakeKeyboardEvent {
462+ constructor ( public keyCode : number ) { }
463+ preventDefault ( ) { }
464+ }
465+
466+
304467
0 commit comments