diff --git a/crates/prose-core-client/src/app/event_handlers/rooms_event_handler.rs b/crates/prose-core-client/src/app/event_handlers/rooms_event_handler.rs index b4dd339d..ec462bb2 100644 --- a/crates/prose-core-client/src/app/event_handlers/rooms_event_handler.rs +++ b/crates/prose-core-client/src/app/event_handlers/rooms_event_handler.rs @@ -247,7 +247,21 @@ impl RoomsEventHandler { is_self_event, &availability, ); - room_changed = true; + + if room.sidebar_state().is_in_sidebar() { + if event.user_id.is_occupant_id() { + // The participant list should be reloaded in the UI to reflect + // the new availability… + self.client_event_dispatcher.dispatch_room_event( + room, + ClientRoomEventType::ParticipantsChanged, + ); + } + + // If this is a DM room, a SidebarChanged event will be fired down the + // line, since the UI displays an availability indicator. + room_changed = true; + } }; // if we do not have a room and the event is from a contact, we'll still want diff --git a/crates/prose-core-client/src/domain/shared/models/user_endpoint_id.rs b/crates/prose-core-client/src/domain/shared/models/user_endpoint_id.rs index de9a9b21..effe01db 100644 --- a/crates/prose-core-client/src/domain/shared/models/user_endpoint_id.rs +++ b/crates/prose-core-client/src/domain/shared/models/user_endpoint_id.rs @@ -45,6 +45,13 @@ impl UserEndpointId { UserEndpointId::Occupant(_) => None, } } + + pub fn is_occupant_id(&self) -> bool { + match self { + UserEndpointId::Occupant(_) => true, + UserEndpointId::User(_) | UserEndpointId::UserResource(_) => false, + } + } } impl From for UserEndpointId { diff --git a/crates/prose-core-client/src/test/room_internals.rs b/crates/prose-core-client/src/test/room_internals.rs index 781c9036..a58e8fc6 100644 --- a/crates/prose-core-client/src/test/room_internals.rs +++ b/crates/prose-core-client/src/test/room_internals.rs @@ -144,6 +144,11 @@ impl Participant { self } + pub fn set_availability(mut self, availability: Availability) -> Self { + self.availability = availability; + self + } + pub fn set_name(mut self, name: impl Into) -> Self { self.name = Some(name.into()); self diff --git a/crates/prose-core-client/tests/rooms_domain_service.rs b/crates/prose-core-client/tests/rooms_domain_service.rs index 94e552e8..09a6225c 100644 --- a/crates/prose-core-client/tests/rooms_domain_service.rs +++ b/crates/prose-core-client/tests/rooms_domain_service.rs @@ -163,7 +163,7 @@ async fn test_joins_room() -> Result<()> { deps.client_event_dispatcher .expect_dispatch_room_event() - .times(3) + .times(6) .returning(|_, _| ()); deps.user_profile_repo diff --git a/crates/prose-core-client/tests/rooms_event_handler.rs b/crates/prose-core-client/tests/rooms_event_handler.rs index 3c0d1fc5..b057b27d 100644 --- a/crates/prose-core-client/tests/rooms_event_handler.rs +++ b/crates/prose-core-client/tests/rooms_event_handler.rs @@ -55,12 +55,12 @@ async fn test_adds_participant() -> Result<()> { deps.client_event_dispatcher .expect_dispatch_room_event() - .once() + .times(2) .with( predicate::eq(room.clone()), predicate::eq(ClientRoomEventType::ParticipantsChanged), ) - .return_once(|_, _| ()); + .returning(|_, _| ()); let event_handler = RoomsEventHandler::from(&deps.into_deps()); @@ -200,12 +200,12 @@ async fn test_handles_disconnected_participant() -> Result<()> { deps.client_event_dispatcher .expect_dispatch_room_event() - .once() + .times(2) .with( predicate::eq(room.clone()), predicate::eq(ClientRoomEventType::ParticipantsChanged), ) - .return_once(|_, _| ()); + .returning(|_, _| ()); let event_handler = RoomsEventHandler::from(&deps.into_deps()); @@ -535,7 +535,7 @@ async fn test_handles_invite() -> Result<()> { } #[tokio::test] -async fn test_handles_presence() -> Result<()> { +async fn test_handles_user_presence() -> Result<()> { let mut deps = MockAppDependencies::default(); let room = Room::for_direct_message( @@ -596,6 +596,69 @@ async fn test_handles_presence() -> Result<()> { Ok(()) } +#[tokio::test] +async fn test_handles_occupant_presence() -> Result<()> { + let mut deps = MockAppDependencies::default(); + + let room = Room::group(room_id!("room@muc.prose.org")).with_participants([( + occupant_id!("room@muc.prose.org/nick"), + Participant::owner() + .set_real_id(&user_id!("nick@prose.org")) + .set_availability(Availability::Available), + )]); + + { + let room = room.clone(); + deps.connected_rooms_repo + .expect_get() + .once() + .with(predicate::eq(room_id!("room@muc.prose.org"))) + .return_once(move |_| Some(room.clone())); + } + + { + let room = room.clone(); + deps.client_event_dispatcher + .expect_dispatch_room_event() + .once() + .with( + predicate::eq(room.clone()), + predicate::eq(ClientRoomEventType::ParticipantsChanged), + ) + .return_once(|_, _| ()); + } + + let event_handler = RoomsEventHandler::from(&deps.into_deps()); + + assert_eq!( + room.participants() + .get(&occupant_id!("room@muc.prose.org/nick").into()) + .unwrap() + .availability, + Availability::Available + ); + + event_handler + .handle_event(ServerEvent::UserStatus(UserStatusEvent { + user_id: occupant_id!("room@muc.prose.org/nick").into(), + r#type: UserStatusEventType::AvailabilityChanged { + availability: Availability::DoNotDisturb, + priority: 1, + }, + })) + .await?; + + assert_eq!( + room.participants() + .get(&occupant_id!("room@muc.prose.org/nick").into()) + .unwrap() + .availability, + Availability::DoNotDisturb + ); + + Ok(()) +} + #[tokio::test] async fn test_handles_contact_presence_with_no_room() -> Result<()> { let mut deps = MockAppDependencies::default();