@truedat/core 8.4.1 → 8.4.3
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
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import userEvent from "@testing-library/user-event";
|
|
2
|
+
import { useLocation } from "react-router";
|
|
3
|
+
|
|
4
|
+
import { render, waitForLoad } from "@truedat/test/render";
|
|
5
|
+
|
|
6
|
+
import SearchContext, { useSearchContext } from "../SearchContext";
|
|
7
|
+
import SearchSelectedFilters from "../SearchSelectedFilters";
|
|
8
|
+
|
|
9
|
+
jest.mock("react-router", () => ({
|
|
10
|
+
...jest.requireActual("react-router"),
|
|
11
|
+
useLocation: jest.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
jest.mock("../SearchContext", () => ({
|
|
15
|
+
__esModule: true,
|
|
16
|
+
default: ({ children }) => <>{children}</>,
|
|
17
|
+
useSearchContext: jest.fn(),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
jest.mock("../UserFilters", () => ({
|
|
21
|
+
__esModule: true,
|
|
22
|
+
default: ({ domainId, selectedUserFilter }) => (
|
|
23
|
+
<div
|
|
24
|
+
data-testid="user-filters"
|
|
25
|
+
data-domain-id={domainId === null ? "null" : String(domainId)}
|
|
26
|
+
data-selected-user-filter={selectedUserFilter ?? "undefined"}
|
|
27
|
+
/>
|
|
28
|
+
),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
jest.mock("../ModalSaveFilter", () => ({
|
|
32
|
+
__esModule: true,
|
|
33
|
+
default: () => <div data-testid="modal-save-filter" />,
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
jest.mock("../FilterDropdown", () => ({
|
|
37
|
+
__esModule: true,
|
|
38
|
+
default: () => <div data-testid="filter-dropdown" />,
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
jest.mock("../FilterQueryDropdown", () => ({
|
|
42
|
+
__esModule: true,
|
|
43
|
+
default: () => <div data-testid="filter-query-dropdown" />,
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
jest.mock("../FilterMultilevelDropdown", () => ({
|
|
47
|
+
__esModule: true,
|
|
48
|
+
default: () => <div data-testid="filter-multilevel-dropdown" />,
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
jest.mock("../HierarchyFilterDropdown", () => ({
|
|
52
|
+
__esModule: true,
|
|
53
|
+
default: () => <div data-testid="hierarchy-filter-dropdown" />,
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
jest.mock("../../components/DomainSearchFilter", () => ({
|
|
57
|
+
__esModule: true,
|
|
58
|
+
default: ({ filter, options }) => (
|
|
59
|
+
<div
|
|
60
|
+
data-testid="domain-search-filter"
|
|
61
|
+
data-filter={filter}
|
|
62
|
+
data-options-size={options ? String(options.length) : "null"}
|
|
63
|
+
/>
|
|
64
|
+
),
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
describe("<SearchSelectedFilters />", () => {
|
|
68
|
+
const resetFilters = jest.fn();
|
|
69
|
+
const openFilter = jest.fn();
|
|
70
|
+
const closeFilter = jest.fn();
|
|
71
|
+
const removeFilter = jest.fn();
|
|
72
|
+
const toggleFilterValue = jest.fn();
|
|
73
|
+
|
|
74
|
+
const messages = {
|
|
75
|
+
"search.applied_filters": "applied filters",
|
|
76
|
+
"search.clear_filters": "clear filters",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const buildContext = (override = {}) => ({
|
|
80
|
+
selectedFilters: [],
|
|
81
|
+
resetFilters,
|
|
82
|
+
filterTypes: {},
|
|
83
|
+
activeFilterName: null,
|
|
84
|
+
activeFilterValues: [],
|
|
85
|
+
userFiltersType: null,
|
|
86
|
+
useDomainSearchFilter: false,
|
|
87
|
+
activeFilterSelectedValues: [],
|
|
88
|
+
name: "name",
|
|
89
|
+
loadingFilters: false,
|
|
90
|
+
openFilter,
|
|
91
|
+
closeFilter,
|
|
92
|
+
removeFilter,
|
|
93
|
+
toggleFilterValue,
|
|
94
|
+
...override,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const renderOpts = {
|
|
98
|
+
messages: {
|
|
99
|
+
en: messages,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
beforeEach(() => {
|
|
104
|
+
jest.clearAllMocks();
|
|
105
|
+
useLocation.mockReturnValue({ pathname: "/concepts" });
|
|
106
|
+
useSearchContext.mockReturnValue(buildContext());
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("does not render applied filters section when there are no selected filters", async () => {
|
|
110
|
+
const rendered = render(<SearchSelectedFilters />, renderOpts);
|
|
111
|
+
await waitForLoad(rendered);
|
|
112
|
+
|
|
113
|
+
expect(rendered.queryByText(/applied filters/i)).toBeNull();
|
|
114
|
+
expect(rendered.queryByText(/clear filters/i)).toBeNull();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("renders user filters with taxonomy domain id in taxonomy routes", async () => {
|
|
118
|
+
useLocation.mockReturnValue({ pathname: "/domains/123/structures" });
|
|
119
|
+
useSearchContext.mockReturnValue(
|
|
120
|
+
buildContext({
|
|
121
|
+
userFiltersType: "business_concept_user_filters",
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
const rendered = render(<SearchSelectedFilters />, renderOpts);
|
|
125
|
+
await waitForLoad(rendered);
|
|
126
|
+
|
|
127
|
+
expect(rendered.getByTestId("user-filters")).toHaveAttribute(
|
|
128
|
+
"data-domain-id",
|
|
129
|
+
"123"
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("renders user filters with null domain id in non taxonomy routes", async () => {
|
|
134
|
+
useLocation.mockReturnValue({ pathname: "/concepts" });
|
|
135
|
+
useSearchContext.mockReturnValue(
|
|
136
|
+
buildContext({
|
|
137
|
+
userFiltersType: "business_concept_user_filters",
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
const rendered = render(<SearchSelectedFilters />, renderOpts);
|
|
141
|
+
await waitForLoad(rendered);
|
|
142
|
+
|
|
143
|
+
expect(rendered.getByTestId("user-filters")).toHaveAttribute(
|
|
144
|
+
"data-domain-id",
|
|
145
|
+
"null"
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("renders domain search filter for domain filter type when feature flag is enabled", async () => {
|
|
150
|
+
useSearchContext.mockReturnValue(
|
|
151
|
+
buildContext({
|
|
152
|
+
selectedFilters: ["taxonomy"],
|
|
153
|
+
filterTypes: { taxonomy: "domain" },
|
|
154
|
+
activeFilterName: "taxonomy",
|
|
155
|
+
activeFilterValues: [{ id: 1 }, { id: 2 }],
|
|
156
|
+
useDomainSearchFilter: true,
|
|
157
|
+
})
|
|
158
|
+
);
|
|
159
|
+
const rendered = render(<SearchSelectedFilters />, renderOpts);
|
|
160
|
+
await waitForLoad(rendered);
|
|
161
|
+
|
|
162
|
+
expect(rendered.getByTestId("domain-search-filter")).toHaveAttribute(
|
|
163
|
+
"data-filter",
|
|
164
|
+
"taxonomy"
|
|
165
|
+
);
|
|
166
|
+
expect(rendered.getByTestId("domain-search-filter")).toHaveAttribute(
|
|
167
|
+
"data-options-size",
|
|
168
|
+
"2"
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("renders multilevel dropdown for domain filter type when feature flag is disabled", async () => {
|
|
173
|
+
useSearchContext.mockReturnValue(
|
|
174
|
+
buildContext({
|
|
175
|
+
selectedFilters: ["taxonomy"],
|
|
176
|
+
filterTypes: { taxonomy: "domain" },
|
|
177
|
+
useDomainSearchFilter: false,
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
const rendered = render(<SearchSelectedFilters />, renderOpts);
|
|
181
|
+
await waitForLoad(rendered);
|
|
182
|
+
|
|
183
|
+
expect(rendered.getByTestId("filter-multilevel-dropdown")).toBeInTheDocument();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("renders hierarchy dropdown for hierarchy filter type", async () => {
|
|
187
|
+
useSearchContext.mockReturnValue(
|
|
188
|
+
buildContext({
|
|
189
|
+
selectedFilters: ["hierarchy_field"],
|
|
190
|
+
filterTypes: { hierarchy_field: "hierarchy" },
|
|
191
|
+
})
|
|
192
|
+
);
|
|
193
|
+
const rendered = render(<SearchSelectedFilters />, renderOpts);
|
|
194
|
+
await waitForLoad(rendered);
|
|
195
|
+
|
|
196
|
+
expect(rendered.getByTestId("hierarchy-filter-dropdown")).toBeInTheDocument();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("renders query dropdown when active filter options length is greater than or equal to eight", async () => {
|
|
200
|
+
useSearchContext.mockReturnValue(
|
|
201
|
+
buildContext({
|
|
202
|
+
selectedFilters: ["status"],
|
|
203
|
+
filterTypes: { status: "term" },
|
|
204
|
+
activeFilterName: "status",
|
|
205
|
+
activeFilterValues: ["a", "b", "c", "d", "e", "f", "g", "h"],
|
|
206
|
+
})
|
|
207
|
+
);
|
|
208
|
+
const rendered = render(<SearchSelectedFilters />, renderOpts);
|
|
209
|
+
await waitForLoad(rendered);
|
|
210
|
+
|
|
211
|
+
expect(rendered.getByTestId("filter-query-dropdown")).toBeInTheDocument();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("renders default dropdown when active filter options length is less than eight", async () => {
|
|
215
|
+
useSearchContext.mockReturnValue(
|
|
216
|
+
buildContext({
|
|
217
|
+
selectedFilters: ["status"],
|
|
218
|
+
filterTypes: { status: "term" },
|
|
219
|
+
activeFilterName: "status",
|
|
220
|
+
activeFilterValues: ["a", "b", "c"],
|
|
221
|
+
})
|
|
222
|
+
);
|
|
223
|
+
const rendered = render(<SearchSelectedFilters />, renderOpts);
|
|
224
|
+
await waitForLoad(rendered);
|
|
225
|
+
|
|
226
|
+
expect(rendered.getByTestId("filter-dropdown")).toBeInTheDocument();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("calls reset filters and clears selected user filter when clear filters is clicked", async () => {
|
|
230
|
+
const user = userEvent.setup({ delay: null });
|
|
231
|
+
useSearchContext.mockReturnValue(
|
|
232
|
+
buildContext({
|
|
233
|
+
selectedFilters: ["status"],
|
|
234
|
+
filterTypes: { status: "term" },
|
|
235
|
+
userFiltersType: "business_concept_user_filters",
|
|
236
|
+
})
|
|
237
|
+
);
|
|
238
|
+
const rendered = render(<SearchSelectedFilters />, renderOpts);
|
|
239
|
+
await waitForLoad(rendered);
|
|
240
|
+
|
|
241
|
+
expect(resetFilters).toHaveBeenCalledTimes(0);
|
|
242
|
+
expect(rendered.getByTestId("user-filters")).toHaveAttribute(
|
|
243
|
+
"data-selected-user-filter",
|
|
244
|
+
"undefined"
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
await user.click(rendered.getByText(/clear filters/i));
|
|
248
|
+
|
|
249
|
+
expect(resetFilters).toHaveBeenCalledTimes(1);
|
|
250
|
+
expect(rendered.getByTestId("user-filters")).toHaveAttribute(
|
|
251
|
+
"data-selected-user-filter",
|
|
252
|
+
""
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("renders save filter modal only when user filters are enabled and there are selected filters", async () => {
|
|
257
|
+
useSearchContext.mockReturnValue(
|
|
258
|
+
buildContext({
|
|
259
|
+
selectedFilters: ["status"],
|
|
260
|
+
filterTypes: { status: "term" },
|
|
261
|
+
userFiltersType: "business_concept_user_filters",
|
|
262
|
+
})
|
|
263
|
+
);
|
|
264
|
+
const rendered = render(<SearchSelectedFilters />, renderOpts);
|
|
265
|
+
await waitForLoad(rendered);
|
|
266
|
+
|
|
267
|
+
expect(rendered.getByTestId("modal-save-filter")).toBeInTheDocument();
|
|
268
|
+
});
|
|
269
|
+
});
|
|
@@ -82,4 +82,44 @@ describe("selectors: makeSearchQuerySelector", () => {
|
|
|
82
82
|
});
|
|
83
83
|
});
|
|
84
84
|
});
|
|
85
|
+
|
|
86
|
+
describe("with useDomainSearchFilter enabled", () => {
|
|
87
|
+
const searchQuerySelectorWithDomainFilter = makeSearchQuerySelector(
|
|
88
|
+
"conceptQuery",
|
|
89
|
+
"conceptActiveFilters",
|
|
90
|
+
undefined,
|
|
91
|
+
{ useDomainSearchFilter: true }
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
it("maps must.taxonomy to must.domain_ids", () => {
|
|
95
|
+
const state = {
|
|
96
|
+
conceptQuery: { query: "" },
|
|
97
|
+
conceptActiveFilters: { taxonomy: [1, 2], foo: ["bar"] },
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
expect(searchQuerySelectorWithDomainFilter(state)).toMatchObject({
|
|
101
|
+
must: { domain_ids: [1, 2], foo: ["bar"] },
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("with useDomainSearchFilter disabled", () => {
|
|
107
|
+
const searchQuerySelectorWithoutDomainFilter = makeSearchQuerySelector(
|
|
108
|
+
"conceptQuery",
|
|
109
|
+
"conceptActiveFilters",
|
|
110
|
+
undefined,
|
|
111
|
+
{ useDomainSearchFilter: false }
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
it("keeps must.taxonomy", () => {
|
|
115
|
+
const state = {
|
|
116
|
+
conceptQuery: { query: "" },
|
|
117
|
+
conceptActiveFilters: { taxonomy: [1, 2], foo: ["bar"] },
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
expect(searchQuerySelectorWithoutDomainFilter(state)).toMatchObject({
|
|
121
|
+
must: { taxonomy: [1, 2], foo: ["bar"] },
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
85
125
|
});
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
identity,
|
|
3
3
|
isEmpty,
|
|
4
4
|
isEqual,
|
|
5
|
+
mapKeys,
|
|
5
6
|
omitBy,
|
|
6
7
|
pick,
|
|
7
8
|
prop,
|
|
@@ -29,8 +30,10 @@ export const makeSearchFiltersSelector = (activeFiltersProp, dateFilterProp) =>
|
|
|
29
30
|
export const makeSearchQuerySelector = (
|
|
30
31
|
queryProp,
|
|
31
32
|
activeFiltersProp,
|
|
32
|
-
dateFilterProp
|
|
33
|
+
dateFilterProp,
|
|
34
|
+
options = {}
|
|
33
35
|
) => {
|
|
36
|
+
const { useDomainSearchFilter = false } = options;
|
|
34
37
|
const searchFiltersSelector = makeSearchFiltersSelector(
|
|
35
38
|
activeFiltersProp,
|
|
36
39
|
dateFilterProp
|
|
@@ -41,7 +44,13 @@ export const makeSearchQuerySelector = (
|
|
|
41
44
|
queryPropsSelector,
|
|
42
45
|
(searchQuery, activeFilters, ownProps) => {
|
|
43
46
|
const { query, page = 1, sort } = searchQuery || {};
|
|
44
|
-
const
|
|
47
|
+
const shouldUseDomainSearchFilter =
|
|
48
|
+
useDomainSearchFilter || ownProps?.useDomainSearchFilter;
|
|
49
|
+
const mapDomainFilterKey = (key) =>
|
|
50
|
+
shouldUseDomainSearchFilter && key === "taxonomy"
|
|
51
|
+
? "domain_ids"
|
|
52
|
+
: key;
|
|
53
|
+
const filters = mapKeys(mapDomainFilterKey)(omitBy(isEmpty)(activeFilters));
|
|
45
54
|
const q = trim(query)
|
|
46
55
|
? {
|
|
47
56
|
must: filters,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.ui.dropdown.domain-search-filter {
|
|
2
|
+
.item-content > .toggle-children {
|
|
3
|
+
margin-left: 8px;
|
|
4
|
+
display: inline-flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
border: 1px solid #d4d4d5;
|
|
8
|
+
border-radius: 999px;
|
|
9
|
+
padding: 1px 6px;
|
|
10
|
+
min-height: 20px;
|
|
11
|
+
background: #f8f8f9;
|
|
12
|
+
cursor: pointer;
|
|
13
|
+
line-height: 1;
|
|
14
|
+
font-size: 11px;
|
|
15
|
+
appearance: none;
|
|
16
|
+
|
|
17
|
+
> .toggle-children-content {
|
|
18
|
+
display: inline-flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: 2px;
|
|
21
|
+
|
|
22
|
+
> i.icon {
|
|
23
|
+
margin: 0;
|
|
24
|
+
font-size: 0.95em;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|