parent
3e2888eb75
commit
24bca458bd
@ -0,0 +1,19 @@
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
logger: {
|
||||
// eslint-disable-next-line no-console
|
||||
log: console.log,
|
||||
warn: console.warn,
|
||||
error: () => { },
|
||||
},
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 0,
|
||||
cacheTime: Infinity,
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export { queryClient };
|
@ -0,0 +1,271 @@
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
import sumBy from 'lodash/sumBy';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { __stub } from 'soapbox/api';
|
||||
import { mockStore, queryClient, renderHook, rootState, waitFor } from 'soapbox/jest/test-helpers';
|
||||
import { normalizeRelationship } from 'soapbox/normalizers';
|
||||
import { flattenPages } from 'soapbox/utils/queries';
|
||||
|
||||
import { IAccount } from '../accounts';
|
||||
import { ChatKeys, IChat, IChatMessage, useChat, useChatActions, useChatMessages, useChats } from '../chats';
|
||||
|
||||
jest.mock('soapbox/utils/queries');
|
||||
|
||||
const chat: IChat = {
|
||||
accepted: true,
|
||||
account: {
|
||||
username: 'username',
|
||||
verified: true,
|
||||
id: '1',
|
||||
acct: 'acct',
|
||||
avatar: 'avatar',
|
||||
avatar_static: 'avatar',
|
||||
display_name: 'my name',
|
||||
} as IAccount,
|
||||
created_at: '2020-06-10T02:05:06.000Z',
|
||||
created_by_account: '1',
|
||||
discarded_at: null,
|
||||
id: '1',
|
||||
last_message: null,
|
||||
latest_read_message_by_account: null,
|
||||
latest_read_message_created_at: null,
|
||||
message_expiration: 1209600,
|
||||
unread: 0,
|
||||
};
|
||||
|
||||
const buildChatMessage = (id: string): IChatMessage => ({
|
||||
id,
|
||||
chat_id: '1',
|
||||
account_id: '1',
|
||||
content: `chat message #${id}`,
|
||||
created_at: '2020-06-10T02:05:06.000Z',
|
||||
unread: true,
|
||||
});
|
||||
|
||||
describe('useChatMessages', () => {
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
describe('when the user is blocked', () => {
|
||||
beforeEach(() => {
|
||||
const state = rootState
|
||||
.set(
|
||||
'relationships',
|
||||
ImmutableMap({ '1': normalizeRelationship({ blocked_by: true }) }),
|
||||
);
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
it('is does not fetch the endpoint', async () => {
|
||||
const { result } = renderHook(() => useChatMessages(chat), undefined, store);
|
||||
|
||||
await waitFor(() => expect(result.current.isFetching).toBe(false));
|
||||
|
||||
expect(result.current.data?.length).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the user is not blocked', () => {
|
||||
describe('with a successful request', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet(`/api/v1/pleroma/chats/${chat.id}/messages`)
|
||||
.reply(200, [
|
||||
buildChatMessage('2'),
|
||||
], {
|
||||
link: `<https://example.com/api/v1/pleroma/chats/${chat.id}/messages?since_id=2>; rel="prev"`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('is successful', async () => {
|
||||
const { result } = renderHook(() => useChatMessages(chat));
|
||||
|
||||
await waitFor(() => expect(result.current.isFetching).toBe(false));
|
||||
|
||||
expect(result.current.data?.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an unsuccessful query', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet(`/api/v1/pleroma/chats/${chat.id}/messages`).networkError();
|
||||
});
|
||||
});
|
||||
|
||||
it('is has error state', async() => {
|
||||
const { result } = renderHook(() => useChatMessages(chat));
|
||||
|
||||
await waitFor(() => expect(result.current.isFetching).toBe(false));
|
||||
|
||||
expect(result.current.error).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useChats', () => {
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
describe('with a successful request', () => {
|
||||
beforeEach(() => {
|
||||
store = mockStore(ImmutableMap());
|
||||
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/pleroma/chats')
|
||||
.reply(200, [
|
||||
chat,
|
||||
], {
|
||||
link: '<https://example.com/api/v1/pleroma/chats?since_id=2>; rel="prev"',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('is successful', async () => {
|
||||
const { result } = renderHook(() => useChats().chatsQuery, undefined, store);
|
||||
|
||||
await waitFor(() => expect(result.current.isFetching).toBe(false));
|
||||
|
||||
expect(result.current.data?.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an unsuccessful query', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/pleroma/chats').networkError();
|
||||
});
|
||||
});
|
||||
|
||||
it('is has error state', async() => {
|
||||
const { result } = renderHook(() => useChats().chatsQuery);
|
||||
|
||||
await waitFor(() => expect(result.current.isFetching).toBe(false));
|
||||
|
||||
expect(result.current.error).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useChat()', () => {
|
||||
beforeEach(() => {
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
describe('with a successful request', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet(`/api/v1/pleroma/chats/${chat.id}`).reply(200, chat);
|
||||
});
|
||||
});
|
||||
|
||||
it('is successful', async () => {
|
||||
const { result } = renderHook(() => useChat(chat.id).chat);
|
||||
|
||||
await waitFor(() => expect(result.current.isFetching).toBe(false));
|
||||
|
||||
expect(result.current.data.id).toBe(chat.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an unsuccessful query', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet(`/api/v1/pleroma/chats/${chat.id}`).networkError();
|
||||
});
|
||||
});
|
||||
|
||||
it('is has error state', async() => {
|
||||
const { result } = renderHook(() => useChat(chat.id).chat);
|
||||
|
||||
await waitFor(() => expect(result.current.isFetching).toBe(false));
|
||||
|
||||
expect(result.current.error).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useChatActions', () => {
|
||||
beforeEach(() => {
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
describe('markChatAsRead()', () => {
|
||||
const nextUnreadCount = 5;
|
||||
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock
|
||||
.onPost(`/api/v1/pleroma/chats/${chat.id}/read`, { last_read_id: '2' })
|
||||
.reply(200, { ...chat, unread: nextUnreadCount });
|
||||
});
|
||||
});
|
||||
|
||||
it('updates the queryCache', async() => {
|
||||
const initialQueryData = {
|
||||
pages: [
|
||||
{ result: [chat], hasMore: false, link: undefined },
|
||||
],
|
||||
pageParams: [undefined],
|
||||
};
|
||||
const initialFlattenedData = flattenPages(initialQueryData);
|
||||
expect(sumBy(initialFlattenedData, (chat: IChat) => chat.unread)).toBe(0);
|
||||
|
||||
queryClient.setQueryData(ChatKeys.chatSearch(), initialQueryData);
|
||||
|
||||
const { result } = renderHook(() => useChatActions(chat.id).markChatAsRead('2'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current).resolves.toBeDefined();
|
||||
});
|
||||
|
||||
const nextQueryData = queryClient.getQueryData(ChatKeys.chatSearch());
|
||||
const nextFlattenedData = flattenPages(nextQueryData as any);
|
||||
expect(sumBy(nextFlattenedData as any, (chat: IChat) => chat.unread)).toBe(nextUnreadCount);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateChat()', () => {
|
||||
const nextUnreadCount = 5;
|
||||
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock
|
||||
.onPatch(`/api/v1/pleroma/chats/${chat.id}`)
|
||||
.reply(200, { ...chat, unread: nextUnreadCount });
|
||||
});
|
||||
});
|
||||
|
||||
it('updates the queryCache for the chat', async() => {
|
||||
const initialQueryData = { ...chat };
|
||||
expect(initialQueryData.message_expiration).toBe(1209600);
|
||||
queryClient.setQueryData(ChatKeys.chat(chat.id), initialQueryData);
|
||||
|
||||
const { result } = renderHook(() => {
|
||||
const { updateChat } = useChatActions(chat.id);
|
||||
|
||||
useEffect(() => {
|
||||
updateChat.mutate({ message_expiration: 1200 });
|
||||
}, []);
|
||||
|
||||
return updateChat;
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
const nextQueryData = queryClient.getQueryData(ChatKeys.chat(chat.id));
|
||||
expect((nextQueryData as any).message_expiration).toBe(1200);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,55 @@
|
||||
import { InfiniteData, QueryKey, UseInfiniteQueryResult } from '@tanstack/react-query';
|
||||
|
||||
import { queryClient } from 'soapbox/jest/test-helpers';
|
||||
|
||||
import { PaginatedResult } from '../queries';
|
||||
|
||||
const flattenPages = <T>(queryData: UseInfiniteQueryResult<PaginatedResult<T>>['data']) => {
|
||||
return queryData?.pages.reduce<T[]>(
|
||||
(prev: T[], curr) => [...curr.result, ...prev],
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
const updatePageItem = <T>(queryKey: QueryKey, newItem: T, isItem: (item: T, newItem: T) => boolean) => {
|
||||
queryClient.setQueriesData<InfiniteData<PaginatedResult<T>>>(queryKey, (data) => {
|
||||
if (data) {
|
||||
const pages = data.pages.map(page => {
|
||||
const result = page.result.map(item => isItem(item, newItem) ? newItem : item);
|
||||
return { ...page, result };
|
||||
});
|
||||
return { ...data, pages };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** Insert the new item at the beginning of the first page. */
|
||||
const appendPageItem = <T>(queryKey: QueryKey, newItem: T) => {
|
||||
queryClient.setQueryData<InfiniteData<PaginatedResult<T>>>(queryKey, (data) => {
|
||||
if (data) {
|
||||
const pages = [...data.pages];
|
||||
pages[0] = { ...pages[0], result: [...pages[0].result, newItem] };
|
||||
return { ...data, pages };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** Remove an item inside if found. */
|
||||
const removePageItem = <T>(queryKey: QueryKey, itemToRemove: T, isItem: (item: T, newItem: T) => boolean) => {
|
||||
queryClient.setQueriesData<InfiniteData<PaginatedResult<T>>>(queryKey, (data) => {
|
||||
if (data) {
|
||||
const pages = data.pages.map(page => {
|
||||
const result = page.result.filter(item => !isItem(item, itemToRemove));
|
||||
return { ...page, result };
|
||||
});
|
||||
return { ...data, pages };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
flattenPages,
|
||||
updatePageItem,
|
||||
appendPageItem,
|
||||
removePageItem,
|
||||
};
|
Loading…
Reference in new issue