1use crate::channel_chat::ChannelChatEvent;
2
3use super::*;
4use client::{Client, UserStore, test::FakeServer};
5use clock::FakeSystemClock;
6use gpui::{App, AppContext as _, Entity, SemanticVersion, TestAppContext};
7use http_client::FakeHttpClient;
8use rpc::proto::{self};
9use settings::SettingsStore;
10
11#[gpui::test]
12fn test_update_channels(cx: &mut App) {
13 let channel_store = init_test(cx);
14
15 update_channels(
16 &channel_store,
17 proto::UpdateChannels {
18 channels: vec![
19 proto::Channel {
20 id: 1,
21 name: "b".to_string(),
22 visibility: proto::ChannelVisibility::Members as i32,
23 parent_path: Vec::new(),
24 channel_order: 1,
25 },
26 proto::Channel {
27 id: 2,
28 name: "a".to_string(),
29 visibility: proto::ChannelVisibility::Members as i32,
30 parent_path: Vec::new(),
31 channel_order: 2,
32 },
33 ],
34 ..Default::default()
35 },
36 cx,
37 );
38 assert_channels(
39 &channel_store,
40 &[
41 //
42 (0, "b".to_string()),
43 (0, "a".to_string()),
44 ],
45 cx,
46 );
47
48 update_channels(
49 &channel_store,
50 proto::UpdateChannels {
51 channels: vec![
52 proto::Channel {
53 id: 3,
54 name: "x".to_string(),
55 visibility: proto::ChannelVisibility::Members as i32,
56 parent_path: vec![1],
57 channel_order: 1,
58 },
59 proto::Channel {
60 id: 4,
61 name: "y".to_string(),
62 visibility: proto::ChannelVisibility::Members as i32,
63 parent_path: vec![2],
64 channel_order: 1,
65 },
66 ],
67 ..Default::default()
68 },
69 cx,
70 );
71 assert_channels(
72 &channel_store,
73 &[
74 (0, "b".to_string()),
75 (1, "x".to_string()),
76 (0, "a".to_string()),
77 (1, "y".to_string()),
78 ],
79 cx,
80 );
81}
82
83#[gpui::test]
84fn test_update_channels_order_independent(cx: &mut App) {
85 /// Based on: https://stackoverflow.com/a/59939809
86 fn unique_permutations<T: Clone>(items: Vec<T>) -> Vec<Vec<T>> {
87 if items.len() == 1 {
88 vec![items]
89 } else {
90 let mut output: Vec<Vec<T>> = vec![];
91
92 for (ix, first) in items.iter().enumerate() {
93 let mut remaining_elements = items.clone();
94 remaining_elements.remove(ix);
95 for mut permutation in unique_permutations(remaining_elements) {
96 permutation.insert(0, first.clone());
97 output.push(permutation);
98 }
99 }
100 output
101 }
102 }
103
104 let test_data = vec![
105 proto::Channel {
106 id: 6,
107 name: "β".to_string(),
108 visibility: proto::ChannelVisibility::Members as i32,
109 parent_path: vec![1, 3],
110 channel_order: 1,
111 },
112 proto::Channel {
113 id: 5,
114 name: "α".to_string(),
115 visibility: proto::ChannelVisibility::Members as i32,
116 parent_path: vec![1],
117 channel_order: 2,
118 },
119 proto::Channel {
120 id: 3,
121 name: "x".to_string(),
122 visibility: proto::ChannelVisibility::Members as i32,
123 parent_path: vec![1],
124 channel_order: 1,
125 },
126 proto::Channel {
127 id: 4,
128 name: "y".to_string(),
129 visibility: proto::ChannelVisibility::Members as i32,
130 parent_path: vec![2],
131 channel_order: 1,
132 },
133 proto::Channel {
134 id: 1,
135 name: "b".to_string(),
136 visibility: proto::ChannelVisibility::Members as i32,
137 parent_path: Vec::new(),
138 channel_order: 1,
139 },
140 proto::Channel {
141 id: 2,
142 name: "a".to_string(),
143 visibility: proto::ChannelVisibility::Members as i32,
144 parent_path: Vec::new(),
145 channel_order: 2,
146 },
147 ];
148
149 let channel_store = init_test(cx);
150 let permutations = unique_permutations(test_data);
151
152 for test_instance in permutations {
153 channel_store.update(cx, |channel_store, _| channel_store.reset());
154
155 update_channels(
156 &channel_store,
157 proto::UpdateChannels {
158 channels: test_instance,
159 ..Default::default()
160 },
161 cx,
162 );
163
164 assert_channels(
165 &channel_store,
166 &[
167 (0, "b".to_string()),
168 (1, "x".to_string()),
169 (2, "β".to_string()),
170 (1, "α".to_string()),
171 (0, "a".to_string()),
172 (1, "y".to_string()),
173 ],
174 cx,
175 );
176 }
177}
178
179#[gpui::test]
180fn test_dangling_channel_paths(cx: &mut App) {
181 let channel_store = init_test(cx);
182
183 update_channels(
184 &channel_store,
185 proto::UpdateChannels {
186 channels: vec![
187 proto::Channel {
188 id: 0,
189 name: "a".to_string(),
190 visibility: proto::ChannelVisibility::Members as i32,
191 parent_path: vec![],
192 channel_order: 1,
193 },
194 proto::Channel {
195 id: 1,
196 name: "b".to_string(),
197 visibility: proto::ChannelVisibility::Members as i32,
198 parent_path: vec![0],
199 channel_order: 1,
200 },
201 proto::Channel {
202 id: 2,
203 name: "c".to_string(),
204 visibility: proto::ChannelVisibility::Members as i32,
205 parent_path: vec![0, 1],
206 channel_order: 1,
207 },
208 ],
209 ..Default::default()
210 },
211 cx,
212 );
213 // Sanity check
214 assert_channels(
215 &channel_store,
216 &[
217 //
218 (0, "a".to_string()),
219 (1, "b".to_string()),
220 (2, "c".to_string()),
221 ],
222 cx,
223 );
224
225 update_channels(
226 &channel_store,
227 proto::UpdateChannels {
228 delete_channels: vec![1, 2],
229 ..Default::default()
230 },
231 cx,
232 );
233
234 // Make sure that the 1/2/3 path is gone
235 assert_channels(&channel_store, &[(0, "a".to_string())], cx);
236}
237
238#[gpui::test]
239async fn test_channel_messages(cx: &mut TestAppContext) {
240 let user_id = 5;
241 let channel_id = 5;
242 let channel_store = cx.update(init_test);
243 let client = channel_store.read_with(cx, |s, _| s.client());
244 let server = FakeServer::for_client(user_id, &client, cx).await;
245
246 // Get the available channels.
247 server.send(proto::UpdateChannels {
248 channels: vec![proto::Channel {
249 id: channel_id,
250 name: "the-channel".to_string(),
251 visibility: proto::ChannelVisibility::Members as i32,
252 parent_path: vec![],
253 channel_order: 1,
254 }],
255 ..Default::default()
256 });
257 cx.executor().run_until_parked();
258 cx.update(|cx| {
259 assert_channels(&channel_store, &[(0, "the-channel".to_string())], cx);
260 });
261
262 // Join a channel and populate its existing messages.
263 let channel = channel_store.update(cx, |store, cx| {
264 let channel_id = store.ordered_channels().next().unwrap().1.id;
265 store.open_channel_chat(channel_id, cx)
266 });
267 let join_channel = server.receive::<proto::JoinChannelChat>().await.unwrap();
268 server.respond(
269 join_channel.receipt(),
270 proto::JoinChannelChatResponse {
271 messages: vec![
272 proto::ChannelMessage {
273 id: 10,
274 body: "a".into(),
275 timestamp: 1000,
276 sender_id: 5,
277 mentions: vec![],
278 nonce: Some(1.into()),
279 reply_to_message_id: None,
280 edited_at: None,
281 },
282 proto::ChannelMessage {
283 id: 11,
284 body: "b".into(),
285 timestamp: 1001,
286 sender_id: 6,
287 mentions: vec![],
288 nonce: Some(2.into()),
289 reply_to_message_id: None,
290 edited_at: None,
291 },
292 ],
293 done: false,
294 },
295 );
296
297 cx.executor().start_waiting();
298
299 // Client requests all users for the received messages
300 let mut get_users = server.receive::<proto::GetUsers>().await.unwrap();
301 get_users.payload.user_ids.sort();
302 assert_eq!(get_users.payload.user_ids, vec![6]);
303 server.respond(
304 get_users.receipt(),
305 proto::UsersResponse {
306 users: vec![proto::User {
307 id: 6,
308 github_login: "maxbrunsfeld".into(),
309 avatar_url: "http://avatar.com/maxbrunsfeld".into(),
310 name: None,
311 }],
312 },
313 );
314
315 let channel = channel.await.unwrap();
316 channel.update(cx, |channel, _| {
317 assert_eq!(
318 channel
319 .messages_in_range(0..2)
320 .map(|message| (message.sender.github_login.clone(), message.body.clone()))
321 .collect::<Vec<_>>(),
322 &[
323 ("user-5".into(), "a".into()),
324 ("maxbrunsfeld".into(), "b".into())
325 ]
326 );
327 });
328
329 // Receive a new message.
330 server.send(proto::ChannelMessageSent {
331 channel_id,
332 message: Some(proto::ChannelMessage {
333 id: 12,
334 body: "c".into(),
335 timestamp: 1002,
336 sender_id: 7,
337 mentions: vec![],
338 nonce: Some(3.into()),
339 reply_to_message_id: None,
340 edited_at: None,
341 }),
342 });
343
344 // Client requests user for message since they haven't seen them yet
345 let get_users = server.receive::<proto::GetUsers>().await.unwrap();
346 assert_eq!(get_users.payload.user_ids, vec![7]);
347 server.respond(
348 get_users.receipt(),
349 proto::UsersResponse {
350 users: vec![proto::User {
351 id: 7,
352 github_login: "as-cii".into(),
353 avatar_url: "http://avatar.com/as-cii".into(),
354 name: None,
355 }],
356 },
357 );
358
359 assert_eq!(
360 channel.next_event(cx).await,
361 ChannelChatEvent::MessagesUpdated {
362 old_range: 2..2,
363 new_count: 1,
364 }
365 );
366 channel.update(cx, |channel, _| {
367 assert_eq!(
368 channel
369 .messages_in_range(2..3)
370 .map(|message| (message.sender.github_login.clone(), message.body.clone()))
371 .collect::<Vec<_>>(),
372 &[("as-cii".into(), "c".into())]
373 )
374 });
375
376 // Scroll up to view older messages.
377 channel.update(cx, |channel, cx| {
378 channel.load_more_messages(cx).unwrap().detach();
379 });
380 let get_messages = server.receive::<proto::GetChannelMessages>().await.unwrap();
381 assert_eq!(get_messages.payload.channel_id, 5);
382 assert_eq!(get_messages.payload.before_message_id, 10);
383 server.respond(
384 get_messages.receipt(),
385 proto::GetChannelMessagesResponse {
386 done: true,
387 messages: vec![
388 proto::ChannelMessage {
389 id: 8,
390 body: "y".into(),
391 timestamp: 998,
392 sender_id: 5,
393 nonce: Some(4.into()),
394 mentions: vec![],
395 reply_to_message_id: None,
396 edited_at: None,
397 },
398 proto::ChannelMessage {
399 id: 9,
400 body: "z".into(),
401 timestamp: 999,
402 sender_id: 6,
403 nonce: Some(5.into()),
404 mentions: vec![],
405 reply_to_message_id: None,
406 edited_at: None,
407 },
408 ],
409 },
410 );
411
412 assert_eq!(
413 channel.next_event(cx).await,
414 ChannelChatEvent::MessagesUpdated {
415 old_range: 0..0,
416 new_count: 2,
417 }
418 );
419 channel.update(cx, |channel, _| {
420 assert_eq!(
421 channel
422 .messages_in_range(0..2)
423 .map(|message| (message.sender.github_login.clone(), message.body.clone()))
424 .collect::<Vec<_>>(),
425 &[
426 ("user-5".into(), "y".into()),
427 ("maxbrunsfeld".into(), "z".into())
428 ]
429 );
430 });
431}
432
433fn init_test(cx: &mut App) -> Entity<ChannelStore> {
434 let settings_store = SettingsStore::test(cx);
435 cx.set_global(settings_store);
436 release_channel::init(SemanticVersion::default(), cx);
437 client::init_settings(cx);
438
439 let clock = Arc::new(FakeSystemClock::new());
440 let http = FakeHttpClient::with_404_response();
441 let client = Client::new(clock, http, cx);
442 let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
443
444 client::init(&client, cx);
445 crate::init(&client, user_store, cx);
446
447 ChannelStore::global(cx)
448}
449
450fn update_channels(
451 channel_store: &Entity<ChannelStore>,
452 message: proto::UpdateChannels,
453 cx: &mut App,
454) {
455 let task = channel_store.update(cx, |store, cx| store.update_channels(message, cx));
456 assert!(task.is_none());
457}
458
459#[track_caller]
460fn assert_channels(
461 channel_store: &Entity<ChannelStore>,
462 expected_channels: &[(usize, String)],
463 cx: &mut App,
464) {
465 let actual = channel_store.update(cx, |store, _| {
466 store
467 .ordered_channels()
468 .map(|(depth, channel)| (depth, channel.name.to_string()))
469 .collect::<Vec<_>>()
470 });
471 assert_eq!(actual, expected_channels);
472}