mirror of
https://github.com/strapi/strapi.git
synced 2025-10-07 14:26:26 +00:00
tests(upload): fix error logs in upload tests (#18261)
* chore: add test utils * test(upload): fix useModalQueryParams * test(upload): fix TableRows * test(upload): fix useConfig test * test(upload): fix useFolder tests * test(upload): fix useFolders test * test(upload): fix SettingsPage tests * test(upload): fix UploadAssetDialog tests * test(upload): fix CarouselAssets tests
This commit is contained in:
parent
eeb77afbca
commit
ddcbafe9d9
@ -1,19 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import { lightTheme, ThemeProvider } from '@strapi/design-system';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { render, waitFor } from '@tests/utils';
|
||||
|
||||
import { CarouselAssets } from '../CarouselAssets';
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
useNotification: jest.fn(() => ({
|
||||
toggleNotification: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
const ASSET_FIXTURES = [
|
||||
{
|
||||
alternativeText: 'alternative text',
|
||||
@ -33,38 +23,21 @@ const ASSET_FIXTURES = [
|
||||
},
|
||||
];
|
||||
|
||||
const client = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const ComponentFixture = (props) => {
|
||||
return (
|
||||
<QueryClientProvider client={client}>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<CarouselAssets
|
||||
assets={ASSET_FIXTURES}
|
||||
label="Carousel"
|
||||
onAddAsset={jest.fn}
|
||||
onDeleteAsset={jest.fn}
|
||||
onDeleteAssetFromMediaLibrary={jest.fn}
|
||||
onEditAsset={jest.fn}
|
||||
onNext={jest.fn}
|
||||
onPrevious={jest.fn}
|
||||
selectedAssetIndex={0}
|
||||
{...props}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
</IntlProvider>
|
||||
</QueryClientProvider>
|
||||
const setup = (props) =>
|
||||
render(
|
||||
<CarouselAssets
|
||||
assets={ASSET_FIXTURES}
|
||||
label="Carousel"
|
||||
onAddAsset={jest.fn}
|
||||
onDeleteAsset={jest.fn}
|
||||
onDeleteAssetFromMediaLibrary={jest.fn}
|
||||
onEditAsset={jest.fn}
|
||||
onNext={jest.fn}
|
||||
onPrevious={jest.fn}
|
||||
selectedAssetIndex={0}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const setup = (props) => render(<ComponentFixture {...props} />);
|
||||
|
||||
describe('MediaLibraryInput | Carousel | CarouselAssets', () => {
|
||||
it('should render empty carousel', () => {
|
||||
@ -90,30 +63,32 @@ describe('MediaLibraryInput | Carousel | CarouselAssets', () => {
|
||||
expect(getByRole('button', { name: 'edit' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onAddAsset', () => {
|
||||
it('should call onAddAsset', async () => {
|
||||
const onAddAssetSpy = jest.fn();
|
||||
const { getByRole } = setup({ onAddAsset: onAddAssetSpy });
|
||||
const { getByRole, user } = setup({ onAddAsset: onAddAssetSpy });
|
||||
|
||||
fireEvent.click(getByRole('button', { name: 'Add' }));
|
||||
await user.click(getByRole('button', { name: 'Add' }));
|
||||
|
||||
expect(onAddAssetSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should call onDeleteAsset', () => {
|
||||
it('should call onDeleteAsset', async () => {
|
||||
const onDeleteAssetSpy = jest.fn();
|
||||
const { getByRole } = setup({ onDeleteAsset: onDeleteAssetSpy });
|
||||
const { getByRole, user } = setup({ onDeleteAsset: onDeleteAssetSpy });
|
||||
|
||||
fireEvent.click(getByRole('button', { name: 'Delete' }));
|
||||
await user.click(getByRole('button', { name: 'Delete' }));
|
||||
|
||||
expect(onDeleteAssetSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should open edit view', () => {
|
||||
const { getByRole, getByText } = setup();
|
||||
it('should open edit view', async () => {
|
||||
const { getByRole, getByText, user, queryByText } = setup();
|
||||
|
||||
fireEvent.click(getByRole('button', { name: 'edit' }));
|
||||
await user.click(getByRole('button', { name: 'edit' }));
|
||||
|
||||
expect(getByText('Details')).toBeInTheDocument();
|
||||
|
||||
await waitFor(() => expect(queryByText('Content is loading.')).not.toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should render the localized label', () => {
|
||||
|
@ -46,7 +46,7 @@ export const TableRows = ({
|
||||
fn: () => handleRowClickFn(element, contentType, id, path),
|
||||
})}
|
||||
>
|
||||
<Td {...stopPropagation}>
|
||||
<Td onClick={(e) => e.stopPropagation()}>
|
||||
<BaseCheckbox
|
||||
aria-label={formatMessage(
|
||||
{
|
||||
|
@ -1,17 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import { lightTheme, ThemeProvider } from '@strapi/design-system';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { RawTable } from '@strapi/design-system';
|
||||
import { render, fireEvent } from '@tests/utils';
|
||||
|
||||
import { TableRows } from '../TableRows';
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
useQueryParams: jest.fn(() => [{ query: {} }]),
|
||||
}));
|
||||
|
||||
const ASSET_FIXTURE = {
|
||||
alternativeText: 'alternative text',
|
||||
createdAt: '2021-10-01T08:04:56.326Z',
|
||||
@ -32,7 +25,7 @@ const ASSET_FIXTURE = {
|
||||
|
||||
const FOLDER_FIXTURE = {
|
||||
createdAt: '2022-11-17T10:40:06.022Z',
|
||||
id: 1,
|
||||
id: 2,
|
||||
name: 'folder 1',
|
||||
type: 'folder',
|
||||
updatedAt: '2022-11-17T10:40:06.022Z',
|
||||
@ -47,26 +40,12 @@ const PROPS_FIXTURE = {
|
||||
selected: [],
|
||||
};
|
||||
|
||||
const ComponentFixture = (props) => {
|
||||
const customProps = {
|
||||
...PROPS_FIXTURE,
|
||||
...props,
|
||||
};
|
||||
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<table>
|
||||
<TableRows {...customProps} />
|
||||
</table>
|
||||
</ThemeProvider>
|
||||
</IntlProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
const setup = (props) => render(<ComponentFixture {...props} />);
|
||||
const setup = (props) =>
|
||||
render(<TableRows {...PROPS_FIXTURE} {...props} />, {
|
||||
renderOptions: {
|
||||
wrapper: ({ children }) => <RawTable>{children}</RawTable>,
|
||||
},
|
||||
});
|
||||
|
||||
describe('TableList | TableRows', () => {
|
||||
describe('rendering assets', () => {
|
||||
@ -85,7 +64,10 @@ describe('TableList | TableRows', () => {
|
||||
const onSelectOneSpy = jest.fn();
|
||||
const { getByRole } = setup({ onSelectOne: onSelectOneSpy });
|
||||
|
||||
fireEvent.click(getByRole('checkbox', { name: 'Select michka asset', hidden: true }));
|
||||
/**
|
||||
* using UserEvent never triggers the onChange event.
|
||||
*/
|
||||
fireEvent.click(getByRole('checkbox', { name: 'Select michka asset' }));
|
||||
|
||||
expect(onSelectOneSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@ -93,34 +75,32 @@ describe('TableList | TableRows', () => {
|
||||
it('should reflect non selected assets state', () => {
|
||||
const { getByRole } = setup();
|
||||
|
||||
expect(
|
||||
getByRole('checkbox', { name: 'Select michka asset', hidden: true })
|
||||
).not.toBeChecked();
|
||||
expect(getByRole('checkbox', { name: 'Select michka asset' })).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('should reflect selected assets state', () => {
|
||||
const { getByRole } = setup({ selected: [{ id: 1, type: 'asset' }] });
|
||||
|
||||
expect(getByRole('checkbox', { name: 'Select michka asset', hidden: true })).toBeChecked();
|
||||
expect(getByRole('checkbox', { name: 'Select michka asset' })).toBeChecked();
|
||||
});
|
||||
|
||||
it('should disable select asset checkbox when users do not have the permission to update', () => {
|
||||
const { getByRole } = setup({ canUpdate: false });
|
||||
|
||||
expect(getByRole('checkbox', { name: 'Select michka asset', hidden: true })).toBeDisabled();
|
||||
expect(getByRole('checkbox', { name: 'Select michka asset' })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should disable select asset checkbox when users if the file type is not allowed', () => {
|
||||
const { getByRole } = setup({ allowedTypes: [] });
|
||||
|
||||
expect(getByRole('checkbox', { name: 'Select michka asset', hidden: true })).toBeDisabled();
|
||||
expect(getByRole('checkbox', { name: 'Select michka asset' })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should call onEditAsset callback', () => {
|
||||
it('should call onEditAsset callback', async () => {
|
||||
const onEditAssetSpy = jest.fn();
|
||||
const { getByRole } = setup({ onEditAsset: onEditAssetSpy });
|
||||
const { getByRole, user } = setup({ onEditAsset: onEditAssetSpy });
|
||||
|
||||
fireEvent.click(getByRole('button', { name: 'Edit', hidden: true }));
|
||||
await user.click(getByRole('button', { name: 'Edit', hidden: true }));
|
||||
|
||||
expect(onEditAssetSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@ -135,14 +115,14 @@ describe('TableList | TableRows', () => {
|
||||
expect(getByText('folder 1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onEditFolder callback', () => {
|
||||
it('should call onEditFolder callback', async () => {
|
||||
const onEditFolderSpy = jest.fn();
|
||||
const { getByRole } = setup({
|
||||
const { getByRole, user } = setup({
|
||||
rows: [FOLDER_FIXTURE],
|
||||
onEditFolder: onEditFolderSpy,
|
||||
});
|
||||
|
||||
fireEvent.click(getByRole('button', { name: 'Edit', hidden: true }));
|
||||
await user.click(getByRole('button', { name: 'Edit', hidden: true }));
|
||||
|
||||
expect(onEditFolderSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@ -159,13 +139,16 @@ describe('TableList | TableRows', () => {
|
||||
expect(getByRole('button', { name: 'Access folder', hidden: true })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onChangeFolder when clicking on folder navigation button', () => {
|
||||
it('should call onChangeFolder when clicking on folder navigation button', async () => {
|
||||
const onChangeFolderSpy = jest.fn();
|
||||
const { getByRole } = setup({ rows: [FOLDER_FIXTURE], onChangeFolder: onChangeFolderSpy });
|
||||
const { getByRole, user } = setup({
|
||||
rows: [FOLDER_FIXTURE],
|
||||
onChangeFolder: onChangeFolderSpy,
|
||||
});
|
||||
|
||||
fireEvent.click(getByRole('button', { name: 'Access folder', hidden: true }));
|
||||
await user.click(getByRole('button', { name: 'Access folder', hidden: true }));
|
||||
|
||||
expect(onChangeFolderSpy).toHaveBeenCalledWith(1);
|
||||
expect(onChangeFolderSpy).toHaveBeenCalledWith(2);
|
||||
});
|
||||
|
||||
it('should reflect non selected folder state', () => {
|
||||
@ -179,7 +162,7 @@ describe('TableList | TableRows', () => {
|
||||
it('should reflect selected folder state', () => {
|
||||
const { getByRole } = setup({
|
||||
rows: [FOLDER_FIXTURE],
|
||||
selected: [{ id: 1, type: 'folder' }],
|
||||
selected: [{ id: 2, type: 'folder' }],
|
||||
});
|
||||
|
||||
expect(getByRole('checkbox', { name: 'Select folder 1 folder', hidden: true })).toBeChecked();
|
||||
@ -208,17 +191,15 @@ describe('TableList | TableRows', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe.only('rendering folder & asset with the same id', () => {
|
||||
describe('rendering folder & asset with the same id', () => {
|
||||
it('should reflect selected only folder state', () => {
|
||||
const { getByRole } = setup({
|
||||
rows: [FOLDER_FIXTURE, ASSET_FIXTURE],
|
||||
selected: [{ id: 1, type: 'folder' }],
|
||||
selected: [{ id: 2, type: 'folder' }],
|
||||
});
|
||||
|
||||
expect(getByRole('checkbox', { name: 'Select folder 1 folder', hidden: true })).toBeChecked();
|
||||
expect(
|
||||
getByRole('checkbox', { name: 'Select michka asset', hidden: true })
|
||||
).not.toBeChecked();
|
||||
expect(getByRole('checkbox', { name: 'Select michka asset' })).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('should reflect selected only asset state', () => {
|
||||
@ -230,7 +211,7 @@ describe('TableList | TableRows', () => {
|
||||
expect(
|
||||
getByRole('checkbox', { name: 'Select folder 1 folder', hidden: true })
|
||||
).not.toBeChecked();
|
||||
expect(getByRole('checkbox', { name: 'Select michka asset', hidden: true })).toBeChecked();
|
||||
expect(getByRole('checkbox', { name: 'Select michka asset' })).toBeChecked();
|
||||
});
|
||||
|
||||
it('should reflect selected both asset & folder state', () => {
|
||||
@ -238,12 +219,12 @@ describe('TableList | TableRows', () => {
|
||||
rows: [FOLDER_FIXTURE, ASSET_FIXTURE],
|
||||
selected: [
|
||||
{ id: 1, type: 'asset' },
|
||||
{ id: 1, type: 'folder' },
|
||||
{ id: 2, type: 'folder' },
|
||||
],
|
||||
});
|
||||
|
||||
expect(getByRole('checkbox', { name: 'Select folder 1 folder', hidden: true })).toBeChecked();
|
||||
expect(getByRole('checkbox', { name: 'Select michka asset', hidden: true })).toBeChecked();
|
||||
expect(getByRole('checkbox', { name: 'Select michka asset' })).toBeChecked();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -134,6 +134,7 @@ UploadAssetDialog.defaultProps = {
|
||||
addUploadedFiles: undefined,
|
||||
folderId: null,
|
||||
initialAssetsToAdd: undefined,
|
||||
onClose() {},
|
||||
trackedLocation: undefined,
|
||||
validateAssetsTypes: undefined,
|
||||
};
|
||||
@ -142,7 +143,7 @@ UploadAssetDialog.propTypes = {
|
||||
addUploadedFiles: PropTypes.func,
|
||||
folderId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
initialAssetsToAdd: PropTypes.arrayOf(AssetDefinition),
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func,
|
||||
trackedLocation: PropTypes.string,
|
||||
validateAssetsTypes: PropTypes.func,
|
||||
};
|
||||
|
@ -1,81 +1,41 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import React from 'react';
|
||||
|
||||
import { lightTheme, ThemeProvider } from '@strapi/design-system';
|
||||
import { TrackingProvider } from '@strapi/helper-plugin';
|
||||
import { fireEvent, render as renderTL, screen, waitFor, within } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { within } from '@testing-library/react';
|
||||
import { fireEvent, render, screen, waitFor } from '@tests/utils';
|
||||
|
||||
import en from '../../../translations/en.json';
|
||||
import { UploadAssetDialog } from '../UploadAssetDialog';
|
||||
|
||||
import { server } from './server';
|
||||
|
||||
jest.mock('../../../utils/getTrad', () => (x) => x);
|
||||
|
||||
jest.mock('react-intl', () => ({
|
||||
useIntl: () => ({ formatMessage: jest.fn(({ id }) => en[id] || id) }),
|
||||
}));
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const render = (props = { onClose() {} }) =>
|
||||
renderTL(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<TrackingProvider>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<UploadAssetDialog {...props} />
|
||||
</ThemeProvider>
|
||||
</TrackingProvider>
|
||||
</QueryClientProvider>,
|
||||
{ container: document.getElementById('app') }
|
||||
);
|
||||
|
||||
describe('UploadAssetDialog', () => {
|
||||
let confirmSpy;
|
||||
|
||||
beforeAll(() => {
|
||||
confirmSpy = jest.spyOn(window, 'confirm');
|
||||
confirmSpy.mockImplementation(jest.fn(() => true));
|
||||
server.listen();
|
||||
});
|
||||
afterEach(() => server.resetHandlers());
|
||||
|
||||
afterAll(() => {
|
||||
confirmSpy.mockRestore();
|
||||
server.close();
|
||||
});
|
||||
|
||||
describe('from computer', () => {
|
||||
it('snapshots the component', () => {
|
||||
render();
|
||||
|
||||
expect(document.body).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('closes the dialog when clicking on cancel on the add asset step', () => {
|
||||
it('closes the dialog when clicking on cancel on the add asset step', async () => {
|
||||
const onCloseSpy = jest.fn();
|
||||
render({ onClose: onCloseSpy, onSuccess() {} });
|
||||
const { user, getByRole } = render(<UploadAssetDialog onClose={onCloseSpy} />);
|
||||
|
||||
fireEvent.click(screen.getByText('app.components.Button.cancel'));
|
||||
await user.click(getByRole('button', { name: 'cancel' }));
|
||||
|
||||
expect(onCloseSpy).toBeCalled();
|
||||
});
|
||||
|
||||
it('open confirm box when clicking on cancel on the pending asset step', () => {
|
||||
it('open confirm box when clicking on cancel on the pending asset step', async () => {
|
||||
const file = new File(['Some stuff'], 'test.png', { type: 'image/png' });
|
||||
const onCloseSpy = jest.fn();
|
||||
|
||||
render({ onClose: onCloseSpy, onSuccess() {} });
|
||||
const { user, getByRole } = render(<UploadAssetDialog />);
|
||||
|
||||
const fileList = [file];
|
||||
fileList.item = (i) => fileList[i];
|
||||
await user.upload(document.querySelector('[type="file"]'), file);
|
||||
|
||||
fireEvent.change(document.querySelector('[type="file"]'), { target: { files: fileList } });
|
||||
fireEvent.click(screen.getByText('app.components.Button.cancel'));
|
||||
await user.click(getByRole('button', { name: 'cancel' }));
|
||||
|
||||
expect(window.confirm).toBeCalled();
|
||||
});
|
||||
@ -86,53 +46,30 @@ describe('UploadAssetDialog', () => {
|
||||
['pdf', 'application/pdf', 'Doc', 1],
|
||||
['unknown', 'unknown', 'Doc', 1],
|
||||
].forEach(([ext, mime, assetType, number]) => {
|
||||
it(`shows ${number} valid ${mime} file`, () => {
|
||||
const onCloseSpy = jest.fn();
|
||||
|
||||
// see https://github.com/testing-library/react-testing-library/issues/470
|
||||
Object.defineProperty(HTMLMediaElement.prototype, 'muted', {
|
||||
set() {},
|
||||
});
|
||||
|
||||
it(`shows ${number} valid ${mime} file`, async () => {
|
||||
const file = new File(['Some stuff'], `test.${ext}`, { type: mime });
|
||||
|
||||
const fileList = [file];
|
||||
fileList.item = (i) => fileList[i];
|
||||
const { user, getByText, getAllByText } = render(<UploadAssetDialog />);
|
||||
|
||||
render({ onClose: onCloseSpy, onSuccess() {} });
|
||||
await user.upload(document.querySelector('[type="file"]'), file);
|
||||
|
||||
fireEvent.change(document.querySelector('[type="file"]'), {
|
||||
target: { files: fileList },
|
||||
});
|
||||
|
||||
expect(screen.getAllByText(`Add new assets`).length).toBe(2);
|
||||
expect(getByText('1 asset ready to upload')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'{number, plural, =0 {No asset} one {1 asset} other {# assets}} ready to upload'
|
||||
)
|
||||
getByText('Manage the assets before adding them to the Media Library')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Manage the assets before adding them to the Media Library')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getAllByText(`test.${ext}`).length).toBe(number);
|
||||
expect(screen.getByText(ext)).toBeInTheDocument();
|
||||
expect(screen.getByText(assetType)).toBeInTheDocument();
|
||||
|
||||
expect(getAllByText(`test.${ext}`).length).toBe(number);
|
||||
expect(getByText(ext)).toBeInTheDocument();
|
||||
expect(getByText(assetType)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('from url', () => {
|
||||
it('snapshots the component', () => {
|
||||
render();
|
||||
|
||||
fireEvent.click(screen.getByText('From url'));
|
||||
|
||||
expect(document.body).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('shows an error message when the asset does not exist', async () => {
|
||||
render();
|
||||
fireEvent.click(screen.getByText('From url'));
|
||||
const { user, getByRole } = render(<UploadAssetDialog />);
|
||||
|
||||
await user.click(getByRole('tab', { name: 'From URL' }));
|
||||
|
||||
const urls = [
|
||||
'http://localhost:5000/an-image.png',
|
||||
@ -140,27 +77,48 @@ describe('UploadAssetDialog', () => {
|
||||
'http://localhost:5000/a-video.mp4',
|
||||
'http://localhost:5000/not-working-like-cors.lutin',
|
||||
'http://localhost:1234/some-where-not-existing.jpg',
|
||||
].join('\n');
|
||||
];
|
||||
|
||||
fireEvent.change(screen.getByLabelText('URL'), { target: { value: urls } });
|
||||
fireEvent.click(screen.getByText('Next'));
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const url of urls) {
|
||||
await user.type(getByRole('textbox', 'URL'), url);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Failed to fetch')).toBeInTheDocument());
|
||||
await user.type(getByRole('textbox', 'URL'), '[Enter]');
|
||||
}
|
||||
|
||||
/**
|
||||
* userEvent does not submit forms.
|
||||
*/
|
||||
fireEvent.click(getByRole('button', { name: 'Next' }));
|
||||
|
||||
await waitFor(() => expect(screen.getByText('An error occured')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('snapshots the component with 4 URLs: 3 valid and one in failure', async () => {
|
||||
render();
|
||||
fireEvent.click(screen.getByText('From url'));
|
||||
const { user, getByText, getByRole } = render(<UploadAssetDialog />);
|
||||
|
||||
await user.click(getByRole('tab', { name: 'From URL' }));
|
||||
|
||||
const urls = [
|
||||
'http://localhost:5000/an-image.png',
|
||||
'http://localhost:5000/a-pdf.pdf',
|
||||
'http://localhost:5000/a-video.mp4',
|
||||
'http://localhost:5000/not-working-like-cors.lutin',
|
||||
].join('\n');
|
||||
];
|
||||
|
||||
fireEvent.change(screen.getByLabelText('URL'), { target: { value: urls } });
|
||||
fireEvent.click(screen.getByText('Next'));
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const url of urls) {
|
||||
await user.type(getByRole('textbox', 'URL'), url);
|
||||
|
||||
if (urls.indexOf(url) < urls.length - 1) {
|
||||
await user.type(getByRole('textbox', 'URL'), '[Enter]');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* userEvent does not submit forms.
|
||||
*/
|
||||
fireEvent.click(getByRole('button', { name: 'Next' }));
|
||||
|
||||
const assets = [
|
||||
{
|
||||
@ -201,25 +159,14 @@ describe('UploadAssetDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByText(
|
||||
'{number, plural, =0 {No asset} one {1 asset} other {# assets}} ready to upload'
|
||||
)
|
||||
).toBeInTheDocument()
|
||||
);
|
||||
expect(screen.getAllByText(`Add new assets`).length).toBe(2);
|
||||
expect(
|
||||
screen.getByText('Manage the assets before adding them to the Media Library')
|
||||
).toBeInTheDocument();
|
||||
await waitFor(() => expect(getByText('4 assets ready to upload')).toBeInTheDocument());
|
||||
|
||||
assets.forEach((asset) => {
|
||||
const dialog = within(screen.getByRole('dialog'));
|
||||
const card = within(dialog.getAllByLabelText(asset.name)[0]);
|
||||
const card = within(screen.getByRole('dialog')).getAllByLabelText(asset.name)[0];
|
||||
|
||||
expect(card.getByText(asset.ext)).toBeInTheDocument();
|
||||
expect(within(card).getByText(asset.ext)).toBeInTheDocument();
|
||||
expect(
|
||||
card.getByText(asset.type.charAt(0).toUpperCase() + asset.type.slice(1))
|
||||
within(card).getByText(`${asset.type.charAt(0).toUpperCase()}${asset.type.slice(1)}`)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,18 +0,0 @@
|
||||
// mocking window.fetch since msw is not able to give back the res.url param
|
||||
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
|
||||
export const server = setupServer(
|
||||
rest.get('*/an-image.png', (req, res, ctx) =>
|
||||
res(ctx.set('Content-Type', 'image/png'), ctx.body())
|
||||
),
|
||||
rest.get('*/a-pdf.pdf', (req, res, ctx) =>
|
||||
res(ctx.set('Content-Type', 'application/pdf'), ctx.body())
|
||||
),
|
||||
rest.get('*/a-video.mp4', (req, res, ctx) =>
|
||||
res(ctx.set('Content-Type', 'video/mp4'), ctx.body())
|
||||
),
|
||||
rest.get('*/not-working-like-cors.lutin', (req, res, ctx) => res(ctx.json({}))),
|
||||
rest.get('*/some-where-not-existing.jpg', (req, res) => res.networkError('Failed to fetch'))
|
||||
);
|
@ -1,183 +1,73 @@
|
||||
import React from 'react';
|
||||
import { act, renderHook, waitFor, screen, server } from '@tests/utils';
|
||||
import { rest } from 'msw';
|
||||
|
||||
import { lightTheme, ThemeProvider } from '@strapi/design-system';
|
||||
import { NotificationsProvider, useFetchClient, useNotification } from '@strapi/helper-plugin';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { QueryClient, QueryClientProvider, useQueryClient } from 'react-query';
|
||||
import { BrowserRouter as Router, Route } from 'react-router-dom';
|
||||
|
||||
import { pageSizes, sortOptions } from '../../constants';
|
||||
import pluginId from '../../pluginId';
|
||||
import { useConfig } from '../useConfig';
|
||||
|
||||
const mockGetResponse = {
|
||||
data: {
|
||||
data: {
|
||||
pageSize: pageSizes[0],
|
||||
sort: sortOptions[0].value,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const notificationStatusMock = jest.fn();
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
useNotification: () => notificationStatusMock,
|
||||
useFetchClient: jest.fn().mockReturnValue({
|
||||
put: jest.fn().mockResolvedValue({ data: { data: {} } }),
|
||||
get: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
const refetchQueriesMock = jest.fn();
|
||||
|
||||
jest.mock('react-query', () => ({
|
||||
...jest.requireActual('react-query'),
|
||||
useQueryClient: () => ({
|
||||
refetchQueries: refetchQueriesMock,
|
||||
}),
|
||||
}));
|
||||
|
||||
const client = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
function ComponentFixture({ children }) {
|
||||
return (
|
||||
<Router>
|
||||
<Route>
|
||||
<QueryClientProvider client={client}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<NotificationsProvider>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
{children}
|
||||
</IntlProvider>
|
||||
</NotificationsProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</Route>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
function setup(...args) {
|
||||
return new Promise((resolve) => {
|
||||
act(() => {
|
||||
resolve(renderHook(() => useConfig(...args), { wrapper: ComponentFixture }));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('useConfig', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('query', () => {
|
||||
test('does call the get endpoint', async () => {
|
||||
const { get } = useFetchClient();
|
||||
get.mockReturnValueOnce(mockGetResponse);
|
||||
|
||||
const { result } = await setup();
|
||||
expect(get).toHaveBeenCalledWith(`/${pluginId}/configuration`);
|
||||
const { result } = renderHook(() => useConfig());
|
||||
|
||||
await waitFor(() => expect(result.current.config.isLoading).toBe(false));
|
||||
expect(result.current.config.data).toEqual(mockGetResponse.data.data);
|
||||
|
||||
expect(result.current.config.data).toMatchInlineSnapshot(`
|
||||
{
|
||||
"pageSize": 20,
|
||||
"sort": "updatedAt:DESC",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('should still return an object even if the server returns a falsey value', async () => {
|
||||
const { get } = useFetchClient();
|
||||
get.mockReturnValueOnce({
|
||||
data: {
|
||||
data: null,
|
||||
},
|
||||
});
|
||||
|
||||
const { result } = await setup();
|
||||
|
||||
await waitFor(() => expect(result.current.config.data).toEqual({}));
|
||||
});
|
||||
|
||||
test('calls toggleNotification in case of error', async () => {
|
||||
const { get } = useFetchClient();
|
||||
const originalConsoleError = console.error;
|
||||
console.error = jest.fn();
|
||||
|
||||
get.mockRejectedValueOnce(new Error('Jest mock error'));
|
||||
const toggleNotification = useNotification();
|
||||
await setup({});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(toggleNotification).toBeCalledWith({
|
||||
type: 'warning',
|
||||
message: { id: 'notification.error' },
|
||||
server.use(
|
||||
rest.get('/upload/configuration', (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.json({
|
||||
data: null,
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useConfig());
|
||||
|
||||
await waitFor(() => expect(result.current.config.data).toEqual({}));
|
||||
|
||||
server.restoreHandlers();
|
||||
});
|
||||
|
||||
test('calls toggleNotification in case of error', async () => {
|
||||
const originalConsoleError = console.error;
|
||||
console.error = jest.fn();
|
||||
server.use(
|
||||
rest.get('/upload/configuration', (req, res, ctx) => {
|
||||
return res(ctx.status(500));
|
||||
})
|
||||
);
|
||||
|
||||
renderHook(() => useConfig());
|
||||
|
||||
await waitFor(() => expect(screen.getByText('notification.error')).toBeInTheDocument());
|
||||
|
||||
console.error = originalConsoleError;
|
||||
server.restoreHandlers();
|
||||
});
|
||||
});
|
||||
|
||||
describe('mutation', () => {
|
||||
test('does call the proper mutation endpoint', async () => {
|
||||
const { put } = useFetchClient();
|
||||
const queryClient = useQueryClient();
|
||||
const { result } = renderHook(() => useConfig());
|
||||
|
||||
let setupResult;
|
||||
await act(async () => {
|
||||
setupResult = await setup();
|
||||
});
|
||||
|
||||
const {
|
||||
result: {
|
||||
current: { mutateConfig },
|
||||
},
|
||||
} = setupResult;
|
||||
|
||||
const mutateWith = {};
|
||||
await act(async () => {
|
||||
await mutateConfig.mutateAsync(mutateWith);
|
||||
});
|
||||
|
||||
expect(put).toHaveBeenCalledWith(`/${pluginId}/configuration`, mutateWith);
|
||||
expect(queryClient.refetchQueries).toHaveBeenCalledWith(['upload', 'configuration'], {
|
||||
active: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('does handle errors', async () => {
|
||||
const { put } = useFetchClient();
|
||||
const originalConsoleError = console.error;
|
||||
console.error = jest.fn();
|
||||
|
||||
const toggleNotification = useNotification();
|
||||
put.mockRejectedValueOnce(new Error('Jest mock error'));
|
||||
|
||||
const {
|
||||
result: { current },
|
||||
} = await setup();
|
||||
const { mutateConfig } = current;
|
||||
|
||||
const mutateWith = {};
|
||||
try {
|
||||
await act(async () => {
|
||||
await mutateConfig.mutateAsync(mutateWith);
|
||||
act(() => {
|
||||
result.current.mutateConfig.mutateAsync({
|
||||
pageSize: 100,
|
||||
sort: 'name:DESC',
|
||||
});
|
||||
} catch {
|
||||
expect(toggleNotification).toBeCalledWith({
|
||||
type: 'warning',
|
||||
message: { id: 'notification.error' },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
console.error = originalConsoleError;
|
||||
expect(result.current.config.isLoading).toBe(true);
|
||||
|
||||
await waitFor(() => expect(result.current.config.isLoading).toBe(false));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,123 +1,58 @@
|
||||
import React from 'react';
|
||||
|
||||
import { lightTheme, ThemeProvider, useNotifyAT } from '@strapi/design-system';
|
||||
import { NotificationsProvider, useFetchClient, useNotification } from '@strapi/helper-plugin';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { BrowserRouter as Router, Route } from 'react-router-dom';
|
||||
import { renderHook, screen, server, waitFor } from '@tests/utils';
|
||||
import { rest } from 'msw';
|
||||
|
||||
import { useFolder } from '../useFolder';
|
||||
|
||||
const notifyStatusMock = jest.fn();
|
||||
|
||||
jest.mock('@strapi/design-system', () => ({
|
||||
...jest.requireActual('@strapi/design-system'),
|
||||
useNotifyAT: () => ({
|
||||
notifyStatus: notifyStatusMock,
|
||||
}),
|
||||
}));
|
||||
|
||||
const notificationStatusMock = jest.fn();
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
useNotification: () => notificationStatusMock,
|
||||
useFetchClient: jest.fn().mockReturnValue({
|
||||
get: jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
id: 1,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
|
||||
const client = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
function ComponentFixture({ children }) {
|
||||
return (
|
||||
<Router>
|
||||
<Route>
|
||||
<QueryClientProvider client={client}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<NotificationsProvider>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
{children}
|
||||
</IntlProvider>
|
||||
</NotificationsProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</Route>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
function setup(...args) {
|
||||
return new Promise((resolve) => {
|
||||
act(() => {
|
||||
resolve(renderHook(() => useFolder(...args), { wrapper: ComponentFixture }));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('useFolder', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('fetches data from the right URL if no query param was set', async () => {
|
||||
const { get } = useFetchClient();
|
||||
const { result } = await setup(1, {});
|
||||
const { result } = renderHook(() => useFolder(1));
|
||||
|
||||
await waitFor(() => result.current.isSuccess);
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(get).toBeCalledWith('/upload/folders/1', {
|
||||
params: {
|
||||
populate: {
|
||||
parent: {
|
||||
populate: {
|
||||
parent: '*',
|
||||
},
|
||||
},
|
||||
},
|
||||
expect(result.current.data).toMatchInlineSnapshot(`
|
||||
{
|
||||
"children": {
|
||||
"count": 2,
|
||||
},
|
||||
})
|
||||
);
|
||||
"createdAt": "2023-06-26T12:48:54.054Z",
|
||||
"files": {
|
||||
"count": 0,
|
||||
},
|
||||
"id": 1,
|
||||
"name": "test",
|
||||
"parent": null,
|
||||
"path": "/1",
|
||||
"pathId": 1,
|
||||
"updatedAt": "2023-06-26T12:48:54.054Z",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('it does not fetch, if enabled is set to false', async () => {
|
||||
const { get } = useFetchClient();
|
||||
const { result } = await setup(1, { enabled: false });
|
||||
const { result } = renderHook(() => useFolder(1, { enabled: false }));
|
||||
|
||||
await waitFor(() => result.current.isSuccess);
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
expect(get).toBeCalledTimes(0);
|
||||
expect(result.current.data).toBe(undefined);
|
||||
});
|
||||
|
||||
test('calls toggleNotification in case of error', async () => {
|
||||
const { get } = useFetchClient();
|
||||
const originalConsoleError = console.error;
|
||||
console.error = jest.fn();
|
||||
|
||||
get.mockRejectedValueOnce(new Error('Jest mock error'));
|
||||
server.use(rest.get('/upload/folders/:id', (req, res, ctx) => res(ctx.status(500))));
|
||||
|
||||
const { notifyStatus } = useNotifyAT();
|
||||
const toggleNotification = useNotification();
|
||||
const { result } = await setup(1, {});
|
||||
const { result } = renderHook(() => useFolder(1));
|
||||
|
||||
await waitFor(() => !result.current.isLoading);
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
expect(toggleNotification).toBeCalled();
|
||||
expect(notifyStatus).not.toBeCalled();
|
||||
expect(screen.getByText('Not found')).toBeInTheDocument();
|
||||
|
||||
console.error = originalConsoleError;
|
||||
server.restoreHandlers();
|
||||
});
|
||||
});
|
||||
|
@ -1,223 +1,138 @@
|
||||
import React from 'react';
|
||||
|
||||
import { lightTheme, ThemeProvider, useNotifyAT } from '@strapi/design-system';
|
||||
import { NotificationsProvider, useFetchClient, useNotification } from '@strapi/helper-plugin';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { BrowserRouter as Router, Route } from 'react-router-dom';
|
||||
import { renderHook, waitFor, screen, server } from '@tests/utils';
|
||||
import { rest } from 'msw';
|
||||
|
||||
import { useFolders } from '../useFolders';
|
||||
|
||||
const notifyStatusMock = jest.fn();
|
||||
|
||||
jest.mock('@strapi/design-system', () => ({
|
||||
...jest.requireActual('@strapi/design-system'),
|
||||
useNotifyAT: () => ({
|
||||
notifyStatus: notifyStatusMock,
|
||||
}),
|
||||
}));
|
||||
|
||||
const notificationStatusMock = jest.fn();
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
useNotification: () => notificationStatusMock,
|
||||
useFetchClient: jest.fn().mockReturnValue({
|
||||
get: jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
id: 1,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
|
||||
const client = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
function ComponentFixture({ children }) {
|
||||
return (
|
||||
<Router>
|
||||
<Route>
|
||||
<QueryClientProvider client={client}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<NotificationsProvider>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
{children}
|
||||
</IntlProvider>
|
||||
</NotificationsProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</Route>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
function setup(...args) {
|
||||
return new Promise((resolve) => {
|
||||
act(() => {
|
||||
resolve(renderHook(() => useFolders(...args), { wrapper: ComponentFixture }));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('useFolders', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('fetches data from the right URL if no query param was set', async () => {
|
||||
const { get } = useFetchClient();
|
||||
const { result } = await setup({});
|
||||
const { result } = renderHook(() => useFolders());
|
||||
|
||||
await waitFor(() => result.current.isSuccess);
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
const expected = {
|
||||
pagination: {
|
||||
pageSize: -1,
|
||||
},
|
||||
filters: {
|
||||
$and: [
|
||||
{
|
||||
parent: {
|
||||
id: {
|
||||
$null: true,
|
||||
},
|
||||
},
|
||||
expect(result.current.data).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"children": {
|
||||
"count": 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
await waitFor(() =>
|
||||
expect(get).toBeCalledWith(`/upload/folders`, {
|
||||
params: expected,
|
||||
})
|
||||
);
|
||||
"createdAt": "2023-06-26T12:48:54.054Z",
|
||||
"files": {
|
||||
"count": 0,
|
||||
},
|
||||
"id": 1,
|
||||
"name": "test",
|
||||
"path": "/1",
|
||||
"pathId": 1,
|
||||
"updatedAt": "2023-06-26T12:48:54.054Z",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('does not use parent filter in params if _q', async () => {
|
||||
const { get } = useFetchClient();
|
||||
const { result } = renderHook(() =>
|
||||
useFolders({
|
||||
query: { folder: 5, _q: 'something', filters: { $and: [{ something: 'true' }] } },
|
||||
})
|
||||
);
|
||||
|
||||
await setup({
|
||||
query: { folder: 5, _q: 'something', filters: { $and: [{ something: 'true' }] } },
|
||||
});
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
const expected = {
|
||||
filters: {
|
||||
$and: [
|
||||
{
|
||||
something: 'true',
|
||||
expect(result.current.data).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"children": {
|
||||
"count": 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
pagination: {
|
||||
pageSize: -1,
|
||||
},
|
||||
_q: 'something',
|
||||
};
|
||||
"createdAt": "2023-06-26T12:48:54.054Z",
|
||||
"files": {
|
||||
"count": 0,
|
||||
},
|
||||
"id": 1,
|
||||
"name": "something",
|
||||
"path": "/1",
|
||||
"pathId": 1,
|
||||
"updatedAt": "2023-06-26T12:48:54.054Z",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(get).toBeCalledWith(`/upload/folders`, {
|
||||
params: expected,
|
||||
});
|
||||
expect(result.current.data[0].name).toBe('something');
|
||||
});
|
||||
|
||||
test('fetches data from the right URL if a query param was set', async () => {
|
||||
const { get } = useFetchClient();
|
||||
const { result } = await setup({ query: { folder: 1 } });
|
||||
const { result } = renderHook(() => useFolders({ query: { folder: 1 } }));
|
||||
|
||||
await waitFor(() => result.current.isSuccess);
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
const expected = {
|
||||
pagination: {
|
||||
pageSize: -1,
|
||||
},
|
||||
filters: {
|
||||
$and: [
|
||||
{
|
||||
parent: {
|
||||
id: 1,
|
||||
},
|
||||
expect(result.current.data).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"children": {
|
||||
"count": 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
expect(get).toBeCalledWith(`/upload/folders`, {
|
||||
params: expected,
|
||||
});
|
||||
});
|
||||
|
||||
test('allows to merge filter query params using filters.$and', async () => {
|
||||
const { get } = useFetchClient();
|
||||
await setup({
|
||||
query: { folder: 5, filters: { $and: [{ something: 'true' }] } },
|
||||
});
|
||||
|
||||
const expected = {
|
||||
filters: {
|
||||
$and: [
|
||||
{
|
||||
something: 'true',
|
||||
"createdAt": "2023-06-26T12:49:31.354Z",
|
||||
"files": {
|
||||
"count": 3,
|
||||
},
|
||||
{
|
||||
parent: {
|
||||
id: 5,
|
||||
},
|
||||
"id": 3,
|
||||
"name": "2022",
|
||||
"path": "/1/3",
|
||||
"pathId": 3,
|
||||
"updatedAt": "2023-06-26T12:49:31.354Z",
|
||||
},
|
||||
{
|
||||
"children": {
|
||||
"count": 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
pagination: {
|
||||
pageSize: -1,
|
||||
},
|
||||
};
|
||||
"createdAt": "2023-06-26T12:49:08.466Z",
|
||||
"files": {
|
||||
"count": 3,
|
||||
},
|
||||
"id": 2,
|
||||
"name": "2023",
|
||||
"path": "/1/2",
|
||||
"pathId": 2,
|
||||
"updatedAt": "2023-06-26T12:49:08.466Z",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(get).toBeCalledWith(`/upload/folders`, {
|
||||
params: expected,
|
||||
result.current.data.forEach((folder) => {
|
||||
/**
|
||||
* We're passing a "current folder" in the query, which means
|
||||
* any folders returned should include the current folder's ID
|
||||
* in it's path because this get's the children of current.
|
||||
*/
|
||||
expect(folder.path.includes('1')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('it does not fetch, if enabled is set to false', async () => {
|
||||
const { get } = useFetchClient();
|
||||
const { result } = await setup({ enabled: false });
|
||||
const { result } = renderHook(() => useFolders({ enabled: false }));
|
||||
|
||||
await waitFor(() => result.current.isSuccess);
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
expect(get).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
test('calls notifyStatus in case of success', async () => {
|
||||
const { notifyStatus } = useNotifyAT();
|
||||
const toggleNotification = useNotification();
|
||||
await setup({});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(notifyStatus).toBeCalledWith('The folders have finished loading.');
|
||||
});
|
||||
|
||||
expect(toggleNotification).toBeCalledTimes(0);
|
||||
expect(result.current.data).toBe(undefined);
|
||||
});
|
||||
|
||||
test('calls toggleNotification in case of error', async () => {
|
||||
const { get } = useFetchClient();
|
||||
const originalConsoleError = console.error;
|
||||
console.error = jest.fn();
|
||||
|
||||
get.mockRejectedValueOnce(new Error('Jest mock error'));
|
||||
server.use(rest.get('/upload/folders', (req, res, ctx) => res(ctx.status(500))));
|
||||
|
||||
const { notifyStatus } = useNotifyAT();
|
||||
const toggleNotification = useNotification();
|
||||
await setup({});
|
||||
const { result } = renderHook(() => useFolders());
|
||||
|
||||
await waitFor(() => expect(toggleNotification).toBeCalled());
|
||||
await waitFor(() => expect(notifyStatus).not.toBeCalled());
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
await waitFor(() => expect(screen.getByText('notification.error')).toBeInTheDocument());
|
||||
|
||||
console.error = originalConsoleError;
|
||||
server.restoreHandlers();
|
||||
});
|
||||
});
|
||||
|
@ -1,45 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { lightTheme, ThemeProvider } from '@strapi/design-system';
|
||||
import { NotificationsProvider } from '@strapi/helper-plugin';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { act, renderHook, waitFor } from '@tests/utils';
|
||||
|
||||
import useModalQueryParams from '../useModalQueryParams';
|
||||
|
||||
/**
|
||||
* TODO: we should set up MSW for these tests
|
||||
*/
|
||||
function setup(...args) {
|
||||
return renderHook(() => useModalQueryParams(...args), {
|
||||
wrapper({ children }) {
|
||||
const client = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<QueryClientProvider client={client}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<NotificationsProvider>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
{children}
|
||||
</IntlProvider>
|
||||
</NotificationsProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const FIXTURE_QUERY = {
|
||||
page: 1,
|
||||
sort: 'updatedAt:DESC',
|
||||
@ -54,17 +16,15 @@ describe('useModalQueryParams', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('setup proper defaults', () => {
|
||||
const {
|
||||
result: {
|
||||
current: [{ queryObject, rawQuery }, callbacks],
|
||||
},
|
||||
} = setup();
|
||||
test('setup proper defaults', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
expect(queryObject).toStrictEqual(FIXTURE_QUERY);
|
||||
expect(rawQuery).toBe('page=1&sort=updatedAt:DESC&pageSize=10');
|
||||
expect(result.current[0].queryObject).toStrictEqual(FIXTURE_QUERY);
|
||||
expect(result.current[0].rawQuery).toMatchInlineSnapshot(
|
||||
`"page=1&sort=updatedAt:DESC&pageSize=10"`
|
||||
);
|
||||
|
||||
expect(callbacks).toStrictEqual({
|
||||
expect(result.current[1]).toStrictEqual({
|
||||
onChangeFilters: expect.any(Function),
|
||||
onChangeFolder: expect.any(Function),
|
||||
onChangePage: expect.any(Function),
|
||||
@ -72,29 +32,25 @@ describe('useModalQueryParams', () => {
|
||||
onChangeSort: expect.any(Function),
|
||||
onChangeSearch: expect.any(Function),
|
||||
});
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
});
|
||||
|
||||
test('set initial state', () => {
|
||||
const {
|
||||
result: { current },
|
||||
} = setup();
|
||||
test('handles initial state', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams({ state: true }));
|
||||
|
||||
expect(current[0].queryObject).toStrictEqual(FIXTURE_QUERY);
|
||||
});
|
||||
|
||||
test('handles initial state', () => {
|
||||
const {
|
||||
result: { current },
|
||||
} = setup({ state: true });
|
||||
|
||||
expect(current[0].queryObject).toStrictEqual({
|
||||
expect(result.current[0].queryObject).toStrictEqual({
|
||||
...FIXTURE_QUERY,
|
||||
state: true,
|
||||
});
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
});
|
||||
|
||||
test('onChangeFilters', () => {
|
||||
const { result } = setup();
|
||||
test('onChangeFilters', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangeFilters([{ some: 'thing' }]);
|
||||
@ -102,6 +58,7 @@ describe('useModalQueryParams', () => {
|
||||
|
||||
expect(result.current[0].queryObject).toStrictEqual({
|
||||
...FIXTURE_QUERY,
|
||||
pageSize: 20,
|
||||
filters: {
|
||||
...FIXTURE_QUERY.filters,
|
||||
$and: [
|
||||
@ -113,8 +70,10 @@ describe('useModalQueryParams', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('onChangeFolder', () => {
|
||||
const { result } = setup();
|
||||
test('onChangeFolder', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangeFolder({ id: 1 }, '/1');
|
||||
@ -122,6 +81,7 @@ describe('useModalQueryParams', () => {
|
||||
|
||||
expect(result.current[0].queryObject).toStrictEqual({
|
||||
...FIXTURE_QUERY,
|
||||
pageSize: 20,
|
||||
folder: {
|
||||
id: 1,
|
||||
},
|
||||
@ -129,8 +89,10 @@ describe('useModalQueryParams', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('onChangePage', () => {
|
||||
const { result } = setup();
|
||||
test('onChangePage', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangePage({ id: 1 });
|
||||
@ -138,14 +100,17 @@ describe('useModalQueryParams', () => {
|
||||
|
||||
expect(result.current[0].queryObject).toStrictEqual({
|
||||
...FIXTURE_QUERY,
|
||||
pageSize: 20,
|
||||
page: {
|
||||
id: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('onChangePageSize', () => {
|
||||
const { result } = setup();
|
||||
test('onChangePageSize', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangePageSize(5);
|
||||
@ -157,8 +122,10 @@ describe('useModalQueryParams', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('onChangePageSize - converts string to numbers', () => {
|
||||
const { result } = setup();
|
||||
test('onChangePageSize - converts string to numbers', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangePageSize('5');
|
||||
@ -170,8 +137,10 @@ describe('useModalQueryParams', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('onChangeSort', () => {
|
||||
const { result } = setup();
|
||||
test('onChangeSort', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangeSort('something:else');
|
||||
@ -179,12 +148,15 @@ describe('useModalQueryParams', () => {
|
||||
|
||||
expect(result.current[0].queryObject).toStrictEqual({
|
||||
...FIXTURE_QUERY,
|
||||
pageSize: 20,
|
||||
sort: 'something:else',
|
||||
});
|
||||
});
|
||||
|
||||
test('onChangeSearch', () => {
|
||||
const { result } = setup();
|
||||
test('onChangeSearch', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangeSearch('something');
|
||||
@ -192,12 +164,15 @@ describe('useModalQueryParams', () => {
|
||||
|
||||
expect(result.current[0].queryObject).toStrictEqual({
|
||||
...FIXTURE_QUERY,
|
||||
pageSize: 20,
|
||||
_q: 'something',
|
||||
});
|
||||
});
|
||||
|
||||
test('onChangeSearch - empty string resets all values and removes _q and page', () => {
|
||||
const { result } = setup();
|
||||
test('onChangeSearch - empty string resets all values and removes _q and page', async () => {
|
||||
const { result } = renderHook(() => useModalQueryParams());
|
||||
|
||||
await waitFor(() => expect(result.current[0].queryObject.pageSize).toBe(20));
|
||||
|
||||
act(() => {
|
||||
result.current[1].onChangePage({ id: 1 });
|
||||
@ -211,6 +186,9 @@ describe('useModalQueryParams', () => {
|
||||
result.current[1].onChangeSearch('');
|
||||
});
|
||||
|
||||
expect(result.current[0].queryObject).toStrictEqual(FIXTURE_QUERY);
|
||||
expect(result.current[0].queryObject).toStrictEqual({
|
||||
...FIXTURE_QUERY,
|
||||
pageSize: 20,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useFetchClient, useNotification, useTracking } from '@strapi/helper-plugin';
|
||||
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
import pluginId from '../pluginId';
|
||||
|
||||
@ -7,7 +7,6 @@ const endpoint = `/${pluginId}/configuration`;
|
||||
const queryKey = [pluginId, 'configuration'];
|
||||
|
||||
export const useConfig = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { trackUsage } = useTracking();
|
||||
const toggleNotification = useNotification();
|
||||
const { get, put } = useFetchClient();
|
||||
@ -33,18 +32,23 @@ export const useConfig = () => {
|
||||
}
|
||||
);
|
||||
|
||||
const putMutation = useMutation(async (body) => put(endpoint, body), {
|
||||
onSuccess() {
|
||||
trackUsage('didEditMediaLibraryConfig');
|
||||
queryClient.refetchQueries(queryKey, { active: true });
|
||||
const putMutation = useMutation(
|
||||
async (body) => {
|
||||
await put(endpoint, body);
|
||||
},
|
||||
onError() {
|
||||
return toggleNotification({
|
||||
type: 'warning',
|
||||
message: { id: 'notification.error' },
|
||||
});
|
||||
},
|
||||
});
|
||||
{
|
||||
onSuccess() {
|
||||
trackUsage('didEditMediaLibraryConfig');
|
||||
config.refetch();
|
||||
},
|
||||
onError() {
|
||||
return toggleNotification({
|
||||
type: 'warning',
|
||||
message: { id: 'notification.error' },
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
config,
|
||||
|
@ -4,45 +4,45 @@ import { useQuery } from 'react-query';
|
||||
import pluginId from '../pluginId';
|
||||
import { getTrad } from '../utils';
|
||||
|
||||
export const useFolder = (id, { enabled = true }) => {
|
||||
export const useFolder = (id, { enabled = true } = {}) => {
|
||||
const toggleNotification = useNotification();
|
||||
const { get } = useFetchClient();
|
||||
|
||||
const fetchFolder = async () => {
|
||||
try {
|
||||
const params = {
|
||||
populate: {
|
||||
parent: {
|
||||
populate: {
|
||||
parent: '*',
|
||||
const { data, error, isLoading } = useQuery(
|
||||
[pluginId, 'folder', id],
|
||||
async () => {
|
||||
const {
|
||||
data: { data },
|
||||
} = await get(`/upload/folders/${id}`, {
|
||||
params: {
|
||||
populate: {
|
||||
parent: {
|
||||
populate: {
|
||||
parent: '*',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const {
|
||||
data: { data },
|
||||
} = await get(`/upload/folders/${id}`, { params });
|
||||
|
||||
return data;
|
||||
} catch (err) {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: {
|
||||
id: getTrad('notification.warning.404'),
|
||||
defaultMessage: 'Not found',
|
||||
},
|
||||
});
|
||||
|
||||
throw err;
|
||||
return data;
|
||||
},
|
||||
{
|
||||
retry: false,
|
||||
enabled,
|
||||
staleTime: 0,
|
||||
cacheTime: 0,
|
||||
onError() {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: {
|
||||
id: getTrad('notification.warning.404'),
|
||||
defaultMessage: 'Not found',
|
||||
},
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const { data, error, isLoading } = useQuery([pluginId, 'folder', id], fetchFolder, {
|
||||
retry: false,
|
||||
enabled,
|
||||
staleTime: 0,
|
||||
cacheTime: 0,
|
||||
});
|
||||
);
|
||||
|
||||
return { data, error, isLoading };
|
||||
};
|
||||
|
@ -1,3 +1,5 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { useNotifyAT } from '@strapi/design-system';
|
||||
import { useFetchClient, useNotification } from '@strapi/helper-plugin';
|
||||
import { stringify } from 'qs';
|
||||
@ -6,7 +8,7 @@ import { useQuery } from 'react-query';
|
||||
|
||||
import pluginId from '../pluginId';
|
||||
|
||||
export const useFolders = ({ enabled = true, query = {} }) => {
|
||||
export const useFolders = ({ enabled = true, query = {} } = {}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const toggleNotification = useNotification();
|
||||
const { notifyStatus } = useNotifyAT();
|
||||
@ -44,39 +46,38 @@ export const useFolders = ({ enabled = true, query = {} }) => {
|
||||
};
|
||||
}
|
||||
|
||||
const fetchFolders = async () => {
|
||||
try {
|
||||
const { data, error, isLoading } = useQuery(
|
||||
[pluginId, 'folders', stringify(params)],
|
||||
async () => {
|
||||
const {
|
||||
data: { data },
|
||||
} = await get('/upload/folders', { params });
|
||||
|
||||
return data;
|
||||
},
|
||||
{
|
||||
enabled,
|
||||
staleTime: 0,
|
||||
cacheTime: 0,
|
||||
onError() {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: { id: 'notification.error' },
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (data) {
|
||||
notifyStatus(
|
||||
formatMessage({
|
||||
id: 'list.asset.at.finished',
|
||||
defaultMessage: 'The folders have finished loading.',
|
||||
})
|
||||
);
|
||||
|
||||
return data;
|
||||
} catch (err) {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: { id: 'notification.error' },
|
||||
});
|
||||
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const { data, error, isLoading } = useQuery(
|
||||
[pluginId, 'folders', stringify(params)],
|
||||
fetchFolders,
|
||||
{
|
||||
enabled,
|
||||
staleTime: 0,
|
||||
cacheTime: 0,
|
||||
}
|
||||
);
|
||||
}, [data, formatMessage, notifyStatus]);
|
||||
|
||||
return { data, error, isLoading };
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useReducer, useRef } from 'react';
|
||||
import React, { useReducer } from 'react';
|
||||
|
||||
import {
|
||||
Box,
|
||||
@ -22,10 +22,10 @@ import {
|
||||
useOverlayBlocker,
|
||||
} from '@strapi/helper-plugin';
|
||||
import { Check } from '@strapi/icons';
|
||||
import axios from 'axios';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
import { PERMISSIONS } from '../../constants';
|
||||
import { getTrad } from '../../utils';
|
||||
@ -38,50 +38,50 @@ export const SettingsPage = () => {
|
||||
const { lockApp, unlockApp } = useOverlayBlocker();
|
||||
const toggleNotification = useNotification();
|
||||
const { get, put } = useFetchClient();
|
||||
|
||||
useFocusWhenNavigate();
|
||||
|
||||
const [{ initialData, isLoading, isSubmiting, modifiedData }, dispatch] = useReducer(
|
||||
reducer,
|
||||
initialState,
|
||||
init
|
||||
);
|
||||
const [{ initialData, modifiedData }, dispatch] = useReducer(reducer, initialState, init);
|
||||
|
||||
const isMounted = useRef(true);
|
||||
const { data, isLoading, refetch } = useQuery({
|
||||
queryKey: ['upload', 'settings'],
|
||||
async queryFn() {
|
||||
const {
|
||||
data: { data },
|
||||
} = await get('/upload/settings');
|
||||
|
||||
useEffect(() => {
|
||||
const CancelToken = axios.CancelToken;
|
||||
const source = CancelToken.source();
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
const getData = async () => {
|
||||
try {
|
||||
const {
|
||||
data: { data },
|
||||
} = await get('/upload/settings', {
|
||||
cancelToken: source.token,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: 'GET_DATA_SUCCEEDED',
|
||||
data,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
if (isMounted.current) {
|
||||
getData();
|
||||
React.useEffect(() => {
|
||||
if (data) {
|
||||
dispatch({
|
||||
type: 'GET_DATA_SUCCEEDED',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
source.cancel('Operation canceled by the user.');
|
||||
isMounted.current = false;
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [data]);
|
||||
|
||||
const isSaveButtonDisabled = isEqual(initialData, modifiedData);
|
||||
|
||||
const { mutateAsync, isLoading: isSubmiting } = useMutation({
|
||||
async mutationFn(body) {
|
||||
return put('/upload/settings', body);
|
||||
},
|
||||
onSuccess() {
|
||||
refetch();
|
||||
|
||||
toggleNotification({
|
||||
type: 'success',
|
||||
message: { id: 'notification.form.success.fields' },
|
||||
});
|
||||
},
|
||||
onError(err) {
|
||||
console.error(err);
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@ -91,24 +91,7 @@ export const SettingsPage = () => {
|
||||
|
||||
lockApp();
|
||||
|
||||
dispatch({ type: 'ON_SUBMIT' });
|
||||
|
||||
try {
|
||||
await put('/upload/settings', modifiedData);
|
||||
|
||||
dispatch({
|
||||
type: 'SUBMIT_SUCCEEDED',
|
||||
});
|
||||
|
||||
toggleNotification({
|
||||
type: 'success',
|
||||
message: { id: 'notification.form.success.fields' },
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
dispatch({ type: 'ON_SUBMIT_ERROR' });
|
||||
}
|
||||
await mutateAsync(modifiedData);
|
||||
|
||||
unlockApp();
|
||||
};
|
||||
@ -138,7 +121,6 @@ export const SettingsPage = () => {
|
||||
primaryAction={
|
||||
<Button
|
||||
disabled={isSaveButtonDisabled}
|
||||
data-testid="save-button"
|
||||
loading={isSubmiting}
|
||||
type="submit"
|
||||
startIcon={<Check />}
|
||||
@ -175,7 +157,6 @@ export const SettingsPage = () => {
|
||||
<GridItem col={6} s={12}>
|
||||
<ToggleInput
|
||||
aria-label="responsiveDimensions"
|
||||
data-testid="responsiveDimensions"
|
||||
checked={modifiedData.responsiveDimensions}
|
||||
hint={formatMessage({
|
||||
id: getTrad('settings.form.responsiveDimensions.description'),
|
||||
@ -205,7 +186,6 @@ export const SettingsPage = () => {
|
||||
<GridItem col={6} s={12}>
|
||||
<ToggleInput
|
||||
aria-label="sizeOptimization"
|
||||
data-testid="sizeOptimization"
|
||||
checked={modifiedData.sizeOptimization}
|
||||
hint={formatMessage({
|
||||
id: getTrad('settings.form.sizeOptimization.description'),
|
||||
@ -235,7 +215,6 @@ export const SettingsPage = () => {
|
||||
<GridItem col={6} s={12}>
|
||||
<ToggleInput
|
||||
aria-label="autoOrientation"
|
||||
data-testid="autoOrientation"
|
||||
checked={modifiedData.autoOrientation}
|
||||
hint={formatMessage({
|
||||
id: getTrad('settings.form.autoOrientation.description'),
|
||||
|
@ -2,8 +2,6 @@ import produce from 'immer';
|
||||
import set from 'lodash/set';
|
||||
|
||||
const initialState = {
|
||||
isLoading: true,
|
||||
isSubmiting: false,
|
||||
initialData: {
|
||||
responsiveDimensions: true,
|
||||
sizeOptimization: true,
|
||||
@ -22,12 +20,7 @@ const reducer = (state, action) =>
|
||||
// eslint-disable-next-line consistent-return
|
||||
produce(state, (drafState) => {
|
||||
switch (action.type) {
|
||||
case 'CANCEL_CHANGES': {
|
||||
drafState.modifiedData = state.initialData;
|
||||
break;
|
||||
}
|
||||
case 'GET_DATA_SUCCEEDED': {
|
||||
drafState.isLoading = false;
|
||||
drafState.initialData = action.data;
|
||||
drafState.modifiedData = action.data;
|
||||
break;
|
||||
@ -36,19 +29,6 @@ const reducer = (state, action) =>
|
||||
set(drafState, ['modifiedData', ...action.keys.split('.')], action.value);
|
||||
break;
|
||||
}
|
||||
case 'ON_SUBMIT': {
|
||||
drafState.isSubmiting = true;
|
||||
break;
|
||||
}
|
||||
case 'SUBMIT_SUCCEEDED': {
|
||||
drafState.initialData = state.modifiedData;
|
||||
drafState.isSubmiting = false;
|
||||
break;
|
||||
}
|
||||
case 'ON_SUBMIT_ERROR': {
|
||||
drafState.isSubmiting = false;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -1,874 +1,34 @@
|
||||
/**
|
||||
*
|
||||
* Tests for SettingsPage
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { lightTheme, ThemeProvider } from '@strapi/design-system';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { render, waitFor } from '@tests/utils';
|
||||
|
||||
import { SettingsPage } from '../index';
|
||||
|
||||
import server from './utils/server';
|
||||
describe('SettingsPage', () => {
|
||||
it('renders', async () => {
|
||||
const { getByRole, queryByText } = render(<SettingsPage />);
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
useNotification: jest.fn(),
|
||||
useOverlayBlocker: () => ({ lockApp: jest.fn(), unlockApp: jest.fn() }),
|
||||
useFocusWhenNavigate: jest.fn(),
|
||||
}));
|
||||
await waitFor(() => expect(queryByText('Loading content.')).not.toBeInTheDocument());
|
||||
|
||||
const App = (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<IntlProvider locale="en" messages={{}} textComponent="span">
|
||||
<SettingsPage />
|
||||
</IntlProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
expect(getByRole('heading', { name: 'Media Library' })).toBeInTheDocument();
|
||||
expect(getByRole('heading', { name: 'Asset management' })).toBeInTheDocument();
|
||||
|
||||
describe('Upload | SettingsPage', () => {
|
||||
beforeAll(() => server.listen());
|
||||
expect(getByRole('button', { name: 'Save' })).toBeInTheDocument();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => server.resetHandlers());
|
||||
|
||||
afterAll(() => server.close());
|
||||
|
||||
it('renders and matches the snapshot', async () => {
|
||||
const { container, getByText } = render(App);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
getByText(
|
||||
'Enabling this option will automatically rotate the image according to EXIF orientation tag.'
|
||||
)
|
||||
).toBeInTheDocument()
|
||||
);
|
||||
|
||||
expect(container).toMatchInlineSnapshot(`
|
||||
.c41 {
|
||||
border: 0;
|
||||
-webkit-clip: rect(0 0 0 0);
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
font-weight: 600;
|
||||
font-size: 2rem;
|
||||
line-height: 1.25;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c11 {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c20 {
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
line-height: 1.25;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c25 {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
font-weight: 600;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c33 {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
font-weight: 600;
|
||||
color: #666687;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.c35 {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
font-weight: 600;
|
||||
color: #4945ff;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.c37 {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c39 {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
font-weight: 600;
|
||||
color: #b72b1a;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
background: #f6f6f9;
|
||||
padding-top: 40px;
|
||||
padding-right: 56px;
|
||||
padding-bottom: 40px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
background: #4945ff;
|
||||
padding: 8px;
|
||||
padding-right: 16px;
|
||||
padding-left: 16px;
|
||||
border-radius: 4px;
|
||||
border-color: #4945ff;
|
||||
border: 1px solid #4945ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
padding-right: 56px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c15 {
|
||||
padding-bottom: 56px;
|
||||
}
|
||||
|
||||
.c18 {
|
||||
background: #ffffff;
|
||||
padding: 24px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 1px 4px rgba(33,33,52,0.1);
|
||||
}
|
||||
|
||||
.c23 {
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.c27 {
|
||||
background: #f6f6f9;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: #dcdce4;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.c30 {
|
||||
background: transparent;
|
||||
padding-top: 8px;
|
||||
padding-right: 12px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 12px;
|
||||
border-radius: 4px;
|
||||
border-color: #f6f6f9;
|
||||
border: 1px solid #f6f6f9;
|
||||
-webkit-flex: 1 1 50%;
|
||||
-ms-flex: 1 1 50%;
|
||||
flex: 1 1 50%;
|
||||
}
|
||||
|
||||
.c34 {
|
||||
background: #ffffff;
|
||||
padding-right: 12px;
|
||||
padding-left: 12px;
|
||||
border-radius: 4px;
|
||||
border-color: #dcdce4;
|
||||
border: 1px solid #dcdce4;
|
||||
-webkit-flex: 1 1 50%;
|
||||
-ms-flex: 1 1 50%;
|
||||
flex: 1 1 50%;
|
||||
}
|
||||
|
||||
.c38 {
|
||||
background: #ffffff;
|
||||
padding-top: 8px;
|
||||
padding-right: 12px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 12px;
|
||||
border-radius: 4px;
|
||||
border-color: #dcdce4;
|
||||
border: 1px solid #dcdce4;
|
||||
-webkit-flex: 1 1 50%;
|
||||
-ms-flex: 1 1 50%;
|
||||
flex: 1 1 50%;
|
||||
}
|
||||
|
||||
.c40 {
|
||||
background: transparent;
|
||||
padding-right: 12px;
|
||||
padding-left: 12px;
|
||||
border-radius: 4px;
|
||||
border-color: #f6f6f9;
|
||||
border: 1px solid #f6f6f9;
|
||||
-webkit-flex: 1 1 50%;
|
||||
-ms-flex: 1 1 50%;
|
||||
flex: 1 1 50%;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.c17 {
|
||||
-webkit-align-items: stretch;
|
||||
-webkit-box-align: stretch;
|
||||
-ms-flex-align: stretch;
|
||||
align-items: stretch;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
gap: 12;
|
||||
}
|
||||
|
||||
.c19 {
|
||||
-webkit-align-items: stretch;
|
||||
-webkit-box-align: stretch;
|
||||
-ms-flex-align: stretch;
|
||||
align-items: stretch;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.c24 {
|
||||
-webkit-align-items: stretch;
|
||||
-webkit-box-align: stretch;
|
||||
-ms-flex-align: stretch;
|
||||
align-items: stretch;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.c28 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-flex-wrap: wrap;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.c31 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.c9 {
|
||||
position: relative;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c9 > svg {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.c9 > svg > g,
|
||||
.c9 > svg path {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.c9[aria-disabled='true'] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.c9:after {
|
||||
-webkit-transition-property: all;
|
||||
transition-property: all;
|
||||
-webkit-transition-duration: 0.2s;
|
||||
transition-duration: 0.2s;
|
||||
border-radius: 8px;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
bottom: -4px;
|
||||
left: -4px;
|
||||
right: -4px;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.c9:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c9:focus-visible:after {
|
||||
border-radius: 8px;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
bottom: -5px;
|
||||
left: -5px;
|
||||
right: -5px;
|
||||
border: 2px solid #4945ff;
|
||||
}
|
||||
|
||||
.c10 {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.c10 svg {
|
||||
height: 0.75rem;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.c10[aria-disabled='true'] {
|
||||
border: 1px solid #dcdce4;
|
||||
background: #eaeaef;
|
||||
}
|
||||
|
||||
.c10[aria-disabled='true'] .c5 {
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c10[aria-disabled='true'] svg > g,
|
||||
.c10[aria-disabled='true'] svg path {
|
||||
fill: #666687;
|
||||
}
|
||||
|
||||
.c10[aria-disabled='true']:active {
|
||||
border: 1px solid #dcdce4;
|
||||
background: #eaeaef;
|
||||
}
|
||||
|
||||
.c10[aria-disabled='true']:active .c5 {
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c10[aria-disabled='true']:active svg > g,
|
||||
.c10[aria-disabled='true']:active svg path {
|
||||
fill: #666687;
|
||||
}
|
||||
|
||||
.c10:hover {
|
||||
border: 1px solid #7b79ff;
|
||||
background: #7b79ff;
|
||||
}
|
||||
|
||||
.c10:active {
|
||||
border: 1px solid #4945ff;
|
||||
background: #4945ff;
|
||||
}
|
||||
|
||||
.c10 svg > g,
|
||||
.c10 svg path {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.c26 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c21 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12,1fr);
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.c22 {
|
||||
grid-column: span 6;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.c14 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.c16 {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.c0:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c29 {
|
||||
outline: none;
|
||||
box-shadow: 0;
|
||||
-webkit-transition-property: border-color,box-shadow,fill;
|
||||
transition-property: border-color,box-shadow,fill;
|
||||
-webkit-transition-duration: 0.2s;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
.c29:focus-within {
|
||||
border: 1px solid #4945ff;
|
||||
box-shadow: #4945ff 0px 0px 0px 2px;
|
||||
}
|
||||
|
||||
.c32 {
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.c36 {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width:68.75rem) {
|
||||
.c22 {
|
||||
grid-column: span 12;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:34.375rem) {
|
||||
.c22 {
|
||||
grid-column: span;
|
||||
}
|
||||
}
|
||||
|
||||
<div>
|
||||
<main
|
||||
aria-labelledby="main-content-title"
|
||||
class="c0"
|
||||
id="main-content"
|
||||
tabindex="-1"
|
||||
>
|
||||
<form>
|
||||
<div
|
||||
style="height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="c1"
|
||||
data-strapi-header="true"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
>
|
||||
<h1
|
||||
class="c5 c6"
|
||||
>
|
||||
Media Library
|
||||
</h1>
|
||||
</div>
|
||||
<button
|
||||
aria-disabled="true"
|
||||
class="c7 c8 c9 c10"
|
||||
data-testid="save-button"
|
||||
disabled=""
|
||||
type="submit"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="1rem"
|
||||
viewBox="0 0 24 24"
|
||||
width="1rem"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M20.727 2.97a.2.2 0 0 1 .286 0l2.85 2.89a.2.2 0 0 1 0 .28L9.554 20.854a.2.2 0 0 1-.285 0l-9.13-9.243a.2.2 0 0 1 0-.281l2.85-2.892a.2.2 0 0 1 .284 0l6.14 6.209L20.726 2.97Z"
|
||||
fill="#212134"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span
|
||||
class="c5 c11"
|
||||
>
|
||||
Save
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
class="c5 c12"
|
||||
>
|
||||
Configure the settings for the Media Library
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c13"
|
||||
>
|
||||
<div
|
||||
class="c14"
|
||||
>
|
||||
<div
|
||||
class="c15 c16"
|
||||
>
|
||||
<div
|
||||
class="c17"
|
||||
>
|
||||
<div
|
||||
class="c18"
|
||||
>
|
||||
<div
|
||||
class="c19"
|
||||
>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
<h2
|
||||
class="c5 c20"
|
||||
>
|
||||
Asset management
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
class="c21"
|
||||
>
|
||||
<div
|
||||
class="c22"
|
||||
>
|
||||
<div
|
||||
class="c23"
|
||||
>
|
||||
<div
|
||||
class="c24"
|
||||
>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
<label
|
||||
class="c5 c25 c26"
|
||||
for=":r0:"
|
||||
>
|
||||
Responsive friendly upload
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="c27 c28 c29"
|
||||
wrap="wrap"
|
||||
>
|
||||
<div
|
||||
class="c30 c31 c32"
|
||||
>
|
||||
<span
|
||||
class="c5 c33"
|
||||
>
|
||||
Off
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="c34 c31 c32"
|
||||
>
|
||||
<span
|
||||
class="c5 c35"
|
||||
>
|
||||
On
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
aria-describedby=":r0:-hint :r0:-error"
|
||||
aria-disabled="false"
|
||||
aria-label="responsiveDimensions"
|
||||
aria-required="false"
|
||||
checked=""
|
||||
class="c36"
|
||||
data-testid="responsiveDimensions"
|
||||
id=":r0:"
|
||||
name="responsiveDimensions"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
class="c5 c37"
|
||||
id=":r0:-hint"
|
||||
>
|
||||
Enabling this option will generate multiple formats (small, medium and large) of the uploaded asset.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c22"
|
||||
>
|
||||
<div
|
||||
class="c23"
|
||||
>
|
||||
<div
|
||||
class="c24"
|
||||
>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
<label
|
||||
class="c5 c25 c26"
|
||||
for=":r2:"
|
||||
>
|
||||
Size optimization
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="c27 c28 c29"
|
||||
wrap="wrap"
|
||||
>
|
||||
<div
|
||||
class="c38 c31 c32"
|
||||
>
|
||||
<span
|
||||
class="c5 c39"
|
||||
>
|
||||
Off
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="c40 c31 c32"
|
||||
>
|
||||
<span
|
||||
class="c5 c33"
|
||||
>
|
||||
On
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
aria-describedby=":r2:-hint :r2:-error"
|
||||
aria-disabled="false"
|
||||
aria-label="sizeOptimization"
|
||||
aria-required="false"
|
||||
class="c36"
|
||||
data-testid="sizeOptimization"
|
||||
id=":r2:"
|
||||
name="sizeOptimization"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
class="c5 c37"
|
||||
id=":r2:-hint"
|
||||
>
|
||||
Enabling this option will reduce the image size and slightly reduce its quality.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c22"
|
||||
>
|
||||
<div
|
||||
class="c23"
|
||||
>
|
||||
<div
|
||||
class="c24"
|
||||
>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
<label
|
||||
class="c5 c25 c26"
|
||||
for=":r4:"
|
||||
>
|
||||
Auto orientation
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="c27 c28 c29"
|
||||
wrap="wrap"
|
||||
>
|
||||
<div
|
||||
class="c30 c31 c32"
|
||||
>
|
||||
<span
|
||||
class="c5 c33"
|
||||
>
|
||||
Off
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="c34 c31 c32"
|
||||
>
|
||||
<span
|
||||
class="c5 c35"
|
||||
>
|
||||
On
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
aria-describedby=":r4:-hint :r4:-error"
|
||||
aria-disabled="false"
|
||||
aria-label="autoOrientation"
|
||||
aria-required="false"
|
||||
checked=""
|
||||
class="c36"
|
||||
data-testid="autoOrientation"
|
||||
id=":r4:"
|
||||
name="autoOrientation"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
class="c5 c37"
|
||||
id=":r4:-hint"
|
||||
>
|
||||
Enabling this option will automatically rotate the image according to EXIF orientation tag.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
<div
|
||||
class="c41"
|
||||
>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-log"
|
||||
role="log"
|
||||
/>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-status"
|
||||
role="status"
|
||||
/>
|
||||
<p
|
||||
aria-live="assertive"
|
||||
aria-relevant="all"
|
||||
id="live-region-alert"
|
||||
role="alert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
expect(getByRole('checkbox', { name: 'responsiveDimensions' })).toBeInTheDocument();
|
||||
expect(getByRole('checkbox', { name: 'sizeOptimization' })).toBeInTheDocument();
|
||||
expect(getByRole('checkbox', { name: 'autoOrientation' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display the form correctly with the initial values', async () => {
|
||||
const { getByTestId } = render(App);
|
||||
const { getByRole, queryByText } = render(<SettingsPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
const responsiveDimension = getByTestId('responsiveDimensions');
|
||||
const sizeOptimization = getByTestId('sizeOptimization');
|
||||
const autoOrientation = getByTestId('autoOrientation');
|
||||
const saveButton = getByTestId('save-button');
|
||||
await waitFor(() => expect(queryByText('Loading content.')).not.toBeInTheDocument());
|
||||
|
||||
expect(responsiveDimension.checked).toBe(true);
|
||||
expect(autoOrientation.checked).toBe(true);
|
||||
expect(sizeOptimization.checked).toBe(false);
|
||||
expect(saveButton).toBeDisabled();
|
||||
});
|
||||
expect(getByRole('button', { name: 'Save' })).toBeDisabled();
|
||||
|
||||
expect(getByRole('checkbox', { name: 'responsiveDimensions' })).toBeChecked();
|
||||
expect(getByRole('checkbox', { name: 'sizeOptimization' })).toBeChecked();
|
||||
expect(getByRole('checkbox', { name: 'autoOrientation' })).toBeChecked();
|
||||
});
|
||||
});
|
||||
|
@ -1,24 +1,6 @@
|
||||
import reducer from '../reducer';
|
||||
|
||||
describe('MEDIA LIBRARY | pages | SettingsPage | reducer', () => {
|
||||
describe('CANCEL_CHANGES', () => {
|
||||
it('should set the modifiedData with the initialData', () => {
|
||||
const action = {
|
||||
type: 'CANCEL_CHANGES',
|
||||
};
|
||||
const state = {
|
||||
initialData: 'test',
|
||||
modifiedData: 'new test',
|
||||
};
|
||||
const expected = {
|
||||
initialData: 'test',
|
||||
modifiedData: 'test',
|
||||
};
|
||||
|
||||
expect(reducer(state, action)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SettingsPage | reducer', () => {
|
||||
describe('GET_DATA_SUCCEEDED', () => {
|
||||
it('should set the modifiedData and the initialData correctly', () => {
|
||||
const action = {
|
||||
@ -27,12 +9,10 @@ describe('MEDIA LIBRARY | pages | SettingsPage | reducer', () => {
|
||||
};
|
||||
const state = {
|
||||
initialData: null,
|
||||
isLoading: true,
|
||||
modifiedData: null,
|
||||
};
|
||||
const expected = {
|
||||
initialData: { test: true },
|
||||
isLoading: false,
|
||||
modifiedData: { test: true },
|
||||
};
|
||||
|
||||
@ -51,40 +31,12 @@ describe('MEDIA LIBRARY | pages | SettingsPage | reducer', () => {
|
||||
initialData: {
|
||||
responsiveDimensions: true,
|
||||
},
|
||||
isLoading: false,
|
||||
modifiedData: {
|
||||
responsiveDimensions: true,
|
||||
},
|
||||
};
|
||||
const expected = {
|
||||
initialData: { responsiveDimensions: true },
|
||||
isLoading: false,
|
||||
modifiedData: { responsiveDimensions: false },
|
||||
};
|
||||
|
||||
expect(reducer(state, action)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SUBMIT_SUCCEEDED', () => {
|
||||
it('should set the initialData with the modifiedData correctly', () => {
|
||||
const action = {
|
||||
type: 'SUBMIT_SUCCEEDED',
|
||||
};
|
||||
const state = {
|
||||
initialData: {
|
||||
responsiveDimensions: true,
|
||||
},
|
||||
isLoading: false,
|
||||
isSubmiting: true,
|
||||
modifiedData: {
|
||||
responsiveDimensions: false,
|
||||
},
|
||||
};
|
||||
const expected = {
|
||||
initialData: { responsiveDimensions: false },
|
||||
isLoading: false,
|
||||
isSubmiting: false,
|
||||
modifiedData: { responsiveDimensions: false },
|
||||
};
|
||||
|
||||
|
@ -1,17 +0,0 @@
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
|
||||
const handlers = [
|
||||
rest.get('*/settings', (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
data: { autoOrientation: true, responsiveDimensions: true, sizeOptimization: false },
|
||||
})
|
||||
);
|
||||
}),
|
||||
];
|
||||
|
||||
const server = setupServer(...handlers);
|
||||
|
||||
export default server;
|
@ -1,28 +1,6 @@
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
|
||||
import { downloadFile } from '../downloadFile';
|
||||
|
||||
const server = setupServer(
|
||||
rest.get('*/some/file', async (req, res, ctx) => {
|
||||
const file = new File([new Blob(['1'.repeat(1024 * 1024 + 1)])], 'image.png', {
|
||||
type: 'image/png',
|
||||
});
|
||||
const buffer = await new Response(file).arrayBuffer();
|
||||
|
||||
return res(ctx.set('Content-Type', 'image/png'), ctx.body(buffer));
|
||||
})
|
||||
);
|
||||
|
||||
describe('Upload | utils | downloadFile', () => {
|
||||
beforeAll(() => {
|
||||
server.listen();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
server.close();
|
||||
});
|
||||
|
||||
describe('downloadFile', () => {
|
||||
test('Download target as blob', async () => {
|
||||
const setAttributeSpy = jest.fn();
|
||||
const clickSpy = jest.fn();
|
||||
|
192
packages/core/upload/admin/tests/server.js
Normal file
192
packages/core/upload/admin/tests/server.js
Normal file
@ -0,0 +1,192 @@
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import qs from 'qs';
|
||||
|
||||
export const server = setupServer(
|
||||
...[
|
||||
rest.get('/upload/configuration', async (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.json({
|
||||
data: {
|
||||
/**
|
||||
* we send the pageSize slightly different to defaults because
|
||||
* in tests we can track that the async functions have finished.
|
||||
*/
|
||||
pageSize: 20,
|
||||
sort: 'updatedAt:DESC',
|
||||
},
|
||||
})
|
||||
);
|
||||
}),
|
||||
rest.put('/upload/configuration', async (req, res, ctx) => {
|
||||
return res(ctx.status(200));
|
||||
}),
|
||||
rest.get('/upload/folders/:id', async (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.json({
|
||||
data: {
|
||||
id: 1,
|
||||
name: 'test',
|
||||
pathId: 1,
|
||||
path: '/1',
|
||||
createdAt: '2023-06-26T12:48:54.054Z',
|
||||
updatedAt: '2023-06-26T12:48:54.054Z',
|
||||
parent: null,
|
||||
children: {
|
||||
count: 2,
|
||||
},
|
||||
files: {
|
||||
count: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
}),
|
||||
rest.get('/upload/folders', async (req, res, ctx) => {
|
||||
const query = qs.parse(req.url.search.slice(1));
|
||||
|
||||
if (query._q) {
|
||||
return res(
|
||||
ctx.json({
|
||||
data: [
|
||||
{
|
||||
createdAt: '2023-06-26T12:48:54.054Z',
|
||||
id: 1,
|
||||
name: query._q,
|
||||
pathId: 1,
|
||||
path: '/1',
|
||||
updatedAt: '2023-06-26T12:48:54.054Z',
|
||||
children: {
|
||||
count: 2,
|
||||
},
|
||||
files: {
|
||||
count: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(query.filters?.$and)) {
|
||||
const [{ parent }] = query.filters.$and;
|
||||
|
||||
if (parent.id === '1') {
|
||||
return res(
|
||||
ctx.json({
|
||||
data: [
|
||||
{
|
||||
createdAt: '2023-06-26T12:49:31.354Z',
|
||||
id: 3,
|
||||
name: '2022',
|
||||
pathId: 3,
|
||||
path: '/1/3',
|
||||
updatedAt: '2023-06-26T12:49:31.354Z',
|
||||
children: {
|
||||
count: 0,
|
||||
},
|
||||
files: {
|
||||
count: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
createdAt: '2023-06-26T12:49:08.466Z',
|
||||
id: 2,
|
||||
name: '2023',
|
||||
pathId: 2,
|
||||
path: '/1/2',
|
||||
updatedAt: '2023-06-26T12:49:08.466Z',
|
||||
children: {
|
||||
count: 0,
|
||||
},
|
||||
files: {
|
||||
count: 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return res(
|
||||
ctx.json({
|
||||
data: [
|
||||
{
|
||||
createdAt: '2023-06-26T12:48:54.054Z',
|
||||
id: 1,
|
||||
name: 'test',
|
||||
pathId: 1,
|
||||
path: '/1',
|
||||
updatedAt: '2023-06-26T12:48:54.054Z',
|
||||
children: {
|
||||
count: 2,
|
||||
},
|
||||
files: {
|
||||
count: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
}),
|
||||
|
||||
rest.get('*/some/file', async (req, res, ctx) => {
|
||||
const file = new File([new Blob(['1'.repeat(1024 * 1024 + 1)])], 'image.png', {
|
||||
type: 'image/png',
|
||||
});
|
||||
const buffer = await new Response(file).arrayBuffer();
|
||||
|
||||
return res(ctx.set('Content-Type', 'image/png'), ctx.body(buffer));
|
||||
}),
|
||||
|
||||
rest.get('/upload/settings', async (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.json({
|
||||
data: {
|
||||
sizeOptimization: true,
|
||||
responsiveDimensions: true,
|
||||
autoOrientation: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
}),
|
||||
|
||||
rest.get('/upload/folder-structure', (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.json({
|
||||
data: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'test',
|
||||
children: [
|
||||
{
|
||||
id: 3,
|
||||
name: '2022',
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '2023',
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
}),
|
||||
|
||||
rest.get('*/an-image.png', (req, res, ctx) =>
|
||||
res(ctx.set('Content-Type', 'image/png'), ctx.body())
|
||||
),
|
||||
rest.get('*/a-pdf.pdf', (req, res, ctx) =>
|
||||
res(ctx.set('Content-Type', 'application/pdf'), ctx.body())
|
||||
),
|
||||
rest.get('*/a-video.mp4', (req, res, ctx) =>
|
||||
res(ctx.set('Content-Type', 'video/mp4'), ctx.body())
|
||||
),
|
||||
rest.get('*/not-working-like-cors.lutin', (req, res, ctx) => res(ctx.json({}))),
|
||||
rest.get('*/some-where-not-existing.jpg', (req, res) => res.networkError('Failed to fetch')),
|
||||
]
|
||||
);
|
13
packages/core/upload/admin/tests/setup.js
Normal file
13
packages/core/upload/admin/tests/setup.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { server } from './server';
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
server.close();
|
||||
});
|
97
packages/core/upload/admin/tests/utils.jsx
Normal file
97
packages/core/upload/admin/tests/utils.jsx
Normal file
@ -0,0 +1,97 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { fixtures } from '@strapi/admin-test-utils';
|
||||
import { DesignSystemProvider } from '@strapi/design-system';
|
||||
import { RBACContext, NotificationsProvider } from '@strapi/helper-plugin';
|
||||
import {
|
||||
renderHook as renderHookRTL,
|
||||
render as renderRTL,
|
||||
waitFor,
|
||||
act,
|
||||
fireEvent,
|
||||
screen,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import PropTypes from 'prop-types';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import { server } from './server';
|
||||
|
||||
const Providers = ({ children, initialEntries }) => {
|
||||
const rbacContextValue = React.useMemo(
|
||||
() => ({
|
||||
allPermissions: fixtures.permissions.allPermissions,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
// no more errors on the console for tests
|
||||
onError() {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
// en is the default locale of the admin app.
|
||||
<MemoryRouter initialEntries={initialEntries}>
|
||||
<IntlProvider locale="en" textComponent="span">
|
||||
<DesignSystemProvider locale="en">
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RBACContext.Provider value={rbacContextValue}>
|
||||
<NotificationsProvider>{children}</NotificationsProvider>
|
||||
</RBACContext.Provider>
|
||||
</QueryClientProvider>
|
||||
</DesignSystemProvider>
|
||||
</IntlProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
Providers.defaultProps = {
|
||||
initialEntries: undefined,
|
||||
};
|
||||
|
||||
Providers.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
initialEntries: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object])),
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
const fallbackWrapper = ({ children }) => <>{children}</>;
|
||||
|
||||
const render = (ui, { renderOptions, userEventOptions, initialEntries } = {}) => {
|
||||
const { wrapper: Wrapper = fallbackWrapper, ...restOptions } = renderOptions ?? {};
|
||||
|
||||
return {
|
||||
...renderRTL(ui, {
|
||||
wrapper: ({ children }) => (
|
||||
<Providers initialEntries={initialEntries}>
|
||||
<Wrapper>{children}</Wrapper>
|
||||
</Providers>
|
||||
),
|
||||
...restOptions,
|
||||
}),
|
||||
user: userEvent.setup(userEventOptions),
|
||||
};
|
||||
};
|
||||
|
||||
const renderHook = (hook, options) => {
|
||||
const { wrapper: Wrapper = fallbackWrapper, ...restOptions } = options ?? {};
|
||||
|
||||
return renderHookRTL(hook, {
|
||||
wrapper: ({ children }) => (
|
||||
<Providers>
|
||||
<Wrapper>{children}</Wrapper>
|
||||
</Providers>
|
||||
),
|
||||
...restOptions,
|
||||
});
|
||||
};
|
||||
|
||||
export { render, renderHook, waitFor, server, act, fireEvent, screen };
|
@ -3,4 +3,8 @@
|
||||
module.exports = {
|
||||
preset: '../../../jest-preset.front.js',
|
||||
displayName: 'Core upload',
|
||||
moduleNameMapper: {
|
||||
'^@tests/(.*)$': '<rootDir>/admin/tests/$1',
|
||||
},
|
||||
setupFilesAfterEnv: ['./admin/tests/setup.js'],
|
||||
};
|
||||
|
8
packages/core/upload/jsconfig.json
Normal file
8
packages/core/upload/jsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@tests/*": ["./admin/tests/*"]
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user