filter polls (#1496)

This commit is contained in:
Konrad Pozniak 2019-09-24 20:33:29 +02:00 committed by GitHub
parent ca3ce63c80
commit a9440ea2ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 256 additions and 5 deletions

View File

@ -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())));
} }

View File

@ -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());
} }

View File

@ -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);
} }

View File

@ -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)
}
}