@truedat/core 6.13.3 → 6.13.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/core",
3
- "version": "6.13.3",
3
+ "version": "6.13.4",
4
4
  "description": "Truedat Web Core",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -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": "2665e3bb640febd00aa49dd06e37b4d391164e74"
121
+ "gitHead": "3fd777c739ce6c9c78d505a85877e0ef6059b19c"
122
122
  }
@@ -0,0 +1,289 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState, useEffect } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { Link } from "react-router-dom";
5
+ import { linkTo } from "@truedat/core/routes";
6
+ import {
7
+ Card,
8
+ Grid,
9
+ Header,
10
+ Icon,
11
+ Input,
12
+ Label,
13
+ Segment,
14
+ } from "semantic-ui-react";
15
+ import { FormattedMessage, useIntl } from "react-intl";
16
+ import { LocalSearchInput } from "./LocalSearchInput";
17
+ import { LocalCustomSearch } from "./LocalCustomSearch";
18
+
19
+ import "../styles/BranchViewer.less";
20
+
21
+ const organizeBranches = (branches) => {
22
+ const grouped = branches.reduce((acc, branch) => {
23
+ const { parent_id } = branch;
24
+
25
+ return {
26
+ ...acc,
27
+ [parent_id]: [
28
+ ...(acc[parent_id] || []),
29
+ {
30
+ ...branch,
31
+ parents: [],
32
+ },
33
+ ],
34
+ };
35
+ }, {});
36
+
37
+ const buildParents = (branch, accParents = []) => {
38
+ if (branch.parent_id === null) {
39
+ return [...accParents];
40
+ }
41
+
42
+ const parentBranch = Object.values(grouped)
43
+ .flat()
44
+ .find((b) => b.id === branch.parent_id);
45
+
46
+ return parentBranch
47
+ ? buildParents(parentBranch, [branch.parent_id, ...accParents])
48
+ : accParents;
49
+ };
50
+
51
+ const buildBranch = (parentId) => {
52
+ const children = grouped[parentId] || [];
53
+
54
+ return children.map((element) => {
55
+ const parents = buildParents(element);
56
+ const childs = buildBranch(element.id);
57
+
58
+ const child = {
59
+ ...element,
60
+ parents,
61
+ childs,
62
+ };
63
+
64
+ return { ...child, path: createPathDescription(child) };
65
+ });
66
+ };
67
+
68
+ const createPathDescription = (branch) => {
69
+ const results = [];
70
+ const { parents } = branch;
71
+
72
+ for (const parent_id of parents) {
73
+ const { name } = branches.find((branch) => branch.id === parent_id);
74
+ results.push(name);
75
+ }
76
+
77
+ return results.length == 0 ? "" : results.join(" > ");
78
+ };
79
+
80
+ return buildBranch(null);
81
+ };
82
+
83
+ const ListBranchItem = ({ branch, handleRedirect, selectedBranch }) => {
84
+ const { id, name } = branch;
85
+ const [expanded, setExpanded] = useState(false);
86
+ const generation = branch.childs;
87
+ const hasChildren = !generation.length == 0;
88
+
89
+ useEffect(() => {
90
+ if (selectedBranch?.parents?.includes(id)) {
91
+ setExpanded(!expanded);
92
+ }
93
+
94
+ if (id === selectedBranch?.id) {
95
+ setExpanded(true);
96
+ }
97
+ }, []);
98
+
99
+ return (
100
+ <li key={id}>
101
+ <div
102
+ style={{
103
+ display: "flex",
104
+ flexDirection: "row",
105
+ alignItems: "self-start",
106
+ }}
107
+ onClick={() => handleRedirect(id)}
108
+ >
109
+ <Input
110
+ labelPosition="left"
111
+ type="text"
112
+ value={name == null ? "" : name}
113
+ className="hierarchyNode"
114
+ disabled={true}
115
+ >
116
+ {hasChildren ? (
117
+ <Label
118
+ basic
119
+ onClick={(e) => {
120
+ e.stopPropagation();
121
+ setExpanded(!expanded);
122
+ }}
123
+ style={{ cursor: "pointer" }}
124
+ >
125
+ <Icon fitted name={expanded ? "dot circle outline" : "circle"} />
126
+ </Label>
127
+ ) : null}
128
+ <input
129
+ className={id === selectedBranch?.id ? "selectedBranch" : " "}
130
+ />
131
+ </Input>
132
+ </div>
133
+ {generation.length == 0 || !expanded ? null : (
134
+ <ListBranch
135
+ childs={branch.childs}
136
+ parentId={id}
137
+ handleRedirect={handleRedirect}
138
+ selectedBranch={selectedBranch}
139
+ />
140
+ )}
141
+ </li>
142
+ );
143
+ };
144
+
145
+ ListBranchItem.propTypes = {
146
+ key: PropTypes.number,
147
+ branch: PropTypes.object,
148
+ handleRedirect: PropTypes.func.isRequired,
149
+ selectedBranch: PropTypes.object,
150
+ };
151
+
152
+ const ListBranch = ({ childs, handleRedirect, selectedBranch }) => {
153
+ return _.isEmpty(childs) ? null : (
154
+ <ul className="wtree expanded">
155
+ {childs.map((child) => {
156
+ return (
157
+ <ListBranchItem
158
+ key={child.id}
159
+ branch={child}
160
+ handleRedirect={handleRedirect}
161
+ selectedBranch={selectedBranch}
162
+ />
163
+ );
164
+ })}
165
+ </ul>
166
+ );
167
+ };
168
+
169
+ ListBranch.propTypes = {
170
+ childs: PropTypes.object,
171
+ handleRedirect: PropTypes.func.isRequired,
172
+ selectedBranch: PropTypes.object,
173
+ };
174
+
175
+ export const BranchViewer = ({
176
+ branches = [],
177
+ handleRedirect,
178
+ idSelectedBranch,
179
+ }) => {
180
+ useIntl();
181
+
182
+ const [filteredBranches, setFilteredBranches] = useState([]);
183
+ const [selectedBranch, setSelectedBranch] = useState({});
184
+ const organizedBranches = _.isNull(branches)
185
+ ? []
186
+ : organizeBranches(branches);
187
+ const searchFields = ["name", "childs"];
188
+
189
+ const checkSearchEnabled = () => {
190
+ return !_.equals(organizedBranches, filteredBranches);
191
+ };
192
+
193
+ useEffect(() => {
194
+ if (_.isEmpty(filteredBranches) && checkSearchEnabled()) {
195
+ setFilteredBranches(organizedBranches);
196
+ }
197
+
198
+ if (!_.isUndefined(idSelectedBranch)) {
199
+ LocalCustomSearch(
200
+ organizedBranches,
201
+ idSelectedBranch,
202
+ setSelectedBranch,
203
+ ["id", "childs"],
204
+ true
205
+ );
206
+ }
207
+ }, [branches]);
208
+
209
+ return (
210
+ <Segment
211
+ style={{
212
+ overflow: "auto",
213
+ }}
214
+ >
215
+ <Grid>
216
+ <Grid.Row>
217
+ <Grid.Column width={16}>
218
+ <LocalSearchInput
219
+ collection={organizedBranches}
220
+ callback={setFilteredBranches}
221
+ searchFields={searchFields}
222
+ searchFunction={LocalCustomSearch}
223
+ enabled={checkSearchEnabled()}
224
+ />
225
+ </Grid.Column>
226
+ </Grid.Row>
227
+ <Grid.Row>
228
+ <Grid.Column width={16}>
229
+ {!_.isEmpty(branches) ? (
230
+ filteredBranches.map((branch) => (
231
+ <Grid.Column key={branch.id}>
232
+ <Grid.Row>
233
+ <ul
234
+ style={{
235
+ listStyleType: "none",
236
+ padding: 0,
237
+ }}
238
+ >
239
+ {checkSearchEnabled(branches) ? (
240
+ <Card
241
+ key={branch.id}
242
+ link
243
+ as={Link}
244
+ to={linkTo.DOMAIN_MEMBERS({
245
+ id: _.get("id")(branch),
246
+ })}
247
+ >
248
+ <Card.Content>
249
+ <Card.Header className="brancheViewerSearchResultHeader">
250
+ {_.get("name")(branch)}
251
+ </Card.Header>
252
+ <Card.Meta className="brancheViewerSearchResultMeta">
253
+ {_.get("path")(branch)}
254
+ </Card.Meta>
255
+ </Card.Content>
256
+ </Card>
257
+ ) : (
258
+ <ListBranchItem
259
+ branch={branch}
260
+ handleRedirect={handleRedirect}
261
+ selectedBranch={selectedBranch}
262
+ />
263
+ )}
264
+ </ul>
265
+ </Grid.Row>
266
+ </Grid.Column>
267
+ ))
268
+ ) : (
269
+ <Header as="h4">
270
+ <Icon name="cube" />
271
+ <Header.Content>
272
+ <FormattedMessage id="domains.notExist" />
273
+ </Header.Content>
274
+ </Header>
275
+ )}
276
+ </Grid.Column>
277
+ </Grid.Row>
278
+ </Grid>
279
+ </Segment>
280
+ );
281
+ };
282
+
283
+ BranchViewer.propTypes = {
284
+ branches: PropTypes.array,
285
+ handleRedirect: PropTypes.func.isRequired,
286
+ idSelectedBranch: PropTypes.number,
287
+ };
288
+
289
+ export default BranchViewer;
@@ -0,0 +1,58 @@
1
+ import _ from "lodash/fp";
2
+ import PropTypes from "prop-types";
3
+
4
+ export const LocalCustomSearch = (
5
+ collection,
6
+ searchString = "",
7
+ callback,
8
+ searchFields,
9
+ getOneOrMany = false
10
+ ) => {
11
+ const results = [];
12
+
13
+ const recursiveSearch = (item) => {
14
+ if (_.isObject(item) && !_.isNull(item)) {
15
+ for (const field of searchFields) {
16
+ const value = item[field];
17
+ if (_.isArray(value) && !_.isEmpty(value)) {
18
+ value.forEach(recursiveSearch);
19
+ } else if (_.isNumber(value) && searchString == value) {
20
+ results.push(item);
21
+ } else if (
22
+ _.isString(value) &&
23
+ value.toLowerCase().includes(searchString.toString().toLowerCase())
24
+ ) {
25
+ results.push(item);
26
+ }
27
+ }
28
+ }
29
+ };
30
+
31
+ const applyFilter = (data) => {
32
+ callback(getOneOrMany ? _.head(data) : data);
33
+ };
34
+
35
+ if (
36
+ searchFields === undefined ||
37
+ searchFields === null ||
38
+ searchFields.length === 0
39
+ ) {
40
+ return -1;
41
+ } else if (_.isEmpty(searchString.toString())) {
42
+ applyFilter(collection);
43
+ return collection.length;
44
+ } else {
45
+ collection.forEach(recursiveSearch);
46
+ applyFilter(results);
47
+ return results.length;
48
+ }
49
+ };
50
+
51
+ LocalCustomSearch.propTypes = {
52
+ collection: PropTypes.array,
53
+ query: PropTypes.string,
54
+ callback: PropTypes.func,
55
+ searchFields: PropTypes.array,
56
+ getOneOrMany: PropTypes.bool,
57
+ };
58
+ export default LocalCustomSearch;
@@ -0,0 +1,67 @@
1
+ import React, { useState } from "react";
2
+ import PropTypes from "prop-types";
3
+ import { Header, Icon, Input } from "semantic-ui-react";
4
+ import { FormattedMessage, useIntl } from "react-intl";
5
+
6
+ import "../styles/LocalSearchInput.less";
7
+
8
+ export const LocalSearchInput = ({
9
+ loading,
10
+ query,
11
+ collection,
12
+ callback,
13
+ searchFields,
14
+ searchFunction,
15
+ enabled,
16
+ }) => {
17
+ const { formatMessage } = useIntl();
18
+ const [countSearch, setCountSearch] = useState(0);
19
+
20
+ const onChange = (value) => {
21
+ setCountSearch(searchFunction(collection, value, callback, searchFields));
22
+ };
23
+
24
+ return (
25
+ <>
26
+ <Input
27
+ value={query}
28
+ className="searchbox"
29
+ onChange={(_e, { value }) => onChange(value)}
30
+ icon={{ name: "search", link: true }}
31
+ loading={loading}
32
+ placeholder={formatMessage({ id: "search.input.placeholder" })}
33
+ />
34
+ {enabled ? (
35
+ <Header as="h4">
36
+ <Icon name="search" />
37
+ <Header.Content>
38
+ {countSearch == -1 && (
39
+ <FormattedMessage id="domains.search.error.fields" />
40
+ )}
41
+ {countSearch == 0 && (
42
+ <FormattedMessage id="domains.search.results.empty" />
43
+ )}
44
+ {countSearch > 0 && (
45
+ <FormattedMessage
46
+ id="domains.search.results.count"
47
+ values={{ count: countSearch }}
48
+ />
49
+ )}
50
+ </Header.Content>
51
+ </Header>
52
+ ) : null}
53
+ </>
54
+ );
55
+ };
56
+
57
+ LocalSearchInput.propTypes = {
58
+ loading: PropTypes.bool,
59
+ query: PropTypes.string,
60
+ collection: PropTypes.array,
61
+ callback: PropTypes.func,
62
+ searchFields: PropTypes.array,
63
+ searchFunction: PropTypes.func,
64
+ enabled: PropTypes.bool,
65
+ };
66
+
67
+ export default LocalSearchInput;
@@ -0,0 +1,220 @@
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 BranchViewer from "../BranchViewer";
7
+
8
+ const handleRedirect = jest.fn();
9
+ const props = {
10
+ branches: [
11
+ {
12
+ description: "No tiene padre",
13
+ domain_group: null,
14
+ external_id: "1",
15
+ id: 1,
16
+ name: "element_1",
17
+ parent_id: null,
18
+ parents: null,
19
+ type: null,
20
+ },
21
+ {
22
+ description: "No tiene padre",
23
+ domain_group: null,
24
+ external_id: "2",
25
+ id: 2,
26
+ name: "element_2",
27
+ parent_id: null,
28
+ parents: null,
29
+ type: null,
30
+ },
31
+ {
32
+ description: "el padre es element_2",
33
+ domain_group: null,
34
+ external_id: "3",
35
+ id: 3,
36
+ name: "element_3",
37
+ parent_id: 2,
38
+ parents: null,
39
+ type: null,
40
+ },
41
+ {
42
+ description: "el padre es element_2",
43
+ domain_group: null,
44
+ external_id: "4",
45
+ id: 4,
46
+ name: "element_4",
47
+ parent_id: 2,
48
+ parents: null,
49
+ type: null,
50
+ },
51
+ {
52
+ description: "No tiene padre",
53
+ domain_group: null,
54
+ external_id: "5",
55
+ id: 5,
56
+ name: "element_5",
57
+ parent_id: null,
58
+ parents: null,
59
+ type: null,
60
+ },
61
+ {
62
+ description: "el padre es element_5",
63
+ domain_group: null,
64
+ external_id: "6",
65
+ id: 6,
66
+ name: "element_6",
67
+ parent_id: 5,
68
+ parents: null,
69
+ type: null,
70
+ },
71
+ {
72
+ description: "el padre es element_6",
73
+ domain_group: null,
74
+ external_id: "7",
75
+ id: 7,
76
+ name: "element_7",
77
+ parent_id: 6,
78
+ parents: null,
79
+ type: null,
80
+ },
81
+ ],
82
+ handleRedirect,
83
+ idSelectedBranch: undefined,
84
+ };
85
+
86
+ const renderOpts = {
87
+ messages: {
88
+ en: {
89
+ "domains.search.results.count": "count",
90
+ "domains.search.results.empty": "empty",
91
+ "search.input.placeholder": "searchbox_placeholder",
92
+ "domains.search.error.fields": "No fields for search",
93
+ "domains.notExist": "notExistDomains",
94
+ },
95
+ },
96
+ };
97
+
98
+ describe("BranchViewer", () => {
99
+ it("matches the latest snapshot", () => {
100
+ const { container } = render(<BranchViewer {...props} />, renderOpts);
101
+
102
+ expect(container).toMatchSnapshot();
103
+ });
104
+
105
+ it("should return 'no branches' messages if branch list empty", async () => {
106
+ const customProps = { ...props, branches: [] };
107
+ const { findByText } = render(
108
+ <BranchViewer {...customProps} />,
109
+ renderOpts
110
+ );
111
+
112
+ expect(await findByText("notExistDomains")).toBeInTheDocument();
113
+ });
114
+
115
+ it("should return 'no branches' messages if branch list undefined", async () => {
116
+ const customProps = { ...props, branches: undefined };
117
+ const { findByText } = render(
118
+ <BranchViewer {...customProps} />,
119
+ renderOpts
120
+ );
121
+
122
+ expect(await findByText("notExistDomains")).toBeInTheDocument();
123
+ });
124
+
125
+ it("should return 'no branches' messages if branch list null", async () => {
126
+ const customProps = { ...props, branches: null };
127
+ const { findByText } = render(
128
+ <BranchViewer {...customProps} />,
129
+ renderOpts
130
+ );
131
+
132
+ expect(await findByText("notExistDomains")).toBeInTheDocument();
133
+ });
134
+
135
+ it("with a valid idSelectedBranch should return 'branches' highlighting selected branch and ancestors expanded", async () => {
136
+ const customProps = { ...props, idSelectedBranch: 7 };
137
+ const { container } = render(<BranchViewer {...customProps} />, renderOpts);
138
+
139
+ const expandedElements = container.querySelectorAll(".expanded");
140
+
141
+ expect(expandedElements.length).toBe(2);
142
+
143
+ expandedElements.forEach((element) => {
144
+ const input = element.querySelector("input");
145
+ if (input) {
146
+ if (input.classList.contains("selectedBranch")) {
147
+ expect(input.value).toBe("element_7");
148
+ } else {
149
+ expect(input.value).toBe("element_6");
150
+ }
151
+ }
152
+ });
153
+ });
154
+ });
155
+
156
+ describe("organizeBranches", () => {
157
+ it("organizeBranches creates 'childs' and 'parents' properties", async () => {
158
+ const { queryByText, getByDisplayValue, queryByDisplayValue } = render(
159
+ <BranchViewer {...props} />,
160
+ renderOpts
161
+ );
162
+
163
+ const input1 = getByDisplayValue("element_1");
164
+ const parent1 = input1.closest("div");
165
+ await waitFor(() =>
166
+ expect(parent1.querySelector("i")).not.toBeInTheDocument()
167
+ );
168
+
169
+ await waitFor(() => expect(queryByText("element_3")).toBeNull());
170
+ await waitFor(() => expect(queryByText("element_4")).toBeNull());
171
+
172
+ const input2 = getByDisplayValue("element_2");
173
+ const parent2 = input2.closest("div");
174
+ const sibling2 = parent2.querySelector("i");
175
+ userEvent.click(sibling2);
176
+
177
+ await waitFor(() => {
178
+ expect(queryByDisplayValue(/element_3/)).toBeInTheDocument();
179
+ expect(queryByDisplayValue(/element_4/)).toBeInTheDocument();
180
+ });
181
+
182
+ await waitFor(() => expect(queryByText("element_6")).toBeNull());
183
+ await waitFor(() => expect(queryByText("element_7")).toBeNull());
184
+
185
+ const input3 = getByDisplayValue("element_5");
186
+ const parent3 = input3.closest("div");
187
+ const sibling3 = parent3.querySelector("i");
188
+ userEvent.click(sibling3);
189
+
190
+ await waitFor(() => {
191
+ expect(queryByDisplayValue(/element_6/)).toBeInTheDocument();
192
+
193
+ const input4 = getByDisplayValue("element_6");
194
+ const parent4 = input4.closest("div");
195
+ const sibling4 = parent4.querySelector("i");
196
+ userEvent.click(sibling4);
197
+
198
+ waitFor(() => {
199
+ expect(queryByDisplayValue(/element_7/)).toBeInTheDocument();
200
+ });
201
+ });
202
+ });
203
+
204
+ it("organizeBranches creates 'path' property", async () => {
205
+ const { container, getByPlaceholderText } = render(
206
+ <BranchViewer {...props} />,
207
+ renderOpts
208
+ );
209
+ const searchBox = getByPlaceholderText("searchbox_placeholder");
210
+
211
+ userEvent.click(searchBox);
212
+ userEvent.type(searchBox, "element_7");
213
+
214
+ await waitFor(() => {
215
+ const element = container.querySelector(".brancheViewerSearchResultMeta");
216
+ expect(element).toBeInTheDocument();
217
+ expect(element).toHaveTextContent("element_5 > element_6");
218
+ });
219
+ });
220
+ });
@@ -0,0 +1,209 @@
1
+ import _ from "lodash/fp";
2
+
3
+ import LocalCustomSearch from "../LocalCustomSearch";
4
+
5
+ const collection = [
6
+ {
7
+ description: "No tiene padre",
8
+ domain_group: null,
9
+ external_id: "1",
10
+ id: 1,
11
+ name: "element_1",
12
+ parent_id: null,
13
+ parents: null,
14
+ type: null,
15
+ },
16
+ {
17
+ description: "No tiene padre",
18
+ domain_group: null,
19
+ external_id: "2",
20
+ id: 2,
21
+ name: "element_2",
22
+ parent_id: null,
23
+ parents: null,
24
+ type: null,
25
+ },
26
+ {
27
+ description: "el padre es element_2",
28
+ domain_group: null,
29
+ external_id: "3",
30
+ id: 3,
31
+ name: "element_3",
32
+ parent_id: 2,
33
+ parents: null,
34
+ type: null,
35
+ },
36
+ {
37
+ description: "el padre es element_2",
38
+ domain_group: null,
39
+ external_id: "4",
40
+ id: 4,
41
+ name: "element_4",
42
+ parent_id: 2,
43
+ parents: null,
44
+ type: null,
45
+ },
46
+ {
47
+ description: "No tiene padre",
48
+ domain_group: null,
49
+ external_id: "5",
50
+ id: 5,
51
+ name: "element_5",
52
+ parent_id: null,
53
+ parents: null,
54
+ type: null,
55
+ },
56
+ {
57
+ description: "el padre es element_5",
58
+ domain_group: null,
59
+ external_id: "6",
60
+ id: 6,
61
+ name: "element_6",
62
+ parent_id: 5,
63
+ parents: null,
64
+ type: null,
65
+ },
66
+ {
67
+ description: "el padre es element_6",
68
+ domain_group: null,
69
+ external_id: "7",
70
+ id: 7,
71
+ name: "element_7",
72
+ parent_id: 6,
73
+ parents: null,
74
+ type: null,
75
+ },
76
+ ];
77
+
78
+ const searchFields = ["description", "name"];
79
+ const getOneOrMany = false;
80
+
81
+ describe("LocalCustomSearch", () => {
82
+ it("LocalCustomSearch filters collection successfully", () => {
83
+ const firstSearch = "No tiene padre";
84
+ const callbackMock = jest.fn();
85
+ const countFirstSearch = 3;
86
+ const resultFirstSearch = collection.filter((element) =>
87
+ element.description.includes(firstSearch)
88
+ );
89
+
90
+ expect(
91
+ LocalCustomSearch(
92
+ collection,
93
+ firstSearch,
94
+ callbackMock,
95
+ searchFields,
96
+ getOneOrMany
97
+ )
98
+ ).toBe(countFirstSearch);
99
+
100
+ expect(callbackMock).toHaveBeenCalledWith(resultFirstSearch);
101
+
102
+ const secondSearch = "padre";
103
+ const countSecondSearch = 7;
104
+
105
+ const resultSecondSearch = collection.filter((element) =>
106
+ element.description.includes(secondSearch)
107
+ );
108
+
109
+ expect(
110
+ LocalCustomSearch(
111
+ collection,
112
+ secondSearch,
113
+ callbackMock,
114
+ searchFields,
115
+ getOneOrMany
116
+ )
117
+ ).toBe(countSecondSearch);
118
+
119
+ expect(callbackMock).toHaveBeenCalledWith(resultSecondSearch);
120
+ });
121
+
122
+ it("LocalCustomSearch filters empty collection", () => {
123
+ const search = "custom search";
124
+ const customCollection = [];
125
+ const callbackMock = jest.fn();
126
+
127
+ expect(
128
+ LocalCustomSearch(
129
+ customCollection,
130
+ search,
131
+ callbackMock,
132
+ searchFields,
133
+ getOneOrMany
134
+ )
135
+ ).toBe(customCollection.length);
136
+
137
+ expect(callbackMock).toHaveBeenCalledWith(customCollection);
138
+ });
139
+
140
+ it("LocalCustomSearch filters collection but searchFields is empty", () => {
141
+ const search = "custom search";
142
+ const customSearchFields = [];
143
+ const callbackMock = jest.fn();
144
+
145
+ expect(
146
+ LocalCustomSearch(
147
+ collection,
148
+ search,
149
+ callbackMock,
150
+ customSearchFields,
151
+ getOneOrMany
152
+ )
153
+ ).toBe(-1);
154
+
155
+ expect(callbackMock).not.toHaveBeenCalled();
156
+ });
157
+
158
+ it("LocalCustomSearch filters collection but searchFields has fields that not exists in elements", () => {
159
+ const search = "custom search";
160
+ const callbackMock = jest.fn();
161
+
162
+ expect(
163
+ LocalCustomSearch(
164
+ collection,
165
+ search,
166
+ callbackMock,
167
+ ["external_id", "title"],
168
+ getOneOrMany
169
+ )
170
+ ).toBe(0);
171
+
172
+ expect(callbackMock).toHaveBeenCalledWith([]);
173
+ });
174
+
175
+ it("LocalCustomSearch filters collection but searchFields is null", () => {
176
+ const search = "custom search";
177
+ const customSearchFields = null;
178
+ const callbackMock = jest.fn();
179
+
180
+ expect(
181
+ LocalCustomSearch(
182
+ collection,
183
+ search,
184
+ callbackMock,
185
+ customSearchFields,
186
+ getOneOrMany
187
+ )
188
+ ).toBe(-1);
189
+
190
+ expect(callbackMock).not.toHaveBeenCalled();
191
+ });
192
+
193
+ it("LocalCustomSearch returns original collection when searchString is empty", () => {
194
+ const search = "";
195
+ const callbackMock = jest.fn();
196
+
197
+ expect(
198
+ LocalCustomSearch(
199
+ collection,
200
+ search,
201
+ callbackMock,
202
+ searchFields,
203
+ getOneOrMany
204
+ )
205
+ ).toBe(collection.length);
206
+
207
+ expect(callbackMock).toHaveBeenCalledWith(collection);
208
+ });
209
+ });
@@ -0,0 +1,203 @@
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 LocalSearchInput from "../LocalSearchInput";
7
+
8
+ const searchFields = ["description", "name"];
9
+ const props = {
10
+ collection: [
11
+ {
12
+ description: "No tiene padre",
13
+ domain_group: null,
14
+ external_id: "1",
15
+ id: 1,
16
+ name: "element_1",
17
+ parent_id: null,
18
+ parents: null,
19
+ type: null,
20
+ },
21
+ {
22
+ description: "No tiene padre",
23
+ domain_group: null,
24
+ external_id: "2",
25
+ id: 2,
26
+ name: "element_2",
27
+ parent_id: null,
28
+ parents: null,
29
+ type: null,
30
+ },
31
+ {
32
+ description: "el padre es element_2",
33
+ domain_group: null,
34
+ external_id: "3",
35
+ id: 3,
36
+ name: "element_3",
37
+ parent_id: 2,
38
+ parents: null,
39
+ type: null,
40
+ },
41
+ {
42
+ description: "el padre es element_2",
43
+ domain_group: null,
44
+ external_id: "4",
45
+ id: 4,
46
+ name: "element_4",
47
+ parent_id: 2,
48
+ parents: null,
49
+ type: null,
50
+ },
51
+ {
52
+ description: "No tiene padre",
53
+ domain_group: null,
54
+ external_id: "5",
55
+ id: 5,
56
+ name: "element_5",
57
+ parent_id: null,
58
+ parents: null,
59
+ type: null,
60
+ },
61
+ {
62
+ description: "el padre es element_5",
63
+ domain_group: null,
64
+ external_id: "6",
65
+ id: 6,
66
+ name: "element_6",
67
+ parent_id: 5,
68
+ parents: null,
69
+ type: null,
70
+ },
71
+ {
72
+ description: "el padre es element_6",
73
+ domain_group: null,
74
+ external_id: "7",
75
+ id: 7,
76
+ name: "element_7",
77
+ parent_id: 6,
78
+ parents: null,
79
+ type: null,
80
+ },
81
+ ],
82
+ searchFields,
83
+ enabled: true,
84
+ };
85
+
86
+ const renderOpts = {
87
+ messages: {
88
+ en: {
89
+ "domains.search.results.count": "count",
90
+ "domains.search.results.empty": "empty",
91
+ "search.input.placeholder": "searchbox_placeholder",
92
+ "domains.search.error.fields": "No fields for search",
93
+ "domains.notExist": "notExistDomains",
94
+ },
95
+ },
96
+ };
97
+
98
+ describe("LocalSearchInput", () => {
99
+ it("matches the latest snapshot", async () => {
100
+ const searchFunction = jest.fn();
101
+
102
+ const customProps = {
103
+ ...props,
104
+ callback: jest.fn,
105
+ searchFunction: searchFunction,
106
+ };
107
+
108
+ const { container, getByPlaceholderText } = render(
109
+ <LocalSearchInput {...customProps} />,
110
+ renderOpts
111
+ );
112
+
113
+ const searchBox = getByPlaceholderText("searchbox_placeholder");
114
+ userEvent.click(searchBox);
115
+ userEvent.type(searchBox, "padre");
116
+
117
+ await waitFor(() => {
118
+ expect(searchFunction).toHaveBeenCalled();
119
+ expect(container).toMatchSnapshot();
120
+ });
121
+ });
122
+
123
+ it("should return 'no branches' messages if branch list empty", async () => {
124
+ const searchFunction = () => {
125
+ return -1;
126
+ };
127
+
128
+ const customProps = {
129
+ ...props,
130
+ collection: [],
131
+ callback: jest.fn,
132
+ searchFunction: searchFunction,
133
+ };
134
+
135
+ const { queryByText, getByPlaceholderText } = render(
136
+ <LocalSearchInput {...customProps} />,
137
+ renderOpts
138
+ );
139
+
140
+ const searchBox = getByPlaceholderText("searchbox_placeholder");
141
+
142
+ userEvent.click(searchBox);
143
+ userEvent.type(searchBox, "padre");
144
+
145
+ await waitFor(() => {
146
+ expect(queryByText(/No fields for search/)).toBeInTheDocument();
147
+ });
148
+ });
149
+
150
+ it("should return 'no branches' messages if branch list undefined", async () => {
151
+ const searchFunction = () => {
152
+ return -1;
153
+ };
154
+
155
+ const customProps = {
156
+ ...props,
157
+ collection: undefined,
158
+ callback: jest.fn,
159
+ searchFunction: searchFunction,
160
+ };
161
+
162
+ const { queryByText, getByPlaceholderText } = render(
163
+ <LocalSearchInput {...customProps} />,
164
+ renderOpts
165
+ );
166
+
167
+ const searchBox = getByPlaceholderText("searchbox_placeholder");
168
+
169
+ userEvent.click(searchBox);
170
+ userEvent.type(searchBox, "padre");
171
+
172
+ await waitFor(() => {
173
+ expect(queryByText(/No fields for search/)).toBeInTheDocument();
174
+ });
175
+ });
176
+
177
+ it("should return 'no branches' messages if branch list null", async () => {
178
+ const searchFunction = () => {
179
+ return -1;
180
+ };
181
+
182
+ const customProps = {
183
+ ...props,
184
+ collection: null,
185
+ callback: jest.fn,
186
+ searchFunction: searchFunction,
187
+ };
188
+
189
+ const { queryByText, getByPlaceholderText } = render(
190
+ <LocalSearchInput {...customProps} />,
191
+ renderOpts
192
+ );
193
+
194
+ const searchBox = getByPlaceholderText("searchbox_placeholder");
195
+
196
+ userEvent.click(searchBox);
197
+ userEvent.type(searchBox, "padre");
198
+
199
+ await waitFor(() => {
200
+ expect(queryByText(/No fields for search/)).toBeInTheDocument();
201
+ });
202
+ });
203
+ });
@@ -0,0 +1,149 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`BranchViewer matches the latest snapshot 1`] = `
4
+ <div>
5
+ <div
6
+ class="ui segment"
7
+ style="overflow: auto;"
8
+ >
9
+ <div
10
+ class="ui grid"
11
+ >
12
+ <div
13
+ class="row"
14
+ >
15
+ <div
16
+ class="sixteen wide column"
17
+ >
18
+ <div
19
+ class="ui icon input searchbox"
20
+ >
21
+ <input
22
+ placeholder="searchbox_placeholder"
23
+ type="text"
24
+ value=""
25
+ />
26
+ <i
27
+ aria-hidden="true"
28
+ class="search link icon"
29
+ />
30
+ </div>
31
+ </div>
32
+ </div>
33
+ <div
34
+ class="row"
35
+ >
36
+ <div
37
+ class="sixteen wide column"
38
+ >
39
+ <div
40
+ class="column"
41
+ >
42
+ <div
43
+ class="row"
44
+ >
45
+ <ul
46
+ style="list-style-type: none; padding: 0px;"
47
+ >
48
+ <li>
49
+ <div
50
+ style="display: flex; flex-direction: row; align-items: self-start;"
51
+ >
52
+ <div
53
+ class="ui disabled left labeled input hierarchyNode"
54
+ >
55
+ <input
56
+ class=" "
57
+ disabled=""
58
+ tabindex="-1"
59
+ type="text"
60
+ value="element_1"
61
+ />
62
+ </div>
63
+ </div>
64
+ </li>
65
+ </ul>
66
+ </div>
67
+ </div>
68
+ <div
69
+ class="column"
70
+ >
71
+ <div
72
+ class="row"
73
+ >
74
+ <ul
75
+ style="list-style-type: none; padding: 0px;"
76
+ >
77
+ <li>
78
+ <div
79
+ style="display: flex; flex-direction: row; align-items: self-start;"
80
+ >
81
+ <div
82
+ class="ui disabled left labeled input hierarchyNode"
83
+ >
84
+ <div
85
+ class="ui basic label"
86
+ style="cursor: pointer;"
87
+ >
88
+ <i
89
+ aria-hidden="true"
90
+ class="circle fitted icon"
91
+ />
92
+ </div>
93
+ <input
94
+ class=" "
95
+ disabled=""
96
+ tabindex="-1"
97
+ type="text"
98
+ value="element_2"
99
+ />
100
+ </div>
101
+ </div>
102
+ </li>
103
+ </ul>
104
+ </div>
105
+ </div>
106
+ <div
107
+ class="column"
108
+ >
109
+ <div
110
+ class="row"
111
+ >
112
+ <ul
113
+ style="list-style-type: none; padding: 0px;"
114
+ >
115
+ <li>
116
+ <div
117
+ style="display: flex; flex-direction: row; align-items: self-start;"
118
+ >
119
+ <div
120
+ class="ui disabled left labeled input hierarchyNode"
121
+ >
122
+ <div
123
+ class="ui basic label"
124
+ style="cursor: pointer;"
125
+ >
126
+ <i
127
+ aria-hidden="true"
128
+ class="circle fitted icon"
129
+ />
130
+ </div>
131
+ <input
132
+ class=" "
133
+ disabled=""
134
+ tabindex="-1"
135
+ type="text"
136
+ value="element_5"
137
+ />
138
+ </div>
139
+ </div>
140
+ </li>
141
+ </ul>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ `;
@@ -0,0 +1,30 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`LocalSearchInput matches the latest snapshot 1`] = `
4
+ <div>
5
+ <div
6
+ class="ui icon input searchbox"
7
+ >
8
+ <input
9
+ placeholder="searchbox_placeholder"
10
+ type="text"
11
+ value=""
12
+ />
13
+ <i
14
+ aria-hidden="true"
15
+ class="search link icon"
16
+ />
17
+ </div>
18
+ <h4
19
+ class="ui header"
20
+ >
21
+ <i
22
+ aria-hidden="true"
23
+ class="search icon"
24
+ />
25
+ <div
26
+ class="content"
27
+ />
28
+ </h4>
29
+ </div>
30
+ `;
@@ -3,6 +3,7 @@ import AdminMenu from "./AdminMenu";
3
3
  import Alert from "./Alert";
4
4
  import Authorized from "./Authorized";
5
5
  import AvailableFilters from "./AvailableFilters";
6
+ import BranchViewer from "./BranchViewer";
6
7
  import CardGroupsAccordion from "./CardGroupsAccordion";
7
8
  import CatalogMenu from "./CatalogMenu";
8
9
  import Comments from "./Comments";
@@ -59,6 +60,7 @@ export {
59
60
  Alert,
60
61
  Authorized,
61
62
  AvailableFilters,
63
+ BranchViewer,
62
64
  CardGroupsAccordion,
63
65
  CatalogMenu,
64
66
  Comments,
@@ -47,6 +47,7 @@ export default {
47
47
  "dateFilter.weeks": "weeks ago",
48
48
  "dateFilter.months": "months ago",
49
49
  "dateFilter.years": "years ago",
50
+ "domains.notExist": "There are no domains",
50
51
  emptyBucket: "(Empty)",
51
52
  "error.content": "Please report this error to the Truedat team.",
52
53
  "error.duplicated": "duplicated",
@@ -48,6 +48,7 @@ export default {
48
48
  "dateFilter.weeks": "semanas",
49
49
  "dateFilter.months": "meses",
50
50
  "dateFilter.years": "años",
51
+ "domains.notExist": "No existen dominios",
51
52
  emptyBucket: "(Vacío)",
52
53
  "error.content": "Por favor, coméntaselo al equipo de Truedat.",
53
54
  "error.duplicated": "duplicado",
@@ -63,7 +64,6 @@ export default {
63
64
  "hierarchy.multiple.placeholder": "Seleccionar Jerarquías",
64
65
  "hierarchy.selector.placeholder": "Seleccionar Jerarquía",
65
66
  "i18n.actions.createMessage": "Crear mensaje",
66
-
67
67
  "i18n.message.form.messageId.placeholder": "Id del mensaje",
68
68
  "i18n.message.form.definition.placeholder": "Definición",
69
69
  "i18n.message.form.description.placeholder": "Descripción",
@@ -0,0 +1,16 @@
1
+
2
+ .brancheViewerSearchResultHeader{
3
+ font-size: 1rem !important;
4
+ }
5
+
6
+ .brancheViewerSearchResultMeta{
7
+ font-size: 0.8rem !important;
8
+ }
9
+
10
+ .selectedBranch{
11
+ background-color: #0d0d0d !important;
12
+ color: #fff !important;
13
+ font-weight: 1000 !important;
14
+ }
15
+
16
+
@@ -0,0 +1,7 @@
1
+ .ui.input.searchbox {
2
+ width: 100%;
3
+ height: 36px;
4
+ border-radius: 4px;
5
+ border: solid 1px #e8e9e9;
6
+ background-color: #ffffff
7
+ }