deltachat/events/
chatlist_events.rs

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