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