Unverified Commit 21ecaa76 authored by Robert Knight's avatar Robert Knight Committed by GitHub

Merge pull request #1233 from hypothesis/move-tab-count-to-store

Move tab counts to store
parents d1669ce3 02235b72
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
const propTypes = require('prop-types'); const propTypes = require('prop-types');
const { createElement } = require('preact'); const { createElement } = require('preact');
const annotationMetadata = require('../annotation-metadata'); const annotationMetadata = require('../util/annotation-metadata');
/** /**
* Render some metadata about an annotation's document and link to it * Render some metadata about an annotation's document and link to it
......
'use strict'; 'use strict';
const annotationMetadata = require('../annotation-metadata'); const annotationMetadata = require('../util/annotation-metadata');
const events = require('../events'); const events = require('../events');
const { isThirdPartyUser } = require('../util/account-id'); const { isThirdPartyUser } = require('../util/account-id');
const serviceConfig = require('../service-config'); const serviceConfig = require('../service-config');
......
...@@ -4,7 +4,7 @@ const { createElement } = require('preact'); ...@@ -4,7 +4,7 @@ const { createElement } = require('preact');
const classnames = require('classnames'); const classnames = require('classnames');
const propTypes = require('prop-types'); const propTypes = require('prop-types');
const annotationMetadata = require('../annotation-metadata'); const annotationMetadata = require('../util/annotation-metadata');
const useStore = require('../store/use-store'); const useStore = require('../store/use-store');
const { withServices } = require('../util/service-context'); const { withServices } = require('../util/service-context');
......
...@@ -21,18 +21,24 @@ const countVisibleAnns = annThread => { ...@@ -21,18 +21,24 @@ const countVisibleAnns = annThread => {
* A bar where the user can clear a selection or search and see whether * A bar where the user can clear a selection or search and see whether
* any search results were found. * any search results were found.
* */ * */
function SearchStatusBar({ function SearchStatusBar({ rootThread }) {
selectedTab, const {
totalAnnotations, directLinkedGroupFetchFailed,
totalNotes, filterQuery,
rootThread, selectedAnnotationMap,
}) { selectedTab,
const storeState = useStore(store => store.getState()); } = useStore(store => ({
directLinkedGroupFetchFailed: store.getState().directLinkedGroupFetchFailed,
filterQuery: store.getState().filterQuery,
selectedAnnotationMap: store.getState().selectedAnnotationMap,
selectedTab: store.getState().selectedTab,
}));
const clearSelection = useStore(store => store.clearSelection); const clearSelection = useStore(store => store.clearSelection);
const filterQuery = storeState.filterQuery; const filterActive = !!filterQuery;
const filterActive = !!storeState.filterQuery; const annotationCount = useStore(store => store.annotationCount());
const noteCount = useStore(store => store.noteCount());
const thread = rootThread.thread(storeState); const thread = useStore(store => rootThread.thread(store.getState()));
const visibleCount = useMemo(() => { const visibleCount = useMemo(() => {
return countVisibleAnns(thread); return countVisibleAnns(thread);
...@@ -51,10 +57,10 @@ function SearchStatusBar({ ...@@ -51,10 +57,10 @@ function SearchStatusBar({
}; };
const areNotAllAnnotationsVisible = () => { const areNotAllAnnotationsVisible = () => {
if (storeState.directLinkedGroupFetchFailed) { if (directLinkedGroupFetchFailed) {
return true; return true;
} }
const selection = storeState.selectedAnnotationMap; const selection = selectedAnnotationMap;
if (!selection) { if (!selection) {
return false; return false;
} }
...@@ -89,13 +95,13 @@ function SearchStatusBar({ ...@@ -89,13 +95,13 @@ function SearchStatusBar({
{selectedTab === uiConstants.TAB_ANNOTATIONS && ( {selectedTab === uiConstants.TAB_ANNOTATIONS && (
<Fragment> <Fragment>
Show all annotations Show all annotations
{totalAnnotations > 1 && <span> ({totalAnnotations})</span>} {annotationCount > 1 && <span> ({annotationCount})</span>}
</Fragment> </Fragment>
)} )}
{selectedTab === uiConstants.TAB_NOTES && ( {selectedTab === uiConstants.TAB_NOTES && (
<Fragment> <Fragment>
Show all notes Show all notes
{totalNotes > 1 && <span> ({totalNotes})</span>} {noteCount > 1 && <span> ({noteCount})</span>}
</Fragment> </Fragment>
)} )}
</button> </button>
...@@ -106,13 +112,7 @@ function SearchStatusBar({ ...@@ -106,13 +112,7 @@ function SearchStatusBar({
} }
SearchStatusBar.propTypes = { SearchStatusBar.propTypes = {
selectedTab: propTypes.oneOf([ // Injected services.
uiConstants.TAB_ANNOTATIONS,
uiConstants.TAB_ORPHANS,
uiConstants.TAB_NOTES,
]).isRequired,
totalAnnotations: propTypes.number.isRequired,
totalNotes: propTypes.number.isRequired,
rootThread: propTypes.object.isRequired, rootThread: propTypes.object.isRequired,
}; };
......
...@@ -69,16 +69,14 @@ Tab.propTypes = { ...@@ -69,16 +69,14 @@ Tab.propTypes = {
* Tabbed display of annotations and notes. * Tabbed display of annotations and notes.
*/ */
function SelectionTabs({ function SelectionTabs({ isLoading, settings, session }) {
isWaitingToAnchorAnnotations, const selectedTab = useStore(store => store.getState().selectedTab);
isLoading, const noteCount = useStore(store => store.noteCount());
selectedTab, const annotationCount = useStore(store => store.annotationCount());
totalAnnotations, const orphanCount = useStore(store => store.orphanCount());
totalNotes, const isWaitingToAnchorAnnotations = useStore(store =>
totalOrphans, store.isWaitingToAnchorAnnotations()
settings, );
session,
}) {
// actions // actions
const store = useStore(store => ({ const store = useStore(store => ({
clearSelectedAnnotations: store.clearSelectedAnnotations, clearSelectedAnnotations: store.clearSelectedAnnotations,
...@@ -94,11 +92,11 @@ function SelectionTabs({ ...@@ -94,11 +92,11 @@ function SelectionTabs({
const showAnnotationsUnavailableMessage = const showAnnotationsUnavailableMessage =
selectedTab === uiConstants.TAB_ANNOTATIONS && selectedTab === uiConstants.TAB_ANNOTATIONS &&
totalAnnotations === 0 && annotationCount === 0 &&
!isWaitingToAnchorAnnotations; !isWaitingToAnchorAnnotations;
const showNotesUnavailableMessage = const showNotesUnavailableMessage =
selectedTab === uiConstants.TAB_NOTES && totalNotes === 0; selectedTab === uiConstants.TAB_NOTES && noteCount === 0;
const showSidebarTutorial = sessionUtil.shouldShowSidebarTutorial( const showSidebarTutorial = sessionUtil.shouldShowSidebarTutorial(
session.state session.state
...@@ -112,7 +110,7 @@ function SelectionTabs({ ...@@ -112,7 +110,7 @@ function SelectionTabs({
})} })}
> >
<Tab <Tab
count={totalAnnotations} count={annotationCount}
isWaitingToAnchor={isWaitingToAnchorAnnotations} isWaitingToAnchor={isWaitingToAnchorAnnotations}
selected={selectedTab === uiConstants.TAB_ANNOTATIONS} selected={selectedTab === uiConstants.TAB_ANNOTATIONS}
type={uiConstants.TAB_ANNOTATIONS} type={uiConstants.TAB_ANNOTATIONS}
...@@ -121,7 +119,7 @@ function SelectionTabs({ ...@@ -121,7 +119,7 @@ function SelectionTabs({
Annotations Annotations
</Tab> </Tab>
<Tab <Tab
count={totalNotes} count={noteCount}
isWaitingToAnchor={isWaitingToAnchorAnnotations} isWaitingToAnchor={isWaitingToAnchorAnnotations}
selected={selectedTab === uiConstants.TAB_NOTES} selected={selectedTab === uiConstants.TAB_NOTES}
type={uiConstants.TAB_NOTES} type={uiConstants.TAB_NOTES}
...@@ -129,9 +127,9 @@ function SelectionTabs({ ...@@ -129,9 +127,9 @@ function SelectionTabs({
> >
Page Notes Page Notes
</Tab> </Tab>
{totalOrphans > 0 && ( {orphanCount > 0 && (
<Tab <Tab
count={totalOrphans} count={orphanCount}
isWaitingToAnchor={isWaitingToAnchorAnnotations} isWaitingToAnchor={isWaitingToAnchorAnnotations}
selected={selectedTab === uiConstants.TAB_ORPHANS} selected={selectedTab === uiConstants.TAB_ORPHANS}
type={uiConstants.TAB_ORPHANS} type={uiConstants.TAB_ORPHANS}
...@@ -182,20 +180,6 @@ SelectionTabs.propTypes = { ...@@ -182,20 +180,6 @@ SelectionTabs.propTypes = {
* Are we waiting on any annotations from the server? * Are we waiting on any annotations from the server?
*/ */
isLoading: propTypes.bool.isRequired, isLoading: propTypes.bool.isRequired,
/**
* Are there any annotations still waiting to anchor?
*/
isWaitingToAnchorAnnotations: propTypes.bool.isRequired,
/**
* The currently selected tab (annotations, notes or orphans).
*/
selectedTab: propTypes.oneOf(['annotation', 'note', 'orphan']).isRequired,
/**
* The totals for each respect tab.
*/
totalAnnotations: propTypes.number.isRequired,
totalNotes: propTypes.number.isRequired,
totalOrphans: propTypes.number.isRequired,
// Injected services. // Injected services.
settings: propTypes.object.isRequired, settings: propTypes.object.isRequired,
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
const events = require('../events'); const events = require('../events');
const isThirdPartyService = require('../util/is-third-party-service'); const isThirdPartyService = require('../util/is-third-party-service');
const tabs = require('../tabs'); const tabs = require('../util/tabs');
// @ngInject // @ngInject
function SidebarContentController( function SidebarContentController(
...@@ -22,19 +22,7 @@ function SidebarContentController( ...@@ -22,19 +22,7 @@ function SidebarContentController(
} }
const unsubscribeAnnotationUI = store.subscribe(function() { const unsubscribeAnnotationUI = store.subscribe(function() {
const state = store.getState();
self.rootThread = thread(); self.rootThread = thread();
self.selectedTab = state.selectedTab;
const counts = tabs.counts(state.annotations);
Object.assign(self, {
totalNotes: counts.notes,
totalAnnotations: counts.annotations,
totalOrphans: counts.orphans,
waitingToAnchorAnnotations: counts.anchoring > 0,
});
}); });
$scope.$on('$destroy', unsubscribeAnnotationUI); $scope.$on('$destroy', unsubscribeAnnotationUI);
......
...@@ -24,7 +24,7 @@ describe('AnnotationDocumentInfo', () => { ...@@ -24,7 +24,7 @@ describe('AnnotationDocumentInfo', () => {
fakeDomainAndTitle = sinon.stub(); fakeDomainAndTitle = sinon.stub();
fakeMetadata = { domainAndTitle: fakeDomainAndTitle }; fakeMetadata = { domainAndTitle: fakeDomainAndTitle };
AnnotationDocumentInfo.$imports.$mock({ AnnotationDocumentInfo.$imports.$mock({
'../annotation-metadata': fakeMetadata, '../util/annotation-metadata': fakeMetadata,
}); });
}); });
......
...@@ -21,11 +21,8 @@ describe('SearchStatusBar', () => { ...@@ -21,11 +21,8 @@ describe('SearchStatusBar', () => {
}; };
fakeStore = { fakeStore = {
getState: sinon.stub(), getState: sinon.stub(),
selectTab: sinon.stub(), annotationCount: sinon.stub().returns(1),
clearSelectedAnnotations: sinon.stub(), noteCount: sinon.stub().returns(0),
clearDirectLinkedGroupFetchFailed: sinon.stub(),
clearDirectLinkedIds: sinon.stub(),
clearSelection: sinon.stub(),
}; };
SearchStatusBar.$imports.$mock({ SearchStatusBar.$imports.$mock({
...@@ -96,13 +93,11 @@ describe('SearchStatusBar', () => { ...@@ -96,13 +93,11 @@ describe('SearchStatusBar', () => {
}); });
fakeStore.getState.returns({ fakeStore.getState.returns({
filterQuery: 'tag:foo', filterQuery: 'tag:foo',
});
const wrapper = createComponent({
selectedTab: 'annotation', selectedTab: 'annotation',
totalAnnotations: 3,
totalNotes: 0,
}); });
fakeStore.annotationCount.returns(3);
const wrapper = createComponent({});
const buttonText = wrapper.find('button').text(); const buttonText = wrapper.find('button').text();
assert.equal(buttonText, 'Clear search'); assert.equal(buttonText, 'Clear search');
...@@ -117,14 +112,11 @@ describe('SearchStatusBar', () => { ...@@ -117,14 +112,11 @@ describe('SearchStatusBar', () => {
filterQuery: null, filterQuery: null,
directLinkedGroupFetchFailed: true, directLinkedGroupFetchFailed: true,
selectedAnnotationMap: { annId: true }, selectedAnnotationMap: { annId: true },
});
const wrapper = createComponent({
selectedTab: 'annotation', selectedTab: 'annotation',
totalAnnotations: 1,
totalNotes: 0,
}); });
const wrapper = createComponent({});
const buttonText = wrapper.find('button').text(); const buttonText = wrapper.find('button').text();
assert.equal(buttonText, 'Show all annotations'); assert.equal(buttonText, 'Show all annotations');
}); });
...@@ -134,14 +126,11 @@ describe('SearchStatusBar', () => { ...@@ -134,14 +126,11 @@ describe('SearchStatusBar', () => {
filterQuery: null, filterQuery: null,
directLinkedGroupFetchFailed: false, directLinkedGroupFetchFailed: false,
selectedAnnotationMap: { annId: true }, selectedAnnotationMap: { annId: true },
});
const wrapper = createComponent({
selectedTab: 'annotation', selectedTab: 'annotation',
totalAnnotations: 1,
totalNotes: 0,
}); });
const wrapper = createComponent({});
const buttonText = wrapper.find('button').text(); const buttonText = wrapper.find('button').text();
assert.equal(buttonText, 'Show all annotations'); assert.equal(buttonText, 'Show all annotations');
}); });
...@@ -152,13 +141,11 @@ describe('SearchStatusBar', () => { ...@@ -152,13 +141,11 @@ describe('SearchStatusBar', () => {
filterQuery: null, filterQuery: null,
directLinkedGroupFetchFailed: false, directLinkedGroupFetchFailed: false,
selectedAnnotationMap: selectedAnnotationMap, selectedAnnotationMap: selectedAnnotationMap,
});
const wrapper = createComponent({
selectedTab: 'annotation', selectedTab: 'annotation',
totalAnnotations: 1,
totalNotes: 0,
}); });
const wrapper = createComponent({});
const buttons = wrapper.find('button'); const buttons = wrapper.find('button');
assert.equal(buttons.length, 0); assert.equal(buttons.length, 0);
}); });
...@@ -203,13 +190,12 @@ describe('SearchStatusBar', () => { ...@@ -203,13 +190,12 @@ describe('SearchStatusBar', () => {
filterQuery: null, filterQuery: null,
directLinkedGroupFetchFailed: false, directLinkedGroupFetchFailed: false,
selectedAnnotationMap: { annId: true }, selectedAnnotationMap: { annId: true },
});
const wrapper = createComponent({
selectedTab: test.selectedTab, selectedTab: test.selectedTab,
totalAnnotations: test.totalAnnotations,
totalNotes: test.totalNotes,
}); });
fakeStore.noteCount.returns(test.totalNotes);
fakeStore.annotationCount.returns(test.totalAnnotations);
const wrapper = createComponent({});
const buttonText = wrapper.find('button').text(); const buttonText = wrapper.find('button').text();
assert.equal(buttonText, test.expectedText); assert.equal(buttonText, test.expectedText);
......
...@@ -11,23 +11,15 @@ describe('SelectionTabs', function() { ...@@ -11,23 +11,15 @@ describe('SelectionTabs', function() {
// mock services // mock services
let fakeSession; let fakeSession;
let fakeSettings; let fakeSettings;
let fakeStore;
// default props // default props
const defaultProps = { const defaultProps = {
isLoading: false, isLoading: false,
isWaitingToAnchorAnnotations: false,
selectedTab: uiConstants.TAB_ANNOTATIONS,
totalAnnotations: 123,
totalNotes: 456,
totalOrphans: 0,
}; };
SelectionTabs.$imports.$mock({ SelectionTabs.$imports.$mock({
'../store/use-store': callback => '../store/use-store': callback => callback(fakeStore),
callback({
clearSelectedAnnotations: sinon.stub(),
selectTab: sinon.stub(),
}),
}); });
function createComponent(props) { function createComponent(props) {
...@@ -64,6 +56,17 @@ describe('SelectionTabs', function() { ...@@ -64,6 +56,17 @@ describe('SelectionTabs', function() {
fakeSettings = { fakeSettings = {
enableExperimentalNewNoteButton: false, enableExperimentalNewNoteButton: false,
}; };
fakeStore = {
clearSelectedAnnotations: sinon.stub(),
selectTab: sinon.stub(),
annotationCount: sinon.stub().returns(123),
noteCount: sinon.stub().returns(456),
orphanCount: sinon.stub().returns(0),
isWaitingToAnchorAnnotations: sinon.stub().returns(false),
getState: sinon
.stub()
.returns({ selectedTab: uiConstants.TAB_ANNOTATIONS }),
};
}); });
const unavailableMessage = wrapper => const unavailableMessage = wrapper =>
...@@ -98,26 +101,23 @@ describe('SelectionTabs', function() { ...@@ -98,26 +101,23 @@ describe('SelectionTabs', function() {
}); });
it('should display notes tab as selected', function() { it('should display notes tab as selected', function() {
const wrapper = createDeepComponent({ fakeStore.getState.returns({ selectedTab: uiConstants.TAB_NOTES });
selectedTab: uiConstants.TAB_NOTES, const wrapper = createDeepComponent({});
});
const tabs = wrapper.find('a'); const tabs = wrapper.find('a');
assert.isTrue(tabs.at(1).hasClass('is-selected')); assert.isTrue(tabs.at(1).hasClass('is-selected'));
}); });
it('should display orphans tab as selected if there is 1 or more orphans', function() { it('should display orphans tab as selected if there is 1 or more orphans', function() {
const wrapper = createDeepComponent({ fakeStore.getState.returns({ selectedTab: uiConstants.TAB_ORPHANS });
selectedTab: uiConstants.TAB_ORPHANS, fakeStore.orphanCount.returns(1);
totalOrphans: 1, const wrapper = createDeepComponent({});
});
const tabs = wrapper.find('a'); const tabs = wrapper.find('a');
assert.isTrue(tabs.at(2).hasClass('is-selected')); assert.isTrue(tabs.at(2).hasClass('is-selected'));
}); });
it('should not display orphans tab if there are 0 orphans', function() { it('should not display orphans tab if there are 0 orphans', function() {
const wrapper = createDeepComponent({ fakeStore.getState.returns({ selectedTab: uiConstants.TAB_ORPHANS });
selectedTab: uiConstants.TAB_ORPHANS, const wrapper = createDeepComponent({});
});
const tabs = wrapper.find('a'); const tabs = wrapper.find('a');
assert.equal(tabs.length, 2); assert.equal(tabs.length, 2);
}); });
...@@ -139,51 +139,48 @@ describe('SelectionTabs', function() { ...@@ -139,51 +139,48 @@ describe('SelectionTabs', function() {
}); });
it('should not display the new-note-btn when the notes tab is active and the new-note-btn is disabled', function() { it('should not display the new-note-btn when the notes tab is active and the new-note-btn is disabled', function() {
const wrapper = createComponent({ fakeStore.getState.returns({ selectedTab: uiConstants.TAB_NOTES });
selectedTab: uiConstants.TAB_NOTES, const wrapper = createComponent({});
});
assert.equal(wrapper.find(NewNoteBtn).length, 0); assert.equal(wrapper.find(NewNoteBtn).length, 0);
}); });
it('should display the new-note-btn when the notes tab is active and the new-note-btn is enabled', function() { it('should display the new-note-btn when the notes tab is active and the new-note-btn is enabled', function() {
fakeSettings.enableExperimentalNewNoteButton = true; fakeSettings.enableExperimentalNewNoteButton = true;
const wrapper = createComponent({ fakeStore.getState.returns({ selectedTab: uiConstants.TAB_NOTES });
selectedTab: uiConstants.TAB_NOTES, const wrapper = createComponent({});
});
assert.equal(wrapper.find(NewNoteBtn).length, 1); assert.equal(wrapper.find(NewNoteBtn).length, 1);
}); });
it('should not display a message when its loading annotation count is 0', function() { it('should not display a message when its loading annotation count is 0', function() {
fakeStore.annotationCount.returns(0);
const wrapper = createComponent({ const wrapper = createComponent({
totalAnnotations: 0,
isLoading: true, isLoading: true,
}); });
assert.isFalse(wrapper.exists('.annotation-unavailable-message__label')); assert.isFalse(wrapper.exists('.annotation-unavailable-message__label'));
}); });
it('should not display a message when its loading notes count is 0', function() { it('should not display a message when its loading notes count is 0', function() {
fakeStore.getState.returns({ selectedTab: uiConstants.TAB_NOTES });
fakeStore.noteCount.returns(0);
const wrapper = createComponent({ const wrapper = createComponent({
selectedTab: uiConstants.TAB_NOTES,
totalNotes: 0,
isLoading: true, isLoading: true,
}); });
assert.isFalse(wrapper.exists('.annotation-unavailable-message__label')); assert.isFalse(wrapper.exists('.annotation-unavailable-message__label'));
}); });
it('should not display the longer version of the no annotations message when there are no annotations and isWaitingToAnchorAnnotations is true', function() { it('should not display the longer version of the no annotations message when there are no annotations and isWaitingToAnchorAnnotations is true', function() {
fakeStore.annotationCount.returns(0);
fakeStore.isWaitingToAnchorAnnotations.returns(true);
const wrapper = createComponent({ const wrapper = createComponent({
totalAnnotations: 0,
isWaitingToAnchorAnnotations: true,
isLoading: false, isLoading: false,
}); });
assert.isFalse(wrapper.exists('.annotation-unavailable-message__label')); assert.isFalse(wrapper.exists('.annotation-unavailable-message__label'));
}); });
it('should display the longer version of the no notes message when there are no notes', function() { it('should display the longer version of the no notes message when there are no notes', function() {
const wrapper = createComponent({ fakeStore.getState.returns({ selectedTab: uiConstants.TAB_NOTES });
selectedTab: uiConstants.TAB_NOTES, fakeStore.noteCount.returns(0);
totalNotes: 0, const wrapper = createComponent({});
});
assert.include( assert.include(
unavailableMessage(wrapper), unavailableMessage(wrapper),
'There are no page notes in this group.' 'There are no page notes in this group.'
...@@ -192,10 +189,9 @@ describe('SelectionTabs', function() { ...@@ -192,10 +189,9 @@ describe('SelectionTabs', function() {
it('should display the prompt to create a note when there are no notes and enableExperimentalNewNoteButton is true', function() { it('should display the prompt to create a note when there are no notes and enableExperimentalNewNoteButton is true', function() {
fakeSettings.enableExperimentalNewNoteButton = true; fakeSettings.enableExperimentalNewNoteButton = true;
const wrapper = createComponent({ fakeStore.getState.returns({ selectedTab: uiConstants.TAB_NOTES });
selectedTab: uiConstants.TAB_NOTES, fakeStore.noteCount.returns(0);
totalNotes: 0, const wrapper = createComponent({});
});
assert.include( assert.include(
wrapper.find('.annotation-unavailable-message__tutorial').text(), wrapper.find('.annotation-unavailable-message__tutorial').text(),
'Create one by clicking the' 'Create one by clicking the'
...@@ -208,9 +204,8 @@ describe('SelectionTabs', function() { ...@@ -208,9 +204,8 @@ describe('SelectionTabs', function() {
}); });
it('should display the longer version of the no annotations message when there are no annotations', function() { it('should display the longer version of the no annotations message when there are no annotations', function() {
const wrapper = createComponent({ fakeStore.annotationCount.returns(0);
totalAnnotations: 0, const wrapper = createComponent({});
});
assert.include( assert.include(
unavailableMessage(wrapper), unavailableMessage(wrapper),
'There are no annotations in this group.' 'There are no annotations in this group.'
...@@ -229,10 +224,9 @@ describe('SelectionTabs', function() { ...@@ -229,10 +224,9 @@ describe('SelectionTabs', function() {
context('when the sidebar tutorial is displayed', function() { context('when the sidebar tutorial is displayed', function() {
it('should display the shorter version of the no notes message when there are no notes', function() { it('should display the shorter version of the no notes message when there are no notes', function() {
fakeSession.state.preferences.show_sidebar_tutorial = true; fakeSession.state.preferences.show_sidebar_tutorial = true;
const wrapper = createComponent({ fakeStore.getState.returns({ selectedTab: uiConstants.TAB_NOTES });
totalNotes: 0, fakeStore.noteCount.returns(0);
selectedTab: uiConstants.TAB_NOTES, const wrapper = createComponent({});
});
const msg = unavailableMessage(wrapper); const msg = unavailableMessage(wrapper);
...@@ -246,9 +240,8 @@ describe('SelectionTabs', function() { ...@@ -246,9 +240,8 @@ describe('SelectionTabs', function() {
it('should display the shorter version of the no annotations message when there are no annotations', function() { it('should display the shorter version of the no annotations message when there are no annotations', function() {
fakeSession.state.preferences.show_sidebar_tutorial = true; fakeSession.state.preferences.show_sidebar_tutorial = true;
const wrapper = createComponent({ fakeStore.annotationCount.returns(0);
totalAnnotations: 0, const wrapper = createComponent({});
});
const msg = unavailableMessage(wrapper); const msg = unavailableMessage(wrapper);
......
'use strict'; 'use strict';
const events = require('../events'); const events = require('../events');
const metadata = require('../annotation-metadata'); const metadata = require('../util/annotation-metadata');
/** /**
* Component which displays a virtualized list of annotation threads. * Component which displays a virtualized list of annotation threads.
......
...@@ -4,7 +4,7 @@ const debounce = require('lodash.debounce'); ...@@ -4,7 +4,7 @@ const debounce = require('lodash.debounce');
const events = require('../events'); const events = require('../events');
const bridgeEvents = require('../../shared/bridge-events'); const bridgeEvents = require('../../shared/bridge-events');
const metadata = require('../annotation-metadata'); const metadata = require('../util/annotation-metadata');
const uiConstants = require('../ui-constants'); const uiConstants = require('../ui-constants');
/** /**
......
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
const buildThread = require('../build-thread'); const buildThread = require('../build-thread');
const events = require('../events'); const events = require('../events');
const memoize = require('../util/memoize'); const memoize = require('../util/memoize');
const metadata = require('../annotation-metadata'); const metadata = require('../util/annotation-metadata');
const tabs = require('../tabs'); const tabs = require('../util/tabs');
const uiConstants = require('../ui-constants'); const uiConstants = require('../ui-constants');
function truthyKeys(map) { function truthyKeys(map) {
......
...@@ -5,8 +5,10 @@ ...@@ -5,8 +5,10 @@
'use strict'; 'use strict';
const { createSelector } = require('reselect');
const arrayUtil = require('../../util/array-util'); const arrayUtil = require('../../util/array-util');
const metadata = require('../../annotation-metadata'); const metadata = require('../../util/annotation-metadata');
const uiConstants = require('../../ui-constants'); const uiConstants = require('../../ui-constants');
const selection = require('./selection'); const selection = require('./selection');
...@@ -388,6 +390,38 @@ function findAnnotationByID(state, id) { ...@@ -388,6 +390,38 @@ function findAnnotationByID(state, id) {
return findByID(state.annotations, id); return findByID(state.annotations, id);
} }
/**
* Return the number of page notes.
*/
const noteCount = createSelector(
state => state.annotations,
annotations => arrayUtil.countIf(annotations, metadata.isPageNote)
);
/**
* Returns the number of annotations (as opposed to notes or orphans).
*/
const annotationCount = createSelector(
state => state.annotations,
annotations => arrayUtil.countIf(annotations, metadata.isAnnotation)
);
/**
* Returns the number of orphaned annotations.
*/
const orphanCount = createSelector(
state => state.annotations,
annotations => arrayUtil.countIf(annotations, metadata.isOrphan)
);
/**
* Returns true if some annotations have not been anchored yet.
*/
const isWaitingToAnchorAnnotations = createSelector(
state => state.annotations,
annotations => annotations.some(metadata.isWaitingToAnchor)
);
module.exports = { module.exports = {
init: init, init: init,
update: update, update: update,
...@@ -403,6 +437,10 @@ module.exports = { ...@@ -403,6 +437,10 @@ module.exports = {
selectors: { selectors: {
annotationExists, annotationExists,
noteCount,
annotationCount,
orphanCount,
isWaitingToAnchorAnnotations,
findAnnotationByID, findAnnotationByID,
findIDsForTags, findIDsForTags,
savedAnnotations, savedAnnotations,
......
...@@ -13,9 +13,10 @@ ...@@ -13,9 +13,10 @@
const { createSelector } = require('reselect'); const { createSelector } = require('reselect');
const immutable = require('seamless-immutable'); const immutable = require('seamless-immutable');
const arrayUtil = require('../../util/array-util');
const metadata = require('../../util/annotation-metadata');
const toSet = require('../../util/array-util').toSet; const toSet = require('../../util/array-util').toSet;
const uiConstants = require('../../ui-constants'); const uiConstants = require('../../ui-constants');
const tabs = require('../../tabs');
const util = require('../util'); const util = require('../util');
...@@ -162,9 +163,12 @@ const update = { ...@@ -162,9 +163,12 @@ const update = {
}, },
ADD_ANNOTATIONS(state, action) { ADD_ANNOTATIONS(state, action) {
const counts = tabs.counts(action.annotations); const noteCount = arrayUtil.countIf(
action.annotations,
metadata.isPageNote
);
// If there are no annotations at all, ADD_ANNOTATIONS will not be called. // If there are no annotations at all, ADD_ANNOTATIONS will not be called.
const haveOnlyPageNotes = counts.notes === action.annotations.length; const haveOnlyPageNotes = noteCount === action.annotations.length;
// If this is the init phase and there are only page notes, select the page notes tab. // If this is the init phase and there are only page notes, select the page notes tab.
if (state.annotations.length === 0 && haveOnlyPageNotes) { if (state.annotations.length === 0 && haveOnlyPageNotes) {
return { selectedTab: uiConstants.TAB_NOTES }; return { selectedTab: uiConstants.TAB_NOTES };
......
...@@ -25,6 +25,68 @@ function createStore() { ...@@ -25,6 +25,68 @@ function createStore() {
// in the tests for the whole Redux store // in the tests for the whole Redux store
describe('annotations reducer', function() { describe('annotations reducer', function() {
describe('isWaitingToAnchorAnnotations', () => {
it('returns true if there are unanchored annotations', () => {
const unanchored = Object.assign(fixtures.oldAnnotation(), {
$orphan: 'undefined',
});
const state = {
annotations: [unanchored, fixtures.defaultAnnotation()],
};
assert.isTrue(selectors.isWaitingToAnchorAnnotations(state));
});
it('returns false if all annotations are anchored', () => {
const state = {
annotations: [
Object.assign(fixtures.oldPageNote(), { $orphan: false }),
Object.assign(fixtures.defaultAnnotation(), { $orphan: false }),
],
};
assert.isFalse(selectors.isWaitingToAnchorAnnotations(state));
});
});
describe('noteCount', () => {
it('returns number of page notes', () => {
const state = {
annotations: [
fixtures.oldPageNote(),
fixtures.oldAnnotation(),
fixtures.defaultAnnotation(),
],
};
assert.deepEqual(selectors.noteCount(state), 1);
});
});
describe('annotationCount', () => {
it('returns number of annotations', () => {
const state = {
annotations: [
fixtures.oldPageNote(),
fixtures.oldAnnotation(),
fixtures.defaultAnnotation(),
],
};
assert.deepEqual(selectors.annotationCount(state), 2);
});
});
describe('orphanCount', () => {
it('returns number of orphaned annotations', () => {
const orphan = Object.assign(fixtures.oldAnnotation(), { $orphan: true });
const state = {
annotations: [
orphan,
fixtures.oldAnnotation(),
fixtures.defaultAnnotation(),
],
};
assert.deepEqual(selectors.orphanCount(state), 1);
});
});
describe('#savedAnnotations', function() { describe('#savedAnnotations', function() {
const savedAnnotations = selectors.savedAnnotations; const savedAnnotations = selectors.savedAnnotations;
......
...@@ -4,7 +4,7 @@ const immutable = require('seamless-immutable'); ...@@ -4,7 +4,7 @@ const immutable = require('seamless-immutable');
const storeFactory = require('../index'); const storeFactory = require('../index');
const annotationFixtures = require('../../test/annotation-fixtures'); const annotationFixtures = require('../../test/annotation-fixtures');
const metadata = require('../../annotation-metadata'); const metadata = require('../../util/annotation-metadata');
const unroll = require('../../../shared/test/util').unroll; const unroll = require('../../../shared/test/util').unroll;
const uiConstants = require('../../ui-constants'); const uiConstants = require('../../ui-constants');
......
<selection-tabs <selection-tabs
ng-if="vm.showSelectedTabs()" ng-if="vm.showSelectedTabs()"
is-waiting-to-anchor-annotations="vm.waitingToAnchorAnnotations" is-loading="vm.isLoading()">
is-loading="vm.isLoading()"
selected-tab="vm.selectedTab"
total-annotations="vm.totalAnnotations"
total-notes="vm.totalNotes"
total-orphans="vm.totalOrphans">
</selection-tabs> </selection-tabs>
<search-status-bar <search-status-bar
class="search-status-bar" ng-if="!vm.isLoading()">
ng-if="!vm.isLoading()"
selected-tab="vm.selectedTab"
total-annotations="vm.totalAnnotations"
total-notes="vm.totalNotes">
</search-status-bar> </search-status-bar>
<!-- Display error message if direct-linked annotation fetch failed. --> <!-- Display error message if direct-linked annotation fetch failed. -->
......
'use strict'; 'use strict';
const buildThread = require('../build-thread'); const buildThread = require('../build-thread');
const metadata = require('../annotation-metadata'); const metadata = require('../util/annotation-metadata');
// Fixture with two top level annotations, one note and one reply // Fixture with two top level annotations, one note and one reply
const SIMPLE_FIXTURE = [ const SIMPLE_FIXTURE = [
......
'use strict'; 'use strict';
// Selectors that calculate the annotation counts displayed in tab headings // Functions that determine which tab an annotation should be displayed in.
// and determine which tab an annotation should be displayed in.
const countIf = require('./util/array-util').countIf;
const metadata = require('./annotation-metadata'); const metadata = require('./annotation-metadata');
const uiConstants = require('./ui-constants'); const uiConstants = require('../ui-constants');
/** /**
* Return the tab in which an annotation should be displayed. * Return the tab in which an annotation should be displayed.
...@@ -37,24 +35,7 @@ function shouldShowInTab(ann, tab) { ...@@ -37,24 +35,7 @@ function shouldShowInTab(ann, tab) {
return tabForAnnotation(ann) === tab; return tabForAnnotation(ann) === tab;
} }
/**
* Return the counts for the headings of different tabs.
*
* @param {Annotation[]} annotations - List of annotations to display
*/
function counts(annotations) {
const counts = {
notes: countIf(annotations, metadata.isPageNote),
annotations: countIf(annotations, metadata.isAnnotation),
orphans: countIf(annotations, metadata.isOrphan),
anchoring: countIf(annotations, metadata.isWaitingToAnchor),
};
return counts;
}
module.exports = { module.exports = {
counts: counts,
shouldShowInTab: shouldShowInTab, shouldShowInTab: shouldShowInTab,
tabForAnnotation: tabForAnnotation, tabForAnnotation: tabForAnnotation,
}; };
'use strict'; 'use strict';
const annotationMetadata = require('../annotation-metadata'); const annotationMetadata = require('../annotation-metadata');
const fixtures = require('./annotation-fixtures'); const fixtures = require('../../test/annotation-fixtures');
const unroll = require('../../shared/test/util').unroll; const unroll = require('../../../shared/test/util').unroll;
const documentMetadata = annotationMetadata.documentMetadata; const documentMetadata = annotationMetadata.documentMetadata;
const domainAndTitle = annotationMetadata.domainAndTitle; const domainAndTitle = annotationMetadata.domainAndTitle;
......
'use strict'; 'use strict';
const fixtures = require('./annotation-fixtures'); const fixtures = require('../../test/annotation-fixtures');
const uiConstants = require('../ui-constants'); const uiConstants = require('../../ui-constants');
const tabs = require('../tabs'); const tabs = require('../tabs');
const unroll = require('../../shared/test/util').unroll; const unroll = require('../../../shared/test/util').unroll;
describe('tabs', function() { describe('tabs', function() {
describe('tabForAnnotation', function() { describe('tabForAnnotation', function() {
...@@ -83,22 +83,4 @@ describe('tabs', function() { ...@@ -83,22 +83,4 @@ describe('tabs', function() {
] ]
); );
}); });
describe('counts', function() {
const annotation = Object.assign(fixtures.defaultAnnotation(), {
$orphan: false,
});
const orphan = Object.assign(fixtures.defaultAnnotation(), {
$orphan: true,
});
it('counts Annotations and Orphans separately', function() {
assert.deepEqual(tabs.counts([annotation, orphan], true), {
anchoring: 0,
annotations: 1,
notes: 0,
orphans: 1,
});
});
});
}); });
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