diff --git a/apps/sensenet/cypress/e2e/search/search.cy.ts b/apps/sensenet/cypress/e2e/search/search.cy.ts index 243448f1e..0462889d2 100644 --- a/apps/sensenet/cypress/e2e/search/search.cy.ts +++ b/apps/sensenet/cypress/e2e/search/search.cy.ts @@ -70,4 +70,52 @@ describe('Search', () => { .then((fullname) => expect(fullname).to.eq('Business Cat')) }) }) + + context('more contentTypes filter', () => { + const term = 'admin*' + before(() => { + cy.login() + cy.visit(pathWithQueryParams({ path: '/', newParams: { repoUrl: Cypress.env('repoUrl') } })) + }) + beforeEach(() => { + cy.get('[data-test="sensenet-logo"]').click() + cy.get('[data-test="search-button"]') + .click() + .get('[data-test="command-box"] input') + .type(term, { delay: 250 }) + .get('[data-test="search-suggestion-list"] ul') + .children() + .as('search') + .first() + .click() + .location() + .should((loc) => { + expect(loc.pathname).to.eq(PATHS.search.appPath) + expect(loc.search).to.eq(`?term=${term}`) + }) + }) + + it('result contains users and groups', () => { + cy.get('[data-test="table-cell-admin"]').should('exist') + cy.get('[data-test="table-cell-administrators"]').should('exist') + cy.get('[data-test="table-cell-captain-picard"]').should('not.exist') + }) + + it('the "more" menu contains more than 20 items including "group" and "user"', () => { + cy.get('[data-test="more-type-filter-button"]').click() + cy.get('[id="more-type-filter"]').contains('li', 'User') + cy.get('[id="more-type-filter"]').contains('li', 'Group') + cy.get('[data-test="more-menu-item-user"]').parent().children().its('length').should('be.gt', 20) + // press to hide the menu to unblock the UI + cy.get('body').trigger('keydown', { keyCode: 27 }) + }) + + it('result contains one user after use the filter', () => { + cy.get('[data-test="more-type-filter-button"]').click().get('[data-test="more-menu-item-user"]').click() + // "MORE" changed to "USER" + cy.get('[data-test="more-type-filter-button"]').contains('User') + // "Administrators" disappeared + cy.get('[data-test="table-cell-administrators"]').should('not.exist') + }) + }) }) diff --git a/apps/sensenet/src/components/search/filters/type-filter.tsx b/apps/sensenet/src/components/search/filters/type-filter.tsx index d55c92026..29facede2 100644 --- a/apps/sensenet/src/components/search/filters/type-filter.tsx +++ b/apps/sensenet/src/components/search/filters/type-filter.tsx @@ -5,7 +5,10 @@ import Image from '@material-ui/icons/Image' import InsertDriveFile from '@material-ui/icons/InsertDriveFile' import Person from '@material-ui/icons/Person' import Search from '@material-ui/icons/Search' -import React, { useState } from 'react' +import { ConstantContent } from '@sensenet/client-core' +import { GenericContent } from '@sensenet/default-content-types' +import { useRepository } from '@sensenet/hooks-react' +import React, { useEffect, useState } from 'react' import { useSearch } from '../../../context/search' import { useLocalization } from '../../../hooks' @@ -75,14 +78,54 @@ const useStyles = makeStyles(() => { }) }) +type moreOptionsItem = { + name: string + type: string +} + export const TypeFilter = () => { + const repo = useRepository() const classes = useStyles() const localization = useLocalization().search.filters.type const [anchorEl, setAnchorEl] = useState(null) - const searchState = useSearch() + const [otherContentTypes, setOtherContentTypes] = useState([]) + + useEffect(() => { + const ac = new AbortController() + const categoryField = repo.schemas.getFieldTypeByName('Categories') + const fetchData = async () => { + try { + if (categoryField) { + const response = await repo.loadCollection({ + path: ConstantContent.PORTAL_ROOT.Path, + oDataOptions: { + query: "-Categories:'*HideByDefault*' +TypeIs:'ContentType' .AUTOFILTERS:OFF", + select: ['Type', 'DisplayName'], + orderby: 'Name', + }, + requestInit: { signal: ac.signal }, + }) + const items: moreOptionsItem[] = response.d.results.map((item) => ({ + name: item.DisplayName ?? item.Name, + type: item.Name, + })) + setOtherContentTypes(items) + } else { + setOtherContentTypes(moreOptions) + } + } catch (error) { + console.error('Error fetching data:', error) + } + } + fetchData() + + return () => { + ac.abort() + } + }, [repo]) - const [[activeFromMore], othersFromMore] = moreOptions.reduce( + const [[activeFromMore], othersFromMore] = otherContentTypes.reduce( ([pass, fail], filter) => { return filter.name === searchState.filters.type.name ? [[...pass, filter], fail] : [pass, [...fail, filter]] }, @@ -108,6 +151,7 @@ export const TypeFilter = () => { ))} { vertical: 'top', horizontal: 'right', }}> - {(othersFromMore as Filter[]).map((filter) => ( - { - setAnchorEl(null) - searchState.setFilters((filters) => - filters.type.name === filter.name ? filters : { ...filters, type: filter }, - ) - }}> - {localization[filter.name as keyof typeof localization]} - - ))} + {(othersFromMore as Filter[]) + .sort((a, b) => a.name.localeCompare(b.name)) + .map((filter) => ( + { + setAnchorEl(null) + searchState.setFilters((filters) => + filters.type.name === filter.name ? filters : { ...filters, type: filter }, + ) + }}> + {filter.name} + + ))} ) diff --git a/apps/sensenet/src/components/search/search-bar.tsx b/apps/sensenet/src/components/search/search-bar.tsx index da96b2dba..d84b38a32 100644 --- a/apps/sensenet/src/components/search/search-bar.tsx +++ b/apps/sensenet/src/components/search/search-bar.tsx @@ -59,15 +59,13 @@ export const SearchBar = () => { className={classes.inputButton} aria-label={localization.clearTerm} title={localization.clearTerm} - onClick={() => null}> - { - if (searchInputRef.current) { - searchInputRef.current.value = '' - } - searchState.setTerm('') - }} - /> + onClick={() => { + if (searchInputRef.current) { + searchInputRef.current.value = '' + } + searchState.setTerm('') + }}> + )}