1
1
import { TestBed , async , ComponentFixture } from '@angular/core/testing' ;
2
2
import { By } from '@angular/platform-browser' ;
3
- import { Component , DebugElement , QueryList , ViewChild , ViewChildren } from '@angular/core' ;
3
+ import { Component , QueryList , ViewChild , ViewChildren } from '@angular/core' ;
4
4
import { MdSelectModule } from './index' ;
5
5
import { OverlayContainer } from '../core/overlay/overlay-container' ;
6
6
import { MdSelect } from './select' ;
7
7
import { MdOption } from './option' ;
8
8
import { Dir } from '../core/rtl/dir' ;
9
+ import { FormControl , ReactiveFormsModule } from '@angular/forms' ;
9
10
10
11
describe ( 'MdSelect' , ( ) => {
11
12
let overlayContainerElement : HTMLElement ;
12
13
let dir : { value : string } ;
13
14
14
15
beforeEach ( async ( ( ) => {
15
16
TestBed . configureTestingModule ( {
16
- imports : [ MdSelectModule . forRoot ( ) ] ,
17
+ imports : [ MdSelectModule . forRoot ( ) , ReactiveFormsModule ] ,
17
18
declarations : [ BasicSelect ] ,
18
19
providers : [
19
20
{ provide : OverlayContainer , useFactory : ( ) => {
@@ -190,7 +191,7 @@ describe('MdSelect', () => {
190
191
} ) ) ;
191
192
192
193
it ( 'should select an option that was added after initialization' , ( ) => {
193
- fixture . componentInstance . foods . push ( { viewValue : 'Pasta' } ) ;
194
+ fixture . componentInstance . foods . push ( { viewValue : 'Pasta' , value : 'pasta-3' } ) ;
194
195
trigger . click ( ) ;
195
196
fixture . detectChanges ( ) ;
196
197
@@ -206,6 +207,114 @@ describe('MdSelect', () => {
206
207
207
208
} ) ;
208
209
210
+ describe ( 'forms integration' , ( ) => {
211
+ let fixture : ComponentFixture < BasicSelect > ;
212
+ let trigger : HTMLElement ;
213
+
214
+ beforeEach ( ( ) => {
215
+ fixture = TestBed . createComponent ( BasicSelect ) ;
216
+ fixture . detectChanges ( ) ;
217
+
218
+ trigger = fixture . debugElement . query ( By . css ( '.md-select-trigger' ) ) . nativeElement ;
219
+ } ) ;
220
+
221
+ it ( 'should set the view value from the form' , ( ) => {
222
+ let value = fixture . debugElement . query ( By . css ( '.md-select-value' ) ) ;
223
+ expect ( value ) . toBeNull ( 'Expected trigger to start with empty value.' ) ;
224
+
225
+ fixture . componentInstance . control . setValue ( 'pizza-1' ) ;
226
+ fixture . detectChanges ( ) ;
227
+
228
+ value = fixture . debugElement . query ( By . css ( '.md-select-value' ) ) ;
229
+ expect ( value . nativeElement . textContent ) . toContain ( 'Pizza' ,
230
+ `Expected trigger to be populated by the control's new value.` ) ;
231
+
232
+ trigger . click ( ) ;
233
+ fixture . detectChanges ( ) ;
234
+
235
+ const options =
236
+ overlayContainerElement . querySelectorAll ( 'md-option' ) as NodeListOf < HTMLElement > ;
237
+ expect ( options [ 1 ] . classList ) . toContain ( 'md-selected' ,
238
+ `Expected the option with the control's new value to be selected.` ) ;
239
+ } ) ;
240
+
241
+ it ( 'should update the form value when the view changes' , ( ) => {
242
+ expect ( fixture . componentInstance . control . value ) . toEqual ( null ,
243
+ `Expected the control's value to be null initially.` ) ;
244
+
245
+ trigger . click ( ) ;
246
+ fixture . detectChanges ( ) ;
247
+
248
+ const option = overlayContainerElement . querySelector ( 'md-option' ) as HTMLElement ;
249
+ option . click ( ) ;
250
+ fixture . detectChanges ( ) ;
251
+
252
+ expect ( fixture . componentInstance . control . value ) . toEqual ( 'steak-0' ,
253
+ `Expected the control's value to be set to the new option the user selected.` ) ;
254
+ } ) ;
255
+
256
+ it ( 'should set the control to touched when the select is touched' , ( ) => {
257
+ expect ( fixture . componentInstance . control . touched ) . toEqual ( false ,
258
+ `Expected the control to start off as untouched.` ) ;
259
+
260
+ trigger . click ( ) ;
261
+ dispatchEvent ( 'blur' , trigger ) ;
262
+ fixture . detectChanges ( ) ;
263
+ expect ( fixture . componentInstance . control . touched ) . toEqual ( false ,
264
+ `Expected the control to stay untouched when menu opened.` ) ;
265
+
266
+ const backdrop =
267
+ overlayContainerElement . querySelector ( '.md-overlay-backdrop' ) as HTMLElement ;
268
+ backdrop . click ( ) ;
269
+ dispatchEvent ( 'blur' , trigger ) ;
270
+ fixture . detectChanges ( ) ;
271
+ expect ( fixture . componentInstance . control . touched ) . toEqual ( true ,
272
+ `Expected the control to be touched as soon as focus left the select.` ) ;
273
+ } ) ;
274
+
275
+ it ( 'should set the control to dirty when the select\'s value changes in the DOM' , ( ) => {
276
+ expect ( fixture . componentInstance . control . dirty ) . toEqual ( false ,
277
+ `Expected control to start out pristine.` ) ;
278
+
279
+ trigger . click ( ) ;
280
+ fixture . detectChanges ( ) ;
281
+
282
+ const option = overlayContainerElement . querySelector ( 'md-option' ) as HTMLElement ;
283
+ option . click ( ) ;
284
+ fixture . detectChanges ( ) ;
285
+
286
+ expect ( fixture . componentInstance . control . dirty ) . toEqual ( true ,
287
+ `Expected control to be dirty after value was changed by user.` ) ;
288
+ } ) ;
289
+
290
+ it ( 'should not set the control to dirty when the value changes programmatically' , ( ) => {
291
+ expect ( fixture . componentInstance . control . dirty ) . toEqual ( false ,
292
+ `Expected control to start out pristine.` ) ;
293
+
294
+ fixture . componentInstance . control . setValue ( 'pizza-1' ) ;
295
+
296
+ expect ( fixture . componentInstance . control . dirty ) . toEqual ( false ,
297
+ `Expected control to stay pristine after value was changed programmatically.` ) ;
298
+ } ) ;
299
+
300
+
301
+ it ( 'should set an asterisk after the placeholder if the control is required' , ( ) => {
302
+ const placeholder =
303
+ fixture . debugElement . query ( By . css ( '.md-select-placeholder' ) ) . nativeElement ;
304
+ const initialContent = getComputedStyle ( placeholder , '::after' ) . getPropertyValue ( 'content' ) ;
305
+
306
+ // must support both default cases to work in all browsers in Saucelabs
307
+ expect ( initialContent === 'none' || initialContent === '' ) . toBe ( true ,
308
+ `Expected placeholder not to have an asterisk, as the control was not required.` ) ;
309
+
310
+ fixture . componentInstance . isRequired = true ;
311
+ fixture . detectChanges ( ) ;
312
+ expect ( getComputedStyle ( placeholder , '::after' ) . getPropertyValue ( 'content' ) ) . toContain ( '*' ,
313
+ `Expected placeholder to have an asterisk, as control was required.` ) ;
314
+ } ) ;
315
+
316
+ } ) ;
317
+
209
318
describe ( 'animations' , ( ) => {
210
319
let fixture : ComponentFixture < BasicSelect > ;
211
320
let trigger : HTMLElement ;
@@ -278,22 +387,40 @@ describe('MdSelect', () => {
278
387
} ) ;
279
388
280
389
describe ( 'for select' , ( ) => {
281
- let select : DebugElement ;
390
+ let select : HTMLElement ;
282
391
283
392
beforeEach ( ( ) => {
284
- select = fixture . debugElement . query ( By . css ( 'md-select' ) ) ;
393
+ select = fixture . debugElement . query ( By . css ( 'md-select' ) ) . nativeElement ;
285
394
} ) ;
286
395
287
396
it ( 'should set the role of the select to listbox' , ( ) => {
288
- expect ( select . nativeElement . getAttribute ( 'role' ) ) . toEqual ( 'listbox' ) ;
397
+ expect ( select . getAttribute ( 'role' ) ) . toEqual ( 'listbox' ) ;
289
398
} ) ;
290
399
291
400
it ( 'should set the aria label of the select to the placeholder' , ( ) => {
292
- expect ( select . nativeElement . getAttribute ( 'aria-label' ) ) . toEqual ( 'Food' ) ;
401
+ expect ( select . getAttribute ( 'aria-label' ) ) . toEqual ( 'Food' ) ;
293
402
} ) ;
294
403
295
404
it ( 'should set the tabindex of the select to 0' , ( ) => {
296
- expect ( select . nativeElement . getAttribute ( 'tabindex' ) ) . toEqual ( '0' ) ;
405
+ expect ( select . getAttribute ( 'tabindex' ) ) . toEqual ( '0' ) ;
406
+ } ) ;
407
+
408
+ it ( 'should set aria-required for required selects' , ( ) => {
409
+ expect ( select . getAttribute ( 'aria-required' ) ) . toEqual ( 'false' ) ;
410
+
411
+ fixture . componentInstance . isRequired = true ;
412
+ fixture . detectChanges ( ) ;
413
+
414
+ expect ( select . getAttribute ( 'aria-required' ) ) . toEqual ( 'true' ) ;
415
+ } ) ;
416
+
417
+ it ( 'should set aria-invalid for selects that are invalid' , ( ) => {
418
+ expect ( select . getAttribute ( 'aria-invalid' ) ) . toEqual ( 'false' ) ;
419
+
420
+ fixture . componentInstance . isRequired = true ;
421
+ fixture . detectChanges ( ) ;
422
+
423
+ expect ( select . getAttribute ( 'aria-invalid' ) ) . toEqual ( 'true' ) ;
297
424
} ) ;
298
425
299
426
} ) ;
@@ -347,19 +474,34 @@ describe('MdSelect', () => {
347
474
@Component ( {
348
475
selector : 'basic-select' ,
349
476
template : `
350
- <md-select placeholder="Food">
351
- <md-option *ngFor="let food of foods">{{ food.viewValue }}</md-option>
477
+ <md-select placeholder="Food" [formControl]="control" [required]="isRequired" >
478
+ <md-option *ngFor="let food of foods" [value]="food.value" >{{ food.viewValue }}</md-option>
352
479
</md-select>
353
480
`
354
481
} )
355
482
class BasicSelect {
356
483
foods = [
357
- { viewValue : 'Steak' } ,
358
- { viewValue : 'Pizza' } ,
359
- { viewValue : 'Tacos' } ,
484
+ { value : 'steak-0' , viewValue : 'Steak' } ,
485
+ { value : 'pizza-1' , viewValue : 'Pizza' } ,
486
+ { value : 'tacos-2' , viewValue : 'Tacos' } ,
360
487
] ;
488
+ control = new FormControl ( ) ;
489
+ isRequired : boolean ;
361
490
362
491
@ViewChild ( MdSelect ) select : MdSelect ;
363
492
@ViewChildren ( MdOption ) options : QueryList < MdOption > ;
493
+ }
364
494
495
+ /**
496
+ * TODO: Move this to core testing utility until Angular has event faking
497
+ * support.
498
+ *
499
+ * Dispatches an event from an element.
500
+ * @param eventName Name of the event
501
+ * @param element The element from which the event will be dispatched.
502
+ */
503
+ function dispatchEvent ( eventName : string , element : HTMLElement ) : void {
504
+ let event = document . createEvent ( 'Event' ) ;
505
+ event . initEvent ( eventName , true , true ) ;
506
+ element . dispatchEvent ( event ) ;
365
507
}
0 commit comments