Make names in group update descriptions tappable.

This commit is contained in:
Greyson Parrelli
2022-04-19 09:32:48 -04:00
committed by Alex Hart
parent 3b17a41415
commit e2cb535f3f
17 changed files with 378 additions and 215 deletions

View File

@@ -1,13 +1,13 @@
package org.thoughtcrime.securesms.database.model;
import android.app.Application;
import android.text.Spannable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import com.annimon.stream.Stream;
import com.google.common.collect.ImmutableMap;
import org.junit.Before;
import org.junit.Rule;
@@ -19,18 +19,18 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.signal.core.util.ThreadUtil;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -38,8 +38,11 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
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.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.thoughtcrime.securesms.groups.v2.ChangeBuilder.changeBy;
import static org.thoughtcrime.securesms.groups.v2.ChangeBuilder.changeByUnknown;
import static org.signal.core.util.StringUtil.isolateBidi;
@@ -60,18 +63,38 @@ public final class GroupsV2UpdateMessageProducerTest {
public MockitoRule rule = MockitoJUnit.rule();
@Mock
public MockedStatic<ThreadUtil> threadUtilMockedStatic;
public MockedStatic<Recipient> recipientMockedStatic;
@Mock
public MockedStatic<RecipientId> recipientIdMockedStatic;
@Before
public void setup() {
you = UUID.randomUUID();
alice = UUID.randomUUID();
bob = UUID.randomUUID();
GroupsV2UpdateMessageProducer.DescribeMemberStrategy describeMember = createDescriber(ImmutableMap.of(alice, "Alice", bob, "Bob"));
producer = new GroupsV2UpdateMessageProducer(ApplicationProvider.getApplicationContext(), describeMember, you);
threadUtilMockedStatic.when(ThreadUtil::assertMainThread).thenCallRealMethod();
threadUtilMockedStatic.when(ThreadUtil::assertNotMainThread).thenCallRealMethod();
recipientIdMockedStatic.when(() -> RecipientId.from(anyLong())).thenCallRealMethod();
RecipientId aliceId = RecipientId.from(1);
RecipientId bobId = RecipientId.from(2);
Recipient aliceRecipient = recipientWithName(aliceId, "Alice");
Recipient bobRecipient = recipientWithName(bobId, "Bob");
producer = new GroupsV2UpdateMessageProducer(ApplicationProvider.getApplicationContext(), you, null);
recipientIdMockedStatic.when(() -> RecipientId.from(ServiceId.from(alice), null)).thenReturn(aliceId);
recipientIdMockedStatic.when(() -> RecipientId.from(ServiceId.from(bob), null)).thenReturn(bobId);
recipientMockedStatic.when(() -> Recipient.resolved(aliceId)).thenReturn(aliceRecipient);
recipientMockedStatic.when(() -> Recipient.resolved(bobId)).thenReturn(bobRecipient);
}
private static Recipient recipientWithName(RecipientId id, String name) {
Recipient recipient = mock(Recipient.class);
when(recipient.getId()).thenReturn(id);
when(recipient.getDisplayName(any())).thenReturn(name);
return recipient;
}
@Test
@@ -1333,11 +1356,11 @@ public final class GroupsV2UpdateMessageProducerTest {
}
private @NonNull List<String> describeChange(@Nullable DecryptedGroup previousGroupState,
@NonNull DecryptedGroupChange change)
@NonNull DecryptedGroupChange change)
{
threadUtilMockedStatic.when(ThreadUtil::isMainThread).thenReturn(false);
return Stream.of(producer.describeChanges(previousGroupState, change))
.map(UpdateDescription::getString)
.map(UpdateDescription::getSpannable)
.map(Spannable::toString)
.toList();
}
@@ -1346,8 +1369,7 @@ public final class GroupsV2UpdateMessageProducerTest {
}
private @NonNull String describeNewGroup(@NonNull DecryptedGroup group, @NonNull DecryptedGroupChange groupChange) {
threadUtilMockedStatic.when(ThreadUtil::isMainThread).thenReturn(false);
return producer.describeNewGroup(group, groupChange).getString();
return producer.describeNewGroup(group, groupChange).getSpannable().toString();
}
private static GroupStateBuilder newGroupBy(UUID foundingMember, int revision) {
@@ -1399,12 +1421,4 @@ public final class GroupsV2UpdateMessageProducerTest {
return builder.build();
}
}
private static @NonNull GroupsV2UpdateMessageProducer.DescribeMemberStrategy createDescriber(@NonNull Map<UUID, String> map) {
return serviceId -> {
String name = map.get(serviceId.uuid());
assertNotNull(name);
return name;
};
}
}

View File

@@ -1,6 +1,12 @@
package org.thoughtcrime.securesms.database.model;
import android.app.Application;
import android.text.SpannableString;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.whispersystems.signalservice.api.push.ServiceId;
import java.util.Arrays;
@@ -13,13 +19,15 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, application = Application.class)
public final class UpdateDescriptionTest {
@Test
public void staticDescription_byGetStaticString() {
UpdateDescription description = UpdateDescription.staticDescription("update", 0);
assertEquals("update", description.getStaticString());
assertEquals("update", description.getStaticSpannable().toString());
}
@Test
@@ -33,30 +41,30 @@ public final class UpdateDescriptionTest {
public void staticDescription_byString() {
UpdateDescription description = UpdateDescription.staticDescription("update", 0);
assertEquals("update", description.getString());
assertEquals("update", description.getSpannable().toString());
}
@Test(expected = UnsupportedOperationException.class)
public void stringFactory_cannot_call_static_string() {
UpdateDescription description = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), () -> "update", 0);
UpdateDescription description = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), () -> new SpannableString("update"), 0);
description.getStaticString();
description.getStaticSpannable();
}
@Test
public void stringFactory_not_evaluated_until_getString() {
AtomicInteger factoryCalls = new AtomicInteger();
UpdateDescription.StringFactory stringFactory = () -> {
UpdateDescription.SpannableFactory stringFactory = () -> {
factoryCalls.incrementAndGet();
return "update";
return new SpannableString("update");
};
UpdateDescription description = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory, 0);
assertEquals(0, factoryCalls.get());
String string = description.getString();
String string = description.getSpannable().toString();
assertEquals("update", string);
assertEquals(1, factoryCalls.get());
@@ -64,13 +72,13 @@ public final class UpdateDescriptionTest {
@Test
public void stringFactory_reevaluated_on_every_call() {
AtomicInteger factoryCalls = new AtomicInteger();
UpdateDescription.StringFactory stringFactory = () -> "call" + factoryCalls.incrementAndGet();
UpdateDescription description = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory, 0);
AtomicInteger factoryCalls = new AtomicInteger();
UpdateDescription.SpannableFactory stringFactory = () -> new SpannableString( "call" + factoryCalls.incrementAndGet());
UpdateDescription description = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory, 0);
assertEquals("call1", description.getString());
assertEquals("call2", description.getString());
assertEquals("call3", description.getString());
assertEquals("call1", description.getSpannable().toString());
assertEquals("call2", description.getSpannable().toString());
assertEquals("call3", description.getSpannable().toString());
}
@Test
@@ -81,8 +89,8 @@ public final class UpdateDescriptionTest {
UpdateDescription description = UpdateDescription.concatWithNewLines(Arrays.asList(description1, description2));
assertTrue(description.isStringStatic());
assertEquals("update1\nupdate2", description.getStaticString());
assertEquals("update1\nupdate2", description.getString());
assertEquals("update1\nupdate2", description.getStaticSpannable().toString());
assertEquals("update1\nupdate2", description.getSpannable().toString());
}
@Test
@@ -96,12 +104,12 @@ public final class UpdateDescriptionTest {
@Test
public void concat_dynamic_lines() {
AtomicInteger factoryCalls1 = new AtomicInteger();
AtomicInteger factoryCalls2 = new AtomicInteger();
UpdateDescription.StringFactory stringFactory1 = () -> "update." + factoryCalls1.incrementAndGet();
UpdateDescription.StringFactory stringFactory2 = () -> "update." + factoryCalls2.incrementAndGet();
UpdateDescription description1 = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory1, 0);
UpdateDescription description2 = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory2, 0);
AtomicInteger factoryCalls1 = new AtomicInteger();
AtomicInteger factoryCalls2 = new AtomicInteger();
UpdateDescription.SpannableFactory stringFactory1 = () -> new SpannableString("update." + factoryCalls1.incrementAndGet());
UpdateDescription.SpannableFactory stringFactory2 = () -> new SpannableString("update." + factoryCalls2.incrementAndGet());
UpdateDescription description1 = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory1, 0);
UpdateDescription description2 = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory2, 0);
factoryCalls1.set(10);
factoryCalls2.set(20);
@@ -110,20 +118,20 @@ public final class UpdateDescriptionTest {
assertFalse(description.isStringStatic());
assertEquals("update.11\nupdate.21", description.getString());
assertEquals("update.12\nupdate.22", description.getString());
assertEquals("update.13\nupdate.23", description.getString());
assertEquals("update.11\nupdate.21", description.getSpannable().toString());
assertEquals("update.12\nupdate.22", description.getSpannable().toString());
assertEquals("update.13\nupdate.23", description.getSpannable().toString());
}
@Test
public void concat_dynamic_lines_and_static_lines() {
AtomicInteger factoryCalls1 = new AtomicInteger();
AtomicInteger factoryCalls2 = new AtomicInteger();
UpdateDescription.StringFactory stringFactory1 = () -> "update." + factoryCalls1.incrementAndGet();
UpdateDescription.StringFactory stringFactory2 = () -> "update." + factoryCalls2.incrementAndGet();
UpdateDescription description1 = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory1, 0);
UpdateDescription description2 = UpdateDescription.staticDescription("static", 0);
UpdateDescription description3 = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory2, 0);
AtomicInteger factoryCalls1 = new AtomicInteger();
AtomicInteger factoryCalls2 = new AtomicInteger();
UpdateDescription.SpannableFactory stringFactory1 = () -> new SpannableString("update." + factoryCalls1.incrementAndGet());
UpdateDescription.SpannableFactory stringFactory2 = () -> new SpannableString("update." + factoryCalls2.incrementAndGet());
UpdateDescription description1 = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory1, 0);
UpdateDescription description2 = UpdateDescription.staticDescription("static", 0);
UpdateDescription description3 = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory2, 0);
factoryCalls1.set(100);
factoryCalls2.set(200);
@@ -132,8 +140,8 @@ public final class UpdateDescriptionTest {
assertFalse(description.isStringStatic());
assertEquals("update.101\nstatic\nupdate.201", description.getString());
assertEquals("update.102\nstatic\nupdate.202", description.getString());
assertEquals("update.103\nstatic\nupdate.203", description.getString());
assertEquals("update.101\nstatic\nupdate.201", description.getSpannable().toString());
assertEquals("update.102\nstatic\nupdate.202", description.getSpannable().toString());
assertEquals("update.103\nstatic\nupdate.203", description.getSpannable().toString());
}
}