deltachat/events/
chatlist_events.rs

1use crate::{chat::ChatId, contact::ContactId, context::Context, EventType};
2
3/// order or content of chatlist changes (chat ids, not the actual chatlist item)
4pub(crate) fn emit_chatlist_changed(context: &Context) {
5    context.emit_event(EventType::ChatlistChanged);
6}
7
8/// Chatlist item of a specific chat changed
9pub(crate) fn emit_chatlist_item_changed(context: &Context, chat_id: ChatId) {
10    context.emit_event(EventType::ChatlistItemChanged {
11        chat_id: Some(chat_id),
12    });
13}
14
15/// Used when you don't know which chatlist items changed, this reloads all cached chatlist items in the UI
16///
17/// Avoid calling this when you can find out the affected chat ids easialy (without extra expensive db queries).
18///
19/// This method is not public, so you have to define and document your new case here in this file.
20fn emit_unknown_chatlist_items_changed(context: &Context) {
21    context.emit_event(EventType::ChatlistItemChanged { chat_id: None });
22}
23
24/// update event for the 1:1 chat with the contact
25/// used when recently seen changes and when profile image changes
26pub(crate) async fn emit_chatlist_item_changed_for_contact_chat(
27    context: &Context,
28    contact_id: ContactId,
29) {
30    match ChatId::lookup_by_contact(context, contact_id).await {
31        Ok(Some(chat_id)) => self::emit_chatlist_item_changed(context, chat_id),
32        Ok(None) => {}
33        Err(error) => context.emit_event(EventType::Error(format!(
34            "failed to find chat id for contact for chatlist event: {error:?}"
35        ))),
36    }
37}
38
39/// update items for chats that have the contact
40/// used when contact changes their name or did AEAP for example
41///
42/// The most common case is that the contact changed their name
43/// and their name should be updated in the chatlistitems for the chats
44/// where they sent the last message as there their name is shown in the summary on those
45pub(crate) fn emit_chatlist_items_changed_for_contact(context: &Context, _contact_id: ContactId) {
46    // note:(treefit): it is too expensive to find the right chats
47    // so we'll just tell ui to reload every loaded item
48    emit_unknown_chatlist_items_changed(context)
49    // note:(treefit): in the future we could instead emit an extra event for this and also store contact id in the chatlistitems
50    // (contact id for dm chats and contact id of contact that wrote the message in the summary)
51    // the ui could then look for this info in the cache and only reload the needed chats.
52}
53
54/// Tests for chatlist events
55///
56/// Only checks if the events are emitted,
57/// does not check for excess/too-many events
58#[cfg(test)]
59mod test_chatlist_events {
60
61    use std::{
62        sync::atomic::{AtomicBool, Ordering},
63        time::Duration,
64    };
65
66    use crate::{
67        chat::{
68            self, create_broadcast_list, create_group_chat, set_muted, ChatId, ChatVisibility,
69            MuteDuration, ProtectionStatus,
70        },
71        config::Config,
72        constants::*,
73        contact::Contact,
74        message::{self, Message, MessageState},
75        reaction,
76        receive_imf::receive_imf,
77        securejoin::{get_securejoin_qr, join_securejoin},
78        test_utils::{TestContext, TestContextManager},
79        EventType,
80    };
81
82    use crate::tools::SystemTime;
83    use anyhow::Result;
84
85    async fn wait_for_chatlist_and_specific_item(context: &TestContext, chat_id: ChatId) {
86        let first_event_is_item = AtomicBool::new(false);
87        context
88            .evtracker
89            .get_matching(|evt| match evt {
90                EventType::ChatlistItemChanged {
91                    chat_id: Some(ev_chat_id),
92                } => {
93                    if ev_chat_id == &chat_id {
94                        first_event_is_item.store(true, Ordering::Relaxed);
95                        true
96                    } else {
97                        false
98                    }
99                }
100                EventType::ChatlistChanged => true,
101                _ => false,
102            })
103            .await;
104        if first_event_is_item.load(Ordering::Relaxed) {
105            wait_for_chatlist(context).await;
106        } else {
107            wait_for_chatlist_specific_item(context, chat_id).await;
108        }
109    }
110
111    async fn wait_for_chatlist_specific_item(context: &TestContext, chat_id: ChatId) {
112        context
113            .evtracker
114            .get_matching(|evt| match evt {
115                EventType::ChatlistItemChanged {
116                    chat_id: Some(ev_chat_id),
117                } => ev_chat_id == &chat_id,
118                _ => false,
119            })
120            .await;
121    }
122
123    async fn wait_for_chatlist_all_items(context: &TestContext) {
124        context
125            .evtracker
126            .get_matching(|evt| matches!(evt, EventType::ChatlistItemChanged { chat_id: None }))
127            .await;
128    }
129
130    async fn wait_for_chatlist(context: &TestContext) {
131        context
132            .evtracker
133            .get_matching(|evt| matches!(evt, EventType::ChatlistChanged))
134            .await;
135    }
136
137    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
138    async fn test_change_chat_visibility() -> Result<()> {
139        let mut tcm = TestContextManager::new();
140        let alice = tcm.alice().await;
141        let chat_id = create_group_chat(
142            &alice,
143            crate::chat::ProtectionStatus::Unprotected,
144            "my_group",
145        )
146        .await?;
147
148        chat_id
149            .set_visibility(&alice, ChatVisibility::Pinned)
150            .await?;
151        wait_for_chatlist_and_specific_item(&alice, chat_id).await;
152
153        chat_id
154            .set_visibility(&alice, ChatVisibility::Archived)
155            .await?;
156        wait_for_chatlist_and_specific_item(&alice, chat_id).await;
157
158        chat_id
159            .set_visibility(&alice, ChatVisibility::Normal)
160            .await?;
161        wait_for_chatlist_and_specific_item(&alice, chat_id).await;
162
163        Ok(())
164    }
165
166    /// mute a chat, archive it, then use another account to send a message to it, the counter on the archived chatlist item should change
167    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
168    async fn test_archived_counter_increases_for_muted_chats() -> Result<()> {
169        let mut tcm = TestContextManager::new();
170        let alice = tcm.alice().await;
171        let bob = tcm.bob().await;
172
173        let chat = alice.create_chat(&bob).await;
174        let sent_msg = alice.send_text(chat.id, "moin").await;
175        bob.recv_msg(&sent_msg).await;
176
177        let bob_chat = bob.create_chat(&alice).await;
178        bob_chat
179            .id
180            .set_visibility(&bob, ChatVisibility::Archived)
181            .await?;
182        set_muted(&bob, bob_chat.id, MuteDuration::Forever).await?;
183
184        bob.evtracker.clear_events();
185
186        let sent_msg = alice.send_text(chat.id, "moin2").await;
187        bob.recv_msg(&sent_msg).await;
188
189        bob.evtracker
190            .get_matching(|evt| match evt {
191                EventType::ChatlistItemChanged {
192                    chat_id: Some(chat_id),
193                } => chat_id.is_archived_link(),
194                _ => false,
195            })
196            .await;
197
198        Ok(())
199    }
200
201    /// Mark noticed on archive-link chatlistitem should update the unread counter on it
202    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
203    async fn test_archived_counter_update_on_mark_noticed() -> Result<()> {
204        let mut tcm = TestContextManager::new();
205        let alice = tcm.alice().await;
206        let bob = tcm.bob().await;
207        let chat = alice.create_chat(&bob).await;
208        let sent_msg = alice.send_text(chat.id, "moin").await;
209        bob.recv_msg(&sent_msg).await;
210        let bob_chat = bob.create_chat(&alice).await;
211        bob_chat
212            .id
213            .set_visibility(&bob, ChatVisibility::Archived)
214            .await?;
215        set_muted(&bob, bob_chat.id, MuteDuration::Forever).await?;
216        let sent_msg = alice.send_text(chat.id, "moin2").await;
217        bob.recv_msg(&sent_msg).await;
218
219        bob.evtracker.clear_events();
220        chat::marknoticed_chat(&bob, DC_CHAT_ID_ARCHIVED_LINK).await?;
221        wait_for_chatlist_specific_item(&bob, DC_CHAT_ID_ARCHIVED_LINK).await;
222
223        Ok(())
224    }
225
226    /// Contact name update - expect all chats to update
227    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
228    async fn test_contact_name_update() -> Result<()> {
229        let mut tcm = TestContextManager::new();
230        let alice = tcm.alice().await;
231        let bob = tcm.bob().await;
232        let alice_to_bob_chat = alice.create_chat(&bob).await;
233        let sent_msg = alice.send_text(alice_to_bob_chat.id, "hello").await;
234        bob.recv_msg(&sent_msg).await;
235
236        bob.evtracker.clear_events();
237        // set alice name then receive messagefrom her with bob
238        alice.set_config(Config::Displayname, Some("Alice")).await?;
239        let sent_msg = alice
240            .send_text(alice_to_bob_chat.id, "hello, I set a displayname")
241            .await;
242        bob.recv_msg(&sent_msg).await;
243        let alice_on_bob = bob.add_or_lookup_contact(&alice).await;
244        assert!(alice_on_bob.get_display_name() == "Alice");
245
246        wait_for_chatlist_all_items(&bob).await;
247
248        bob.evtracker.clear_events();
249        // set name
250        alice_on_bob.id.set_name(&bob, "Alice2").await?;
251        assert!(bob.add_or_lookup_contact(&alice).await.get_display_name() == "Alice2");
252
253        wait_for_chatlist_all_items(&bob).await;
254
255        Ok(())
256    }
257
258    /// Contact changed avatar
259    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
260    async fn test_contact_changed_avatar() -> Result<()> {
261        let mut tcm = TestContextManager::new();
262        let alice = tcm.alice().await;
263        let bob = tcm.bob().await;
264        let alice_to_bob_chat = alice.create_chat(&bob).await;
265        let sent_msg = alice.send_text(alice_to_bob_chat.id, "hello").await;
266        bob.recv_msg(&sent_msg).await;
267
268        bob.evtracker.clear_events();
269        // set alice avatar then receive messagefrom her with bob
270        let file = alice.dir.path().join("avatar.png");
271        let bytes = include_bytes!("../../test-data/image/avatar64x64.png");
272        tokio::fs::write(&file, bytes).await?;
273        alice
274            .set_config(Config::Selfavatar, Some(file.to_str().unwrap()))
275            .await?;
276        let sent_msg = alice
277            .send_text(alice_to_bob_chat.id, "hello, I have a new avatar")
278            .await;
279        bob.recv_msg(&sent_msg).await;
280        let alice_on_bob = bob.add_or_lookup_contact(&alice).await;
281        assert!(alice_on_bob.get_profile_image(&bob).await?.is_some());
282
283        wait_for_chatlist_specific_item(&bob, bob.create_chat(&alice).await.id).await;
284        Ok(())
285    }
286
287    /// Delete chat
288    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
289    async fn test_delete_chat() -> Result<()> {
290        let mut tcm = TestContextManager::new();
291        let alice = tcm.alice().await;
292        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
293
294        alice.evtracker.clear_events();
295        chat.delete(&alice).await?;
296        wait_for_chatlist(&alice).await;
297        Ok(())
298    }
299
300    /// Create group chat
301    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
302    async fn test_create_group_chat() -> Result<()> {
303        let mut tcm = TestContextManager::new();
304        let alice = tcm.alice().await;
305        alice.evtracker.clear_events();
306        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
307        wait_for_chatlist_and_specific_item(&alice, chat).await;
308        Ok(())
309    }
310
311    /// Create broadcastlist
312    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
313    async fn test_create_broadcastlist() -> Result<()> {
314        let mut tcm = TestContextManager::new();
315        let alice = tcm.alice().await;
316        alice.evtracker.clear_events();
317        create_broadcast_list(&alice).await?;
318        wait_for_chatlist(&alice).await;
319        Ok(())
320    }
321
322    /// Mute chat
323    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
324    async fn test_mute_chat() -> Result<()> {
325        let mut tcm = TestContextManager::new();
326        let alice = tcm.alice().await;
327        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
328
329        alice.evtracker.clear_events();
330        chat::set_muted(&alice, chat, MuteDuration::Forever).await?;
331        wait_for_chatlist_specific_item(&alice, chat).await;
332
333        alice.evtracker.clear_events();
334        chat::set_muted(&alice, chat, MuteDuration::NotMuted).await?;
335        wait_for_chatlist_specific_item(&alice, chat).await;
336
337        Ok(())
338    }
339
340    /// Expiry of mute should also trigger an event
341    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
342    #[ignore = "does not work yet"]
343    async fn test_mute_chat_expired() -> Result<()> {
344        let mut tcm = TestContextManager::new();
345        let alice = tcm.alice().await;
346        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
347
348        let mute_duration = MuteDuration::Until(
349            std::time::SystemTime::now()
350                .checked_add(Duration::from_secs(2))
351                .unwrap(),
352        );
353        chat::set_muted(&alice, chat, mute_duration).await?;
354        alice.evtracker.clear_events();
355        SystemTime::shift(Duration::from_secs(3));
356        wait_for_chatlist_specific_item(&alice, chat).await;
357
358        Ok(())
359    }
360
361    /// Change chat name
362    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
363    async fn test_change_chat_name() -> Result<()> {
364        let mut tcm = TestContextManager::new();
365        let alice = tcm.alice().await;
366        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
367
368        alice.evtracker.clear_events();
369        chat::set_chat_name(&alice, chat, "New Name").await?;
370        wait_for_chatlist_specific_item(&alice, chat).await;
371
372        Ok(())
373    }
374
375    /// Change chat profile image
376    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
377    async fn test_change_chat_profile_image() -> Result<()> {
378        let mut tcm = TestContextManager::new();
379        let alice = tcm.alice().await;
380        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
381
382        alice.evtracker.clear_events();
383        let file = alice.dir.path().join("avatar.png");
384        let bytes = include_bytes!("../../test-data/image/avatar64x64.png");
385        tokio::fs::write(&file, bytes).await?;
386        chat::set_chat_profile_image(&alice, chat, file.to_str().unwrap()).await?;
387        wait_for_chatlist_specific_item(&alice, chat).await;
388
389        Ok(())
390    }
391
392    /// Receive group and receive name change
393    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
394    async fn test_receiving_group_and_group_changes() -> Result<()> {
395        let mut tcm = TestContextManager::new();
396        let alice = tcm.alice().await;
397        let bob = tcm.bob().await;
398        let chat = alice
399            .create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[&bob])
400            .await;
401
402        let sent_msg = alice.send_text(chat, "Hello").await;
403        let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
404        wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await;
405        chat_id_for_bob.accept(&bob).await?;
406
407        bob.evtracker.clear_events();
408        chat::set_chat_name(&alice, chat, "New Name").await?;
409        let sent_msg = alice.send_text(chat, "Hello").await;
410        bob.recv_msg(&sent_msg).await;
411        wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await;
412
413        Ok(())
414    }
415
416    /// Accept contact request
417    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
418    async fn test_accept_contact_request() -> Result<()> {
419        let mut tcm = TestContextManager::new();
420        let alice = tcm.alice().await;
421        let bob = tcm.bob().await;
422        let chat = alice
423            .create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[&bob])
424            .await;
425        let sent_msg = alice.send_text(chat, "Hello").await;
426        let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
427
428        bob.evtracker.clear_events();
429        chat_id_for_bob.accept(&bob).await?;
430        wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await;
431
432        Ok(())
433    }
434
435    /// Block contact request
436    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
437    async fn test_block_contact_request() -> Result<()> {
438        let mut tcm = TestContextManager::new();
439        let alice = tcm.alice().await;
440        let bob = tcm.bob().await;
441        let chat = alice
442            .create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[&bob])
443            .await;
444        let sent_msg = alice.send_text(chat, "Hello").await;
445        let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
446
447        bob.evtracker.clear_events();
448        chat_id_for_bob.block(&bob).await?;
449        wait_for_chatlist(&bob).await;
450
451        Ok(())
452    }
453
454    /// Delete message
455    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
456    async fn test_delete_message() -> Result<()> {
457        let mut tcm = TestContextManager::new();
458        let alice = tcm.alice().await;
459        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
460        let message = chat::send_text_msg(&alice, chat, "Hello World".to_owned()).await?;
461
462        alice.evtracker.clear_events();
463        message::delete_msgs(&alice, &[message]).await?;
464        wait_for_chatlist_specific_item(&alice, chat).await;
465
466        Ok(())
467    }
468
469    /// Click on chat should remove the unread count (on msgs noticed)
470    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
471    async fn test_msgs_noticed_on_chat() -> Result<()> {
472        let mut tcm = TestContextManager::new();
473        let alice = tcm.alice().await;
474        let bob = tcm.bob().await;
475
476        let chat = alice
477            .create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[&bob])
478            .await;
479        let sent_msg = alice.send_text(chat, "Hello").await;
480        let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
481        chat_id_for_bob.accept(&bob).await?;
482
483        let sent_msg = alice.send_text(chat, "New Message").await;
484        let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
485        assert!(chat_id_for_bob.get_fresh_msg_cnt(&bob).await? >= 1);
486
487        bob.evtracker.clear_events();
488        chat::marknoticed_chat(&bob, chat_id_for_bob).await?;
489        wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await;
490
491        Ok(())
492    }
493
494    // Block and Unblock contact
495    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
496    async fn test_unblock_contact() -> Result<()> {
497        let mut tcm = TestContextManager::new();
498        let alice = tcm.alice().await;
499        let contact_id = Contact::create(&alice, "example", "example@example.com").await?;
500        let _ = ChatId::create_for_contact(&alice, contact_id).await;
501
502        alice.evtracker.clear_events();
503        Contact::block(&alice, contact_id).await?;
504        wait_for_chatlist(&alice).await;
505
506        alice.evtracker.clear_events();
507        Contact::unblock(&alice, contact_id).await?;
508        wait_for_chatlist(&alice).await;
509
510        Ok(())
511    }
512
513    /// Tests that expired disappearing message
514    /// produces events about chatlist being modified.
515    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
516    async fn test_update_after_ephemeral_messages() -> Result<()> {
517        let mut tcm = TestContextManager::new();
518        let alice = tcm.alice().await;
519        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
520        chat.set_ephemeral_timer(&alice, crate::ephemeral::Timer::Enabled { duration: 60 })
521            .await?;
522        alice
523            .evtracker
524            .get_matching(|evt| matches!(evt, EventType::ChatEphemeralTimerModified { .. }))
525            .await;
526
527        let _ = chat::send_text_msg(&alice, chat, "Hello".to_owned()).await?;
528        wait_for_chatlist_and_specific_item(&alice, chat).await;
529
530        SystemTime::shift(Duration::from_secs(70));
531        crate::ephemeral::delete_expired_messages(&alice, crate::tools::time()).await?;
532        wait_for_chatlist_and_specific_item(&alice, chat).await;
533
534        Ok(())
535    }
536
537    /// AdHoc (Groups without a group ID.) group receiving
538    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
539    async fn test_adhoc_group() -> Result<()> {
540        let alice = TestContext::new_alice().await;
541        let mime = br#"Subject: First thread
542Message-ID: first@example.org
543To: Alice <alice@example.org>, Bob <bob@example.net>
544From: Claire <claire@example.org>
545Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
546
547First thread."#;
548
549        alice.evtracker.clear_events();
550        receive_imf(&alice, mime, false).await?;
551        wait_for_chatlist(&alice).await;
552
553        Ok(())
554    }
555
556    /// Test both direction of securejoin
557    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
558    async fn test_secure_join_group() -> Result<()> {
559        let mut tcm = TestContextManager::new();
560        let alice = tcm.alice().await;
561        let bob = tcm.bob().await;
562
563        let alice_chatid =
564            chat::create_group_chat(&alice.ctx, ProtectionStatus::Protected, "the chat").await?;
565
566        // Step 1: Generate QR-code, secure-join implied by chatid
567        let qr = get_securejoin_qr(&alice.ctx, Some(alice_chatid)).await?;
568
569        // Step 2: Bob scans QR-code, sends vg-request
570        bob.evtracker.clear_events();
571        let bob_chatid = join_securejoin(&bob.ctx, &qr).await?;
572        wait_for_chatlist(&bob).await;
573
574        let sent = bob.pop_sent_msg().await;
575
576        // Step 3: Alice receives vg-request, sends vg-auth-required
577        alice.evtracker.clear_events();
578        alice.recv_msg_trash(&sent).await;
579
580        let sent = alice.pop_sent_msg().await;
581
582        // Step 4: Bob receives vg-auth-required, sends vg-request-with-auth
583        bob.evtracker.clear_events();
584        bob.recv_msg_trash(&sent).await;
585        wait_for_chatlist_and_specific_item(&bob, bob_chatid).await;
586
587        let sent = bob.pop_sent_msg().await;
588
589        // Step 5+6: Alice receives vg-request-with-auth, sends vg-member-added
590        alice.evtracker.clear_events();
591        alice.recv_msg_trash(&sent).await;
592        wait_for_chatlist_and_specific_item(&alice, alice_chatid).await;
593
594        let sent = alice.pop_sent_msg().await;
595
596        // Step 7: Bob receives vg-member-added
597        bob.evtracker.clear_events();
598        bob.recv_msg(&sent).await;
599        wait_for_chatlist_and_specific_item(&bob, bob_chatid).await;
600
601        Ok(())
602    }
603
604    /// Call Resend on message
605    ///
606    /// (the event is technically only needed if it is the last message in the chat, but checking that would be too expensive so the event is always emitted)
607    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
608    async fn test_resend_message() -> Result<()> {
609        let mut tcm = TestContextManager::new();
610        let alice = tcm.alice().await;
611        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
612
613        let msg_id = chat::send_text_msg(&alice, chat, "Hello".to_owned()).await?;
614        let _ = alice.pop_sent_msg().await;
615
616        let message = Message::load_from_db(&alice, msg_id).await?;
617        assert_eq!(message.get_state(), MessageState::OutDelivered);
618
619        alice.evtracker.clear_events();
620        chat::resend_msgs(&alice, &[msg_id]).await?;
621        wait_for_chatlist_specific_item(&alice, chat).await;
622
623        Ok(())
624    }
625
626    /// test that setting a reaction emits chatlistitem update event
627    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
628    async fn test_reaction() -> Result<()> {
629        let mut tcm = TestContextManager::new();
630        let alice = tcm.alice().await;
631        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
632        let msg_id = chat::send_text_msg(&alice, chat, "Hello".to_owned()).await?;
633        let _ = alice.pop_sent_msg().await;
634
635        alice.evtracker.clear_events();
636        reaction::send_reaction(&alice, msg_id, "👍").await?;
637        let _ = alice.pop_sent_msg().await;
638        wait_for_chatlist_specific_item(&alice, chat).await;
639
640        Ok(())
641    }
642}