1use crate::{
2 rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
3 tests::TestServer,
4};
5use call::ActiveCall;
6use channel::Channel;
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, _| {
411 assert_eq!(
412 buffer.channel().as_ref(),
413 &channel(channel_id, "the-channel")
414 );
415 assert!(!buffer.is_connected());
416 });
417
418 deterministic.run_until_parked();
419
420 server.allow_connections();
421 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
422
423 deterministic.run_until_parked();
424
425 client_a
426 .channel_store()
427 .update(cx_a, |channel_store, _| {
428 channel_store.remove_channel(channel_id)
429 })
430 .await
431 .unwrap();
432 deterministic.run_until_parked();
433
434 // Channel buffer observed the deletion
435 channel_buffer_b.update(cx_b, |buffer, _| {
436 assert_eq!(
437 buffer.channel().as_ref(),
438 &channel(channel_id, "the-channel")
439 );
440 assert!(!buffer.is_connected());
441 });
442}
443
444fn channel(id: u64, name: &'static str) -> Channel {
445 Channel {
446 id,
447 name: name.to_string(),
448 has_note_changed: false,
449 }
450}
451
452#[gpui::test]
453async fn test_rejoin_channel_buffer(
454 deterministic: Arc<Deterministic>,
455 cx_a: &mut TestAppContext,
456 cx_b: &mut TestAppContext,
457) {
458 deterministic.forbid_parking();
459 let mut server = TestServer::start(&deterministic).await;
460 let client_a = server.create_client(cx_a, "user_a").await;
461 let client_b = server.create_client(cx_b, "user_b").await;
462
463 let channel_id = server
464 .make_channel(
465 "the-channel",
466 None,
467 (&client_a, cx_a),
468 &mut [(&client_b, cx_b)],
469 )
470 .await;
471
472 let channel_buffer_a = client_a
473 .channel_store()
474 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
475 .await
476 .unwrap();
477 let channel_buffer_b = client_b
478 .channel_store()
479 .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
480 .await
481 .unwrap();
482
483 channel_buffer_a.update(cx_a, |buffer, cx| {
484 buffer.buffer().update(cx, |buffer, cx| {
485 buffer.edit([(0..0, "1")], None, cx);
486 })
487 });
488 deterministic.run_until_parked();
489
490 // Client A disconnects.
491 server.forbid_connections();
492 server.disconnect_client(client_a.peer_id().unwrap());
493
494 // Both clients make an edit.
495 channel_buffer_a.update(cx_a, |buffer, cx| {
496 buffer.buffer().update(cx, |buffer, cx| {
497 buffer.edit([(1..1, "2")], None, cx);
498 })
499 });
500 channel_buffer_b.update(cx_b, |buffer, cx| {
501 buffer.buffer().update(cx, |buffer, cx| {
502 buffer.edit([(0..0, "0")], None, cx);
503 })
504 });
505
506 // Both clients see their own edit.
507 deterministic.run_until_parked();
508 channel_buffer_a.read_with(cx_a, |buffer, cx| {
509 assert_eq!(buffer.buffer().read(cx).text(), "12");
510 });
511 channel_buffer_b.read_with(cx_b, |buffer, cx| {
512 assert_eq!(buffer.buffer().read(cx).text(), "01");
513 });
514
515 // Client A reconnects. Both clients see each other's edits, and see
516 // the same collaborators.
517 server.allow_connections();
518 deterministic.advance_clock(RECEIVE_TIMEOUT);
519 channel_buffer_a.read_with(cx_a, |buffer, cx| {
520 assert_eq!(buffer.buffer().read(cx).text(), "012");
521 });
522 channel_buffer_b.read_with(cx_b, |buffer, cx| {
523 assert_eq!(buffer.buffer().read(cx).text(), "012");
524 });
525
526 channel_buffer_a.read_with(cx_a, |buffer_a, _| {
527 channel_buffer_b.read_with(cx_b, |buffer_b, _| {
528 assert_eq!(buffer_a.collaborators(), buffer_b.collaborators());
529 });
530 });
531}
532
533#[gpui::test]
534async fn test_channel_buffers_and_server_restarts(
535 deterministic: Arc<Deterministic>,
536 cx_a: &mut TestAppContext,
537 cx_b: &mut TestAppContext,
538 cx_c: &mut TestAppContext,
539) {
540 deterministic.forbid_parking();
541 let mut server = TestServer::start(&deterministic).await;
542 let client_a = server.create_client(cx_a, "user_a").await;
543 let client_b = server.create_client(cx_b, "user_b").await;
544 let client_c = server.create_client(cx_c, "user_c").await;
545
546 let channel_id = server
547 .make_channel(
548 "the-channel",
549 None,
550 (&client_a, cx_a),
551 &mut [(&client_b, cx_b), (&client_c, cx_c)],
552 )
553 .await;
554
555 let channel_buffer_a = client_a
556 .channel_store()
557 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
558 .await
559 .unwrap();
560 let channel_buffer_b = client_b
561 .channel_store()
562 .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
563 .await
564 .unwrap();
565 let _channel_buffer_c = client_c
566 .channel_store()
567 .update(cx_c, |store, cx| store.open_channel_buffer(channel_id, cx))
568 .await
569 .unwrap();
570
571 channel_buffer_a.update(cx_a, |buffer, cx| {
572 buffer.buffer().update(cx, |buffer, cx| {
573 buffer.edit([(0..0, "1")], None, cx);
574 })
575 });
576 deterministic.run_until_parked();
577
578 // Client C can't reconnect.
579 client_c.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
580
581 // Server stops.
582 server.reset().await;
583 deterministic.advance_clock(RECEIVE_TIMEOUT);
584
585 // While the server is down, both clients make an edit.
586 channel_buffer_a.update(cx_a, |buffer, cx| {
587 buffer.buffer().update(cx, |buffer, cx| {
588 buffer.edit([(1..1, "2")], None, cx);
589 })
590 });
591 channel_buffer_b.update(cx_b, |buffer, cx| {
592 buffer.buffer().update(cx, |buffer, cx| {
593 buffer.edit([(0..0, "0")], None, cx);
594 })
595 });
596
597 // Server restarts.
598 server.start().await.unwrap();
599 deterministic.advance_clock(CLEANUP_TIMEOUT);
600
601 // Clients reconnects. Clients A and B see each other's edits, and see
602 // that client C has disconnected.
603 channel_buffer_a.read_with(cx_a, |buffer, cx| {
604 assert_eq!(buffer.buffer().read(cx).text(), "012");
605 });
606 channel_buffer_b.read_with(cx_b, |buffer, cx| {
607 assert_eq!(buffer.buffer().read(cx).text(), "012");
608 });
609
610 channel_buffer_a.read_with(cx_a, |buffer_a, _| {
611 channel_buffer_b.read_with(cx_b, |buffer_b, _| {
612 assert_collaborators(
613 buffer_a.collaborators(),
614 &[client_a.user_id(), client_b.user_id()],
615 );
616 assert_eq!(buffer_a.collaborators(), buffer_b.collaborators());
617 });
618 });
619}
620
621#[gpui::test(iterations = 10)]
622async fn test_following_to_channel_notes_without_a_shared_project(
623 deterministic: Arc<Deterministic>,
624 mut cx_a: &mut TestAppContext,
625 mut cx_b: &mut TestAppContext,
626 mut cx_c: &mut TestAppContext,
627) {
628 deterministic.forbid_parking();
629 let mut server = TestServer::start(&deterministic).await;
630 let client_a = server.create_client(cx_a, "user_a").await;
631 let client_b = server.create_client(cx_b, "user_b").await;
632
633 let client_c = server.create_client(cx_c, "user_c").await;
634
635 cx_a.update(editor::init);
636 cx_b.update(editor::init);
637 cx_c.update(editor::init);
638 cx_a.update(collab_ui::channel_view::init);
639 cx_b.update(collab_ui::channel_view::init);
640 cx_c.update(collab_ui::channel_view::init);
641
642 let channel_1_id = server
643 .make_channel(
644 "channel-1",
645 None,
646 (&client_a, cx_a),
647 &mut [(&client_b, cx_b), (&client_c, cx_c)],
648 )
649 .await;
650 let channel_2_id = server
651 .make_channel(
652 "channel-2",
653 None,
654 (&client_a, cx_a),
655 &mut [(&client_b, cx_b), (&client_c, cx_c)],
656 )
657 .await;
658
659 // Clients A, B, and C join a channel.
660 let active_call_a = cx_a.read(ActiveCall::global);
661 let active_call_b = cx_b.read(ActiveCall::global);
662 let active_call_c = cx_c.read(ActiveCall::global);
663 for (call, cx) in [
664 (&active_call_a, &mut cx_a),
665 (&active_call_b, &mut cx_b),
666 (&active_call_c, &mut cx_c),
667 ] {
668 call.update(*cx, |call, cx| call.join_channel(channel_1_id, cx))
669 .await
670 .unwrap();
671 }
672 deterministic.run_until_parked();
673
674 // Clients A, B, and C all open their own unshared projects.
675 client_a.fs().insert_tree("/a", json!({})).await;
676 client_b.fs().insert_tree("/b", json!({})).await;
677 client_c.fs().insert_tree("/c", json!({})).await;
678 let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
679 let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
680 let (project_c, _) = client_b.build_local_project("/c", cx_c).await;
681 let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
682 let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
683 let _workspace_c = client_c.build_workspace(&project_c, cx_c).root(cx_c);
684
685 active_call_a
686 .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
687 .await
688 .unwrap();
689
690 // Client A opens the notes for channel 1.
691 let channel_view_1_a = cx_a
692 .update(|cx| ChannelView::open(channel_1_id, workspace_a.clone(), cx))
693 .await
694 .unwrap();
695 channel_view_1_a.update(cx_a, |notes, cx| {
696 assert_eq!(notes.channel(cx).name, "channel-1");
697 notes.editor.update(cx, |editor, cx| {
698 editor.insert("Hello from A.", cx);
699 editor.change_selections(None, cx, |selections| {
700 selections.select_ranges(vec![3..4]);
701 });
702 });
703 });
704
705 // Client B follows client A.
706 workspace_b
707 .update(cx_b, |workspace, cx| {
708 workspace
709 .toggle_follow(client_a.peer_id().unwrap(), cx)
710 .unwrap()
711 })
712 .await
713 .unwrap();
714
715 // Client B is taken to the notes for channel 1, with the same
716 // text selected as client A.
717 deterministic.run_until_parked();
718 let channel_view_1_b = workspace_b.read_with(cx_b, |workspace, cx| {
719 assert_eq!(
720 workspace.leader_for_pane(workspace.active_pane()),
721 Some(client_a.peer_id().unwrap())
722 );
723 workspace
724 .active_item(cx)
725 .expect("no active item")
726 .downcast::<ChannelView>()
727 .expect("active item is not a channel view")
728 });
729 channel_view_1_b.read_with(cx_b, |notes, cx| {
730 assert_eq!(notes.channel(cx).name, "channel-1");
731 let editor = notes.editor.read(cx);
732 assert_eq!(editor.text(cx), "Hello from A.");
733 assert_eq!(editor.selections.ranges::<usize>(cx), &[3..4]);
734 });
735
736 // Client A opens the notes for channel 2.
737 let channel_view_2_a = cx_a
738 .update(|cx| ChannelView::open(channel_2_id, workspace_a.clone(), cx))
739 .await
740 .unwrap();
741 channel_view_2_a.read_with(cx_a, |notes, cx| {
742 assert_eq!(notes.channel(cx).name, "channel-2");
743 });
744
745 // Client B is taken to the notes for channel 2.
746 deterministic.run_until_parked();
747 let channel_view_2_b = workspace_b.read_with(cx_b, |workspace, cx| {
748 assert_eq!(
749 workspace.leader_for_pane(workspace.active_pane()),
750 Some(client_a.peer_id().unwrap())
751 );
752 workspace
753 .active_item(cx)
754 .expect("no active item")
755 .downcast::<ChannelView>()
756 .expect("active item is not a channel view")
757 });
758 channel_view_2_b.read_with(cx_b, |notes, cx| {
759 assert_eq!(notes.channel(cx).name, "channel-2");
760 });
761}
762
763#[gpui::test]
764async fn test_channel_buffer_changes(
765 deterministic: Arc<Deterministic>,
766 cx_a: &mut TestAppContext,
767 cx_b: &mut TestAppContext,
768) {
769 deterministic.forbid_parking();
770 let mut server = TestServer::start(&deterministic).await;
771 let client_a = server.create_client(cx_a, "user_a").await;
772 let client_b = server.create_client(cx_b, "user_b").await;
773
774 let channel_id = server
775 .make_channel(
776 "the-channel",
777 None,
778 (&client_a, cx_a),
779 &mut [(&client_b, cx_b)],
780 )
781 .await;
782
783 let channel_buffer_a = client_a
784 .channel_store()
785 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
786 .await
787 .unwrap();
788
789 // Client A makes an edit, and client B should see that the note has changed.
790 channel_buffer_a.update(cx_a, |buffer, cx| {
791 buffer.buffer().update(cx, |buffer, cx| {
792 buffer.edit([(0..0, "1")], None, cx);
793 })
794 });
795 deterministic.run_until_parked();
796
797 let has_buffer_changed = cx_b.read(|cx| {
798 client_b
799 .channel_store()
800 .read(cx)
801 .has_channel_buffer_changed(channel_id)
802 .unwrap()
803 });
804
805 assert!(has_buffer_changed);
806
807 // Opening the buffer should clear the changed flag.
808 let channel_buffer_b = client_b
809 .channel_store()
810 .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
811 .await
812 .unwrap();
813 deterministic.run_until_parked();
814
815 let has_buffer_changed = cx_b.read(|cx| {
816 client_b
817 .channel_store()
818 .read(cx)
819 .has_channel_buffer_changed(channel_id)
820 .unwrap()
821 });
822
823 assert!(!has_buffer_changed);
824
825 // Editing the channel while the buffer is open shuold 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
841 assert!(!has_buffer_changed);
842
843 // Closing the buffer should re-enable change tracking
844 cx_b.update(|_| {
845 drop(channel_buffer_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
865 assert!(has_buffer_changed);
866}
867
868#[track_caller]
869fn assert_collaborators(collaborators: &HashMap<PeerId, Collaborator>, ids: &[Option<UserId>]) {
870 let mut user_ids = collaborators
871 .values()
872 .map(|collaborator| collaborator.user_id)
873 .collect::<Vec<_>>();
874 user_ids.sort();
875 assert_eq!(
876 user_ids,
877 ids.into_iter().map(|id| id.unwrap()).collect::<Vec<_>>()
878 );
879}
880
881fn buffer_text(channel_buffer: &ModelHandle<language::Buffer>, cx: &mut TestAppContext) -> String {
882 channel_buffer.read_with(cx, |buffer, _| buffer.text())
883}