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