
There's still a bit more work to do on this, but this PR is compiling (with warnings) after eliminating the key types. When the tasks below are complete, this will be the new narrative for GPUI: - `Entity<T>` - This replaces `View<T>`/`Model<T>`. It represents a unit of state, and if `T` implements `Render`, then `Entity<T>` implements `Element`. - `&mut App` This replaces `AppContext` and represents the app. - `&mut Context<T>` This replaces `ModelContext` and derefs to `App`. It is provided by the framework when updating an entity. - `&mut Window` Broken out of `&mut WindowContext` which no longer exists. Every method that once took `&mut WindowContext` now takes `&mut Window, &mut App` and every method that took `&mut ViewContext<T>` now takes `&mut Window, &mut Context<T>` Not pictured here are the two other failed attempts. It's been quite a month! Tasks: - [x] Remove `View`, `ViewContext`, `WindowContext` and thread through `Window` - [x] [@cole-miller @mikayla-maki] Redraw window when entities change - [x] [@cole-miller @mikayla-maki] Get examples and Zed running - [x] [@cole-miller @mikayla-maki] Fix Zed rendering - [x] [@mikayla-maki] Fix todo! macros and comments - [x] Fix a bug where the editor would not be redrawn because of view caching - [x] remove publicness window.notify() and replace with `AppContext::notify` - [x] remove `observe_new_window_models`, replace with `observe_new_models` with an optional window - [x] Fix a bug where the project panel would not be redrawn because of the wrong refresh() call being used - [x] Fix the tests - [x] Fix warnings by eliminating `Window` params or using `_` - [x] Fix conflicts - [x] Simplify generic code where possible - [x] Rename types - [ ] Update docs ### issues post merge - [x] Issues switching between normal and insert mode - [x] Assistant re-rendering failure - [x] Vim test failures - [x] Mac build issue Release Notes: - N/A --------- Co-authored-by: Antonio Scandurra <me@as-cii.com> Co-authored-by: Cole Miller <cole@zed.dev> Co-authored-by: Mikayla <mikayla@zed.dev> Co-authored-by: Joseph <joseph@zed.dev> Co-authored-by: max <max@zed.dev> Co-authored-by: Michael Sloan <michael@zed.dev> Co-authored-by: Mikayla Maki <mikaylamaki@Mikaylas-MacBook-Pro.local> Co-authored-by: Mikayla <mikayla.c.maki@gmail.com> Co-authored-by: joão <joao@zed.dev>
1460 lines
40 KiB
Rust
1460 lines
40 KiB
Rust
use crate::{
|
|
db::{self, UserId},
|
|
rpc::RECONNECT_TIMEOUT,
|
|
tests::{room_participants, RoomParticipants, TestServer},
|
|
};
|
|
use call::ActiveCall;
|
|
use channel::{ChannelMembership, ChannelStore};
|
|
use client::{ChannelId, User};
|
|
use futures::future::try_join_all;
|
|
use gpui::{BackgroundExecutor, Entity, SharedString, TestAppContext};
|
|
use rpc::{
|
|
proto::{self, ChannelRole},
|
|
RECEIVE_TIMEOUT,
|
|
};
|
|
use std::sync::Arc;
|
|
|
|
#[gpui::test]
|
|
async fn test_core_channels(
|
|
executor: BackgroundExecutor,
|
|
cx_a: &mut TestAppContext,
|
|
cx_b: &mut TestAppContext,
|
|
) {
|
|
let mut server = TestServer::start(executor.clone()).await;
|
|
let client_a = server.create_client(cx_a, "user_a").await;
|
|
let client_b = server.create_client(cx_b, "user_b").await;
|
|
|
|
let channel_a_id = client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.create_channel("channel-a", None, cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
let channel_b_id = client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.create_channel("channel-b", Some(channel_a_id), cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
assert_channels(
|
|
client_a.channel_store(),
|
|
cx_a,
|
|
&[
|
|
ExpectedChannel {
|
|
id: channel_a_id,
|
|
name: "channel-a".into(),
|
|
depth: 0,
|
|
},
|
|
ExpectedChannel {
|
|
id: channel_b_id,
|
|
name: "channel-b".into(),
|
|
depth: 1,
|
|
},
|
|
],
|
|
);
|
|
|
|
cx_b.read(|cx| {
|
|
client_b.channel_store().read_with(cx, |channels, _| {
|
|
assert!(channels.ordered_channels().collect::<Vec<_>>().is_empty())
|
|
})
|
|
});
|
|
|
|
// Invite client B to channel A as client A.
|
|
client_a
|
|
.channel_store()
|
|
.update(cx_a, |store, cx| {
|
|
assert!(!store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
|
|
|
|
let invite = store.invite_member(
|
|
channel_a_id,
|
|
client_b.user_id().unwrap(),
|
|
proto::ChannelRole::Member,
|
|
cx,
|
|
);
|
|
|
|
// Make sure we're synchronously storing the pending invite
|
|
assert!(store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
|
|
invite
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
// Client A sees that B has been invited.
|
|
executor.run_until_parked();
|
|
assert_channel_invitations(
|
|
client_b.channel_store(),
|
|
cx_b,
|
|
&[ExpectedChannel {
|
|
id: channel_a_id,
|
|
name: "channel-a".into(),
|
|
depth: 0,
|
|
}],
|
|
);
|
|
|
|
let members = client_a
|
|
.channel_store()
|
|
.update(cx_a, |store, cx| {
|
|
assert!(!store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
|
|
store.fuzzy_search_members(channel_a_id, "".to_string(), 10, cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
assert_members_eq(
|
|
&members,
|
|
&[
|
|
(
|
|
client_a.user_id().unwrap(),
|
|
proto::ChannelRole::Admin,
|
|
proto::channel_member::Kind::Member,
|
|
),
|
|
(
|
|
client_b.user_id().unwrap(),
|
|
proto::ChannelRole::Member,
|
|
proto::channel_member::Kind::Invitee,
|
|
),
|
|
],
|
|
);
|
|
|
|
// Client B accepts the invitation.
|
|
client_b
|
|
.channel_store()
|
|
.update(cx_b, |channels, cx| {
|
|
channels.respond_to_channel_invite(channel_a_id, true, cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
executor.run_until_parked();
|
|
|
|
// Client B now sees that they are a member of channel A and its existing subchannels.
|
|
assert_channel_invitations(client_b.channel_store(), cx_b, &[]);
|
|
assert_channels(
|
|
client_b.channel_store(),
|
|
cx_b,
|
|
&[
|
|
ExpectedChannel {
|
|
id: channel_a_id,
|
|
name: "channel-a".into(),
|
|
depth: 0,
|
|
},
|
|
ExpectedChannel {
|
|
id: channel_b_id,
|
|
name: "channel-b".into(),
|
|
depth: 1,
|
|
},
|
|
],
|
|
);
|
|
|
|
let channel_c_id = client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.create_channel("channel-c", Some(channel_b_id), cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
assert_channels(
|
|
client_b.channel_store(),
|
|
cx_b,
|
|
&[
|
|
ExpectedChannel {
|
|
id: channel_a_id,
|
|
name: "channel-a".into(),
|
|
depth: 0,
|
|
},
|
|
ExpectedChannel {
|
|
id: channel_b_id,
|
|
name: "channel-b".into(),
|
|
depth: 1,
|
|
},
|
|
ExpectedChannel {
|
|
id: channel_c_id,
|
|
name: "channel-c".into(),
|
|
depth: 2,
|
|
},
|
|
],
|
|
);
|
|
|
|
// Update client B's membership to channel A to be an admin.
|
|
client_a
|
|
.channel_store()
|
|
.update(cx_a, |store, cx| {
|
|
store.set_member_role(
|
|
channel_a_id,
|
|
client_b.user_id().unwrap(),
|
|
proto::ChannelRole::Admin,
|
|
cx,
|
|
)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
executor.run_until_parked();
|
|
|
|
// Observe that client B is now an admin of channel A, and that
|
|
// their admin privileges extend to subchannels of channel A.
|
|
assert_channel_invitations(client_b.channel_store(), cx_b, &[]);
|
|
assert_channels(
|
|
client_b.channel_store(),
|
|
cx_b,
|
|
&[
|
|
ExpectedChannel {
|
|
id: channel_a_id,
|
|
name: "channel-a".into(),
|
|
depth: 0,
|
|
},
|
|
ExpectedChannel {
|
|
id: channel_b_id,
|
|
name: "channel-b".into(),
|
|
depth: 1,
|
|
},
|
|
ExpectedChannel {
|
|
id: channel_c_id,
|
|
name: "channel-c".into(),
|
|
depth: 2,
|
|
},
|
|
],
|
|
);
|
|
|
|
// Client A deletes the channel, deletion also deletes subchannels.
|
|
client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, _| {
|
|
channel_store.remove_channel(channel_b_id)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
assert_channels(
|
|
client_a.channel_store(),
|
|
cx_a,
|
|
&[ExpectedChannel {
|
|
id: channel_a_id,
|
|
name: "channel-a".into(),
|
|
depth: 0,
|
|
}],
|
|
);
|
|
assert_channels(
|
|
client_b.channel_store(),
|
|
cx_b,
|
|
&[ExpectedChannel {
|
|
id: channel_a_id,
|
|
name: "channel-a".into(),
|
|
depth: 0,
|
|
}],
|
|
);
|
|
|
|
// Remove client B
|
|
client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.remove_member(channel_a_id, client_b.user_id().unwrap(), cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
// Client A still has their channel
|
|
assert_channels(
|
|
client_a.channel_store(),
|
|
cx_a,
|
|
&[ExpectedChannel {
|
|
id: channel_a_id,
|
|
name: "channel-a".into(),
|
|
depth: 0,
|
|
}],
|
|
);
|
|
|
|
// Client B no longer has access to the channel
|
|
assert_channels(client_b.channel_store(), cx_b, &[]);
|
|
|
|
server.forbid_connections();
|
|
server.disconnect_client(client_a.peer_id().unwrap());
|
|
executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
|
|
|
|
server
|
|
.app_state
|
|
.db
|
|
.rename_channel(
|
|
db::ChannelId::from_proto(channel_a_id.0),
|
|
UserId::from_proto(client_a.id()),
|
|
"channel-a-renamed",
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
server.allow_connections();
|
|
executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
|
|
assert_channels(
|
|
client_a.channel_store(),
|
|
cx_a,
|
|
&[ExpectedChannel {
|
|
id: channel_a_id,
|
|
name: "channel-a-renamed".into(),
|
|
depth: 0,
|
|
}],
|
|
);
|
|
}
|
|
|
|
#[track_caller]
|
|
fn assert_participants_eq(participants: &[Arc<User>], expected_partitipants: &[u64]) {
|
|
assert_eq!(
|
|
participants.iter().map(|p| p.id).collect::<Vec<_>>(),
|
|
expected_partitipants
|
|
);
|
|
}
|
|
|
|
#[track_caller]
|
|
fn assert_members_eq(
|
|
members: &[ChannelMembership],
|
|
expected_members: &[(u64, proto::ChannelRole, proto::channel_member::Kind)],
|
|
) {
|
|
assert_eq!(
|
|
members
|
|
.iter()
|
|
.map(|member| (member.user.id, member.role, member.kind))
|
|
.collect::<Vec<_>>(),
|
|
expected_members
|
|
);
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn test_joining_channel_ancestor_member(
|
|
executor: BackgroundExecutor,
|
|
cx_a: &mut TestAppContext,
|
|
cx_b: &mut TestAppContext,
|
|
) {
|
|
let mut server = TestServer::start(executor.clone()).await;
|
|
|
|
let client_a = server.create_client(cx_a, "user_a").await;
|
|
let client_b = server.create_client(cx_b, "user_b").await;
|
|
|
|
let parent_id = server
|
|
.make_channel("parent", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
|
|
.await;
|
|
|
|
let sub_id = client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.create_channel("sub_channel", Some(parent_id), cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
let active_call_b = cx_b.read(ActiveCall::global);
|
|
|
|
assert!(active_call_b
|
|
.update(cx_b, |active_call, cx| active_call.join_channel(sub_id, cx))
|
|
.await
|
|
.is_ok());
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn test_channel_room(
|
|
executor: BackgroundExecutor,
|
|
cx_a: &mut TestAppContext,
|
|
cx_b: &mut TestAppContext,
|
|
cx_c: &mut TestAppContext,
|
|
) {
|
|
let mut server = TestServer::start(executor.clone()).await;
|
|
let client_a = server.create_client(cx_a, "user_a").await;
|
|
let client_b = server.create_client(cx_b, "user_b").await;
|
|
let client_c = server.create_client(cx_c, "user_c").await;
|
|
|
|
let zed_id = server
|
|
.make_channel(
|
|
"zed",
|
|
None,
|
|
(&client_a, cx_a),
|
|
&mut [(&client_b, cx_b), (&client_c, cx_c)],
|
|
)
|
|
.await;
|
|
|
|
let active_call_a = cx_a.read(ActiveCall::global);
|
|
let active_call_b = cx_b.read(ActiveCall::global);
|
|
|
|
active_call_a
|
|
.update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
|
|
.await
|
|
.unwrap();
|
|
|
|
// Give everyone a chance to observe user A joining
|
|
executor.run_until_parked();
|
|
let room_a =
|
|
cx_a.read(|cx| active_call_a.read_with(cx, |call, _| call.room().unwrap().clone()));
|
|
cx_a.read(|cx| room_a.read_with(cx, |room, _| assert!(room.is_connected())));
|
|
|
|
cx_a.read(|cx| {
|
|
client_a.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(
|
|
channels.channel_participants(zed_id),
|
|
&[client_a.user_id().unwrap()],
|
|
);
|
|
})
|
|
});
|
|
|
|
assert_channels(
|
|
client_b.channel_store(),
|
|
cx_b,
|
|
&[ExpectedChannel {
|
|
id: zed_id,
|
|
name: "zed".into(),
|
|
depth: 0,
|
|
}],
|
|
);
|
|
cx_b.read(|cx| {
|
|
client_b.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(
|
|
channels.channel_participants(zed_id),
|
|
&[client_a.user_id().unwrap()],
|
|
);
|
|
})
|
|
});
|
|
|
|
cx_c.read(|cx| {
|
|
client_c.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(
|
|
channels.channel_participants(zed_id),
|
|
&[client_a.user_id().unwrap()],
|
|
);
|
|
})
|
|
});
|
|
|
|
active_call_b
|
|
.update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
cx_a.read(|cx| {
|
|
client_a.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(
|
|
channels.channel_participants(zed_id),
|
|
&[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
|
|
);
|
|
})
|
|
});
|
|
|
|
cx_b.read(|cx| {
|
|
client_b.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(
|
|
channels.channel_participants(zed_id),
|
|
&[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
|
|
);
|
|
})
|
|
});
|
|
|
|
cx_c.read(|cx| {
|
|
client_c.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(
|
|
channels.channel_participants(zed_id),
|
|
&[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
|
|
);
|
|
})
|
|
});
|
|
|
|
let room_a =
|
|
cx_a.read(|cx| active_call_a.read_with(cx, |call, _| call.room().unwrap().clone()));
|
|
cx_a.read(|cx| room_a.read_with(cx, |room, _| assert!(room.is_connected())));
|
|
assert_eq!(
|
|
room_participants(&room_a, cx_a),
|
|
RoomParticipants {
|
|
remote: vec!["user_b".to_string()],
|
|
pending: vec![]
|
|
}
|
|
);
|
|
|
|
let room_b =
|
|
cx_b.read(|cx| active_call_b.read_with(cx, |call, _| call.room().unwrap().clone()));
|
|
cx_b.read(|cx| room_b.read_with(cx, |room, _| assert!(room.is_connected())));
|
|
assert_eq!(
|
|
room_participants(&room_b, cx_b),
|
|
RoomParticipants {
|
|
remote: vec!["user_a".to_string()],
|
|
pending: vec![]
|
|
}
|
|
);
|
|
|
|
// Make sure that leaving and rejoining works
|
|
|
|
active_call_a
|
|
.update(cx_a, |active_call, cx| active_call.hang_up(cx))
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
cx_a.read(|cx| {
|
|
client_a.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(
|
|
channels.channel_participants(zed_id),
|
|
&[client_b.user_id().unwrap()],
|
|
);
|
|
})
|
|
});
|
|
|
|
cx_b.read(|cx| {
|
|
client_b.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(
|
|
channels.channel_participants(zed_id),
|
|
&[client_b.user_id().unwrap()],
|
|
);
|
|
})
|
|
});
|
|
|
|
cx_c.read(|cx| {
|
|
client_c.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(
|
|
channels.channel_participants(zed_id),
|
|
&[client_b.user_id().unwrap()],
|
|
);
|
|
})
|
|
});
|
|
|
|
active_call_b
|
|
.update(cx_b, |active_call, cx| active_call.hang_up(cx))
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
cx_a.read(|cx| {
|
|
client_a.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(channels.channel_participants(zed_id), &[]);
|
|
})
|
|
});
|
|
|
|
cx_b.read(|cx| {
|
|
client_b.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(channels.channel_participants(zed_id), &[]);
|
|
})
|
|
});
|
|
|
|
cx_c.read(|cx| {
|
|
client_c.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(channels.channel_participants(zed_id), &[]);
|
|
})
|
|
});
|
|
|
|
active_call_a
|
|
.update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
|
|
.await
|
|
.unwrap();
|
|
|
|
active_call_b
|
|
.update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
let room_a =
|
|
cx_a.read(|cx| active_call_a.read_with(cx, |call, _| call.room().unwrap().clone()));
|
|
cx_a.read(|cx| room_a.read_with(cx, |room, _| assert!(room.is_connected())));
|
|
assert_eq!(
|
|
room_participants(&room_a, cx_a),
|
|
RoomParticipants {
|
|
remote: vec!["user_b".to_string()],
|
|
pending: vec![]
|
|
}
|
|
);
|
|
|
|
let room_b =
|
|
cx_b.read(|cx| active_call_b.read_with(cx, |call, _| call.room().unwrap().clone()));
|
|
cx_b.read(|cx| room_b.read_with(cx, |room, _| assert!(room.is_connected())));
|
|
assert_eq!(
|
|
room_participants(&room_b, cx_b),
|
|
RoomParticipants {
|
|
remote: vec!["user_a".to_string()],
|
|
pending: vec![]
|
|
}
|
|
);
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn test_channel_jumping(executor: BackgroundExecutor, cx_a: &mut TestAppContext) {
|
|
let mut server = TestServer::start(executor.clone()).await;
|
|
let client_a = server.create_client(cx_a, "user_a").await;
|
|
|
|
let zed_id = server
|
|
.make_channel("zed", None, (&client_a, cx_a), &mut [])
|
|
.await;
|
|
let rust_id = server
|
|
.make_channel("rust", None, (&client_a, cx_a), &mut [])
|
|
.await;
|
|
|
|
let active_call_a = cx_a.read(ActiveCall::global);
|
|
|
|
active_call_a
|
|
.update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
|
|
.await
|
|
.unwrap();
|
|
|
|
// Give everything a chance to observe user A joining
|
|
executor.run_until_parked();
|
|
|
|
cx_a.read(|cx| {
|
|
client_a.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(
|
|
channels.channel_participants(zed_id),
|
|
&[client_a.user_id().unwrap()],
|
|
);
|
|
assert_participants_eq(channels.channel_participants(rust_id), &[]);
|
|
})
|
|
});
|
|
|
|
active_call_a
|
|
.update(cx_a, |active_call, cx| {
|
|
active_call.join_channel(rust_id, cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
cx_a.read(|cx| {
|
|
client_a.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(channels.channel_participants(zed_id), &[]);
|
|
assert_participants_eq(
|
|
channels.channel_participants(rust_id),
|
|
&[client_a.user_id().unwrap()],
|
|
);
|
|
})
|
|
});
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn test_permissions_update_while_invited(
|
|
executor: BackgroundExecutor,
|
|
cx_a: &mut TestAppContext,
|
|
cx_b: &mut TestAppContext,
|
|
) {
|
|
let mut server = TestServer::start(executor.clone()).await;
|
|
let client_a = server.create_client(cx_a, "user_a").await;
|
|
let client_b = server.create_client(cx_b, "user_b").await;
|
|
|
|
let rust_id = server
|
|
.make_channel("rust", None, (&client_a, cx_a), &mut [])
|
|
.await;
|
|
|
|
client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.invite_member(
|
|
rust_id,
|
|
client_b.user_id().unwrap(),
|
|
proto::ChannelRole::Member,
|
|
cx,
|
|
)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
assert_channel_invitations(
|
|
client_b.channel_store(),
|
|
cx_b,
|
|
&[ExpectedChannel {
|
|
depth: 0,
|
|
id: rust_id,
|
|
name: "rust".into(),
|
|
}],
|
|
);
|
|
assert_channels(client_b.channel_store(), cx_b, &[]);
|
|
|
|
// Update B's invite before they've accepted it
|
|
client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.set_member_role(
|
|
rust_id,
|
|
client_b.user_id().unwrap(),
|
|
proto::ChannelRole::Admin,
|
|
cx,
|
|
)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
assert_channel_invitations(
|
|
client_b.channel_store(),
|
|
cx_b,
|
|
&[ExpectedChannel {
|
|
depth: 0,
|
|
id: rust_id,
|
|
name: "rust".into(),
|
|
}],
|
|
);
|
|
assert_channels(client_b.channel_store(), cx_b, &[]);
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn test_channel_rename(
|
|
executor: BackgroundExecutor,
|
|
cx_a: &mut TestAppContext,
|
|
cx_b: &mut TestAppContext,
|
|
) {
|
|
let mut server = TestServer::start(executor.clone()).await;
|
|
let client_a = server.create_client(cx_a, "user_a").await;
|
|
let client_b = server.create_client(cx_b, "user_b").await;
|
|
|
|
let rust_id = server
|
|
.make_channel("rust", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
|
|
.await;
|
|
|
|
// Rename the channel
|
|
client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.rename(rust_id, "#rust-archive", cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
// Client A sees the channel with its new name.
|
|
assert_channels(
|
|
client_a.channel_store(),
|
|
cx_a,
|
|
&[ExpectedChannel {
|
|
depth: 0,
|
|
id: rust_id,
|
|
name: "rust-archive".into(),
|
|
}],
|
|
);
|
|
|
|
// Client B sees the channel with its new name.
|
|
assert_channels(
|
|
client_b.channel_store(),
|
|
cx_b,
|
|
&[ExpectedChannel {
|
|
depth: 0,
|
|
id: rust_id,
|
|
name: "rust-archive".into(),
|
|
}],
|
|
);
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn test_call_from_channel(
|
|
executor: BackgroundExecutor,
|
|
cx_a: &mut TestAppContext,
|
|
cx_b: &mut TestAppContext,
|
|
cx_c: &mut TestAppContext,
|
|
) {
|
|
let mut server = TestServer::start(executor.clone()).await;
|
|
let client_a = server.create_client(cx_a, "user_a").await;
|
|
let client_b = server.create_client(cx_b, "user_b").await;
|
|
let client_c = server.create_client(cx_c, "user_c").await;
|
|
server
|
|
.make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
|
.await;
|
|
|
|
let channel_id = server
|
|
.make_channel(
|
|
"x",
|
|
None,
|
|
(&client_a, cx_a),
|
|
&mut [(&client_b, cx_b), (&client_c, cx_c)],
|
|
)
|
|
.await;
|
|
|
|
let active_call_a = cx_a.read(ActiveCall::global);
|
|
let active_call_b = cx_b.read(ActiveCall::global);
|
|
|
|
active_call_a
|
|
.update(cx_a, |call, cx| call.join_channel(channel_id, cx))
|
|
.await
|
|
.unwrap();
|
|
|
|
// Client A calls client B while in the channel.
|
|
active_call_a
|
|
.update(cx_a, |call, cx| {
|
|
call.invite(client_b.user_id().unwrap(), None, cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
// Client B accepts the call.
|
|
executor.run_until_parked();
|
|
active_call_b
|
|
.update(cx_b, |call, cx| call.accept_incoming(cx))
|
|
.await
|
|
.unwrap();
|
|
|
|
// Client B sees that they are now in the channel
|
|
executor.run_until_parked();
|
|
cx_b.read(|cx| {
|
|
active_call_b.read_with(cx, |call, cx| {
|
|
assert_eq!(call.channel_id(cx), Some(channel_id));
|
|
})
|
|
});
|
|
cx_b.read(|cx| {
|
|
client_b.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(
|
|
channels.channel_participants(channel_id),
|
|
&[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
|
|
);
|
|
})
|
|
});
|
|
|
|
// Clients A and C also see that client B is in the channel.
|
|
cx_a.read(|cx| {
|
|
client_a.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(
|
|
channels.channel_participants(channel_id),
|
|
&[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
|
|
);
|
|
})
|
|
});
|
|
cx_c.read(|cx| {
|
|
client_c.channel_store().read_with(cx, |channels, _| {
|
|
assert_participants_eq(
|
|
channels.channel_participants(channel_id),
|
|
&[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
|
|
);
|
|
})
|
|
});
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn test_lost_channel_creation(
|
|
executor: BackgroundExecutor,
|
|
cx_a: &mut TestAppContext,
|
|
cx_b: &mut TestAppContext,
|
|
) {
|
|
let mut server = TestServer::start(executor.clone()).await;
|
|
let client_a = server.create_client(cx_a, "user_a").await;
|
|
let client_b = server.create_client(cx_b, "user_b").await;
|
|
|
|
server
|
|
.make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
|
.await;
|
|
|
|
let channel_id = server
|
|
.make_channel("x", None, (&client_a, cx_a), &mut [])
|
|
.await;
|
|
|
|
// Invite a member
|
|
client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.invite_member(
|
|
channel_id,
|
|
client_b.user_id().unwrap(),
|
|
proto::ChannelRole::Member,
|
|
cx,
|
|
)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
// Sanity check, B has the invitation
|
|
assert_channel_invitations(
|
|
client_b.channel_store(),
|
|
cx_b,
|
|
&[ExpectedChannel {
|
|
depth: 0,
|
|
id: channel_id,
|
|
name: "x".into(),
|
|
}],
|
|
);
|
|
|
|
// A creates a subchannel while the invite is still pending.
|
|
let subchannel_id = client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.create_channel("subchannel", Some(channel_id), cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
// Make sure A sees their new channel
|
|
assert_channels(
|
|
client_a.channel_store(),
|
|
cx_a,
|
|
&[
|
|
ExpectedChannel {
|
|
depth: 0,
|
|
id: channel_id,
|
|
name: "x".into(),
|
|
},
|
|
ExpectedChannel {
|
|
depth: 1,
|
|
id: subchannel_id,
|
|
name: "subchannel".into(),
|
|
},
|
|
],
|
|
);
|
|
|
|
// Client B accepts the invite
|
|
client_b
|
|
.channel_store()
|
|
.update(cx_b, |channel_store, cx| {
|
|
channel_store.respond_to_channel_invite(channel_id, true, cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
// Client B should now see the channel
|
|
assert_channels(
|
|
client_b.channel_store(),
|
|
cx_b,
|
|
&[
|
|
ExpectedChannel {
|
|
depth: 0,
|
|
id: channel_id,
|
|
name: "x".into(),
|
|
},
|
|
ExpectedChannel {
|
|
depth: 1,
|
|
id: subchannel_id,
|
|
name: "subchannel".into(),
|
|
},
|
|
],
|
|
);
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn test_channel_link_notifications(
|
|
executor: BackgroundExecutor,
|
|
cx_a: &mut TestAppContext,
|
|
cx_b: &mut TestAppContext,
|
|
cx_c: &mut TestAppContext,
|
|
) {
|
|
let mut server = TestServer::start(executor.clone()).await;
|
|
let client_a = server.create_client(cx_a, "user_a").await;
|
|
let client_b = server.create_client(cx_b, "user_b").await;
|
|
let client_c = server.create_client(cx_c, "user_c").await;
|
|
|
|
let user_b = client_b.user_id().unwrap();
|
|
let user_c = client_c.user_id().unwrap();
|
|
|
|
let channels = server
|
|
.make_channel_tree(&[("zed", None)], (&client_a, cx_a))
|
|
.await;
|
|
let zed_channel = channels[0];
|
|
|
|
try_join_all(client_a.channel_store().update(cx_a, |channel_store, cx| {
|
|
[
|
|
channel_store.set_channel_visibility(zed_channel, proto::ChannelVisibility::Public, cx),
|
|
channel_store.invite_member(zed_channel, user_b, proto::ChannelRole::Member, cx),
|
|
channel_store.invite_member(zed_channel, user_c, proto::ChannelRole::Guest, cx),
|
|
]
|
|
}))
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
client_b
|
|
.channel_store()
|
|
.update(cx_b, |channel_store, cx| {
|
|
channel_store.respond_to_channel_invite(zed_channel, true, cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
client_c
|
|
.channel_store()
|
|
.update(cx_c, |channel_store, cx| {
|
|
channel_store.respond_to_channel_invite(zed_channel, true, cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
// we have an admin (a), member (b) and guest (c) all part of the zed channel.
|
|
|
|
// create a new private channel, make it public, and move it under the previous one, and verify it shows for b and not c
|
|
let active_channel = client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.create_channel("active", Some(zed_channel), cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
// the new channel shows for b and not c
|
|
assert_channels_list_shape(
|
|
client_a.channel_store(),
|
|
cx_a,
|
|
&[(zed_channel, 0), (active_channel, 1)],
|
|
);
|
|
assert_channels_list_shape(
|
|
client_b.channel_store(),
|
|
cx_b,
|
|
&[(zed_channel, 0), (active_channel, 1)],
|
|
);
|
|
assert_channels_list_shape(client_c.channel_store(), cx_c, &[(zed_channel, 0)]);
|
|
|
|
let vim_channel = client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.create_channel("vim", Some(zed_channel), cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.set_channel_visibility(vim_channel, proto::ChannelVisibility::Public, cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
// the new channel shows for b and c
|
|
assert_channels_list_shape(
|
|
client_a.channel_store(),
|
|
cx_a,
|
|
&[(zed_channel, 0), (active_channel, 1), (vim_channel, 1)],
|
|
);
|
|
assert_channels_list_shape(
|
|
client_b.channel_store(),
|
|
cx_b,
|
|
&[(zed_channel, 0), (active_channel, 1), (vim_channel, 1)],
|
|
);
|
|
assert_channels_list_shape(
|
|
client_c.channel_store(),
|
|
cx_c,
|
|
&[(zed_channel, 0), (vim_channel, 1)],
|
|
);
|
|
|
|
let helix_channel = client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.create_channel("helix", Some(zed_channel), cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.move_channel(helix_channel, vim_channel, cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.set_channel_visibility(
|
|
helix_channel,
|
|
proto::ChannelVisibility::Public,
|
|
cx,
|
|
)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
cx_a.run_until_parked();
|
|
|
|
// the new channel shows for b and c
|
|
assert_channels_list_shape(
|
|
client_b.channel_store(),
|
|
cx_b,
|
|
&[
|
|
(zed_channel, 0),
|
|
(active_channel, 1),
|
|
(vim_channel, 1),
|
|
(helix_channel, 2),
|
|
],
|
|
);
|
|
assert_channels_list_shape(
|
|
client_c.channel_store(),
|
|
cx_c,
|
|
&[(zed_channel, 0), (vim_channel, 1), (helix_channel, 2)],
|
|
);
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn test_channel_membership_notifications(
|
|
executor: BackgroundExecutor,
|
|
cx_a: &mut TestAppContext,
|
|
cx_b: &mut TestAppContext,
|
|
) {
|
|
let mut server = TestServer::start(executor.clone()).await;
|
|
let client_a = server.create_client(cx_a, "user_a").await;
|
|
let client_b = server.create_client(cx_b, "user_c").await;
|
|
|
|
let user_b = client_b.user_id().unwrap();
|
|
|
|
let channels = server
|
|
.make_channel_tree(
|
|
&[("zed", None), ("vim", Some("zed")), ("opensource", None)],
|
|
(&client_a, cx_a),
|
|
)
|
|
.await;
|
|
let zed_channel = channels[0];
|
|
let vim_channel = channels[1];
|
|
let opensource_channel = channels[2];
|
|
|
|
try_join_all(client_a.channel_store().update(cx_a, |channel_store, cx| {
|
|
[
|
|
channel_store.set_channel_visibility(zed_channel, proto::ChannelVisibility::Public, cx),
|
|
channel_store.set_channel_visibility(vim_channel, proto::ChannelVisibility::Public, cx),
|
|
channel_store.invite_member(zed_channel, user_b, proto::ChannelRole::Admin, cx),
|
|
channel_store.invite_member(opensource_channel, user_b, proto::ChannelRole::Member, cx),
|
|
]
|
|
}))
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
client_b
|
|
.channel_store()
|
|
.update(cx_b, |channel_store, cx| {
|
|
channel_store.respond_to_channel_invite(zed_channel, true, cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
// we have an admin (a), and a guest (b) with access to all of zed, and membership in vim.
|
|
assert_channels(
|
|
client_b.channel_store(),
|
|
cx_b,
|
|
&[
|
|
ExpectedChannel {
|
|
depth: 0,
|
|
id: zed_channel,
|
|
name: "zed".into(),
|
|
},
|
|
ExpectedChannel {
|
|
depth: 1,
|
|
id: vim_channel,
|
|
name: "vim".into(),
|
|
},
|
|
],
|
|
);
|
|
|
|
client_b.channel_store().update(cx_b, |channel_store, _| {
|
|
channel_store.is_channel_admin(zed_channel)
|
|
});
|
|
|
|
client_b
|
|
.channel_store()
|
|
.update(cx_b, |channel_store, cx| {
|
|
channel_store.respond_to_channel_invite(opensource_channel, true, cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
cx_a.run_until_parked();
|
|
|
|
client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.set_member_role(opensource_channel, user_b, ChannelRole::Admin, cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
cx_a.run_until_parked();
|
|
|
|
client_b.channel_store().update(cx_b, |channel_store, _| {
|
|
channel_store.is_channel_admin(opensource_channel)
|
|
});
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn test_guest_access(
|
|
executor: BackgroundExecutor,
|
|
cx_a: &mut TestAppContext,
|
|
cx_b: &mut TestAppContext,
|
|
) {
|
|
let mut server = TestServer::start(executor.clone()).await;
|
|
let client_a = server.create_client(cx_a, "user_a").await;
|
|
let client_b = server.create_client(cx_b, "user_b").await;
|
|
|
|
let channels = server
|
|
.make_channel_tree(
|
|
&[("channel-a", None), ("channel-b", Some("channel-a"))],
|
|
(&client_a, cx_a),
|
|
)
|
|
.await;
|
|
let channel_a = channels[0];
|
|
let channel_b = channels[1];
|
|
|
|
let active_call_b = cx_b.read(ActiveCall::global);
|
|
|
|
// Non-members should not be allowed to join
|
|
assert!(active_call_b
|
|
.update(cx_b, |call, cx| call.join_channel(channel_a, cx))
|
|
.await
|
|
.is_err());
|
|
|
|
// Make channels A and B public
|
|
client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.set_channel_visibility(channel_a, proto::ChannelVisibility::Public, cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.set_channel_visibility(channel_b, proto::ChannelVisibility::Public, cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
// Client B joins channel A as a guest
|
|
active_call_b
|
|
.update(cx_b, |call, cx| call.join_channel(channel_a, cx))
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
assert_channels_list_shape(
|
|
client_a.channel_store(),
|
|
cx_a,
|
|
&[(channel_a, 0), (channel_b, 1)],
|
|
);
|
|
assert_channels_list_shape(
|
|
client_b.channel_store(),
|
|
cx_b,
|
|
&[(channel_a, 0), (channel_b, 1)],
|
|
);
|
|
|
|
client_a.channel_store().update(cx_a, |channel_store, _| {
|
|
let participants = channel_store.channel_participants(channel_a);
|
|
assert_eq!(participants.len(), 1);
|
|
assert_eq!(participants[0].id, client_b.user_id().unwrap());
|
|
});
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn test_invite_access(
|
|
executor: BackgroundExecutor,
|
|
cx_a: &mut TestAppContext,
|
|
cx_b: &mut TestAppContext,
|
|
) {
|
|
let mut server = TestServer::start(executor.clone()).await;
|
|
let client_a = server.create_client(cx_a, "user_a").await;
|
|
let client_b = server.create_client(cx_b, "user_b").await;
|
|
|
|
let channels = server
|
|
.make_channel_tree(
|
|
&[("channel-a", None), ("channel-b", Some("channel-a"))],
|
|
(&client_a, cx_a),
|
|
)
|
|
.await;
|
|
let channel_a_id = channels[0];
|
|
let channel_b_id = channels[0];
|
|
|
|
let active_call_b = cx_b.read(ActiveCall::global);
|
|
|
|
// should not be allowed to join
|
|
assert!(active_call_b
|
|
.update(cx_b, |call, cx| call.join_channel(channel_b_id, cx))
|
|
.await
|
|
.is_err());
|
|
|
|
client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.invite_member(
|
|
channel_a_id,
|
|
client_b.user_id().unwrap(),
|
|
ChannelRole::Member,
|
|
cx,
|
|
)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
active_call_b
|
|
.update(cx_b, |call, cx| call.join_channel(channel_b_id, cx))
|
|
.await
|
|
.unwrap();
|
|
|
|
executor.run_until_parked();
|
|
|
|
client_b.channel_store().update(cx_b, |channel_store, _| {
|
|
assert!(channel_store.channel_for_id(channel_b_id).is_some());
|
|
assert!(channel_store.channel_for_id(channel_a_id).is_some());
|
|
});
|
|
|
|
client_a.channel_store().update(cx_a, |channel_store, _| {
|
|
let participants = channel_store.channel_participants(channel_b_id);
|
|
assert_eq!(participants.len(), 1);
|
|
assert_eq!(participants[0].id, client_b.user_id().unwrap());
|
|
})
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn test_leave_channel(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
|
|
let (_server, _client_a, client_b, channel_id) = TestServer::start2(cx_a, cx_b).await;
|
|
|
|
client_b
|
|
.channel_store()
|
|
.update(cx_b, |channel_store, cx| {
|
|
channel_store.remove_member(channel_id, client_b.user_id().unwrap(), cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
cx_a.run_until_parked();
|
|
|
|
assert_eq!(
|
|
client_b
|
|
.channel_store()
|
|
.read_with(cx_b, |store, _| store.channels().count()),
|
|
0
|
|
);
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn test_channel_moving(
|
|
executor: BackgroundExecutor,
|
|
cx_a: &mut TestAppContext,
|
|
_cx_b: &mut TestAppContext,
|
|
_cx_c: &mut TestAppContext,
|
|
) {
|
|
let mut server = TestServer::start(executor.clone()).await;
|
|
let client_a = server.create_client(cx_a, "user_a").await;
|
|
|
|
let channels = server
|
|
.make_channel_tree(
|
|
&[
|
|
("channel-a", None),
|
|
("channel-b", Some("channel-a")),
|
|
("channel-c", Some("channel-b")),
|
|
("channel-d", Some("channel-c")),
|
|
],
|
|
(&client_a, cx_a),
|
|
)
|
|
.await;
|
|
let channel_a_id = channels[0];
|
|
let channel_b_id = channels[1];
|
|
let channel_c_id = channels[2];
|
|
let channel_d_id = channels[3];
|
|
|
|
// Current shape:
|
|
// a - b - c - d
|
|
assert_channels_list_shape(
|
|
client_a.channel_store(),
|
|
cx_a,
|
|
&[
|
|
(channel_a_id, 0),
|
|
(channel_b_id, 1),
|
|
(channel_c_id, 2),
|
|
(channel_d_id, 3),
|
|
],
|
|
);
|
|
|
|
client_a
|
|
.channel_store()
|
|
.update(cx_a, |channel_store, cx| {
|
|
channel_store.move_channel(channel_d_id, channel_b_id, cx)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
// Current shape:
|
|
// /- d
|
|
// a - b -- c
|
|
assert_channels_list_shape(
|
|
client_a.channel_store(),
|
|
cx_a,
|
|
&[
|
|
(channel_a_id, 0),
|
|
(channel_b_id, 1),
|
|
(channel_c_id, 2),
|
|
(channel_d_id, 2),
|
|
],
|
|
);
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
struct ExpectedChannel {
|
|
depth: usize,
|
|
id: ChannelId,
|
|
name: SharedString,
|
|
}
|
|
|
|
#[track_caller]
|
|
fn assert_channel_invitations(
|
|
channel_store: &Entity<ChannelStore>,
|
|
cx: &TestAppContext,
|
|
expected_channels: &[ExpectedChannel],
|
|
) {
|
|
let actual = cx.read(|cx| {
|
|
channel_store.read_with(cx, |store, _| {
|
|
store
|
|
.channel_invitations()
|
|
.iter()
|
|
.map(|channel| ExpectedChannel {
|
|
depth: 0,
|
|
name: channel.name.clone(),
|
|
id: channel.id,
|
|
})
|
|
.collect::<Vec<_>>()
|
|
})
|
|
});
|
|
assert_eq!(actual, expected_channels);
|
|
}
|
|
|
|
#[track_caller]
|
|
fn assert_channels(
|
|
channel_store: &Entity<ChannelStore>,
|
|
cx: &TestAppContext,
|
|
expected_channels: &[ExpectedChannel],
|
|
) {
|
|
let actual = cx.read(|cx| {
|
|
channel_store.read_with(cx, |store, _| {
|
|
store
|
|
.ordered_channels()
|
|
.map(|(depth, channel)| ExpectedChannel {
|
|
depth,
|
|
name: channel.name.clone(),
|
|
id: channel.id,
|
|
})
|
|
.collect::<Vec<_>>()
|
|
})
|
|
});
|
|
pretty_assertions::assert_eq!(actual, expected_channels);
|
|
}
|
|
|
|
#[track_caller]
|
|
fn assert_channels_list_shape(
|
|
channel_store: &Entity<ChannelStore>,
|
|
cx: &TestAppContext,
|
|
expected_channels: &[(ChannelId, usize)],
|
|
) {
|
|
let actual = cx.read(|cx| {
|
|
channel_store.read_with(cx, |store, _| {
|
|
store
|
|
.ordered_channels()
|
|
.map(|(depth, channel)| (channel.id, depth))
|
|
.collect::<Vec<_>>()
|
|
})
|
|
});
|
|
pretty_assertions::assert_eq!(actual, expected_channels);
|
|
}
|