Ensured versions footer has proper classes based on sidebar status, not selected server

This commit is contained in:
Alejandro Celaya 2022-03-11 16:03:15 +01:00
parent 4f731d9de8
commit 73d4707420
11 changed files with 76 additions and 22 deletions

View file

@ -11,6 +11,11 @@ import NotFound from './NotFound';
import { AsideMenuProps } from './AsideMenu'; import { AsideMenuProps } from './AsideMenu';
import './MenuLayout.scss'; import './MenuLayout.scss';
interface MenuLayoutProps {
sidebarRendered: Function;
sidebarNotRendered: Function;
}
const MenuLayout = ( const MenuLayout = (
TagsList: FC, TagsList: FC,
ShortUrlsList: FC, ShortUrlsList: FC,
@ -24,13 +29,19 @@ const MenuLayout = (
Overview: FC, Overview: FC,
EditShortUrl: FC, EditShortUrl: FC,
ManageDomains: FC, ManageDomains: FC,
) => withSelectedServer(({ selectedServer }) => { ) => withSelectedServer<MenuLayoutProps>(({ selectedServer, sidebarNotRendered, sidebarRendered }) => {
const location = useLocation(); const location = useLocation();
const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle(); const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle();
const showContent = isReachableServer(selectedServer);
useEffect(() => hideSidebar(), [ location ]); useEffect(() => hideSidebar(), [ location ]);
useEffect(() => {
showContent && sidebarRendered();
if (!isReachableServer(selectedServer)) { return () => sidebarNotRendered();
}, []);
if (!showContent) {
return <ServerError />; return <ServerError />;
} }

View file

@ -1,13 +1,13 @@
import { pipe } from 'ramda'; import { pipe } from 'ramda';
import { ExternalLink } from 'react-external-link'; import { ExternalLink } from 'react-external-link';
import { versionToPrintable, versionToSemVer } from '../utils/helpers/version'; import { versionToPrintable, versionToSemVer } from '../utils/helpers/version';
import { isReachableServer } from '../servers/data'; import { isReachableServer, SelectedServer } from '../servers/data';
import { ShlinkVersionsContainerProps } from './ShlinkVersionsContainer';
const SHLINK_WEB_CLIENT_VERSION = '%_VERSION_%'; const SHLINK_WEB_CLIENT_VERSION = '%_VERSION_%';
const normalizeVersion = pipe(versionToSemVer(), versionToPrintable); const normalizeVersion = pipe(versionToSemVer(), versionToPrintable);
export interface ShlinkVersionsProps extends ShlinkVersionsContainerProps { export interface ShlinkVersionsProps {
selectedServer: SelectedServer;
clientVersion?: string; clientVersion?: string;
} }

View file

@ -1,6 +1,6 @@
@import '../utils/base'; @import '../utils/base';
.shlink-versions-container--with-server { .shlink-versions-container--with-sidebar {
margin-left: 0; margin-left: 0;
@media (min-width: $mdMin) { @media (min-width: $mdMin) {

View file

@ -1,15 +1,17 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { isReachableServer, SelectedServer } from '../servers/data'; import { SelectedServer } from '../servers/data';
import ShlinkVersions from './ShlinkVersions'; import ShlinkVersions from './ShlinkVersions';
import { Sidebar } from './reducers/sidebar';
import './ShlinkVersionsContainer.scss'; import './ShlinkVersionsContainer.scss';
export interface ShlinkVersionsContainerProps { export interface ShlinkVersionsContainerProps {
selectedServer: SelectedServer; selectedServer: SelectedServer;
sidebar: Sidebar;
} }
const ShlinkVersionsContainer = ({ selectedServer }: ShlinkVersionsContainerProps) => { const ShlinkVersionsContainer = ({ selectedServer, sidebar }: ShlinkVersionsContainerProps) => {
const classes = classNames('text-center', { const classes = classNames('text-center', {
'shlink-versions-container--with-server': isReachableServer(selectedServer), 'shlink-versions-container--with-sidebar': sidebar.hasSidebar,
}); });
return ( return (

View file

@ -0,0 +1,27 @@
import { Action } from 'redux';
import { buildActionCreator, buildReducer } from '../../utils/helpers/redux';
/* eslint-disable padding-line-between-statements */
export const SIDEBAR_RENDERED = 'shlink/common/SIDEBAR_RENDERED';
export const SIDEBAR_NOT_RENDERED = 'shlink/common/SIDEBAR_NOT_RENDERED';
/* eslint-enable padding-line-between-statements */
export interface Sidebar {
hasSidebar: boolean;
}
type SidebarRenderedAction = Action<string>;
type SidebarNotRenderedAction = Action<string>;
const initialState: Sidebar = {
hasSidebar: false,
};
export default buildReducer<Sidebar, SidebarRenderedAction & SidebarNotRenderedAction>({
[SIDEBAR_RENDERED]: () => ({ hasSidebar: true }),
[SIDEBAR_NOT_RENDERED]: () => ({ hasSidebar: false }),
}, initialState);
export const sidebarRendered = buildActionCreator(SIDEBAR_RENDERED);
export const sidebarNotRendered = buildActionCreator(SIDEBAR_NOT_RENDERED);

View file

@ -9,6 +9,7 @@ import ErrorHandler from '../ErrorHandler';
import ShlinkVersionsContainer from '../ShlinkVersionsContainer'; import ShlinkVersionsContainer from '../ShlinkVersionsContainer';
import { ConnectDecorator } from '../../container/types'; import { ConnectDecorator } from '../../container/types';
import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer'; import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer';
import { sidebarNotRendered, sidebarRendered } from '../reducers/sidebar';
import { ImageDownloader } from './ImageDownloader'; import { ImageDownloader } from './ImageDownloader';
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
@ -44,14 +45,18 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
'EditShortUrl', 'EditShortUrl',
'ManageDomains', 'ManageDomains',
); );
bottle.decorator('MenuLayout', connect([ 'selectedServer' ], [ 'selectServer' ])); bottle.decorator('MenuLayout', connect([ 'selectedServer' ], [ 'selectServer', 'sidebarRendered', 'sidebarNotRendered' ]));
bottle.serviceFactory('AsideMenu', AsideMenu, 'DeleteServerButton'); bottle.serviceFactory('AsideMenu', AsideMenu, 'DeleteServerButton');
bottle.serviceFactory('ShlinkVersionsContainer', () => ShlinkVersionsContainer); bottle.serviceFactory('ShlinkVersionsContainer', () => ShlinkVersionsContainer);
bottle.decorator('ShlinkVersionsContainer', connect([ 'selectedServer' ])); bottle.decorator('ShlinkVersionsContainer', connect([ 'selectedServer', 'sidebar' ]));
bottle.serviceFactory('ErrorHandler', ErrorHandler, 'window', 'console'); bottle.serviceFactory('ErrorHandler', ErrorHandler, 'window', 'console');
// Actions
bottle.serviceFactory('sidebarRendered', () => sidebarRendered);
bottle.serviceFactory('sidebarNotRendered', () => sidebarNotRendered);
}; };
export default provideServices; export default provideServices;

View file

@ -14,6 +14,7 @@ import { TagVisits } from '../visits/reducers/tagVisits';
import { DomainsList } from '../domains/reducers/domainsList'; import { DomainsList } from '../domains/reducers/domainsList';
import { VisitsOverview } from '../visits/reducers/visitsOverview'; import { VisitsOverview } from '../visits/reducers/visitsOverview';
import { VisitsInfo } from '../visits/types'; import { VisitsInfo } from '../visits/types';
import { Sidebar } from '../common/reducers/sidebar';
export interface ShlinkState { export interface ShlinkState {
servers: ServersMap; servers: ServersMap;
@ -35,6 +36,7 @@ export interface ShlinkState {
domainsList: DomainsList; domainsList: DomainsList;
visitsOverview: VisitsOverview; visitsOverview: VisitsOverview;
appUpdated: boolean; appUpdated: boolean;
sidebar: Sidebar;
} }
export type ConnectDecorator = (props: string[] | null, actions?: string[]) => any; export type ConnectDecorator = (props: string[] | null, actions?: string[]) => any;

View file

@ -18,6 +18,7 @@ import settingsReducer from '../settings/reducers/settings';
import domainsListReducer from '../domains/reducers/domainsList'; import domainsListReducer from '../domains/reducers/domainsList';
import visitsOverviewReducer from '../visits/reducers/visitsOverview'; import visitsOverviewReducer from '../visits/reducers/visitsOverview';
import appUpdatesReducer from '../app/reducers/appUpdates'; import appUpdatesReducer from '../app/reducers/appUpdates';
import sidebarReducer from '../common/reducers/sidebar';
import { ShlinkState } from '../container/types'; import { ShlinkState } from '../container/types';
export default combineReducers<ShlinkState>({ export default combineReducers<ShlinkState>({
@ -40,4 +41,5 @@ export default combineReducers<ShlinkState>({
domainsList: domainsListReducer, domainsList: domainsListReducer,
visitsOverview: visitsOverviewReducer, visitsOverview: visitsOverviewReducer,
appUpdated: appUpdatesReducer, appUpdated: appUpdatesReducer,
sidebar: sidebarReducer,
}); });

View file

@ -43,7 +43,6 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.decorator('CreateServer', connect([ 'selectedServer', 'servers' ], [ 'createServer', 'resetSelectedServer' ])); bottle.decorator('CreateServer', connect([ 'selectedServer', 'servers' ], [ 'createServer', 'resetSelectedServer' ]));
bottle.serviceFactory('EditServer', EditServer, 'ServerError'); bottle.serviceFactory('EditServer', EditServer, 'ServerError');
bottle.decorator('EditServer', withoutSelectedServer);
bottle.decorator('EditServer', connect([ 'selectedServer' ], [ 'editServer', 'selectServer', 'resetSelectedServer' ])); bottle.decorator('EditServer', connect([ 'selectedServer' ], [ 'editServer', 'selectServer', 'resetSelectedServer' ]));
bottle.serviceFactory('ServersDropdown', () => ServersDropdown); bottle.serviceFactory('ServersDropdown', () => ServersDropdown);

View file

@ -20,7 +20,14 @@ describe('<MenuLayout />', () => {
const createWrapper = (selectedServer: SelectedServer) => { const createWrapper = (selectedServer: SelectedServer) => {
(useParams as any).mockReturnValue({ serverId: 'abc123' }); (useParams as any).mockReturnValue({ serverId: 'abc123' });
wrapper = shallow(<MenuLayout selectServer={jest.fn()} selectedServer={selectedServer} />); wrapper = shallow(
<MenuLayout
sidebarNotRendered={jest.fn()}
sidebarRendered={jest.fn}
selectServer={jest.fn()}
selectedServer={selectedServer}
/>,
);
return wrapper; return wrapper;
}; };

View file

@ -1,13 +1,14 @@
import { shallow, ShallowWrapper } from 'enzyme'; import { shallow, ShallowWrapper } from 'enzyme';
import { Mock } from 'ts-mockery'; import { Mock } from 'ts-mockery';
import ShlinkVersionsContainer from '../../src/common/ShlinkVersionsContainer'; import ShlinkVersionsContainer from '../../src/common/ShlinkVersionsContainer';
import { NonReachableServer, NotFoundServer, ReachableServer, SelectedServer } from '../../src/servers/data'; import { SelectedServer } from '../../src/servers/data';
import { Sidebar } from '../../src/common/reducers/sidebar';
describe('<ShlinkVersionsContainer />', () => { describe('<ShlinkVersionsContainer />', () => {
let wrapper: ShallowWrapper; let wrapper: ShallowWrapper;
const createWrapper = (selectedServer: SelectedServer) => { const createWrapper = (sidebar: Sidebar) => {
wrapper = shallow(<ShlinkVersionsContainer selectedServer={selectedServer} />); wrapper = shallow(<ShlinkVersionsContainer selectedServer={Mock.all<SelectedServer>()} sidebar={sidebar} />);
return wrapper; return wrapper;
}; };
@ -15,12 +16,10 @@ describe('<ShlinkVersionsContainer />', () => {
afterEach(() => wrapper?.unmount()); afterEach(() => wrapper?.unmount());
it.each([ it.each([
[ null, 'text-center' ], [{ hasSidebar: false }, 'text-center' ],
[ Mock.of<NotFoundServer>({ serverNotFound: true }), 'text-center' ], [{ hasSidebar: true }, 'text-center shlink-versions-container--with-sidebar' ],
[ Mock.of<NonReachableServer>({ serverNotReachable: true }), 'text-center' ], ])('renders proper col classes based on sidebar status', (sidebar, expectedClasses) => {
[ Mock.of<ReachableServer>({ version: '1.0.0' }), 'text-center shlink-versions-container--with-server' ], const wrapper = createWrapper(sidebar);
])('renders proper col classes based on type of selected server', (selectedServer, expectedClasses) => {
const wrapper = createWrapper(selectedServer);
expect(wrapper.find('div').prop('className')).toEqual(`${expectedClasses}`); expect(wrapper.find('div').prop('className')).toEqual(`${expectedClasses}`);
}); });