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            }],
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}