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