Commit 6bf8e3b9 authored by Lyza Danger Gardner's avatar Lyza Danger Gardner Committed by Lyza Gardner

Add NotebookResultCount component

parent 193f6f7f
import { createElement } from 'preact';
import useRootThread from './hooks/use-root-thread';
import { useStoreProxy } from '../store/use-store';
import { countVisible } from '../util/thread';
/**
* Render count of annotations (or filtered results) visible in the notebook view
*
* There are three possible overall states:
* - No results (regardless of whether annotations are filtered): "No results"
* - Annotations are unfiltered: "X threads (Y annotations)"
* - Annotations are filtered: "X results [(and Y more)]"
*/
function NotebookResultCount() {
const store = useStoreProxy();
const forcedVisibleCount = store.forcedVisibleAnnotations().length;
const hasAppliedFilter = store.hasAppliedFilter();
const rootThread = useRootThread();
const visibleCount = countVisible(rootThread);
const hasResults = rootThread.children.length > 0;
const hasForcedVisible = forcedVisibleCount > 0;
const matchCount = visibleCount - forcedVisibleCount;
const threadCount = rootThread.children.length;
return (
<span className="notebook-result-count">
{!hasResults && <h2>No results</h2>}
{hasResults && hasAppliedFilter && (
<span>
<h2>
{matchCount} {matchCount === 1 ? 'result' : 'results'}
</h2>
{hasForcedVisible && <em> (and {forcedVisibleCount} more)</em>}
</span>
)}
{hasResults && !hasAppliedFilter && (
<span>
<h2>
{threadCount} {threadCount === 1 ? 'thread' : 'threads'}
</h2>{' '}
<em>
({visibleCount} {visibleCount === 1 ? 'annotation' : 'annotations'})
</em>
</span>
)}
</span>
);
}
NotebookResultCount.propTypes = {};
export default NotebookResultCount;
...@@ -6,6 +6,7 @@ import { withServices } from '../util/service-context'; ...@@ -6,6 +6,7 @@ import { withServices } from '../util/service-context';
import useRootThread from './hooks/use-root-thread'; import useRootThread from './hooks/use-root-thread';
import { useStoreProxy } from '../store/use-store'; import { useStoreProxy } from '../store/use-store';
import NotebookResultCount from './notebook-result-count';
import ThreadList from './thread-list'; import ThreadList from './thread-list';
/** /**
...@@ -38,31 +39,13 @@ function NotebookView({ loadAnnotationsService }) { ...@@ -38,31 +39,13 @@ function NotebookView({ loadAnnotationsService }) {
const rootThread = useRootThread(); const rootThread = useRootThread();
const groupName = focusedGroup?.name ?? '…'; const groupName = focusedGroup?.name ?? '…';
const hasResults = rootThread.totalChildren > 0;
const threadCount =
rootThread.totalChildren === 1
? '1 thread'
: `${rootThread.totalChildren} threads`;
const annotationCount =
rootThread.replyCount === 1
? '1 annotation'
: `${rootThread.replyCount} annotations`;
return ( return (
<div className="notebook-view"> <div className="notebook-view">
<header className="notebook-view__heading"> <header className="notebook-view__heading">
<h1>{groupName}</h1> <h1>{groupName}</h1>
</header> </header>
<div className="notebook-view__results"> <div className="notebook-view__results">
{hasResults ? ( <NotebookResultCount />
<span>
<h2>{threadCount}</h2> ({annotationCount})
</span>
) : (
<span>
<h2>No results</h2>
</span>
)}
</div> </div>
<div className="notebook-view__items"> <div className="notebook-view__items">
<ThreadList thread={rootThread} /> <ThreadList thread={rootThread} />
......
import { mount } from 'enzyme';
import { createElement } from 'preact';
import NotebookResultCount from '../notebook-result-count';
import { $imports } from '../notebook-result-count';
describe('NotebookResultCount', () => {
let fakeCountVisible;
let fakeUseRootThread;
let fakeStore;
const createComponent = () => {
return mount(<NotebookResultCount />);
};
beforeEach(() => {
fakeCountVisible = sinon.stub().returns(0);
fakeUseRootThread = sinon.stub().returns({});
fakeStore = {
forcedVisibleAnnotations: sinon.stub().returns([]),
hasAppliedFilter: sinon.stub().returns(false),
};
$imports.$mock({
'./hooks/use-root-thread': fakeUseRootThread,
'../store/use-store': { useStoreProxy: () => fakeStore },
'../util/thread': { countVisible: fakeCountVisible },
});
});
afterEach(() => {
$imports.$restore();
});
context('when there are no results', () => {
it('should show "No Results" if no filters are applied', () => {
fakeStore.hasAppliedFilter.returns(false);
fakeUseRootThread.returns({ children: [] });
const wrapper = createComponent();
assert.equal(wrapper.text(), 'No results');
});
it('should show "No Results" if filters are applied', () => {
fakeStore.hasAppliedFilter.returns(true);
fakeUseRootThread.returns({ children: [] });
const wrapper = createComponent();
assert.equal(wrapper.text(), 'No results');
});
});
context('no applied filter', () => {
[
{
thread: { children: [1] },
visibleCount: 1,
expected: '1 thread (1 annotation)',
},
{
thread: { children: [1] },
visibleCount: 2,
expected: '1 thread (2 annotations)',
},
{
thread: { children: [1, 2] },
visibleCount: 2,
expected: '2 threads (2 annotations)',
},
].forEach(test => {
it('should render a count of threads and annotations', () => {
fakeCountVisible.returns(test.visibleCount);
fakeUseRootThread.returns(test.thread);
const wrapper = createComponent();
assert.equal(wrapper.text(), test.expected);
});
});
});
context('with one or more applied filters', () => {
[
{
forcedVisible: [],
thread: { children: [1] },
visibleCount: 1,
expected: '1 result',
},
{
forcedVisible: [],
thread: { children: [1] },
visibleCount: 2,
expected: '2 results',
},
{
forcedVisible: [1],
thread: { children: [1] },
visibleCount: 3,
expected: '2 results (and 1 more)',
},
].forEach(test => {
it('should render a count of results', () => {
fakeStore.hasAppliedFilter.returns(true);
fakeStore.forcedVisibleAnnotations.returns(test.forcedVisible);
fakeUseRootThread.returns(test.thread);
fakeCountVisible.returns(test.visibleCount);
const wrapper = createComponent();
assert.equal(wrapper.text(), test.expected);
});
});
});
});
...@@ -64,34 +64,9 @@ describe('NotebookView', () => { ...@@ -64,34 +64,9 @@ describe('NotebookView', () => {
assert.equal(wrapper.find('.notebook-view__heading').text(), '…'); assert.equal(wrapper.find('.notebook-view__heading').text(), '…');
}); });
describe('results count', () => { it('renders results (counts)', () => {
[
{
rootThread: { totalChildren: 5, replyCount: 15 },
expected: '5 threads (15 annotations)',
},
{
rootThread: { totalChildren: 0, replyCount: 0 },
expected: 'No results',
},
{
rootThread: { totalChildren: 0, replyCount: 15 },
expected: 'No results',
},
{
rootThread: { totalChildren: 1, replyCount: 1 },
expected: '1 thread (1 annotation)',
},
].forEach(test => {
it('renders number of threads and annotations', () => {
fakeUseRootThread.returns(test.rootThread);
const wrapper = createComponent(); const wrapper = createComponent();
assert.isTrue(wrapper.find('NotebookResultCount').exists());
assert.equal(
wrapper.find('.notebook-view__results').text(),
test.expected
);
});
}); });
}); });
}); });
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