extract chat log event handler for testability
Pure refactor: move the log-event handling out of the ListConvosProviderInner effect closure into an exported handleConvoLogEvents function, so tests can drive it directly with a QueryClient and a batch of logs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ import throttle from 'lodash.throttle'
|
||||
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||
import {useCurrentConvoId} from '#/state/messages/current-convo-id'
|
||||
import {useMessagesEventBus} from '#/state/messages/events'
|
||||
import {type MessagesEventBusEvent} from '#/state/messages/events/types'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {invalidateJoinLinkPreviewsForConvo} from '#/state/queries/join-links'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
@@ -207,7 +208,63 @@ export function ListConvosProviderInner({
|
||||
const unsub = messagesBus.on(
|
||||
events => {
|
||||
if (events.type !== 'logs') return
|
||||
handleConvoLogEvents({
|
||||
queryClient,
|
||||
logs: events.logs,
|
||||
currentConvoId,
|
||||
currentAccountDid: currentAccount?.did,
|
||||
onRefetchNeeded: debouncedRefetch,
|
||||
})
|
||||
},
|
||||
{
|
||||
// get events for all chats
|
||||
convoId: undefined,
|
||||
},
|
||||
)
|
||||
|
||||
return () => unsub()
|
||||
}, [
|
||||
messagesBus,
|
||||
currentConvoId,
|
||||
queryClient,
|
||||
currentAccount?.did,
|
||||
debouncedRefetch,
|
||||
])
|
||||
|
||||
const ctx = useMemo(() => {
|
||||
const convos = data?.pages.flatMap(page => page.convos) ?? []
|
||||
return {
|
||||
accepted: convos.filter(conv => conv.status === 'accepted'),
|
||||
request: convos.filter(conv => conv.status === 'request'),
|
||||
}
|
||||
}, [data])
|
||||
|
||||
return (
|
||||
<ListConvosContext.Provider value={ctx}>
|
||||
{children}
|
||||
</ListConvosContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a batch of chat log events to the React Query caches (convo lists,
|
||||
* single-convo, members, requests). Exported as a pure function - separate
|
||||
* from the event-bus subscription in ListConvosProviderInner - so it can be
|
||||
* unit-tested directly without mounting the provider.
|
||||
*/
|
||||
export function handleConvoLogEvents({
|
||||
queryClient,
|
||||
logs,
|
||||
currentConvoId,
|
||||
currentAccountDid,
|
||||
onRefetchNeeded,
|
||||
}: {
|
||||
queryClient: QueryClient
|
||||
logs: Extract<MessagesEventBusEvent, {type: 'logs'}>['logs']
|
||||
currentConvoId: string | undefined
|
||||
currentAccountDid: string | undefined
|
||||
onRefetchNeeded: () => void
|
||||
}): void {
|
||||
function mutateMembers(
|
||||
convoId: string,
|
||||
fn: (
|
||||
@@ -225,9 +282,7 @@ export function ListConvosProviderInner({
|
||||
|
||||
function updateConvoInAllLists(
|
||||
convoId: string,
|
||||
fn: (
|
||||
convo: ChatBskyConvoDefs.ConvoView,
|
||||
) => ChatBskyConvoDefs.ConvoView,
|
||||
fn: (convo: ChatBskyConvoDefs.ConvoView) => ChatBskyConvoDefs.ConvoView,
|
||||
) {
|
||||
queryClient.setQueriesData<ConvoListQueryData>(
|
||||
{queryKey: [RQKEY_ROOT]},
|
||||
@@ -241,9 +296,7 @@ export function ListConvosProviderInner({
|
||||
|
||||
function mutateConvoView(
|
||||
convoId: string,
|
||||
fn: (
|
||||
convo: ChatBskyConvoDefs.ConvoView,
|
||||
) => ChatBskyConvoDefs.ConvoView,
|
||||
fn: (convo: ChatBskyConvoDefs.ConvoView) => ChatBskyConvoDefs.ConvoView,
|
||||
) {
|
||||
queryClient.setQueryData<ChatBskyConvoDefs.ConvoView>(
|
||||
CONVO_KEY(convoId),
|
||||
@@ -290,11 +343,7 @@ export function ListConvosProviderInner({
|
||||
)
|
||||
}
|
||||
|
||||
function handleMemberRemoved(
|
||||
convoId: string,
|
||||
did: string,
|
||||
rev: string,
|
||||
) {
|
||||
function handleMemberRemoved(convoId: string, did: string, rev: string) {
|
||||
// If the optimistic remove already dropped them from the full
|
||||
// list, skip the memberCount decrement to avoid double-counting.
|
||||
const alreadyRemovedMember =
|
||||
@@ -312,9 +361,9 @@ export function ListConvosProviderInner({
|
||||
)
|
||||
}
|
||||
|
||||
for (const log of events.logs) {
|
||||
for (const log of logs) {
|
||||
if (ChatBskyConvoDefs.isLogBeginConvo(log)) {
|
||||
debouncedRefetch()
|
||||
onRefetchNeeded()
|
||||
} else if (ChatBskyConvoDefs.isLogLeaveConvo(log)) {
|
||||
deleteConvoFromAllLists(log.convoId)
|
||||
// The viewer is no longer in this convo (they left on another
|
||||
@@ -369,7 +418,7 @@ export function ListConvosProviderInner({
|
||||
// the remaining logs in this batch still apply - the bus advances
|
||||
// its cursor past this batch, so a dropped log is never
|
||||
// redelivered.
|
||||
debouncedRefetch()
|
||||
onRefetchNeeded()
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -382,9 +431,7 @@ export function ListConvosProviderInner({
|
||||
}
|
||||
|
||||
// add relatedProfiles to members list, but making sure to dedupe
|
||||
const relatedProfilesSansMembers = (
|
||||
logRef.relatedProfiles ?? []
|
||||
).filter(
|
||||
const relatedProfilesSansMembers = (logRef.relatedProfiles ?? []).filter(
|
||||
profile =>
|
||||
!foundConvo.members.some(member => member.did === profile.did),
|
||||
)
|
||||
@@ -399,7 +446,7 @@ export function ListConvosProviderInner({
|
||||
foundConvo.id !== currentConvoId
|
||||
? (ChatBskyConvoDefs.isMessageView(logRef.message) ||
|
||||
ChatBskyConvoDefs.isDeletedMessageView(logRef.message)) &&
|
||||
logRef.message.sender.did !== currentAccount?.did
|
||||
logRef.message.sender.did !== currentAccountDid
|
||||
? foundConvo.unreadCount + 1
|
||||
: foundConvo.unreadCount
|
||||
: 0,
|
||||
@@ -418,10 +465,7 @@ export function ListConvosProviderInner({
|
||||
if (i === 0) {
|
||||
return {
|
||||
...page,
|
||||
convos: [
|
||||
updatedConvo,
|
||||
...filterConvoFromPage(page.convos),
|
||||
],
|
||||
convos: [updatedConvo, ...filterConvoFromPage(page.convos)],
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -487,8 +531,7 @@ export function ListConvosProviderInner({
|
||||
})),
|
||||
)
|
||||
} else if (ChatBskyConvoDefs.isLogAcceptConvo(log)) {
|
||||
const requestQueries =
|
||||
queryClient.getQueriesData<ConvoListQueryData>({
|
||||
const requestQueries = queryClient.getQueriesData<ConvoListQueryData>({
|
||||
queryKey: RQKEY_PARTIAL('request'),
|
||||
})
|
||||
let foundConvo: ChatBskyConvoDefs.ConvoView | null = null
|
||||
@@ -501,7 +544,7 @@ export function ListConvosProviderInner({
|
||||
// Use continue (not return) so the remaining logs in this batch
|
||||
// still apply - the bus advances its cursor past this batch, so a
|
||||
// dropped log is never redelivered.
|
||||
debouncedRefetch()
|
||||
onRefetchNeeded()
|
||||
continue
|
||||
}
|
||||
if (log.rev <= foundConvo.rev) {
|
||||
@@ -545,7 +588,7 @@ export function ListConvosProviderInner({
|
||||
},
|
||||
(old?: ConvoListQueryData) => {
|
||||
if (!old) {
|
||||
debouncedRefetch()
|
||||
onRefetchNeeded()
|
||||
return old
|
||||
}
|
||||
return {
|
||||
@@ -645,11 +688,11 @@ export function ListConvosProviderInner({
|
||||
ChatBskyConvoDefs.isLogDisableJoinLink(log)
|
||||
) {
|
||||
// Join link data not included in the log event, trigger refetch to get it
|
||||
debouncedRefetch()
|
||||
onRefetchNeeded()
|
||||
} else if (ChatBskyConvoDefs.isLogEditGroup(log)) {
|
||||
// Updated group details (name etc.) aren't included in the log
|
||||
// event, so refetch to pick them up.
|
||||
debouncedRefetch()
|
||||
onRefetchNeeded()
|
||||
} else if (
|
||||
ChatBskyConvoDefs.isLogApproveJoinRequest(log) ||
|
||||
ChatBskyConvoDefs.isLogRejectJoinRequest(log)
|
||||
@@ -693,7 +736,7 @@ export function ListConvosProviderInner({
|
||||
} else if (ChatBskyConvoDefs.isLogOutgoingJoinRequest(log)) {
|
||||
// Viewer isn't in the chat yet, but the inbox surfaces outgoing
|
||||
// requests, so refetch to pick up the new entry.
|
||||
debouncedRefetch()
|
||||
onRefetchNeeded()
|
||||
} else if (ChatBskyConvoDefs.isLogWithdrawIncomingJoinRequest(log)) {
|
||||
// A requester rescinded their request to a group the viewer owns.
|
||||
// Mirror of isLogIncomingJoinRequest: decrement the counts.
|
||||
@@ -715,9 +758,7 @@ export function ListConvosProviderInner({
|
||||
log.convoId,
|
||||
withRevGuard(log.rev, convo => {
|
||||
// add relatedProfiles to members list, but making sure to dedupe
|
||||
const relatedProfilesSansMembers = (
|
||||
log.relatedProfiles ?? []
|
||||
).filter(
|
||||
const relatedProfilesSansMembers = (log.relatedProfiles ?? []).filter(
|
||||
profile =>
|
||||
!convo.members.some(member => member.did === profile.did),
|
||||
)
|
||||
@@ -752,7 +793,7 @@ export function ListConvosProviderInner({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: CONVO_KEY(log.convoId),
|
||||
})
|
||||
debouncedRefetch()
|
||||
onRefetchNeeded()
|
||||
} else if (ChatBskyConvoDefs.isLogRemoveMember(log)) {
|
||||
const data = log.message.data
|
||||
if (
|
||||
@@ -767,7 +808,7 @@ export function ListConvosProviderInner({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: CONVO_KEY(log.convoId),
|
||||
})
|
||||
debouncedRefetch()
|
||||
onRefetchNeeded()
|
||||
} else if (ChatBskyConvoDefs.isLogMemberJoin(log)) {
|
||||
const data = log.message.data
|
||||
if (
|
||||
@@ -786,7 +827,7 @@ export function ListConvosProviderInner({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: CONVO_KEY(log.convoId),
|
||||
})
|
||||
debouncedRefetch()
|
||||
onRefetchNeeded()
|
||||
} else if (ChatBskyConvoDefs.isLogMemberLeave(log)) {
|
||||
const data = log.message.data
|
||||
if (
|
||||
@@ -800,7 +841,7 @@ export function ListConvosProviderInner({
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: CONVO_KEY(log.convoId),
|
||||
})
|
||||
debouncedRefetch()
|
||||
onRefetchNeeded()
|
||||
} else if (ChatBskyConvoDefs.isLogRemoveReaction(log)) {
|
||||
queryClient.setQueriesData(
|
||||
{queryKey: [RQKEY_ROOT]},
|
||||
@@ -837,35 +878,6 @@ export function ListConvosProviderInner({
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
// get events for all chats
|
||||
convoId: undefined,
|
||||
},
|
||||
)
|
||||
|
||||
return () => unsub()
|
||||
}, [
|
||||
messagesBus,
|
||||
currentConvoId,
|
||||
queryClient,
|
||||
currentAccount?.did,
|
||||
debouncedRefetch,
|
||||
])
|
||||
|
||||
const ctx = useMemo(() => {
|
||||
const convos = data?.pages.flatMap(page => page.convos) ?? []
|
||||
return {
|
||||
accepted: convos.filter(conv => conv.status === 'accepted'),
|
||||
request: convos.filter(conv => conv.status === 'request'),
|
||||
}
|
||||
}, [data])
|
||||
|
||||
return (
|
||||
<ListConvosContext.Provider value={ctx}>
|
||||
{children}
|
||||
</ListConvosContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useUnreadMessageCount() {
|
||||
|
||||
Reference in New Issue
Block a user