@truedat/core 8.5.8 → 8.6.0
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 +4 -4
- package/src/components/AddMemberForm.js +3 -1
- package/src/components/ResourceMember.js +5 -5
- package/src/components/__tests__/AddMemberForm.spec.js +38 -6
- package/src/components/__tests__/ResourceMembers.spec.js +38 -3
- package/src/components/__tests__/__snapshots__/AddMemberForm.spec.js.snap +14 -0
- package/src/components/__tests__/__snapshots__/ResourceMembers.spec.js.snap +22 -0
- package/src/selectors/__tests__/getRecipients.spec.js +365 -0
- package/src/selectors/getRecipients.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/core",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.6.0",
|
|
4
4
|
"description": "Truedat Web Core",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"@testing-library/jest-dom": "^6.6.3",
|
|
55
55
|
"@testing-library/react": "^16.3.0",
|
|
56
56
|
"@testing-library/user-event": "^14.6.1",
|
|
57
|
-
"@truedat/test": "8.
|
|
57
|
+
"@truedat/test": "8.6.0",
|
|
58
58
|
"identity-obj-proxy": "^3.0.0",
|
|
59
59
|
"jest": "^29.7.0",
|
|
60
60
|
"redux-saga-test-plan": "^4.0.6"
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"@tiptap/starter-kit": "^3.20.0",
|
|
70
70
|
"@xyflow/react": "^12.6.4",
|
|
71
71
|
"axios": "^1.15.0",
|
|
72
|
-
"dompurify": "^3.
|
|
72
|
+
"dompurify": "^3.4.0",
|
|
73
73
|
"elkjs": "^0.10.0",
|
|
74
74
|
"graphql": "^16.11.0",
|
|
75
75
|
"lodash": "^4.17.21",
|
|
@@ -95,5 +95,5 @@
|
|
|
95
95
|
"swr": "^2.3.3",
|
|
96
96
|
"turndown": "^7.2.2"
|
|
97
97
|
},
|
|
98
|
-
"gitHead": "
|
|
98
|
+
"gitHead": "9aa93d0e27de7c0fcb472d58eb69aeaf6a2dd6bc"
|
|
99
99
|
}
|
|
@@ -31,6 +31,8 @@ const roleOptions = (roles) =>
|
|
|
31
31
|
_.map(roleOption)
|
|
32
32
|
)(roles);
|
|
33
33
|
|
|
34
|
+
const selectOption = _.pick(["key", "text", "value", "id", "icon"]);
|
|
35
|
+
|
|
34
36
|
export const AddMemberForm = ({ resource, onSuccess, roles, options }) => {
|
|
35
37
|
const { trigger: addDomainMember, isMutating: isSubmitting } =
|
|
36
38
|
useAclEntryCreate(resource);
|
|
@@ -83,7 +85,7 @@ export const AddMemberForm = ({ resource, onSuccess, roles, options }) => {
|
|
|
83
85
|
onSearchChange={onSearch}
|
|
84
86
|
search={_.identity}
|
|
85
87
|
selection
|
|
86
|
-
options={options}
|
|
88
|
+
options={_.map(selectOption)(options)}
|
|
87
89
|
required
|
|
88
90
|
label={{
|
|
89
91
|
children: formatMessage({ id: "domain.member" }),
|
|
@@ -20,7 +20,7 @@ const memberTypeToPopup = {
|
|
|
20
20
|
const retrieveNameFromMember = (principal_type, principal) => {
|
|
21
21
|
switch (principal_type) {
|
|
22
22
|
case "group":
|
|
23
|
-
return null;
|
|
23
|
+
return principal.alias ? principal.name : null;
|
|
24
24
|
case "user":
|
|
25
25
|
return principal.user_name;
|
|
26
26
|
}
|
|
@@ -29,7 +29,7 @@ const retrieveNameFromMember = (principal_type, principal) => {
|
|
|
29
29
|
const retrieveFullNameFromMember = (principal_type, principal) => {
|
|
30
30
|
switch (principal_type) {
|
|
31
31
|
case "group":
|
|
32
|
-
return principal.name;
|
|
32
|
+
return principal.alias ? principal.alias : principal.name;
|
|
33
33
|
case "user":
|
|
34
34
|
return principal.full_name;
|
|
35
35
|
}
|
|
@@ -121,11 +121,11 @@ export const ResourceMember = ({
|
|
|
121
121
|
const fullName = retrieveFullNameFromMember(principal_type, principal);
|
|
122
122
|
const canDelete = _.flow(
|
|
123
123
|
_.path("self.methods"),
|
|
124
|
-
_.includes("DELETE")
|
|
124
|
+
_.includes("DELETE"),
|
|
125
125
|
)(_links);
|
|
126
126
|
const canUpdate = _.flow(
|
|
127
127
|
_.path("self.methods"),
|
|
128
|
-
_.includes("UPDATE")
|
|
128
|
+
_.includes("UPDATE"),
|
|
129
129
|
)(_links);
|
|
130
130
|
return (
|
|
131
131
|
<Card className="domain-member">
|
|
@@ -159,7 +159,7 @@ export const ResourceMember = ({
|
|
|
159
159
|
id: acl_entry_id,
|
|
160
160
|
acl_entry: { description },
|
|
161
161
|
},
|
|
162
|
-
{ onSuccess: onUpdate }
|
|
162
|
+
{ onSuccess: onUpdate },
|
|
163
163
|
);
|
|
164
164
|
}}
|
|
165
165
|
/>
|
|
@@ -25,6 +25,14 @@ describe("<AddMemberForm />", () => {
|
|
|
25
25
|
const options = [
|
|
26
26
|
{ key: 1, text: "john", value: "user_1", id: "1" },
|
|
27
27
|
{ key: 2, text: "mambo", value: "group_2", id: "2" },
|
|
28
|
+
{
|
|
29
|
+
key: 3,
|
|
30
|
+
text: "Group Alias",
|
|
31
|
+
value: "group_3",
|
|
32
|
+
id: "3",
|
|
33
|
+
name: "actual_group_name",
|
|
34
|
+
alias: "Group Alias",
|
|
35
|
+
},
|
|
28
36
|
];
|
|
29
37
|
const roles = [
|
|
30
38
|
{ key: 1, text: "role1", value: "role1" },
|
|
@@ -46,8 +54,8 @@ describe("<AddMemberForm />", () => {
|
|
|
46
54
|
// Submit button should initially be disabled
|
|
47
55
|
await waitFor(async () =>
|
|
48
56
|
expect(
|
|
49
|
-
await rendered.findByRole("button", { name: /add_member/i })
|
|
50
|
-
).toBeDisabled()
|
|
57
|
+
await rendered.findByRole("button", { name: /add_member/i }),
|
|
58
|
+
).toBeDisabled(),
|
|
51
59
|
);
|
|
52
60
|
|
|
53
61
|
// Description
|
|
@@ -57,12 +65,12 @@ describe("<AddMemberForm />", () => {
|
|
|
57
65
|
// Submit button should now be enabled
|
|
58
66
|
await waitFor(async () =>
|
|
59
67
|
expect(
|
|
60
|
-
await rendered.findByRole("button", { name: /add_member/i })
|
|
61
|
-
).not.toBeDisabled()
|
|
68
|
+
await rendered.findByRole("button", { name: /add_member/i }),
|
|
69
|
+
).not.toBeDisabled(),
|
|
62
70
|
);
|
|
63
71
|
|
|
64
72
|
await user.click(
|
|
65
|
-
await rendered.findByRole("button", { name: /add_member/i })
|
|
73
|
+
await rendered.findByRole("button", { name: /add_member/i }),
|
|
66
74
|
);
|
|
67
75
|
|
|
68
76
|
await waitFor(() =>
|
|
@@ -74,7 +82,31 @@ describe("<AddMemberForm />", () => {
|
|
|
74
82
|
principal_id: 1,
|
|
75
83
|
description: "",
|
|
76
84
|
},
|
|
77
|
-
})
|
|
85
|
+
}),
|
|
78
86
|
);
|
|
79
87
|
});
|
|
88
|
+
|
|
89
|
+
it("displays group alias in dropdown when available", async () => {
|
|
90
|
+
const rendered = render(<AddMemberForm {...props} />);
|
|
91
|
+
await waitForLoad(rendered);
|
|
92
|
+
|
|
93
|
+
const [dropdown] = rendered.getAllByRole("combobox");
|
|
94
|
+
await user.click(dropdown);
|
|
95
|
+
|
|
96
|
+
// Should display the alias "Group Alias" in the dropdown, not the actual name
|
|
97
|
+
expect(rendered.getByText(/group alias/i)).toBeInTheDocument();
|
|
98
|
+
// And the group without alias should display its name
|
|
99
|
+
expect(rendered.getByText(/mambo/i)).toBeInTheDocument();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("displays group name when alias is not available", async () => {
|
|
103
|
+
const rendered = render(<AddMemberForm {...props} />);
|
|
104
|
+
await waitForLoad(rendered);
|
|
105
|
+
|
|
106
|
+
const [dropdown] = rendered.getAllByRole("combobox");
|
|
107
|
+
await user.click(dropdown);
|
|
108
|
+
|
|
109
|
+
// Group "mambo" without alias should be displayed
|
|
110
|
+
expect(rendered.getByText(/mambo/i)).toBeInTheDocument();
|
|
111
|
+
});
|
|
80
112
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { waitFor } from "@testing-library/react";
|
|
2
1
|
import userEvent from "@testing-library/user-event";
|
|
3
2
|
import { render, waitForLoad } from "@truedat/test/render";
|
|
4
3
|
import { useAclEntries } from "@truedat/core/hooks/useAclEntries";
|
|
@@ -27,9 +26,26 @@ describe("<ResourceMembers />", () => {
|
|
|
27
26
|
principal_type: "group",
|
|
28
27
|
role_id: 2,
|
|
29
28
|
role_name: "data_owner",
|
|
30
|
-
principal: {
|
|
29
|
+
principal: {
|
|
30
|
+
description: "aaa bbb cc d",
|
|
31
|
+
id: 2,
|
|
32
|
+
name: "grupo1 ",
|
|
33
|
+
alias: "Group Alias 1",
|
|
34
|
+
},
|
|
31
35
|
acl_entry_id: 1,
|
|
32
36
|
},
|
|
37
|
+
{
|
|
38
|
+
principal_type: "group",
|
|
39
|
+
role_id: 2,
|
|
40
|
+
role_name: "data_owner",
|
|
41
|
+
principal: {
|
|
42
|
+
description: "bbb ccc dd e",
|
|
43
|
+
id: 3,
|
|
44
|
+
name: "grupo2",
|
|
45
|
+
alias: null,
|
|
46
|
+
},
|
|
47
|
+
acl_entry_id: 5,
|
|
48
|
+
},
|
|
33
49
|
{
|
|
34
50
|
principal: {
|
|
35
51
|
email: "test@test.com",
|
|
@@ -91,7 +107,7 @@ describe("<ResourceMembers />", () => {
|
|
|
91
107
|
const user = userEvent.setup({ delay: null });
|
|
92
108
|
|
|
93
109
|
await user.type(input, "data");
|
|
94
|
-
expect(document.getElementsByClassName("card").length).toBe(
|
|
110
|
+
expect(document.getElementsByClassName("card").length).toBe(3);
|
|
95
111
|
|
|
96
112
|
await user.type(input, " owner");
|
|
97
113
|
expect(document.getElementsByClassName("card").length).toBe(1);
|
|
@@ -122,4 +138,23 @@ describe("<ResourceMembers />", () => {
|
|
|
122
138
|
expect(rendered.queryByRole("heading", { name: /datos a/i })).toBeNull();
|
|
123
139
|
expect(rendered.queryByRole("heading", { name: /last r/i })).toBeNull();
|
|
124
140
|
});
|
|
141
|
+
|
|
142
|
+
it("displays group alias when available", async () => {
|
|
143
|
+
const rendered = render(<ResourceMembers {...props} />);
|
|
144
|
+
await waitForLoad(rendered);
|
|
145
|
+
|
|
146
|
+
// Group with alias should display "Group Alias 1" as header
|
|
147
|
+
expect(rendered.getByText(/group alias 1/i)).toBeInTheDocument();
|
|
148
|
+
// And the actual group name should be in the description
|
|
149
|
+
expect(rendered.getByText(/grupo1/i)).toBeInTheDocument();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("displays group name when alias is not available", async () => {
|
|
153
|
+
const rendered = render(<ResourceMembers {...props} />);
|
|
154
|
+
await waitForLoad(rendered);
|
|
155
|
+
|
|
156
|
+
// Group without alias should display only the name
|
|
157
|
+
const grupo2Cards = rendered.queryAllByText(/grupo2/i);
|
|
158
|
+
expect(grupo2Cards.length).toBeGreaterThan(0);
|
|
159
|
+
});
|
|
125
160
|
});
|
|
@@ -71,6 +71,20 @@ exports[`<AddMemberForm /> matches the latest snapshot 1`] = `
|
|
|
71
71
|
mambo
|
|
72
72
|
</span>
|
|
73
73
|
</div>
|
|
74
|
+
<div
|
|
75
|
+
aria-checked="false"
|
|
76
|
+
aria-selected="false"
|
|
77
|
+
class="item"
|
|
78
|
+
id="3"
|
|
79
|
+
role="option"
|
|
80
|
+
style="pointer-events: all;"
|
|
81
|
+
>
|
|
82
|
+
<span
|
|
83
|
+
class="text"
|
|
84
|
+
>
|
|
85
|
+
Group Alias
|
|
86
|
+
</span>
|
|
87
|
+
</div>
|
|
74
88
|
</div>
|
|
75
89
|
</div>
|
|
76
90
|
</div>
|
|
@@ -56,8 +56,30 @@ exports[`<ResourceMembers /> matches the latest snapshot 1`] = `
|
|
|
56
56
|
aria-hidden="true"
|
|
57
57
|
class="users icon"
|
|
58
58
|
/>
|
|
59
|
+
Group Alias 1
|
|
60
|
+
</div>
|
|
61
|
+
<div
|
|
62
|
+
class="description"
|
|
63
|
+
>
|
|
59
64
|
grupo1
|
|
60
65
|
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
<div
|
|
69
|
+
class="ui card domain-member"
|
|
70
|
+
>
|
|
71
|
+
<div
|
|
72
|
+
class="content domain-member__content"
|
|
73
|
+
>
|
|
74
|
+
<div
|
|
75
|
+
class="header"
|
|
76
|
+
>
|
|
77
|
+
<i
|
|
78
|
+
aria-hidden="true"
|
|
79
|
+
class="users icon"
|
|
80
|
+
/>
|
|
81
|
+
grupo2
|
|
82
|
+
</div>
|
|
61
83
|
<div
|
|
62
84
|
class="description"
|
|
63
85
|
/>
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { getRecipients } from "../getRecipients";
|
|
2
|
+
|
|
3
|
+
describe("getRecipients", () => {
|
|
4
|
+
it("transforms users correctly", () => {
|
|
5
|
+
const state = {
|
|
6
|
+
usersSearch: [
|
|
7
|
+
{ id: 1, full_name: "John Doe" },
|
|
8
|
+
{ id: 2, full_name: "Jane Smith" },
|
|
9
|
+
],
|
|
10
|
+
groupsSearch: [],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const result = getRecipients(state);
|
|
14
|
+
|
|
15
|
+
expect(result).toEqual(
|
|
16
|
+
expect.arrayContaining([
|
|
17
|
+
expect.objectContaining({
|
|
18
|
+
id: 1,
|
|
19
|
+
full_name: "John Doe",
|
|
20
|
+
role: "user",
|
|
21
|
+
text: "John Doe",
|
|
22
|
+
value: "user_1",
|
|
23
|
+
icon: "user",
|
|
24
|
+
}),
|
|
25
|
+
expect.objectContaining({
|
|
26
|
+
id: 2,
|
|
27
|
+
full_name: "Jane Smith",
|
|
28
|
+
role: "user",
|
|
29
|
+
text: "Jane Smith",
|
|
30
|
+
value: "user_2",
|
|
31
|
+
icon: "user",
|
|
32
|
+
}),
|
|
33
|
+
]),
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("displays group alias when available", () => {
|
|
38
|
+
const state = {
|
|
39
|
+
usersSearch: [],
|
|
40
|
+
groupsSearch: [
|
|
41
|
+
{ id: 1, name: "group1", alias: "Group Alias 1", users: [] },
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const result = getRecipients(state);
|
|
46
|
+
|
|
47
|
+
expect(result).toContainEqual(
|
|
48
|
+
expect.objectContaining({
|
|
49
|
+
id: 1,
|
|
50
|
+
name: "group1",
|
|
51
|
+
alias: "Group Alias 1",
|
|
52
|
+
role: "group",
|
|
53
|
+
text: "Group Alias 1",
|
|
54
|
+
value: "group_1",
|
|
55
|
+
icon: "group",
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("displays group name when alias is not available", () => {
|
|
61
|
+
const state = {
|
|
62
|
+
usersSearch: [],
|
|
63
|
+
groupsSearch: [{ id: 2, name: "group2", alias: null, users: [] }],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const result = getRecipients(state);
|
|
67
|
+
|
|
68
|
+
expect(result).toContainEqual(
|
|
69
|
+
expect.objectContaining({
|
|
70
|
+
id: 2,
|
|
71
|
+
name: "group2",
|
|
72
|
+
alias: null,
|
|
73
|
+
role: "group",
|
|
74
|
+
text: "group2",
|
|
75
|
+
value: "group_2",
|
|
76
|
+
icon: "group",
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("displays group name when alias is empty string", () => {
|
|
82
|
+
const state = {
|
|
83
|
+
usersSearch: [],
|
|
84
|
+
groupsSearch: [{ id: 3, name: "group3", alias: "", users: [] }],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const result = getRecipients(state);
|
|
88
|
+
|
|
89
|
+
expect(result).toContainEqual(
|
|
90
|
+
expect.objectContaining({
|
|
91
|
+
id: 3,
|
|
92
|
+
name: "group3",
|
|
93
|
+
alias: "",
|
|
94
|
+
role: "group",
|
|
95
|
+
text: "group3",
|
|
96
|
+
value: "group_3",
|
|
97
|
+
icon: "group",
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("combines users and groups correctly", () => {
|
|
103
|
+
const state = {
|
|
104
|
+
usersSearch: [{ id: 1, full_name: "John Doe" }],
|
|
105
|
+
groupsSearch: [
|
|
106
|
+
{ id: 1, name: "group1", alias: "Group Alias", users: [] },
|
|
107
|
+
{ id: 2, name: "group2", alias: null, users: [] },
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const result = getRecipients(state);
|
|
112
|
+
|
|
113
|
+
expect(result).toHaveLength(3);
|
|
114
|
+
expect(result).toContainEqual(
|
|
115
|
+
expect.objectContaining({
|
|
116
|
+
id: 1,
|
|
117
|
+
full_name: "John Doe",
|
|
118
|
+
role: "user",
|
|
119
|
+
text: "John Doe",
|
|
120
|
+
value: "user_1",
|
|
121
|
+
}),
|
|
122
|
+
);
|
|
123
|
+
expect(result).toContainEqual(
|
|
124
|
+
expect.objectContaining({
|
|
125
|
+
id: 1,
|
|
126
|
+
name: "group1",
|
|
127
|
+
alias: "Group Alias",
|
|
128
|
+
role: "group",
|
|
129
|
+
text: "Group Alias",
|
|
130
|
+
value: "group_1",
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
expect(result).toContainEqual(
|
|
134
|
+
expect.objectContaining({
|
|
135
|
+
id: 2,
|
|
136
|
+
name: "group2",
|
|
137
|
+
alias: null,
|
|
138
|
+
role: "group",
|
|
139
|
+
text: "group2",
|
|
140
|
+
value: "group_2",
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("handles empty users and groups", () => {
|
|
146
|
+
const state = {
|
|
147
|
+
usersSearch: [],
|
|
148
|
+
groupsSearch: [],
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const result = getRecipients(state);
|
|
152
|
+
|
|
153
|
+
expect(result).toEqual([]);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("handles missing usersSearch and groupsSearch properties", () => {
|
|
157
|
+
const state = {};
|
|
158
|
+
|
|
159
|
+
const result = getRecipients(state);
|
|
160
|
+
|
|
161
|
+
expect(result).toEqual([]);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("filters out unwanted properties from users", () => {
|
|
165
|
+
const state = {
|
|
166
|
+
usersSearch: [
|
|
167
|
+
{
|
|
168
|
+
id: 1,
|
|
169
|
+
full_name: "John Doe",
|
|
170
|
+
email: "john@example.com",
|
|
171
|
+
password: "secret",
|
|
172
|
+
extra_field: "should_be_ignored",
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
groupsSearch: [],
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const result = getRecipients(state);
|
|
179
|
+
|
|
180
|
+
expect(result).toHaveLength(1);
|
|
181
|
+
expect(result[0]).toHaveProperty("id");
|
|
182
|
+
expect(result[0]).toHaveProperty("full_name");
|
|
183
|
+
expect(result[0]).not.toHaveProperty("email");
|
|
184
|
+
expect(result[0]).not.toHaveProperty("password");
|
|
185
|
+
expect(result[0]).not.toHaveProperty("extra_field");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("filters out unwanted properties from groups", () => {
|
|
189
|
+
const state = {
|
|
190
|
+
usersSearch: [],
|
|
191
|
+
groupsSearch: [
|
|
192
|
+
{
|
|
193
|
+
id: 1,
|
|
194
|
+
name: "group1",
|
|
195
|
+
alias: "Group Alias",
|
|
196
|
+
description: "should_be_ignored",
|
|
197
|
+
users: [],
|
|
198
|
+
created_at: "2024-01-01",
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const result = getRecipients(state);
|
|
204
|
+
|
|
205
|
+
expect(result).toHaveLength(1);
|
|
206
|
+
expect(result[0]).toHaveProperty("id");
|
|
207
|
+
expect(result[0]).toHaveProperty("name");
|
|
208
|
+
expect(result[0]).toHaveProperty("alias");
|
|
209
|
+
expect(result[0]).toHaveProperty("users");
|
|
210
|
+
expect(result[0]).not.toHaveProperty("description");
|
|
211
|
+
expect(result[0]).not.toHaveProperty("created_at");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("preserves order of users and groups", () => {
|
|
215
|
+
const state = {
|
|
216
|
+
usersSearch: [
|
|
217
|
+
{ id: 1, full_name: "User 1" },
|
|
218
|
+
{ id: 2, full_name: "User 2" },
|
|
219
|
+
{ id: 3, full_name: "User 3" },
|
|
220
|
+
],
|
|
221
|
+
groupsSearch: [
|
|
222
|
+
{ id: 1, name: "Group 1", alias: null, users: [] },
|
|
223
|
+
{ id: 2, name: "Group 2", alias: "GA 2", users: [] },
|
|
224
|
+
],
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const result = getRecipients(state);
|
|
228
|
+
|
|
229
|
+
expect(result).toHaveLength(5);
|
|
230
|
+
expect(result[0].value).toBe("user_1");
|
|
231
|
+
expect(result[1].value).toBe("user_2");
|
|
232
|
+
expect(result[2].value).toBe("user_3");
|
|
233
|
+
expect(result[3].value).toBe("group_1");
|
|
234
|
+
expect(result[4].value).toBe("group_2");
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("handles groups with special characters in names", () => {
|
|
238
|
+
const state = {
|
|
239
|
+
usersSearch: [],
|
|
240
|
+
groupsSearch: [
|
|
241
|
+
{ id: 1, name: "group-with-dashes", alias: "Group & Co.", users: [] },
|
|
242
|
+
{ id: 2, name: "group/with/slashes", alias: null, users: [] },
|
|
243
|
+
],
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const result = getRecipients(state);
|
|
247
|
+
|
|
248
|
+
expect(result).toHaveLength(2);
|
|
249
|
+
expect(result[0].text).toBe("Group & Co.");
|
|
250
|
+
expect(result[1].text).toBe("group/with/slashes");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("handles users with whitespace in names", () => {
|
|
254
|
+
const state = {
|
|
255
|
+
usersSearch: [
|
|
256
|
+
{ id: 1, full_name: " John Doe " },
|
|
257
|
+
{ id: 2, full_name: "Jane\nSmith" },
|
|
258
|
+
],
|
|
259
|
+
groupsSearch: [],
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const result = getRecipients(state);
|
|
263
|
+
|
|
264
|
+
expect(result).toHaveLength(2);
|
|
265
|
+
expect(result[0].text).toBe(" John Doe ");
|
|
266
|
+
expect(result[1].text).toBe("Jane\nSmith");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("converts numeric IDs to string values correctly", () => {
|
|
270
|
+
const state = {
|
|
271
|
+
usersSearch: [{ id: 123, full_name: "Test" }],
|
|
272
|
+
groupsSearch: [{ id: 456, name: "TestGroup", alias: null, users: [] }],
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const result = getRecipients(state);
|
|
276
|
+
|
|
277
|
+
expect(result[0].value).toBe("user_123");
|
|
278
|
+
expect(result[1].value).toBe("group_456");
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("handles undefined alias correctly", () => {
|
|
282
|
+
const state = {
|
|
283
|
+
usersSearch: [],
|
|
284
|
+
groupsSearch: [{ id: 1, name: "group1", alias: undefined, users: [] }],
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const result = getRecipients(state);
|
|
288
|
+
|
|
289
|
+
expect(result).toHaveLength(1);
|
|
290
|
+
expect(result[0].text).toBe("group1");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("memoizes results for same state", () => {
|
|
294
|
+
const state = {
|
|
295
|
+
usersSearch: [{ id: 1, full_name: "John" }],
|
|
296
|
+
groupsSearch: [{ id: 1, name: "group1", alias: "GA1", users: [] }],
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const result1 = getRecipients(state);
|
|
300
|
+
const result2 = getRecipients(state);
|
|
301
|
+
|
|
302
|
+
expect(result1).toBe(result2);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("returns different results for different state", () => {
|
|
306
|
+
const state1 = {
|
|
307
|
+
usersSearch: [{ id: 1, full_name: "John" }],
|
|
308
|
+
groupsSearch: [],
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const state2 = {
|
|
312
|
+
usersSearch: [{ id: 1, full_name: "Jane" }],
|
|
313
|
+
groupsSearch: [],
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const result1 = getRecipients(state1);
|
|
317
|
+
const result2 = getRecipients(state2);
|
|
318
|
+
|
|
319
|
+
expect(result1).not.toBe(result2);
|
|
320
|
+
expect(result1[0].full_name).toBe("John");
|
|
321
|
+
expect(result2[0].full_name).toBe("Jane");
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("includes all required fields for users", () => {
|
|
325
|
+
const state = {
|
|
326
|
+
usersSearch: [{ id: 1, full_name: "John Doe" }],
|
|
327
|
+
groupsSearch: [],
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const result = getRecipients(state);
|
|
331
|
+
|
|
332
|
+
expect(result[0]).toEqual(
|
|
333
|
+
expect.objectContaining({
|
|
334
|
+
id: 1,
|
|
335
|
+
full_name: "John Doe",
|
|
336
|
+
role: "user",
|
|
337
|
+
text: "John Doe",
|
|
338
|
+
value: "user_1",
|
|
339
|
+
icon: "user",
|
|
340
|
+
}),
|
|
341
|
+
);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it("includes all required fields for groups", () => {
|
|
345
|
+
const state = {
|
|
346
|
+
usersSearch: [],
|
|
347
|
+
groupsSearch: [{ id: 1, name: "group1", alias: "GA1", users: [] }],
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const result = getRecipients(state);
|
|
351
|
+
|
|
352
|
+
expect(result[0]).toEqual(
|
|
353
|
+
expect.objectContaining({
|
|
354
|
+
id: 1,
|
|
355
|
+
name: "group1",
|
|
356
|
+
alias: "GA1",
|
|
357
|
+
users: [],
|
|
358
|
+
role: "group",
|
|
359
|
+
text: "GA1",
|
|
360
|
+
value: "group_1",
|
|
361
|
+
icon: "group",
|
|
362
|
+
}),
|
|
363
|
+
);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
@@ -18,11 +18,11 @@ const usersAsOptions = (users) =>
|
|
|
18
18
|
|
|
19
19
|
const groupsAsOptions = (groups) =>
|
|
20
20
|
_.flow(
|
|
21
|
-
_.map(_.pick(["id", "name", "users"])),
|
|
21
|
+
_.map(_.pick(["id", "name", "alias", "users"])),
|
|
22
22
|
_.map((g) => ({
|
|
23
23
|
...g,
|
|
24
24
|
role: "group",
|
|
25
|
-
text: _.prop("name")(g),
|
|
25
|
+
text: _.prop("alias")(g) || _.prop("name")(g),
|
|
26
26
|
value: `group_${_.prop("id")(g)}`,
|
|
27
27
|
icon: "group",
|
|
28
28
|
}))
|