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