@truedat/bg 8.13.4 → 8.13.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.
- package/package.json +3 -3
- package/src/concepts/api.js +5 -9
- package/src/concepts/components/ConceptEdit.js +23 -1
- package/src/concepts/components/ConceptForm.js +28 -1
- package/src/concepts/components/ConceptRoutes.js +10 -13
- package/src/concepts/components/ConceptsUploadButton.js +14 -5
- package/src/concepts/components/__tests__/ConceptEdit.dateRestrictions.spec.js +84 -0
- package/src/concepts/components/__tests__/ConceptEdit.spec.js +29 -0
- package/src/concepts/components/__tests__/ConceptEditLoader.spec.js +159 -0
- package/src/concepts/components/__tests__/ConceptForm.spec.js +97 -1
- package/src/concepts/components/__tests__/ConceptRoutes.spec.js +11 -6
- package/src/concepts/components/__tests__/__snapshots__/ConceptRoutes.spec.js.snap +8 -11
- package/src/concepts/hooks/useConcepts.js +9 -4
- package/src/concepts/sagas/__tests__/downloadConcepts.spec.js +3 -3
- package/src/concepts/sagas/downloadConcepts.js +2 -2
- package/src/concepts/selectors/__tests__/messages.spec.js +63 -0
- package/src/concepts/selectors/index.js +0 -4
- package/src/concepts/selectors/messages.js +20 -3
- package/src/concepts/components/ConceptsUploadEvents.js +0 -31
- package/src/concepts/components/ConceptsUploadEventsTable.js +0 -183
- package/src/concepts/components/__tests__/ConceptsUploadEventsTable.spec.js +0 -100
- package/src/concepts/components/__tests__/__snapshots__/ConceptsUploadEventsTable.spec.js.snap +0 -115
- package/src/concepts/hooks/useUploadEvents.js +0 -11
- package/src/concepts/selectors/__tests__/getConceptUploadEventColumns.spec.js +0 -20
- package/src/concepts/selectors/getConceptUploadEventColumns.js +0 -66
- package/src/concepts/styles/ConceptsUploadEventColumns.less +0 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/bg",
|
|
3
|
-
"version": "8.13.
|
|
3
|
+
"version": "8.13.6",
|
|
4
4
|
"description": "Truedat Web Business Glossary",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@testing-library/jest-dom": "^6.6.3",
|
|
58
58
|
"@testing-library/react": "^16.3.0",
|
|
59
59
|
"@testing-library/user-event": "^14.6.1",
|
|
60
|
-
"@truedat/test": "8.13.
|
|
60
|
+
"@truedat/test": "8.13.6",
|
|
61
61
|
"identity-obj-proxy": "^3.0.0",
|
|
62
62
|
"jest": "^29.7.0",
|
|
63
63
|
"redux-saga-test-plan": "^4.0.6"
|
|
@@ -90,5 +90,5 @@
|
|
|
90
90
|
"semantic-ui-react": "^3.0.0-beta.2",
|
|
91
91
|
"swr": "^2.3.3"
|
|
92
92
|
},
|
|
93
|
-
"gitHead": "
|
|
93
|
+
"gitHead": "c0943c8cf31e82a81fdca24d68ef57406fe5bcbc"
|
|
94
94
|
}
|
package/src/concepts/api.js
CHANGED
|
@@ -3,12 +3,11 @@ const API_BUSINESS_CONCEPT_VERSION =
|
|
|
3
3
|
const API_BUSINESS_CONCEPT_VERSIONS_ACTIONS =
|
|
4
4
|
"/api/business_concept_versions/actions";
|
|
5
5
|
const API_BUSINESS_CONCEPT_VERSIONS_CSV = "/api/business_concept_versions/csv";
|
|
6
|
-
const
|
|
7
|
-
"/api/
|
|
6
|
+
const API_BUSINESS_CONCEPTS_XLSX_DOWNLOAD =
|
|
7
|
+
"/api/business_concepts/xlsx/download";
|
|
8
8
|
const API_BUSINESS_CONCEPT_VERSIONS_SEARCH =
|
|
9
9
|
"/api/business_concept_versions/search";
|
|
10
|
-
const
|
|
11
|
-
"/api/business_concept_versions/upload";
|
|
10
|
+
const API_BUSINESS_CONCEPTS_XLSX_UPLOAD = "/api/business_concepts/xlsx/upload";
|
|
12
11
|
const API_CONCEPT_ARCHIVE = "/api/business_concepts/:id/versions";
|
|
13
12
|
const API_CONCEPT_LINKS_DOWNLOAD =
|
|
14
13
|
"/api/business_concept_versions/links/download";
|
|
@@ -17,8 +16,6 @@ const API_CONCEPT_FILTERS = "/api/business_concept_filters/search";
|
|
|
17
16
|
const API_CONCEPT_SHARED_DOMAINS = "/api/business_concepts/:id/shared_domains";
|
|
18
17
|
const API_BUSINESS_CONCEPT_BULK_UPDATE =
|
|
19
18
|
"/api/business_concept_versions/bulk_update";
|
|
20
|
-
const API_BUSINESS_CONCEPT_BULK_UPLOAD =
|
|
21
|
-
"/api/business_concepts/bulk_upload_event";
|
|
22
19
|
const API_BUSINESS_CONCEPT_SET_CONFIDENTIAL =
|
|
23
20
|
"/api/business_concept_versions/:id/set_confidential";
|
|
24
21
|
const API_CONCEPT_SUGGESTIONS = "/api/business_concept_versions/suggestions";
|
|
@@ -28,15 +25,14 @@ export {
|
|
|
28
25
|
API_BUSINESS_CONCEPT_VERSION,
|
|
29
26
|
API_BUSINESS_CONCEPT_VERSIONS_ACTIONS,
|
|
30
27
|
API_BUSINESS_CONCEPT_VERSIONS_CSV,
|
|
31
|
-
|
|
28
|
+
API_BUSINESS_CONCEPTS_XLSX_DOWNLOAD,
|
|
32
29
|
API_BUSINESS_CONCEPT_VERSIONS_SEARCH,
|
|
33
|
-
|
|
30
|
+
API_BUSINESS_CONCEPTS_XLSX_UPLOAD,
|
|
34
31
|
API_CONCEPT_ARCHIVE,
|
|
35
32
|
API_CONCEPT_LINKS_DOWNLOAD,
|
|
36
33
|
API_CONCEPT_LINKS_UPLOAD,
|
|
37
34
|
API_CONCEPT_FILTERS,
|
|
38
35
|
API_CONCEPT_SHARED_DOMAINS,
|
|
39
36
|
API_BUSINESS_CONCEPT_BULK_UPDATE,
|
|
40
|
-
API_BUSINESS_CONCEPT_BULK_UPLOAD,
|
|
41
37
|
API_BUSINESS_CONCEPT_SET_CONFIDENTIAL,
|
|
42
38
|
};
|
|
@@ -7,6 +7,7 @@ import { applyTemplate } from "@truedat/df/utils";
|
|
|
7
7
|
import { useLanguage } from "@truedat/core/i18n";
|
|
8
8
|
import { splitTranslatableFields } from "@truedat/core/services/i18nContent";
|
|
9
9
|
import { useTemplate } from "@truedat/core/hooks";
|
|
10
|
+
import { Loading, TemplateLoadError } from "@truedat/core/components";
|
|
10
11
|
import { conceptAction } from "../routines";
|
|
11
12
|
import ConceptForm from "./ConceptForm";
|
|
12
13
|
|
|
@@ -25,6 +26,10 @@ export const ConceptEdit = ({
|
|
|
25
26
|
const { domain } = concept;
|
|
26
27
|
const [i18nConcept, setI18nConcept] = useState(content);
|
|
27
28
|
const { defaultLang, altLangs } = useLanguage();
|
|
29
|
+
const [originalI18nConcept] = useState(() => ({
|
|
30
|
+
...concept.i18n_content,
|
|
31
|
+
[defaultLang]: { content: concept.dynamic_content },
|
|
32
|
+
}));
|
|
28
33
|
|
|
29
34
|
const templateFields = splitTranslatableFields(template);
|
|
30
35
|
const translatableKeys = _.keys(templateFields.translatable);
|
|
@@ -66,6 +71,7 @@ export const ConceptEdit = ({
|
|
|
66
71
|
resourceId={concept.business_concept_id}
|
|
67
72
|
template={template}
|
|
68
73
|
i18nConcept={i18nConcept}
|
|
74
|
+
originalI18nConcept={originalI18nConcept}
|
|
69
75
|
loading={templateLoading || localesLoading}
|
|
70
76
|
setI18nConcept={setI18nConcept}
|
|
71
77
|
onSubmit={handleSubmit}
|
|
@@ -91,12 +97,28 @@ export const ConceptEditLoader = (props) => {
|
|
|
91
97
|
const { dynamic_content: propContent, name: propName, domain } = concept;
|
|
92
98
|
|
|
93
99
|
const { locales } = useLanguage();
|
|
94
|
-
const {
|
|
100
|
+
const {
|
|
101
|
+
data,
|
|
102
|
+
error: templateError,
|
|
103
|
+
loading: templateLoading,
|
|
104
|
+
refetch: refetchTemplate,
|
|
105
|
+
} = useTemplate({
|
|
95
106
|
name: templateName,
|
|
96
107
|
domainIds: [domain?.id],
|
|
97
108
|
});
|
|
98
109
|
|
|
99
110
|
const template = _.prop("template")(data);
|
|
111
|
+
|
|
112
|
+
if (templateError && !templateLoading) {
|
|
113
|
+
return <TemplateLoadError onRetry={() => refetchTemplate()} />;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (templateLoading && !template) return <Loading />;
|
|
117
|
+
|
|
118
|
+
if (!template) {
|
|
119
|
+
return <TemplateLoadError onRetry={() => refetchTemplate()} />;
|
|
120
|
+
}
|
|
121
|
+
|
|
100
122
|
const processedContent = applyTemplate(template);
|
|
101
123
|
|
|
102
124
|
const translatableKeys = _.keys(
|
|
@@ -22,15 +22,29 @@ import { useAvailabilityCheck as useTranslatableCheck } from "@truedat/ai/hooks/
|
|
|
22
22
|
import { splitTranslatableFields } from "@truedat/core/services/i18nContent";
|
|
23
23
|
import { TranslationModal } from "@truedat/ai/components";
|
|
24
24
|
import { useWebContext } from "@truedat/core/webContext";
|
|
25
|
+
import { validateContent } from "@truedat/df/utils";
|
|
25
26
|
|
|
26
27
|
const SelectDynamicFormWithTranslations = lazy(
|
|
27
28
|
() => import("@truedat/df/components/SelectDynamicFormWithTranslations"),
|
|
28
29
|
);
|
|
29
30
|
|
|
31
|
+
export const conceptHasDateRestrictionErrors = (
|
|
32
|
+
template,
|
|
33
|
+
i18nConcept,
|
|
34
|
+
originalI18nConcept,
|
|
35
|
+
) =>
|
|
36
|
+
Object.entries(i18nConcept || {}).some(([lang, { content } = {}]) =>
|
|
37
|
+
validateContent(
|
|
38
|
+
template,
|
|
39
|
+
originalI18nConcept?.[lang]?.content,
|
|
40
|
+
)(content || {}).some(({ error }) => error === "date out of range"),
|
|
41
|
+
);
|
|
42
|
+
|
|
30
43
|
const ConceptForm = ({
|
|
31
44
|
actionKey,
|
|
32
45
|
domainId,
|
|
33
46
|
i18nConcept,
|
|
47
|
+
originalI18nConcept,
|
|
34
48
|
loading,
|
|
35
49
|
onSubmit,
|
|
36
50
|
resourceId,
|
|
@@ -240,7 +254,16 @@ const ConceptForm = ({
|
|
|
240
254
|
)(i18nConcept);
|
|
241
255
|
const validTemplate = () =>
|
|
242
256
|
_.isObject(template) && isNonEmptyString(template?.name);
|
|
243
|
-
const
|
|
257
|
+
const hasDateRestrictionErrors = conceptHasDateRestrictionErrors(
|
|
258
|
+
template,
|
|
259
|
+
i18nConcept,
|
|
260
|
+
originalI18nConcept,
|
|
261
|
+
);
|
|
262
|
+
const isInvalid =
|
|
263
|
+
!validDomain() ||
|
|
264
|
+
!validNames() ||
|
|
265
|
+
!validTemplate() ||
|
|
266
|
+
hasDateRestrictionErrors;
|
|
244
267
|
|
|
245
268
|
const renderDomainSelector = () =>
|
|
246
269
|
actionKey === "create" ? (
|
|
@@ -274,6 +297,9 @@ const ConceptForm = ({
|
|
|
274
297
|
required
|
|
275
298
|
header={renderFormHeader()}
|
|
276
299
|
i18nContent={_.mapValues("content", i18nConcept)}
|
|
300
|
+
originalI18nContent={
|
|
301
|
+
originalI18nConcept && _.mapValues("content", originalI18nConcept)
|
|
302
|
+
}
|
|
277
303
|
name={template?.name}
|
|
278
304
|
actionKey={actionKey}
|
|
279
305
|
onChangeContent={handleI18nConceptContentChange}
|
|
@@ -354,6 +380,7 @@ ConceptForm.propTypes = {
|
|
|
354
380
|
domainId: PropTypes.number,
|
|
355
381
|
template: PropTypes.object,
|
|
356
382
|
i18nConcept: PropTypes.object,
|
|
383
|
+
originalI18nConcept: PropTypes.object,
|
|
357
384
|
onSubmit: PropTypes.func,
|
|
358
385
|
resourceId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
359
386
|
setDomainId: PropTypes.func,
|
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
CONCEPT_LINKS_MANAGEMENT,
|
|
11
11
|
CONCEPT_VERSION,
|
|
12
12
|
CONCEPTS_BULK_UPDATE,
|
|
13
|
-
|
|
13
|
+
CONCEPTS_UPLOAD_JOBS,
|
|
14
|
+
CONCEPTS_UPLOAD_JOB,
|
|
14
15
|
CONCEPTS_NEW,
|
|
15
16
|
CONCEPTS_PENDING,
|
|
16
17
|
CONCEPTS_DEPRECATED,
|
|
@@ -23,6 +24,8 @@ import {
|
|
|
23
24
|
import { useIntl } from "react-intl";
|
|
24
25
|
import { getSidemenuGlossarySubscopes } from "@truedat/core/selectors/getSidemenuGlossarySubscopes";
|
|
25
26
|
import { SearchContextProvider } from "@truedat/core/search/SearchContext";
|
|
27
|
+
import UploadJobs from "@truedat/core/components/UploadJobs";
|
|
28
|
+
import UploadJob from "@truedat/core/components/UploadJob";
|
|
26
29
|
import { useLinksSearch, useLinksFilters } from "../relations/hooks/useLinks";
|
|
27
30
|
import ConceptLinksApprovals from "../relations/components/ConceptLinksApprovals";
|
|
28
31
|
import ConceptLinksApprovalResults from "../relations/components/ConceptLinksApprovalResults";
|
|
@@ -36,7 +39,6 @@ import Concepts from "./Concepts";
|
|
|
36
39
|
import ConceptsBulkUpdate from "./ConceptsBulkUpdate";
|
|
37
40
|
|
|
38
41
|
import ConceptSubscriptionLoader from "./ConceptSubscriptionLoader";
|
|
39
|
-
import ConceptsUploadEvents from "./ConceptsUploadEvents";
|
|
40
42
|
|
|
41
43
|
const RelationTagsLoader = React.lazy(
|
|
42
44
|
() => import("@truedat/lm/components/RelationTagsLoader"),
|
|
@@ -165,16 +167,12 @@ const ConceptRoutes = ({
|
|
|
165
167
|
</>
|
|
166
168
|
}
|
|
167
169
|
/>
|
|
168
|
-
<Route
|
|
169
|
-
|
|
170
|
-
path="
|
|
171
|
-
>
|
|
172
|
-
<Route
|
|
173
|
-
// CONCEPTS_BULK_UPLOAD_EVENTS = "/concepts/management/bulk_upload_event";
|
|
174
|
-
path="bulk_upload_event"
|
|
175
|
-
element={<ConceptsUploadEvents />}
|
|
176
|
-
/>
|
|
170
|
+
<Route path="uploadJobs">
|
|
171
|
+
<Route index element={<UploadJobs scope="concepts" />} />
|
|
172
|
+
<Route path=":id" element={<UploadJob scope="concepts" />} />
|
|
173
|
+
</Route>
|
|
177
174
|
|
|
175
|
+
<Route path="management">
|
|
178
176
|
<Route
|
|
179
177
|
path="approvals/results"
|
|
180
178
|
element={<ConceptLinksApprovalResults />}
|
|
@@ -232,9 +230,8 @@ const ConceptRoutes = ({
|
|
|
232
230
|
element={
|
|
233
231
|
<>
|
|
234
232
|
<ConceptCrumbs conceptAction="concepts.actions.edit" />
|
|
235
|
-
<TemplatesLoader scope="bg" />
|
|
236
233
|
<ConceptLoader />
|
|
237
|
-
{conceptLoaded
|
|
234
|
+
{conceptLoaded ? <ConceptEdit /> : null}
|
|
238
235
|
</>
|
|
239
236
|
}
|
|
240
237
|
/>
|
|
@@ -4,7 +4,7 @@ import { FormattedMessage, useIntl } from "react-intl";
|
|
|
4
4
|
import UploadModal from "@truedat/core/components/UploadModal";
|
|
5
5
|
import { useWebContext } from "@truedat/core/webContext";
|
|
6
6
|
import { useConceptsUpload } from "../hooks/useConcepts";
|
|
7
|
-
import {
|
|
7
|
+
import { getConceptsUploadAlertMessage } from "../selectors/messages";
|
|
8
8
|
|
|
9
9
|
export const ConceptsUploadButton = ({ canAutoPublish }) => {
|
|
10
10
|
const { formatMessage, locale } = useIntl();
|
|
@@ -21,13 +21,16 @@ export const ConceptsUploadButton = ({ canAutoPublish }) => {
|
|
|
21
21
|
data.append("lang", locale);
|
|
22
22
|
triggerUpload(data)
|
|
23
23
|
.then(({ status, data }) => {
|
|
24
|
-
const alertMessage =
|
|
24
|
+
const alertMessage = getConceptsUploadAlertMessage({ status, data });
|
|
25
25
|
setAlertMessage(alertMessage);
|
|
26
26
|
})
|
|
27
27
|
.catch((error) => {
|
|
28
28
|
if (error.response) {
|
|
29
29
|
const { status, data } = error.response;
|
|
30
|
-
const alertMessage =
|
|
30
|
+
const alertMessage = getConceptsUploadAlertMessage({
|
|
31
|
+
status,
|
|
32
|
+
data,
|
|
33
|
+
});
|
|
31
34
|
setAlertMessage(alertMessage);
|
|
32
35
|
} else {
|
|
33
36
|
setAlertMessage({ error: true, content: error.message });
|
|
@@ -60,13 +63,19 @@ export const ConceptsUploadButton = ({ canAutoPublish }) => {
|
|
|
60
63
|
handleSubmit={(data) => {
|
|
61
64
|
triggerUpload(data)
|
|
62
65
|
.then(({ status, data }) => {
|
|
63
|
-
const alertMessage =
|
|
66
|
+
const alertMessage = getConceptsUploadAlertMessage({
|
|
67
|
+
status,
|
|
68
|
+
data,
|
|
69
|
+
});
|
|
64
70
|
setAlertMessage(alertMessage);
|
|
65
71
|
})
|
|
66
72
|
.catch((error) => {
|
|
67
73
|
if (error.response) {
|
|
68
74
|
const { status, data } = error.response;
|
|
69
|
-
const alertMessage =
|
|
75
|
+
const alertMessage = getConceptsUploadAlertMessage({
|
|
76
|
+
status,
|
|
77
|
+
data,
|
|
78
|
+
});
|
|
70
79
|
setAlertMessage(alertMessage);
|
|
71
80
|
} else {
|
|
72
81
|
setAlertMessage(error.message);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { act, render } from "@testing-library/react";
|
|
2
|
+
import ConceptForm from "../ConceptForm";
|
|
3
|
+
import { ConceptEdit } from "../ConceptEdit";
|
|
4
|
+
|
|
5
|
+
jest.mock("../ConceptForm", () => ({
|
|
6
|
+
__esModule: true,
|
|
7
|
+
default: jest.fn(() => null),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
jest.mock("@truedat/core/i18n", () => ({
|
|
11
|
+
useLanguage: () => ({ defaultLang: "en", altLangs: ["es"] }),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const savedDate = { value: "2027-01-01", origin: "user" };
|
|
15
|
+
const concept = {
|
|
16
|
+
domain: { id: 1 },
|
|
17
|
+
dynamic_content: { fecha: savedDate },
|
|
18
|
+
i18n_content: { es: { content: { fecha: savedDate } } },
|
|
19
|
+
};
|
|
20
|
+
const props = {
|
|
21
|
+
concept,
|
|
22
|
+
content: {
|
|
23
|
+
en: { content: concept.dynamic_content },
|
|
24
|
+
es: {
|
|
25
|
+
content: {
|
|
26
|
+
...concept.i18n_content.es.content,
|
|
27
|
+
newDate: { value: "2027-01-01", origin: "default" },
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
template: {},
|
|
32
|
+
conceptAction: jest.fn(),
|
|
33
|
+
processedContent: (content) => content,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const formProps = () => ConceptForm.mock.calls.at(-1)[0];
|
|
37
|
+
|
|
38
|
+
describe("ConceptEdit date restriction originals", () => {
|
|
39
|
+
beforeEach(() => jest.clearAllMocks());
|
|
40
|
+
|
|
41
|
+
it("uses persisted default-language dates as the baseline", () => {
|
|
42
|
+
render(<ConceptEdit {...props} />);
|
|
43
|
+
|
|
44
|
+
expect(formProps().originalI18nConcept.en.content).toEqual(
|
|
45
|
+
concept.dynamic_content,
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("does not exempt defaults inserted into translated content", () => {
|
|
50
|
+
render(<ConceptEdit {...props} />);
|
|
51
|
+
|
|
52
|
+
expect(formProps().originalI18nConcept.es.content).not.toHaveProperty(
|
|
53
|
+
"newDate",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("keeps the baseline when editable content changes", () => {
|
|
58
|
+
render(<ConceptEdit {...props} />);
|
|
59
|
+
|
|
60
|
+
act(() => {
|
|
61
|
+
formProps().setI18nConcept({
|
|
62
|
+
en: { content: { fecha: { value: "2027-02-01", origin: "user" } } },
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(formProps().originalI18nConcept.en.content.fecha).toEqual(savedDate);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("keeps the edit-session baseline across refreshed source props", () => {
|
|
70
|
+
const { rerender } = render(<ConceptEdit {...props} />);
|
|
71
|
+
|
|
72
|
+
rerender(
|
|
73
|
+
<ConceptEdit
|
|
74
|
+
{...props}
|
|
75
|
+
concept={{
|
|
76
|
+
...concept,
|
|
77
|
+
dynamic_content: { fecha: { value: "2027-02-01", origin: "user" } },
|
|
78
|
+
}}
|
|
79
|
+
/>,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
expect(formProps().originalI18nConcept.en.content.fecha).toEqual(savedDate);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { fireEvent } from "@testing-library/react";
|
|
1
2
|
import { render } from "@truedat/test/render";
|
|
2
3
|
import { waitFor } from "@testing-library/react";
|
|
3
4
|
import { LangProviderWrapper } from "@truedat/core/i18n";
|
|
5
|
+
import { useTemplate } from "@truedat/core/hooks";
|
|
4
6
|
import ConceptEdit from "../ConceptEdit";
|
|
5
7
|
|
|
6
8
|
jest.mock("@truedat/ai/hooks/useTranslations", () => {
|
|
@@ -58,6 +60,7 @@ jest.mock("@truedat/core/hooks", () => ({
|
|
|
58
60
|
useTemplate: jest.fn(() => ({
|
|
59
61
|
loading: false,
|
|
60
62
|
data: { template },
|
|
63
|
+
refetch: jest.fn(),
|
|
61
64
|
})),
|
|
62
65
|
}));
|
|
63
66
|
|
|
@@ -126,10 +129,36 @@ describe("<ConceptEdit />", () => {
|
|
|
126
129
|
const props = {
|
|
127
130
|
concept,
|
|
128
131
|
};
|
|
132
|
+
|
|
133
|
+
beforeEach(() => {
|
|
134
|
+
useTemplate.mockReturnValue({
|
|
135
|
+
loading: false,
|
|
136
|
+
data: { template },
|
|
137
|
+
refetch: jest.fn(),
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
129
141
|
it("matches the latest snapshot", async () => {
|
|
130
142
|
const { container, queryByText } = renderComponent(props);
|
|
131
143
|
|
|
132
144
|
await waitFor(() => expect(queryByText(/foo_user/i)).toBeInTheDocument());
|
|
133
145
|
expect(container).toMatchSnapshot();
|
|
134
146
|
});
|
|
147
|
+
|
|
148
|
+
it("shows a visible retry message when its template fails to load", () => {
|
|
149
|
+
const refetch = jest.fn();
|
|
150
|
+
useTemplate.mockReturnValue({
|
|
151
|
+
loading: false,
|
|
152
|
+
error: new Error("template request failed"),
|
|
153
|
+
refetch,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const { getByText, getByRole } = renderComponent({
|
|
157
|
+
concept: { ...concept, type: "template1" },
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expect(getByText(/templates could not be loaded/i)).toBeInTheDocument();
|
|
161
|
+
fireEvent.click(getByRole("button", { name: /retry/i }));
|
|
162
|
+
expect(refetch).toHaveBeenCalledTimes(1);
|
|
163
|
+
});
|
|
135
164
|
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { fireEvent, render } from "@testing-library/react";
|
|
2
|
+
import { render as renderWithProviders } from "@truedat/test/render";
|
|
3
|
+
import { LangProviderWrapper } from "@truedat/core/i18n";
|
|
4
|
+
import { useTemplate } from "@truedat/core/hooks";
|
|
5
|
+
import ConceptForm from "../ConceptForm";
|
|
6
|
+
import { ConceptEditLoader } from "../ConceptEdit";
|
|
7
|
+
|
|
8
|
+
jest.mock("@truedat/core/hooks", () => ({
|
|
9
|
+
useTemplate: jest.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
jest.mock("../ConceptForm", () => ({
|
|
13
|
+
__esModule: true,
|
|
14
|
+
default: jest.fn(() => null),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const template = {
|
|
18
|
+
name: "concept-template",
|
|
19
|
+
content: [
|
|
20
|
+
{
|
|
21
|
+
fields: [
|
|
22
|
+
{ name: "description", widget: "string" },
|
|
23
|
+
{ name: "owner", widget: "dropdown" },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const concept = {
|
|
30
|
+
business_concept_id: 42,
|
|
31
|
+
type: template.name,
|
|
32
|
+
name: "BCV name",
|
|
33
|
+
domain: { id: 7 },
|
|
34
|
+
dynamic_content: {
|
|
35
|
+
description: { value: "BCV description", origin: "user" },
|
|
36
|
+
owner: { value: "alice", origin: "user" },
|
|
37
|
+
},
|
|
38
|
+
i18n_content: {
|
|
39
|
+
es: {
|
|
40
|
+
name: "Nombre traducido",
|
|
41
|
+
content: {
|
|
42
|
+
description: { value: "Descripción BCV", origin: "user" },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
describe("ConceptEditLoader", () => {
|
|
49
|
+
const props = {
|
|
50
|
+
concept,
|
|
51
|
+
templateName: template.name,
|
|
52
|
+
conceptAction: jest.fn(),
|
|
53
|
+
action: {},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
jest.clearAllMocks();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("waits for the BCV template before initializing the form with BCV content", () => {
|
|
61
|
+
useTemplate.mockReturnValueOnce({ loading: true }).mockReturnValueOnce({
|
|
62
|
+
loading: false,
|
|
63
|
+
data: { template },
|
|
64
|
+
refetch: jest.fn(),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const { rerender } = render(
|
|
68
|
+
<LangProviderWrapper langs={["en", "es"]}>
|
|
69
|
+
<ConceptEditLoader {...props} />
|
|
70
|
+
</LangProviderWrapper>,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect(ConceptForm).not.toHaveBeenCalled();
|
|
74
|
+
|
|
75
|
+
rerender(
|
|
76
|
+
<LangProviderWrapper langs={["en", "es"]}>
|
|
77
|
+
<ConceptEditLoader {...props} />
|
|
78
|
+
</LangProviderWrapper>,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(ConceptForm).toHaveBeenCalledTimes(1);
|
|
82
|
+
const formProps = ConceptForm.mock.calls[0][0];
|
|
83
|
+
expect(formProps.i18nConcept.en).toMatchObject({
|
|
84
|
+
name: "BCV name",
|
|
85
|
+
content: concept.dynamic_content,
|
|
86
|
+
});
|
|
87
|
+
expect(formProps.i18nConcept.es).toMatchObject({
|
|
88
|
+
name: "Nombre traducido",
|
|
89
|
+
content: {
|
|
90
|
+
description: { value: "Descripción BCV", origin: "user" },
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
expect(formProps.template).toEqual(template);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("shows a retry message when the completed query has no template", () => {
|
|
97
|
+
useTemplate.mockReturnValue({
|
|
98
|
+
loading: false,
|
|
99
|
+
data: { template: null },
|
|
100
|
+
refetch: jest.fn(),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const rendered = renderWithProviders(
|
|
104
|
+
<LangProviderWrapper langs={["en", "es"]}>
|
|
105
|
+
<ConceptEditLoader {...props} />
|
|
106
|
+
</LangProviderWrapper>,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(
|
|
110
|
+
rendered.getByText(/templates could not be loaded/i),
|
|
111
|
+
).toBeInTheDocument();
|
|
112
|
+
expect(
|
|
113
|
+
rendered.getByRole("button", { name: /retry/i }),
|
|
114
|
+
).toBeInTheDocument();
|
|
115
|
+
expect(ConceptForm).not.toHaveBeenCalled();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("loads the form after retrying a failed template query", () => {
|
|
119
|
+
const refetch = jest.fn(() => {
|
|
120
|
+
useTemplate.mockReturnValue({
|
|
121
|
+
loading: true,
|
|
122
|
+
refetch,
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
useTemplate.mockReturnValue({
|
|
126
|
+
loading: false,
|
|
127
|
+
error: new Error("template request failed"),
|
|
128
|
+
refetch,
|
|
129
|
+
});
|
|
130
|
+
const component = () => (
|
|
131
|
+
<LangProviderWrapper langs={["en", "es"]}>
|
|
132
|
+
<ConceptEditLoader {...props} />
|
|
133
|
+
</LangProviderWrapper>
|
|
134
|
+
);
|
|
135
|
+
const rendered = renderWithProviders(component());
|
|
136
|
+
|
|
137
|
+
fireEvent.click(rendered.getByRole("button", { name: /retry/i }));
|
|
138
|
+
expect(refetch).toHaveBeenCalledTimes(1);
|
|
139
|
+
|
|
140
|
+
rendered.rerender(component());
|
|
141
|
+
expect(ConceptForm).not.toHaveBeenCalled();
|
|
142
|
+
expect(
|
|
143
|
+
rendered.queryByText(/templates could not be loaded/i),
|
|
144
|
+
).not.toBeInTheDocument();
|
|
145
|
+
|
|
146
|
+
useTemplate.mockReturnValue({
|
|
147
|
+
loading: false,
|
|
148
|
+
data: { template },
|
|
149
|
+
refetch,
|
|
150
|
+
});
|
|
151
|
+
rendered.rerender(component());
|
|
152
|
+
|
|
153
|
+
expect(ConceptForm).toHaveBeenCalled();
|
|
154
|
+
expect(ConceptForm.mock.calls[0][0].i18nConcept.en).toMatchObject({
|
|
155
|
+
name: concept.name,
|
|
156
|
+
content: concept.dynamic_content,
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|