decidim-comments 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +33 -0
- data/app/assets/javascripts/decidim/comments/bundle.js +44 -44
- data/app/assets/javascripts/decidim/comments/bundle.js.map +1 -1
- data/app/assets/javascripts/decidim/comments/comments.js.erb +2 -0
- data/app/frontend/application/{apollo_client.js → apollo_client.ts} +5 -5
- data/app/frontend/application/application.component.test.tsx +36 -0
- data/app/frontend/application/application.component.tsx +37 -0
- data/app/frontend/application/icon.component.test.tsx +49 -0
- data/app/frontend/application/icon.component.tsx +35 -0
- data/app/frontend/comments/{add_comment_form.component.test.jsx → add_comment_form.component.test.tsx} +98 -92
- data/app/frontend/comments/{add_comment_form.component.jsx → add_comment_form.component.tsx} +152 -153
- data/app/frontend/comments/{comment.component.test.jsx → comment.component.test.tsx} +59 -71
- data/app/frontend/comments/{comment.component.jsx → comment.component.tsx} +114 -116
- data/app/frontend/comments/comment_order_selector.component.test.tsx +21 -0
- data/app/frontend/comments/comment_order_selector.component.tsx +88 -0
- data/app/frontend/comments/comment_thread.component.test.tsx +65 -0
- data/app/frontend/comments/comment_thread.component.tsx +70 -0
- data/app/frontend/comments/{comments.component.test.jsx → comments.component.test.tsx} +38 -81
- data/app/frontend/comments/{comments.component.jsx → comments.component.tsx} +49 -63
- data/app/frontend/comments/down_vote_button.component.test.tsx +39 -0
- data/app/frontend/comments/down_vote_button.component.tsx +89 -0
- data/app/frontend/comments/up_vote_button.component.test.tsx +39 -0
- data/app/frontend/comments/up_vote_button.component.tsx +89 -0
- data/app/frontend/comments/vote_button.component.tsx +36 -0
- data/app/frontend/comments/{vote_button_component.test.jsx → vote_button_component.test.tsx} +16 -20
- data/app/frontend/entry.ts +19 -0
- data/app/frontend/{comments → fragments}/add_comment_form_commentable.fragment.graphql +1 -1
- data/app/frontend/{comments → fragments}/add_comment_form_session.fragment.graphql +1 -1
- data/app/frontend/{comments → fragments}/comment.fragment.graphql +3 -1
- data/app/frontend/{comments → fragments}/comment_data.fragment.graphql +6 -3
- data/app/frontend/{comments → fragments}/comment_thread.fragment.graphql +3 -1
- data/app/frontend/{comments/down_vote.fragment.graphql → fragments/down_vote_button.fragment.graphql} +2 -2
- data/app/frontend/{comments/up_vote.fragment.graphql → fragments/up_vote_button.fragment.graphql} +2 -2
- data/app/frontend/{comments/add_comment_form.mutation.graphql → mutations/add_comment.mutation.graphql} +3 -1
- data/app/frontend/{comments → mutations}/down_vote.mutation.graphql +3 -1
- data/app/frontend/{comments → mutations}/up_vote.mutation.graphql +3 -1
- data/app/frontend/{comments → queries}/comments.query.graphql +4 -1
- data/app/frontend/support/{asset_url.js → asset_url.ts} +1 -1
- data/app/frontend/support/{generate_comments_data.js → generate_comments_data.ts} +11 -6
- data/app/frontend/support/{generate_user_data.js → generate_user_data.ts} +2 -2
- data/app/frontend/support/{generate_user_group_data.js → generate_user_group_data.ts} +2 -2
- data/app/frontend/support/graphql_transformer.js +32 -0
- data/app/frontend/support/load_translations.ts +44 -0
- data/app/frontend/support/{require_all.js → require_all.ts} +1 -1
- data/app/frontend/support/{resolve_graphql_query.js → resolve_graphql_query.ts} +7 -7
- data/app/frontend/support/schema.ts +119 -0
- data/config/locales/eu.yml +29 -5
- metadata +49 -51
- data/app/frontend/application/application.component.jsx +0 -37
- data/app/frontend/application/application.component.test.jsx +0 -33
- data/app/frontend/application/icon.component.jsx +0 -26
- data/app/frontend/application/icon.component.test.jsx +0 -53
- data/app/frontend/comments/comment_order_selector.component.jsx +0 -72
- data/app/frontend/comments/comment_order_selector.component.test.jsx +0 -20
- data/app/frontend/comments/comment_thread.component.jsx +0 -75
- data/app/frontend/comments/comment_thread.component.test.jsx +0 -83
- data/app/frontend/comments/down_vote_button.component.jsx +0 -98
- data/app/frontend/comments/down_vote_button.component.test.jsx +0 -48
- data/app/frontend/comments/featured_comment.component.jsx +0 -23
- data/app/frontend/comments/featured_comment.component.test.jsx +0 -15
- data/app/frontend/comments/up_vote_button.component.jsx +0 -98
- data/app/frontend/comments/up_vote_button.component.test.jsx +0 -48
- data/app/frontend/comments/vote_button.component.jsx +0 -32
- data/app/frontend/entry.js +0 -17
- data/app/frontend/entry.test.js +0 -31
- data/app/frontend/support/load_translations.js +0 -23
- data/app/frontend/support/stub_component.js +0 -29
@@ -0,0 +1,35 @@
|
|
1
|
+
import * as React from "react";
|
2
|
+
import assetUrl from "../support/asset_url";
|
3
|
+
|
4
|
+
interface IconProps {
|
5
|
+
name: string;
|
6
|
+
userAgent: string;
|
7
|
+
iconExtraClassName?: string;
|
8
|
+
}
|
9
|
+
|
10
|
+
export const Icon: React.SFC<IconProps> = ({ name, userAgent, iconExtraClassName }) => {
|
11
|
+
if (userAgent.match(/PhantomJS/) || userAgent.match(/Node/)) {
|
12
|
+
return <span className={`icon ${iconExtraClassName} ${name}`}>{name}</span>;
|
13
|
+
}
|
14
|
+
|
15
|
+
return (
|
16
|
+
<svg className={`icon ${iconExtraClassName} ${name}`}>
|
17
|
+
<use xmlnsXlink="http://www.w3.org/1999/xlink" xlinkHref={`${assetUrl("icons.svg")}#${name}`} />
|
18
|
+
</svg>
|
19
|
+
);
|
20
|
+
};
|
21
|
+
|
22
|
+
Icon.defaultProps = {
|
23
|
+
iconExtraClassName: "icon--before",
|
24
|
+
};
|
25
|
+
|
26
|
+
interface IconWithoutUserAgentProps {
|
27
|
+
name: string;
|
28
|
+
iconExtraClassName?: string;
|
29
|
+
}
|
30
|
+
|
31
|
+
const IconWithoutUserAgent: React.SFC<IconWithoutUserAgentProps> = ({ name, iconExtraClassName }) => (
|
32
|
+
<Icon name={name} userAgent={navigator.userAgent} iconExtraClassName={iconExtraClassName} />
|
33
|
+
);
|
34
|
+
|
35
|
+
export default IconWithoutUserAgent;
|
@@ -1,184 +1,190 @@
|
|
1
|
-
|
2
|
-
import
|
1
|
+
import { mount, ReactWrapper, shallow } from "enzyme";
|
2
|
+
import * as React from "react";
|
3
3
|
|
4
|
-
import { AddCommentForm }
|
4
|
+
import { AddCommentForm } from "./add_comment_form.component";
|
5
5
|
|
6
|
-
import generateUserData
|
7
|
-
import generateUserGroupData from
|
6
|
+
import generateUserData from "../support/generate_user_data";
|
7
|
+
import generateUserGroupData from "../support/generate_user_group_data";
|
8
|
+
import { loadLocaleTranslations } from "../support/load_translations";
|
8
9
|
|
9
10
|
describe("<AddCommentForm />", () => {
|
10
|
-
let session = null;
|
11
|
+
let session: any = null;
|
11
12
|
const commentable = {
|
12
13
|
id: "1",
|
13
|
-
type: "Decidim::DummyResource"
|
14
|
+
type: "Decidim::DummyResource",
|
14
15
|
};
|
15
|
-
const addCommentStub = () => {
|
16
|
+
const addCommentStub = (): any => {
|
16
17
|
return null;
|
17
|
-
}
|
18
|
+
};
|
18
19
|
|
19
20
|
beforeEach(() => {
|
21
|
+
loadLocaleTranslations("en");
|
20
22
|
session = {
|
21
23
|
user: generateUserData(),
|
22
|
-
verifiedUserGroups: []
|
24
|
+
verifiedUserGroups: [],
|
25
|
+
};
|
26
|
+
window.DecidimComments = {
|
27
|
+
assets: {
|
28
|
+
"icons.svg": "/assets/icons.svg",
|
29
|
+
},
|
23
30
|
};
|
24
31
|
});
|
25
32
|
|
26
33
|
it("should render a div with class add-comment", () => {
|
27
34
|
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
|
28
|
-
expect(wrapper.find(
|
35
|
+
expect(wrapper.find("div.add-comment")).toBeDefined();
|
29
36
|
});
|
30
37
|
|
31
38
|
it("should have a reference to body textarea", () => {
|
32
39
|
const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
|
33
|
-
expect(wrapper.instance().bodyTextArea).
|
40
|
+
expect((wrapper.instance() as AddCommentForm).bodyTextArea).toBeDefined();
|
34
41
|
});
|
35
42
|
|
36
43
|
it("should initialize with a state property disabled as true", () => {
|
37
44
|
const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
|
38
|
-
expect(wrapper
|
45
|
+
expect(wrapper.state()).toHaveProperty("disabled", true);
|
39
46
|
});
|
40
47
|
|
41
48
|
it("should have a default prop showTitle as true", () => {
|
42
49
|
const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
|
43
|
-
expect(wrapper
|
50
|
+
expect(wrapper.props()).toHaveProperty("showTitle", true);
|
44
51
|
});
|
45
52
|
|
46
53
|
it("should not render the title if prop showTitle is false", () => {
|
47
54
|
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} showTitle={false} />);
|
48
|
-
expect(wrapper.find(
|
55
|
+
expect(wrapper.find("h5.section-heading").exists()).toBeFalsy();
|
49
56
|
});
|
50
57
|
|
51
58
|
it("should have a default prop submitButtonClassName as 'button button--sc'", () => {
|
52
59
|
const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
|
53
|
-
expect(wrapper
|
60
|
+
expect(wrapper.props()).toHaveProperty("submitButtonClassName", "button button--sc");
|
54
61
|
});
|
55
62
|
|
56
63
|
it("should have a default prop maxLength of 1000", () => {
|
57
64
|
const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
|
58
|
-
expect(wrapper
|
65
|
+
expect(wrapper.props()).toHaveProperty("maxLength", 1000);
|
59
66
|
});
|
60
67
|
|
61
|
-
|
62
68
|
it("should use prop submitButtonClassName as a className prop for submit button", () => {
|
63
69
|
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} submitButtonClassName="button small hollow" />);
|
64
|
-
expect(wrapper.find('button[type="submit"]')
|
65
|
-
expect(wrapper.find('button[type="submit"]')
|
66
|
-
expect(wrapper.find('button[type="submit"]')
|
70
|
+
expect(wrapper.find('button[type="submit"]').hasClass("button")).toBeTruthy();
|
71
|
+
expect(wrapper.find('button[type="submit"]').hasClass("small")).toBeTruthy();
|
72
|
+
expect(wrapper.find('button[type="submit"]').hasClass("hollow")).toBeTruthy();
|
67
73
|
});
|
68
74
|
|
69
75
|
it("should enable the submit button if textarea is not blank", () => {
|
70
76
|
const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
|
71
|
-
wrapper.find(
|
77
|
+
wrapper.find("textarea").simulate("change", {
|
72
78
|
target: {
|
73
|
-
value:
|
74
|
-
}
|
79
|
+
value: "This is a comment",
|
80
|
+
},
|
75
81
|
});
|
76
|
-
expect(wrapper.find('button[type="submit"]')).not.
|
82
|
+
expect(wrapper.find('button[type="submit"]').props()).not.toHaveProperty("disabled", true);
|
77
83
|
});
|
78
84
|
|
79
85
|
it("should disable the submit button if textarea is blank", () => {
|
80
86
|
const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
|
81
|
-
wrapper.find(
|
87
|
+
wrapper.find("textarea").simulate("change", {
|
82
88
|
target: {
|
83
|
-
value:
|
84
|
-
}
|
89
|
+
value: "This will be deleted",
|
90
|
+
},
|
85
91
|
});
|
86
|
-
wrapper.find(
|
92
|
+
wrapper.find("textarea").simulate("change", {
|
87
93
|
target: {
|
88
|
-
value:
|
89
|
-
}
|
94
|
+
value: "",
|
95
|
+
},
|
90
96
|
});
|
91
|
-
expect(wrapper.find('button[type="submit"]')).
|
97
|
+
expect(wrapper.find('button[type="submit"]').props()).toHaveProperty("disabled", true);
|
92
98
|
});
|
93
99
|
|
94
100
|
it("should not render a div with class 'opinion-toggle'", () => {
|
95
101
|
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
|
96
|
-
expect(wrapper.find(
|
102
|
+
expect(wrapper.find(".opinion-toggle").exists()).toBeFalsy();
|
97
103
|
});
|
98
104
|
|
99
105
|
describe("submitting the form", () => {
|
100
|
-
let addComment
|
101
|
-
let onCommentAdded
|
102
|
-
let wrapper
|
103
|
-
let message = null;
|
106
|
+
let addComment: jasmine.Spy;
|
107
|
+
let onCommentAdded: jasmine.Spy ;
|
108
|
+
let wrapper: ReactWrapper<any, {}>;
|
109
|
+
let message: any = null;
|
104
110
|
|
105
111
|
beforeEach(() => {
|
106
|
-
addComment =
|
107
|
-
onCommentAdded =
|
112
|
+
addComment = jasmine.createSpy("addComment");
|
113
|
+
onCommentAdded = jasmine.createSpy("onCommentAdded");
|
108
114
|
wrapper = mount(<AddCommentForm addComment={addComment} session={session} commentable={commentable} onCommentAdded={onCommentAdded} />);
|
109
|
-
message =
|
110
|
-
wrapper.instance().bodyTextArea.value = message;
|
115
|
+
message = "This will be submitted";
|
116
|
+
(wrapper.instance() as AddCommentForm).bodyTextArea.value = message;
|
111
117
|
});
|
112
118
|
|
113
119
|
it("should call addComment prop with the textarea value and state property alignment", () => {
|
114
|
-
wrapper.find(
|
115
|
-
expect(addComment).
|
120
|
+
wrapper.find("form").simulate("submit");
|
121
|
+
expect(addComment).toHaveBeenCalledWith({ body: message, alignment: 0 });
|
116
122
|
});
|
117
123
|
|
118
124
|
it("should reset textarea", () => {
|
119
|
-
wrapper.find(
|
120
|
-
expect(wrapper.find(
|
125
|
+
wrapper.find("form").simulate("submit");
|
126
|
+
expect((wrapper.find("textarea").get(0) as any).value).toBe("");
|
121
127
|
});
|
122
128
|
|
123
129
|
it("should prevent default form submission", () => {
|
124
|
-
const preventDefault =
|
125
|
-
wrapper.find(
|
126
|
-
expect(preventDefault).
|
130
|
+
const preventDefault = jasmine.createSpy("preventDefault");
|
131
|
+
wrapper.find("form").simulate("submit", { preventDefault });
|
132
|
+
expect(preventDefault).toHaveBeenCalled();
|
127
133
|
});
|
128
134
|
|
129
135
|
it("should call the prop onCommentAdded function", () => {
|
130
|
-
wrapper.find(
|
131
|
-
expect(onCommentAdded).
|
136
|
+
wrapper.find("form").simulate("submit");
|
137
|
+
expect(onCommentAdded).toHaveBeenCalled();
|
132
138
|
});
|
133
139
|
});
|
134
140
|
|
135
141
|
it("should initialize state with a property alignment and value 0", () => {
|
136
|
-
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable />);
|
137
|
-
expect(wrapper
|
142
|
+
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable={true} />);
|
143
|
+
expect(wrapper.state()).toHaveProperty("alignment", 0);
|
138
144
|
});
|
139
145
|
|
140
146
|
describe("when receiving an optional prop arguable with value true", () => {
|
141
147
|
it("should render a div with class 'opinion-toggle'", () => {
|
142
|
-
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable />);
|
143
|
-
expect(wrapper.find(
|
148
|
+
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable={true} />);
|
149
|
+
expect(wrapper.find(".opinion-toggle")).toBeDefined();
|
144
150
|
});
|
145
151
|
|
146
152
|
it("should set state alignment to 1 if user clicks ok button and change its class", () => {
|
147
|
-
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable />);
|
148
|
-
wrapper.find(
|
149
|
-
expect(wrapper.find(
|
150
|
-
expect(wrapper
|
153
|
+
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable={true} />);
|
154
|
+
wrapper.find(".opinion-toggle--ok").simulate("click");
|
155
|
+
expect(wrapper.find(".opinion-toggle--ok").hasClass("is-active")).toBeTruthy();
|
156
|
+
expect(wrapper.state()).toHaveProperty("alignment", 1);
|
151
157
|
});
|
152
158
|
|
153
159
|
it("should set state alignment to -11 if user clicks ko button and change its class", () => {
|
154
|
-
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable />);
|
155
|
-
wrapper.find(
|
156
|
-
expect(wrapper.find(
|
157
|
-
expect(wrapper
|
160
|
+
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable={true} />);
|
161
|
+
wrapper.find(".opinion-toggle--ko").simulate("click");
|
162
|
+
expect(wrapper.find(".opinion-toggle--ko").hasClass("is-active")).toBeTruthy();
|
163
|
+
expect(wrapper.state()).toHaveProperty("alignment", -1);
|
158
164
|
});
|
159
165
|
|
160
166
|
describe("submitting the form", () => {
|
161
|
-
let
|
162
|
-
let
|
163
|
-
let message
|
167
|
+
let wrapper: ReactWrapper<any, {}>;
|
168
|
+
let addComment: jasmine.Spy;
|
169
|
+
let message: string;
|
164
170
|
|
165
171
|
beforeEach(() => {
|
166
|
-
addComment =
|
167
|
-
wrapper = mount(<AddCommentForm addComment={addComment} session={session} commentable={commentable} arguable />);
|
168
|
-
message =
|
169
|
-
wrapper.instance().bodyTextArea.value = message;
|
172
|
+
addComment = jasmine.createSpy("addComment");
|
173
|
+
wrapper = mount(<AddCommentForm addComment={addComment} session={session} commentable={commentable} arguable={true} />);
|
174
|
+
message = "This will be submitted";
|
175
|
+
(wrapper.instance() as AddCommentForm).bodyTextArea.value = message;
|
170
176
|
});
|
171
177
|
|
172
178
|
it("should call addComment prop with the state's property alignment", () => {
|
173
|
-
wrapper.find(
|
174
|
-
wrapper.find(
|
175
|
-
expect(addComment).
|
179
|
+
wrapper.find("button.opinion-toggle--ko").simulate("click");
|
180
|
+
wrapper.find("form").simulate("submit");
|
181
|
+
expect(addComment).toHaveBeenCalledWith({ body: message, alignment: -1 });
|
176
182
|
});
|
177
183
|
|
178
184
|
it("should reset the state to its initial state", () => {
|
179
|
-
wrapper.find(
|
180
|
-
wrapper.find(
|
181
|
-
expect(wrapper
|
185
|
+
wrapper.find("button.opinion-toggle--ok").simulate("click");
|
186
|
+
wrapper.find("form").simulate("submit");
|
187
|
+
expect(wrapper.state()).toHaveProperty("alignment", 0);
|
182
188
|
});
|
183
189
|
});
|
184
190
|
});
|
@@ -187,48 +193,48 @@ describe("<AddCommentForm />", () => {
|
|
187
193
|
beforeEach(() => {
|
188
194
|
session.verifiedUserGroups = [
|
189
195
|
generateUserGroupData(),
|
190
|
-
generateUserGroupData()
|
196
|
+
generateUserGroupData(),
|
191
197
|
];
|
192
198
|
});
|
193
199
|
|
194
200
|
it("should have a reference to user_group_id select", () => {
|
195
201
|
const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
|
196
|
-
expect(wrapper.instance().userGroupIdSelect).
|
202
|
+
expect((wrapper.instance() as AddCommentForm).userGroupIdSelect).toBeDefined();
|
197
203
|
});
|
198
204
|
|
199
205
|
it("should render a select with option tags for each verified user group", () => {
|
200
206
|
const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
|
201
|
-
expect(wrapper.find(
|
207
|
+
expect(wrapper.find("select").children("option").length).toBe(3);
|
202
208
|
});
|
203
209
|
|
204
210
|
describe("submitting the form", () => {
|
205
|
-
let addComment
|
206
|
-
let wrapper
|
207
|
-
let message
|
208
|
-
let userGroupId
|
211
|
+
let addComment: jasmine.Spy;
|
212
|
+
let wrapper: ReactWrapper<any, {}>;
|
213
|
+
let message: string;
|
214
|
+
let userGroupId: string;
|
209
215
|
|
210
216
|
beforeEach(() => {
|
211
|
-
addComment =
|
217
|
+
addComment = jasmine.createSpy("addComment");
|
212
218
|
wrapper = mount(<AddCommentForm addComment={addComment} session={session} commentable={commentable} />);
|
213
|
-
message =
|
219
|
+
message = "This will be submitted";
|
214
220
|
userGroupId = session.verifiedUserGroups[1].id;
|
215
|
-
wrapper.instance().bodyTextArea.value = message;
|
216
|
-
wrapper.instance().userGroupIdSelect.value = userGroupId;
|
221
|
+
(wrapper.instance() as AddCommentForm).bodyTextArea.value = message;
|
222
|
+
(wrapper.instance() as AddCommentForm).userGroupIdSelect.value = userGroupId;
|
217
223
|
});
|
218
224
|
|
219
225
|
it("should call addComment prop with the body textarea, alignment and user_group_id select values", () => {
|
220
|
-
wrapper.find(
|
221
|
-
expect(addComment).
|
226
|
+
wrapper.find("form").simulate("submit");
|
227
|
+
expect(addComment).toHaveBeenCalledWith({ body: message, alignment: 0, userGroupId });
|
222
228
|
});
|
223
229
|
|
224
230
|
describe("when user_group_id is blank", () => {
|
225
231
|
beforeEach(() => {
|
226
|
-
wrapper.instance().userGroupIdSelect.value =
|
232
|
+
(wrapper.instance() as AddCommentForm).userGroupIdSelect.value = "";
|
227
233
|
});
|
228
234
|
|
229
235
|
it("should call addComment prop with the body textarea and alignment", () => {
|
230
|
-
wrapper.find(
|
231
|
-
expect(addComment).
|
236
|
+
wrapper.find("form").simulate("submit");
|
237
|
+
expect(addComment).toHaveBeenCalledWith({ body: message, alignment: 0 });
|
232
238
|
});
|
233
239
|
});
|
234
240
|
});
|
@@ -241,7 +247,7 @@ describe("<AddCommentForm />", () => {
|
|
241
247
|
|
242
248
|
it("display a message to sign in or sign up", () => {
|
243
249
|
const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
|
244
|
-
expect(wrapper.find(
|
250
|
+
expect(wrapper.find("span").text()).toContain("sign up");
|
245
251
|
});
|
246
252
|
});
|
247
253
|
});
|
data/app/frontend/comments/{add_comment_form.component.jsx → add_comment_form.component.tsx}
RENAMED
@@ -1,39 +1,69 @@
|
|
1
1
|
/* eslint-disable no-return-assign, react/no-unused-prop-types, max-lines */
|
2
|
-
import
|
3
|
-
import
|
4
|
-
import
|
5
|
-
import
|
6
|
-
|
7
|
-
import
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
import
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
2
|
+
import * as classnames from "classnames";
|
3
|
+
import * as React from "react";
|
4
|
+
import { graphql } from "react-apollo";
|
5
|
+
import * as uuid from "uuid";
|
6
|
+
|
7
|
+
import Icon from "../application/icon.component";
|
8
|
+
|
9
|
+
const { I18n, Translate } = require("react-i18nify");
|
10
|
+
|
11
|
+
import {
|
12
|
+
AddCommentFormCommentableFragment,
|
13
|
+
AddCommentFormSessionFragment,
|
14
|
+
AddCommentMutation,
|
15
|
+
CommentFragment,
|
16
|
+
GetCommentsQuery,
|
17
|
+
} from "../support/schema";
|
18
|
+
|
19
|
+
interface AddCommentFormProps {
|
20
|
+
session: AddCommentFormSessionFragment & {
|
21
|
+
user: any;
|
22
|
+
} | null;
|
23
|
+
commentable: AddCommentFormCommentableFragment;
|
24
|
+
showTitle?: boolean;
|
25
|
+
submitButtonClassName?: string;
|
26
|
+
autoFocus?: boolean;
|
27
|
+
maxLength?: number;
|
28
|
+
arguable?: boolean;
|
29
|
+
addComment?: (data: { body: string, alignment: number, userGroupId?: string }) => void;
|
30
|
+
onCommentAdded?: () => void;
|
31
|
+
}
|
32
|
+
|
33
|
+
interface AddCommentFormState {
|
34
|
+
disabled: boolean;
|
35
|
+
error: boolean;
|
36
|
+
alignment: number;
|
37
|
+
}
|
19
38
|
|
20
39
|
/**
|
21
40
|
* Renders a form to create new comments.
|
22
41
|
* @class
|
23
42
|
* @augments Component
|
24
43
|
*/
|
25
|
-
export class AddCommentForm extends Component {
|
26
|
-
|
44
|
+
export class AddCommentForm extends React.Component<AddCommentFormProps, AddCommentFormState> {
|
45
|
+
public static defaultProps = {
|
46
|
+
showTitle: true,
|
47
|
+
submitButtonClassName: "button button--sc",
|
48
|
+
arguable: false,
|
49
|
+
autoFocus: false,
|
50
|
+
maxLength: 1000,
|
51
|
+
};
|
52
|
+
|
53
|
+
public bodyTextArea: HTMLTextAreaElement;
|
54
|
+
public userGroupIdSelect: HTMLSelectElement;
|
55
|
+
|
56
|
+
constructor(props: AddCommentFormProps) {
|
27
57
|
super(props);
|
28
58
|
|
29
59
|
this.state = {
|
30
60
|
disabled: true,
|
31
61
|
error: false,
|
32
|
-
alignment: 0
|
62
|
+
alignment: 0,
|
33
63
|
};
|
34
64
|
}
|
35
65
|
|
36
|
-
render() {
|
66
|
+
public render() {
|
37
67
|
return (
|
38
68
|
<div className="add-comment">
|
39
69
|
{this._renderHeading()}
|
@@ -49,13 +79,13 @@ export class AddCommentForm extends Component {
|
|
49
79
|
* @private
|
50
80
|
* @returns {Void|DOMElement} - The heading or an empty element
|
51
81
|
*/
|
52
|
-
_renderHeading() {
|
82
|
+
private _renderHeading() {
|
53
83
|
const { showTitle } = this.props;
|
54
84
|
|
55
85
|
if (showTitle) {
|
56
86
|
return (
|
57
87
|
<h5 className="section-heading">
|
58
|
-
{
|
88
|
+
{I18n.t("components.add_comment_form.title")}
|
59
89
|
</h5>
|
60
90
|
);
|
61
91
|
}
|
@@ -68,7 +98,7 @@ export class AddCommentForm extends Component {
|
|
68
98
|
* @private
|
69
99
|
* @returns {Void|DOMElement} - The message or an empty element.
|
70
100
|
*/
|
71
|
-
_renderAccountMessage() {
|
101
|
+
private _renderAccountMessage() {
|
72
102
|
const { session } = this.props;
|
73
103
|
|
74
104
|
if (!session) {
|
@@ -78,7 +108,7 @@ export class AddCommentForm extends Component {
|
|
78
108
|
value="components.add_comment_form.account_message"
|
79
109
|
sign_in_url="/users/sign_in"
|
80
110
|
sign_up_url="/users/sign_up"
|
81
|
-
dangerousHTML
|
111
|
+
dangerousHTML={true}
|
82
112
|
/>
|
83
113
|
</p>
|
84
114
|
);
|
@@ -92,21 +122,23 @@ export class AddCommentForm extends Component {
|
|
92
122
|
* @private
|
93
123
|
* @returns {Void|DOMElement} - The add comment form on an empty element.
|
94
124
|
*/
|
95
|
-
_renderForm() {
|
125
|
+
private _renderForm() {
|
96
126
|
const { session, submitButtonClassName, commentable: { id, type } } = this.props;
|
97
127
|
const { disabled } = this.state;
|
98
128
|
|
99
129
|
if (session) {
|
100
130
|
return (
|
101
|
-
<form onSubmit={
|
131
|
+
<form onSubmit={this.addComment}>
|
102
132
|
{this._renderCommentAs()}
|
103
133
|
<div className="field">
|
104
|
-
<label className="show-for-sr" htmlFor={`add-comment-${type}-${id}`}>{
|
134
|
+
<label className="show-for-sr" htmlFor={`add-comment-${type}-${id}`}>{I18n.t("components.add_comment_form.form.body.label")}</label>
|
105
135
|
{this._renderTextArea()}
|
106
136
|
{this._renderTextAreaError()}
|
107
|
-
<button
|
108
|
-
|
109
|
-
|
137
|
+
<button
|
138
|
+
type="submit"
|
139
|
+
className={submitButtonClassName}
|
140
|
+
disabled={disabled}
|
141
|
+
>
|
110
142
|
{I18n.t("components.add_comment_form.form.submit")}
|
111
143
|
</button>
|
112
144
|
</div>
|
@@ -122,13 +154,13 @@ export class AddCommentForm extends Component {
|
|
122
154
|
* @private
|
123
155
|
* @returns {Void|DOMElement} - The heading or an empty element
|
124
156
|
*/
|
125
|
-
_renderTextArea() {
|
157
|
+
private _renderTextArea() {
|
126
158
|
const { commentable: { id, type }, autoFocus, maxLength } = this.props;
|
127
159
|
const { error } = this.state;
|
128
|
-
const className = classnames({
|
160
|
+
const className = classnames({ "is-invalid-input": error });
|
129
161
|
|
130
|
-
let textAreaProps = {
|
131
|
-
ref: (textarea) => {this.bodyTextArea = textarea},
|
162
|
+
let textAreaProps: any = {
|
163
|
+
ref: (textarea: HTMLTextAreaElement) => {this.bodyTextArea = textarea; },
|
132
164
|
id: `add-comment-${type}-${id}`,
|
133
165
|
className,
|
134
166
|
rows: "4",
|
@@ -136,10 +168,11 @@ export class AddCommentForm extends Component {
|
|
136
168
|
required: "required",
|
137
169
|
pattern: `^(.){0,${maxLength}}$`,
|
138
170
|
placeholder: I18n.t("components.add_comment_form.form.body.placeholder"),
|
139
|
-
onChange: (evt) => this._checkCommentBody(evt.target.value)
|
171
|
+
onChange: (evt: React.ChangeEvent<HTMLTextAreaElement>) => this._checkCommentBody(evt.target.value),
|
140
172
|
};
|
173
|
+
|
141
174
|
if (autoFocus) {
|
142
|
-
textAreaProps.autoFocus =
|
175
|
+
textAreaProps.autoFocus = "autoFocus";
|
143
176
|
}
|
144
177
|
|
145
178
|
return (
|
@@ -152,14 +185,14 @@ export class AddCommentForm extends Component {
|
|
152
185
|
* @private
|
153
186
|
* @returns {Void|DOMElement} - The error or an empty element
|
154
187
|
*/
|
155
|
-
_renderTextAreaError() {
|
188
|
+
private _renderTextAreaError() {
|
156
189
|
const { maxLength } = this.props;
|
157
190
|
const { error } = this.state;
|
158
191
|
|
159
192
|
if (error) {
|
160
193
|
return (
|
161
194
|
<span className="form-error is-visible">
|
162
|
-
{
|
195
|
+
{I18n.t("components.add_comment_form.form.form_error", { length: maxLength })}
|
163
196
|
</span>
|
164
197
|
);
|
165
198
|
}
|
@@ -167,23 +200,29 @@ export class AddCommentForm extends Component {
|
|
167
200
|
return null;
|
168
201
|
}
|
169
202
|
|
203
|
+
private setAlignment = (alignment: number) => {
|
204
|
+
return () => {
|
205
|
+
this.setState({ alignment });
|
206
|
+
};
|
207
|
+
}
|
208
|
+
|
170
209
|
/**
|
171
210
|
* Render opinion buttons or not based on the arguable prop
|
172
211
|
* @private
|
173
212
|
* @returns {Void|DOMElement} - Returns nothing or a wrapper with buttons
|
174
213
|
*/
|
175
|
-
_renderOpinionButtons() {
|
214
|
+
private _renderOpinionButtons() {
|
176
215
|
const { session, arguable } = this.props;
|
177
216
|
const { alignment } = this.state;
|
178
|
-
const buttonClassName = classnames(
|
179
|
-
const okButtonClassName = classnames(buttonClassName,
|
180
|
-
|
217
|
+
const buttonClassName = classnames("button", "tiny", "button--muted");
|
218
|
+
const okButtonClassName = classnames(buttonClassName, "opinion-toggle--ok", {
|
219
|
+
"is-active": alignment === 1,
|
181
220
|
});
|
182
|
-
const koButtonClassName = classnames(buttonClassName,
|
183
|
-
|
221
|
+
const koButtonClassName = classnames(buttonClassName, "opinion-toggle--ko", {
|
222
|
+
"is-active": alignment === -1,
|
184
223
|
});
|
185
|
-
const neutralButtonClassName = classnames(buttonClassName,
|
186
|
-
|
224
|
+
const neutralButtonClassName = classnames(buttonClassName, "opinion-toggle--meh", {
|
225
|
+
"is-active": alignment === 0,
|
187
226
|
});
|
188
227
|
|
189
228
|
if (session && arguable) {
|
@@ -191,19 +230,19 @@ export class AddCommentForm extends Component {
|
|
191
230
|
<div className="opinion-toggle button-group">
|
192
231
|
<button
|
193
232
|
className={okButtonClassName}
|
194
|
-
onClick={
|
233
|
+
onClick={this.setAlignment(1)}
|
195
234
|
>
|
196
235
|
<Icon iconExtraClassName="" name="icon-thumb-up" />
|
197
236
|
</button>
|
198
237
|
<button
|
199
238
|
className={neutralButtonClassName}
|
200
|
-
onClick={
|
239
|
+
onClick={this.setAlignment(0)}
|
201
240
|
>
|
202
|
-
{
|
241
|
+
{I18n.t("components.add_comment_form.opinion.neutral")}
|
203
242
|
</button>
|
204
243
|
<button
|
205
244
|
className={koButtonClassName}
|
206
|
-
onClick={
|
245
|
+
onClick={this.setAlignment(-1)}
|
207
246
|
>
|
208
247
|
<Icon iconExtraClassName="" name="icon-thumb-down" />
|
209
248
|
</button>
|
@@ -214,34 +253,39 @@ export class AddCommentForm extends Component {
|
|
214
253
|
return null;
|
215
254
|
}
|
216
255
|
|
256
|
+
private setUserGroupIdSelect = (select: HTMLSelectElement) => {this.userGroupIdSelect = select; };
|
257
|
+
|
217
258
|
/**
|
218
259
|
* Render a select with an option for each user's verified group
|
219
260
|
* @private
|
220
261
|
* @returns {Void|DOMElement} - Returns nothing or a form field.
|
221
262
|
*/
|
222
|
-
_renderCommentAs() {
|
263
|
+
private _renderCommentAs() {
|
223
264
|
const { session, commentable: { id, type } } = this.props;
|
224
|
-
const { user, verifiedUserGroups } = session;
|
225
265
|
|
226
|
-
if (
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
266
|
+
if (session) {
|
267
|
+
const { user, verifiedUserGroups } = session;
|
268
|
+
|
269
|
+
if (verifiedUserGroups.length > 0) {
|
270
|
+
return (
|
271
|
+
<div className="field">
|
272
|
+
<label htmlFor={`add-comment-${type}-${id}-user-group-id`}>
|
273
|
+
{I18n.t("components.add_comment_form.form.user_group_id.label")}
|
274
|
+
</label>
|
275
|
+
<select
|
276
|
+
ref={this.setUserGroupIdSelect}
|
277
|
+
id={`add-comment-${type}-${id}-user-group-id`}
|
278
|
+
>
|
279
|
+
<option value="">{user.name}</option>
|
280
|
+
{
|
281
|
+
verifiedUserGroups.map((userGroup) => (
|
282
|
+
<option key={userGroup.id} value={userGroup.id}>{userGroup.name}</option>
|
283
|
+
))
|
284
|
+
}
|
285
|
+
</select>
|
286
|
+
</div>
|
287
|
+
);
|
288
|
+
}
|
245
289
|
}
|
246
290
|
|
247
291
|
return null;
|
@@ -253,9 +297,9 @@ export class AddCommentForm extends Component {
|
|
253
297
|
* @param {string} body - The comment's body
|
254
298
|
* @returns {Void} - Returns nothing
|
255
299
|
*/
|
256
|
-
_checkCommentBody(body) {
|
300
|
+
private _checkCommentBody(body: string) {
|
257
301
|
const { maxLength } = this.props;
|
258
|
-
this.setState({ disabled: body ===
|
302
|
+
this.setState({ disabled: body === "", error: body === "" || (maxLength !== undefined && body.length > maxLength) });
|
259
303
|
}
|
260
304
|
|
261
305
|
/**
|
@@ -265,20 +309,22 @@ export class AddCommentForm extends Component {
|
|
265
309
|
* @param {DOMEvent} evt - The form's submission event
|
266
310
|
* @returns {Void} - Returns nothing
|
267
311
|
*/
|
268
|
-
|
312
|
+
private addComment = (evt: React.FormEvent<HTMLFormElement>) => {
|
269
313
|
const { alignment } = this.state;
|
270
314
|
const { addComment, onCommentAdded } = this.props;
|
271
|
-
let addCommentParams = { body: this.bodyTextArea.value, alignment };
|
315
|
+
let addCommentParams: { body: string, alignment: number, userGroupId?: string } = { body: this.bodyTextArea.value, alignment };
|
272
316
|
|
273
317
|
evt.preventDefault();
|
274
318
|
|
275
|
-
if (this.userGroupIdSelect && this.userGroupIdSelect.value !==
|
319
|
+
if (this.userGroupIdSelect && this.userGroupIdSelect.value !== "") {
|
276
320
|
addCommentParams.userGroupId = this.userGroupIdSelect.value;
|
277
321
|
}
|
278
322
|
|
279
|
-
addComment
|
323
|
+
if (addComment) {
|
324
|
+
addComment(addCommentParams);
|
325
|
+
}
|
280
326
|
|
281
|
-
this.bodyTextArea.value =
|
327
|
+
this.bodyTextArea.value = "";
|
282
328
|
this.setState({ alignment: 0 });
|
283
329
|
|
284
330
|
if (onCommentAdded) {
|
@@ -287,80 +333,33 @@ export class AddCommentForm extends Component {
|
|
287
333
|
}
|
288
334
|
}
|
289
335
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
user: PropTypes.shape({
|
294
|
-
name: PropTypes.string.isRequired
|
295
|
-
}),
|
296
|
-
verifiedUserGroups: PropTypes.arrayOf(
|
297
|
-
PropTypes.shape({
|
298
|
-
name: PropTypes.string.isRequired
|
299
|
-
})
|
300
|
-
).isRequired
|
301
|
-
}),
|
302
|
-
commentable: PropTypes.shape({
|
303
|
-
id: PropTypes.string.isRequired,
|
304
|
-
type: PropTypes.string.isRequired
|
305
|
-
}),
|
306
|
-
showTitle: PropTypes.bool.isRequired,
|
307
|
-
submitButtonClassName: PropTypes.string.isRequired,
|
308
|
-
onCommentAdded: PropTypes.func,
|
309
|
-
arguable: PropTypes.bool,
|
310
|
-
autoFocus: PropTypes.bool,
|
311
|
-
maxLength: PropTypes.number.isRequired
|
312
|
-
};
|
313
|
-
|
314
|
-
AddCommentForm.defaultProps = {
|
315
|
-
onCommentAdded: function() {},
|
316
|
-
showTitle: true,
|
317
|
-
submitButtonClassName: 'button button--sc',
|
318
|
-
arguable: false,
|
319
|
-
autoFocus: false,
|
320
|
-
maxLength: 1000
|
321
|
-
};
|
322
|
-
|
323
|
-
AddCommentForm.fragments = {
|
324
|
-
session: gql`
|
325
|
-
${addCommentFormSessionFragment}
|
326
|
-
`,
|
327
|
-
commentable: gql`
|
328
|
-
${addCommentFormCommentableFragment}
|
329
|
-
`
|
330
|
-
};
|
331
|
-
|
332
|
-
const AddCommentFormWithMutation = graphql(gql`
|
333
|
-
${addCommentMutation}
|
334
|
-
${commentThreadFragment}
|
335
|
-
${commentFragment}
|
336
|
-
${commentDataFragment}
|
337
|
-
${upVoteFragment}
|
338
|
-
${downVoteFragment}
|
339
|
-
`, {
|
336
|
+
const addCommentMutation = require("../mutations/add_comment.mutation.graphql");
|
337
|
+
|
338
|
+
const AddCommentFormWithMutation = graphql(addCommentMutation, {
|
340
339
|
props: ({ ownProps, mutate }) => ({
|
341
|
-
addComment: ({ body, alignment, userGroupId }) => mutate({
|
340
|
+
addComment: ({ body, alignment, userGroupId }: { body: string, alignment: number, userGroupId: string }) => mutate({
|
342
341
|
variables: {
|
343
342
|
commentableId: ownProps.commentable.id,
|
344
343
|
commentableType: ownProps.commentable.type,
|
345
344
|
body,
|
346
345
|
alignment,
|
347
|
-
userGroupId
|
346
|
+
userGroupId,
|
348
347
|
},
|
349
348
|
optimisticResponse: {
|
350
349
|
commentable: {
|
351
|
-
__typename:
|
350
|
+
__typename: "CommentableMutation",
|
352
351
|
addComment: {
|
353
|
-
__typename:
|
352
|
+
__typename: "Comment",
|
354
353
|
id: uuid(),
|
355
354
|
sgid: uuid(),
|
356
355
|
type: "Decidim::Comments::Comment",
|
357
356
|
createdAt: new Date().toISOString(),
|
358
357
|
body,
|
359
|
-
alignment
|
358
|
+
alignment,
|
360
359
|
author: {
|
361
|
-
__typename:
|
360
|
+
__typename: "User",
|
362
361
|
name: ownProps.session.user.name,
|
363
|
-
avatarUrl: ownProps.session.user.avatarUrl
|
362
|
+
avatarUrl: ownProps.session.user.avatarUrl,
|
364
363
|
},
|
365
364
|
comments: [],
|
366
365
|
hasComments: false,
|
@@ -369,32 +368,32 @@ const AddCommentFormWithMutation = graphql(gql`
|
|
369
368
|
upVoted: false,
|
370
369
|
downVotes: 0,
|
371
370
|
downVoted: false,
|
372
|
-
alreadyReported: false
|
373
|
-
}
|
374
|
-
}
|
371
|
+
alreadyReported: false,
|
372
|
+
},
|
373
|
+
},
|
375
374
|
},
|
376
375
|
updateQueries: {
|
377
|
-
GetComments: (prev, { mutationResult: { data } }) => {
|
376
|
+
GetComments: (prev: GetCommentsQuery, { mutationResult: { data } }: { mutationResult: { data: AddCommentMutation }}) => {
|
378
377
|
const { id, type } = ownProps.commentable;
|
379
|
-
const newComment = data.commentable.addComment;
|
378
|
+
const newComment = data.commentable && data.commentable.addComment;
|
380
379
|
let comments = [];
|
381
380
|
|
382
|
-
const commentReducer = (comment) => {
|
381
|
+
const commentReducer = (comment: CommentFragment): CommentFragment => {
|
383
382
|
const replies = comment.comments || [];
|
384
383
|
|
385
|
-
if (comment.id === id) {
|
384
|
+
if (newComment && comment.id === id) {
|
386
385
|
return {
|
387
386
|
...comment,
|
388
387
|
hasComments: true,
|
389
388
|
comments: [
|
390
389
|
...replies,
|
391
|
-
newComment
|
392
|
-
]
|
390
|
+
newComment,
|
391
|
+
],
|
393
392
|
};
|
394
393
|
}
|
395
394
|
return {
|
396
395
|
...comment,
|
397
|
-
comments: replies.map(commentReducer)
|
396
|
+
comments: replies.map(commentReducer),
|
398
397
|
};
|
399
398
|
};
|
400
399
|
|
@@ -403,7 +402,7 @@ const AddCommentFormWithMutation = graphql(gql`
|
|
403
402
|
} else {
|
404
403
|
comments = [
|
405
404
|
...prev.commentable.comments,
|
406
|
-
newComment
|
405
|
+
newComment,
|
407
406
|
];
|
408
407
|
}
|
409
408
|
|
@@ -411,13 +410,13 @@ const AddCommentFormWithMutation = graphql(gql`
|
|
411
410
|
...prev,
|
412
411
|
commentable: {
|
413
412
|
...prev.commentable,
|
414
|
-
comments
|
415
|
-
}
|
413
|
+
comments,
|
414
|
+
},
|
416
415
|
};
|
417
|
-
}
|
418
|
-
}
|
419
|
-
})
|
420
|
-
})
|
416
|
+
},
|
417
|
+
},
|
418
|
+
}),
|
419
|
+
}),
|
421
420
|
})(AddCommentForm);
|
422
421
|
|
423
422
|
export default AddCommentFormWithMutation;
|