1
+ import { ArrayDataSource } from '@angular/cdk/collections' ;
1
2
import { dispatchFakeEvent } from '@angular/cdk/testing' ;
2
3
import { Component , Input , ViewChild , ViewContainerRef , ViewEncapsulation } from '@angular/core' ;
3
4
import { ComponentFixture , fakeAsync , flush , TestBed } from '@angular/core/testing' ;
4
- import { animationFrameScheduler } from 'rxjs' ;
5
+ import { animationFrameScheduler , Subject } from 'rxjs' ;
5
6
import { ScrollingModule } from './scrolling-module' ;
6
7
import { CdkVirtualForOf } from './virtual-for-of' ;
7
8
import { CdkVirtualScrollViewport } from './virtual-scroll-viewport' ;
@@ -272,25 +273,9 @@ describe('CdkVirtualScrollViewport', () => {
272
273
expect ( viewport . getOffsetToRenderedContentStart ( ) )
273
274
. toBe ( testComponent . itemSize , 'should be scrolled to bottom of 5 item list' ) ;
274
275
} ) ) ;
275
- } ) ;
276
-
277
- describe ( 'with FixedSizeVirtualScrollStrategy and horizontal orientation' , ( ) => {
278
- let fixture : ComponentFixture < FixedHorizontalVirtualScroll > ;
279
- let testComponent : FixedHorizontalVirtualScroll ;
280
- let viewport : CdkVirtualScrollViewport ;
281
276
282
- beforeEach ( ( ) => {
283
- TestBed . configureTestingModule ( {
284
- imports : [ ScrollingModule ] ,
285
- declarations : [ FixedHorizontalVirtualScroll ] ,
286
- } ) . compileComponents ( ) ;
287
-
288
- fixture = TestBed . createComponent ( FixedHorizontalVirtualScroll ) ;
289
- testComponent = fixture . componentInstance ;
290
- viewport = testComponent . viewport ;
291
- } ) ;
292
-
293
- it ( 'should update viewport as user scrolls right' , fakeAsync ( ( ) => {
277
+ it ( 'should update viewport as user scrolls right in horizontal mode' , fakeAsync ( ( ) => {
278
+ testComponent . orientation = 'horizontal' ;
294
279
finishInit ( fixture ) ;
295
280
296
281
const maxOffset =
@@ -315,7 +300,8 @@ describe('CdkVirtualScrollViewport', () => {
315
300
}
316
301
} ) ) ;
317
302
318
- it ( 'should update viewport as user scrolls left' , fakeAsync ( ( ) => {
303
+ it ( 'should update viewport as user scrolls left in horizontal mode' , fakeAsync ( ( ) => {
304
+ testComponent . orientation = 'horizontal' ;
319
305
finishInit ( fixture ) ;
320
306
321
307
const maxOffset =
@@ -339,6 +325,134 @@ describe('CdkVirtualScrollViewport', () => {
339
325
`rendered content size should match expected value at offset ${ offset } ` ) ;
340
326
}
341
327
} ) ) ;
328
+
329
+ it ( 'should work with an Observable' , fakeAsync ( ( ) => {
330
+ const data = new Subject < number [ ] > ( ) ;
331
+ testComponent . items = data as any ;
332
+ finishInit ( fixture ) ;
333
+
334
+ expect ( viewport . getRenderedRange ( ) )
335
+ . toEqual ( { start : 0 , end : 0 } , 'no items should be rendered' ) ;
336
+
337
+ data . next ( [ 1 , 2 , 3 ] ) ;
338
+ fixture . detectChanges ( ) ;
339
+
340
+ expect ( viewport . getRenderedRange ( ) )
341
+ . toEqual ( { start : 0 , end : 3 } , 'newly emitted items should be rendered' ) ;
342
+ } ) ) ;
343
+
344
+ it ( 'should work with a DataSource' , fakeAsync ( ( ) => {
345
+ const data = new Subject < number [ ] > ( ) ;
346
+ testComponent . items = new ArrayDataSource ( data ) as any ;
347
+ finishInit ( fixture ) ;
348
+ flush ( ) ;
349
+
350
+ expect ( viewport . getRenderedRange ( ) )
351
+ . toEqual ( { start : 0 , end : 0 } , 'no items should be rendered' ) ;
352
+
353
+ data . next ( [ 1 , 2 , 3 ] ) ;
354
+ fixture . detectChanges ( ) ;
355
+ flush ( ) ;
356
+
357
+ expect ( viewport . getRenderedRange ( ) )
358
+ . toEqual ( { start : 0 , end : 3 } , 'newly emitted items should be rendered' ) ;
359
+ } ) ) ;
360
+
361
+ it ( 'should trackBy value by default' , fakeAsync ( ( ) => {
362
+ testComponent . items = [ ] ;
363
+ spyOn ( testComponent . virtualForViewContainer , 'detach' ) . and . callThrough ( ) ;
364
+ finishInit ( fixture ) ;
365
+
366
+ testComponent . items = [ 0 ] ;
367
+ fixture . detectChanges ( ) ;
368
+
369
+ expect ( testComponent . virtualForViewContainer . detach ) . not . toHaveBeenCalled ( ) ;
370
+
371
+ testComponent . items = [ 1 ] ;
372
+ fixture . detectChanges ( ) ;
373
+
374
+ expect ( testComponent . virtualForViewContainer . detach ) . toHaveBeenCalled ( ) ;
375
+ } ) ) ;
376
+
377
+ it ( 'should trackBy index when specified' , fakeAsync ( ( ) => {
378
+ testComponent . trackBy = i => i ;
379
+ testComponent . items = [ ] ;
380
+ spyOn ( testComponent . virtualForViewContainer , 'detach' ) . and . callThrough ( ) ;
381
+ finishInit ( fixture ) ;
382
+
383
+ testComponent . items = [ 0 ] ;
384
+ fixture . detectChanges ( ) ;
385
+
386
+ expect ( testComponent . virtualForViewContainer . detach ) . not . toHaveBeenCalled ( ) ;
387
+
388
+ testComponent . items = [ 1 ] ;
389
+ fixture . detectChanges ( ) ;
390
+
391
+ expect ( testComponent . virtualForViewContainer . detach ) . not . toHaveBeenCalled ( ) ;
392
+ } ) ) ;
393
+
394
+ it ( 'should recycle views when template cache is large enough to accommodate' , fakeAsync ( ( ) => {
395
+ testComponent . trackBy = i => i ;
396
+ const spy =
397
+ spyOn ( testComponent . virtualForViewContainer , 'createEmbeddedView' ) . and . callThrough ( ) ;
398
+ finishInit ( fixture ) ;
399
+
400
+ // Should create views for the initial rendered items.
401
+ expect ( testComponent . virtualForViewContainer . createEmbeddedView ) . toHaveBeenCalledTimes ( 4 ) ;
402
+
403
+ spy . calls . reset ( ) ;
404
+ triggerScroll ( viewport , 10 ) ;
405
+ fixture . detectChanges ( ) ;
406
+
407
+ // As we first start to scroll we need to create one more item. This is because the first item
408
+ // is still partially on screen and therefore can't be removed yet. At the same time a new
409
+ // item is now partially on the screen at the bottom and so a new view is needed.
410
+ expect ( testComponent . virtualForViewContainer . createEmbeddedView ) . toHaveBeenCalledTimes ( 1 ) ;
411
+
412
+ spy . calls . reset ( ) ;
413
+ const maxOffset =
414
+ testComponent . itemSize * testComponent . items . length - testComponent . viewportSize ;
415
+ for ( let offset = 10 ; offset <= maxOffset ; offset += 10 ) {
416
+ triggerScroll ( viewport , offset ) ;
417
+ fixture . detectChanges ( ) ;
418
+ }
419
+
420
+ // As we scroll through the rest of the items, no new views should be created, our existing 5
421
+ // can just be recycled as appropriate.
422
+ expect ( testComponent . virtualForViewContainer . createEmbeddedView ) . not . toHaveBeenCalled ( ) ;
423
+ } ) ) ;
424
+
425
+ it ( 'should not recycle views when template cache is full' , fakeAsync ( ( ) => {
426
+ testComponent . trackBy = i => i ;
427
+ testComponent . templateCacheSize = 0 ;
428
+ const spy =
429
+ spyOn ( testComponent . virtualForViewContainer , 'createEmbeddedView' ) . and . callThrough ( ) ;
430
+ finishInit ( fixture ) ;
431
+
432
+ // Should create views for the initial rendered items.
433
+ expect ( testComponent . virtualForViewContainer . createEmbeddedView ) . toHaveBeenCalledTimes ( 4 ) ;
434
+
435
+ spy . calls . reset ( ) ;
436
+ triggerScroll ( viewport , 10 ) ;
437
+ fixture . detectChanges ( ) ;
438
+
439
+ // As we first start to scroll we need to create one more item. This is because the first item
440
+ // is still partially on screen and therefore can't be removed yet. At the same time a new
441
+ // item is now partially on the screen at the bottom and so a new view is needed.
442
+ expect ( testComponent . virtualForViewContainer . createEmbeddedView ) . toHaveBeenCalledTimes ( 1 ) ;
443
+
444
+ spy . calls . reset ( ) ;
445
+ const maxOffset =
446
+ testComponent . itemSize * testComponent . items . length - testComponent . viewportSize ;
447
+ for ( let offset = 10 ; offset <= maxOffset ; offset += 10 ) {
448
+ triggerScroll ( viewport , offset ) ;
449
+ fixture . detectChanges ( ) ;
450
+ }
451
+
452
+ // Since our template cache size is 0, as we scroll through the rest of the items, we need to
453
+ // create a new view for each one.
454
+ expect ( testComponent . virtualForViewContainer . createEmbeddedView ) . toHaveBeenCalledTimes ( 5 ) ;
455
+ } ) ) ;
342
456
} ) ;
343
457
} ) ;
344
458
@@ -370,48 +484,46 @@ function triggerScroll(viewport: CdkVirtualScrollViewport, offset?: number) {
370
484
@Component ( {
371
485
template : `
372
486
<cdk-virtual-scroll-viewport
373
- class="viewport" [itemSize]="itemSize" [bufferSize]="bufferSize"
374
- [style.height.px]="viewportSize" [style.width.px]="viewportCrossSize">
375
- <div class="item" *cdkVirtualFor="let item of items; let i = index"
376
- [style.height.px]="itemSize">
487
+ [itemSize]="itemSize" [bufferSize]="bufferSize" [orientation]="orientation"
488
+ [style.height.px]="viewportHeight" [style.width.px]="viewportWidth">
489
+ <div class="item"
490
+ *cdkVirtualFor="let item of items; let i = index; trackBy: trackBy; \
491
+ templateCacheSize: templateCacheSize"
492
+ [style.height.px]="itemSize" [style.width.px]="itemSize">
377
493
{{i}} - {{item}}
378
494
</div>
379
495
</cdk-virtual-scroll-viewport>
380
496
` ,
381
- styles : [ `.cdk-virtual-scroll-content-wrapper { display: flex; flex-direction: column; }` ] ,
497
+ styles : [ `
498
+ .cdk-virtual-scroll-content-wrapper {
499
+ display: flex;
500
+ flex-direction: column;
501
+ }
502
+
503
+ .cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper {
504
+ flex-direction: row;
505
+ }
506
+ ` ] ,
382
507
encapsulation : ViewEncapsulation . None ,
383
508
} )
384
509
class FixedVirtualScroll {
385
510
@ViewChild ( CdkVirtualScrollViewport ) viewport : CdkVirtualScrollViewport ;
386
- @ViewChild ( CdkVirtualForOf , { read : ViewContainerRef } ) cdkForOfViewContainer : ViewContainerRef ;
511
+ @ViewChild ( CdkVirtualForOf , { read : ViewContainerRef } ) virtualForViewContainer : ViewContainerRef ;
387
512
513
+ @Input ( ) orientation = 'vertical' ;
388
514
@Input ( ) viewportSize = 200 ;
389
515
@Input ( ) viewportCrossSize = 100 ;
390
516
@Input ( ) itemSize = 50 ;
391
517
@Input ( ) bufferSize = 0 ;
392
518
@Input ( ) items = Array ( 10 ) . fill ( 0 ) . map ( ( _ , i ) => i ) ;
393
- }
519
+ @Input ( ) trackBy ;
520
+ @Input ( ) templateCacheSize = 20 ;
394
521
395
- @Component ( {
396
- template : `
397
- <cdk-virtual-scroll-viewport
398
- class="viewport" [itemSize]="itemSize" [bufferSize]="bufferSize" orientation="horizontal"
399
- [style.width.px]="viewportSize" [style.height.px]="viewportCrossSize">
400
- <div class="item" *cdkVirtualFor="let item of items; let i = index"
401
- [style.width.px]="itemSize">
402
- {{i}} - {{item}}
403
- </div>
404
- </cdk-virtual-scroll-viewport>
405
- ` ,
406
- styles : [ `.cdk-virtual-scroll-content-wrapper { display: flex; flex-direction: row; }` ] ,
407
- encapsulation : ViewEncapsulation . None ,
408
- } )
409
- class FixedHorizontalVirtualScroll {
410
- @ViewChild ( CdkVirtualScrollViewport ) viewport : CdkVirtualScrollViewport ;
522
+ get viewportWidth ( ) {
523
+ return this . orientation == 'horizontal' ? this . viewportSize : this . viewportCrossSize ;
524
+ }
411
525
412
- @Input ( ) viewportSize = 200 ;
413
- @Input ( ) viewportCrossSize = 100 ;
414
- @Input ( ) itemSize = 50 ;
415
- @Input ( ) bufferSize = 0 ;
416
- @Input ( ) items = Array ( 10 ) . fill ( 0 ) . map ( ( _ , i ) => i ) ;
526
+ get viewportHeight ( ) {
527
+ return this . orientation == 'horizontal' ? this . viewportCrossSize : this . viewportSize ;
528
+ }
417
529
}
0 commit comments