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 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}