@truedat/core 8.4.2 → 8.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/components/DomainSearchFilter.js +213 -0
- package/src/components/DomainSearchFilterItem.js +129 -0
- package/src/components/SelectedFilters.js +8 -1
- package/src/components/__tests__/DomainSearchFilter.spec.js +187 -0
- package/src/components/__tests__/DomainSearchFilterItem.spec.js +106 -0
- package/src/components/__tests__/__snapshots__/DomainSearchFilter.spec.js.snap +62 -0
- package/src/components/__tests__/__snapshots__/DomainSearchFilterItem.spec.js.snap +130 -0
- package/src/components/index.js +2 -0
- package/src/search/SearchContext.js +13 -2
- package/src/search/SearchSelectedFilters.js +17 -1
- package/src/search/__tests__/SearchContext.spec.js +350 -1
- package/src/search/__tests__/SearchSelectedFilters.spec.js +269 -0
- package/src/selectors/__tests__/makeSearchQuerySelector.spec.js +40 -0
- package/src/selectors/makeSearchQuerySelector.js +11 -2
- package/src/styles/DomainSearchFilter.less +28 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/core",
|
|
3
|
-
"version": "8.4.
|
|
3
|
+
"version": "8.4.4",
|
|
4
4
|
"description": "Truedat Web Core",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@testing-library/jest-dom": "^6.6.3",
|
|
52
52
|
"@testing-library/react": "^16.3.0",
|
|
53
53
|
"@testing-library/user-event": "^14.6.1",
|
|
54
|
-
"@truedat/test": "8.4.
|
|
54
|
+
"@truedat/test": "8.4.4",
|
|
55
55
|
"identity-obj-proxy": "^3.0.0",
|
|
56
56
|
"jest": "^29.7.0",
|
|
57
57
|
"redux-saga-test-plan": "^4.0.6"
|
|
@@ -97,5 +97,5 @@
|
|
|
97
97
|
"swr": "^2.3.3",
|
|
98
98
|
"turndown": "^7.2.2"
|
|
99
99
|
},
|
|
100
|
-
"gitHead": "
|
|
100
|
+
"gitHead": "0d8d2a2f4526c425fcef656a6004484cbd755774"
|
|
101
101
|
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
import PropTypes from "prop-types";
|
|
4
|
+
import { FormattedMessage } from "react-intl";
|
|
5
|
+
import {
|
|
6
|
+
Label,
|
|
7
|
+
Icon,
|
|
8
|
+
Input,
|
|
9
|
+
Dropdown,
|
|
10
|
+
Dimmer,
|
|
11
|
+
Loader,
|
|
12
|
+
} from "semantic-ui-react";
|
|
13
|
+
import { lowerDeburr } from "../services/sort";
|
|
14
|
+
import DomainSearchFilterItem from "./DomainSearchFilterItem";
|
|
15
|
+
|
|
16
|
+
export const DomainSearchFilter = ({
|
|
17
|
+
name,
|
|
18
|
+
activeValues,
|
|
19
|
+
closeFilter,
|
|
20
|
+
filter,
|
|
21
|
+
loading,
|
|
22
|
+
openFilter,
|
|
23
|
+
options,
|
|
24
|
+
removeFilter,
|
|
25
|
+
toggleFilterValue,
|
|
26
|
+
}) => {
|
|
27
|
+
const [selected, setSelected] = useState();
|
|
28
|
+
const [query, setQuery] = useState();
|
|
29
|
+
const [open, setOpen] = useState([]);
|
|
30
|
+
const [displayed, setDisplayed] = useState([]);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const activeOptions = _.filter((option) =>
|
|
34
|
+
_.includes(option.id)(activeValues)
|
|
35
|
+
)(options);
|
|
36
|
+
|
|
37
|
+
_.flow(_.map("id"), _.uniq, setSelected)(activeOptions);
|
|
38
|
+
if (_.isEmpty(open) && _.isEmpty(displayed)) {
|
|
39
|
+
const withAncestors = _.flow(
|
|
40
|
+
_.reduce(
|
|
41
|
+
(acc, option) => [...acc, ..._.map("id")(option.ancestors)],
|
|
42
|
+
[]
|
|
43
|
+
),
|
|
44
|
+
_.uniq
|
|
45
|
+
)(activeOptions);
|
|
46
|
+
setOpen(withAncestors);
|
|
47
|
+
setDisplayed([..._.map("id")(activeOptions), ...withAncestors]);
|
|
48
|
+
}
|
|
49
|
+
}, [activeValues, options]);
|
|
50
|
+
|
|
51
|
+
const handleOpen = (selection) => {
|
|
52
|
+
const option = _.find({ id: selection })(options);
|
|
53
|
+
const isOpen = _.contains(selection)(open);
|
|
54
|
+
const children = _.map("id")(option.children);
|
|
55
|
+
const descendents = _.map("id")(option.descendents);
|
|
56
|
+
|
|
57
|
+
if (isOpen) {
|
|
58
|
+
setOpen(_.without([selection, ...descendents])(open));
|
|
59
|
+
setDisplayed(_.without(descendents)(displayed));
|
|
60
|
+
} else {
|
|
61
|
+
setOpen(_.union([selection])(open));
|
|
62
|
+
setDisplayed(_.union(children)(displayed));
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handleClick = (e, selection) => {
|
|
67
|
+
const option = _.find({ id: selection })(options);
|
|
68
|
+
const descendentIds = _.map("id")(option.descendents);
|
|
69
|
+
const treeIds = [selection, ...descendentIds];
|
|
70
|
+
const value = _.includes(selection)(activeValues)
|
|
71
|
+
? _.without(treeIds)(activeValues)
|
|
72
|
+
: _.union(treeIds)(activeValues);
|
|
73
|
+
toggleFilterValue({ filter, value });
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleToggleChildren = (_e, selection) => {
|
|
77
|
+
const option = _.find({ id: selection })(options);
|
|
78
|
+
if (!option) return;
|
|
79
|
+
const descendentIds = _.map("id")(option.descendents);
|
|
80
|
+
if (_.isEmpty(descendentIds)) return;
|
|
81
|
+
const allSelected = _.every((id) => _.includes(id)(activeValues))(
|
|
82
|
+
descendentIds
|
|
83
|
+
);
|
|
84
|
+
const value = allSelected
|
|
85
|
+
? _.without(descendentIds)(activeValues)
|
|
86
|
+
: _.union(descendentIds)(activeValues);
|
|
87
|
+
toggleFilterValue({ filter, value });
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const displayAll = () => {
|
|
91
|
+
const ids = _.map("id")(options);
|
|
92
|
+
setOpen(ids);
|
|
93
|
+
setDisplayed(ids);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const handleSearch = (e, { value }) => {
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
setQuery(lowerDeburr(value));
|
|
99
|
+
if (!_.isEmpty(value)) {
|
|
100
|
+
displayAll();
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const match = (value, currentQuery) =>
|
|
104
|
+
_.contains(currentQuery)(lowerDeburr(value));
|
|
105
|
+
const filterSearch = (all) => {
|
|
106
|
+
if (query) {
|
|
107
|
+
return _.filter(
|
|
108
|
+
(domain) =>
|
|
109
|
+
match(domain.name, query) ||
|
|
110
|
+
_.some((descendent) => match(descendent.name, query))(
|
|
111
|
+
domain.descendents
|
|
112
|
+
)
|
|
113
|
+
)(all);
|
|
114
|
+
}
|
|
115
|
+
return all;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const filterDisplayed = (all) =>
|
|
119
|
+
_.filter((domain) => domain.level == 0 || _.contains(domain.id)(displayed))(
|
|
120
|
+
all
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const filteredOptions = _.flow(filterSearch, filterDisplayed)(options);
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<Dropdown
|
|
127
|
+
name={name || "domainSearchFilter"}
|
|
128
|
+
className="domain-search-filter"
|
|
129
|
+
item
|
|
130
|
+
floating
|
|
131
|
+
icon={false}
|
|
132
|
+
upward={false}
|
|
133
|
+
onOpen={() => openFilter({ filter })}
|
|
134
|
+
onClose={() => closeFilter({ filter })}
|
|
135
|
+
trigger={
|
|
136
|
+
<Label key={filter}>
|
|
137
|
+
<FormattedMessage id={`filters.${filter}`} defaultMessage={filter} />
|
|
138
|
+
<Icon
|
|
139
|
+
name="delete"
|
|
140
|
+
onClick={(e) => {
|
|
141
|
+
e.preventDefault();
|
|
142
|
+
e.stopPropagation();
|
|
143
|
+
removeFilter({ filter });
|
|
144
|
+
}}
|
|
145
|
+
/>
|
|
146
|
+
</Label>
|
|
147
|
+
}
|
|
148
|
+
open={!_.isEmpty(options)}
|
|
149
|
+
>
|
|
150
|
+
<Dimmer.Dimmable dimmed={loading} as={Dropdown.Menu}>
|
|
151
|
+
<>
|
|
152
|
+
<Input
|
|
153
|
+
icon="search"
|
|
154
|
+
iconPosition="left"
|
|
155
|
+
className="search"
|
|
156
|
+
onKeyDown={(e) => {
|
|
157
|
+
if (e.key === " ") {
|
|
158
|
+
e.stopPropagation();
|
|
159
|
+
}
|
|
160
|
+
}}
|
|
161
|
+
onChange={handleSearch}
|
|
162
|
+
onClick={(e) => {
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
e.stopPropagation();
|
|
165
|
+
}}
|
|
166
|
+
/>
|
|
167
|
+
<Dropdown.Menu scrolling>
|
|
168
|
+
{_.map.convert({ cap: false })((option, i) => (
|
|
169
|
+
<DomainSearchFilterItem
|
|
170
|
+
key={i}
|
|
171
|
+
onOpen={handleOpen}
|
|
172
|
+
onClick={handleClick}
|
|
173
|
+
open={_.contains(option.id)(open)}
|
|
174
|
+
canOpen={_.negate(_.isEmpty)(option.children)}
|
|
175
|
+
selected={_.contains(option.id)(selected)}
|
|
176
|
+
onToggleChildren={handleToggleChildren}
|
|
177
|
+
toggleChildrenOn={_.every((id) => _.includes(id)(activeValues))(
|
|
178
|
+
_.map("id")(option.descendents)
|
|
179
|
+
)}
|
|
180
|
+
partialSelected={
|
|
181
|
+
!_.contains(option.id)(selected) &&
|
|
182
|
+
_.some((id) => _.includes(id)(selected))(
|
|
183
|
+
_.map("id")(option.descendents)
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
{...option}
|
|
187
|
+
/>
|
|
188
|
+
))(filteredOptions)}
|
|
189
|
+
</Dropdown.Menu>
|
|
190
|
+
</>
|
|
191
|
+
{loading && (
|
|
192
|
+
<Dimmer active inverted>
|
|
193
|
+
<Loader size="tiny" />
|
|
194
|
+
</Dimmer>
|
|
195
|
+
)}
|
|
196
|
+
</Dimmer.Dimmable>
|
|
197
|
+
</Dropdown>
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
DomainSearchFilter.propTypes = {
|
|
202
|
+
name: PropTypes.string,
|
|
203
|
+
activeValues: PropTypes.array,
|
|
204
|
+
closeFilter: PropTypes.func,
|
|
205
|
+
filter: PropTypes.string,
|
|
206
|
+
loading: PropTypes.bool,
|
|
207
|
+
openFilter: PropTypes.func,
|
|
208
|
+
options: PropTypes.array,
|
|
209
|
+
removeFilter: PropTypes.func,
|
|
210
|
+
toggleFilterValue: PropTypes.func,
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export default DomainSearchFilter;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import PropTypes from "prop-types";
|
|
2
|
+
import { useIntl } from "react-intl";
|
|
3
|
+
import { Icon, Dropdown } from "semantic-ui-react";
|
|
4
|
+
|
|
5
|
+
const ToggleChildrenButton = ({ toggleChildrenOn, onClick }) => {
|
|
6
|
+
const { formatMessage } = useIntl();
|
|
7
|
+
const state = toggleChildrenOn ? "on" : "off";
|
|
8
|
+
|
|
9
|
+
const icon = formatMessage({
|
|
10
|
+
id: `domain.selector.toggleChildren.${state}.icon`,
|
|
11
|
+
defaultMessage: toggleChildrenOn ? "toggle on" : "toggle off",
|
|
12
|
+
}).replace("none", "");
|
|
13
|
+
|
|
14
|
+
const label = formatMessage({
|
|
15
|
+
id: `domain.selector.toggleChildren.${state}.label`,
|
|
16
|
+
defaultMessage: "none",
|
|
17
|
+
}).replace("none", "");
|
|
18
|
+
|
|
19
|
+
const hoverText = formatMessage({
|
|
20
|
+
id: `domain.selector.toggleChildren.${state}.hover`,
|
|
21
|
+
defaultMessage: "none",
|
|
22
|
+
}).replace("none", "");
|
|
23
|
+
|
|
24
|
+
if (!icon && !label) return null;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<button type="button" className="toggle-children" onClick={onClick}>
|
|
28
|
+
<span title={hoverText} className="toggle-children-content">
|
|
29
|
+
{icon && <Icon name={icon} />}
|
|
30
|
+
{label && <span>{label}</span>}
|
|
31
|
+
</span>
|
|
32
|
+
</button>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
ToggleChildrenButton.propTypes = {
|
|
37
|
+
toggleChildrenOn: PropTypes.bool,
|
|
38
|
+
onClick: PropTypes.func,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const DomainSearchFilterItem = ({
|
|
42
|
+
id,
|
|
43
|
+
canOpen,
|
|
44
|
+
onOpen,
|
|
45
|
+
onClick,
|
|
46
|
+
onToggleChildren,
|
|
47
|
+
selected,
|
|
48
|
+
toggleChildrenOn = false,
|
|
49
|
+
partialSelected = false,
|
|
50
|
+
open,
|
|
51
|
+
name,
|
|
52
|
+
level,
|
|
53
|
+
disabled = false,
|
|
54
|
+
className,
|
|
55
|
+
}) => {
|
|
56
|
+
const { formatMessage } = useIntl();
|
|
57
|
+
|
|
58
|
+
const handleOpen = (e) => {
|
|
59
|
+
e && e.preventDefault();
|
|
60
|
+
e && e.stopPropagation();
|
|
61
|
+
onOpen(id);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const handleClick = (e) => {
|
|
65
|
+
e && e.preventDefault();
|
|
66
|
+
e && e.stopPropagation();
|
|
67
|
+
onClick(e, id);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const handleToggleChildren = (e) => {
|
|
71
|
+
e && e.preventDefault();
|
|
72
|
+
e && e.stopPropagation();
|
|
73
|
+
onToggleChildren && onToggleChildren(e, id);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const itemStyle = {
|
|
77
|
+
marginLeft: `${20 * level}px`,
|
|
78
|
+
paddingLeft: !canOpen ? "25px" : "5px",
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Dropdown.Item onClick={handleClick} className={className}>
|
|
83
|
+
<div className="item-content" style={itemStyle}>
|
|
84
|
+
{canOpen ? (
|
|
85
|
+
<Icon
|
|
86
|
+
name={open ? "chevron down" : "chevron right"}
|
|
87
|
+
onClick={handleOpen}
|
|
88
|
+
/>
|
|
89
|
+
) : null}
|
|
90
|
+
<Icon
|
|
91
|
+
name={
|
|
92
|
+
selected
|
|
93
|
+
? "check square outline"
|
|
94
|
+
: partialSelected
|
|
95
|
+
? "minus square outline"
|
|
96
|
+
: "square outline"
|
|
97
|
+
}
|
|
98
|
+
/>
|
|
99
|
+
<span style={{ opacity: disabled ? 0.45 : 1 }} title={name}>
|
|
100
|
+
{name}
|
|
101
|
+
</span>
|
|
102
|
+
{canOpen && onToggleChildren ? (
|
|
103
|
+
<ToggleChildrenButton
|
|
104
|
+
toggleChildrenOn={toggleChildrenOn}
|
|
105
|
+
onClick={handleToggleChildren}
|
|
106
|
+
/>
|
|
107
|
+
) : null}
|
|
108
|
+
</div>
|
|
109
|
+
</Dropdown.Item>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
DomainSearchFilterItem.propTypes = {
|
|
114
|
+
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
115
|
+
canOpen: PropTypes.bool,
|
|
116
|
+
className: PropTypes.string,
|
|
117
|
+
onOpen: PropTypes.func,
|
|
118
|
+
onClick: PropTypes.func,
|
|
119
|
+
onToggleChildren: PropTypes.func,
|
|
120
|
+
selected: PropTypes.bool,
|
|
121
|
+
toggleChildrenOn: PropTypes.bool,
|
|
122
|
+
partialSelected: PropTypes.bool,
|
|
123
|
+
open: PropTypes.bool,
|
|
124
|
+
name: PropTypes.string,
|
|
125
|
+
level: PropTypes.number,
|
|
126
|
+
disabled: PropTypes.bool,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export default DomainSearchFilterItem;
|
|
@@ -6,6 +6,7 @@ import FilterDropdown from "./FilterDropdown";
|
|
|
6
6
|
import FilterMultilevelDropdown from "./FilterMultilevelDropdown";
|
|
7
7
|
import HierarchyFilterDropdown from "./HierarchyFilterDropdown";
|
|
8
8
|
import SearchFilterDropdown from "./SearchFilterDropdown";
|
|
9
|
+
import DomainSearchFilter from "./DomainSearchFilter";
|
|
9
10
|
import ModalSaveFilter from "./ModalSaveFilter";
|
|
10
11
|
import UserFilters from "./UserFilters";
|
|
11
12
|
|
|
@@ -30,6 +31,7 @@ export const SelectedFilters = ({
|
|
|
30
31
|
userFilterScope,
|
|
31
32
|
searchFiltersPropsMapping,
|
|
32
33
|
searchFilterDispacher,
|
|
34
|
+
useDomainSearchFilter,
|
|
33
35
|
}) => {
|
|
34
36
|
const { pathname } = useLocation();
|
|
35
37
|
const taxonomyPathRegex = /^\/domains\/(\d+)\/\w+$/;
|
|
@@ -77,7 +79,11 @@ export const SelectedFilters = ({
|
|
|
77
79
|
|
|
78
80
|
switch (filterType) {
|
|
79
81
|
case "domain":
|
|
80
|
-
return
|
|
82
|
+
return useDomainSearchFilter ? (
|
|
83
|
+
<DomainSearchFilter {...props} />
|
|
84
|
+
) : (
|
|
85
|
+
<FilterMultilevelDropdown {...props} />
|
|
86
|
+
);
|
|
81
87
|
case "hierarchy":
|
|
82
88
|
return <HierarchyFilterDropdown {...props} />;
|
|
83
89
|
case "search":
|
|
@@ -131,6 +137,7 @@ SelectedFilters.propTypes = {
|
|
|
131
137
|
userFilterScope: PropTypes.string,
|
|
132
138
|
searchFiltersPropsMapping: PropTypes.object,
|
|
133
139
|
searchFilterDispacher: PropTypes.func,
|
|
140
|
+
useDomainSearchFilter: PropTypes.bool,
|
|
134
141
|
};
|
|
135
142
|
|
|
136
143
|
export default SelectedFilters;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { fireEvent } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
import { render } from "@truedat/test/render";
|
|
4
|
+
import { DomainSearchFilter } from "../DomainSearchFilter";
|
|
5
|
+
|
|
6
|
+
jest.mock("../DomainSearchFilterItem", () => {
|
|
7
|
+
const MockDomainSearchFilterItem = ({
|
|
8
|
+
id,
|
|
9
|
+
name,
|
|
10
|
+
onOpen,
|
|
11
|
+
onClick,
|
|
12
|
+
onToggleChildren,
|
|
13
|
+
}) => (
|
|
14
|
+
<div>
|
|
15
|
+
<span>{name}</span>
|
|
16
|
+
<button type="button" onClick={() => onOpen(id)}>
|
|
17
|
+
{`open-${id}`}
|
|
18
|
+
</button>
|
|
19
|
+
<button type="button" onClick={(e) => onClick(e, id)}>
|
|
20
|
+
{`select-${id}`}
|
|
21
|
+
</button>
|
|
22
|
+
<button type="button" onClick={(e) => onToggleChildren(e, id)}>
|
|
23
|
+
{`toggle-${id}`}
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
__esModule: true,
|
|
30
|
+
DomainSearchFilterItem: MockDomainSearchFilterItem,
|
|
31
|
+
default: MockDomainSearchFilterItem,
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("<DomainSearchFilter />", () => {
|
|
36
|
+
const getProps = (overrides = {}) => ({
|
|
37
|
+
filter: "taxonomy",
|
|
38
|
+
activeValues: [],
|
|
39
|
+
closeFilter: jest.fn(),
|
|
40
|
+
openFilter: jest.fn(),
|
|
41
|
+
removeFilter: jest.fn(),
|
|
42
|
+
toggleFilterValue: jest.fn(),
|
|
43
|
+
loading: false,
|
|
44
|
+
options: [
|
|
45
|
+
{
|
|
46
|
+
id: 1,
|
|
47
|
+
name: "Domain 1",
|
|
48
|
+
level: 0,
|
|
49
|
+
ancestors: [],
|
|
50
|
+
children: [{ id: 2, name: "Domain 2", level: 1 }],
|
|
51
|
+
descendents: [{ id: 2, name: "Domain 2", level: 1 }],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 2,
|
|
55
|
+
name: "Domain 2",
|
|
56
|
+
level: 1,
|
|
57
|
+
ancestors: [{ id: 1, name: "Domain 1" }],
|
|
58
|
+
children: [],
|
|
59
|
+
descendents: [],
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
...overrides,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("matches the latest snapshot", () => {
|
|
66
|
+
const rendered = render(<DomainSearchFilter {...getProps()} />);
|
|
67
|
+
expect(rendered.container).toMatchSnapshot();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("calls removeFilter when delete is clicked", async () => {
|
|
71
|
+
const props = getProps();
|
|
72
|
+
const user = userEvent.setup({ delay: null });
|
|
73
|
+
const rendered = render(<DomainSearchFilter {...props} />);
|
|
74
|
+
|
|
75
|
+
await user.click(rendered.container.querySelector(".delete.icon"));
|
|
76
|
+
|
|
77
|
+
expect(props.removeFilter).toHaveBeenCalledWith({ filter: "taxonomy" });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("calls toggleFilterValue adding selected option and descendents", async () => {
|
|
81
|
+
const props = getProps();
|
|
82
|
+
const user = userEvent.setup({ delay: null });
|
|
83
|
+
const rendered = render(<DomainSearchFilter {...props} />);
|
|
84
|
+
|
|
85
|
+
await user.click(rendered.getByText(/select-1/i));
|
|
86
|
+
|
|
87
|
+
expect(props.toggleFilterValue).toHaveBeenCalledWith({
|
|
88
|
+
filter: "taxonomy",
|
|
89
|
+
value: [1, 2],
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("calls toggleFilterValue removing selected option and descendents", async () => {
|
|
94
|
+
const props = getProps({ activeValues: [1, 2] });
|
|
95
|
+
const user = userEvent.setup({ delay: null });
|
|
96
|
+
const rendered = render(<DomainSearchFilter {...props} />);
|
|
97
|
+
|
|
98
|
+
await user.click(rendered.getByText(/select-1/i));
|
|
99
|
+
|
|
100
|
+
expect(props.toggleFilterValue).toHaveBeenCalledWith({
|
|
101
|
+
filter: "taxonomy",
|
|
102
|
+
value: [],
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("calls toggleFilterValue adding descendents from toggle children", async () => {
|
|
107
|
+
const props = getProps();
|
|
108
|
+
const user = userEvent.setup({ delay: null });
|
|
109
|
+
const rendered = render(<DomainSearchFilter {...props} />);
|
|
110
|
+
|
|
111
|
+
await user.click(rendered.getByText(/toggle-1/i));
|
|
112
|
+
|
|
113
|
+
expect(props.toggleFilterValue).toHaveBeenCalledWith({
|
|
114
|
+
filter: "taxonomy",
|
|
115
|
+
value: [2],
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("calls toggleFilterValue removing descendents from toggle children", async () => {
|
|
120
|
+
const props = getProps({ activeValues: [2] });
|
|
121
|
+
const user = userEvent.setup({ delay: null });
|
|
122
|
+
const rendered = render(<DomainSearchFilter {...props} />);
|
|
123
|
+
|
|
124
|
+
await user.click(rendered.getByText(/toggle-1/i));
|
|
125
|
+
|
|
126
|
+
expect(props.toggleFilterValue).toHaveBeenCalledWith({
|
|
127
|
+
filter: "taxonomy",
|
|
128
|
+
value: [],
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("shows child domains when searching by descendent name", async () => {
|
|
133
|
+
const props = getProps();
|
|
134
|
+
const user = userEvent.setup({ delay: null });
|
|
135
|
+
const rendered = render(<DomainSearchFilter {...props} />);
|
|
136
|
+
const searchInput = rendered.container.querySelector(".search input");
|
|
137
|
+
|
|
138
|
+
expect(rendered.queryByText(/domain 2/i)).not.toBeInTheDocument();
|
|
139
|
+
|
|
140
|
+
await user.type(searchInput, "domain 2");
|
|
141
|
+
|
|
142
|
+
expect(rendered.getByText(/domain 2/i)).toBeInTheDocument();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("does not expand all domains when search value is empty", () => {
|
|
146
|
+
const props = getProps();
|
|
147
|
+
const rendered = render(<DomainSearchFilter {...props} />);
|
|
148
|
+
const searchInput = rendered.container.querySelector(".search input");
|
|
149
|
+
|
|
150
|
+
fireEvent.change(searchInput, { target: { value: "" } });
|
|
151
|
+
|
|
152
|
+
expect(rendered.queryByText(/domain 2/i)).not.toBeInTheDocument();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("stops propagation on space keydown in search input", async () => {
|
|
156
|
+
const props = getProps();
|
|
157
|
+
const user = userEvent.setup({ delay: null });
|
|
158
|
+
const onKeyDown = jest.fn();
|
|
159
|
+
const rendered = render(
|
|
160
|
+
<div onKeyDown={onKeyDown}>
|
|
161
|
+
<DomainSearchFilter {...props} />
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
const searchInput = rendered.container.querySelector(".search input");
|
|
165
|
+
|
|
166
|
+
await user.click(searchInput);
|
|
167
|
+
await user.keyboard(" ");
|
|
168
|
+
|
|
169
|
+
expect(onKeyDown).not.toHaveBeenCalled();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("prevents default and stops propagation on search input click", async () => {
|
|
173
|
+
const props = getProps();
|
|
174
|
+
const user = userEvent.setup({ delay: null });
|
|
175
|
+
const onClick = jest.fn();
|
|
176
|
+
const rendered = render(
|
|
177
|
+
<div onClick={onClick}>
|
|
178
|
+
<DomainSearchFilter {...props} />
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
const searchInput = rendered.container.querySelector(".search input");
|
|
182
|
+
|
|
183
|
+
await user.click(searchInput);
|
|
184
|
+
|
|
185
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import userEvent from "@testing-library/user-event";
|
|
2
|
+
import { render } from "@truedat/test/render";
|
|
3
|
+
import { DomainSearchFilterItem } from "../DomainSearchFilterItem";
|
|
4
|
+
|
|
5
|
+
describe("<DomainSearchFilterItem />", () => {
|
|
6
|
+
const getProps = (overrides = {}) => ({
|
|
7
|
+
id: 1,
|
|
8
|
+
canOpen: true,
|
|
9
|
+
onOpen: jest.fn(),
|
|
10
|
+
onClick: jest.fn(),
|
|
11
|
+
onToggleChildren: jest.fn(),
|
|
12
|
+
selected: false,
|
|
13
|
+
toggleChildrenOn: false,
|
|
14
|
+
partialSelected: false,
|
|
15
|
+
open: false,
|
|
16
|
+
name: "Domain 1",
|
|
17
|
+
level: 0,
|
|
18
|
+
disabled: false,
|
|
19
|
+
...overrides,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("matches the latest snapshot", () => {
|
|
23
|
+
const rendered = render(<DomainSearchFilterItem {...getProps()} />);
|
|
24
|
+
expect(rendered.container).toMatchSnapshot();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("matches snapshot when selected is true", () => {
|
|
28
|
+
const rendered = render(
|
|
29
|
+
<DomainSearchFilterItem {...getProps()} selected={true} />
|
|
30
|
+
);
|
|
31
|
+
expect(rendered.container).toMatchSnapshot();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("matches snapshot when partial selected is true", () => {
|
|
35
|
+
const rendered = render(
|
|
36
|
+
<DomainSearchFilterItem
|
|
37
|
+
{...getProps()}
|
|
38
|
+
selected={false}
|
|
39
|
+
partialSelected={true}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
expect(rendered.container).toMatchSnapshot();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("calls onOpen when chevron icon is clicked", async () => {
|
|
46
|
+
const props = getProps({ canOpen: true, open: false });
|
|
47
|
+
const user = userEvent.setup({ delay: null });
|
|
48
|
+
const rendered = render(<DomainSearchFilterItem {...props} />);
|
|
49
|
+
|
|
50
|
+
await user.click(rendered.container.querySelector(".chevron.right.icon"));
|
|
51
|
+
|
|
52
|
+
expect(props.onOpen).toHaveBeenCalledWith(1);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("calls onClick with event and id when item is clicked", async () => {
|
|
56
|
+
const props = getProps();
|
|
57
|
+
const user = userEvent.setup({ delay: null });
|
|
58
|
+
const rendered = render(<DomainSearchFilterItem {...props} />);
|
|
59
|
+
|
|
60
|
+
await user.click(rendered.getByText(/domain 1/i));
|
|
61
|
+
|
|
62
|
+
expect(props.onClick).toHaveBeenCalledTimes(1);
|
|
63
|
+
expect(props.onClick.mock.calls[0][1]).toBe(1);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("calls onToggleChildren when toggle icon is clicked", async () => {
|
|
67
|
+
const props = getProps({ canOpen: true, toggleChildrenOn: false });
|
|
68
|
+
const user = userEvent.setup({ delay: null });
|
|
69
|
+
const rendered = render(<DomainSearchFilterItem {...props} />);
|
|
70
|
+
|
|
71
|
+
await user.click(rendered.container.querySelector(".toggle-children"));
|
|
72
|
+
|
|
73
|
+
expect(props.onToggleChildren).toHaveBeenCalledTimes(1);
|
|
74
|
+
expect(props.onToggleChildren.mock.calls[0][1]).toBe(1);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("does not render chevron and toggle icons when canOpen is false", () => {
|
|
78
|
+
const rendered = render(
|
|
79
|
+
<DomainSearchFilterItem {...getProps({ canOpen: false })} />
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
expect(
|
|
83
|
+
rendered.container.querySelector(".chevron.right.icon")
|
|
84
|
+
).not.toBeInTheDocument();
|
|
85
|
+
expect(
|
|
86
|
+
rendered.container.querySelector(".toggle-children")
|
|
87
|
+
).not.toBeInTheDocument();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("does not render the toggle button when icon and label i18n are none", () => {
|
|
91
|
+
const messages = {
|
|
92
|
+
en: {
|
|
93
|
+
"domain.selector.toggleChildren.off.icon": "none",
|
|
94
|
+
"domain.selector.toggleChildren.off.label": "none",
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
const rendered = render(
|
|
98
|
+
<DomainSearchFilterItem {...getProps({ canOpen: true })} />,
|
|
99
|
+
{ messages }
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(
|
|
103
|
+
rendered.container.querySelector(".toggle-children")
|
|
104
|
+
).not.toBeInTheDocument();
|
|
105
|
+
});
|
|
106
|
+
});
|