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