diff --git a/internal/roomname.go b/internal/roomname.go index f60a022..76cd02b 100644 --- a/internal/roomname.go +++ b/internal/roomname.go @@ -109,12 +109,6 @@ func (m *RoomMetadata) SameRoomName(other *RoomMetadata) bool { sameHeroNames(m.Heroes, other.Heroes)) } -// SameRoomAvatar checks if the fields relevant for room avatars have changed between the two metadatas. -// Returns true if there are no changes. -func (m *RoomMetadata) SameRoomAvatar(other *RoomMetadata) bool { - return m.AvatarEvent == other.AvatarEvent && sameHeroAvatars(m.Heroes, other.Heroes) -} - func (m *RoomMetadata) SameJoinCount(other *RoomMetadata) bool { return m.JoinCount == other.JoinCount } @@ -138,21 +132,6 @@ func sameHeroNames(a, b []Hero) bool { return true } -func sameHeroAvatars(a, b []Hero) bool { - if len(a) != len(b) { - return false - } - for i := range a { - if a[i].ID != b[i].ID { - return false - } - if a[i].Avatar != b[i].Avatar { - return false - } - } - return true -} - func (m *RoomMetadata) RemoveHero(userID string) { for i, h := range m.Heroes { if h.ID == userID { diff --git a/sync3/lists.go b/sync3/lists.go index 414d064..823637b 100644 --- a/sync3/lists.go +++ b/sync3/lists.go @@ -85,7 +85,7 @@ func (s *InternalRequestLists) SetRoom(r RoomConnMetadata) (delta RoomDelta) { // to conclude. r.CanonicalisedName = existing.CanonicalisedName } - delta.RoomAvatarChanged = !existing.SameRoomAvatar(&r.RoomMetadata) + delta.RoomAvatarChanged = !existing.SameRoomAvatar(&r) if delta.RoomAvatarChanged { r.ResolvedAvatarURL = internal.CalculateAvatar(&r.RoomMetadata, r.IsDM) } diff --git a/sync3/room.go b/sync3/room.go index 0693580..c539763 100644 --- a/sync3/room.go +++ b/sync3/room.go @@ -56,6 +56,37 @@ type RoomConnMetadata struct { LastInterestedEventTimestamps map[string]uint64 } +// SameRoomAvatar checks if the fields relevant for room avatars have changed between the two metadatas. +// Returns true if there are no changes. +func (r *RoomConnMetadata) SameRoomAvatar(next *RoomConnMetadata) bool { + sameRoomAvatar := r.AvatarEvent == next.AvatarEvent + if next.IsDM { + // the avatar is the same IF: + // - the m.room.avatar event is the same AND + // - the heroes haven't changed AND + // - the number of heroes is 1 + return sameRoomAvatar && sameHeroAvatars(r.Heroes, next.Heroes) && len(next.Heroes) == 1 + } + // the avatar is the same IF: + // - the m.room.avatar event is the same + return sameRoomAvatar +} + +func sameHeroAvatars(a, b []internal.Hero) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i].ID != b[i].ID { + return false + } + if a[i].Avatar != b[i].Avatar { + return false + } + } + return true +} + func (r *RoomConnMetadata) GetLastInterestedEventTimestamp(listKey string) uint64 { ts, ok := r.LastInterestedEventTimestamps[listKey] if ok { diff --git a/tests-e2e/lists_test.go b/tests-e2e/lists_test.go index bdb1c4e..4d0bfc3 100644 --- a/tests-e2e/lists_test.go +++ b/tests-e2e/lists_test.go @@ -1731,8 +1731,60 @@ func TestAvatarFieldInRoomResponse(t *testing.T) { t.Log("Alice syncs until she sees the invite.") res = alice.SlidingSyncUntilMembership(t, res.Pos, dmInvited, alice, "invite") - t.Log("The new room should use Chris's avatar.") - m.MatchResponse(t, res, m.MatchRoomSubscription(dmInvited, m.MatchRoomAvatar(chris.AvatarURL))) + t.Log("The new room should appear as a DM and use Chris's avatar.") + m.MatchResponse(t, res, m.MatchRoomSubscription(dmInvited, m.MatchRoomIsDM(true), m.MatchRoomAvatar(chris.AvatarURL))) + + t.Run("Creator of a non-DM never sees an avatar", func(t *testing.T) { + t.Log("Alice makes a new room which is not a DM.") + privateGroup := alice.MustCreateRoom(t, map[string]interface{}{ + "preset": "trusted_private_chat", + "is_direct": false, + }) + + t.Log("Alice sees the group. It has no avatar.") + res = alice.SlidingSyncUntil(t, res.Pos, sync3.Request{}, m.MatchRoomSubscription(privateGroup, m.MatchRoomUnsetAvatar())) + m.MatchResponse(t, res, m.MatchRoomSubscription(privateGroup, m.MatchRoomIsDM(false))) + + t.Log("Alice invites Bob to the group, who accepts.") + alice.MustInviteRoom(t, privateGroup, bob.UserID) + bob.MustJoinRoom(t, privateGroup, nil) + + t.Log("Alice sees Bob join. The room still has no avatar.") + res = alice.SlidingSyncUntil(t, res.Pos, sync3.Request{}, func(response *sync3.Response) error { + matchNoAvatarChange := m.MatchRoomSubscription(privateGroup, m.MatchRoomUnchangedAvatar()) + if err := matchNoAvatarChange(response); err != nil { + t.Fatalf("Saw group avatar change: %s", err) + } + matchJoin := m.MatchRoomSubscription(privateGroup, MatchRoomTimelineMostRecent(1, []Event{ + { + Type: "m.room.member", + Sender: bob.UserID, + StateKey: ptr(bob.UserID), + }, + })) + return matchJoin(response) + }) + + t.Log("Alice invites Chris to the group, who accepts.") + alice.MustInviteRoom(t, privateGroup, chris.UserID) + chris.MustJoinRoom(t, privateGroup, nil) + + t.Log("Alice sees Chris join. The room still has no avatar.") + res = alice.SlidingSyncUntil(t, res.Pos, sync3.Request{}, func(response *sync3.Response) error { + matchNoAvatarChange := m.MatchRoomSubscription(privateGroup, m.MatchRoomUnchangedAvatar()) + if err := matchNoAvatarChange(response); err != nil { + t.Fatalf("Saw group avatar change: %s", err) + } + matchJoin := m.MatchRoomSubscription(privateGroup, MatchRoomTimelineMostRecent(1, []Event{ + { + Type: "m.room.member", + Sender: chris.UserID, + StateKey: ptr(chris.UserID), + }, + })) + return matchJoin(response) + }) + }) }) }