channel_store_tests.rs

  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}