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        let addr = alice_on_bob.get_addr();
251        Contact::create(&bob, "Alice2", addr).await?;
252        assert!(bob.add_or_lookup_contact(&alice).await.get_display_name() == "Alice2");
253
254        wait_for_chatlist_all_items(&bob).await;
255
256        Ok(())
257    }
258
259    /// Contact changed avatar
260    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
261    async fn test_contact_changed_avatar() -> Result<()> {
262        let mut tcm = TestContextManager::new();
263        let alice = tcm.alice().await;
264        let bob = tcm.bob().await;
265        let alice_to_bob_chat = alice.create_chat(&bob).await;
266        let sent_msg = alice.send_text(alice_to_bob_chat.id, "hello").await;
267        bob.recv_msg(&sent_msg).await;
268
269        bob.evtracker.clear_events();
270        // set alice avatar then receive messagefrom her with bob
271        let file = alice.dir.path().join("avatar.png");
272        let bytes = include_bytes!("../../test-data/image/avatar64x64.png");
273        tokio::fs::write(&file, bytes).await?;
274        alice
275            .set_config(Config::Selfavatar, Some(file.to_str().unwrap()))
276            .await?;
277        let sent_msg = alice
278            .send_text(alice_to_bob_chat.id, "hello, I have a new avatar")
279            .await;
280        bob.recv_msg(&sent_msg).await;
281        let alice_on_bob = bob.add_or_lookup_contact(&alice).await;
282        assert!(alice_on_bob.get_profile_image(&bob).await?.is_some());
283
284        wait_for_chatlist_specific_item(&bob, bob.create_chat(&alice).await.id).await;
285        Ok(())
286    }
287
288    /// Delete chat
289    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
290    async fn test_delete_chat() -> Result<()> {
291        let mut tcm = TestContextManager::new();
292        let alice = tcm.alice().await;
293        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
294
295        alice.evtracker.clear_events();
296        chat.delete(&alice).await?;
297        wait_for_chatlist(&alice).await;
298        Ok(())
299    }
300
301    /// Create group chat
302    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
303    async fn test_create_group_chat() -> Result<()> {
304        let mut tcm = TestContextManager::new();
305        let alice = tcm.alice().await;
306        alice.evtracker.clear_events();
307        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
308        wait_for_chatlist_and_specific_item(&alice, chat).await;
309        Ok(())
310    }
311
312    /// Create broadcastlist
313    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
314    async fn test_create_broadcastlist() -> Result<()> {
315        let mut tcm = TestContextManager::new();
316        let alice = tcm.alice().await;
317        alice.evtracker.clear_events();
318        create_broadcast_list(&alice).await?;
319        wait_for_chatlist(&alice).await;
320        Ok(())
321    }
322
323    /// Mute chat
324    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
325    async fn test_mute_chat() -> Result<()> {
326        let mut tcm = TestContextManager::new();
327        let alice = tcm.alice().await;
328        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
329
330        alice.evtracker.clear_events();
331        chat::set_muted(&alice, chat, MuteDuration::Forever).await?;
332        wait_for_chatlist_specific_item(&alice, chat).await;
333
334        alice.evtracker.clear_events();
335        chat::set_muted(&alice, chat, MuteDuration::NotMuted).await?;
336        wait_for_chatlist_specific_item(&alice, chat).await;
337
338        Ok(())
339    }
340
341    /// Expiry of mute should also trigger an event
342    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
343    #[ignore = "does not work yet"]
344    async fn test_mute_chat_expired() -> Result<()> {
345        let mut tcm = TestContextManager::new();
346        let alice = tcm.alice().await;
347        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
348
349        let mute_duration = MuteDuration::Until(
350            std::time::SystemTime::now()
351                .checked_add(Duration::from_secs(2))
352                .unwrap(),
353        );
354        chat::set_muted(&alice, chat, mute_duration).await?;
355        alice.evtracker.clear_events();
356        SystemTime::shift(Duration::from_secs(3));
357        wait_for_chatlist_specific_item(&alice, chat).await;
358
359        Ok(())
360    }
361
362    /// Change chat name
363    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
364    async fn test_change_chat_name() -> Result<()> {
365        let mut tcm = TestContextManager::new();
366        let alice = tcm.alice().await;
367        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
368
369        alice.evtracker.clear_events();
370        chat::set_chat_name(&alice, chat, "New Name").await?;
371        wait_for_chatlist_specific_item(&alice, chat).await;
372
373        Ok(())
374    }
375
376    /// Change chat profile image
377    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
378    async fn test_change_chat_profile_image() -> Result<()> {
379        let mut tcm = TestContextManager::new();
380        let alice = tcm.alice().await;
381        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
382
383        alice.evtracker.clear_events();
384        let file = alice.dir.path().join("avatar.png");
385        let bytes = include_bytes!("../../test-data/image/avatar64x64.png");
386        tokio::fs::write(&file, bytes).await?;
387        chat::set_chat_profile_image(&alice, chat, file.to_str().unwrap()).await?;
388        wait_for_chatlist_specific_item(&alice, chat).await;
389
390        Ok(())
391    }
392
393    /// Receive group and receive name change
394    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
395    async fn test_receiving_group_and_group_changes() -> Result<()> {
396        let mut tcm = TestContextManager::new();
397        let alice = tcm.alice().await;
398        let bob = tcm.bob().await;
399        let chat = alice
400            .create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[&bob])
401            .await;
402
403        let sent_msg = alice.send_text(chat, "Hello").await;
404        let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
405        wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await;
406        chat_id_for_bob.accept(&bob).await?;
407
408        bob.evtracker.clear_events();
409        chat::set_chat_name(&alice, chat, "New Name").await?;
410        let sent_msg = alice.send_text(chat, "Hello").await;
411        bob.recv_msg(&sent_msg).await;
412        wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await;
413
414        Ok(())
415    }
416
417    /// Accept contact request
418    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
419    async fn test_accept_contact_request() -> Result<()> {
420        let mut tcm = TestContextManager::new();
421        let alice = tcm.alice().await;
422        let bob = tcm.bob().await;
423        let chat = alice
424            .create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[&bob])
425            .await;
426        let sent_msg = alice.send_text(chat, "Hello").await;
427        let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
428
429        bob.evtracker.clear_events();
430        chat_id_for_bob.accept(&bob).await?;
431        wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await;
432
433        Ok(())
434    }
435
436    /// Block contact request
437    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
438    async fn test_block_contact_request() -> Result<()> {
439        let mut tcm = TestContextManager::new();
440        let alice = tcm.alice().await;
441        let bob = tcm.bob().await;
442        let chat = alice
443            .create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[&bob])
444            .await;
445        let sent_msg = alice.send_text(chat, "Hello").await;
446        let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
447
448        bob.evtracker.clear_events();
449        chat_id_for_bob.block(&bob).await?;
450        wait_for_chatlist(&bob).await;
451
452        Ok(())
453    }
454
455    /// Delete message
456    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
457    async fn test_delete_message() -> Result<()> {
458        let mut tcm = TestContextManager::new();
459        let alice = tcm.alice().await;
460        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
461        let message = chat::send_text_msg(&alice, chat, "Hello World".to_owned()).await?;
462
463        alice.evtracker.clear_events();
464        message::delete_msgs(&alice, &[message]).await?;
465        wait_for_chatlist_specific_item(&alice, chat).await;
466
467        Ok(())
468    }
469
470    /// Click on chat should remove the unread count (on msgs noticed)
471    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
472    async fn test_msgs_noticed_on_chat() -> Result<()> {
473        let mut tcm = TestContextManager::new();
474        let alice = tcm.alice().await;
475        let bob = tcm.bob().await;
476
477        let chat = alice
478            .create_group_with_members(ProtectionStatus::Unprotected, "My Group", &[&bob])
479            .await;
480        let sent_msg = alice.send_text(chat, "Hello").await;
481        let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
482        chat_id_for_bob.accept(&bob).await?;
483
484        let sent_msg = alice.send_text(chat, "New Message").await;
485        let chat_id_for_bob = bob.recv_msg(&sent_msg).await.chat_id;
486        assert!(chat_id_for_bob.get_fresh_msg_cnt(&bob).await? >= 1);
487
488        bob.evtracker.clear_events();
489        chat::marknoticed_chat(&bob, chat_id_for_bob).await?;
490        wait_for_chatlist_specific_item(&bob, chat_id_for_bob).await;
491
492        Ok(())
493    }
494
495    // Block and Unblock contact
496    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
497    async fn test_unblock_contact() -> Result<()> {
498        let mut tcm = TestContextManager::new();
499        let alice = tcm.alice().await;
500        let contact_id = Contact::create(&alice, "example", "example@example.com").await?;
501        let _ = ChatId::create_for_contact(&alice, contact_id).await;
502
503        alice.evtracker.clear_events();
504        Contact::block(&alice, contact_id).await?;
505        wait_for_chatlist(&alice).await;
506
507        alice.evtracker.clear_events();
508        Contact::unblock(&alice, contact_id).await?;
509        wait_for_chatlist(&alice).await;
510
511        Ok(())
512    }
513
514    /// Tests that expired disappearing message
515    /// produces events about chatlist being modified.
516    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
517    async fn test_update_after_ephemeral_messages() -> Result<()> {
518        let mut tcm = TestContextManager::new();
519        let alice = tcm.alice().await;
520        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
521        chat.set_ephemeral_timer(&alice, crate::ephemeral::Timer::Enabled { duration: 60 })
522            .await?;
523        alice
524            .evtracker
525            .get_matching(|evt| matches!(evt, EventType::ChatEphemeralTimerModified { .. }))
526            .await;
527
528        let _ = chat::send_text_msg(&alice, chat, "Hello".to_owned()).await?;
529        wait_for_chatlist_and_specific_item(&alice, chat).await;
530
531        SystemTime::shift(Duration::from_secs(70));
532        crate::ephemeral::delete_expired_messages(&alice, crate::tools::time()).await?;
533        wait_for_chatlist_and_specific_item(&alice, chat).await;
534
535        Ok(())
536    }
537
538    /// AdHoc (Groups without a group ID.) group receiving
539    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
540    async fn test_adhoc_group() -> Result<()> {
541        let alice = TestContext::new_alice().await;
542        let mime = br#"Subject: First thread
543Message-ID: first@example.org
544To: Alice <alice@example.org>, Bob <bob@example.net>
545From: Claire <claire@example.org>
546Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
547
548First thread."#;
549
550        alice.evtracker.clear_events();
551        receive_imf(&alice, mime, false).await?;
552        wait_for_chatlist(&alice).await;
553
554        Ok(())
555    }
556
557    /// Test both direction of securejoin
558    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
559    async fn test_secure_join_group() -> Result<()> {
560        let mut tcm = TestContextManager::new();
561        let alice = tcm.alice().await;
562        let bob = tcm.bob().await;
563
564        let alice_chatid =
565            chat::create_group_chat(&alice.ctx, ProtectionStatus::Protected, "the chat").await?;
566
567        // Step 1: Generate QR-code, secure-join implied by chatid
568        let qr = get_securejoin_qr(&alice.ctx, Some(alice_chatid)).await?;
569
570        // Step 2: Bob scans QR-code, sends vg-request
571        bob.evtracker.clear_events();
572        let bob_chatid = join_securejoin(&bob.ctx, &qr).await?;
573        wait_for_chatlist(&bob).await;
574
575        let sent = bob.pop_sent_msg().await;
576
577        // Step 3: Alice receives vg-request, sends vg-auth-required
578        alice.evtracker.clear_events();
579        alice.recv_msg_trash(&sent).await;
580
581        let sent = alice.pop_sent_msg().await;
582
583        // Step 4: Bob receives vg-auth-required, sends vg-request-with-auth
584        bob.evtracker.clear_events();
585        bob.recv_msg_trash(&sent).await;
586        wait_for_chatlist_and_specific_item(&bob, bob_chatid).await;
587
588        let sent = bob.pop_sent_msg().await;
589
590        // Step 5+6: Alice receives vg-request-with-auth, sends vg-member-added
591        alice.evtracker.clear_events();
592        alice.recv_msg_trash(&sent).await;
593        wait_for_chatlist_and_specific_item(&alice, alice_chatid).await;
594
595        let sent = alice.pop_sent_msg().await;
596
597        // Step 7: Bob receives vg-member-added
598        bob.evtracker.clear_events();
599        bob.recv_msg(&sent).await;
600        wait_for_chatlist_and_specific_item(&bob, bob_chatid).await;
601
602        Ok(())
603    }
604
605    /// Call Resend on message
606    ///
607    /// (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)
608    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
609    async fn test_resend_message() -> Result<()> {
610        let mut tcm = TestContextManager::new();
611        let alice = tcm.alice().await;
612        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
613
614        let msg_id = chat::send_text_msg(&alice, chat, "Hello".to_owned()).await?;
615        let _ = alice.pop_sent_msg().await;
616
617        let message = Message::load_from_db(&alice, msg_id).await?;
618        assert_eq!(message.get_state(), MessageState::OutDelivered);
619
620        alice.evtracker.clear_events();
621        chat::resend_msgs(&alice, &[msg_id]).await?;
622        wait_for_chatlist_specific_item(&alice, chat).await;
623
624        Ok(())
625    }
626
627    /// test that setting a reaction emits chatlistitem update event
628    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
629    async fn test_reaction() -> Result<()> {
630        let mut tcm = TestContextManager::new();
631        let alice = tcm.alice().await;
632        let chat = create_group_chat(&alice, ProtectionStatus::Protected, "My Group").await?;
633        let msg_id = chat::send_text_msg(&alice, chat, "Hello".to_owned()).await?;
634        let _ = alice.pop_sent_msg().await;
635
636        alice.evtracker.clear_events();
637        reaction::send_reaction(&alice, msg_id, "👍").await?;
638        let _ = alice.pop_sent_msg().await;
639        wait_for_chatlist_specific_item(&alice, chat).await;
640
641        Ok(())
642    }
643}