ConversationTest.java

  1package eu.siacs.conversations.entities;
  2
  3import static org.mockito.ArgumentMatchers.any;
  4import static org.mockito.Mockito.doAnswer;
  5import static org.mockito.Mockito.mock;
  6import static org.mockito.Mockito.when;
  7
  8import java.util.concurrent.atomic.AtomicReference;
  9
 10import org.junit.Before;
 11import org.junit.BeforeClass;
 12import org.junit.Test;
 13import org.junit.runner.RunWith;
 14import org.robolectric.RobolectricTestRunner;
 15import org.robolectric.RuntimeEnvironment;
 16import org.robolectric.Shadows;
 17import org.robolectric.annotation.Config;
 18import org.robolectric.annotation.ConscryptMode;
 19
 20import android.os.Build;
 21import android.os.Looper;
 22import android.view.View;
 23import android.widget.ListView;
 24import android.widget.RelativeLayout;
 25import androidx.viewpager.widget.ViewPager;
 26import com.google.android.material.tabs.TabLayout;
 27import eu.siacs.conversations.Conversations;
 28import eu.siacs.conversations.R;
 29import eu.siacs.conversations.xml.Namespace;
 30import eu.siacs.conversations.xmpp.Jid;
 31import im.conversations.android.xmpp.model.disco.info.Feature;
 32import im.conversations.android.xmpp.model.disco.info.InfoQuery;
 33import junit.framework.Assert;
 34
 35@RunWith(RobolectricTestRunner.class)
 36@Config(sdk = Build.VERSION_CODES.TIRAMISU, application = Conversations.class)
 37@ConscryptMode(ConscryptMode.Mode.OFF)
 38public class ConversationTest {
 39    private static final InfoQuery INFO_QUERY_WITH_OCCUPANT_ID = new InfoQuery();
 40    private static final InfoQuery INFO_QUERY_WITHOUT_OCCUPANT_ID = new InfoQuery();
 41
 42    private Conversation withOccupantId;
 43    private Conversation withoutOccupantId;
 44    private Conversation nullMucOptions;
 45
 46    @BeforeClass
 47    public static void setupClass() {
 48        final var occupantIdFeature = new Feature();
 49        occupantIdFeature.setVar(Namespace.OCCUPANT_ID);
 50        INFO_QUERY_WITH_OCCUPANT_ID.addChild(occupantIdFeature);
 51    }
 52
 53    @Before
 54    public void setUp() throws Exception {
 55        final var account = mock(Account.class);
 56        when(account.getJid()).thenReturn(Jid.ofLocalAndDomain("testAccount", "example.org"));
 57
 58        withOccupantId = new Conversation(
 59            "Test MUC",
 60            account,
 61            Jid.ofLocalAndDomain("testMuc", "example.org"),
 62            Conversation.MODE_MULTI
 63        );
 64        withOccupantId.getMucOptions().updateConfiguration(INFO_QUERY_WITH_OCCUPANT_ID);
 65
 66        withoutOccupantId = new Conversation(
 67            "Test MUC",
 68            account,
 69            Jid.ofLocalAndDomain("testMuc", "example.org"),
 70            Conversation.MODE_MULTI
 71        );
 72        withoutOccupantId.getMucOptions().updateConfiguration(INFO_QUERY_WITHOUT_OCCUPANT_ID);
 73
 74        nullMucOptions = new Conversation(
 75            "Test MUC",
 76            account,
 77            Jid.ofLocalAndDomain("testMuc", "example.org"),
 78            Conversation.MODE_MULTI
 79        );
 80        final var mucOptionsField = Conversation.class.getDeclaredField("mucOptions");
 81        mucOptionsField.setAccessible(true);
 82        ((AtomicReference<?>) mucOptionsField.get(nullMucOptions)).set(null);
 83    }
 84
 85    @Test
 86    public void getMucOccupantsCacheReturnsCacheWhenMucOptionsIsNull() throws Exception {
 87        var cache = nullMucOptions.getMucOccupantCache();
 88        Assert.assertNotNull("Should return cache when mucOptions is null", cache);
 89    }
 90
 91    @Test
 92    public void getMucOccupantsCacheReturnsCacheWhenFeatureSupported() throws Exception {
 93        var cache = withOccupantId.getMucOccupantCache();
 94        Assert.assertNotNull("Should return cache when occupant-id is supported", cache);
 95    }
 96
 97    @Test
 98    public void getMucOccupantsCacheReturnsCacheWhenFeatureNotSupported() throws Exception {
 99        var cache = withoutOccupantId.getMucOccupantCache();
100        Assert.assertNotNull("Should return cache even when occupant-id is not supported", cache);
101    }
102
103    @Test
104    public void setupViewPagerListenerDoesNotLeakBetweenConversations() {
105        final var context = RuntimeEnvironment.getApplication();
106        final var pager = new ViewPager(context);
107        final var tabs = mock(TabLayout.class);
108
109        final int[] selectedTabPosition = {0};
110        doAnswer(invocation -> {
111            ViewPager vp = invocation.getArgument(0);
112            vp.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
113                public void onPageScrollStateChanged(int state) {}
114                public void onPageScrolled(int p, float o, int opx) {}
115                public void onPageSelected(int position) {
116                    selectedTabPosition[0] = position;
117                }
118            });
119            return null;
120        }).when(tabs).setupWithViewPager(any(ViewPager.class));
121        when(tabs.getSelectedTabPosition()).thenAnswer(inv -> selectedTabPosition[0]);
122
123        final var page1 = new RelativeLayout(context);
124        final var page2 = new RelativeLayout(context);
125        final var commandsView = new ListView(context);
126        commandsView.setId(R.id.commands_view);
127        page2.addView(commandsView);
128        pager.addView(page1);
129        pager.addView(page2);
130
131        final var account = mock(Account.class);
132        when(account.getJid()).thenReturn(Jid.ofLocalAndDomain("testAccount", "example.org"));
133        final var roster = mock(Roster.class);
134        when(account.getRoster()).thenReturn(roster);
135
136        final var mucJid = Jid.ofLocalAndDomain("operations", "conference.soprani.ca");
137        final var mucContact = mock(Contact.class);
138        when(mucContact.isApp()).thenReturn(false);
139        when(mucContact.getJid()).thenReturn(mucJid);
140        when(roster.getContact(mucJid)).thenReturn(mucContact);
141
142        final var appJid = Jid.ofDomain("jmp.chat");
143        final var appContact = mock(Contact.class);
144        when(appContact.isApp()).thenReturn(true);
145        when(appContact.getJid()).thenReturn(appJid);
146        when(roster.getContact(appJid)).thenReturn(appContact);
147
148        final var muc = new Conversation("MUC", account, mucJid, Conversation.MODE_MULTI);
149        final var app = new Conversation("App", account, appJid, Conversation.MODE_SINGLE);
150
151        muc.setupViewPager(pager, tabs, false, null);
152        muc.showViewPager();
153
154        pager.layout(0, 0, 1024, 768);
155        Shadows.shadowOf(Looper.getMainLooper()).idle();
156
157        Assert.assertEquals("MUC should start on Conversation tab", 0, muc.getCurrentTab());
158
159        app.setupViewPager(pager, tabs, false, muc);
160        app.showViewPager();
161
162        Shadows.shadowOf(Looper.getMainLooper()).idle();
163
164        pager.setCurrentItem(1);
165
166        Assert.assertEquals(
167            "Page change on app conversation must not corrupt MUC's tab state",
168            0,
169            muc.getCurrentTab());
170
171        Assert.assertEquals(
172            "Tab indicator must stay in sync with the selected page",
173            1,
174            tabs.getSelectedTabPosition());
175    }
176}