@truedat/bg 8.13.5 → 8.13.7
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/components/ConceptEdit.js +23 -1
- package/src/concepts/components/ConceptForm.js +28 -1
- package/src/concepts/components/ConceptRoutes.js +1 -2
- 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 +3 -1
- package/src/concepts/components/__tests__/__snapshots__/ConceptRoutes.spec.js.snap +0 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/bg",
|
|
3
|
-
"version": "8.13.
|
|
3
|
+
"version": "8.13.7",
|
|
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.7",
|
|
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": "704dcfcba34b6094a368a93059de7f4937502f32"
|
|
94
94
|
}
|
|
@@ -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,
|
|
@@ -230,9 +230,8 @@ const ConceptRoutes = ({
|
|
|
230
230
|
element={
|
|
231
231
|
<>
|
|
232
232
|
<ConceptCrumbs conceptAction="concepts.actions.edit" />
|
|
233
|
-
<TemplatesLoader scope="bg" />
|
|
234
233
|
<ConceptLoader />
|
|
235
|
-
{conceptLoaded
|
|
234
|
+
{conceptLoaded ? <ConceptEdit /> : null}
|
|
236
235
|
</>
|
|
237
236
|
}
|
|
238
237
|
/>
|
|
@@ -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
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { waitFor } from "@testing-library/react";
|
|
2
2
|
import { render, waitForLoad } from "@truedat/test/render";
|
|
3
3
|
import { domainsMock, multipleTemplatesMock } from "@truedat/test/mocks";
|
|
4
|
-
import ConceptForm from "../ConceptForm";
|
|
4
|
+
import ConceptForm, { conceptHasDateRestrictionErrors } from "../ConceptForm";
|
|
5
5
|
|
|
6
6
|
jest.mock("@truedat/ai/hooks/useSuggestions", () => {
|
|
7
7
|
const originalModule = jest.requireActual("@truedat/ai/hooks/useSuggestions");
|
|
@@ -76,7 +76,103 @@ const props = {
|
|
|
76
76
|
const templateVariables = { scope: "bg", domainIds: [1] };
|
|
77
77
|
const domainVariables = { action: "createBusinessConcept" };
|
|
78
78
|
|
|
79
|
+
const dateTemplate = {
|
|
80
|
+
content: [
|
|
81
|
+
{
|
|
82
|
+
name: "dates",
|
|
83
|
+
fields: [
|
|
84
|
+
{
|
|
85
|
+
name: "fecha",
|
|
86
|
+
type: "date",
|
|
87
|
+
restrictions: { resolved: { min: null, max: "2026-10-15" } },
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const savedConcept = {
|
|
95
|
+
en: { content: { fecha: { value: "2026-10-16", origin: "user" } } },
|
|
96
|
+
};
|
|
97
|
+
|
|
79
98
|
describe("<ConceptForm />", () => {
|
|
99
|
+
it("allows an unchanged existing date outside the latest restriction", () => {
|
|
100
|
+
expect(
|
|
101
|
+
conceptHasDateRestrictionErrors(
|
|
102
|
+
dateTemplate,
|
|
103
|
+
{
|
|
104
|
+
en: {
|
|
105
|
+
name: "Updated name",
|
|
106
|
+
content: { fecha: { value: "2026-10-16", origin: "user" } },
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
savedConcept,
|
|
110
|
+
),
|
|
111
|
+
).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("rejects a modified date outside the latest restriction", () => {
|
|
115
|
+
expect(
|
|
116
|
+
conceptHasDateRestrictionErrors(
|
|
117
|
+
dateTemplate,
|
|
118
|
+
{ en: { content: { fecha: { value: "2026-10-17", origin: "user" } } } },
|
|
119
|
+
savedConcept,
|
|
120
|
+
),
|
|
121
|
+
).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("does not exempt a new language using another language's saved date", () => {
|
|
125
|
+
expect(
|
|
126
|
+
conceptHasDateRestrictionErrors(
|
|
127
|
+
dateTemplate,
|
|
128
|
+
{
|
|
129
|
+
...savedConcept,
|
|
130
|
+
es: { content: { fecha: { value: "2026-10-16", origin: "user" } } },
|
|
131
|
+
},
|
|
132
|
+
savedConcept,
|
|
133
|
+
),
|
|
134
|
+
).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("detects an invalid date so Save can be disabled", () => {
|
|
138
|
+
const template = {
|
|
139
|
+
content: [
|
|
140
|
+
{
|
|
141
|
+
name: "dates",
|
|
142
|
+
fields: [
|
|
143
|
+
{
|
|
144
|
+
name: "fecha",
|
|
145
|
+
type: "date",
|
|
146
|
+
restrictions: {
|
|
147
|
+
resolved: { min: null, max: "2026-10-15" },
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
expect(
|
|
156
|
+
conceptHasDateRestrictionErrors(template, {
|
|
157
|
+
en: {
|
|
158
|
+
content: {
|
|
159
|
+
fecha: { value: "2026-10-16", origin: "user" },
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
}),
|
|
163
|
+
).toBe(true);
|
|
164
|
+
|
|
165
|
+
expect(
|
|
166
|
+
conceptHasDateRestrictionErrors(template, {
|
|
167
|
+
en: {
|
|
168
|
+
content: {
|
|
169
|
+
fecha: { value: "2026-10-15", origin: "user" },
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
}),
|
|
173
|
+
).toBe(false);
|
|
174
|
+
});
|
|
175
|
+
|
|
80
176
|
describe("with multiple templates", () => {
|
|
81
177
|
const renderOpts = {
|
|
82
178
|
mocks: [
|
|
@@ -152,10 +152,12 @@ describe("<ConceptRoutes />", () => {
|
|
|
152
152
|
state: {
|
|
153
153
|
concept: { business_concept_id: "123" },
|
|
154
154
|
templatesLoading: false,
|
|
155
|
-
templates:
|
|
155
|
+
templates: [],
|
|
156
156
|
},
|
|
157
157
|
});
|
|
158
158
|
await waitForLoad(rendered);
|
|
159
|
+
expect(rendered.getByText("ConceptEdit")).toBeInTheDocument();
|
|
160
|
+
expect(rendered.queryByText("TemplatesLoader")).not.toBeInTheDocument();
|
|
159
161
|
expect(rendered.container).toMatchSnapshot();
|
|
160
162
|
});
|
|
161
163
|
|