@truedat/lm 5.13.0 → 5.13.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/lm",
3
- "version": "5.13.0",
3
+ "version": "5.13.2",
4
4
  "description": "Truedat Link Manager",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -86,7 +86,7 @@
86
86
  ]
87
87
  },
88
88
  "dependencies": {
89
- "@truedat/core": "5.13.0",
89
+ "@truedat/core": "5.13.2",
90
90
  "path-to-regexp": "^1.7.0",
91
91
  "prop-types": "^15.8.1",
92
92
  "react-graph-vis": "1.0.6",
@@ -107,5 +107,5 @@
107
107
  "react-dom": ">= 16.8.6 < 17",
108
108
  "semantic-ui-react": ">= 2.0.3 < 2.2"
109
109
  },
110
- "gitHead": "fb6c617448a53494b5031dbb8b8418d8f4cf19ba"
110
+ "gitHead": "f702ebedee85733e60b46b26fd468a18e93b8c20"
111
111
  }
@@ -3,41 +3,35 @@ import React, { useState } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { connect } from "react-redux";
5
5
  import { useIntl } from "react-intl";
6
- import {
7
- Button,
8
- Checkbox,
9
- Form,
10
- Grid,
11
- Header,
12
- Segment,
13
- } from "semantic-ui-react";
6
+ import { Button, Form, Grid, Header, Segment } from "semantic-ui-react";
14
7
  import { HistoryBackButton } from "@truedat/core/components";
15
8
  import { linkTo } from "@truedat/core/routes";
9
+ import { makeTagOptionsSelector } from "@truedat/core/selectors";
16
10
  import { linkConcept } from "../routines";
17
11
  import RelationTagsLoader from "./RelationTagsLoader";
18
12
 
13
+ const TagTypeDropdownSelector = React.lazy(() =>
14
+ import("@truedat/lm/components/TagTypeDropdownSelector")
15
+ );
16
+
19
17
  const ConceptSelector = React.lazy(() =>
20
18
  import("@truedat/bg/concepts/relations/components/ConceptSelector")
21
19
  );
22
20
 
21
+ const filters = {
22
+ current: [true],
23
+ status: ["pending_approval", "draft", "rejected", "published"],
24
+ };
25
+
23
26
  export const ImplementationRelationForm = ({
24
27
  conceptLinking,
25
28
  implementation,
26
29
  tagOptions,
27
30
  linkConcept,
31
+ selectedRelationTags,
28
32
  }) => {
29
33
  const { formatMessage } = useIntl();
30
- const [selectedTag, setSelectedTag] = useState([]);
31
34
  const [selectedConcept, setSelectedConcept] = useState(null);
32
- const submitDisabled = conceptLinking || !selectedConcept;
33
-
34
- const handleTagOnChange = (e, { value }) => {
35
- const newSelectedTag = _.cond([
36
- [_.includes(value), _.without([value])],
37
- [_.stubTrue, _.concat(value)],
38
- ])(selectedTag);
39
- setSelectedTag(newSelectedTag);
40
- };
41
35
 
42
36
  const handleConceptSelected = (selectedConcept) =>
43
37
  setSelectedConcept(selectedConcept);
@@ -53,7 +47,7 @@ export const ImplementationRelationForm = ({
53
47
  source_type: "implementation_ref",
54
48
  target_id: selectedConcept.business_concept_id,
55
49
  target_type: "business_concept",
56
- tag_ids: selectedTag ? selectedTag : [],
50
+ tag_ids: selectedRelationTags ? selectedRelationTags : [],
57
51
  context: {},
58
52
  };
59
53
  linkConcept(conceptLink);
@@ -72,44 +66,29 @@ export const ImplementationRelationForm = ({
72
66
  />
73
67
 
74
68
  {!_.isEmpty(tagOptions) ? (
75
- <Form.Field style={{ marginBottom: "10px" }}>
76
- <label>
77
- {formatMessage({ id: "conceptRelations.relationType" })}
78
- </label>
79
- {tagOptions.map((tagOption, key) => (
80
- <div key={key} style={{ margin: "6px 0" }}>
81
- <Checkbox
82
- toggle
83
- label={formatMessage({
84
- id: `conceptRelations.relationType.${tagOption.text}`,
85
- defaultMessage: tagOption.text,
86
- })}
87
- name={tagOption.text}
88
- value={tagOption.id}
89
- onChange={handleTagOnChange}
90
- />
91
- </div>
92
- ))}
69
+ <Form.Field className="concept-relation-form">
70
+ <TagTypeDropdownSelector options={tagOptions} />
93
71
  </Form.Field>
94
72
  ) : null}
95
73
  <ConceptSelector
96
74
  selectedConcept={selectedConcept}
97
75
  handleConceptSelected={handleConceptSelected}
76
+ defaultFilters={filters}
98
77
  />
99
78
 
100
79
  <div className="actions">
101
- <HistoryBackButton
102
- content={formatMessage({ id: "actions.cancel" })}
103
- disabled={conceptLinking}
104
- />
105
-
106
80
  <Button
107
81
  primary
108
82
  content={formatMessage({ id: "actions.create" })}
109
- disabled={submitDisabled}
83
+ disabled={!selectedConcept}
110
84
  loading={conceptLinking}
111
85
  onClick={handleSubmit}
112
86
  />
87
+
88
+ <HistoryBackButton
89
+ content={formatMessage({ id: "actions.cancel" })}
90
+ disabled={conceptLinking}
91
+ />
113
92
  </div>
114
93
  </Grid.Column>
115
94
  </Grid>
@@ -122,26 +101,20 @@ ImplementationRelationForm.propTypes = {
122
101
  tagOptions: PropTypes.array,
123
102
  conceptLinking: PropTypes.bool,
124
103
  linkConcept: PropTypes.func,
104
+ selectedRelationTags: PropTypes.array,
125
105
  };
126
106
 
127
- const tagsToOptions = _.map(({ id: id, value: { type } }) => ({
128
- id,
129
- text: type,
130
- }));
131
-
132
- const mapStateToProps = (state) => ({
133
- conceptLinking: state.conceptLinking,
134
- implementation: state.ruleImplementation,
135
- tagOptions: _.flow(
136
- _.filter(
137
- (tag) =>
138
- _.path("value.target_type")(tag) == "implementations" ||
139
- !_.path("value.target_type")
140
- ),
141
- tagsToOptions
142
- )(state.relationTags),
143
- });
107
+ const makeMapStateToProps = () => {
108
+ const getTagOptions = makeTagOptionsSelector("implementations");
109
+ const mapStateToProps = (state) => ({
110
+ tagOptions: getTagOptions(state),
111
+ conceptLinking: state.conceptLinking,
112
+ implementation: state.ruleImplementation,
113
+ selectedRelationTags: state.selectedRelationTags,
114
+ });
115
+ return mapStateToProps;
116
+ };
144
117
 
145
- export default connect(mapStateToProps, { linkConcept })(
118
+ export default connect(makeMapStateToProps, { linkConcept })(
146
119
  ImplementationRelationForm
147
120
  );
@@ -1,11 +1,11 @@
1
1
  import _ from "lodash/fp";
2
- import React from "react";
2
+ import React, { useState } from "react";
3
3
  import PropTypes from "prop-types";
4
- import { FormattedMessage, injectIntl } from "react-intl";
4
+ import { FormattedMessage } from "react-intl";
5
5
  import { connect } from "react-redux";
6
- import { compose } from "redux";
7
- import { Button, Grid, Header, Form, Checkbox } from "semantic-ui-react";
6
+ import { Button, Grid, Header, Form } from "semantic-ui-react";
8
7
  import { HistoryBackButton } from "@truedat/core/components";
8
+ import { makeTagOptionsSelector } from "@truedat/core/selectors";
9
9
  import { linkTo } from "@truedat/core/routes";
10
10
  import { linkConcept } from "../routines";
11
11
  import RelationTagsLoader from "./RelationTagsLoader";
@@ -14,139 +14,98 @@ const ConceptSelector = React.lazy(() =>
14
14
  import("@truedat/bg/concepts/relations/components/ConceptSelector")
15
15
  );
16
16
 
17
- export class StructureRelationForm extends React.Component {
18
- state = {
19
- selectedTag: [],
20
- selectedConcept: null,
21
- };
22
- handleTagOnChange = (e, { value }) => {
23
- const { selectedTag } = this.state;
24
-
25
- const newSelectedTag = _.cond([
26
- [_.includes(value), _.without([value])],
27
- [_.stubTrue, _.concat(value)],
28
- ])(selectedTag);
17
+ const TagTypeDropdownSelector = React.lazy(() =>
18
+ import("@truedat/lm/components/TagTypeDropdownSelector")
19
+ );
29
20
 
30
- this.setState({ selectedTag: newSelectedTag });
31
- };
32
- handleConceptSelected = (selectedConcept) =>
33
- this.setState({ selectedConcept });
21
+ const filters = {
22
+ current: [true],
23
+ status: ["pending_approval", "draft", "rejected", "published"],
24
+ };
34
25
 
35
- handleSubmit = () => {
36
- const { selectedConcept, selectedTag } = this.state;
37
- const { structure, linkConcept } = this.props;
26
+ export const StructureRelationForm = ({
27
+ structure,
28
+ tagOptions,
29
+ linkConcept,
30
+ selectedRelationTags,
31
+ conceptLinking,
32
+ }) => {
33
+ const [selectedConcept, setSelectedConcept] = useState(null);
38
34
 
39
- const redirectUrl = linkTo.STRUCTURE({ id: structure.id });
35
+ const handleConceptSelected = (selectedConcept) =>
36
+ setSelectedConcept(selectedConcept);
40
37
 
38
+ const handleSubmit = () => {
39
+ const redirectUrl = linkTo.STRUCTURE_LINKS({ id: structure.id });
41
40
  const conceptLink = {
42
41
  redirectUrl,
43
42
  source_id: selectedConcept.business_concept_id,
44
43
  source_type: "business_concept",
45
44
  target_id: structure.id,
46
45
  target_type: "data_structure",
47
- tag_ids: selectedTag ? selectedTag : [],
46
+ tag_ids: selectedRelationTags ? selectedRelationTags : [],
48
47
  context: {},
49
48
  };
50
49
  linkConcept(conceptLink);
51
50
  };
52
51
 
53
- render() {
54
- const { selectedConcept } = this.state;
55
- const {
56
- conceptLinking,
57
- intl: { formatMessage },
58
- tagOptions,
59
- } = this.props;
60
- const submitDisabled = conceptLinking || !selectedConcept;
61
-
62
- return (
63
- <Grid>
64
- <Grid.Column width={16}>
65
- <RelationTagsLoader />
66
- <Header
67
- as="h4"
68
- content={<FormattedMessage id="structures.relation.new.header" />}
52
+ return (
53
+ <Grid>
54
+ <Grid.Column width={16}>
55
+ <RelationTagsLoader />
56
+ <Header
57
+ as="h4"
58
+ content={<FormattedMessage id="structures.relation.new.header" />}
59
+ />
60
+
61
+ {!_.isEmpty(tagOptions) ? (
62
+ <Form.Field className="concept-relation-form">
63
+ <TagTypeDropdownSelector options={tagOptions} />
64
+ </Form.Field>
65
+ ) : null}
66
+
67
+ <ConceptSelector
68
+ selectedConcept={selectedConcept}
69
+ handleConceptSelected={handleConceptSelected}
70
+ defaultFilters={filters}
71
+ />
72
+
73
+ <div className="actions">
74
+ <Button
75
+ primary
76
+ content={<FormattedMessage id="actions.create" />}
77
+ disabled={!selectedConcept}
78
+ loading={conceptLinking}
79
+ onClick={handleSubmit}
69
80
  />
70
81
 
71
- {!_.isEmpty(tagOptions) && (
72
- <Form.Field style={{ marginBottom: "10px" }}>
73
- <label>
74
- <FormattedMessage id="conceptRelations.relationType" />
75
- </label>
76
-
77
- {tagOptions.map((tagOption, key) => (
78
- <div
79
- key={key}
80
- style={{
81
- margin: "6px 0",
82
- }}
83
- >
84
- <Checkbox
85
- toggle
86
- label={formatMessage({
87
- id: `conceptRelations.relationType.${tagOption.text}`,
88
- defaultMessage: tagOption.text,
89
- })}
90
- name={tagOption.text}
91
- value={tagOption.value}
92
- onChange={this.handleTagOnChange}
93
- />
94
- </div>
95
- ))}
96
- </Form.Field>
97
- )}
98
-
99
- <ConceptSelector
100
- loadConcepts={false}
101
- selectedConcept={selectedConcept}
102
- handleConceptSelected={this.handleConceptSelected}
82
+ <HistoryBackButton
83
+ content={<FormattedMessage id="actions.cancel" />}
103
84
  />
104
- <div className="actions">
105
- <HistoryBackButton
106
- content={<FormattedMessage id="actions.cancel" />}
107
- disabled={conceptLinking}
108
- />
109
- <Button
110
- primary
111
- content={<FormattedMessage id="actions.create" />}
112
- disabled={submitDisabled}
113
- loading={conceptLinking}
114
- onClick={this.handleSubmit}
115
- />
116
- </div>
117
- </Grid.Column>
118
- </Grid>
119
- );
120
- }
121
- }
85
+ </div>
86
+ </Grid.Column>
87
+ </Grid>
88
+ );
89
+ };
122
90
 
123
91
  StructureRelationForm.propTypes = {
124
92
  conceptLinking: PropTypes.bool,
125
- intl: PropTypes.object,
126
93
  linkConcept: PropTypes.func,
127
94
  structure: PropTypes.object,
128
95
  tagOptions: PropTypes.array,
129
96
  };
130
97
 
131
- const tagsToOptions = _.map(({ id: value, value: { type } }) => ({
132
- value,
133
- text: type,
134
- }));
135
-
136
- const mapStateToProps = ({ conceptLinking, relationTags, structure }) => ({
137
- conceptLinking,
138
- tagOptions: _.flow(
139
- _.filter(
140
- (tag) =>
141
- _.path("value.target_type")(tag) == "data_field" ||
142
- !_.path("value.target_type")
143
- ),
144
- tagsToOptions
145
- )(relationTags),
146
- structure,
147
- });
98
+ const makeMapStateToProps = () => {
99
+ const getTagOptions = makeTagOptionsSelector("data_field");
100
+ const mapStateToProps = (state) => ({
101
+ conceptLinking: state.conceptLinking,
102
+ tagOptions: getTagOptions(state),
103
+ selectedRelationTags: state.selectedRelationTags,
104
+ structure: state.structure,
105
+ });
106
+ return mapStateToProps;
107
+ };
148
108
 
149
- export default compose(
150
- injectIntl,
151
- connect(mapStateToProps, { linkConcept })
152
- )(StructureRelationForm);
109
+ export default connect(makeMapStateToProps, { linkConcept })(
110
+ StructureRelationForm
111
+ );
@@ -1,33 +1,145 @@
1
- import React, { Suspense } from "react";
1
+ import React from "react";
2
2
  import { render } from "@truedat/test/render";
3
+ import { linkTo } from "@truedat/core/routes";
4
+ import userEvent from "@testing-library/user-event";
5
+ import { waitFor } from "@testing-library/react";
3
6
  import { ImplementationRelationForm } from "../ImplementationRelationForm";
7
+ import en from "../../messages/en";
4
8
 
5
- describe("<ImplementationRelationForm />", () => {
6
- const renderOpts = {
7
- messages: {
8
- en: {
9
- "implementations.relation.new.header": "header",
10
- "conceptRelations.relationType": "relationType",
11
- "actions.cancel": "cancel",
12
- "actions.create": "create",
13
- "conceptRelations.relationType.text": "text",
14
- },
9
+ const linkConcept = jest.fn();
10
+
11
+ const domain = {
12
+ external_id: "foo domain",
13
+ id: 381,
14
+ name: "foo domain",
15
+ };
16
+
17
+ const tagOptions = [
18
+ { id: 1, text: "relates_to", value: 0 },
19
+ { id: 2, text: "text", value: 1 },
20
+ ];
21
+
22
+ const concepts = [
23
+ {
24
+ _actions: { can_create_structure_link: true },
25
+ business_concept_id: 2,
26
+ domain: domain,
27
+ id: 12355,
28
+ name: "foo concept",
29
+ status: "published",
30
+ },
31
+ {
32
+ _actions: { can_create_structure_link: true },
33
+ business_concept_id: 123,
34
+ domain: domain,
35
+ id: 12290,
36
+ name: "bar concept",
37
+ status: "draft",
38
+ },
39
+ ];
40
+
41
+ const props = {
42
+ implementation: { rule_id: 2, id: 4, implementation_ref: 1 },
43
+ tagOptions,
44
+ conceptLinking: false,
45
+ linkConcept,
46
+ selectedRelationTags: [1],
47
+ };
48
+
49
+ const renderOpts = {
50
+ messages: {
51
+ en: {
52
+ ...en,
53
+ "implementations.relation.new.header": "header",
54
+ "conceptRelations.relationType": "relationType",
55
+ "actions.cancel": "Cancel",
56
+ "actions.create": "Create",
57
+ "conceptRelations.relationType.text": "text",
58
+ "concepts.props.name": "Name",
59
+ "concepts.props.domain": "Domain",
60
+ "concepts.props.status": "Status",
61
+ "search.save_filters": "Save Filters",
62
+ "search.clear_filters": "Clear Filters",
63
+ "search.applied_filters": "Applied Filters",
64
+ "conceptRelations.relatedConcept": "Related Concept",
65
+ "concepts.search.placeholder": "Search",
15
66
  },
16
- };
17
-
18
- const props = {
19
- implementation: { rule_id: 2, id: 4, implementation_ref: 1 },
20
- tagOptions: [{ id: 2, text: "text" }],
21
- conceptLinking: false,
22
- linkConcept: () => {},
23
- };
24
- it("matches the latest snapshot", () => {
25
- const { container } = render(
26
- <Suspense fallback={null}>
27
- <ImplementationRelationForm {...props} />
28
- </Suspense>,
67
+ },
68
+ state: {
69
+ concepts,
70
+ conceptActiveFilters: { filter: "some" },
71
+ },
72
+ fallback: "lazy",
73
+ };
74
+
75
+ describe("<ImplementationRelationForm />", () => {
76
+ it("matches the latest snapshot", async () => {
77
+ const { container, getByText } = render(
78
+ <ImplementationRelationForm {...props} />,
79
+
29
80
  renderOpts
30
81
  );
82
+
83
+ await waitFor(() => {
84
+ expect(getByText(/relates_to/)).toBeInTheDocument();
85
+ });
31
86
  expect(container).toMatchSnapshot();
32
87
  });
88
+
89
+ it("disables submit and shows loading state if concept is linking", async () => {
90
+ const newProps = {
91
+ ...props,
92
+ conceptLinking: true,
93
+ };
94
+
95
+ const { queryByText, queryByRole, getByText, findByRole, findByText } =
96
+ render(<ImplementationRelationForm {...newProps} />, renderOpts);
97
+
98
+ await waitFor(() => {
99
+ expect(getByText(/relates_to/)).toBeInTheDocument();
100
+ });
101
+
102
+ expect(queryByRole("button", { name: /Create/ })).toBeDisabled();
103
+ userEvent.click(await findByText(/relates_to/));
104
+ userEvent.click(await queryByText(/bar concept/));
105
+
106
+ await waitFor(() => {
107
+ expect(queryByRole("button", { name: /Create/ })).toBeEnabled();
108
+ });
109
+
110
+ userEvent.click(await findByRole("button", { name: /Create/ }));
111
+
112
+ await waitFor(() => {
113
+ expect(queryByRole("button", { name: /Create/ })).toHaveClass("loading");
114
+ });
115
+ });
116
+
117
+ it("submits the selected relation", async () => {
118
+ const { queryByText, getByText, findByRole, findByText } = render(
119
+ <ImplementationRelationForm {...props} />,
120
+ renderOpts
121
+ );
122
+
123
+ await waitFor(() => {
124
+ expect(getByText(/relates_to/)).toBeInTheDocument();
125
+ });
126
+
127
+ userEvent.click(await findByText(/relates_to/));
128
+ userEvent.click(await queryByText(/bar concept/));
129
+
130
+ userEvent.click(await findByRole("button", { name: /Create/ }));
131
+
132
+ const expectedRelation = {
133
+ redirectUrl: linkTo.IMPLEMENTATION_CONCEPT_LINKS({
134
+ implementation_id: 4,
135
+ }),
136
+ source_id: 1,
137
+ source_type: "implementation_ref",
138
+ target_id: 123,
139
+ target_type: "business_concept",
140
+ tag_ids: [1],
141
+ context: {},
142
+ };
143
+ expect(linkConcept.mock.calls[0][0]).toEqual(expectedRelation);
144
+ });
33
145
  });
@@ -1,108 +1,148 @@
1
1
  import React from "react";
2
- import { shallowWithIntl } from "@truedat/test/intl-stub";
2
+ import { render } from "@truedat/test/render";
3
3
  import { linkTo } from "@truedat/core/routes";
4
+ import userEvent from "@testing-library/user-event";
5
+ import { waitFor } from "@testing-library/react";
4
6
  import { StructureRelationForm } from "../StructureRelationForm";
7
+ import en from "../../messages/en";
8
+
9
+ const linkConcept = jest.fn();
10
+
11
+ const domain = {
12
+ external_id: "foo domain",
13
+ id: 381,
14
+ name: "foo domain",
15
+ };
16
+
17
+ const concepts = [
18
+ {
19
+ _actions: { can_create_structure_link: true },
20
+ business_concept_id: 2,
21
+ domain: domain,
22
+ id: 12355,
23
+ name: "foo concept",
24
+ status: "published",
25
+ },
26
+ {
27
+ _actions: { can_create_structure_link: true },
28
+ business_concept_id: 123,
29
+ domain: domain,
30
+ id: 12290,
31
+ name: "bar concept",
32
+ status: "draft",
33
+ },
34
+ ];
35
+
36
+ const tagOptions = [
37
+ { id: 1, text: "relates_to", value: 0 },
38
+ { id: 2, text: "text", value: 1 },
39
+ ];
40
+
41
+ const structure = {
42
+ group: "s_group",
43
+ system: { name: "sys_name" },
44
+ name: "s_name",
45
+ id: 3,
46
+ };
47
+
48
+ const props = {
49
+ tagOptions,
50
+ structure,
51
+ linkConcept,
52
+ selectedRelationTags: [1],
53
+ };
54
+ const messages = {
55
+ en: {
56
+ ...en,
57
+ "structures.relation.new.header": "Header",
58
+ "actions.create": "Create",
59
+ "actions.cancel": "Cancel",
60
+ "concepts.props.name": "Name",
61
+ "concepts.props.domain": "Domain",
62
+ "concepts.props.status": "Status",
63
+ "search.save_filters": "Save Filters",
64
+ "search.clear_filters": "Clear Filters",
65
+ "search.applied_filters": "Applied Filters",
66
+ "conceptRelations.relatedConcept": "Related Concept",
67
+ "concepts.search.placeholder": "Search",
68
+ },
69
+ };
70
+
71
+ const renderOpts = {
72
+ messages,
73
+ state: {
74
+ structure,
75
+ concepts,
76
+ conceptActiveFilters: { filter: "some" },
77
+ },
78
+ fallback: "lazy",
79
+ };
5
80
 
6
81
  describe("<StructureRelationForm />", () => {
7
- it("matches the latest snapshot", () => {
8
- const props = {
9
- tagOptions: [],
10
- structureField: {},
11
- structure: {},
12
- linkConcept: jest.fn()
13
- };
14
- const wrapper = shallowWithIntl(<StructureRelationForm {...props} />);
15
- expect(wrapper).toMatchSnapshot();
16
- });
82
+ it("matches the latest snapshot", async () => {
83
+ const { container, getByText } = render(
84
+ <StructureRelationForm {...props} />,
85
+ renderOpts
86
+ );
17
87
 
18
- it("disables submit and shows loading state if concept is linking", () => {
19
- const props = {
20
- tagOptions: [],
21
- structureField: {},
22
- structure: {},
23
- conceptLinking: true
24
- };
25
- const wrapper = shallowWithIntl(<StructureRelationForm {...props} />);
26
- const submitButton = wrapper.find(".actions").find("Button[primary=true]");
27
- expect(submitButton.prop("loading")).toBe(true);
28
- expect(submitButton.prop("disabled")).toBe(true);
88
+ await waitFor(() => {
89
+ expect(getByText(/relates_to/)).toBeInTheDocument();
90
+ });
91
+
92
+ expect(container).toMatchSnapshot();
29
93
  });
30
94
 
31
- it("handles tag and concept selection", () => {
32
- const props = {
33
- tagOptions: [
34
- { text: "-", value: 0 },
35
- { text: "relates to", value: 1 }
36
- ],
37
- structureField: {},
38
- structure: {},
39
- linkConcept: jest.fn()
95
+ it("disables submit and shows loading state if concept is linking", async () => {
96
+ const newProps = {
97
+ ...props,
98
+ conceptLinking: true,
40
99
  };
41
- const wrapper = shallowWithIntl(<StructureRelationForm {...props} />);
42
100
 
43
- const selectedTag = [1];
44
- const selectedConcept = { business_concept_id: 123, id: 2 };
45
- const expectedState = { selectedTag, selectedConcept };
101
+ const { queryByText, queryByRole, getByText, findByRole, findByText } =
102
+ render(<StructureRelationForm {...newProps} />, renderOpts);
46
103
 
47
- wrapper
48
- .find("Checkbox")
49
- .at(0)
50
- .prop("onChange")(null, { value: selectedTag });
104
+ await waitFor(() => {
105
+ expect(getByText(/relates_to/)).toBeInTheDocument();
106
+ });
51
107
 
52
- wrapper
53
- .findWhere(n => n.prop("handleConceptSelected"))
54
- .prop("handleConceptSelected")(selectedConcept);
108
+ expect(queryByRole("button", { name: /Create/ })).toBeDisabled();
109
+ userEvent.click(await findByText(/relates_to/));
110
+ userEvent.click(await queryByText(/bar concept/));
55
111
 
56
- expect(wrapper.state()).toEqual(expectedState);
57
- });
112
+ await waitFor(() => {
113
+ expect(queryByRole("button", { name: /Create/ })).toBeEnabled();
114
+ });
58
115
 
59
- it("submits the selected relation", () => {
60
- const linkConcept = jest.fn();
61
- const props = {
62
- linkConcept,
63
- tagOptions: [
64
- { text: "-", value: 0 },
65
- { text: "relates to", value: 2 }
66
- ],
67
- structure: {
68
- group: "s_group",
69
- system: { name: "sys_name" },
70
- name: "s_name",
71
- id: 3
72
- },
73
- structureField: { id: 545, name: "f_name" }
74
- };
75
- const wrapper = shallowWithIntl(<StructureRelationForm {...props} />);
116
+ userEvent.click(await findByRole("button", { name: /Create/ }));
76
117
 
77
- const selectedConcept = { business_concept_id: 123, id: 2 };
118
+ await waitFor(() => {
119
+ expect(queryByRole("button", { name: /Create/ })).toHaveClass("loading");
120
+ });
121
+ });
78
122
 
79
- wrapper
80
- .find("Checkbox")
81
- .at(0)
82
- .prop("onChange")(null, { value: 0 });
123
+ it("submits the selected relation", async () => {
124
+ const { queryByText, getByText, findByRole, findByText } = render(
125
+ <StructureRelationForm {...props} />,
126
+ renderOpts
127
+ );
83
128
 
84
- wrapper
85
- .find("Checkbox")
86
- .at(1)
87
- .prop("onChange")(null, { value: 2 });
129
+ await waitFor(() => {
130
+ expect(getByText(/relates_to/)).toBeInTheDocument();
131
+ });
88
132
 
89
- wrapper
90
- .findWhere(n => n.prop("handleConceptSelected"))
91
- .prop("handleConceptSelected")(selectedConcept);
133
+ userEvent.click(await findByText(/relates_to/));
134
+ userEvent.click(await queryByText(/bar concept/));
92
135
 
93
- wrapper
94
- .find("Button")
95
- .find({ primary: true })
96
- .prop("onClick")();
136
+ userEvent.click(await findByRole("button", { name: /Create/ }));
97
137
 
98
138
  const expectedRelation = {
99
- redirectUrl: linkTo.STRUCTURE({ id: 3 }),
139
+ redirectUrl: linkTo.STRUCTURE_LINKS({ id: 3 }),
100
140
  source_id: 123,
101
141
  source_type: "business_concept",
102
142
  target_id: 3,
103
143
  target_type: "data_structure",
104
- tag_ids: [2, 0],
105
- context: {}
144
+ tag_ids: [1],
145
+ context: {},
106
146
  };
107
147
  expect(linkConcept.mock.calls[0][0]).toEqual(expectedRelation);
108
148
  });
@@ -4,7 +4,7 @@ exports[`<ImplementationRelationForm /> matches the latest snapshot 1`] = `
4
4
  <div>
5
5
  <div
6
6
  class="ui bottom attached segment"
7
- style="display: none;"
7
+ style=""
8
8
  >
9
9
  <div
10
10
  class="ui grid"
@@ -19,49 +19,244 @@ exports[`<ImplementationRelationForm /> matches the latest snapshot 1`] = `
19
19
  header
20
20
  </h4>
21
21
  <div
22
- class="field"
23
- style="margin-bottom: 10px;"
22
+ class="field concept-relation-form"
24
23
  >
25
- <label>
26
- relationType
27
- </label>
28
24
  <div
29
- style="margin: 6px 0px;"
25
+ class="field"
30
26
  >
27
+ <b>
28
+ <label>
29
+ Relation types
30
+ </label>
31
+ </b>
32
+ <div>
33
+ <div
34
+ aria-expanded="false"
35
+ class="ui selection dropdown concept-link-dropdown"
36
+ role="listbox"
37
+ tabindex="0"
38
+ >
39
+ <i
40
+ aria-hidden="true"
41
+ class="dropdown icon"
42
+ />
43
+ <div
44
+ class="menu transition"
45
+ >
46
+ <div
47
+ aria-checked="false"
48
+ aria-selected="true"
49
+ class="selected item"
50
+ role="option"
51
+ style="pointer-events: all;"
52
+ >
53
+ <span
54
+ class="text"
55
+ >
56
+ relates_to
57
+ </span>
58
+ </div>
59
+ <div
60
+ aria-checked="false"
61
+ aria-selected="false"
62
+ class="item"
63
+ role="option"
64
+ style="pointer-events: all;"
65
+ >
66
+ <span
67
+ class="text"
68
+ >
69
+ text
70
+ </span>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ <label>
78
+ Related Concept
79
+ </label>
80
+ <div
81
+ class="ui segment"
82
+ >
83
+ <div
84
+ class="ui action left icon input"
85
+ >
86
+ <input
87
+ placeholder="Search"
88
+ type="text"
89
+ value=""
90
+ />
91
+ <i
92
+ aria-hidden="true"
93
+ class="search link icon"
94
+ />
31
95
  <div
32
- class="ui toggle checkbox"
96
+ aria-expanded="false"
97
+ class="ui button floating labeled scrolling dropdown icon"
98
+ role="listbox"
99
+ tabindex="0"
33
100
  >
34
- <input
35
- class="hidden"
36
- name="text"
37
- readonly=""
38
- tabindex="0"
39
- type="checkbox"
40
- value="2"
101
+ <div
102
+ aria-atomic="true"
103
+ aria-live="polite"
104
+ class="divider text"
105
+ role="alert"
106
+ >
107
+ Filters
108
+ </div>
109
+ <i
110
+ aria-hidden="true"
111
+ class="filter icon"
112
+ />
113
+ <div
114
+ class="menu transition"
115
+ >
116
+ <div
117
+ class="item"
118
+ role="option"
119
+ >
120
+ <em>
121
+ (reset filters)
122
+ </em>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </div>
127
+ <div
128
+ class="selectedFilters"
129
+ >
130
+ <div
131
+ class="appliedFilters"
132
+ >
133
+ Applied Filters
134
+ </div>
135
+ <div
136
+ aria-expanded="false"
137
+ class="ui floating item scrolling dropdown"
138
+ role="listbox"
139
+ tabindex="0"
140
+ >
141
+ <div
142
+ class="ui label"
143
+ >
144
+ filter
145
+ <i
146
+ aria-hidden="true"
147
+ class="delete icon"
148
+ />
149
+ </div>
150
+ <div
151
+ class="menu transition dimmable"
41
152
  />
42
- <label>
43
- text
44
- </label>
45
153
  </div>
154
+ <a
155
+ class="resetFilters"
156
+ >
157
+ Clear Filters
158
+ </a>
159
+ <a
160
+ class="resetFilters"
161
+ >
162
+ Save Filters
163
+ </a>
46
164
  </div>
165
+ <table
166
+ class="ui small selectable table"
167
+ >
168
+ <thead
169
+ class=""
170
+ >
171
+ <tr
172
+ class=""
173
+ >
174
+ <th
175
+ class=""
176
+ >
177
+ Name
178
+ </th>
179
+ <th
180
+ class=""
181
+ >
182
+ Domain
183
+ </th>
184
+ <th
185
+ class=""
186
+ >
187
+ Status
188
+ </th>
189
+ </tr>
190
+ </thead>
191
+ <tbody
192
+ class=""
193
+ >
194
+ <tr
195
+ class=""
196
+ >
197
+ <td
198
+ class=""
199
+ >
200
+ foo concept
201
+ </td>
202
+ <td
203
+ class=""
204
+ >
205
+ foo domain
206
+ </td>
207
+ <td
208
+ class=""
209
+ >
210
+ <div
211
+ class="ui green label"
212
+ >
213
+ published
214
+ </div>
215
+ </td>
216
+ </tr>
217
+ <tr
218
+ class=""
219
+ >
220
+ <td
221
+ class=""
222
+ >
223
+ bar concept
224
+ </td>
225
+ <td
226
+ class=""
227
+ >
228
+ foo domain
229
+ </td>
230
+ <td
231
+ class=""
232
+ >
233
+ <div
234
+ class="ui olive label"
235
+ >
236
+ draft
237
+ </div>
238
+ </td>
239
+ </tr>
240
+ </tbody>
241
+ </table>
47
242
  </div>
48
243
  <div
49
244
  class="actions"
50
245
  >
51
- <a
52
- class="ui secondary button"
53
- href="/"
54
- role="button"
55
- >
56
- cancel
57
- </a>
58
246
  <button
59
247
  class="ui primary disabled button"
60
248
  disabled=""
61
249
  tabindex="-1"
62
250
  >
63
- create
251
+ Create
64
252
  </button>
253
+ <a
254
+ class="ui secondary button"
255
+ href="/"
256
+ role="button"
257
+ >
258
+ Cancel
259
+ </a>
65
260
  </div>
66
261
  </div>
67
262
  </div>
@@ -1,46 +1,260 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`<StructureRelationForm /> matches the latest snapshot 1`] = `
4
- <Grid>
5
- <GridColumn
6
- width={16}
4
+ <div>
5
+ <div
6
+ class="ui grid"
7
+ style=""
7
8
  >
8
- <Connect(RelationTagsLoader) />
9
- <Header
10
- as="h4"
11
- content={
12
- <Memo(MemoizedFormattedMessage)
13
- id="structures.relation.new.header"
14
- />
15
- }
16
- />
17
- <lazy
18
- handleConceptSelected={[Function]}
19
- loadConcepts={false}
20
- selectedConcept={null}
21
- />
22
9
  <div
23
- className="actions"
10
+ class="sixteen wide column"
24
11
  >
25
- <HistoryBackButton
26
- content={
27
- <Memo(MemoizedFormattedMessage)
28
- id="actions.cancel"
12
+ <h4
13
+ class="ui header"
14
+ >
15
+ Header
16
+ </h4>
17
+ <div
18
+ class="field concept-relation-form"
19
+ >
20
+ <div
21
+ class="field"
22
+ >
23
+ <b>
24
+ <label>
25
+ Relation types
26
+ </label>
27
+ </b>
28
+ <div>
29
+ <div
30
+ aria-expanded="false"
31
+ class="ui selection dropdown concept-link-dropdown"
32
+ role="listbox"
33
+ tabindex="0"
34
+ >
35
+ <i
36
+ aria-hidden="true"
37
+ class="dropdown icon"
38
+ />
39
+ <div
40
+ class="menu transition"
41
+ >
42
+ <div
43
+ aria-checked="false"
44
+ aria-selected="true"
45
+ class="selected item"
46
+ role="option"
47
+ style="pointer-events: all;"
48
+ >
49
+ <span
50
+ class="text"
51
+ >
52
+ relates_to
53
+ </span>
54
+ </div>
55
+ <div
56
+ aria-checked="false"
57
+ aria-selected="false"
58
+ class="item"
59
+ role="option"
60
+ style="pointer-events: all;"
61
+ >
62
+ <span
63
+ class="text"
64
+ >
65
+ text
66
+ </span>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ <label>
74
+ Related Concept
75
+ </label>
76
+ <div
77
+ class="ui segment"
78
+ >
79
+ <div
80
+ class="ui action left icon input"
81
+ >
82
+ <input
83
+ placeholder="Search"
84
+ type="text"
85
+ value=""
29
86
  />
30
- }
31
- />
32
- <Button
33
- as="button"
34
- content={
35
- <Memo(MemoizedFormattedMessage)
36
- id="actions.create"
87
+ <i
88
+ aria-hidden="true"
89
+ class="search link icon"
37
90
  />
38
- }
39
- disabled={true}
40
- onClick={[Function]}
41
- primary={true}
42
- />
91
+ <div
92
+ aria-expanded="false"
93
+ class="ui button floating labeled scrolling dropdown icon"
94
+ role="listbox"
95
+ tabindex="0"
96
+ >
97
+ <div
98
+ aria-atomic="true"
99
+ aria-live="polite"
100
+ class="divider text"
101
+ role="alert"
102
+ >
103
+ Filters
104
+ </div>
105
+ <i
106
+ aria-hidden="true"
107
+ class="filter icon"
108
+ />
109
+ <div
110
+ class="menu transition"
111
+ >
112
+ <div
113
+ class="item"
114
+ role="option"
115
+ >
116
+ <em>
117
+ (reset filters)
118
+ </em>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ <div
124
+ class="selectedFilters"
125
+ >
126
+ <div
127
+ class="appliedFilters"
128
+ >
129
+ Applied Filters
130
+ </div>
131
+ <div
132
+ aria-expanded="false"
133
+ class="ui floating item scrolling dropdown"
134
+ role="listbox"
135
+ tabindex="0"
136
+ >
137
+ <div
138
+ class="ui label"
139
+ >
140
+ filter
141
+ <i
142
+ aria-hidden="true"
143
+ class="delete icon"
144
+ />
145
+ </div>
146
+ <div
147
+ class="menu transition dimmable"
148
+ />
149
+ </div>
150
+ <a
151
+ class="resetFilters"
152
+ >
153
+ Clear Filters
154
+ </a>
155
+ <a
156
+ class="resetFilters"
157
+ >
158
+ Save Filters
159
+ </a>
160
+ </div>
161
+ <table
162
+ class="ui small selectable table"
163
+ >
164
+ <thead
165
+ class=""
166
+ >
167
+ <tr
168
+ class=""
169
+ >
170
+ <th
171
+ class=""
172
+ >
173
+ Name
174
+ </th>
175
+ <th
176
+ class=""
177
+ >
178
+ Domain
179
+ </th>
180
+ <th
181
+ class=""
182
+ >
183
+ Status
184
+ </th>
185
+ </tr>
186
+ </thead>
187
+ <tbody
188
+ class=""
189
+ >
190
+ <tr
191
+ class=""
192
+ >
193
+ <td
194
+ class=""
195
+ >
196
+ foo concept
197
+ </td>
198
+ <td
199
+ class=""
200
+ >
201
+ foo domain
202
+ </td>
203
+ <td
204
+ class=""
205
+ >
206
+ <div
207
+ class="ui green label"
208
+ >
209
+ published
210
+ </div>
211
+ </td>
212
+ </tr>
213
+ <tr
214
+ class=""
215
+ >
216
+ <td
217
+ class=""
218
+ >
219
+ bar concept
220
+ </td>
221
+ <td
222
+ class=""
223
+ >
224
+ foo domain
225
+ </td>
226
+ <td
227
+ class=""
228
+ >
229
+ <div
230
+ class="ui olive label"
231
+ >
232
+ draft
233
+ </div>
234
+ </td>
235
+ </tr>
236
+ </tbody>
237
+ </table>
238
+ </div>
239
+ <div
240
+ class="actions"
241
+ >
242
+ <button
243
+ class="ui primary disabled button"
244
+ disabled=""
245
+ tabindex="-1"
246
+ >
247
+ Create
248
+ </button>
249
+ <a
250
+ class="ui secondary button"
251
+ href="/"
252
+ role="button"
253
+ >
254
+ Cancel
255
+ </a>
256
+ </div>
43
257
  </div>
44
- </GridColumn>
45
- </Grid>
258
+ </div>
259
+ </div>
46
260
  `;