Skip to content

Commit

Permalink
Test sending receipts on events related to the root of a thread. (#501)
Browse files Browse the repository at this point in the history
MSC3771 specify that events related to the root event should be
valid in a threaded receipt to mark the thread as read.
  • Loading branch information
clokep authored Oct 27, 2022
1 parent bd831c9 commit fa14a52
Showing 1 changed file with 84 additions and 15 deletions.
99 changes: 84 additions & 15 deletions tests/csapi/thread_notifications_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,15 @@ func syncHasThreadedReadReceipt(roomID, userID, eventID, threadID string) client
//
// This sends four messages as alice creating a timeline like:
//
// A<--B<--C [thread]
// A<--B<--C<--E [m.thread to A]
// ^
// +---D [main timeline]
// |
// +---D [main timeline]
// |
// +<--F [m.reference to A]
//
// |
// +<--G [m.annotation to F]
//
// Where C and D generate highlight notifications.
//
Expand Down Expand Up @@ -121,6 +127,43 @@ func TestThreadedReceipts(t *testing.T) {
},
})

// Send another event in the thread created above.
alice.SendEventSynced(t, roomID, b.Event{
Type: "m.room.message",
Content: map[string]interface{}{
"msgtype": "m.text",
"body": "End thread",
"m.relates_to": map[string]interface{}{
"event_id": eventA,
"rel_type": "m.thread",
},
},
})

// Create another event related to the root event via a reference (i.e. but
// not via a thread). Then create an event which is an annotation to it.
eventF := alice.SendEventSynced(t, roomID, b.Event{
Type: "m.room.message",
Content: map[string]interface{}{
"msgtype": "m.text",
"body": "Reference!",
"m.relates_to": map[string]interface{}{
"event_id": eventA,
"rel_type": "m.reference",
},
},
})
eventG := alice.SendEventSynced(t, roomID, b.Event{
Type: "m.room.reaction",
Content: map[string]interface{}{
"m.relates_to": map[string]interface{}{
"event_id": eventF,
"rel_type": "m.annotation",
"key": "test",
},
},
})

// A filter to get thread notifications.
threadFilter := `{"room":{"timeline":{"unread_thread_notifications":true}}}`

Expand All @@ -132,7 +175,7 @@ func TestThreadedReceipts(t *testing.T) {
return r.Get("event_id").Str == eventD
}),
syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool {
return r.Get("highlight_count").Num == 2 && r.Get("notification_count").Num == 4 && !t.Exists()
return r.Get("highlight_count").Num == 2 && r.Get("notification_count").Num == 6 && !t.Exists()
}),
)
bob.MustSyncUntil(
Expand All @@ -143,8 +186,8 @@ func TestThreadedReceipts(t *testing.T) {
}),
syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool {
threadNotifications := t.Get(client.GjsonEscape(eventA))
return r.Get("highlight_count").Num == 1 && r.Get("notification_count").Num == 2 &&
threadNotifications.Get("highlight_count").Num == 1 && threadNotifications.Get("notification_count").Num == 2
return r.Get("highlight_count").Num == 1 && r.Get("notification_count").Num == 3 &&
threadNotifications.Get("highlight_count").Num == 1 && threadNotifications.Get("notification_count").Num == 3
}),
)

Expand All @@ -159,7 +202,7 @@ func TestThreadedReceipts(t *testing.T) {
}),
syncHasThreadedReadReceipt(roomID, bob.UserID, eventA, "main"),
syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool {
return r.Get("highlight_count").Num == 2 && r.Get("notification_count").Num == 3 && !t.Exists()
return r.Get("highlight_count").Num == 2 && r.Get("notification_count").Num == 5 && !t.Exists()
}),
)
bob.MustSyncUntil(
Expand All @@ -171,8 +214,8 @@ func TestThreadedReceipts(t *testing.T) {
syncHasThreadedReadReceipt(roomID, bob.UserID, eventA, "main"),
syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool {
threadNotifications := t.Get(client.GjsonEscape(eventA))
return r.Get("highlight_count").Num == 1 && r.Get("notification_count").Num == 1 &&
threadNotifications.Get("highlight_count").Num == 1 && threadNotifications.Get("notification_count").Num == 2
return r.Get("highlight_count").Num == 1 && r.Get("notification_count").Num == 2 &&
threadNotifications.Get("highlight_count").Num == 1 && threadNotifications.Get("notification_count").Num == 3
}),
)

Expand All @@ -187,7 +230,7 @@ func TestThreadedReceipts(t *testing.T) {
syncHasThreadedReadReceipt(roomID, bob.UserID, eventA, "main"),
syncHasThreadedReadReceipt(roomID, bob.UserID, eventB, eventA),
syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool {
return r.Get("highlight_count").Num == 2 && r.Get("notification_count").Num == 2 && !t.Exists()
return r.Get("highlight_count").Num == 2 && r.Get("notification_count").Num == 4 && !t.Exists()
}),
)
bob.MustSyncUntil(
Expand All @@ -200,13 +243,12 @@ func TestThreadedReceipts(t *testing.T) {
syncHasThreadedReadReceipt(roomID, bob.UserID, eventB, eventA),
syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool {
threadNotifications := t.Get(client.GjsonEscape(eventA))
return r.Get("highlight_count").Num == 1 && r.Get("notification_count").Num == 1 &&
threadNotifications.Get("highlight_count").Num == 1 && threadNotifications.Get("notification_count").Num == 1
return r.Get("highlight_count").Num == 1 && r.Get("notification_count").Num == 2 &&
threadNotifications.Get("highlight_count").Num == 1 && threadNotifications.Get("notification_count").Num == 2
}),
)

// Mark the entire room as read by sending an unthreaded read receipt on the last
// event. This clears all notification counts.
// Use an unthreaded receipt to mark the second thread event and an unthreaded event as read.
bob.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "receipt", "m.read", eventD}, client.WithJSONBody(t, struct{}{}))
bob.MustSyncUntil(
t, client.SyncReq{Since: bobNextBatch},
Expand All @@ -215,7 +257,7 @@ func TestThreadedReceipts(t *testing.T) {
}),
syncHasUnthreadedReadReceipt(roomID, bob.UserID, eventD),
syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool {
return r.Get("highlight_count").Num == 0 && r.Get("notification_count").Num == 0 && !t.Exists()
return r.Get("highlight_count").Num == 0 && r.Get("notification_count").Num == 2 && !t.Exists()
}),
)
bob.MustSyncUntil(
Expand All @@ -226,7 +268,34 @@ func TestThreadedReceipts(t *testing.T) {
}),
syncHasUnthreadedReadReceipt(roomID, bob.UserID, eventD),
syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool {
return r.Get("highlight_count").Num == 0 && r.Get("notification_count").Num == 0 && !t.Exists()
threadNotifications := t.Get(client.GjsonEscape(eventA))
return r.Get("highlight_count").Num == 0 && r.Get("notification_count").Num == 1 &&
threadNotifications.Get("highlight_count").Num == 0 && threadNotifications.Get("notification_count").Num == 1
}),
)

// Finally, mark the entire thread as read, using the annotation.
//
// Note that this will *not* affect the main timeline.
bob.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "receipt", "m.read", eventG}, client.WithJSONBody(t, map[string]interface{}{"thread_id": eventA}))
bob.MustSyncUntil(
t, client.SyncReq{Since: bobNextBatch},
client.SyncTimelineHas(roomID, func(r gjson.Result) bool {
return r.Get("event_id").Str == eventD
}),
syncHasUnthreadedReadReceipt(roomID, bob.UserID, eventD),
syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool {
return r.Get("highlight_count").Num == 0 && r.Get("notification_count").Num == 1 && !t.Exists()
}),
)
bob.MustSyncUntil(
t,
client.SyncReq{Since: bobNextBatch, Filter: threadFilter}, client.SyncTimelineHas(roomID, func(r gjson.Result) bool {
return r.Get("event_id").Str == eventD
}),
syncHasUnthreadedReadReceipt(roomID, bob.UserID, eventD),
syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool {
return r.Get("highlight_count").Num == 0 && r.Get("notification_count").Num == 1 && !t.Exists()
}),
)
}

0 comments on commit fa14a52

Please sign in to comment.