mirror of
https://github.com/strapi/strapi.git
synced 2025-08-11 02:07:51 +00:00
Merge pull request #16804 from strapi/chore/refactor-use-content-types-hook
Chore: Refactor useModels to use react-query rather than useReducer
This commit is contained in:
commit
e576dae3be
@ -1,5 +1,5 @@
|
|||||||
export { default as useConfigurations } from './useConfigurations';
|
export { default as useConfigurations } from './useConfigurations';
|
||||||
export { default as useModels } from './useModels';
|
export { useContentTypes } from './useContentTypes';
|
||||||
export { default as useFetchPermissionsLayout } from './useFetchPermissionsLayout';
|
export { default as useFetchPermissionsLayout } from './useFetchPermissionsLayout';
|
||||||
export { default as useFetchRole } from './useFetchRole';
|
export { default as useFetchRole } from './useFetchRole';
|
||||||
export { default as useMenu } from './useMenu';
|
export { default as useMenu } from './useMenu';
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
export * from './useContentTypes';
|
@ -0,0 +1,142 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { setupServer } from 'msw/node';
|
||||||
|
import { rest } from 'msw';
|
||||||
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||||
|
|
||||||
|
import { useContentTypes } from '../useContentTypes';
|
||||||
|
|
||||||
|
jest.mock('@strapi/helper-plugin', () => ({
|
||||||
|
...jest.requireActual('@strapi/helper-plugin'),
|
||||||
|
useNotification: jest.fn().mockReturnValue(jest.fn),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const server = setupServer(
|
||||||
|
rest.get('*/content-manager/content-types', (req, res, ctx) =>
|
||||||
|
res(
|
||||||
|
ctx.json({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
uid: 'admin::collectionType',
|
||||||
|
isDisplayed: true,
|
||||||
|
apiID: 'permission',
|
||||||
|
kind: 'collectionType',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
uid: 'admin::collectionTypeNotDispalyed',
|
||||||
|
isDisplayed: false,
|
||||||
|
apiID: 'permission',
|
||||||
|
kind: 'collectionType',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
uid: 'admin::singleType',
|
||||||
|
isDisplayed: true,
|
||||||
|
kind: 'singleType',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
uid: 'admin::singleTypeNotDispalyed',
|
||||||
|
isDisplayed: false,
|
||||||
|
kind: 'singleType',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
rest.get('*/content-manager/components', (req, res, ctx) =>
|
||||||
|
res(
|
||||||
|
ctx.json({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
uid: 'basic.relation',
|
||||||
|
isDisplayed: true,
|
||||||
|
apiID: 'relation',
|
||||||
|
category: 'basic',
|
||||||
|
info: {
|
||||||
|
displayName: 'Relation',
|
||||||
|
},
|
||||||
|
options: {},
|
||||||
|
attributes: {
|
||||||
|
id: {
|
||||||
|
type: 'integer',
|
||||||
|
},
|
||||||
|
categories: {
|
||||||
|
type: 'relation',
|
||||||
|
relation: 'oneToMany',
|
||||||
|
target: 'api::category.category',
|
||||||
|
targetModel: 'api::category.category',
|
||||||
|
relationType: 'oneToMany',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const setup = () =>
|
||||||
|
renderHook(() => useContentTypes(), {
|
||||||
|
wrapper({ children }) {
|
||||||
|
const client = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
retry: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QueryClientProvider client={client}>
|
||||||
|
<IntlProvider locale="en" messages={{}}>
|
||||||
|
{children}
|
||||||
|
</IntlProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('useContentTypes', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
server.listen();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fetches models and content-types', async () => {
|
||||||
|
const { result, waitFor } = setup();
|
||||||
|
|
||||||
|
expect(result.current.isLoading).toBe(true);
|
||||||
|
|
||||||
|
expect(result.current.components).toStrictEqual([]);
|
||||||
|
expect(result.current.singleTypes).toStrictEqual([]);
|
||||||
|
expect(result.current.collectionTypes).toStrictEqual([]);
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||||
|
|
||||||
|
expect(result.current.components).toStrictEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
uid: 'basic.relation',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.current.collectionTypes).toStrictEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
uid: 'admin::collectionType',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(result.current.singleTypes).toStrictEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
uid: 'admin::singleType',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,45 @@
|
|||||||
|
import { useAPIErrorHandler, useFetchClient, useNotification } from '@strapi/helper-plugin';
|
||||||
|
import { useQueries } from 'react-query';
|
||||||
|
|
||||||
|
export function useContentTypes() {
|
||||||
|
const { get } = useFetchClient();
|
||||||
|
const { formatAPIError } = useAPIErrorHandler();
|
||||||
|
const toggleNotification = useNotification();
|
||||||
|
const queries = useQueries(
|
||||||
|
['components', 'content-types'].map((type) => {
|
||||||
|
return {
|
||||||
|
queryKey: ['content-manager', type],
|
||||||
|
async queryFn() {
|
||||||
|
const {
|
||||||
|
data: { data },
|
||||||
|
} = await get(`/content-manager/${type}`);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onError(error) {
|
||||||
|
toggleNotification({
|
||||||
|
type: 'warning',
|
||||||
|
message: formatAPIError(error),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const [components, contentTypes] = queries;
|
||||||
|
const isLoading = components.isLoading || contentTypes.isLoading;
|
||||||
|
|
||||||
|
const collectionTypes = (contentTypes?.data ?? []).filter(
|
||||||
|
(contentType) => contentType.kind === 'collectionType' && contentType.isDisplayed
|
||||||
|
);
|
||||||
|
const singleTypes = (contentTypes?.data ?? []).filter(
|
||||||
|
(contentType) => contentType.kind !== 'collectionType' && contentType.isDisplayed
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isLoading,
|
||||||
|
components: components?.data ?? [],
|
||||||
|
collectionTypes,
|
||||||
|
singleTypes,
|
||||||
|
};
|
||||||
|
}
|
@ -1,58 +0,0 @@
|
|||||||
import { useReducer, useEffect, useCallback } from 'react';
|
|
||||||
import { useFetchClient, useNotification } from '@strapi/helper-plugin';
|
|
||||||
import reducer, { initialState } from './reducer';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: refactor this to not use the `useReducer` hook,
|
|
||||||
* it's not really necessary. Also use `useQuery`?
|
|
||||||
*/
|
|
||||||
const useModels = () => {
|
|
||||||
const toggleNotification = useNotification();
|
|
||||||
const [state, dispatch] = useReducer(reducer, initialState);
|
|
||||||
|
|
||||||
const { get } = useFetchClient();
|
|
||||||
|
|
||||||
const fetchModels = useCallback(async () => {
|
|
||||||
dispatch({
|
|
||||||
type: 'GET_MODELS',
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const [
|
|
||||||
{
|
|
||||||
data: { data: components },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: { data: contentTypes },
|
|
||||||
},
|
|
||||||
] = await Promise.all(
|
|
||||||
['components', 'content-types'].map((endPoint) => get(`/content-manager/${endPoint}`))
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: 'GET_MODELS_SUCCEDED',
|
|
||||||
contentTypes,
|
|
||||||
components,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
dispatch({
|
|
||||||
type: 'GET_MODELS_ERROR',
|
|
||||||
});
|
|
||||||
toggleNotification({
|
|
||||||
type: 'warning',
|
|
||||||
message: { id: 'notification.error' },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [toggleNotification, get]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchModels();
|
|
||||||
}, [fetchModels]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
getData: fetchModels,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useModels;
|
|
@ -1,45 +0,0 @@
|
|||||||
/* eslint-disable consistent-return */
|
|
||||||
import produce from 'immer';
|
|
||||||
|
|
||||||
export const initialState = {
|
|
||||||
collectionTypes: [],
|
|
||||||
components: [],
|
|
||||||
isLoading: true,
|
|
||||||
singleTypes: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const reducer = (state, action) =>
|
|
||||||
produce(state, (draftState) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'GET_MODELS': {
|
|
||||||
draftState.collectionTypes = initialState.collectionTypes;
|
|
||||||
draftState.singleTypes = initialState.singleTypes;
|
|
||||||
draftState.components = initialState.components;
|
|
||||||
draftState.isLoading = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'GET_MODELS_ERROR': {
|
|
||||||
draftState.collectionTypes = initialState.collectionTypes;
|
|
||||||
draftState.singleTypes = initialState.singleTypes;
|
|
||||||
draftState.components = initialState.components;
|
|
||||||
draftState.isLoading = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'GET_MODELS_SUCCEDED': {
|
|
||||||
const getContentTypeByKind = (kind) =>
|
|
||||||
action.contentTypes.filter(
|
|
||||||
(contentType) => contentType.isDisplayed && contentType.kind === kind
|
|
||||||
);
|
|
||||||
|
|
||||||
draftState.isLoading = false;
|
|
||||||
draftState.collectionTypes = getContentTypeByKind('collectionType');
|
|
||||||
draftState.singleTypes = getContentTypeByKind('singleType');
|
|
||||||
draftState.components = action.components;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return draftState;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default reducer;
|
|
@ -1,132 +0,0 @@
|
|||||||
import reducer from '../reducer';
|
|
||||||
|
|
||||||
describe('ADMIN | HOOKS | useModels | reducer', () => {
|
|
||||||
describe('DEFAULT_ACTION', () => {
|
|
||||||
it('should return the initialState', () => {
|
|
||||||
const state = {
|
|
||||||
test: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(reducer(state, {})).toEqual(state);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET_MODELS_ERROR', () => {
|
|
||||||
it('should set isLoading to false is an error occured', () => {
|
|
||||||
const action = {
|
|
||||||
type: 'GET_MODELS_ERROR',
|
|
||||||
};
|
|
||||||
const initialState = {
|
|
||||||
collectionTypes: [],
|
|
||||||
components: [],
|
|
||||||
singleTypes: [
|
|
||||||
{
|
|
||||||
uid: 'app.homepage',
|
|
||||||
isDisplayed: true,
|
|
||||||
kind: 'singleType',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
isLoading: true,
|
|
||||||
};
|
|
||||||
const expected = {
|
|
||||||
collectionTypes: [],
|
|
||||||
components: [],
|
|
||||||
singleTypes: [],
|
|
||||||
isLoading: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(reducer(initialState, action)).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET_MODELS', () => {
|
|
||||||
it('should set isLoading to true to start getting the data', () => {
|
|
||||||
const action = {
|
|
||||||
type: 'GET_MODELS',
|
|
||||||
};
|
|
||||||
const initialState = {
|
|
||||||
collectionTypes: [
|
|
||||||
{
|
|
||||||
uid: 'app.category',
|
|
||||||
isDisplayed: true,
|
|
||||||
kind: 'collectionType',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uid: 'app.category',
|
|
||||||
isDisplayed: true,
|
|
||||||
kind: 'collectionType',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
singleTypes: [
|
|
||||||
{
|
|
||||||
uid: 'app.homepage',
|
|
||||||
isDisplayed: true,
|
|
||||||
kind: 'singleType',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
components: [{}],
|
|
||||||
isLoading: false,
|
|
||||||
};
|
|
||||||
const expected = {
|
|
||||||
collectionTypes: [],
|
|
||||||
components: [],
|
|
||||||
singleTypes: [],
|
|
||||||
isLoading: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(reducer(initialState, action)).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET_MODELS_SUCCEDED', () => {
|
|
||||||
it('should return the state with the collectionTypes and singleTypes', () => {
|
|
||||||
const action = {
|
|
||||||
type: 'GET_MODELS_SUCCEDED',
|
|
||||||
contentTypes: [
|
|
||||||
{
|
|
||||||
uid: 'app.homepage',
|
|
||||||
isDisplayed: true,
|
|
||||||
kind: 'singleType',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uid: 'permissions.role',
|
|
||||||
isDisplayed: false,
|
|
||||||
kind: 'collectionType',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uid: 'app.category',
|
|
||||||
isDisplayed: true,
|
|
||||||
kind: 'collectionType',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
components: [],
|
|
||||||
};
|
|
||||||
const initialState = {
|
|
||||||
collectionTypes: [],
|
|
||||||
components: [],
|
|
||||||
singleTypes: [],
|
|
||||||
isLoading: true,
|
|
||||||
};
|
|
||||||
const expected = {
|
|
||||||
collectionTypes: [
|
|
||||||
{
|
|
||||||
uid: 'app.category',
|
|
||||||
isDisplayed: true,
|
|
||||||
kind: 'collectionType',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
singleTypes: [
|
|
||||||
{
|
|
||||||
uid: 'app.homepage',
|
|
||||||
isDisplayed: true,
|
|
||||||
kind: 'singleType',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
components: [],
|
|
||||||
isLoading: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(reducer(initialState, action)).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -12,7 +12,7 @@ import { LoadingIndicatorPage, useGuidedTour } from '@strapi/helper-plugin';
|
|||||||
import { Layout, Main, Box, Grid, GridItem } from '@strapi/design-system';
|
import { Layout, Main, Box, Grid, GridItem } from '@strapi/design-system';
|
||||||
import useLicenseLimitNotification from 'ee_else_ce/hooks/useLicenseLimitNotification';
|
import useLicenseLimitNotification from 'ee_else_ce/hooks/useLicenseLimitNotification';
|
||||||
import cornerOrnamentPath from './assets/corner-ornament.svg';
|
import cornerOrnamentPath from './assets/corner-ornament.svg';
|
||||||
import { useModels } from '../../hooks';
|
import { useContentTypes } from '../../hooks/useContentTypes';
|
||||||
import isGuidedTourCompleted from '../../components/GuidedTour/utils/isGuidedTourCompleted';
|
import isGuidedTourCompleted from '../../components/GuidedTour/utils/isGuidedTourCompleted';
|
||||||
import GuidedTourHomepage from '../../components/GuidedTour/Homepage';
|
import GuidedTourHomepage from '../../components/GuidedTour/Homepage';
|
||||||
import SocialLinks from './SocialLinks';
|
import SocialLinks from './SocialLinks';
|
||||||
@ -31,7 +31,7 @@ const LogoContainer = styled(Box)`
|
|||||||
|
|
||||||
const HomePage = () => {
|
const HomePage = () => {
|
||||||
// Temporary until we develop the menu API
|
// Temporary until we develop the menu API
|
||||||
const { collectionTypes, singleTypes, isLoading: isLoadingForModels } = useModels();
|
const { collectionTypes, singleTypes, isLoading: isLoadingForModels } = useContentTypes();
|
||||||
const { guidedTourState, isGuidedTourVisible, isSkipped } = useGuidedTour();
|
const { guidedTourState, isGuidedTourVisible, isSkipped } = useGuidedTour();
|
||||||
useLicenseLimitNotification();
|
useLicenseLimitNotification();
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import { IntlProvider } from 'react-intl';
|
|||||||
import { useAppInfo } from '@strapi/helper-plugin';
|
import { useAppInfo } from '@strapi/helper-plugin';
|
||||||
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
||||||
import HomePage from '../index';
|
import HomePage from '../index';
|
||||||
import { useModels } from '../../../hooks';
|
import { useContentTypes } from '../../../hooks/useContentTypes';
|
||||||
|
|
||||||
jest.mock('@strapi/helper-plugin', () => ({
|
jest.mock('@strapi/helper-plugin', () => ({
|
||||||
...jest.requireActual('@strapi/helper-plugin'),
|
...jest.requireActual('@strapi/helper-plugin'),
|
||||||
@ -30,9 +30,7 @@ jest.mock('@strapi/helper-plugin', () => ({
|
|||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('../../../hooks', () => ({
|
jest.mock('../../../hooks/useContentTypes');
|
||||||
useModels: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('ee_else_ce/hooks/useLicenseLimitNotification', () => ({
|
jest.mock('ee_else_ce/hooks/useLicenseLimitNotification', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
@ -52,11 +50,11 @@ const App = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
describe('Homepage', () => {
|
describe('Homepage', () => {
|
||||||
useModels.mockImplementation(() => ({
|
useContentTypes.mockReturnValue({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
collectionTypes: [],
|
collectionTypes: [],
|
||||||
singleTypes: [],
|
singleTypes: [],
|
||||||
}));
|
});
|
||||||
|
|
||||||
test('should render all homepage links', () => {
|
test('should render all homepage links', () => {
|
||||||
const { getByRole } = render(App);
|
const { getByRole } = render(App);
|
||||||
@ -113,11 +111,11 @@ describe('Homepage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should display particular text and action when there are collectionTypes and singletypes', () => {
|
it('should display particular text and action when there are collectionTypes and singletypes', () => {
|
||||||
useModels.mockImplementation(() => ({
|
useContentTypes.mockReturnValue({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
collectionTypes: [{ uuid: 102 }],
|
collectionTypes: [{ uuid: 102 }],
|
||||||
singleTypes: [{ isDisplayed: true }],
|
singleTypes: [{ isDisplayed: true }],
|
||||||
}));
|
});
|
||||||
|
|
||||||
const { getByText, getByRole } = render(App);
|
const { getByText, getByRole } = render(App);
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
import { Main } from '@strapi/design-system';
|
import { Main } from '@strapi/design-system';
|
||||||
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
||||||
import { useHistory, useRouteMatch } from 'react-router-dom';
|
import { useHistory, useRouteMatch } from 'react-router-dom';
|
||||||
import { useModels } from '../../../../../hooks';
|
import { useContentTypes } from '../../../../../hooks/useContentTypes';
|
||||||
import WebhookForm from './components/WebhookForm';
|
import WebhookForm from './components/WebhookForm';
|
||||||
import cleanData from './utils/formatData';
|
import cleanData from './utils/formatData';
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ const EditView = () => {
|
|||||||
const { lockApp, unlockApp } = useOverlayBlocker();
|
const { lockApp, unlockApp } = useOverlayBlocker();
|
||||||
const toggleNotification = useNotification();
|
const toggleNotification = useNotification();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { isLoading: isLoadingForModels, collectionTypes } = useModels();
|
const { isLoading: isLoadingForModels, collectionTypes } = useContentTypes();
|
||||||
const { put, get, post } = useFetchClient();
|
const { put, get, post } = useFetchClient();
|
||||||
|
|
||||||
const isCreating = id === 'create';
|
const isCreating = id === 'create';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user