@truedat/core 6.5.2 → 6.5.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/search/FilterDropdown.js +14 -15
- package/src/search/FilterQueryDropdown.js +140 -0
- package/src/search/SearchContext.js +30 -18
- package/src/search/SearchSelectedFilters.js +5 -0
- package/src/search/__tests__/FilterDropdown.spec.js +99 -0
- package/src/search/__tests__/FilterItem.spec.js +46 -0
- package/src/search/__tests__/FilterQueryDropdown.spec.js +200 -0
- package/src/search/__tests__/SearchWidget.spec.js +52 -0
- package/src/search/__tests__/__snapshots__/FilterDropdown.spec.js.snap +48 -0
- package/src/search/__tests__/__snapshots__/FilterItem.spec.js.snap +17 -0
- package/src/search/__tests__/__snapshots__/FilterQueryDropdown.spec.js.snap +151 -0
- package/src/search/__tests__/__snapshots__/SearchWidget.spec.js.snap +54 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/core",
|
|
3
|
-
"version": "6.5.
|
|
3
|
+
"version": "6.5.4",
|
|
4
4
|
"description": "Truedat Web Core",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"jsnext:main": "src/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@testing-library/react": "^12.0.0",
|
|
37
37
|
"@testing-library/react-hooks": "^8.0.1",
|
|
38
38
|
"@testing-library/user-event": "^13.2.1",
|
|
39
|
-
"@truedat/test": "6.5.
|
|
39
|
+
"@truedat/test": "6.5.4",
|
|
40
40
|
"babel-jest": "^28.1.0",
|
|
41
41
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
|
42
42
|
"babel-plugin-lodash": "^3.3.4",
|
|
@@ -118,5 +118,5 @@
|
|
|
118
118
|
"react-dom": ">= 16.8.6 < 17",
|
|
119
119
|
"semantic-ui-react": ">= 2.0.3 < 2.2"
|
|
120
120
|
},
|
|
121
|
-
"gitHead": "
|
|
121
|
+
"gitHead": "ca29de8f75c143bb950bb8c9fc8a4d96c97464fb"
|
|
122
122
|
}
|
|
@@ -21,6 +21,7 @@ export default function FilterDropdown() {
|
|
|
21
21
|
toggleFilterValue,
|
|
22
22
|
} = useSearchContext();
|
|
23
23
|
|
|
24
|
+
const sortedOptions = _.sortBy(accentInsensitivePathOrder("text"))(options);
|
|
24
25
|
return (
|
|
25
26
|
<Dropdown
|
|
26
27
|
item
|
|
@@ -49,21 +50,19 @@ export default function FilterDropdown() {
|
|
|
49
50
|
open={!_.isEmpty(options)}
|
|
50
51
|
>
|
|
51
52
|
<Dimmer.Dimmable as={Dropdown.Menu} dimmed={loading}>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
))
|
|
66
|
-
)(options)}
|
|
53
|
+
<>
|
|
54
|
+
{sortedOptions.map((option, i) => (
|
|
55
|
+
<FilterItem
|
|
56
|
+
key={i}
|
|
57
|
+
filter={filter}
|
|
58
|
+
toggleFilterValue={toggleFilterValue}
|
|
59
|
+
option={option}
|
|
60
|
+
active={_.includes(_.prop("value")(option))(
|
|
61
|
+
activeFilterSelectedValues
|
|
62
|
+
)}
|
|
63
|
+
/>
|
|
64
|
+
))}
|
|
65
|
+
</>
|
|
67
66
|
{loading && (
|
|
68
67
|
<Dimmer active inverted>
|
|
69
68
|
<Loader size="tiny" />
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React, { useState, useEffect } from "react";
|
|
3
|
+
import { FormattedMessage } from "react-intl";
|
|
4
|
+
import {
|
|
5
|
+
Label,
|
|
6
|
+
Icon,
|
|
7
|
+
Dropdown,
|
|
8
|
+
Dimmer,
|
|
9
|
+
Loader,
|
|
10
|
+
Input,
|
|
11
|
+
} from "semantic-ui-react";
|
|
12
|
+
import {
|
|
13
|
+
accentInsensitivePathOrder,
|
|
14
|
+
lowerDeburr,
|
|
15
|
+
} from "@truedat/core/services/sort";
|
|
16
|
+
import FilterItem from "./FilterItem";
|
|
17
|
+
|
|
18
|
+
import { useSearchContext } from "./SearchContext";
|
|
19
|
+
|
|
20
|
+
const removePrefix = _.replace(/^.*\./, "");
|
|
21
|
+
|
|
22
|
+
export default function FilterQueryDropdown() {
|
|
23
|
+
const {
|
|
24
|
+
loadingFilters: loading,
|
|
25
|
+
filter,
|
|
26
|
+
options,
|
|
27
|
+
activeFilterSelectedValues,
|
|
28
|
+
openFilter,
|
|
29
|
+
closeFilter,
|
|
30
|
+
removeFilter,
|
|
31
|
+
toggleFilterValue,
|
|
32
|
+
} = useSearchContext();
|
|
33
|
+
|
|
34
|
+
const [selected, setSelected] = useState([]);
|
|
35
|
+
const [localQuery, setLocalQuery] = useState("");
|
|
36
|
+
|
|
37
|
+
const sortedOptions = _.sortBy(accentInsensitivePathOrder("text"))(options);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const activeOptions = _.filter((option) =>
|
|
41
|
+
_.includes(option.value)(activeFilterSelectedValues)
|
|
42
|
+
)(options);
|
|
43
|
+
|
|
44
|
+
_.flow(
|
|
45
|
+
_.reduce((acc, option) => [...acc, option], []),
|
|
46
|
+
_.uniq,
|
|
47
|
+
setSelected
|
|
48
|
+
)(activeOptions);
|
|
49
|
+
}, [activeFilterSelectedValues, options]);
|
|
50
|
+
|
|
51
|
+
const handleSearch = (e, { value }) => {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
setLocalQuery(lowerDeburr(value));
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const match = (name, localQuery) => _.contains(localQuery)(lowerDeburr(name));
|
|
57
|
+
const filteredOptions = _.filter(
|
|
58
|
+
(option) =>
|
|
59
|
+
match(option.text, localQuery) &&
|
|
60
|
+
!_.includes(option.value)(activeFilterSelectedValues)
|
|
61
|
+
)(sortedOptions);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Dropdown
|
|
65
|
+
item
|
|
66
|
+
floating
|
|
67
|
+
scrolling
|
|
68
|
+
icon={false}
|
|
69
|
+
upward={false}
|
|
70
|
+
trigger={
|
|
71
|
+
<Label key={filter}>
|
|
72
|
+
<FormattedMessage
|
|
73
|
+
id={`filters.${filter}`}
|
|
74
|
+
defaultMessage={removePrefix(filter)}
|
|
75
|
+
/>
|
|
76
|
+
<Icon
|
|
77
|
+
name="delete"
|
|
78
|
+
onClick={(e) => {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
e.stopPropagation();
|
|
81
|
+
removeFilter({ filter });
|
|
82
|
+
}}
|
|
83
|
+
/>
|
|
84
|
+
</Label>
|
|
85
|
+
}
|
|
86
|
+
onOpen={() => openFilter({ filter })}
|
|
87
|
+
onClose={() => closeFilter({ filter })}
|
|
88
|
+
open={!_.isEmpty(options)}
|
|
89
|
+
>
|
|
90
|
+
<Dimmer.Dimmable as={Dropdown.Menu} dimmed={loading}>
|
|
91
|
+
<>
|
|
92
|
+
<>
|
|
93
|
+
<Input
|
|
94
|
+
icon="search"
|
|
95
|
+
iconPosition="left"
|
|
96
|
+
className="search"
|
|
97
|
+
onKeyDown={(e) => {
|
|
98
|
+
if (e.key === " ") {
|
|
99
|
+
e.stopPropagation();
|
|
100
|
+
}
|
|
101
|
+
}}
|
|
102
|
+
onChange={handleSearch}
|
|
103
|
+
value={localQuery}
|
|
104
|
+
onClick={(e) => {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
e.stopPropagation();
|
|
107
|
+
}}
|
|
108
|
+
/>
|
|
109
|
+
{selected.map((option, i) => (
|
|
110
|
+
<FilterItem
|
|
111
|
+
key={i}
|
|
112
|
+
filter={filter}
|
|
113
|
+
toggleFilterValue={toggleFilterValue}
|
|
114
|
+
option={option}
|
|
115
|
+
active={true}
|
|
116
|
+
/>
|
|
117
|
+
))}
|
|
118
|
+
<Dropdown.Divider />
|
|
119
|
+
{filteredOptions.map((option, i) => (
|
|
120
|
+
<FilterItem
|
|
121
|
+
key={i}
|
|
122
|
+
filter={filter}
|
|
123
|
+
toggleFilterValue={toggleFilterValue}
|
|
124
|
+
option={option}
|
|
125
|
+
active={_.includes(_.prop("value")(option))(
|
|
126
|
+
activeFilterSelectedValues
|
|
127
|
+
)}
|
|
128
|
+
/>
|
|
129
|
+
))}
|
|
130
|
+
</>
|
|
131
|
+
</>
|
|
132
|
+
{loading && (
|
|
133
|
+
<Dimmer active inverted>
|
|
134
|
+
<Loader size="tiny" />
|
|
135
|
+
</Dimmer>
|
|
136
|
+
)}
|
|
137
|
+
</Dimmer.Dimmable>
|
|
138
|
+
</Dropdown>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
@@ -41,7 +41,6 @@ export const SearchContextProvider = (props) => {
|
|
|
41
41
|
const [activeFilterName, setActiveFilterName] = useState([]);
|
|
42
42
|
const [allActiveFilters, setAllActiveFilters] = useState({});
|
|
43
43
|
const [hiddenFilters, setHiddenFilters] = useState({});
|
|
44
|
-
|
|
45
44
|
const [filterParams, setFilterParams] = useState({});
|
|
46
45
|
const [sortColumn, setSortColumn] = useState(initialSortColumn);
|
|
47
46
|
const [sortDirection, setSortDirection] = useState(initialSortDirection);
|
|
@@ -118,7 +117,8 @@ export const SearchContextProvider = (props) => {
|
|
|
118
117
|
type,
|
|
119
118
|
}),
|
|
120
119
|
formatFilterValues,
|
|
121
|
-
_.map(makeOption(translations(formatMessage), activeFilterName))
|
|
120
|
+
_.map(makeOption(translations(formatMessage), activeFilterName)),
|
|
121
|
+
_.reject((item) => _.values(item).includes(undefined))
|
|
122
122
|
)(filters);
|
|
123
123
|
|
|
124
124
|
const activeFilterSelectedValues = _.flow(
|
|
@@ -149,9 +149,7 @@ export const SearchContextProvider = (props) => {
|
|
|
149
149
|
const sort = useMemo(
|
|
150
150
|
() =>
|
|
151
151
|
sortColumn
|
|
152
|
-
? {
|
|
153
|
-
[sortColumn]: sortDirection === "ascending" ? "asc" : "desc",
|
|
154
|
-
}
|
|
152
|
+
? { [sortColumn]: sortDirection === "ascending" ? "asc" : "desc" }
|
|
155
153
|
: null,
|
|
156
154
|
[sortColumn, sortDirection]
|
|
157
155
|
);
|
|
@@ -193,18 +191,11 @@ export const SearchContextProvider = (props) => {
|
|
|
193
191
|
}, [query, searchMust, sort, defaultFilters, page, size]);
|
|
194
192
|
|
|
195
193
|
const makeFiltersGroup = (filters, groups) =>
|
|
196
|
-
_.
|
|
197
|
-
_.
|
|
198
|
-
_.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
)(groups)
|
|
202
|
-
),
|
|
203
|
-
_.toPairs,
|
|
204
|
-
_.remove(([key, _value]) => {
|
|
205
|
-
return _.startsWith("_")(key);
|
|
206
|
-
}),
|
|
207
|
-
_.fromPairs
|
|
194
|
+
_.groupBy((filter) =>
|
|
195
|
+
_.flow(
|
|
196
|
+
_.find(([_group, fields]) => _.contains(filter)(fields)),
|
|
197
|
+
_.prop("[0]")
|
|
198
|
+
)(groups)
|
|
208
199
|
)(filters);
|
|
209
200
|
|
|
210
201
|
const filtersByGroup = makeFiltersGroup(availableFilters, filtersGroup);
|
|
@@ -225,6 +216,25 @@ export const SearchContextProvider = (props) => {
|
|
|
225
216
|
])
|
|
226
217
|
)(filtersGroup);
|
|
227
218
|
|
|
219
|
+
const handleSortSelection = (columnName) => {
|
|
220
|
+
if (!columnName) return;
|
|
221
|
+
|
|
222
|
+
if (sortColumn !== columnName) {
|
|
223
|
+
setSortColumn(columnName);
|
|
224
|
+
setSortDirection("ascending");
|
|
225
|
+
} else {
|
|
226
|
+
setSortDirection(
|
|
227
|
+
sortDirection === "ascending" ? "descending" : "ascending"
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const setQueryAndScoreSort = (query) => {
|
|
233
|
+
setSortColumn(query == "" ? initialSortColumn : "_score");
|
|
234
|
+
setSortDirection(query == "" ? initialSortDirection : "descending");
|
|
235
|
+
setQuery(query);
|
|
236
|
+
};
|
|
237
|
+
|
|
228
238
|
const context = {
|
|
229
239
|
disabled: false,
|
|
230
240
|
loadingFilters,
|
|
@@ -250,7 +260,7 @@ export const SearchContextProvider = (props) => {
|
|
|
250
260
|
toggleFilterValue,
|
|
251
261
|
toggleHiddenFilterValue,
|
|
252
262
|
searchMust,
|
|
253
|
-
setQuery,
|
|
263
|
+
setQuery: setQueryAndScoreSort,
|
|
254
264
|
setAllActiveFilters,
|
|
255
265
|
allActiveFilters,
|
|
256
266
|
filters,
|
|
@@ -262,6 +272,8 @@ export const SearchContextProvider = (props) => {
|
|
|
262
272
|
sortDirection,
|
|
263
273
|
setSortColumn,
|
|
264
274
|
setSortDirection,
|
|
275
|
+
handleSortSelection,
|
|
276
|
+
initialSortColumn,
|
|
265
277
|
selectPage,
|
|
266
278
|
setSize,
|
|
267
279
|
count,
|
|
@@ -5,10 +5,13 @@ import { useUserFilters, useUserFiltersCreate } from "../hooks/useUserFilters";
|
|
|
5
5
|
import ModalSaveFilter from "../components/ModalSaveFilter";
|
|
6
6
|
import UserFilters from "./UserFilters";
|
|
7
7
|
import FilterDropdown from "./FilterDropdown";
|
|
8
|
+
import FilterQueryDropdown from "./FilterQueryDropdown";
|
|
8
9
|
import FilterMultilevelDropdown from "./FilterMultilevelDropdown";
|
|
9
10
|
import HierarchyFilterDropdown from "./HierarchyFilterDropdown";
|
|
10
11
|
import SearchContext, { useSearchContext } from "./SearchContext";
|
|
11
12
|
|
|
13
|
+
const MIN_LENGTH_FOR_QUERY_DROPDOWN = 8;
|
|
14
|
+
|
|
12
15
|
export default function SearchSelectedFilters() {
|
|
13
16
|
const context = useSearchContext();
|
|
14
17
|
const {
|
|
@@ -69,6 +72,8 @@ export default function SearchSelectedFilters() {
|
|
|
69
72
|
<FilterMultilevelDropdown />
|
|
70
73
|
) : filterType === "hierarchy" ? (
|
|
71
74
|
<HierarchyFilterDropdown />
|
|
75
|
+
) : _.size(options) >= MIN_LENGTH_FOR_QUERY_DROPDOWN ? (
|
|
76
|
+
<FilterQueryDropdown />
|
|
72
77
|
) : (
|
|
73
78
|
<FilterDropdown />
|
|
74
79
|
)}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { render } from "@truedat/test/render";
|
|
4
|
+
import { waitFor } from "@testing-library/react";
|
|
5
|
+
import userEvent from "@testing-library/user-event";
|
|
6
|
+
import SearchContext from "@truedat/core/search/SearchContext";
|
|
7
|
+
|
|
8
|
+
import FilterDropdown from "../FilterDropdown";
|
|
9
|
+
|
|
10
|
+
const options = _.map((value) => ({ value, text: value }))([
|
|
11
|
+
"value1",
|
|
12
|
+
"value2",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
const searchProps = {
|
|
16
|
+
loadingFilters: false,
|
|
17
|
+
openFilter: jest.fn(),
|
|
18
|
+
closeFilter: jest.fn(),
|
|
19
|
+
removeFilter: jest.fn(),
|
|
20
|
+
toggleFilterValue: jest.fn(),
|
|
21
|
+
useFilters: jest.fn(),
|
|
22
|
+
useSearch: jest.fn(),
|
|
23
|
+
filter: "foo",
|
|
24
|
+
options,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe("<FilterDropdown/>", () => {
|
|
28
|
+
it("matches the latest snapshot", () => {
|
|
29
|
+
const { container } = render(
|
|
30
|
+
<SearchContext.Provider value={searchProps}>
|
|
31
|
+
<FilterDropdown />
|
|
32
|
+
</SearchContext.Provider>
|
|
33
|
+
);
|
|
34
|
+
expect(container).toMatchSnapshot();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("no render search input if has more than eight options", () => {
|
|
38
|
+
const { container } = render(
|
|
39
|
+
<SearchContext.Provider value={searchProps}>
|
|
40
|
+
<FilterDropdown />
|
|
41
|
+
</SearchContext.Provider>
|
|
42
|
+
);
|
|
43
|
+
const searchInput = container.querySelector(".search input[type='text']");
|
|
44
|
+
expect(searchInput).not.toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("dispatches openFilter", () => {
|
|
48
|
+
const customProps = {
|
|
49
|
+
...searchProps,
|
|
50
|
+
options: [],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const { rerender } = render(
|
|
54
|
+
<SearchContext.Provider value={customProps}>
|
|
55
|
+
<FilterDropdown />
|
|
56
|
+
</SearchContext.Provider>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
rerender(
|
|
60
|
+
<SearchContext.Provider value={searchProps}>
|
|
61
|
+
<FilterDropdown />
|
|
62
|
+
</SearchContext.Provider>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
waitFor(() => expect(searchProps.openFilter).toBeCalledTimes(1));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("dispatches closeFilter", () => {
|
|
69
|
+
const customProps = {
|
|
70
|
+
...searchProps,
|
|
71
|
+
options: [],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const { rerender } = render(
|
|
75
|
+
<SearchContext.Provider value={searchProps}>
|
|
76
|
+
<FilterDropdown />
|
|
77
|
+
</SearchContext.Provider>
|
|
78
|
+
);
|
|
79
|
+
rerender(
|
|
80
|
+
<SearchContext.Provider value={customProps}>
|
|
81
|
+
<FilterDropdown />
|
|
82
|
+
</SearchContext.Provider>
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
waitFor(() => expect(searchProps.closeFilter).toBeCalledTimes(1));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("remove filter dispatches removeFilter", async () => {
|
|
89
|
+
const { container } = render(
|
|
90
|
+
<SearchContext.Provider value={searchProps}>
|
|
91
|
+
<FilterDropdown />
|
|
92
|
+
</SearchContext.Provider>
|
|
93
|
+
);
|
|
94
|
+
userEvent.click(container.querySelector('[class="delete icon"]'));
|
|
95
|
+
await waitFor(() => {
|
|
96
|
+
expect(searchProps.removeFilter).toBeCalledTimes(1);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@truedat/test/render";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
import { FilterItem } from "../FilterItem";
|
|
5
|
+
|
|
6
|
+
const props = {
|
|
7
|
+
active: true,
|
|
8
|
+
filter: "foo",
|
|
9
|
+
toggleFilterValue: jest.fn(),
|
|
10
|
+
option: { value: "foo", text: "bar" },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe("<FilterItem/>", () => {
|
|
14
|
+
it("matches the latest snapshot", () => {
|
|
15
|
+
const { container } = render(<FilterItem {...props} />);
|
|
16
|
+
expect(container).toMatchSnapshot();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("show active with false", () => {
|
|
20
|
+
const newProps = {
|
|
21
|
+
...props,
|
|
22
|
+
active: false,
|
|
23
|
+
};
|
|
24
|
+
const { getByRole } = render(<FilterItem {...newProps} />);
|
|
25
|
+
const divElement = getByRole("option");
|
|
26
|
+
expect(divElement).toHaveAttribute("aria-checked", "false");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("show empty filter", () => {
|
|
30
|
+
const newProps = {
|
|
31
|
+
...props,
|
|
32
|
+
option: { value: "foo", text: "" },
|
|
33
|
+
};
|
|
34
|
+
const { getByText } = render(<FilterItem {...newProps} />);
|
|
35
|
+
const emptyElement = getByText("Empty");
|
|
36
|
+
expect(emptyElement).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("dispatches toggleFilterValue on click", () => {
|
|
40
|
+
const { getByRole } = render(<FilterItem {...props} />);
|
|
41
|
+
expect(props.toggleFilterValue.mock.calls.length).toBe(0);
|
|
42
|
+
const dropdownItem = getByRole("option", { name: "bar" });
|
|
43
|
+
userEvent.click(dropdownItem);
|
|
44
|
+
expect(props.toggleFilterValue.mock.calls.length).toBe(1);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { render } from "@truedat/test/render";
|
|
4
|
+
import { waitFor, within } from "@testing-library/react";
|
|
5
|
+
import userEvent from "@testing-library/user-event";
|
|
6
|
+
import SearchContext from "@truedat/core/search/SearchContext";
|
|
7
|
+
import FilterQueryDropdown from "../FilterQueryDropdown";
|
|
8
|
+
|
|
9
|
+
const options = _.range(0, 10).map((n) => ({
|
|
10
|
+
value: `value${n}`,
|
|
11
|
+
text: `value${n}`,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const searchProps = {
|
|
15
|
+
loadingFilters: false,
|
|
16
|
+
openFilter: jest.fn(),
|
|
17
|
+
closeFilter: jest.fn(),
|
|
18
|
+
removeFilter: jest.fn(),
|
|
19
|
+
toggleFilterValue: jest.fn(),
|
|
20
|
+
useFilters: jest.fn(),
|
|
21
|
+
useSearch: jest.fn(),
|
|
22
|
+
filter: "foo",
|
|
23
|
+
options,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe("<FilterQueryDropdown/>", () => {
|
|
27
|
+
it("matches the latest snapshot", () => {
|
|
28
|
+
const { container } = render(
|
|
29
|
+
<SearchContext.Provider value={searchProps}>
|
|
30
|
+
<FilterQueryDropdown />
|
|
31
|
+
</SearchContext.Provider>
|
|
32
|
+
);
|
|
33
|
+
expect(container).toMatchSnapshot();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("render search input if has more than eight options", () => {
|
|
37
|
+
const { container } = render(
|
|
38
|
+
<SearchContext.Provider value={searchProps}>
|
|
39
|
+
<FilterQueryDropdown />
|
|
40
|
+
</SearchContext.Provider>
|
|
41
|
+
);
|
|
42
|
+
const searchInput = container.querySelector(".search input[type='text']");
|
|
43
|
+
expect(searchInput).toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("check option check it correctly", () => {
|
|
47
|
+
const selectedOption = searchProps.options[6].text;
|
|
48
|
+
|
|
49
|
+
const { getByRole, getAllByRole, rerender } = render(
|
|
50
|
+
<SearchContext.Provider value={searchProps}>
|
|
51
|
+
<FilterQueryDropdown />
|
|
52
|
+
</SearchContext.Provider>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(searchProps.toggleFilterValue).toBeCalledTimes(0);
|
|
56
|
+
userEvent.click(
|
|
57
|
+
getByRole("option", { name: new RegExp(selectedOption, "i") })
|
|
58
|
+
);
|
|
59
|
+
expect(searchProps.toggleFilterValue).toBeCalledWith({
|
|
60
|
+
filter: searchProps.filter,
|
|
61
|
+
value: selectedOption,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const rerenderProps = {
|
|
65
|
+
...searchProps,
|
|
66
|
+
activeFilterSelectedValues: [selectedOption],
|
|
67
|
+
};
|
|
68
|
+
rerender(
|
|
69
|
+
<SearchContext.Provider value={rerenderProps}>
|
|
70
|
+
<FilterQueryDropdown />
|
|
71
|
+
</SearchContext.Provider>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
expect(
|
|
75
|
+
within(getAllByRole("option")[0]).queryByText(selectedOption)
|
|
76
|
+
).not.toBeNull();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("type in filter input filter options", () => {
|
|
80
|
+
const searchText = "3";
|
|
81
|
+
|
|
82
|
+
const { getByRole, queryByRole, getAllByRole, container } = render(
|
|
83
|
+
<SearchContext.Provider value={searchProps}>
|
|
84
|
+
<FilterQueryDropdown />
|
|
85
|
+
</SearchContext.Provider>
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const input = container.querySelector(".search input[type='text']");
|
|
89
|
+
|
|
90
|
+
expect(
|
|
91
|
+
getByRole("option", {
|
|
92
|
+
name: new RegExp(searchProps.options[1].text, "i"),
|
|
93
|
+
})
|
|
94
|
+
).toBeInTheDocument();
|
|
95
|
+
|
|
96
|
+
userEvent.type(input, searchText);
|
|
97
|
+
|
|
98
|
+
expect(
|
|
99
|
+
queryByRole("option", {
|
|
100
|
+
name: new RegExp(searchProps.options[1].text, "i"),
|
|
101
|
+
})
|
|
102
|
+
).toBeNull();
|
|
103
|
+
|
|
104
|
+
expect(
|
|
105
|
+
within(getByRole("option")).getByText(`value${searchText}`)
|
|
106
|
+
).toBeInTheDocument();
|
|
107
|
+
|
|
108
|
+
expect(getAllByRole("option").length).toBe(1);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("type in filter not hidde selected values", async () => {
|
|
112
|
+
const selectedOption = options[6].text;
|
|
113
|
+
|
|
114
|
+
const customProps = {
|
|
115
|
+
...searchProps,
|
|
116
|
+
activeFilterSelectedValues: [selectedOption],
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const searchText = "3";
|
|
120
|
+
|
|
121
|
+
const { getAllByRole, container, queryByRole } = render(
|
|
122
|
+
<SearchContext.Provider value={customProps}>
|
|
123
|
+
<FilterQueryDropdown />
|
|
124
|
+
</SearchContext.Provider>
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const input = container.querySelector(".search input[type='text']");
|
|
128
|
+
|
|
129
|
+
userEvent.type(input, searchText);
|
|
130
|
+
|
|
131
|
+
expect(
|
|
132
|
+
within(getAllByRole("option")[0]).getByText(selectedOption)
|
|
133
|
+
).toBeInTheDocument();
|
|
134
|
+
|
|
135
|
+
expect(
|
|
136
|
+
within(getAllByRole("option")[1]).getByText(`value${searchText}`)
|
|
137
|
+
).toBeInTheDocument();
|
|
138
|
+
|
|
139
|
+
expect(
|
|
140
|
+
queryByRole("option", {
|
|
141
|
+
name: new RegExp(customProps.options[1].text, "i"),
|
|
142
|
+
})
|
|
143
|
+
).toBeNull();
|
|
144
|
+
|
|
145
|
+
expect(getAllByRole("option").length).toBe(2);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("dispatches openFilter", () => {
|
|
149
|
+
const customProps = {
|
|
150
|
+
...searchProps,
|
|
151
|
+
options: [],
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const { rerender } = render(
|
|
155
|
+
<SearchContext.Provider value={customProps}>
|
|
156
|
+
<FilterQueryDropdown />
|
|
157
|
+
</SearchContext.Provider>
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
rerender(
|
|
161
|
+
<SearchContext.Provider value={searchProps}>
|
|
162
|
+
<FilterQueryDropdown />
|
|
163
|
+
</SearchContext.Provider>
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
waitFor(() => expect(searchProps.openFilter).toBeCalledTimes(1));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("dispatches closeFilter", () => {
|
|
170
|
+
const customProps = {
|
|
171
|
+
...searchProps,
|
|
172
|
+
options: [],
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const { rerender } = render(
|
|
176
|
+
<SearchContext.Provider value={searchProps}>
|
|
177
|
+
<FilterQueryDropdown />
|
|
178
|
+
</SearchContext.Provider>
|
|
179
|
+
);
|
|
180
|
+
rerender(
|
|
181
|
+
<SearchContext.Provider value={customProps}>
|
|
182
|
+
<FilterQueryDropdown />
|
|
183
|
+
</SearchContext.Provider>
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
waitFor(() => expect(searchProps.closeFilter).toBeCalledTimes(1));
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("remove filter dispatches removeFilter", async () => {
|
|
190
|
+
const { container } = render(
|
|
191
|
+
<SearchContext.Provider value={searchProps}>
|
|
192
|
+
<FilterQueryDropdown />
|
|
193
|
+
</SearchContext.Provider>
|
|
194
|
+
);
|
|
195
|
+
userEvent.click(container.querySelector('[class="delete icon"]'));
|
|
196
|
+
await waitFor(() => {
|
|
197
|
+
expect(searchProps.removeFilter).toBeCalledTimes(1);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { render } from "@truedat/test/render";
|
|
4
|
+
import { waitFor } from "@testing-library/react";
|
|
5
|
+
import userEvent from "@testing-library/user-event";
|
|
6
|
+
import SearchContext from "@truedat/core/search/SearchContext";
|
|
7
|
+
import SearchWidget from "../SearchWidget";
|
|
8
|
+
|
|
9
|
+
const messages = {
|
|
10
|
+
en: {
|
|
11
|
+
"search.placeholder": "Search...",
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const renderOpts = { messages };
|
|
16
|
+
|
|
17
|
+
const setQuery = jest.fn();
|
|
18
|
+
const searchProps = {
|
|
19
|
+
userFiltersType: "foo",
|
|
20
|
+
useFilters: jest.fn(),
|
|
21
|
+
useSearch: jest.fn(),
|
|
22
|
+
loadingFilters: false,
|
|
23
|
+
query: "",
|
|
24
|
+
setQuery,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe("<SearchWidget/>", () => {
|
|
28
|
+
it("matches the latest snapshot", () => {
|
|
29
|
+
const { container } = render(
|
|
30
|
+
<SearchContext.Provider value={searchProps}>
|
|
31
|
+
<SearchWidget />
|
|
32
|
+
</SearchContext.Provider>,
|
|
33
|
+
renderOpts
|
|
34
|
+
);
|
|
35
|
+
expect(container).toMatchSnapshot();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("type in filter input filter options", async () => {
|
|
39
|
+
const { getByRole } = render(
|
|
40
|
+
<SearchContext.Provider value={searchProps}>
|
|
41
|
+
<SearchWidget />
|
|
42
|
+
</SearchContext.Provider>,
|
|
43
|
+
renderOpts
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
userEvent.type(await getByRole("textbox"), "3");
|
|
47
|
+
|
|
48
|
+
await waitFor(() => {
|
|
49
|
+
expect(setQuery).toBeCalledTimes(1);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`<FilterDropdown/> matches the latest snapshot 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
aria-expanded="true"
|
|
7
|
+
class="ui active visible floating item scrolling dropdown"
|
|
8
|
+
role="listbox"
|
|
9
|
+
tabindex="0"
|
|
10
|
+
>
|
|
11
|
+
<div
|
|
12
|
+
class="ui label"
|
|
13
|
+
>
|
|
14
|
+
foo
|
|
15
|
+
<i
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
class="delete icon"
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
<div
|
|
21
|
+
class="menu transition dimmable visible"
|
|
22
|
+
>
|
|
23
|
+
<div
|
|
24
|
+
aria-checked="false"
|
|
25
|
+
class="item"
|
|
26
|
+
role="option"
|
|
27
|
+
>
|
|
28
|
+
<i
|
|
29
|
+
aria-hidden="true"
|
|
30
|
+
class="square outline icon"
|
|
31
|
+
/>
|
|
32
|
+
value1
|
|
33
|
+
</div>
|
|
34
|
+
<div
|
|
35
|
+
aria-checked="false"
|
|
36
|
+
class="item"
|
|
37
|
+
role="option"
|
|
38
|
+
>
|
|
39
|
+
<i
|
|
40
|
+
aria-hidden="true"
|
|
41
|
+
class="square outline icon"
|
|
42
|
+
/>
|
|
43
|
+
value2
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
`;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`<FilterItem/> matches the latest snapshot 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
aria-checked="true"
|
|
7
|
+
class="active item"
|
|
8
|
+
role="option"
|
|
9
|
+
>
|
|
10
|
+
<i
|
|
11
|
+
aria-hidden="true"
|
|
12
|
+
class="check square outline icon"
|
|
13
|
+
/>
|
|
14
|
+
bar
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
`;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`<FilterQueryDropdown/> matches the latest snapshot 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
aria-expanded="true"
|
|
7
|
+
class="ui active visible floating item scrolling dropdown"
|
|
8
|
+
role="listbox"
|
|
9
|
+
tabindex="0"
|
|
10
|
+
>
|
|
11
|
+
<div
|
|
12
|
+
class="ui label"
|
|
13
|
+
>
|
|
14
|
+
foo
|
|
15
|
+
<i
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
class="delete icon"
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
<div
|
|
21
|
+
class="menu transition dimmable visible"
|
|
22
|
+
>
|
|
23
|
+
<div
|
|
24
|
+
class="ui left icon input search"
|
|
25
|
+
>
|
|
26
|
+
<input
|
|
27
|
+
type="text"
|
|
28
|
+
value=""
|
|
29
|
+
/>
|
|
30
|
+
<i
|
|
31
|
+
aria-hidden="true"
|
|
32
|
+
class="search icon"
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
<div
|
|
36
|
+
class="divider"
|
|
37
|
+
/>
|
|
38
|
+
<div
|
|
39
|
+
aria-checked="false"
|
|
40
|
+
class="item"
|
|
41
|
+
role="option"
|
|
42
|
+
>
|
|
43
|
+
<i
|
|
44
|
+
aria-hidden="true"
|
|
45
|
+
class="square outline icon"
|
|
46
|
+
/>
|
|
47
|
+
value0
|
|
48
|
+
</div>
|
|
49
|
+
<div
|
|
50
|
+
aria-checked="false"
|
|
51
|
+
class="item"
|
|
52
|
+
role="option"
|
|
53
|
+
>
|
|
54
|
+
<i
|
|
55
|
+
aria-hidden="true"
|
|
56
|
+
class="square outline icon"
|
|
57
|
+
/>
|
|
58
|
+
value1
|
|
59
|
+
</div>
|
|
60
|
+
<div
|
|
61
|
+
aria-checked="false"
|
|
62
|
+
class="item"
|
|
63
|
+
role="option"
|
|
64
|
+
>
|
|
65
|
+
<i
|
|
66
|
+
aria-hidden="true"
|
|
67
|
+
class="square outline icon"
|
|
68
|
+
/>
|
|
69
|
+
value2
|
|
70
|
+
</div>
|
|
71
|
+
<div
|
|
72
|
+
aria-checked="false"
|
|
73
|
+
class="item"
|
|
74
|
+
role="option"
|
|
75
|
+
>
|
|
76
|
+
<i
|
|
77
|
+
aria-hidden="true"
|
|
78
|
+
class="square outline icon"
|
|
79
|
+
/>
|
|
80
|
+
value3
|
|
81
|
+
</div>
|
|
82
|
+
<div
|
|
83
|
+
aria-checked="false"
|
|
84
|
+
class="item"
|
|
85
|
+
role="option"
|
|
86
|
+
>
|
|
87
|
+
<i
|
|
88
|
+
aria-hidden="true"
|
|
89
|
+
class="square outline icon"
|
|
90
|
+
/>
|
|
91
|
+
value4
|
|
92
|
+
</div>
|
|
93
|
+
<div
|
|
94
|
+
aria-checked="false"
|
|
95
|
+
class="item"
|
|
96
|
+
role="option"
|
|
97
|
+
>
|
|
98
|
+
<i
|
|
99
|
+
aria-hidden="true"
|
|
100
|
+
class="square outline icon"
|
|
101
|
+
/>
|
|
102
|
+
value5
|
|
103
|
+
</div>
|
|
104
|
+
<div
|
|
105
|
+
aria-checked="false"
|
|
106
|
+
class="item"
|
|
107
|
+
role="option"
|
|
108
|
+
>
|
|
109
|
+
<i
|
|
110
|
+
aria-hidden="true"
|
|
111
|
+
class="square outline icon"
|
|
112
|
+
/>
|
|
113
|
+
value6
|
|
114
|
+
</div>
|
|
115
|
+
<div
|
|
116
|
+
aria-checked="false"
|
|
117
|
+
class="item"
|
|
118
|
+
role="option"
|
|
119
|
+
>
|
|
120
|
+
<i
|
|
121
|
+
aria-hidden="true"
|
|
122
|
+
class="square outline icon"
|
|
123
|
+
/>
|
|
124
|
+
value7
|
|
125
|
+
</div>
|
|
126
|
+
<div
|
|
127
|
+
aria-checked="false"
|
|
128
|
+
class="item"
|
|
129
|
+
role="option"
|
|
130
|
+
>
|
|
131
|
+
<i
|
|
132
|
+
aria-hidden="true"
|
|
133
|
+
class="square outline icon"
|
|
134
|
+
/>
|
|
135
|
+
value8
|
|
136
|
+
</div>
|
|
137
|
+
<div
|
|
138
|
+
aria-checked="false"
|
|
139
|
+
class="item"
|
|
140
|
+
role="option"
|
|
141
|
+
>
|
|
142
|
+
<i
|
|
143
|
+
aria-hidden="true"
|
|
144
|
+
class="square outline icon"
|
|
145
|
+
/>
|
|
146
|
+
value9
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
`;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`<SearchWidget/> matches the latest snapshot 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
class="ui action left icon input"
|
|
7
|
+
>
|
|
8
|
+
<input
|
|
9
|
+
placeholder="Search..."
|
|
10
|
+
type="text"
|
|
11
|
+
value=""
|
|
12
|
+
/>
|
|
13
|
+
<i
|
|
14
|
+
aria-hidden="true"
|
|
15
|
+
class="search link icon"
|
|
16
|
+
/>
|
|
17
|
+
<div
|
|
18
|
+
aria-busy="false"
|
|
19
|
+
aria-expanded="false"
|
|
20
|
+
class="ui button floating labeled scrolling dropdown icon"
|
|
21
|
+
role="listbox"
|
|
22
|
+
tabindex="0"
|
|
23
|
+
>
|
|
24
|
+
<div
|
|
25
|
+
aria-atomic="true"
|
|
26
|
+
aria-live="polite"
|
|
27
|
+
class="divider text"
|
|
28
|
+
role="alert"
|
|
29
|
+
>
|
|
30
|
+
Filters
|
|
31
|
+
</div>
|
|
32
|
+
<i
|
|
33
|
+
aria-hidden="true"
|
|
34
|
+
class="filter icon"
|
|
35
|
+
/>
|
|
36
|
+
<div
|
|
37
|
+
class="menu transition"
|
|
38
|
+
>
|
|
39
|
+
<div
|
|
40
|
+
class="item"
|
|
41
|
+
role="option"
|
|
42
|
+
>
|
|
43
|
+
<em>
|
|
44
|
+
(reset filters)
|
|
45
|
+
</em>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
<div
|
|
51
|
+
class="selectedFilters"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
`;
|