diff --git a/src/components/FunctionalArea/TopBar/TopBar.component.test.tsx b/src/components/FunctionalArea/TopBar/TopBar.component.test.tsx index ae9f17c99c9042ec6c43ad19c455f335e7071b19..b4b95ab3472de6d85c4ad6c3db85268a6599c048 100644 --- a/src/components/FunctionalArea/TopBar/TopBar.component.test.tsx +++ b/src/components/FunctionalArea/TopBar/TopBar.component.test.tsx @@ -41,4 +41,16 @@ describe('TopBar - component', () => { expect(isOpen).toBe(true); expect(drawerName).toBe('submaps'); }); + + it('should open overlays drawer on overlays button click', () => { + const { store } = renderComponent({ drawer: initialStateFixture }); + + const button = screen.getByRole('button', { name: 'Overlays' }); + button.click(); + + const { isOpen, drawerName } = store.getState().drawer; + + expect(isOpen).toBe(true); + expect(drawerName).toBe('overlays'); + }); }); diff --git a/src/components/FunctionalArea/TopBar/TopBar.component.tsx b/src/components/FunctionalArea/TopBar/TopBar.component.tsx index 9641b686e7aa05ef497a1bf4022bd50c2c8779f7..77c4eab9a59caabf1aebe8739f2679fe4966cab2 100644 --- a/src/components/FunctionalArea/TopBar/TopBar.component.tsx +++ b/src/components/FunctionalArea/TopBar/TopBar.component.tsx @@ -1,6 +1,6 @@ import { SearchBar } from '@/components/FunctionalArea/TopBar/SearchBar'; import { UserAvatar } from '@/components/FunctionalArea/TopBar/UserAvatar'; -import { openSubmapsDrawer } from '@/redux/drawer/drawer.slice'; +import { openOverlaysDrawer, openSubmapsDrawer } from '@/redux/drawer/drawer.slice'; import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { Button } from '@/shared/Button'; @@ -11,6 +11,10 @@ export const TopBar = (): JSX.Element => { dispatch(openSubmapsDrawer()); }; + const onOverlaysClick = (): void => { + dispatch(openOverlaysDrawer()); + }; + return ( <div className="flex h-16 w-full flex-row items-center justify-between border-b border-font-500 border-opacity-[0.12] bg-white py-4 pl-7 pr-6"> <div className="flex flex-row items-center"> @@ -19,7 +23,7 @@ export const TopBar = (): JSX.Element => { <Button icon="plus" isIcon isFrontIcon className="ml-8 mr-4" onClick={onSubmapsClick}> Submaps </Button> - <Button icon="plus" isIcon isFrontIcon> + <Button icon="plus" isIcon isFrontIcon onClick={onOverlaysClick}> Overlays </Button> </div> diff --git a/src/components/Map/Drawer/Drawer.component.tsx b/src/components/Map/Drawer/Drawer.component.tsx index f47cc969b69a6d31352f9ca949b13c682584347d..ace83985ff36e0cad91467f50ae0a16c2a6d0c4d 100644 --- a/src/components/Map/Drawer/Drawer.component.tsx +++ b/src/components/Map/Drawer/Drawer.component.tsx @@ -5,6 +5,7 @@ import { twMerge } from 'tailwind-merge'; import { ReactionDrawer } from './ReactionDrawer'; import { SearchDrawerWrapper as SearchDrawerContent } from './SearchDrawerWrapper'; import { SubmapsDrawer } from './SubmapsDrawer'; +import { OverlaysDrawer } from './OverlaysDrawer'; import { BioEntityDrawer } from './BioEntityDrawer/BioEntityDrawer.component'; export const Drawer = (): JSX.Element => { @@ -21,6 +22,7 @@ export const Drawer = (): JSX.Element => { {isOpen && drawerName === 'search' && <SearchDrawerContent />} {isOpen && drawerName === 'submaps' && <SubmapsDrawer />} {isOpen && drawerName === 'reaction' && <ReactionDrawer />} + {isOpen && drawerName === 'overlays' && <OverlaysDrawer />} {isOpen && drawerName === 'bio-entity' && <BioEntityDrawer />} </div> ); diff --git a/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/GeneralOverlays.component.test.tsx b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/GeneralOverlays.component.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e8114a80254ed752dcdeb36300ee5f93c10acc55 --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/GeneralOverlays.component.test.tsx @@ -0,0 +1,43 @@ +import { + InitialStoreState, + getReduxWrapperWithStore, +} from '@/utils/testing/getReduxWrapperWithStore'; +import { render, screen } from '@testing-library/react'; +import { StoreType } from '@/redux/store'; +import { + OVERLAYS_PUBLIC_FETCHED_STATE_MOCK, + PUBLIC_OVERLAYS_MOCK, +} from '@/redux/overlays/overlays.mock'; +import { GeneralOverlays } from './GeneralOverlays.component'; + +const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); + + return ( + render( + <Wrapper> + <GeneralOverlays /> + </Wrapper>, + ), + { + store, + } + ); +}; + +describe('GeneralOverlays - component', () => { + describe('render', () => { + const publicOverlaysNames = PUBLIC_OVERLAYS_MOCK.map(({ name }) => name); + + it.each(publicOverlaysNames)('should display %s overlay item', source => { + renderComponent({ overlays: OVERLAYS_PUBLIC_FETCHED_STATE_MOCK }); + + expect(screen.getByText(source)).toBeInTheDocument(); + }); + }); + + describe('view overlays', () => { + // TODO implement when connecting logic to component + it.skip('should allow to turn on more then one overlay', () => {}); + }); +}); diff --git a/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/GeneralOverlays.component.tsx b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/GeneralOverlays.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c398dc343041ec6a6df218691482d81ed310e3a9 --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/GeneralOverlays.component.tsx @@ -0,0 +1,18 @@ +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { overlaysDataSelector } from '@/redux/overlays/overlays.selectors'; +import { OverlayListItem } from './OverlayListItem'; + +export const GeneralOverlays = (): JSX.Element => { + const generalPublicOverlays = useAppSelector(overlaysDataSelector); + + return ( + <div className="border-b border-b-divide p-6"> + <p className="mb-5 text-sm font-semibold">General Overlays:</p> + <ul> + {generalPublicOverlays.map(overlay => ( + <OverlayListItem key={overlay.idObject} name={overlay.name} /> + ))} + </ul> + </div> + ); +}; diff --git a/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/OverlayListItem.component.test.tsx b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/OverlayListItem.component.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2ae9126e2c73e07ea4345317757dff1eb53426ac --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/OverlayListItem.component.test.tsx @@ -0,0 +1,36 @@ +import { StoreType } from '@/redux/store'; +import { render, screen } from '@testing-library/react'; +import { + InitialStoreState, + getReduxWrapperWithStore, +} from '@/utils/testing/getReduxWrapperWithStore'; +import { OverlayListItem } from './OverlayListItem.component'; + +const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); + + return ( + render( + <Wrapper> + <OverlayListItem name="Ageing brain" /> + </Wrapper>, + ), + { + store, + } + ); +}; + +describe('OverlayListItem - component', () => { + it('should render component with correct properties', () => { + renderComponent(); + + expect(screen.getByText('Ageing brain')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'View' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Download' })).toBeInTheDocument(); + }); + // TODO implement when connecting logic to component + it.skip('should trigger view overlays on view button click', () => {}); + // TODO implement when connecting logic to component + it.skip('should trigger download overlay to PC on download button click', () => {}); +}); diff --git a/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/OverlayListItem.component.tsx b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/OverlayListItem.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a29195c79b88436e94ca026360d3e8d6da976d4e --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/OverlayListItem.component.tsx @@ -0,0 +1,24 @@ +import { Button } from '@/shared/Button'; + +interface OverlayListItemProps { + name: string; +} + +export const OverlayListItem = ({ name }: OverlayListItemProps): JSX.Element => { + const onViewOverlay = (): void => {}; + const onDownloadOverlay = (): void => {}; + + return ( + <li className="flex flex-row flex-nowrap justify-between pl-5 [&:not(:last-of-type)]:mb-4"> + <span>{name}</span> + <div className="flex flex-row flex-nowrap"> + <Button variantStyles="ghost" className="mr-4 max-h-8" onClick={onViewOverlay}> + View + </Button> + <Button className="max-h-8" variantStyles="ghost" onClick={onDownloadOverlay}> + Download + </Button> + </div> + </li> + ); +}; diff --git a/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/index.ts b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..1864e773ef7bdb8335a11f06dd79734e318b86dc --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/index.ts @@ -0,0 +1 @@ +export { OverlayListItem } from './OverlayListItem.component'; diff --git a/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/index.ts b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..03394a71c41d6b0374c8d38dc72a265869f72120 --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/index.ts @@ -0,0 +1 @@ +export { GeneralOverlays } from './GeneralOverlays.component'; diff --git a/src/components/Map/Drawer/OverlaysDrawer/OverlaysDrawer.component.tsx b/src/components/Map/Drawer/OverlaysDrawer/OverlaysDrawer.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..dacfc35bd0ccdeab6a213e68aa4996a7f4777274 --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/OverlaysDrawer.component.tsx @@ -0,0 +1,11 @@ +import { DrawerHeading } from '@/shared/DrawerHeading'; +import { GeneralOverlays } from './GeneralOverlays'; + +export const OverlaysDrawer = (): JSX.Element => { + return ( + <div data-testid="overlays-drawer"> + <DrawerHeading title="Overlays" /> + <GeneralOverlays /> + </div> + ); +}; diff --git a/src/components/Map/Drawer/OverlaysDrawer/index.ts b/src/components/Map/Drawer/OverlaysDrawer/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..2be3e15de1f9778f2077c68067ff7e7e2fd582ea --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/index.ts @@ -0,0 +1 @@ +export { OverlaysDrawer } from './OverlaysDrawer.component'; diff --git a/src/redux/drawer/drawer.reducers.ts b/src/redux/drawer/drawer.reducers.ts index bd9131823d3160fcb886c4bf2c0ae02fab2bf1b3..8ecb83281c39220a3909308e0c902e518f68842b 100644 --- a/src/redux/drawer/drawer.reducers.ts +++ b/src/redux/drawer/drawer.reducers.ts @@ -28,6 +28,11 @@ export const openSubmapsDrawerReducer = (state: DrawerState): void => { state.drawerName = 'submaps'; }; +export const openOverlaysDrawerReducer = (state: DrawerState): void => { + state.isOpen = true; + state.drawerName = 'overlays'; +}; + export const selectTabReducer = ( state: DrawerState, action: OpenSearchDrawerWithSelectedTabReducerAction, diff --git a/src/redux/drawer/drawer.slice.ts b/src/redux/drawer/drawer.slice.ts index e15cf4db8d83549baf52bddc37548c8d53b27d8c..769b5cf0a23560b03b607ccfff67beff81e429f4 100644 --- a/src/redux/drawer/drawer.slice.ts +++ b/src/redux/drawer/drawer.slice.ts @@ -7,6 +7,7 @@ import { displayEntityDetailsReducer, displayGroupedSearchResultsReducer, openDrawerReducer, + openOverlaysDrawerReducer, openBioEntityDrawerByIdReducer, openReactionDrawerByIdReducer, openSearchDrawerWithSelectedTabReducer, @@ -22,6 +23,7 @@ const drawerSlice = createSlice({ openDrawer: openDrawerReducer, openSearchDrawerWithSelectedTab: openSearchDrawerWithSelectedTabReducer, openSubmapsDrawer: openSubmapsDrawerReducer, + openOverlaysDrawer: openOverlaysDrawerReducer, selectTab: selectTabReducer, closeDrawer: closeDrawerReducer, displayDrugsList: displayDrugsListReducer, @@ -38,6 +40,7 @@ export const { openDrawer, openSearchDrawerWithSelectedTab, openSubmapsDrawer, + openOverlaysDrawer, selectTab, closeDrawer, displayDrugsList, diff --git a/src/redux/overlays/overlays.mock.ts b/src/redux/overlays/overlays.mock.ts index 1a8037b6ba6e1ca5a7d096a71ae79a6f3c388fd5..cdb5593cba6857acbbcafbb9d1886f93457e33cf 100644 --- a/src/redux/overlays/overlays.mock.ts +++ b/src/redux/overlays/overlays.mock.ts @@ -1,4 +1,5 @@ import { DEFAULT_ERROR } from '@/constants/errors'; +import { MapOverlay } from '@/types/models'; import { OverlaysState } from './overlays.types'; export const OVERLAYS_INITIAL_STATE_MOCK: OverlaysState = { @@ -6,3 +7,74 @@ export const OVERLAYS_INITIAL_STATE_MOCK: OverlaysState = { loading: 'idle', error: DEFAULT_ERROR, }; + +export const PUBLIC_OVERLAYS_MOCK: MapOverlay[] = [ + { + name: 'PD substantia nigra', + googleLicenseConsent: false, + creator: 'appu-admin', + description: + 'Differential transcriptome expression from post mortem tissue. Meta-analysis from 8 published datasets, FDR = 0.05, see PMIDs 23832570 and 25447234.', + genomeType: null, + genomeVersion: null, + idObject: 11, + publicOverlay: true, + type: 'GENERIC', + order: 1, + }, + { + name: 'Ageing brain', + googleLicenseConsent: false, + creator: 'appu-admin', + description: + 'Differential transcriptome expression from post mortem tissue. Source: Allen Brain Atlas datasets, see PMID 25447234.', + genomeType: null, + genomeVersion: null, + idObject: 12, + publicOverlay: true, + type: 'GENERIC', + order: 2, + }, + { + name: 'PRKN variants example', + googleLicenseConsent: false, + creator: 'appu-admin', + description: 'PRKN variants', + genomeType: 'UCSC', + genomeVersion: 'hg19', + idObject: 17, + publicOverlay: true, + type: 'GENETIC_VARIANT', + order: 3, + }, + { + name: 'PRKN variants doubled', + googleLicenseConsent: false, + creator: 'appu-admin', + description: 'PRKN variants', + genomeType: 'UCSC', + genomeVersion: 'hg19', + idObject: 18, + publicOverlay: true, + type: 'GENETIC_VARIANT', + order: 4, + }, + { + name: 'Generic advanced format overlay', + googleLicenseConsent: false, + creator: 'appu-admin', + description: 'Data set provided by a user', + genomeType: null, + genomeVersion: null, + idObject: 20, + publicOverlay: true, + type: 'GENERIC', + order: 5, + }, +]; + +export const OVERLAYS_PUBLIC_FETCHED_STATE_MOCK: OverlaysState = { + data: PUBLIC_OVERLAYS_MOCK, + loading: 'succeeded', + error: DEFAULT_ERROR, +}; diff --git a/src/types/drawerName.ts b/src/types/drawerName.ts index d34edf92b24a530f1055aa5fe92aa29d26820e8d..a5f3e3d2ff2f0155c67bdb1a458fc2ce64c5764f 100644 --- a/src/types/drawerName.ts +++ b/src/types/drawerName.ts @@ -7,4 +7,5 @@ export type DrawerName = | 'legend' | 'submaps' | 'reaction' + | 'overlays' | 'bio-entity';