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 },
25 proto::Channel {
26 id: 2,
27 name: "a".to_string(),
28 visibility: proto::ChannelVisibility::Members as i32,
29 parent_path: Vec::new(),
30 },
31 ],
32 ..Default::default()
33 },
34 cx,
35 );
36 assert_channels(
37 &channel_store,
38 &[
39 //
40 (0, "a".to_string()),
41 (0, "b".to_string()),
42 ],
43 cx,
44 );
45
46 update_channels(
47 &channel_store,
48 proto::UpdateChannels {
49 channels: vec![
50 proto::Channel {
51 id: 3,
52 name: "x".to_string(),
53 visibility: proto::ChannelVisibility::Members as i32,
54 parent_path: vec![1],
55 },
56 proto::Channel {
57 id: 4,
58 name: "y".to_string(),
59 visibility: proto::ChannelVisibility::Members as i32,
60 parent_path: vec![2],
61 },
62 ],
63 ..Default::default()
64 },
65 cx,
66 );
67 assert_channels(
68 &channel_store,
69 &[
70 (0, "a".to_string()),
71 (1, "y".to_string()),
72 (0, "b".to_string()),
73 (1, "x".to_string()),
74 ],
75 cx,
76 );
77}
78
79#[gpui::test]
80fn test_dangling_channel_paths(cx: &mut App) {
81 let channel_store = init_test(cx);
82
83 update_channels(
84 &channel_store,
85 proto::UpdateChannels {
86 channels: vec![
87 proto::Channel {
88 id: 0,
89 name: "a".to_string(),
90 visibility: proto::ChannelVisibility::Members as i32,
91 parent_path: vec![],
92 },
93 proto::Channel {
94 id: 1,
95 name: "b".to_string(),
96 visibility: proto::ChannelVisibility::Members as i32,
97 parent_path: vec![0],
98 },
99 proto::Channel {
100 id: 2,
101 name: "c".to_string(),
102 visibility: proto::ChannelVisibility::Members as i32,
103 parent_path: vec![0, 1],
104 },
105 ],
106 ..Default::default()
107 },
108 cx,
109 );
110 // Sanity check
111 assert_channels(
112 &channel_store,
113 &[
114 //
115 (0, "a".to_string()),
116 (1, "b".to_string()),
117 (2, "c".to_string()),
118 ],
119 cx,
120 );
121
122 update_channels(
123 &channel_store,
124 proto::UpdateChannels {
125 delete_channels: vec![1, 2],
126 ..Default::default()
127 },
128 cx,
129 );
130
131 // Make sure that the 1/2/3 path is gone
132 assert_channels(&channel_store, &[(0, "a".to_string())], cx);
133}
134
135#[gpui::test]
136async fn test_channel_messages(cx: &mut TestAppContext) {
137 let user_id = 5;
138 let channel_id = 5;
139 let channel_store = cx.update(init_test);
140 let client = channel_store.read_with(cx, |s, _| s.client());
141 let server = FakeServer::for_client(user_id, &client, cx).await;
142
143 // Get the available channels.
144 server.send(proto::UpdateChannels {
145 channels: vec![proto::Channel {
146 id: channel_id,
147 name: "the-channel".to_string(),
148 visibility: proto::ChannelVisibility::Members as i32,
149 parent_path: vec![],
150 }],
151 ..Default::default()
152 });
153 cx.executor().run_until_parked();
154 cx.update(|cx| {
155 assert_channels(&channel_store, &[(0, "the-channel".to_string())], cx);
156 });
157
158 let get_users = server.receive::<proto::GetUsers>().await.unwrap();
159 assert_eq!(get_users.payload.user_ids, vec![5]);
160 server.respond(
161 get_users.receipt(),
162 proto::UsersResponse {
163 users: vec![proto::User {
164 id: 5,
165 github_login: "nathansobo".into(),
166 avatar_url: "http://avatar.com/nathansobo".into(),
167 name: None,
168 email: None,
169 }],
170 },
171 );
172
173 // Join a channel and populate its existing messages.
174 let channel = channel_store.update(cx, |store, cx| {
175 let channel_id = store.ordered_channels().next().unwrap().1.id;
176 store.open_channel_chat(channel_id, cx)
177 });
178 let join_channel = server.receive::<proto::JoinChannelChat>().await.unwrap();
179 server.respond(
180 join_channel.receipt(),
181 proto::JoinChannelChatResponse {
182 messages: vec![
183 proto::ChannelMessage {
184 id: 10,
185 body: "a".into(),
186 timestamp: 1000,
187 sender_id: 5,
188 mentions: vec![],
189 nonce: Some(1.into()),
190 reply_to_message_id: None,
191 edited_at: None,
192 },
193 proto::ChannelMessage {
194 id: 11,
195 body: "b".into(),
196 timestamp: 1001,
197 sender_id: 6,
198 mentions: vec![],
199 nonce: Some(2.into()),
200 reply_to_message_id: None,
201 edited_at: None,
202 },
203 ],
204 done: false,
205 },
206 );
207
208 cx.executor().start_waiting();
209
210 // Client requests all users for the received messages
211 let mut get_users = server.receive::<proto::GetUsers>().await.unwrap();
212 get_users.payload.user_ids.sort();
213 assert_eq!(get_users.payload.user_ids, vec![6]);
214 server.respond(
215 get_users.receipt(),
216 proto::UsersResponse {
217 users: vec![proto::User {
218 id: 6,
219 github_login: "maxbrunsfeld".into(),
220 avatar_url: "http://avatar.com/maxbrunsfeld".into(),
221 name: None,
222 email: None,
223 }],
224 },
225 );
226
227 let channel = channel.await.unwrap();
228 channel.update(cx, |channel, _| {
229 assert_eq!(
230 channel
231 .messages_in_range(0..2)
232 .map(|message| (message.sender.github_login.clone(), message.body.clone()))
233 .collect::<Vec<_>>(),
234 &[
235 ("nathansobo".into(), "a".into()),
236 ("maxbrunsfeld".into(), "b".into())
237 ]
238 );
239 });
240
241 // Receive a new message.
242 server.send(proto::ChannelMessageSent {
243 channel_id,
244 message: Some(proto::ChannelMessage {
245 id: 12,
246 body: "c".into(),
247 timestamp: 1002,
248 sender_id: 7,
249 mentions: vec![],
250 nonce: Some(3.into()),
251 reply_to_message_id: None,
252 edited_at: None,
253 }),
254 });
255
256 // Client requests user for message since they haven't seen them yet
257 let get_users = server.receive::<proto::GetUsers>().await.unwrap();
258 assert_eq!(get_users.payload.user_ids, vec![7]);
259 server.respond(
260 get_users.receipt(),
261 proto::UsersResponse {
262 users: vec![proto::User {
263 id: 7,
264 github_login: "as-cii".into(),
265 avatar_url: "http://avatar.com/as-cii".into(),
266 name: None,
267 email: None,
268 }],
269 },
270 );
271
272 assert_eq!(
273 channel.next_event(cx).await,
274 ChannelChatEvent::MessagesUpdated {
275 old_range: 2..2,
276 new_count: 1,
277 }
278 );
279 channel.update(cx, |channel, _| {
280 assert_eq!(
281 channel
282 .messages_in_range(2..3)
283 .map(|message| (message.sender.github_login.clone(), message.body.clone()))
284 .collect::<Vec<_>>(),
285 &[("as-cii".into(), "c".into())]
286 )
287 });
288
289 // Scroll up to view older messages.
290 channel.update(cx, |channel, cx| {
291 channel.load_more_messages(cx).unwrap().detach();
292 });
293 let get_messages = server.receive::<proto::GetChannelMessages>().await.unwrap();
294 assert_eq!(get_messages.payload.channel_id, 5);
295 assert_eq!(get_messages.payload.before_message_id, 10);
296 server.respond(
297 get_messages.receipt(),
298 proto::GetChannelMessagesResponse {
299 done: true,
300 messages: vec![
301 proto::ChannelMessage {
302 id: 8,
303 body: "y".into(),
304 timestamp: 998,
305 sender_id: 5,
306 nonce: Some(4.into()),
307 mentions: vec![],
308 reply_to_message_id: None,
309 edited_at: None,
310 },
311 proto::ChannelMessage {
312 id: 9,
313 body: "z".into(),
314 timestamp: 999,
315 sender_id: 6,
316 nonce: Some(5.into()),
317 mentions: vec![],
318 reply_to_message_id: None,
319 edited_at: None,
320 },
321 ],
322 },
323 );
324
325 assert_eq!(
326 channel.next_event(cx).await,
327 ChannelChatEvent::MessagesUpdated {
328 old_range: 0..0,
329 new_count: 2,
330 }
331 );
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(), "y".into()),
340 ("maxbrunsfeld".into(), "z".into())
341 ]
342 );
343 });
344}
345
346fn init_test(cx: &mut App) -> Entity<ChannelStore> {
347 let settings_store = SettingsStore::test(cx);
348 cx.set_global(settings_store);
349 release_channel::init(SemanticVersion::default(), cx);
350 client::init_settings(cx);
351
352 let clock = Arc::new(FakeSystemClock::new());
353 let http = FakeHttpClient::with_404_response();
354 let client = Client::new(clock, http.clone(), cx);
355 let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
356
357 client::init(&client, cx);
358 crate::init(&client, user_store, cx);
359
360 ChannelStore::global(cx)
361}
362
363fn update_channels(
364 channel_store: &Entity<ChannelStore>,
365 message: proto::UpdateChannels,
366 cx: &mut App,
367) {
368 let task = channel_store.update(cx, |store, cx| store.update_channels(message, cx));
369 assert!(task.is_none());
370}
371
372#[track_caller]
373fn assert_channels(
374 channel_store: &Entity<ChannelStore>,
375 expected_channels: &[(usize, String)],
376 cx: &mut App,
377) {
378 let actual = channel_store.update(cx, |store, _| {
379 store
380 .ordered_channels()
381 .map(|(depth, channel)| (depth, channel.name.to_string()))
382 .collect::<Vec<_>>()
383 });
384 assert_eq!(actual, expected_channels);
385}