diff --git a/web/src/features/messages/ChatListColumn.tsx b/web/src/features/messages/ChatListColumn.tsx index e8465168..7fad4491 100644 --- a/web/src/features/messages/ChatListColumn.tsx +++ b/web/src/features/messages/ChatListColumn.tsx @@ -6,6 +6,7 @@ import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'; import { Avatar } from '@/app/Avatar'; import { Icon } from '@/app/Icon'; import { CreateGroupModal } from '@/features/groups/CreateGroupModal'; +import { friendAvatarLookup } from '@/features/messages/chat-list'; import { fetchAllPages } from '@/lib/api/client'; import { getFriends } from '@/lib/api/friends'; import { getGroups } from '@/lib/api/groups'; @@ -74,7 +75,7 @@ export function ChatListColumn() { // to carry the REST-only `avatar_file_id` this app uploads avatars through -- shares ContactsListColumn's // exact `['friends']` query key, so this is a cache read whenever that page has already been visited. const friendsQuery = useQuery({ queryKey: ['friends'], queryFn: () => fetchAllPages((cursor) => getFriends('', cursor)) }); - const avatarByUserId = new Map(friendsQuery.data?.map((f) => [f.user_id, f.avatar_file_id])); + const avatarByPeer = friendAvatarLookup(friendsQuery.data); const [query, setQuery] = useState(''); const [creatingGroup, setCreatingGroup] = useState(false); const sorted = [...contacts].sort((a, b) => { @@ -158,7 +159,7 @@ export function ChatListColumn() { const memberCount = isGroup ? memberCountByTopic.get(contact.topic) : undefined; const muted = !!contact.private?.mute_notifications; const avatarFileId = - contact.topic === 'slf' ? meQuery.data?.profile.avatar_file_id : isGroup ? avatarByGroupTopic.get(contact.topic) : avatarByUserId.get(contact.topic); + contact.topic === 'slf' ? meQuery.data?.profile.avatar_file_id : isGroup ? avatarByGroupTopic.get(contact.topic) : avatarByPeer.get(contact.topic); return ( (); + for (const friend of friends) { + if (friend.user_id) avatars.set(friend.user_id, friend.avatar_file_id); + if (friend.topic) avatars.set(friend.topic, friend.avatar_file_id); + } + return avatars; +} diff --git a/web/tests/chat-list.test.ts b/web/tests/chat-list.test.ts new file mode 100644 index 00000000..e404dd60 --- /dev/null +++ b/web/tests/chat-list.test.ts @@ -0,0 +1,24 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { friendAvatarLookup } from '../src/features/messages/chat-list.ts'; + +test('friend avatars resolve by both user ID and established P2P topic', () => { + const avatars = friendAvatarLookup([ + { + user_id: 'usr-potter', + topic: 'p2p-potter', + avatar_file_id: 'https://example.com/potter.png', + }, + ]); + + assert.equal(avatars.get('usr-potter'), 'https://example.com/potter.png'); + assert.equal(avatars.get('p2p-potter'), 'https://example.com/potter.png'); +}); + +test('friend avatar lookup tolerates a new conversation without a topic alias', () => { + const avatars = friendAvatarLookup([{ user_id: 'usr-new', avatar_file_id: 'avatar-file-id' }]); + + assert.equal(avatars.get('usr-new'), 'avatar-file-id'); + assert.equal(avatars.has(''), false); +});