@truedat/lm 8.2.0 → 8.2.2
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/api.js +4 -0
- package/src/components/ConceptLinkForm.js +111 -0
- package/src/components/LinksPagination.js +15 -0
- package/src/components/LinksSearch.js +139 -0
- package/src/components/QualityControlConcepts.js +156 -0
- package/src/components/QualityControlStructures.js +183 -0
- package/src/components/RelationTagForm.js +4 -0
- package/src/components/StructureLinkForm.js +105 -0
- package/src/components/__tests__/ConceptLinkForm.spec.js +252 -0
- package/src/components/__tests__/LinksSearch.spec.js +299 -0
- package/src/components/__tests__/QualityControlConcepts.spec.js +262 -0
- package/src/components/__tests__/QualityControlStructures.spec.js +213 -0
- package/src/components/__tests__/StructureLinkForm.spec.js +284 -0
- package/src/components/__tests__/__snapshots__/ConceptLinkForm.spec.js.snap +48 -0
- package/src/components/__tests__/__snapshots__/LinksSearch.spec.js.snap +29 -0
- package/src/components/__tests__/__snapshots__/NewRelationTag.spec.js.snap +13 -0
- package/src/components/__tests__/__snapshots__/QualityControlConcepts.spec.js.snap +49 -0
- package/src/components/__tests__/__snapshots__/QualityControlStructures.spec.js.snap +49 -0
- package/src/components/__tests__/__snapshots__/RelationTagForm.spec.js.snap +13 -0
- package/src/components/__tests__/__snapshots__/StructureLinkForm.spec.js.snap +48 -0
- package/src/hooks/useRelations.js +53 -0
- package/src/messages/en.js +1 -0
- package/src/messages/es.js +1 -0
- package/src/selectors/index.js +1 -0
- package/src/selectors/messages.js +48 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import { lazy, useState, useEffect } from "react";
|
|
3
|
+
import { useSelector, useDispatch } from "react-redux";
|
|
4
|
+
import { useIntl } from "react-intl";
|
|
5
|
+
import { clearSelectedRelationTags } from "../routines";
|
|
6
|
+
import { Divider, Button } from "semantic-ui-react";
|
|
7
|
+
import { HistoryBackButton } from "@truedat/core/components";
|
|
8
|
+
import { useWebContext } from "@truedat/core/webContext";
|
|
9
|
+
import RelationTagsLoader from "./RelationTagsLoader";
|
|
10
|
+
import { useCreateRelation } from "../hooks/useRelations";
|
|
11
|
+
import { useNavigate } from "react-router";
|
|
12
|
+
import { getRelationErrorAlertMessage } from "../selectors/messages";
|
|
13
|
+
|
|
14
|
+
const TagTypeDropdownSelector = lazy(
|
|
15
|
+
() => import("@truedat/lm/components/TagTypeDropdownSelector")
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const StructureSelector = lazy(
|
|
19
|
+
() => import("@truedat/dd/components/StructureSelector")
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export const StructureLinkForm = ({
|
|
23
|
+
sourceId,
|
|
24
|
+
sourceType,
|
|
25
|
+
redirectUrl,
|
|
26
|
+
selectTagOptions,
|
|
27
|
+
}) => {
|
|
28
|
+
const navigate = useNavigate();
|
|
29
|
+
const selectedRelationTags = useSelector(
|
|
30
|
+
(state) => state?.selectedRelationTags
|
|
31
|
+
);
|
|
32
|
+
const tagOptions = useSelector(selectTagOptions);
|
|
33
|
+
const { trigger: createRelation, isMutating: creatingRelation } =
|
|
34
|
+
useCreateRelation();
|
|
35
|
+
const dispatch = useDispatch();
|
|
36
|
+
const [selectedStructure, setSelectedStructure] = useState(null);
|
|
37
|
+
const { formatMessage } = useIntl();
|
|
38
|
+
const { setAlertMessage } = useWebContext();
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
return () => {
|
|
42
|
+
dispatch(clearSelectedRelationTags.trigger());
|
|
43
|
+
};
|
|
44
|
+
}, [clearSelectedRelationTags, dispatch]);
|
|
45
|
+
|
|
46
|
+
const handleSubmit = () => {
|
|
47
|
+
const link = {
|
|
48
|
+
source_id: sourceId,
|
|
49
|
+
source_type: sourceType,
|
|
50
|
+
target_id: selectedStructure?.id,
|
|
51
|
+
target_type: "data_structure",
|
|
52
|
+
tag_ids: selectedRelationTags ? selectedRelationTags : [],
|
|
53
|
+
};
|
|
54
|
+
createRelation({ relation: link })
|
|
55
|
+
.then(({ data }) => {
|
|
56
|
+
navigate(redirectUrl, {
|
|
57
|
+
state: {
|
|
58
|
+
createdRelation: {
|
|
59
|
+
...data?.data,
|
|
60
|
+
target_name: selectedStructure?.name,
|
|
61
|
+
target_data: selectedStructure,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
})
|
|
66
|
+
.catch((error) => {
|
|
67
|
+
const alertMessage = getRelationErrorAlertMessage(error, formatMessage);
|
|
68
|
+
if (alertMessage) {
|
|
69
|
+
setAlertMessage(alertMessage);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const disabled = !(selectedStructure && selectedRelationTags);
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<>
|
|
78
|
+
<Divider hidden />
|
|
79
|
+
<RelationTagsLoader />
|
|
80
|
+
{!_.isEmpty(tagOptions) && (
|
|
81
|
+
<>
|
|
82
|
+
<TagTypeDropdownSelector options={tagOptions} />
|
|
83
|
+
<Divider hidden />
|
|
84
|
+
</>
|
|
85
|
+
)}
|
|
86
|
+
<StructureSelector
|
|
87
|
+
selectedStructure={selectedStructure}
|
|
88
|
+
onSelect={setSelectedStructure}
|
|
89
|
+
/>
|
|
90
|
+
<Divider hidden />
|
|
91
|
+
<Button.Group>
|
|
92
|
+
<Button
|
|
93
|
+
primary
|
|
94
|
+
loading={creatingRelation}
|
|
95
|
+
content={formatMessage({ id: "actions.create" })}
|
|
96
|
+
disabled={disabled}
|
|
97
|
+
onClick={handleSubmit}
|
|
98
|
+
/>
|
|
99
|
+
<HistoryBackButton content={formatMessage({ id: "actions.cancel" })} />
|
|
100
|
+
</Button.Group>
|
|
101
|
+
</>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export default StructureLinkForm;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import userEvent from "@testing-library/user-event";
|
|
2
|
+
import { waitFor } from "@testing-library/react";
|
|
3
|
+
import { render, waitForLoad } from "@truedat/test/render";
|
|
4
|
+
import { ConceptLinkForm } from "../ConceptLinkForm";
|
|
5
|
+
|
|
6
|
+
const mockNavigate = jest.fn();
|
|
7
|
+
const mockCreateRelation = jest.fn();
|
|
8
|
+
const mockDispatch = jest.fn();
|
|
9
|
+
|
|
10
|
+
jest.mock("react-router", () => ({
|
|
11
|
+
...jest.requireActual("react-router"),
|
|
12
|
+
useNavigate: () => mockNavigate,
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
jest.mock("../../hooks/useRelations", () => ({
|
|
16
|
+
useCreateRelation: () => ({
|
|
17
|
+
trigger: mockCreateRelation,
|
|
18
|
+
isMutating: false,
|
|
19
|
+
}),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
jest.mock("react-redux", () => ({
|
|
23
|
+
...jest.requireActual("react-redux"),
|
|
24
|
+
useDispatch: jest.fn(() => mockDispatch),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
jest.mock("react-intl", () => ({
|
|
28
|
+
...jest.requireActual("react-intl"),
|
|
29
|
+
useIntl: () => ({
|
|
30
|
+
formatMessage: ({ id }) => id,
|
|
31
|
+
}),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
jest.mock("../../routines", () => ({
|
|
35
|
+
clearSelectedRelationTags: {
|
|
36
|
+
trigger: jest.fn(() => ({ type: "CLEAR_SELECTED_RELATION_TAGS" })),
|
|
37
|
+
},
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
jest.mock("../RelationTagsLoader", () =>
|
|
41
|
+
jest.fn(() => <div>RelationTagsLoader</div>)
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
jest.mock("@truedat/lm/components/TagTypeDropdownSelector", () =>
|
|
45
|
+
jest.fn(({ options }) => (
|
|
46
|
+
<div>
|
|
47
|
+
<div>TagTypeDropdownSelector</div>
|
|
48
|
+
{options?.map((option, i) => (
|
|
49
|
+
<button
|
|
50
|
+
key={i}
|
|
51
|
+
onClick={() => option.onClick && option.onClick(option.value)}
|
|
52
|
+
>
|
|
53
|
+
{option.text}
|
|
54
|
+
</button>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
))
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
jest.mock("@truedat/bg/concepts/relations/components/ConceptSelector", () =>
|
|
61
|
+
jest.fn(({ handleConceptSelected }) => (
|
|
62
|
+
<div>
|
|
63
|
+
<div>ConceptSelector</div>
|
|
64
|
+
<button
|
|
65
|
+
onClick={() =>
|
|
66
|
+
handleConceptSelected({
|
|
67
|
+
business_concept_id: 123,
|
|
68
|
+
name: "Test Concept",
|
|
69
|
+
id: 456,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
>
|
|
73
|
+
Select Concept
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
))
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
jest.mock("@truedat/core/components", () => ({
|
|
80
|
+
HistoryBackButton: ({ content }) => <button>{content}</button>,
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
const props = {
|
|
84
|
+
targetId: 10,
|
|
85
|
+
targetType: "quality_control",
|
|
86
|
+
redirectUrl: "/qualityControls/10/version/1/links/concepts",
|
|
87
|
+
selectTagOptions: jest.fn(() => [{ id: 1, text: "relates_to", value: 1 }]),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const renderOpts = {
|
|
91
|
+
state: {
|
|
92
|
+
relationTags: [{ value: { target_type: "quality_control" } }],
|
|
93
|
+
selectedRelationTags: [1],
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
describe("<ConceptLinkForm />", () => {
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
jest.clearAllMocks();
|
|
100
|
+
mockCreateRelation.mockResolvedValue({
|
|
101
|
+
data: {
|
|
102
|
+
data: {
|
|
103
|
+
id: 789,
|
|
104
|
+
source_id: 123,
|
|
105
|
+
target_id: 10,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("matches the latest snapshot", async () => {
|
|
112
|
+
const rendered = render(<ConceptLinkForm {...props} />, renderOpts);
|
|
113
|
+
await waitForLoad(rendered);
|
|
114
|
+
expect(rendered.container).toMatchSnapshot();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("renders all form components", async () => {
|
|
118
|
+
const rendered = render(<ConceptLinkForm {...props} />, renderOpts);
|
|
119
|
+
await waitForLoad(rendered);
|
|
120
|
+
|
|
121
|
+
expect(rendered.getByText("RelationTagsLoader")).toBeInTheDocument();
|
|
122
|
+
expect(rendered.getByText("TagTypeDropdownSelector")).toBeInTheDocument();
|
|
123
|
+
expect(rendered.getByText("ConceptSelector")).toBeInTheDocument();
|
|
124
|
+
expect(rendered.getByText("actions.create")).toBeInTheDocument();
|
|
125
|
+
expect(rendered.getByText("actions.cancel")).toBeInTheDocument();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("disables create button when concept or tags are not selected", async () => {
|
|
129
|
+
const rendered = render(<ConceptLinkForm {...props} />, {
|
|
130
|
+
state: {
|
|
131
|
+
relationTags: [],
|
|
132
|
+
selectedRelationTags: null,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
await waitForLoad(rendered);
|
|
136
|
+
|
|
137
|
+
const createButton = rendered.getByText("actions.create");
|
|
138
|
+
expect(createButton).toBeDisabled();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("enables create button when both concept and tags are selected", async () => {
|
|
142
|
+
const user = userEvent.setup({ delay: null });
|
|
143
|
+
const rendered = render(<ConceptLinkForm {...props} />, renderOpts);
|
|
144
|
+
await waitForLoad(rendered);
|
|
145
|
+
|
|
146
|
+
const createButton = rendered.getByText("actions.create");
|
|
147
|
+
expect(createButton).toBeDisabled();
|
|
148
|
+
|
|
149
|
+
await user.click(rendered.getByText("Select Concept"));
|
|
150
|
+
|
|
151
|
+
await waitFor(() => {
|
|
152
|
+
expect(createButton).toBeEnabled();
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("calls createRelation and navigates on submit", async () => {
|
|
157
|
+
const user = userEvent.setup({ delay: null });
|
|
158
|
+
const rendered = render(<ConceptLinkForm {...props} />, renderOpts);
|
|
159
|
+
await waitForLoad(rendered);
|
|
160
|
+
|
|
161
|
+
await user.click(rendered.getByText("Select Concept"));
|
|
162
|
+
|
|
163
|
+
await waitFor(() => {
|
|
164
|
+
expect(rendered.getByText("actions.create")).toBeEnabled();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
await user.click(rendered.getByText("actions.create"));
|
|
168
|
+
|
|
169
|
+
await waitFor(() => {
|
|
170
|
+
expect(mockCreateRelation).toHaveBeenCalledWith({
|
|
171
|
+
relation: {
|
|
172
|
+
source_id: 123,
|
|
173
|
+
source_type: "business_concept",
|
|
174
|
+
target_id: 10,
|
|
175
|
+
target_type: "quality_control",
|
|
176
|
+
tag_ids: [1],
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await waitFor(() => {
|
|
182
|
+
expect(mockNavigate).toHaveBeenCalledWith(props.redirectUrl, {
|
|
183
|
+
state: {
|
|
184
|
+
createdRelation: {
|
|
185
|
+
id: 789,
|
|
186
|
+
source_id: 123,
|
|
187
|
+
target_id: 10,
|
|
188
|
+
source_name: "Test Concept",
|
|
189
|
+
source_data: {
|
|
190
|
+
business_concept_id: 123,
|
|
191
|
+
name: "Test Concept",
|
|
192
|
+
id: 456,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("dispatches clearSelectedRelationTags on unmount", () => {
|
|
201
|
+
const { unmount } = render(<ConceptLinkForm {...props} />, renderOpts);
|
|
202
|
+
unmount();
|
|
203
|
+
|
|
204
|
+
expect(mockDispatch).toHaveBeenCalledWith({
|
|
205
|
+
type: "CLEAR_SELECTED_RELATION_TAGS",
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("handles empty tag_ids when selectedRelationTags is empty", async () => {
|
|
210
|
+
const user = userEvent.setup({ delay: null });
|
|
211
|
+
const rendered = render(<ConceptLinkForm {...props} />, {
|
|
212
|
+
state: { selectedRelationTags: [] },
|
|
213
|
+
});
|
|
214
|
+
await waitForLoad(rendered);
|
|
215
|
+
|
|
216
|
+
await user.click(rendered.getByText("Select Concept"));
|
|
217
|
+
|
|
218
|
+
await waitFor(() => {
|
|
219
|
+
expect(rendered.getByText("actions.create")).toBeEnabled();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
await user.click(rendered.getByText("actions.create"));
|
|
223
|
+
|
|
224
|
+
await waitFor(() => {
|
|
225
|
+
expect(mockCreateRelation).toHaveBeenCalledWith({
|
|
226
|
+
relation: {
|
|
227
|
+
source_id: 123,
|
|
228
|
+
source_type: "business_concept",
|
|
229
|
+
target_id: 10,
|
|
230
|
+
target_type: "quality_control",
|
|
231
|
+
tag_ids: [],
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("does not render TagTypeDropdownSelector when tagOptions is empty", async () => {
|
|
238
|
+
const rendered = render(
|
|
239
|
+
<ConceptLinkForm
|
|
240
|
+
{...{ ...props, selectTagOptions: jest.fn(() => []) }}
|
|
241
|
+
/>,
|
|
242
|
+
{
|
|
243
|
+
state: {},
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
await waitForLoad(rendered);
|
|
247
|
+
|
|
248
|
+
expect(
|
|
249
|
+
rendered.queryByText("TagTypeDropdownSelector")
|
|
250
|
+
).not.toBeInTheDocument();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { waitFor } from "@testing-library/react";
|
|
2
|
+
import { render, waitForLoad } from "@truedat/test/render";
|
|
3
|
+
import LinksSearch from "../LinksSearch";
|
|
4
|
+
|
|
5
|
+
const mockNavigate = jest.fn();
|
|
6
|
+
const mockSearchRelations = jest.fn();
|
|
7
|
+
const mockSearchRelationsFilters = jest.fn();
|
|
8
|
+
|
|
9
|
+
jest.mock("react-router", () => ({
|
|
10
|
+
...jest.requireActual("react-router"),
|
|
11
|
+
useNavigate: () => mockNavigate,
|
|
12
|
+
useLocation: () => ({ pathname: "/test/path" }),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
jest.mock("../../hooks/useRelations", () => ({
|
|
16
|
+
useRelations: () => ({
|
|
17
|
+
trigger: mockSearchRelations,
|
|
18
|
+
}),
|
|
19
|
+
useRelationFilters: () => ({
|
|
20
|
+
trigger: mockSearchRelationsFilters,
|
|
21
|
+
}),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
jest.mock("@truedat/core/search/SearchContext", () => {
|
|
25
|
+
const originalModule = jest.requireActual(
|
|
26
|
+
"@truedat/core/search/SearchContext"
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
__esModule: true,
|
|
31
|
+
...originalModule,
|
|
32
|
+
useSearchContext: jest.fn(),
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
jest.mock("../LinksPagination", () =>
|
|
37
|
+
jest.fn(() => <div>LinksPagination</div>)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
jest.mock("@truedat/core/search/SearchWidget", () =>
|
|
41
|
+
jest.fn(() => <div>SearchWidget</div>)
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const { useSearchContext } = require("@truedat/core/search/SearchContext");
|
|
45
|
+
|
|
46
|
+
const columns = [
|
|
47
|
+
{
|
|
48
|
+
header: "concepts.props.name",
|
|
49
|
+
fieldSelector: (item) => item.name,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
header: "concepts.props.domain",
|
|
53
|
+
fieldSelector: (item) => item.domain,
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const defaultFilters = {
|
|
58
|
+
target_id: 10,
|
|
59
|
+
target_type: "quality_control",
|
|
60
|
+
source_type: "business_concept",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const searchData = {
|
|
64
|
+
data: [
|
|
65
|
+
{ id: 1, name: "Concept 1", domain: "Domain 1" },
|
|
66
|
+
{ id: 2, name: "Concept 2", domain: "Domain 2" },
|
|
67
|
+
{ id: 3, name: "Concept 3", domain: "Domain 1" },
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const filterParams = {
|
|
72
|
+
must: defaultFilters,
|
|
73
|
+
page: 0,
|
|
74
|
+
size: 10,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
describe("<LinksSearch />", () => {
|
|
78
|
+
beforeEach(() => {
|
|
79
|
+
jest.clearAllMocks();
|
|
80
|
+
mockSearchRelations.mockResolvedValue({ data: searchData });
|
|
81
|
+
mockSearchRelationsFilters.mockResolvedValue({ data: {} });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("matches the latest snapshot", async () => {
|
|
85
|
+
useSearchContext.mockReturnValue({
|
|
86
|
+
searchData: { data: [] },
|
|
87
|
+
loading: false,
|
|
88
|
+
filterParams,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const rendered = render(
|
|
92
|
+
<LinksSearch columns={columns} defaultFilters={defaultFilters} />
|
|
93
|
+
);
|
|
94
|
+
await waitForLoad(rendered);
|
|
95
|
+
expect(rendered.container).toMatchSnapshot();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("renders SearchWidget and LinksPagination", async () => {
|
|
99
|
+
useSearchContext.mockReturnValue({
|
|
100
|
+
searchData: { data: [] },
|
|
101
|
+
loading: false,
|
|
102
|
+
filterParams,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const rendered = render(
|
|
106
|
+
<LinksSearch columns={columns} defaultFilters={defaultFilters} />
|
|
107
|
+
);
|
|
108
|
+
await waitForLoad(rendered);
|
|
109
|
+
|
|
110
|
+
expect(rendered.getByText("SearchWidget")).toBeInTheDocument();
|
|
111
|
+
expect(rendered.getByText("LinksPagination")).toBeInTheDocument();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("displays empty message when no relations", async () => {
|
|
115
|
+
useSearchContext.mockReturnValue({
|
|
116
|
+
searchData: { data: [] },
|
|
117
|
+
loading: false,
|
|
118
|
+
filterParams,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const rendered = render(
|
|
122
|
+
<LinksSearch columns={columns} defaultFilters={defaultFilters} />
|
|
123
|
+
);
|
|
124
|
+
await waitForLoad(rendered);
|
|
125
|
+
|
|
126
|
+
await waitFor(() => {
|
|
127
|
+
expect(rendered.getByText("linked_concepts.empty")).toBeInTheDocument();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("displays relations table when data is available", async () => {
|
|
132
|
+
useSearchContext.mockReturnValue({
|
|
133
|
+
searchData,
|
|
134
|
+
loading: false,
|
|
135
|
+
filterParams,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const rendered = render(
|
|
139
|
+
<LinksSearch columns={columns} defaultFilters={defaultFilters} />
|
|
140
|
+
);
|
|
141
|
+
await waitForLoad(rendered);
|
|
142
|
+
|
|
143
|
+
await waitFor(() => {
|
|
144
|
+
expect(rendered.getByText("Concept 1")).toBeInTheDocument();
|
|
145
|
+
expect(rendered.getByText("Concept 2")).toBeInTheDocument();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("includes extraRow when provided and not in data", async () => {
|
|
150
|
+
useSearchContext.mockReturnValue({
|
|
151
|
+
searchData,
|
|
152
|
+
loading: false,
|
|
153
|
+
filterParams,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const extraRow = { id: 999, name: "New Concept", domain: "New Domain" };
|
|
157
|
+
|
|
158
|
+
const rendered = render(
|
|
159
|
+
<LinksSearch
|
|
160
|
+
columns={columns}
|
|
161
|
+
defaultFilters={defaultFilters}
|
|
162
|
+
extraRow={extraRow}
|
|
163
|
+
/>
|
|
164
|
+
);
|
|
165
|
+
await waitForLoad(rendered);
|
|
166
|
+
|
|
167
|
+
await waitFor(() => {
|
|
168
|
+
expect(rendered.getByText("New Concept")).toBeInTheDocument();
|
|
169
|
+
expect(rendered.getByText("Concept 1")).toBeInTheDocument();
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("does not include extraRow when it already exists in data", async () => {
|
|
174
|
+
useSearchContext.mockReturnValue({
|
|
175
|
+
searchData,
|
|
176
|
+
loading: false,
|
|
177
|
+
filterParams,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const extraRow = { id: 1, name: "Concept 1", domain: "Domain 1" };
|
|
181
|
+
|
|
182
|
+
const rendered = render(
|
|
183
|
+
<LinksSearch
|
|
184
|
+
columns={columns}
|
|
185
|
+
defaultFilters={defaultFilters}
|
|
186
|
+
extraRow={extraRow}
|
|
187
|
+
/>
|
|
188
|
+
);
|
|
189
|
+
await waitForLoad(rendered);
|
|
190
|
+
|
|
191
|
+
await waitFor(() => {
|
|
192
|
+
const concept1Elements = rendered.queryAllByText("Concept 1");
|
|
193
|
+
expect(concept1Elements.length).toBe(1);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("filters out deletedLinkId from data", async () => {
|
|
198
|
+
useSearchContext.mockReturnValue({
|
|
199
|
+
searchData,
|
|
200
|
+
loading: false,
|
|
201
|
+
filterParams,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const rendered = render(
|
|
205
|
+
<LinksSearch
|
|
206
|
+
columns={columns}
|
|
207
|
+
defaultFilters={defaultFilters}
|
|
208
|
+
deletedLinkId={2}
|
|
209
|
+
/>
|
|
210
|
+
);
|
|
211
|
+
await waitForLoad(rendered);
|
|
212
|
+
|
|
213
|
+
await waitFor(() => {
|
|
214
|
+
expect(rendered.getByText("Concept 1")).toBeInTheDocument();
|
|
215
|
+
expect(rendered.queryByText("Concept 2")).not.toBeInTheDocument();
|
|
216
|
+
expect(rendered.getByText("Concept 3")).toBeInTheDocument();
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("triggers refetch when refetchSearch is true", async () => {
|
|
221
|
+
const setRefetchSearch = jest.fn();
|
|
222
|
+
|
|
223
|
+
useSearchContext.mockReturnValue({
|
|
224
|
+
searchData: { data: [] },
|
|
225
|
+
loading: false,
|
|
226
|
+
filterParams,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const rendered = render(
|
|
230
|
+
<LinksSearch
|
|
231
|
+
columns={columns}
|
|
232
|
+
defaultFilters={defaultFilters}
|
|
233
|
+
refetchSearch={true}
|
|
234
|
+
setRefetchSearch={setRefetchSearch}
|
|
235
|
+
/>
|
|
236
|
+
);
|
|
237
|
+
await waitForLoad(rendered);
|
|
238
|
+
|
|
239
|
+
await waitFor(() => {
|
|
240
|
+
expect(mockSearchRelations).toHaveBeenCalledWith(filterParams);
|
|
241
|
+
expect(mockSearchRelationsFilters).toHaveBeenCalledWith(filterParams);
|
|
242
|
+
expect(setRefetchSearch).toHaveBeenCalledWith(false);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("clears extraRow when filterParams change", async () => {
|
|
247
|
+
const initialFilterParams = { ...filterParams, page: 0 };
|
|
248
|
+
const changedFilterParams = { ...filterParams, page: 1 };
|
|
249
|
+
|
|
250
|
+
useSearchContext
|
|
251
|
+
.mockReturnValueOnce({
|
|
252
|
+
searchData,
|
|
253
|
+
loading: false,
|
|
254
|
+
filterParams: initialFilterParams,
|
|
255
|
+
})
|
|
256
|
+
.mockReturnValueOnce({
|
|
257
|
+
searchData,
|
|
258
|
+
loading: false,
|
|
259
|
+
filterParams: changedFilterParams,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const extraRow = { id: 999, name: "New Concept", domain: "New Domain" };
|
|
263
|
+
|
|
264
|
+
const rendered = render(
|
|
265
|
+
<LinksSearch
|
|
266
|
+
columns={columns}
|
|
267
|
+
defaultFilters={defaultFilters}
|
|
268
|
+
extraRow={extraRow}
|
|
269
|
+
/>
|
|
270
|
+
);
|
|
271
|
+
await waitForLoad(rendered);
|
|
272
|
+
|
|
273
|
+
await waitFor(() => {
|
|
274
|
+
expect(rendered.getByText("New Concept")).toBeInTheDocument();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Simulate filterParams change by re-rendering with new filterParams
|
|
278
|
+
useSearchContext.mockReturnValue({
|
|
279
|
+
searchData,
|
|
280
|
+
loading: false,
|
|
281
|
+
filterParams: changedFilterParams,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
rendered.rerender(
|
|
285
|
+
<LinksSearch
|
|
286
|
+
columns={columns}
|
|
287
|
+
defaultFilters={defaultFilters}
|
|
288
|
+
extraRow={extraRow}
|
|
289
|
+
/>
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
await waitFor(() => {
|
|
293
|
+
expect(mockNavigate).toHaveBeenCalledWith("/test/path", {
|
|
294
|
+
replace: true,
|
|
295
|
+
state: null,
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
});
|