Skip to content
Snippets Groups Projects
Commit ced9d6f3 authored by Tadeusz Miesiąc's avatar Tadeusz Miesiąc
Browse files

feat(search drawer: conten accordion): implemented content accordion with dummy data

parent c250dcaa
No related branches found
No related tags found
2 merge requests!223reset the pin numbers before search results are fetch (so the results will be...,!27feat(search drawer: content accordion): implemented content accordion with dummy data
Pipeline #79308 passed
Showing
with 358 additions and 50 deletions
...@@ -26,6 +26,7 @@ const config = { ...@@ -26,6 +26,7 @@ const config = {
coverageReporters: ['html', 'text', 'text-summary', 'cobertura'], coverageReporters: ['html', 'text', 'text-summary', 'cobertura'],
setupFilesAfterEnv: ['<rootDir>/setupTests.ts'], setupFilesAfterEnv: ['<rootDir>/setupTests.ts'],
prettierPath: require.resolve('prettier-2'), prettierPath: require.resolve('prettier-2'),
watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],
}; };
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
......
...@@ -56,6 +56,7 @@ ...@@ -56,6 +56,7 @@
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0", "jest-environment-jsdom": "^29.7.0",
"jest-junit": "^16.0.0", "jest-junit": "^16.0.0",
"jest-watch-typeahead": "^2.2.2",
"lint-staged": "^14.0.1", "lint-staged": "^14.0.1",
"prettier": "^3.0.3", "prettier": "^3.0.3",
"prettier-2": "npm:prettier@^2", "prettier-2": "npm:prettier@^2",
...@@ -8087,6 +8088,130 @@ ...@@ -8087,6 +8088,130 @@
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
"dev": true "dev": true
}, },
"node_modules/jest-watch-typeahead": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-2.2.2.tgz",
"integrity": "sha512-+QgOFW4o5Xlgd6jGS5X37i08tuuXNW8X0CV9WNFi+3n8ExCIP+E1melYhvYLjv5fE6D0yyzk74vsSO8I6GqtvQ==",
"dev": true,
"dependencies": {
"ansi-escapes": "^6.0.0",
"chalk": "^5.2.0",
"jest-regex-util": "^29.0.0",
"jest-watcher": "^29.0.0",
"slash": "^5.0.0",
"string-length": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": "^14.17.0 || ^16.10.0 || >=18.0.0"
},
"peerDependencies": {
"jest": "^27.0.0 || ^28.0.0 || ^29.0.0"
}
},
"node_modules/jest-watch-typeahead/node_modules/ansi-escapes": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz",
"integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==",
"dev": true,
"dependencies": {
"type-fest": "^3.0.0"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/jest-watch-typeahead/node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/jest-watch-typeahead/node_modules/chalk": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
"dev": true,
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/jest-watch-typeahead/node_modules/char-regex": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz",
"integrity": "sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==",
"dev": true,
"engines": {
"node": ">=12.20"
}
},
"node_modules/jest-watch-typeahead/node_modules/slash": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
"integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
"dev": true,
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/jest-watch-typeahead/node_modules/string-length": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz",
"integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==",
"dev": true,
"dependencies": {
"char-regex": "^2.0.0",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/jest-watch-typeahead/node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/jest-watch-typeahead/node_modules/type-fest": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
"integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
"dev": true,
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/jest-watcher": { "node_modules/jest-watcher": {
"version": "29.7.0", "version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz",
......
import { screen, fireEvent, type RenderResult } from '@testing-library/react'; import { ToolkitStoreWithSingleSlice } from '@/utils/createStoreInstanceUsingSliceReducer';
import { renderComponentWithProvider } from '@/utils/renderComponentWithProvider'; import { DrawerState } from '@/redux/drawer/drawer.types';
import { getReduxWrapperUsingSliceReducer } from '@/utils/testing/getReduxWrapper';
import { screen, render, act, fireEvent } from '@testing-library/react';
import drawerReducer, { openDrawer } from '@/redux/drawer/drawer.slice';
import { Drawer } from './Drawer.component'; import { Drawer } from './Drawer.component';
const renderComponent = (): RenderResult => renderComponentWithProvider(<Drawer />); const renderComponent = (): { store: ToolkitStoreWithSingleSlice<DrawerState> } => {
const { Wrapper, store } = getReduxWrapperUsingSliceReducer('drawer', drawerReducer);
return (
render(
<Wrapper>
<Drawer />
</Wrapper>,
),
{
store,
}
);
};
describe('Drawer - component', () => { describe('Drawer - component', () => {
it('should render Drawer', () => { it('should render Drawer', () => {
...@@ -11,13 +27,42 @@ describe('Drawer - component', () => { ...@@ -11,13 +27,42 @@ describe('Drawer - component', () => {
expect(screen.getByRole('drawer')).toBeInTheDocument(); expect(screen.getByRole('drawer')).toBeInTheDocument();
}); });
it('should close Drawer', async () => { it('should not display drawer when its not open', () => {
renderComponent(); renderComponent();
const button = screen.getByRole('close-drawer-button'); expect(screen.getByRole('drawer')).not.toHaveClass('translate-x-0');
});
describe('search drawer ', () => {
it('should open drawer and display search drawer content', async () => {
const { store } = renderComponent();
await fireEvent.click(button); expect(screen.queryByTestId('search-drawer-content')).not.toBeInTheDocument();
expect(screen.getByRole('drawer')).not.toHaveClass('translate-x-0'); await act(() => {
store.dispatch(openDrawer('search'));
});
expect(screen.getByTestId('search-drawer-content')).toBeInTheDocument();
});
it('should close drawer after pressing close button', async () => {
const { store } = renderComponent();
await act(() => {
store.dispatch(openDrawer('search'));
});
expect(screen.getByTestId('search-drawer-content')).toBeInTheDocument();
const button = screen.getByRole('close-drawer-button');
await act(() => {
fireEvent.click(button);
});
expect(screen.getByRole('drawer')).not.toHaveClass('translate-x-0');
expect(screen.queryByTestId('search-drawer-content')).not.toBeInTheDocument();
});
}); });
}); });
import dynamic from 'next/dynamic';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import { IconButton } from '@/shared/IconButton';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { closeDrawer } from '@/redux/drawer/drawer.slice';
import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { drawerDataSelector } from '@/redux/drawer/drawer.selectors'; import { drawerDataSelector } from '@/redux/drawer/drawer.selectors';
import { import { DRAWER_ROLE } from '@/components/Map/Drawer/Drawer.constants';
CLOSE_BUTTON_ROLE,
DRAWER_ROLE,
SOURCE_FROM_DRAWER,
} from '@/components/Map/Drawer/Drawer.constants';
export const Drawer = (): JSX.Element => { const SearchDrawerContent = dynamic(
const dispatch = useAppDispatch(); async () =>
const drawerData = useAppSelector(drawerDataSelector); import('@/components/Map/Drawer/SearchDrawerContent').then(
const { open } = drawerData; module => module.SearchDrawerContent,
),
{
ssr: false,
},
);
const handleCloseDrawer = (): void => { export const Drawer = (): JSX.Element => {
// eslint-disable-next-line prefer-template const { open, drawerName } = useAppSelector(drawerDataSelector);
dispatch(closeDrawer(SOURCE_FROM_DRAWER));
};
return ( return (
<div <div
...@@ -28,19 +25,8 @@ export const Drawer = (): JSX.Element => { ...@@ -28,19 +25,8 @@ export const Drawer = (): JSX.Element => {
)} )}
role={DRAWER_ROLE} role={DRAWER_ROLE}
> >
<div className="flex items-center justify-between border-b border-b-divide px-6 py-8 text-xl"> {open && drawerName === 'search' && <SearchDrawerContent />}
<div> {/* other drawers comes here, should use dynamic import */}
<span className="font-normal">Search: </span>
<span className="font-semibold">NADH</span>
</div>
<IconButton
className="bg-white-pearl"
classNameIcon="fill-font-500"
icon="close"
role={CLOSE_BUTTON_ROLE}
onClick={handleCloseDrawer}
/>
</div>
</div> </div>
); );
}; };
export const DRAWER_ROLE = 'drawer'; export const DRAWER_ROLE = 'drawer';
export const CLOSE_BUTTON_ROLE = 'close-drawer-button';
export const SOURCE_FROM_DRAWER = 'search'; export const SOURCE_FROM_DRAWER = 'search';
import {
Accordion,
AccordionItem,
AccordionItemButton,
AccordionItemPanel,
AccordionItemHeading,
} from '@/shared/Accordion';
import { BioEntitiesSubmapItem } from '@/components/Map/Drawer/SearchDrawerContent/BioEntitiesAccordion/BioEntitiesSubmapItem';
export const BioEntitiesAccordion = (): JSX.Element => {
const entity = { mapName: 'main map', numberOfEntities: 21 };
return (
<Accordion allowZeroExpanded>
<AccordionItem>
<AccordionItemHeading>
<AccordionItemButton>Content (2137)</AccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel className="">
<BioEntitiesSubmapItem
mapName={entity.mapName}
numberOfEntities={entity.numberOfEntities}
/>
<BioEntitiesSubmapItem
mapName={entity.mapName}
numberOfEntities={entity.numberOfEntities}
/>
<BioEntitiesSubmapItem
mapName={entity.mapName}
numberOfEntities={entity.numberOfEntities}
/>
<BioEntitiesSubmapItem
mapName={entity.mapName}
numberOfEntities={entity.numberOfEntities}
/>
</AccordionItemPanel>
</AccordionItem>
</Accordion>
);
};
import { render, RenderResult, screen } from '@testing-library/react';
import {
BioEntitiesSubmapItem,
BioEntitiesSubmapItemProps,
} from './BioEntitiesSubmapItem.component';
const renderComponent = ({ mapName, numberOfEntities }: BioEntitiesSubmapItemProps): RenderResult =>
render(<BioEntitiesSubmapItem mapName={mapName} numberOfEntities={numberOfEntities} />);
describe('BioEntitiesSubmapItem - component', () => {
it('should display map name,number of elements, icon', () => {
renderComponent({ mapName: 'main map', numberOfEntities: 21 });
expect(screen.getByText('main map (21)')).toBeInTheDocument();
});
});
import { Icon } from '@/shared/Icon';
export interface BioEntitiesSubmapItemProps {
mapName: string;
numberOfEntities: string | number;
}
export const BioEntitiesSubmapItem = ({
mapName,
numberOfEntities,
}: BioEntitiesSubmapItemProps): JSX.Element => (
<div className="flex flex-row flex-nowrap justify-between pl-6 [&:not(:last-of-type)]:pb-4">
<p>
{mapName} ({numberOfEntities})
</p>
<Icon name="arrow" className="h-6 w-6 fill-font-500" />
</div>
);
export { BioEntitiesSubmapItem } from './BioEntitiesSubmapItem.component';
export { BioEntitiesAccordion } from './BioEntitiesAccordion.component';
import { BioEntitiesAccordion } from '@/components/Map/Drawer/SearchDrawerContent/BioEntitiesAccordion';
import { closeDrawer } from '@/redux/drawer/drawer.slice';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { IconButton } from '@/shared/IconButton';
export const CLOSE_BUTTON_ROLE = 'close-drawer-button';
export const SearchDrawerContent = (): JSX.Element => {
const dispatch = useAppDispatch();
const handleCloseDrawer = (): void => {
dispatch(closeDrawer());
};
return (
<div className="flex flex-col" data-testid="search-drawer-content">
<div className="flex items-center justify-between border-b border-b-divide px-6">
<div className=" py-8 text-xl">
<span className="font-normal">Search: </span>
<span className="font-semibold">NADH</span>
</div>
<IconButton
className="bg-white-pearl"
classNameIcon="fill-font-500"
icon="close"
role={CLOSE_BUTTON_ROLE}
onClick={handleCloseDrawer}
/>
</div>
<div className="px-6">
<BioEntitiesAccordion />
</div>
</div>
);
};
export { SearchDrawerContent } from './SearchDrawerContent.component';
...@@ -6,7 +6,7 @@ import type { DrawerState } from './drawer.types'; ...@@ -6,7 +6,7 @@ import type { DrawerState } from './drawer.types';
const INITIAL_STATE: DrawerState = { const INITIAL_STATE: DrawerState = {
open: false, open: false,
pathName: 'none', drawerName: 'none',
}; };
type SliceReducerType = ToolkitStore< type SliceReducerType = ToolkitStore<
...@@ -38,20 +38,20 @@ describe('drawer reducer', () => { ...@@ -38,20 +38,20 @@ describe('drawer reducer', () => {
it('should update the store when you click a project info button on the nav bar', async () => { it('should update the store when you click a project info button on the nav bar', async () => {
const { type } = await store.dispatch(openDrawer('project-info')); const { type } = await store.dispatch(openDrawer('project-info'));
const { open, pathName } = store.getState().drawer; const { open, drawerName } = store.getState().drawer;
expect(type).toBe('drawer/openDrawer'); expect(type).toBe('drawer/openDrawer');
expect(open).toBe(true); expect(open).toBe(true);
expect(pathName).toEqual('project-info'); expect(drawerName).toEqual('project-info');
}); });
it('should update the store when you click the close button on the drawer', async () => { it('should update the store when you click the close button on the drawer', async () => {
const { type } = await store.dispatch(closeDrawer('project-info')); const { type } = await store.dispatch(closeDrawer());
const { open, pathName } = store.getState().drawer; const { open, drawerName } = store.getState().drawer;
expect(type).toBe('drawer/closeDrawer'); expect(type).toBe('drawer/closeDrawer');
expect(open).toBe(false); expect(open).toBe(false);
expect(pathName).toEqual('project-info'); expect(drawerName).toEqual('none');
}); });
it.skip('should update the store when you type in the search', async () => { it.skip('should update the store when you type in the search', async () => {
......
...@@ -4,10 +4,9 @@ import { PathName } from '@/types/pathName'; ...@@ -4,10 +4,9 @@ import { PathName } from '@/types/pathName';
export const openDrawerReducer = (state: DrawerState, action: PayloadAction<PathName>): void => { export const openDrawerReducer = (state: DrawerState, action: PayloadAction<PathName>): void => {
state.open = true; state.open = true;
state.pathName = action.payload; state.drawerName = action.payload;
}; };
export const closeDrawerReducer = (state: DrawerState, action: PayloadAction<PathName>): void => { export const closeDrawerReducer = (state: DrawerState): void => {
state.open = false; state.open = false;
state.pathName = action.payload;
}; };
...@@ -4,7 +4,7 @@ import { openDrawerReducer, closeDrawerReducer } from './drawer.reducers'; ...@@ -4,7 +4,7 @@ import { openDrawerReducer, closeDrawerReducer } from './drawer.reducers';
const initialState: DrawerState = { const initialState: DrawerState = {
open: false, open: false,
pathName: 'none', drawerName: 'none',
}; };
const drawerSlice = createSlice({ const drawerSlice = createSlice({
......
export type DrawerState = { export type DrawerState = {
open: boolean; open: boolean;
pathName: 'none' | 'search' | 'project-info' | 'plugins' | 'export' | 'legend'; drawerName: 'none' | 'search' | 'project-info' | 'plugins' | 'export' | 'legend';
}; };
import { twMerge } from 'tailwind-merge';
import { Accordion as Ac } from 'react-accessible-accordion'; import { Accordion as Ac } from 'react-accessible-accordion';
import { DivAttributes } from 'react-accessible-accordion/dist/types/helpers/types'; import { DivAttributes } from 'react-accessible-accordion/dist/types/helpers/types';
...@@ -9,6 +10,7 @@ type AccordionProps = Pick<DivAttributes, Exclude<keyof DivAttributes, 'onChange ...@@ -9,6 +10,7 @@ type AccordionProps = Pick<DivAttributes, Exclude<keyof DivAttributes, 'onChange
allowMultipleExpanded?: boolean; allowMultipleExpanded?: boolean;
allowZeroExpanded?: boolean; allowZeroExpanded?: boolean;
onChange?(args: ID[]): void; onChange?(args: ID[]): void;
className?: string;
}; };
export const Accordion = ({ export const Accordion = ({
...@@ -17,6 +19,7 @@ export const Accordion = ({ ...@@ -17,6 +19,7 @@ export const Accordion = ({
allowMultipleExpanded, allowMultipleExpanded,
allowZeroExpanded, allowZeroExpanded,
onChange, onChange,
className,
...rest ...rest
}: AccordionProps): JSX.Element => ( }: AccordionProps): JSX.Element => (
<Ac <Ac
...@@ -24,6 +27,7 @@ export const Accordion = ({ ...@@ -24,6 +27,7 @@ export const Accordion = ({
allowMultipleExpanded={allowMultipleExpanded} allowMultipleExpanded={allowMultipleExpanded}
allowZeroExpanded={allowZeroExpanded} allowZeroExpanded={allowZeroExpanded}
onChange={onChange} onChange={onChange}
className={twMerge('text-base', className)}
{...rest} {...rest}
> >
{children} {children}
......
import { AccordionItemPanel as AIP } from 'react-accessible-accordion'; import { AccordionItemPanel as AIP } from 'react-accessible-accordion';
import { twMerge } from 'tailwind-merge';
interface AccordionItemPanelProps { interface AccordionItemPanelProps {
className?: string;
children: React.ReactNode; children: React.ReactNode;
} }
export const AccordionItemPanel = ({ children }: AccordionItemPanelProps): JSX.Element => ( export const AccordionItemPanel = ({
<AIP className="pb-4">{children}</AIP> className,
children,
}: AccordionItemPanelProps): JSX.Element => (
<AIP className={twMerge('pb-4', className)}>{children}</AIP>
); );
import { Reducer } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import {
ToolkitStoreWithSingleSlice,
createStoreInstanceUsingSliceReducer,
} from '../createStoreInstanceUsingSliceReducer';
interface WrapperProps {
children: React.ReactNode;
}
type GetReduxWrapperUsingSliceReducer = <StateType>(
name: string,
passedReducer: Reducer<StateType>,
) => {
Wrapper: ({ children }: WrapperProps) => JSX.Element;
store: ToolkitStoreWithSingleSlice<StateType>;
};
export const getReduxWrapperUsingSliceReducer: GetReduxWrapperUsingSliceReducer = (
reducerName,
reducerInstance,
) => {
const store = createStoreInstanceUsingSliceReducer(reducerName, reducerInstance);
const Wrapper = ({ children }: WrapperProps): JSX.Element => (
<Provider store={store}>{children}</Provider>
);
return { Wrapper, store };
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment