@truedat/qx 8.2.0 → 8.2.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/qx",
3
- "version": "8.2.0",
3
+ "version": "8.2.1",
4
4
  "description": "Truedat Web Quality Experience package",
5
5
  "sideEffects": false,
6
6
  "module": "src/index.js",
@@ -53,7 +53,7 @@
53
53
  "@testing-library/jest-dom": "^6.6.3",
54
54
  "@testing-library/react": "^16.3.0",
55
55
  "@testing-library/user-event": "^14.6.1",
56
- "@truedat/test": "8.2.0",
56
+ "@truedat/test": "8.2.1",
57
57
  "identity-obj-proxy": "^3.0.0",
58
58
  "jest": "^29.7.0",
59
59
  "redux-saga-test-plan": "^4.0.6"
@@ -86,5 +86,5 @@
86
86
  "semantic-ui-react": "^3.0.0-beta.2",
87
87
  "swr": "^2.3.3"
88
88
  },
89
- "gitHead": "a0b6d89f64e82602a1f3c5e250691526fe0c558a"
89
+ "gitHead": "3008f4a5a734dc1f12e9f25a790542b56aed61d2"
90
90
  }
@@ -0,0 +1,38 @@
1
+ import { lazy, use } from "react";
2
+ import { Header } from "semantic-ui-react";
3
+ import { useIntl } from "react-intl";
4
+ import QxContext from "../QxContext";
5
+ import { makeTagOptionsSelector } from "@truedat/core/selectors";
6
+ import { linkTo } from "@truedat/core/routes";
7
+
8
+ const ConceptLinkForm = lazy(
9
+ () => import("@truedat/lm/components/ConceptLinkForm")
10
+ );
11
+
12
+ const selectTagOptions = makeTagOptionsSelector("quality_control");
13
+
14
+ const ConceptLinkNew = () => {
15
+ const { formatMessage } = useIntl();
16
+ const { qualityControl } = use(QxContext);
17
+ return (
18
+ <>
19
+ <Header
20
+ as="h4"
21
+ content={formatMessage({
22
+ id: "quality_controls.relation.new.concept.header",
23
+ })}
24
+ />
25
+ <ConceptLinkForm
26
+ targetId={qualityControl.id}
27
+ targetType="quality_control"
28
+ redirectUrl={linkTo.QUALITY_CONTROL_CONCEPTS({
29
+ id: qualityControl.id,
30
+ version: qualityControl.version,
31
+ })}
32
+ selectTagOptions={selectTagOptions}
33
+ />
34
+ </>
35
+ );
36
+ };
37
+
38
+ export default ConceptLinkNew;
@@ -0,0 +1,15 @@
1
+ import { lazy, use } from "react";
2
+ import QxContext from "../QxContext";
3
+
4
+ const QualityControlConcepts = lazy(
5
+ () => import("@truedat/lm/components/QualityControlConcepts")
6
+ );
7
+
8
+ const ConceptLinks = () => {
9
+ const { qualityControl, actions } = use(QxContext);
10
+ return (
11
+ <QualityControlConcepts qualityControl={qualityControl} actions={actions} />
12
+ );
13
+ };
14
+
15
+ export default ConceptLinks;
@@ -114,7 +114,14 @@ export default function QualityControlActions() {
114
114
 
115
115
  const availableActions = _.flow(
116
116
  _.reject((action) =>
117
- _.includes(action, ["delete_score", "update_main", "execute"])
117
+ _.includes(action, [
118
+ "delete_score",
119
+ "update_main",
120
+ "execute",
121
+ "link_to_business_concept",
122
+ ,
123
+ "link_to_data_structure",
124
+ ])
118
125
  ),
119
126
  _.map((action) => {
120
127
  const actionIcon = {
@@ -1,9 +1,11 @@
1
1
  import _ from "lodash/fp";
2
+ import { useEffect, useState } from "react";
2
3
  import PropTypes from "prop-types";
3
4
  import { FormattedMessage } from "react-intl";
4
5
  import { useParams, Outlet } from "react-router";
5
6
  import { Header, Icon, Segment, Grid, Label } from "semantic-ui-react";
6
7
  import { colorForStatus } from "@truedat/core/services/statusColor";
8
+ import { useRelations } from "@truedat/lm/hooks/useRelations";
7
9
 
8
10
  import { useQualityControl } from "../../hooks/useQualityControls";
9
11
  import QxContext from "../QxContext";
@@ -23,10 +25,51 @@ export default function QualityControlHeader({ children }) {
23
25
  ? ["play", "green"]
24
26
  : ["pause", "orange"];
25
27
 
28
+ const qualityControlId = qualityControl?.id;
29
+ const { trigger: searchRelations, isMutating: searchingRelations } =
30
+ useRelations();
31
+ const [conceptLinkCount, setConceptLinkCount] = useState(0);
32
+ const [structureLinkCount, setStructureLinkCount] = useState(0);
33
+
34
+ useEffect(() => {
35
+ if (qualityControlId) {
36
+ const conceptLinksPayload = {
37
+ must: {
38
+ target_id: qualityControlId,
39
+ target_type: "quality_control",
40
+ source_type: "business_concept",
41
+ },
42
+ size: 0,
43
+ };
44
+ const structureLinksPayload = {
45
+ must: {
46
+ source_id: qualityControlId,
47
+ source_type: "quality_control",
48
+ target_type: "data_structure",
49
+ },
50
+ size: 0,
51
+ };
52
+
53
+ Promise.all([
54
+ searchRelations(conceptLinksPayload),
55
+ searchRelations(structureLinksPayload),
56
+ ]).then(([conceptResponse, structureResponse]) => {
57
+ const conceptCount = parseInt(
58
+ conceptResponse?.headers?.["x-total-count"] || "0"
59
+ );
60
+ const structureCount = parseInt(
61
+ structureResponse?.headers?.["x-total-count"] || "0"
62
+ );
63
+ setConceptLinkCount(conceptCount);
64
+ setStructureLinkCount(structureCount);
65
+ });
66
+ }
67
+ }, [qualityControlId, searchRelations]);
68
+
26
69
  return (
27
70
  <QxContext value={context}>
28
71
  <QualityControlCrumbs qualityControl={qualityControl} />
29
- <Segment loading={loading}>
72
+ <Segment loading={loading || searchingRelations}>
30
73
  {!_.isEmpty(qualityControl) ? (
31
74
  <>
32
75
  <Grid>
@@ -70,7 +113,11 @@ export default function QualityControlHeader({ children }) {
70
113
  id={`quality_control.status.${qualityControl.status}`}
71
114
  />
72
115
  </Label>
73
- <QualityControlTabs />
116
+ <QualityControlTabs
117
+ conceptLinkCount={conceptLinkCount}
118
+ structureLinkCount={structureLinkCount}
119
+ actions={actions}
120
+ />
74
121
  {children ? children : <Outlet />}
75
122
  </>
76
123
  ) : null}
@@ -1,6 +1,6 @@
1
1
  import { Route, Routes, useParams } from "react-router";
2
2
  import { SearchContextProvider } from "@truedat/core/search/SearchContext";
3
- // This is only for information purposes, we are not using these routes in the code
3
+ // This is only for information purposes, don't remove
4
4
  import {
5
5
  QUALITY_CONTROLS_DEPRECATED,
6
6
  QUALITY_CONTROLS_DRAFTS,
@@ -25,6 +25,10 @@ import QualityControlScores from "./QualityControlScores";
25
25
  import NewQualityControl from "./NewQualityControl";
26
26
  import EditQualityControl from "./EditQualityControl";
27
27
  import NewDraftQualityControl from "./NewDraftQualityControl";
28
+ import ConceptLinks from "./ConceptLinks";
29
+ import ConceptLinkNew from "./ConceptLinkNew";
30
+ import StructureLinks from "./StructureLinks";
31
+ import StructureLinkNew from "./StructureLinkNew";
28
32
  import { ScoreContextProvider } from "../scores/ScoreContext";
29
33
  import QualityControlEvents from "./QualityControlEvents";
30
34
 
@@ -94,6 +98,14 @@ export default function QualityControlRoutes() {
94
98
 
95
99
  <Route path=":id/version/:version" element={<QualityControlHeader />}>
96
100
  <Route index element={<QualityControl />} />
101
+ <Route path="links/concepts">
102
+ <Route index element={<ConceptLinks />} />
103
+ <Route path="new" element={<ConceptLinkNew />} />
104
+ </Route>
105
+ <Route path="links/structures">
106
+ <Route index element={<StructureLinks />} />
107
+ <Route path="new" element={<StructureLinkNew />} />
108
+ </Route>
97
109
  <Route path="history" element={<QualityControlHistory />} />
98
110
  <Route path="scores" element={<LoaderQualityControlScores />} />
99
111
  <Route path="events" element={<QualityControlEvents />} />
@@ -1,39 +1,80 @@
1
+ import _ from "lodash/fp";
2
+ import PropTypes from "prop-types";
1
3
  import { Menu } from "semantic-ui-react";
2
4
  import { Link, useLocation } from "react-router";
3
5
  import { useParams } from "react-router";
4
6
  import { FormattedMessage } from "react-intl";
5
7
  import { linkTo } from "@truedat/core/routes";
6
8
 
7
- export default function QualityControlTabs() {
9
+ export default function QualityControlTabs({
10
+ actions,
11
+ conceptLinkCount = 0,
12
+ structureLinkCount = 0,
13
+ }) {
8
14
  const { pathname } = useLocation();
9
15
  const urlParams = useParams();
16
+ const canCreateConceptLink = _.includes("link_to_business_concept")(actions);
17
+ const canCreateStructureLink = _.includes("link_to_data_structure")(actions);
10
18
 
11
19
  const tabs = [
12
20
  {
13
- url: linkTo.QUALITY_CONTROL(urlParams),
21
+ url: [linkTo.QUALITY_CONTROL(urlParams)],
14
22
  text: "quality_control.tabs.quality_control",
23
+ display: true,
15
24
  },
16
25
  {
17
- url: linkTo.QUALITY_CONTROL_HISTORY(urlParams),
26
+ url: [
27
+ linkTo.QUALITY_CONTROL_CONCEPTS(urlParams),
28
+ linkTo.QUALITY_CONTROL_CONCEPT_LINKS_NEW(urlParams),
29
+ ],
30
+ text: "quality_control.tabs.concept_links",
31
+ display: conceptLinkCount !== 0 || canCreateConceptLink,
32
+ },
33
+ {
34
+ url: [
35
+ linkTo.QUALITY_CONTROL_STRUCTURES(urlParams),
36
+ linkTo.QUALITY_CONTROL_STRUCTURE_LINKS_NEW(urlParams),
37
+ ],
38
+ text: "quality_control.tabs.structure_links",
39
+ display: structureLinkCount !== 0 || canCreateStructureLink,
40
+ },
41
+ {
42
+ url: [linkTo.QUALITY_CONTROL_HISTORY(urlParams)],
18
43
  text: "quality_control.tabs.history",
44
+ display: true,
19
45
  },
20
46
  {
21
- url: linkTo.QUALITY_CONTROL_SCORES(urlParams),
47
+ url: [linkTo.QUALITY_CONTROL_SCORES(urlParams)],
22
48
  text: "quality_control.tabs.scores",
49
+ display: true,
23
50
  },
24
51
  {
25
- url: linkTo.QUALITY_CONTROL_EVENTS(urlParams),
52
+ url: [linkTo.QUALITY_CONTROL_EVENTS(urlParams)],
26
53
  text: "quality_control.tabs.events",
54
+ display: true,
27
55
  },
28
56
  ];
29
57
 
30
58
  return (
31
59
  <Menu attached="top" pointing secondary tabular>
32
- {tabs.map(({ url, text }, key) => (
33
- <Menu.Item key={key} active={pathname === url} as={Link} to={url}>
34
- <FormattedMessage id={text} />
35
- </Menu.Item>
36
- ))}
60
+ {tabs
61
+ .filter(({ display }) => display)
62
+ .map(({ url, text }, key) => (
63
+ <Menu.Item
64
+ key={key}
65
+ active={_.includes(pathname)(url)}
66
+ as={Link}
67
+ to={url[0]}
68
+ >
69
+ <FormattedMessage id={text} />
70
+ </Menu.Item>
71
+ ))}
37
72
  </Menu>
38
73
  );
39
74
  }
75
+
76
+ QualityControlTabs.propTypes = {
77
+ actions: PropTypes.array,
78
+ conceptLinkCount: PropTypes.number,
79
+ structureLinkCount: PropTypes.number,
80
+ };
@@ -0,0 +1,38 @@
1
+ import { lazy, use } from "react";
2
+ import { Header } from "semantic-ui-react";
3
+ import { useIntl } from "react-intl";
4
+ import QxContext from "../QxContext";
5
+ import { makeTagOptionsSelector } from "@truedat/core/selectors";
6
+ import { linkTo } from "@truedat/core/routes";
7
+
8
+ const StructureLinkForm = lazy(
9
+ () => import("@truedat/lm/components/StructureLinkForm")
10
+ );
11
+
12
+ const selectTagOptions = makeTagOptionsSelector("quality_control");
13
+
14
+ const StructureLinkNew = () => {
15
+ const { formatMessage } = useIntl();
16
+ const { qualityControl } = use(QxContext);
17
+ return (
18
+ <>
19
+ <Header
20
+ as="h4"
21
+ content={formatMessage({
22
+ id: "quality_controls.relation.new.structure.header",
23
+ })}
24
+ />
25
+ <StructureLinkForm
26
+ sourceId={qualityControl.id}
27
+ sourceType="quality_control"
28
+ redirectUrl={linkTo.QUALITY_CONTROL_STRUCTURES({
29
+ id: qualityControl.id,
30
+ version: qualityControl.version,
31
+ })}
32
+ selectTagOptions={selectTagOptions}
33
+ />
34
+ </>
35
+ );
36
+ };
37
+
38
+ export default StructureLinkNew;
@@ -0,0 +1,18 @@
1
+ import { lazy, use } from "react";
2
+ import QxContext from "../QxContext";
3
+
4
+ const QualityControlStructures = lazy(
5
+ () => import("@truedat/lm/components/QualityControlStructures")
6
+ );
7
+
8
+ const StructureLinks = () => {
9
+ const { qualityControl, actions } = use(QxContext);
10
+ return (
11
+ <QualityControlStructures
12
+ qualityControl={qualityControl}
13
+ actions={actions}
14
+ />
15
+ );
16
+ };
17
+
18
+ export default StructureLinks;
@@ -0,0 +1,63 @@
1
+ import { render, waitForLoad } from "@truedat/test/render";
2
+ import ConceptLinkNew from "../ConceptLinkNew";
3
+ import QxContext from "../../QxContext";
4
+
5
+ jest.mock("react-intl", () => ({
6
+ ...jest.requireActual("react-intl"),
7
+ useIntl: () => ({
8
+ formatMessage: ({ id }) => id,
9
+ }),
10
+ }));
11
+
12
+ jest.mock("@truedat/core/selectors", () => ({
13
+ makeTagOptionsSelector: jest.fn(() => jest.fn(() => [])),
14
+ }));
15
+
16
+ jest.mock("@truedat/core/routes", () => ({
17
+ linkTo: {
18
+ QUALITY_CONTROL_CONCEPTS: jest.fn(
19
+ ({ id, version }) =>
20
+ `/qualityControls/${id}/version/${version}/links/concepts`
21
+ ),
22
+ },
23
+ }));
24
+
25
+ jest.mock("@truedat/lm/components/ConceptLinkForm", () =>
26
+ jest.fn(() => <div>ConceptLinkForm</div>)
27
+ );
28
+
29
+ const qualityControl = {
30
+ id: 10,
31
+ version: 1,
32
+ name: "Test Quality Control",
33
+ };
34
+
35
+ const context = {
36
+ qualityControl,
37
+ };
38
+
39
+ describe("<ConceptLinkNew />", () => {
40
+ it("matches the latest snapshot", async () => {
41
+ const rendered = render(
42
+ <QxContext value={context}>
43
+ <ConceptLinkNew />
44
+ </QxContext>
45
+ );
46
+ await waitForLoad(rendered);
47
+ expect(rendered.container).toMatchSnapshot();
48
+ });
49
+
50
+ it("renders header and ConceptLinkForm", async () => {
51
+ const rendered = render(
52
+ <QxContext value={context}>
53
+ <ConceptLinkNew />
54
+ </QxContext>
55
+ );
56
+ await waitForLoad(rendered);
57
+
58
+ expect(
59
+ rendered.getByText("quality_controls.relation.new.concept.header")
60
+ ).toBeInTheDocument();
61
+ expect(rendered.getByText("ConceptLinkForm")).toBeInTheDocument();
62
+ });
63
+ });
@@ -0,0 +1,43 @@
1
+ import { render, waitForLoad } from "@truedat/test/render";
2
+ import ConceptLinks from "../ConceptLinks";
3
+ import QxContext from "../../QxContext";
4
+
5
+ jest.mock("@truedat/lm/components/QualityControlConcepts", () =>
6
+ jest.fn(() => <div>QualityControlConcepts</div>)
7
+ );
8
+
9
+ const qualityControl = {
10
+ id: 10,
11
+ version: 1,
12
+ name: "Test Quality Control",
13
+ };
14
+
15
+ const actions = ["link_to_business_concept"];
16
+
17
+ const context = {
18
+ qualityControl,
19
+ actions,
20
+ };
21
+
22
+ describe("<ConceptLinks />", () => {
23
+ it("matches the latest snapshot", async () => {
24
+ const rendered = render(
25
+ <QxContext value={context}>
26
+ <ConceptLinks />
27
+ </QxContext>
28
+ );
29
+ await waitForLoad(rendered);
30
+ expect(rendered.container).toMatchSnapshot();
31
+ });
32
+
33
+ it("renders QualityControlConcepts", async () => {
34
+ const rendered = render(
35
+ <QxContext value={context}>
36
+ <ConceptLinks />
37
+ </QxContext>
38
+ );
39
+ await waitForLoad(rendered);
40
+
41
+ expect(rendered.getByText("QualityControlConcepts")).toBeInTheDocument();
42
+ });
43
+ });
@@ -1,13 +1,23 @@
1
+ import { waitFor } from "@testing-library/react";
1
2
  import { render, waitForLoad } from "@truedat/test/render";
2
3
  import { useQualityControl } from "@truedat/qx/hooks/useQualityControls";
3
4
  import QualityControlHeader from "../QualityControlHeader";
4
5
  import { qualityControlData } from "./__fixtures__/qualityControlHelper";
5
6
 
7
+ const mockSearchRelations = jest.fn();
8
+
6
9
  jest.mock("react-router", () => ({
7
10
  ...jest.requireActual("react-router"),
8
11
  useParams: () => ({ id: "8", version: "1" }),
9
12
  }));
10
13
 
14
+ jest.mock("@truedat/lm/hooks/useRelations", () => ({
15
+ useRelations: () => ({
16
+ trigger: mockSearchRelations,
17
+ isMutating: false,
18
+ }),
19
+ }));
20
+
11
21
  jest.mock("@truedat/qx/hooks/useQualityControls", () => {
12
22
  const { qualityControlData } = jest.requireActual(
13
23
  "@truedat/qx/components/qualityControls/__tests__/__fixtures__/qualityControlHelper"
@@ -49,6 +59,34 @@ jest.mock("@truedat/qx/hooks/useQualityControls", () => {
49
59
  });
50
60
 
51
61
  describe("<QualityControlHeader />", () => {
62
+ const defaultMockImplementation = () => ({
63
+ data: {
64
+ data: qualityControlData(),
65
+ _actions: [
66
+ "deprecate",
67
+ "create_draft",
68
+ "toggle_active",
69
+ "delete_score",
70
+ "update_main",
71
+ "execute",
72
+ "delete",
73
+ ],
74
+ },
75
+ loading: false,
76
+ mutate: jest.fn(),
77
+ });
78
+
79
+ beforeEach(() => {
80
+ useQualityControl.mockImplementation(defaultMockImplementation);
81
+ mockSearchRelations.mockResolvedValue({
82
+ headers: { "x-total-count": "5" },
83
+ });
84
+ });
85
+
86
+ afterEach(() => {
87
+ jest.clearAllMocks();
88
+ });
89
+
52
90
  it("matches the latest snapshot with control quality active", async () => {
53
91
  const rendered = render(<QualityControlHeader />);
54
92
  await waitForLoad(rendered);
@@ -78,4 +116,116 @@ describe("<QualityControlHeader />", () => {
78
116
 
79
117
  expect(rendered.queryByText(".header")).not.toBeInTheDocument();
80
118
  });
119
+
120
+ it("fetches concept and structure link counts", async () => {
121
+ useQualityControl.mockImplementation(() => ({
122
+ data: {
123
+ data: qualityControlData(),
124
+ _actions: [],
125
+ },
126
+ loading: false,
127
+ mutate: jest.fn(),
128
+ }));
129
+
130
+ mockSearchRelations
131
+ .mockResolvedValueOnce({
132
+ headers: { "x-total-count": "10" },
133
+ })
134
+ .mockResolvedValueOnce({
135
+ headers: { "x-total-count": "7" },
136
+ });
137
+
138
+ const rendered = render(<QualityControlHeader />);
139
+ await waitForLoad(rendered);
140
+
141
+ await waitFor(() => {
142
+ expect(mockSearchRelations).toHaveBeenCalledTimes(2);
143
+ });
144
+
145
+ expect(mockSearchRelations).toHaveBeenCalledWith({
146
+ must: {
147
+ target_id: qualityControlData().id,
148
+ target_type: "quality_control",
149
+ source_type: "business_concept",
150
+ },
151
+ size: 0,
152
+ });
153
+
154
+ expect(mockSearchRelations).toHaveBeenCalledWith({
155
+ must: {
156
+ source_id: qualityControlData().id,
157
+ source_type: "quality_control",
158
+ target_type: "data_structure",
159
+ },
160
+ size: 0,
161
+ });
162
+ });
163
+
164
+ it("handles missing x-total-count header gracefully", async () => {
165
+ useQualityControl.mockImplementation(() => ({
166
+ data: {
167
+ data: qualityControlData(),
168
+ _actions: [],
169
+ },
170
+ loading: false,
171
+ mutate: jest.fn(),
172
+ }));
173
+
174
+ mockSearchRelations.mockResolvedValue({
175
+ headers: {},
176
+ });
177
+
178
+ const rendered = render(<QualityControlHeader />);
179
+ await waitForLoad(rendered);
180
+
181
+ await waitFor(() => {
182
+ expect(mockSearchRelations).toHaveBeenCalled();
183
+ });
184
+
185
+ expect(rendered.container).toBeInTheDocument();
186
+ });
187
+
188
+ it("fetches counts with correct payloads and updates state", async () => {
189
+ useQualityControl.mockImplementation(() => ({
190
+ data: {
191
+ data: qualityControlData(),
192
+ _actions: [],
193
+ },
194
+ loading: false,
195
+ mutate: jest.fn(),
196
+ }));
197
+
198
+ mockSearchRelations
199
+ .mockResolvedValueOnce({
200
+ headers: { "x-total-count": "15" },
201
+ })
202
+ .mockResolvedValueOnce({
203
+ headers: { "x-total-count": "23" },
204
+ });
205
+
206
+ const rendered = render(<QualityControlHeader />);
207
+ await waitForLoad(rendered);
208
+
209
+ await waitFor(() => {
210
+ expect(mockSearchRelations).toHaveBeenCalledTimes(2);
211
+ });
212
+
213
+ expect(mockSearchRelations).toHaveBeenNthCalledWith(1, {
214
+ must: {
215
+ target_id: qualityControlData().id,
216
+ target_type: "quality_control",
217
+ source_type: "business_concept",
218
+ },
219
+ size: 0,
220
+ });
221
+
222
+ expect(mockSearchRelations).toHaveBeenNthCalledWith(2, {
223
+ must: {
224
+ source_id: qualityControlData().id,
225
+ source_type: "quality_control",
226
+ target_type: "data_structure",
227
+ },
228
+ size: 0,
229
+ });
230
+ });
81
231
  });
@@ -0,0 +1,93 @@
1
+ import { render, waitForLoad } from "@truedat/test/render";
2
+ import StructureLinkNew from "../StructureLinkNew";
3
+ import QxContext from "../../QxContext";
4
+
5
+ jest.mock("react-intl", () => ({
6
+ ...jest.requireActual("react-intl"),
7
+ useIntl: () => ({
8
+ formatMessage: ({ id }) => id,
9
+ }),
10
+ }));
11
+
12
+ jest.mock("@truedat/core/selectors", () => ({
13
+ makeTagOptionsSelector: jest.fn(() => jest.fn(() => [])),
14
+ }));
15
+
16
+ jest.mock("@truedat/core/routes", () => ({
17
+ linkTo: {
18
+ QUALITY_CONTROL_STRUCTURES: jest.fn(
19
+ ({ id, version }) =>
20
+ `/qualityControls/${id}/version/${version}/links/structures`
21
+ ),
22
+ },
23
+ }));
24
+
25
+ jest.mock("@truedat/lm/components/StructureLinkForm", () =>
26
+ jest.fn(({ sourceId, sourceType, redirectUrl, selectTagOptions }) => (
27
+ <div>
28
+ <div>StructureLinkForm</div>
29
+ <div data-testid="source-id">{sourceId}</div>
30
+ <div data-testid="source-type">{sourceType}</div>
31
+ <div data-testid="redirect-url">{redirectUrl}</div>
32
+ <div data-testid="has-select-tag-options">
33
+ {selectTagOptions ? "true" : "false"}
34
+ </div>
35
+ </div>
36
+ ))
37
+ );
38
+
39
+ const qualityControl = {
40
+ id: 10,
41
+ version: 1,
42
+ name: "Test Quality Control",
43
+ };
44
+
45
+ const context = {
46
+ qualityControl,
47
+ };
48
+
49
+ describe("<StructureLinkNew />", () => {
50
+ it("matches the latest snapshot", async () => {
51
+ const rendered = render(
52
+ <QxContext value={context}>
53
+ <StructureLinkNew />
54
+ </QxContext>
55
+ );
56
+ await waitForLoad(rendered);
57
+ expect(rendered.container).toMatchSnapshot();
58
+ });
59
+
60
+ it("renders header and StructureLinkForm", async () => {
61
+ const rendered = render(
62
+ <QxContext value={context}>
63
+ <StructureLinkNew />
64
+ </QxContext>
65
+ );
66
+ await waitForLoad(rendered);
67
+
68
+ expect(
69
+ rendered.getByText("quality_controls.relation.new.structure.header")
70
+ ).toBeInTheDocument();
71
+ expect(rendered.getByText("StructureLinkForm")).toBeInTheDocument();
72
+ });
73
+
74
+ it("passes correct props to StructureLinkForm", async () => {
75
+ const rendered = render(
76
+ <QxContext value={context}>
77
+ <StructureLinkNew />
78
+ </QxContext>
79
+ );
80
+ await waitForLoad(rendered);
81
+
82
+ expect(rendered.getByTestId("source-id").textContent).toBe("10");
83
+ expect(rendered.getByTestId("source-type").textContent).toBe(
84
+ "quality_control"
85
+ );
86
+ expect(rendered.getByTestId("redirect-url").textContent).toContain(
87
+ "/qualityControls/10/version/1/links/structures"
88
+ );
89
+ expect(rendered.getByTestId("has-select-tag-options").textContent).toBe(
90
+ "true"
91
+ );
92
+ });
93
+ });
@@ -0,0 +1,79 @@
1
+ import { render, waitForLoad } from "@truedat/test/render";
2
+ import StructureLinks from "../StructureLinks";
3
+ import QxContext from "../../QxContext";
4
+
5
+ jest.mock("@truedat/lm/components/QualityControlStructures", () =>
6
+ jest.fn(({ qualityControl, actions }) => (
7
+ <div>
8
+ <div>QualityControlStructures</div>
9
+ <div data-testid="quality-control-id">{qualityControl?.id}</div>
10
+ <div data-testid="quality-control-name">{qualityControl?.name}</div>
11
+ <div data-testid="actions-count">{actions?.length || 0}</div>
12
+ </div>
13
+ ))
14
+ );
15
+
16
+ const qualityControl = {
17
+ id: 10,
18
+ version: 1,
19
+ name: "Test Quality Control",
20
+ };
21
+
22
+ const actions = ["link_to_data_structure"];
23
+
24
+ const context = {
25
+ qualityControl,
26
+ actions,
27
+ };
28
+
29
+ describe("<StructureLinks />", () => {
30
+ it("matches the latest snapshot", async () => {
31
+ const rendered = render(
32
+ <QxContext value={context}>
33
+ <StructureLinks />
34
+ </QxContext>
35
+ );
36
+ await waitForLoad(rendered);
37
+ expect(rendered.container).toMatchSnapshot();
38
+ });
39
+
40
+ it("renders QualityControlStructures", async () => {
41
+ const rendered = render(
42
+ <QxContext value={context}>
43
+ <StructureLinks />
44
+ </QxContext>
45
+ );
46
+ await waitForLoad(rendered);
47
+
48
+ expect(rendered.getByText("QualityControlStructures")).toBeInTheDocument();
49
+ });
50
+
51
+ it("passes qualityControl and actions to QualityControlStructures", async () => {
52
+ const rendered = render(
53
+ <QxContext value={context}>
54
+ <StructureLinks />
55
+ </QxContext>
56
+ );
57
+ await waitForLoad(rendered);
58
+
59
+ expect(rendered.getByTestId("quality-control-id").textContent).toBe("10");
60
+ expect(rendered.getByTestId("quality-control-name").textContent).toBe(
61
+ "Test Quality Control"
62
+ );
63
+ expect(rendered.getByTestId("actions-count").textContent).toBe("1");
64
+ });
65
+
66
+ it("handles missing actions gracefully", async () => {
67
+ const contextWithoutActions = {
68
+ qualityControl,
69
+ };
70
+ const rendered = render(
71
+ <QxContext value={contextWithoutActions}>
72
+ <StructureLinks />
73
+ </QxContext>
74
+ );
75
+ await waitForLoad(rendered);
76
+
77
+ expect(rendered.getByTestId("actions-count").textContent).toBe("0");
78
+ });
79
+ });
@@ -0,0 +1,14 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<ConceptLinkNew /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <h4
6
+ class="ui header"
7
+ >
8
+ quality_controls.relation.new.concept.header
9
+ </h4>
10
+ <div>
11
+ ConceptLinkForm
12
+ </div>
13
+ </div>
14
+ `;
@@ -0,0 +1,9 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<ConceptLinks /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <div>
6
+ QualityControlConcepts
7
+ </div>
8
+ </div>
9
+ `;
@@ -0,0 +1,36 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<StructureLinkNew /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <h4
6
+ class="ui header"
7
+ >
8
+ quality_controls.relation.new.structure.header
9
+ </h4>
10
+ <div>
11
+ <div>
12
+ StructureLinkForm
13
+ </div>
14
+ <div
15
+ data-testid="source-id"
16
+ >
17
+ 10
18
+ </div>
19
+ <div
20
+ data-testid="source-type"
21
+ >
22
+ quality_control
23
+ </div>
24
+ <div
25
+ data-testid="redirect-url"
26
+ >
27
+ /qualityControls/10/version/1/links/structures
28
+ </div>
29
+ <div
30
+ data-testid="has-select-tag-options"
31
+ >
32
+ true
33
+ </div>
34
+ </div>
35
+ </div>
36
+ `;
@@ -0,0 +1,26 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<StructureLinks /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <div>
6
+ <div>
7
+ QualityControlStructures
8
+ </div>
9
+ <div
10
+ data-testid="quality-control-id"
11
+ >
12
+ 10
13
+ </div>
14
+ <div
15
+ data-testid="quality-control-name"
16
+ >
17
+ Test Quality Control
18
+ </div>
19
+ <div
20
+ data-testid="actions-count"
21
+ >
22
+ 1
23
+ </div>
24
+ </div>
25
+ </div>
26
+ `;