kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Merge branch 'react-query' into 'develop'
Introduce React Query See merge request soapbox-pub/soapbox-fe!1666environments/review-develop-3zknud/deployments/731
commit
1d5428be2c
|
@ -1,58 +0,0 @@
|
||||||
import { __stub } from 'soapbox/api';
|
|
||||||
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
|
|
||||||
|
|
||||||
import { fetchCarouselAvatars } from '../carousels';
|
|
||||||
|
|
||||||
describe('fetchCarouselAvatars()', () => {
|
|
||||||
let store: ReturnType<typeof mockStore>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
store = mockStore(rootState);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a successful API request', () => {
|
|
||||||
let avatars: Record<string, any>[];
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
avatars = [
|
|
||||||
{ account_id: '1', acct: 'jl', account_avatar: 'https://example.com/some.jpg' },
|
|
||||||
];
|
|
||||||
|
|
||||||
__stub((mock) => {
|
|
||||||
mock.onGet('/api/v1/truth/carousels/avatars').reply(200, avatars);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fetch the users from the API', async() => {
|
|
||||||
const expectedActions = [
|
|
||||||
{ type: 'CAROUSEL_AVATAR_REQUEST' },
|
|
||||||
{ type: 'CAROUSEL_AVATAR_SUCCESS', payload: avatars },
|
|
||||||
];
|
|
||||||
|
|
||||||
await store.dispatch(fetchCarouselAvatars());
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with an unsuccessful API request', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
__stub((mock) => {
|
|
||||||
mock.onGet('/api/v1/truth/carousels/avatars').networkError();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should dispatch failed action', async() => {
|
|
||||||
const expectedActions = [
|
|
||||||
{ type: 'CAROUSEL_AVATAR_REQUEST' },
|
|
||||||
{ type: 'CAROUSEL_AVATAR_FAIL' },
|
|
||||||
];
|
|
||||||
|
|
||||||
await store.dispatch(fetchCarouselAvatars());
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,25 +0,0 @@
|
||||||
import { AxiosResponse } from 'axios';
|
|
||||||
|
|
||||||
import { AppDispatch, RootState } from 'soapbox/store';
|
|
||||||
|
|
||||||
import api from '../api';
|
|
||||||
|
|
||||||
const CAROUSEL_AVATAR_REQUEST = 'CAROUSEL_AVATAR_REQUEST';
|
|
||||||
const CAROUSEL_AVATAR_SUCCESS = 'CAROUSEL_AVATAR_SUCCESS';
|
|
||||||
const CAROUSEL_AVATAR_FAIL = 'CAROUSEL_AVATAR_FAIL';
|
|
||||||
|
|
||||||
const fetchCarouselAvatars = () => (dispatch: AppDispatch, getState: () => RootState) => {
|
|
||||||
dispatch({ type: CAROUSEL_AVATAR_REQUEST });
|
|
||||||
|
|
||||||
return api(getState)
|
|
||||||
.get('/api/v1/truth/carousels/avatars')
|
|
||||||
.then((response: AxiosResponse) => dispatch({ type: CAROUSEL_AVATAR_SUCCESS, payload: response.data }))
|
|
||||||
.catch(() => dispatch({ type: CAROUSEL_AVATAR_FAIL }));
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
CAROUSEL_AVATAR_REQUEST,
|
|
||||||
CAROUSEL_AVATAR_SUCCESS,
|
|
||||||
CAROUSEL_AVATAR_FAIL,
|
|
||||||
fetchCarouselAvatars,
|
|
||||||
};
|
|
|
@ -2,7 +2,8 @@ import userEvent from '@testing-library/user-event';
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
import { Map as ImmutableMap } from 'immutable';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { __stub } from '../../../api';
|
import { __stub } from 'soapbox/api';
|
||||||
|
|
||||||
import { render, screen, waitFor } from '../../../jest/test-helpers';
|
import { render, screen, waitFor } from '../../../jest/test-helpers';
|
||||||
import FeedCarousel from '../feed-carousel';
|
import FeedCarousel from '../feed-carousel';
|
||||||
|
|
||||||
|
@ -55,55 +56,59 @@ describe('<FeedCarousel />', () => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render the Carousel', () => {
|
describe('with avatars', () => {
|
||||||
store.carousels = {
|
beforeEach(() => {
|
||||||
avatars: [
|
__stub((mock) => {
|
||||||
{ account_id: '1', acct: 'a', account_avatar: 'https://example.com/some.jpg' },
|
mock.onGet('/api/v1/truth/carousels/avatars')
|
||||||
],
|
.reply(200, [
|
||||||
};
|
{ account_id: '1', acct: 'a', account_avatar: 'https://example.com/some.jpg' },
|
||||||
|
{ account_id: '2', acct: 'b', account_avatar: 'https://example.com/some.jpg' },
|
||||||
|
{ account_id: '3', acct: 'c', account_avatar: 'https://example.com/some.jpg' },
|
||||||
|
{ account_id: '4', acct: 'd', account_avatar: 'https://example.com/some.jpg' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
render(<FeedCarousel />, undefined, store);
|
it('should render the Carousel', async() => {
|
||||||
|
render(<FeedCarousel />, undefined, store);
|
||||||
|
|
||||||
expect(screen.queryAllByTestId('feed-carousel')).toHaveLength(1);
|
await waitFor(() => {
|
||||||
|
expect(screen.queryAllByTestId('feed-carousel')).toHaveLength(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with 0 avatars', () => {
|
describe('with 0 avatars', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.carousels = {
|
__stub((mock) => mock.onGet('/api/v1/truth/carousels/avatars').reply(200, []));
|
||||||
avatars: [],
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the error message', () => {
|
it('renders nothing', async() => {
|
||||||
render(<FeedCarousel />, undefined, store);
|
render(<FeedCarousel />, undefined, store);
|
||||||
|
|
||||||
expect(screen.queryAllByTestId('feed-carousel-error')).toHaveLength(0);
|
await waitFor(() => {
|
||||||
|
expect(screen.queryAllByTestId('feed-carousel')).toHaveLength(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with a failed request to the API', () => {
|
describe('with a failed request to the API', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.carousels = {
|
__stub((mock) => mock.onGet('/api/v1/truth/carousels/avatars').networkError());
|
||||||
avatars: [],
|
|
||||||
error: true,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the error message', () => {
|
it('renders the error message', async() => {
|
||||||
render(<FeedCarousel />, undefined, store);
|
render(<FeedCarousel />, undefined, store);
|
||||||
|
|
||||||
expect(screen.getByTestId('feed-carousel-error')).toBeInTheDocument();
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('feed-carousel-error')).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with multiple pages of avatars', () => {
|
describe('with multiple pages of avatars', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.carousels = {
|
__stub((mock) => {
|
||||||
error: false,
|
|
||||||
avatars: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
__stub(mock => {
|
|
||||||
mock.onGet('/api/v1/truth/carousels/avatars')
|
mock.onGet('/api/v1/truth/carousels/avatars')
|
||||||
.reply(200, [
|
.reply(200, [
|
||||||
{ account_id: '1', acct: 'a', account_avatar: 'https://example.com/some.jpg' },
|
{ account_id: '1', acct: 'a', account_avatar: 'https://example.com/some.jpg' },
|
||||||
|
|
|
@ -2,9 +2,9 @@ import classNames from 'classnames';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { fetchCarouselAvatars } from 'soapbox/actions/carousels';
|
|
||||||
import { replaceHomeTimeline } from 'soapbox/actions/timelines';
|
import { replaceHomeTimeline } from 'soapbox/actions/timelines';
|
||||||
import { useAppDispatch, useAppSelector, useDimensions, useFeatures } from 'soapbox/hooks';
|
import { useAppDispatch, useAppSelector, useDimensions, useFeatures } from 'soapbox/hooks';
|
||||||
|
import useCarouselAvatars from 'soapbox/queries/carousels';
|
||||||
|
|
||||||
import { Card, HStack, Icon, Stack, Text } from '../../components/ui';
|
import { Card, HStack, Icon, Stack, Text } from '../../components/ui';
|
||||||
import PlaceholderAvatar from '../placeholder/components/placeholder_avatar';
|
import PlaceholderAvatar from '../placeholder/components/placeholder_avatar';
|
||||||
|
@ -15,10 +15,10 @@ const CarouselItem = ({ avatar }: { avatar: any }) => {
|
||||||
const selectedAccountId = useAppSelector(state => state.timelines.get('home')?.feedAccountId);
|
const selectedAccountId = useAppSelector(state => state.timelines.get('home')?.feedAccountId);
|
||||||
const isSelected = avatar.account_id === selectedAccountId;
|
const isSelected = avatar.account_id === selectedAccountId;
|
||||||
|
|
||||||
const [isLoading, setLoading] = useState<boolean>(false);
|
const [isFetching, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (isLoading) {
|
if (isFetching) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ const CarouselItem = ({ avatar }: { avatar: any }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div aria-disabled={isLoading} onClick={handleClick} className='cursor-pointer' role='filter-feed-by-user'>
|
<div aria-disabled={isFetching} onClick={handleClick} className='cursor-pointer' role='filter-feed-by-user'>
|
||||||
<Stack className='w-16 h-auto' space={3}>
|
<Stack className='w-16 h-auto' space={3}>
|
||||||
<div className='block mx-auto relative w-14 h-14 rounded-full'>
|
<div className='block mx-auto relative w-14 h-14 rounded-full'>
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
|
@ -59,17 +59,15 @@ const CarouselItem = ({ avatar }: { avatar: any }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const FeedCarousel = () => {
|
const FeedCarousel = () => {
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
|
|
||||||
|
const { data: avatars, isFetching, isError } = useCarouselAvatars();
|
||||||
|
|
||||||
const [cardRef, setCardRef, { width }] = useDimensions();
|
const [cardRef, setCardRef, { width }] = useDimensions();
|
||||||
|
|
||||||
const [pageSize, setPageSize] = useState<number>(0);
|
const [pageSize, setPageSize] = useState<number>(0);
|
||||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||||
|
|
||||||
const avatars = useAppSelector((state) => state.carousels.avatars);
|
|
||||||
const isLoading = useAppSelector((state) => state.carousels.isLoading);
|
|
||||||
const hasError = useAppSelector((state) => state.carousels.error);
|
|
||||||
const numberOfPages = Math.ceil(avatars.length / pageSize);
|
const numberOfPages = Math.ceil(avatars.length / pageSize);
|
||||||
const widthPerAvatar = (cardRef?.scrollWidth || 0) / avatars.length;
|
const widthPerAvatar = (cardRef?.scrollWidth || 0) / avatars.length;
|
||||||
|
|
||||||
|
@ -85,17 +83,11 @@ const FeedCarousel = () => {
|
||||||
}
|
}
|
||||||
}, [width, widthPerAvatar]);
|
}, [width, widthPerAvatar]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (features.feedUserFiltering) {
|
|
||||||
dispatch(fetchCarouselAvatars());
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!features.feedUserFiltering) {
|
if (!features.feedUserFiltering) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasError) {
|
if (isError) {
|
||||||
return (
|
return (
|
||||||
<Card variant='rounded' size='lg' data-testid='feed-carousel-error'>
|
<Card variant='rounded' size='lg' data-testid='feed-carousel-error'>
|
||||||
<Text align='center'>
|
<Text align='center'>
|
||||||
|
@ -133,7 +125,7 @@ const FeedCarousel = () => {
|
||||||
style={{ transform: `translateX(-${(currentPage - 1) * 100}%)` }}
|
style={{ transform: `translateX(-${(currentPage - 1) * 100}%)` }}
|
||||||
ref={setCardRef}
|
ref={setCardRef}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isFetching ? (
|
||||||
new Array(pageSize).fill(0).map((_, idx) => (
|
new Array(pageSize).fill(0).map((_, idx) => (
|
||||||
<div className='w-16 text-center' key={idx}>
|
<div className='w-16 text-center' key={idx}>
|
||||||
<PlaceholderAvatar size={56} withText />
|
<PlaceholderAvatar size={56} withText />
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export { useAccount } from './useAccount';
|
export { useAccount } from './useAccount';
|
||||||
|
export { useApi } from './useApi';
|
||||||
export { useAppDispatch } from './useAppDispatch';
|
export { useAppDispatch } from './useAppDispatch';
|
||||||
export { useAppSelector } from './useAppSelector';
|
export { useAppSelector } from './useAppSelector';
|
||||||
export { useDimensions } from './useDimensions';
|
export { useDimensions } from './useDimensions';
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import api from 'soapbox/api';
|
||||||
|
|
||||||
|
import { useAppDispatch } from './useAppDispatch';
|
||||||
|
|
||||||
|
/** Use stateful Axios client with auth from Redux. */
|
||||||
|
export const useApi = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
return dispatch((_dispatch, getState) => {
|
||||||
|
return api(getState);
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,6 +1,7 @@
|
||||||
import { configureMockStore } from '@jedmao/redux-mock-store';
|
import { configureMockStore } from '@jedmao/redux-mock-store';
|
||||||
import { QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { render, RenderOptions } from '@testing-library/react';
|
import { render, RenderOptions } from '@testing-library/react';
|
||||||
|
import { renderHook, RenderHookOptions } from '@testing-library/react-hooks';
|
||||||
import { merge } from 'immutable';
|
import { merge } from 'immutable';
|
||||||
import React, { FC, ReactElement } from 'react';
|
import React, { FC, ReactElement } from 'react';
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
|
@ -10,8 +11,6 @@ import { Action, applyMiddleware, createStore } from 'redux';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
|
|
||||||
import { queryClient } from 'soapbox/queries/client';
|
|
||||||
|
|
||||||
import NotificationsContainer from '../features/ui/containers/notifications_container';
|
import NotificationsContainer from '../features/ui/containers/notifications_container';
|
||||||
import { default as rootReducer } from '../reducers';
|
import { default as rootReducer } from '../reducers';
|
||||||
|
|
||||||
|
@ -28,8 +27,22 @@ const applyActions = (state: any, actions: any, reducer: any) => {
|
||||||
return actions.reduce((state: any, action: any) => reducer(state, action), state);
|
return actions.reduce((state: any, action: any) => reducer(state, action), state);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createTestStore = (initialState: any) => createStore(rootReducer, initialState, applyMiddleware(thunk));
|
/** React Query client for tests. */
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
logger: {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
log: console.log,
|
||||||
|
warn: console.warn,
|
||||||
|
error: () => { },
|
||||||
|
},
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
retry: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const createTestStore = (initialState: any) => createStore(rootReducer, initialState, applyMiddleware(thunk));
|
||||||
const TestApp: FC<any> = ({ children, storeProps, routerProps = {} }) => {
|
const TestApp: FC<any> = ({ children, storeProps, routerProps = {} }) => {
|
||||||
let store: ReturnType<typeof createTestStore>;
|
let store: ReturnType<typeof createTestStore>;
|
||||||
let appState = rootState;
|
let appState = rootState;
|
||||||
|
@ -71,6 +84,18 @@ const customRender = (
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** Like renderHook, but with access to the Redux store. */
|
||||||
|
const customRenderHook = <T extends {}>(
|
||||||
|
callback: (props?: any) => any,
|
||||||
|
options?: Omit<RenderHookOptions<T>, 'wrapper'>,
|
||||||
|
store?: any,
|
||||||
|
) => {
|
||||||
|
return renderHook(callback, {
|
||||||
|
wrapper: ({ children }) => <TestApp children={children} storeProps={store} />,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const mockWindowProperty = (property: any, value: any) => {
|
const mockWindowProperty = (property: any, value: any) => {
|
||||||
const { [property]: originalProperty } = window;
|
const { [property]: originalProperty } = window;
|
||||||
delete window[property];
|
delete window[property];
|
||||||
|
@ -91,6 +116,7 @@ const mockWindowProperty = (property: any, value: any) => {
|
||||||
export * from '@testing-library/react';
|
export * from '@testing-library/react';
|
||||||
export {
|
export {
|
||||||
customRender as render,
|
customRender as render,
|
||||||
|
customRenderHook as renderHook,
|
||||||
mockStore,
|
mockStore,
|
||||||
applyActions,
|
applyActions,
|
||||||
rootState,
|
rootState,
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { __stub } from 'soapbox/api';
|
||||||
|
import { renderHook, waitFor } from 'soapbox/jest/test-helpers';
|
||||||
|
|
||||||
|
import useCarouselAvatars from '../carousels';
|
||||||
|
|
||||||
|
describe('useCarouselAvatars', () => {
|
||||||
|
describe('with a successful query', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
__stub((mock) => {
|
||||||
|
mock.onGet('/api/v1/truth/carousels/avatars')
|
||||||
|
.reply(200, [
|
||||||
|
{ account_id: '1', acct: 'a', account_avatar: 'https://example.com/some.jpg' },
|
||||||
|
{ account_id: '2', acct: 'b', account_avatar: 'https://example.com/some.jpg' },
|
||||||
|
{ account_id: '3', acct: 'c', account_avatar: 'https://example.com/some.jpg' },
|
||||||
|
{ account_id: '4', acct: 'd', account_avatar: 'https://example.com/some.jpg' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is successful', async() => {
|
||||||
|
const { result } = renderHook(() => useCarouselAvatars());
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isFetching).toBe(false));
|
||||||
|
|
||||||
|
expect(result.current.data?.length).toBe(4);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with an unsuccessful query', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
__stub((mock) => {
|
||||||
|
mock.onGet('/api/v1/truth/carousels/avatars').networkError();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is successful', async() => {
|
||||||
|
const { result } = renderHook(() => useCarouselAvatars());
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isFetching).toBe(false));
|
||||||
|
|
||||||
|
expect(result.current.error).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useApi } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
type Avatar = {
|
||||||
|
account_id: string
|
||||||
|
account_avatar: string
|
||||||
|
username: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useCarouselAvatars() {
|
||||||
|
const api = useApi();
|
||||||
|
|
||||||
|
const getCarouselAvatars = async() => {
|
||||||
|
const { data } = await api.get('/api/v1/truth/carousels/avatars');
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = useQuery<Avatar[]>(['carouselAvatars'], getCarouselAvatars, {
|
||||||
|
placeholderData: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const avatars = result.data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
data: avatars || [],
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,50 +0,0 @@
|
||||||
import { AnyAction } from 'redux';
|
|
||||||
|
|
||||||
import {
|
|
||||||
CAROUSEL_AVATAR_REQUEST,
|
|
||||||
CAROUSEL_AVATAR_SUCCESS,
|
|
||||||
CAROUSEL_AVATAR_FAIL,
|
|
||||||
} from 'soapbox/actions/carousels';
|
|
||||||
|
|
||||||
import reducer from '../carousels';
|
|
||||||
|
|
||||||
describe('carousels reducer', () => {
|
|
||||||
it('should return the initial state', () => {
|
|
||||||
expect(reducer(undefined, {} as AnyAction)).toEqual({
|
|
||||||
avatars: [],
|
|
||||||
error: false,
|
|
||||||
isLoading: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('CAROUSEL_AVATAR_REQUEST', () => {
|
|
||||||
it('sets "isLoading" to "true"', () => {
|
|
||||||
const initialState = { isLoading: false, avatars: [], error: false };
|
|
||||||
const action = { type: CAROUSEL_AVATAR_REQUEST };
|
|
||||||
expect(reducer(initialState, action).isLoading).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('CAROUSEL_AVATAR_SUCCESS', () => {
|
|
||||||
it('sets the next state', () => {
|
|
||||||
const initialState = { isLoading: true, avatars: [], error: false };
|
|
||||||
const action = { type: CAROUSEL_AVATAR_SUCCESS, payload: [45] };
|
|
||||||
const result = reducer(initialState, action);
|
|
||||||
|
|
||||||
expect(result.isLoading).toEqual(false);
|
|
||||||
expect(result.avatars).toEqual([45]);
|
|
||||||
expect(result.error).toEqual(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('CAROUSEL_AVATAR_FAIL', () => {
|
|
||||||
it('sets "isLoading" to "true"', () => {
|
|
||||||
const initialState = { isLoading: true, avatars: [], error: false };
|
|
||||||
const action = { type: CAROUSEL_AVATAR_FAIL };
|
|
||||||
const result = reducer(initialState, action);
|
|
||||||
|
|
||||||
expect(result.isLoading).toEqual(false);
|
|
||||||
expect(result.error).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,38 +0,0 @@
|
||||||
import { AnyAction } from 'redux';
|
|
||||||
|
|
||||||
import {
|
|
||||||
CAROUSEL_AVATAR_REQUEST,
|
|
||||||
CAROUSEL_AVATAR_SUCCESS,
|
|
||||||
CAROUSEL_AVATAR_FAIL,
|
|
||||||
} from '../actions/carousels';
|
|
||||||
|
|
||||||
type Avatar = {
|
|
||||||
account_id: string
|
|
||||||
account_avatar: string
|
|
||||||
username: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CarouselsState = {
|
|
||||||
avatars: Avatar[]
|
|
||||||
isLoading: boolean
|
|
||||||
error: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState: CarouselsState = {
|
|
||||||
avatars: [],
|
|
||||||
isLoading: false,
|
|
||||||
error: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function rules(state: CarouselsState = initialState, action: AnyAction): CarouselsState {
|
|
||||||
switch (action.type) {
|
|
||||||
case CAROUSEL_AVATAR_REQUEST:
|
|
||||||
return { ...state, isLoading: true };
|
|
||||||
case CAROUSEL_AVATAR_SUCCESS:
|
|
||||||
return { ...state, isLoading: false, avatars: action.payload };
|
|
||||||
case CAROUSEL_AVATAR_FAIL:
|
|
||||||
return { ...state, isLoading: false, error: true };
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,7 +15,6 @@ import aliases from './aliases';
|
||||||
import announcements from './announcements';
|
import announcements from './announcements';
|
||||||
import auth from './auth';
|
import auth from './auth';
|
||||||
import backups from './backups';
|
import backups from './backups';
|
||||||
import carousels from './carousels';
|
|
||||||
import chat_message_lists from './chat_message_lists';
|
import chat_message_lists from './chat_message_lists';
|
||||||
import chat_messages from './chat_messages';
|
import chat_messages from './chat_messages';
|
||||||
import chats from './chats';
|
import chats from './chats';
|
||||||
|
@ -124,7 +123,6 @@ const reducers = {
|
||||||
onboarding,
|
onboarding,
|
||||||
rules,
|
rules,
|
||||||
history,
|
history,
|
||||||
carousels,
|
|
||||||
announcements,
|
announcements,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue