mirror of
https://github.com/strapi/strapi.git
synced 2025-11-30 00:51:06 +00:00
Merge branch 'main' into fix/issue-9308-File_upload_related_fields_returning_null
This commit is contained in:
commit
14ba46950b
@ -116,7 +116,7 @@ filter out the relation from the array of relations. This is handled inside the
|
|||||||
|
|
||||||
:::note
|
:::note
|
||||||
Connecting relations adds the item to the end of the list, whilst loading more relations prepends to
|
Connecting relations adds the item to the end of the list, whilst loading more relations prepends to
|
||||||
the beginning of the list. This is the expected behaviour.
|
the beginning of the list. This is the expected behaviour, to keep the order of the list in the UI in sync with the API response.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
The `RelationInput` component takes the field in `modifiedData` as its source of truth. You could therefore consider this to
|
The `RelationInput` component takes the field in `modifiedData` as its source of truth. You could therefore consider this to
|
||||||
@ -126,7 +126,7 @@ data for the api.
|
|||||||
|
|
||||||
### Cleaning data to be posted to the API
|
### Cleaning data to be posted to the API
|
||||||
|
|
||||||
The API to update the enttiy expects relations to be categorised into two groups, a `connect` array and `disconnect` array.
|
The API to update the entity expects relations to be categorised into two groups, a `connect` array and `disconnect` array.
|
||||||
You could do this as the user interacts with the input but we found this to be confusing and then involved us managing three
|
You could do this as the user interacts with the input but we found this to be confusing and then involved us managing three
|
||||||
different arrays which makes the code more complex. Instead, because the browser doesn't really care about whats new and removed
|
different arrays which makes the code more complex. Instead, because the browser doesn't really care about whats new and removed
|
||||||
and we have a copy of the slice of data we're mutating from the server we can run a small diff algorithm to determine which
|
and we have a copy of the slice of data we're mutating from the server we can run a small diff algorithm to determine which
|
||||||
@ -142,3 +142,85 @@ relations have been connected and which have been disconnected. Returning an obj
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Frontend component architecture
|
## Frontend component architecture
|
||||||
|
|
||||||
|
The input field for relation fields consist of two components:
|
||||||
|
|
||||||
|
### `RelationInputDataManager`
|
||||||
|
|
||||||
|
This container component handles data fetching and data normalization for the `RelationInput` component. This has been extracted from
|
||||||
|
the `RelationInput` so that Strapi is able to move the underlying component into the design-system if the community would need it
|
||||||
|
(most other input components can be consumed from there).
|
||||||
|
|
||||||
|
### `RelationInput`
|
||||||
|
|
||||||
|
This component is the presentational counterpart to the `RelationInputDataManager` component. It renders an input field based on the data passed from the data manager.
|
||||||
|
|
||||||
|
Under the hood it is using `react-window` to render a list of relations in a virtualized view. Some fields need to render thousands of relations, which
|
||||||
|
would otherwise have a negative impact on the overall performance of the content-manager.
|
||||||
|
|
||||||
|
## useRelation() hook
|
||||||
|
|
||||||
|
This hook takes care of data-fetching and normalizes results relations aswell as search-results.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { relations: RelationResults, search: RelationResults, searchFor } = useRelation(reactQueryCacheKey: string, options: Options);
|
||||||
|
```
|
||||||
|
|
||||||
|
### `Options`
|
||||||
|
|
||||||
|
`option`s is a mandatory configuration and should implement the following shape:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Options = {
|
||||||
|
name: string; // name of the relation field
|
||||||
|
relation: RelationConfiguration;
|
||||||
|
search: SearchConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
type RelationConfiguration = {
|
||||||
|
endpoint: string; // URL from where existing relations should be fetched
|
||||||
|
enabled: boolean; // defines whether relations should be fetched once the hook is called
|
||||||
|
pageParams: object; // additional query params which will be appended to `endpoint`
|
||||||
|
onLoad: (results: RelationResult[]) => void; // callback that will be fired after relations have been fetched (paginated)
|
||||||
|
normalizeArguments = {
|
||||||
|
mainFieldName: string; // name of the target model main field, determining which field to display (fallback: id)
|
||||||
|
shouldAddLink: boolean; // if the user is allowed to read the target model, the returned relations should include a link to the target
|
||||||
|
targetModel: object; // target content-type model
|
||||||
|
};
|
||||||
|
pageGoal: number; // the current page-count of the already loaded relations used to keep the redux store and query cache in sync.
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchConfiguration = {
|
||||||
|
endpoint: string; // URL from where new relations should be fetched
|
||||||
|
pageParams: object; // additional query params which will be appended to `endpoint`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Return values
|
||||||
|
|
||||||
|
`relations` and `search` both return a consistent relation format:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type RelationResults = RelationResult[];
|
||||||
|
|
||||||
|
type RelationResult = {
|
||||||
|
id: number;
|
||||||
|
href?: string; // based on `shouldAddLink` and the `targetModel`
|
||||||
|
publicationState: 'draft' | 'published';
|
||||||
|
mainField: string; // will fallback to "id" if not set
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `relations`
|
||||||
|
|
||||||
|
`relations` refers to a [inifinite-query return type](https://tanstack.com/query/v4/docs/react/guides/infinite-queries) from react-query. It exposes paginated relational data
|
||||||
|
aswell as methods to check if there are more pages or fetch more paginated results. Relations for a given field are fetched as soon as the hook is called.
|
||||||
|
|
||||||
|
#### `search`
|
||||||
|
|
||||||
|
`search` refers to a [inifinite-query return type](https://tanstack.com/query/v4/docs/react/guides/infinite-queries) from react-query. It exposes paginated search results
|
||||||
|
for a relational field. Search results are only fetched after `searchFor()` has been called.
|
||||||
|
|
||||||
|
#### `searchFor(string)`
|
||||||
|
|
||||||
|
`searchFor` is a method which can be used to search for entities which haven't been connected with the source entity yet. The method accepts a search-term: `searchFor("term")`.
|
||||||
|
|||||||
@ -14,7 +14,7 @@ The following example shows a basic way to use the `useFetchClient` hook to make
|
|||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
import {useState} from "react"
|
import {useState} from "react"
|
||||||
import useFetchClient from '@strapi/admin/admin/src/hooks/useFetchClient';
|
import { useFetchClient } from '@strapi/helper-plugin';
|
||||||
|
|
||||||
const Component = () => {
|
const Component = () => {
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState([]);
|
||||||
@ -57,7 +57,7 @@ The following information is the internal additions we've added to the axios ins
|
|||||||
|
|
||||||
### Base URL
|
### Base URL
|
||||||
|
|
||||||
The default URL will be the one defined in the environment variable: `STRAPI_ADMIN_BACKEND_URL`.
|
The default URL is the one defined in the _getFetchClient_ utility
|
||||||
|
|
||||||
### Interceptors
|
### Interceptors
|
||||||
|
|
||||||
@ -68,8 +68,6 @@ The request interceptor adds the following parameters to the header:
|
|||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
Authorization: `Bearer <AUTH_TOKEN>`,
|
Authorization: `Bearer <AUTH_TOKEN>`,
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { Link } from '@strapi/design-system/v2/Link';
|
|||||||
|
|
||||||
const Notification = ({ dispatch, notification }) => {
|
const Notification = ({ dispatch, notification }) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { message, link, type, id, onClose, timeout, blockTransition } = notification;
|
const { message, link, type, id, onClose, timeout, blockTransition, title } = notification;
|
||||||
|
|
||||||
const formattedMessage = (msg) =>
|
const formattedMessage = (msg) =>
|
||||||
typeof msg === 'string' ? msg : formatMessage(msg, msg.values);
|
typeof msg === 'string' ? msg : formatMessage(msg, msg.values);
|
||||||
@ -37,6 +37,7 @@ const Notification = ({ dispatch, notification }) => {
|
|||||||
let variant;
|
let variant;
|
||||||
let alertTitle;
|
let alertTitle;
|
||||||
|
|
||||||
|
// TODO break out this logic into separate file
|
||||||
if (type === 'info') {
|
if (type === 'info') {
|
||||||
variant = 'default';
|
variant = 'default';
|
||||||
alertTitle = formatMessage({
|
alertTitle = formatMessage({
|
||||||
@ -44,17 +45,29 @@ const Notification = ({ dispatch, notification }) => {
|
|||||||
defaultMessage: 'Information:',
|
defaultMessage: 'Information:',
|
||||||
});
|
});
|
||||||
} else if (type === 'warning') {
|
} else if (type === 'warning') {
|
||||||
|
// type should be renamed to danger in the future, but it might introduce changes if done now
|
||||||
|
variant = 'danger';
|
||||||
|
alertTitle = formatMessage({
|
||||||
|
id: 'notification.warning.title',
|
||||||
|
defaultMessage: 'Warning:',
|
||||||
|
});
|
||||||
|
} else if (type === 'softWarning') {
|
||||||
|
// type should be renamed to just warning in the future
|
||||||
|
variant = 'warning';
|
||||||
alertTitle = formatMessage({
|
alertTitle = formatMessage({
|
||||||
id: 'notification.warning.title',
|
id: 'notification.warning.title',
|
||||||
defaultMessage: 'Warning:',
|
defaultMessage: 'Warning:',
|
||||||
});
|
});
|
||||||
variant = 'danger';
|
|
||||||
} else {
|
} else {
|
||||||
|
variant = 'success';
|
||||||
alertTitle = formatMessage({
|
alertTitle = formatMessage({
|
||||||
id: 'notification.success.title',
|
id: 'notification.success.title',
|
||||||
defaultMessage: 'Success:',
|
defaultMessage: 'Success:',
|
||||||
});
|
});
|
||||||
variant = 'success';
|
}
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
alertTitle = typeof title === 'string' ? title : formatMessage(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -125,6 +138,14 @@ Notification.propTypes = {
|
|||||||
onClose: PropTypes.func,
|
onClose: PropTypes.func,
|
||||||
timeout: PropTypes.number,
|
timeout: PropTypes.number,
|
||||||
blockTransition: PropTypes.bool,
|
blockTransition: PropTypes.bool,
|
||||||
|
title: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
defaultMessage: PropTypes.string,
|
||||||
|
values: PropTypes.object,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,7 @@ const notificationReducer = (state = initialState, action) =>
|
|||||||
timeout: get(action, ['config', 'timeout'], 2500),
|
timeout: get(action, ['config', 'timeout'], 2500),
|
||||||
blockTransition: get(action, ['config', 'blockTransition'], false),
|
blockTransition: get(action, ['config', 'blockTransition'], false),
|
||||||
onClose: get(action, ['config', 'onClose'], null),
|
onClose: get(action, ['config', 'onClose'], null),
|
||||||
|
title: get(action, ['config', 'title'], null),
|
||||||
});
|
});
|
||||||
draftState.notifId = state.notifId + 1;
|
draftState.notifId = state.notifId + 1;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -36,6 +36,7 @@ describe('ADMIN | COMPONENTS | NOTIFICATIONS | reducer', () => {
|
|||||||
timeout: 2500,
|
timeout: 2500,
|
||||||
blockTransition: false,
|
blockTransition: false,
|
||||||
onClose: null,
|
onClose: null,
|
||||||
|
title: null,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
notifId: 1,
|
notifId: 1,
|
||||||
|
|||||||
@ -10,4 +10,3 @@ export { default as usePermissionsDataManager } from './usePermissionsDataManage
|
|||||||
export { default as useReleaseNotification } from './useReleaseNotification';
|
export { default as useReleaseNotification } from './useReleaseNotification';
|
||||||
export { default as useThemeToggle } from './useThemeToggle';
|
export { default as useThemeToggle } from './useThemeToggle';
|
||||||
export { default as useRegenerate } from './useRegenerate';
|
export { default as useRegenerate } from './useRegenerate';
|
||||||
export { default as useFetchClient } from './useFetchClient';
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {
|
|||||||
TrackingProvider,
|
TrackingProvider,
|
||||||
prefixFileUrlWithBackendUrl,
|
prefixFileUrlWithBackendUrl,
|
||||||
useAppInfos,
|
useAppInfos,
|
||||||
|
useFetchClient,
|
||||||
} from '@strapi/helper-plugin';
|
} from '@strapi/helper-plugin';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { SkipToContent } from '@strapi/design-system/Main';
|
import { SkipToContent } from '@strapi/design-system/Main';
|
||||||
@ -25,7 +26,7 @@ import NotFoundPage from '../NotFoundPage';
|
|||||||
import UseCasePage from '../UseCasePage';
|
import UseCasePage from '../UseCasePage';
|
||||||
import { getUID } from './utils';
|
import { getUID } from './utils';
|
||||||
import routes from './utils/routes';
|
import routes from './utils/routes';
|
||||||
import { useConfigurations, useFetchClient } from '../../hooks';
|
import { useConfigurations } from '../../hooks';
|
||||||
|
|
||||||
const AuthenticatedApp = lazy(() =>
|
const AuthenticatedApp = lazy(() =>
|
||||||
import(/* webpackChunkName: "Admin-authenticatedApp" */ '../../components/AuthenticatedApp')
|
import(/* webpackChunkName: "Admin-authenticatedApp" */ '../../components/AuthenticatedApp')
|
||||||
|
|||||||
@ -3,3 +3,7 @@
|
|||||||
## Description
|
## Description
|
||||||
|
|
||||||
Helper to develop Strapi plugins.
|
Helper to develop Strapi plugins.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Please read our [Contributing Guide](../../../CONTRIBUTING.md) before submitting a Pull Request to the project.
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* useFetchClient
|
||||||
|
*
|
||||||
|
*/
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { getFetchClient } from '../../utils/getFetchClient';
|
import getFetchClient from '../../utils/getFetchClient';
|
||||||
|
|
||||||
const useFetchClient = () => {
|
const useFetchClient = () => {
|
||||||
const controller = useRef(null);
|
const controller = useRef(null);
|
||||||
@ -7,6 +12,7 @@ const useFetchClient = () => {
|
|||||||
if (controller.current === null) {
|
if (controller.current === null) {
|
||||||
controller.current = new AbortController();
|
controller.current = new AbortController();
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
controller.current.abort();
|
controller.current.abort();
|
||||||
@ -20,7 +20,7 @@ const HomePage = () => {
|
|||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
toggleNotification({
|
toggleNotification({
|
||||||
// required
|
// required
|
||||||
type: 'info|success|warning',
|
type: 'info|success|warning|softWarning',
|
||||||
// required
|
// required
|
||||||
message: { id: 'notification.version.update.message', defaultMessage: 'A new version is available' },
|
message: { id: 'notification.version.update.message', defaultMessage: 'A new version is available' },
|
||||||
// optional
|
// optional
|
||||||
@ -35,6 +35,8 @@ const HomePage = () => {
|
|||||||
blockTransition: true,
|
blockTransition: true,
|
||||||
// optional
|
// optional
|
||||||
onClose: () => localStorage.setItem('STRAPI_UPDATE_NOTIF', true),
|
onClose: () => localStorage.setItem('STRAPI_UPDATE_NOTIF', true),
|
||||||
|
// optional
|
||||||
|
title: { id: 'notification.default.title, defaultMessage: 'Warning: '}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export { default as useRBAC } from './hooks/useRBAC';
|
|||||||
export { default as usePersistentState } from './hooks/usePersistentState';
|
export { default as usePersistentState } from './hooks/usePersistentState';
|
||||||
export { default as useFocusWhenNavigate } from './hooks/useFocusWhenNavigate';
|
export { default as useFocusWhenNavigate } from './hooks/useFocusWhenNavigate';
|
||||||
export { default as useLockScroll } from './hooks/useLockScroll';
|
export { default as useLockScroll } from './hooks/useLockScroll';
|
||||||
|
export { default as useFetchClient } from './hooks/useFetchClient';
|
||||||
|
|
||||||
// Providers
|
// Providers
|
||||||
export { default as GuidedTourProvider } from './providers/GuidedTourProvider';
|
export { default as GuidedTourProvider } from './providers/GuidedTourProvider';
|
||||||
@ -100,3 +101,4 @@ export { default as wrapAxiosInstance } from './utils/wrapAxiosInstance';
|
|||||||
export { default as request } from './utils/request';
|
export { default as request } from './utils/request';
|
||||||
export { default as getAPIInnerErrors } from './utils/getAPIInnerErrors';
|
export { default as getAPIInnerErrors } from './utils/getAPIInnerErrors';
|
||||||
export { default as getYupInnerErrors } from './utils/getYupInnerErrors';
|
export { default as getYupInnerErrors } from './utils/getYupInnerErrors';
|
||||||
|
export { default as getFetchClient } from './utils/getFetchClient';
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { auth } from '@strapi/helper-plugin';
|
import auth from '../auth';
|
||||||
|
|
||||||
export const reqInterceptor = async (config) => {
|
export const reqInterceptor = async (config) => {
|
||||||
config.headers = {
|
config.headers = {
|
||||||
Authorization: `Bearer ${auth.getToken()}`,
|
Authorization: `Bearer ${auth.getToken()}`,
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
@ -33,13 +31,16 @@ export const addInterceptors = (instance) => {
|
|||||||
instance.interceptors.response.use(resInterceptor, resErrorInterceptor);
|
instance.interceptors.response.use(resInterceptor, resErrorInterceptor);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchClient = ({ baseURL }) => {
|
export const fetchClient = () => {
|
||||||
const instance = axios.create({
|
const instance = axios.create({
|
||||||
baseURL,
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
addInterceptors(instance);
|
addInterceptors(instance);
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default fetchClient({ baseURL: process.env.STRAPI_ADMIN_BACKEND_URL });
|
export default fetchClient();
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { auth } from '@strapi/helper-plugin';
|
import auth from '../../auth';
|
||||||
import {
|
import {
|
||||||
reqInterceptor,
|
reqInterceptor,
|
||||||
reqErrorInterceptor,
|
reqErrorInterceptor,
|
||||||
@ -7,13 +7,13 @@ import {
|
|||||||
resErrorInterceptor,
|
resErrorInterceptor,
|
||||||
fetchClient,
|
fetchClient,
|
||||||
addInterceptors,
|
addInterceptors,
|
||||||
} from '../fetchClient';
|
} from '../index';
|
||||||
|
|
||||||
const token = 'coolToken';
|
const token = 'coolToken';
|
||||||
auth.getToken = jest.fn().mockReturnValue(token);
|
auth.getToken = jest.fn().mockReturnValue(token);
|
||||||
auth.clearAppStorage = jest.fn().mockReturnValue(token);
|
auth.clearAppStorage = jest.fn().mockReturnValue(token);
|
||||||
|
|
||||||
describe('ADMIN | utils | fetchClient', () => {
|
describe('HELPER-PLUGIN | utils | fetchClient', () => {
|
||||||
describe('Test the interceptors', () => {
|
describe('Test the interceptors', () => {
|
||||||
it('API request should add authorization token to header', async () => {
|
it('API request should add authorization token to header', async () => {
|
||||||
const apiInstance = fetchClient({
|
const apiInstance = fetchClient({
|
||||||
@ -21,7 +21,6 @@ describe('ADMIN | utils | fetchClient', () => {
|
|||||||
});
|
});
|
||||||
const result = await apiInstance.interceptors.request.handlers[0].fulfilled({ headers: {} });
|
const result = await apiInstance.interceptors.request.handlers[0].fulfilled({ headers: {} });
|
||||||
expect(result.headers.Authorization).toContain(`Bearer ${token}`);
|
expect(result.headers.Authorization).toContain(`Bearer ${token}`);
|
||||||
expect(result.headers.Accept).toBe('application/json');
|
|
||||||
expect(apiInstance.interceptors.response.handlers[0].fulfilled('foo')).toBe('foo');
|
expect(apiInstance.interceptors.response.handlers[0].fulfilled('foo')).toBe('foo');
|
||||||
});
|
});
|
||||||
describe('Test the addInterceptor function', () => {
|
describe('Test the addInterceptor function', () => {
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import instance from './fetchClient';
|
import instance from '../fetchClient';
|
||||||
|
|
||||||
export const getFetchClient = (defaultOptions = {}) => {
|
const getFetchClient = (defaultOptions = {}) => {
|
||||||
|
instance.defaults.baseURL = window.strapi.backendURL;
|
||||||
return {
|
return {
|
||||||
get: (url, config) => instance.get(url, { ...defaultOptions, ...config }),
|
get: (url, config) => instance.get(url, { ...defaultOptions, ...config }),
|
||||||
put: (url, data, config) => instance.put(url, data, { ...defaultOptions, ...config }),
|
put: (url, data, config) => instance.put(url, data, { ...defaultOptions, ...config }),
|
||||||
@ -8,3 +9,5 @@ export const getFetchClient = (defaultOptions = {}) => {
|
|||||||
del: (url, config) => instance.delete(url, { ...defaultOptions, ...config }),
|
del: (url, config) => instance.delete(url, { ...defaultOptions, ...config }),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default getFetchClient;
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import { auth } from '@strapi/helper-plugin';
|
import auth from '../../auth';
|
||||||
import { getFetchClient } from '../getFetchClient';
|
import getFetchClient from '../index';
|
||||||
|
|
||||||
const token = 'coolToken';
|
const token = 'coolToken';
|
||||||
auth.getToken = jest.fn().mockReturnValue(token);
|
auth.getToken = jest.fn().mockReturnValue(token);
|
||||||
|
|
||||||
describe('ADMIN | utils | getFetchClient', () => {
|
describe('HELPER-PLUGIN | utils | getFetchClient', () => {
|
||||||
it('should return the 4 HTTP methods to call GET, POST, PUT and DELETE apis', () => {
|
it('should return the 4 HTTP methods to call GET, POST, PUT and DELETE apis', () => {
|
||||||
const response = getFetchClient();
|
const response = getFetchClient();
|
||||||
expect(response).toHaveProperty('get');
|
expect(response).toHaveProperty('get');
|
||||||
@ -19,7 +19,6 @@ describe('ADMIN | utils | getFetchClient', () => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
const { headers } = err.config;
|
const { headers } = err.config;
|
||||||
expect(headers.Authorization).toContain(`Bearer ${token}`);
|
expect(headers.Authorization).toContain(`Bearer ${token}`);
|
||||||
expect(headers.Accept).toBe('application/json');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -5,7 +5,7 @@ function wrapAxiosInstance(instance) {
|
|||||||
(methodName) => {
|
(methodName) => {
|
||||||
wrapper[methodName] = (...args) => {
|
wrapper[methodName] = (...args) => {
|
||||||
console.log(
|
console.log(
|
||||||
'Deprecation warning: Usage of "axiosInstance" utility is deprecated and will be removed in the next major release. Instead, use the useFetchClient() hook, which is exported from the admin: { useFetchClient } from "@strapi/helper-plugin"'
|
'Deprecation warning: Usage of "axiosInstance" utility is deprecated and will be removed in the next major release. Instead, use the useFetchClient() hook, which is exported from the helper-plugin: { useFetchClient } from "@strapi/helper-plugin"'
|
||||||
);
|
);
|
||||||
|
|
||||||
return instance[methodName](...args);
|
return instance[methodName](...args);
|
||||||
|
|||||||
@ -8,35 +8,18 @@ import { Typography } from '@strapi/design-system/Typography';
|
|||||||
import { PreviewCell } from './PreviewCell';
|
import { PreviewCell } from './PreviewCell';
|
||||||
import { formatBytes } from '../../utils';
|
import { formatBytes } from '../../utils';
|
||||||
|
|
||||||
export const CellContent = ({
|
export const CellContent = ({ cellType, contentType, content, name }) => {
|
||||||
alternativeText,
|
|
||||||
content,
|
|
||||||
cellType,
|
|
||||||
elementType,
|
|
||||||
mime,
|
|
||||||
fileExtension,
|
|
||||||
thumbnailURL,
|
|
||||||
url,
|
|
||||||
}) => {
|
|
||||||
const { formatDate, formatMessage } = useIntl();
|
const { formatDate, formatMessage } = useIntl();
|
||||||
|
|
||||||
switch (cellType) {
|
switch (cellType) {
|
||||||
case 'image':
|
case 'image':
|
||||||
return (
|
return <PreviewCell type={contentType} content={content} />;
|
||||||
<PreviewCell
|
|
||||||
alternativeText={alternativeText}
|
|
||||||
fileExtension={fileExtension}
|
|
||||||
mime={mime}
|
|
||||||
type={elementType}
|
|
||||||
thumbnailURL={thumbnailURL}
|
|
||||||
url={url}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case 'date':
|
case 'date':
|
||||||
return <Typography>{formatDate(parseISO(content), { dateStyle: 'full' })}</Typography>;
|
return <Typography>{formatDate(parseISO(content[name]), { dateStyle: 'full' })}</Typography>;
|
||||||
|
|
||||||
case 'size':
|
case 'size':
|
||||||
if (elementType === 'folder')
|
if (contentType === 'folder')
|
||||||
return (
|
return (
|
||||||
<Typography
|
<Typography
|
||||||
aria-label={formatMessage({
|
aria-label={formatMessage({
|
||||||
@ -48,10 +31,10 @@ export const CellContent = ({
|
|||||||
</Typography>
|
</Typography>
|
||||||
);
|
);
|
||||||
|
|
||||||
return <Typography>{formatBytes(content)}</Typography>;
|
return <Typography>{formatBytes(content[name])}</Typography>;
|
||||||
|
|
||||||
case 'ext':
|
case 'ext':
|
||||||
if (elementType === 'folder')
|
if (contentType === 'folder')
|
||||||
return (
|
return (
|
||||||
<Typography
|
<Typography
|
||||||
aria-label={formatMessage({
|
aria-label={formatMessage({
|
||||||
@ -63,10 +46,10 @@ export const CellContent = ({
|
|||||||
</Typography>
|
</Typography>
|
||||||
);
|
);
|
||||||
|
|
||||||
return <Typography>{getFileExtension(content).toUpperCase()}</Typography>;
|
return <Typography>{getFileExtension(content[name]).toUpperCase()}</Typography>;
|
||||||
|
|
||||||
case 'text':
|
case 'text':
|
||||||
return <Typography>{content}</Typography>;
|
return <Typography>{content[name]}</Typography>;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
@ -82,22 +65,19 @@ export const CellContent = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
CellContent.defaultProps = {
|
|
||||||
alternativeText: null,
|
|
||||||
content: '',
|
|
||||||
fileExtension: '',
|
|
||||||
mime: '',
|
|
||||||
thumbnailURL: null,
|
|
||||||
url: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
CellContent.propTypes = {
|
CellContent.propTypes = {
|
||||||
alternativeText: PropTypes.string,
|
|
||||||
content: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
||||||
fileExtension: PropTypes.string,
|
|
||||||
mime: PropTypes.string,
|
|
||||||
thumbnailURL: PropTypes.string,
|
|
||||||
cellType: PropTypes.string.isRequired,
|
cellType: PropTypes.string.isRequired,
|
||||||
elementType: PropTypes.string.isRequired,
|
contentType: PropTypes.string.isRequired,
|
||||||
url: PropTypes.string,
|
content: PropTypes.shape({
|
||||||
|
alternativeText: PropTypes.string,
|
||||||
|
ext: PropTypes.string,
|
||||||
|
formats: PropTypes.shape({
|
||||||
|
thumbnail: PropTypes.shape({
|
||||||
|
url: PropTypes.string,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
mime: PropTypes.string,
|
||||||
|
url: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,69 +1,80 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { prefixFileUrlWithBackendUrl, pxToRem } from '@strapi/helper-plugin';
|
import { getFileExtension, prefixFileUrlWithBackendUrl, pxToRem } from '@strapi/helper-plugin';
|
||||||
import { Avatar } from '@strapi/design-system/Avatar';
|
import { Avatar, Initials } from '@strapi/design-system/Avatar';
|
||||||
import { Flex } from '@strapi/design-system/Flex';
|
import { Box } from '@strapi/design-system/Box';
|
||||||
import { Typography } from '@strapi/design-system/Typography';
|
|
||||||
import { Icon } from '@strapi/design-system/Icon';
|
import { Icon } from '@strapi/design-system/Icon';
|
||||||
import Folder from '@strapi/icons/Folder';
|
import Folder from '@strapi/icons/Folder';
|
||||||
|
|
||||||
const GenericAssetWrapper = styled(Flex)`
|
import { AssetType } from '../../constants';
|
||||||
span {
|
import { createAssetUrl } from '../../utils';
|
||||||
/* The smallest fontSize in the DS is not small enough in this case */
|
import { VideoPreview } from '../AssetCard/VideoPreview';
|
||||||
font-size: ${pxToRem(10)};
|
|
||||||
|
const VideoPreviewWrapper = styled(Box)`
|
||||||
|
figure {
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas,
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const PreviewCell = ({ alternativeText, fileExtension, mime, thumbnailURL, type, url }) => {
|
export const PreviewCell = ({ type, content }) => {
|
||||||
if (type === 'folder') {
|
if (type === 'folder') {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Initials background="secondary100" textColor="secondary600">
|
||||||
background="secondary100"
|
<Icon color="secondary500" width={pxToRem(16)} height={pxToRem(16)} as={Folder} />
|
||||||
height={pxToRem(26)}
|
</Initials>
|
||||||
justifyContent="center"
|
|
||||||
width={pxToRem(26)}
|
|
||||||
borderRadius="50%"
|
|
||||||
>
|
|
||||||
<Icon color="secondary500" as={Folder} />
|
|
||||||
</Flex>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mime.includes('image')) {
|
const { alternativeText, ext, formats, mime, name, url } = content;
|
||||||
const mediaURL = prefixFileUrlWithBackendUrl(thumbnailURL) ?? prefixFileUrlWithBackendUrl(url);
|
|
||||||
|
if (mime.includes(AssetType.Image)) {
|
||||||
|
const mediaURL =
|
||||||
|
prefixFileUrlWithBackendUrl(formats?.thumbnail?.url) ?? prefixFileUrlWithBackendUrl(url);
|
||||||
|
|
||||||
return <Avatar src={mediaURL} alt={alternativeText} preview />;
|
return <Avatar src={mediaURL} alt={alternativeText} preview />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mime.includes(AssetType.Video)) {
|
||||||
|
return (
|
||||||
|
<VideoPreviewWrapper>
|
||||||
|
<VideoPreview
|
||||||
|
url={createAssetUrl(content, true)}
|
||||||
|
mime={mime}
|
||||||
|
alt={alternativeText ?? name}
|
||||||
|
/>
|
||||||
|
</VideoPreviewWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericAssetWrapper
|
<Initials background="secondary100" textColor="secondary600">
|
||||||
background="secondary100"
|
{getFileExtension(ext)}
|
||||||
height={pxToRem(26)}
|
</Initials>
|
||||||
justifyContent="center"
|
|
||||||
width={pxToRem(26)}
|
|
||||||
borderRadius="50%"
|
|
||||||
>
|
|
||||||
<Typography variant="sigma" textColor="secondary600">
|
|
||||||
{fileExtension}
|
|
||||||
</Typography>
|
|
||||||
</GenericAssetWrapper>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
PreviewCell.defaultProps = {
|
|
||||||
alternativeText: null,
|
|
||||||
fileExtension: '',
|
|
||||||
mime: '',
|
|
||||||
thumbnailURL: null,
|
|
||||||
url: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
PreviewCell.propTypes = {
|
PreviewCell.propTypes = {
|
||||||
alternativeText: PropTypes.string,
|
content: PropTypes.shape({
|
||||||
fileExtension: PropTypes.string,
|
alternativeText: PropTypes.string,
|
||||||
mime: PropTypes.string,
|
ext: PropTypes.string,
|
||||||
thumbnailURL: PropTypes.string,
|
formats: PropTypes.shape({
|
||||||
|
thumbnail: PropTypes.shape({
|
||||||
|
url: PropTypes.string,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
mime: PropTypes.string,
|
||||||
|
name: PropTypes.string,
|
||||||
|
url: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
url: PropTypes.string,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { getFileExtension, onRowClick, stopPropagation } from '@strapi/helper-plugin';
|
import { onRowClick, stopPropagation } from '@strapi/helper-plugin';
|
||||||
import { BaseCheckbox } from '@strapi/design-system/BaseCheckbox';
|
import { BaseCheckbox } from '@strapi/design-system/BaseCheckbox';
|
||||||
import { Flex } from '@strapi/design-system/Flex';
|
import { Flex } from '@strapi/design-system/Flex';
|
||||||
import { IconButton } from '@strapi/design-system/IconButton';
|
import { IconButton } from '@strapi/design-system/IconButton';
|
||||||
@ -35,18 +35,7 @@ export const TableRows = ({
|
|||||||
return (
|
return (
|
||||||
<Tbody>
|
<Tbody>
|
||||||
{rows.map((element) => {
|
{rows.map((element) => {
|
||||||
const {
|
const { id, isSelectable, name, folderURL, type: contentType } = element;
|
||||||
alternativeText,
|
|
||||||
id,
|
|
||||||
isSelectable,
|
|
||||||
name,
|
|
||||||
ext,
|
|
||||||
url,
|
|
||||||
mime,
|
|
||||||
folderURL,
|
|
||||||
formats,
|
|
||||||
type: elementType,
|
|
||||||
} = element;
|
|
||||||
|
|
||||||
const isSelected = !!selected.find((currentRow) => currentRow.id === id);
|
const isSelected = !!selected.find((currentRow) => currentRow.id === id);
|
||||||
|
|
||||||
@ -54,16 +43,16 @@ export const TableRows = ({
|
|||||||
<Tr
|
<Tr
|
||||||
key={id}
|
key={id}
|
||||||
{...onRowClick({
|
{...onRowClick({
|
||||||
fn: () => handleRowClickFn(element, elementType, id),
|
fn: () => handleRowClickFn(element, contentType, id),
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Td {...stopPropagation}>
|
<Td {...stopPropagation}>
|
||||||
<BaseCheckbox
|
<BaseCheckbox
|
||||||
aria-label={formatMessage(
|
aria-label={formatMessage(
|
||||||
{
|
{
|
||||||
id: elementType === 'asset' ? 'list-assets-select' : 'list.folder.select',
|
id: contentType === 'asset' ? 'list-assets-select' : 'list.folder.select',
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
elementType === 'asset' ? 'Select {name} asset' : 'Select {name} folder',
|
contentType === 'asset' ? 'Select {name} asset' : 'Select {name} folder',
|
||||||
},
|
},
|
||||||
{ name }
|
{ name }
|
||||||
)}
|
)}
|
||||||
@ -76,14 +65,10 @@ export const TableRows = ({
|
|||||||
return (
|
return (
|
||||||
<Td key={name}>
|
<Td key={name}>
|
||||||
<CellContent
|
<CellContent
|
||||||
alternativeText={alternativeText}
|
content={element}
|
||||||
content={element[name]}
|
|
||||||
fileExtension={getFileExtension(ext)}
|
|
||||||
mime={mime}
|
|
||||||
cellType={cellType}
|
cellType={cellType}
|
||||||
elementType={elementType}
|
contentType={contentType}
|
||||||
thumbnailURL={formats?.thumbnail?.url}
|
name={name}
|
||||||
url={url}
|
|
||||||
/>
|
/>
|
||||||
</Td>
|
</Td>
|
||||||
);
|
);
|
||||||
@ -91,7 +76,7 @@ export const TableRows = ({
|
|||||||
|
|
||||||
<Td {...stopPropagation}>
|
<Td {...stopPropagation}>
|
||||||
<Flex justifyContent="flex-end">
|
<Flex justifyContent="flex-end">
|
||||||
{elementType === 'folder' && (
|
{contentType === 'folder' && (
|
||||||
<IconButton
|
<IconButton
|
||||||
forwardedAs={folderURL ? Link : undefined}
|
forwardedAs={folderURL ? Link : undefined}
|
||||||
label={formatMessage({
|
label={formatMessage({
|
||||||
@ -111,7 +96,7 @@ export const TableRows = ({
|
|||||||
defaultMessage: 'Edit',
|
defaultMessage: 'Edit',
|
||||||
})}
|
})}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
elementType === 'asset' ? onEditAsset(element) : onEditFolder(element)
|
contentType === 'asset' ? onEditAsset(element) : onEditFolder(element)
|
||||||
}
|
}
|
||||||
noBorder
|
noBorder
|
||||||
>
|
>
|
||||||
|
|||||||
@ -6,14 +6,20 @@ import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
|||||||
import { CellContent } from '../CellContent';
|
import { CellContent } from '../CellContent';
|
||||||
|
|
||||||
const PROPS_FIXTURE = {
|
const PROPS_FIXTURE = {
|
||||||
alternativeText: 'alternative alt',
|
|
||||||
cellType: 'image',
|
cellType: 'image',
|
||||||
elementType: 'asset',
|
contentType: 'asset',
|
||||||
content: 'michka-picture-url-default.jpeg',
|
content: {
|
||||||
fileExtension: '.jpeg',
|
alternativeText: 'alternative alt',
|
||||||
mime: 'image/jpeg',
|
ext: 'jpeg',
|
||||||
thumbnailURL: 'michka-picture-url-thumbnail.jpeg',
|
formats: {
|
||||||
url: 'michka-picture-url-default.jpeg',
|
thumbnail: {
|
||||||
|
url: 'michka-picture-url-thumbnail.jpeg',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mime: 'image/jpeg',
|
||||||
|
url: 'michka-picture-url-default.jpeg',
|
||||||
|
},
|
||||||
|
name: 'preview',
|
||||||
};
|
};
|
||||||
|
|
||||||
const ComponentFixture = (props) => {
|
const ComponentFixture = (props) => {
|
||||||
@ -42,49 +48,63 @@ describe('TableList | CellContent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should render image cell type when element type is asset and mime does not include image', () => {
|
it('should render image cell type when element type is asset and mime does not include image', () => {
|
||||||
const { container, getByText } = setup({ mime: 'application/pdf', fileExtension: 'pdf' });
|
const { container, getByText } = setup({
|
||||||
|
content: { ...PROPS_FIXTURE.element, mime: 'application/pdf', ext: 'pdf' },
|
||||||
|
});
|
||||||
|
|
||||||
expect(getByText('pdf')).toBeInTheDocument();
|
expect(getByText('pdf')).toBeInTheDocument();
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render image cell type when element type is folder', () => {
|
it('should render image cell type when element type is folder', () => {
|
||||||
const { container } = setup({ elementType: 'folder' });
|
const { container } = setup({ contentType: 'folder' });
|
||||||
|
|
||||||
expect(container.querySelector('path')).toBeInTheDocument();
|
expect(container.querySelector('path')).toBeInTheDocument();
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render text cell type', () => {
|
it('should render text cell type', () => {
|
||||||
const { container, getByText } = setup({ cellType: 'text', content: 'some text' });
|
const { container, getByText } = setup({
|
||||||
|
cellType: 'text',
|
||||||
|
content: { ...PROPS_FIXTURE.content, name: 'some text' },
|
||||||
|
name: 'name',
|
||||||
|
});
|
||||||
|
|
||||||
expect(getByText('some text')).toBeInTheDocument();
|
expect(getByText('some text')).toBeInTheDocument();
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render extension cell type when element type is asset', () => {
|
it('should render extension cell type when element type is asset', () => {
|
||||||
const { container, getByText } = setup({ cellType: 'ext', content: '.pdf' });
|
const { container, getByText } = setup({
|
||||||
|
cellType: 'ext',
|
||||||
|
content: { ...PROPS_FIXTURE.content, ext: '.pdf' },
|
||||||
|
name: 'ext',
|
||||||
|
});
|
||||||
|
|
||||||
expect(getByText('PDF')).toBeInTheDocument();
|
expect(getByText('PDF')).toBeInTheDocument();
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render extension cell type with "-" when element type is folder', () => {
|
it('should render extension cell type with "-" when element type is folder', () => {
|
||||||
const { container, getByText } = setup({ cellType: 'ext', elementType: 'folder' });
|
const { container, getByText } = setup({ cellType: 'ext', contentType: 'folder' });
|
||||||
|
|
||||||
expect(getByText('-')).toBeInTheDocument();
|
expect(getByText('-')).toBeInTheDocument();
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render size cell type when element type is asset', () => {
|
it('should render size cell type when element type is asset', () => {
|
||||||
const { container, getByText } = setup({ cellType: 'size', content: '20.5435' });
|
const { container, getByText } = setup({
|
||||||
|
cellType: 'size',
|
||||||
|
content: { ...PROPS_FIXTURE.content, size: '20.5435' },
|
||||||
|
name: 'size',
|
||||||
|
});
|
||||||
|
|
||||||
expect(getByText('21KB')).toBeInTheDocument();
|
expect(getByText('21KB')).toBeInTheDocument();
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render size cell type with "-" when element type is folder', () => {
|
it('should render size cell type with "-" when element type is folder', () => {
|
||||||
const { container, getByText } = setup({ cellType: 'size', elementType: 'folder' });
|
const { container, getByText } = setup({ cellType: 'size', contentType: 'folder' });
|
||||||
|
|
||||||
expect(getByText('-')).toBeInTheDocument();
|
expect(getByText('-')).toBeInTheDocument();
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
@ -93,7 +113,8 @@ describe('TableList | CellContent', () => {
|
|||||||
it('should render date cell type', () => {
|
it('should render date cell type', () => {
|
||||||
const { container, getByText } = setup({
|
const { container, getByText } = setup({
|
||||||
cellType: 'date',
|
cellType: 'date',
|
||||||
content: '2022-11-18T12:08:02.202Z',
|
content: { ...PROPS_FIXTURE.content, updatedAt: '2022-11-18T12:08:02.202Z' },
|
||||||
|
name: 'updatedAt',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(getByText('Friday, November 18, 2022')).toBeInTheDocument();
|
expect(getByText('Friday, November 18, 2022')).toBeInTheDocument();
|
||||||
|
|||||||
@ -5,12 +5,17 @@ import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
|||||||
import { PreviewCell } from '../PreviewCell';
|
import { PreviewCell } from '../PreviewCell';
|
||||||
|
|
||||||
const PROPS_FIXTURE = {
|
const PROPS_FIXTURE = {
|
||||||
alternativeText: 'alternative alt',
|
content: {
|
||||||
fileExtension: 'jpeg',
|
alternativeText: 'alternative alt',
|
||||||
mime: 'image/jpeg',
|
ext: 'jpeg',
|
||||||
name: 'michka',
|
formats: {
|
||||||
thumbnailURL: 'michka-picture-url-thumbnail.jpeg',
|
thumbnail: {
|
||||||
url: 'michka-picture-url-default.jpeg',
|
url: 'michka-picture-url-thumbnail.jpeg',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mime: 'image/jpeg',
|
||||||
|
url: 'michka-picture-url-default.jpeg',
|
||||||
|
},
|
||||||
type: 'asset',
|
type: 'asset',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,7 +46,7 @@ describe('TableList | PreviewCell', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should render an image with default url if thumbnail is not available', () => {
|
it('should render an image with default url if thumbnail is not available', () => {
|
||||||
const { getByRole } = setup({ thumbnailURL: undefined });
|
const { getByRole } = setup({ content: { ...PROPS_FIXTURE.content, formats: undefined } });
|
||||||
|
|
||||||
expect(getByRole('img', { name: 'alternative alt' })).toHaveAttribute(
|
expect(getByRole('img', { name: 'alternative alt' })).toHaveAttribute(
|
||||||
'src',
|
'src',
|
||||||
@ -59,7 +64,9 @@ describe('TableList | PreviewCell', () => {
|
|||||||
|
|
||||||
describe('rendering files', () => {
|
describe('rendering files', () => {
|
||||||
it('should render a file with fileExtension', () => {
|
it('should render a file with fileExtension', () => {
|
||||||
const { getByText } = setup({ mime: 'application/pdf', fileExtension: 'pdf' });
|
const { getByText } = setup({
|
||||||
|
content: { ...PROPS_FIXTURE.content, mime: 'application/pdf', ext: 'pdf' },
|
||||||
|
});
|
||||||
|
|
||||||
expect(getByText('pdf')).toBeInTheDocument();
|
expect(getByText('pdf')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -218,8 +218,8 @@ exports[`TableList | CellContent should render image cell type when element type
|
|||||||
.c0 {
|
.c0 {
|
||||||
background: #eaf5ff;
|
background: #eaf5ff;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 1.625rem;
|
width: 26px;
|
||||||
height: 1.625rem;
|
height: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c1 {
|
.c1 {
|
||||||
@ -241,22 +241,23 @@ exports[`TableList | CellContent should render image cell type when element type
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c3 {
|
.c3 {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.43;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.6875rem;
|
font-size: 0.6875rem;
|
||||||
line-height: 1.45;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: #0c75af;
|
color: #0c75af;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c2 span {
|
.c2 span {
|
||||||
font-size: 0.625rem;
|
line-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="c0 c1 c2"
|
class="c0 c1 c2"
|
||||||
height="1.625rem"
|
height="26px"
|
||||||
width="1.625rem"
|
width="26px"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="c3"
|
class="c3"
|
||||||
@ -355,7 +356,7 @@ exports[`TableList | CellContent should render image cell type when element type
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`TableList | CellContent should render image cell type when element type is folder 1`] = `
|
exports[`TableList | CellContent should render image cell type when element type is folder 1`] = `
|
||||||
.c4 {
|
.c6 {
|
||||||
border: 0;
|
border: 0;
|
||||||
-webkit-clip: rect(0 0 0 0);
|
-webkit-clip: rect(0 0 0 0);
|
||||||
clip: rect(0 0 0 0);
|
clip: rect(0 0 0 0);
|
||||||
@ -370,12 +371,14 @@ exports[`TableList | CellContent should render image cell type when element type
|
|||||||
.c0 {
|
.c0 {
|
||||||
background: #eaf5ff;
|
background: #eaf5ff;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 1.625rem;
|
width: 26px;
|
||||||
height: 1.625rem;
|
height: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c2 {
|
.c4 {
|
||||||
color: #66b7f1;
|
color: #66b7f1;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c1 {
|
.c1 {
|
||||||
@ -396,32 +399,49 @@ exports[`TableList | CellContent should render image cell type when element type
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c3 path {
|
.c3 {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.43;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
color: #0c75af;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c5 path {
|
||||||
fill: #66b7f1;
|
fill: #66b7f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c2 span {
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="c0 c1"
|
class="c0 c1 c2"
|
||||||
height="1.625rem"
|
height="26px"
|
||||||
width="1.625rem"
|
width="26px"
|
||||||
>
|
>
|
||||||
<svg
|
<span
|
||||||
class="c2 c3"
|
class="c3"
|
||||||
fill="none"
|
|
||||||
height="1em"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="1em"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
d="M12.414 5H21a1 1 0 011 1v14a1 1 0 01-1 1H3a1 1 0 01-1-1V4a1 1 0 011-1h7.414l2 2z"
|
class="c4 c5"
|
||||||
fill="#212134"
|
fill="none"
|
||||||
/>
|
height="1rem"
|
||||||
</svg>
|
viewBox="0 0 24 24"
|
||||||
|
width="1rem"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12.414 5H21a1 1 0 011 1v14a1 1 0 01-1 1H3a1 1 0 01-1-1V4a1 1 0 011-1h7.414l2 2z"
|
||||||
|
fill="#212134"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c4"
|
class="c6"
|
||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user