Unverified Commit 91186a01 authored by Imran Salam's avatar Imran Salam Committed by GitHub
Browse files

Fix focus entity on GB being stored in web browser history (#446)

parent 240644c4
......@@ -161,7 +161,9 @@ export const ExampleObjectLinks = (props: BrowserProps) => {
return (
<div key={exampleObject.object_id} className={styles.exampleLink}>
<Link to={path}>Example {exampleObject.type}</Link>
<Link to={path} replace>
Example {exampleObject.type}
</Link>
</div>
);
});
......
......@@ -144,7 +144,7 @@ const GeneSummary = () => {
<div className={`${styles.row} ${styles.spaceAbove}`}>
<div className={styles.value}>
<ViewInApp links={{ entityViewer: entityViewerUrl }} />
<ViewInApp links={{ entityViewer: { url: entityViewerUrl } }} />
</div>
</div>
</div>
......
......@@ -328,7 +328,7 @@ const TranscriptSummary = () => {
<div className={styles.value}>
<ViewInApp
classNames={{ label: styles.lightText }}
links={{ entityViewer: entityViewerUrl }}
links={{ entityViewer: { url: entityViewerUrl } }}
/>
</div>
</div>
......
......@@ -20,7 +20,7 @@
font-weight: 300;
}
.title{
.title {
font-size: 14px;
margin: 5px 0 10px 10px;
font-weight: $bold;
......
......@@ -15,148 +15,250 @@
*/
import React from 'react';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import faker from 'faker';
import times from 'lodash/times';
import set from 'lodash/fp/set';
import { createEnsObject } from 'tests/fixtures/ens-object';
import { TrackPanelBookmarks } from './TrackPanelBookmarks';
import { DrawerView } from 'src/content/app/browser/drawer/drawerState';
import { PreviouslyViewedObject } from '../../trackPanelState';
import * as trackPanelActions from '../../trackPanelActions';
jest.mock('react-router-dom', () => ({
Link: (props: any) => (
<div {...props} className={'link'}>
<a href={props.to} onClick={props.onClick}>
{props.children}
</div>
</a>
)
}));
const createPreviouslyViewedLink = (): PreviouslyViewedObject => ({
const genomeId = 'triticum_aestivum_GCA_900519105_1';
const geneId = 'TraesCS3D02G273600';
const region = '3D:2585940-2634711';
const geneObjectId = `${genomeId}:gene:${geneId}`;
const regionObjectId = `${genomeId}:region:${region}`;
const createRandomPreviouslyViewedObject = (): PreviouslyViewedObject => ({
genome_id: faker.random.word(),
object_id: `${faker.random.word()}:gene:${faker.random.uuid()}`,
object_type: 'gene',
label: faker.random.word()
});
const closeTrackPanelModal = jest.fn();
const updateTrackStatesAndSave = jest.fn();
const fetchExampleEnsObjects = jest.fn();
const changeDrawerViewAndOpen = jest.fn();
const previouslyViewedObjects = [
{
genome_id: genomeId,
object_id: geneObjectId,
object_type: 'gene',
label: geneId
},
{
genome_id: genomeId,
object_id: regionObjectId,
object_type: 'region',
label: region
}
];
describe('<TrackPanelBookmarks />', () => {
const numberOfExampleObjects = faker.random.number({ min: 5, max: 10 });
const numberOfPreviouslyViewedObjects = faker.random.number({
min: 5,
max: 20
});
const example_objects = [
{
id: geneId,
type: 'gene'
},
{
id: region,
type: 'region'
}
];
const mockState = {
browser: {
browserEntity: {
activeGenomeId: genomeId,
activeEnsObjectIds: {
[genomeId]: geneObjectId
}
},
trackPanel: {
[genomeId]: {
isTrackPanelModalOpened: true,
trackPanelModalView: '',
previouslyViewedObjects
}
}
},
drawer: {
isDrawerOpened: { [genomeId]: false },
drawerView: { [genomeId]: DrawerView.BOOKMARKS },
activeDrawerTrackIds: {}
},
ensObjects: {
[geneObjectId]: {
data: {
description: 'Heat shock protein 101',
genome_id: genomeId,
label: geneId,
location: {
chromosome: '3D',
end: 379539827,
start: 379535906
},
stable_id: geneId,
type: 'gene',
object_id: geneObjectId
}
},
[regionObjectId]: {
data: {
genome_id: genomeId,
label: region,
location: {
chromosome: '3D',
start: 2585940,
end: 2634711
},
type: 'region',
object_id: regionObjectId
}
}
},
genome: {
genomeInfo: {
genomeInfoData: {
[genomeId]: {
example_objects,
genome_id: genomeId
}
}
}
}
};
const mockStore = configureMockStore([thunk]);
let store: ReturnType<typeof mockStore>;
const props = {
activeGenomeId: faker.random.word(),
exampleEnsObjects: times(numberOfExampleObjects, () => createEnsObject()),
previouslyViewedObjects: times(numberOfPreviouslyViewedObjects, () =>
createPreviouslyViewedLink()
),
fetchExampleEnsObjects,
updateTrackStatesAndSave,
closeTrackPanelModal,
changeDrawerViewAndOpen
};
const wrapInRedux = (state: typeof mockState = mockState) => {
store = mockStore(state);
return render(
<Provider store={store}>
<TrackPanelBookmarks />
</Provider>
);
};
describe('<TrackPanelBookmarks />', () => {
beforeEach(() => {
jest.resetAllMocks();
});
it('renders example links', () => {
wrapInRedux();
const exampleGeneLink = screen.getByText('Example gene') as HTMLElement;
const exampleRegionLink = screen.getByText('Example region') as HTMLElement;
const expectedGeneHref = `/genome-browser/${genomeId}?focus=gene:${geneId}`;
const expectedRegionHref = `/genome-browser/${genomeId}?focus=region:${region}`;
expect(exampleGeneLink.getAttribute('href')).toBe(expectedGeneHref);
expect(exampleRegionLink.getAttribute('href')).toBe(expectedRegionHref);
});
it('renders previously viewed links', () => {
const { container } = render(<TrackPanelBookmarks {...props} />);
const links = [...container.querySelectorAll('.link')] as HTMLElement[];
const linkTexts = props.previouslyViewedObjects.map(({ label }) => label);
wrapInRedux();
const geneLink = screen.getByText(geneId) as HTMLElement;
const regionLink = screen.getByText(region) as HTMLElement;
expect(
linkTexts.every((text) =>
links.find((link) => {
return link.innerHTML === text;
})
)
).toBe(true);
const expectedGeneHref = `/genome-browser/${genomeId}?focus=gene:${geneId}`;
const expectedRegionHref = `/genome-browser/${genomeId}?focus=region:${region}`;
expect(geneLink.getAttribute('href')).toBe(expectedGeneHref);
expect(regionLink.getAttribute('href')).toBe(expectedRegionHref);
});
it('closes the bookmarks modal when a bookmark link is clicked', () => {
render(<TrackPanelBookmarks {...props} />);
const previouslyViewedLinksContainer = screen.getByTestId(
'previously viewed links'
);
const firstLink = (previouslyViewedLinksContainer as HTMLElement).querySelector(
'.link'
);
jest.spyOn(trackPanelActions, 'closeTrackPanelModal');
const { container } = wrapInRedux();
const firstLink = container.querySelector('a');
userEvent.click(firstLink as HTMLElement);
expect(closeTrackPanelModal).toBeCalled();
expect(trackPanelActions.closeTrackPanelModal).toHaveBeenCalled();
(trackPanelActions.closeTrackPanelModal as any).mockRestore();
});
it('shows the ellipsis only when the total objects is more than 20', () => {
const previouslyViewedObjects = times(20, () =>
createPreviouslyViewedLink()
);
const { rerender } = render(
<TrackPanelBookmarks
{...props}
previouslyViewedObjects={previouslyViewedObjects}
/>
it('shows the ellipsis only when there are more than 20 objects', () => {
let wrapper = wrapInRedux(
set(
`browser.trackPanel.${genomeId}.previouslyViewedObjects`,
times(20, () => createRandomPreviouslyViewedObject()),
mockState
)
);
const previouslyViewedSection = screen.getByText('Previously viewed');
expect(previouslyViewedSection.querySelector('button')).toBeFalsy();
expect(
wrapper.container.querySelector(
'.trackPanelBookmarks .sectionTitle button'
)
).toBeFalsy();
// Add another link to make it 21 links
previouslyViewedObjects.push(createPreviouslyViewedLink());
rerender(
<TrackPanelBookmarks
{...props}
previouslyViewedObjects={previouslyViewedObjects}
/>
// Add 21 links to see if ellipsis is shown
wrapper = wrapInRedux(
set(
`browser.trackPanel.${genomeId}.previouslyViewedObjects`,
times(21, () => createRandomPreviouslyViewedObject()),
mockState
)
);
expect(previouslyViewedSection.querySelector('button')).toBeTruthy();
expect(
wrapper.container.querySelector(
'.trackPanelBookmarks .sectionTitle button'
)
).toBeTruthy();
});
it('calls changeDrawerViewAndOpen when the ellipsis is clicked', () => {
const previouslyViewedObjects = times(21, () =>
createPreviouslyViewedLink()
);
render(
<TrackPanelBookmarks
{...props}
previouslyViewedObjects={previouslyViewedObjects}
/>
it('changes drawer view and toggles drawer when the ellipsis is clicked', () => {
const { container } = wrapInRedux(
set(
`browser.trackPanel.${genomeId}.previouslyViewedObjects`,
times(21, () => createRandomPreviouslyViewedObject()),
mockState
)
);
const previouslyViewedSection = screen.getByText('Previously viewed');
const ellipsisButton = previouslyViewedSection.querySelector(
'button'
const ellipsisButton = container.querySelector(
'.sectionTitle button'
) as HTMLElement;
userEvent.click(ellipsisButton);
expect(changeDrawerViewAndOpen).toBeCalled();
});
const dispatchedDrawerActions = store.getActions();
it('renders correct number of links to example objects', () => {
render(<TrackPanelBookmarks {...props} />);
const exampleLinksWrapper = screen.getByTestId('example links');
const updateDrawerViewAction = dispatchedDrawerActions.find(
(action) => action.type === 'drawer/update-drawer-view'
);
const toggleDrawerAction = dispatchedDrawerActions.find(
(action) => action.type === 'drawer/toggle-drawer'
);
expect(exampleLinksWrapper?.querySelectorAll('.link').length).toBe(
numberOfExampleObjects
expect(updateDrawerViewAction.payload[genomeId]).toEqual(
DrawerView.BOOKMARKS
);
expect(toggleDrawerAction.payload[genomeId]).toEqual(true);
});
it('calls closeTrackPanelModal when an example object link is clicked', () => {
render(<TrackPanelBookmarks {...props} />);
const exampleLinksWrapper = screen.getByTestId('example links');
const exampleLink = exampleLinksWrapper?.querySelector('.link');
userEvent.click(exampleLink as HTMLElement);
it('renders correct number of links to example objects', () => {
const { container } = wrapInRedux();
expect(closeTrackPanelModal).toBeCalled();
expect(container.querySelectorAll('.exampleLinks a').length).toBe(
example_objects.length
);
});
});
......@@ -15,58 +15,47 @@
*/
import React from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { useSelector, useDispatch } from 'react-redux';
import upperFirst from 'lodash/upperFirst';
import { Link } from 'react-router-dom';
import analyticsTracking from 'src/services/analytics-service';
import * as urlFor from 'src/shared/helpers/urlHelper';
import { buildFocusIdForUrl } from 'src/shared/state/ens-object/ensObjectHelpers';
import { getBrowserActiveGenomeId } from 'src/content/app/browser/browserSelectors';
import { getActiveGenomePreviouslyViewedObjects } from 'src/content/app/browser/track-panel/trackPanelSelectors';
import { getExampleEnsObjects } from 'src/shared/state/ens-object/ensObjectSelectors';
import { closeTrackPanelModal } from '../../trackPanelActions';
import { updateTrackStatesAndSave } from 'src/content/app/browser/browserActions';
import { changeDrawerViewAndOpen } from 'src/content/app/browser/drawer/drawerActions';
import ImageButton from 'src/shared/components/image-button/ImageButton';
import { ReactComponent as EllipsisIcon } from 'static/img/track-panel/ellipsis.svg';
import { RootState } from 'src/store';
import { EnsObject } from 'src/shared/state/ens-object/ensObjectTypes';
import { Status } from 'src/shared/types/status';
import { PreviouslyViewedObject } from 'src/content/app/browser/track-panel/trackPanelState';
import { BrowserTrackStates } from 'src/content/app/browser/track-panel/trackPanelConfig';
import styles from './TrackPanelBookmarks.scss';
import { DrawerView } from 'src/content/app/browser/drawer/drawerState';
export type TrackPanelBookmarksProps = {
activeGenomeId: string | null;
exampleEnsObjects: EnsObject[];
previouslyViewedObjects: PreviouslyViewedObject[];
updateTrackStatesAndSave: (trackStates: BrowserTrackStates) => void;
closeTrackPanelModal: () => void;
changeDrawerViewAndOpen: (drawerView: DrawerView) => void;
};
export const ExampleLinks = () => {
const exampleEnsObjects = useSelector(getExampleEnsObjects);
const activeGenomeId = useSelector(getBrowserActiveGenomeId);
const dispatch = useDispatch();
const onLinkClick = () => dispatch(closeTrackPanelModal());
type ExampleLinksProps = Pick<
TrackPanelBookmarksProps,
'exampleEnsObjects' | 'activeGenomeId' | 'closeTrackPanelModal'
>;
export const ExampleLinks = (props: ExampleLinksProps) => {
return (
<div data-test-id="example links">
<div data-test-id="example links" className="exampleLinks">
<div className={styles.sectionTitle}>Example links</div>
{props.exampleEnsObjects.map((exampleObject) => {
{exampleEnsObjects.map((exampleObject) => {
const path = urlFor.browser({
genomeId: props.activeGenomeId,
genomeId: activeGenomeId,
focus: buildFocusIdForUrl(exampleObject.object_id)
});
return (
<div key={exampleObject.object_id} className={styles.linkHolder}>
<Link to={path} onClick={props.closeTrackPanelModal}>
<Link to={path} onClick={onLinkClick} replace>
Example {exampleObject.type}
</Link>
</div>
......@@ -76,14 +65,12 @@ export const ExampleLinks = (props: ExampleLinksProps) => {
);
};
type PreviouslyViewedLinksProps = Pick<
TrackPanelBookmarksProps,
| 'previouslyViewedObjects'
| 'updateTrackStatesAndSave'
| 'closeTrackPanelModal'
>;
export const PreviouslyViewedLinks = () => {
const previouslyViewedObjects = useSelector(
getActiveGenomePreviouslyViewedObjects
);
const dispatch = useDispatch();
export const PreviouslyViewedLinks = (props: PreviouslyViewedLinksProps) => {
const onLinkClick = (objectType: string, index: number) => {
analyticsTracking.trackEvent({
category: 'recent_bookmark_link',
......@@ -92,12 +79,12 @@ export const PreviouslyViewedLinks = (props: PreviouslyViewedLinksProps) => {
value: index + 1
});
props.closeTrackPanelModal();
dispatch(closeTrackPanelModal());
};
return (
<div data-test-id="previously viewed links">
{[...props.previouslyViewedObjects]
{[...previouslyViewedObjects]
.reverse()
.map((previouslyViewedObject, index) => {
const path = urlFor.browser({
......@@ -111,6 +98,7 @@ export const PreviouslyViewedLinks = (props: PreviouslyViewedLinksProps) => {
className={styles.linkHolder}
>
<Link
replace
to={path}
onClick={() =>
onLinkClick(previouslyViewedObject.object_type, index)
......@@ -128,14 +116,12 @@ export const PreviouslyViewedLinks = (props: PreviouslyViewedLinksProps) => {
);
};
export const TrackPanelBookmarks = (props: TrackPanelBookmarksProps) => {
const {
previouslyViewedObjects,
exampleEnsObjects,
activeGenomeId,
updateTrackStatesAndSave,
closeTrackPanelModal
} = props;
export const TrackPanelBookmarks = () => {
const previouslyViewedObjects = useSelector(
getActiveGenomePreviouslyViewedObjects
);
const exampleEnsObjects = useSelector(getExampleEnsObjects);
const dispatch = useDispatch();
const limitedPreviouslyViewedObjects = previouslyViewedObjects.slice(-20);
......@@ -147,26 +133,18 @@ export const TrackPanelBookmarks = (props: TrackPanelBookmarksProps) => {
value: previouslyViewedObjects.length
});
props.changeDrawerViewAndOpen(DrawerView.BOOKMARKS);
dispatch(changeDrawerViewAndOpen(DrawerView.BOOKMARKS));
};
return (
<section className="trackPanelBookmarks">
<div className={styles.title}>Bookmarks</div>
{exampleEnsObjects.length ? (
<>
<ExampleLinks
exampleEnsObjects={exampleEnsObjects}
activeGenomeId={activeGenomeId}
closeTrackPanelModal={closeTrackPanelModal}
/>
</>
) : null}
{exampleEnsObjects.length ? <ExampleLinks /> : null}
{limitedPreviouslyViewedObjects.length ? (
<>
<div className={styles.sectionTitle}>
<div data-test-id="previously viewed" className={styles.sectionTitle}>
Previously viewed
{props.previouslyViewedObjects.length > 20 && (
{previouslyViewedObjects.length > 20 && (
<span className={styles.ellipsis}>
<ImageButton
status={Status.DEFAULT}
......