mockLoggedInUser,
mockPaging,
mockRawIssue,
+ mockRule,
mockRuleDetails,
} from '../../helpers/testMocks';
import {
RawIssuesResponse,
ReferencedComponent,
} from '../../types/issues';
+import { SearchRulesQuery } from '../../types/rules';
import { Standards } from '../../types/security';
import {
Dict,
FlowType,
+ Rule,
RuleActivation,
RuleDetails,
SnippetsByComponent,
setIssueTransition,
setIssueType,
} from '../issues';
-import { getRuleDetails } from '../rules';
+import { getRuleDetails, searchRules } from '../rules';
import { dismissNotice, getCurrentUser, searchUsers } from '../users';
function mockReferenceComponent(override?: Partial<ReferencedComponent>) {
currentUser: LoggedInUser;
standards?: Standards;
defaultList: IssueData[];
+ rulesList: Rule[];
list: IssueData[];
constructor() {
snippets: {},
},
];
+ this.rulesList = [
+ mockRule({
+ key: 'simpleRuleId',
+ name: 'Simple rule',
+ lang: 'java',
+ langName: 'Java',
+ type: 'CODE_SMELL',
+ }),
+ mockRule({
+ key: 'advancedRuleId',
+ name: 'Advanced rule',
+ lang: 'web',
+ langName: 'HTML',
+ type: 'VULNERABILITY',
+ }),
+ mockRule({
+ key: 'cpp:S6069',
+ lang: 'cpp',
+ langName: 'C++',
+ name: 'Security hotspot rule',
+ type: 'SECURITY_HOTSPOT',
+ }),
+ mockRule({
+ key: 'tsql:S131',
+ name: '"CASE" expressions should end with "ELSE" clauses',
+ lang: 'tsql',
+ langName: 'T-SQL',
+ }),
+ ];
this.list = cloneDeep(this.defaultList);
(searchIssues as jest.Mock).mockImplementation(this.handleSearchIssues);
(getRuleDetails as jest.Mock).mockImplementation(this.handleGetRuleDetails);
+ jest.mocked(searchRules).mockImplementation(this.handleSearchRules);
(getIssueFlowSnippets as jest.Mock).mockImplementation(this.handleGetIssueFlowSnippets);
(bulkChangeIssues as jest.Mock).mockImplementation(this.handleBulkChangeIssues);
(getCurrentUser as jest.Mock).mockImplementation(this.handleGetCurrentUser);
return this.reply(issue.snippets);
};
+ handleSearchRules = (req: SearchRulesQuery) => {
+ const rules = this.rulesList.filter((rule) => {
+ const query = req.q?.toLowerCase() || '';
+ const nameMatches = rule.name.toLowerCase().includes(query);
+ const keyMatches = rule.key.toLowerCase().includes(query);
+ const isTypeRight = req.types?.includes(rule.type);
+ return isTypeRight && (nameMatches || keyMatches);
+ });
+ return this.reply({
+ p: 1,
+ ps: 30,
+ rules,
+ total: rules.length,
+ });
+ };
+
handleGetRuleDetails = (parameters: {
actives?: boolean;
key: string;
pageSize,
total: filteredList.length,
}),
+ rules: this.rulesList,
users: [
{ login: 'login0' },
{ login: 'login1', name: 'Login 1' },
: undefined;
render() {
+ const { open, value } = this.props;
+
return (
<FacetBox property="availableSince">
<FacetHeader
name={translate('coding_rules.facet.available_since')}
onClear={this.handleClear}
onClick={this.handleHeaderClick}
- open={this.props.open}
+ open={open}
values={this.getValues()}
/>
- {this.props.open && (
+ {open && (
<DateInput
name="available-since"
onChange={this.handlePeriodChange}
placeholder={translate('date')}
- value={this.props.value}
+ value={value}
/>
)}
</FacetBox>
};
render() {
- const { disabled, renderTextName = defaultRenderName, stats } = this.props;
+ const {
+ children,
+ disabled,
+ disabledHelper,
+ open,
+ property,
+ renderTextName = defaultRenderName,
+ stats,
+ } = this.props;
const values = this.props.values.map(renderTextName);
const items =
this.props.options ||
return (
<FacetBox
className={classNames({ 'search-navigator-facet-box-forbidden': disabled })}
- property={this.props.property}
+ property={property}
>
<FacetHeader
- name={translate('coding_rules.facet', this.props.property)}
+ name={translate('coding_rules.facet', property)}
disabled={disabled}
- disabledHelper={this.props.disabledHelper}
+ disabledHelper={disabledHelper}
onClear={this.handleClear}
onClick={disabled ? undefined : this.handleHeaderClick}
- open={this.props.open && !disabled}
+ open={open && !disabled}
values={values}
>
- {this.props.children}
+ {children}
</FacetHeader>
- {this.props.open && items !== undefined && (
- <FacetItemsList>{items.map(this.renderItem)}</FacetItemsList>
+ {open && items !== undefined && (
+ <FacetItemsList label={property}>{items.map(this.renderItem)}</FacetItemsList>
)}
- {this.props.open && this.props.renderFooter !== undefined && this.props.renderFooter()}
+ {open && this.props.renderFooter !== undefined && this.props.renderFooter()}
</FacetBox>
);
}
};
render() {
- const { languages, referencedProfiles } = this.props;
+ const { languages, open, referencedProfiles } = this.props;
let profiles = Object.values(referencedProfiles);
if (languages.length > 0) {
profiles = profiles.filter((profile) => languages.includes(profile.language));
(profile) => profile.languageName
);
+ const property = 'profile';
+
return (
- <FacetBox property="profile">
+ <FacetBox property={property}>
<FacetHeader
name={translate('coding_rules.facet.qprofile')}
onClear={this.handleClear}
onClick={this.handleHeaderClick}
- open={this.props.open}
+ open={open}
values={this.getTextValue()}
>
<DocumentationTooltip
/>
</FacetHeader>
- {this.props.open && <FacetItemsList>{profiles.map(this.renderItem)}</FacetItemsList>}
+ {open && <FacetItemsList label={property}>{profiles.map(this.renderItem)}</FacetItemsList>}
</FacetBox>
);
}
};
render() {
- const { domain } = this.props;
+ const { domain, open } = this.props;
const helperMessageKey = `component_measures.domain_facets.${domain.name}.help`;
const helper = hasMessage(helperMessageKey) ? translate(helperMessageKey) : undefined;
return (
helper={helper}
name={getLocalizedMetricDomain(domain.name)}
onClick={this.handleHeaderClick}
- open={this.props.open}
+ open={open}
values={this.getValues()}
/>
- {this.props.open && (
- <FacetItemsList>
+ {open && (
+ <FacetItemsList label={domain.name}>
{this.renderOverviewFacet()}
{this.renderItemsFacet()}
</FacetItemsList>
const facetName = translate('component_measures.overview', value, 'facet');
return (
<FacetBox property={value}>
- <FacetItemsList>
+ <FacetItemsList label={value}>
<FacetItem
active={value === selected}
key={value}
open={true}
values={[]}
/>
- <FacetItemsList>
+ <FacetItemsList
+ label="Reliability"
+ >
<FacetItem
active={false}
halfWidth={false}
]
}
/>
- <FacetItemsList>
+ <FacetItemsList
+ label="Reliability"
+ >
<FacetItem
active={false}
halfWidth={false}
open={true}
values={[]}
/>
- <FacetItemsList>
+ <FacetItemsList
+ label="Reliability"
+ >
<FacetItem
active={false}
halfWidth={false}
open={true}
values={[]}
/>
- <FacetItemsList>
+ <FacetItemsList
+ label="Reliability"
+ >
<FacetItem
active={false}
halfWidth={false}
// Rule
await user.click(ui.ruleFacet.get());
await user.click(screen.getByRole('checkbox', { name: 'other' }));
+ expect(screen.getByRole('checkbox', { name: '(HTML) Advanced rule' })).toBeInTheDocument(); // Name should apply to the rule
// Tag
await user.click(ui.tagFacet.get());
expect(ui.issueItem2.get()).toBeInTheDocument();
expect(ui.issueItem3.get()).toBeInTheDocument();
});
+
+ it('should search for rules with proper types', async () => {
+ const user = userEvent.setup();
+
+ renderIssueApp();
+
+ await user.click(await ui.ruleFacet.find());
+ await user.type(ui.ruleFacetSearch.get(), 'rule');
+ expect(within(ui.ruleFacetList.get()).getAllByRole('checkbox')).toHaveLength(2);
+ expect(
+ within(ui.ruleFacetList.get()).getByRole('checkbox', {
+ name: /Advanced rule/,
+ })
+ ).toBeInTheDocument();
+ expect(
+ within(ui.ruleFacetList.get()).getByRole('checkbox', {
+ name: /Simple rule/,
+ })
+ ).toBeInTheDocument();
+
+ await user.click(ui.vulnerabilityIssueTypeFilter.get());
+ // after changing the issue type filter, search field is reset, so we type again
+ await user.type(ui.ruleFacetSearch.get(), 'rule');
+
+ expect(
+ within(ui.ruleFacetList.get()).getByRole('checkbox', {
+ name: /Advanced rule/,
+ })
+ ).toBeInTheDocument();
+ expect(
+ within(ui.ruleFacetList.get()).queryByRole('checkbox', {
+ name: /Simple rule/,
+ })
+ ).not.toBeInTheDocument();
+ });
});
});
}
render() {
+ const { fetching, open } = this.props;
+
return (
<FacetBox property={this.property}>
<FacetHeader
- fetching={this.props.fetching}
+ fetching={fetching}
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
onClick={this.handleHeaderClick}
- open={this.props.open}
+ open={open}
values={this.getValues()}
/>
- {this.props.open && this.renderInner()}
+ {open && this.renderInner()}
</FacetBox>
);
}
return (
<FacetBox property={PROPERTY}>
- <FacetItemsList>
+ <FacetItemsList label={PROPERTY}>
<FacetItem
active={newCodeSelected}
loading={fetching}
import { translate } from '../../../helpers/l10n';
import { IssueResolution } from '../../../types/issues';
import { Dict } from '../../../types/types';
-import { formatFacetStat, Query } from '../utils';
+import { Query, formatFacetStat } from '../utils';
interface Props {
fetching: boolean;
};
render() {
- const { resolutions, stats = {} } = this.props;
+ const { fetching, open, resolutions, stats = {} } = this.props;
const values = resolutions.map((resolution) => this.getFacetItemName(resolution));
return (
<FacetBox property={this.property}>
<FacetHeader
- fetching={this.props.fetching}
+ fetching={fetching}
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
onClick={this.handleHeaderClick}
- open={this.props.open}
+ open={open}
values={values}
/>
- {this.props.open && (
+ {open && (
<>
- <FacetItemsList>{RESOLUTIONS.map(this.renderItem)}</FacetItemsList>
+ <FacetItemsList label={this.property}>
+ {RESOLUTIONS.map(this.renderItem)}
+ </FacetItemsList>
<MultipleSelectionHint
options={Object.keys(stats).length}
values={resolutions.length}
import * as React from 'react';
import { searchRules } from '../../../api/rules';
import ListStyleFacet from '../../../components/facet/ListStyleFacet';
+import { ISSUE_TYPES } from '../../../helpers/constants';
import { translate } from '../../../helpers/l10n';
-import { Facet, ReferencedRule } from '../../../types/issues';
+import { Facet, IssueType, ReferencedRule } from '../../../types/issues';
import { Dict, Rule } from '../../../types/types';
import { Query } from '../utils';
interface Props {
fetching: boolean;
- languages: string[];
loadSearchResultCount: (property: string, changes: Partial<Query>) => Promise<Facet>;
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
open: boolean;
query: Query;
referencedRules: Dict<ReferencedRule>;
- rules: string[];
stats: Dict<number> | undefined;
}
export default class RuleFacet extends React.PureComponent<Props> {
handleSearch = (query: string, page = 1) => {
- const { languages } = this.props;
+ const { languages, types } = this.props.query;
return searchRules({
f: 'name,langName',
languages: languages.length ? languages.join() : undefined,
q: query,
p: page,
ps: 30,
+ types: types.length
+ ? types.join()
+ : ISSUE_TYPES.filter((type) => type !== IssueType.SecurityHotspot).join(),
s: 'name',
include_external: true,
}).then((response) => ({
};
render() {
+ const { fetching, open, query, stats } = this.props;
+
return (
<ListStyleFacet<Rule>
facetHeader={translate('issues.facet.rules')}
- fetching={this.props.fetching}
+ fetching={fetching}
getFacetItemText={this.getRuleName}
getSearchResultKey={(rule) => rule.key}
getSearchResultText={(rule) => rule.name}
onChange={this.props.onChange}
onSearch={this.handleSearch}
onToggle={this.props.onToggle}
- open={this.props.open}
+ open={open}
property="rules"
- query={omit(this.props.query, 'rules')}
+ query={omit(query, 'rules')}
renderFacetItem={this.getRuleName}
renderSearchResult={this.renderSearchResult}
searchPlaceholder={translate('search.search_for_rules')}
- stats={this.props.stats}
- values={this.props.rules}
+ stats={stats}
+ values={query.rules}
/>
);
}
const { fetching, open, scopes = [], stats = {} } = props;
const values = scopes.map((scope) => translate('issue.scope', scope));
+ const property = 'scopes';
+
return (
- <FacetBox property="scopes">
+ <FacetBox property={property}>
<FacetHeader
fetching={fetching}
name={translate('issues.facet.scopes')}
{open && (
<>
- <FacetItemsList>
+ <FacetItemsList label={property}>
{SOURCE_SCOPES.map(({ scope, qualifier }) => {
const active = scopes.includes(scope);
const stat = stats[scope];
import SeverityHelper from '../../../components/shared/SeverityHelper';
import { translate } from '../../../helpers/l10n';
import { Dict } from '../../../types/types';
-import { formatFacetStat, Query } from '../utils';
+import { Query, formatFacetStat } from '../utils';
interface Props {
fetching: boolean;
};
render() {
- const { severities, stats = {} } = this.props;
+ const { fetching, open, severities, stats = {} } = this.props;
const values = severities.map((severity) => translate('severity', severity));
return (
<FacetBox property={this.property}>
<FacetHeader
- fetching={this.props.fetching}
+ fetching={fetching}
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
onClick={this.handleHeaderClick}
- open={this.props.open}
+ open={open}
values={values}
/>
- {this.props.open && (
+ {open && (
<>
- <FacetItemsList>{SEVERITIES.map(this.renderItem)}</FacetItemsList>
+ <FacetItemsList label={this.property}>{SEVERITIES.map(this.renderItem)}</FacetItemsList>
<MultipleSelectionHint options={Object.keys(stats).length} values={severities.length} />
</>
)}
/>
<RuleFacet
fetching={this.props.loadingFacets.rules === true}
- languages={query.languages}
loadSearchResultCount={this.props.loadSearchResultCount}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
open={!!openFacets.rules}
query={query}
referencedRules={this.props.referencedRules}
- rules={query.rules}
stats={facets.rules}
/>
<TagFacet
import { Facet } from '../../../types/issues';
import { SecurityStandard, Standards } from '../../../types/security';
import { Dict } from '../../../types/types';
-import { formatFacetStat, Query, STANDARDS } from '../utils';
+import { Query, STANDARDS, formatFacetStat } from '../utils';
interface Props {
cwe: string[];
return null;
}
const categories = sortBy(Object.keys(stats), (key) => -stats[key]);
- return this.renderFacetItemsList(stats, values, categories, renderName, renderName, onClick);
+ return this.renderFacetItemsList(
+ stats,
+ values,
+ categories,
+ valuesProp,
+ renderName,
+ renderName,
+ onClick
+ );
};
// eslint-disable-next-line max-params
stats: any,
values: string[],
categories: string[],
+ listLabel: ValuesProp,
renderName: (standards: Standards, category: string) => React.ReactNode,
renderTooltip: (standards: Standards, category: string) => string,
onClick: (x: string, multiple?: boolean) => void
};
return (
- <FacetItemsList>
+ <FacetItemsList label={listLabel}>
{categories.map((category) => (
<FacetItem
active={values.includes(category)}
const allItemShown = limitedList.length + selectedBelowLimit.length === sortedItems.length;
return (
<>
- <FacetItemsList>
+ <FacetItemsList label={SecurityStandard.SONARSOURCE}>
{limitedList.map((item) => (
<FacetItem
active={values.includes(item)}
{selectedBelowLimit.length > 0 && (
<>
{!allItemShown && <div className="note spacer-bottom text-center">⋯</div>}
- <FacetItemsList>
+ <FacetItemsList label={SecurityStandard.SONARSOURCE}>
{selectedBelowLimit.map((item) => (
<FacetItem
active={true}
}
renderSubFacets() {
+ const {
+ cwe,
+ cweOpen,
+ cweStats,
+ fetchingCwe,
+ fetchingOwaspTop10,
+ 'fetchingOwaspTop10-2021': fetchingOwaspTop102021,
+ fetchingSonarSourceSecurity,
+ owaspTop10,
+ owaspTop10Open,
+ 'owaspTop10-2021Open': owaspTop102021Open,
+ 'owaspTop10-2021': owaspTop102021,
+ query,
+ sonarsourceSecurity,
+ sonarsourceSecurityOpen,
+ } = this.props;
return (
<>
<FacetBox className="is-inner" property={SecurityStandard.SONARSOURCE}>
<FacetHeader
- fetching={this.props.fetchingSonarSourceSecurity}
+ fetching={fetchingSonarSourceSecurity}
name={translate('issues.facet.sonarsourceSecurity')}
onClick={this.handleSonarSourceSecurityHeaderClick}
- open={this.props.sonarsourceSecurityOpen}
- values={this.props.sonarsourceSecurity.map((item) =>
+ open={sonarsourceSecurityOpen}
+ values={sonarsourceSecurity.map((item) =>
renderSonarSourceSecurityCategory(this.state.standards, item)
)}
/>
- {this.props.sonarsourceSecurityOpen && (
+ {sonarsourceSecurityOpen && (
<>
{this.renderSonarSourceSecurityList()}
{this.renderSonarSourceSecurityHint()}
</FacetBox>
<FacetBox className="is-inner" property={SecurityStandard.OWASP_TOP10_2021}>
<FacetHeader
- fetching={this.props['fetchingOwaspTop10-2021']}
+ fetching={fetchingOwaspTop102021}
name={translate('issues.facet.owaspTop10_2021')}
onClick={this.handleOwaspTop102021HeaderClick}
- open={this.props['owaspTop10-2021Open']}
- values={this.props['owaspTop10-2021'].map((item) =>
+ open={owaspTop102021Open}
+ values={owaspTop102021.map((item) =>
renderOwaspTop102021Category(this.state.standards, item)
)}
/>
- {this.props['owaspTop10-2021Open'] && (
+ {owaspTop102021Open && (
<>
{this.renderOwaspTop102021List()}
{this.renderOwaspTop102021Hint()}
</FacetBox>
<FacetBox className="is-inner" property={SecurityStandard.OWASP_TOP10}>
<FacetHeader
- fetching={this.props.fetchingOwaspTop10}
+ fetching={fetchingOwaspTop10}
name={translate('issues.facet.owaspTop10')}
onClick={this.handleOwaspTop10HeaderClick}
- open={this.props.owaspTop10Open}
- values={this.props.owaspTop10.map((item) =>
- renderOwaspTop10Category(this.state.standards, item)
- )}
+ open={owaspTop10Open}
+ values={owaspTop10.map((item) => renderOwaspTop10Category(this.state.standards, item))}
/>
- {this.props.owaspTop10Open && (
+ {owaspTop10Open && (
<>
{this.renderOwaspTop10List()}
{this.renderOwaspTop10Hint()}
<ListStyleFacet<string>
className="is-inner"
facetHeader={translate('issues.facet.cwe')}
- fetching={this.props.fetchingCwe}
+ fetching={fetchingCwe}
getFacetItemText={(item) => renderCWECategory(this.state.standards, item)}
getSearchResultKey={(item) => item}
getSearchResultText={(item) => renderCWECategory(this.state.standards, item)}
onChange={this.props.onChange}
onSearch={this.handleCWESearch}
onToggle={this.props.onToggle}
- open={this.props.cweOpen}
+ open={cweOpen}
property={SecurityStandard.CWE}
- query={omit(this.props.query, 'cwe')}
+ query={omit(query, 'cwe')}
renderFacetItem={(item) => renderCWECategory(this.state.standards, item)}
renderSearchResult={(item, query) =>
highlightTerm(renderCWECategory(this.state.standards, item), query)
}
searchPlaceholder={translate('search.search_for_cwe')}
- stats={this.props.cweStats}
- values={this.props.cwe}
+ stats={cweStats}
+ values={cwe}
/>
</>
);
}
render() {
+ const { open } = this.props;
+
return (
<FacetBox property={this.property}>
<FacetHeader
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
onClick={this.handleHeaderClick}
- open={this.props.open}
+ open={open}
values={this.getValues()}
/>
- {this.props.open && this.renderSubFacets()}
+ {open && this.renderSubFacets()}
</FacetBox>
);
}
import StatusHelper from '../../../components/shared/StatusHelper';
import { translate } from '../../../helpers/l10n';
import { Dict } from '../../../types/types';
-import { formatFacetStat, Query } from '../utils';
+import { Query, formatFacetStat } from '../utils';
interface Props {
fetching: boolean;
};
render() {
- const { statuses, stats = {} } = this.props;
+ const { fetching, open, statuses, stats = {} } = this.props;
const values = statuses.map((status) => translate('issue.status', status));
return (
<FacetBox property={this.property}>
<FacetHeader
- fetching={this.props.fetching}
+ fetching={fetching}
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
onClick={this.handleHeaderClick}
- open={this.props.open}
+ open={open}
values={values}
/>
- {this.props.open && (
+ {open && (
<>
- <FacetItemsList>{STATUSES.map(this.renderItem)}</FacetItemsList>
+ <FacetItemsList label={this.property}>{STATUSES.map(this.renderItem)}</FacetItemsList>
<MultipleSelectionHint options={Object.keys(stats).length} values={statuses.length} />
</>
)}
import { ISSUE_TYPES } from '../../../helpers/constants';
import { translate } from '../../../helpers/l10n';
import { Dict } from '../../../types/types';
-import { formatFacetStat, Query } from '../utils';
+import { Query, formatFacetStat } from '../utils';
interface Props {
fetching: boolean;
};
render() {
- const { types, stats = {} } = this.props;
+ const { fetching, open, types, stats = {} } = this.props;
const values = types.map((type) => translate('issue.type', type));
return (
<FacetBox property={this.property}>
<FacetHeader
- fetching={this.props.fetching}
+ fetching={fetching}
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
onClick={this.handleHeaderClick}
- open={this.props.open}
+ open={open}
values={values}
/>
- {this.props.open && (
+ {open && (
<>
- <FacetItemsList>
+ <FacetItemsList label={this.property}>
{ISSUE_TYPES.filter((t) => t !== 'SECURITY_HOTSPOT').map(this.renderItem)}
</FacetItemsList>
<MultipleSelectionHint options={Object.keys(stats).length} values={types.length} />
dateInputYearSelect: byRole('combobox', { name: 'Year:' }),
clearAllFilters: byRole('button', { name: 'clear_all_filters' }),
+
+ ruleFacetList: byRole('list', { name: 'rules' }),
+ ruleFacetSearch: byRole('searchbox', { name: 'search.search_for_rules' }),
};
export async function waitOnDataLoaded() {
}
export default function FacetBox(props: FacetBoxProps) {
+ const { children, className, property } = props;
+
return (
- <div
- className={classNames('search-navigator-facet-box', props.className)}
- data-property={props.property}
- >
- {props.children}
+ <div className={classNames('search-navigator-facet-box', className)} data-property={property}>
+ {children}
</div>
);
}
export interface FacetItemsListProps {
children?: React.ReactNode;
- title?: string;
+ label: string;
}
-export default function FacetItemsList({ children, title }: FacetItemsListProps) {
+export default function FacetItemsList({ children, label }: FacetItemsListProps) {
return (
- <div className="search-navigator-facet-list" role="list">
- {title && (
- <div className="search-navigator-facet-list-title" role="presentation">
- {title}
- </div>
- )}
+ <div className="search-navigator-facet-list" role="list" aria-label={label}>
{children}
</div>
);
};
renderList() {
- const { stats, showMoreAriaLabel, showLessAriaLabel } = this.props;
+ const {
+ maxInitialItems,
+ maxItems,
+ property,
+ stats,
+ showMoreAriaLabel,
+ showLessAriaLabel,
+ values,
+ } = this.props;
if (!stats) {
return null;
const limitedList = this.state.showFullList
? sortedItems
- : sortedItems.slice(0, this.props.maxInitialItems);
+ : sortedItems.slice(0, maxInitialItems);
// make sure all selected items are displayed
const selectedBelowLimit = this.state.showFullList
? []
- : sortedItems
- .slice(this.props.maxInitialItems)
- .filter((item) => this.props.values.includes(item));
+ : sortedItems.slice(maxInitialItems).filter((item) => values.includes(item));
- const mightHaveMoreResults = sortedItems.length >= this.props.maxItems;
+ const mightHaveMoreResults = sortedItems.length >= maxItems;
return (
<>
- <FacetItemsList>
+ <FacetItemsList label={property}>
{limitedList.map((item) => (
<FacetItem
active={this.props.values.includes(item)}
{selectedBelowLimit.length > 0 && (
<>
<div className="note spacer-bottom text-center">⋯</div>
- <FacetItemsList>
+ <FacetItemsList label={property}>
{selectedBelowLimit.map((item) => (
<FacetItem
active={true}
}
renderSearchResults() {
- const { showMoreAriaLabel } = this.props;
+ const { property, showMoreAriaLabel } = this.props;
const { searching, searchMaxResults, searchResults, searchPaging } = this.state;
if (!searching && (!searchResults || !searchResults.length)) {
return (
<>
- <FacetItemsList>
+ <FacetItemsList label={property}>
{searchResults.map((result) => this.renderSearchResult(result))}
</FacetItemsList>
{searchMaxResults && (
}
render() {
- const { disabled, stats = {} } = this.props;
+ const {
+ className,
+ disabled,
+ disabledHelper,
+ facetHeader,
+ fetching,
+ open,
+ property,
+ stats = {},
+ values: propsValues,
+ } = this.props;
const { query, searching, searchResults } = this.state;
- const values = this.props.values.map((item) => this.props.getFacetItemText(item));
+ const values = propsValues.map((item) => this.props.getFacetItemText(item));
const loadingResults =
query !== '' && searching && (searchResults === undefined || searchResults.length === 0);
const showList = !query || loadingResults;
return (
<FacetBox
- className={classNames(this.props.className, {
+ className={classNames(className, {
'search-navigator-facet-box-forbidden': disabled,
})}
- property={this.props.property}
+ property={property}
>
<FacetHeader
- fetching={this.props.fetching}
- name={this.props.facetHeader}
+ fetching={fetching}
+ name={facetHeader}
disabled={disabled}
- disabledHelper={this.props.disabledHelper}
+ disabledHelper={disabledHelper}
onClear={this.handleClear}
onClick={disabled ? undefined : this.handleHeaderClick}
- open={this.props.open && !disabled}
+ open={open && !disabled}
values={values}
/>
- {this.props.open && !disabled && (
+ {open && !disabled && (
<>
{this.renderSearch()}
{showList ? this.renderList() : this.renderSearchResults()}
expect(screen.queryByRole('checkbox', { name: 'foo' })).not.toBeInTheDocument();
});
-it('should correctly render a facet item list with title', () => {
- renderFacet(undefined, { open: true }, { title: 'My list title' });
- expect(screen.getByText('My list title')).toBeInTheDocument();
-});
-
function renderFacet(
facetBoxProps: Partial<FacetBoxProps> = {},
facetHeaderProps: Partial<FacetHeader['props']> = {},
const [open, setOpen] = React.useState(facetHeaderProps.open ?? false);
const [values, setValues] = React.useState(facetHeaderProps.values ?? undefined);
+ const property = 'foo';
+
return (
- <FacetBox property="foo" {...facetBoxProps}>
+ <FacetBox property={property} {...facetBoxProps}>
<FacetHeader
name="foo"
onClick={() => setOpen(!open)}
/>
{open && (
- <FacetItemsList {...facetItemListProps}>
+ <FacetItemsList label={property} {...facetItemListProps}>
<FacetItem
active={true}
name="Foo/Bar"
placeholder="search for foo..."
value=""
/>
- <FacetItemsList>
+ <FacetItemsList
+ label="foo"
+ >
<FacetItem
active={true}
halfWidth={false}
>
⋯
</div>
- <FacetItemsList>
+ <FacetItemsList
+ label="foo"
+ >
<FacetItem
active={true}
halfWidth={false}
placeholder="search for foo..."
value=""
/>
- <FacetItemsList>
+ <FacetItemsList
+ label="foo"
+ >
<FacetItem
active={false}
halfWidth={false}
placeholder="search for foo..."
value="query"
/>
- <FacetItemsList>
+ <FacetItemsList
+ label="foo"
+ >
<FacetItem
active={false}
halfWidth={false}
placeholder="search for foo..."
value="query"
/>
- <FacetItemsList>
+ <FacetItemsList
+ label="foo"
+ >
<FacetItem
active={false}
halfWidth={false}
placeholder="search for foo..."
value=""
/>
- <FacetItemsList>
+ <FacetItemsList
+ label="foo"
+ >
<FacetItem
active={false}
halfWidth={false}