Skip to content

Commit

Permalink
fix: preserve location changing module
Browse files Browse the repository at this point in the history
refs: SHELL-47 (#235)
  • Loading branch information
CataldoMazzilli authored Apr 11, 2023
1 parent b698224 commit 29cf18a
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 34 deletions.
19 changes: 7 additions & 12 deletions src/boot/app/app-loader-mounter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/

import React, { FC, memo, Suspense, useMemo } from 'react';
import { isEmpty, map } from 'lodash';
import React, { FC, Suspense, useMemo } from 'react';
import { map } from 'lodash';
import { useAppStore } from '../../store/app';
import AppContextProvider from './app-context-provider';

Expand All @@ -21,16 +21,11 @@ const AppLoaderMounter: FC = () => {
const entryPoints = useAppStore((s) => s.entryPoints);
const entries = useMemo(
() =>
isEmpty(entryPoints)
? null
: map(entryPoints, (Comp, appId) => {
const MemoComp = memo(Comp);
return (
<Mounter key={appId} appId={appId}>
<MemoComp />
</Mounter>
);
}),
map(entryPoints, (Comp, appId) => (
<Mounter key={appId} appId={appId}>
<Comp />
</Mounter>
)),
[entryPoints]
);

Expand Down
4 changes: 2 additions & 2 deletions src/boot/app/load-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { forOwn } from 'lodash';
import { ComponentType } from 'react';
import { ComponentType, memo } from 'react';
import { useAppStore } from '../../store/app';
import { getAppFunctions } from './app-loader-functions';
import { Spinner } from '../../ui-extras/spinner';
Expand Down Expand Up @@ -54,7 +54,7 @@ export function loadApp(appPkg: CarbonioModule): Promise<CarbonioModule> {
useAppStore.setState((state) => ({
entryPoints: {
...state.entryPoints,
[appPkg.name]: appComponent
[appPkg.name]: memo(appComponent)
}
}));
console.info(
Expand Down
124 changes: 124 additions & 0 deletions src/shell/shell-primary-bar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,57 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React from 'react';
import { screen } from '@testing-library/react';
import { Route, Switch, useParams, useRouteMatch } from 'react-router-dom';
import { Button, Text } from '@zextras/carbonio-design-system';
import { setup } from '../test/utils';
import ShellPrimaryBar from './shell-primary-bar';
import { useAppStore } from '../store/app';
import { PrimaryBarView } from '../../types';
import { usePushHistoryCallback } from '../history/hooks';
import AppViewContainer from './app-view-container';

const ShellWrapper = (): JSX.Element => (
<>
<ShellPrimaryBar />
<AppViewContainer />
</>
);

const AboutView = (): JSX.Element | null => {
const { view } = useParams<{ view: string }>();
return (
<div>
<Text>{view}</Text>
</div>
);
};

const MailsView = (): JSX.Element => {
const { path } = useRouteMatch();
const push = usePushHistoryCallback();

return (
<Switch>
<Route path={`${path}/:view`}>
<AboutView />
</Route>
<Route path={`${path}/`}>
<Text>default mails view</Text>
<Button
label={'navigate to about'}
onClick={(): void => push({ route: 'mails', path: 'about' })}
/>
</Route>
</Switch>
);
};

const FilesView = (): JSX.Element => (
<div>
<Text>files view</Text>
</div>
);

describe('Shell primary bar', () => {
test('Show a component for each primary bar view registered in the store', () => {
Expand Down Expand Up @@ -74,4 +121,81 @@ describe('Shell primary bar', () => {
expect(getByRoleWithIcon('button', { icon: 'People' })).toBeEnabled();
expect(queryByRoleWithIcon('button', { icon: 'Activity' })).not.toBeInTheDocument();
});

test('When return to a visited module, the last visited view is preserved', async () => {
useAppStore.getState().setters.addApps([
{
commit: '',
description: 'Mails module',
display: 'Mails',
icon: 'MailModOutline',
js_entrypoint: '',
name: 'carbonio-mails-ui',
priority: 1,
type: 'carbonio',
version: '0.0.1'
},
{
commit: '',
description: 'Files module',
display: 'Files',
icon: 'DriveOutline',
js_entrypoint: '',
name: 'carbonio-files-ui',
priority: 2,
type: 'carbonio',
version: '0.0.1'
}
]);
useAppStore.getState().setters.addRoute({
id: 'mails',
route: 'mails',
position: 1,
visible: true,
label: 'Mails',
primaryBar: 'MailModOutline',
appView: MailsView,
badge: { show: false },
app: 'carbonio-mails-ui'
});

useAppStore.getState().setters.addRoute({
id: 'files',
route: 'files',
position: 2,
visible: true,
label: 'Files',
primaryBar: 'DriveOutline',
appView: FilesView,
badge: { show: false },
app: 'carbonio-files-ui'
});

const { getByRoleWithIcon, user } = setup(<ShellWrapper />);
const mailsIcon = getByRoleWithIcon('button', { icon: 'MailModOutline' });
expect(mailsIcon).toBeVisible();
expect(mailsIcon).toBeEnabled();
const filesIcon = getByRoleWithIcon('button', { icon: 'DriveOutline' });
expect(filesIcon).toBeVisible();
expect(filesIcon).toBeEnabled();

expect(screen.getByText('default mails view')).toBeVisible();
expect(screen.queryByText('about')).not.toBeInTheDocument();
expect(screen.queryByText('files view')).not.toBeInTheDocument();

await user.click(screen.getByRole('button', { name: 'navigate to about' }));
expect(screen.getByText('about')).toBeVisible();
expect(screen.queryByText('default mails view')).not.toBeInTheDocument();
expect(screen.queryByText('files view')).not.toBeInTheDocument();

await user.click(filesIcon);
expect(screen.getByText('files view')).toBeVisible();
expect(screen.queryByText('about')).not.toBeInTheDocument();
expect(screen.queryByText('default mails view')).not.toBeInTheDocument();

await user.click(mailsIcon);
expect(screen.getByText('about')).toBeVisible();
expect(screen.queryByText('default mails view')).not.toBeInTheDocument();
expect(screen.queryByText('files view')).not.toBeInTheDocument();
});
});
39 changes: 19 additions & 20 deletions src/shell/shell-primary-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

import { Container, IconButton, Row, Tooltip } from '@zextras/carbonio-design-system';
import { map, isEmpty, trim, filter, sortBy, noop } from 'lodash';
import React, { useState, useEffect, useMemo } from 'react';
import React, { useEffect, useMemo, useRef } from 'react';
import styled from 'styled-components';
import { useHistory } from 'react-router-dom';
import { useHistory, useLocation } from 'react-router-dom';
import { useAppStore } from '../store/app';
import { AppRoute, PrimaryAccessoryView, PrimaryBarView } from '../../types';
import BadgeWrap from './badge-wrap';
Expand Down Expand Up @@ -97,29 +97,28 @@ const ShellPrimaryBarComponent = ({
activeRoute
}: ShellPrimaryBarComponentProps): JSX.Element | null => {
const primaryBarViews = useAppStore((s) => s.views.primaryBar);
const [routes, setRoutes] = useState<Record<string, string>>({});
const history = useHistory();
const { push } = useHistory();

const { pathname, search } = useLocation();
const routesRef = useRef<Record<string, string>>({});

useEffect(() => {
setRoutes((prevState) =>
primaryBarViews.reduce((accumulator, view) => {
if (!accumulator[view.id]) {
accumulator[view.id] = view.route;
}
return accumulator;
}, prevState)
);
routesRef.current = primaryBarViews.reduce((accumulator, view) => {
if (!accumulator[view.id]) {
accumulator[view.id] = view.route;
}
return accumulator;
}, routesRef.current);
}, [primaryBarViews]);

useEffect(() => {
if (activeRoute) {
setRoutes((prevRoutes) => ({
...prevRoutes,
[activeRoute.id]: `${trim(history.location.pathname, '/')}${history.location.search}`
}));
routesRef.current = {
...routesRef.current,
[activeRoute.id]: `${trim(pathname, '/')}${search}`
};
}
}, [activeRoute, history.location, primaryBarViews]);

}, [activeRoute, pathname, search, primaryBarViews]);
const primaryBarAccessoryViews = useAppStore((s) => s.views.primaryBarAccessories);

const accessoryViews = useMemo(
Expand All @@ -137,13 +136,13 @@ const ShellPrimaryBarComponent = ({
view.visible ? (
<PrimaryBarElement
key={view.id}
onClick={(): void => history.push(`/${routes[view.id]}`)}
onClick={(): void => push(`/${routesRef.current[view.id]}`)}
view={view}
active={activeRoute?.id === view.id}
/>
) : null
),
[activeRoute?.id, history, primaryBarViews, routes]
[activeRoute?.id, push, primaryBarViews]
);

const accessoryItems = useMemo(
Expand Down

0 comments on commit 29cf18a

Please sign in to comment.