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,
|
||||
};
|
@ -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);
|
||||
});
|
||||
};
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in new issue