mirror of
https://github.com/strapi/strapi.git
synced 2025-11-03 19:36:20 +00:00
Merge pull request #10932 from strapi/ds-migration/layout-ml
Adding layouts for ML
This commit is contained in:
commit
42f79ad767
@ -39,11 +39,6 @@ const SettingsPage = lazy(() =>
|
||||
// import(
|
||||
// /* webpackChunkName: "content-type-builder" */ '@strapi/plugin-content-type-builder/admin/src/pages/App'
|
||||
// )
|
||||
// );
|
||||
// const Upload = lazy(() =>
|
||||
// import(/* webpackChunkName: "upload" */ '@strapi/plugin-upload/admin/src/pages/App')
|
||||
// );
|
||||
|
||||
// Simple hook easier for testing
|
||||
const useTrackUsage = () => {
|
||||
const { trackUsage } = useTracking();
|
||||
@ -88,9 +83,8 @@ const Admin = () => {
|
||||
<Route path="/content-manager" component={CM} />
|
||||
{/* TODO */}
|
||||
{/* <
|
||||
|
||||
<Route path="/plugins/content-type-builder" component={CTB} />
|
||||
<Route path="/plugins/upload" component={Upload} /> */}
|
||||
*/}
|
||||
{routes}
|
||||
<Route path="/settings/:settingId" component={SettingsPage} />
|
||||
<Route path="/settings" component={SettingsPage} exact />
|
||||
|
||||
@ -617,5 +617,7 @@
|
||||
"or": "OR",
|
||||
"request.error.model.unknown": "This model doesn't exist",
|
||||
"skipToContent": "Skip to content",
|
||||
"clearLabel": "Clear"
|
||||
"clearLabel": "Clear",
|
||||
"submit": "Submit",
|
||||
"anErrorOccurred": "Woops! Something went wrong. Please, try again."
|
||||
}
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
<!--- AnErrorOccurred.stories.mdx --->
|
||||
|
||||
import { Meta, ArgsTable, Canvas, Story } from '@storybook/addon-docs';
|
||||
import { Main, Row, Button } from '@strapi/parts';
|
||||
import AnErrorOccurred from './index';
|
||||
|
||||
<Meta title="components/AnErrorOccurred" />
|
||||
|
||||
# AnErrorOccurred
|
||||
|
||||
This component is used to display an empty state.
|
||||
|
||||
## Usage
|
||||
|
||||
<Canvas>
|
||||
<Story name="base">
|
||||
<Main>
|
||||
<AnErrorOccurred
|
||||
content={{
|
||||
id: 'app.components.EmptyStateLayout.content-document',
|
||||
defaultMessage: 'An error occured',
|
||||
}}
|
||||
action={<Button>Do something</Button>}
|
||||
/>
|
||||
</Main>
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
### Props
|
||||
|
||||
<ArgsTable of={AnErrorOccurred} />
|
||||
@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import AlertWarningIcon from '@strapi/icons/AlertWarningIcon';
|
||||
import { EmptyStateLayout } from '@strapi/parts/EmptyStateLayout';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
const AnErrorOccurred = ({ content, ...rest }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<EmptyStateLayout
|
||||
icon={<AlertWarningIcon width="10rem" />}
|
||||
content={formatMessage(
|
||||
{ id: content.id, defaultMessage: content.defaultMessage },
|
||||
content.values
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
AnErrorOccurred.defaultProps = {
|
||||
content: {
|
||||
id: 'anErrorOccurred',
|
||||
defaultMessage: 'Woops! Something went wrong. Please, try again.',
|
||||
values: {},
|
||||
},
|
||||
};
|
||||
|
||||
AnErrorOccurred.propTypes = {
|
||||
content: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
defaultMessage: PropTypes.string,
|
||||
values: PropTypes.object,
|
||||
}),
|
||||
};
|
||||
|
||||
export default AnErrorOccurred;
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { SearchIcon } from '@strapi/icons';
|
||||
@ -7,11 +7,29 @@ import { IconButton } from '@strapi/parts/IconButton';
|
||||
import useQueryParams from '../../hooks/useQueryParams';
|
||||
|
||||
const Search = ({ label }) => {
|
||||
const wrapperRef = useRef(null);
|
||||
const iconButtonRef = useRef(null);
|
||||
const isMountedRef = useRef(false);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [{ query }, setQuery] = useQueryParams();
|
||||
const [value, setValue] = useState(query?._q || '');
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const handleToggle = () => setIsOpen(prev => !prev);
|
||||
|
||||
useEffect(() => {
|
||||
if (isMountedRef.current) {
|
||||
if (isOpen) {
|
||||
wrapperRef.current.querySelector('input').focus();
|
||||
} else {
|
||||
iconButtonRef.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
isMountedRef.current = true;
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
if (value) {
|
||||
@ -25,26 +43,26 @@ const Search = ({ label }) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [value]);
|
||||
|
||||
const handleToggle = () => {
|
||||
setIsOpen(prev => !prev);
|
||||
};
|
||||
|
||||
if (isOpen) {
|
||||
return (
|
||||
<Searchbar
|
||||
name="search"
|
||||
onChange={({ target: { value } }) => setValue(value)}
|
||||
onBlur={() => setIsOpen(false)}
|
||||
value={value}
|
||||
clearLabel={formatMessage({ id: 'clearLabel', defaultMessage: 'Clear' })}
|
||||
onClear={() => setValue('')}
|
||||
>
|
||||
{label}
|
||||
</Searchbar>
|
||||
<div ref={wrapperRef}>
|
||||
<Searchbar
|
||||
name="search"
|
||||
onChange={({ target: { value } }) => setValue(value)}
|
||||
onBlur={() => setIsOpen(false)}
|
||||
value={value}
|
||||
clearLabel={formatMessage({ id: 'clearLabel', defaultMessage: 'Clear' })}
|
||||
onClear={() => setValue('')}
|
||||
>
|
||||
{label}
|
||||
</Searchbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <IconButton icon={<SearchIcon />} label="Search" onClick={handleToggle} />;
|
||||
return (
|
||||
<IconButton ref={iconButtonRef} icon={<SearchIcon />} label="Search" onClick={handleToggle} />
|
||||
);
|
||||
};
|
||||
|
||||
Search.propTypes = {
|
||||
|
||||
@ -186,6 +186,7 @@ export { default as EmptyStateLayout } from './components/EmptyStateLayout';
|
||||
export { default as NoContent } from './components/NoContent';
|
||||
export { default as NoMedia } from './components/NoMedia';
|
||||
export { default as NoPermissions } from './components/NoPermissions';
|
||||
export { default as AnErrorOccurred } from './components/AnErrorOccurred';
|
||||
export { default as EmptyBodyTable } from './components/EmptyBodyTable';
|
||||
export { default as GenericInput } from './components/GenericInput';
|
||||
export * from './components/InjectionZone';
|
||||
|
||||
52
packages/core/upload/admin/src/hooks/useAssets.js
Normal file
52
packages/core/upload/admin/src/hooks/useAssets.js
Normal file
@ -0,0 +1,52 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useNotifyAT } from '@strapi/parts/LiveRegions';
|
||||
import { useNotification, useQueryParams } from '@strapi/helper-plugin';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { axiosInstance, getRequestUrl } from '../utils';
|
||||
|
||||
export const useAssets = ({ skipWhen }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const toggleNotification = useNotification();
|
||||
const { notifyStatus } = useNotifyAT();
|
||||
const [{ rawQuery, query }, setQuery] = useQueryParams();
|
||||
const dataRequestURL = getRequestUrl('files');
|
||||
|
||||
const { data, error, isLoading } = useQuery(
|
||||
'assets',
|
||||
async () => {
|
||||
const { data } = await axiosInstance.get(`${dataRequestURL}${rawQuery}`);
|
||||
|
||||
return data;
|
||||
},
|
||||
{ enabled: !skipWhen }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!query) {
|
||||
setQuery({ sort: 'updatedAt:DESC', page: 1, pageSize: 10 });
|
||||
}
|
||||
}, [query, setQuery]);
|
||||
|
||||
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 };
|
||||
};
|
||||
@ -23,7 +23,20 @@ export default {
|
||||
register(app) {
|
||||
// TODO update doc and guides
|
||||
app.addComponents({ name: 'media-library', Component: InputModalStepper });
|
||||
app.addMenuLink({
|
||||
to: `/plugins/${pluginId}`,
|
||||
icon,
|
||||
intlLabel: {
|
||||
id: `${pluginId}.plugin.name`,
|
||||
defaultMessage: 'Media Library',
|
||||
},
|
||||
permissions: pluginPermissions.main,
|
||||
Component: async () => {
|
||||
const component = await import(/* webpackChunkName: "media-library-page" */ './pages/App');
|
||||
|
||||
return component;
|
||||
},
|
||||
});
|
||||
// TODO
|
||||
// app.addCorePluginMenuLink({
|
||||
// to: `/plugins/${pluginId}`,
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
// TODO: implement the view
|
||||
export const ListView = ({ assets }) => {
|
||||
return <div>Number of assets: {assets.length}</div>;
|
||||
};
|
||||
|
||||
ListView.propTypes = {
|
||||
assets: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
};
|
||||
@ -1,31 +1,133 @@
|
||||
import React from 'react';
|
||||
import { Switch, Route, Redirect } from 'react-router-dom';
|
||||
import { LoadingIndicatorPage, useRBAC } from '@strapi/helper-plugin';
|
||||
import pluginId from '../../pluginId';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { useIntl } from 'react-intl';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
LoadingIndicatorPage,
|
||||
useRBAC,
|
||||
useFocusWhenNavigate,
|
||||
NoPermissions,
|
||||
NoMedia,
|
||||
AnErrorOccurred,
|
||||
Search,
|
||||
} from '@strapi/helper-plugin';
|
||||
import { Layout, HeaderLayout, ContentLayout, ActionLayout } from '@strapi/parts/Layout';
|
||||
import { Main } from '@strapi/parts/Main';
|
||||
import { Button } from '@strapi/parts/Button';
|
||||
import AddIcon from '@strapi/icons/AddIcon';
|
||||
import { Box } from '@strapi/parts/Box';
|
||||
import { BaseCheckbox } from '@strapi/parts/BaseCheckbox';
|
||||
import { ListView } from './components/ListView';
|
||||
import { useAssets } from '../../hooks/useAssets';
|
||||
import { getTrad } from '../../utils';
|
||||
import pluginPermissions from '../../permissions';
|
||||
import { AppContext } from '../../contexts';
|
||||
|
||||
import HomePage from '../HomePage';
|
||||
const BoxWithHeight = styled(Box)`
|
||||
height: ${32 / 16}rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const App = () => {
|
||||
const state = useRBAC(pluginPermissions);
|
||||
const { formatMessage } = useIntl();
|
||||
const { data, isLoading, error } = useAssets({
|
||||
skipWhen: !state.allowedActions.canMain,
|
||||
});
|
||||
|
||||
// Show a loader while all permissions are being checked
|
||||
if (state.isLoading) {
|
||||
return <LoadingIndicatorPage />;
|
||||
useFocusWhenNavigate();
|
||||
|
||||
const canRead = state.allowedActions.canMain;
|
||||
const loading = state.isLoading || isLoading;
|
||||
|
||||
if (!loading && !canRead) {
|
||||
return <Redirect to="/" />;
|
||||
}
|
||||
|
||||
if (state.allowedActions.canMain) {
|
||||
return (
|
||||
<AppContext.Provider value={state}>
|
||||
<Switch>
|
||||
<Route path={`/plugins/${pluginId}`} component={HomePage} />
|
||||
</Switch>
|
||||
</AppContext.Provider>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Layout>
|
||||
<Main aria-busy={loading}>
|
||||
<HeaderLayout
|
||||
title={formatMessage({
|
||||
id: getTrad('plugin.name'),
|
||||
defaultMessage: 'Media Library',
|
||||
})}
|
||||
subtitle={formatMessage(
|
||||
{
|
||||
id: getTrad(
|
||||
data?.length > 0
|
||||
? 'header.content.assets-multiple'
|
||||
: 'header.content.assets.assets-single'
|
||||
),
|
||||
defaultMessage: '0 assets',
|
||||
},
|
||||
{ number: data?.length || 0 }
|
||||
)}
|
||||
primaryAction={
|
||||
<Button startIcon={<AddIcon />}>
|
||||
{formatMessage({
|
||||
id: getTrad('header.actions.upload-assets'),
|
||||
defaultMessage: 'Upload new assets',
|
||||
})}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
return <Redirect to="/" />;
|
||||
<ActionLayout
|
||||
startActions={
|
||||
<>
|
||||
<BoxWithHeight
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
background="neutral0"
|
||||
hasRadius
|
||||
borderColor="neutral200"
|
||||
>
|
||||
<BaseCheckbox
|
||||
aria-label={formatMessage({
|
||||
id: getTrad('bulk.select.label'),
|
||||
defaultMessage: 'Select all assets',
|
||||
})}
|
||||
/>
|
||||
</BoxWithHeight>
|
||||
<Button variant="tertiary">Filter</Button>
|
||||
</>
|
||||
}
|
||||
endActions={
|
||||
<Search
|
||||
label={formatMessage({
|
||||
id: getTrad('search.label'),
|
||||
defaultMessage: 'Search for an asset',
|
||||
})}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<ContentLayout>
|
||||
{loading && <LoadingIndicatorPage />}
|
||||
{error && <AnErrorOccurred />}
|
||||
{!canRead && <NoPermissions />}
|
||||
{canRead && data && data.length === 0 && (
|
||||
<NoMedia
|
||||
action={
|
||||
<Button variant="secondary" startIcon={<AddIcon />}>
|
||||
{formatMessage({
|
||||
id: getTrad('modal.header.browse'),
|
||||
defaultMessage: 'Upload assets',
|
||||
})}
|
||||
</Button>
|
||||
}
|
||||
content={formatMessage({
|
||||
id: getTrad('list.assets.empty'),
|
||||
defaultMessage: 'Upload your first assets...',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{canRead && data && data.length > 0 && <ListView assets={data} />}
|
||||
</ContentLayout>
|
||||
</Main>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
96
packages/core/upload/admin/src/pages/App/tests/App.test.js
Normal file
96
packages/core/upload/admin/src/pages/App/tests/App.test.js
Normal file
@ -0,0 +1,96 @@
|
||||
import React from 'react';
|
||||
import { ThemeProvider, lightTheme } from '@strapi/parts';
|
||||
import { QueryClientProvider, QueryClient } from 'react-query';
|
||||
import { render as renderTL, screen, waitFor } from '@testing-library/react';
|
||||
import { useRBAC } from '@strapi/helper-plugin';
|
||||
import { Provider } from 'react-redux';
|
||||
import { combineReducers, createStore } from 'redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import reducers from '../../../reducers';
|
||||
import en from '../../../translations/en.json';
|
||||
import server from './server';
|
||||
import MediaLibraryPage from '..';
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
useRBAC: jest.fn(),
|
||||
useNotification: jest.fn(() => jest.fn()),
|
||||
}));
|
||||
|
||||
jest.mock('../../../utils', () => ({
|
||||
...jest.requireActual('../../../utils'),
|
||||
getTrad: x => x,
|
||||
}));
|
||||
|
||||
jest.mock('react-intl', () => ({
|
||||
FormattedMessage: ({ id }) => id,
|
||||
useIntl: () => ({ formatMessage: jest.fn(({ id }) => en[id]) }),
|
||||
}));
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const store = createStore(combineReducers(reducers));
|
||||
|
||||
const renderML = () =>
|
||||
renderTL(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<MemoryRouter>
|
||||
<MediaLibraryPage />
|
||||
</MemoryRouter>
|
||||
</ThemeProvider>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
describe('Media library homepage', () => {
|
||||
beforeAll(() => server.listen());
|
||||
|
||||
beforeEach(() => {
|
||||
useRBAC.mockReturnValue({ isLoading: false, allowedActions: { canMain: true } });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterAll(() => server.close());
|
||||
|
||||
describe('loading state', () => {
|
||||
it('shows a loader when resolving the permissions', () => {
|
||||
useRBAC.mockReturnValue({ isLoading: true, allowedActions: { canMain: false } });
|
||||
|
||||
renderML();
|
||||
|
||||
expect(screen.getByRole('main').getAttribute('aria-busy')).toBe('true');
|
||||
expect(screen.getByText('Loading content.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows a loader when resolving the assets', () => {
|
||||
renderML();
|
||||
|
||||
expect(screen.getByRole('main').getAttribute('aria-busy')).toBe('true');
|
||||
expect(screen.getByText('Loading content.')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('empty state', () => {
|
||||
it('shows an empty state when there are no assets found', async () => {
|
||||
renderML();
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText('Upload your first assets...')).toBeInTheDocument()
|
||||
);
|
||||
|
||||
expect(screen.getByRole('main').getAttribute('aria-busy')).toBe('false');
|
||||
});
|
||||
});
|
||||
});
|
||||
10
packages/core/upload/admin/src/pages/App/tests/server.js
Normal file
10
packages/core/upload/admin/src/pages/App/tests/server.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { setupServer } from 'msw/node';
|
||||
import { rest } from 'msw';
|
||||
|
||||
const server = setupServer(
|
||||
rest.get('*/upload/files*', (req, res, ctx) => {
|
||||
return res(ctx.json([]));
|
||||
})
|
||||
);
|
||||
|
||||
export default server;
|
||||
@ -17,10 +17,11 @@ import { useAppContext, useSelectTimestamps } from '../../hooks';
|
||||
import Container from '../../components/Container';
|
||||
import HomePageContent from './HomePageContent';
|
||||
import HomePageModalStepper from '../../components/HomePageModalStepper';
|
||||
import { generateStringFromParams, getHeaderLabel } from './utils';
|
||||
import { getHeaderLabel } from './utils';
|
||||
import init from './init';
|
||||
import reducer, { initialState } from './reducer';
|
||||
|
||||
// TODO: remove this file when ML is migrated
|
||||
const HomePage = () => {
|
||||
const toggleNotification = useNotification();
|
||||
const { allowedActions } = useAppContext();
|
||||
@ -72,7 +73,7 @@ const HomePage = () => {
|
||||
|
||||
const fetchData = async () => {
|
||||
const dataRequestURL = getRequestUrl('files');
|
||||
const params = generateStringFromParams(query);
|
||||
const params = query;
|
||||
|
||||
const paramsToSend = params.includes('sort')
|
||||
? params
|
||||
@ -98,7 +99,7 @@ const HomePage = () => {
|
||||
};
|
||||
|
||||
const fetchDataCount = async () => {
|
||||
const params = generateStringFromParams(query, ['_limit', '_start']);
|
||||
const params = query;
|
||||
const requestURL = getRequestUrl('files/count');
|
||||
|
||||
try {
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import { isEmpty, toString } from 'lodash';
|
||||
import generateParamsFromQuery from './generateParamsFromQuery';
|
||||
|
||||
const generateStringFromParams = (query, paramsToFilter = []) => {
|
||||
let paramsString = '';
|
||||
const paramsObject = generateParamsFromQuery(query);
|
||||
|
||||
Object.keys(paramsObject)
|
||||
.filter(key => {
|
||||
return !paramsToFilter.includes(key) && !isEmpty(toString(paramsObject[key]));
|
||||
})
|
||||
.forEach(key => {
|
||||
const value = paramsObject[key];
|
||||
|
||||
if (key.includes('mime') && value === 'file') {
|
||||
const revertedKey = key.includes('_ncontains') ? 'mime_contains' : 'mime_ncontains';
|
||||
|
||||
paramsString += `&${revertedKey}=image&${revertedKey}=video`;
|
||||
} else {
|
||||
paramsString += `&${key}=${value}`;
|
||||
}
|
||||
});
|
||||
|
||||
return paramsString.substring(1);
|
||||
};
|
||||
|
||||
export default generateStringFromParams;
|
||||
@ -1,2 +1 @@
|
||||
export { default as generateStringFromParams } from './generateStringFromParams';
|
||||
export { default as getHeaderLabel } from './getHeaderLabel';
|
||||
|
||||
@ -1,63 +0,0 @@
|
||||
import generateStringFromParams from '../generateStringFromParams';
|
||||
|
||||
describe('MEDIA LIBRARY | pages | HomePage | utils', () => {
|
||||
describe('generateStringFromParams', () => {
|
||||
it('should return a string with query params if query is empty', () => {
|
||||
const search = '';
|
||||
const query = new URLSearchParams(search);
|
||||
|
||||
const expected = '_limit=10&_start=0';
|
||||
|
||||
expect(generateStringFromParams(query)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should return a string with query params if search is not empty', () => {
|
||||
const search = '_limit=20&_start=0&mime_contains=image';
|
||||
const query = new URLSearchParams(search);
|
||||
|
||||
const expected = '_limit=20&_start=0&mime_contains=image';
|
||||
|
||||
expect(generateStringFromParams(query)).toEqual(expected);
|
||||
});
|
||||
|
||||
describe('return a string with converted filters if value is file', () => {
|
||||
it('should return _ncontains instead of _contains', () => {
|
||||
const search = '?mime_ncontains=file';
|
||||
const query = new URLSearchParams(search);
|
||||
|
||||
const expected = '_limit=10&_start=0&mime_contains=image&mime_contains=video';
|
||||
|
||||
expect(generateStringFromParams(query)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should return _contains instead of _ncontains', () => {
|
||||
const search = '?_limit=20&_start=0&mime_contains=file';
|
||||
const query = new URLSearchParams(search);
|
||||
|
||||
const expected = '_limit=20&_start=0&mime_ncontains=image&mime_ncontains=video';
|
||||
|
||||
expect(generateStringFromParams(query)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('it should filter the defined params', () => {
|
||||
it('should return _ncontains instead of _contains', () => {
|
||||
const search = '?mime_ncontains=file&test=true';
|
||||
const query = new URLSearchParams(search);
|
||||
|
||||
const expected = '_limit=10&_start=0&mime_contains=image&mime_contains=video&test=true';
|
||||
|
||||
expect(generateStringFromParams(query, [])).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should not return the _limit param', () => {
|
||||
const search = '?mime_ncontains=file';
|
||||
const query = new URLSearchParams(search);
|
||||
|
||||
const expected = '_start=0&mime_contains=image&mime_contains=video';
|
||||
|
||||
expect(generateStringFromParams(query, ['_limit'])).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"button.next": "Next",
|
||||
"bulk.select.label": "Select all assets",
|
||||
"checkControl.crop-duplicate": "Duplicate & crop the asset",
|
||||
"checkControl.crop-original": "Crop the original asset",
|
||||
"control-card.add": "Add",
|
||||
@ -39,6 +40,8 @@
|
||||
"list.assets.selected.plural": "{number} assets selected",
|
||||
"list.assets.selected.singular": "{number} asset selected",
|
||||
"list.assets.type-not-allowed": "This type of file is not allowed.",
|
||||
"list.assets.empty": "Upload your first assets...",
|
||||
"list.asset.at.finished": "The assets have finished loading.",
|
||||
"modal.file-details.date": "Date",
|
||||
"modal.file-details.dimensions": "Dimensions",
|
||||
"modal.file-details.extension": "Extension",
|
||||
@ -62,7 +65,8 @@
|
||||
"plugin.description.long": "Media file management.",
|
||||
"plugin.description.short": "Media file management.",
|
||||
"plugin.name": "Media Library",
|
||||
"search.placeholder": "Search for an asset...",
|
||||
"search.label": "Search for an asset",
|
||||
"search.placeholder": "e.g: the first dog on the moon",
|
||||
"settings.form.autoOrientation.description": "Automatically rotate image according to EXIF orientation tag",
|
||||
"settings.form.autoOrientation.label": "Enable auto orientation",
|
||||
"settings.form.responsiveDimensions.description": "It automatically generates multiple formats (large, medium, small) of the uploaded asset",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user