feat: add useConversations hook
This commit is contained in:
@@ -0,0 +1,49 @@
|
|||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { renderHook, waitFor, act } from '@testing-library/react';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { MessagingProvider } from '../provider';
|
||||||
|
import { MockAdapter } from '../adapters/mock';
|
||||||
|
import { useConversations } from './use-conversations';
|
||||||
|
import type { MessagingAdapter } from '../adapter';
|
||||||
|
|
||||||
|
const wrap = (adapter: MessagingAdapter) =>
|
||||||
|
function Wrapper({ children }: { children: ReactNode }) {
|
||||||
|
return <MessagingProvider adapter={adapter}>{children}</MessagingProvider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('useConversations', () => {
|
||||||
|
it('starts loading, then resolves the adapter list', async () => {
|
||||||
|
const { result } = renderHook(() => useConversations(), { wrapper: wrap(new MockAdapter()) });
|
||||||
|
expect(result.current.loading).toBe(true);
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.loading).toBe(false));
|
||||||
|
expect(result.current.conversations).toHaveLength(2);
|
||||||
|
expect(result.current.conversations[0]!.threadId).toBe('th_mock_1');
|
||||||
|
expect(result.current.error).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('surfaces adapter failure as error state and never throws', async () => {
|
||||||
|
const adapter = new MockAdapter();
|
||||||
|
vi.spyOn(adapter, 'listConversations').mockRejectedValue(new Error('data door down'));
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useConversations(), { wrapper: wrap(adapter) });
|
||||||
|
await waitFor(() => expect(result.current.loading).toBe(false));
|
||||||
|
expect(result.current.error).toBe('data door down');
|
||||||
|
expect(result.current.conversations).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('refetch picks up newly opened threads', async () => {
|
||||||
|
const adapter = new MockAdapter();
|
||||||
|
const { result } = renderHook(() => useConversations(), { wrapper: wrap(adapter) });
|
||||||
|
await waitFor(() => expect(result.current.conversations).toHaveLength(2));
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await adapter.openThread({ participantIds: ['pp_dan'] });
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
result.current.refetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.conversations).toHaveLength(3));
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useAdapter } from '../provider';
|
||||||
|
import type { Conversation } from '../types';
|
||||||
|
|
||||||
|
export interface ConversationsState {
|
||||||
|
conversations: Conversation[];
|
||||||
|
loading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
refetch: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useConversations(): ConversationsState {
|
||||||
|
const adapter = useAdapter();
|
||||||
|
const [conversations, setConversations] = useState<Conversation[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [nonce, setNonce] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let alive = true;
|
||||||
|
setLoading(true);
|
||||||
|
adapter
|
||||||
|
.listConversations()
|
||||||
|
.then((list) => {
|
||||||
|
if (!alive) return;
|
||||||
|
setConversations(list);
|
||||||
|
setError(null);
|
||||||
|
})
|
||||||
|
.catch((e: unknown) => {
|
||||||
|
if (!alive) return;
|
||||||
|
setError(e instanceof Error ? e.message : String(e));
|
||||||
|
setConversations([]);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (alive) setLoading(false);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
alive = false;
|
||||||
|
};
|
||||||
|
}, [adapter, nonce]);
|
||||||
|
|
||||||
|
const refetch = useCallback(() => setNonce((n) => n + 1), []);
|
||||||
|
|
||||||
|
return { conversations, loading, error, refetch };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user