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//todo!(editor)
286// #[track_caller]
287// fn assert_remote_selections(
288// editor: &mut Editor,
289// expected_selections: &[(Option<ParticipantIndex>, Range<usize>)],
290// cx: &mut ViewContext<Editor>,
291// ) {
292// let snapshot = editor.snapshot(cx);
293// let range = Anchor::min()..Anchor::max();
294// let remote_selections = snapshot
295// .remote_selections_in_range(&range, editor.collaboration_hub().unwrap(), cx)
296// .map(|s| {
297// let start = s.selection.start.to_offset(&snapshot.buffer_snapshot);
298// let end = s.selection.end.to_offset(&snapshot.buffer_snapshot);
299// (s.participant_index, start..end)
300// })
301// .collect::<Vec<_>>();
302// assert_eq!(
303// remote_selections, expected_selections,
304// "incorrect remote selections"
305// );
306// }
307
308#[gpui::test]
309async fn test_multiple_handles_to_channel_buffer(
310 deterministic: BackgroundExecutor,
311 cx_a: &mut TestAppContext,
312) {
313 let mut server = TestServer::start(deterministic.clone()).await;
314 let client_a = server.create_client(cx_a, "user_a").await;
315
316 let channel_id = server
317 .make_channel("the-channel", None, (&client_a, cx_a), &mut [])
318 .await;
319
320 let channel_buffer_1 = client_a
321 .channel_store()
322 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
323 let channel_buffer_2 = client_a
324 .channel_store()
325 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
326 let channel_buffer_3 = client_a
327 .channel_store()
328 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
329
330 // All concurrent tasks for opening a channel buffer return the same model handle.
331 let (channel_buffer, channel_buffer_2, channel_buffer_3) =
332 future::try_join3(channel_buffer_1, channel_buffer_2, channel_buffer_3)
333 .await
334 .unwrap();
335 let channel_buffer_model_id = channel_buffer.entity_id();
336 assert_eq!(channel_buffer, channel_buffer_2);
337 assert_eq!(channel_buffer, channel_buffer_3);
338
339 channel_buffer.update(cx_a, |buffer, cx| {
340 buffer.buffer().update(cx, |buffer, cx| {
341 buffer.edit([(0..0, "hello")], None, cx);
342 })
343 });
344 deterministic.run_until_parked();
345
346 cx_a.update(|_| {
347 drop(channel_buffer);
348 drop(channel_buffer_2);
349 drop(channel_buffer_3);
350 });
351 deterministic.run_until_parked();
352
353 // The channel buffer can be reopened after dropping it.
354 let channel_buffer = client_a
355 .channel_store()
356 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
357 .await
358 .unwrap();
359 assert_ne!(channel_buffer.entity_id(), channel_buffer_model_id);
360 channel_buffer.update(cx_a, |buffer, cx| {
361 buffer.buffer().update(cx, |buffer, _| {
362 assert_eq!(buffer.text(), "hello");
363 })
364 });
365}
366
367#[gpui::test]
368async fn test_channel_buffer_disconnect(
369 deterministic: BackgroundExecutor,
370 cx_a: &mut TestAppContext,
371 cx_b: &mut TestAppContext,
372) {
373 let mut server = TestServer::start(deterministic.clone()).await;
374 let client_a = server.create_client(cx_a, "user_a").await;
375 let client_b = server.create_client(cx_b, "user_b").await;
376
377 let channel_id = server
378 .make_channel(
379 "the-channel",
380 None,
381 (&client_a, cx_a),
382 &mut [(&client_b, cx_b)],
383 )
384 .await;
385
386 let channel_buffer_a = client_a
387 .channel_store()
388 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
389 .await
390 .unwrap();
391
392 let channel_buffer_b = client_b
393 .channel_store()
394 .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
395 .await
396 .unwrap();
397
398 server.forbid_connections();
399 server.disconnect_client(client_a.peer_id().unwrap());
400 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
401
402 channel_buffer_a.update(cx_a, |buffer, cx| {
403 assert_eq!(buffer.channel(cx).unwrap().name, "the-channel");
404 assert!(!buffer.is_connected());
405 });
406
407 deterministic.run_until_parked();
408
409 server.allow_connections();
410 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
411
412 deterministic.run_until_parked();
413
414 client_a
415 .channel_store()
416 .update(cx_a, |channel_store, _| {
417 channel_store.remove_channel(channel_id)
418 })
419 .await
420 .unwrap();
421 deterministic.run_until_parked();
422
423 // Channel buffer observed the deletion
424 channel_buffer_b.update(cx_b, |buffer, cx| {
425 assert!(buffer.channel(cx).is_none());
426 assert!(!buffer.is_connected());
427 });
428}
429
430#[gpui::test]
431async fn test_rejoin_channel_buffer(
432 deterministic: BackgroundExecutor,
433 cx_a: &mut TestAppContext,
434 cx_b: &mut TestAppContext,
435) {
436 let mut server = TestServer::start(deterministic.clone()).await;
437 let client_a = server.create_client(cx_a, "user_a").await;
438 let client_b = server.create_client(cx_b, "user_b").await;
439
440 let channel_id = server
441 .make_channel(
442 "the-channel",
443 None,
444 (&client_a, cx_a),
445 &mut [(&client_b, cx_b)],
446 )
447 .await;
448
449 let channel_buffer_a = client_a
450 .channel_store()
451 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
452 .await
453 .unwrap();
454 let channel_buffer_b = client_b
455 .channel_store()
456 .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
457 .await
458 .unwrap();
459
460 channel_buffer_a.update(cx_a, |buffer, cx| {
461 buffer.buffer().update(cx, |buffer, cx| {
462 buffer.edit([(0..0, "1")], None, cx);
463 })
464 });
465 deterministic.run_until_parked();
466
467 // Client A disconnects.
468 server.forbid_connections();
469 server.disconnect_client(client_a.peer_id().unwrap());
470
471 // Both clients make an edit.
472 channel_buffer_a.update(cx_a, |buffer, cx| {
473 buffer.buffer().update(cx, |buffer, cx| {
474 buffer.edit([(1..1, "2")], None, cx);
475 })
476 });
477 channel_buffer_b.update(cx_b, |buffer, cx| {
478 buffer.buffer().update(cx, |buffer, cx| {
479 buffer.edit([(0..0, "0")], None, cx);
480 })
481 });
482
483 // Both clients see their own edit.
484 deterministic.run_until_parked();
485 channel_buffer_a.read_with(cx_a, |buffer, cx| {
486 assert_eq!(buffer.buffer().read(cx).text(), "12");
487 });
488 channel_buffer_b.read_with(cx_b, |buffer, cx| {
489 assert_eq!(buffer.buffer().read(cx).text(), "01");
490 });
491
492 // Client A reconnects. Both clients see each other's edits, and see
493 // the same collaborators.
494 server.allow_connections();
495 deterministic.advance_clock(RECEIVE_TIMEOUT);
496 channel_buffer_a.read_with(cx_a, |buffer, cx| {
497 assert_eq!(buffer.buffer().read(cx).text(), "012");
498 });
499 channel_buffer_b.read_with(cx_b, |buffer, cx| {
500 assert_eq!(buffer.buffer().read(cx).text(), "012");
501 });
502
503 channel_buffer_a.read_with(cx_a, |buffer_a, _| {
504 channel_buffer_b.read_with(cx_b, |buffer_b, _| {
505 assert_eq!(buffer_a.collaborators(), buffer_b.collaborators());
506 });
507 });
508}
509
510#[gpui::test]
511async fn test_channel_buffers_and_server_restarts(
512 deterministic: BackgroundExecutor,
513 cx_a: &mut TestAppContext,
514 cx_b: &mut TestAppContext,
515 cx_c: &mut TestAppContext,
516) {
517 let mut server = TestServer::start(deterministic.clone()).await;
518 let client_a = server.create_client(cx_a, "user_a").await;
519 let client_b = server.create_client(cx_b, "user_b").await;
520 let client_c = server.create_client(cx_c, "user_c").await;
521
522 let channel_id = server
523 .make_channel(
524 "the-channel",
525 None,
526 (&client_a, cx_a),
527 &mut [(&client_b, cx_b), (&client_c, cx_c)],
528 )
529 .await;
530
531 let channel_buffer_a = client_a
532 .channel_store()
533 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
534 .await
535 .unwrap();
536 let channel_buffer_b = client_b
537 .channel_store()
538 .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
539 .await
540 .unwrap();
541 let _channel_buffer_c = client_c
542 .channel_store()
543 .update(cx_c, |store, cx| store.open_channel_buffer(channel_id, cx))
544 .await
545 .unwrap();
546
547 channel_buffer_a.update(cx_a, |buffer, cx| {
548 buffer.buffer().update(cx, |buffer, cx| {
549 buffer.edit([(0..0, "1")], None, cx);
550 })
551 });
552 deterministic.run_until_parked();
553
554 // Client C can't reconnect.
555 client_c.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
556
557 // Server stops.
558 server.reset().await;
559 deterministic.advance_clock(RECEIVE_TIMEOUT);
560
561 // While the server is down, both clients make an edit.
562 channel_buffer_a.update(cx_a, |buffer, cx| {
563 buffer.buffer().update(cx, |buffer, cx| {
564 buffer.edit([(1..1, "2")], None, cx);
565 })
566 });
567 channel_buffer_b.update(cx_b, |buffer, cx| {
568 buffer.buffer().update(cx, |buffer, cx| {
569 buffer.edit([(0..0, "0")], None, cx);
570 })
571 });
572
573 // Server restarts.
574 server.start().await.unwrap();
575 deterministic.advance_clock(CLEANUP_TIMEOUT);
576
577 // Clients reconnects. Clients A and B see each other's edits, and see
578 // that client C has disconnected.
579 channel_buffer_a.read_with(cx_a, |buffer, cx| {
580 assert_eq!(buffer.buffer().read(cx).text(), "012");
581 });
582 channel_buffer_b.read_with(cx_b, |buffer, cx| {
583 assert_eq!(buffer.buffer().read(cx).text(), "012");
584 });
585
586 channel_buffer_a.read_with(cx_a, |buffer_a, _| {
587 channel_buffer_b.read_with(cx_b, |buffer_b, _| {
588 assert_collaborators(
589 buffer_a.collaborators(),
590 &[client_a.user_id(), client_b.user_id()],
591 );
592 assert_eq!(buffer_a.collaborators(), buffer_b.collaborators());
593 });
594 });
595}
596
597//todo!(collab_ui)
598// #[gpui::test(iterations = 10)]
599// async fn test_following_to_channel_notes_without_a_shared_project(
600// deterministic: BackgroundExecutor,
601// mut cx_a: &mut TestAppContext,
602// mut cx_b: &mut TestAppContext,
603// mut cx_c: &mut TestAppContext,
604// ) {
605// let mut server = TestServer::start(&deterministic).await;
606// let client_a = server.create_client(cx_a, "user_a").await;
607// let client_b = server.create_client(cx_b, "user_b").await;
608
609// let client_c = server.create_client(cx_c, "user_c").await;
610
611// cx_a.update(editor::init);
612// cx_b.update(editor::init);
613// cx_c.update(editor::init);
614// cx_a.update(collab_ui::channel_view::init);
615// cx_b.update(collab_ui::channel_view::init);
616// cx_c.update(collab_ui::channel_view::init);
617
618// let channel_1_id = server
619// .make_channel(
620// "channel-1",
621// None,
622// (&client_a, cx_a),
623// &mut [(&client_b, cx_b), (&client_c, cx_c)],
624// )
625// .await;
626// let channel_2_id = server
627// .make_channel(
628// "channel-2",
629// None,
630// (&client_a, cx_a),
631// &mut [(&client_b, cx_b), (&client_c, cx_c)],
632// )
633// .await;
634
635// // Clients A, B, and C join a channel.
636// let active_call_a = cx_a.read(ActiveCall::global);
637// let active_call_b = cx_b.read(ActiveCall::global);
638// let active_call_c = cx_c.read(ActiveCall::global);
639// for (call, cx) in [
640// (&active_call_a, &mut cx_a),
641// (&active_call_b, &mut cx_b),
642// (&active_call_c, &mut cx_c),
643// ] {
644// call.update(*cx, |call, cx| call.join_channel(channel_1_id, cx))
645// .await
646// .unwrap();
647// }
648// deterministic.run_until_parked();
649
650// // Clients A, B, and C all open their own unshared projects.
651// client_a.fs().insert_tree("/a", json!({})).await;
652// client_b.fs().insert_tree("/b", json!({})).await;
653// client_c.fs().insert_tree("/c", json!({})).await;
654// let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
655// let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
656// let (project_c, _) = client_b.build_local_project("/c", cx_c).await;
657// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
658// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
659// let _workspace_c = client_c.build_workspace(&project_c, cx_c).root(cx_c);
660
661// active_call_a
662// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
663// .await
664// .unwrap();
665
666// // Client A opens the notes for channel 1.
667// let channel_view_1_a = cx_a
668// .update(|cx| ChannelView::open(channel_1_id, workspace_a.clone(), cx))
669// .await
670// .unwrap();
671// channel_view_1_a.update(cx_a, |notes, cx| {
672// assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
673// notes.editor.update(cx, |editor, cx| {
674// editor.insert("Hello from A.", cx);
675// editor.change_selections(None, cx, |selections| {
676// selections.select_ranges(vec![3..4]);
677// });
678// });
679// });
680
681// // Client B follows client A.
682// workspace_b
683// .update(cx_b, |workspace, cx| {
684// workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
685// })
686// .await
687// .unwrap();
688
689// // Client B is taken to the notes for channel 1, with the same
690// // text selected as client A.
691// deterministic.run_until_parked();
692// let channel_view_1_b = workspace_b.read_with(cx_b, |workspace, cx| {
693// assert_eq!(
694// workspace.leader_for_pane(workspace.active_pane()),
695// Some(client_a.peer_id().unwrap())
696// );
697// workspace
698// .active_item(cx)
699// .expect("no active item")
700// .downcast::<ChannelView>()
701// .expect("active item is not a channel view")
702// });
703// channel_view_1_b.read_with(cx_b, |notes, cx| {
704// assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
705// let editor = notes.editor.read(cx);
706// assert_eq!(editor.text(cx), "Hello from A.");
707// assert_eq!(editor.selections.ranges::<usize>(cx), &[3..4]);
708// });
709
710// // Client A opens the notes for channel 2.
711// let channel_view_2_a = cx_a
712// .update(|cx| ChannelView::open(channel_2_id, workspace_a.clone(), cx))
713// .await
714// .unwrap();
715// channel_view_2_a.read_with(cx_a, |notes, cx| {
716// assert_eq!(notes.channel(cx).unwrap().name, "channel-2");
717// });
718
719// // Client B is taken to the notes for channel 2.
720// deterministic.run_until_parked();
721// let channel_view_2_b = workspace_b.read_with(cx_b, |workspace, cx| {
722// assert_eq!(
723// workspace.leader_for_pane(workspace.active_pane()),
724// Some(client_a.peer_id().unwrap())
725// );
726// workspace
727// .active_item(cx)
728// .expect("no active item")
729// .downcast::<ChannelView>()
730// .expect("active item is not a channel view")
731// });
732// channel_view_2_b.read_with(cx_b, |notes, cx| {
733// assert_eq!(notes.channel(cx).unwrap().name, "channel-2");
734// });
735// }
736
737//todo!(collab_ui)
738// #[gpui::test]
739// async fn test_channel_buffer_changes(
740// deterministic: BackgroundExecutor,
741// cx_a: &mut TestAppContext,
742// cx_b: &mut TestAppContext,
743// ) {
744// let mut server = TestServer::start(&deterministic).await;
745// let client_a = server.create_client(cx_a, "user_a").await;
746// let client_b = server.create_client(cx_b, "user_b").await;
747
748// let channel_id = server
749// .make_channel(
750// "the-channel",
751// None,
752// (&client_a, cx_a),
753// &mut [(&client_b, cx_b)],
754// )
755// .await;
756
757// let channel_buffer_a = client_a
758// .channel_store()
759// .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
760// .await
761// .unwrap();
762
763// // Client A makes an edit, and client B should see that the note has changed.
764// channel_buffer_a.update(cx_a, |buffer, cx| {
765// buffer.buffer().update(cx, |buffer, cx| {
766// buffer.edit([(0..0, "1")], None, cx);
767// })
768// });
769// deterministic.run_until_parked();
770
771// let has_buffer_changed = cx_b.update(|cx| {
772// client_b
773// .channel_store()
774// .read(cx)
775// .has_channel_buffer_changed(channel_id)
776// .unwrap()
777// });
778// assert!(has_buffer_changed);
779
780// // Opening the buffer should clear the changed flag.
781// let project_b = client_b.build_empty_local_project(cx_b);
782// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
783// let channel_view_b = cx_b
784// .update(|cx| ChannelView::open(channel_id, workspace_b.clone(), cx))
785// .await
786// .unwrap();
787// deterministic.run_until_parked();
788
789// let has_buffer_changed = cx_b.update(|cx| {
790// client_b
791// .channel_store()
792// .read(cx)
793// .has_channel_buffer_changed(channel_id)
794// .unwrap()
795// });
796// assert!(!has_buffer_changed);
797
798// // Editing the channel while the buffer is open should not show that the buffer has changed.
799// channel_buffer_a.update(cx_a, |buffer, cx| {
800// buffer.buffer().update(cx, |buffer, cx| {
801// buffer.edit([(0..0, "2")], None, cx);
802// })
803// });
804// deterministic.run_until_parked();
805
806// let has_buffer_changed = cx_b.read(|cx| {
807// client_b
808// .channel_store()
809// .read(cx)
810// .has_channel_buffer_changed(channel_id)
811// .unwrap()
812// });
813// assert!(!has_buffer_changed);
814
815// deterministic.advance_clock(ACKNOWLEDGE_DEBOUNCE_INTERVAL);
816
817// // Test that the server is tracking things correctly, and we retain our 'not changed'
818// // state across a disconnect
819// server.simulate_long_connection_interruption(client_b.peer_id().unwrap(), &deterministic);
820// let has_buffer_changed = cx_b.read(|cx| {
821// client_b
822// .channel_store()
823// .read(cx)
824// .has_channel_buffer_changed(channel_id)
825// .unwrap()
826// });
827// assert!(!has_buffer_changed);
828
829// // Closing the buffer should re-enable change tracking
830// cx_b.update(|cx| {
831// workspace_b.update(cx, |workspace, cx| {
832// workspace.close_all_items_and_panes(&Default::default(), cx)
833// });
834
835// drop(channel_view_b)
836// });
837
838// deterministic.run_until_parked();
839
840// channel_buffer_a.update(cx_a, |buffer, cx| {
841// buffer.buffer().update(cx, |buffer, cx| {
842// buffer.edit([(0..0, "3")], None, cx);
843// })
844// });
845// deterministic.run_until_parked();
846
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
857#[track_caller]
858fn assert_collaborators(collaborators: &HashMap<PeerId, Collaborator>, ids: &[Option<UserId>]) {
859 let mut user_ids = collaborators
860 .values()
861 .map(|collaborator| collaborator.user_id)
862 .collect::<Vec<_>>();
863 user_ids.sort();
864 assert_eq!(
865 user_ids,
866 ids.into_iter().map(|id| id.unwrap()).collect::<Vec<_>>()
867 );
868}
869
870fn buffer_text(channel_buffer: &Model<language::Buffer>, cx: &mut TestAppContext) -> String {
871 channel_buffer.read_with(cx, |buffer, _| buffer.text())
872}