From 091a002b7de768c18c38895109dc46ab1c3ebca1 Mon Sep 17 00:00:00 2001 From: JJM van Deursen Date: Wed, 4 Mar 2020 11:07:40 +0100 Subject: [PATCH] [SIG-2331] Categories overview only shows active categories --- .../categories/__tests__/selectors.test.js | 48 +++++++++--- src/models/categories/selectors.js | 77 +++++++++++++------ .../settings/categories/Overview/index.js | 4 +- 3 files changed, 95 insertions(+), 34 deletions(-) diff --git a/src/models/categories/__tests__/selectors.test.js b/src/models/categories/__tests__/selectors.test.js index b4f543e25e..63f91d8c05 100644 --- a/src/models/categories/__tests__/selectors.test.js +++ b/src/models/categories/__tests__/selectors.test.js @@ -4,14 +4,16 @@ import categoriesJson from 'utils/__tests__/fixtures/categories_private.json'; import { initialState } from '../reducer'; import { - selectCategoriesDomain, + filterForMain, + filterForSub, + makeSelectAllCategories, + makeSelectAllSubCategories, + makeSelectByMainCategory, makeSelectCategories, makeSelectMainCategories, - makeSelectSubCategories, - makeSelectByMainCategory, makeSelectStructuredCategories, - filterForMain, - filterForSub, + makeSelectSubCategories, + selectCategoriesDomain, } from '../selectors'; const state = fromJS({ @@ -33,7 +35,7 @@ describe('models/categories/selectors', () => { }); test('makeSelectCategories', () => { - expect(makeSelectCategories.resultFunc(fromJS(initialState))).toBeNull(); + expect(makeSelectCategories.resultFunc(initialState)).toBeNull(); const categories = makeSelectCategories.resultFunc(state); const first = categories.first().toJS(); @@ -63,13 +65,26 @@ describe('models/categories/selectors', () => { expect(second).toEqual(secondWithExtraProps); }); - test('makeSelectCategories for inactive categories', () => { + test('makeSelectCategories should only return active categories', () => { const total = categoriesJson.results.length; const inactive = categoriesJson.results.filter(({ is_active }) => !is_active).length; - const result = makeSelectCategories.resultFunc(state); + const result = makeSelectCategories.resultFunc(state).toJS(); + + expect(result.length).toEqual(total - inactive); + + result.forEach(category => { + expect(category.is_active).toEqual(true); + }); + }); + + test('makeSelectAllCategories', () => { + const total = categoriesJson.results.length; + expect(makeSelectAllCategories.resultFunc(initialState)).toBeNull(); + + const result = makeSelectAllCategories.resultFunc(state); - expect(result.toJS().length).toEqual(total - inactive); + expect(result.toJS().length).toEqual(total); }); test('makeSelectMainCategories', () => { @@ -102,6 +117,21 @@ describe('models/categories/selectors', () => { expect(slugs).toEqual(keys); }); + test('makeSelectAllSubCategories', () => { + expect(makeSelectAllSubCategories.resultFunc()).toBeNull(); + + const subCategories = makeSelectAllSubCategories.resultFunc( + makeSelectAllCategories.resultFunc(state) + ); + const slugs = subCategories.map(({ slug }) => slug).sort(); + const keys = categoriesJson.results + .filter(filterForSub) + .map(({ slug }) => slug) + .sort(); + + expect(slugs).toEqual(keys); + }); + test('makeSelectByMainCategory', () => { const subCategories = makeSelectSubCategories.resultFunc( makeSelectCategories.resultFunc(state) diff --git a/src/models/categories/selectors.js b/src/models/categories/selectors.js index 9f006fdc14..120b6242b9 100644 --- a/src/models/categories/selectors.js +++ b/src/models/categories/selectors.js @@ -6,8 +6,26 @@ import { initialState } from './reducer'; export const selectCategoriesDomain = state => (state && state.get('categories')) || initialState; +const mappedSequence = results => + Seq(results) + .sort((a, b) => + a.get('name').toLowerCase() > b.get('name').toLowerCase() ? 1 : -1 + ) + .map(category => + category + .set('fk', category.get('id')) + .set('id', category.getIn(['_links', 'self', 'public'])) + .set('key', category.getIn(['_links', 'self', 'public'])) + .set('value', category.get('name')) + .set( + 'parentKey', + category.hasIn(['_links', 'sia:parent']) && + category.getIn(['_links', 'sia:parent', 'public']) + ) + ); + /** - * Alphabetically sorted list of all categories + * Alphabetically sorted list of all categories, excluding inactive categories * * Category data, coming from the API, is enriched so that specific props, like `id` and `key` * are present in the objects that components expect to receive. @@ -23,23 +41,25 @@ export const makeSelectCategories = createSelector( return null; } - return Seq(results) - .sort((a, b) => - a.get('name').toLowerCase() > b.get('name').toLowerCase() ? 1 : -1 - ) - .filter(category => category.get('is_active')) - .map(category => - category - .set('fk', category.get('id')) - .set('id', category.getIn(['_links', 'self', 'public'])) - .set('key', category.getIn(['_links', 'self', 'public'])) - .set('value', category.get('name')) - .set( - 'parentKey', - category.hasIn(['_links', 'sia:parent']) && - category.getIn(['_links', 'sia:parent', 'public']) - ) - ); + return mappedSequence(results).filter(category => category.get('is_active')); + } +); + +/** + * Alphabetically sorted list of all categories + * + * @returns {IndexedIterable} + */ +export const makeSelectAllCategories = createSelector( + selectCategoriesDomain, + state => { + const results = state.getIn(['categories', 'results']); + + if (!results) { + return null; + } + + return mappedSequence(results); } ); @@ -67,8 +87,12 @@ export const makeSelectMainCategories = createSelector( export const filterForSub = ({ _links }) => _links['sia:parent'] !== undefined; +const getHasParent = state => state.filter( + category => category.getIn(['_links', 'sia:parent']) !== undefined +); + /** - * Get all subcategories, sorted by name + * Get all subcategories, sorted by name, excluding inactive subcategories * * @returns {Object[]} */ @@ -79,11 +103,18 @@ export const makeSelectSubCategories = createSelector( return null; } - const categories = state.filter( - category => category.getIn(['_links', 'sia:parent']) !== undefined - ); + return getHasParent(state).toJS(); + } +); - return categories.toJS(); +export const makeSelectAllSubCategories = createSelector( + makeSelectAllCategories, + state => { + if (!state) { + return null; + } + + return getHasParent(state).toJS(); } ); diff --git a/src/signals/settings/categories/Overview/index.js b/src/signals/settings/categories/Overview/index.js index 98f9e7d5a8..cd2f4ec285 100644 --- a/src/signals/settings/categories/Overview/index.js +++ b/src/signals/settings/categories/Overview/index.js @@ -9,7 +9,7 @@ import { useParams, useHistory } from 'react-router-dom'; import PageHeader from 'signals/settings/components/PageHeader'; import LoadingIndicator from 'shared/components/LoadingIndicator'; import Pagination from 'components/Pagination'; -import { makeSelectSubCategories } from 'models/categories/selectors'; +import { makeSelectAllSubCategories } from 'models/categories/selectors'; import { makeSelectUserCan } from 'containers/App/selectors'; import { CATEGORY_URL, CATEGORIES_PAGED_URL } from 'signals/settings/routes'; import DataView from 'components/DataView'; @@ -132,7 +132,7 @@ CategoriesOverviewContainer.propTypes = { }; const mapStateToProps = createStructuredSelector({ - subCategories: makeSelectSubCategories, + subCategories: makeSelectAllSubCategories, userCan: makeSelectUserCan, });