filter polls (#1496)
This commit is contained in:
parent
ca3ce63c80
commit
a9440ea2ab
|
@ -33,6 +33,7 @@ import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.PopupMenu;
|
import androidx.appcompat.widget.PopupMenu;
|
||||||
import androidx.core.app.ActivityOptionsCompat;
|
import androidx.core.app.ActivityOptionsCompat;
|
||||||
|
@ -52,6 +53,7 @@ import com.keylesspalace.tusky.db.AccountManager;
|
||||||
import com.keylesspalace.tusky.di.Injectable;
|
import com.keylesspalace.tusky.di.Injectable;
|
||||||
import com.keylesspalace.tusky.entity.Attachment;
|
import com.keylesspalace.tusky.entity.Attachment;
|
||||||
import com.keylesspalace.tusky.entity.Filter;
|
import com.keylesspalace.tusky.entity.Filter;
|
||||||
|
import com.keylesspalace.tusky.entity.PollOption;
|
||||||
import com.keylesspalace.tusky.entity.Status;
|
import com.keylesspalace.tusky.entity.Status;
|
||||||
import com.keylesspalace.tusky.network.MastodonApi;
|
import com.keylesspalace.tusky.network.MastodonApi;
|
||||||
import com.keylesspalace.tusky.network.TimelineCases;
|
import com.keylesspalace.tusky.network.TimelineCases;
|
||||||
|
@ -445,7 +447,8 @@ public abstract class SFragment extends BaseFragment implements Injectable {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void reloadFilters(boolean forceRefresh) {
|
@VisibleForTesting
|
||||||
|
public void reloadFilters(boolean forceRefresh) {
|
||||||
if (filters != null && !forceRefresh) {
|
if (filters != null && !forceRefresh) {
|
||||||
applyFilters(forceRefresh);
|
applyFilters(forceRefresh);
|
||||||
return;
|
return;
|
||||||
|
@ -469,7 +472,7 @@ public abstract class SFragment extends BaseFragment implements Injectable {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean filterIsRelevant(Filter filter) {
|
protected boolean filterIsRelevant(@NonNull Filter filter) {
|
||||||
// Called when building local filter expression
|
// Called when building local filter expression
|
||||||
// Override to select relevant filters for your fragment
|
// Override to select relevant filters for your fragment
|
||||||
return false;
|
return false;
|
||||||
|
@ -480,7 +483,17 @@ public abstract class SFragment extends BaseFragment implements Injectable {
|
||||||
// Override to refresh your fragment
|
// Override to refresh your fragment
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean shouldFilterStatus(Status status) {
|
@VisibleForTesting
|
||||||
|
public boolean shouldFilterStatus(Status status) {
|
||||||
|
|
||||||
|
if(filterRemoveRegex && status.getPoll() != null) {
|
||||||
|
for(PollOption option: status.getPoll().getOptions()) {
|
||||||
|
if(filterRemoveRegexMatcher.reset(option.getTitle()).find()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (filterRemoveRegex && (filterRemoveRegexMatcher.reset(status.getActionableStatus().getContent()).find()
|
return (filterRemoveRegex && (filterRemoveRegexMatcher.reset(status.getActionableStatus().getContent()).find()
|
||||||
|| (!status.getSpoilerText().isEmpty() && filterRemoveRegexMatcher.reset(status.getActionableStatus().getSpoilerText()).find())));
|
|| (!status.getSpoilerText().isEmpty() && filterRemoveRegexMatcher.reset(status.getActionableStatus().getSpoilerText()).find())));
|
||||||
}
|
}
|
||||||
|
|
|
@ -376,7 +376,7 @@ public class TimelineFragment extends SFragment implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean filterIsRelevant(Filter filter) {
|
protected boolean filterIsRelevant(@NonNull Filter filter) {
|
||||||
return filterContextMatchesKind(kind, filter.getContext());
|
return filterContextMatchesKind(kind, filter.getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -686,7 +686,7 @@ public final class ViewThreadFragment extends SFragment implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean filterIsRelevant(Filter filter) {
|
protected boolean filterIsRelevant(@NonNull Filter filter) {
|
||||||
return filter.getContext().contains(Filter.THREAD);
|
return filter.getContext().contains(Filter.THREAD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,238 @@
|
||||||
|
package com.keylesspalace.tusky
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.SpannedString
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.keylesspalace.tusky.entity.Filter
|
||||||
|
import com.keylesspalace.tusky.entity.Poll
|
||||||
|
import com.keylesspalace.tusky.entity.PollOption
|
||||||
|
import com.keylesspalace.tusky.entity.Status
|
||||||
|
import com.keylesspalace.tusky.fragment.SFragment
|
||||||
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
|
import com.nhaarman.mockitokotlin2.mock
|
||||||
|
import okhttp3.Request
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.robolectric.Robolectric
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Callback
|
||||||
|
import retrofit2.Response
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Config(application = FakeTuskyApplication::class)
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class FilterTest {
|
||||||
|
|
||||||
|
private val fragment = FakeFragment()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
|
||||||
|
val controller = Robolectric.buildActivity(FakeActivity::class.java)
|
||||||
|
val activity = controller.get()
|
||||||
|
|
||||||
|
activity.accountManager = mock()
|
||||||
|
activity.themeUtils = mock()
|
||||||
|
val apiMock = Mockito.mock(MastodonApi::class.java)
|
||||||
|
Mockito.`when`(apiMock.getFilters()).thenReturn(object: Call<List<Filter>> {
|
||||||
|
override fun isExecuted(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
override fun clone(): Call<List<Filter>> {
|
||||||
|
throw Error("not implemented")
|
||||||
|
}
|
||||||
|
override fun isCanceled(): Boolean {
|
||||||
|
throw Error("not implemented")
|
||||||
|
}
|
||||||
|
override fun cancel() {
|
||||||
|
throw Error("not implemented")
|
||||||
|
}
|
||||||
|
override fun execute(): Response<List<Filter>> {
|
||||||
|
throw Error("not implemented")
|
||||||
|
}
|
||||||
|
override fun request(): Request {
|
||||||
|
throw Error("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enqueue(callback: Callback<List<Filter>>) {
|
||||||
|
callback.onResponse(
|
||||||
|
this,
|
||||||
|
Response.success(
|
||||||
|
listOf(
|
||||||
|
Filter(
|
||||||
|
id = "123",
|
||||||
|
phrase = "badWord",
|
||||||
|
context = listOf(Filter.HOME),
|
||||||
|
expiresAt = null,
|
||||||
|
irreversible = false,
|
||||||
|
wholeWord = false
|
||||||
|
),
|
||||||
|
Filter(
|
||||||
|
id = "123",
|
||||||
|
phrase = "badWholeWord",
|
||||||
|
context = listOf(Filter.HOME, Filter.PUBLIC),
|
||||||
|
expiresAt = null,
|
||||||
|
irreversible = false,
|
||||||
|
wholeWord = true
|
||||||
|
),
|
||||||
|
Filter(
|
||||||
|
id = "123",
|
||||||
|
phrase = "wrongContext",
|
||||||
|
context = listOf(Filter.PUBLIC),
|
||||||
|
expiresAt = null,
|
||||||
|
irreversible = false,
|
||||||
|
wholeWord = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
activity.mastodonApi = apiMock
|
||||||
|
|
||||||
|
|
||||||
|
controller.create().start()
|
||||||
|
|
||||||
|
fragment.mastodonApi = apiMock
|
||||||
|
|
||||||
|
|
||||||
|
activity.supportFragmentManager.beginTransaction()
|
||||||
|
.replace(R.id.activity_main, fragment, "fragment")
|
||||||
|
.commit()
|
||||||
|
|
||||||
|
fragment.reloadFilters(false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldNotFilter() {
|
||||||
|
assertFalse(fragment.shouldFilterStatus(
|
||||||
|
mockStatus(content = "should not be filtered")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldNotFilter_whenContextDoesNotMatch() {
|
||||||
|
assertFalse(fragment.shouldFilterStatus(
|
||||||
|
mockStatus(content = "one two wrongContext three")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldFilter_whenContentMatchesBadWord() {
|
||||||
|
assertTrue(fragment.shouldFilterStatus(
|
||||||
|
mockStatus(content = "one two badWord three")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldFilter_whenContentMatchesBadWordPart() {
|
||||||
|
assertTrue(fragment.shouldFilterStatus(
|
||||||
|
mockStatus(content = "one two badWordPart three")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldFilter_whenContentMatchesBadWholeWord() {
|
||||||
|
assertTrue(fragment.shouldFilterStatus(
|
||||||
|
mockStatus(content = "one two badWholeWord three")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldNotFilter_whenContentDoesNotMAtchWholeWord() {
|
||||||
|
assertFalse(fragment.shouldFilterStatus(
|
||||||
|
mockStatus(content = "one two badWholeWordTest three")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldFilter_whenSpoilerTextDoesMatch() {
|
||||||
|
assertTrue(fragment.shouldFilterStatus(
|
||||||
|
mockStatus(
|
||||||
|
content = "should not be filtered",
|
||||||
|
spoilerText = "badWord should be filtered"
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldFilter_whenPollTextDoesMatch() {
|
||||||
|
assertTrue(fragment.shouldFilterStatus(
|
||||||
|
mockStatus(
|
||||||
|
content = "should not be filtered",
|
||||||
|
spoilerText = "should not be filtered",
|
||||||
|
pollOptions = listOf("should not be filtered", "badWord")
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mockStatus(
|
||||||
|
content: String = "",
|
||||||
|
spoilerText: String = "",
|
||||||
|
pollOptions: List<String>? = null
|
||||||
|
): Status {
|
||||||
|
return Status(
|
||||||
|
id = "123",
|
||||||
|
url = "https://mastodon.social/@Tusky/100571663297225812",
|
||||||
|
account = mock(),
|
||||||
|
inReplyToId = null,
|
||||||
|
inReplyToAccountId = null,
|
||||||
|
reblog = null,
|
||||||
|
content = SpannedString(content),
|
||||||
|
createdAt = Date(),
|
||||||
|
emojis = emptyList(),
|
||||||
|
reblogsCount = 0,
|
||||||
|
favouritesCount = 0,
|
||||||
|
reblogged = false,
|
||||||
|
favourited = false,
|
||||||
|
sensitive = false,
|
||||||
|
spoilerText = spoilerText,
|
||||||
|
visibility = Status.Visibility.PUBLIC,
|
||||||
|
attachments = arrayListOf(),
|
||||||
|
mentions = emptyArray(),
|
||||||
|
application = null,
|
||||||
|
pinned = false,
|
||||||
|
poll = if (pollOptions != null) {
|
||||||
|
Poll(
|
||||||
|
id = "1234",
|
||||||
|
expiresAt = null,
|
||||||
|
expired = false,
|
||||||
|
multiple = false,
|
||||||
|
votesCount = 0,
|
||||||
|
options = pollOptions.map {
|
||||||
|
PollOption(it, 0)
|
||||||
|
},
|
||||||
|
voted = false
|
||||||
|
)
|
||||||
|
} else null,
|
||||||
|
card = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeActivity: BottomSheetActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_main)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeFragment: SFragment() {
|
||||||
|
override fun removeItem(position: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReblog(reblog: Boolean, position: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun filterIsRelevant(filter: Filter): Boolean {
|
||||||
|
return filter.context.contains(Filter.HOME)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue