1b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik/* 2b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik * Copyright 2017 The Android Open Source Project 3b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik * 4b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik * Licensed under the Apache License, Version 2.0 (the "License"); 5b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik * you may not use this file except in compliance with the License. 6b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik * You may obtain a copy of the License at 7b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik * 8b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik * http://www.apache.org/licenses/LICENSE-2.0 9b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik * 10b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik * Unless required by applicable law or agreed to in writing, software 11b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik * distributed under the License is distributed on an "AS IS" BASIS, 12b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik * See the License for the specific language governing permissions and 14b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik * limitations under the License. 15b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik */ 16b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 17ba069d50913c3fb250bb60ec310439db36895337Alan Viverettepackage androidx.paging 18b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 19b632de54cccea95ff4d879b512fca7281783b3a4Chris Craikimport org.junit.Assert.assertEquals 209bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craikimport org.junit.Assert.assertTrue 21b632de54cccea95ff4d879b512fca7281783b3a4Chris Craikimport org.junit.Assert.fail 22b632de54cccea95ff4d879b512fca7281783b3a4Chris Craikimport org.junit.Test 23b632de54cccea95ff4d879b512fca7281783b3a4Chris Craikimport org.junit.runner.RunWith 24b632de54cccea95ff4d879b512fca7281783b3a4Chris Craikimport org.junit.runners.JUnit4 25792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craikimport org.mockito.Mockito.mock 26792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craikimport org.mockito.Mockito.verify 27792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craikimport org.mockito.Mockito.verifyNoMoreInteractions 28b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 29b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik@RunWith(JUnit4::class) 30b632de54cccea95ff4d879b512fca7281783b3a4Chris Craikclass PageKeyedDataSourceTest { 31b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik private val mMainThread = TestExecutor() 32b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik private val mBackgroundThread = TestExecutor() 33b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 34b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik internal data class Item(val name: String) 35b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 36b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik internal data class Page(val prev: String?, val data: List<Item>, val next: String?) 37b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 38b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik internal class ItemDataSource(val data: Map<String, Page> = PAGE_MAP) 39b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik : PageKeyedDataSource<String, Item>() { 40b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 41b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik private fun getPage(key: String): Page = data[key]!! 42b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 43b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik override fun loadInitial( 44b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik params: LoadInitialParams<String>, 45b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik callback: LoadInitialCallback<String, Item>) { 46b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik val page = getPage(INIT_KEY) 47b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik callback.onResult(page.data, page.prev, page.next) 48b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 49b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 50b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<String, Item>) { 51b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik val page = getPage(params.key) 52b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik callback.onResult(page.data, page.prev) 53b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 54b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 55b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String, Item>) { 56b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik val page = getPage(params.key) 57b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik callback.onResult(page.data, page.next) 58b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 59b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 60b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 61b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik @Test 62b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik fun loadFullVerify() { 63b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik // validate paging entire ItemDataSource results in full, correctly ordered data 64b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik val pagedList = ContiguousPagedList<String, Item>(ItemDataSource(), 65b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik mMainThread, mBackgroundThread, 66e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik null, PagedList.Config.Builder().setPageSize(100).build(), null, 67e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik ContiguousPagedList.LAST_LOAD_UNSPECIFIED) 68b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 69b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik // validate initial load 70b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik assertEquals(PAGE_MAP[INIT_KEY]!!.data, pagedList) 71b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 72b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik // flush the remaining loads 73b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik for (i in 0..PAGE_MAP.keys.size) { 74b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik pagedList.loadAround(0) 75b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik pagedList.loadAround(pagedList.size - 1) 76b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik drain() 77b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 78b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 79b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik // validate full load 80b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik assertEquals(ITEM_LIST, pagedList) 81b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 82b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 837f66b3c746dfb80a4d6ce1533811a8f9cc15c755Chris Craik private fun performLoadInitial(invalidateDataSource: Boolean = false, 847f66b3c746dfb80a4d6ce1533811a8f9cc15c755Chris Craik callbackInvoker: 857f66b3c746dfb80a4d6ce1533811a8f9cc15c755Chris Craik (callback: PageKeyedDataSource.LoadInitialCallback<String, String>) -> Unit) { 86b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik val dataSource = object : PageKeyedDataSource<String, String>() { 87b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik override fun loadInitial( 88b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik params: LoadInitialParams<String>, 89b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik callback: LoadInitialCallback<String, String>) { 907f66b3c746dfb80a4d6ce1533811a8f9cc15c755Chris Craik if (invalidateDataSource) { 917f66b3c746dfb80a4d6ce1533811a8f9cc15c755Chris Craik // invalidate data source so it's invalid when onResult() called 927f66b3c746dfb80a4d6ce1533811a8f9cc15c755Chris Craik invalidate() 937f66b3c746dfb80a4d6ce1533811a8f9cc15c755Chris Craik } 94b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik callbackInvoker(callback) 95b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 96b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 97b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik override fun loadBefore( 98b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik params: LoadParams<String>, 99b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik callback: LoadCallback<String, String>) { 100b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik fail("loadBefore not expected") 101b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 102b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 103b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik override fun loadAfter( 104b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik params: LoadParams<String>, 105b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik callback: LoadCallback<String, String>) { 106b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik fail("loadAfter not expected") 107b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 108b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 109b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 110b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik ContiguousPagedList<String, String>( 111b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik dataSource, FailExecutor(), FailExecutor(), null, 112b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik PagedList.Config.Builder() 113b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik .setPageSize(10) 114b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik .build(), 115e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik "", 116e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik ContiguousPagedList.LAST_LOAD_UNSPECIFIED) 117b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 118b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 119b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik @Test 120b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik fun loadInitialCallbackSuccess() = performLoadInitial { 121b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik // LoadInitialCallback correct usage 122b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik it.onResult(listOf("a", "b"), 0, 2, null, null) 123b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 124b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 125b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik @Test 126b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik fun loadInitialCallbackNotPageSizeMultiple() = performLoadInitial { 127b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik // Keyed LoadInitialCallback *can* accept result that's not a multiple of page size 128b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik val elevenLetterList = List(11) { "" + 'a' + it } 129b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik it.onResult(elevenLetterList, 0, 12, null, null) 130b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 131b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 132b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik @Test(expected = IllegalArgumentException::class) 133b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik fun loadInitialCallbackListTooBig() = performLoadInitial { 134b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik // LoadInitialCallback can't accept pos + list > totalCount 135b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik it.onResult(listOf("a", "b", "c"), 0, 2, null, null) 136b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 137b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 138b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik @Test(expected = IllegalArgumentException::class) 139b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik fun loadInitialCallbackPositionTooLarge() = performLoadInitial { 140b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik // LoadInitialCallback can't accept pos + list > totalCount 141b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik it.onResult(listOf("a", "b"), 1, 2, null, null) 142b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 143b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 144b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik @Test(expected = IllegalArgumentException::class) 145b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik fun loadInitialCallbackPositionNegative() = performLoadInitial { 146b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik // LoadInitialCallback can't accept negative position 147b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik it.onResult(listOf("a", "b", "c"), -1, 2, null, null) 148b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 149b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 150b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik @Test(expected = IllegalArgumentException::class) 151b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik fun loadInitialCallbackEmptyCannotHavePlaceholders() = performLoadInitial { 152b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik // LoadInitialCallback can't accept empty result unless data set is empty 153b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik it.onResult(emptyList(), 0, 2, null, null) 154b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 155b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 1567f66b3c746dfb80a4d6ce1533811a8f9cc15c755Chris Craik @Test 1577f66b3c746dfb80a4d6ce1533811a8f9cc15c755Chris Craik fun initialLoadCallbackInvalidThreeArg() = performLoadInitial(invalidateDataSource = true) { 1587f66b3c746dfb80a4d6ce1533811a8f9cc15c755Chris Craik // LoadInitialCallback doesn't throw on invalid args if DataSource is invalid 1597f66b3c746dfb80a4d6ce1533811a8f9cc15c755Chris Craik it.onResult(emptyList(), 0, 1, null, null) 1607f66b3c746dfb80a4d6ce1533811a8f9cc15c755Chris Craik } 1617f66b3c746dfb80a4d6ce1533811a8f9cc15c755Chris Craik 162792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik private abstract class WrapperDataSource<K, A, B>(private val source: PageKeyedDataSource<K, A>) 163792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik : PageKeyedDataSource<K, B>() { 1649bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) { 1659bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik source.addInvalidatedCallback(onInvalidatedCallback) 166792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik } 167792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik 1689bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik override fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) { 1699bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik source.removeInvalidatedCallback(onInvalidatedCallback) 170792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik } 171792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik 1729bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik override fun invalidate() { 1739bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik source.invalidate() 1749bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik } 1759bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik 1769bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik override fun isInvalid(): Boolean { 1779bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik return source.isInvalid 178792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik } 179792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik 180792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik override fun loadInitial(params: LoadInitialParams<K>, 181792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik callback: LoadInitialCallback<K, B>) { 182792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik source.loadInitial(params, object : LoadInitialCallback<K, A>() { 183792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik override fun onResult(data: List<A>, position: Int, totalCount: Int, 184792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik previousPageKey: K?, nextPageKey: K?) { 185792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik callback.onResult(convert(data), position, totalCount, 186792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik previousPageKey, nextPageKey) 187792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik } 188792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik 189792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik override fun onResult(data: MutableList<A>, previousPageKey: K?, nextPageKey: K?) { 190792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik callback.onResult(convert(data), previousPageKey, nextPageKey) 191792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik } 192792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik }) 193792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik } 194792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik 195792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik override fun loadBefore(params: LoadParams<K>, callback: LoadCallback<K, B>) { 196792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik source.loadBefore(params, object : LoadCallback<K, A>() { 197792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik override fun onResult(data: List<A>, adjacentPageKey: K?) { 198792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik callback.onResult(convert(data), adjacentPageKey) 199792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik } 200792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik }) 201792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik } 202792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik 203792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik override fun loadAfter(params: LoadParams<K>, callback: LoadCallback<K, B>) { 204792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik source.loadAfter(params, object : LoadCallback<K, A>() { 205792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik override fun onResult(data: List<A>, adjacentPageKey: K?) { 206792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik callback.onResult(convert(data), adjacentPageKey) 207792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik } 208792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik }) 209792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik } 210792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik 211792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik protected abstract fun convert(source: List<A>): List<B> 212792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik } 213792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik 214792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik private class StringWrapperDataSource<K, V>(source: PageKeyedDataSource<K, V>) 215792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik : WrapperDataSource<K, V, String>(source) { 216792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik override fun convert(source: List<V>): List<String> { 217792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik return source.map { it.toString() } 218792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik } 219792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik } 220792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik 22149da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik private fun verifyWrappedDataSource(createWrapper: 22249da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik (PageKeyedDataSource<String, Item>) -> PageKeyedDataSource<String, String>) { 223792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik // verify that it's possible to wrap a PageKeyedDataSource, and add info to its data 224792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik val orig = ItemDataSource(data = PAGE_MAP) 22549da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik val wrapper = createWrapper(orig) 226792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik 227792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik // load initial 228792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik @Suppress("UNCHECKED_CAST") 229792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik val loadInitialCallback = mock(PageKeyedDataSource.LoadInitialCallback::class.java) 230792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik as PageKeyedDataSource.LoadInitialCallback<String, String> 231792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik 232792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik wrapper.loadInitial(PageKeyedDataSource.LoadInitialParams<String>(4, true), 233792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik loadInitialCallback) 234792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik val expectedInitial = PAGE_MAP.get(INIT_KEY)!! 235792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik verify(loadInitialCallback).onResult(expectedInitial.data.map { it.toString() }, 236792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik expectedInitial.prev, expectedInitial.next) 237792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik verifyNoMoreInteractions(loadInitialCallback) 238792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik 239792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik @Suppress("UNCHECKED_CAST") 240792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik val loadCallback = mock(PageKeyedDataSource.LoadCallback::class.java) 241792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik as PageKeyedDataSource.LoadCallback<String, String> 24249da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik // load after 243792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik wrapper.loadAfter(PageKeyedDataSource.LoadParams(expectedInitial.next!!, 4), loadCallback) 244792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik val expectedAfter = PAGE_MAP.get(expectedInitial.next)!! 245792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik verify(loadCallback).onResult(expectedAfter.data.map { it.toString() }, 246792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik expectedAfter.next) 247792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik verifyNoMoreInteractions(loadCallback) 248792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik 249792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik // load before 250792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik wrapper.loadBefore(PageKeyedDataSource.LoadParams(expectedAfter.prev!!, 4), loadCallback) 251792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik verify(loadCallback).onResult(expectedInitial.data.map { it.toString() }, 252792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik expectedInitial.prev) 253792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik verifyNoMoreInteractions(loadCallback) 2549bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik 2559bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik // verify invalidation 2569bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik orig.invalidate() 2579bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik assertTrue(wrapper.isInvalid) 258792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik } 259792ed30b1e6c4056e121d6b2b9a8bee4f5529828Chris Craik 26049da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik @Test 26149da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik fun testManualWrappedDataSource() = verifyWrappedDataSource { 26249da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik StringWrapperDataSource(it) 26349da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik } 26449da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik 26549da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik @Test 26649da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik fun testListConverterWrappedDataSource() = verifyWrappedDataSource { 26749da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik it.mapByPage { it.map { it.toString() } } 26849da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik } 2699bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik 27049da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik @Test 27149da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik fun testItemConverterWrappedDataSource() = verifyWrappedDataSource { 27249da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik it.map { it.toString() } 27349da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik } 27449da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik 2759bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik @Test 2769bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik fun testInvalidateToWrapper() { 2779bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik val orig = ItemDataSource() 2789bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik val wrapper = orig.map { it.toString() } 2799bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik 2809bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik orig.invalidate() 2819bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik assertTrue(wrapper.isInvalid) 2829bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik } 2839bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik 2849bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik @Test 2859bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik fun testInvalidateFromWrapper() { 2869bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik val orig = ItemDataSource() 2879bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik val wrapper = orig.map { it.toString() } 2889bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik 2899bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik wrapper.invalidate() 2909bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik assertTrue(orig.isInvalid) 2919bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik } 2929bcee26a52af7d5d890ccb9c8dc1c6f456aa90b6Chris Craik 293b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik companion object { 294b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik // first load is 2nd page to ensure we test prepend as well as append behavior 295b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik private val INIT_KEY: String = "key 2" 296b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik private val PAGE_MAP: Map<String, Page> 297b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik private val ITEM_LIST: List<Item> 298b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 299b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik init { 300b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik val map = HashMap<String, Page>() 301b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik val list = ArrayList<Item>() 302b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik val pageCount = 5 303b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik for (i in 1..pageCount) { 304b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik val data = List(4) { Item("name $i $it") } 305b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik list.addAll(data) 306b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 307b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik val key = "key $i" 308b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik val prev = if (i > 1) ("key " + (i - 1)) else null 309b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik val next = if (i < pageCount) ("key " + (i + 1)) else null 310b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik map.put(key, Page(prev, data, next)) 311b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 312b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik PAGE_MAP = map 313b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik ITEM_LIST = list 314b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 315b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 316b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik 317b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik private fun drain() { 318b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik var executed: Boolean 319b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik do { 320b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik executed = mBackgroundThread.executeAll() 321b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik executed = mMainThread.executeAll() || executed 322b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } while (executed) 323b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik } 324b632de54cccea95ff4d879b512fca7281783b3a4Chris Craik} 325