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 let get_users = server.receive::<proto::GetUsers>().await.unwrap();
263 assert_eq!(get_users.payload.user_ids, vec![5]);
264 server.respond(
265 get_users.receipt(),
266 proto::UsersResponse {
267 users: vec![proto::User {
268 id: 5,
269 github_login: "nathansobo".into(),
270 avatar_url: "http://avatar.com/nathansobo".into(),
271 name: None,
272 email: None,
273 }],
274 },
275 );
276
277 // Join a channel and populate its existing messages.
278 let channel = channel_store.update(cx, |store, cx| {
279 let channel_id = store.ordered_channels().next().unwrap().1.id;
280 store.open_channel_chat(channel_id, cx)
281 });
282 let join_channel = server.receive::<proto::JoinChannelChat>().await.unwrap();
283 server.respond(
284 join_channel.receipt(),
285 proto::JoinChannelChatResponse {
286 messages: vec![
287 proto::ChannelMessage {
288 id: 10,
289 body: "a".into(),
290 timestamp: 1000,
291 sender_id: 5,
292 mentions: vec![],
293 nonce: Some(1.into()),
294 reply_to_message_id: None,
295 edited_at: None,
296 },
297 proto::ChannelMessage {
298 id: 11,
299 body: "b".into(),
300 timestamp: 1001,
301 sender_id: 6,
302 mentions: vec![],
303 nonce: Some(2.into()),
304 reply_to_message_id: None,
305 edited_at: None,
306 },
307 ],
308 done: false,
309 },
310 );
311
312 cx.executor().start_waiting();
313
314 // Client requests all users for the received messages
315 let mut get_users = server.receive::<proto::GetUsers>().await.unwrap();
316 get_users.payload.user_ids.sort();
317 assert_eq!(get_users.payload.user_ids, vec![6]);
318 server.respond(
319 get_users.receipt(),
320 proto::UsersResponse {
321 users: vec![proto::User {
322 id: 6,
323 github_login: "maxbrunsfeld".into(),
324 avatar_url: "http://avatar.com/maxbrunsfeld".into(),
325 name: None,
326 email: None,
327 }],
328 },
329 );
330
331 let channel = channel.await.unwrap();
332 channel.update(cx, |channel, _| {
333 assert_eq!(
334 channel
335 .messages_in_range(0..2)
336 .map(|message| (message.sender.github_login.clone(), message.body.clone()))
337 .collect::<Vec<_>>(),
338 &[
339 ("nathansobo".into(), "a".into()),
340 ("maxbrunsfeld".into(), "b".into())
341 ]
342 );
343 });
344
345 // Receive a new message.
346 server.send(proto::ChannelMessageSent {
347 channel_id,
348 message: Some(proto::ChannelMessage {
349 id: 12,
350 body: "c".into(),
351 timestamp: 1002,
352 sender_id: 7,
353 mentions: vec![],
354 nonce: Some(3.into()),
355 reply_to_message_id: None,
356 edited_at: None,
357 }),
358 });
359
360 // Client requests user for message since they haven't seen them yet
361 let get_users = server.receive::<proto::GetUsers>().await.unwrap();
362 assert_eq!(get_users.payload.user_ids, vec![7]);
363 server.respond(
364 get_users.receipt(),
365 proto::UsersResponse {
366 users: vec![proto::User {
367 id: 7,
368 github_login: "as-cii".into(),
369 avatar_url: "http://avatar.com/as-cii".into(),
370 name: None,
371 email: None,
372 }],
373 },
374 );
375
376 assert_eq!(
377 channel.next_event(cx).await,
378 ChannelChatEvent::MessagesUpdated {
379 old_range: 2..2,
380 new_count: 1,
381 }
382 );
383 channel.update(cx, |channel, _| {
384 assert_eq!(
385 channel
386 .messages_in_range(2..3)
387 .map(|message| (message.sender.github_login.clone(), message.body.clone()))
388 .collect::<Vec<_>>(),
389 &[("as-cii".into(), "c".into())]
390 )
391 });
392
393 // Scroll up to view older messages.
394 channel.update(cx, |channel, cx| {
395 channel.load_more_messages(cx).unwrap().detach();
396 });
397 let get_messages = server.receive::<proto::GetChannelMessages>().await.unwrap();
398 assert_eq!(get_messages.payload.channel_id, 5);
399 assert_eq!(get_messages.payload.before_message_id, 10);
400 server.respond(
401 get_messages.receipt(),
402 proto::GetChannelMessagesResponse {
403 done: true,
404 messages: vec![
405 proto::ChannelMessage {
406 id: 8,
407 body: "y".into(),
408 timestamp: 998,
409 sender_id: 5,
410 nonce: Some(4.into()),
411 mentions: vec![],
412 reply_to_message_id: None,
413 edited_at: None,
414 },
415 proto::ChannelMessage {
416 id: 9,
417 body: "z".into(),
418 timestamp: 999,
419 sender_id: 6,
420 nonce: Some(5.into()),
421 mentions: vec![],
422 reply_to_message_id: None,
423 edited_at: None,
424 },
425 ],
426 },
427 );
428
429 assert_eq!(
430 channel.next_event(cx).await,
431 ChannelChatEvent::MessagesUpdated {
432 old_range: 0..0,
433 new_count: 2,
434 }
435 );
436 channel.update(cx, |channel, _| {
437 assert_eq!(
438 channel
439 .messages_in_range(0..2)
440 .map(|message| (message.sender.github_login.clone(), message.body.clone()))
441 .collect::<Vec<_>>(),
442 &[
443 ("nathansobo".into(), "y".into()),
444 ("maxbrunsfeld".into(), "z".into())
445 ]
446 );
447 });
448}
449
450fn init_test(cx: &mut App) -> Entity<ChannelStore> {
451 let settings_store = SettingsStore::test(cx);
452 cx.set_global(settings_store);
453 release_channel::init(SemanticVersion::default(), cx);
454 client::init_settings(cx);
455
456 let clock = Arc::new(FakeSystemClock::new());
457 let http = FakeHttpClient::with_404_response();
458 let client = Client::new(clock, http.clone(), cx);
459 let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
460
461 client::init(&client, cx);
462 crate::init(&client, user_store, cx);
463
464 ChannelStore::global(cx)
465}
466
467fn update_channels(
468 channel_store: &Entity<ChannelStore>,
469 message: proto::UpdateChannels,
470 cx: &mut App,
471) {
472 let task = channel_store.update(cx, |store, cx| store.update_channels(message, cx));
473 assert!(task.is_none());
474}
475
476#[track_caller]
477fn assert_channels(
478 channel_store: &Entity<ChannelStore>,
479 expected_channels: &[(usize, String)],
480 cx: &mut App,
481) {
482 let actual = channel_store.update(cx, |store, _| {
483 store
484 .ordered_channels()
485 .map(|(depth, channel)| (depth, channel.name.to_string()))
486 .collect::<Vec<_>>()
487 });
488 assert_eq!(actual, expected_channels);
489}