1 /* 2 * Copyright 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.paging.testing 18 19 import androidx.kruth.assertThat 20 import androidx.paging.PagingConfig 21 import androidx.paging.PagingSource.LoadResult 22 import androidx.paging.PagingState 23 import androidx.paging.TestPagingSource 24 import kotlin.test.Test 25 import kotlin.test.assertFailsWith 26 import kotlin.test.assertTrue 27 import kotlinx.coroutines.ExperimentalCoroutinesApi 28 import kotlinx.coroutines.delay 29 import kotlinx.coroutines.launch 30 import kotlinx.coroutines.test.advanceUntilIdle 31 import kotlinx.coroutines.test.runTest 32 33 @OptIn(ExperimentalCoroutinesApi::class) 34 class TestPagerTest { 35 36 @Test refresh_nullKeynull37 fun refresh_nullKey() { 38 val source = TestPagingSource() 39 val pager = TestPager(CONFIG, source) 40 41 runTest { 42 val result = pager.refresh(null) as LoadResult.Page 43 44 assertThat(result.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4)).inOrder() 45 } 46 } 47 48 @Test refresh_withInitialKeynull49 fun refresh_withInitialKey() { 50 val source = TestPagingSource() 51 val pager = TestPager(CONFIG, source) 52 53 runTest { 54 val result = pager.refresh(50) as LoadResult.Page 55 56 assertThat(result.data).containsExactlyElementsIn(listOf(50, 51, 52, 53, 54)).inOrder() 57 } 58 } 59 60 @Test refresh_returnErrornull61 fun refresh_returnError() { 62 val source = TestPagingSource() 63 val pager = TestPager(CONFIG, source) 64 65 runTest { 66 source.errorNextLoad = true 67 val result = pager.refresh() 68 assertTrue(result is LoadResult.Error) 69 70 val page = pager.getLastLoadedPage() 71 assertThat(page).isNull() 72 } 73 } 74 75 @Test refresh_returnInvalidnull76 fun refresh_returnInvalid() { 77 val source = TestPagingSource() 78 val pager = TestPager(CONFIG, source) 79 80 runTest { 81 source.nextLoadResult = LoadResult.Invalid() 82 val result = pager.refresh() 83 assertTrue(result is LoadResult.Invalid) 84 85 val page = pager.getLastLoadedPage() 86 assertThat(page).isNull() 87 } 88 } 89 90 @Test refresh_invalidPagingSourcenull91 fun refresh_invalidPagingSource() { 92 val source = TestPagingSource() 93 val pager = TestPager(CONFIG, source) 94 95 runTest { 96 source.invalidate() 97 assertTrue(source.invalid) 98 // simulate a PagingSource that returns LoadResult.Invalid when it's invalidated 99 source.nextLoadResult = LoadResult.Invalid() 100 101 assertThat(pager.refresh()).isInstanceOf<LoadResult.Invalid<Int, Int>>() 102 } 103 } 104 105 @Test refresh_getLastLoadedPagenull106 fun refresh_getLastLoadedPage() { 107 val source = TestPagingSource() 108 val pager = TestPager(CONFIG, source) 109 110 runTest { 111 val page: LoadResult.Page<Int, Int>? = 112 pager.run { 113 refresh() 114 getLastLoadedPage() 115 } 116 assertThat(page).isNotNull() 117 assertThat(page?.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4)).inOrder() 118 } 119 } 120 121 @Test getLastLoadedPage_afterInvalidPagingSourcenull122 fun getLastLoadedPage_afterInvalidPagingSource() { 123 val source = TestPagingSource() 124 val pager = TestPager(CONFIG, source) 125 126 runTest { 127 val page = 128 pager.run { 129 refresh() 130 append() // page should be this appended page 131 source.invalidate() 132 assertTrue(source.invalid) 133 getLastLoadedPage() 134 } 135 assertThat(page).isNotNull() 136 assertThat(page?.data).containsExactlyElementsIn(listOf(5, 6, 7)).inOrder() 137 } 138 } 139 140 @Test refresh_getPagesnull141 fun refresh_getPages() { 142 val source = TestPagingSource() 143 val pager = TestPager(CONFIG, source) 144 145 runTest { 146 val pages = 147 pager.run { 148 refresh() 149 getPages() 150 } 151 assertThat(pages).hasSize(1) 152 assertThat(pages) 153 .containsExactlyElementsIn(listOf(listOf(0, 1, 2, 3, 4).asPage())) 154 .inOrder() 155 } 156 } 157 158 @Test <lambda>null159 fun getPages_multiplePages() = runTest { 160 val source = TestPagingSource() 161 val pager = TestPager(CONFIG, source) 162 163 pager.run { 164 refresh(20) 165 prepend() 166 } 167 assertThat(pager.getPages()) 168 .containsExactlyElementsIn( 169 listOf( 170 // prepend 171 listOf(17, 18, 19).asPage(), 172 // refresh 173 listOf(20, 21, 22, 23, 24).asPage(), 174 ) 175 ) 176 .inOrder() 177 } 178 179 @Test <lambda>null180 fun getPages_fromEmptyList() = runTest { 181 val source = TestPagingSource() 182 val pager = TestPager(CONFIG, source) 183 val pages = pager.getPages() 184 assertThat(pages).isEmpty() 185 } 186 187 @Test getPages_afterInvalidPagingSourcenull188 fun getPages_afterInvalidPagingSource() { 189 val source = TestPagingSource() 190 val pager = TestPager(CONFIG, source) 191 192 runTest { 193 val pages = 194 pager.run { 195 refresh() 196 append() 197 source.invalidate() 198 assertTrue(source.invalid) 199 getPages() 200 } 201 assertThat(pages) 202 .containsExactlyElementsIn( 203 listOf(listOf(0, 1, 2, 3, 4).asPage(), listOf(5, 6, 7).asPage()) 204 ) 205 .inOrder() 206 } 207 } 208 209 @Test <lambda>null210 fun getPages_multiThread() = runTest { 211 val source = TestPagingSource() 212 val pager = TestPager(CONFIG, source) 213 214 var pages: List<LoadResult.Page<Int, Int>>? = null 215 val job = launch { 216 pager.run { 217 refresh(20) // first 218 pages = getPages() // third 219 prepend() // fifth 220 } 221 } 222 job.start() 223 assertTrue(job.isActive) 224 val pages2 = 225 pager.run { 226 delay(200) // let launch start first 227 append() // second 228 prepend() // fourth 229 getPages() // sixth 230 } 231 232 advanceUntilIdle() 233 assertThat(pages) 234 .containsExactlyElementsIn( 235 listOf( 236 // should contain first and second load 237 listOf(20, 21, 22, 23, 24).asPage(), // refresh 238 listOf(25, 26, 27).asPage(), // append 239 ) 240 ) 241 .inOrder() 242 assertThat(pages2) 243 .containsExactlyElementsIn( 244 // should contain all loads 245 listOf( 246 listOf(14, 15, 16).asPage(), 247 listOf(17, 18, 19).asPage(), 248 listOf(20, 21, 22, 23, 24).asPage(), 249 listOf(25, 26, 27).asPage(), 250 ) 251 ) 252 .inOrder() 253 } 254 255 @Test multipleRefresh_onSinglePager_throwsnull256 fun multipleRefresh_onSinglePager_throws() { 257 val source = TestPagingSource() 258 val pager = TestPager(CONFIG, source) 259 260 runTest { 261 pager.run { 262 // second refresh should throw since testPager is not mult-generational 263 assertFailsWith<IllegalStateException> { 264 refresh() 265 refresh() 266 } 267 } 268 assertTrue(source.invalid) 269 // the first refresh should still have succeeded 270 assertThat(pager.getPages()).hasSize(1) 271 } 272 } 273 274 @Test multipleRefresh_onMultiplePagersnull275 fun multipleRefresh_onMultiplePagers() = runTest { 276 val source1 = TestPagingSource() 277 val pager1 = TestPager(CONFIG, source1) 278 279 // first gen 280 val result1 = pager1.run { refresh() } as LoadResult.Page 281 282 assertThat(result1.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4)).inOrder() 283 284 // second gen 285 val source2 = TestPagingSource() 286 val pager2 = TestPager(CONFIG, source2) 287 288 val result2 = pager2.run { refresh() } as LoadResult.Page 289 290 assertThat(result2.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4)).inOrder() 291 } 292 293 @Test <lambda>null294 fun simpleAppend() = runTest { 295 val source = TestPagingSource() 296 val pager = TestPager(CONFIG, source) 297 298 val result = 299 pager.run { 300 refresh(null) 301 append() 302 } as LoadResult.Page 303 304 assertThat(result.data).containsExactlyElementsIn(listOf(5, 6, 7)).inOrder() 305 assertThat(pager.getPages()) 306 .containsExactlyElementsIn( 307 listOf(listOf(0, 1, 2, 3, 4).asPage(), listOf(5, 6, 7).asPage()) 308 ) 309 .inOrder() 310 } 311 312 @Test <lambda>null313 fun simplePrepend() = runTest { 314 val source = TestPagingSource() 315 val pager = TestPager(CONFIG, source) 316 317 val result = 318 pager.run { 319 refresh(30) 320 prepend() 321 } as LoadResult.Page 322 323 assertThat(result.data).containsExactlyElementsIn(listOf(27, 28, 29)).inOrder() 324 // prepended pages should be inserted before refresh 325 assertThat(pager.getPages()) 326 .containsExactlyElementsIn( 327 listOf( 328 // prepend 329 listOf(27, 28, 29).asPage(), 330 // refresh 331 listOf(30, 31, 32, 33, 34).asPage() 332 ) 333 ) 334 .inOrder() 335 } 336 337 @Test <lambda>null338 fun append_beforeRefresh_throws() = runTest { 339 val source = TestPagingSource() 340 val pager = TestPager(CONFIG, source) 341 assertFailsWith<IllegalStateException> { pager.append() } 342 } 343 344 @Test prepend_beforeRefresh_throwsnull345 fun prepend_beforeRefresh_throws() = runTest { 346 val source = TestPagingSource() 347 val pager = TestPager(CONFIG, source) 348 assertFailsWith<IllegalStateException> { pager.prepend() } 349 } 350 351 @Test <lambda>null352 fun append_invalidPagingSource() = runTest { 353 val source = TestPagingSource() 354 val pager = TestPager(CONFIG, source) 355 356 val result = 357 pager.run { 358 refresh() 359 source.invalidate() 360 assertThat(source.invalid).isTrue() 361 // simulate a PagingSource which returns LoadResult.Invalid when it's invalidated 362 source.nextLoadResult = LoadResult.Invalid() 363 append() 364 } 365 assertThat(result).isInstanceOf<LoadResult.Invalid<Int, Int>>() 366 } 367 368 @Test <lambda>null369 fun prepend_invalidPagingSource() = runTest { 370 val source = TestPagingSource() 371 val pager = TestPager(CONFIG, source) 372 373 val result = 374 pager.run { 375 refresh(initialKey = 20) 376 source.invalidate() 377 assertThat(source.invalid).isTrue() 378 // simulate a PagingSource which returns LoadResult.Invalid when it's invalidated 379 source.nextLoadResult = LoadResult.Invalid() 380 prepend() 381 } 382 assertThat(result).isInstanceOf<LoadResult.Invalid<Int, Int>>() 383 } 384 385 @Test <lambda>null386 fun consecutive_append() = runTest { 387 val source = TestPagingSource() 388 val pager = TestPager(CONFIG, source) 389 390 pager.run { 391 refresh(20) 392 append() 393 append() 394 } as LoadResult.Page 395 396 assertThat(pager.getPages()) 397 .containsExactlyElementsIn( 398 listOf( 399 listOf(20, 21, 22, 23, 24).asPage(), 400 listOf(25, 26, 27).asPage(), 401 listOf(28, 29, 30).asPage() 402 ) 403 ) 404 .inOrder() 405 } 406 407 @Test <lambda>null408 fun consecutive_prepend() = runTest { 409 val source = TestPagingSource() 410 val pager = TestPager(CONFIG, source) 411 412 pager.run { 413 refresh(20) 414 prepend() 415 prepend() 416 } as LoadResult.Page 417 418 // prepended pages should be ordered before the refresh 419 assertThat(pager.getPages()) 420 .containsExactlyElementsIn( 421 listOf( 422 // 2nd prepend 423 listOf(14, 15, 16).asPage(), 424 // 1st prepend 425 listOf(17, 18, 19).asPage(), 426 // refresh 427 listOf(20, 21, 22, 23, 24).asPage(), 428 ) 429 ) 430 .inOrder() 431 } 432 433 @Test <lambda>null434 fun append_then_prepend() = runTest { 435 val source = TestPagingSource() 436 val pager = TestPager(CONFIG, source) 437 438 pager.run { 439 refresh(20) 440 append() 441 prepend() 442 } as LoadResult.Page 443 444 assertThat(pager.getPages()) 445 .containsExactlyElementsIn( 446 listOf( 447 // prepend 448 listOf(17, 18, 19).asPage(), 449 // refresh 450 listOf(20, 21, 22, 23, 24).asPage(), 451 // append 452 listOf(25, 26, 27).asPage(), 453 ) 454 ) 455 .inOrder() 456 } 457 458 @Test <lambda>null459 fun prepend_then_append() = runTest { 460 val source = TestPagingSource() 461 val pager = TestPager(CONFIG, source) 462 463 pager.run { 464 refresh(20) 465 prepend() 466 append() 467 } as LoadResult.Page 468 469 assertThat(pager.getPages()) 470 .containsExactlyElementsIn( 471 listOf( 472 // prepend 473 listOf(17, 18, 19).asPage(), 474 // refresh 475 listOf(20, 21, 22, 23, 24).asPage(), 476 // append 477 listOf(25, 26, 27).asPage(), 478 ) 479 ) 480 .inOrder() 481 } 482 483 @Test <lambda>null484 fun multiThread_loads() = runTest { 485 val source = TestPagingSource() 486 val pager = TestPager(CONFIG, source) 487 // load operations upon completion add an int to the list. 488 // after all loads complete, we assert the order that the ints were added. 489 val loadOrder = mutableListOf<Int>() 490 491 val job = launch { 492 pager.run { 493 refresh(20).also { loadOrder.add(1) } // first load 494 prepend().also { loadOrder.add(3) } // third load 495 append().also { loadOrder.add(5) } // fifth load 496 } 497 } 498 job.start() 499 assertTrue(job.isActive) 500 501 pager.run { 502 // give some time for job to start 503 delay(200) 504 append().also { loadOrder.add(2) } // second load 505 prepend().also { loadOrder.add(4) } // fourth load 506 } 507 508 advanceUntilIdle() 509 assertThat(loadOrder).containsExactlyElementsIn(listOf(1, 2, 3, 4, 5)).inOrder() 510 assertThat(pager.getPages()) 511 .containsExactlyElementsIn( 512 listOf( 513 listOf(14, 15, 16).asPage(), 514 listOf(17, 18, 19).asPage(), 515 listOf(20, 21, 22, 23, 24).asPage(), 516 listOf(25, 26, 27).asPage(), 517 listOf(28, 29, 30).asPage(), 518 ) 519 ) 520 .inOrder() 521 } 522 523 @Test <lambda>null524 fun multiThread_operations() = runTest { 525 val source = TestPagingSource() 526 val pager = TestPager(CONFIG, source) 527 // operations upon completion add an int to the list. 528 // after all operations complete, we assert the order that the ints were added. 529 val loadOrder = mutableListOf<Int>() 530 531 var lastLoadedPage: LoadResult.Page<Int, Int>? = null 532 val job = launch { 533 pager.run { 534 refresh(20).also { loadOrder.add(1) } // first operation 535 // third operation, should return first appended page 536 lastLoadedPage = getLastLoadedPage().also { loadOrder.add(3) } 537 append().also { loadOrder.add(5) } // fifth operation 538 prepend().also { loadOrder.add(7) } // last operation 539 } 540 } 541 job.start() 542 assertTrue(job.isActive) 543 544 val pages = 545 pager.run { 546 // give some time for job to start first 547 delay(200) 548 append().also { loadOrder.add(2) } // second operation 549 prepend().also { loadOrder.add(4) } // fourth operation 550 // sixth operation, should return 4 pages 551 getPages().also { loadOrder.add(6) } 552 } 553 554 advanceUntilIdle() 555 assertThat(loadOrder).containsExactlyElementsIn(listOf(1, 2, 3, 4, 5, 6, 7)).inOrder() 556 assertThat(lastLoadedPage) 557 .isEqualTo( 558 listOf(25, 26, 27).asPage(), 559 ) 560 // should not contain the second prepend, with a total of 4 pages 561 assertThat(pages) 562 .containsExactlyElementsIn( 563 listOf( 564 listOf(17, 18, 19).asPage(), // first prepend 565 listOf(20, 21, 22, 23, 24).asPage(), // refresh 566 listOf(25, 26, 27).asPage(), // first append 567 listOf(28, 29, 30).asPage(), // second append 568 ) 569 ) 570 .inOrder() 571 } 572 573 @Test <lambda>null574 fun getPagingStateWithAnchorPosition_placeHoldersEnabled() = runTest { 575 val source = TestPagingSource() 576 val pager = TestPager(CONFIG, source) 577 578 val state = 579 pager.run { 580 refresh(20) 581 prepend() 582 append() 583 getPagingState(7) 584 } 585 // in this case anchorPos is a placeholder at index 7 586 assertThat(state) 587 .isEqualTo( 588 PagingState( 589 pages = 590 listOf( 591 listOf(17, 18, 19).asPage(), 592 // refresh 593 listOf(20, 21, 22, 23, 24).asPage(), 594 // append 595 listOf(25, 26, 27).asPage(), 596 ), 597 anchorPosition = 7, 598 config = CONFIG, 599 leadingPlaceholderCount = 17 600 ) 601 ) 602 val source2 = TestPagingSource() 603 val pager2 = TestPager(CONFIG, source) 604 val page = pager2.run { refresh(source2.getRefreshKey(state)) } 605 assertThat(page).isEqualTo(listOf(7, 8, 9, 10, 11).asPage()) 606 } 607 608 @Test <lambda>null609 fun getPagingStateWithAnchorPosition_placeHoldersDisabled() = runTest { 610 val source = TestPagingSource(placeholdersEnabled = false) 611 val config = PagingConfig(pageSize = 3, initialLoadSize = 5, enablePlaceholders = false) 612 val pager = TestPager(config, source) 613 614 val state = 615 pager.run { 616 refresh(20) 617 prepend() 618 append() 619 getPagingState(7) 620 } 621 assertThat(state) 622 .isEqualTo( 623 PagingState( 624 pages = 625 listOf( 626 listOf(17, 18, 19).asPage(placeholdersEnabled = false), 627 // refresh 628 listOf(20, 21, 22, 23, 24).asPage(placeholdersEnabled = false), 629 // append 630 listOf(25, 26, 27).asPage(placeholdersEnabled = false), 631 ), 632 anchorPosition = 7, 633 config = config, 634 leadingPlaceholderCount = 0 635 ) 636 ) 637 val source2 = TestPagingSource() 638 val pager2 = TestPager(CONFIG, source) 639 val page = pager2.run { refresh(source2.getRefreshKey(state)) } 640 // without placeholders, Paging currently has no way to translate item[7] within loaded 641 // pages into its absolute position within available data. Hence anchorPosition 7 will 642 // reference item[7] within available data. 643 assertThat(page).isEqualTo(listOf(7, 8, 9, 10, 11).asPage(placeholdersEnabled = false)) 644 } 645 646 @Test <lambda>null647 fun getPagingStateWithAnchorPosition_indexOutOfBoundsWithPlaceholders() = runTest { 648 val source = TestPagingSource() 649 val pager = TestPager(CONFIG, source) 650 651 val msg = 652 assertFailsWith<IllegalStateException> { 653 pager.run { 654 refresh() 655 append() 656 getPagingState(-1) 657 } 658 } 659 .message 660 assertThat(msg) 661 .isEqualTo( 662 "anchorPosition -1 is out of bounds between [0..${ITEM_COUNT - 1}]. Please " + 663 "provide a valid anchorPosition." 664 ) 665 666 val msg2 = 667 assertFailsWith<IllegalStateException> { pager.getPagingState(ITEM_COUNT) }.message 668 assertThat(msg2) 669 .isEqualTo( 670 "anchorPosition $ITEM_COUNT is out of bounds between [0..${ITEM_COUNT - 1}]. " + 671 "Please provide a valid anchorPosition." 672 ) 673 } 674 675 @Test <lambda>null676 fun getPagingStateWithAnchorPosition_indexOutOfBoundsWithoutPlaceholders() = runTest { 677 val source = TestPagingSource() 678 val pager = 679 TestPager( 680 PagingConfig(pageSize = 3, initialLoadSize = 5, enablePlaceholders = false), 681 source 682 ) 683 684 val msg = 685 assertFailsWith<IllegalStateException> { 686 pager.run { 687 refresh() 688 append() 689 getPagingState(-1) 690 } 691 } 692 .message 693 assertThat(msg) 694 .isEqualTo( 695 "anchorPosition -1 is out of bounds between [0..7]. Please " + 696 "provide a valid anchorPosition." 697 ) 698 699 // total loaded items = 8, anchorPos with index 8 should be out of bounds 700 val msg2 = assertFailsWith<IllegalStateException> { pager.getPagingState(8) }.message 701 assertThat(msg2) 702 .isEqualTo( 703 "anchorPosition 8 is out of bounds between [0..7]. Please " + 704 "provide a valid anchorPosition." 705 ) 706 } 707 708 @Test <lambda>null709 fun getPagingStateWithAnchorLookup_placeHoldersEnabled() = runTest { 710 val source = TestPagingSource() 711 val pager = TestPager(CONFIG, source) 712 713 val state = 714 pager.run { 715 refresh(20) 716 prepend() 717 append() 718 getPagingState { it == TestPagingSource.ITEMS[22] } 719 } 720 assertThat(state) 721 .isEqualTo( 722 PagingState( 723 pages = 724 listOf( 725 listOf(17, 18, 19).asPage(), 726 // refresh 727 listOf(20, 21, 22, 23, 24).asPage(), 728 // append 729 listOf(25, 26, 27).asPage(), 730 ), 731 anchorPosition = 22, 732 config = CONFIG, 733 leadingPlaceholderCount = 17 734 ) 735 ) 736 // use state to getRefreshKey 737 val source2 = TestPagingSource() 738 val pager2 = TestPager(CONFIG, source) 739 val page = pager2.run { refresh(source2.getRefreshKey(state)) } 740 assertThat(page).isEqualTo(listOf(22, 23, 24, 25, 26).asPage()) 741 } 742 743 @Test <lambda>null744 fun getPagingStateWithAnchorLookup_placeHoldersDisabled() = runTest { 745 val source = TestPagingSource(placeholdersEnabled = false) 746 val config = PagingConfig(pageSize = 3, initialLoadSize = 5, enablePlaceholders = false) 747 val pager = TestPager(config, source) 748 749 val state = 750 pager.run { 751 refresh(20) 752 prepend() 753 append() 754 getPagingState { it == TestPagingSource.ITEMS[22] } // item 22 in this case 755 } 756 assertThat(state) 757 .isEqualTo( 758 PagingState( 759 pages = 760 listOf( 761 listOf(17, 18, 19).asPage(placeholdersEnabled = false), 762 // refresh 763 listOf(20, 21, 22, 23, 24).asPage(placeholdersEnabled = false), 764 // append 765 listOf(25, 26, 27).asPage(placeholdersEnabled = false), 766 ), 767 anchorPosition = 5, 768 config = config, 769 leadingPlaceholderCount = 0 770 ) 771 ) 772 // use state to getRefreshKey 773 val source2 = TestPagingSource() 774 val pager2 = TestPager(CONFIG, source) 775 val page = pager2.run { refresh(source2.getRefreshKey(state)) } 776 // without placeholders, Paging currently has no way to translate item[5] within loaded 777 // pages into its absolute position within available data. anchorPosition 5 will reference 778 // item[5] within available data. 779 assertThat(page).isEqualTo(listOf(5, 6, 7, 8, 9).asPage(placeholdersEnabled = false)) 780 } 781 782 @Test <lambda>null783 fun getPagingStateWithAnchorLookup_itemNotFoundThrows() = runTest { 784 val source = TestPagingSource(placeholdersEnabled = false) 785 val config = PagingConfig(pageSize = 3, initialLoadSize = 5, enablePlaceholders = false) 786 val pager = TestPager(config, source) 787 788 val msg = 789 assertFailsWith<IllegalArgumentException> { 790 pager.run { 791 refresh(20) 792 prepend() 793 append() 794 getPagingState { it == TestPagingSource.ITEMS[10] } 795 } 796 } 797 .message 798 assertThat(msg) 799 .isEqualTo( 800 "The given predicate has returned false for every loaded item. To generate a" + 801 "PagingState anchored to an item, the expected item must have already " + 802 "been loaded." 803 ) 804 } 805 806 @Test <lambda>null807 fun dropPrependedPage() = runTest { 808 val source = TestPagingSource() 809 val config = 810 PagingConfig( 811 pageSize = 3, 812 initialLoadSize = 5, 813 enablePlaceholders = false, 814 maxSize = 10 815 ) 816 val pager = TestPager(config, source) 817 pager.run { 818 refresh(20) 819 prepend() 820 } 821 assertThat(pager.getPages()) 822 .containsExactlyElementsIn( 823 listOf( 824 listOf(17, 18, 19).asPage(), 825 // refresh 826 listOf(20, 21, 22, 23, 24).asPage(), 827 ) 828 ) 829 830 // this append should trigger paging to drop the prepended page 831 pager.append() 832 assertThat(pager.getPages()) 833 .containsExactlyElementsIn( 834 listOf( 835 listOf(20, 21, 22, 23, 24).asPage(), 836 listOf(25, 26, 27).asPage(), 837 ) 838 ) 839 } 840 841 @Test <lambda>null842 fun dropAppendedPage() = runTest { 843 val source = TestPagingSource() 844 val config = 845 PagingConfig( 846 pageSize = 3, 847 initialLoadSize = 5, 848 enablePlaceholders = false, 849 maxSize = 10 850 ) 851 val pager = TestPager(config, source) 852 pager.run { 853 refresh(20) 854 append() 855 } 856 assertThat(pager.getPages()) 857 .containsExactlyElementsIn( 858 listOf( 859 listOf(20, 21, 22, 23, 24).asPage(), 860 listOf(25, 26, 27).asPage(), 861 ) 862 ) 863 864 // this prepend should trigger paging to drop the prepended page 865 pager.prepend() 866 assertThat(pager.getPages()) 867 .containsExactlyElementsIn( 868 listOf( 869 listOf(17, 18, 19).asPage(), 870 listOf(20, 21, 22, 23, 24).asPage(), 871 ) 872 ) 873 } 874 875 @Test <lambda>null876 fun dropInitialRefreshedPage() = runTest { 877 val source = TestPagingSource() 878 val config = 879 PagingConfig( 880 pageSize = 3, 881 initialLoadSize = 5, 882 enablePlaceholders = false, 883 maxSize = 10 884 ) 885 val pager = TestPager(config, source) 886 pager.run { 887 refresh(20) 888 append() 889 } 890 assertThat(pager.getPages()) 891 .containsExactlyElementsIn( 892 listOf( 893 listOf(20, 21, 22, 23, 24).asPage(), 894 listOf(25, 26, 27).asPage(), 895 ) 896 ) 897 898 // this append should trigger paging to drop the first page which is the initial refresh 899 pager.append() 900 assertThat(pager.getPages()) 901 .containsExactlyElementsIn( 902 listOf( 903 listOf(25, 26, 27).asPage(), 904 listOf(28, 29, 30).asPage(), 905 ) 906 ) 907 } 908 909 @Test <lambda>null910 fun dropRespectsPrefetchDistance_InDroppedDirection() = runTest { 911 val source = TestPagingSource() 912 val config = 913 PagingConfig( 914 pageSize = 1, 915 initialLoadSize = 10, 916 enablePlaceholders = false, 917 maxSize = 5, 918 prefetchDistance = 2 919 ) 920 val pager = TestPager(config, source) 921 pager.refresh(20) 922 assertThat(pager.getPages()) 923 .containsExactlyElementsIn( 924 listOf( 925 listOf(20, 21, 22, 23, 24, 25, 26, 27, 28, 29).asPage(), 926 ) 927 ) 928 929 // these appends should would normally trigger paging to drop first page, but it won't 930 // in this case due to prefetchDistance 931 pager.run { 932 append() 933 append() 934 } 935 assertThat(pager.getPages()) 936 .containsExactlyElementsIn( 937 listOf( 938 // second page counted towards prefetch distance 939 listOf(20, 21, 22, 23, 24, 25, 26, 27, 28, 29).asPage(), 940 // first page counted towards prefetch distance 941 listOf(30).asPage(), 942 listOf(31).asPage() 943 ) 944 ) 945 } 946 947 @Test <lambda>null948 fun drop_noOpUnderTwoPages() = runTest { 949 val source = TestPagingSource() 950 val config = 951 PagingConfig( 952 pageSize = 1, 953 initialLoadSize = 5, 954 enablePlaceholders = false, 955 maxSize = 3, 956 prefetchDistance = 1 957 ) 958 val pager = TestPager(config, source) 959 val result = pager.refresh() as LoadResult.Page 960 assertThat(result.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4)) 961 962 pager.append() 963 // data size exceeds maxSize but no data should be dropped 964 assertThat(pager.getPages().flatten()).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4, 5)) 965 } 966 967 private val CONFIG = 968 PagingConfig( 969 pageSize = 3, 970 initialLoadSize = 5, 971 ) 972 asPagenull973 private fun List<Int>.asPage(placeholdersEnabled: Boolean = true): LoadResult.Page<Int, Int> { 974 val itemsBefore = 975 if (placeholdersEnabled) { 976 if (first() == 0) 0 else first() 977 } else { 978 Int.MIN_VALUE 979 } 980 val itemsAfter = 981 if (placeholdersEnabled) { 982 if (last() == ITEM_COUNT - 1) 0 else ITEM_COUNT - 1 - last() 983 } else { 984 Int.MIN_VALUE 985 } 986 return LoadResult.Page( 987 data = this, 988 prevKey = if (first() == 0) null else first() - 1, 989 nextKey = if (last() == ITEM_COUNT - 1) null else last() + 1, 990 itemsBefore = itemsBefore, 991 itemsAfter = itemsAfter 992 ) 993 } 994 995 private val ITEM_COUNT = 100 996 } 997