diff --git a/src/contact.rs b/src/contact.rs index 10a83ad7e..130374f91 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -1926,17 +1926,15 @@ pub(crate) async fn set_profile_image( ); let mut contact = Contact::get_by_id(context, contact_id).await?; - let changed = match profile_image { - AvatarAction::Change(profile_image) => { - contact.param.set(Param::ProfileImage, profile_image); - true - } - AvatarAction::Delete => { - contact.param.remove(Param::ProfileImage); - true - } + let profile_image_opt = match profile_image { + AvatarAction::Change(profile_image) => Some(profile_image), + AvatarAction::Delete => None, }; + let changed = contact.param.get(Param::ProfileImage) != profile_image_opt.map(|s| s.as_str()); if changed { + contact + .param + .set_optional(Param::ProfileImage, profile_image_opt); contact.update_param(context).await?; context.emit_event(EventType::ContactsChanged(Some(contact_id))); chatlist_events::emit_chatlist_item_changed_for_contact_chat(context, contact_id).await; diff --git a/src/contact/contact_tests.rs b/src/contact/contact_tests.rs index ecf781fdd..98606d46a 100644 --- a/src/contact/contact_tests.rs +++ b/src/contact/contact_tests.rs @@ -1479,3 +1479,74 @@ async fn test_name_changes() -> Result<()> { Ok(()) } + +/// Tests that ChatlistItemChanged event is not emitted if the same contact avatar is received. +/// +/// We don't test ContactsChanged event because it is emitted in any case due to "last seen" update. +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_same_avatar_no_changed_event() -> Result<()> { + let mut tcm = TestContextManager::new(); + + let alice = &tcm.alice().await; + let bob = &tcm.bob().await; + let bob2 = &tcm.bob().await; + + // Create chat with Bob. + // No messages will be sent to it, + // but it should be updated when Bob's avatar changes. + let alice_bob_chat = alice.create_chat(bob).await; + + async fn wait_for_chatlist_event(context: &TestContext, expected_chat_id: ChatId) { + context + .evtracker + .get_matching(|evt| { + matches!(evt, EventType::ChatlistItemChanged { chat_id } if *chat_id == Some(expected_chat_id)) + }) + .await; + } + + // One event is for adding E2EE notice ("Messages are end-to-end encrypted") + // and another event is for creating the chat itself. + wait_for_chatlist_event(alice, alice_bob_chat.id).await; + wait_for_chatlist_event(alice, alice_bob_chat.id).await; + + // Create a group chat and single chat with Bob. + // Bob will send messages to group chat, + // but single chat should get ChatlistItemChanged event + // when avatar changes. + let alice_group_id = alice.create_group_with_members("test group", &[bob]).await; + let alice_sent_msg = alice.send_text(alice_group_id, "testing").await; + let bob_group_id = bob.recv_msg(&alice_sent_msg).await.chat_id; + bob_group_id.accept(bob).await?; + let bob2_group_id = bob2.recv_msg(&alice_sent_msg).await.chat_id; + bob2_group_id.accept(bob2).await?; + + // Bob sets the same avatar on two devices at the same time. + // Receiving the same avatar should only emit the event about contact change once. + for profile in &[bob, bob2] { + let avatar_src = profile.get_blobdir().join("avatar.png"); + tokio::fs::write(&avatar_src, test_utils::AVATAR_900x900_BYTES).await?; + profile + .set_config(Config::Selfavatar, Some(avatar_src.to_str().unwrap())) + .await?; + } + + // Avatar is changed by a group message, + // so chatlist item for single chat should be refreshed. + let sent = bob.send_text(bob_group_id, "Avatar change").await; + alice.recv_msg(&sent).await; + wait_for_chatlist_event(alice, alice_bob_chat.id).await; + + // Avatar is the same, so single chat is not updated. + let sent2 = bob2.send_text(bob2_group_id, "No avatar change").await; + alice.recv_msg(&sent2).await; + let event_opt = alice + .evtracker + .get_matching_opt(alice, |evt| { + matches!(evt, EventType::ChatlistItemChanged { chat_id } if *chat_id == Some(alice_bob_chat.id)) + }) + .await; + assert_eq!(event_opt, None); + + Ok(()) +}