Add tests for search functionality in SFragment (#617)
* Add tests for search functionality in SFragment * Parameterize url matching tests * Clean up / compartmentalize search tests * Make SFragmentTest filesystem location match package namemain
parent
c85f7a5cd2
commit
be819cc05b
@ -0,0 +1,311 @@ |
||||
/* Copyright 2018 Levi Bard |
||||
* |
||||
* This file is a part of Tusky. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the |
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even |
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
||||
* Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not, |
||||
* see <http://www.gnu.org/licenses>. */ |
||||
|
||||
package com.keylesspalace.tusky.fragment |
||||
|
||||
import android.text.SpannedString |
||||
import com.keylesspalace.tusky.entity.Account |
||||
import com.keylesspalace.tusky.entity.SearchResults |
||||
import com.keylesspalace.tusky.entity.Status |
||||
import com.keylesspalace.tusky.network.MastodonApi |
||||
import com.keylesspalace.tusky.network.TimelineCases |
||||
import okhttp3.Request |
||||
import org.junit.Assert |
||||
import org.junit.Before |
||||
import org.junit.Test |
||||
import org.junit.runner.RunWith |
||||
import org.junit.runners.Parameterized |
||||
import org.mockito.ArgumentMatchers |
||||
import org.mockito.Mockito |
||||
import org.mockito.Mockito.* |
||||
import retrofit2.Call |
||||
import retrofit2.Callback |
||||
import retrofit2.Response |
||||
import java.util.* |
||||
|
||||
class SFragmentTest { |
||||
private lateinit var fragment : FakeSFragment |
||||
private lateinit var apiMock: MastodonApi |
||||
private val accountQuery = "http://mastodon.foo.bar/@User" |
||||
private val statusQuery = "http://mastodon.foo.bar/@User/345678" |
||||
private val nonMastodonQuery = "http://medium.com/@correspondent/345678" |
||||
private val emptyCallback = FakeSearchResults() |
||||
|
||||
private val account = Account ( |
||||
"1", |
||||
"admin", |
||||
"admin", |
||||
"Ad Min", |
||||
SpannedString(""), |
||||
"http://mastodon.foo.bar", |
||||
"", |
||||
"", |
||||
false, |
||||
0, |
||||
0, |
||||
0, |
||||
null |
||||
) |
||||
private val accountCallback = FakeSearchResults(account) |
||||
|
||||
private val status = Status( |
||||
"1", |
||||
statusQuery, |
||||
account, |
||||
null, |
||||
null, |
||||
null, |
||||
SpannedString("omgwat"), |
||||
Date(), |
||||
Collections.emptyList(), |
||||
0, |
||||
0, |
||||
false, |
||||
false, |
||||
false, |
||||
"", |
||||
Status.Visibility.PUBLIC, |
||||
arrayOf(), |
||||
arrayOf(), |
||||
null |
||||
) |
||||
private val statusCallback = FakeSearchResults(status) |
||||
|
||||
@Before |
||||
fun setup() { |
||||
fragment = FakeSFragment() |
||||
|
||||
apiMock = Mockito.mock(MastodonApi::class.java) |
||||
`when`(apiMock.search(eq(accountQuery), ArgumentMatchers.anyBoolean())).thenReturn(accountCallback) |
||||
`when`(apiMock.search(eq(statusQuery), ArgumentMatchers.anyBoolean())).thenReturn(statusCallback) |
||||
`when`(apiMock.search(eq(nonMastodonQuery), ArgumentMatchers.anyBoolean())).thenReturn(emptyCallback) |
||||
fragment.mastodonApi = apiMock |
||||
} |
||||
|
||||
@RunWith(Parameterized::class) |
||||
class UrlMatchingTests(val url: String, val expectedResult: Boolean) { |
||||
companion object { |
||||
@Parameterized.Parameters(name = "match_{0}") |
||||
@JvmStatic |
||||
fun data() : Iterable<Any> { |
||||
return listOf( |
||||
arrayOf("https://mastodon.foo.bar/@User", true), |
||||
arrayOf("http://mastodon.foo.bar/@abc123", true), |
||||
arrayOf("https://mastodon.foo.bar/@user/345667890345678", true), |
||||
arrayOf("https://mastodon.foo.bar/@user/3", true), |
||||
arrayOf("https://pleroma.foo.bar/users/meh3223", true), |
||||
arrayOf("https://pleroma.foo.bar/users/2345", true), |
||||
arrayOf("https://pleroma.foo.bar/notice/9", true), |
||||
arrayOf("https://pleroma.foo.bar/notice/9345678", true), |
||||
arrayOf("https://pleroma.foo.bar/objects/abcdef-123-abcd-9876543", true), |
||||
arrayOf("https://google.com/", false), |
||||
arrayOf("https://mastodon.foo.bar/@User?foo=bar", false), |
||||
arrayOf("https://mastodon.foo.bar/@User#foo", false), |
||||
arrayOf("http://mastodon.foo.bar/@", false), |
||||
arrayOf("http://mastodon.foo.bar/@/345678", false), |
||||
arrayOf("https://mastodon.foo.bar/@user/345667890345678/", false), |
||||
arrayOf("https://mastodon.foo.bar/@user/3abce", false), |
||||
arrayOf("https://pleroma.foo.bar/users/", false), |
||||
arrayOf("https://pleroma.foo.bar/user/2345", false), |
||||
arrayOf("https://pleroma.foo.bar/notice/wat", false), |
||||
arrayOf("https://pleroma.foo.bar/notices/123456", false), |
||||
arrayOf("https://pleroma.foo.bar/object/abcdef-123-abcd-9876543", false), |
||||
arrayOf("https://pleroma.foo.bar/objects/xabcdef-123-abcd-9876543", false), |
||||
arrayOf("https://pleroma.foo.bar/objects/xabcdef-123-abcd-9876543/", false), |
||||
arrayOf("https://pleroma.foo.bar/objects/xabcdef-123-abcd_9876543", false) |
||||
) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun test() { |
||||
Assert.assertEquals(expectedResult, SFragment.looksLikeMastodonUrl(url)) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun beginEndSearch_setIsSearching_isSearchingAfterBegin() { |
||||
fragment.onBeginSearch("https://mastodon.foo.bar/@User") |
||||
Assert.assertTrue(fragment.isSearching) |
||||
} |
||||
|
||||
@Test |
||||
fun beginEndSearch_setIsSearching_isNotSearchingAfterEnd() { |
||||
val validUrl = "https://mastodon.foo.bar/@User" |
||||
fragment.onBeginSearch(validUrl) |
||||
fragment.onEndSearch(validUrl) |
||||
Assert.assertFalse(fragment.isSearching) |
||||
} |
||||
|
||||
@Test |
||||
fun beginEndSearch_setIsSearching_doesNotCancelSearchWhenResponseFromPreviousSearchIsReceived() { |
||||
val validUrl = "https://mastodon.foo.bar/@User" |
||||
val invalidUrl = "" |
||||
|
||||
fragment.onBeginSearch(validUrl) |
||||
fragment.onEndSearch(invalidUrl) |
||||
Assert.assertTrue(fragment.isSearching) |
||||
} |
||||
|
||||
@Test |
||||
fun cancelActiveSearch() { |
||||
val url = "https://mastodon.foo.bar/@User" |
||||
|
||||
fragment.onBeginSearch(url) |
||||
fragment.cancelActiveSearch() |
||||
Assert.assertFalse(fragment.isSearching) |
||||
} |
||||
|
||||
@Test |
||||
fun getCancelSearchRequested_detectsURL() { |
||||
val firstUrl = "https://mastodon.foo.bar/@User" |
||||
val secondUrl = "https://mastodon.foo.bar/@meh" |
||||
|
||||
fragment.onBeginSearch(firstUrl) |
||||
fragment.cancelActiveSearch() |
||||
|
||||
fragment.onBeginSearch(secondUrl) |
||||
Assert.assertTrue(fragment.getCancelSearchRequested(firstUrl)) |
||||
Assert.assertFalse(fragment.getCancelSearchRequested(secondUrl)) |
||||
} |
||||
|
||||
@Test |
||||
fun search_inIdealConditions_returnsRequestedResults_forAccount() { |
||||
fragment.onViewURL(accountQuery) |
||||
accountCallback.invokeCallback() |
||||
Assert.assertEquals(account.id, fragment.accountId) |
||||
} |
||||
|
||||
@Test |
||||
fun search_inIdealConditions_returnsRequestedResults_forStatus() { |
||||
fragment.onViewURL(statusQuery) |
||||
statusCallback.invokeCallback() |
||||
Assert.assertEquals(status, fragment.status) |
||||
} |
||||
|
||||
@Test |
||||
fun search_inIdealConditions_returnsRequestedResults_forNonMastodonURL() { |
||||
fragment.onViewURL(nonMastodonQuery) |
||||
emptyCallback.invokeCallback() |
||||
Assert.assertEquals(nonMastodonQuery, fragment.url) |
||||
} |
||||
|
||||
@Test |
||||
fun search_withCancellation_doesNotLoadUrl_forAccount() { |
||||
fragment.onViewURL(accountQuery) |
||||
Assert.assertTrue(fragment.isSearching) |
||||
fragment.cancelActiveSearch() |
||||
Assert.assertFalse(fragment.isSearching) |
||||
accountCallback.invokeCallback() |
||||
Assert.assertEquals(null, fragment.accountId) |
||||
} |
||||
|
||||
@Test |
||||
fun search_withCancellation_doesNotLoadUrl_forStatus() { |
||||
fragment.onViewURL(accountQuery) |
||||
fragment.cancelActiveSearch() |
||||
accountCallback.invokeCallback() |
||||
Assert.assertEquals(null, fragment.accountId) |
||||
} |
||||
|
||||
@Test |
||||
fun search_withCancellation_doesNotLoadUrl_forNonMastodonURL() { |
||||
fragment.onViewURL(nonMastodonQuery) |
||||
fragment.cancelActiveSearch() |
||||
emptyCallback.invokeCallback() |
||||
Assert.assertEquals(null, fragment.url) |
||||
} |
||||
|
||||
@Test |
||||
fun search_withPreviousCancellation_completes() { |
||||
// begin/cancel account search |
||||
fragment.onViewURL(accountQuery) |
||||
fragment.cancelActiveSearch() |
||||
|
||||
// begin status search |
||||
fragment.onViewURL(statusQuery) |
||||
|
||||
// return response from account search |
||||
accountCallback.invokeCallback() |
||||
|
||||
// ensure that status search is still ongoing |
||||
Assert.assertTrue(fragment.isSearching) |
||||
statusCallback.invokeCallback() |
||||
|
||||
// ensure that the result of the status search was recorded |
||||
// and the account search wasn't |
||||
Assert.assertEquals(status, fragment.status) |
||||
Assert.assertEquals(null, fragment.accountId) |
||||
} |
||||
|
||||
class FakeSearchResults : Call<SearchResults> |
||||
{ |
||||
private var searchResults: SearchResults |
||||
private var callback: Callback<SearchResults>? = null |
||||
|
||||
constructor() { |
||||
searchResults = SearchResults(Collections.emptyList(), Collections.emptyList(), Collections.emptyList()) |
||||
} |
||||
|
||||
constructor(status: Status) { |
||||
searchResults = SearchResults(Collections.emptyList(), listOf(status), Collections.emptyList()) |
||||
} |
||||
|
||||
constructor(account: Account) { |
||||
searchResults = SearchResults(listOf(account), Collections.emptyList(), Collections.emptyList()) |
||||
} |
||||
|
||||
fun invokeCallback() { |
||||
callback?.onResponse(this, Response.success(searchResults)) |
||||
} |
||||
|
||||
override fun enqueue(callback: Callback<SearchResults>?) { |
||||
this.callback = callback |
||||
} |
||||
|
||||
override fun isExecuted(): Boolean { throw NotImplementedError() } |
||||
override fun clone(): Call<SearchResults> { throw NotImplementedError() } |
||||
override fun isCanceled(): Boolean { throw NotImplementedError() } |
||||
override fun cancel() { throw NotImplementedError() } |
||||
override fun execute(): Response<SearchResults> { throw NotImplementedError() } |
||||
override fun request(): Request { throw NotImplementedError() } |
||||
} |
||||
|
||||
class FakeSFragment : SFragment() { |
||||
var status: Status? = null |
||||
var accountId: String? = null |
||||
var url: String? = null |
||||
|
||||
init { |
||||
callList = mutableListOf() |
||||
} |
||||
|
||||
override fun openLink(url: String) { |
||||
this.url = url |
||||
} |
||||
|
||||
override fun viewAccount(id: String?) { |
||||
accountId = id |
||||
} |
||||
|
||||
override fun viewThread(status: Status?) { |
||||
this.status = status |
||||
} |
||||
|
||||
override fun removeItem(position: Int) { throw NotImplementedError() } |
||||
override fun removeAllByAccountId(accountId: String?) { throw NotImplementedError() } |
||||
override fun timelineCases(): TimelineCases { throw NotImplementedError() } |
||||
} |
||||
} |
Loading…
Reference in new issue