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