Skip to content
Snippets Groups Projects
Commit 7c09ccde authored by Adrian Orłów's avatar Adrian Orłów
Browse files

fix: pr rfc changes

parent c13a4728
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...,!65feat: Add reaction drawer (MIN-140)
Pipeline #82362 passed
Showing
with 221 additions and 57 deletions
import { openSearchDrawerWithSelectedTab, openSubmapsDrawer } from '@/redux/drawer/drawer.slice'; import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
import { reactionsFixture } from '@/models/fixtures/reactionFixture';
import {
openReactionDrawerById,
openSearchDrawerWithSelectedTab,
openSubmapsDrawer,
} from '@/redux/drawer/drawer.slice';
import { getReactionsByIds } from '@/redux/reactions/reactions.thunks';
import { StoreType } from '@/redux/store'; import { StoreType } from '@/redux/store';
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; import {
import { act, fireEvent, render, screen } from '@testing-library/react'; InitialStoreState,
getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore';
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import type {} from 'redux-thunk/extend-redux';
import { Drawer } from './Drawer.component'; import { Drawer } from './Drawer.component';
const renderComponent = (): { store: StoreType } => { const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore(); const { Wrapper, store } = getReduxWrapperWithStore(initialStore);
return ( return (
render( render(
<Wrapper> <Wrapper>
...@@ -77,4 +88,25 @@ describe('Drawer - component', () => { ...@@ -77,4 +88,25 @@ describe('Drawer - component', () => {
expect(screen.getByTestId('submap-drawer')).toBeInTheDocument(); expect(screen.getByTestId('submap-drawer')).toBeInTheDocument();
}); });
}); });
describe('reaction drawer', () => {
it('should open drawer and display reaction', async () => {
const { id } = reactionsFixture[FIRST_ARRAY_ELEMENT];
const { store } = renderComponent({
reactions: {
data: reactionsFixture,
loading: 'succeeded',
error: { message: '', name: '' },
},
});
expect(screen.queryByTestId('reaction-drawer')).not.toBeInTheDocument();
store.dispatch(getReactionsByIds([id]));
store.dispatch(openReactionDrawerById(id));
await waitFor(() => expect(screen.getByTestId('reaction-drawer')).toBeInTheDocument());
});
});
}); });
import { SECOND } from '@/constants/common'; import { SECOND_ARRAY_ELEMENT } from '@/constants/common';
import { reactionsFixture } from '@/models/fixtures/reactionFixture'; import { reactionsFixture } from '@/models/fixtures/reactionFixture';
import { DRAWER_INITIAL_STATE } from '@/redux/drawer/drawer.constants'; import { DRAWER_INITIAL_STATE } from '@/redux/drawer/drawer.constants';
import { StoreType } from '@/redux/store'; import { StoreType } from '@/redux/store';
...@@ -52,7 +52,7 @@ describe('ReactionDrawer - component', () => { ...@@ -52,7 +52,7 @@ describe('ReactionDrawer - component', () => {
}); });
describe('when there IS a matching reaction', () => { describe('when there IS a matching reaction', () => {
const reaction = reactionsFixture[SECOND]; const reaction = reactionsFixture[SECOND_ARRAY_ELEMENT];
const filteredReferences = reaction.references.filter( const filteredReferences = reaction.references.filter(
ref => ref.link !== null && ref.link !== undefined, ref => ref.link !== null && ref.link !== undefined,
......
import { currentDrawerReactionSelector } from '@/redux/reactions/reactions.selector'; import {
currentDrawerReactionGroupedReferencesSelector,
currentDrawerReactionSelector,
} from '@/redux/reactions/reactions.selector';
import { DrawerHeading } from '@/shared/DrawerHeading'; import { DrawerHeading } from '@/shared/DrawerHeading';
import { Icon } from '@/shared/Icon';
import { useMemo } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { DEFAULT_REFERENCE_SOURCE } from './ReactionDrawer.constants'; import { ReferenceGroup } from './ReferenceGroup';
import { getFilteredReferences } from './utils/getFilteredReferences';
import { getGroupedReferences } from './utils/getGroupedReferences';
export const ReactionDrawer = (): React.ReactNode => { export const ReactionDrawer = (): React.ReactNode => {
const reaction = useSelector(currentDrawerReactionSelector); const reaction = useSelector(currentDrawerReactionSelector);
const referencesGrouped = useSelector(currentDrawerReactionGroupedReferencesSelector);
const referencesGrouped = useMemo(() => {
const referencesFiltered = getFilteredReferences(reaction);
return getGroupedReferences(referencesFiltered);
}, [reaction]);
if (!reaction) { if (!reaction) {
return null; return null;
} }
return ( return (
<div className="h-full max-h-full"> <div className="h-full max-h-full" data-testid="reaction-drawer">
<DrawerHeading <DrawerHeading
title={ title={
<> <>
...@@ -34,18 +29,8 @@ export const ReactionDrawer = (): React.ReactNode => { ...@@ -34,18 +29,8 @@ export const ReactionDrawer = (): React.ReactNode => {
</div> </div>
<hr className="border-b border-b-divide" /> <hr className="border-b border-b-divide" />
<h3 className="font-semibold">Annotations:</h3> <h3 className="font-semibold">Annotations:</h3>
{referencesGrouped.map(({ source, references }) => ( {referencesGrouped.map(group => (
<> <ReferenceGroup key={group.source} group={group} />
<h3 className="font-semibold">Source: {source || DEFAULT_REFERENCE_SOURCE}</h3>
{references.map(({ id, link, type }) => (
<a key={id} href={link} target="_blank">
<div className="flex justify-between">
<span>{`${type} (${id})`}</span>
<Icon name="arrow" className="h-6 w-6 fill-font-500" />
</div>
</a>
))}
</>
))} ))}
</div> </div>
</div> </div>
......
import { StoreType } from '@/redux/store';
import { ReferenceFiltered } from '@/types/reference';
import {
InitialStoreState,
getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore';
import { render, screen } from '@testing-library/react';
import { Props, ReferenceGroup } from './ReferenceGroup.component';
const renderComponent = (
props: Props,
initialStoreState: InitialStoreState = {},
): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
return (
render(
<Wrapper>
<ReferenceGroup {...props} />
</Wrapper>,
),
{
store,
}
);
};
describe('ReactionDrawer - component', () => {
beforeEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
});
const singleReference = {
link: 'https://www.ncbi.nlm.nih.gov/pubmed/24448649',
article: {
title:
'The nutrient-responsive transcription factor TFE3 promotes autophagy, lysosomal biogenesis, and clearance of cellular debris.',
authors: [
'Martina JA',
' Diab HI',
' Lishu L',
' Jeong-A L',
' Patange S',
' Raben N',
' Puertollano R.',
],
journal: 'Science signaling',
year: 2014,
link: 'https://www.ncbi.nlm.nih.gov/pubmed/24448649',
pubmedId: '24448649',
citationCount: 321,
},
type: 'PUBMED',
resource: '24448649',
id: 154973,
annotatorClassName: '',
};
const cases: [string, ReferenceFiltered[]][] = [
['', [singleReference]],
[
'source1',
[
{
...singleReference,
annotatorClassName: 'source1',
id: 1,
},
{
...singleReference,
annotatorClassName: 'source1',
id: 2,
},
],
],
[
'source2',
[
{
...singleReference,
annotatorClassName: 'source2',
id: 3,
},
],
],
];
it.each(cases)('should show reference group with source=%s', (source, references) => {
const referencesTextHref: [string, string][] = references.map(ref => [
`${ref.type} (${ref.id})`,
ref.link as string,
]);
renderComponent({
group: {
source,
references,
},
});
referencesTextHref.forEach(([refText, href]) => {
const linkReferenceSpan = screen.getByText(refText, { exact: false });
const linkReferenceAnchor = linkReferenceSpan.closest('a');
expect(linkReferenceSpan).toBeInTheDocument();
expect(linkReferenceAnchor).toBeInTheDocument();
expect(linkReferenceAnchor?.href).toBe(`${href}`);
});
});
});
import { Icon } from '@/shared/Icon';
import { ReferenceGroup as ReferenceGroupType } from '@/types/reference';
import { DEFAULT_REFERENCE_SOURCE } from '../ReactionDrawer.constants';
export interface Props {
group: ReferenceGroupType;
}
export const ReferenceGroup = ({ group: { source, references } }: Props): JSX.Element => (
<>
<h3 className="font-semibold">Source: {source || DEFAULT_REFERENCE_SOURCE}</h3>
{references.map(({ id, link, type }) => (
<a key={id} href={link} target="_blank">
<div className="flex justify-between">
<span>{`${type} (${id})`}</span>
<Icon name="arrow" className="h-6 w-6 fill-font-500" />
</div>
</a>
))}
</>
);
export { ReferenceGroup } from './ReferenceGroup.component';
import { FIRST, SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; import { FIRST_ARRAY_ELEMENT, SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture'; import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture';
import { ELEMENT_SEARCH_RESULT_MOCK_ALIAS } from '@/models/mocks/elementSearchResultMock'; import { ELEMENT_SEARCH_RESULT_MOCK_ALIAS } from '@/models/mocks/elementSearchResultMock';
import { apiPath } from '@/redux/apiPath'; import { apiPath } from '@/redux/apiPath';
...@@ -31,7 +31,7 @@ describe('handleAliasResults - util', () => { ...@@ -31,7 +31,7 @@ describe('handleAliasResults - util', () => {
await waitFor(() => { await waitFor(() => {
const actions = store.getActions(); const actions = store.getActions();
expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
expect(actions[FIRST].type).toEqual('project/getMultiBioEntity/pending'); expect(actions[FIRST_ARRAY_ELEMENT].type).toEqual('project/getMultiBioEntity/pending');
}); });
}); });
}); });
/* eslint-disable no-magic-numbers */ /* eslint-disable no-magic-numbers */
import { FIRST, SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; import { FIRST_ARRAY_ELEMENT, SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture'; import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture';
import { reactionsFixture } from '@/models/fixtures/reactionFixture'; import { reactionsFixture } from '@/models/fixtures/reactionFixture';
import { import {
...@@ -47,7 +47,7 @@ describe('handleReactionResults - util', () => { ...@@ -47,7 +47,7 @@ describe('handleReactionResults - util', () => {
const actions = store.getActions(); const actions = store.getActions();
expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
expect(actions[2].type).toEqual('drawer/openReactionDrawerById'); expect(actions[2].type).toEqual('drawer/openReactionDrawerById');
expect(actions[2].payload).toEqual(reactionsFixture[FIRST].id); expect(actions[2].payload).toEqual(reactionsFixture[FIRST_ARRAY_ELEMENT].id);
}); });
it('should run setBioEntityContent to empty array as third action', () => { it('should run setBioEntityContent to empty array as third action', () => {
......
import { FIRST, SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; import { FIRST_ARRAY_ELEMENT, SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
import { getMultiBioEntity } from '@/redux/bioEntity/bioEntity.thunks'; import { getMultiBioEntity } from '@/redux/bioEntity/bioEntity.thunks';
import { openReactionDrawerById } from '@/redux/drawer/drawer.slice'; import { openReactionDrawerById } from '@/redux/drawer/drawer.slice';
import { getReactionsByIds } from '@/redux/reactions/reactions.thunks'; import { getReactionsByIds } from '@/redux/reactions/reactions.thunks';
...@@ -16,7 +16,7 @@ export const handleReactionResults = ...@@ -16,7 +16,7 @@ export const handleReactionResults =
return; return;
} }
const reaction = payload[FIRST]; const reaction = payload[FIRST_ARRAY_ELEMENT];
const { products, reactants, modifiers } = reaction; const { products, reactants, modifiers } = reaction;
const productsIds = products.map(p => p.aliasId); const productsIds = products.map(p => p.aliasId);
const reactantsIds = reactants.map(r => r.aliasId); const reactantsIds = reactants.map(r => r.aliasId);
......
import { FIRST } from '@/constants/common'; import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
import { AppDispatch } from '@/redux/store'; import { AppDispatch } from '@/redux/store';
import { ElementSearchResult } from '@/types/models'; import { ElementSearchResult } from '@/types/models';
import { handleAliasResults } from './handleAliasResults'; import { handleAliasResults } from './handleAliasResults';
...@@ -13,7 +13,7 @@ export const handleSearchResultAction = async ({ ...@@ -13,7 +13,7 @@ export const handleSearchResultAction = async ({
searchResults, searchResults,
dispatch, dispatch,
}: HandleSearchResultActionInput): Promise<void> => { }: HandleSearchResultActionInput): Promise<void> => {
const closestSearchResult = searchResults[FIRST]; const closestSearchResult = searchResults[FIRST_ARRAY_ELEMENT];
const { type } = closestSearchResult; const { type } = closestSearchResult;
const action = { const action = {
ALIAS: handleAliasResults, ALIAS: handleAliasResults,
......
export const SIZE_OF_EMPTY_ARRAY = 0; export const SIZE_OF_EMPTY_ARRAY = 0;
export const ZERO = 0; export const ZERO = 0;
export const FIRST = 0; export const FIRST_ARRAY_ELEMENT = 0;
export const ONE = 1; export const ONE = 1;
export const SECOND = 1; export const SECOND_ARRAY_ELEMENT = 1;
import { FIRST } from '@/constants/common'; import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
import { Reference } from '@/types/models'; import { Reference } from '@/types/models';
export const REFERENCES_MOCK_ALL_VALID: Reference[] = [ export const REFERENCES_MOCK_ALL_VALID: Reference[] = [
...@@ -175,4 +175,4 @@ export const REFERENCES_MOCK_ALL_INVALID: Reference[] = [ ...@@ -175,4 +175,4 @@ export const REFERENCES_MOCK_ALL_INVALID: Reference[] = [
}, },
]; ];
export const SINGLE_VALID_REFERENCE = REFERENCES_MOCK_ALL_VALID[FIRST]; export const SINGLE_VALID_REFERENCE = REFERENCES_MOCK_ALL_VALID[FIRST_ARRAY_ELEMENT];
...@@ -3,6 +3,8 @@ import { createSelector } from '@reduxjs/toolkit'; ...@@ -3,6 +3,8 @@ import { createSelector } from '@reduxjs/toolkit';
import { currentDrawerReactionIdSelector } from '../drawer/drawer.selectors'; import { currentDrawerReactionIdSelector } from '../drawer/drawer.selectors';
import { currentModelIdSelector } from '../models/models.selectors'; import { currentModelIdSelector } from '../models/models.selectors';
import { rootSelector } from '../root/root.selectors'; import { rootSelector } from '../root/root.selectors';
import { getReferencesWithoutEmptyLink } from './utils/getFilteredReferences';
import { getReferencesGroupedBySource } from './utils/getGroupedReferences';
export const reactionsSelector = createSelector(rootSelector, state => state.reactions); export const reactionsSelector = createSelector(rootSelector, state => state.reactions);
...@@ -25,3 +27,11 @@ export const currentDrawerReactionSelector = createSelector( ...@@ -25,3 +27,11 @@ export const currentDrawerReactionSelector = createSelector(
(reactions, currentDrawerReactionId) => (reactions, currentDrawerReactionId) =>
reactions.find(({ id }) => id === currentDrawerReactionId), reactions.find(({ id }) => id === currentDrawerReactionId),
); );
export const currentDrawerReactionGroupedReferencesSelector = createSelector(
currentDrawerReactionSelector,
reaction => {
const referencesFiltered = getReferencesWithoutEmptyLink(reaction);
return getReferencesGroupedBySource(referencesFiltered);
},
);
...@@ -3,8 +3,8 @@ import { ...@@ -3,8 +3,8 @@ import {
REFERENCES_MOCK_ALL_VALID, REFERENCES_MOCK_ALL_VALID,
} from '@/models/mocks/referencesMock'; } from '@/models/mocks/referencesMock';
import { Reaction } from '@/types/models'; import { Reaction } from '@/types/models';
import { ReferenceFiltered } from '../ReactionDrawer.types'; import { ReferenceFiltered } from '@/types/reference';
import { getFilteredReferences } from './getFilteredReferences'; import { getReferencesWithoutEmptyLink } from './getFilteredReferences';
describe('getFilteredReferences - subUtil', () => { describe('getFilteredReferences - subUtil', () => {
const cases: [Pick<Reaction, 'references'>, ReferenceFiltered[]][] = [ const cases: [Pick<Reaction, 'references'>, ReferenceFiltered[]][] = [
...@@ -29,6 +29,6 @@ describe('getFilteredReferences - subUtil', () => { ...@@ -29,6 +29,6 @@ describe('getFilteredReferences - subUtil', () => {
]; ];
it.each(cases)('should return valid filtered references', (reaction, result) => { it.each(cases)('should return valid filtered references', (reaction, result) => {
expect(getFilteredReferences(reaction)).toStrictEqual(result); expect(getReferencesWithoutEmptyLink(reaction)).toStrictEqual(result);
}); });
}); });
import { Reaction } from '@/types/models'; import { Reaction } from '@/types/models';
import { ReferenceFiltered } from '../ReactionDrawer.types'; import { ReferenceFiltered } from '@/types/reference';
type InputReaction = Pick<Reaction, 'references'>; type InputReaction = Pick<Reaction, 'references'>;
export const getFilteredReferences = (reaction: InputReaction | undefined): ReferenceFiltered[] => export const getReferencesWithoutEmptyLink = (
reaction: InputReaction | undefined,
): ReferenceFiltered[] =>
(reaction?.references || []).filter( (reaction?.references || []).filter(
(ref): ref is ReferenceFiltered => ref.link !== null && ref.link !== undefined, (ref): ref is ReferenceFiltered => ref.link !== null && ref.link !== undefined,
); );
import { ReferenceFiltered } from '../ReactionDrawer.types'; import { ReferenceFiltered } from '@/types/reference';
import { getGroupedReferences } from './getGroupedReferences'; import { getReferencesGroupedBySource } from './getGroupedReferences';
describe('getGroupedReferences - util', () => { describe('getGroupedReferences - util', () => {
const singleReference = { const singleReference = {
...@@ -82,7 +82,7 @@ describe('getGroupedReferences - util', () => { ...@@ -82,7 +82,7 @@ describe('getGroupedReferences - util', () => {
]; ];
it.each(cases)('should return correct grouped references', (references, referencesGrouped) => it.each(cases)('should return correct grouped references', (references, referencesGrouped) =>
expect(getGroupedReferences(references as ReferenceFiltered[])).toMatchObject( expect(getReferencesGroupedBySource(references as ReferenceFiltered[])).toMatchObject(
referencesGrouped, referencesGrouped,
), ),
); );
......
import { ReferenceFiltered, ReferenceGrouped } from '@/types/reference';
import { groupBy } from '@/utils/array/groupBy'; import { groupBy } from '@/utils/array/groupBy';
import { ReferenceFiltered, ReferenceGrouped } from '../ReactionDrawer.types';
export const getGroupedReferences = (references: ReferenceFiltered[]): ReferenceGrouped => { export const getReferencesGroupedBySource = (references: ReferenceFiltered[]): ReferenceGrouped => {
const referencesGroupedObject = groupBy(references, ref => ref.annotatorClassName); const referencesGroupedObject = groupBy(references, ref => ref.annotatorClassName);
return Object.entries(referencesGroupedObject).map(([source, refs]) => ({ return Object.entries(referencesGroupedObject).map(([source, refs]) => ({
......
...@@ -4,7 +4,7 @@ import { IconButton } from '@/shared/IconButton'; ...@@ -4,7 +4,7 @@ import { IconButton } from '@/shared/IconButton';
import { CLOSE_BUTTON_ROLE } from '../DrawerHeadingBackwardButton/DrawerHeadingBackwardButton.constants'; import { CLOSE_BUTTON_ROLE } from '../DrawerHeadingBackwardButton/DrawerHeadingBackwardButton.constants';
interface DrawerHeadingProps { interface DrawerHeadingProps {
title: string | JSX.Element; title: string | React.ReactNode;
} }
export const DrawerHeading = ({ title }: DrawerHeadingProps): JSX.Element => { export const DrawerHeading = ({ title }: DrawerHeadingProps): JSX.Element => {
......
import { Reference } from '@/types/models'; import { Reference } from './models';
export type ReferenceFiltered = Omit<Reference, 'link'> & { link: string }; export type ReferenceFiltered = Omit<Reference, 'link'> & { link: string };
export type ReferenceGrouped = { export type ReferenceGroup = {
references: ReferenceFiltered[]; references: ReferenceFiltered[];
source: string; source: string;
}[]; };
export type ReferenceGrouped = ReferenceGroup[];
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