refactor: Use view binding to replace Kotlin synthetics (#824)

* refactor: Migrate home screen to data binding

* Add view binding

* Migrate ConversationView to view binding

* Migrate ConversationActivityV2 to view binding

* View model refactor

* Move more functionality to the view model

* Add ui state events flow

* Update conversation item bindings

* Update profile picture view bindings

* Replace Kotlin synthetics with view bindings

* Fix qr code fragment binding and optimize imports

* View binding refactors

* Make TextSecurePreferences an interface and add an implementation to improve testability

* Add conversation repository

* Migrate remaining TextSecurePreferences functions into the interface

* Add unit conversation unit tests

* Add unit test coverage for remaining view model functions
This commit is contained in:
ceokot
2022-01-14 07:56:15 +02:00
committed by GitHub
parent 366b5abdc8
commit c113a447cf
98 changed files with 3579 additions and 2365 deletions

View File

@@ -0,0 +1,11 @@
package org.thoughtcrime.securesms
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import org.junit.Rule
open class BaseViewModelTest: BaseCoroutineTest() {
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
}

View File

@@ -0,0 +1,173 @@
package org.thoughtcrime.securesms.conversation.v2
import kotlinx.coroutines.flow.first
import org.hamcrest.CoreMatchers.endsWith
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.anySet
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.BaseViewModelTest
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.repository.ConversationRepository
import org.thoughtcrime.securesms.repository.ResultOf
import org.mockito.Mockito.`when` as whenever
class ConversationViewModelTest: BaseViewModelTest() {
private val repository = mock(ConversationRepository::class.java)
private val threadId = 123L
private lateinit var recipient: Recipient
private val viewModel: ConversationViewModel by lazy {
ConversationViewModel(threadId, repository)
}
@Before
fun setUp() {
recipient = mock(Recipient::class.java)
whenever(repository.isOxenHostedOpenGroup(anyLong())).thenReturn(true)
whenever(repository.getRecipientForThreadId(anyLong())).thenReturn(recipient)
}
@Test
fun `should emit group type on init`() = runBlockingTest {
assertTrue(viewModel.uiState.first().isOxenHostedOpenGroup)
}
@Test
fun `should save draft message`() {
val draft = "Hi there"
viewModel.saveDraft(draft)
verify(repository).saveDraft(threadId, draft)
}
@Test
fun `should retrieve draft message`() {
val draft = "Hi there"
whenever(repository.getDraft(anyLong())).thenReturn(draft)
val result = viewModel.getDraft()
verify(repository).getDraft(threadId)
assertThat(result, equalTo(draft))
}
@Test
fun `should invite contacts`() {
val contacts = listOf<Recipient>()
viewModel.inviteContacts(contacts)
verify(repository).inviteContacts(threadId, contacts)
}
@Test
fun `should unblock contact recipient`() {
whenever(recipient.isContactRecipient).thenReturn(true)
viewModel.unblock()
verify(repository).unblock(recipient)
}
@Test
fun `should delete locally`() {
val message = mock(MessageRecord::class.java)
viewModel.deleteLocally(message)
verify(repository).deleteLocally(recipient, message)
}
@Test
fun `should emit error message on failure to delete a message for everyone`() = runBlockingTest {
val message = mock(MessageRecord::class.java)
val error = Throwable()
whenever(repository.deleteForEveryone(anyLong(), any(), any()))
.thenReturn(ResultOf.Failure(error))
viewModel.deleteForEveryone(message)
assertThat(viewModel.uiState.first().uiMessages.first().message, endsWith("$error"))
}
@Test
fun `should emit error message on failure to delete messages without unsend request`() =
runBlockingTest {
val message = mock(MessageRecord::class.java)
val error = Throwable()
whenever(repository.deleteMessageWithoutUnsendRequest(anyLong(), anySet()))
.thenReturn(ResultOf.Failure(error))
viewModel.deleteMessagesWithoutUnsendRequest(setOf(message))
assertThat(viewModel.uiState.first().uiMessages.first().message, endsWith("$error"))
}
@Test
fun `should emit error message on ban user failure`() = runBlockingTest {
val error = Throwable()
whenever(repository.banUser(anyLong(), any())).thenReturn(ResultOf.Failure(error))
viewModel.banUser(recipient)
assertThat(viewModel.uiState.first().uiMessages.first().message, endsWith("$error"))
}
@Test
fun `should emit a message on ban user success`() = runBlockingTest {
whenever(repository.banUser(anyLong(), any())).thenReturn(ResultOf.Success(Unit))
viewModel.banUser(recipient)
assertThat(
viewModel.uiState.first().uiMessages.first().message,
equalTo("Successfully banned user")
)
}
@Test
fun `should emit error message on ban user and delete all failure`() = runBlockingTest {
val error = Throwable()
whenever(repository.banAndDeleteAll(anyLong(), any())).thenReturn(ResultOf.Failure(error))
viewModel.banAndDeleteAll(recipient)
assertThat(viewModel.uiState.first().uiMessages.first().message, endsWith("$error"))
}
@Test
fun `should emit a message on ban user and delete all success`() = runBlockingTest {
whenever(repository.banAndDeleteAll(anyLong(), any())).thenReturn(ResultOf.Success(Unit))
viewModel.banAndDeleteAll(recipient)
assertThat(
viewModel.uiState.first().uiMessages.first().message,
equalTo("Successfully banned user and deleted all their messages")
)
}
@Test
fun `should remove shown message`() = runBlockingTest {
// Given that a message is generated
whenever(repository.banUser(anyLong(), any())).thenReturn(ResultOf.Success(Unit))
viewModel.banUser(recipient)
assertThat(viewModel.uiState.value.uiMessages.size, equalTo(1))
// When the message is shown
viewModel.messageShown(viewModel.uiState.first().uiMessages.first().id)
// Then it should be removed
assertThat(viewModel.uiState.value.uiMessages.size, equalTo(0))
}
}

View File

@@ -1,12 +1,20 @@
package org.thoughtcrime.securesms.jobs;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import org.junit.Test;
import org.session.libsession.messaging.utilities.Data;
import org.thoughtcrime.securesms.database.JobDatabase;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
import org.thoughtcrime.securesms.jobmanager.persistence.ConstraintSpec;
import org.thoughtcrime.securesms.jobmanager.persistence.DependencySpec;
@@ -17,14 +25,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class FastJobStorageTest {
private static final JsonDataSerializer serializer = new JsonDataSerializer();

View File

@@ -1,7 +1,8 @@
package org.thoughtcrime.securesms.recipients;
import android.content.Intent;
import android.provider.ContactsContract;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import junit.framework.TestCase;
@@ -10,45 +11,14 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import org.session.libsession.utilities.Address;
import static android.provider.ContactsContract.Intents.Insert.EMAIL;
import static android.provider.ContactsContract.Intents.Insert.NAME;
import static android.provider.ContactsContract.Intents.Insert.PHONE;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.session.libsession.utilities.recipients.Recipient;
import org.session.libsession.utilities.recipients.RecipientExporter;
//FIXME AC: This test group is outdated.
@Ignore("This test group uses outdated instrumentation and needs a migration to modern tools.")
@RunWith(MockitoJUnitRunner.class)
public final class RecipientExporterTest extends TestCase {
@Test
public void asAddContactIntent_with_phone_number() {
Recipient recipient = givenRecipient("Alice", givenPhoneNumber("+1555123456"));
Intent intent = RecipientExporter.export(recipient).asAddContactIntent();
assertEquals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction());
assertEquals(ContactsContract.Contacts.CONTENT_ITEM_TYPE, intent.getType());
assertEquals("Alice", intent.getStringExtra(NAME));
assertEquals("+1555123456", intent.getStringExtra(PHONE));
assertNull(intent.getStringExtra(EMAIL));
}
@Test
public void asAddContactIntent_with_email() {
Recipient recipient = givenRecipient("Bob", givenEmail("bob@signal.org"));
Intent intent = RecipientExporter.export(recipient).asAddContactIntent();
assertEquals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction());
assertEquals(ContactsContract.Contacts.CONTENT_ITEM_TYPE, intent.getType());
assertEquals("Bob", intent.getStringExtra(NAME));
assertEquals("bob@signal.org", intent.getStringExtra(EMAIL));
assertNull(intent.getStringExtra(PHONE));
}
@Test
public void asAddContactIntent_with_neither_email_nor_phone() {
RecipientExporter exporter = RecipientExporter.export(givenRecipient("Bob", mock(Address.class)));
@@ -64,19 +34,4 @@ public final class RecipientExporterTest extends TestCase {
return recipient;
}
private Address givenPhoneNumber(String phoneNumber) {
Address address = mock(Address.class);
when(address.isPhone()).thenReturn(true);
when(address.toPhoneString()).thenReturn(phoneNumber);
when(address.toEmailString()).thenThrow(new RuntimeException());
return address;
}
private Address givenEmail(String email) {
Address address = mock(Address.class);
when(address.isEmail()).thenReturn(true);
when(address.toEmailString()).thenReturn(email);
when(address.toPhoneString()).thenThrow(new RuntimeException());
return address;
}
}

View File

@@ -1,61 +0,0 @@
package org.thoughtcrime.securesms.service;
import org.junit.Before;
import org.junit.Test;
import org.thoughtcrime.securesms.BaseUnitTest;
import org.session.libsignal.utilities.guava.Optional;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.contains;
import static org.mockito.Mockito.when;
public class VerificationCodeParserTest extends BaseUnitTest {
private static Map<String, String> CHALLENGES = new HashMap<String,String>() {{
put("Your TextSecure verification code: 337-337", "337337");
put("XXX\nYour TextSecure verification code: 1337-1337", "13371337");
put("Your TextSecure verification code: 337-1337", "3371337");
put("Your TextSecure verification code: 1337-337", "1337337");
put("Your TextSecure verification code: 1337-1337", "13371337");
put("XXXYour TextSecure verification code: 1337-1337", "13371337");
put("Your TextSecure verification code: 1337-1337XXX", "13371337");
put("Your TextSecure verification code 1337-1337", "13371337");
put("Your Signal verification code: 337-337", "337337");
put("XXX\nYour Signal verification code: 1337-1337", "13371337");
put("Your Signal verification code: 337-1337", "3371337");
put("Your Signal verification code: 1337-337", "1337337");
put("Your Signal verification code: 1337-1337", "13371337");
put("XXXYour Signal verification code: 1337-1337", "13371337");
put("Your Signal verification code: 1337-1337XXX", "13371337");
put("Your Signal verification code 1337-1337", "13371337");
put("<#>Your Signal verification code: 1337-1337 aAbBcCdDeEf", "13371337");
put("<#> Your Signal verification code: 1337-1337 aAbBcCdDeEf", "13371337");
put("<#>Your Signal verification code: 1337-1337\naAbBcCdDeEf", "13371337");
put("<#> Your Signal verification code: 1337-1337\naAbBcCdDeEf", "13371337");
put("<#> Your Signal verification code: 1337-1337\n\naAbBcCdDeEf", "13371337");
}};
@Before
@Override
public void setUp() throws Exception {
super.setUp();
when(sharedPreferences.getBoolean(contains("pref_verifying"), anyBoolean())).thenReturn(true);
}
@Test
public void testChallenges() {
for (Entry<String,String> challenge : CHALLENGES.entrySet()) {
Optional<String> result = VerificationCodeParser.parse(context, challenge.getKey());
assertTrue(result.isPresent());
assertEquals(result.get(), challenge.getValue());
}
}
}

View File

@@ -1,56 +0,0 @@
package org.thoughtcrime.securesms.util;
import org.junit.Before;
import org.junit.Test;
import static junit.framework.Assert.assertEquals;
public class DelimiterUtilTest {
@Before
public void setup() {}
@Test
public void testEscape() {
assertEquals(DelimiterUtil.escape("MTV Music", ' '), "MTV\\ Music");
assertEquals(DelimiterUtil.escape("MTV Music", ' '), "MTV\\ \\ Music");
assertEquals(DelimiterUtil.escape("MTV,Music", ','), "MTV\\,Music");
assertEquals(DelimiterUtil.escape("MTV,,Music", ','), "MTV\\,\\,Music");
assertEquals(DelimiterUtil.escape("MTV Music", '+'), "MTV Music");
}
@Test
public void testSplit() {
String[] parts = DelimiterUtil.split("MTV\\ Music", ' ');
assertEquals(parts.length, 1);
assertEquals(parts[0], "MTV\\ Music");
parts = DelimiterUtil.split("MTV Music", ' ');
assertEquals(parts.length, 2);
assertEquals(parts[0], "MTV");
assertEquals(parts[1], "Music");
}
@Test
public void testEscapeSplit() {
String input = "MTV Music";
String intermediate = DelimiterUtil.escape(input, ' ');
String[] parts = DelimiterUtil.split(intermediate, ' ');
assertEquals(parts.length, 1);
assertEquals(parts[0], "MTV\\ Music");
assertEquals(DelimiterUtil.unescape(parts[0], ' '), "MTV Music");
input = "MTV\\ Music";
intermediate = DelimiterUtil.escape(input, ' ');
parts = DelimiterUtil.split(intermediate, ' ');
assertEquals(parts.length, 1);
assertEquals(parts[0], "MTV\\\\ Music");
assertEquals(DelimiterUtil.unescape(parts[0], ' '), "MTV\\ Music");
}
}

View File

@@ -0,0 +1,50 @@
package org.thoughtcrime.securesms.util
import org.junit.Assert.assertEquals
import org.junit.Test
import org.session.libsession.utilities.DelimiterUtil
class DelimiterUtilTest {
@Test
fun testEscape() {
assertEquals(DelimiterUtil.escape("MTV Music", ' '), "MTV\\ Music")
assertEquals(DelimiterUtil.escape("MTV Music", ' '), "MTV\\ \\ Music")
assertEquals(DelimiterUtil.escape("MTV,Music", ','), "MTV\\,Music")
assertEquals(DelimiterUtil.escape("MTV,,Music", ','), "MTV\\,\\,Music")
assertEquals(DelimiterUtil.escape("MTV Music", '+'), "MTV Music")
}
@Test
fun testSplit() {
var parts = DelimiterUtil.split("MTV\\ Music", ' ')
assertEquals(parts.size, 1)
assertEquals(parts[0], "MTV\\ Music")
parts = DelimiterUtil.split("MTV Music", ' ')
assertEquals(parts.size, 2)
assertEquals(parts[0], "MTV")
assertEquals(parts[1], "Music")
}
@Test
fun testEscapeSplit() {
var input = "MTV Music"
var intermediate = DelimiterUtil.escape(input, ' ')
var parts = DelimiterUtil.split(intermediate, ' ')
assertEquals(parts.size, 1)
assertEquals(parts[0], "MTV\\ Music")
assertEquals(DelimiterUtil.unescape(parts[0], ' '), "MTV Music")
input = "MTV\\ Music"
intermediate = DelimiterUtil.escape(input, ' ')
parts = DelimiterUtil.split(intermediate, ' ')
assertEquals(parts.size, 1)
assertEquals(parts[0], "MTV\\\\ Music")
assertEquals(DelimiterUtil.unescape(parts[0], ' '), "MTV\\ Music")
}
}

View File

@@ -1,67 +0,0 @@
package org.thoughtcrime.securesms.util;
import junit.framework.AssertionFailedError;
import org.junit.Test;
import org.thoughtcrime.securesms.BaseUnitTest;
import org.session.libsignal.service.api.util.InvalidNumberException;
import org.session.libsignal.service.api.util.PhoneNumberFormatter;
import static org.assertj.core.api.Assertions.assertThat;
public class PhoneNumberFormatterTest extends BaseUnitTest {
private static final String LOCAL_NUMBER_US = "+15555555555";
private static final String NUMBER_CH = "+41446681800";
private static final String NUMBER_UK = "+442079460018";
private static final String NUMBER_DE = "+4930123456";
private static final String NUMBER_MOBILE_DE = "+49171123456";
private static final String COUNTRY_CODE_CH = "41";
private static final String COUNTRY_CODE_UK = "44";
private static final String COUNTRY_CODE_DE = "49";
@Test
public void testFormatNumber() throws Exception, InvalidNumberException {
assertThat(PhoneNumberFormatter.formatNumber("(555) 555-5555", LOCAL_NUMBER_US)).isEqualTo(LOCAL_NUMBER_US);
assertThat(PhoneNumberFormatter.formatNumber("555-5555", LOCAL_NUMBER_US)).isEqualTo(LOCAL_NUMBER_US);
assertThat(PhoneNumberFormatter.formatNumber("(123) 555-5555", LOCAL_NUMBER_US)).isNotEqualTo(LOCAL_NUMBER_US);
}
@Test
public void testFormatNumberEmail() throws Exception {
try {
PhoneNumberFormatter.formatNumber("person@domain.com", LOCAL_NUMBER_US);
throw new AssertionFailedError("should have thrown on email");
} catch (InvalidNumberException ine) {
// success
}
}
@Test
public void testFormatNumberE164() throws Exception, InvalidNumberException {
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_UK, "(020) 7946 0018")).isEqualTo(NUMBER_UK);
// assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_UK, "044 20 7946 0018")).isEqualTo(NUMBER_UK);
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_UK, "+442079460018")).isEqualTo(NUMBER_UK);
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_CH, "+41 44 668 18 00")).isEqualTo(NUMBER_CH);
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_CH, "+41 (044) 6681800")).isEqualTo(NUMBER_CH);
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "0049 030 123456")).isEqualTo(NUMBER_DE);
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "0049 (0)30123456")).isEqualTo(NUMBER_DE);
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "0049((0)30)123456")).isEqualTo(NUMBER_DE);
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "+49 (0) 30 1 2 3 45 6 ")).isEqualTo(NUMBER_DE);
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "030 123456")).isEqualTo(NUMBER_DE);
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "0171123456")).isEqualTo(NUMBER_MOBILE_DE);
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "0171/123456")).isEqualTo(NUMBER_MOBILE_DE);
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "+490171/123456")).isEqualTo(NUMBER_MOBILE_DE);
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "00490171/123456")).isEqualTo(NUMBER_MOBILE_DE);
assertThat(PhoneNumberFormatter.formatE164(COUNTRY_CODE_DE, "0049171/123456")).isEqualTo(NUMBER_MOBILE_DE);
}
@Test
public void testFormatRemoteNumberE164() throws Exception, InvalidNumberException {
assertThat(PhoneNumberFormatter.formatNumber("+4402079460018", LOCAL_NUMBER_US)).isEqualTo(NUMBER_UK);
}
}

View File

@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.util.dynamiclanguage;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.session.libsession.utilities.dynamiclanguage.LanguageString;
import java.util.Arrays;
import java.util.Collection;

View File

@@ -7,6 +7,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.session.libsession.utilities.dynamiclanguage.LocaleParser;
import network.loki.messenger.BuildConfig;
import java.util.Arrays;