@truedat/core 7.0.5 → 7.0.6

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.
@@ -12,11 +12,6 @@ export const match =
12
12
  _.contains(query)(lowerDeburr(name));
13
13
  export const matchAny = recursiveMatch(match);
14
14
 
15
- const maybeMapKeys = _.map((option) => ({
16
- ...option,
17
- id: _.has("key")(option) ? option.key : option.id,
18
- }));
19
-
20
15
  export const SelectedLabels = ({
21
16
  disabled,
22
17
  onClick,
@@ -64,50 +59,83 @@ const labelValues = _.cond([
64
59
  ]);
65
60
 
66
61
  export const TreeSelector = ({
62
+ allNodesOpen,
67
63
  check = false,
64
+ className = "",
68
65
  disabled = false,
69
66
  error,
70
67
  label,
71
68
  labels = false,
72
69
  multiple = false,
73
70
  name,
71
+ notDropdown,
74
72
  onBlur,
75
73
  onChange,
76
74
  options,
77
75
  minDepth = 0,
76
+ ascendants,
78
77
  placeholder,
79
78
  required = false,
80
- value: initialValue,
79
+ value,
81
80
  }) => {
82
- const [value, setValue] = useState(
83
- _.defaultTo(multiple ? [] : null)(initialValue)
84
- );
85
81
  const [query, setQuery] = useState();
86
- const [open, setOpen] = useState([]);
82
+ const [open, setOpen] = useState(null);
87
83
  const [displayed, setDisplayed] = useState([]);
84
+ const [currentAllNodesOpen, setCurrentAllNodesOpen] = useState(false);
88
85
 
89
- useEffect(() => {
90
- if (initialValue !== value)
91
- setValue(_.defaultTo(multiple ? [] : null)(initialValue));
92
- }, [initialValue]);
86
+ const selected = (optionId) =>
87
+ multiple ? _.contains(optionId)(value) : value === optionId;
93
88
 
94
89
  useEffect(() => {
90
+ if (!_.isEqual(currentAllNodesOpen, allNodesOpen)) {
91
+ if (allNodesOpen) {
92
+ const allNodeIds = _.map("id", options);
93
+ setOpen(allNodeIds);
94
+ setDisplayed(allNodeIds);
95
+ setCurrentAllNodesOpen(allNodesOpen);
96
+ } else {
97
+ setOpen([]);
98
+ setDisplayed([]);
99
+ setCurrentAllNodesOpen(allNodesOpen);
100
+ }
101
+ return;
102
+ }
103
+
95
104
  const ids = childIds(open)(options);
96
- setDisplayed((displayed) => _.union(displayed)(ids));
97
- }, [options, open, value]);
105
+ const missingAscendants = _.difference(ascendants, open);
106
+ setDisplayed(_.union(displayed)(ids));
107
+
108
+ if (missingAscendants.length > 0) {
109
+ setOpen(_.union(open, ascendants));
110
+ missingAscendants.forEach((ancestorId) => handleOpen(ancestorId));
111
+ }
112
+ if (notDropdown && value && open && !open.find((v) => v === value)) {
113
+ handleOpen(value);
114
+ }
115
+ }, [options, open, ascendants, allNodesOpen]);
116
+
117
+ useEffect(() => {
118
+ if (notDropdown && value && open && !open.find((v) => v === value)) {
119
+ handleOpen(value);
120
+ }
121
+ }, [value]);
98
122
 
99
123
  const handleOpen = (id) => {
100
- const option = _.find({ id })(maybeMapKeys(options));
101
- const isOpen = _.contains(id)(open);
102
- const childIds = _.map("id")(option.children);
103
- const descendentIds = descendents(option);
104
-
105
- if (isOpen) {
106
- setOpen(_.without([id, ...descendentIds])(open));
107
- setDisplayed(_.without(descendentIds)(displayed));
108
- } else {
109
- setOpen(_.union([id])(open));
110
- setDisplayed(_.union(childIds)(displayed));
124
+ const option = _.find({ id })(options);
125
+ if (option) {
126
+ const isOpen = _.contains(id)(open);
127
+ const childIds = _.map("id")(option.children);
128
+ const descendentIds = descendents(option);
129
+
130
+ if (isOpen) {
131
+ const subTree = [id, ...descendentIds];
132
+ const collapsible = !_.some(selected)(subTree);
133
+ collapsible && setOpen(_.without(subTree)(open));
134
+ collapsible && setDisplayed(_.without(descendentIds)(displayed));
135
+ } else {
136
+ setOpen(_.union([id])(open));
137
+ setDisplayed(_.union(childIds)(displayed));
138
+ }
111
139
  }
112
140
  };
113
141
 
@@ -120,14 +148,16 @@ export const TreeSelector = ({
120
148
  const handleSearch = (e, { value }) => {
121
149
  e.preventDefault();
122
150
  setQuery(lowerDeburr(value));
123
- if (!_.isEmpty(value)) {
151
+ if (_.isEmpty(value)) {
152
+ setOpen([]);
153
+ setDisplayed([]);
154
+ } else {
124
155
  displayAll();
125
156
  }
126
157
  };
127
158
 
128
159
  const handleClick = (e, id) => {
129
160
  const nextValue = multiple ? toggle(id)(value) : value === id ? null : id;
130
- setValue(nextValue);
131
161
  onChange && onChange(e, { value: nextValue });
132
162
  };
133
163
 
@@ -141,7 +171,7 @@ export const TreeSelector = ({
141
171
  <SelectedLabels
142
172
  disabled={disabled}
143
173
  placeholder={placeholder}
144
- options={maybeMapKeys(options)}
174
+ options={options}
145
175
  onClick={handleClick}
146
176
  value={labelValues(value)}
147
177
  />
@@ -154,7 +184,6 @@ export const TreeSelector = ({
154
184
  const items = _.flow(
155
185
  filterSearch,
156
186
  filterDisplayed,
157
- maybeMapKeys,
158
187
  _.map((option) => (
159
188
  <DropdownMenuItem
160
189
  key={option?.id}
@@ -165,32 +194,21 @@ export const TreeSelector = ({
165
194
  canOpen={!_.isEmpty(option.children)}
166
195
  level={option.level}
167
196
  disabled={!isEnabled(option, minDepth)}
168
- selected={multiple ? _.contains(option.id)(value) : value === option.id}
197
+ selected={selected(option.id)}
198
+ className={ascendants?.includes(String(option?.id)) ? "ascendant" : ""}
169
199
  {...option}
170
200
  />
171
201
  ))
172
202
  )(options);
173
203
 
174
- return (
175
- <Form.Dropdown
176
- className="fix-dropdown-selector"
177
- error={error}
178
- floating
179
- label={label}
180
- multiple={multiple}
181
- name={name}
182
- onBlur={onBlur}
183
- required={required}
184
- trigger={trigger}
185
- upward={false}
186
- value={value}
187
- disabled={disabled}
188
- >
189
- <Dropdown.Menu>
204
+ const renderTreeSelector = () => {
205
+ const content = (
206
+ <Dropdown.Menu className={notDropdown ? className : ""}>
190
207
  <Input
208
+ fluid
191
209
  icon="search"
192
210
  iconPosition="left"
193
- className="search"
211
+ className={`search ${notDropdown ? "notDropdownInput" : ""}`}
194
212
  onKeyDown={(e) => {
195
213
  if (e.key === " ") {
196
214
  e.stopPropagation();
@@ -202,20 +220,52 @@ export const TreeSelector = ({
202
220
  e.stopPropagation();
203
221
  }}
204
222
  />
205
- <Dropdown.Menu scrolling>{items}</Dropdown.Menu>
223
+ <Dropdown.Menu
224
+ scrolling
225
+ className={notDropdown ? "notDropdownSelector" : ""}
226
+ >
227
+ {items}
228
+ </Dropdown.Menu>
206
229
  </Dropdown.Menu>
207
- </Form.Dropdown>
208
- );
230
+ );
231
+
232
+ return !notDropdown ? (
233
+ <Form.Dropdown
234
+ className={`fix-dropdown-selector ${!notDropdown ? className : ""}`}
235
+ error={error}
236
+ floating
237
+ label={label}
238
+ multiple={multiple}
239
+ name={name}
240
+ onBlur={onBlur}
241
+ required={required}
242
+ trigger={trigger}
243
+ upward={false}
244
+ value={value}
245
+ disabled={disabled}
246
+ >
247
+ {content}
248
+ </Form.Dropdown>
249
+ ) : (
250
+ <>{content}</>
251
+ );
252
+ };
253
+
254
+ return renderTreeSelector();
209
255
  };
210
256
 
211
257
  TreeSelector.propTypes = {
258
+ allNodeIds: PropTypes.bool,
259
+ ascendants: PropTypes.array,
212
260
  check: PropTypes.bool,
261
+ className: PropTypes.string,
213
262
  disabled: PropTypes.bool,
214
263
  error: PropTypes.bool,
215
264
  label: PropTypes.string,
216
265
  labels: PropTypes.bool,
217
266
  multiple: PropTypes.bool,
218
267
  name: PropTypes.string,
268
+ notDropdown: PropTypes.bool,
219
269
  onBlur: PropTypes.func,
220
270
  onChange: PropTypes.func,
221
271
  options: PropTypes.array,
@@ -4,7 +4,7 @@ import { AddResourceMember } from "../AddResourceMember";
4
4
 
5
5
  const props = {
6
6
  type: "domain",
7
- id: 1,
7
+ id: "1",
8
8
  onSuccess: jest.fn(),
9
9
  };
10
10
 
@@ -12,7 +12,24 @@ const renderOpts = { mocks: [domainsMock(variables)] };
12
12
 
13
13
  describe("<DomainSelector />", () => {
14
14
  it("matches latest snapshot", async () => {
15
- const props = { action, onChange: jest.fn() };
15
+ const props = { action, onChange: jest.fn(), className: "test className" };
16
+ const { container, queryByText } = render(
17
+ <DomainSelector {...props} />,
18
+ renderOpts
19
+ );
20
+ await waitFor(() => {
21
+ expect(queryByText(/fooDomain/)).toBeInTheDocument();
22
+ });
23
+ expect(container).toMatchSnapshot();
24
+ });
25
+
26
+ it("matches latest snapshot with notDropdow parameter", async () => {
27
+ const props = {
28
+ action,
29
+ onChange: jest.fn(),
30
+ className: "test className",
31
+ notDropdown: true,
32
+ };
16
33
  const { container, queryByText } = render(
17
34
  <DomainSelector {...props} />,
18
35
  renderOpts
@@ -0,0 +1,42 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import Hierarchy from "../Hierarchy";
4
+ import en from "../../../../df/src/messages/en";
5
+
6
+ const hierarchy = {
7
+ id: 1,
8
+ name: "Baggins",
9
+ description: "bar",
10
+ nodes: [
11
+ {
12
+ id: 11,
13
+ name: "Fosco",
14
+ parentId: null,
15
+ },
16
+ {
17
+ id: 12,
18
+ name: "Fosco children",
19
+ parentId: 11,
20
+ },
21
+ ],
22
+ };
23
+ const props = { hierarchy: hierarchy, isEditionMode: false };
24
+
25
+ const renderOpts = {
26
+ messages: { en: en },
27
+ };
28
+
29
+ describe("<Hierarchy />", () => {
30
+ it("matches the last snapshot with edition mode false", () => {
31
+ const { container } = render(<Hierarchy {...props} />, renderOpts);
32
+ expect(container).toMatchSnapshot();
33
+ });
34
+
35
+ it("matches snapshot with edition mode", () => {
36
+ const { container } = render(
37
+ <Hierarchy {...{ ...props, isEditionMode: true }} />,
38
+ renderOpts
39
+ );
40
+ expect(container).toMatchSnapshot();
41
+ });
42
+ });
@@ -0,0 +1,203 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import { render } from "@truedat/test/render";
4
+ import HierarchyNodeFinder from "../HierarchyNodeFinder";
5
+
6
+ jest.mock("@apollo/client", () => ({
7
+ ...jest.requireActual("@apollo/client"),
8
+ useQuery: jest.fn(),
9
+ }));
10
+
11
+ const nodes = [
12
+ {
13
+ description: "No tiene padre",
14
+ domain_group: null,
15
+ external_id: "1",
16
+ id: 1,
17
+ name: "element_1",
18
+ parent_id: null,
19
+ parents: null,
20
+ type: null,
21
+ },
22
+ {
23
+ description: "No tiene padre",
24
+ domain_group: null,
25
+ external_id: "2",
26
+ id: 2,
27
+ name: "element_2",
28
+ parent_id: null,
29
+ parents: null,
30
+ type: null,
31
+ },
32
+ {
33
+ description: "el padre es element_2",
34
+ domain_group: null,
35
+ external_id: "3",
36
+ id: 3,
37
+ name: "element_3",
38
+ parent_id: 2,
39
+ parents: null,
40
+ type: null,
41
+ },
42
+ {
43
+ description: "el padre es element_2",
44
+ domain_group: null,
45
+ external_id: "4",
46
+ id: 4,
47
+ name: "element_4",
48
+ parent_id: 2,
49
+ parents: null,
50
+ type: null,
51
+ },
52
+ {
53
+ description: "No tiene padre",
54
+ domain_group: null,
55
+ external_id: "5",
56
+ id: 5,
57
+ name: "element_5",
58
+ parent_id: null,
59
+ parents: null,
60
+ type: null,
61
+ },
62
+ {
63
+ description: "el padre es element_5",
64
+ domain_group: null,
65
+ external_id: "6",
66
+ id: 6,
67
+ name: "element_6",
68
+ parent_id: 5,
69
+ parents: null,
70
+ type: null,
71
+ },
72
+ {
73
+ description: "el padre es element_6",
74
+ domain_group: null,
75
+ external_id: "7",
76
+ id: 7,
77
+ name: "element_7",
78
+ parent_id: 6,
79
+ parents: null,
80
+ type: null,
81
+ },
82
+ ];
83
+ const idSelectedNode = 7;
84
+
85
+ const props = {
86
+ nodes,
87
+ idSelectedNode,
88
+ };
89
+
90
+ const renderOpts = {
91
+ messages: {
92
+ en: {
93
+ "domains.notExist": "notExistDomains",
94
+ "domain.selector.placeholder": "Select a domain...",
95
+ "actions.open.all": "Open all",
96
+ },
97
+ },
98
+ };
99
+
100
+ describe("HierarchyNodeFinder", () => {
101
+ it("matches the latest snapshot", () => {
102
+ require("@apollo/client").useQuery.mockReturnValue({
103
+ loading: false,
104
+ error: null,
105
+ data: {
106
+ domains: [
107
+ {
108
+ __typename: "Domain",
109
+ id: "1",
110
+ external_id: "element_1",
111
+ name: "element_1",
112
+ parentId: null,
113
+ actions: [],
114
+ },
115
+ {
116
+ __typename: "Domain",
117
+ id: "2",
118
+ name: "element_2",
119
+ external_id: "element_2",
120
+ parentId: null,
121
+ actions: [],
122
+ },
123
+ {
124
+ __typename: "Domain",
125
+ id: "3",
126
+ external_id: "element_3",
127
+ name: "element_3",
128
+ parentId: "2",
129
+ actions: [],
130
+ },
131
+ {
132
+ __typename: "Domain",
133
+ id: "4",
134
+ external_id: "element_4",
135
+ name: "element_4",
136
+ parentId: "2",
137
+ actions: [],
138
+ },
139
+ {
140
+ __typename: "Domain",
141
+ id: "5",
142
+ external_id: "element_5",
143
+ name: "element_5",
144
+ parentId: null,
145
+ actions: [],
146
+ },
147
+ {
148
+ __typename: "Domain",
149
+ id: "6",
150
+ external_id: "element_6",
151
+ name: "element_6",
152
+ parentId: "5",
153
+ actions: [],
154
+ },
155
+ {
156
+ __typename: "Domain",
157
+ id: "7",
158
+ external_id: "element_7",
159
+ name: "element_7",
160
+ parentId: "6",
161
+ actions: [],
162
+ },
163
+ ],
164
+ },
165
+ });
166
+ const { container } = render(
167
+ <HierarchyNodeFinder {...props} />,
168
+ renderOpts
169
+ );
170
+
171
+ expect(container).toMatchSnapshot();
172
+ });
173
+
174
+ it("should return 'no nodes' messages if node list empty", async () => {
175
+ const customProps = { ...props, nodes: [] };
176
+ const { findByText } = render(
177
+ <HierarchyNodeFinder {...customProps} />,
178
+ renderOpts
179
+ );
180
+
181
+ expect(await findByText("notExistDomains")).toBeInTheDocument();
182
+ });
183
+
184
+ it("should return 'no nodes' messages if node list undefined", async () => {
185
+ const customProps = { ...props, nodes: undefined };
186
+ const { findByText } = render(
187
+ <HierarchyNodeFinder {...customProps} />,
188
+ renderOpts
189
+ );
190
+
191
+ expect(await findByText("notExistDomains")).toBeInTheDocument();
192
+ });
193
+
194
+ it("should return 'no nodes' messages if node list null", async () => {
195
+ const customProps = { ...props, nodes: null };
196
+ const { findByText } = render(
197
+ <HierarchyNodeFinder {...customProps} />,
198
+ renderOpts
199
+ );
200
+
201
+ expect(await findByText("notExistDomains")).toBeInTheDocument();
202
+ });
203
+ });
@@ -11,7 +11,6 @@ jest.spyOn(React, "useContext").mockImplementation(() => intl);
11
11
  jest.mock("@truedat/core/hooks/useAclEntries");
12
12
  jest.mock("react-router-dom", () => ({
13
13
  ...jest.requireActual("react-router-dom"),
14
- useParams: () => ({ id: 1 }),
15
14
  }));
16
15
 
17
16
  jest.mock("@truedat/core/hooks/useAclEntries", () => ({
@@ -74,7 +73,7 @@ describe("<ResourceMembers />", () => {
74
73
  ],
75
74
  };
76
75
 
77
- const props = { type: "domain" };
76
+ const props = { type: "domain", id: 1 };
78
77
 
79
78
  useAclEntries.mockReturnValue({
80
79
  data: { ...aclEntries, actions: { canCreate: true } },
@@ -13,7 +13,22 @@ const renderOpts = {};
13
13
 
14
14
  describe("<TreeSelector />", () => {
15
15
  it("matches latest snapshot", () => {
16
- const props = { options, placeholder: "Select a domain" };
16
+ const props = {
17
+ options,
18
+ placeholder: "Select a domain",
19
+ className: "testClassName",
20
+ };
21
+ const { container } = render(<TreeSelector {...props} />, renderOpts);
22
+ expect(container).toMatchSnapshot();
23
+ });
24
+
25
+ it("matches latest snapshot with notDropdown parameter", () => {
26
+ const props = {
27
+ options,
28
+ placeholder: "Select a domain",
29
+ className: "testClassName",
30
+ notDropdown: true,
31
+ };
17
32
  const { container } = render(<TreeSelector {...props} />, renderOpts);
18
33
  expect(container).toMatchSnapshot();
19
34
  });
@@ -23,6 +38,7 @@ describe("<TreeSelector />", () => {
23
38
  onChange: jest.fn(),
24
39
  options,
25
40
  placeholder: "Select a domain",
41
+ value: [],
26
42
  };
27
43
  const { getByText, getByRole } = render(
28
44
  <TreeSelector multiple {...props} />,
@@ -70,6 +86,7 @@ describe("<TreeSelector />", () => {
70
86
  options,
71
87
  placeholder: "Select a domain",
72
88
  minDepth: minDepth,
89
+ value: [],
73
90
  };
74
91
 
75
92
  const { getByText, getByRole } = render(
@@ -116,6 +133,7 @@ describe("<TreeSelector />", () => {
116
133
  options,
117
134
  placeholder: "Select a domain",
118
135
  minDepth: minDepth,
136
+ value: [],
119
137
  };
120
138
 
121
139
  const { getByText, getByRole } = render(
@@ -3,7 +3,7 @@
3
3
  exports[`<DomainSelector /> matches latest snapshot 1`] = `
4
4
  <div>
5
5
  <div
6
- class="field fix-dropdown-selector"
6
+ class="field fix-dropdown-selector test className"
7
7
  >
8
8
  <div
9
9
  aria-disabled="false"
@@ -24,7 +24,7 @@ exports[`<DomainSelector /> matches latest snapshot 1`] = `
24
24
  class="menu transition"
25
25
  >
26
26
  <div
27
- class="ui left icon input search"
27
+ class="ui fluid left icon input search "
28
28
  >
29
29
  <input
30
30
  type="text"
@@ -100,3 +100,84 @@ exports[`<DomainSelector /> matches latest snapshot 1`] = `
100
100
  </div>
101
101
  </div>
102
102
  `;
103
+
104
+ exports[`<DomainSelector /> matches latest snapshot with notDropdow parameter 1`] = `
105
+ <div>
106
+ <div
107
+ class="menu transition test className"
108
+ >
109
+ <div
110
+ class="ui fluid left icon input search notDropdownInput"
111
+ >
112
+ <input
113
+ type="text"
114
+ />
115
+ <i
116
+ aria-hidden="true"
117
+ class="search icon"
118
+ />
119
+ </div>
120
+ <div
121
+ class="scrolling menu transition notDropdownSelector"
122
+ >
123
+ <div
124
+ aria-selected="false"
125
+ class="item"
126
+ role="option"
127
+ >
128
+ <div
129
+ style="margin-left: 0px;"
130
+ >
131
+ <i
132
+ aria-hidden="true"
133
+ class="plus icon"
134
+ />
135
+ <span
136
+ style="opacity: 1;"
137
+ >
138
+ barDomain
139
+ </span>
140
+ </div>
141
+ </div>
142
+ <div
143
+ aria-selected="false"
144
+ class="item"
145
+ role="option"
146
+ >
147
+ <div
148
+ style="margin-left: 0px;"
149
+ >
150
+ <i
151
+ aria-hidden="true"
152
+ class="icon"
153
+ />
154
+ <span
155
+ style="opacity: 1;"
156
+ >
157
+ bazDomain
158
+ </span>
159
+ </div>
160
+ </div>
161
+ <div
162
+ aria-selected="false"
163
+ class="item"
164
+ role="option"
165
+ >
166
+ <div
167
+ style="margin-left: 0px;"
168
+ >
169
+ <i
170
+ aria-hidden="true"
171
+ class="icon"
172
+ />
173
+ <span
174
+ style="opacity: 1;"
175
+ >
176
+ fooDomain
177
+ </span>
178
+ </div>
179
+ </div>
180
+ </div>
181
+ </div>
182
+ </div>
183
+ `;