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