Commit bcd4f9b5 authored by Lyza Danger Gardner's avatar Lyza Danger Gardner Committed by Lyza Gardner

Add `useRootThread` hook

Refactor root-thread usage by UI components to be via a preact hook
parent e76b5470
......@@ -4,6 +4,7 @@ import propTypes from 'prop-types';
import useStore from '../store/use-store';
import { withServices } from '../util/service-context';
import useRootThread from './hooks/use-root-thread';
import ThreadList from './thread-list';
import SidebarContentError from './sidebar-content-error';
......@@ -11,17 +12,11 @@ import SidebarContentError from './sidebar-content-error';
/**
* The main content for the single annotation page (aka. https://hypothes.is/a/<annotation ID>)
*/
function AnnotationViewerContent({
loadAnnotationsService,
onLogin,
rootThread: rootThreadService,
}) {
function AnnotationViewerContent({ loadAnnotationsService, onLogin }) {
const annotationId = useStore(store => store.routeParams().id);
const clearAnnotations = useStore(store => store.clearAnnotations);
const highlightAnnotations = useStore(store => store.highlightAnnotations);
const rootThread = useStore(store =>
rootThreadService.thread(store.getState())
);
const rootThread = useRootThread();
const setExpanded = useStore(store => store.setExpanded);
const userid = useStore(store => store.profile().userid);
......@@ -100,12 +95,8 @@ AnnotationViewerContent.propTypes = {
// Injected.
loadAnnotationsService: propTypes.object,
rootThread: propTypes.object,
};
AnnotationViewerContent.injectedProps = [
'loadAnnotationsService',
'rootThread',
];
AnnotationViewerContent.injectedProps = ['loadAnnotationsService'];
export default withServices(AnnotationViewerContent);
import { useService } from '../../util/service-context';
import useStore from '../../store/use-store';
/**
* Gather together state relevant to building a root thread of annotations and
* replies and return an updated root thread when changes occur.
*/
export default function useRootThread() {
const rootThreadService = useService('rootThread');
// Use a to-be-written selector to get relevant selection state, e.g.
// filters and forced-visible annotations, etc.
// const selectionState = useStore(store => store.getSelectionState());
// const route = useStore(store => store.routeName());
// const annotations = useStore(store => store.annotations());
// return useMemo(() => rootThreadService.buildRootThread(annotations, selectionState, route), [annotations, selectionState, route]);
return useStore(store => rootThreadService.thread(store.getState()));
}
import { createElement } from 'preact';
import { useMemo } from 'preact/hooks';
import propTypes from 'prop-types';
import useStore from '../store/use-store';
import uiConstants from '../ui-constants';
import { withServices } from '../util/service-context';
import useRootThread from './hooks/use-root-thread';
import Button from './button';
......@@ -25,8 +24,8 @@ const countVisibleAnns = annThread => {
* UI for displaying information about the currently-applied filtering of
* annotations, and, in some cases, a mechanism for clearing the filter(s).
* */
function SearchStatusBar({ rootThread }) {
const thread = useStore(store => rootThread.thread(store.getState()));
function SearchStatusBar() {
const thread = useRootThread();
const actions = useStore(store => ({
clearSelection: store.clearSelection,
......@@ -170,11 +169,7 @@ function SearchStatusBar({ rootThread }) {
);
}
SearchStatusBar.propTypes = {
// Injected services.
rootThread: propTypes.object.isRequired,
};
SearchStatusBar.injectedProps = ['rootThread'];
// Necessary for test-mocking purposes (`mockImportedComponents`)
SearchStatusBar.propTypes = {};
export default withServices(SearchStatusBar);
export default SearchStatusBar;
......@@ -2,6 +2,7 @@ import { createElement } from 'preact';
import propTypes from 'prop-types';
import { useEffect, useRef } from 'preact/hooks';
import useRootThread from './hooks/use-root-thread';
import { withServices } from '../util/service-context';
import useStore from '../store/use-store';
import { tabForAnnotation } from '../util/tabs';
......@@ -22,12 +23,9 @@ function SidebarContent({
onLogin,
onSignUp,
loadAnnotationsService,
rootThread: rootThreadService,
streamer,
}) {
const rootThread = useStore(store =>
rootThreadService.thread(store.getState())
);
const rootThread = useRootThread();
// Store state values
const focusedGroupId = useStore(store => store.focusedGroupId());
......@@ -153,14 +151,12 @@ SidebarContent.propTypes = {
// Injected
frameSync: propTypes.object,
loadAnnotationsService: propTypes.object,
rootThread: propTypes.object,
streamer: propTypes.object,
};
SidebarContent.injectedProps = [
'frameSync',
'loadAnnotationsService',
'rootThread',
'streamer',
];
......
......@@ -4,6 +4,7 @@ import propTypes from 'prop-types';
import * as searchFilter from '../util/search-filter';
import { withServices } from '../util/service-context';
import useRootThread from './hooks/use-root-thread';
import useStore from '../store/use-store';
import ThreadList from './thread-list';
......@@ -11,7 +12,7 @@ import ThreadList from './thread-list';
/**
* The main content of the "stream" route (https://hypothes.is/stream)
*/
function StreamContent({ api, rootThread: rootThreadService, toastMessenger }) {
function StreamContent({ api, toastMessenger }) {
const addAnnotations = useStore(store => store.addAnnotations);
const annotationFetchStarted = useStore(
store => store.annotationFetchStarted
......@@ -68,9 +69,7 @@ function StreamContent({ api, rootThread: rootThreadService, toastMessenger }) {
toastMessenger,
]);
const rootThread = useStore(store =>
rootThreadService.thread(store.getState())
);
const rootThread = useRootThread();
return <ThreadList thread={rootThread} />;
}
......@@ -78,10 +77,9 @@ function StreamContent({ api, rootThread: rootThreadService, toastMessenger }) {
StreamContent.propTypes = {
// Injected services.
api: propTypes.object,
rootThread: propTypes.object,
toastMessenger: propTypes.object,
};
StreamContent.injectedProps = ['api', 'rootThread', 'toastMessenger'];
StreamContent.injectedProps = ['api', 'toastMessenger'];
export default withServices(StreamContent);
......@@ -11,7 +11,7 @@ import AnnotationViewerContent, {
describe('AnnotationViewerContent', () => {
let fakeStore;
let fakeOnLogin;
let fakeRootThread;
let fakeUseRootThread;
let fakeLoadAnnotationsService;
beforeEach(() => {
......@@ -30,10 +30,11 @@ describe('AnnotationViewerContent', () => {
fakeOnLogin = sinon.stub();
fakeRootThread = { thread: sinon.stub().returns({}) };
fakeUseRootThread = sinon.stub().returns({});
$imports.$mock(mockImportedComponents());
$imports.$mock({
'./hooks/use-root-thread': fakeUseRootThread,
'../store/use-store': callback => callback(fakeStore),
});
});
......@@ -47,7 +48,6 @@ describe('AnnotationViewerContent', () => {
<AnnotationViewerContent
loadAnnotationsService={fakeLoadAnnotationsService}
onLogin={fakeOnLogin}
rootThread={fakeRootThread}
{...props}
/>
);
......
......@@ -8,17 +8,15 @@ import { checkAccessibility } from '../../../test-util/accessibility';
import mockImportedComponents from '../../../test-util/mock-imported-components';
describe('SearchStatusBar', () => {
let fakeRootThread;
let fakeUseRootThread;
let fakeStore;
function createComponent(props) {
return mount(<SearchStatusBar rootThread={fakeRootThread} {...props} />);
return mount(<SearchStatusBar {...props} />);
}
beforeEach(() => {
fakeRootThread = {
thread: sinon.stub().returns({ children: [] }),
};
fakeUseRootThread = sinon.stub().returns({ children: [] });
fakeStore = {
getState: sinon.stub().returns({
selection: {},
......@@ -32,6 +30,7 @@ describe('SearchStatusBar', () => {
$imports.$mock(mockImportedComponents());
$imports.$mock({
'./hooks/use-root-thread': fakeUseRootThread,
'../store/use-store': callback => callback(fakeStore),
});
});
......@@ -105,7 +104,7 @@ describe('SearchStatusBar', () => {
},
].forEach(test => {
it(test.description, () => {
fakeRootThread.thread.returns({
fakeUseRootThread.returns({
children: test.children,
});
......@@ -156,7 +155,7 @@ describe('SearchStatusBar', () => {
},
].forEach(test => {
it(test.description, () => {
fakeRootThread.thread.returns({
fakeUseRootThread.returns({
children: test.children,
});
const wrapper = createComponent({});
......@@ -169,7 +168,7 @@ describe('SearchStatusBar', () => {
});
it('should not display user-focused mode text if filtered mode is (also) applied', () => {
fakeRootThread.thread.returns({
fakeUseRootThread.returns({
children: [
{ id: '1', visible: true, children: [] },
{ id: '2', visible: true, children: [] },
......
......@@ -10,7 +10,7 @@ import mockImportedComponents from '../../../test-util/mock-imported-components'
describe('SidebarContent', () => {
let fakeFrameSync;
let fakeLoadAnnotationsService;
let fakeRootThreadService;
let fakeUseRootThread;
let fakeStore;
let fakeStreamer;
let fakeTabsUtil;
......@@ -22,7 +22,6 @@ describe('SidebarContent', () => {
onSignUp={() => null}
frameSync={fakeFrameSync}
loadAnnotationsService={fakeLoadAnnotationsService}
rootThread={fakeRootThreadService}
streamer={fakeStreamer}
{...props}
/>
......@@ -36,9 +35,7 @@ describe('SidebarContent', () => {
fakeLoadAnnotationsService = {
load: sinon.stub(),
};
fakeRootThreadService = {
thread: sinon.stub().returns({}),
};
fakeUseRootThread = sinon.stub().returns({});
fakeStreamer = {
connect: sinon.stub(),
};
......@@ -69,6 +66,7 @@ describe('SidebarContent', () => {
$imports.$mock(mockImportedComponents());
$imports.$mock({
'./hooks/use-root-thread': fakeUseRootThread,
'../store/use-store': callback => callback(fakeStore),
'../util/tabs': fakeTabsUtil,
});
......
......@@ -8,7 +8,7 @@ import StreamContent, { $imports } from '../stream-content';
describe('StreamContent', () => {
let fakeApi;
let fakeRootThread;
let fakeUseRootThread;
let fakeSearchFilter;
let fakeStore;
let fakeToastMessenger;
......@@ -18,9 +18,7 @@ describe('StreamContent', () => {
search: sinon.stub().resolves({ rows: [], replies: [], total: 0 }),
};
fakeRootThread = {
thread: sinon.stub().returns({}),
};
fakeUseRootThread = sinon.stub();
fakeSearchFilter = {
toObject: sinon.stub().returns({}),
......@@ -42,6 +40,7 @@ describe('StreamContent', () => {
$imports.$mock(mockImportedComponents());
$imports.$mock({
'./hooks/use-root-thread': fakeUseRootThread,
'../store/use-store': callback => callback(fakeStore),
'../util/search-filter': fakeSearchFilter,
});
......@@ -53,11 +52,7 @@ describe('StreamContent', () => {
function createComponent() {
return mount(
<StreamContent
api={fakeApi}
rootThread={fakeRootThread}
toastMessenger={fakeToastMessenger}
/>
<StreamContent api={fakeApi} toastMessenger={fakeToastMessenger} />
);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment