Commit a22aef07 authored by Hannah Stepanek's avatar Hannah Stepanek

Use store for tab counts & selection

Previously, the number of annotations in each tab and the currently
selected tab were passed from the sidebar into the search-status-bar
and selection-tab components. Now, they are  grabbed from the store.
parent d0dcc7b4
......@@ -21,16 +21,14 @@ const countVisibleAnns = annThread => {
* A bar where the user can clear a selection or search and see whether
* any search results were found.
* */
function SearchStatusBar({
selectedTab,
totalAnnotations,
totalNotes,
rootThread,
}) {
function SearchStatusBar({ rootThread }) {
const storeState = useStore(store => store.getState());
const clearSelection = useStore(store => store.clearSelection);
const filterQuery = storeState.filterQuery;
const filterActive = !!storeState.filterQuery;
const annotationCount = useStore(store => store.annotationCount());
const noteCount = useStore(store => store.noteCount());
const selectedTab = storeState.selectedTab;
const thread = rootThread.thread(storeState);
......@@ -89,13 +87,13 @@ function SearchStatusBar({
{selectedTab === uiConstants.TAB_ANNOTATIONS && (
<Fragment>
Show all annotations
{totalAnnotations > 1 && <span> ({totalAnnotations})</span>}
{annotationCount > 1 && <span> ({annotationCount})</span>}
</Fragment>
)}
{selectedTab === uiConstants.TAB_NOTES && (
<Fragment>
Show all notes
{totalNotes > 1 && <span> ({totalNotes})</span>}
{noteCount > 1 && <span> ({noteCount})</span>}
</Fragment>
)}
</button>
......@@ -106,13 +104,7 @@ function SearchStatusBar({
}
SearchStatusBar.propTypes = {
selectedTab: propTypes.oneOf([
uiConstants.TAB_ANNOTATIONS,
uiConstants.TAB_ORPHANS,
uiConstants.TAB_NOTES,
]).isRequired,
totalAnnotations: propTypes.number.isRequired,
totalNotes: propTypes.number.isRequired,
// Injected services.
rootThread: propTypes.object.isRequired,
};
......
......@@ -69,16 +69,14 @@ Tab.propTypes = {
* Tabbed display of annotations and notes.
*/
function SelectionTabs({
isWaitingToAnchorAnnotations,
isLoading,
selectedTab,
totalAnnotations,
totalNotes,
totalOrphans,
settings,
session,
}) {
function SelectionTabs({ isLoading, settings, session }) {
const selectedTab = useStore(store => store.getState().selectedTab);
const noteCount = useStore(store => store.noteCount());
const annotationCount = useStore(store => store.annotationCount());
const orphanCount = useStore(store => store.orphanCount());
const isWaitingToAnchorAnnotations = useStore(store =>
store.isWaitingToAnchorAnnotations()
);
// actions
const store = useStore(store => ({
clearSelectedAnnotations: store.clearSelectedAnnotations,
......@@ -94,11 +92,11 @@ function SelectionTabs({
const showAnnotationsUnavailableMessage =
selectedTab === uiConstants.TAB_ANNOTATIONS &&
totalAnnotations === 0 &&
annotationCount === 0 &&
!isWaitingToAnchorAnnotations;
const showNotesUnavailableMessage =
selectedTab === uiConstants.TAB_NOTES && totalNotes === 0;
selectedTab === uiConstants.TAB_NOTES && noteCount === 0;
const showSidebarTutorial = sessionUtil.shouldShowSidebarTutorial(
session.state
......@@ -112,7 +110,7 @@ function SelectionTabs({
})}
>
<Tab
count={totalAnnotations}
count={annotationCount}
isWaitingToAnchor={isWaitingToAnchorAnnotations}
selected={selectedTab === uiConstants.TAB_ANNOTATIONS}
type={uiConstants.TAB_ANNOTATIONS}
......@@ -121,7 +119,7 @@ function SelectionTabs({
Annotations
</Tab>
<Tab
count={totalNotes}
count={noteCount}
isWaitingToAnchor={isWaitingToAnchorAnnotations}
selected={selectedTab === uiConstants.TAB_NOTES}
type={uiConstants.TAB_NOTES}
......@@ -129,9 +127,9 @@ function SelectionTabs({
>
Page Notes
</Tab>
{totalOrphans > 0 && (
{orphanCount > 0 && (
<Tab
count={totalOrphans}
count={orphanCount}
isWaitingToAnchor={isWaitingToAnchorAnnotations}
selected={selectedTab === uiConstants.TAB_ORPHANS}
type={uiConstants.TAB_ORPHANS}
......@@ -182,20 +180,6 @@ SelectionTabs.propTypes = {
* Are we waiting on any annotations from the server?
*/
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.
settings: propTypes.object.isRequired,
......
......@@ -22,19 +22,7 @@ function SidebarContentController(
}
const unsubscribeAnnotationUI = store.subscribe(function() {
const state = store.getState();
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);
......
......@@ -21,11 +21,8 @@ describe('SearchStatusBar', () => {
};
fakeStore = {
getState: sinon.stub(),
selectTab: sinon.stub(),
clearSelectedAnnotations: sinon.stub(),
clearDirectLinkedGroupFetchFailed: sinon.stub(),
clearDirectLinkedIds: sinon.stub(),
clearSelection: sinon.stub(),
annotationCount: sinon.stub().returns(1),
noteCount: sinon.stub().returns(0),
};
SearchStatusBar.$imports.$mock({
......@@ -96,13 +93,11 @@ describe('SearchStatusBar', () => {
});
fakeStore.getState.returns({
filterQuery: 'tag:foo',
});
const wrapper = createComponent({
selectedTab: 'annotation',
totalAnnotations: 3,
totalNotes: 0,
});
fakeStore.annotationCount.returns(3);
const wrapper = createComponent({});
const buttonText = wrapper.find('button').text();
assert.equal(buttonText, 'Clear search');
......@@ -117,14 +112,11 @@ describe('SearchStatusBar', () => {
filterQuery: null,
directLinkedGroupFetchFailed: true,
selectedAnnotationMap: { annId: true },
});
const wrapper = createComponent({
selectedTab: 'annotation',
totalAnnotations: 1,
totalNotes: 0,
});
const wrapper = createComponent({});
const buttonText = wrapper.find('button').text();
assert.equal(buttonText, 'Show all annotations');
});
......@@ -134,14 +126,11 @@ describe('SearchStatusBar', () => {
filterQuery: null,
directLinkedGroupFetchFailed: false,
selectedAnnotationMap: { annId: true },
});
const wrapper = createComponent({
selectedTab: 'annotation',
totalAnnotations: 1,
totalNotes: 0,
});
const wrapper = createComponent({});
const buttonText = wrapper.find('button').text();
assert.equal(buttonText, 'Show all annotations');
});
......@@ -152,13 +141,11 @@ describe('SearchStatusBar', () => {
filterQuery: null,
directLinkedGroupFetchFailed: false,
selectedAnnotationMap: selectedAnnotationMap,
});
const wrapper = createComponent({
selectedTab: 'annotation',
totalAnnotations: 1,
totalNotes: 0,
});
const wrapper = createComponent({});
const buttons = wrapper.find('button');
assert.equal(buttons.length, 0);
});
......@@ -203,13 +190,12 @@ describe('SearchStatusBar', () => {
filterQuery: null,
directLinkedGroupFetchFailed: false,
selectedAnnotationMap: { annId: true },
});
const wrapper = createComponent({
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();
assert.equal(buttonText, test.expectedText);
......
......@@ -11,23 +11,15 @@ describe('SelectionTabs', function() {
// mock services
let fakeSession;
let fakeSettings;
let fakeStore;
// default props
const defaultProps = {
isLoading: false,
isWaitingToAnchorAnnotations: false,
selectedTab: uiConstants.TAB_ANNOTATIONS,
totalAnnotations: 123,
totalNotes: 456,
totalOrphans: 0,
};
SelectionTabs.$imports.$mock({
'../store/use-store': callback =>
callback({
clearSelectedAnnotations: sinon.stub(),
selectTab: sinon.stub(),
}),
'../store/use-store': callback => callback(fakeStore),
});
function createComponent(props) {
......@@ -64,6 +56,17 @@ describe('SelectionTabs', function() {
fakeSettings = {
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 =>
......@@ -98,26 +101,23 @@ describe('SelectionTabs', function() {
});
it('should display notes tab as selected', function() {
const wrapper = createDeepComponent({
selectedTab: uiConstants.TAB_NOTES,
});
fakeStore.getState.returns({ selectedTab: uiConstants.TAB_NOTES });
const wrapper = createDeepComponent({});
const tabs = wrapper.find('a');
assert.isTrue(tabs.at(1).hasClass('is-selected'));
});
it('should display orphans tab as selected if there is 1 or more orphans', function() {
const wrapper = createDeepComponent({
selectedTab: uiConstants.TAB_ORPHANS,
totalOrphans: 1,
});
fakeStore.getState.returns({ selectedTab: uiConstants.TAB_ORPHANS });
fakeStore.orphanCount.returns(1);
const wrapper = createDeepComponent({});
const tabs = wrapper.find('a');
assert.isTrue(tabs.at(2).hasClass('is-selected'));
});
it('should not display orphans tab if there are 0 orphans', function() {
const wrapper = createDeepComponent({
selectedTab: uiConstants.TAB_ORPHANS,
});
fakeStore.getState.returns({ selectedTab: uiConstants.TAB_ORPHANS });
const wrapper = createDeepComponent({});
const tabs = wrapper.find('a');
assert.equal(tabs.length, 2);
});
......@@ -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() {
const wrapper = createComponent({
selectedTab: uiConstants.TAB_NOTES,
});
fakeStore.getState.returns({ selectedTab: uiConstants.TAB_NOTES });
const wrapper = createComponent({});
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() {
fakeSettings.enableExperimentalNewNoteButton = true;
const wrapper = createComponent({
selectedTab: uiConstants.TAB_NOTES,
});
fakeStore.getState.returns({ selectedTab: uiConstants.TAB_NOTES });
const wrapper = createComponent({});
assert.equal(wrapper.find(NewNoteBtn).length, 1);
});
it('should not display a message when its loading annotation count is 0', function() {
fakeStore.annotationCount.returns(0);
const wrapper = createComponent({
totalAnnotations: 0,
isLoading: true,
});
assert.isFalse(wrapper.exists('.annotation-unavailable-message__label'));
});
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({
selectedTab: uiConstants.TAB_NOTES,
totalNotes: 0,
isLoading: true,
});
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() {
fakeStore.annotationCount.returns(0);
fakeStore.isWaitingToAnchorAnnotations.returns(true);
const wrapper = createComponent({
totalAnnotations: 0,
isWaitingToAnchorAnnotations: true,
isLoading: false,
});
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() {
const wrapper = createComponent({
selectedTab: uiConstants.TAB_NOTES,
totalNotes: 0,
});
fakeStore.getState.returns({ selectedTab: uiConstants.TAB_NOTES });
fakeStore.noteCount.returns(0);
const wrapper = createComponent({});
assert.include(
unavailableMessage(wrapper),
'There are no page notes in this group.'
......@@ -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() {
fakeSettings.enableExperimentalNewNoteButton = true;
const wrapper = createComponent({
selectedTab: uiConstants.TAB_NOTES,
totalNotes: 0,
});
fakeStore.getState.returns({ selectedTab: uiConstants.TAB_NOTES });
fakeStore.noteCount.returns(0);
const wrapper = createComponent({});
assert.include(
wrapper.find('.annotation-unavailable-message__tutorial').text(),
'Create one by clicking the'
......@@ -208,9 +204,8 @@ describe('SelectionTabs', function() {
});
it('should display the longer version of the no annotations message when there are no annotations', function() {
const wrapper = createComponent({
totalAnnotations: 0,
});
fakeStore.annotationCount.returns(0);
const wrapper = createComponent({});
assert.include(
unavailableMessage(wrapper),
'There are no annotations in this group.'
......@@ -229,10 +224,9 @@ describe('SelectionTabs', 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() {
fakeSession.state.preferences.show_sidebar_tutorial = true;
const wrapper = createComponent({
totalNotes: 0,
selectedTab: uiConstants.TAB_NOTES,
});
fakeStore.getState.returns({ selectedTab: uiConstants.TAB_NOTES });
fakeStore.noteCount.returns(0);
const wrapper = createComponent({});
const msg = unavailableMessage(wrapper);
......@@ -246,9 +240,8 @@ describe('SelectionTabs', 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;
const wrapper = createComponent({
totalAnnotations: 0,
});
fakeStore.annotationCount.returns(0);
const wrapper = createComponent({});
const msg = unavailableMessage(wrapper);
......
......@@ -3,7 +3,6 @@
// Selectors that calculate the annotation counts displayed in tab headings
// and determine which tab an annotation should be displayed in.
const countIf = require('./util/array-util').countIf;
const metadata = require('./annotation-metadata');
const uiConstants = require('./ui-constants');
......@@ -37,24 +36,7 @@ function shouldShowInTab(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 = {
counts: counts,
shouldShowInTab: shouldShowInTab,
tabForAnnotation: tabForAnnotation,
};
<selection-tabs
ng-if="vm.showSelectedTabs()"
is-waiting-to-anchor-annotations="vm.waitingToAnchorAnnotations"
is-loading="vm.isLoading()"
selected-tab="vm.selectedTab"
total-annotations="vm.totalAnnotations"
total-notes="vm.totalNotes"
total-orphans="vm.totalOrphans">
is-loading="vm.isLoading()">
</selection-tabs>
<search-status-bar
class="search-status-bar"
ng-if="!vm.isLoading()"
selected-tab="vm.selectedTab"
total-annotations="vm.totalAnnotations"
total-notes="vm.totalNotes">
ng-if="!vm.isLoading()">
</search-status-bar>
<!-- Display error message if direct-linked annotation fetch failed. -->
......
......@@ -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