| /* |
| * Copyright 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.paging.compose |
| |
| import androidx.compose.foundation.layout.Spacer |
| import androidx.compose.foundation.layout.height |
| import androidx.compose.foundation.layout.width |
| import androidx.compose.foundation.lazy.LazyColumn |
| import androidx.compose.foundation.lazy.LazyListState |
| import androidx.compose.foundation.lazy.LazyRow |
| import androidx.compose.foundation.lazy.rememberLazyListState |
| import androidx.compose.foundation.text.BasicText |
| import androidx.compose.runtime.Composable |
| import androidx.compose.runtime.remember |
| import androidx.compose.ui.Modifier |
| import androidx.compose.ui.platform.testTag |
| import androidx.compose.ui.test.assertIsDisplayed |
| import androidx.compose.ui.test.assertIsNotDisplayed |
| import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo |
| import androidx.compose.ui.test.junit4.createComposeRule |
| import androidx.compose.ui.test.onNodeWithTag |
| import androidx.compose.ui.test.onNodeWithText |
| import androidx.compose.ui.unit.dp |
| import androidx.paging.CombinedLoadStates |
| import androidx.paging.LoadState |
| import androidx.paging.LoadStates |
| import androidx.paging.Pager |
| import androidx.paging.PagingConfig |
| import androidx.paging.PagingSource |
| import androidx.paging.TestPagingSource |
| import androidx.test.ext.junit.runners.AndroidJUnit4 |
| import androidx.test.filters.LargeTest |
| import com.google.common.truth.Truth.assertThat |
| import kotlinx.coroutines.isActive |
| import kotlinx.coroutines.runBlocking |
| import kotlinx.coroutines.ExperimentalCoroutinesApi |
| import kotlinx.coroutines.test.StandardTestDispatcher |
| import org.junit.Assert.assertFalse |
| import org.junit.Ignore |
| import org.junit.Rule |
| import org.junit.Test |
| import org.junit.runner.RunWith |
| |
| @OptIn(ExperimentalCoroutinesApi::class) |
| @LargeTest |
| @RunWith(AndroidJUnit4::class) |
| class LazyPagingItemsTest { |
| @get:Rule |
| val rule = createComposeRule() |
| |
| val items = (1..10).toList().map { it } |
| private val itemsSizePx = 30f |
| private val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() } |
| |
| private fun createPager( |
| config: PagingConfig = PagingConfig( |
| pageSize = 1, |
| enablePlaceholders = false, |
| maxSize = 200, |
| initialLoadSize = 3, |
| prefetchDistance = 1, |
| ), |
| loadDelay: Long = 0, |
| pagingSourceFactory: () -> PagingSource<Int, Int> = { |
| TestPagingSource(items = items, loadDelay = loadDelay) |
| } |
| ): Pager<Int, Int> { |
| return Pager(config = config, pagingSourceFactory = pagingSourceFactory) |
| } |
| |
| private fun createPagerWithPlaceholders( |
| config: PagingConfig = PagingConfig( |
| pageSize = 1, |
| enablePlaceholders = true, |
| maxSize = 200, |
| initialLoadSize = 3, |
| prefetchDistance = 0, |
| ) |
| ) = Pager( |
| config = config, |
| pagingSourceFactory = { TestPagingSource(items = items, loadDelay = 0) } |
| ) |
| |
| @Test |
| fun lazyPagingInitialLoadState() { |
| val pager = createPager() |
| val loadStates: MutableList<CombinedLoadStates> = mutableListOf() |
| rule.setContent { |
| val lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| loadStates.add(lazyPagingItems.loadState) |
| } |
| |
| rule.waitForIdle() |
| |
| val expected = CombinedLoadStates( |
| refresh = LoadState.Loading, |
| prepend = LoadState.NotLoading(false), |
| append = LoadState.NotLoading(false), |
| source = LoadStates( |
| LoadState.Loading, |
| LoadState.NotLoading(false), |
| LoadState.NotLoading(false) |
| ), |
| mediator = null |
| ) |
| assertThat(loadStates).isNotEmpty() |
| assertThat(loadStates.first()).isEqualTo(expected) |
| } |
| |
| @Test |
| fun lazyPagingLoadStateAfterRefresh() { |
| val pager = createPager(loadDelay = 100) |
| |
| val loadStates: MutableList<CombinedLoadStates> = mutableListOf() |
| |
| lateinit var lazyPagingItems: LazyPagingItems<Int> |
| rule.setContent { |
| lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| loadStates.add(lazyPagingItems.loadState) |
| } |
| |
| // wait for both compose and paging to complete |
| rule.waitUntil { |
| rule.waitForIdle() |
| lazyPagingItems.loadState.refresh == LoadState.NotLoading(false) |
| } |
| |
| rule.runOnIdle { |
| // we only want loadStates after manual refresh |
| loadStates.clear() |
| lazyPagingItems.refresh() |
| } |
| |
| // wait for both compose and paging to complete |
| rule.waitUntil { |
| rule.waitForIdle() |
| lazyPagingItems.loadState.refresh == LoadState.NotLoading(false) |
| } |
| |
| assertThat(loadStates).isNotEmpty() |
| val expected = CombinedLoadStates( |
| refresh = LoadState.Loading, |
| prepend = LoadState.NotLoading(false), |
| append = LoadState.NotLoading(false), |
| source = LoadStates( |
| LoadState.Loading, |
| LoadState.NotLoading(false), |
| LoadState.NotLoading(false) |
| ), |
| mediator = null |
| ) |
| assertThat(loadStates.first()).isEqualTo(expected) |
| } |
| |
| @Test |
| fun lazyPagingColumnShowsItems() { |
| val pager = createPager() |
| rule.setContent { |
| val lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| LazyColumn(Modifier.height(200.dp)) { |
| items(count = lazyPagingItems.itemCount) { index -> |
| val item = lazyPagingItems[index] |
| Spacer(Modifier.height(101.dp).fillParentMaxWidth().testTag("$item")) |
| } |
| } |
| } |
| |
| rule.waitForIdle() |
| |
| rule.onNodeWithTag("1") |
| .assertIsDisplayed() |
| |
| rule.onNodeWithTag("2") |
| .assertIsDisplayed() |
| |
| rule.onNodeWithTag("3") |
| .assertDoesNotExist() |
| |
| rule.onNodeWithTag("4") |
| .assertDoesNotExist() |
| } |
| |
| @Suppress("DEPRECATION") |
| @Test |
| fun lazyPagingColumnShowsIndexedItems() { |
| val pager = createPager() |
| rule.setContent { |
| val lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| LazyColumn(Modifier.height(200.dp)) { |
| itemsIndexed(lazyPagingItems) { index, item -> |
| Spacer( |
| Modifier.height(101.dp).fillParentMaxWidth() |
| .testTag("$index-$item") |
| ) |
| } |
| } |
| } |
| |
| rule.waitForIdle() |
| |
| rule.onNodeWithTag("0-1") |
| .assertIsDisplayed() |
| |
| rule.onNodeWithTag("1-2") |
| .assertIsDisplayed() |
| |
| rule.onNodeWithTag("2-3") |
| .assertDoesNotExist() |
| |
| rule.onNodeWithTag("3-4") |
| .assertDoesNotExist() |
| } |
| |
| @Test |
| fun lazyPagingRowShowsItems() { |
| val pager = createPager() |
| rule.setContent { |
| val lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| LazyRow(Modifier.width(200.dp)) { |
| items(count = lazyPagingItems.itemCount) { index -> |
| val item = lazyPagingItems[index] |
| Spacer(Modifier.width(101.dp).fillParentMaxHeight().testTag("$item")) |
| } |
| } |
| } |
| |
| rule.waitForIdle() |
| |
| rule.onNodeWithTag("1") |
| .assertIsDisplayed() |
| |
| rule.onNodeWithTag("2") |
| .assertIsDisplayed() |
| |
| rule.onNodeWithTag("3") |
| .assertDoesNotExist() |
| |
| rule.onNodeWithTag("4") |
| .assertDoesNotExist() |
| } |
| |
| @Suppress("DEPRECATION") |
| @Test |
| fun lazyPagingRowShowsIndexedItems() { |
| val pager = createPager() |
| rule.setContent { |
| val lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| LazyRow(Modifier.width(200.dp)) { |
| itemsIndexed(lazyPagingItems) { index, item -> |
| Spacer( |
| Modifier.width(101.dp).fillParentMaxHeight() |
| .testTag("$index-$item") |
| ) |
| } |
| } |
| } |
| |
| rule.waitForIdle() |
| |
| rule.onNodeWithTag("0-1") |
| .assertIsDisplayed() |
| |
| rule.onNodeWithTag("1-2") |
| .assertIsDisplayed() |
| |
| rule.onNodeWithTag("2-3") |
| .assertDoesNotExist() |
| |
| rule.onNodeWithTag("3-4") |
| .assertDoesNotExist() |
| } |
| |
| @Test |
| fun differentContentTypes() { |
| val pager = createPagerWithPlaceholders() |
| lateinit var state: LazyListState |
| |
| rule.setContent { |
| state = rememberLazyListState() |
| |
| val lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| |
| for (i in 0 until lazyPagingItems.itemCount) { |
| lazyPagingItems[i] |
| } |
| |
| LazyColumn(Modifier.height(itemsSizeDp * 2.5f), state) { |
| item(contentType = "not-to-reuse--1") { |
| Content("-1") |
| } |
| item(contentType = "reuse") { |
| Content("0") |
| } |
| items( |
| count = lazyPagingItems.itemCount, |
| contentType = lazyPagingItems.itemContentType( |
| contentType = { if (it == 8) "reuse" else "not-to-reuse-$it" } |
| ) |
| ) { index -> |
| val item = lazyPagingItems[index] |
| Content("$item") |
| } |
| } |
| } |
| |
| rule.runOnIdle { |
| runBlocking { |
| state.scrollToItem(2) |
| // now items -1 and 0 are put into reusables |
| } |
| } |
| |
| rule.onNodeWithTag("-1") |
| .assertExists() |
| .assertIsNotDisplayed() |
| rule.onNodeWithTag("0") |
| .assertExists() |
| .assertIsNotDisplayed() |
| |
| rule.runOnIdle { |
| runBlocking { |
| state.scrollToItem(8) |
| // item 8 should reuse slot 0 |
| } |
| } |
| |
| rule.onNodeWithTag("-1") |
| .assertExists() |
| .assertIsNotDisplayed() |
| // node reused |
| rule.onNodeWithTag("0") |
| .assertDoesNotExist() |
| rule.onNodeWithTag("7") |
| .assertIsDisplayed() |
| rule.onNodeWithTag("8") |
| .assertIsDisplayed() |
| rule.onNodeWithTag("9") |
| .assertIsDisplayed() |
| } |
| |
| @Test |
| fun nullItemContentType() { |
| val pager = createPagerWithPlaceholders() |
| lateinit var state: LazyListState |
| |
| var loadedItem6 = false |
| |
| rule.setContent { |
| state = rememberLazyListState() |
| |
| val lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| // Trigger page fetch until all items 1-6 are loaded |
| for (i in 0 until minOf(lazyPagingItems.itemCount, 6)) { |
| lazyPagingItems[i] |
| loadedItem6 = lazyPagingItems.peek(i) == 6 |
| } |
| |
| LazyColumn(Modifier.height(itemsSizeDp * 2.5f), state) { |
| item(contentType = "not-to-reuse--1") { |
| Content("-1") |
| } |
| // to be reused later by placeholder item |
| item(contentType = PagingPlaceholderContentType) { |
| Content("0") |
| } |
| items( |
| count = lazyPagingItems.itemCount, |
| // item 7 would be null, which should default to PagingPlaceholderContentType |
| contentType = lazyPagingItems.itemContentType( |
| contentType = { "not-to-reuse-$it" } |
| ) |
| ) { index -> |
| val item = lazyPagingItems[index] |
| Content("$item") |
| } |
| } |
| } |
| |
| rule.waitUntil { |
| loadedItem6 |
| } |
| |
| rule.runOnIdle { |
| runBlocking { |
| state.scrollToItem(2) |
| // now items -1 and 0 are put into reusables |
| } |
| } |
| |
| rule.onNodeWithTag("-1") |
| .assertExists() |
| .assertIsNotDisplayed() |
| rule.onNodeWithTag("0") |
| .assertExists() |
| .assertIsNotDisplayed() |
| |
| rule.runOnIdle { |
| runBlocking { |
| state.scrollToItem(6) |
| // item 7 which is null should reuse slot 0 |
| } |
| } |
| |
| rule.onNodeWithTag("-1") |
| .assertExists() |
| .assertIsNotDisplayed() |
| // node reused |
| rule.onNodeWithTag("0") |
| .assertDoesNotExist() |
| } |
| |
| @Test |
| fun nullContentType() { |
| val pager = createPagerWithPlaceholders() |
| lateinit var state: LazyListState |
| |
| rule.setContent { |
| state = rememberLazyListState() |
| |
| val lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| for (i in 0 until lazyPagingItems.itemCount) { |
| lazyPagingItems[i] |
| } |
| |
| LazyColumn(Modifier.height(itemsSizeDp * 2.5f), state) { |
| item(contentType = "not-to-reuse--1") { |
| Content("-1") |
| } |
| // to be reused later by real items |
| item(contentType = null) { |
| Content("0") |
| } |
| items( |
| count = lazyPagingItems.itemCount, |
| // should default to null |
| contentType = lazyPagingItems.itemContentType(null) |
| ) { index -> |
| val item = lazyPagingItems[index] |
| Content("$item") |
| } |
| } |
| } |
| |
| rule.runOnIdle { |
| runBlocking { |
| state.scrollToItem(2) |
| // now items -1 and 0 are put into reusables |
| } |
| } |
| |
| rule.onNodeWithTag("-1") |
| .assertExists() |
| .assertIsNotDisplayed() |
| rule.onNodeWithTag("0") |
| .assertExists() |
| .assertIsNotDisplayed() |
| |
| rule.runOnIdle { |
| runBlocking { |
| // item 4 |
| state.scrollToItem(3) |
| } |
| } |
| |
| rule.onNodeWithTag("-1") |
| .assertExists() |
| .assertIsNotDisplayed() |
| // node reused |
| rule.onNodeWithTag("0") |
| .assertDoesNotExist() |
| rule.onNodeWithTag("4") |
| .assertExists() |
| .assertIsDisplayed() |
| } |
| |
| @Test |
| fun itemSnapshotList() { |
| lateinit var lazyPagingItems: LazyPagingItems<Int> |
| val pager = createPager() |
| var composedCount = 0 |
| rule.setContent { |
| lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| |
| for (i in 0 until lazyPagingItems.itemCount) { |
| lazyPagingItems[i] |
| } |
| composedCount = lazyPagingItems.itemCount |
| } |
| |
| rule.waitUntil { |
| composedCount == items.size |
| } |
| |
| assertThat(lazyPagingItems.itemSnapshotList).isEqualTo(items) |
| } |
| |
| @Test |
| fun peek() { |
| lateinit var lazyPagingItems: LazyPagingItems<Int> |
| var composedCount = 0 |
| val pager = createPager() |
| rule.setContent { |
| lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| |
| // Trigger page fetch until all items 0-6 are loaded |
| for (i in 0 until minOf(lazyPagingItems.itemCount, 5)) { |
| lazyPagingItems[i] |
| } |
| composedCount = lazyPagingItems.itemCount |
| } |
| |
| rule.waitUntil { |
| composedCount == 6 |
| } |
| |
| rule.runOnIdle { |
| assertThat(lazyPagingItems.itemCount).isEqualTo(6) |
| for (i in 0..4) { |
| assertThat(lazyPagingItems.peek(i)).isEqualTo(items[i]) |
| } |
| } |
| |
| rule.runOnIdle { |
| // Verify peek does not trigger page fetch. |
| assertThat(lazyPagingItems.itemCount).isEqualTo(6) |
| } |
| } |
| |
| @Test |
| fun retry() { |
| var factoryCallCount = 0 |
| lateinit var pagingSource: TestPagingSource |
| val pager = createPager { |
| factoryCallCount++ |
| pagingSource = TestPagingSource(items = items, loadDelay = 0) |
| if (factoryCallCount == 1) { |
| pagingSource.errorNextLoad = true |
| } |
| pagingSource |
| } |
| |
| lateinit var lazyPagingItems: LazyPagingItems<Int> |
| rule.setContent { |
| lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| } |
| |
| assertThat(lazyPagingItems.itemSnapshotList).isEmpty() |
| |
| lazyPagingItems.retry() |
| rule.waitForIdle() |
| |
| assertThat(lazyPagingItems.itemSnapshotList).isNotEmpty() |
| // Verify retry does not invalidate. |
| assertThat(factoryCallCount).isEqualTo(1) |
| } |
| |
| @Test |
| fun refresh() { |
| var factoryCallCount = 0 |
| lateinit var pagingSource: TestPagingSource |
| val pager = createPager { |
| factoryCallCount++ |
| pagingSource = TestPagingSource(items = items, loadDelay = 0) |
| if (factoryCallCount == 1) { |
| pagingSource.errorNextLoad = true |
| } |
| pagingSource |
| } |
| |
| lateinit var lazyPagingItems: LazyPagingItems<Int> |
| rule.setContent { |
| lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| } |
| |
| assertThat(lazyPagingItems.itemSnapshotList).isEmpty() |
| |
| lazyPagingItems.refresh() |
| rule.waitForIdle() |
| |
| assertThat(lazyPagingItems.itemSnapshotList).isNotEmpty() |
| assertThat(factoryCallCount).isEqualTo(2) |
| } |
| |
| @Test |
| fun itemCountIsObservable() { |
| val items = mutableListOf(0, 1) |
| val pager = createPager { |
| TestPagingSource(items = items, loadDelay = 0) |
| } |
| |
| var composedCount = 0 |
| lateinit var lazyPagingItems: LazyPagingItems<Int> |
| rule.setContent { |
| lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| composedCount = lazyPagingItems.itemCount |
| } |
| |
| rule.waitUntil { |
| composedCount == 2 |
| } |
| |
| rule.runOnIdle { |
| items += 2 |
| lazyPagingItems.refresh() |
| } |
| |
| rule.waitUntil { |
| composedCount == 3 |
| } |
| |
| rule.runOnIdle { |
| items.clear() |
| items.add(0) |
| lazyPagingItems.refresh() |
| } |
| |
| rule.waitUntil { |
| composedCount == 1 |
| } |
| } |
| |
| @Test |
| fun worksWhenUsedWithoutExtension() { |
| val items = mutableListOf(10, 20) |
| val pager = createPager { |
| TestPagingSource(items = items, loadDelay = 0) |
| } |
| |
| lateinit var lazyPagingItems: LazyPagingItems<Int> |
| rule.setContent { |
| lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| LazyColumn(Modifier.height(300.dp)) { |
| items(lazyPagingItems.itemCount) { |
| val item = lazyPagingItems[it] |
| Spacer(Modifier.height(101.dp).fillParentMaxWidth().testTag("$item")) |
| } |
| } |
| } |
| |
| rule.onNodeWithTag("10") |
| .assertIsDisplayed() |
| |
| rule.onNodeWithTag("20") |
| .assertIsDisplayed() |
| |
| rule.runOnIdle { |
| items.clear() |
| items.addAll(listOf(30, 20, 40)) |
| lazyPagingItems.refresh() |
| } |
| |
| rule.onNodeWithTag("30") |
| .assertIsDisplayed() |
| |
| rule.onNodeWithTag("20") |
| .assertIsDisplayed() |
| |
| rule.onNodeWithTag("40") |
| .assertIsDisplayed() |
| |
| rule.onNodeWithTag("10") |
| .assertDoesNotExist() |
| } |
| |
| @Test |
| fun updatingItem() { |
| val items = mutableListOf(1, 2, 3) |
| val pager = createPager( |
| PagingConfig( |
| pageSize = 3, |
| enablePlaceholders = false, |
| maxSize = 200, |
| initialLoadSize = 3, |
| prefetchDistance = 3, |
| ) |
| ) { |
| TestPagingSource(items = items, loadDelay = 0) |
| } |
| |
| val itemSize = with(rule.density) { 100.dp.roundToPx().toDp() } |
| |
| lateinit var lazyPagingItems: LazyPagingItems<Int> |
| rule.setContent { |
| lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| LazyColumn(Modifier.height(itemSize * 3)) { |
| items(count = lazyPagingItems.itemCount) { index -> |
| val item = lazyPagingItems[index] |
| Spacer(Modifier.height(itemSize).fillParentMaxWidth().testTag("$item")) |
| } |
| } |
| } |
| |
| rule.runOnIdle { |
| items.clear() |
| items.addAll(listOf(1, 4, 3)) |
| lazyPagingItems.refresh() |
| } |
| |
| rule.onNodeWithTag("1") |
| .assertTopPositionInRootIsEqualTo(0.dp) |
| |
| rule.onNodeWithTag("4") |
| .assertTopPositionInRootIsEqualTo(itemSize) |
| |
| rule.onNodeWithTag("3") |
| .assertTopPositionInRootIsEqualTo(itemSize * 2) |
| |
| rule.onNodeWithTag("2") |
| .assertDoesNotExist() |
| } |
| |
| @Test |
| fun addingNewItem() { |
| val items = mutableListOf(1, 2) |
| val pager = createPager( |
| PagingConfig( |
| pageSize = 3, |
| enablePlaceholders = false, |
| maxSize = 200, |
| initialLoadSize = 3, |
| prefetchDistance = 3, |
| ) |
| ) { |
| TestPagingSource(items = items, loadDelay = 0) |
| } |
| |
| val itemSize = with(rule.density) { 100.dp.roundToPx().toDp() } |
| |
| lateinit var lazyPagingItems: LazyPagingItems<Int> |
| rule.setContent { |
| lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| LazyColumn(Modifier.height(itemSize * 3)) { |
| items(count = lazyPagingItems.itemCount) { index -> |
| val item = lazyPagingItems[index] |
| Spacer(Modifier.height(itemSize).fillParentMaxWidth().testTag("$item")) |
| } |
| } |
| } |
| |
| rule.runOnIdle { |
| items.clear() |
| items.addAll(listOf(1, 2, 3)) |
| lazyPagingItems.refresh() |
| } |
| |
| rule.onNodeWithTag("1") |
| .assertTopPositionInRootIsEqualTo(0.dp) |
| |
| rule.onNodeWithTag("2") |
| .assertTopPositionInRootIsEqualTo(itemSize) |
| |
| rule.onNodeWithTag("3") |
| .assertTopPositionInRootIsEqualTo(itemSize * 2) |
| } |
| |
| @Ignore // b/229089541 |
| @Test |
| fun removingItem() { |
| val items = mutableListOf(1, 2, 3) |
| val pager = createPager( |
| PagingConfig( |
| pageSize = 3, |
| enablePlaceholders = false, |
| maxSize = 200, |
| initialLoadSize = 3, |
| prefetchDistance = 3, |
| ) |
| ) { |
| TestPagingSource(items = items, loadDelay = 0) |
| } |
| |
| val itemSize = with(rule.density) { 100.dp.roundToPx().toDp() } |
| |
| lateinit var lazyPagingItems: LazyPagingItems<Int> |
| rule.setContent { |
| lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| LazyColumn(Modifier.height(itemSize * 3)) { |
| items(count = lazyPagingItems.itemCount) { index -> |
| val item = lazyPagingItems[index] |
| Spacer(Modifier.height(itemSize).fillParentMaxWidth().testTag("$item")) |
| } |
| } |
| } |
| |
| rule.runOnIdle { |
| items.clear() |
| items.addAll(listOf(2, 3)) |
| lazyPagingItems.refresh() |
| } |
| |
| rule.onNodeWithTag("2") |
| .assertTopPositionInRootIsEqualTo(0.dp) |
| |
| rule.onNodeWithTag("3") |
| .assertTopPositionInRootIsEqualTo(itemSize) |
| |
| rule.onNodeWithTag("1") |
| .assertDoesNotExist() |
| } |
| |
| @Test |
| fun stateIsMovedWithItemWithCustomKey_items() { |
| val items = mutableListOf(1) |
| val pager = createPager { |
| TestPagingSource(items = items, loadDelay = 0) |
| } |
| |
| lateinit var lazyPagingItems: LazyPagingItems<Int> |
| var counter = 0 |
| rule.setContent { |
| lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| LazyColumn { |
| items( |
| count = lazyPagingItems.itemCount, |
| key = lazyPagingItems.itemKey { it }, |
| ) { index -> |
| val item = lazyPagingItems[index] |
| BasicText( |
| "Item=$item. counter=${remember { counter++ }}" |
| ) |
| } |
| } |
| } |
| |
| rule.runOnIdle { |
| items.clear() |
| items.addAll(listOf(0, 1)) |
| lazyPagingItems.refresh() |
| } |
| |
| rule.onNodeWithText("Item=0. counter=1") |
| .assertExists() |
| |
| rule.onNodeWithText("Item=1. counter=0") |
| .assertExists() |
| } |
| |
| @Suppress("DEPRECATION") |
| @Test |
| fun stateIsMovedWithItemWithCustomKey_itemsIndexed() { |
| val items = mutableListOf(1) |
| val pager = createPager { |
| TestPagingSource(items = items, loadDelay = 0) |
| } |
| |
| lateinit var lazyPagingItems: LazyPagingItems<Int> |
| rule.setContent { |
| lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| LazyColumn { |
| itemsIndexed(lazyPagingItems, key = { _, item -> item }) { index, item -> |
| BasicText( |
| "Item=$item. index=$index. remembered index=${remember { index }}" |
| ) |
| } |
| } |
| } |
| |
| rule.runOnIdle { |
| items.clear() |
| items.addAll(listOf(0, 1)) |
| lazyPagingItems.refresh() |
| } |
| |
| rule.onNodeWithText("Item=0. index=0. remembered index=0") |
| .assertExists() |
| |
| rule.onNodeWithText("Item=1. index=1. remembered index=0") |
| .assertExists() |
| } |
| |
| @Test |
| fun collectOnDefaultThread() { |
| val items = mutableListOf(1, 2, 3) |
| val pager = createPager { |
| TestPagingSource(items = items, loadDelay = 0) |
| } |
| |
| lateinit var lazyPagingItems: LazyPagingItems<Int> |
| rule.setContent { |
| lazyPagingItems = pager.flow.collectAsLazyPagingItems() |
| } |
| |
| rule.waitUntil { |
| lazyPagingItems.itemCount == 3 |
| } |
| assertThat(lazyPagingItems.itemSnapshotList).containsExactlyElementsIn( |
| items |
| ) |
| } |
| |
| @Test |
| fun collectOnWorkerThread() { |
| val items = mutableListOf(1, 2, 3) |
| val pager = createPager { |
| TestPagingSource(items = items, loadDelay = 0) |
| } |
| |
| val context = StandardTestDispatcher() |
| lateinit var lazyPagingItems: LazyPagingItems<Int> |
| rule.setContent { |
| lazyPagingItems = pager.flow.collectAsLazyPagingItems(context) |
| } |
| |
| rule.runOnIdle { |
| assertFalse(context.isActive) |
| // collection should not have started yet |
| assertThat(lazyPagingItems.itemSnapshotList).isEmpty() |
| } |
| |
| // start LaunchedEffects |
| context.scheduler.advanceUntilIdle() |
| |
| rule.runOnIdle { |
| // continue with pagingDataDiffer collections |
| context.scheduler.advanceUntilIdle() |
| } |
| rule.waitUntil { |
| lazyPagingItems.itemCount == items.size |
| } |
| assertThat(lazyPagingItems.itemSnapshotList).containsExactlyElementsIn( |
| items |
| ) |
| } |
| |
| @Composable |
| private fun Content(tag: String) { |
| Spacer(Modifier.height(itemsSizeDp).width(10.dp).testTag(tag)) |
| } |
| } |