channel_buffer_tests.rs

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