integration_tests.rs

   1use crate::{
   2    RoomParticipants, TestClient, TestServer, channel_id, following_tests::join_channel,
   3    room_participants,
   4};
   5use anyhow::{Result, anyhow};
   6use assistant_slash_command::SlashCommandWorkingSet;
   7use assistant_text_thread::TextThreadStore;
   8use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus, assert_hunks};
   9use call::{ActiveCall, Room, room};
  10use client::{RECEIVE_TIMEOUT, User};
  11use collab::rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT};
  12use collections::{BTreeMap, HashMap, HashSet};
  13use fs::{FakeFs, Fs as _, RemoveOptions};
  14use futures::{StreamExt as _, channel::mpsc};
  15use git::{
  16    repository::repo_path,
  17    status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode},
  18};
  19use gpui::{
  20    App, BackgroundExecutor, Entity, Modifiers, MouseButton, MouseDownEvent, TestAppContext,
  21    UpdateGlobal, px, size,
  22};
  23use language::{
  24    Diagnostic, DiagnosticEntry, DiagnosticSourceKind, FakeLspAdapter, Language, LanguageConfig,
  25    LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope,
  26    language_settings::{Formatter, FormatterList},
  27    rust_lang, tree_sitter_rust, tree_sitter_typescript,
  28};
  29use lsp::{DEFAULT_LSP_REQUEST_TIMEOUT, LanguageServerId, OneOf};
  30use parking_lot::Mutex;
  31use pretty_assertions::assert_eq;
  32use project::{
  33    DiagnosticSummary, HoverBlockKind, Project, ProjectPath,
  34    lsp_store::{FormatTrigger, LspFormatTarget, SymbolLocation},
  35    search::{SearchQuery, SearchResult},
  36};
  37use prompt_store::PromptBuilder;
  38use rand::prelude::*;
  39use serde_json::json;
  40use settings::{LanguageServerFormatterSpecifier, PrettierSettingsContent, SettingsStore};
  41use std::{
  42    cell::{Cell, RefCell},
  43    env, future, mem,
  44    path::{Path, PathBuf},
  45    rc::Rc,
  46    sync::{
  47        Arc,
  48        atomic::{AtomicBool, Ordering::SeqCst},
  49    },
  50    time::Duration,
  51};
  52use unindent::Unindent as _;
  53use util::{path, rel_path::rel_path, uri};
  54use workspace::{Pane, ParticipantLocation};
  55
  56#[ctor::ctor]
  57fn init_logger() {
  58    zlog::init_test();
  59}
  60
  61#[gpui::test(iterations = 10)]
  62async fn test_database_failure_during_client_reconnection(
  63    executor: BackgroundExecutor,
  64    cx: &mut TestAppContext,
  65) {
  66    let mut server = TestServer::start(executor.clone()).await;
  67    let client = server.create_client(cx, "user_a").await;
  68
  69    // Keep disconnecting the client until a database failure prevents it from
  70    // reconnecting.
  71    server.test_db.set_query_failure_probability(0.3);
  72    loop {
  73        server.disconnect_client(client.peer_id().unwrap());
  74        executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
  75        if !client.status().borrow().is_connected() {
  76            break;
  77        }
  78    }
  79
  80    // Make the database healthy again and ensure the client can finally connect.
  81    server.test_db.set_query_failure_probability(0.);
  82    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
  83    assert!(
  84        matches!(*client.status().borrow(), client::Status::Connected { .. }),
  85        "status was {:?}",
  86        *client.status().borrow()
  87    );
  88}
  89
  90#[gpui::test(iterations = 10)]
  91async fn test_basic_calls(
  92    executor: BackgroundExecutor,
  93    cx_a: &mut TestAppContext,
  94    cx_b: &mut TestAppContext,
  95    cx_b2: &mut TestAppContext,
  96    cx_c: &mut TestAppContext,
  97) {
  98    let mut server = TestServer::start(executor.clone()).await;
  99
 100    let client_a = server.create_client(cx_a, "user_a").await;
 101    let client_b = server.create_client(cx_b, "user_b").await;
 102    let client_c = server.create_client(cx_c, "user_c").await;
 103    server
 104        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
 105        .await;
 106
 107    let active_call_a = cx_a.read(ActiveCall::global);
 108    let active_call_b = cx_b.read(ActiveCall::global);
 109    let active_call_c = cx_c.read(ActiveCall::global);
 110
 111    // Call user B from client A.
 112    active_call_a
 113        .update(cx_a, |call, cx| {
 114            call.invite(client_b.user_id().unwrap(), None, cx)
 115        })
 116        .await
 117        .unwrap();
 118    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 119    executor.run_until_parked();
 120    assert_eq!(
 121        room_participants(&room_a, cx_a),
 122        RoomParticipants {
 123            remote: Default::default(),
 124            pending: vec!["user_b".to_string()]
 125        }
 126    );
 127
 128    // User B receives the call.
 129
 130    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 131    let call_b = incoming_call_b.next().await.unwrap().unwrap();
 132    assert_eq!(call_b.calling_user.github_login, "user_a");
 133
 134    // User B connects via another client and also receives a ring on the newly-connected client.
 135    let _client_b2 = server.create_client(cx_b2, "user_b").await;
 136    let active_call_b2 = cx_b2.read(ActiveCall::global);
 137
 138    let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
 139    executor.run_until_parked();
 140    let call_b2 = incoming_call_b2.next().await.unwrap().unwrap();
 141    assert_eq!(call_b2.calling_user.github_login, "user_a");
 142
 143    // User B joins the room using the first client.
 144    active_call_b
 145        .update(cx_b, |call, cx| call.accept_incoming(cx))
 146        .await
 147        .unwrap();
 148
 149    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 150    assert!(incoming_call_b.next().await.unwrap().is_none());
 151
 152    executor.run_until_parked();
 153    assert_eq!(
 154        room_participants(&room_a, cx_a),
 155        RoomParticipants {
 156            remote: vec!["user_b".to_string()],
 157            pending: Default::default()
 158        }
 159    );
 160    assert_eq!(
 161        room_participants(&room_b, cx_b),
 162        RoomParticipants {
 163            remote: vec!["user_a".to_string()],
 164            pending: Default::default()
 165        }
 166    );
 167
 168    // Call user C from client B.
 169
 170    let mut incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
 171    active_call_b
 172        .update(cx_b, |call, cx| {
 173            call.invite(client_c.user_id().unwrap(), None, cx)
 174        })
 175        .await
 176        .unwrap();
 177
 178    executor.run_until_parked();
 179    assert_eq!(
 180        room_participants(&room_a, cx_a),
 181        RoomParticipants {
 182            remote: vec!["user_b".to_string()],
 183            pending: vec!["user_c".to_string()]
 184        }
 185    );
 186    assert_eq!(
 187        room_participants(&room_b, cx_b),
 188        RoomParticipants {
 189            remote: vec!["user_a".to_string()],
 190            pending: vec!["user_c".to_string()]
 191        }
 192    );
 193
 194    // User C receives the call, but declines it.
 195    let call_c = incoming_call_c.next().await.unwrap().unwrap();
 196    assert_eq!(call_c.calling_user.github_login, "user_b");
 197    active_call_c.update(cx_c, |call, cx| call.decline_incoming(cx).unwrap());
 198    assert!(incoming_call_c.next().await.unwrap().is_none());
 199
 200    executor.run_until_parked();
 201    assert_eq!(
 202        room_participants(&room_a, cx_a),
 203        RoomParticipants {
 204            remote: vec!["user_b".to_string()],
 205            pending: Default::default()
 206        }
 207    );
 208    assert_eq!(
 209        room_participants(&room_b, cx_b),
 210        RoomParticipants {
 211            remote: vec!["user_a".to_string()],
 212            pending: Default::default()
 213        }
 214    );
 215
 216    // Call user C again from user A.
 217    active_call_a
 218        .update(cx_a, |call, cx| {
 219            call.invite(client_c.user_id().unwrap(), None, cx)
 220        })
 221        .await
 222        .unwrap();
 223
 224    executor.run_until_parked();
 225    assert_eq!(
 226        room_participants(&room_a, cx_a),
 227        RoomParticipants {
 228            remote: vec!["user_b".to_string()],
 229            pending: vec!["user_c".to_string()]
 230        }
 231    );
 232    assert_eq!(
 233        room_participants(&room_b, cx_b),
 234        RoomParticipants {
 235            remote: vec!["user_a".to_string()],
 236            pending: vec!["user_c".to_string()]
 237        }
 238    );
 239
 240    // User C accepts the call.
 241    let call_c = incoming_call_c.next().await.unwrap().unwrap();
 242    assert_eq!(call_c.calling_user.github_login, "user_a");
 243    active_call_c
 244        .update(cx_c, |call, cx| call.accept_incoming(cx))
 245        .await
 246        .unwrap();
 247    assert!(incoming_call_c.next().await.unwrap().is_none());
 248
 249    let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
 250
 251    executor.run_until_parked();
 252    assert_eq!(
 253        room_participants(&room_a, cx_a),
 254        RoomParticipants {
 255            remote: vec!["user_b".to_string(), "user_c".to_string()],
 256            pending: Default::default()
 257        }
 258    );
 259    assert_eq!(
 260        room_participants(&room_b, cx_b),
 261        RoomParticipants {
 262            remote: vec!["user_a".to_string(), "user_c".to_string()],
 263            pending: Default::default()
 264        }
 265    );
 266    assert_eq!(
 267        room_participants(&room_c, cx_c),
 268        RoomParticipants {
 269            remote: vec!["user_a".to_string(), "user_b".to_string()],
 270            pending: Default::default()
 271        }
 272    );
 273
 274    // User A shares their screen
 275    let display = gpui::TestScreenCaptureSource::new();
 276    let events_b = active_call_events(cx_b);
 277    let events_c = active_call_events(cx_c);
 278    cx_a.set_screen_capture_sources(vec![display]);
 279    let screen_a = cx_a
 280        .update(|cx| cx.screen_capture_sources())
 281        .await
 282        .unwrap()
 283        .unwrap()
 284        .into_iter()
 285        .next()
 286        .unwrap();
 287    active_call_a
 288        .update(cx_a, |call, cx| {
 289            call.room()
 290                .unwrap()
 291                .update(cx, |room, cx| room.share_screen(screen_a, cx))
 292        })
 293        .await
 294        .unwrap();
 295
 296    executor.run_until_parked();
 297
 298    // User B observes the remote screen sharing track.
 299    assert_eq!(events_b.borrow().len(), 1);
 300    let event_b = events_b.borrow().first().unwrap().clone();
 301    if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event_b {
 302        assert_eq!(participant_id, client_a.peer_id().unwrap());
 303
 304        room_b.read_with(cx_b, |room, _| {
 305            assert_eq!(
 306                room.remote_participants()[&client_a.user_id().unwrap()]
 307                    .video_tracks
 308                    .len(),
 309                1
 310            );
 311        });
 312    } else {
 313        panic!("unexpected event")
 314    }
 315
 316    // User C observes the remote screen sharing track.
 317    assert_eq!(events_c.borrow().len(), 1);
 318    let event_c = events_c.borrow().first().unwrap().clone();
 319    if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event_c {
 320        assert_eq!(participant_id, client_a.peer_id().unwrap());
 321
 322        room_c.read_with(cx_c, |room, _| {
 323            assert_eq!(
 324                room.remote_participants()[&client_a.user_id().unwrap()]
 325                    .video_tracks
 326                    .len(),
 327                1
 328            );
 329        });
 330    } else {
 331        panic!("unexpected event")
 332    }
 333
 334    // User A leaves the room.
 335    active_call_a
 336        .update(cx_a, |call, cx| {
 337            let hang_up = call.hang_up(cx);
 338            assert!(call.room().is_none());
 339            hang_up
 340        })
 341        .await
 342        .unwrap();
 343    executor.run_until_parked();
 344    assert_eq!(
 345        room_participants(&room_a, cx_a),
 346        RoomParticipants {
 347            remote: Default::default(),
 348            pending: Default::default()
 349        }
 350    );
 351    assert_eq!(
 352        room_participants(&room_b, cx_b),
 353        RoomParticipants {
 354            remote: vec!["user_c".to_string()],
 355            pending: Default::default()
 356        }
 357    );
 358    assert_eq!(
 359        room_participants(&room_c, cx_c),
 360        RoomParticipants {
 361            remote: vec!["user_b".to_string()],
 362            pending: Default::default()
 363        }
 364    );
 365
 366    // User B gets disconnected from the LiveKit server, which causes them
 367    // to automatically leave the room. User C leaves the room as well because
 368    // nobody else is in there.
 369    server
 370        .test_livekit_server
 371        .disconnect_client(client_b.user_id().unwrap().to_string())
 372        .await;
 373    executor.run_until_parked();
 374
 375    active_call_b.read_with(cx_b, |call, _| assert!(call.room().is_none()));
 376
 377    active_call_c.read_with(cx_c, |call, _| assert!(call.room().is_none()));
 378    assert_eq!(
 379        room_participants(&room_a, cx_a),
 380        RoomParticipants {
 381            remote: Default::default(),
 382            pending: Default::default()
 383        }
 384    );
 385    assert_eq!(
 386        room_participants(&room_b, cx_b),
 387        RoomParticipants {
 388            remote: Default::default(),
 389            pending: Default::default()
 390        }
 391    );
 392    assert_eq!(
 393        room_participants(&room_c, cx_c),
 394        RoomParticipants {
 395            remote: Default::default(),
 396            pending: Default::default()
 397        }
 398    );
 399}
 400
 401#[gpui::test(iterations = 10)]
 402async fn test_calling_multiple_users_simultaneously(
 403    executor: BackgroundExecutor,
 404    cx_a: &mut TestAppContext,
 405    cx_b: &mut TestAppContext,
 406    cx_c: &mut TestAppContext,
 407    cx_d: &mut TestAppContext,
 408) {
 409    let mut server = TestServer::start(executor.clone()).await;
 410
 411    let client_a = server.create_client(cx_a, "user_a").await;
 412    let client_b = server.create_client(cx_b, "user_b").await;
 413    let client_c = server.create_client(cx_c, "user_c").await;
 414    let client_d = server.create_client(cx_d, "user_d").await;
 415    server
 416        .make_contacts(&mut [
 417            (&client_a, cx_a),
 418            (&client_b, cx_b),
 419            (&client_c, cx_c),
 420            (&client_d, cx_d),
 421        ])
 422        .await;
 423
 424    let active_call_a = cx_a.read(ActiveCall::global);
 425    let active_call_b = cx_b.read(ActiveCall::global);
 426    let active_call_c = cx_c.read(ActiveCall::global);
 427    let active_call_d = cx_d.read(ActiveCall::global);
 428
 429    // Simultaneously call user B and user C from client A.
 430    let b_invite = active_call_a.update(cx_a, |call, cx| {
 431        call.invite(client_b.user_id().unwrap(), None, cx)
 432    });
 433    let c_invite = active_call_a.update(cx_a, |call, cx| {
 434        call.invite(client_c.user_id().unwrap(), None, cx)
 435    });
 436    b_invite.await.unwrap();
 437    c_invite.await.unwrap();
 438
 439    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 440    executor.run_until_parked();
 441    assert_eq!(
 442        room_participants(&room_a, cx_a),
 443        RoomParticipants {
 444            remote: Default::default(),
 445            pending: vec!["user_b".to_string(), "user_c".to_string()]
 446        }
 447    );
 448
 449    // Call client D from client A.
 450    active_call_a
 451        .update(cx_a, |call, cx| {
 452            call.invite(client_d.user_id().unwrap(), None, cx)
 453        })
 454        .await
 455        .unwrap();
 456    executor.run_until_parked();
 457    assert_eq!(
 458        room_participants(&room_a, cx_a),
 459        RoomParticipants {
 460            remote: Default::default(),
 461            pending: vec![
 462                "user_b".to_string(),
 463                "user_c".to_string(),
 464                "user_d".to_string()
 465            ]
 466        }
 467    );
 468
 469    // Accept the call on all clients simultaneously.
 470    let accept_b = active_call_b.update(cx_b, |call, cx| call.accept_incoming(cx));
 471    let accept_c = active_call_c.update(cx_c, |call, cx| call.accept_incoming(cx));
 472    let accept_d = active_call_d.update(cx_d, |call, cx| call.accept_incoming(cx));
 473    accept_b.await.unwrap();
 474    accept_c.await.unwrap();
 475    accept_d.await.unwrap();
 476
 477    executor.run_until_parked();
 478
 479    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 480
 481    let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
 482
 483    let room_d = active_call_d.read_with(cx_d, |call, _| call.room().unwrap().clone());
 484    assert_eq!(
 485        room_participants(&room_a, cx_a),
 486        RoomParticipants {
 487            remote: vec![
 488                "user_b".to_string(),
 489                "user_c".to_string(),
 490                "user_d".to_string(),
 491            ],
 492            pending: Default::default()
 493        }
 494    );
 495    assert_eq!(
 496        room_participants(&room_b, cx_b),
 497        RoomParticipants {
 498            remote: vec![
 499                "user_a".to_string(),
 500                "user_c".to_string(),
 501                "user_d".to_string(),
 502            ],
 503            pending: Default::default()
 504        }
 505    );
 506    assert_eq!(
 507        room_participants(&room_c, cx_c),
 508        RoomParticipants {
 509            remote: vec![
 510                "user_a".to_string(),
 511                "user_b".to_string(),
 512                "user_d".to_string(),
 513            ],
 514            pending: Default::default()
 515        }
 516    );
 517    assert_eq!(
 518        room_participants(&room_d, cx_d),
 519        RoomParticipants {
 520            remote: vec![
 521                "user_a".to_string(),
 522                "user_b".to_string(),
 523                "user_c".to_string(),
 524            ],
 525            pending: Default::default()
 526        }
 527    );
 528}
 529
 530#[gpui::test(iterations = 10)]
 531async fn test_joining_channels_and_calling_multiple_users_simultaneously(
 532    executor: BackgroundExecutor,
 533    cx_a: &mut TestAppContext,
 534    cx_b: &mut TestAppContext,
 535    cx_c: &mut TestAppContext,
 536) {
 537    let mut server = TestServer::start(executor.clone()).await;
 538
 539    let client_a = server.create_client(cx_a, "user_a").await;
 540    let client_b = server.create_client(cx_b, "user_b").await;
 541    let client_c = server.create_client(cx_c, "user_c").await;
 542    server
 543        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
 544        .await;
 545
 546    let channel_1 = server
 547        .make_channel(
 548            "channel1",
 549            None,
 550            (&client_a, cx_a),
 551            &mut [(&client_b, cx_b), (&client_c, cx_c)],
 552        )
 553        .await;
 554
 555    let channel_2 = server
 556        .make_channel(
 557            "channel2",
 558            None,
 559            (&client_a, cx_a),
 560            &mut [(&client_b, cx_b), (&client_c, cx_c)],
 561        )
 562        .await;
 563
 564    let active_call_a = cx_a.read(ActiveCall::global);
 565
 566    // Simultaneously join channel 1 and then channel 2
 567    active_call_a
 568        .update(cx_a, |call, cx| call.join_channel(channel_1, cx))
 569        .detach();
 570    let join_channel_2 = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_2, cx));
 571
 572    join_channel_2.await.unwrap();
 573
 574    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 575    executor.run_until_parked();
 576
 577    assert_eq!(channel_id(&room_a, cx_a), Some(channel_2));
 578
 579    // Leave the room
 580    active_call_a
 581        .update(cx_a, |call, cx| call.hang_up(cx))
 582        .await
 583        .unwrap();
 584
 585    // Initiating invites and then joining a channel should fail gracefully
 586    let b_invite = active_call_a.update(cx_a, |call, cx| {
 587        call.invite(client_b.user_id().unwrap(), None, cx)
 588    });
 589    let c_invite = active_call_a.update(cx_a, |call, cx| {
 590        call.invite(client_c.user_id().unwrap(), None, cx)
 591    });
 592
 593    let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx));
 594
 595    b_invite.await.unwrap();
 596    c_invite.await.unwrap();
 597    join_channel.await.unwrap();
 598
 599    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 600    executor.run_until_parked();
 601
 602    assert_eq!(
 603        room_participants(&room_a, cx_a),
 604        RoomParticipants {
 605            remote: Default::default(),
 606            pending: vec!["user_b".to_string(), "user_c".to_string()]
 607        }
 608    );
 609
 610    assert_eq!(channel_id(&room_a, cx_a), None);
 611
 612    // Leave the room
 613    active_call_a
 614        .update(cx_a, |call, cx| call.hang_up(cx))
 615        .await
 616        .unwrap();
 617
 618    // Simultaneously join channel 1 and call user B and user C from client A.
 619    let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx));
 620
 621    let b_invite = active_call_a.update(cx_a, |call, cx| {
 622        call.invite(client_b.user_id().unwrap(), None, cx)
 623    });
 624    let c_invite = active_call_a.update(cx_a, |call, cx| {
 625        call.invite(client_c.user_id().unwrap(), None, cx)
 626    });
 627
 628    join_channel.await.unwrap();
 629    b_invite.await.unwrap();
 630    c_invite.await.unwrap();
 631
 632    active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 633    executor.run_until_parked();
 634}
 635
 636#[gpui::test(iterations = 10)]
 637async fn test_room_uniqueness(
 638    executor: BackgroundExecutor,
 639    cx_a: &mut TestAppContext,
 640    cx_a2: &mut TestAppContext,
 641    cx_b: &mut TestAppContext,
 642    cx_b2: &mut TestAppContext,
 643    cx_c: &mut TestAppContext,
 644) {
 645    let mut server = TestServer::start(executor.clone()).await;
 646    let client_a = server.create_client(cx_a, "user_a").await;
 647    let _client_a2 = server.create_client(cx_a2, "user_a").await;
 648    let client_b = server.create_client(cx_b, "user_b").await;
 649    let _client_b2 = server.create_client(cx_b2, "user_b").await;
 650    let client_c = server.create_client(cx_c, "user_c").await;
 651    server
 652        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
 653        .await;
 654
 655    let active_call_a = cx_a.read(ActiveCall::global);
 656    let active_call_a2 = cx_a2.read(ActiveCall::global);
 657    let active_call_b = cx_b.read(ActiveCall::global);
 658    let active_call_b2 = cx_b2.read(ActiveCall::global);
 659    let active_call_c = cx_c.read(ActiveCall::global);
 660
 661    // Call user B from client A.
 662    active_call_a
 663        .update(cx_a, |call, cx| {
 664            call.invite(client_b.user_id().unwrap(), None, cx)
 665        })
 666        .await
 667        .unwrap();
 668
 669    // Ensure a new room can't be created given user A just created one.
 670    active_call_a2
 671        .update(cx_a2, |call, cx| {
 672            call.invite(client_c.user_id().unwrap(), None, cx)
 673        })
 674        .await
 675        .unwrap_err();
 676
 677    active_call_a2.read_with(cx_a2, |call, _| assert!(call.room().is_none()));
 678
 679    // User B receives the call from user A.
 680
 681    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 682    let call_b1 = incoming_call_b.next().await.unwrap().unwrap();
 683    assert_eq!(call_b1.calling_user.github_login, "user_a");
 684
 685    // Ensure calling users A and B from client C fails.
 686    active_call_c
 687        .update(cx_c, |call, cx| {
 688            call.invite(client_a.user_id().unwrap(), None, cx)
 689        })
 690        .await
 691        .unwrap_err();
 692    active_call_c
 693        .update(cx_c, |call, cx| {
 694            call.invite(client_b.user_id().unwrap(), None, cx)
 695        })
 696        .await
 697        .unwrap_err();
 698
 699    // Ensure User B can't create a room while they still have an incoming call.
 700    active_call_b2
 701        .update(cx_b2, |call, cx| {
 702            call.invite(client_c.user_id().unwrap(), None, cx)
 703        })
 704        .await
 705        .unwrap_err();
 706
 707    active_call_b2.read_with(cx_b2, |call, _| assert!(call.room().is_none()));
 708
 709    // User B joins the room and calling them after they've joined still fails.
 710    active_call_b
 711        .update(cx_b, |call, cx| call.accept_incoming(cx))
 712        .await
 713        .unwrap();
 714    active_call_c
 715        .update(cx_c, |call, cx| {
 716            call.invite(client_b.user_id().unwrap(), None, cx)
 717        })
 718        .await
 719        .unwrap_err();
 720
 721    // Ensure User B can't create a room while they belong to another room.
 722    active_call_b2
 723        .update(cx_b2, |call, cx| {
 724            call.invite(client_c.user_id().unwrap(), None, cx)
 725        })
 726        .await
 727        .unwrap_err();
 728
 729    active_call_b2.read_with(cx_b2, |call, _| assert!(call.room().is_none()));
 730
 731    // Client C can successfully call client B after client B leaves the room.
 732    active_call_b
 733        .update(cx_b, |call, cx| call.hang_up(cx))
 734        .await
 735        .unwrap();
 736    executor.run_until_parked();
 737    active_call_c
 738        .update(cx_c, |call, cx| {
 739            call.invite(client_b.user_id().unwrap(), None, cx)
 740        })
 741        .await
 742        .unwrap();
 743    executor.run_until_parked();
 744    let call_b2 = incoming_call_b.next().await.unwrap().unwrap();
 745    assert_eq!(call_b2.calling_user.github_login, "user_c");
 746}
 747
 748#[gpui::test(iterations = 10)]
 749async fn test_client_disconnecting_from_room(
 750    executor: BackgroundExecutor,
 751    cx_a: &mut TestAppContext,
 752    cx_b: &mut TestAppContext,
 753) {
 754    let mut server = TestServer::start(executor.clone()).await;
 755    let client_a = server.create_client(cx_a, "user_a").await;
 756    let client_b = server.create_client(cx_b, "user_b").await;
 757    server
 758        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 759        .await;
 760
 761    let active_call_a = cx_a.read(ActiveCall::global);
 762    let active_call_b = cx_b.read(ActiveCall::global);
 763
 764    // Call user B from client A.
 765    active_call_a
 766        .update(cx_a, |call, cx| {
 767            call.invite(client_b.user_id().unwrap(), None, cx)
 768        })
 769        .await
 770        .unwrap();
 771
 772    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 773
 774    // User B receives the call and joins the room.
 775
 776    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 777    incoming_call_b.next().await.unwrap().unwrap();
 778    active_call_b
 779        .update(cx_b, |call, cx| call.accept_incoming(cx))
 780        .await
 781        .unwrap();
 782
 783    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 784    executor.run_until_parked();
 785    assert_eq!(
 786        room_participants(&room_a, cx_a),
 787        RoomParticipants {
 788            remote: vec!["user_b".to_string()],
 789            pending: Default::default()
 790        }
 791    );
 792    assert_eq!(
 793        room_participants(&room_b, cx_b),
 794        RoomParticipants {
 795            remote: vec!["user_a".to_string()],
 796            pending: Default::default()
 797        }
 798    );
 799
 800    // User A automatically reconnects to the room upon disconnection.
 801    server.disconnect_client(client_a.peer_id().unwrap());
 802    executor.advance_clock(RECEIVE_TIMEOUT);
 803    executor.run_until_parked();
 804    assert_eq!(
 805        room_participants(&room_a, cx_a),
 806        RoomParticipants {
 807            remote: vec!["user_b".to_string()],
 808            pending: Default::default()
 809        }
 810    );
 811    assert_eq!(
 812        room_participants(&room_b, cx_b),
 813        RoomParticipants {
 814            remote: vec!["user_a".to_string()],
 815            pending: Default::default()
 816        }
 817    );
 818
 819    // When user A disconnects, both client A and B clear their room on the active call.
 820    server.forbid_connections();
 821    server.disconnect_client(client_a.peer_id().unwrap());
 822    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 823
 824    active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
 825
 826    active_call_b.read_with(cx_b, |call, _| assert!(call.room().is_none()));
 827    assert_eq!(
 828        room_participants(&room_a, cx_a),
 829        RoomParticipants {
 830            remote: Default::default(),
 831            pending: Default::default()
 832        }
 833    );
 834    assert_eq!(
 835        room_participants(&room_b, cx_b),
 836        RoomParticipants {
 837            remote: Default::default(),
 838            pending: Default::default()
 839        }
 840    );
 841
 842    // Allow user A to reconnect to the server.
 843    server.allow_connections();
 844    executor.advance_clock(RECONNECT_TIMEOUT);
 845
 846    // Call user B again from client A.
 847    active_call_a
 848        .update(cx_a, |call, cx| {
 849            call.invite(client_b.user_id().unwrap(), None, cx)
 850        })
 851        .await
 852        .unwrap();
 853
 854    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 855
 856    // User B receives the call and joins the room.
 857
 858    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 859    incoming_call_b.next().await.unwrap().unwrap();
 860    active_call_b
 861        .update(cx_b, |call, cx| call.accept_incoming(cx))
 862        .await
 863        .unwrap();
 864
 865    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 866    executor.run_until_parked();
 867    assert_eq!(
 868        room_participants(&room_a, cx_a),
 869        RoomParticipants {
 870            remote: vec!["user_b".to_string()],
 871            pending: Default::default()
 872        }
 873    );
 874    assert_eq!(
 875        room_participants(&room_b, cx_b),
 876        RoomParticipants {
 877            remote: vec!["user_a".to_string()],
 878            pending: Default::default()
 879        }
 880    );
 881
 882    // User B gets disconnected from the LiveKit server, which causes it
 883    // to automatically leave the room.
 884    server
 885        .test_livekit_server
 886        .disconnect_client(client_b.user_id().unwrap().to_string())
 887        .await;
 888    executor.run_until_parked();
 889    active_call_a.update(cx_a, |call, _| assert!(call.room().is_none()));
 890    active_call_b.update(cx_b, |call, _| assert!(call.room().is_none()));
 891    assert_eq!(
 892        room_participants(&room_a, cx_a),
 893        RoomParticipants {
 894            remote: Default::default(),
 895            pending: Default::default()
 896        }
 897    );
 898    assert_eq!(
 899        room_participants(&room_b, cx_b),
 900        RoomParticipants {
 901            remote: Default::default(),
 902            pending: Default::default()
 903        }
 904    );
 905}
 906
 907#[gpui::test(iterations = 10)]
 908async fn test_server_restarts(
 909    executor: BackgroundExecutor,
 910    cx_a: &mut TestAppContext,
 911    cx_b: &mut TestAppContext,
 912    cx_c: &mut TestAppContext,
 913    cx_d: &mut TestAppContext,
 914) {
 915    let mut server = TestServer::start(executor.clone()).await;
 916    let client_a = server.create_client(cx_a, "user_a").await;
 917    client_a
 918        .fs()
 919        .insert_tree("/a", json!({ "a.txt": "a-contents" }))
 920        .await;
 921
 922    // Invite client B to collaborate on a project
 923    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
 924
 925    let client_b = server.create_client(cx_b, "user_b").await;
 926    let client_c = server.create_client(cx_c, "user_c").await;
 927    let client_d = server.create_client(cx_d, "user_d").await;
 928    server
 929        .make_contacts(&mut [
 930            (&client_a, cx_a),
 931            (&client_b, cx_b),
 932            (&client_c, cx_c),
 933            (&client_d, cx_d),
 934        ])
 935        .await;
 936
 937    let active_call_a = cx_a.read(ActiveCall::global);
 938    let active_call_b = cx_b.read(ActiveCall::global);
 939    let active_call_c = cx_c.read(ActiveCall::global);
 940    let active_call_d = cx_d.read(ActiveCall::global);
 941
 942    // User A calls users B, C, and D.
 943    active_call_a
 944        .update(cx_a, |call, cx| {
 945            call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
 946        })
 947        .await
 948        .unwrap();
 949    active_call_a
 950        .update(cx_a, |call, cx| {
 951            call.invite(client_c.user_id().unwrap(), Some(project_a.clone()), cx)
 952        })
 953        .await
 954        .unwrap();
 955    active_call_a
 956        .update(cx_a, |call, cx| {
 957            call.invite(client_d.user_id().unwrap(), Some(project_a.clone()), cx)
 958        })
 959        .await
 960        .unwrap();
 961
 962    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 963
 964    // User B receives the call and joins the room.
 965
 966    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 967    assert!(incoming_call_b.next().await.unwrap().is_some());
 968    active_call_b
 969        .update(cx_b, |call, cx| call.accept_incoming(cx))
 970        .await
 971        .unwrap();
 972
 973    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 974
 975    // User C receives the call and joins the room.
 976
 977    let mut incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
 978    assert!(incoming_call_c.next().await.unwrap().is_some());
 979    active_call_c
 980        .update(cx_c, |call, cx| call.accept_incoming(cx))
 981        .await
 982        .unwrap();
 983
 984    let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
 985
 986    // User D receives the call but doesn't join the room yet.
 987
 988    let mut incoming_call_d = active_call_d.read_with(cx_d, |call, _| call.incoming());
 989    assert!(incoming_call_d.next().await.unwrap().is_some());
 990
 991    executor.run_until_parked();
 992    assert_eq!(
 993        room_participants(&room_a, cx_a),
 994        RoomParticipants {
 995            remote: vec!["user_b".to_string(), "user_c".to_string()],
 996            pending: vec!["user_d".to_string()]
 997        }
 998    );
 999    assert_eq!(
1000        room_participants(&room_b, cx_b),
1001        RoomParticipants {
1002            remote: vec!["user_a".to_string(), "user_c".to_string()],
1003            pending: vec!["user_d".to_string()]
1004        }
1005    );
1006    assert_eq!(
1007        room_participants(&room_c, cx_c),
1008        RoomParticipants {
1009            remote: vec!["user_a".to_string(), "user_b".to_string()],
1010            pending: vec!["user_d".to_string()]
1011        }
1012    );
1013
1014    // The server is torn down.
1015    server.reset().await;
1016
1017    // Users A and B reconnect to the call. User C has troubles reconnecting, so it leaves the room.
1018    client_c.override_establish_connection(|_, cx| cx.spawn(async |_| future::pending().await));
1019    executor.advance_clock(RECONNECT_TIMEOUT);
1020    assert_eq!(
1021        room_participants(&room_a, cx_a),
1022        RoomParticipants {
1023            remote: vec!["user_b".to_string(), "user_c".to_string()],
1024            pending: vec!["user_d".to_string()]
1025        }
1026    );
1027    assert_eq!(
1028        room_participants(&room_b, cx_b),
1029        RoomParticipants {
1030            remote: vec!["user_a".to_string(), "user_c".to_string()],
1031            pending: vec!["user_d".to_string()]
1032        }
1033    );
1034    assert_eq!(
1035        room_participants(&room_c, cx_c),
1036        RoomParticipants {
1037            remote: vec![],
1038            pending: vec![]
1039        }
1040    );
1041
1042    // User D is notified again of the incoming call and accepts it.
1043    assert!(incoming_call_d.next().await.unwrap().is_some());
1044    active_call_d
1045        .update(cx_d, |call, cx| call.accept_incoming(cx))
1046        .await
1047        .unwrap();
1048    executor.run_until_parked();
1049
1050    let room_d = active_call_d.read_with(cx_d, |call, _| call.room().unwrap().clone());
1051    assert_eq!(
1052        room_participants(&room_a, cx_a),
1053        RoomParticipants {
1054            remote: vec![
1055                "user_b".to_string(),
1056                "user_c".to_string(),
1057                "user_d".to_string(),
1058            ],
1059            pending: vec![]
1060        }
1061    );
1062    assert_eq!(
1063        room_participants(&room_b, cx_b),
1064        RoomParticipants {
1065            remote: vec![
1066                "user_a".to_string(),
1067                "user_c".to_string(),
1068                "user_d".to_string(),
1069            ],
1070            pending: vec![]
1071        }
1072    );
1073    assert_eq!(
1074        room_participants(&room_c, cx_c),
1075        RoomParticipants {
1076            remote: vec![],
1077            pending: vec![]
1078        }
1079    );
1080    assert_eq!(
1081        room_participants(&room_d, cx_d),
1082        RoomParticipants {
1083            remote: vec![
1084                "user_a".to_string(),
1085                "user_b".to_string(),
1086                "user_c".to_string(),
1087            ],
1088            pending: vec![]
1089        }
1090    );
1091
1092    // The server finishes restarting, cleaning up stale connections.
1093    server.start().await.unwrap();
1094    executor.advance_clock(CLEANUP_TIMEOUT);
1095    assert_eq!(
1096        room_participants(&room_a, cx_a),
1097        RoomParticipants {
1098            remote: vec!["user_b".to_string(), "user_d".to_string()],
1099            pending: vec![]
1100        }
1101    );
1102    assert_eq!(
1103        room_participants(&room_b, cx_b),
1104        RoomParticipants {
1105            remote: vec!["user_a".to_string(), "user_d".to_string()],
1106            pending: vec![]
1107        }
1108    );
1109    assert_eq!(
1110        room_participants(&room_c, cx_c),
1111        RoomParticipants {
1112            remote: vec![],
1113            pending: vec![]
1114        }
1115    );
1116    assert_eq!(
1117        room_participants(&room_d, cx_d),
1118        RoomParticipants {
1119            remote: vec!["user_a".to_string(), "user_b".to_string()],
1120            pending: vec![]
1121        }
1122    );
1123
1124    // User D hangs up.
1125    active_call_d
1126        .update(cx_d, |call, cx| call.hang_up(cx))
1127        .await
1128        .unwrap();
1129    executor.run_until_parked();
1130    assert_eq!(
1131        room_participants(&room_a, cx_a),
1132        RoomParticipants {
1133            remote: vec!["user_b".to_string()],
1134            pending: vec![]
1135        }
1136    );
1137    assert_eq!(
1138        room_participants(&room_b, cx_b),
1139        RoomParticipants {
1140            remote: vec!["user_a".to_string()],
1141            pending: vec![]
1142        }
1143    );
1144    assert_eq!(
1145        room_participants(&room_c, cx_c),
1146        RoomParticipants {
1147            remote: vec![],
1148            pending: vec![]
1149        }
1150    );
1151    assert_eq!(
1152        room_participants(&room_d, cx_d),
1153        RoomParticipants {
1154            remote: vec![],
1155            pending: vec![]
1156        }
1157    );
1158
1159    // User B calls user D again.
1160    active_call_b
1161        .update(cx_b, |call, cx| {
1162            call.invite(client_d.user_id().unwrap(), None, cx)
1163        })
1164        .await
1165        .unwrap();
1166
1167    // User D receives the call but doesn't join the room yet.
1168
1169    let mut incoming_call_d = active_call_d.read_with(cx_d, |call, _| call.incoming());
1170    assert!(incoming_call_d.next().await.unwrap().is_some());
1171    executor.run_until_parked();
1172    assert_eq!(
1173        room_participants(&room_a, cx_a),
1174        RoomParticipants {
1175            remote: vec!["user_b".to_string()],
1176            pending: vec!["user_d".to_string()]
1177        }
1178    );
1179    assert_eq!(
1180        room_participants(&room_b, cx_b),
1181        RoomParticipants {
1182            remote: vec!["user_a".to_string()],
1183            pending: vec!["user_d".to_string()]
1184        }
1185    );
1186
1187    // The server is torn down.
1188    server.reset().await;
1189
1190    // Users A and B have troubles reconnecting, so they leave the room.
1191    client_a.override_establish_connection(|_, cx| cx.spawn(async |_| future::pending().await));
1192    client_b.override_establish_connection(|_, cx| cx.spawn(async |_| future::pending().await));
1193    client_c.override_establish_connection(|_, cx| cx.spawn(async |_| future::pending().await));
1194    executor.advance_clock(RECONNECT_TIMEOUT);
1195    assert_eq!(
1196        room_participants(&room_a, cx_a),
1197        RoomParticipants {
1198            remote: vec![],
1199            pending: vec![]
1200        }
1201    );
1202    assert_eq!(
1203        room_participants(&room_b, cx_b),
1204        RoomParticipants {
1205            remote: vec![],
1206            pending: vec![]
1207        }
1208    );
1209
1210    // User D is notified again of the incoming call but doesn't accept it.
1211    assert!(incoming_call_d.next().await.unwrap().is_some());
1212
1213    // The server finishes restarting, cleaning up stale connections and canceling the
1214    // call to user D because the room has become empty.
1215    server.start().await.unwrap();
1216    executor.advance_clock(CLEANUP_TIMEOUT);
1217    assert!(incoming_call_d.next().await.unwrap().is_none());
1218}
1219
1220#[gpui::test(iterations = 10)]
1221async fn test_calls_on_multiple_connections(
1222    executor: BackgroundExecutor,
1223    cx_a: &mut TestAppContext,
1224    cx_b1: &mut TestAppContext,
1225    cx_b2: &mut TestAppContext,
1226) {
1227    let mut server = TestServer::start(executor.clone()).await;
1228    let client_a = server.create_client(cx_a, "user_a").await;
1229    let client_b1 = server.create_client(cx_b1, "user_b").await;
1230    let client_b2 = server.create_client(cx_b2, "user_b").await;
1231    server
1232        .make_contacts(&mut [(&client_a, cx_a), (&client_b1, cx_b1)])
1233        .await;
1234
1235    let active_call_a = cx_a.read(ActiveCall::global);
1236    let active_call_b1 = cx_b1.read(ActiveCall::global);
1237    let active_call_b2 = cx_b2.read(ActiveCall::global);
1238
1239    let mut incoming_call_b1 = active_call_b1.read_with(cx_b1, |call, _| call.incoming());
1240
1241    let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
1242    assert!(incoming_call_b1.next().await.unwrap().is_none());
1243    assert!(incoming_call_b2.next().await.unwrap().is_none());
1244
1245    // Call user B from client A, ensuring both clients for user B ring.
1246    active_call_a
1247        .update(cx_a, |call, cx| {
1248            call.invite(client_b1.user_id().unwrap(), None, cx)
1249        })
1250        .await
1251        .unwrap();
1252    executor.run_until_parked();
1253    assert!(incoming_call_b1.next().await.unwrap().is_some());
1254    assert!(incoming_call_b2.next().await.unwrap().is_some());
1255
1256    // User B declines the call on one of the two connections, causing both connections
1257    // to stop ringing.
1258    active_call_b2.update(cx_b2, |call, cx| call.decline_incoming(cx).unwrap());
1259    executor.run_until_parked();
1260    assert!(incoming_call_b1.next().await.unwrap().is_none());
1261    assert!(incoming_call_b2.next().await.unwrap().is_none());
1262
1263    // Call user B again from client A.
1264    active_call_a
1265        .update(cx_a, |call, cx| {
1266            call.invite(client_b1.user_id().unwrap(), None, cx)
1267        })
1268        .await
1269        .unwrap();
1270    executor.run_until_parked();
1271    assert!(incoming_call_b1.next().await.unwrap().is_some());
1272    assert!(incoming_call_b2.next().await.unwrap().is_some());
1273
1274    // User B accepts the call on one of the two connections, causing both connections
1275    // to stop ringing.
1276    active_call_b2
1277        .update(cx_b2, |call, cx| call.accept_incoming(cx))
1278        .await
1279        .unwrap();
1280    executor.run_until_parked();
1281    assert!(incoming_call_b1.next().await.unwrap().is_none());
1282    assert!(incoming_call_b2.next().await.unwrap().is_none());
1283
1284    // User B disconnects the client that is not on the call. Everything should be fine.
1285    client_b1.disconnect(&cx_b1.to_async());
1286    executor.advance_clock(RECEIVE_TIMEOUT);
1287    client_b1
1288        .connect(false, &cx_b1.to_async())
1289        .await
1290        .into_response()
1291        .unwrap();
1292
1293    // User B hangs up, and user A calls them again.
1294    active_call_b2
1295        .update(cx_b2, |call, cx| call.hang_up(cx))
1296        .await
1297        .unwrap();
1298    executor.run_until_parked();
1299    active_call_a
1300        .update(cx_a, |call, cx| {
1301            call.invite(client_b1.user_id().unwrap(), None, cx)
1302        })
1303        .await
1304        .unwrap();
1305    executor.run_until_parked();
1306    assert!(incoming_call_b1.next().await.unwrap().is_some());
1307    assert!(incoming_call_b2.next().await.unwrap().is_some());
1308
1309    // User A cancels the call, causing both connections to stop ringing.
1310    active_call_a
1311        .update(cx_a, |call, cx| {
1312            call.cancel_invite(client_b1.user_id().unwrap(), cx)
1313        })
1314        .await
1315        .unwrap();
1316    executor.run_until_parked();
1317    assert!(incoming_call_b1.next().await.unwrap().is_none());
1318    assert!(incoming_call_b2.next().await.unwrap().is_none());
1319
1320    // User A calls user B again.
1321    active_call_a
1322        .update(cx_a, |call, cx| {
1323            call.invite(client_b1.user_id().unwrap(), None, cx)
1324        })
1325        .await
1326        .unwrap();
1327    executor.run_until_parked();
1328    assert!(incoming_call_b1.next().await.unwrap().is_some());
1329    assert!(incoming_call_b2.next().await.unwrap().is_some());
1330
1331    // User A hangs up, causing both connections to stop ringing.
1332    active_call_a
1333        .update(cx_a, |call, cx| call.hang_up(cx))
1334        .await
1335        .unwrap();
1336    executor.run_until_parked();
1337    assert!(incoming_call_b1.next().await.unwrap().is_none());
1338    assert!(incoming_call_b2.next().await.unwrap().is_none());
1339
1340    // User A calls user B again.
1341    active_call_a
1342        .update(cx_a, |call, cx| {
1343            call.invite(client_b1.user_id().unwrap(), None, cx)
1344        })
1345        .await
1346        .unwrap();
1347    executor.run_until_parked();
1348    assert!(incoming_call_b1.next().await.unwrap().is_some());
1349    assert!(incoming_call_b2.next().await.unwrap().is_some());
1350
1351    // User A disconnects, causing both connections to stop ringing.
1352    server.forbid_connections();
1353    server.disconnect_client(client_a.peer_id().unwrap());
1354    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
1355    assert!(incoming_call_b1.next().await.unwrap().is_none());
1356    assert!(incoming_call_b2.next().await.unwrap().is_none());
1357
1358    // User A reconnects automatically, then calls user B again.
1359    server.allow_connections();
1360    executor.advance_clock(RECONNECT_TIMEOUT);
1361    active_call_a
1362        .update(cx_a, |call, cx| {
1363            call.invite(client_b1.user_id().unwrap(), None, cx)
1364        })
1365        .await
1366        .unwrap();
1367    executor.run_until_parked();
1368    assert!(incoming_call_b1.next().await.unwrap().is_some());
1369    assert!(incoming_call_b2.next().await.unwrap().is_some());
1370
1371    // User B disconnects all clients, causing user A to no longer see a pending call for them.
1372    server.forbid_connections();
1373    server.disconnect_client(client_b1.peer_id().unwrap());
1374    server.disconnect_client(client_b2.peer_id().unwrap());
1375    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
1376
1377    active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
1378}
1379
1380#[gpui::test(iterations = 10)]
1381async fn test_unshare_project(
1382    executor: BackgroundExecutor,
1383    cx_a: &mut TestAppContext,
1384    cx_b: &mut TestAppContext,
1385    cx_c: &mut TestAppContext,
1386) {
1387    let mut server = TestServer::start(executor.clone()).await;
1388    let client_a = server.create_client(cx_a, "user_a").await;
1389    let client_b = server.create_client(cx_b, "user_b").await;
1390    let client_c = server.create_client(cx_c, "user_c").await;
1391    server
1392        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1393        .await;
1394
1395    let active_call_a = cx_a.read(ActiveCall::global);
1396    let active_call_b = cx_b.read(ActiveCall::global);
1397
1398    client_a
1399        .fs()
1400        .insert_tree(
1401            "/a",
1402            json!({
1403                "a.txt": "a-contents",
1404                "b.txt": "b-contents",
1405            }),
1406        )
1407        .await;
1408
1409    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1410    let project_id = active_call_a
1411        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1412        .await
1413        .unwrap();
1414
1415    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
1416    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1417    executor.run_until_parked();
1418
1419    assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
1420
1421    project_b
1422        .update(cx_b, |p, cx| {
1423            p.open_buffer((worktree_id, rel_path("a.txt")), cx)
1424        })
1425        .await
1426        .unwrap();
1427
1428    // When client B leaves the room, the project becomes read-only.
1429    active_call_b
1430        .update(cx_b, |call, cx| call.hang_up(cx))
1431        .await
1432        .unwrap();
1433    executor.run_until_parked();
1434
1435    assert!(project_b.read_with(cx_b, |project, cx| project.is_disconnected(cx)));
1436
1437    // Client C opens the project.
1438    let project_c = client_c.join_remote_project(project_id, cx_c).await;
1439
1440    // When client A unshares the project, client C's project becomes read-only.
1441    project_a
1442        .update(cx_a, |project, cx| project.unshare(cx))
1443        .unwrap();
1444    executor.run_until_parked();
1445
1446    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.has_update_observer()));
1447
1448    assert!(project_c.read_with(cx_c, |project, cx| project.is_disconnected(cx)));
1449
1450    // Client C can open the project again after client A re-shares.
1451    let project_id = active_call_a
1452        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1453        .await
1454        .unwrap();
1455    let project_c2 = client_c.join_remote_project(project_id, cx_c).await;
1456    executor.run_until_parked();
1457
1458    assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
1459    project_c2
1460        .update(cx_c, |p, cx| {
1461            p.open_buffer((worktree_id, rel_path("a.txt")), cx)
1462        })
1463        .await
1464        .unwrap();
1465
1466    // When client A (the host) leaves the room, the project gets unshared and guests are notified.
1467    active_call_a
1468        .update(cx_a, |call, cx| call.hang_up(cx))
1469        .await
1470        .unwrap();
1471    executor.run_until_parked();
1472
1473    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
1474
1475    project_c2.read_with(cx_c, |project, cx| {
1476        assert!(project.is_disconnected(cx));
1477        assert!(project.collaborators().is_empty());
1478    });
1479}
1480
1481#[gpui::test(iterations = 10)]
1482async fn test_project_reconnect(
1483    executor: BackgroundExecutor,
1484    cx_a: &mut TestAppContext,
1485    cx_b: &mut TestAppContext,
1486) {
1487    let mut server = TestServer::start(executor.clone()).await;
1488    let client_a = server.create_client(cx_a, "user_a").await;
1489    let client_b = server.create_client(cx_b, "user_b").await;
1490    server
1491        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1492        .await;
1493
1494    cx_b.update(editor::init);
1495
1496    client_a
1497        .fs()
1498        .insert_tree(
1499            path!("/root-1"),
1500            json!({
1501                "dir1": {
1502                    "a.txt": "a",
1503                    "b.txt": "b",
1504                    "subdir1": {
1505                        "c.txt": "c",
1506                        "d.txt": "d",
1507                        "e.txt": "e",
1508                    }
1509                },
1510                "dir2": {
1511                    "v.txt": "v",
1512                },
1513                "dir3": {
1514                    "w.txt": "w",
1515                    "x.txt": "x",
1516                    "y.txt": "y",
1517                },
1518                "dir4": {
1519                    "z.txt": "z",
1520                },
1521            }),
1522        )
1523        .await;
1524    client_a
1525        .fs()
1526        .insert_tree(
1527            path!("/root-2"),
1528            json!({
1529                "2.txt": "2",
1530            }),
1531        )
1532        .await;
1533    client_a
1534        .fs()
1535        .insert_tree(
1536            path!("/root-3"),
1537            json!({
1538                "3.txt": "3",
1539            }),
1540        )
1541        .await;
1542
1543    let active_call_a = cx_a.read(ActiveCall::global);
1544    let (project_a1, _) = client_a
1545        .build_local_project(path!("/root-1/dir1"), cx_a)
1546        .await;
1547    let (project_a2, _) = client_a.build_local_project(path!("/root-2"), cx_a).await;
1548    let (project_a3, _) = client_a.build_local_project(path!("/root-3"), cx_a).await;
1549    let worktree_a1 =
1550        project_a1.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
1551    let project1_id = active_call_a
1552        .update(cx_a, |call, cx| call.share_project(project_a1.clone(), cx))
1553        .await
1554        .unwrap();
1555    let project2_id = active_call_a
1556        .update(cx_a, |call, cx| call.share_project(project_a2.clone(), cx))
1557        .await
1558        .unwrap();
1559    let project3_id = active_call_a
1560        .update(cx_a, |call, cx| call.share_project(project_a3.clone(), cx))
1561        .await
1562        .unwrap();
1563
1564    let project_b1 = client_b.join_remote_project(project1_id, cx_b).await;
1565    let project_b2 = client_b.join_remote_project(project2_id, cx_b).await;
1566    let project_b3 = client_b.join_remote_project(project3_id, cx_b).await;
1567    executor.run_until_parked();
1568
1569    let worktree1_id = worktree_a1.read_with(cx_a, |worktree, _| {
1570        assert!(worktree.has_update_observer());
1571        worktree.id()
1572    });
1573    let (worktree_a2, _) = project_a1
1574        .update(cx_a, |p, cx| {
1575            p.find_or_create_worktree(path!("/root-1/dir2"), true, cx)
1576        })
1577        .await
1578        .unwrap();
1579    executor.run_until_parked();
1580
1581    let worktree2_id = worktree_a2.read_with(cx_a, |tree, _| {
1582        assert!(tree.has_update_observer());
1583        tree.id()
1584    });
1585    executor.run_until_parked();
1586
1587    project_b1.read_with(cx_b, |project, cx| {
1588        assert!(project.worktree_for_id(worktree2_id, cx).is_some())
1589    });
1590
1591    let buffer_a1 = project_a1
1592        .update(cx_a, |p, cx| {
1593            p.open_buffer((worktree1_id, rel_path("a.txt")), cx)
1594        })
1595        .await
1596        .unwrap();
1597    let buffer_b1 = project_b1
1598        .update(cx_b, |p, cx| {
1599            p.open_buffer((worktree1_id, rel_path("a.txt")), cx)
1600        })
1601        .await
1602        .unwrap();
1603
1604    // Drop client A's connection.
1605    server.forbid_connections();
1606    server.disconnect_client(client_a.peer_id().unwrap());
1607    executor.advance_clock(RECEIVE_TIMEOUT);
1608
1609    project_a1.read_with(cx_a, |project, _| {
1610        assert!(project.is_shared());
1611        assert_eq!(project.collaborators().len(), 1);
1612    });
1613
1614    project_b1.read_with(cx_b, |project, cx| {
1615        assert!(!project.is_disconnected(cx));
1616        assert_eq!(project.collaborators().len(), 1);
1617    });
1618
1619    worktree_a1.read_with(cx_a, |tree, _| assert!(tree.has_update_observer()));
1620
1621    // While client A is disconnected, add and remove files from client A's project.
1622    client_a
1623        .fs()
1624        .insert_tree(
1625            path!("/root-1/dir1/subdir2"),
1626            json!({
1627                "f.txt": "f-contents",
1628                "g.txt": "g-contents",
1629                "h.txt": "h-contents",
1630                "i.txt": "i-contents",
1631            }),
1632        )
1633        .await;
1634    client_a
1635        .fs()
1636        .remove_dir(
1637            path!("/root-1/dir1/subdir1").as_ref(),
1638            RemoveOptions {
1639                recursive: true,
1640                ..Default::default()
1641            },
1642        )
1643        .await
1644        .unwrap();
1645
1646    // While client A is disconnected, add and remove worktrees from client A's project.
1647    project_a1.update(cx_a, |project, cx| {
1648        project.remove_worktree(worktree2_id, cx)
1649    });
1650    let (worktree_a3, _) = project_a1
1651        .update(cx_a, |p, cx| {
1652            p.find_or_create_worktree(path!("/root-1/dir3"), true, cx)
1653        })
1654        .await
1655        .unwrap();
1656    worktree_a3
1657        .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
1658        .await;
1659
1660    let worktree3_id = worktree_a3.read_with(cx_a, |tree, _| {
1661        assert!(!tree.has_update_observer());
1662        tree.id()
1663    });
1664    executor.run_until_parked();
1665
1666    // While client A is disconnected, close project 2
1667    cx_a.update(|_| drop(project_a2));
1668
1669    // While client A is disconnected, mutate a buffer on both the host and the guest.
1670    buffer_a1.update(cx_a, |buf, cx| buf.edit([(0..0, "W")], None, cx));
1671    buffer_b1.update(cx_b, |buf, cx| buf.edit([(1..1, "Z")], None, cx));
1672    executor.run_until_parked();
1673
1674    // Client A reconnects. Their project is re-shared, and client B re-joins it.
1675    server.allow_connections();
1676    client_a
1677        .connect(false, &cx_a.to_async())
1678        .await
1679        .into_response()
1680        .unwrap();
1681    executor.run_until_parked();
1682
1683    project_a1.read_with(cx_a, |project, cx| {
1684        assert!(project.is_shared());
1685        assert!(worktree_a1.read(cx).has_update_observer());
1686        assert_eq!(
1687            worktree_a1.read(cx).snapshot().paths().collect::<Vec<_>>(),
1688            vec![
1689                rel_path("a.txt"),
1690                rel_path("b.txt"),
1691                rel_path("subdir2"),
1692                rel_path("subdir2/f.txt"),
1693                rel_path("subdir2/g.txt"),
1694                rel_path("subdir2/h.txt"),
1695                rel_path("subdir2/i.txt")
1696            ]
1697        );
1698        assert!(worktree_a3.read(cx).has_update_observer());
1699        assert_eq!(
1700            worktree_a3.read(cx).snapshot().paths().collect::<Vec<_>>(),
1701            vec![rel_path("w.txt"), rel_path("x.txt"), rel_path("y.txt")]
1702        );
1703    });
1704
1705    project_b1.read_with(cx_b, |project, cx| {
1706        assert!(!project.is_disconnected(cx));
1707        assert_eq!(
1708            project
1709                .worktree_for_id(worktree1_id, cx)
1710                .unwrap()
1711                .read(cx)
1712                .snapshot()
1713                .paths()
1714                .collect::<Vec<_>>(),
1715            vec![
1716                rel_path("a.txt"),
1717                rel_path("b.txt"),
1718                rel_path("subdir2"),
1719                rel_path("subdir2/f.txt"),
1720                rel_path("subdir2/g.txt"),
1721                rel_path("subdir2/h.txt"),
1722                rel_path("subdir2/i.txt")
1723            ]
1724        );
1725        assert!(project.worktree_for_id(worktree2_id, cx).is_none());
1726        assert_eq!(
1727            project
1728                .worktree_for_id(worktree3_id, cx)
1729                .unwrap()
1730                .read(cx)
1731                .snapshot()
1732                .paths()
1733                .collect::<Vec<_>>(),
1734            vec![rel_path("w.txt"), rel_path("x.txt"), rel_path("y.txt")]
1735        );
1736    });
1737
1738    project_b2.read_with(cx_b, |project, cx| assert!(project.is_disconnected(cx)));
1739
1740    project_b3.read_with(cx_b, |project, cx| assert!(!project.is_disconnected(cx)));
1741
1742    buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WaZ"));
1743
1744    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "WaZ"));
1745
1746    // Drop client B's connection.
1747    server.forbid_connections();
1748    server.disconnect_client(client_b.peer_id().unwrap());
1749    executor.advance_clock(RECEIVE_TIMEOUT);
1750
1751    // While client B is disconnected, add and remove files from client A's project
1752    client_a
1753        .fs()
1754        .insert_file(path!("/root-1/dir1/subdir2/j.txt"), "j-contents".into())
1755        .await;
1756    client_a
1757        .fs()
1758        .remove_file(
1759            path!("/root-1/dir1/subdir2/i.txt").as_ref(),
1760            Default::default(),
1761        )
1762        .await
1763        .unwrap();
1764
1765    // While client B is disconnected, add and remove worktrees from client A's project.
1766    let (worktree_a4, _) = project_a1
1767        .update(cx_a, |p, cx| {
1768            p.find_or_create_worktree(path!("/root-1/dir4"), true, cx)
1769        })
1770        .await
1771        .unwrap();
1772    executor.run_until_parked();
1773
1774    let worktree4_id = worktree_a4.read_with(cx_a, |tree, _| {
1775        assert!(tree.has_update_observer());
1776        tree.id()
1777    });
1778    project_a1.update(cx_a, |project, cx| {
1779        project.remove_worktree(worktree3_id, cx)
1780    });
1781    executor.run_until_parked();
1782
1783    // While client B is disconnected, mutate a buffer on both the host and the guest.
1784    buffer_a1.update(cx_a, |buf, cx| buf.edit([(1..1, "X")], None, cx));
1785    buffer_b1.update(cx_b, |buf, cx| buf.edit([(2..2, "Y")], None, cx));
1786    executor.run_until_parked();
1787
1788    // While disconnected, close project 3
1789    cx_a.update(|_| drop(project_a3));
1790    executor.run_until_parked();
1791
1792    // Client B reconnects. They re-join the room and the remaining shared project.
1793    server.allow_connections();
1794    client_b
1795        .connect(false, &cx_b.to_async())
1796        .await
1797        .into_response()
1798        .unwrap();
1799    executor.run_until_parked();
1800
1801    project_b1.read_with(cx_b, |project, cx| {
1802        assert!(!project.is_disconnected(cx));
1803        assert_eq!(
1804            project
1805                .worktree_for_id(worktree1_id, cx)
1806                .unwrap()
1807                .read(cx)
1808                .snapshot()
1809                .paths()
1810                .collect::<Vec<_>>(),
1811            vec![
1812                rel_path("a.txt"),
1813                rel_path("b.txt"),
1814                rel_path("subdir2"),
1815                rel_path("subdir2/f.txt"),
1816                rel_path("subdir2/g.txt"),
1817                rel_path("subdir2/h.txt"),
1818                rel_path("subdir2/j.txt")
1819            ]
1820        );
1821        assert!(project.worktree_for_id(worktree2_id, cx).is_none());
1822        assert_eq!(
1823            project
1824                .worktree_for_id(worktree4_id, cx)
1825                .unwrap()
1826                .read(cx)
1827                .snapshot()
1828                .paths()
1829                .map(|p| p.as_unix_str())
1830                .collect::<Vec<_>>(),
1831            vec!["z.txt"]
1832        );
1833    });
1834
1835    project_b3.read_with(cx_b, |project, cx| assert!(project.is_disconnected(cx)));
1836
1837    buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WXaYZ"));
1838
1839    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "WXaYZ"));
1840}
1841
1842#[gpui::test(iterations = 10)]
1843async fn test_active_call_events(
1844    executor: BackgroundExecutor,
1845    cx_a: &mut TestAppContext,
1846    cx_b: &mut TestAppContext,
1847) {
1848    let mut server = TestServer::start(executor.clone()).await;
1849    let client_a = server.create_client(cx_a, "user_a").await;
1850    let client_b = server.create_client(cx_b, "user_b").await;
1851    client_a.fs().insert_tree("/a", json!({})).await;
1852    client_b.fs().insert_tree("/b", json!({})).await;
1853
1854    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
1855    let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
1856
1857    server
1858        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1859        .await;
1860    executor.run_until_parked();
1861
1862    let active_call_a = cx_a.read(ActiveCall::global);
1863    let active_call_b = cx_b.read(ActiveCall::global);
1864
1865    let events_a = active_call_events(cx_a);
1866    let events_b = active_call_events(cx_b);
1867
1868    let project_a_id = active_call_a
1869        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1870        .await
1871        .unwrap();
1872    executor.run_until_parked();
1873    assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
1874    assert_eq!(
1875        mem::take(&mut *events_b.borrow_mut()),
1876        vec![room::Event::RemoteProjectShared {
1877            owner: Arc::new(User {
1878                id: client_a.user_id().unwrap(),
1879                github_login: "user_a".into(),
1880                avatar_uri: "avatar_a".into(),
1881                name: None,
1882            }),
1883            project_id: project_a_id,
1884            worktree_root_names: vec!["a".to_string()],
1885        }]
1886    );
1887
1888    let project_b_id = active_call_b
1889        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1890        .await
1891        .unwrap();
1892    executor.run_until_parked();
1893    assert_eq!(
1894        mem::take(&mut *events_a.borrow_mut()),
1895        vec![room::Event::RemoteProjectShared {
1896            owner: Arc::new(User {
1897                id: client_b.user_id().unwrap(),
1898                github_login: "user_b".into(),
1899                avatar_uri: "avatar_b".into(),
1900                name: None,
1901            }),
1902            project_id: project_b_id,
1903            worktree_root_names: vec!["b".to_string()]
1904        }]
1905    );
1906    assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
1907
1908    // Sharing a project twice is idempotent.
1909    let project_b_id_2 = active_call_b
1910        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1911        .await
1912        .unwrap();
1913    assert_eq!(project_b_id_2, project_b_id);
1914    executor.run_until_parked();
1915    assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
1916    assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
1917
1918    // Unsharing a project should dispatch the RemoteProjectUnshared event.
1919    active_call_a
1920        .update(cx_a, |call, cx| call.hang_up(cx))
1921        .await
1922        .unwrap();
1923    executor.run_until_parked();
1924
1925    assert_eq!(
1926        mem::take(&mut *events_a.borrow_mut()),
1927        vec![room::Event::RoomLeft { channel_id: None }]
1928    );
1929    assert_eq!(
1930        mem::take(&mut *events_b.borrow_mut()),
1931        vec![room::Event::RemoteProjectUnshared {
1932            project_id: project_a_id,
1933        }]
1934    );
1935}
1936
1937fn active_call_events(cx: &mut TestAppContext) -> Rc<RefCell<Vec<room::Event>>> {
1938    let events = Rc::new(RefCell::new(Vec::new()));
1939    let active_call = cx.read(ActiveCall::global);
1940    cx.update({
1941        let events = events.clone();
1942        |cx| {
1943            cx.subscribe(&active_call, move |_, event, _| {
1944                events.borrow_mut().push(event.clone())
1945            })
1946            .detach()
1947        }
1948    });
1949    events
1950}
1951
1952#[gpui::test]
1953async fn test_mute_deafen(
1954    executor: BackgroundExecutor,
1955    cx_a: &mut TestAppContext,
1956    cx_b: &mut TestAppContext,
1957    cx_c: &mut TestAppContext,
1958) {
1959    let mut server = TestServer::start(executor.clone()).await;
1960    let client_a = server.create_client(cx_a, "user_a").await;
1961    let client_b = server.create_client(cx_b, "user_b").await;
1962    let client_c = server.create_client(cx_c, "user_c").await;
1963
1964    server
1965        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1966        .await;
1967
1968    let active_call_a = cx_a.read(ActiveCall::global);
1969    let active_call_b = cx_b.read(ActiveCall::global);
1970    let active_call_c = cx_c.read(ActiveCall::global);
1971
1972    // User A calls user B, B answers.
1973    active_call_a
1974        .update(cx_a, |call, cx| {
1975            call.invite(client_b.user_id().unwrap(), None, cx)
1976        })
1977        .await
1978        .unwrap();
1979    executor.run_until_parked();
1980    active_call_b
1981        .update(cx_b, |call, cx| call.accept_incoming(cx))
1982        .await
1983        .unwrap();
1984    executor.run_until_parked();
1985
1986    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
1987    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
1988
1989    room_a.read_with(cx_a, |room, _| assert!(!room.is_muted()));
1990    room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
1991
1992    // Users A and B are both unmuted.
1993    assert_eq!(
1994        participant_audio_state(&room_a, cx_a),
1995        &[ParticipantAudioState {
1996            user_id: client_b.user_id().unwrap(),
1997            is_muted: false,
1998            audio_tracks_playing: vec![true],
1999        }]
2000    );
2001    assert_eq!(
2002        participant_audio_state(&room_b, cx_b),
2003        &[ParticipantAudioState {
2004            user_id: client_a.user_id().unwrap(),
2005            is_muted: false,
2006            audio_tracks_playing: vec![true],
2007        }]
2008    );
2009
2010    // User A mutes
2011    room_a.update(cx_a, |room, cx| room.toggle_mute(cx));
2012    executor.run_until_parked();
2013
2014    // User A hears user B, but B doesn't hear A.
2015    room_a.read_with(cx_a, |room, _| assert!(room.is_muted()));
2016    room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
2017    assert_eq!(
2018        participant_audio_state(&room_a, cx_a),
2019        &[ParticipantAudioState {
2020            user_id: client_b.user_id().unwrap(),
2021            is_muted: false,
2022            audio_tracks_playing: vec![true],
2023        }]
2024    );
2025    assert_eq!(
2026        participant_audio_state(&room_b, cx_b),
2027        &[ParticipantAudioState {
2028            user_id: client_a.user_id().unwrap(),
2029            is_muted: true,
2030            audio_tracks_playing: vec![true],
2031        }]
2032    );
2033
2034    // User A deafens
2035    room_a.update(cx_a, |room, cx| room.toggle_deafen(cx));
2036    executor.run_until_parked();
2037
2038    // User A does not hear user B.
2039    room_a.read_with(cx_a, |room, _| assert!(room.is_muted()));
2040    room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
2041    assert_eq!(
2042        participant_audio_state(&room_a, cx_a),
2043        &[ParticipantAudioState {
2044            user_id: client_b.user_id().unwrap(),
2045            is_muted: false,
2046            audio_tracks_playing: vec![false],
2047        }]
2048    );
2049    assert_eq!(
2050        participant_audio_state(&room_b, cx_b),
2051        &[ParticipantAudioState {
2052            user_id: client_a.user_id().unwrap(),
2053            is_muted: true,
2054            audio_tracks_playing: vec![true],
2055        }]
2056    );
2057
2058    // User B calls user C, C joins.
2059    active_call_b
2060        .update(cx_b, |call, cx| {
2061            call.invite(client_c.user_id().unwrap(), None, cx)
2062        })
2063        .await
2064        .unwrap();
2065    executor.run_until_parked();
2066    active_call_c
2067        .update(cx_c, |call, cx| call.accept_incoming(cx))
2068        .await
2069        .unwrap();
2070    executor.run_until_parked();
2071
2072    // User A does not hear users B or C.
2073    assert_eq!(
2074        participant_audio_state(&room_a, cx_a),
2075        &[
2076            ParticipantAudioState {
2077                user_id: client_b.user_id().unwrap(),
2078                is_muted: false,
2079                audio_tracks_playing: vec![false],
2080            },
2081            ParticipantAudioState {
2082                user_id: client_c.user_id().unwrap(),
2083                is_muted: false,
2084                audio_tracks_playing: vec![false],
2085            }
2086        ]
2087    );
2088    assert_eq!(
2089        participant_audio_state(&room_b, cx_b),
2090        &[
2091            ParticipantAudioState {
2092                user_id: client_a.user_id().unwrap(),
2093                is_muted: true,
2094                audio_tracks_playing: vec![true],
2095            },
2096            ParticipantAudioState {
2097                user_id: client_c.user_id().unwrap(),
2098                is_muted: false,
2099                audio_tracks_playing: vec![true],
2100            }
2101        ]
2102    );
2103
2104    #[derive(PartialEq, Eq, Debug)]
2105    struct ParticipantAudioState {
2106        user_id: u64,
2107        is_muted: bool,
2108        audio_tracks_playing: Vec<bool>,
2109    }
2110
2111    fn participant_audio_state(
2112        room: &Entity<Room>,
2113        cx: &TestAppContext,
2114    ) -> Vec<ParticipantAudioState> {
2115        room.read_with(cx, |room, _| {
2116            room.remote_participants()
2117                .iter()
2118                .map(|(user_id, participant)| ParticipantAudioState {
2119                    user_id: *user_id,
2120                    is_muted: participant.muted,
2121                    audio_tracks_playing: participant
2122                        .audio_tracks
2123                        .values()
2124                        .map(|(track, _)| track.enabled())
2125                        .collect(),
2126                })
2127                .collect::<Vec<_>>()
2128        })
2129    }
2130}
2131
2132#[gpui::test(iterations = 10)]
2133async fn test_room_location(
2134    executor: BackgroundExecutor,
2135    cx_a: &mut TestAppContext,
2136    cx_b: &mut TestAppContext,
2137) {
2138    let mut server = TestServer::start(executor.clone()).await;
2139    let client_a = server.create_client(cx_a, "user_a").await;
2140    let client_b = server.create_client(cx_b, "user_b").await;
2141    client_a.fs().insert_tree("/a", json!({})).await;
2142    client_b.fs().insert_tree("/b", json!({})).await;
2143
2144    let active_call_a = cx_a.read(ActiveCall::global);
2145    let active_call_b = cx_b.read(ActiveCall::global);
2146
2147    let a_notified = Rc::new(Cell::new(false));
2148    cx_a.update({
2149        let notified = a_notified.clone();
2150        |cx| {
2151            cx.observe(&active_call_a, move |_, _| notified.set(true))
2152                .detach()
2153        }
2154    });
2155
2156    let b_notified = Rc::new(Cell::new(false));
2157    cx_b.update({
2158        let b_notified = b_notified.clone();
2159        |cx| {
2160            cx.observe(&active_call_b, move |_, _| b_notified.set(true))
2161                .detach()
2162        }
2163    });
2164
2165    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
2166    active_call_a
2167        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2168        .await
2169        .unwrap();
2170    let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
2171
2172    server
2173        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2174        .await;
2175
2176    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
2177
2178    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
2179    executor.run_until_parked();
2180    assert!(a_notified.take());
2181    assert_eq!(
2182        participant_locations(&room_a, cx_a),
2183        vec![("user_b".to_string(), ParticipantLocation::External)]
2184    );
2185    assert!(b_notified.take());
2186    assert_eq!(
2187        participant_locations(&room_b, cx_b),
2188        vec![("user_a".to_string(), ParticipantLocation::UnsharedProject)]
2189    );
2190
2191    let project_a_id = active_call_a
2192        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2193        .await
2194        .unwrap();
2195    executor.run_until_parked();
2196    assert!(a_notified.take());
2197    assert_eq!(
2198        participant_locations(&room_a, cx_a),
2199        vec![("user_b".to_string(), ParticipantLocation::External)]
2200    );
2201    assert!(b_notified.take());
2202    assert_eq!(
2203        participant_locations(&room_b, cx_b),
2204        vec![(
2205            "user_a".to_string(),
2206            ParticipantLocation::SharedProject {
2207                project_id: project_a_id
2208            }
2209        )]
2210    );
2211
2212    let project_b_id = active_call_b
2213        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
2214        .await
2215        .unwrap();
2216    executor.run_until_parked();
2217    assert!(a_notified.take());
2218    assert_eq!(
2219        participant_locations(&room_a, cx_a),
2220        vec![("user_b".to_string(), ParticipantLocation::External)]
2221    );
2222    assert!(b_notified.take());
2223    assert_eq!(
2224        participant_locations(&room_b, cx_b),
2225        vec![(
2226            "user_a".to_string(),
2227            ParticipantLocation::SharedProject {
2228                project_id: project_a_id
2229            }
2230        )]
2231    );
2232
2233    active_call_b
2234        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2235        .await
2236        .unwrap();
2237    executor.run_until_parked();
2238    assert!(a_notified.take());
2239    assert_eq!(
2240        participant_locations(&room_a, cx_a),
2241        vec![(
2242            "user_b".to_string(),
2243            ParticipantLocation::SharedProject {
2244                project_id: project_b_id
2245            }
2246        )]
2247    );
2248    assert!(b_notified.take());
2249    assert_eq!(
2250        participant_locations(&room_b, cx_b),
2251        vec![(
2252            "user_a".to_string(),
2253            ParticipantLocation::SharedProject {
2254                project_id: project_a_id
2255            }
2256        )]
2257    );
2258
2259    active_call_b
2260        .update(cx_b, |call, cx| call.set_location(None, cx))
2261        .await
2262        .unwrap();
2263    executor.run_until_parked();
2264    assert!(a_notified.take());
2265    assert_eq!(
2266        participant_locations(&room_a, cx_a),
2267        vec![("user_b".to_string(), ParticipantLocation::External)]
2268    );
2269    assert!(b_notified.take());
2270    assert_eq!(
2271        participant_locations(&room_b, cx_b),
2272        vec![(
2273            "user_a".to_string(),
2274            ParticipantLocation::SharedProject {
2275                project_id: project_a_id
2276            }
2277        )]
2278    );
2279
2280    fn participant_locations(
2281        room: &Entity<Room>,
2282        cx: &TestAppContext,
2283    ) -> Vec<(String, ParticipantLocation)> {
2284        room.read_with(cx, |room, _| {
2285            room.remote_participants()
2286                .values()
2287                .map(|participant| {
2288                    (
2289                        participant.user.github_login.to_string(),
2290                        participant.location,
2291                    )
2292                })
2293                .collect()
2294        })
2295    }
2296}
2297
2298#[gpui::test(iterations = 10)]
2299async fn test_propagate_saves_and_fs_changes(
2300    executor: BackgroundExecutor,
2301    cx_a: &mut TestAppContext,
2302    cx_b: &mut TestAppContext,
2303    cx_c: &mut TestAppContext,
2304) {
2305    let mut server = TestServer::start(executor.clone()).await;
2306    let client_a = server.create_client(cx_a, "user_a").await;
2307    let client_b = server.create_client(cx_b, "user_b").await;
2308    let client_c = server.create_client(cx_c, "user_c").await;
2309
2310    server
2311        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2312        .await;
2313    let active_call_a = cx_a.read(ActiveCall::global);
2314
2315    let rust = Arc::new(Language::new(
2316        LanguageConfig {
2317            name: "Rust".into(),
2318            matcher: LanguageMatcher {
2319                path_suffixes: vec!["rs".to_string()],
2320                ..Default::default()
2321            },
2322            ..Default::default()
2323        },
2324        Some(tree_sitter_rust::LANGUAGE.into()),
2325    ));
2326    let javascript = Arc::new(Language::new(
2327        LanguageConfig {
2328            name: "JavaScript".into(),
2329            matcher: LanguageMatcher {
2330                path_suffixes: vec!["js".to_string()],
2331                ..Default::default()
2332            },
2333            ..Default::default()
2334        },
2335        Some(tree_sitter_rust::LANGUAGE.into()),
2336    ));
2337    for client in [&client_a, &client_b, &client_c] {
2338        client.language_registry().add(rust.clone());
2339        client.language_registry().add(javascript.clone());
2340    }
2341
2342    client_a
2343        .fs()
2344        .insert_tree(
2345            path!("/a"),
2346            json!({
2347                "file1.rs": "",
2348                "file2": ""
2349            }),
2350        )
2351        .await;
2352    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2353
2354    let worktree_a = project_a.read_with(cx_a, |p, cx| p.worktrees(cx).next().unwrap());
2355    let project_id = active_call_a
2356        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2357        .await
2358        .unwrap();
2359
2360    // Join that worktree as clients B and C.
2361    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2362    let project_c = client_c.join_remote_project(project_id, cx_c).await;
2363
2364    let worktree_b = project_b.read_with(cx_b, |p, cx| p.worktrees(cx).next().unwrap());
2365
2366    let worktree_c = project_c.read_with(cx_c, |p, cx| p.worktrees(cx).next().unwrap());
2367
2368    // Open and edit a buffer as both guests B and C.
2369    let buffer_b = project_b
2370        .update(cx_b, |p, cx| {
2371            p.open_buffer((worktree_id, rel_path("file1.rs")), cx)
2372        })
2373        .await
2374        .unwrap();
2375    let buffer_c = project_c
2376        .update(cx_c, |p, cx| {
2377            p.open_buffer((worktree_id, rel_path("file1.rs")), cx)
2378        })
2379        .await
2380        .unwrap();
2381
2382    buffer_b.read_with(cx_b, |buffer, _| {
2383        assert_eq!(buffer.language().unwrap().name(), "Rust");
2384    });
2385
2386    buffer_c.read_with(cx_c, |buffer, _| {
2387        assert_eq!(buffer.language().unwrap().name(), "Rust");
2388    });
2389    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "i-am-b, ")], None, cx));
2390    buffer_c.update(cx_c, |buf, cx| buf.edit([(0..0, "i-am-c, ")], None, cx));
2391
2392    // Open and edit that buffer as the host.
2393    let buffer_a = project_a
2394        .update(cx_a, |p, cx| {
2395            p.open_buffer((worktree_id, rel_path("file1.rs")), cx)
2396        })
2397        .await
2398        .unwrap();
2399
2400    executor.run_until_parked();
2401
2402    buffer_a.read_with(cx_a, |buf, _| assert_eq!(buf.text(), "i-am-c, i-am-b, "));
2403    buffer_a.update(cx_a, |buf, cx| {
2404        buf.edit([(buf.len()..buf.len(), "i-am-a")], None, cx)
2405    });
2406
2407    executor.run_until_parked();
2408
2409    buffer_a.read_with(cx_a, |buf, _| {
2410        assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
2411    });
2412
2413    buffer_b.read_with(cx_b, |buf, _| {
2414        assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
2415    });
2416
2417    buffer_c.read_with(cx_c, |buf, _| {
2418        assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
2419    });
2420
2421    // Edit the buffer as the host and concurrently save as guest B.
2422    let save_b = project_b.update(cx_b, |project, cx| {
2423        project.save_buffer(buffer_b.clone(), cx)
2424    });
2425    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "hi-a, ")], None, cx));
2426    save_b.await.unwrap();
2427    assert_eq!(
2428        client_a.fs().load("/a/file1.rs".as_ref()).await.unwrap(),
2429        "hi-a, i-am-c, i-am-b, i-am-a"
2430    );
2431
2432    executor.run_until_parked();
2433
2434    buffer_a.read_with(cx_a, |buf, _| assert!(!buf.is_dirty()));
2435
2436    buffer_b.read_with(cx_b, |buf, _| assert!(!buf.is_dirty()));
2437
2438    buffer_c.read_with(cx_c, |buf, _| assert!(!buf.is_dirty()));
2439
2440    // Make changes on host's file system, see those changes on guest worktrees.
2441    client_a
2442        .fs()
2443        .rename(
2444            path!("/a/file1.rs").as_ref(),
2445            path!("/a/file1.js").as_ref(),
2446            Default::default(),
2447        )
2448        .await
2449        .unwrap();
2450    client_a
2451        .fs()
2452        .rename(
2453            path!("/a/file2").as_ref(),
2454            path!("/a/file3").as_ref(),
2455            Default::default(),
2456        )
2457        .await
2458        .unwrap();
2459    client_a
2460        .fs()
2461        .insert_file(path!("/a/file4"), "4".into())
2462        .await;
2463    executor.run_until_parked();
2464
2465    worktree_a.read_with(cx_a, |tree, _| {
2466        assert_eq!(
2467            tree.paths().collect::<Vec<_>>(),
2468            [rel_path("file1.js"), rel_path("file3"), rel_path("file4")]
2469        )
2470    });
2471
2472    worktree_b.read_with(cx_b, |tree, _| {
2473        assert_eq!(
2474            tree.paths().collect::<Vec<_>>(),
2475            [rel_path("file1.js"), rel_path("file3"), rel_path("file4")]
2476        )
2477    });
2478
2479    worktree_c.read_with(cx_c, |tree, _| {
2480        assert_eq!(
2481            tree.paths().collect::<Vec<_>>(),
2482            [rel_path("file1.js"), rel_path("file3"), rel_path("file4")]
2483        )
2484    });
2485
2486    // Ensure buffer files are updated as well.
2487
2488    buffer_a.read_with(cx_a, |buffer, _| {
2489        assert_eq!(buffer.file().unwrap().path().as_ref(), rel_path("file1.js"));
2490        assert_eq!(buffer.language().unwrap().name(), "JavaScript");
2491    });
2492
2493    buffer_b.read_with(cx_b, |buffer, _| {
2494        assert_eq!(buffer.file().unwrap().path().as_ref(), rel_path("file1.js"));
2495        assert_eq!(buffer.language().unwrap().name(), "JavaScript");
2496    });
2497
2498    buffer_c.read_with(cx_c, |buffer, _| {
2499        assert_eq!(buffer.file().unwrap().path().as_ref(), rel_path("file1.js"));
2500        assert_eq!(buffer.language().unwrap().name(), "JavaScript");
2501    });
2502
2503    let new_buffer_a = project_a
2504        .update(cx_a, |p, cx| p.create_buffer(None, false, cx))
2505        .await
2506        .unwrap();
2507
2508    let new_buffer_id = new_buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id());
2509    let new_buffer_b = project_b
2510        .update(cx_b, |p, cx| p.open_buffer_by_id(new_buffer_id, cx))
2511        .await
2512        .unwrap();
2513
2514    new_buffer_b.read_with(cx_b, |buffer, _| {
2515        assert!(buffer.file().is_none());
2516    });
2517
2518    new_buffer_a.update(cx_a, |buffer, cx| {
2519        buffer.edit([(0..0, "ok")], None, cx);
2520    });
2521    project_a
2522        .update(cx_a, |project, cx| {
2523            let path = ProjectPath {
2524                path: rel_path("file3.rs").into(),
2525                worktree_id: worktree_a.read(cx).id(),
2526            };
2527
2528            project.save_buffer_as(new_buffer_a.clone(), path, cx)
2529        })
2530        .await
2531        .unwrap();
2532
2533    executor.run_until_parked();
2534
2535    new_buffer_b.read_with(cx_b, |buffer_b, _| {
2536        assert_eq!(
2537            buffer_b.file().unwrap().path().as_ref(),
2538            rel_path("file3.rs")
2539        );
2540
2541        new_buffer_a.read_with(cx_a, |buffer_a, _| {
2542            assert_eq!(buffer_b.saved_mtime(), buffer_a.saved_mtime());
2543            assert_eq!(buffer_b.saved_version(), buffer_a.saved_version());
2544        });
2545    });
2546}
2547
2548#[gpui::test(iterations = 10)]
2549async fn test_git_diff_base_change(
2550    executor: BackgroundExecutor,
2551    cx_a: &mut TestAppContext,
2552    cx_b: &mut TestAppContext,
2553) {
2554    let mut server = TestServer::start(executor.clone()).await;
2555    let client_a = server.create_client(cx_a, "user_a").await;
2556    let client_b = server.create_client(cx_b, "user_b").await;
2557    server
2558        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2559        .await;
2560    let active_call_a = cx_a.read(ActiveCall::global);
2561
2562    client_a
2563        .fs()
2564        .insert_tree(
2565            "/dir",
2566            json!({
2567            ".git": {},
2568            "sub": {
2569                ".git": {},
2570                "b.txt": "
2571                    one
2572                    two
2573                    three
2574                ".unindent(),
2575            },
2576            "a.txt": "
2577                    one
2578                    two
2579                    three
2580                ".unindent(),
2581            }),
2582        )
2583        .await;
2584
2585    let (project_local, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2586    let project_id = active_call_a
2587        .update(cx_a, |call, cx| {
2588            call.share_project(project_local.clone(), cx)
2589        })
2590        .await
2591        .unwrap();
2592
2593    let project_remote = client_b.join_remote_project(project_id, cx_b).await;
2594
2595    let staged_text = "
2596        one
2597        three
2598    "
2599    .unindent();
2600
2601    let committed_text = "
2602        one
2603        TWO
2604        three
2605    "
2606    .unindent();
2607
2608    let new_committed_text = "
2609        one
2610        TWO_HUNDRED
2611        three
2612    "
2613    .unindent();
2614
2615    let new_staged_text = "
2616        one
2617        two
2618    "
2619    .unindent();
2620
2621    client_a
2622        .fs()
2623        .set_index_for_repo(Path::new("/dir/.git"), &[("a.txt", staged_text.clone())]);
2624    client_a.fs().set_head_for_repo(
2625        Path::new("/dir/.git"),
2626        &[("a.txt", committed_text.clone())],
2627        "deadbeef",
2628    );
2629
2630    // Create the buffer
2631    let buffer_local_a = project_local
2632        .update(cx_a, |p, cx| {
2633            p.open_buffer((worktree_id, rel_path("a.txt")), cx)
2634        })
2635        .await
2636        .unwrap();
2637    let local_unstaged_diff_a = project_local
2638        .update(cx_a, |p, cx| {
2639            p.open_unstaged_diff(buffer_local_a.clone(), cx)
2640        })
2641        .await
2642        .unwrap();
2643
2644    // Wait for it to catch up to the new diff
2645    executor.run_until_parked();
2646    local_unstaged_diff_a.read_with(cx_a, |diff, cx| {
2647        let buffer = buffer_local_a.read(cx);
2648        assert_eq!(
2649            diff.base_text_string(cx).as_deref(),
2650            Some(staged_text.as_str())
2651        );
2652        assert_hunks(
2653            diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
2654            buffer,
2655            &diff.base_text_string(cx).unwrap(),
2656            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2657        );
2658    });
2659
2660    // Create remote buffer
2661    let remote_buffer_a = project_remote
2662        .update(cx_b, |p, cx| {
2663            p.open_buffer((worktree_id, rel_path("a.txt")), cx)
2664        })
2665        .await
2666        .unwrap();
2667    let remote_unstaged_diff_a = project_remote
2668        .update(cx_b, |p, cx| {
2669            p.open_unstaged_diff(remote_buffer_a.clone(), cx)
2670        })
2671        .await
2672        .unwrap();
2673
2674    // Wait remote buffer to catch up to the new diff
2675    executor.run_until_parked();
2676    remote_unstaged_diff_a.read_with(cx_b, |diff, cx| {
2677        let buffer = remote_buffer_a.read(cx);
2678        assert_eq!(
2679            diff.base_text_string(cx).as_deref(),
2680            Some(staged_text.as_str())
2681        );
2682        assert_hunks(
2683            diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
2684            buffer,
2685            &diff.base_text_string(cx).unwrap(),
2686            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2687        );
2688    });
2689
2690    // Open uncommitted changes on the guest, without opening them on the host first
2691    let remote_uncommitted_diff_a = project_remote
2692        .update(cx_b, |p, cx| {
2693            p.open_uncommitted_diff(remote_buffer_a.clone(), cx)
2694        })
2695        .await
2696        .unwrap();
2697    executor.run_until_parked();
2698    remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| {
2699        let buffer = remote_buffer_a.read(cx);
2700        assert_eq!(
2701            diff.base_text_string(cx).as_deref(),
2702            Some(committed_text.as_str())
2703        );
2704        assert_hunks(
2705            diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
2706            buffer,
2707            &diff.base_text_string(cx).unwrap(),
2708            &[(
2709                1..2,
2710                "TWO\n",
2711                "two\n",
2712                DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
2713            )],
2714        );
2715    });
2716
2717    // Update the index text of the open buffer
2718    client_a.fs().set_index_for_repo(
2719        Path::new("/dir/.git"),
2720        &[("a.txt", new_staged_text.clone())],
2721    );
2722    client_a.fs().set_head_for_repo(
2723        Path::new("/dir/.git"),
2724        &[("a.txt", new_committed_text.clone())],
2725        "deadbeef",
2726    );
2727
2728    // Wait for buffer_local_a to receive it
2729    executor.run_until_parked();
2730    local_unstaged_diff_a.read_with(cx_a, |diff, cx| {
2731        let buffer = buffer_local_a.read(cx);
2732        assert_eq!(
2733            diff.base_text_string(cx).as_deref(),
2734            Some(new_staged_text.as_str())
2735        );
2736        assert_hunks(
2737            diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
2738            buffer,
2739            &diff.base_text_string(cx).unwrap(),
2740            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2741        );
2742    });
2743
2744    // Guest receives index text update
2745    remote_unstaged_diff_a.read_with(cx_b, |diff, cx| {
2746        let buffer = remote_buffer_a.read(cx);
2747        assert_eq!(
2748            diff.base_text_string(cx).as_deref(),
2749            Some(new_staged_text.as_str())
2750        );
2751        assert_hunks(
2752            diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
2753            buffer,
2754            &diff.base_text_string(cx).unwrap(),
2755            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2756        );
2757    });
2758
2759    remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| {
2760        let buffer = remote_buffer_a.read(cx);
2761        assert_eq!(
2762            diff.base_text_string(cx).as_deref(),
2763            Some(new_committed_text.as_str())
2764        );
2765        assert_hunks(
2766            diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
2767            buffer,
2768            &diff.base_text_string(cx).unwrap(),
2769            &[(
2770                1..2,
2771                "TWO_HUNDRED\n",
2772                "two\n",
2773                DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
2774            )],
2775        );
2776    });
2777
2778    // Nested git dir
2779    let staged_text = "
2780        one
2781        three
2782    "
2783    .unindent();
2784
2785    let new_staged_text = "
2786        one
2787        two
2788    "
2789    .unindent();
2790
2791    client_a.fs().set_index_for_repo(
2792        Path::new("/dir/sub/.git"),
2793        &[("b.txt", staged_text.clone())],
2794    );
2795
2796    // Create the buffer
2797    let buffer_local_b = project_local
2798        .update(cx_a, |p, cx| {
2799            p.open_buffer((worktree_id, rel_path("sub/b.txt")), cx)
2800        })
2801        .await
2802        .unwrap();
2803    let local_unstaged_diff_b = project_local
2804        .update(cx_a, |p, cx| {
2805            p.open_unstaged_diff(buffer_local_b.clone(), cx)
2806        })
2807        .await
2808        .unwrap();
2809
2810    // Wait for it to catch up to the new diff
2811    executor.run_until_parked();
2812    local_unstaged_diff_b.read_with(cx_a, |diff, cx| {
2813        let buffer = buffer_local_b.read(cx);
2814        assert_eq!(
2815            diff.base_text_string(cx).as_deref(),
2816            Some(staged_text.as_str())
2817        );
2818        assert_hunks(
2819            diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
2820            buffer,
2821            &diff.base_text_string(cx).unwrap(),
2822            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2823        );
2824    });
2825
2826    // Create remote buffer
2827    let remote_buffer_b = project_remote
2828        .update(cx_b, |p, cx| {
2829            p.open_buffer((worktree_id, rel_path("sub/b.txt")), cx)
2830        })
2831        .await
2832        .unwrap();
2833    let remote_unstaged_diff_b = project_remote
2834        .update(cx_b, |p, cx| {
2835            p.open_unstaged_diff(remote_buffer_b.clone(), cx)
2836        })
2837        .await
2838        .unwrap();
2839
2840    executor.run_until_parked();
2841    remote_unstaged_diff_b.read_with(cx_b, |diff, cx| {
2842        let buffer = remote_buffer_b.read(cx);
2843        assert_eq!(
2844            diff.base_text_string(cx).as_deref(),
2845            Some(staged_text.as_str())
2846        );
2847        assert_hunks(
2848            diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
2849            buffer,
2850            &staged_text,
2851            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2852        );
2853    });
2854
2855    // Updatet the staged text
2856    client_a.fs().set_index_for_repo(
2857        Path::new("/dir/sub/.git"),
2858        &[("b.txt", new_staged_text.clone())],
2859    );
2860
2861    // Wait for buffer_local_b to receive it
2862    executor.run_until_parked();
2863    local_unstaged_diff_b.read_with(cx_a, |diff, cx| {
2864        let buffer = buffer_local_b.read(cx);
2865        assert_eq!(
2866            diff.base_text_string(cx).as_deref(),
2867            Some(new_staged_text.as_str())
2868        );
2869        assert_hunks(
2870            diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
2871            buffer,
2872            &new_staged_text,
2873            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2874        );
2875    });
2876
2877    remote_unstaged_diff_b.read_with(cx_b, |diff, cx| {
2878        let buffer = remote_buffer_b.read(cx);
2879        assert_eq!(
2880            diff.base_text_string(cx).as_deref(),
2881            Some(new_staged_text.as_str())
2882        );
2883        assert_hunks(
2884            diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
2885            buffer,
2886            &new_staged_text,
2887            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2888        );
2889    });
2890}
2891
2892#[gpui::test(iterations = 10)]
2893async fn test_git_branch_name(
2894    executor: BackgroundExecutor,
2895    cx_a: &mut TestAppContext,
2896    cx_b: &mut TestAppContext,
2897    cx_c: &mut TestAppContext,
2898) {
2899    let mut server = TestServer::start(executor.clone()).await;
2900    let client_a = server.create_client(cx_a, "user_a").await;
2901    let client_b = server.create_client(cx_b, "user_b").await;
2902    let client_c = server.create_client(cx_c, "user_c").await;
2903    server
2904        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2905        .await;
2906    let active_call_a = cx_a.read(ActiveCall::global);
2907
2908    client_a
2909        .fs()
2910        .insert_tree(
2911            "/dir",
2912            json!({
2913            ".git": {},
2914            }),
2915        )
2916        .await;
2917
2918    let (project_local, _worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2919    let project_id = active_call_a
2920        .update(cx_a, |call, cx| {
2921            call.share_project(project_local.clone(), cx)
2922        })
2923        .await
2924        .unwrap();
2925
2926    let project_remote = client_b.join_remote_project(project_id, cx_b).await;
2927    client_a
2928        .fs()
2929        .set_branch_name(Path::new("/dir/.git"), Some("branch-1"));
2930
2931    // Wait for it to catch up to the new branch
2932    executor.run_until_parked();
2933
2934    #[track_caller]
2935    fn assert_branch(branch_name: Option<impl Into<String>>, project: &Project, cx: &App) {
2936        let branch_name = branch_name.map(Into::into);
2937        let repositories = project.repositories(cx).values().collect::<Vec<_>>();
2938        assert_eq!(repositories.len(), 1);
2939        let repository = repositories[0].clone();
2940        assert_eq!(
2941            repository
2942                .read(cx)
2943                .branch
2944                .as_ref()
2945                .map(|branch| branch.name().to_owned()),
2946            branch_name
2947        )
2948    }
2949
2950    // Smoke test branch reading
2951
2952    project_local.read_with(cx_a, |project, cx| {
2953        assert_branch(Some("branch-1"), project, cx)
2954    });
2955
2956    project_remote.read_with(cx_b, |project, cx| {
2957        assert_branch(Some("branch-1"), project, cx)
2958    });
2959
2960    client_a
2961        .fs()
2962        .set_branch_name(Path::new("/dir/.git"), Some("branch-2"));
2963
2964    // Wait for buffer_local_a to receive it
2965    executor.run_until_parked();
2966
2967    // Smoke test branch reading
2968
2969    project_local.read_with(cx_a, |project, cx| {
2970        assert_branch(Some("branch-2"), project, cx)
2971    });
2972
2973    project_remote.read_with(cx_b, |project, cx| {
2974        assert_branch(Some("branch-2"), project, cx)
2975    });
2976
2977    let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
2978    executor.run_until_parked();
2979
2980    project_remote_c.read_with(cx_c, |project, cx| {
2981        assert_branch(Some("branch-2"), project, cx)
2982    });
2983}
2984
2985#[gpui::test]
2986async fn test_git_status_sync(
2987    executor: BackgroundExecutor,
2988    cx_a: &mut TestAppContext,
2989    cx_b: &mut TestAppContext,
2990    cx_c: &mut TestAppContext,
2991) {
2992    let mut server = TestServer::start(executor.clone()).await;
2993    let client_a = server.create_client(cx_a, "user_a").await;
2994    let client_b = server.create_client(cx_b, "user_b").await;
2995    let client_c = server.create_client(cx_c, "user_c").await;
2996    server
2997        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2998        .await;
2999    let active_call_a = cx_a.read(ActiveCall::global);
3000
3001    client_a
3002        .fs()
3003        .insert_tree(
3004            path!("/dir"),
3005            json!({
3006                ".git": {},
3007                "a.txt": "a",
3008                "b.txt": "b",
3009                "c.txt": "c",
3010            }),
3011        )
3012        .await;
3013
3014    // Initially, a.txt is uncommitted, but present in the index,
3015    // and b.txt is unmerged.
3016    client_a.fs().set_head_for_repo(
3017        path!("/dir/.git").as_ref(),
3018        &[("b.txt", "B".into()), ("c.txt", "c".into())],
3019        "deadbeef",
3020    );
3021    client_a.fs().set_index_for_repo(
3022        path!("/dir/.git").as_ref(),
3023        &[
3024            ("a.txt", "".into()),
3025            ("b.txt", "B".into()),
3026            ("c.txt", "c".into()),
3027        ],
3028    );
3029    client_a.fs().set_unmerged_paths_for_repo(
3030        path!("/dir/.git").as_ref(),
3031        &[(
3032            repo_path("b.txt"),
3033            UnmergedStatus {
3034                first_head: UnmergedStatusCode::Updated,
3035                second_head: UnmergedStatusCode::Deleted,
3036            },
3037        )],
3038    );
3039
3040    const A_STATUS_START: FileStatus = FileStatus::Tracked(TrackedStatus {
3041        index_status: StatusCode::Added,
3042        worktree_status: StatusCode::Modified,
3043    });
3044    const B_STATUS_START: FileStatus = FileStatus::Unmerged(UnmergedStatus {
3045        first_head: UnmergedStatusCode::Updated,
3046        second_head: UnmergedStatusCode::Deleted,
3047    });
3048
3049    let (project_local, _worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3050    let project_id = active_call_a
3051        .update(cx_a, |call, cx| {
3052            call.share_project(project_local.clone(), cx)
3053        })
3054        .await
3055        .unwrap();
3056
3057    let project_remote = client_b.join_remote_project(project_id, cx_b).await;
3058
3059    // Wait for it to catch up to the new status
3060    executor.run_until_parked();
3061
3062    #[track_caller]
3063    fn assert_status(file: &str, status: Option<FileStatus>, project: &Project, cx: &App) {
3064        let file = repo_path(file);
3065        let repos = project
3066            .repositories(cx)
3067            .values()
3068            .cloned()
3069            .collect::<Vec<_>>();
3070        assert_eq!(repos.len(), 1);
3071        let repo = repos.into_iter().next().unwrap();
3072        assert_eq!(
3073            repo.read(cx)
3074                .status_for_path(&file)
3075                .map(|entry| entry.status),
3076            status
3077        );
3078    }
3079
3080    project_local.read_with(cx_a, |project, cx| {
3081        assert_status("a.txt", Some(A_STATUS_START), project, cx);
3082        assert_status("b.txt", Some(B_STATUS_START), project, cx);
3083        assert_status("c.txt", None, project, cx);
3084    });
3085
3086    project_remote.read_with(cx_b, |project, cx| {
3087        assert_status("a.txt", Some(A_STATUS_START), project, cx);
3088        assert_status("b.txt", Some(B_STATUS_START), project, cx);
3089        assert_status("c.txt", None, project, cx);
3090    });
3091
3092    const A_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3093        index_status: StatusCode::Added,
3094        worktree_status: StatusCode::Unmodified,
3095    });
3096    const B_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3097        index_status: StatusCode::Deleted,
3098        worktree_status: StatusCode::Added,
3099    });
3100    const C_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3101        index_status: StatusCode::Unmodified,
3102        worktree_status: StatusCode::Modified,
3103    });
3104
3105    // Delete b.txt from the index, mark conflict as resolved,
3106    // and modify c.txt in the working copy.
3107    client_a.fs().set_index_for_repo(
3108        path!("/dir/.git").as_ref(),
3109        &[("a.txt", "a".into()), ("c.txt", "c".into())],
3110    );
3111    client_a
3112        .fs()
3113        .set_unmerged_paths_for_repo(path!("/dir/.git").as_ref(), &[]);
3114    client_a
3115        .fs()
3116        .atomic_write(path!("/dir/c.txt").into(), "CC".into())
3117        .await
3118        .unwrap();
3119
3120    // Wait for buffer_local_a to receive it
3121    executor.run_until_parked();
3122
3123    // Smoke test status reading
3124    project_local.read_with(cx_a, |project, cx| {
3125        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3126        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3127        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3128    });
3129
3130    project_remote.read_with(cx_b, |project, cx| {
3131        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3132        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3133        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3134    });
3135
3136    // And synchronization while joining
3137    let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
3138    executor.run_until_parked();
3139
3140    project_remote_c.read_with(cx_c, |project, cx| {
3141        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3142        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3143        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3144    });
3145
3146    // Now remove the original git repository and check that collaborators are notified.
3147    client_a
3148        .fs()
3149        .remove_dir(path!("/dir/.git").as_ref(), RemoveOptions::default())
3150        .await
3151        .unwrap();
3152
3153    executor.run_until_parked();
3154    project_remote.update(cx_b, |project, cx| {
3155        pretty_assertions::assert_eq!(
3156            project.git_store().read(cx).repo_snapshots(cx),
3157            HashMap::default()
3158        );
3159    });
3160    project_remote_c.update(cx_c, |project, cx| {
3161        pretty_assertions::assert_eq!(
3162            project.git_store().read(cx).repo_snapshots(cx),
3163            HashMap::default()
3164        );
3165    });
3166}
3167
3168#[gpui::test(iterations = 10)]
3169async fn test_fs_operations(
3170    executor: BackgroundExecutor,
3171    cx_a: &mut TestAppContext,
3172    cx_b: &mut TestAppContext,
3173) {
3174    let mut server = TestServer::start(executor.clone()).await;
3175    let client_a = server.create_client(cx_a, "user_a").await;
3176    let client_b = server.create_client(cx_b, "user_b").await;
3177    server
3178        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3179        .await;
3180    let active_call_a = cx_a.read(ActiveCall::global);
3181
3182    client_a
3183        .fs()
3184        .insert_tree(
3185            path!("/dir"),
3186            json!({
3187                "a.txt": "a-contents",
3188                "b.txt": "b-contents",
3189            }),
3190        )
3191        .await;
3192    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3193    let project_id = active_call_a
3194        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3195        .await
3196        .unwrap();
3197    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3198
3199    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
3200    let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
3201
3202    let entry = project_b
3203        .update(cx_b, |project, cx| {
3204            project.create_entry((worktree_id, rel_path("c.txt")), false, cx)
3205        })
3206        .await
3207        .unwrap()
3208        .into_included()
3209        .unwrap();
3210
3211    worktree_a.read_with(cx_a, |worktree, _| {
3212        assert_eq!(
3213            worktree.paths().collect::<Vec<_>>(),
3214            [rel_path("a.txt"), rel_path("b.txt"), rel_path("c.txt")]
3215        );
3216    });
3217
3218    worktree_b.read_with(cx_b, |worktree, _| {
3219        assert_eq!(
3220            worktree.paths().collect::<Vec<_>>(),
3221            [rel_path("a.txt"), rel_path("b.txt"), rel_path("c.txt")]
3222        );
3223    });
3224
3225    project_b
3226        .update(cx_b, |project, cx| {
3227            project.rename_entry(entry.id, (worktree_id, rel_path("d.txt")).into(), cx)
3228        })
3229        .await
3230        .unwrap()
3231        .into_included()
3232        .unwrap();
3233
3234    worktree_a.read_with(cx_a, |worktree, _| {
3235        assert_eq!(
3236            worktree.paths().collect::<Vec<_>>(),
3237            [rel_path("a.txt"), rel_path("b.txt"), rel_path("d.txt")]
3238        );
3239    });
3240
3241    worktree_b.read_with(cx_b, |worktree, _| {
3242        assert_eq!(
3243            worktree
3244                .paths()
3245                .map(|p| p.as_unix_str())
3246                .collect::<Vec<_>>(),
3247            ["a.txt", "b.txt", "d.txt"]
3248        );
3249    });
3250
3251    let dir_entry = project_b
3252        .update(cx_b, |project, cx| {
3253            project.create_entry((worktree_id, rel_path("DIR")), true, cx)
3254        })
3255        .await
3256        .unwrap()
3257        .into_included()
3258        .unwrap();
3259
3260    worktree_a.read_with(cx_a, |worktree, _| {
3261        assert_eq!(
3262            worktree
3263                .paths()
3264                .map(|p| p.as_unix_str())
3265                .collect::<Vec<_>>(),
3266            ["DIR", "a.txt", "b.txt", "d.txt"]
3267        );
3268    });
3269
3270    worktree_b.read_with(cx_b, |worktree, _| {
3271        assert_eq!(
3272            worktree
3273                .paths()
3274                .map(|p| p.as_unix_str())
3275                .collect::<Vec<_>>(),
3276            ["DIR", "a.txt", "b.txt", "d.txt"]
3277        );
3278    });
3279
3280    project_b
3281        .update(cx_b, |project, cx| {
3282            project.create_entry((worktree_id, rel_path("DIR/e.txt")), false, cx)
3283        })
3284        .await
3285        .unwrap()
3286        .into_included()
3287        .unwrap();
3288
3289    project_b
3290        .update(cx_b, |project, cx| {
3291            project.create_entry((worktree_id, rel_path("DIR/SUBDIR")), true, cx)
3292        })
3293        .await
3294        .unwrap()
3295        .into_included()
3296        .unwrap();
3297
3298    project_b
3299        .update(cx_b, |project, cx| {
3300            project.create_entry((worktree_id, rel_path("DIR/SUBDIR/f.txt")), false, cx)
3301        })
3302        .await
3303        .unwrap()
3304        .into_included()
3305        .unwrap();
3306
3307    worktree_a.read_with(cx_a, |worktree, _| {
3308        assert_eq!(
3309            worktree.paths().collect::<Vec<_>>(),
3310            [
3311                rel_path("DIR"),
3312                rel_path("DIR/SUBDIR"),
3313                rel_path("DIR/SUBDIR/f.txt"),
3314                rel_path("DIR/e.txt"),
3315                rel_path("a.txt"),
3316                rel_path("b.txt"),
3317                rel_path("d.txt")
3318            ]
3319        );
3320    });
3321
3322    worktree_b.read_with(cx_b, |worktree, _| {
3323        assert_eq!(
3324            worktree.paths().collect::<Vec<_>>(),
3325            [
3326                rel_path("DIR"),
3327                rel_path("DIR/SUBDIR"),
3328                rel_path("DIR/SUBDIR/f.txt"),
3329                rel_path("DIR/e.txt"),
3330                rel_path("a.txt"),
3331                rel_path("b.txt"),
3332                rel_path("d.txt")
3333            ]
3334        );
3335    });
3336
3337    project_b
3338        .update(cx_b, |project, cx| {
3339            project.copy_entry(
3340                entry.id,
3341                (worktree_b.read(cx).id(), rel_path("f.txt")).into(),
3342                cx,
3343            )
3344        })
3345        .await
3346        .unwrap()
3347        .unwrap();
3348
3349    worktree_a.read_with(cx_a, |worktree, _| {
3350        assert_eq!(
3351            worktree.paths().collect::<Vec<_>>(),
3352            [
3353                rel_path("DIR"),
3354                rel_path("DIR/SUBDIR"),
3355                rel_path("DIR/SUBDIR/f.txt"),
3356                rel_path("DIR/e.txt"),
3357                rel_path("a.txt"),
3358                rel_path("b.txt"),
3359                rel_path("d.txt"),
3360                rel_path("f.txt")
3361            ]
3362        );
3363    });
3364
3365    worktree_b.read_with(cx_b, |worktree, _| {
3366        assert_eq!(
3367            worktree.paths().collect::<Vec<_>>(),
3368            [
3369                rel_path("DIR"),
3370                rel_path("DIR/SUBDIR"),
3371                rel_path("DIR/SUBDIR/f.txt"),
3372                rel_path("DIR/e.txt"),
3373                rel_path("a.txt"),
3374                rel_path("b.txt"),
3375                rel_path("d.txt"),
3376                rel_path("f.txt")
3377            ]
3378        );
3379    });
3380
3381    project_b
3382        .update(cx_b, |project, cx| {
3383            project.delete_entry(dir_entry.id, false, cx).unwrap()
3384        })
3385        .await
3386        .unwrap();
3387    executor.run_until_parked();
3388
3389    worktree_a.read_with(cx_a, |worktree, _| {
3390        assert_eq!(
3391            worktree
3392                .paths()
3393                .map(|p| p.as_unix_str())
3394                .collect::<Vec<_>>(),
3395            ["a.txt", "b.txt", "d.txt", "f.txt"]
3396        );
3397    });
3398
3399    worktree_b.read_with(cx_b, |worktree, _| {
3400        assert_eq!(
3401            worktree
3402                .paths()
3403                .map(|p| p.as_unix_str())
3404                .collect::<Vec<_>>(),
3405            ["a.txt", "b.txt", "d.txt", "f.txt"]
3406        );
3407    });
3408
3409    project_b
3410        .update(cx_b, |project, cx| {
3411            project.delete_entry(entry.id, false, cx).unwrap()
3412        })
3413        .await
3414        .unwrap();
3415
3416    worktree_a.read_with(cx_a, |worktree, _| {
3417        assert_eq!(
3418            worktree
3419                .paths()
3420                .map(|p| p.as_unix_str())
3421                .collect::<Vec<_>>(),
3422            ["a.txt", "b.txt", "f.txt"]
3423        );
3424    });
3425
3426    worktree_b.read_with(cx_b, |worktree, _| {
3427        assert_eq!(
3428            worktree
3429                .paths()
3430                .map(|p| p.as_unix_str())
3431                .collect::<Vec<_>>(),
3432            ["a.txt", "b.txt", "f.txt"]
3433        );
3434    });
3435}
3436
3437#[gpui::test(iterations = 10)]
3438async fn test_local_settings(
3439    executor: BackgroundExecutor,
3440    cx_a: &mut TestAppContext,
3441    cx_b: &mut TestAppContext,
3442) {
3443    let mut server = TestServer::start(executor.clone()).await;
3444    let client_a = server.create_client(cx_a, "user_a").await;
3445    let client_b = server.create_client(cx_b, "user_b").await;
3446    server
3447        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3448        .await;
3449    let active_call_a = cx_a.read(ActiveCall::global);
3450
3451    // As client A, open a project that contains some local settings files
3452    client_a
3453        .fs()
3454        .insert_tree(
3455            "/dir",
3456            json!({
3457                ".zed": {
3458                    "settings.json": r#"{ "tab_size": 2 }"#
3459                },
3460                "a": {
3461                    ".zed": {
3462                        "settings.json": r#"{ "tab_size": 8 }"#
3463                    },
3464                    "a.txt": "a-contents",
3465                },
3466                "b": {
3467                    "b.txt": "b-contents",
3468                }
3469            }),
3470        )
3471        .await;
3472    let (project_a, _) = client_a.build_local_project("/dir", cx_a).await;
3473    executor.run_until_parked();
3474    let project_id = active_call_a
3475        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3476        .await
3477        .unwrap();
3478    executor.run_until_parked();
3479
3480    // As client B, join that project and observe the local settings.
3481    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3482
3483    let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
3484    executor.run_until_parked();
3485    cx_b.read(|cx| {
3486        let store = cx.global::<SettingsStore>();
3487        assert_eq!(
3488            store
3489                .local_settings(worktree_b.read(cx).id())
3490                .map(|(path, content)| (
3491                    path,
3492                    content.all_languages.defaults.tab_size.map(Into::into)
3493                ))
3494                .collect::<Vec<_>>(),
3495            &[
3496                (rel_path("").into(), Some(2)),
3497                (rel_path("a").into(), Some(8)),
3498            ]
3499        )
3500    });
3501
3502    // As client A, update a settings file. As Client B, see the changed settings.
3503    client_a
3504        .fs()
3505        .insert_file("/dir/.zed/settings.json", r#"{}"#.into())
3506        .await;
3507    executor.run_until_parked();
3508    cx_b.read(|cx| {
3509        let store = cx.global::<SettingsStore>();
3510        assert_eq!(
3511            store
3512                .local_settings(worktree_b.read(cx).id())
3513                .map(|(path, content)| (
3514                    path,
3515                    content.all_languages.defaults.tab_size.map(Into::into)
3516                ))
3517                .collect::<Vec<_>>(),
3518            &[(rel_path("").into(), None), (rel_path("a").into(), Some(8)),]
3519        )
3520    });
3521
3522    // As client A, create and remove some settings files. As client B, see the changed settings.
3523    client_a
3524        .fs()
3525        .remove_file("/dir/.zed/settings.json".as_ref(), Default::default())
3526        .await
3527        .unwrap();
3528    client_a
3529        .fs()
3530        .create_dir("/dir/b/.zed".as_ref())
3531        .await
3532        .unwrap();
3533    client_a
3534        .fs()
3535        .insert_file("/dir/b/.zed/settings.json", r#"{"tab_size": 4}"#.into())
3536        .await;
3537    executor.run_until_parked();
3538    cx_b.read(|cx| {
3539        let store = cx.global::<SettingsStore>();
3540        assert_eq!(
3541            store
3542                .local_settings(worktree_b.read(cx).id())
3543                .map(|(path, content)| (
3544                    path,
3545                    content.all_languages.defaults.tab_size.map(Into::into)
3546                ))
3547                .collect::<Vec<_>>(),
3548            &[
3549                (rel_path("a").into(), Some(8)),
3550                (rel_path("b").into(), Some(4)),
3551            ]
3552        )
3553    });
3554
3555    // As client B, disconnect.
3556    server.forbid_connections();
3557    server.disconnect_client(client_b.peer_id().unwrap());
3558
3559    // As client A, change and remove settings files while client B is disconnected.
3560    client_a
3561        .fs()
3562        .insert_file("/dir/a/.zed/settings.json", r#"{"hard_tabs":true}"#.into())
3563        .await;
3564    client_a
3565        .fs()
3566        .remove_file("/dir/b/.zed/settings.json".as_ref(), Default::default())
3567        .await
3568        .unwrap();
3569    executor.run_until_parked();
3570
3571    // As client B, reconnect and see the changed settings.
3572    server.allow_connections();
3573    executor.advance_clock(RECEIVE_TIMEOUT);
3574    cx_b.read(|cx| {
3575        let store = cx.global::<SettingsStore>();
3576        assert_eq!(
3577            store
3578                .local_settings(worktree_b.read(cx).id())
3579                .map(|(path, content)| (path, content.all_languages.defaults.hard_tabs))
3580                .collect::<Vec<_>>(),
3581            &[(rel_path("a").into(), Some(true))],
3582        )
3583    });
3584}
3585
3586#[gpui::test(iterations = 10)]
3587async fn test_buffer_conflict_after_save(
3588    executor: BackgroundExecutor,
3589    cx_a: &mut TestAppContext,
3590    cx_b: &mut TestAppContext,
3591) {
3592    let mut server = TestServer::start(executor.clone()).await;
3593    let client_a = server.create_client(cx_a, "user_a").await;
3594    let client_b = server.create_client(cx_b, "user_b").await;
3595    server
3596        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3597        .await;
3598    let active_call_a = cx_a.read(ActiveCall::global);
3599
3600    client_a
3601        .fs()
3602        .insert_tree(
3603            path!("/dir"),
3604            json!({
3605                "a.txt": "a-contents",
3606            }),
3607        )
3608        .await;
3609    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3610    let project_id = active_call_a
3611        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3612        .await
3613        .unwrap();
3614    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3615
3616    // Open a buffer as client B
3617    let buffer_b = project_b
3618        .update(cx_b, |p, cx| {
3619            p.open_buffer((worktree_id, rel_path("a.txt")), cx)
3620        })
3621        .await
3622        .unwrap();
3623
3624    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "world ")], None, cx));
3625
3626    buffer_b.read_with(cx_b, |buf, _| {
3627        assert!(buf.is_dirty());
3628        assert!(!buf.has_conflict());
3629    });
3630
3631    project_b
3632        .update(cx_b, |project, cx| {
3633            project.save_buffer(buffer_b.clone(), cx)
3634        })
3635        .await
3636        .unwrap();
3637
3638    buffer_b.read_with(cx_b, |buffer_b, _| assert!(!buffer_b.is_dirty()));
3639
3640    buffer_b.read_with(cx_b, |buf, _| {
3641        assert!(!buf.has_conflict());
3642    });
3643
3644    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "hello ")], None, cx));
3645
3646    buffer_b.read_with(cx_b, |buf, _| {
3647        assert!(buf.is_dirty());
3648        assert!(!buf.has_conflict());
3649    });
3650}
3651
3652#[gpui::test(iterations = 10)]
3653async fn test_buffer_reloading(
3654    executor: BackgroundExecutor,
3655    cx_a: &mut TestAppContext,
3656    cx_b: &mut TestAppContext,
3657) {
3658    let mut server = TestServer::start(executor.clone()).await;
3659    let client_a = server.create_client(cx_a, "user_a").await;
3660    let client_b = server.create_client(cx_b, "user_b").await;
3661    server
3662        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3663        .await;
3664    let active_call_a = cx_a.read(ActiveCall::global);
3665
3666    client_a
3667        .fs()
3668        .insert_tree(
3669            path!("/dir"),
3670            json!({
3671                "a.txt": "a\nb\nc",
3672            }),
3673        )
3674        .await;
3675    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3676    let project_id = active_call_a
3677        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3678        .await
3679        .unwrap();
3680    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3681
3682    // Open a buffer as client B
3683    let buffer_b = project_b
3684        .update(cx_b, |p, cx| {
3685            p.open_buffer((worktree_id, rel_path("a.txt")), cx)
3686        })
3687        .await
3688        .unwrap();
3689
3690    buffer_b.read_with(cx_b, |buf, _| {
3691        assert!(!buf.is_dirty());
3692        assert!(!buf.has_conflict());
3693        assert_eq!(buf.line_ending(), LineEnding::Unix);
3694    });
3695
3696    let new_contents = Rope::from("d\ne\nf");
3697    client_a
3698        .fs()
3699        .save(
3700            path!("/dir/a.txt").as_ref(),
3701            &new_contents,
3702            LineEnding::Windows,
3703        )
3704        .await
3705        .unwrap();
3706
3707    executor.run_until_parked();
3708
3709    buffer_b.read_with(cx_b, |buf, _| {
3710        assert_eq!(buf.text(), new_contents.to_string());
3711        assert!(!buf.is_dirty());
3712        assert!(!buf.has_conflict());
3713        assert_eq!(buf.line_ending(), LineEnding::Windows);
3714    });
3715}
3716
3717#[gpui::test(iterations = 10)]
3718async fn test_editing_while_guest_opens_buffer(
3719    executor: BackgroundExecutor,
3720    cx_a: &mut TestAppContext,
3721    cx_b: &mut TestAppContext,
3722) {
3723    let mut server = TestServer::start(executor.clone()).await;
3724    let client_a = server.create_client(cx_a, "user_a").await;
3725    let client_b = server.create_client(cx_b, "user_b").await;
3726    server
3727        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3728        .await;
3729    let active_call_a = cx_a.read(ActiveCall::global);
3730
3731    client_a
3732        .fs()
3733        .insert_tree(path!("/dir"), json!({ "a.txt": "a-contents" }))
3734        .await;
3735    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3736    let project_id = active_call_a
3737        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3738        .await
3739        .unwrap();
3740    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3741
3742    // Open a buffer as client A
3743    let buffer_a = project_a
3744        .update(cx_a, |p, cx| {
3745            p.open_buffer((worktree_id, rel_path("a.txt")), cx)
3746        })
3747        .await
3748        .unwrap();
3749
3750    // Start opening the same buffer as client B
3751    let open_buffer = project_b.update(cx_b, |p, cx| {
3752        p.open_buffer((worktree_id, rel_path("a.txt")), cx)
3753    });
3754    let buffer_b = cx_b.executor().spawn(open_buffer);
3755
3756    // Edit the buffer as client A while client B is still opening it.
3757    cx_b.executor().simulate_random_delay().await;
3758    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "X")], None, cx));
3759    cx_b.executor().simulate_random_delay().await;
3760    buffer_a.update(cx_a, |buf, cx| buf.edit([(1..1, "Y")], None, cx));
3761
3762    let text = buffer_a.read_with(cx_a, |buf, _| buf.text());
3763    let buffer_b = buffer_b.await.unwrap();
3764    executor.run_until_parked();
3765
3766    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), text));
3767}
3768
3769#[gpui::test(iterations = 10)]
3770async fn test_leaving_worktree_while_opening_buffer(
3771    executor: BackgroundExecutor,
3772    cx_a: &mut TestAppContext,
3773    cx_b: &mut TestAppContext,
3774) {
3775    let mut server = TestServer::start(executor.clone()).await;
3776    let client_a = server.create_client(cx_a, "user_a").await;
3777    let client_b = server.create_client(cx_b, "user_b").await;
3778    server
3779        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3780        .await;
3781    let active_call_a = cx_a.read(ActiveCall::global);
3782
3783    client_a
3784        .fs()
3785        .insert_tree("/dir", json!({ "a.txt": "a-contents" }))
3786        .await;
3787    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3788    let project_id = active_call_a
3789        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3790        .await
3791        .unwrap();
3792    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3793
3794    // See that a guest has joined as client A.
3795    executor.run_until_parked();
3796
3797    project_a.read_with(cx_a, |p, _| assert_eq!(p.collaborators().len(), 1));
3798
3799    // Begin opening a buffer as client B, but leave the project before the open completes.
3800    let open_buffer = project_b.update(cx_b, |p, cx| {
3801        p.open_buffer((worktree_id, rel_path("a.txt")), cx)
3802    });
3803    let buffer_b = cx_b.executor().spawn(open_buffer);
3804    cx_b.update(|_| drop(project_b));
3805    drop(buffer_b);
3806
3807    // See that the guest has left.
3808    executor.run_until_parked();
3809
3810    project_a.read_with(cx_a, |p, _| assert!(p.collaborators().is_empty()));
3811}
3812
3813#[gpui::test(iterations = 10)]
3814async fn test_canceling_buffer_opening(
3815    executor: BackgroundExecutor,
3816    cx_a: &mut TestAppContext,
3817    cx_b: &mut TestAppContext,
3818) {
3819    let mut server = TestServer::start(executor.clone()).await;
3820    let client_a = server.create_client(cx_a, "user_a").await;
3821    let client_b = server.create_client(cx_b, "user_b").await;
3822    server
3823        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3824        .await;
3825    let active_call_a = cx_a.read(ActiveCall::global);
3826
3827    client_a
3828        .fs()
3829        .insert_tree(
3830            "/dir",
3831            json!({
3832                "a.txt": "abc",
3833            }),
3834        )
3835        .await;
3836    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3837    let project_id = active_call_a
3838        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3839        .await
3840        .unwrap();
3841    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3842
3843    let buffer_a = project_a
3844        .update(cx_a, |p, cx| {
3845            p.open_buffer((worktree_id, rel_path("a.txt")), cx)
3846        })
3847        .await
3848        .unwrap();
3849
3850    // Open a buffer as client B but cancel after a random amount of time.
3851    let buffer_b = project_b.update(cx_b, |p, cx| {
3852        p.open_buffer_by_id(buffer_a.read_with(cx_a, |a, _| a.remote_id()), cx)
3853    });
3854    executor.simulate_random_delay().await;
3855    drop(buffer_b);
3856
3857    // Try opening the same buffer again as client B, and ensure we can
3858    // still do it despite the cancellation above.
3859    let buffer_b = project_b
3860        .update(cx_b, |p, cx| {
3861            p.open_buffer_by_id(buffer_a.read_with(cx_a, |a, _| a.remote_id()), cx)
3862        })
3863        .await
3864        .unwrap();
3865
3866    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "abc"));
3867}
3868
3869#[gpui::test(iterations = 10)]
3870async fn test_leaving_project(
3871    executor: BackgroundExecutor,
3872    cx_a: &mut TestAppContext,
3873    cx_b: &mut TestAppContext,
3874    cx_c: &mut TestAppContext,
3875) {
3876    let mut server = TestServer::start(executor.clone()).await;
3877    let client_a = server.create_client(cx_a, "user_a").await;
3878    let client_b = server.create_client(cx_b, "user_b").await;
3879    let client_c = server.create_client(cx_c, "user_c").await;
3880    server
3881        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
3882        .await;
3883    let active_call_a = cx_a.read(ActiveCall::global);
3884
3885    client_a
3886        .fs()
3887        .insert_tree(
3888            "/a",
3889            json!({
3890                "a.txt": "a-contents",
3891                "b.txt": "b-contents",
3892            }),
3893        )
3894        .await;
3895    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
3896    let project_id = active_call_a
3897        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3898        .await
3899        .unwrap();
3900    let project_b1 = client_b.join_remote_project(project_id, cx_b).await;
3901    let project_c = client_c.join_remote_project(project_id, cx_c).await;
3902
3903    // Client A sees that a guest has joined.
3904    executor.run_until_parked();
3905
3906    project_a.read_with(cx_a, |project, _| {
3907        assert_eq!(project.collaborators().len(), 2);
3908    });
3909
3910    project_b1.read_with(cx_b, |project, _| {
3911        assert_eq!(project.collaborators().len(), 2);
3912    });
3913
3914    project_c.read_with(cx_c, |project, _| {
3915        assert_eq!(project.collaborators().len(), 2);
3916    });
3917
3918    // Client B opens a buffer.
3919    let buffer_b1 = project_b1
3920        .update(cx_b, |project, cx| {
3921            let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
3922            project.open_buffer((worktree_id, rel_path("a.txt")), cx)
3923        })
3924        .await
3925        .unwrap();
3926
3927    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
3928
3929    // Drop client B's project and ensure client A and client C observe client B leaving.
3930    cx_b.update(|_| drop(project_b1));
3931    executor.run_until_parked();
3932
3933    project_a.read_with(cx_a, |project, _| {
3934        assert_eq!(project.collaborators().len(), 1);
3935    });
3936
3937    project_c.read_with(cx_c, |project, _| {
3938        assert_eq!(project.collaborators().len(), 1);
3939    });
3940
3941    // Client B re-joins the project and can open buffers as before.
3942    let project_b2 = client_b.join_remote_project(project_id, cx_b).await;
3943    executor.run_until_parked();
3944
3945    project_a.read_with(cx_a, |project, _| {
3946        assert_eq!(project.collaborators().len(), 2);
3947    });
3948
3949    project_b2.read_with(cx_b, |project, _| {
3950        assert_eq!(project.collaborators().len(), 2);
3951    });
3952
3953    project_c.read_with(cx_c, |project, _| {
3954        assert_eq!(project.collaborators().len(), 2);
3955    });
3956
3957    let buffer_b2 = project_b2
3958        .update(cx_b, |project, cx| {
3959            let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
3960            project.open_buffer((worktree_id, rel_path("a.txt")), cx)
3961        })
3962        .await
3963        .unwrap();
3964
3965    buffer_b2.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
3966
3967    project_a.read_with(cx_a, |project, _| {
3968        assert_eq!(project.collaborators().len(), 2);
3969    });
3970
3971    // Drop client B's connection and ensure client A and client C observe client B leaving.
3972    client_b.disconnect(&cx_b.to_async());
3973    executor.advance_clock(RECONNECT_TIMEOUT);
3974
3975    project_a.read_with(cx_a, |project, _| {
3976        assert_eq!(project.collaborators().len(), 1);
3977    });
3978
3979    project_b2.read_with(cx_b, |project, cx| {
3980        assert!(project.is_disconnected(cx));
3981    });
3982
3983    project_c.read_with(cx_c, |project, _| {
3984        assert_eq!(project.collaborators().len(), 1);
3985    });
3986
3987    // Client B can't join the project, unless they re-join the room.
3988    cx_b.spawn(|cx| {
3989        Project::in_room(
3990            project_id,
3991            client_b.app_state.client.clone(),
3992            client_b.user_store().clone(),
3993            client_b.language_registry().clone(),
3994            FakeFs::new(cx.background_executor().clone()),
3995            cx,
3996        )
3997    })
3998    .await
3999    .unwrap_err();
4000
4001    // Simulate connection loss for client C and ensure client A observes client C leaving the project.
4002    client_c.wait_for_current_user(cx_c).await;
4003    server.forbid_connections();
4004    server.disconnect_client(client_c.peer_id().unwrap());
4005    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
4006    executor.run_until_parked();
4007
4008    project_a.read_with(cx_a, |project, _| {
4009        assert_eq!(project.collaborators().len(), 0);
4010    });
4011
4012    project_b2.read_with(cx_b, |project, cx| {
4013        assert!(project.is_disconnected(cx));
4014    });
4015
4016    project_c.read_with(cx_c, |project, cx| {
4017        assert!(project.is_disconnected(cx));
4018    });
4019}
4020
4021#[gpui::test(iterations = 10)]
4022async fn test_collaborating_with_diagnostics(
4023    executor: BackgroundExecutor,
4024    cx_a: &mut TestAppContext,
4025    cx_b: &mut TestAppContext,
4026    cx_c: &mut TestAppContext,
4027) {
4028    let mut server = TestServer::start(executor.clone()).await;
4029    let client_a = server.create_client(cx_a, "user_a").await;
4030    let client_b = server.create_client(cx_b, "user_b").await;
4031    let client_c = server.create_client(cx_c, "user_c").await;
4032    server
4033        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
4034        .await;
4035    let active_call_a = cx_a.read(ActiveCall::global);
4036
4037    client_a.language_registry().add(Arc::new(Language::new(
4038        LanguageConfig {
4039            name: "Rust".into(),
4040            matcher: LanguageMatcher {
4041                path_suffixes: vec!["rs".to_string()],
4042                ..Default::default()
4043            },
4044            ..Default::default()
4045        },
4046        Some(tree_sitter_rust::LANGUAGE.into()),
4047    )));
4048    let mut fake_language_servers = client_a
4049        .language_registry()
4050        .register_fake_lsp("Rust", Default::default());
4051
4052    // Share a project as client A
4053    client_a
4054        .fs()
4055        .insert_tree(
4056            path!("/a"),
4057            json!({
4058                "a.rs": "let one = two",
4059                "other.rs": "",
4060            }),
4061        )
4062        .await;
4063    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4064
4065    // Cause the language server to start.
4066    let _buffer = project_a
4067        .update(cx_a, |project, cx| {
4068            project.open_local_buffer_with_lsp(path!("/a/other.rs"), cx)
4069        })
4070        .await
4071        .unwrap();
4072
4073    // Simulate a language server reporting errors for a file.
4074    let mut fake_language_server = fake_language_servers.next().await.unwrap();
4075    fake_language_server
4076        .receive_notification::<lsp::notification::DidOpenTextDocument>()
4077        .await;
4078    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4079        lsp::PublishDiagnosticsParams {
4080            uri: lsp::Uri::from_file_path(path!("/a/a.rs")).unwrap(),
4081            version: None,
4082            diagnostics: vec![lsp::Diagnostic {
4083                severity: Some(lsp::DiagnosticSeverity::WARNING),
4084                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4085                message: "message 0".to_string(),
4086                ..Default::default()
4087            }],
4088        },
4089    );
4090
4091    // Client A shares the project and, simultaneously, the language server
4092    // publishes a diagnostic. This is done to ensure that the server always
4093    // observes the latest diagnostics for a worktree.
4094    let project_id = active_call_a
4095        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4096        .await
4097        .unwrap();
4098    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4099        lsp::PublishDiagnosticsParams {
4100            uri: lsp::Uri::from_file_path(path!("/a/a.rs")).unwrap(),
4101            version: None,
4102            diagnostics: vec![lsp::Diagnostic {
4103                severity: Some(lsp::DiagnosticSeverity::ERROR),
4104                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4105                message: "message 1".to_string(),
4106                ..Default::default()
4107            }],
4108        },
4109    );
4110
4111    // Join the worktree as client B.
4112    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4113
4114    // Wait for server to see the diagnostics update.
4115    executor.run_until_parked();
4116
4117    // Ensure client B observes the new diagnostics.
4118
4119    project_b.read_with(cx_b, |project, cx| {
4120        assert_eq!(
4121            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4122            &[(
4123                ProjectPath {
4124                    worktree_id,
4125                    path: rel_path("a.rs").into(),
4126                },
4127                LanguageServerId(0),
4128                DiagnosticSummary {
4129                    error_count: 1,
4130                    warning_count: 0,
4131                },
4132            )]
4133        )
4134    });
4135
4136    // Join project as client C and observe the diagnostics.
4137    let project_c = client_c.join_remote_project(project_id, cx_c).await;
4138    executor.run_until_parked();
4139    let project_c_diagnostic_summaries =
4140        Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
4141            project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
4142        })));
4143    project_c.update(cx_c, |_, cx| {
4144        let summaries = project_c_diagnostic_summaries.clone();
4145        cx.subscribe(&project_c, {
4146            move |p, _, event, cx| {
4147                if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
4148                    *summaries.borrow_mut() = p.diagnostic_summaries(false, cx).collect();
4149                }
4150            }
4151        })
4152        .detach();
4153    });
4154
4155    executor.run_until_parked();
4156    assert_eq!(
4157        project_c_diagnostic_summaries.borrow().as_slice(),
4158        &[(
4159            ProjectPath {
4160                worktree_id,
4161                path: rel_path("a.rs").into(),
4162            },
4163            LanguageServerId(0),
4164            DiagnosticSummary {
4165                error_count: 1,
4166                warning_count: 0,
4167            },
4168        )]
4169    );
4170
4171    // Simulate a language server reporting more errors for a file.
4172    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4173        lsp::PublishDiagnosticsParams {
4174            uri: lsp::Uri::from_file_path(path!("/a/a.rs")).unwrap(),
4175            version: None,
4176            diagnostics: vec![
4177                lsp::Diagnostic {
4178                    severity: Some(lsp::DiagnosticSeverity::ERROR),
4179                    range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4180                    message: "message 1".to_string(),
4181                    ..Default::default()
4182                },
4183                lsp::Diagnostic {
4184                    severity: Some(lsp::DiagnosticSeverity::WARNING),
4185                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 13)),
4186                    message: "message 2".to_string(),
4187                    ..Default::default()
4188                },
4189            ],
4190        },
4191    );
4192
4193    // Clients B and C get the updated summaries
4194    executor.run_until_parked();
4195
4196    project_b.read_with(cx_b, |project, cx| {
4197        assert_eq!(
4198            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4199            [(
4200                ProjectPath {
4201                    worktree_id,
4202                    path: rel_path("a.rs").into(),
4203                },
4204                LanguageServerId(0),
4205                DiagnosticSummary {
4206                    error_count: 1,
4207                    warning_count: 1,
4208                },
4209            )]
4210        );
4211    });
4212
4213    project_c.read_with(cx_c, |project, cx| {
4214        assert_eq!(
4215            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4216            [(
4217                ProjectPath {
4218                    worktree_id,
4219                    path: rel_path("a.rs").into(),
4220                },
4221                LanguageServerId(0),
4222                DiagnosticSummary {
4223                    error_count: 1,
4224                    warning_count: 1,
4225                },
4226            )]
4227        );
4228    });
4229
4230    // Open the file with the errors on client B. They should be present.
4231    let open_buffer = project_b.update(cx_b, |p, cx| {
4232        p.open_buffer((worktree_id, rel_path("a.rs")), cx)
4233    });
4234    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
4235
4236    buffer_b.read_with(cx_b, |buffer, _| {
4237        assert_eq!(
4238            buffer
4239                .snapshot()
4240                .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
4241                .collect::<Vec<_>>(),
4242            &[
4243                DiagnosticEntry {
4244                    range: Point::new(0, 4)..Point::new(0, 7),
4245                    diagnostic: Diagnostic {
4246                        group_id: 2,
4247                        message: "message 1".to_string(),
4248                        severity: lsp::DiagnosticSeverity::ERROR,
4249                        is_primary: true,
4250                        source_kind: DiagnosticSourceKind::Pushed,
4251                        ..Diagnostic::default()
4252                    }
4253                },
4254                DiagnosticEntry {
4255                    range: Point::new(0, 10)..Point::new(0, 13),
4256                    diagnostic: Diagnostic {
4257                        group_id: 3,
4258                        severity: lsp::DiagnosticSeverity::WARNING,
4259                        message: "message 2".to_string(),
4260                        is_primary: true,
4261                        source_kind: DiagnosticSourceKind::Pushed,
4262                        ..Diagnostic::default()
4263                    }
4264                }
4265            ]
4266        );
4267    });
4268
4269    // Simulate a language server reporting no errors for a file.
4270    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4271        lsp::PublishDiagnosticsParams {
4272            uri: lsp::Uri::from_file_path(path!("/a/a.rs")).unwrap(),
4273            version: None,
4274            diagnostics: Vec::new(),
4275        },
4276    );
4277    executor.run_until_parked();
4278
4279    project_a.read_with(cx_a, |project, cx| {
4280        assert_eq!(
4281            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4282            []
4283        )
4284    });
4285
4286    project_b.read_with(cx_b, |project, cx| {
4287        assert_eq!(
4288            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4289            []
4290        )
4291    });
4292
4293    project_c.read_with(cx_c, |project, cx| {
4294        assert_eq!(
4295            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4296            []
4297        )
4298    });
4299}
4300
4301#[gpui::test(iterations = 10)]
4302async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
4303    executor: BackgroundExecutor,
4304    cx_a: &mut TestAppContext,
4305    cx_b: &mut TestAppContext,
4306) {
4307    let mut server = TestServer::start(executor.clone()).await;
4308    let client_a = server.create_client(cx_a, "user_a").await;
4309    let client_b = server.create_client(cx_b, "user_b").await;
4310    server
4311        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4312        .await;
4313
4314    client_a.language_registry().add(rust_lang());
4315    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4316        "Rust",
4317        FakeLspAdapter {
4318            disk_based_diagnostics_progress_token: Some("the-disk-based-token".into()),
4319            disk_based_diagnostics_sources: vec!["the-disk-based-diagnostics-source".into()],
4320            ..Default::default()
4321        },
4322    );
4323
4324    let file_names = &["one.rs", "two.rs", "three.rs", "four.rs", "five.rs"];
4325    client_a
4326        .fs()
4327        .insert_tree(
4328            path!("/test"),
4329            json!({
4330                "one.rs": "const ONE: usize = 1;",
4331                "two.rs": "const TWO: usize = 2;",
4332                "three.rs": "const THREE: usize = 3;",
4333                "four.rs": "const FOUR: usize = 3;",
4334                "five.rs": "const FIVE: usize = 3;",
4335            }),
4336        )
4337        .await;
4338
4339    let (project_a, worktree_id) = client_a.build_local_project(path!("/test"), cx_a).await;
4340
4341    // Share a project as client A
4342    let active_call_a = cx_a.read(ActiveCall::global);
4343    let project_id = active_call_a
4344        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4345        .await
4346        .unwrap();
4347
4348    // Join the project as client B and open all three files.
4349    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4350    let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
4351        project_b.update(cx_b, |p, cx| {
4352            p.open_buffer_with_lsp((worktree_id, rel_path(file_name)), cx)
4353        })
4354    }))
4355    .await
4356    .unwrap();
4357
4358    // Simulate a language server reporting errors for a file.
4359    let fake_language_server = fake_language_servers.next().await.unwrap();
4360    executor.run_until_parked();
4361    fake_language_server
4362        .request::<lsp::request::WorkDoneProgressCreate>(
4363            lsp::WorkDoneProgressCreateParams {
4364                token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4365            },
4366            DEFAULT_LSP_REQUEST_TIMEOUT,
4367        )
4368        .await
4369        .into_response()
4370        .unwrap();
4371    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
4372        token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4373        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
4374            lsp::WorkDoneProgressBegin {
4375                title: "Progress Began".into(),
4376                ..Default::default()
4377            },
4378        )),
4379    });
4380    for file_name in file_names {
4381        fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4382            lsp::PublishDiagnosticsParams {
4383                uri: lsp::Uri::from_file_path(Path::new(path!("/test")).join(file_name)).unwrap(),
4384                version: None,
4385                diagnostics: vec![lsp::Diagnostic {
4386                    severity: Some(lsp::DiagnosticSeverity::WARNING),
4387                    source: Some("the-disk-based-diagnostics-source".into()),
4388                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
4389                    message: "message one".to_string(),
4390                    ..Default::default()
4391                }],
4392            },
4393        );
4394    }
4395    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
4396        token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4397        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
4398            lsp::WorkDoneProgressEnd { message: None },
4399        )),
4400    });
4401
4402    // When the "disk base diagnostics finished" message is received, the buffers'
4403    // diagnostics are expected to be present.
4404    let disk_based_diagnostics_finished = Arc::new(AtomicBool::new(false));
4405    project_b.update(cx_b, {
4406        let project_b = project_b.clone();
4407        let disk_based_diagnostics_finished = disk_based_diagnostics_finished.clone();
4408        move |_, cx| {
4409            cx.subscribe(&project_b, move |_, _, event, cx| {
4410                if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
4411                    disk_based_diagnostics_finished.store(true, SeqCst);
4412                    for (buffer, _) in &guest_buffers {
4413                        assert_eq!(
4414                            buffer
4415                                .read(cx)
4416                                .snapshot()
4417                                .diagnostics_in_range::<_, usize>(0..5, false)
4418                                .count(),
4419                            1,
4420                            "expected a diagnostic for buffer {:?}",
4421                            buffer.read(cx).file().unwrap().path(),
4422                        );
4423                    }
4424                }
4425            })
4426            .detach();
4427        }
4428    });
4429
4430    executor.run_until_parked();
4431    assert!(disk_based_diagnostics_finished.load(SeqCst));
4432}
4433
4434#[gpui::test(iterations = 10)]
4435async fn test_reloading_buffer_manually(
4436    executor: BackgroundExecutor,
4437    cx_a: &mut TestAppContext,
4438    cx_b: &mut TestAppContext,
4439) {
4440    let mut server = TestServer::start(executor.clone()).await;
4441    let client_a = server.create_client(cx_a, "user_a").await;
4442    let client_b = server.create_client(cx_b, "user_b").await;
4443    server
4444        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4445        .await;
4446    let active_call_a = cx_a.read(ActiveCall::global);
4447
4448    client_a
4449        .fs()
4450        .insert_tree(path!("/a"), json!({ "a.rs": "let one = 1;" }))
4451        .await;
4452    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4453    let buffer_a = project_a
4454        .update(cx_a, |p, cx| {
4455            p.open_buffer((worktree_id, rel_path("a.rs")), cx)
4456        })
4457        .await
4458        .unwrap();
4459    let project_id = active_call_a
4460        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4461        .await
4462        .unwrap();
4463
4464    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4465
4466    let open_buffer = project_b.update(cx_b, |p, cx| {
4467        p.open_buffer((worktree_id, rel_path("a.rs")), cx)
4468    });
4469    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
4470    buffer_b.update(cx_b, |buffer, cx| {
4471        buffer.edit([(4..7, "six")], None, cx);
4472        buffer.edit([(10..11, "6")], None, cx);
4473        assert_eq!(buffer.text(), "let six = 6;");
4474        assert!(buffer.is_dirty());
4475        assert!(!buffer.has_conflict());
4476    });
4477    executor.run_until_parked();
4478
4479    buffer_a.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "let six = 6;"));
4480
4481    client_a
4482        .fs()
4483        .save(
4484            path!("/a/a.rs").as_ref(),
4485            &Rope::from("let seven = 7;"),
4486            LineEnding::Unix,
4487        )
4488        .await
4489        .unwrap();
4490    executor.run_until_parked();
4491
4492    buffer_a.read_with(cx_a, |buffer, _| assert!(buffer.has_conflict()));
4493
4494    buffer_b.read_with(cx_b, |buffer, _| assert!(buffer.has_conflict()));
4495
4496    project_b
4497        .update(cx_b, |project, cx| {
4498            project.reload_buffers(HashSet::from_iter([buffer_b.clone()]), true, cx)
4499        })
4500        .await
4501        .unwrap();
4502
4503    buffer_a.read_with(cx_a, |buffer, _| {
4504        assert_eq!(buffer.text(), "let seven = 7;");
4505        assert!(!buffer.is_dirty());
4506        assert!(!buffer.has_conflict());
4507    });
4508
4509    buffer_b.read_with(cx_b, |buffer, _| {
4510        assert_eq!(buffer.text(), "let seven = 7;");
4511        assert!(!buffer.is_dirty());
4512        assert!(!buffer.has_conflict());
4513    });
4514
4515    buffer_a.update(cx_a, |buffer, cx| {
4516        // Undoing on the host is a no-op when the reload was initiated by the guest.
4517        buffer.undo(cx);
4518        assert_eq!(buffer.text(), "let seven = 7;");
4519        assert!(!buffer.is_dirty());
4520        assert!(!buffer.has_conflict());
4521    });
4522    buffer_b.update(cx_b, |buffer, cx| {
4523        // Undoing on the guest rolls back the buffer to before it was reloaded but the conflict gets cleared.
4524        buffer.undo(cx);
4525        assert_eq!(buffer.text(), "let six = 6;");
4526        assert!(buffer.is_dirty());
4527        assert!(!buffer.has_conflict());
4528    });
4529}
4530
4531#[gpui::test(iterations = 10)]
4532async fn test_formatting_buffer(
4533    executor: BackgroundExecutor,
4534    cx_a: &mut TestAppContext,
4535    cx_b: &mut TestAppContext,
4536) {
4537    let mut server = TestServer::start(executor.clone()).await;
4538    let client_a = server.create_client(cx_a, "user_a").await;
4539    let client_b = server.create_client(cx_b, "user_b").await;
4540    server
4541        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4542        .await;
4543    let active_call_a = cx_a.read(ActiveCall::global);
4544
4545    client_a.language_registry().add(rust_lang());
4546    let mut fake_language_servers = client_a
4547        .language_registry()
4548        .register_fake_lsp("Rust", FakeLspAdapter::default());
4549
4550    // Here we insert a fake tree with a directory that exists on disk. This is needed
4551    // because later we'll invoke a command, which requires passing a working directory
4552    // that points to a valid location on disk.
4553    let directory = env::current_dir().unwrap();
4554    client_a
4555        .fs()
4556        .insert_tree(&directory, json!({ "a.rs": "let one = \"two\"" }))
4557        .await;
4558    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
4559    let project_id = active_call_a
4560        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4561        .await
4562        .unwrap();
4563    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4564
4565    let buffer_b = project_b
4566        .update(cx_b, |p, cx| {
4567            p.open_buffer((worktree_id, rel_path("a.rs")), cx)
4568        })
4569        .await
4570        .unwrap();
4571
4572    let _handle = project_b.update(cx_b, |project, cx| {
4573        project.register_buffer_with_language_servers(&buffer_b, cx)
4574    });
4575    let fake_language_server = fake_language_servers.next().await.unwrap();
4576    executor.run_until_parked();
4577    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(|_, _| async move {
4578        Ok(Some(vec![
4579            lsp::TextEdit {
4580                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
4581                new_text: "h".to_string(),
4582            },
4583            lsp::TextEdit {
4584                range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
4585                new_text: "y".to_string(),
4586            },
4587        ]))
4588    });
4589
4590    project_b
4591        .update(cx_b, |project, cx| {
4592            project.format(
4593                HashSet::from_iter([buffer_b.clone()]),
4594                LspFormatTarget::Buffers,
4595                true,
4596                FormatTrigger::Save,
4597                cx,
4598            )
4599        })
4600        .await
4601        .unwrap();
4602
4603    // The edits from the LSP are applied, and a final newline is added.
4604    assert_eq!(
4605        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4606        "let honey = \"two\"\n"
4607    );
4608
4609    // There is no `awk` command on Windows.
4610    #[cfg(not(target_os = "windows"))]
4611    {
4612        // Ensure buffer can be formatted using an external command. Notice how the
4613        // host's configuration is honored as opposed to using the guest's settings.
4614        cx_a.update(|cx| {
4615            SettingsStore::update_global(cx, |store, cx| {
4616                store.update_user_settings(cx, |file| {
4617                    file.project.all_languages.defaults.formatter =
4618                        Some(FormatterList::Single(Formatter::External {
4619                            command: "awk".into(),
4620                            arguments: Some(vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()]),
4621                        }));
4622                });
4623            });
4624        });
4625
4626        executor.allow_parking();
4627        project_b
4628            .update(cx_b, |project, cx| {
4629                project.format(
4630                    HashSet::from_iter([buffer_b.clone()]),
4631                    LspFormatTarget::Buffers,
4632                    true,
4633                    FormatTrigger::Save,
4634                    cx,
4635                )
4636            })
4637            .await
4638            .unwrap();
4639        assert_eq!(
4640            buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4641            format!("let honey = \"{}/a.rs\"\n", directory.to_str().unwrap())
4642        );
4643    }
4644}
4645
4646#[gpui::test(iterations = 10)]
4647async fn test_range_formatting_buffer(
4648    executor: BackgroundExecutor,
4649    cx_a: &mut TestAppContext,
4650    cx_b: &mut TestAppContext,
4651) {
4652    let mut server = TestServer::start(executor.clone()).await;
4653    let client_a = server.create_client(cx_a, "user_a").await;
4654    let client_b = server.create_client(cx_b, "user_b").await;
4655    server
4656        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4657        .await;
4658    let active_call_a = cx_a.read(ActiveCall::global);
4659
4660    client_a.language_registry().add(rust_lang());
4661    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4662        "Rust",
4663        FakeLspAdapter {
4664            capabilities: lsp::ServerCapabilities {
4665                document_range_formatting_provider: Some(OneOf::Left(true)),
4666                ..Default::default()
4667            },
4668            ..Default::default()
4669        },
4670    );
4671
4672    let directory = env::current_dir().unwrap();
4673    client_a
4674        .fs()
4675        .insert_tree(&directory, json!({ "a.rs": "one\ntwo\nthree\n" }))
4676        .await;
4677    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
4678    let project_id = active_call_a
4679        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4680        .await
4681        .unwrap();
4682    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4683
4684    let buffer_b = project_b
4685        .update(cx_b, |p, cx| {
4686            p.open_buffer((worktree_id, rel_path("a.rs")), cx)
4687        })
4688        .await
4689        .unwrap();
4690
4691    let _handle = project_b.update(cx_b, |project, cx| {
4692        project.register_buffer_with_language_servers(&buffer_b, cx)
4693    });
4694    let fake_language_server = fake_language_servers.next().await.unwrap();
4695    executor.run_until_parked();
4696
4697    fake_language_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
4698        |params, _| async move {
4699            assert_eq!(params.range.start, lsp::Position::new(0, 0));
4700            assert_eq!(params.range.end, lsp::Position::new(1, 3));
4701            Ok(Some(vec![lsp::TextEdit::new(
4702                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4703                ", ".to_string(),
4704            )]))
4705        },
4706    );
4707
4708    let buffer_id = buffer_b.read_with(cx_b, |buffer, _| buffer.remote_id());
4709    let ranges = buffer_b.read_with(cx_b, |buffer, _| {
4710        let start = buffer.anchor_before(0);
4711        let end = buffer.anchor_after(7);
4712        vec![start..end]
4713    });
4714
4715    let mut ranges_map = BTreeMap::new();
4716    ranges_map.insert(buffer_id, ranges);
4717
4718    project_b
4719        .update(cx_b, |project, cx| {
4720            project.format(
4721                HashSet::from_iter([buffer_b.clone()]),
4722                LspFormatTarget::Ranges(ranges_map),
4723                true,
4724                FormatTrigger::Manual,
4725                cx,
4726            )
4727        })
4728        .await
4729        .unwrap();
4730
4731    assert_eq!(
4732        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4733        "one, two\nthree\n"
4734    );
4735}
4736
4737#[gpui::test(iterations = 10)]
4738async fn test_prettier_formatting_buffer(
4739    executor: BackgroundExecutor,
4740    cx_a: &mut TestAppContext,
4741    cx_b: &mut TestAppContext,
4742) {
4743    let mut server = TestServer::start(executor.clone()).await;
4744    let client_a = server.create_client(cx_a, "user_a").await;
4745    let client_b = server.create_client(cx_b, "user_b").await;
4746    server
4747        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4748        .await;
4749    let active_call_a = cx_a.read(ActiveCall::global);
4750
4751    let test_plugin = "test_plugin";
4752
4753    client_a.language_registry().add(Arc::new(Language::new(
4754        LanguageConfig {
4755            name: "TypeScript".into(),
4756            matcher: LanguageMatcher {
4757                path_suffixes: vec!["ts".to_string()],
4758                ..Default::default()
4759            },
4760            ..Default::default()
4761        },
4762        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
4763    )));
4764    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4765        "TypeScript",
4766        FakeLspAdapter {
4767            prettier_plugins: vec![test_plugin],
4768            ..Default::default()
4769        },
4770    );
4771
4772    // Here we insert a fake tree with a directory that exists on disk. This is needed
4773    // because later we'll invoke a command, which requires passing a working directory
4774    // that points to a valid location on disk.
4775    let directory = env::current_dir().unwrap();
4776    let buffer_text = "let one = \"two\"";
4777    client_a
4778        .fs()
4779        .insert_tree(&directory, json!({ "a.ts": buffer_text }))
4780        .await;
4781    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
4782    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
4783    let open_buffer = project_a.update(cx_a, |p, cx| {
4784        p.open_buffer((worktree_id, rel_path("a.ts")), cx)
4785    });
4786    let buffer_a = cx_a.executor().spawn(open_buffer).await.unwrap();
4787
4788    let project_id = active_call_a
4789        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4790        .await
4791        .unwrap();
4792    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4793    let (buffer_b, _) = project_b
4794        .update(cx_b, |p, cx| {
4795            p.open_buffer_with_lsp((worktree_id, rel_path("a.ts")), cx)
4796        })
4797        .await
4798        .unwrap();
4799
4800    cx_a.update(|cx| {
4801        SettingsStore::update_global(cx, |store, cx| {
4802            store.update_user_settings(cx, |file| {
4803                file.project.all_languages.defaults.formatter = Some(FormatterList::default());
4804                file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
4805                    allowed: Some(true),
4806                    ..Default::default()
4807                });
4808            });
4809        });
4810    });
4811    cx_b.update(|cx| {
4812        SettingsStore::update_global(cx, |store, cx| {
4813            store.update_user_settings(cx, |file| {
4814                file.project.all_languages.defaults.formatter = Some(FormatterList::Single(
4815                    Formatter::LanguageServer(LanguageServerFormatterSpecifier::Current),
4816                ));
4817                file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
4818                    allowed: Some(true),
4819                    ..Default::default()
4820                });
4821            });
4822        });
4823    });
4824    let fake_language_server = fake_language_servers.next().await.unwrap();
4825    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(|_, _| async move {
4826        panic!(
4827            "Unexpected: prettier should be preferred since it's enabled and language supports it"
4828        )
4829    });
4830
4831    project_b
4832        .update(cx_b, |project, cx| {
4833            project.format(
4834                HashSet::from_iter([buffer_b.clone()]),
4835                LspFormatTarget::Buffers,
4836                true,
4837                FormatTrigger::Save,
4838                cx,
4839            )
4840        })
4841        .await
4842        .unwrap();
4843
4844    executor.run_until_parked();
4845    assert_eq!(
4846        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4847        buffer_text.to_string() + "\n" + prettier_format_suffix,
4848        "Prettier formatting was not applied to client buffer after client's request"
4849    );
4850
4851    project_a
4852        .update(cx_a, |project, cx| {
4853            project.format(
4854                HashSet::from_iter([buffer_a.clone()]),
4855                LspFormatTarget::Buffers,
4856                true,
4857                FormatTrigger::Manual,
4858                cx,
4859            )
4860        })
4861        .await
4862        .unwrap();
4863
4864    executor.run_until_parked();
4865    assert_eq!(
4866        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4867        buffer_text.to_string() + "\n" + prettier_format_suffix + "\n" + prettier_format_suffix,
4868        "Prettier formatting was not applied to client buffer after host's request"
4869    );
4870}
4871
4872#[gpui::test(iterations = 10)]
4873async fn test_definition(
4874    executor: BackgroundExecutor,
4875    cx_a: &mut TestAppContext,
4876    cx_b: &mut TestAppContext,
4877) {
4878    let mut server = TestServer::start(executor.clone()).await;
4879    let client_a = server.create_client(cx_a, "user_a").await;
4880    let client_b = server.create_client(cx_b, "user_b").await;
4881    server
4882        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4883        .await;
4884    let active_call_a = cx_a.read(ActiveCall::global);
4885
4886    let capabilities = lsp::ServerCapabilities {
4887        definition_provider: Some(OneOf::Left(true)),
4888        type_definition_provider: Some(lsp::TypeDefinitionProviderCapability::Simple(true)),
4889        ..lsp::ServerCapabilities::default()
4890    };
4891    client_a.language_registry().add(rust_lang());
4892    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4893        "Rust",
4894        FakeLspAdapter {
4895            capabilities: capabilities.clone(),
4896            ..FakeLspAdapter::default()
4897        },
4898    );
4899    client_b.language_registry().add(rust_lang());
4900    client_b.language_registry().register_fake_lsp_adapter(
4901        "Rust",
4902        FakeLspAdapter {
4903            capabilities,
4904            ..FakeLspAdapter::default()
4905        },
4906    );
4907
4908    client_a
4909        .fs()
4910        .insert_tree(
4911            path!("/root"),
4912            json!({
4913                "dir-1": {
4914                    "a.rs": "const ONE: usize = b::TWO + b::THREE;",
4915                },
4916                "dir-2": {
4917                    "b.rs": "const TWO: c::T2 = 2;\nconst THREE: usize = 3;",
4918                    "c.rs": "type T2 = usize;",
4919                }
4920            }),
4921        )
4922        .await;
4923    let (project_a, worktree_id) = client_a
4924        .build_local_project(path!("/root/dir-1"), cx_a)
4925        .await;
4926    let project_id = active_call_a
4927        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4928        .await
4929        .unwrap();
4930    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4931
4932    // Open the file on client B.
4933    let (buffer_b, _handle) = project_b
4934        .update(cx_b, |p, cx| {
4935            p.open_buffer_with_lsp((worktree_id, rel_path("a.rs")), cx)
4936        })
4937        .await
4938        .unwrap();
4939
4940    // Request the definition of a symbol as the guest.
4941    let fake_language_server = fake_language_servers.next().await.unwrap();
4942    fake_language_server.set_request_handler::<lsp::request::GotoDefinition, _, _>(
4943        |_, _| async move {
4944            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
4945                lsp::Location::new(
4946                    lsp::Uri::from_file_path(path!("/root/dir-2/b.rs")).unwrap(),
4947                    lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
4948                ),
4949            )))
4950        },
4951    );
4952    cx_a.run_until_parked();
4953    cx_b.run_until_parked();
4954
4955    let definitions_1 = project_b
4956        .update(cx_b, |p, cx| p.definitions(&buffer_b, 23, cx))
4957        .await
4958        .unwrap()
4959        .unwrap();
4960    cx_b.read(|cx| {
4961        assert_eq!(
4962            definitions_1.len(),
4963            1,
4964            "Unexpected definitions: {definitions_1:?}"
4965        );
4966        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
4967        let target_buffer = definitions_1[0].target.buffer.read(cx);
4968        assert_eq!(
4969            target_buffer.text(),
4970            "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
4971        );
4972        assert_eq!(
4973            definitions_1[0].target.range.to_point(target_buffer),
4974            Point::new(0, 6)..Point::new(0, 9)
4975        );
4976    });
4977
4978    // Try getting more definitions for the same buffer, ensuring the buffer gets reused from
4979    // the previous call to `definition`.
4980    fake_language_server.set_request_handler::<lsp::request::GotoDefinition, _, _>(
4981        |_, _| async move {
4982            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
4983                lsp::Location::new(
4984                    lsp::Uri::from_file_path(path!("/root/dir-2/b.rs")).unwrap(),
4985                    lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
4986                ),
4987            )))
4988        },
4989    );
4990
4991    let definitions_2 = project_b
4992        .update(cx_b, |p, cx| p.definitions(&buffer_b, 33, cx))
4993        .await
4994        .unwrap()
4995        .unwrap();
4996    cx_b.read(|cx| {
4997        assert_eq!(definitions_2.len(), 1);
4998        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
4999        let target_buffer = definitions_2[0].target.buffer.read(cx);
5000        assert_eq!(
5001            target_buffer.text(),
5002            "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
5003        );
5004        assert_eq!(
5005            definitions_2[0].target.range.to_point(target_buffer),
5006            Point::new(1, 6)..Point::new(1, 11)
5007        );
5008    });
5009    assert_eq!(
5010        definitions_1[0].target.buffer,
5011        definitions_2[0].target.buffer
5012    );
5013
5014    fake_language_server.set_request_handler::<lsp::request::GotoTypeDefinition, _, _>(
5015        |req, _| async move {
5016            assert_eq!(
5017                req.text_document_position_params.position,
5018                lsp::Position::new(0, 7)
5019            );
5020            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
5021                lsp::Location::new(
5022                    lsp::Uri::from_file_path(path!("/root/dir-2/c.rs")).unwrap(),
5023                    lsp::Range::new(lsp::Position::new(0, 5), lsp::Position::new(0, 7)),
5024                ),
5025            )))
5026        },
5027    );
5028
5029    let type_definitions = project_b
5030        .update(cx_b, |p, cx| p.type_definitions(&buffer_b, 7, cx))
5031        .await
5032        .unwrap()
5033        .unwrap();
5034    cx_b.read(|cx| {
5035        assert_eq!(
5036            type_definitions.len(),
5037            1,
5038            "Unexpected type definitions: {type_definitions:?}"
5039        );
5040        let target_buffer = type_definitions[0].target.buffer.read(cx);
5041        assert_eq!(target_buffer.text(), "type T2 = usize;");
5042        assert_eq!(
5043            type_definitions[0].target.range.to_point(target_buffer),
5044            Point::new(0, 5)..Point::new(0, 7)
5045        );
5046    });
5047}
5048
5049#[gpui::test(iterations = 10)]
5050async fn test_references(
5051    executor: BackgroundExecutor,
5052    cx_a: &mut TestAppContext,
5053    cx_b: &mut TestAppContext,
5054) {
5055    let mut server = TestServer::start(executor.clone()).await;
5056    let client_a = server.create_client(cx_a, "user_a").await;
5057    let client_b = server.create_client(cx_b, "user_b").await;
5058    server
5059        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5060        .await;
5061    let active_call_a = cx_a.read(ActiveCall::global);
5062
5063    let capabilities = lsp::ServerCapabilities {
5064        references_provider: Some(lsp::OneOf::Left(true)),
5065        ..lsp::ServerCapabilities::default()
5066    };
5067    client_a.language_registry().add(rust_lang());
5068    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
5069        "Rust",
5070        FakeLspAdapter {
5071            name: "my-fake-lsp-adapter",
5072            capabilities: capabilities.clone(),
5073            ..FakeLspAdapter::default()
5074        },
5075    );
5076    client_b.language_registry().add(rust_lang());
5077    client_b.language_registry().register_fake_lsp_adapter(
5078        "Rust",
5079        FakeLspAdapter {
5080            name: "my-fake-lsp-adapter",
5081            capabilities,
5082            ..FakeLspAdapter::default()
5083        },
5084    );
5085
5086    client_a
5087        .fs()
5088        .insert_tree(
5089            path!("/root"),
5090            json!({
5091                "dir-1": {
5092                    "one.rs": "const ONE: usize = 1;",
5093                    "two.rs": "const TWO: usize = one::ONE + one::ONE;",
5094                },
5095                "dir-2": {
5096                    "three.rs": "const THREE: usize = two::TWO + one::ONE;",
5097                }
5098            }),
5099        )
5100        .await;
5101    let (project_a, worktree_id) = client_a
5102        .build_local_project(path!("/root/dir-1"), cx_a)
5103        .await;
5104    let project_id = active_call_a
5105        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5106        .await
5107        .unwrap();
5108    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5109
5110    // Open the file on client B.
5111    let (buffer_b, _handle) = project_b
5112        .update(cx_b, |p, cx| {
5113            p.open_buffer_with_lsp((worktree_id, rel_path("one.rs")), cx)
5114        })
5115        .await
5116        .unwrap();
5117
5118    // Request references to a symbol as the guest.
5119    let fake_language_server = fake_language_servers.next().await.unwrap();
5120    let (lsp_response_tx, rx) = mpsc::unbounded::<Result<Option<Vec<lsp::Location>>>>();
5121    fake_language_server.set_request_handler::<lsp::request::References, _, _>({
5122        let rx = Arc::new(Mutex::new(Some(rx)));
5123        move |params, _| {
5124            assert_eq!(
5125                params.text_document_position.text_document.uri.as_str(),
5126                uri!("file:///root/dir-1/one.rs")
5127            );
5128            let rx = rx.clone();
5129            async move {
5130                let mut response_rx = rx.lock().take().unwrap();
5131                let result = response_rx.next().await.unwrap();
5132                *rx.lock() = Some(response_rx);
5133                result
5134            }
5135        }
5136    });
5137    cx_a.run_until_parked();
5138    cx_b.run_until_parked();
5139
5140    let references = project_b.update(cx_b, |p, cx| p.references(&buffer_b, 7, cx));
5141
5142    // User is informed that a request is pending.
5143    executor.run_until_parked();
5144    project_b.read_with(cx_b, |project, cx| {
5145        let status = project.language_server_statuses(cx).next().unwrap().1;
5146        assert_eq!(status.name.0, "my-fake-lsp-adapter");
5147        assert_eq!(
5148            status.pending_work.values().next().unwrap().message,
5149            Some("Finding references...".into())
5150        );
5151    });
5152
5153    // Cause the language server to respond.
5154    lsp_response_tx
5155        .unbounded_send(Ok(Some(vec![
5156            lsp::Location {
5157                uri: lsp::Uri::from_file_path(path!("/root/dir-1/two.rs")).unwrap(),
5158                range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
5159            },
5160            lsp::Location {
5161                uri: lsp::Uri::from_file_path(path!("/root/dir-1/two.rs")).unwrap(),
5162                range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
5163            },
5164            lsp::Location {
5165                uri: lsp::Uri::from_file_path(path!("/root/dir-2/three.rs")).unwrap(),
5166                range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
5167            },
5168        ])))
5169        .unwrap();
5170
5171    let references = references.await.unwrap().unwrap();
5172    executor.run_until_parked();
5173    project_b.read_with(cx_b, |project, cx| {
5174        // User is informed that a request is no longer pending.
5175        let status = project.language_server_statuses(cx).next().unwrap().1;
5176        assert!(status.pending_work.is_empty());
5177
5178        assert_eq!(references.len(), 3);
5179        assert_eq!(project.worktrees(cx).count(), 2);
5180
5181        let two_buffer = references[0].buffer.read(cx);
5182        let three_buffer = references[2].buffer.read(cx);
5183        assert_eq!(
5184            two_buffer.file().unwrap().path().as_ref(),
5185            rel_path("two.rs")
5186        );
5187        assert_eq!(references[1].buffer, references[0].buffer);
5188        assert_eq!(
5189            three_buffer.file().unwrap().full_path(cx),
5190            Path::new(path!("/root/dir-2/three.rs"))
5191        );
5192
5193        assert_eq!(references[0].range.to_offset(two_buffer), 24..27);
5194        assert_eq!(references[1].range.to_offset(two_buffer), 35..38);
5195        assert_eq!(references[2].range.to_offset(three_buffer), 37..40);
5196    });
5197
5198    let references = project_b.update(cx_b, |p, cx| p.references(&buffer_b, 7, cx));
5199
5200    // User is informed that a request is pending.
5201    executor.run_until_parked();
5202    project_b.read_with(cx_b, |project, cx| {
5203        let status = project.language_server_statuses(cx).next().unwrap().1;
5204        assert_eq!(status.name.0, "my-fake-lsp-adapter");
5205        assert_eq!(
5206            status.pending_work.values().next().unwrap().message,
5207            Some("Finding references...".into())
5208        );
5209    });
5210
5211    // Cause the LSP request to fail.
5212    lsp_response_tx
5213        .unbounded_send(Err(anyhow!("can't find references")))
5214        .unwrap();
5215    assert_eq!(references.await.unwrap().unwrap(), []);
5216
5217    // User is informed that the request is no longer pending.
5218    executor.run_until_parked();
5219    project_b.read_with(cx_b, |project, cx| {
5220        let status = project.language_server_statuses(cx).next().unwrap().1;
5221        assert!(status.pending_work.is_empty());
5222    });
5223}
5224
5225#[gpui::test(iterations = 10)]
5226async fn test_project_search(
5227    executor: BackgroundExecutor,
5228    cx_a: &mut TestAppContext,
5229    cx_b: &mut TestAppContext,
5230) {
5231    let mut server = TestServer::start(executor.clone()).await;
5232    let client_a = server.create_client(cx_a, "user_a").await;
5233    let client_b = server.create_client(cx_b, "user_b").await;
5234    server
5235        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5236        .await;
5237    let active_call_a = cx_a.read(ActiveCall::global);
5238
5239    client_a
5240        .fs()
5241        .insert_tree(
5242            "/root",
5243            json!({
5244                "dir-1": {
5245                    "a": "hello world",
5246                    "b": "goodnight moon",
5247                    "c": "a world of goo",
5248                    "d": "world champion of clown world",
5249                },
5250                "dir-2": {
5251                    "e": "disney world is fun",
5252                }
5253            }),
5254        )
5255        .await;
5256    let (project_a, _) = client_a.build_local_project("/root/dir-1", cx_a).await;
5257    let (worktree_2, _) = project_a
5258        .update(cx_a, |p, cx| {
5259            p.find_or_create_worktree("/root/dir-2", true, cx)
5260        })
5261        .await
5262        .unwrap();
5263    worktree_2
5264        .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
5265        .await;
5266    let project_id = active_call_a
5267        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5268        .await
5269        .unwrap();
5270
5271    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5272
5273    // Perform a search as the guest.
5274    let mut results = HashMap::default();
5275    let search_rx = project_b.update(cx_b, |project, cx| {
5276        project.search(
5277            SearchQuery::text(
5278                "world",
5279                false,
5280                false,
5281                false,
5282                Default::default(),
5283                Default::default(),
5284                false,
5285                None,
5286            )
5287            .unwrap(),
5288            cx,
5289        )
5290    });
5291    while let Ok(result) = search_rx.rx.recv().await {
5292        match result {
5293            SearchResult::Buffer { buffer, ranges } => {
5294                results.entry(buffer).or_insert(ranges);
5295            }
5296            SearchResult::LimitReached => {
5297                panic!(
5298                    "Unexpectedly reached search limit in tests. If you do want to assert limit-reached, change this panic call."
5299                )
5300            }
5301        };
5302    }
5303
5304    let mut ranges_by_path = results
5305        .into_iter()
5306        .map(|(buffer, ranges)| {
5307            buffer.read_with(cx_b, |buffer, cx| {
5308                let path = buffer.file().unwrap().full_path(cx);
5309                let offset_ranges = ranges
5310                    .into_iter()
5311                    .map(|range| range.to_offset(buffer))
5312                    .collect::<Vec<_>>();
5313                (path, offset_ranges)
5314            })
5315        })
5316        .collect::<Vec<_>>();
5317    ranges_by_path.sort_by_key(|(path, _)| path.clone());
5318
5319    assert_eq!(
5320        ranges_by_path,
5321        &[
5322            (PathBuf::from("dir-1/a"), vec![6..11]),
5323            (PathBuf::from("dir-1/c"), vec![2..7]),
5324            (PathBuf::from("dir-1/d"), vec![0..5, 24..29]),
5325            (PathBuf::from("dir-2/e"), vec![7..12]),
5326        ]
5327    );
5328}
5329
5330#[gpui::test(iterations = 10)]
5331async fn test_document_highlights(
5332    executor: BackgroundExecutor,
5333    cx_a: &mut TestAppContext,
5334    cx_b: &mut TestAppContext,
5335) {
5336    let mut server = TestServer::start(executor.clone()).await;
5337    let client_a = server.create_client(cx_a, "user_a").await;
5338    let client_b = server.create_client(cx_b, "user_b").await;
5339    server
5340        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5341        .await;
5342    let active_call_a = cx_a.read(ActiveCall::global);
5343
5344    client_a
5345        .fs()
5346        .insert_tree(
5347            path!("/root-1"),
5348            json!({
5349                "main.rs": "fn double(number: i32) -> i32 { number + number }",
5350            }),
5351        )
5352        .await;
5353
5354    client_a.language_registry().add(rust_lang());
5355    let capabilities = lsp::ServerCapabilities {
5356        document_highlight_provider: Some(lsp::OneOf::Left(true)),
5357        ..lsp::ServerCapabilities::default()
5358    };
5359    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
5360        "Rust",
5361        FakeLspAdapter {
5362            capabilities: capabilities.clone(),
5363            ..FakeLspAdapter::default()
5364        },
5365    );
5366    client_b.language_registry().add(rust_lang());
5367    client_b.language_registry().register_fake_lsp_adapter(
5368        "Rust",
5369        FakeLspAdapter {
5370            capabilities,
5371            ..FakeLspAdapter::default()
5372        },
5373    );
5374
5375    let (project_a, worktree_id) = client_a.build_local_project(path!("/root-1"), cx_a).await;
5376    let project_id = active_call_a
5377        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5378        .await
5379        .unwrap();
5380    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5381
5382    // Open the file on client B.
5383    let (buffer_b, _handle) = project_b
5384        .update(cx_b, |p, cx| {
5385            p.open_buffer_with_lsp((worktree_id, rel_path("main.rs")), cx)
5386        })
5387        .await
5388        .unwrap();
5389
5390    // Request document highlights as the guest.
5391    let fake_language_server = fake_language_servers.next().await.unwrap();
5392    fake_language_server.set_request_handler::<lsp::request::DocumentHighlightRequest, _, _>(
5393        |params, _| async move {
5394            assert_eq!(
5395                params
5396                    .text_document_position_params
5397                    .text_document
5398                    .uri
5399                    .as_str(),
5400                uri!("file:///root-1/main.rs")
5401            );
5402            assert_eq!(
5403                params.text_document_position_params.position,
5404                lsp::Position::new(0, 34)
5405            );
5406            Ok(Some(vec![
5407                lsp::DocumentHighlight {
5408                    kind: Some(lsp::DocumentHighlightKind::WRITE),
5409                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 16)),
5410                },
5411                lsp::DocumentHighlight {
5412                    kind: Some(lsp::DocumentHighlightKind::READ),
5413                    range: lsp::Range::new(lsp::Position::new(0, 32), lsp::Position::new(0, 38)),
5414                },
5415                lsp::DocumentHighlight {
5416                    kind: Some(lsp::DocumentHighlightKind::READ),
5417                    range: lsp::Range::new(lsp::Position::new(0, 41), lsp::Position::new(0, 47)),
5418                },
5419            ]))
5420        },
5421    );
5422    cx_a.run_until_parked();
5423    cx_b.run_until_parked();
5424
5425    let highlights = project_b
5426        .update(cx_b, |p, cx| p.document_highlights(&buffer_b, 34, cx))
5427        .await
5428        .unwrap();
5429
5430    buffer_b.read_with(cx_b, |buffer, _| {
5431        let snapshot = buffer.snapshot();
5432
5433        let highlights = highlights
5434            .into_iter()
5435            .map(|highlight| (highlight.kind, highlight.range.to_offset(&snapshot)))
5436            .collect::<Vec<_>>();
5437        assert_eq!(
5438            highlights,
5439            &[
5440                (lsp::DocumentHighlightKind::WRITE, 10..16),
5441                (lsp::DocumentHighlightKind::READ, 32..38),
5442                (lsp::DocumentHighlightKind::READ, 41..47)
5443            ]
5444        )
5445    });
5446}
5447
5448#[gpui::test(iterations = 10)]
5449async fn test_lsp_hover(
5450    executor: BackgroundExecutor,
5451    cx_a: &mut TestAppContext,
5452    cx_b: &mut TestAppContext,
5453) {
5454    let mut server = TestServer::start(executor.clone()).await;
5455    let client_a = server.create_client(cx_a, "user_a").await;
5456    let client_b = server.create_client(cx_b, "user_b").await;
5457    server
5458        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5459        .await;
5460    let active_call_a = cx_a.read(ActiveCall::global);
5461
5462    client_a
5463        .fs()
5464        .insert_tree(
5465            path!("/root-1"),
5466            json!({
5467                "main.rs": "use std::collections::HashMap;",
5468            }),
5469        )
5470        .await;
5471
5472    client_a.language_registry().add(rust_lang());
5473    let language_server_names = ["rust-analyzer", "CrabLang-ls"];
5474    let capabilities_1 = lsp::ServerCapabilities {
5475        hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
5476        ..lsp::ServerCapabilities::default()
5477    };
5478    let capabilities_2 = lsp::ServerCapabilities {
5479        hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
5480        ..lsp::ServerCapabilities::default()
5481    };
5482    let mut language_servers = [
5483        client_a.language_registry().register_fake_lsp(
5484            "Rust",
5485            FakeLspAdapter {
5486                name: language_server_names[0],
5487                capabilities: capabilities_1.clone(),
5488                ..FakeLspAdapter::default()
5489            },
5490        ),
5491        client_a.language_registry().register_fake_lsp(
5492            "Rust",
5493            FakeLspAdapter {
5494                name: language_server_names[1],
5495                capabilities: capabilities_2.clone(),
5496                ..FakeLspAdapter::default()
5497            },
5498        ),
5499    ];
5500    client_b.language_registry().add(rust_lang());
5501    client_b.language_registry().register_fake_lsp_adapter(
5502        "Rust",
5503        FakeLspAdapter {
5504            name: language_server_names[0],
5505            capabilities: capabilities_1,
5506            ..FakeLspAdapter::default()
5507        },
5508    );
5509    client_b.language_registry().register_fake_lsp_adapter(
5510        "Rust",
5511        FakeLspAdapter {
5512            name: language_server_names[1],
5513            capabilities: capabilities_2,
5514            ..FakeLspAdapter::default()
5515        },
5516    );
5517
5518    let (project_a, worktree_id) = client_a.build_local_project(path!("/root-1"), cx_a).await;
5519    let project_id = active_call_a
5520        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5521        .await
5522        .unwrap();
5523    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5524
5525    // Open the file as the guest
5526    let (buffer_b, _handle) = project_b
5527        .update(cx_b, |p, cx| {
5528            p.open_buffer_with_lsp((worktree_id, rel_path("main.rs")), cx)
5529        })
5530        .await
5531        .unwrap();
5532
5533    let mut servers_with_hover_requests = HashMap::default();
5534    for i in 0..language_server_names.len() {
5535        let new_server = language_servers[i].next().await.unwrap_or_else(|| {
5536            panic!(
5537                "Failed to get language server #{i} with name {}",
5538                &language_server_names[i]
5539            )
5540        });
5541        let new_server_name = new_server.server.name();
5542        assert!(
5543            !servers_with_hover_requests.contains_key(&new_server_name),
5544            "Unexpected: initialized server with the same name twice. Name: `{new_server_name}`"
5545        );
5546        match new_server_name.as_ref() {
5547            "CrabLang-ls" => {
5548                servers_with_hover_requests.insert(
5549                    new_server_name.clone(),
5550                    new_server.set_request_handler::<lsp::request::HoverRequest, _, _>(
5551                        move |params, _| {
5552                            assert_eq!(
5553                                params
5554                                    .text_document_position_params
5555                                    .text_document
5556                                    .uri
5557                                    .as_str(),
5558                                uri!("file:///root-1/main.rs")
5559                            );
5560                            let name = new_server_name.clone();
5561                            async move {
5562                                Ok(Some(lsp::Hover {
5563                                    contents: lsp::HoverContents::Scalar(
5564                                        lsp::MarkedString::String(format!("{name} hover")),
5565                                    ),
5566                                    range: None,
5567                                }))
5568                            }
5569                        },
5570                    ),
5571                );
5572            }
5573            "rust-analyzer" => {
5574                servers_with_hover_requests.insert(
5575                    new_server_name.clone(),
5576                    new_server.set_request_handler::<lsp::request::HoverRequest, _, _>(
5577                        |params, _| async move {
5578                            assert_eq!(
5579                                params
5580                                    .text_document_position_params
5581                                    .text_document
5582                                    .uri
5583                                    .as_str(),
5584                                uri!("file:///root-1/main.rs")
5585                            );
5586                            assert_eq!(
5587                                params.text_document_position_params.position,
5588                                lsp::Position::new(0, 22)
5589                            );
5590                            Ok(Some(lsp::Hover {
5591                                contents: lsp::HoverContents::Array(vec![
5592                                    lsp::MarkedString::String("Test hover content.".to_string()),
5593                                    lsp::MarkedString::LanguageString(lsp::LanguageString {
5594                                        language: "Rust".to_string(),
5595                                        value: "let foo = 42;".to_string(),
5596                                    }),
5597                                ]),
5598                                range: Some(lsp::Range::new(
5599                                    lsp::Position::new(0, 22),
5600                                    lsp::Position::new(0, 29),
5601                                )),
5602                            }))
5603                        },
5604                    ),
5605                );
5606            }
5607            unexpected => panic!("Unexpected server name: {unexpected}"),
5608        }
5609    }
5610    cx_a.run_until_parked();
5611    cx_b.run_until_parked();
5612
5613    // Request hover information as the guest.
5614    let mut hovers = project_b
5615        .update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
5616        .await
5617        .unwrap();
5618    assert_eq!(
5619        hovers.len(),
5620        2,
5621        "Expected two hovers from both language servers, but got: {hovers:?}"
5622    );
5623
5624    let _: Vec<()> = futures::future::join_all(servers_with_hover_requests.into_values().map(
5625        |mut hover_request| async move {
5626            hover_request
5627                .next()
5628                .await
5629                .expect("All hover requests should have been triggered")
5630        },
5631    ))
5632    .await;
5633
5634    hovers.sort_by_key(|hover| hover.contents.len());
5635    let first_hover = hovers.first().cloned().unwrap();
5636    assert_eq!(
5637        first_hover.contents,
5638        vec![project::HoverBlock {
5639            text: "CrabLang-ls hover".to_string(),
5640            kind: HoverBlockKind::Markdown,
5641        },]
5642    );
5643    let second_hover = hovers.last().cloned().unwrap();
5644    assert_eq!(
5645        second_hover.contents,
5646        vec![
5647            project::HoverBlock {
5648                text: "Test hover content.".to_string(),
5649                kind: HoverBlockKind::Markdown,
5650            },
5651            project::HoverBlock {
5652                text: "let foo = 42;".to_string(),
5653                kind: HoverBlockKind::Code {
5654                    language: "Rust".to_string()
5655                },
5656            }
5657        ]
5658    );
5659    buffer_b.read_with(cx_b, |buffer, _| {
5660        let snapshot = buffer.snapshot();
5661        assert_eq!(second_hover.range.unwrap().to_offset(&snapshot), 22..29);
5662    });
5663}
5664
5665#[gpui::test(iterations = 10)]
5666async fn test_project_symbols(
5667    executor: BackgroundExecutor,
5668    cx_a: &mut TestAppContext,
5669    cx_b: &mut TestAppContext,
5670) {
5671    let mut server = TestServer::start(executor.clone()).await;
5672    let client_a = server.create_client(cx_a, "user_a").await;
5673    let client_b = server.create_client(cx_b, "user_b").await;
5674    server
5675        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5676        .await;
5677    let active_call_a = cx_a.read(ActiveCall::global);
5678
5679    client_a.language_registry().add(rust_lang());
5680    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
5681        "Rust",
5682        FakeLspAdapter {
5683            capabilities: lsp::ServerCapabilities {
5684                workspace_symbol_provider: Some(OneOf::Left(true)),
5685                ..Default::default()
5686            },
5687            ..Default::default()
5688        },
5689    );
5690
5691    client_a
5692        .fs()
5693        .insert_tree(
5694            path!("/code"),
5695            json!({
5696                "crate-1": {
5697                    "one.rs": "const ONE: usize = 1;",
5698                },
5699                "crate-2": {
5700                    "two.rs": "const TWO: usize = 2; const THREE: usize = 3;",
5701                },
5702                "private": {
5703                    "passwords.txt": "the-password",
5704                }
5705            }),
5706        )
5707        .await;
5708    let (project_a, worktree_id) = client_a
5709        .build_local_project(path!("/code/crate-1"), cx_a)
5710        .await;
5711    let project_id = active_call_a
5712        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5713        .await
5714        .unwrap();
5715    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5716
5717    // Cause the language server to start.
5718    let _buffer = project_b
5719        .update(cx_b, |p, cx| {
5720            p.open_buffer_with_lsp((worktree_id, rel_path("one.rs")), cx)
5721        })
5722        .await
5723        .unwrap();
5724
5725    let fake_language_server = fake_language_servers.next().await.unwrap();
5726    executor.run_until_parked();
5727    fake_language_server.set_request_handler::<lsp::WorkspaceSymbolRequest, _, _>(
5728        |_, _| async move {
5729            Ok(Some(lsp::WorkspaceSymbolResponse::Flat(vec![
5730                #[allow(deprecated)]
5731                lsp::SymbolInformation {
5732                    name: "TWO".into(),
5733                    location: lsp::Location {
5734                        uri: lsp::Uri::from_file_path(path!("/code/crate-2/two.rs")).unwrap(),
5735                        range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
5736                    },
5737                    kind: lsp::SymbolKind::CONSTANT,
5738                    tags: None,
5739                    container_name: None,
5740                    deprecated: None,
5741                },
5742            ])))
5743        },
5744    );
5745
5746    // Request the definition of a symbol as the guest.
5747    let symbols = project_b
5748        .update(cx_b, |p, cx| p.symbols("two", cx))
5749        .await
5750        .unwrap();
5751    assert_eq!(symbols.len(), 1);
5752    assert_eq!(symbols[0].name, "TWO");
5753
5754    // Open one of the returned symbols.
5755    let buffer_b_2 = project_b
5756        .update(cx_b, |project, cx| {
5757            project.open_buffer_for_symbol(&symbols[0], cx)
5758        })
5759        .await
5760        .unwrap();
5761
5762    buffer_b_2.read_with(cx_b, |buffer, cx| {
5763        assert_eq!(
5764            buffer.file().unwrap().full_path(cx),
5765            Path::new(path!("/code/crate-2/two.rs"))
5766        );
5767    });
5768
5769    // Attempt to craft a symbol and violate host's privacy by opening an arbitrary file.
5770    let mut fake_symbol = symbols[0].clone();
5771    fake_symbol.path = SymbolLocation::OutsideProject {
5772        abs_path: Path::new(path!("/code/secrets")).into(),
5773        signature: [0x17; 32],
5774    };
5775    let error = project_b
5776        .update(cx_b, |project, cx| {
5777            project.open_buffer_for_symbol(&fake_symbol, cx)
5778        })
5779        .await
5780        .unwrap_err();
5781    assert!(error.to_string().contains("invalid symbol signature"));
5782}
5783
5784#[gpui::test(iterations = 10)]
5785async fn test_open_buffer_while_getting_definition_pointing_to_it(
5786    executor: BackgroundExecutor,
5787    cx_a: &mut TestAppContext,
5788    cx_b: &mut TestAppContext,
5789    mut rng: StdRng,
5790) {
5791    let mut server = TestServer::start(executor.clone()).await;
5792    let client_a = server.create_client(cx_a, "user_a").await;
5793    let client_b = server.create_client(cx_b, "user_b").await;
5794    server
5795        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5796        .await;
5797    let active_call_a = cx_a.read(ActiveCall::global);
5798
5799    let capabilities = lsp::ServerCapabilities {
5800        definition_provider: Some(OneOf::Left(true)),
5801        ..lsp::ServerCapabilities::default()
5802    };
5803    client_a.language_registry().add(rust_lang());
5804    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
5805        "Rust",
5806        FakeLspAdapter {
5807            capabilities: capabilities.clone(),
5808            ..FakeLspAdapter::default()
5809        },
5810    );
5811    client_b.language_registry().add(rust_lang());
5812    client_b.language_registry().register_fake_lsp_adapter(
5813        "Rust",
5814        FakeLspAdapter {
5815            capabilities,
5816            ..FakeLspAdapter::default()
5817        },
5818    );
5819
5820    client_a
5821        .fs()
5822        .insert_tree(
5823            path!("/root"),
5824            json!({
5825                "a.rs": "const ONE: usize = b::TWO;",
5826                "b.rs": "const TWO: usize = 2",
5827            }),
5828        )
5829        .await;
5830    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
5831    let project_id = active_call_a
5832        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5833        .await
5834        .unwrap();
5835    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5836
5837    let (buffer_b1, _lsp) = project_b
5838        .update(cx_b, |p, cx| {
5839            p.open_buffer_with_lsp((worktree_id, rel_path("a.rs")), cx)
5840        })
5841        .await
5842        .unwrap();
5843
5844    let fake_language_server = fake_language_servers.next().await.unwrap();
5845    fake_language_server.set_request_handler::<lsp::request::GotoDefinition, _, _>(
5846        |_, _| async move {
5847            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
5848                lsp::Location::new(
5849                    lsp::Uri::from_file_path(path!("/root/b.rs")).unwrap(),
5850                    lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
5851                ),
5852            )))
5853        },
5854    );
5855
5856    let definitions;
5857    let buffer_b2;
5858    if rng.random() {
5859        cx_a.run_until_parked();
5860        cx_b.run_until_parked();
5861        definitions = project_b.update(cx_b, |p, cx| p.definitions(&buffer_b1, 23, cx));
5862        (buffer_b2, _) = project_b
5863            .update(cx_b, |p, cx| {
5864                p.open_buffer_with_lsp((worktree_id, rel_path("b.rs")), cx)
5865            })
5866            .await
5867            .unwrap();
5868    } else {
5869        (buffer_b2, _) = project_b
5870            .update(cx_b, |p, cx| {
5871                p.open_buffer_with_lsp((worktree_id, rel_path("b.rs")), cx)
5872            })
5873            .await
5874            .unwrap();
5875        cx_a.run_until_parked();
5876        cx_b.run_until_parked();
5877        definitions = project_b.update(cx_b, |p, cx| p.definitions(&buffer_b1, 23, cx));
5878    }
5879
5880    let definitions = definitions.await.unwrap().unwrap();
5881    assert_eq!(
5882        definitions.len(),
5883        1,
5884        "Unexpected definitions: {definitions:?}"
5885    );
5886    assert_eq!(definitions[0].target.buffer, buffer_b2);
5887}
5888
5889#[gpui::test(iterations = 10)]
5890async fn test_contacts(
5891    executor: BackgroundExecutor,
5892    cx_a: &mut TestAppContext,
5893    cx_b: &mut TestAppContext,
5894    cx_c: &mut TestAppContext,
5895    cx_d: &mut TestAppContext,
5896) {
5897    let mut server = TestServer::start(executor.clone()).await;
5898    let client_a = server.create_client(cx_a, "user_a").await;
5899    let client_b = server.create_client(cx_b, "user_b").await;
5900    let client_c = server.create_client(cx_c, "user_c").await;
5901    let client_d = server.create_client(cx_d, "user_d").await;
5902    server
5903        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
5904        .await;
5905    let active_call_a = cx_a.read(ActiveCall::global);
5906    let active_call_b = cx_b.read(ActiveCall::global);
5907    let active_call_c = cx_c.read(ActiveCall::global);
5908    let _active_call_d = cx_d.read(ActiveCall::global);
5909
5910    executor.run_until_parked();
5911    assert_eq!(
5912        contacts(&client_a, cx_a),
5913        [
5914            ("user_b".to_string(), "online", "free"),
5915            ("user_c".to_string(), "online", "free")
5916        ]
5917    );
5918    assert_eq!(
5919        contacts(&client_b, cx_b),
5920        [
5921            ("user_a".to_string(), "online", "free"),
5922            ("user_c".to_string(), "online", "free")
5923        ]
5924    );
5925    assert_eq!(
5926        contacts(&client_c, cx_c),
5927        [
5928            ("user_a".to_string(), "online", "free"),
5929            ("user_b".to_string(), "online", "free")
5930        ]
5931    );
5932    assert_eq!(contacts(&client_d, cx_d), []);
5933
5934    server.disconnect_client(client_c.peer_id().unwrap());
5935    server.forbid_connections();
5936    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
5937    assert_eq!(
5938        contacts(&client_a, cx_a),
5939        [
5940            ("user_b".to_string(), "online", "free"),
5941            ("user_c".to_string(), "offline", "free")
5942        ]
5943    );
5944    assert_eq!(
5945        contacts(&client_b, cx_b),
5946        [
5947            ("user_a".to_string(), "online", "free"),
5948            ("user_c".to_string(), "offline", "free")
5949        ]
5950    );
5951    assert_eq!(contacts(&client_c, cx_c), []);
5952    assert_eq!(contacts(&client_d, cx_d), []);
5953
5954    server.allow_connections();
5955    client_c
5956        .connect(false, &cx_c.to_async())
5957        .await
5958        .into_response()
5959        .unwrap();
5960
5961    executor.run_until_parked();
5962    assert_eq!(
5963        contacts(&client_a, cx_a),
5964        [
5965            ("user_b".to_string(), "online", "free"),
5966            ("user_c".to_string(), "online", "free")
5967        ]
5968    );
5969    assert_eq!(
5970        contacts(&client_b, cx_b),
5971        [
5972            ("user_a".to_string(), "online", "free"),
5973            ("user_c".to_string(), "online", "free")
5974        ]
5975    );
5976    assert_eq!(
5977        contacts(&client_c, cx_c),
5978        [
5979            ("user_a".to_string(), "online", "free"),
5980            ("user_b".to_string(), "online", "free")
5981        ]
5982    );
5983    assert_eq!(contacts(&client_d, cx_d), []);
5984
5985    active_call_a
5986        .update(cx_a, |call, cx| {
5987            call.invite(client_b.user_id().unwrap(), None, cx)
5988        })
5989        .await
5990        .unwrap();
5991    executor.run_until_parked();
5992    assert_eq!(
5993        contacts(&client_a, cx_a),
5994        [
5995            ("user_b".to_string(), "online", "busy"),
5996            ("user_c".to_string(), "online", "free")
5997        ]
5998    );
5999    assert_eq!(
6000        contacts(&client_b, cx_b),
6001        [
6002            ("user_a".to_string(), "online", "busy"),
6003            ("user_c".to_string(), "online", "free")
6004        ]
6005    );
6006    assert_eq!(
6007        contacts(&client_c, cx_c),
6008        [
6009            ("user_a".to_string(), "online", "busy"),
6010            ("user_b".to_string(), "online", "busy")
6011        ]
6012    );
6013    assert_eq!(contacts(&client_d, cx_d), []);
6014
6015    // Client B and client D become contacts while client B is being called.
6016    server
6017        .make_contacts(&mut [(&client_b, cx_b), (&client_d, cx_d)])
6018        .await;
6019    executor.run_until_parked();
6020    assert_eq!(
6021        contacts(&client_a, cx_a),
6022        [
6023            ("user_b".to_string(), "online", "busy"),
6024            ("user_c".to_string(), "online", "free")
6025        ]
6026    );
6027    assert_eq!(
6028        contacts(&client_b, cx_b),
6029        [
6030            ("user_a".to_string(), "online", "busy"),
6031            ("user_c".to_string(), "online", "free"),
6032            ("user_d".to_string(), "online", "free"),
6033        ]
6034    );
6035    assert_eq!(
6036        contacts(&client_c, cx_c),
6037        [
6038            ("user_a".to_string(), "online", "busy"),
6039            ("user_b".to_string(), "online", "busy")
6040        ]
6041    );
6042    assert_eq!(
6043        contacts(&client_d, cx_d),
6044        [("user_b".to_string(), "online", "busy")]
6045    );
6046
6047    active_call_b.update(cx_b, |call, cx| call.decline_incoming(cx).unwrap());
6048    executor.run_until_parked();
6049    assert_eq!(
6050        contacts(&client_a, cx_a),
6051        [
6052            ("user_b".to_string(), "online", "free"),
6053            ("user_c".to_string(), "online", "free")
6054        ]
6055    );
6056    assert_eq!(
6057        contacts(&client_b, cx_b),
6058        [
6059            ("user_a".to_string(), "online", "free"),
6060            ("user_c".to_string(), "online", "free"),
6061            ("user_d".to_string(), "online", "free")
6062        ]
6063    );
6064    assert_eq!(
6065        contacts(&client_c, cx_c),
6066        [
6067            ("user_a".to_string(), "online", "free"),
6068            ("user_b".to_string(), "online", "free")
6069        ]
6070    );
6071    assert_eq!(
6072        contacts(&client_d, cx_d),
6073        [("user_b".to_string(), "online", "free")]
6074    );
6075
6076    active_call_c
6077        .update(cx_c, |call, cx| {
6078            call.invite(client_a.user_id().unwrap(), None, cx)
6079        })
6080        .await
6081        .unwrap();
6082    executor.run_until_parked();
6083    assert_eq!(
6084        contacts(&client_a, cx_a),
6085        [
6086            ("user_b".to_string(), "online", "free"),
6087            ("user_c".to_string(), "online", "busy")
6088        ]
6089    );
6090    assert_eq!(
6091        contacts(&client_b, cx_b),
6092        [
6093            ("user_a".to_string(), "online", "busy"),
6094            ("user_c".to_string(), "online", "busy"),
6095            ("user_d".to_string(), "online", "free")
6096        ]
6097    );
6098    assert_eq!(
6099        contacts(&client_c, cx_c),
6100        [
6101            ("user_a".to_string(), "online", "busy"),
6102            ("user_b".to_string(), "online", "free")
6103        ]
6104    );
6105    assert_eq!(
6106        contacts(&client_d, cx_d),
6107        [("user_b".to_string(), "online", "free")]
6108    );
6109
6110    active_call_a
6111        .update(cx_a, |call, cx| call.accept_incoming(cx))
6112        .await
6113        .unwrap();
6114    executor.run_until_parked();
6115    assert_eq!(
6116        contacts(&client_a, cx_a),
6117        [
6118            ("user_b".to_string(), "online", "free"),
6119            ("user_c".to_string(), "online", "busy")
6120        ]
6121    );
6122    assert_eq!(
6123        contacts(&client_b, cx_b),
6124        [
6125            ("user_a".to_string(), "online", "busy"),
6126            ("user_c".to_string(), "online", "busy"),
6127            ("user_d".to_string(), "online", "free")
6128        ]
6129    );
6130    assert_eq!(
6131        contacts(&client_c, cx_c),
6132        [
6133            ("user_a".to_string(), "online", "busy"),
6134            ("user_b".to_string(), "online", "free")
6135        ]
6136    );
6137    assert_eq!(
6138        contacts(&client_d, cx_d),
6139        [("user_b".to_string(), "online", "free")]
6140    );
6141
6142    active_call_a
6143        .update(cx_a, |call, cx| {
6144            call.invite(client_b.user_id().unwrap(), None, cx)
6145        })
6146        .await
6147        .unwrap();
6148    executor.run_until_parked();
6149    assert_eq!(
6150        contacts(&client_a, cx_a),
6151        [
6152            ("user_b".to_string(), "online", "busy"),
6153            ("user_c".to_string(), "online", "busy")
6154        ]
6155    );
6156    assert_eq!(
6157        contacts(&client_b, cx_b),
6158        [
6159            ("user_a".to_string(), "online", "busy"),
6160            ("user_c".to_string(), "online", "busy"),
6161            ("user_d".to_string(), "online", "free")
6162        ]
6163    );
6164    assert_eq!(
6165        contacts(&client_c, cx_c),
6166        [
6167            ("user_a".to_string(), "online", "busy"),
6168            ("user_b".to_string(), "online", "busy")
6169        ]
6170    );
6171    assert_eq!(
6172        contacts(&client_d, cx_d),
6173        [("user_b".to_string(), "online", "busy")]
6174    );
6175
6176    active_call_a
6177        .update(cx_a, |call, cx| call.hang_up(cx))
6178        .await
6179        .unwrap();
6180    executor.run_until_parked();
6181    assert_eq!(
6182        contacts(&client_a, cx_a),
6183        [
6184            ("user_b".to_string(), "online", "free"),
6185            ("user_c".to_string(), "online", "free")
6186        ]
6187    );
6188    assert_eq!(
6189        contacts(&client_b, cx_b),
6190        [
6191            ("user_a".to_string(), "online", "free"),
6192            ("user_c".to_string(), "online", "free"),
6193            ("user_d".to_string(), "online", "free")
6194        ]
6195    );
6196    assert_eq!(
6197        contacts(&client_c, cx_c),
6198        [
6199            ("user_a".to_string(), "online", "free"),
6200            ("user_b".to_string(), "online", "free")
6201        ]
6202    );
6203    assert_eq!(
6204        contacts(&client_d, cx_d),
6205        [("user_b".to_string(), "online", "free")]
6206    );
6207
6208    active_call_a
6209        .update(cx_a, |call, cx| {
6210            call.invite(client_b.user_id().unwrap(), None, cx)
6211        })
6212        .await
6213        .unwrap();
6214    executor.run_until_parked();
6215    assert_eq!(
6216        contacts(&client_a, cx_a),
6217        [
6218            ("user_b".to_string(), "online", "busy"),
6219            ("user_c".to_string(), "online", "free")
6220        ]
6221    );
6222    assert_eq!(
6223        contacts(&client_b, cx_b),
6224        [
6225            ("user_a".to_string(), "online", "busy"),
6226            ("user_c".to_string(), "online", "free"),
6227            ("user_d".to_string(), "online", "free")
6228        ]
6229    );
6230    assert_eq!(
6231        contacts(&client_c, cx_c),
6232        [
6233            ("user_a".to_string(), "online", "busy"),
6234            ("user_b".to_string(), "online", "busy")
6235        ]
6236    );
6237    assert_eq!(
6238        contacts(&client_d, cx_d),
6239        [("user_b".to_string(), "online", "busy")]
6240    );
6241
6242    server.forbid_connections();
6243    server.disconnect_client(client_a.peer_id().unwrap());
6244    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
6245    assert_eq!(contacts(&client_a, cx_a), []);
6246    assert_eq!(
6247        contacts(&client_b, cx_b),
6248        [
6249            ("user_a".to_string(), "offline", "free"),
6250            ("user_c".to_string(), "online", "free"),
6251            ("user_d".to_string(), "online", "free")
6252        ]
6253    );
6254    assert_eq!(
6255        contacts(&client_c, cx_c),
6256        [
6257            ("user_a".to_string(), "offline", "free"),
6258            ("user_b".to_string(), "online", "free")
6259        ]
6260    );
6261    assert_eq!(
6262        contacts(&client_d, cx_d),
6263        [("user_b".to_string(), "online", "free")]
6264    );
6265
6266    // Test removing a contact
6267    client_b
6268        .user_store()
6269        .update(cx_b, |store, cx| {
6270            store.remove_contact(client_c.user_id().unwrap(), cx)
6271        })
6272        .await
6273        .unwrap();
6274    executor.run_until_parked();
6275    assert_eq!(
6276        contacts(&client_b, cx_b),
6277        [
6278            ("user_a".to_string(), "offline", "free"),
6279            ("user_d".to_string(), "online", "free")
6280        ]
6281    );
6282    assert_eq!(
6283        contacts(&client_c, cx_c),
6284        [("user_a".to_string(), "offline", "free"),]
6285    );
6286
6287    fn contacts(
6288        client: &TestClient,
6289        cx: &TestAppContext,
6290    ) -> Vec<(String, &'static str, &'static str)> {
6291        client.user_store().read_with(cx, |store, _| {
6292            store
6293                .contacts()
6294                .iter()
6295                .map(|contact| {
6296                    (
6297                        contact.user.github_login.clone().to_string(),
6298                        if contact.online { "online" } else { "offline" },
6299                        if contact.busy { "busy" } else { "free" },
6300                    )
6301                })
6302                .collect()
6303        })
6304    }
6305}
6306
6307#[gpui::test(iterations = 10)]
6308async fn test_contact_requests(
6309    executor: BackgroundExecutor,
6310    cx_a: &mut TestAppContext,
6311    cx_a2: &mut TestAppContext,
6312    cx_b: &mut TestAppContext,
6313    cx_b2: &mut TestAppContext,
6314    cx_c: &mut TestAppContext,
6315    cx_c2: &mut TestAppContext,
6316) {
6317    // Connect to a server as 3 clients.
6318    let mut server = TestServer::start(executor.clone()).await;
6319    let client_a = server.create_client(cx_a, "user_a").await;
6320    let client_a2 = server.create_client(cx_a2, "user_a").await;
6321    let client_b = server.create_client(cx_b, "user_b").await;
6322    let client_b2 = server.create_client(cx_b2, "user_b").await;
6323    let client_c = server.create_client(cx_c, "user_c").await;
6324    let client_c2 = server.create_client(cx_c2, "user_c").await;
6325
6326    assert_eq!(client_a.user_id().unwrap(), client_a2.user_id().unwrap());
6327    assert_eq!(client_b.user_id().unwrap(), client_b2.user_id().unwrap());
6328    assert_eq!(client_c.user_id().unwrap(), client_c2.user_id().unwrap());
6329
6330    // User A and User C request that user B become their contact.
6331    client_a
6332        .user_store()
6333        .update(cx_a, |store, cx| {
6334            store.request_contact(client_b.user_id().unwrap(), cx)
6335        })
6336        .await
6337        .unwrap();
6338    client_c
6339        .user_store()
6340        .update(cx_c, |store, cx| {
6341            store.request_contact(client_b.user_id().unwrap(), cx)
6342        })
6343        .await
6344        .unwrap();
6345    executor.run_until_parked();
6346
6347    // All users see the pending request appear in all their clients.
6348    assert_eq!(
6349        client_a.summarize_contacts(cx_a).outgoing_requests,
6350        &["user_b"]
6351    );
6352    assert_eq!(
6353        client_a2.summarize_contacts(cx_a2).outgoing_requests,
6354        &["user_b"]
6355    );
6356    assert_eq!(
6357        client_b.summarize_contacts(cx_b).incoming_requests,
6358        &["user_a", "user_c"]
6359    );
6360    assert_eq!(
6361        client_b2.summarize_contacts(cx_b2).incoming_requests,
6362        &["user_a", "user_c"]
6363    );
6364    assert_eq!(
6365        client_c.summarize_contacts(cx_c).outgoing_requests,
6366        &["user_b"]
6367    );
6368    assert_eq!(
6369        client_c2.summarize_contacts(cx_c2).outgoing_requests,
6370        &["user_b"]
6371    );
6372
6373    // Contact requests are present upon connecting (tested here via disconnect/reconnect)
6374    disconnect_and_reconnect(&client_a, cx_a).await;
6375    disconnect_and_reconnect(&client_b, cx_b).await;
6376    disconnect_and_reconnect(&client_c, cx_c).await;
6377    executor.run_until_parked();
6378    assert_eq!(
6379        client_a.summarize_contacts(cx_a).outgoing_requests,
6380        &["user_b"]
6381    );
6382    assert_eq!(
6383        client_b.summarize_contacts(cx_b).incoming_requests,
6384        &["user_a", "user_c"]
6385    );
6386    assert_eq!(
6387        client_c.summarize_contacts(cx_c).outgoing_requests,
6388        &["user_b"]
6389    );
6390
6391    // User B accepts the request from user A.
6392    client_b
6393        .user_store()
6394        .update(cx_b, |store, cx| {
6395            store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
6396        })
6397        .await
6398        .unwrap();
6399
6400    executor.run_until_parked();
6401
6402    // User B sees user A as their contact now in all client, and the incoming request from them is removed.
6403    let contacts_b = client_b.summarize_contacts(cx_b);
6404    assert_eq!(contacts_b.current, &["user_a"]);
6405    assert_eq!(contacts_b.incoming_requests, &["user_c"]);
6406    let contacts_b2 = client_b2.summarize_contacts(cx_b2);
6407    assert_eq!(contacts_b2.current, &["user_a"]);
6408    assert_eq!(contacts_b2.incoming_requests, &["user_c"]);
6409
6410    // User A sees user B as their contact now in all clients, and the outgoing request to them is removed.
6411    let contacts_a = client_a.summarize_contacts(cx_a);
6412    assert_eq!(contacts_a.current, &["user_b"]);
6413    assert!(contacts_a.outgoing_requests.is_empty());
6414    let contacts_a2 = client_a2.summarize_contacts(cx_a2);
6415    assert_eq!(contacts_a2.current, &["user_b"]);
6416    assert!(contacts_a2.outgoing_requests.is_empty());
6417
6418    // Contacts are present upon connecting (tested here via disconnect/reconnect)
6419    disconnect_and_reconnect(&client_a, cx_a).await;
6420    disconnect_and_reconnect(&client_b, cx_b).await;
6421    disconnect_and_reconnect(&client_c, cx_c).await;
6422    executor.run_until_parked();
6423    assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
6424    assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
6425    assert_eq!(
6426        client_b.summarize_contacts(cx_b).incoming_requests,
6427        &["user_c"]
6428    );
6429    assert!(client_c.summarize_contacts(cx_c).current.is_empty());
6430    assert_eq!(
6431        client_c.summarize_contacts(cx_c).outgoing_requests,
6432        &["user_b"]
6433    );
6434
6435    // User B rejects the request from user C.
6436    client_b
6437        .user_store()
6438        .update(cx_b, |store, cx| {
6439            store.respond_to_contact_request(client_c.user_id().unwrap(), false, cx)
6440        })
6441        .await
6442        .unwrap();
6443
6444    executor.run_until_parked();
6445
6446    // User B doesn't see user C as their contact, and the incoming request from them is removed.
6447    let contacts_b = client_b.summarize_contacts(cx_b);
6448    assert_eq!(contacts_b.current, &["user_a"]);
6449    assert!(contacts_b.incoming_requests.is_empty());
6450    let contacts_b2 = client_b2.summarize_contacts(cx_b2);
6451    assert_eq!(contacts_b2.current, &["user_a"]);
6452    assert!(contacts_b2.incoming_requests.is_empty());
6453
6454    // User C doesn't see user B as their contact, and the outgoing request to them is removed.
6455    let contacts_c = client_c.summarize_contacts(cx_c);
6456    assert!(contacts_c.current.is_empty());
6457    assert!(contacts_c.outgoing_requests.is_empty());
6458    let contacts_c2 = client_c2.summarize_contacts(cx_c2);
6459    assert!(contacts_c2.current.is_empty());
6460    assert!(contacts_c2.outgoing_requests.is_empty());
6461
6462    // Incoming/outgoing requests are not present upon connecting (tested here via disconnect/reconnect)
6463    disconnect_and_reconnect(&client_a, cx_a).await;
6464    disconnect_and_reconnect(&client_b, cx_b).await;
6465    disconnect_and_reconnect(&client_c, cx_c).await;
6466    executor.run_until_parked();
6467    assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
6468    assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
6469    assert!(
6470        client_b
6471            .summarize_contacts(cx_b)
6472            .incoming_requests
6473            .is_empty()
6474    );
6475    assert!(client_c.summarize_contacts(cx_c).current.is_empty());
6476    assert!(
6477        client_c
6478            .summarize_contacts(cx_c)
6479            .outgoing_requests
6480            .is_empty()
6481    );
6482
6483    async fn disconnect_and_reconnect(client: &TestClient, cx: &mut TestAppContext) {
6484        client.disconnect(&cx.to_async());
6485        client.clear_contacts(cx).await;
6486        client
6487            .connect(false, &cx.to_async())
6488            .await
6489            .into_response()
6490            .unwrap();
6491    }
6492}
6493
6494#[gpui::test(iterations = 10)]
6495async fn test_join_call_after_screen_was_shared(
6496    executor: BackgroundExecutor,
6497    cx_a: &mut TestAppContext,
6498    cx_b: &mut TestAppContext,
6499) {
6500    let mut server = TestServer::start(executor.clone()).await;
6501
6502    let client_a = server.create_client(cx_a, "user_a").await;
6503    let client_b = server.create_client(cx_b, "user_b").await;
6504    server
6505        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6506        .await;
6507
6508    let active_call_a = cx_a.read(ActiveCall::global);
6509    let active_call_b = cx_b.read(ActiveCall::global);
6510
6511    // Call users B and C from client A.
6512    active_call_a
6513        .update(cx_a, |call, cx| {
6514            call.invite(client_b.user_id().unwrap(), None, cx)
6515        })
6516        .await
6517        .unwrap();
6518
6519    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
6520    executor.run_until_parked();
6521    assert_eq!(
6522        room_participants(&room_a, cx_a),
6523        RoomParticipants {
6524            remote: Default::default(),
6525            pending: vec!["user_b".to_string()]
6526        }
6527    );
6528
6529    // User B receives the call.
6530
6531    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
6532    let call_b = incoming_call_b.next().await.unwrap().unwrap();
6533    assert_eq!(call_b.calling_user.github_login, "user_a");
6534
6535    // User A shares their screen
6536    let display = gpui::TestScreenCaptureSource::new();
6537    cx_a.set_screen_capture_sources(vec![display]);
6538    let screen_a = cx_a
6539        .update(|cx| cx.screen_capture_sources())
6540        .await
6541        .unwrap()
6542        .unwrap()
6543        .into_iter()
6544        .next()
6545        .unwrap();
6546
6547    active_call_a
6548        .update(cx_a, |call, cx| {
6549            call.room()
6550                .unwrap()
6551                .update(cx, |room, cx| room.share_screen(screen_a, cx))
6552        })
6553        .await
6554        .unwrap();
6555
6556    client_b.user_store().update(cx_b, |user_store, _| {
6557        user_store.clear_cache();
6558    });
6559
6560    // User B joins the room
6561    active_call_b
6562        .update(cx_b, |call, cx| call.accept_incoming(cx))
6563        .await
6564        .unwrap();
6565
6566    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
6567    assert!(incoming_call_b.next().await.unwrap().is_none());
6568
6569    executor.run_until_parked();
6570    assert_eq!(
6571        room_participants(&room_a, cx_a),
6572        RoomParticipants {
6573            remote: vec!["user_b".to_string()],
6574            pending: vec![],
6575        }
6576    );
6577    assert_eq!(
6578        room_participants(&room_b, cx_b),
6579        RoomParticipants {
6580            remote: vec!["user_a".to_string()],
6581            pending: vec![],
6582        }
6583    );
6584
6585    // Ensure User B sees User A's screenshare.
6586
6587    room_b.read_with(cx_b, |room, _| {
6588        assert_eq!(
6589            room.remote_participants()
6590                .get(&client_a.user_id().unwrap())
6591                .unwrap()
6592                .video_tracks
6593                .len(),
6594            1
6595        );
6596    });
6597}
6598
6599#[cfg(target_os = "linux")]
6600#[gpui::test(iterations = 10)]
6601async fn test_share_screen_wayland(
6602    executor: BackgroundExecutor,
6603    cx_a: &mut TestAppContext,
6604    cx_b: &mut TestAppContext,
6605) {
6606    let mut server = TestServer::start(executor.clone()).await;
6607
6608    let client_a = server.create_client(cx_a, "user_a").await;
6609    let client_b = server.create_client(cx_b, "user_b").await;
6610    server
6611        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6612        .await;
6613
6614    let active_call_a = cx_a.read(ActiveCall::global);
6615    let active_call_b = cx_b.read(ActiveCall::global);
6616
6617    // User A calls user B.
6618    active_call_a
6619        .update(cx_a, |call, cx| {
6620            call.invite(client_b.user_id().unwrap(), None, cx)
6621        })
6622        .await
6623        .unwrap();
6624
6625    // User B accepts.
6626    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
6627    executor.run_until_parked();
6628    incoming_call_b.next().await.unwrap().unwrap();
6629    active_call_b
6630        .update(cx_b, |call, cx| call.accept_incoming(cx))
6631        .await
6632        .unwrap();
6633
6634    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
6635    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
6636    executor.run_until_parked();
6637
6638    // User A shares their screen via the Wayland path.
6639    let events_b = active_call_events(cx_b);
6640    active_call_a
6641        .update(cx_a, |call, cx| {
6642            call.room()
6643                .unwrap()
6644                .update(cx, |room, cx| room.share_screen_wayland(cx))
6645        })
6646        .await
6647        .unwrap();
6648
6649    executor.run_until_parked();
6650
6651    // Room A is sharing and has a nonzero synthetic screen ID.
6652    room_a.read_with(cx_a, |room, _| {
6653        assert!(room.is_sharing_screen());
6654        let screen_id = room.shared_screen_id();
6655        assert!(screen_id.is_some(), "shared_screen_id should be Some");
6656        assert_ne!(screen_id.unwrap(), 0, "synthetic ID must be nonzero");
6657    });
6658
6659    // User B observes the remote screen sharing track.
6660    assert_eq!(events_b.borrow().len(), 1);
6661    if let call::room::Event::RemoteVideoTracksChanged { participant_id } =
6662        events_b.borrow().first().unwrap()
6663    {
6664        assert_eq!(*participant_id, client_a.peer_id().unwrap());
6665        room_b.read_with(cx_b, |room, _| {
6666            assert_eq!(
6667                room.remote_participants()[&client_a.user_id().unwrap()]
6668                    .video_tracks
6669                    .len(),
6670                1
6671            );
6672        });
6673    } else {
6674        panic!("expected RemoteVideoTracksChanged event");
6675    }
6676}
6677
6678#[cfg(target_os = "linux")]
6679#[gpui::test(iterations = 10)]
6680async fn test_unshare_screen_wayland(
6681    executor: BackgroundExecutor,
6682    cx_a: &mut TestAppContext,
6683    cx_b: &mut TestAppContext,
6684) {
6685    let mut server = TestServer::start(executor.clone()).await;
6686
6687    let client_a = server.create_client(cx_a, "user_a").await;
6688    let client_b = server.create_client(cx_b, "user_b").await;
6689    server
6690        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6691        .await;
6692
6693    let active_call_a = cx_a.read(ActiveCall::global);
6694    let active_call_b = cx_b.read(ActiveCall::global);
6695
6696    // User A calls user B.
6697    active_call_a
6698        .update(cx_a, |call, cx| {
6699            call.invite(client_b.user_id().unwrap(), None, cx)
6700        })
6701        .await
6702        .unwrap();
6703
6704    // User B accepts.
6705    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
6706    executor.run_until_parked();
6707    incoming_call_b.next().await.unwrap().unwrap();
6708    active_call_b
6709        .update(cx_b, |call, cx| call.accept_incoming(cx))
6710        .await
6711        .unwrap();
6712
6713    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
6714    executor.run_until_parked();
6715
6716    // User A shares their screen via the Wayland path.
6717    active_call_a
6718        .update(cx_a, |call, cx| {
6719            call.room()
6720                .unwrap()
6721                .update(cx, |room, cx| room.share_screen_wayland(cx))
6722        })
6723        .await
6724        .unwrap();
6725    executor.run_until_parked();
6726
6727    room_a.read_with(cx_a, |room, _| {
6728        assert!(room.is_sharing_screen());
6729    });
6730
6731    // User A stops sharing.
6732    room_a
6733        .update(cx_a, |room, cx| room.unshare_screen(true, cx))
6734        .unwrap();
6735    executor.run_until_parked();
6736
6737    // Room A is no longer sharing, screen ID is gone.
6738    room_a.read_with(cx_a, |room, _| {
6739        assert!(!room.is_sharing_screen());
6740        assert!(room.shared_screen_id().is_none());
6741    });
6742}
6743
6744#[gpui::test]
6745async fn test_right_click_menu_behind_collab_panel(cx: &mut TestAppContext) {
6746    let mut server = TestServer::start(cx.executor().clone()).await;
6747    let client_a = server.create_client(cx, "user_a").await;
6748    let (_workspace_a, cx) = client_a.build_test_workspace(cx).await;
6749
6750    cx.simulate_resize(size(px(300.), px(300.)));
6751
6752    cx.simulate_keystrokes("cmd-n cmd-n cmd-n");
6753    cx.update(|window, _cx| window.refresh());
6754
6755    let new_tab_button_bounds = cx.debug_bounds("ICON-Plus").unwrap();
6756
6757    cx.simulate_event(MouseDownEvent {
6758        button: MouseButton::Right,
6759        position: new_tab_button_bounds.center(),
6760        modifiers: Modifiers::default(),
6761        click_count: 1,
6762        first_mouse: false,
6763    });
6764
6765    // regression test that the right click menu for tabs does not open.
6766    assert!(cx.debug_bounds("MENU_ITEM-Close").is_none());
6767
6768    let tab_bounds = cx.debug_bounds("TAB-1").unwrap();
6769    cx.simulate_event(MouseDownEvent {
6770        button: MouseButton::Right,
6771        position: tab_bounds.center(),
6772        modifiers: Modifiers::default(),
6773        click_count: 1,
6774        first_mouse: false,
6775    });
6776    assert!(cx.debug_bounds("MENU_ITEM-Close").is_some());
6777}
6778
6779#[gpui::test]
6780async fn test_pane_split_left(cx: &mut TestAppContext) {
6781    let (_, client) = TestServer::start1(cx).await;
6782    let (workspace, cx) = client.build_test_workspace(cx).await;
6783
6784    cx.simulate_keystrokes("cmd-n");
6785    workspace.update(cx, |workspace, cx| {
6786        assert!(workspace.items(cx).collect::<Vec<_>>().len() == 1);
6787    });
6788    cx.simulate_keystrokes("cmd-k left");
6789    workspace.update(cx, |workspace, cx| {
6790        assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
6791    });
6792    cx.simulate_keystrokes("cmd-k");
6793    // Sleep past the historical timeout to ensure the multi-stroke binding
6794    // still fires now that unambiguous prefixes no longer auto-expire.
6795    cx.executor().advance_clock(Duration::from_secs(2));
6796    cx.simulate_keystrokes("left");
6797    workspace.update(cx, |workspace, cx| {
6798        assert!(workspace.items(cx).collect::<Vec<_>>().len() == 3);
6799    });
6800}
6801
6802#[gpui::test]
6803async fn test_join_after_restart(cx1: &mut TestAppContext, cx2: &mut TestAppContext) {
6804    let (mut server, client) = TestServer::start1(cx1).await;
6805    let channel1 = server.make_public_channel("channel1", &client, cx1).await;
6806    let channel2 = server.make_public_channel("channel2", &client, cx1).await;
6807
6808    join_channel(channel1, &client, cx1).await.unwrap();
6809    drop(client);
6810
6811    let client2 = server.create_client(cx2, "user_a").await;
6812    join_channel(channel2, &client2, cx2).await.unwrap();
6813}
6814
6815#[gpui::test]
6816async fn test_preview_tabs(cx: &mut TestAppContext) {
6817    let (_server, client) = TestServer::start1(cx).await;
6818    let (workspace, cx) = client.build_test_workspace(cx).await;
6819    let project = workspace.read_with(cx, |workspace, _| workspace.project().clone());
6820
6821    let worktree_id = project.update(cx, |project, cx| {
6822        project.worktrees(cx).next().unwrap().read(cx).id()
6823    });
6824
6825    let path_1 = ProjectPath {
6826        worktree_id,
6827        path: rel_path("1.txt").into(),
6828    };
6829    let path_2 = ProjectPath {
6830        worktree_id,
6831        path: rel_path("2.js").into(),
6832    };
6833    let path_3 = ProjectPath {
6834        worktree_id,
6835        path: rel_path("3.rs").into(),
6836    };
6837
6838    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
6839
6840    let get_path = |pane: &Pane, idx: usize, cx: &App| {
6841        pane.item_for_index(idx).unwrap().project_path(cx).unwrap()
6842    };
6843
6844    // Opening item 3 as a "permanent" tab
6845    workspace
6846        .update_in(cx, |workspace, window, cx| {
6847            workspace.open_path(path_3.clone(), None, false, window, cx)
6848        })
6849        .await
6850        .unwrap();
6851
6852    pane.update(cx, |pane, cx| {
6853        assert_eq!(pane.items_len(), 1);
6854        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6855        assert_eq!(pane.preview_item_id(), None);
6856
6857        assert!(!pane.can_navigate_backward());
6858        assert!(!pane.can_navigate_forward());
6859    });
6860
6861    // Open item 1 as preview
6862    workspace
6863        .update_in(cx, |workspace, window, cx| {
6864            workspace.open_path_preview(path_1.clone(), None, true, true, true, window, cx)
6865        })
6866        .await
6867        .unwrap();
6868
6869    pane.update(cx, |pane, cx| {
6870        assert_eq!(pane.items_len(), 2);
6871        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6872        assert_eq!(get_path(pane, 1, cx), path_1.clone());
6873        assert_eq!(
6874            pane.preview_item_id(),
6875            Some(pane.items().nth(1).unwrap().item_id())
6876        );
6877
6878        assert!(pane.can_navigate_backward());
6879        assert!(!pane.can_navigate_forward());
6880    });
6881
6882    // Open item 2 as preview
6883    workspace
6884        .update_in(cx, |workspace, window, cx| {
6885            workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
6886        })
6887        .await
6888        .unwrap();
6889
6890    pane.update(cx, |pane, cx| {
6891        assert_eq!(pane.items_len(), 2);
6892        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6893        assert_eq!(get_path(pane, 1, cx), path_2.clone());
6894        assert_eq!(
6895            pane.preview_item_id(),
6896            Some(pane.items().nth(1).unwrap().item_id())
6897        );
6898
6899        assert!(pane.can_navigate_backward());
6900        assert!(!pane.can_navigate_forward());
6901    });
6902
6903    // Going back should show item 1 as preview
6904    workspace
6905        .update_in(cx, |workspace, window, cx| {
6906            workspace.go_back(pane.downgrade(), window, cx)
6907        })
6908        .await
6909        .unwrap();
6910
6911    pane.update(cx, |pane, cx| {
6912        assert_eq!(pane.items_len(), 2);
6913        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6914        assert_eq!(get_path(pane, 1, cx), path_1.clone());
6915        assert_eq!(
6916            pane.preview_item_id(),
6917            Some(pane.items().nth(1).unwrap().item_id())
6918        );
6919
6920        assert!(pane.can_navigate_backward());
6921        assert!(pane.can_navigate_forward());
6922    });
6923
6924    // Closing item 1
6925    pane.update_in(cx, |pane, window, cx| {
6926        pane.close_item_by_id(
6927            pane.active_item().unwrap().item_id(),
6928            workspace::SaveIntent::Skip,
6929            window,
6930            cx,
6931        )
6932    })
6933    .await
6934    .unwrap();
6935
6936    pane.update(cx, |pane, cx| {
6937        assert_eq!(pane.items_len(), 1);
6938        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6939        assert_eq!(pane.preview_item_id(), None);
6940
6941        assert!(pane.can_navigate_backward());
6942        assert!(!pane.can_navigate_forward());
6943    });
6944
6945    // Going back should show item 1 as preview
6946    workspace
6947        .update_in(cx, |workspace, window, cx| {
6948            workspace.go_back(pane.downgrade(), window, cx)
6949        })
6950        .await
6951        .unwrap();
6952
6953    pane.update(cx, |pane, cx| {
6954        assert_eq!(pane.items_len(), 2);
6955        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6956        assert_eq!(get_path(pane, 1, cx), path_1.clone());
6957        assert_eq!(
6958            pane.preview_item_id(),
6959            Some(pane.items().nth(1).unwrap().item_id())
6960        );
6961
6962        assert!(pane.can_navigate_backward());
6963        assert!(pane.can_navigate_forward());
6964    });
6965
6966    // Close permanent tab
6967    pane.update_in(cx, |pane, window, cx| {
6968        let id = pane.items().next().unwrap().item_id();
6969        pane.close_item_by_id(id, workspace::SaveIntent::Skip, window, cx)
6970    })
6971    .await
6972    .unwrap();
6973
6974    pane.update(cx, |pane, cx| {
6975        assert_eq!(pane.items_len(), 1);
6976        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6977        assert_eq!(
6978            pane.preview_item_id(),
6979            Some(pane.items().next().unwrap().item_id())
6980        );
6981
6982        assert!(pane.can_navigate_backward());
6983        assert!(pane.can_navigate_forward());
6984    });
6985
6986    // Split pane to the right
6987    pane.update_in(cx, |pane, window, cx| {
6988        pane.split(
6989            workspace::SplitDirection::Right,
6990            workspace::SplitMode::default(),
6991            window,
6992            cx,
6993        );
6994    });
6995    cx.run_until_parked();
6996    let right_pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
6997
6998    right_pane.update(cx, |pane, cx| {
6999        // Nav history is now cloned in an pane split, but that's inconvenient
7000        // for this test, which uses the presence of a backwards history item as
7001        // an indication that a preview item was successfully opened
7002        pane.nav_history_mut().clear(cx);
7003    });
7004
7005    pane.update(cx, |pane, cx| {
7006        assert_eq!(pane.items_len(), 1);
7007        assert_eq!(get_path(pane, 0, cx), path_1.clone());
7008        assert_eq!(
7009            pane.preview_item_id(),
7010            Some(pane.items().next().unwrap().item_id())
7011        );
7012
7013        assert!(pane.can_navigate_backward());
7014        assert!(pane.can_navigate_forward());
7015    });
7016
7017    right_pane.update(cx, |pane, cx| {
7018        assert_eq!(pane.items_len(), 1);
7019        assert_eq!(get_path(pane, 0, cx), path_1.clone());
7020        assert_eq!(pane.preview_item_id(), None);
7021
7022        assert!(!pane.can_navigate_backward());
7023        assert!(!pane.can_navigate_forward());
7024    });
7025
7026    // Open item 2 as preview in right pane
7027    workspace
7028        .update_in(cx, |workspace, window, cx| {
7029            workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
7030        })
7031        .await
7032        .unwrap();
7033
7034    pane.update(cx, |pane, cx| {
7035        assert_eq!(pane.items_len(), 1);
7036        assert_eq!(get_path(pane, 0, cx), path_1.clone());
7037        assert_eq!(
7038            pane.preview_item_id(),
7039            Some(pane.items().next().unwrap().item_id())
7040        );
7041
7042        assert!(pane.can_navigate_backward());
7043        assert!(pane.can_navigate_forward());
7044    });
7045
7046    right_pane.update(cx, |pane, cx| {
7047        assert_eq!(pane.items_len(), 2);
7048        assert_eq!(get_path(pane, 0, cx), path_1.clone());
7049        assert_eq!(get_path(pane, 1, cx), path_2.clone());
7050        assert_eq!(
7051            pane.preview_item_id(),
7052            Some(pane.items().nth(1).unwrap().item_id())
7053        );
7054
7055        assert!(pane.can_navigate_backward());
7056        assert!(!pane.can_navigate_forward());
7057    });
7058
7059    // Focus left pane
7060    workspace.update_in(cx, |workspace, window, cx| {
7061        workspace.activate_pane_in_direction(workspace::SplitDirection::Left, window, cx)
7062    });
7063
7064    // Open item 2 as preview in left pane
7065    workspace
7066        .update_in(cx, |workspace, window, cx| {
7067            workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
7068        })
7069        .await
7070        .unwrap();
7071
7072    pane.update(cx, |pane, cx| {
7073        assert_eq!(pane.items_len(), 1);
7074        assert_eq!(get_path(pane, 0, cx), path_2.clone());
7075        assert_eq!(
7076            pane.preview_item_id(),
7077            Some(pane.items().next().unwrap().item_id())
7078        );
7079
7080        assert!(pane.can_navigate_backward());
7081        assert!(!pane.can_navigate_forward());
7082    });
7083
7084    right_pane.update(cx, |pane, cx| {
7085        assert_eq!(pane.items_len(), 2);
7086        assert_eq!(get_path(pane, 0, cx), path_1.clone());
7087        assert_eq!(get_path(pane, 1, cx), path_2.clone());
7088        assert_eq!(
7089            pane.preview_item_id(),
7090            Some(pane.items().nth(1).unwrap().item_id())
7091        );
7092
7093        assert!(pane.can_navigate_backward());
7094        assert!(!pane.can_navigate_forward());
7095    });
7096}
7097
7098#[gpui::test(iterations = 10)]
7099async fn test_context_collaboration_with_reconnect(
7100    executor: BackgroundExecutor,
7101    cx_a: &mut TestAppContext,
7102    cx_b: &mut TestAppContext,
7103) {
7104    let mut server = TestServer::start(executor.clone()).await;
7105    let client_a = server.create_client(cx_a, "user_a").await;
7106    let client_b = server.create_client(cx_b, "user_b").await;
7107    server
7108        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
7109        .await;
7110    let active_call_a = cx_a.read(ActiveCall::global);
7111
7112    client_a.fs().insert_tree("/a", Default::default()).await;
7113    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
7114    let project_id = active_call_a
7115        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
7116        .await
7117        .unwrap();
7118    let project_b = client_b.join_remote_project(project_id, cx_b).await;
7119
7120    // Client A sees that a guest has joined.
7121    executor.run_until_parked();
7122
7123    project_a.read_with(cx_a, |project, _| {
7124        assert_eq!(project.collaborators().len(), 1);
7125    });
7126    project_b.read_with(cx_b, |project, _| {
7127        assert_eq!(project.collaborators().len(), 1);
7128    });
7129
7130    let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
7131    let text_thread_store_a = cx_a
7132        .update(|cx| {
7133            TextThreadStore::new(
7134                project_a.clone(),
7135                prompt_builder.clone(),
7136                Arc::new(SlashCommandWorkingSet::default()),
7137                cx,
7138            )
7139        })
7140        .await
7141        .unwrap();
7142    let text_thread_store_b = cx_b
7143        .update(|cx| {
7144            TextThreadStore::new(
7145                project_b.clone(),
7146                prompt_builder.clone(),
7147                Arc::new(SlashCommandWorkingSet::default()),
7148                cx,
7149            )
7150        })
7151        .await
7152        .unwrap();
7153
7154    // Client A creates a new chats.
7155    let text_thread_a = text_thread_store_a.update(cx_a, |store, cx| store.create(cx));
7156    executor.run_until_parked();
7157
7158    // Client B retrieves host's contexts and joins one.
7159    let text_thread_b = text_thread_store_b
7160        .update(cx_b, |store, cx| {
7161            let host_text_threads = store.host_text_threads().collect::<Vec<_>>();
7162            assert_eq!(host_text_threads.len(), 1);
7163            store.open_remote(host_text_threads[0].id.clone(), cx)
7164        })
7165        .await
7166        .unwrap();
7167
7168    // Host and guest make changes
7169    text_thread_a.update(cx_a, |text_thread, cx| {
7170        text_thread.buffer().update(cx, |buffer, cx| {
7171            buffer.edit([(0..0, "Host change\n")], None, cx)
7172        })
7173    });
7174    text_thread_b.update(cx_b, |text_thread, cx| {
7175        text_thread.buffer().update(cx, |buffer, cx| {
7176            buffer.edit([(0..0, "Guest change\n")], None, cx)
7177        })
7178    });
7179    executor.run_until_parked();
7180    assert_eq!(
7181        text_thread_a.read_with(cx_a, |text_thread, cx| text_thread.buffer().read(cx).text()),
7182        "Guest change\nHost change\n"
7183    );
7184    assert_eq!(
7185        text_thread_b.read_with(cx_b, |text_thread, cx| text_thread.buffer().read(cx).text()),
7186        "Guest change\nHost change\n"
7187    );
7188
7189    // Disconnect client A and make some changes while disconnected.
7190    server.disconnect_client(client_a.peer_id().unwrap());
7191    server.forbid_connections();
7192    text_thread_a.update(cx_a, |text_thread, cx| {
7193        text_thread.buffer().update(cx, |buffer, cx| {
7194            buffer.edit([(0..0, "Host offline change\n")], None, cx)
7195        })
7196    });
7197    text_thread_b.update(cx_b, |text_thread, cx| {
7198        text_thread.buffer().update(cx, |buffer, cx| {
7199            buffer.edit([(0..0, "Guest offline change\n")], None, cx)
7200        })
7201    });
7202    executor.run_until_parked();
7203    assert_eq!(
7204        text_thread_a.read_with(cx_a, |text_thread, cx| text_thread.buffer().read(cx).text()),
7205        "Host offline change\nGuest change\nHost change\n"
7206    );
7207    assert_eq!(
7208        text_thread_b.read_with(cx_b, |text_thread, cx| text_thread.buffer().read(cx).text()),
7209        "Guest offline change\nGuest change\nHost change\n"
7210    );
7211
7212    // Allow client A to reconnect and verify that contexts converge.
7213    server.allow_connections();
7214    executor.advance_clock(RECEIVE_TIMEOUT);
7215    assert_eq!(
7216        text_thread_a.read_with(cx_a, |text_thread, cx| text_thread.buffer().read(cx).text()),
7217        "Guest offline change\nHost offline change\nGuest change\nHost change\n"
7218    );
7219    assert_eq!(
7220        text_thread_b.read_with(cx_b, |text_thread, cx| text_thread.buffer().read(cx).text()),
7221        "Guest offline change\nHost offline change\nGuest change\nHost change\n"
7222    );
7223
7224    // Client A disconnects without being able to reconnect. Context B becomes readonly.
7225    server.forbid_connections();
7226    server.disconnect_client(client_a.peer_id().unwrap());
7227    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
7228    text_thread_b.read_with(cx_b, |text_thread, cx| {
7229        assert!(text_thread.buffer().read(cx).read_only());
7230    });
7231}
7232
7233#[gpui::test]
7234async fn test_remote_git_branches(
7235    executor: BackgroundExecutor,
7236    cx_a: &mut TestAppContext,
7237    cx_b: &mut TestAppContext,
7238) {
7239    let mut server = TestServer::start(executor.clone()).await;
7240    let client_a = server.create_client(cx_a, "user_a").await;
7241    let client_b = server.create_client(cx_b, "user_b").await;
7242    server
7243        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
7244        .await;
7245    let active_call_a = cx_a.read(ActiveCall::global);
7246
7247    client_a
7248        .fs()
7249        .insert_tree("/project", serde_json::json!({ ".git":{} }))
7250        .await;
7251    let branches = ["main", "dev", "feature-1"];
7252    client_a
7253        .fs()
7254        .insert_branches(Path::new("/project/.git"), &branches);
7255    let branches_set = branches
7256        .into_iter()
7257        .map(ToString::to_string)
7258        .collect::<HashSet<_>>();
7259
7260    let (project_a, _) = client_a.build_local_project("/project", cx_a).await;
7261
7262    let project_id = active_call_a
7263        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
7264        .await
7265        .unwrap();
7266    let project_b = client_b.join_remote_project(project_id, cx_b).await;
7267
7268    // Client A sees that a guest has joined and the repo has been populated
7269    executor.run_until_parked();
7270
7271    let repo_b = cx_b.update(|cx| project_b.read(cx).active_repository(cx).unwrap());
7272
7273    let branches_b = cx_b
7274        .update(|cx| repo_b.update(cx, |repository, _| repository.branches()))
7275        .await
7276        .unwrap()
7277        .unwrap();
7278
7279    let new_branch = branches[2];
7280
7281    let branches_b = branches_b
7282        .into_iter()
7283        .map(|branch| branch.name().to_string())
7284        .collect::<HashSet<_>>();
7285
7286    assert_eq!(branches_b, branches_set);
7287
7288    cx_b.update(|cx| {
7289        repo_b.update(cx, |repository, _cx| {
7290            repository.change_branch(new_branch.to_string())
7291        })
7292    })
7293    .await
7294    .unwrap()
7295    .unwrap();
7296
7297    executor.run_until_parked();
7298
7299    let host_branch = cx_a.update(|cx| {
7300        project_a.update(cx, |project, cx| {
7301            project
7302                .repositories(cx)
7303                .values()
7304                .next()
7305                .unwrap()
7306                .read(cx)
7307                .branch
7308                .as_ref()
7309                .unwrap()
7310                .clone()
7311        })
7312    });
7313
7314    assert_eq!(host_branch.name(), branches[2]);
7315
7316    // Also try creating a new branch
7317    cx_b.update(|cx| {
7318        repo_b.update(cx, |repository, _cx| {
7319            repository.create_branch("totally-new-branch".to_string(), None)
7320        })
7321    })
7322    .await
7323    .unwrap()
7324    .unwrap();
7325
7326    cx_b.update(|cx| {
7327        repo_b.update(cx, |repository, _cx| {
7328            repository.change_branch("totally-new-branch".to_string())
7329        })
7330    })
7331    .await
7332    .unwrap()
7333    .unwrap();
7334
7335    executor.run_until_parked();
7336
7337    let host_branch = cx_a.update(|cx| {
7338        project_a.update(cx, |project, cx| {
7339            project
7340                .repositories(cx)
7341                .values()
7342                .next()
7343                .unwrap()
7344                .read(cx)
7345                .branch
7346                .as_ref()
7347                .unwrap()
7348                .clone()
7349        })
7350    });
7351
7352    assert_eq!(host_branch.name(), "totally-new-branch");
7353}
7354
7355#[gpui::test]
7356async fn test_guest_can_rejoin_shared_project_after_leaving_call(
7357    executor: BackgroundExecutor,
7358    cx_a: &mut TestAppContext,
7359    cx_b: &mut TestAppContext,
7360    cx_c: &mut TestAppContext,
7361) {
7362    let mut server = TestServer::start(executor.clone()).await;
7363    let client_a = server.create_client(cx_a, "user_a").await;
7364    let client_b = server.create_client(cx_b, "user_b").await;
7365    let client_c = server.create_client(cx_c, "user_c").await;
7366
7367    server
7368        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
7369        .await;
7370
7371    client_a
7372        .fs()
7373        .insert_tree(
7374            path!("/project"),
7375            json!({
7376                "file.txt": "hello\n",
7377            }),
7378        )
7379        .await;
7380
7381    let (project_a, _worktree_id) = client_a.build_local_project(path!("/project"), cx_a).await;
7382    let active_call_a = cx_a.read(ActiveCall::global);
7383    let project_id = active_call_a
7384        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
7385        .await
7386        .unwrap();
7387
7388    let _project_b = client_b.join_remote_project(project_id, cx_b).await;
7389    executor.run_until_parked();
7390
7391    // third client joins call to prevent room from being torn down
7392    let _project_c = client_c.join_remote_project(project_id, cx_c).await;
7393    executor.run_until_parked();
7394
7395    let active_call_b = cx_b.read(ActiveCall::global);
7396    active_call_b
7397        .update(cx_b, |call, cx| call.hang_up(cx))
7398        .await
7399        .unwrap();
7400    executor.run_until_parked();
7401
7402    let user_id_b = client_b.current_user_id(cx_b).to_proto();
7403    let active_call_a = cx_a.read(ActiveCall::global);
7404    active_call_a
7405        .update(cx_a, |call, cx| call.invite(user_id_b, None, cx))
7406        .await
7407        .unwrap();
7408    executor.run_until_parked();
7409    let active_call_b = cx_b.read(ActiveCall::global);
7410    active_call_b
7411        .update(cx_b, |call, cx| call.accept_incoming(cx))
7412        .await
7413        .unwrap();
7414    executor.run_until_parked();
7415
7416    let _project_b2 = client_b.join_remote_project(project_id, cx_b).await;
7417    executor.run_until_parked();
7418
7419    project_a.read_with(cx_a, |project, _| {
7420        let guest_count = project
7421            .collaborators()
7422            .values()
7423            .filter(|c| !c.is_host)
7424            .count();
7425
7426        assert_eq!(
7427            guest_count, 2,
7428            "host should have exactly one guest collaborator after rejoin"
7429        );
7430    });
7431
7432    _project_b.read_with(cx_b, |project, _| {
7433        assert_eq!(
7434            project.client_subscriptions().len(),
7435            0,
7436            "We should clear all host subscriptions after leaving the project"
7437        );
7438    })
7439}