useModalQueryParams: Add tests

This commit is contained in:
Gustav Hansen 2022-04-11 16:32:29 +02:00
parent 3933455e6c
commit 39d018d4a2
10 changed files with 252 additions and 138 deletions

View File

@ -80,6 +80,22 @@
"component": "blog.test-como",
"required": false,
"min": 2
},
"wat": {
"type": "component",
"repeatable": true,
"component": "basic.simple"
},
"wat2": {
"type": "dynamiczone",
"components": [
"basic.simple"
]
},
"bla": {
"type": "component",
"repeatable": false,
"component": "basic.simple"
}
}
}

View File

@ -13,6 +13,11 @@
},
"test": {
"type": "string"
},
"addresses": {
"type": "relation",
"relation": "oneToMany",
"target": "api::address.address"
}
}
}

View File

@ -32,6 +32,7 @@ const EndBlockActions = styled(StartBlockActions)`
export const BrowseStep = ({
allowedTypes,
assets,
folders,
multiple,
onChangeFilters,
onChangePage,
@ -97,6 +98,8 @@ export const BrowseStep = ({
</Box>
)}
{folders.length > 0 && <></>}
{assets.length > 0 ? (
<AssetList
allowedTypes={allowedTypes}
@ -120,6 +123,7 @@ export const BrowseStep = ({
</Box>
)}
</Stack>
{pagination.pageCount > 0 && (
<Flex justifyContent="space-between">
<PageSize pageSize={queryObject.pageSize} onChangePageSize={onChangePageSize} />
@ -143,7 +147,11 @@ BrowseStep.defaultProps = {
BrowseStep.propTypes = {
allowedTypes: PropTypes.arrayOf(PropTypes.string),
// TODO: add asset & folder shapes
assets: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
folders: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
multiple: PropTypes.bool,
onChangeFilters: PropTypes.func.isRequired,
onChangePage: PropTypes.func.isRequired,

View File

@ -10,13 +10,15 @@ import { useIntl } from 'react-intl';
import { Tabs, Tab, TabGroup, TabPanels, TabPanel } from '@strapi/design-system/Tabs';
import { Badge } from '@strapi/design-system/Badge';
import { Loader } from '@strapi/design-system/Loader';
import { Stack } from '@strapi/design-system/Stack';
import { NoPermissions, AnErrorOccurred, useSelectionState } from '@strapi/helper-plugin';
import getTrad from '../../utils/getTrad';
import { SelectedStep } from './SelectedStep';
import { BrowseStep } from './BrowseStep';
import { useMediaLibraryPermissions } from '../../hooks/useMediaLibraryPermissions';
import { useModalAssets } from '../../hooks/useModalAssets';
import useModalQueryParams from '../../hooks/useModalAssets/useModalQueryParams';
import { useAssets } from '../../hooks/useAssets';
import { useFolders } from '../../hooks/useFolders';
import useModalQueryParams from '../../hooks/useModalQueryParams';
import { AssetDefinition } from '../../constants';
import getAllowedFiles from '../../utils/getAllowedFiles';
import { DialogTitle } from './DialogTitle';
@ -29,6 +31,7 @@ export const AssetDialog = ({
allowedTypes,
onClose,
onAddAsset,
onAddFolder,
onValidate,
multiple,
initiallySelectedAssets,
@ -46,12 +49,28 @@ export const AssetDialog = ({
} = useMediaLibraryPermissions();
const [
{ rawQuery, queryObject },
{ onChangeFilters, onChangePage, onChangePageSize, onChangeSort, onChangeSearch },
{
onChangeFilters,
onChangePage,
onChangePageSize,
onChangeSort,
onChangeSearch,
onChangeFolder,
},
] = useModalQueryParams();
const { data, isLoading, error } = useModalAssets({ skipWhen: !canRead, rawQuery });
const {
data: { pagination, results: assets } = {},
isLoading: isLoadingAssets,
error: errorAssets,
} = useAssets({ skipWhen: !canRead, rawQuery });
const {
data: { results: folders } = {},
isLoading: isLoadingFolders,
error: errorFolders,
} = useFolders();
const [selectedAssets, { selectOne, selectAll, selectOnly, setSelections }] = useSelectionState(
'id',
['id'],
initiallySelectedAssets
);
@ -75,10 +94,10 @@ export const AssetDialog = ({
return multiple ? selectOne(asset) : selectOnly(asset);
};
const loading = isLoadingPermissions || isLoading;
const assets = data?.results;
const isLoading = isLoadingPermissions || isLoadingAssets || isLoadingFolders;
const hasError = errorAssets || errorFolders;
if (loading) {
if (isLoading) {
return (
<ModalLayout onClose={onClose} labelledBy="asset-dialog-title" aria-busy>
<DialogTitle />
@ -95,7 +114,7 @@ export const AssetDialog = ({
);
}
if (error) {
if (hasError) {
return (
<ModalLayout onClose={onClose} labelledBy="asset-dialog-title">
<DialogTitle />
@ -125,12 +144,21 @@ export const AssetDialog = ({
count={6}
action={
canCreate ? (
<Button variant="secondary" startIcon={<PlusIcon />} onClick={onAddAsset}>
{formatMessage({
id: getTrad('header.actions.add-assets'),
defaultMessage: 'Add new assets',
})}
</Button>
<Stack space={2} horizontal id="asset-dialog-title">
<Button variant="tertiary" onClick={onAddFolder}>
{formatMessage({
id: getTrad('header.actions.add-folder'),
defaultMessage: 'Add folder',
})}
</Button>
<Button variant="secondary" startIcon={<PlusIcon />} onClick={onAddAsset}>
{formatMessage({
id: getTrad('header.actions.add-assets'),
defaultMessage: 'Add new assets',
})}
</Button>
</Stack>
) : (
undefined
)
@ -175,7 +203,7 @@ export const AssetDialog = ({
};
return (
<ModalLayout onClose={onClose} labelledBy="asset-dialog-title" aria-busy={loading}>
<ModalLayout onClose={onClose} labelledBy="asset-dialog-title" aria-busy={isLoading}>
<DialogTitle />
<TabGroup
@ -218,14 +246,16 @@ export const AssetDialog = ({
<BrowseStep
allowedTypes={allowedTypes}
assets={assets}
folder={folders}
onSelectAsset={handleSelectAsset}
selectedAssets={selectedAssets}
multiple={multiple}
onSelectAllAsset={handleSelectAllAssets}
onEditAsset={canUpdate ? setAssetToEdit : undefined}
pagination={data?.pagination}
pagination={pagination}
queryObject={queryObject}
onChangeFilters={onChangeFilters}
onChangeFolder={onChangeFolder}
onChangePage={onChangePage}
onChangePageSize={onChangePageSize}
onChangeSort={onChangeSort}
@ -262,6 +292,7 @@ AssetDialog.propTypes = {
initiallySelectedAssets: PropTypes.arrayOf(AssetDefinition),
multiple: PropTypes.bool,
onAddAsset: PropTypes.func.isRequired,
onAddFolder: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
onValidate: PropTypes.func.isRequired,
trackedLocation: PropTypes.string,

View File

@ -3,27 +3,33 @@ import PropTypes from 'prop-types';
import { AssetDialog } from '../AssetDialog';
import { UploadAssetDialog } from '../UploadAssetDialog/UploadAssetDialog';
const Steps = {
SelectAsset: 'SelectAsset',
UploadAsset: 'UploadAsset',
const STEPS = {
AssetSelect: 'SelectAsset',
AssetUpload: 'UploadAsset',
FolderCreate: 'FolderCreate',
};
export const MediaLibraryDialog = ({ onClose, onSelectAssets, allowedTypes }) => {
const [step, setStep] = useState(Steps.SelectAsset);
const [step, setStep] = useState(STEPS.AssetSelect);
if (step === Steps.SelectAsset) {
if (step === STEPS.AssetSelect) {
return (
<AssetDialog
allowedTypes={allowedTypes}
onClose={onClose}
onValidate={onSelectAssets}
onAddAsset={() => setStep(Steps.UploadAsset)}
onAddAsset={() => setStep(STEPS.AssetUpload)}
onAddFolder={() => setStep(STEPS.FolderCreate)}
multiple
/>
);
}
return <UploadAssetDialog onClose={() => setStep(Steps.SelectAsset)} />;
if (step === STEPS.FolderCreate) {
return null;
}
return <UploadAssetDialog onClose={() => setStep(STEPS.AssetSelect)} />;
};
MediaLibraryDialog.defaultProps = {

View File

@ -0,0 +1,156 @@
import { renderHook, act } from '@testing-library/react-hooks';
import useModalQueryParams from '../useModalQueryParams';
const FIXTURE_QUERY = {
page: 1,
sort: 'updatedAt:DESC',
pageSize: 10,
filters: {
$and: [],
},
};
function setup(...args) {
return renderHook(() => useModalQueryParams(...args));
}
describe('useModalQueryParams', () => {
test('setup proper defaults', () => {
const {
result: {
current: [{ queryObject, rawQuery }, callbacks],
},
} = setup();
expect(queryObject).toStrictEqual(FIXTURE_QUERY);
expect(rawQuery).toBe('page=1&sort=updatedAt:DESC&pageSize=10');
expect(callbacks).toStrictEqual({
onChangeFilters: expect.any(Function),
onChangeFolder: expect.any(Function),
onChangePage: expect.any(Function),
onChangePageSize: expect.any(Function),
onChangeSort: expect.any(Function),
onChangeSearch: expect.any(Function),
});
});
test('onChangeFilters', () => {
const { result } = setup();
act(() => {
result.current[1].onChangeFilters({ some: 'thing' });
});
expect(result.current[0].queryObject).toStrictEqual({
...FIXTURE_QUERY,
filters: {
...FIXTURE_QUERY.filters,
$and: {
some: 'thing',
},
},
});
});
test('onChangeFolder', () => {
const { result } = setup();
act(() => {
result.current[1].onChangeFolder({ id: 1 });
});
expect(result.current[0].queryObject).toStrictEqual({
...FIXTURE_QUERY,
folder: {
id: 1,
},
});
});
test('onChangePage', () => {
const { result } = setup();
act(() => {
result.current[1].onChangePage({ id: 1 });
});
expect(result.current[0].queryObject).toStrictEqual({
...FIXTURE_QUERY,
page: {
id: 1,
},
});
});
test('onChangePageSize', () => {
const { result } = setup();
act(() => {
result.current[1].onChangePageSize(5);
});
expect(result.current[0].queryObject).toStrictEqual({
...FIXTURE_QUERY,
pageSize: 5,
});
});
test('onChangePageSize - converts string to numbers', () => {
const { result } = setup();
act(() => {
result.current[1].onChangePageSize('5');
});
expect(result.current[0].queryObject).toStrictEqual({
...FIXTURE_QUERY,
pageSize: 5,
});
});
test('onChangeSort', () => {
const { result } = setup();
act(() => {
result.current[1].onChangeSort('something:else');
});
expect(result.current[0].queryObject).toStrictEqual({
...FIXTURE_QUERY,
sort: 'something:else',
});
});
test('onChangeSearch', () => {
const { result } = setup();
act(() => {
result.current[1].onChangeSearch('something');
});
expect(result.current[0].queryObject).toStrictEqual({
...FIXTURE_QUERY,
_q: 'something',
});
});
test('onChangeSearch - empty string resets all values and removes _q and page', () => {
const { result } = setup();
act(() => {
result.current[1].onChangePage({ id: 1 });
});
act(() => {
result.current[1].onChangeSearch('something');
});
act(() => {
result.current[1].onChangeSearch('');
});
expect(result.current[0].queryObject).toStrictEqual(FIXTURE_QUERY);
});
});

View File

@ -1,19 +1,18 @@
import { useQuery } from 'react-query';
import { useNotifyAT } from '@strapi/design-system/LiveRegions';
import { useNotification, useQueryParams } from '@strapi/helper-plugin';
import { useNotification } from '@strapi/helper-plugin';
import { useIntl } from 'react-intl';
import { axiosInstance, getRequestUrl } from '../utils';
export const useAssets = ({ skipWhen }) => {
export const useAssets = ({ skipWhen, rawQuery }) => {
const { formatMessage } = useIntl();
const toggleNotification = useNotification();
const { notifyStatus } = useNotifyAT();
const [{ rawQuery }] = useQueryParams();
const dataRequestURL = getRequestUrl('files');
const getAssets = async () => {
try {
const { data } = await axiosInstance.get(`${dataRequestURL}${rawQuery}`);
const { data } = await axiosInstance.get(`${dataRequestURL}${rawQuery || ''}`);
notifyStatus(
formatMessage({

View File

@ -1,19 +1,18 @@
import { useQuery } from 'react-query';
import { useNotifyAT } from '@strapi/design-system/LiveRegions';
import { useNotification, useQueryParams } from '@strapi/helper-plugin';
import { useNotification } from '@strapi/helper-plugin';
import { useIntl } from 'react-intl';
import { axiosInstance, getRequestUrl } from '../utils';
export const useFolders = ({ enabled = true }) => {
export const useFolders = ({ enabled = true, rawQuery } = {}) => {
const { formatMessage } = useIntl();
const toggleNotification = useNotification();
const { notifyStatus } = useNotifyAT();
const [{ rawQuery }] = useQueryParams();
const dataRequestURL = getRequestUrl('folders');
const fetchFolders = async () => {
try {
const { data } = await axiosInstance.get(`${dataRequestURL}${rawQuery}`);
const { data } = await axiosInstance.get(`${dataRequestURL}${rawQuery || ''}`);
notifyStatus(
formatMessage({

View File

@ -1,47 +0,0 @@
import { useEffect } from 'react';
import { useQuery } from 'react-query';
import { useNotifyAT } from '@strapi/design-system/LiveRegions';
import { useNotification } from '@strapi/helper-plugin';
import { useIntl } from 'react-intl';
import { axiosInstance, getRequestUrl } from '../../utils';
export const useModalAssets = ({ skipWhen, rawQuery }) => {
const { formatMessage } = useIntl();
const toggleNotification = useNotification();
const { notifyStatus } = useNotifyAT();
const dataRequestURL = getRequestUrl('files');
const getAssets = async () => {
const { data } = await axiosInstance.get(`${dataRequestURL}?${rawQuery}`);
return data;
};
const { data, error, isLoading } = useQuery([`assets`, rawQuery], getAssets, {
enabled: !skipWhen,
staleTime: 0,
cacheTime: 0,
});
useEffect(() => {
if (data) {
notifyStatus(
formatMessage({
id: 'list.asset.at.finished',
defaultMessage: 'The assets have finished loading.',
})
);
}
}, [data, notifyStatus, formatMessage]);
useEffect(() => {
if (error) {
toggleNotification({
type: 'warning',
message: { id: 'notification.error' },
});
}
}, [error, toggleNotification]);
return { data, error, isLoading };
};

View File

@ -1,59 +0,0 @@
import { useState } from 'react';
import { stringify } from 'qs';
const useModalQueryParams = () => {
const [queryObject, setQueryObject] = useState({
page: 1,
sort: 'updatedAt:DESC',
pageSize: 10,
filters: {
$and: [],
},
});
const handleChangeFilters = nextFilters => {
setQueryObject(prev => ({ ...prev, page: 1, filters: { $and: nextFilters } }));
};
const handleChangePageSize = pageSize => {
setQueryObject(prev => ({ ...prev, pageSize: parseInt(pageSize, 10), page: 1 }));
};
const handeChangePage = page => {
setQueryObject(prev => ({ ...prev, page }));
};
const handleChangeSort = sort => {
setQueryObject(prev => ({ ...prev, sort }));
};
const handleChangeSearch = _q => {
if (_q) {
setQueryObject(prev => ({ ...prev, _q, page: 1 }));
} else {
const newState = { page: 1 };
Object.keys(queryObject).forEach(key => {
if (!['page', '_q'].includes(key)) {
newState[key] = queryObject[key];
}
});
setQueryObject(newState);
}
};
return [
{ queryObject, rawQuery: stringify(queryObject, { encode: false }) },
{
onChangeFilters: handleChangeFilters,
onChangePage: handeChangePage,
onChangePageSize: handleChangePageSize,
onChangeSort: handleChangeSort,
onChangeSearch: handleChangeSearch,
},
];
};
export default useModalQueryParams;