channel_buffer_tests.rs

  1use std::ops::Range;
  2
  3use crate::{
  4    rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
  5    tests::TestServer,
  6};
  7use client::{Collaborator, ParticipantIndex, UserId};
  8use collections::HashMap;
  9use editor::{Anchor, Editor, ToOffset};
 10use futures::future;
 11use gpui::{BackgroundExecutor, Model, TestAppContext, ViewContext};
 12use rpc::{proto::PeerId, RECEIVE_TIMEOUT};
 13
 14#[gpui::test]
 15async fn test_core_channel_buffers(
 16    executor: BackgroundExecutor,
 17    cx_a: &mut TestAppContext,
 18    cx_b: &mut TestAppContext,
 19) {
 20    let mut server = TestServer::start(executor.clone()).await;
 21    let client_a = server.create_client(cx_a, "user_a").await;
 22    let client_b = server.create_client(cx_b, "user_b").await;
 23
 24    let channel_id = server
 25        .make_channel("zed", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
 26        .await;
 27
 28    // Client A joins the channel buffer
 29    let channel_buffer_a = client_a
 30        .channel_store()
 31        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
 32        .await
 33        .unwrap();
 34
 35    // Client A edits the buffer
 36    let buffer_a = channel_buffer_a.read_with(cx_a, |buffer, _| buffer.buffer());
 37    buffer_a.update(cx_a, |buffer, cx| {
 38        buffer.edit([(0..0, "hello world")], None, cx)
 39    });
 40    buffer_a.update(cx_a, |buffer, cx| {
 41        buffer.edit([(5..5, ", cruel")], None, cx)
 42    });
 43    buffer_a.update(cx_a, |buffer, cx| {
 44        buffer.edit([(0..5, "goodbye")], None, cx)
 45    });
 46    buffer_a.update(cx_a, |buffer, cx| buffer.undo(cx));
 47    assert_eq!(buffer_text(&buffer_a, cx_a), "hello, cruel world");
 48    executor.run_until_parked();
 49
 50    // Client B joins the channel buffer
 51    let channel_buffer_b = client_b
 52        .channel_store()
 53        .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
 54        .await
 55        .unwrap();
 56    channel_buffer_b.read_with(cx_b, |buffer, _| {
 57        assert_collaborators(
 58            buffer.collaborators(),
 59            &[client_a.user_id(), client_b.user_id()],
 60        );
 61    });
 62
 63    // Client B sees the correct text, and then edits it
 64    let buffer_b = channel_buffer_b.read_with(cx_b, |buffer, _| buffer.buffer());
 65    assert_eq!(
 66        buffer_b.read_with(cx_b, |buffer, _| buffer.remote_id()),
 67        buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id())
 68    );
 69    assert_eq!(buffer_text(&buffer_b, cx_b), "hello, cruel world");
 70    buffer_b.update(cx_b, |buffer, cx| {
 71        buffer.edit([(7..12, "beautiful")], None, cx)
 72    });
 73
 74    // Both A and B see the new edit
 75    executor.run_until_parked();
 76    assert_eq!(buffer_text(&buffer_a, cx_a), "hello, beautiful world");
 77    assert_eq!(buffer_text(&buffer_b, cx_b), "hello, beautiful world");
 78
 79    // Client A closes the channel buffer.
 80    cx_a.update(|_| drop(channel_buffer_a));
 81    executor.run_until_parked();
 82
 83    // Client B sees that client A is gone from the channel buffer.
 84    channel_buffer_b.read_with(cx_b, |buffer, _| {
 85        assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
 86    });
 87
 88    // Client A rejoins the channel buffer
 89    let _channel_buffer_a = client_a
 90        .channel_store()
 91        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
 92        .await
 93        .unwrap();
 94    executor.run_until_parked();
 95
 96    // Sanity test, make sure we saw A rejoining
 97    channel_buffer_b.read_with(cx_b, |buffer, _| {
 98        assert_collaborators(
 99            &buffer.collaborators(),
100            &[client_a.user_id(), client_b.user_id()],
101        );
102    });
103
104    // Client A loses connection.
105    server.forbid_connections();
106    server.disconnect_client(client_a.peer_id().unwrap());
107    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
108
109    // Client B observes A disconnect
110    channel_buffer_b.read_with(cx_b, |buffer, _| {
111        assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
112    });
113
114    // TODO:
115    // - Test synchronizing offline updates, what happens to A's channel buffer when A disconnects
116    // - Test interaction with channel deletion while buffer is open
117}
118
119// todo!("collab_ui")
120// #[gpui::test]
121// async fn test_channel_notes_participant_indices(
122//     executor: BackgroundExecutor,
123//     mut cx_a: &mut TestAppContext,
124//     mut cx_b: &mut TestAppContext,
125//     cx_c: &mut TestAppContext,
126// ) {
127//     let mut server = TestServer::start(&executor).await;
128//     let client_a = server.create_client(cx_a, "user_a").await;
129//     let client_b = server.create_client(cx_b, "user_b").await;
130//     let client_c = server.create_client(cx_c, "user_c").await;
131
132//     let active_call_a = cx_a.read(ActiveCall::global);
133//     let active_call_b = cx_b.read(ActiveCall::global);
134
135//     cx_a.update(editor::init);
136//     cx_b.update(editor::init);
137//     cx_c.update(editor::init);
138
139//     let channel_id = server
140//         .make_channel(
141//             "the-channel",
142//             None,
143//             (&client_a, cx_a),
144//             &mut [(&client_b, cx_b), (&client_c, cx_c)],
145//         )
146//         .await;
147
148//     client_a
149//         .fs()
150//         .insert_tree("/root", json!({"file.txt": "123"}))
151//         .await;
152//     let (project_a, worktree_id_a) = client_a.build_local_project("/root", cx_a).await;
153//     let project_b = client_b.build_empty_local_project(cx_b);
154//     let project_c = client_c.build_empty_local_project(cx_c);
155//     let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
156//     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
157//     let workspace_c = client_c.build_workspace(&project_c, cx_c).root(cx_c);
158
159//     // Clients A, B, and C open the channel notes
160//     let channel_view_a = cx_a
161//         .update(|cx| ChannelView::open(channel_id, workspace_a.clone(), cx))
162//         .await
163//         .unwrap();
164//     let channel_view_b = cx_b
165//         .update(|cx| ChannelView::open(channel_id, workspace_b.clone(), cx))
166//         .await
167//         .unwrap();
168//     let channel_view_c = cx_c
169//         .update(|cx| ChannelView::open(channel_id, workspace_c.clone(), cx))
170//         .await
171//         .unwrap();
172
173//     // Clients A, B, and C all insert and select some text
174//     channel_view_a.update(cx_a, |notes, cx| {
175//         notes.editor.update(cx, |editor, cx| {
176//             editor.insert("a", cx);
177//             editor.change_selections(None, cx, |selections| {
178//                 selections.select_ranges(vec![0..1]);
179//             });
180//         });
181//     });
182//     executor.run_until_parked();
183//     channel_view_b.update(cx_b, |notes, cx| {
184//         notes.editor.update(cx, |editor, cx| {
185//             editor.move_down(&Default::default(), cx);
186//             editor.insert("b", cx);
187//             editor.change_selections(None, cx, |selections| {
188//                 selections.select_ranges(vec![1..2]);
189//             });
190//         });
191//     });
192//     executor.run_until_parked();
193//     channel_view_c.update(cx_c, |notes, cx| {
194//         notes.editor.update(cx, |editor, cx| {
195//             editor.move_down(&Default::default(), cx);
196//             editor.insert("c", cx);
197//             editor.change_selections(None, cx, |selections| {
198//                 selections.select_ranges(vec![2..3]);
199//             });
200//         });
201//     });
202
203//     // Client A sees clients B and C without assigned colors, because they aren't
204//     // in a call together.
205//     executor.run_until_parked();
206//     channel_view_a.update(cx_a, |notes, cx| {
207//         notes.editor.update(cx, |editor, cx| {
208//             assert_remote_selections(editor, &[(None, 1..2), (None, 2..3)], cx);
209//         });
210//     });
211
212//     // Clients A and B join the same call.
213//     for (call, cx) in [(&active_call_a, &mut cx_a), (&active_call_b, &mut cx_b)] {
214//         call.update(*cx, |call, cx| call.join_channel(channel_id, cx))
215//             .await
216//             .unwrap();
217//     }
218
219//     // Clients A and B see each other with two different assigned colors. Client C
220//     // still doesn't have a color.
221//     executor.run_until_parked();
222//     channel_view_a.update(cx_a, |notes, cx| {
223//         notes.editor.update(cx, |editor, cx| {
224//             assert_remote_selections(
225//                 editor,
226//                 &[(Some(ParticipantIndex(1)), 1..2), (None, 2..3)],
227//                 cx,
228//             );
229//         });
230//     });
231//     channel_view_b.update(cx_b, |notes, cx| {
232//         notes.editor.update(cx, |editor, cx| {
233//             assert_remote_selections(
234//                 editor,
235//                 &[(Some(ParticipantIndex(0)), 0..1), (None, 2..3)],
236//                 cx,
237//             );
238//         });
239//     });
240
241//     // Client A shares a project, and client B joins.
242//     let project_id = active_call_a
243//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
244//         .await
245//         .unwrap();
246//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
247//     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
248
249//     // Clients A and B open the same file.
250//     let editor_a = workspace_a
251//         .update(cx_a, |workspace, cx| {
252//             workspace.open_path((worktree_id_a, "file.txt"), None, true, cx)
253//         })
254//         .await
255//         .unwrap()
256//         .downcast::<Editor>()
257//         .unwrap();
258//     let editor_b = workspace_b
259//         .update(cx_b, |workspace, cx| {
260//             workspace.open_path((worktree_id_a, "file.txt"), None, true, cx)
261//         })
262//         .await
263//         .unwrap()
264//         .downcast::<Editor>()
265//         .unwrap();
266
267//     editor_a.update(cx_a, |editor, cx| {
268//         editor.change_selections(None, cx, |selections| {
269//             selections.select_ranges(vec![0..1]);
270//         });
271//     });
272//     editor_b.update(cx_b, |editor, cx| {
273//         editor.change_selections(None, cx, |selections| {
274//             selections.select_ranges(vec![2..3]);
275//         });
276//     });
277//     executor.run_until_parked();
278
279//     // Clients A and B see each other with the same colors as in the channel notes.
280//     editor_a.update(cx_a, |editor, cx| {
281//         assert_remote_selections(editor, &[(Some(ParticipantIndex(1)), 2..3)], cx);
282//     });
283//     editor_b.update(cx_b, |editor, cx| {
284//         assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], cx);
285//     });
286// }
287
288#[track_caller]
289fn assert_remote_selections(
290    editor: &mut Editor,
291    expected_selections: &[(Option<ParticipantIndex>, Range<usize>)],
292    cx: &mut ViewContext<Editor>,
293) {
294    let snapshot = editor.snapshot(cx);
295    let range = Anchor::min()..Anchor::max();
296    let remote_selections = snapshot
297        .remote_selections_in_range(&range, editor.collaboration_hub().unwrap(), cx)
298        .map(|s| {
299            let start = s.selection.start.to_offset(&snapshot.buffer_snapshot);
300            let end = s.selection.end.to_offset(&snapshot.buffer_snapshot);
301            (s.participant_index, start..end)
302        })
303        .collect::<Vec<_>>();
304    assert_eq!(
305        remote_selections, expected_selections,
306        "incorrect remote selections"
307    );
308}
309
310#[gpui::test]
311async fn test_multiple_handles_to_channel_buffer(
312    deterministic: BackgroundExecutor,
313    cx_a: &mut TestAppContext,
314) {
315    let mut server = TestServer::start(deterministic.clone()).await;
316    let client_a = server.create_client(cx_a, "user_a").await;
317
318    let channel_id = server
319        .make_channel("the-channel", None, (&client_a, cx_a), &mut [])
320        .await;
321
322    let channel_buffer_1 = client_a
323        .channel_store()
324        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
325    let channel_buffer_2 = client_a
326        .channel_store()
327        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
328    let channel_buffer_3 = client_a
329        .channel_store()
330        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
331
332    // All concurrent tasks for opening a channel buffer return the same model handle.
333    let (channel_buffer, channel_buffer_2, channel_buffer_3) =
334        future::try_join3(channel_buffer_1, channel_buffer_2, channel_buffer_3)
335            .await
336            .unwrap();
337    let channel_buffer_model_id = channel_buffer.entity_id();
338    assert_eq!(channel_buffer, channel_buffer_2);
339    assert_eq!(channel_buffer, channel_buffer_3);
340
341    channel_buffer.update(cx_a, |buffer, cx| {
342        buffer.buffer().update(cx, |buffer, cx| {
343            buffer.edit([(0..0, "hello")], None, cx);
344        })
345    });
346    deterministic.run_until_parked();
347
348    cx_a.update(|_| {
349        drop(channel_buffer);
350        drop(channel_buffer_2);
351        drop(channel_buffer_3);
352    });
353    deterministic.run_until_parked();
354
355    // The channel buffer can be reopened after dropping it.
356    let channel_buffer = client_a
357        .channel_store()
358        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
359        .await
360        .unwrap();
361    assert_ne!(channel_buffer.entity_id(), channel_buffer_model_id);
362    channel_buffer.update(cx_a, |buffer, cx| {
363        buffer.buffer().update(cx, |buffer, _| {
364            assert_eq!(buffer.text(), "hello");
365        })
366    });
367}
368
369#[gpui::test]
370async fn test_channel_buffer_disconnect(
371    deterministic: BackgroundExecutor,
372    cx_a: &mut TestAppContext,
373    cx_b: &mut TestAppContext,
374) {
375    let mut server = TestServer::start(deterministic.clone()).await;
376    let client_a = server.create_client(cx_a, "user_a").await;
377    let client_b = server.create_client(cx_b, "user_b").await;
378
379    let channel_id = server
380        .make_channel(
381            "the-channel",
382            None,
383            (&client_a, cx_a),
384            &mut [(&client_b, cx_b)],
385        )
386        .await;
387
388    let channel_buffer_a = client_a
389        .channel_store()
390        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
391        .await
392        .unwrap();
393
394    let channel_buffer_b = client_b
395        .channel_store()
396        .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
397        .await
398        .unwrap();
399
400    server.forbid_connections();
401    server.disconnect_client(client_a.peer_id().unwrap());
402    deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
403
404    channel_buffer_a.update(cx_a, |buffer, cx| {
405        assert_eq!(buffer.channel(cx).unwrap().name, "the-channel");
406        assert!(!buffer.is_connected());
407    });
408
409    deterministic.run_until_parked();
410
411    server.allow_connections();
412    deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
413
414    deterministic.run_until_parked();
415
416    client_a
417        .channel_store()
418        .update(cx_a, |channel_store, _| {
419            channel_store.remove_channel(channel_id)
420        })
421        .await
422        .unwrap();
423    deterministic.run_until_parked();
424
425    // Channel buffer observed the deletion
426    channel_buffer_b.update(cx_b, |buffer, cx| {
427        assert!(buffer.channel(cx).is_none());
428        assert!(!buffer.is_connected());
429    });
430}
431
432#[gpui::test]
433async fn test_rejoin_channel_buffer(
434    deterministic: BackgroundExecutor,
435    cx_a: &mut TestAppContext,
436    cx_b: &mut TestAppContext,
437) {
438    let mut server = TestServer::start(deterministic.clone()).await;
439    let client_a = server.create_client(cx_a, "user_a").await;
440    let client_b = server.create_client(cx_b, "user_b").await;
441
442    let channel_id = server
443        .make_channel(
444            "the-channel",
445            None,
446            (&client_a, cx_a),
447            &mut [(&client_b, cx_b)],
448        )
449        .await;
450
451    let channel_buffer_a = client_a
452        .channel_store()
453        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
454        .await
455        .unwrap();
456    let channel_buffer_b = client_b
457        .channel_store()
458        .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
459        .await
460        .unwrap();
461
462    channel_buffer_a.update(cx_a, |buffer, cx| {
463        buffer.buffer().update(cx, |buffer, cx| {
464            buffer.edit([(0..0, "1")], None, cx);
465        })
466    });
467    deterministic.run_until_parked();
468
469    // Client A disconnects.
470    server.forbid_connections();
471    server.disconnect_client(client_a.peer_id().unwrap());
472
473    // Both clients make an edit.
474    channel_buffer_a.update(cx_a, |buffer, cx| {
475        buffer.buffer().update(cx, |buffer, cx| {
476            buffer.edit([(1..1, "2")], None, cx);
477        })
478    });
479    channel_buffer_b.update(cx_b, |buffer, cx| {
480        buffer.buffer().update(cx, |buffer, cx| {
481            buffer.edit([(0..0, "0")], None, cx);
482        })
483    });
484
485    // Both clients see their own edit.
486    deterministic.run_until_parked();
487    channel_buffer_a.read_with(cx_a, |buffer, cx| {
488        assert_eq!(buffer.buffer().read(cx).text(), "12");
489    });
490    channel_buffer_b.read_with(cx_b, |buffer, cx| {
491        assert_eq!(buffer.buffer().read(cx).text(), "01");
492    });
493
494    // Client A reconnects. Both clients see each other's edits, and see
495    // the same collaborators.
496    server.allow_connections();
497    deterministic.advance_clock(RECEIVE_TIMEOUT);
498    channel_buffer_a.read_with(cx_a, |buffer, cx| {
499        assert_eq!(buffer.buffer().read(cx).text(), "012");
500    });
501    channel_buffer_b.read_with(cx_b, |buffer, cx| {
502        assert_eq!(buffer.buffer().read(cx).text(), "012");
503    });
504
505    channel_buffer_a.read_with(cx_a, |buffer_a, _| {
506        channel_buffer_b.read_with(cx_b, |buffer_b, _| {
507            assert_eq!(buffer_a.collaborators(), buffer_b.collaborators());
508        });
509    });
510}
511
512#[gpui::test]
513async fn test_channel_buffers_and_server_restarts(
514    deterministic: BackgroundExecutor,
515    cx_a: &mut TestAppContext,
516    cx_b: &mut TestAppContext,
517    cx_c: &mut TestAppContext,
518) {
519    let mut server = TestServer::start(deterministic.clone()).await;
520    let client_a = server.create_client(cx_a, "user_a").await;
521    let client_b = server.create_client(cx_b, "user_b").await;
522    let client_c = server.create_client(cx_c, "user_c").await;
523
524    let channel_id = server
525        .make_channel(
526            "the-channel",
527            None,
528            (&client_a, cx_a),
529            &mut [(&client_b, cx_b), (&client_c, cx_c)],
530        )
531        .await;
532
533    let channel_buffer_a = client_a
534        .channel_store()
535        .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
536        .await
537        .unwrap();
538    let channel_buffer_b = client_b
539        .channel_store()
540        .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
541        .await
542        .unwrap();
543    let _channel_buffer_c = client_c
544        .channel_store()
545        .update(cx_c, |store, cx| store.open_channel_buffer(channel_id, cx))
546        .await
547        .unwrap();
548
549    channel_buffer_a.update(cx_a, |buffer, cx| {
550        buffer.buffer().update(cx, |buffer, cx| {
551            buffer.edit([(0..0, "1")], None, cx);
552        })
553    });
554    deterministic.run_until_parked();
555
556    // Client C can't reconnect.
557    client_c.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
558
559    // Server stops.
560    server.reset().await;
561    deterministic.advance_clock(RECEIVE_TIMEOUT);
562
563    // While the server is down, both clients make an edit.
564    channel_buffer_a.update(cx_a, |buffer, cx| {
565        buffer.buffer().update(cx, |buffer, cx| {
566            buffer.edit([(1..1, "2")], None, cx);
567        })
568    });
569    channel_buffer_b.update(cx_b, |buffer, cx| {
570        buffer.buffer().update(cx, |buffer, cx| {
571            buffer.edit([(0..0, "0")], None, cx);
572        })
573    });
574
575    // Server restarts.
576    server.start().await.unwrap();
577    deterministic.advance_clock(CLEANUP_TIMEOUT);
578
579    // Clients reconnects. Clients A and B see each other's edits, and see
580    // that client C has disconnected.
581    channel_buffer_a.read_with(cx_a, |buffer, cx| {
582        assert_eq!(buffer.buffer().read(cx).text(), "012");
583    });
584    channel_buffer_b.read_with(cx_b, |buffer, cx| {
585        assert_eq!(buffer.buffer().read(cx).text(), "012");
586    });
587
588    channel_buffer_a.read_with(cx_a, |buffer_a, _| {
589        channel_buffer_b.read_with(cx_b, |buffer_b, _| {
590            assert_collaborators(
591                buffer_a.collaborators(),
592                &[client_a.user_id(), client_b.user_id()],
593            );
594            assert_eq!(buffer_a.collaborators(), buffer_b.collaborators());
595        });
596    });
597}
598
599//todo!(collab_ui)
600// #[gpui::test(iterations = 10)]
601// async fn test_following_to_channel_notes_without_a_shared_project(
602//     deterministic: BackgroundExecutor,
603//     mut cx_a: &mut TestAppContext,
604//     mut cx_b: &mut TestAppContext,
605//     mut cx_c: &mut TestAppContext,
606// ) {
607//     let mut server = TestServer::start(&deterministic).await;
608//     let client_a = server.create_client(cx_a, "user_a").await;
609//     let client_b = server.create_client(cx_b, "user_b").await;
610
611//     let client_c = server.create_client(cx_c, "user_c").await;
612
613//     cx_a.update(editor::init);
614//     cx_b.update(editor::init);
615//     cx_c.update(editor::init);
616//     cx_a.update(collab_ui::channel_view::init);
617//     cx_b.update(collab_ui::channel_view::init);
618//     cx_c.update(collab_ui::channel_view::init);
619
620//     let channel_1_id = server
621//         .make_channel(
622//             "channel-1",
623//             None,
624//             (&client_a, cx_a),
625//             &mut [(&client_b, cx_b), (&client_c, cx_c)],
626//         )
627//         .await;
628//     let channel_2_id = server
629//         .make_channel(
630//             "channel-2",
631//             None,
632//             (&client_a, cx_a),
633//             &mut [(&client_b, cx_b), (&client_c, cx_c)],
634//         )
635//         .await;
636
637//     // Clients A, B, and C join a channel.
638//     let active_call_a = cx_a.read(ActiveCall::global);
639//     let active_call_b = cx_b.read(ActiveCall::global);
640//     let active_call_c = cx_c.read(ActiveCall::global);
641//     for (call, cx) in [
642//         (&active_call_a, &mut cx_a),
643//         (&active_call_b, &mut cx_b),
644//         (&active_call_c, &mut cx_c),
645//     ] {
646//         call.update(*cx, |call, cx| call.join_channel(channel_1_id, cx))
647//             .await
648//             .unwrap();
649//     }
650//     deterministic.run_until_parked();
651
652//     // Clients A, B, and C all open their own unshared projects.
653//     client_a.fs().insert_tree("/a", json!({})).await;
654//     client_b.fs().insert_tree("/b", json!({})).await;
655//     client_c.fs().insert_tree("/c", json!({})).await;
656//     let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
657//     let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
658//     let (project_c, _) = client_b.build_local_project("/c", cx_c).await;
659//     let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
660//     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
661//     let _workspace_c = client_c.build_workspace(&project_c, cx_c).root(cx_c);
662
663//     active_call_a
664//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
665//         .await
666//         .unwrap();
667
668//     // Client A opens the notes for channel 1.
669//     let channel_view_1_a = cx_a
670//         .update(|cx| ChannelView::open(channel_1_id, workspace_a.clone(), cx))
671//         .await
672//         .unwrap();
673//     channel_view_1_a.update(cx_a, |notes, cx| {
674//         assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
675//         notes.editor.update(cx, |editor, cx| {
676//             editor.insert("Hello from A.", cx);
677//             editor.change_selections(None, cx, |selections| {
678//                 selections.select_ranges(vec![3..4]);
679//             });
680//         });
681//     });
682
683//     // Client B follows client A.
684//     workspace_b
685//         .update(cx_b, |workspace, cx| {
686//             workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
687//         })
688//         .await
689//         .unwrap();
690
691//     // Client B is taken to the notes for channel 1, with the same
692//     // text selected as client A.
693//     deterministic.run_until_parked();
694//     let channel_view_1_b = workspace_b.read_with(cx_b, |workspace, cx| {
695//         assert_eq!(
696//             workspace.leader_for_pane(workspace.active_pane()),
697//             Some(client_a.peer_id().unwrap())
698//         );
699//         workspace
700//             .active_item(cx)
701//             .expect("no active item")
702//             .downcast::<ChannelView>()
703//             .expect("active item is not a channel view")
704//     });
705//     channel_view_1_b.read_with(cx_b, |notes, cx| {
706//         assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
707//         let editor = notes.editor.read(cx);
708//         assert_eq!(editor.text(cx), "Hello from A.");
709//         assert_eq!(editor.selections.ranges::<usize>(cx), &[3..4]);
710//     });
711
712//     // Client A opens the notes for channel 2.
713//     let channel_view_2_a = cx_a
714//         .update(|cx| ChannelView::open(channel_2_id, workspace_a.clone(), cx))
715//         .await
716//         .unwrap();
717//     channel_view_2_a.read_with(cx_a, |notes, cx| {
718//         assert_eq!(notes.channel(cx).unwrap().name, "channel-2");
719//     });
720
721//     // Client B is taken to the notes for channel 2.
722//     deterministic.run_until_parked();
723//     let channel_view_2_b = workspace_b.read_with(cx_b, |workspace, cx| {
724//         assert_eq!(
725//             workspace.leader_for_pane(workspace.active_pane()),
726//             Some(client_a.peer_id().unwrap())
727//         );
728//         workspace
729//             .active_item(cx)
730//             .expect("no active item")
731//             .downcast::<ChannelView>()
732//             .expect("active item is not a channel view")
733//     });
734//     channel_view_2_b.read_with(cx_b, |notes, cx| {
735//         assert_eq!(notes.channel(cx).unwrap().name, "channel-2");
736//     });
737// }
738
739//todo!(collab_ui)
740// #[gpui::test]
741// async fn test_channel_buffer_changes(
742//     deterministic: BackgroundExecutor,
743//     cx_a: &mut TestAppContext,
744//     cx_b: &mut TestAppContext,
745// ) {
746//     let mut server = TestServer::start(&deterministic).await;
747//     let client_a = server.create_client(cx_a, "user_a").await;
748//     let client_b = server.create_client(cx_b, "user_b").await;
749
750//     let channel_id = server
751//         .make_channel(
752//             "the-channel",
753//             None,
754//             (&client_a, cx_a),
755//             &mut [(&client_b, cx_b)],
756//         )
757//         .await;
758
759//     let channel_buffer_a = client_a
760//         .channel_store()
761//         .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
762//         .await
763//         .unwrap();
764
765//     // Client A makes an edit, and client B should see that the note has changed.
766//     channel_buffer_a.update(cx_a, |buffer, cx| {
767//         buffer.buffer().update(cx, |buffer, cx| {
768//             buffer.edit([(0..0, "1")], None, cx);
769//         })
770//     });
771//     deterministic.run_until_parked();
772
773//     let has_buffer_changed = cx_b.update(|cx| {
774//         client_b
775//             .channel_store()
776//             .read(cx)
777//             .has_channel_buffer_changed(channel_id)
778//             .unwrap()
779//     });
780//     assert!(has_buffer_changed);
781
782//     // Opening the buffer should clear the changed flag.
783//     let project_b = client_b.build_empty_local_project(cx_b);
784//     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
785//     let channel_view_b = cx_b
786//         .update(|cx| ChannelView::open(channel_id, workspace_b.clone(), cx))
787//         .await
788//         .unwrap();
789//     deterministic.run_until_parked();
790
791//     let has_buffer_changed = cx_b.update(|cx| {
792//         client_b
793//             .channel_store()
794//             .read(cx)
795//             .has_channel_buffer_changed(channel_id)
796//             .unwrap()
797//     });
798//     assert!(!has_buffer_changed);
799
800//     // Editing the channel while the buffer is open should not show that the buffer has changed.
801//     channel_buffer_a.update(cx_a, |buffer, cx| {
802//         buffer.buffer().update(cx, |buffer, cx| {
803//             buffer.edit([(0..0, "2")], None, cx);
804//         })
805//     });
806//     deterministic.run_until_parked();
807
808//     let has_buffer_changed = cx_b.read(|cx| {
809//         client_b
810//             .channel_store()
811//             .read(cx)
812//             .has_channel_buffer_changed(channel_id)
813//             .unwrap()
814//     });
815//     assert!(!has_buffer_changed);
816
817//     deterministic.advance_clock(ACKNOWLEDGE_DEBOUNCE_INTERVAL);
818
819//     // Test that the server is tracking things correctly, and we retain our 'not changed'
820//     // state across a disconnect
821//     server.simulate_long_connection_interruption(client_b.peer_id().unwrap(), &deterministic);
822//     let has_buffer_changed = cx_b.read(|cx| {
823//         client_b
824//             .channel_store()
825//             .read(cx)
826//             .has_channel_buffer_changed(channel_id)
827//             .unwrap()
828//     });
829//     assert!(!has_buffer_changed);
830
831//     // Closing the buffer should re-enable change tracking
832//     cx_b.update(|cx| {
833//         workspace_b.update(cx, |workspace, cx| {
834//             workspace.close_all_items_and_panes(&Default::default(), cx)
835//         });
836
837//         drop(channel_view_b)
838//     });
839
840//     deterministic.run_until_parked();
841
842//     channel_buffer_a.update(cx_a, |buffer, cx| {
843//         buffer.buffer().update(cx, |buffer, cx| {
844//             buffer.edit([(0..0, "3")], None, cx);
845//         })
846//     });
847//     deterministic.run_until_parked();
848
849//     let has_buffer_changed = cx_b.read(|cx| {
850//         client_b
851//             .channel_store()
852//             .read(cx)
853//             .has_channel_buffer_changed(channel_id)
854//             .unwrap()
855//     });
856//     assert!(has_buffer_changed);
857// }
858
859#[track_caller]
860fn assert_collaborators(collaborators: &HashMap<PeerId, Collaborator>, ids: &[Option<UserId>]) {
861    let mut user_ids = collaborators
862        .values()
863        .map(|collaborator| collaborator.user_id)
864        .collect::<Vec<_>>();
865    user_ids.sort();
866    assert_eq!(
867        user_ids,
868        ids.into_iter().map(|id| id.unwrap()).collect::<Vec<_>>()
869    );
870}
871
872fn buffer_text(channel_buffer: &Model<language::Buffer>, cx: &mut TestAppContext) -> String {
873    channel_buffer.read_with(cx, |buffer, _| buffer.text())
874}