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}