Fix after PR feedback

Signed-off-by: HichamELBSI <elabbassih@gmail.com>
This commit is contained in:
HichamELBSI 2021-06-15 17:37:31 +02:00
parent a68384567f
commit e73a145535
9 changed files with 622 additions and 8 deletions

View File

@ -0,0 +1,57 @@
import selectMenuLinks from '../selectors';
import { fixtures } from '../../../../../../../admin-test-utils';
describe('CONTENT MANAGER | Containers | CollectionTypeFormWrapper | selectors', () => {
let store;
beforeEach(() => {
store = { ...fixtures.store.state };
});
describe('selectMenuLinks', () => {
it('should extract the collection type links from the global state', () => {
store['content-manager_app'] = {
collectionTypeLinks: [
{
kind: 'collectionType',
name: 'application::address.address',
search: 'page=1&pageSize=50&_sort=city:ASC&plugins[i18n][locale]=fr',
title: 'Addresses',
to: '/plugins/content-manager/collectionType/application::address.address',
uid: 'application::address.address',
},
{
kind: 'collectionType',
name: 'application::category.category',
search: 'page=1&pageSize=50&_sort=city:ASC&plugins[i18n][locale]=fr',
title: 'Categories',
to: '/plugins/content-manager/collectionType/application::category.category',
uid: 'application::category.category',
},
],
};
const actual = selectMenuLinks(store);
const expected = [
{
kind: 'collectionType',
name: 'application::address.address',
search: 'page=1&pageSize=50&_sort=city:ASC&plugins[i18n][locale]=fr',
title: 'Addresses',
to: '/plugins/content-manager/collectionType/application::address.address',
uid: 'application::address.address',
},
{
kind: 'collectionType',
name: 'application::category.category',
search: 'page=1&pageSize=50&_sort=city:ASC&plugins[i18n][locale]=fr',
title: 'Categories',
to: '/plugins/content-manager/collectionType/application::category.category',
uid: 'application::category.category',
},
];
expect(actual).toEqual(expected);
});
});
});

View File

@ -1,6 +1,6 @@
import getRedirectionLink, { mergeParams } from '../getRedirectionLink';
describe('CONTENT MANAGER | Containers | CollectionTypeFormWrapper | utils ', () => {
describe('CONTENT MANAGER | Containers | CollectionTypeFormWrapper | utils', () => {
describe('getRedirectionLink', () => {
it('should return an when no links is matching the slug', () => {
const links = [

View File

@ -37,7 +37,7 @@ export default {
pluginsSectionLinks: [
{
destination: `/plugins/${pluginId}`,
icon,
icon: 'book-open',
label: {
id: `${pluginId}.plugin.name`,
defaultMessage: 'Content manager',

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Switch, Route, useRouteMatch, Redirect } from 'react-router-dom';
import { Switch, Route, useRouteMatch, Redirect, useLocation } from 'react-router-dom';
import { CheckPagePermissions, LoadingIndicatorPage, NotFound } from '@strapi/helper-plugin';
import { DndProvider } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
@ -14,15 +14,36 @@ import useModels from './useModels';
const App = () => {
const contentTypeMatch = useRouteMatch(`/plugins/${pluginId}/:kind/:uid`);
const { status, collectionTypeLinks, singleTypeLinks } = useModels();
const models = [...collectionTypeLinks, ...singleTypeLinks];
const { status, collectionTypeLinks, singleTypeLinks, models } = useModels();
const authorisedModels = [...collectionTypeLinks, ...singleTypeLinks];
const { pathname } = useLocation();
if (status === 'loading') {
return <LoadingIndicatorPage />;
}
if (!contentTypeMatch && models.length > 0) {
return <Redirect to={`${models[0].to}${models[0].search ? `?${models[0].search}` : ''}`} />;
// Redirect the user to the 403 page
if (
authorisedModels.length === 0 &&
models.length > 0 &&
pathname !== `/plugins/${pluginId}/403`
) {
return <Redirect to={`/plugins/${pluginId}/403`} />;
}
// Redirect the user to the create content type page
if (models.length === 0 && pathname !== '/plugins/content-manager/no-content-types') {
return <Redirect to={`/plugins/${pluginId}/no-content-types`} />;
}
if (!contentTypeMatch && authorisedModels.length > 0) {
return (
<Redirect
to={`${authorisedModels[0].to}${
authorisedModels[0].search ? `?${authorisedModels[0].search}` : ''
}`}
/>
);
}
return (
@ -47,6 +68,16 @@ const App = () => {
path={`/plugins/${pluginId}/singleType/:slug`}
component={SingleTypeRecursivePath}
/>
{/* These pages must be defined */}
<Route
path={`/plugins/${pluginId}/403`}
render={() => <div>TBD No rights to see the content types</div>}
/>
<Route
path={`/plugins/${pluginId}/no-content-types`}
render={() => <div>TBD No ct</div>}
/>
<Route path="" component={NotFound} />
</Switch>
</div>

View File

@ -0,0 +1,27 @@
import { setContentTypeLinks } from '../actions';
describe('Content Manager | App | actions', () => {
it('should format the setContentTypeLinks action', () => {
const authorizedCtLinks = [{ title: 'addresses', uid: 'address' }];
const authorizedStLinks = [{ title: 'Home page', uid: 'homepage' }];
const models = [
{ kind: 'singleType', uid: 'homepage' },
{ kind: 'collectionType', uid: 'address' },
];
const components = [];
const expected = {
type: 'ContentManager/App/SET_CONTENT_TYPE_LINKS',
data: {
authorizedCtLinks,
authorizedStLinks,
contentTypeSchemas: models,
components,
},
};
expect(setContentTypeLinks(authorizedCtLinks, authorizedStLinks, models, components)).toEqual(
expected
);
});
});

View File

@ -0,0 +1,474 @@
/* eslint-disable no-irregular-whitespace */
import React from 'react';
import { createStore, combineReducers } from 'redux';
import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import { Provider } from 'react-redux';
import { render, screen } from '@testing-library/react';
import ContentManagerApp from '..';
import cmReducers from '../../../reducers';
import pluginId from '../../../pluginId';
import useModels from '../useModels';
jest.mock('../useModels', () =>
jest.fn(() => {
return {};
})
);
jest.mock('react-intl', () => ({
FormattedMessage: () => 'label',
useIntl: () => ({ formatMessage: jest.fn(() => 'label') }),
}));
jest.mock('@fortawesome/react-fontawesome', () => ({
FontAwesomeIcon: () => null,
}));
describe('Content manager | App | main', () => {
beforeEach(() => {
jest.resetModules(); // Most important - it clears the cache
});
afterEach(() => {
jest.resetAllMocks();
});
it('should not crash', () => {
const contentManagerState = {
collectionTypeLinks: [
{
uid: 'category',
title: 'Categories',
name: 'category',
to: '/category',
kind: 'collectionType',
isDisplayed: true,
permissions: [
{
action: 'plugins::content-manager.explorer.read',
subject: 'category',
},
],
},
],
singleTypeLinks: [
{
uid: 'homepage',
title: 'Home page',
name: 'homepage',
to: '/homepage',
kind: 'singleType',
isDisplayed: true,
permissions: [
{
action: 'plugins::content-manager.explorer.read',
subject: 'homepage',
},
],
},
],
models: [
{
kind: 'collectionType',
uid: 'category',
info: { label: 'Categories', name: 'category' },
},
{ kind: 'singleType', uid: 'homepage', info: { label: 'Home page', name: 'homepage' } },
],
components: [],
status: 'resolved',
};
useModels.mockImplementation(() => contentManagerState);
const rootReducer = combineReducers(cmReducers);
const store = createStore(rootReducer, { [`${pluginId}_app`]: contentManagerState });
const history = createMemoryHistory();
history.push('/plugins/content-manager/collectionType/category');
const { container } = render(
<Provider store={store}>
<Router history={history}>
<ContentManagerApp />
</Router>
</Provider>
);
expect(screen.getByText('Home page')).toBeVisible();
expect(screen.getByText('Categories')).toBeVisible();
expect(container.firstChild).toMatchInlineSnapshot(`
.c2 {
margin-bottom: 0;
padding-left: 0;
max-height: 182px;
overflow-y: auto;
}
.c2::-webkit-scrollbar {
background: transparent;
}
.c2::-webkit-scrollbar-track {
background: transparent;
}
.c2::-webkit-scrollbar-track:hover {
background: transparent;
}
.c2 li {
position: relative;
margin-bottom: 2px;
}
.c2 a {
display: block;
padding-left: 30px;
height: 34px;
border-radius: 2px;
}
.c2 a p {
color: #2D3138;
font-size: 13px;
line-height: 34px;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
margin-bottom: 0;
}
.c2 a.active {
background-color: #e9eaeb;
}
.c2 a.active p {
font-weight: 600;
}
.c2 a.active svg {
color: #2D3138;
}
.c2 a:hover {
-webkit-text-decoration: none;
text-decoration: none;
}
.c1 {
margin-bottom: 24px;
}
.c1 button {
outline: 0;
}
.c1 .count-info {
position: relative;
display: inline-block;
height: 14px;
min-width: 14px;
margin-top: 2px;
padding: 1px 3px;
text-align: center;
border-radius: 2px;
}
.c1 .count-info:before {
content: attr(datadescr);
position: absolute;
top: 1px;
height: 14px;
min-width: 14px;
padding: 0px 3px;
background-color: #E9EAEB;
border-radius: 2px;
}
.c1 .list-header {
color: #919bae;
}
.c1 .list-header > div {
position: relative;
}
.c1 .list-header h3 {
margin-bottom: 10px;
padding-right: 20px;
padding-top: 2px;
font-family: Lato;
font-size: 1.1rem;
line-height: normal;
-webkit-letter-spacing: 0.1rem;
-moz-letter-spacing: 0.1rem;
-ms-letter-spacing: 0.1rem;
letter-spacing: 0.1rem;
font-weight: bold;
text-transform: uppercase;
}
.c1 .list-header h3 + button {
position: absolute;
top: 2px;
right: 0;
padding: 2px 0 0px 5px;
line-height: 11px;
}
.c1 .list-header h3 + button i,
.c1 .list-header h3 + button svg {
font-size: 11px;
}
.c1 .list-header .search-wrapper {
margin-bottom: 7px;
}
.c1 .list-header .search-wrapper::after {
display: block;
content: '';
height: 2px;
width: calc(100% - 20px);
background: #E9EAEB;
}
.c1 .list-header .search-wrapper > svg {
position: absolute;
bottom: 6px;
left: 0;
font-size: 11px;
}
.c1 .list-header .search-wrapper button {
position: absolute;
top: 0;
right: 0;
padding: 5px 0 0px 5px;
line-height: 11px;
}
.c1 .list-header .search-wrapper button i,
.c1 .list-header .search-wrapper button svg {
font-size: 11px;
}
.c1 ul {
list-style: none;
padding-top: 2px;
}
.c1 ul li a {
text-transform: capitalize;
}
.c3 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: space-around;
-webkit-justify-content: space-around;
-ms-flex-pack: space-around;
justify-content: space-around;
width: 100%;
height: 100vh;
}
.c3 > div {
margin: auto;
width: 50px;
height: 50px;
border: 6px solid #f3f3f3;
border-top: 6px solid #1c91e7;
border-radius: 50%;
-webkit-animation: fEWCgj 2s linear infinite;
animation: fEWCgj 2s linear infinite;
}
.c0 {
width: 100%;
min-height: calc(100vh - 6rem);
background-color: #f2f3f4;
padding-top: 3.1rem;
padding-left: 2rem;
padding-right: 2rem;
}
<div
class="container-fluid"
>
<div
class="row"
>
<div
class="c0 col-md-3"
>
<div
class="c1"
>
<div
class="list-header"
>
<div
class="title-wrapper"
>
<h3>
label
  
<span
class="count-info"
datadescr="1"
>
1
</span>
</h3>
<button />
</div>
</div>
<div>
<ul
class="c2"
>
<li>
<a
href="/category"
>
<p>
Categories
</p>
</a>
</li>
</ul>
</div>
</div>
<div
class="c1"
>
<div
class="list-header"
>
<div
class="title-wrapper"
>
<h3>
label
  
<span
class="count-info"
datadescr="1"
>
1
</span>
</h3>
<button />
</div>
</div>
<div>
<ul
class="c2"
>
<li>
<a
href="/homepage"
>
<p>
Home page
</p>
</a>
</li>
</ul>
</div>
</div>
</div>
<div
class="col-md-9 content"
style="padding: 0px 30px;"
>
<div
class="c3"
>
<div />
</div>
</div>
</div>
</div>
`);
});
it('should redirect to 403 page', () => {
const history = createMemoryHistory();
const contentManagerState = {
collectionTypeLinks: [],
singleTypeLinks: [],
models: [
{
kind: 'collectionType',
uid: 'category',
info: { label: 'Categories', name: 'category' },
},
{ kind: 'singleType', uid: 'homepage', info: { label: 'Home page', name: 'homepage' } },
],
components: [],
status: 'resolved',
};
useModels.mockImplementation(() => contentManagerState);
jest.mock('../useModels', () =>
jest.fn(() => {
return contentManagerState;
})
);
const rootReducer = combineReducers(cmReducers);
const store = createStore(rootReducer, { [`${pluginId}_app`]: contentManagerState });
history.push('/plugins/content-manager/collectionType/category');
render(
<Provider store={store}>
<Router history={history}>
<ContentManagerApp />
</Router>
</Provider>
);
expect(history.location.pathname).toEqual(`/plugins/${pluginId}/403`);
});
it('should redirect to the no-content-types page', () => {
const history = createMemoryHistory();
const contentManagerState = {
collectionTypeLinks: [],
singleTypeLinks: [],
models: [],
components: [],
status: 'resolved',
};
useModels.mockImplementation(() => contentManagerState);
jest.mock('../useModels', () =>
jest.fn(() => {
return contentManagerState;
})
);
const rootReducer = combineReducers(cmReducers);
const store = createStore(rootReducer, { [`${pluginId}_app`]: contentManagerState });
history.push('/plugins/content-manager/collectionType/category');
render(
<Provider store={store}>
<Router
history={history}
// initialEntries={['/plugins/content-manager/collectionType/category']}
>
<ContentManagerApp />
</Router>
</Provider>
);
expect(history.location.pathname).toEqual(`/plugins/${pluginId}/no-content-types`);
});
});

View File

@ -0,0 +1,24 @@
import { fixtures } from '../../../../../../../admin-test-utils';
import { makeSelectModels } from '../selectors';
describe('Content Manager | App | selectors', () => {
let store;
beforeEach(() => {
store = { ...fixtures.store.state };
});
describe('makeSelectModels', () => {
it('should extract the models from the content manager state in the global store', () => {
store['content-manager_app'] = {
models: [{ uid: 'category' }, { uid: 'address' }],
};
const modelSelector = makeSelectModels();
const actual = modelSelector(store);
const expected = [{ uid: 'category' }, { uid: 'address' }];
expect(actual).toEqual(expected);
});
});
});

View File

@ -163,6 +163,7 @@
"permissions.not-allowed.update": "You are not allowed to see this document",
"plugin.description.long": "Quick way to see, edit and delete the data in your database.",
"plugin.description.short": "Quick way to see, edit and delete the data in your database.",
"plugin.name": "Content manager",
"popUpWarning.bodyMessage.contentType.delete": "Are you sure you want to delete this entry?",
"popUpWarning.bodyMessage.contentType.delete.all": "Are you sure you want to delete these entries?",
"popUpWarning.warning.cancelAllSettings": "Are you sure you want to cancel your modifications?",

View File

@ -3,7 +3,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
position: fixed;
top: 0;
left: 25rem;
left: 27rem;
display: flex;
align-items: center;
overflow: hidden;