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    // 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}