decidim-comments 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +42 -0
- data/Rakefile +2 -0
- data/app/assets/config/decidim_comments_manifest.js +1 -0
- data/app/assets/javascripts/decidim/comments/bundle.js +504 -0
- data/app/assets/javascripts/decidim/comments/bundle.js.map +1 -0
- data/app/assets/javascripts/decidim/comments/comments.js.erb +8 -0
- data/app/commands/decidim/comments/create_comment.rb +40 -0
- data/app/forms/decidim/comments/comment_form.rb +16 -0
- data/app/frontend/application/apollo_client.js +16 -0
- data/app/frontend/application/application.component.jsx +37 -0
- data/app/frontend/application/application.component.test.jsx +33 -0
- data/app/frontend/application/icon.component.jsx +21 -0
- data/app/frontend/application/icon.component.test.jsx +43 -0
- data/app/frontend/comments/add_comment_form.component.jsx +250 -0
- data/app/frontend/comments/add_comment_form.component.test.jsx +173 -0
- data/app/frontend/comments/add_comment_form.mutation.graphql +8 -0
- data/app/frontend/comments/comment.component.jsx +202 -0
- data/app/frontend/comments/comment.component.test.jsx +125 -0
- data/app/frontend/comments/comment.fragment.graphql +12 -0
- data/app/frontend/comments/comment_data.fragment.graphql +11 -0
- data/app/frontend/comments/comment_order_selector.component.jsx +28 -0
- data/app/frontend/comments/comment_order_selector.component.test.jsx +9 -0
- data/app/frontend/comments/comment_thread.component.jsx +64 -0
- data/app/frontend/comments/comment_thread.component.test.jsx +71 -0
- data/app/frontend/comments/comment_thread.fragment.graphql +9 -0
- data/app/frontend/comments/comments.component.jsx +139 -0
- data/app/frontend/comments/comments.component.test.jsx +106 -0
- data/app/frontend/comments/comments.query.graphql +10 -0
- data/app/frontend/comments/featured_comment.component.jsx +23 -0
- data/app/frontend/comments/featured_comment.component.test.jsx +15 -0
- data/app/frontend/entry.js +24 -0
- data/app/frontend/entry.test.js +29 -0
- data/app/frontend/support/asset_url.js +11 -0
- data/app/frontend/support/generate_comments_data.js +29 -0
- data/app/frontend/support/generate_current_user_data.js +13 -0
- data/app/frontend/support/load_translations.js +23 -0
- data/app/frontend/support/require_all.js +10 -0
- data/app/frontend/support/resolve_graphql_query.js +37 -0
- data/app/frontend/support/stub_component.js +29 -0
- data/app/helpers/decidim/comments/comments_helper.rb +51 -0
- data/app/models/decidim/comments/comment.rb +55 -0
- data/app/types/decidim/comments/add_comment_type.rb +12 -0
- data/app/types/decidim/comments/author_type.rb +15 -0
- data/app/types/decidim/comments/comment_type.rb +28 -0
- data/config/i18n-tasks.yml +124 -0
- data/config/locales/ca.yml +35 -0
- data/config/locales/en.yml +36 -0
- data/config/locales/es.yml +35 -0
- data/db/migrate/20161130143508_create_comments.rb +11 -0
- data/db/migrate/20161214082645_add_depth_to_comments.rb +5 -0
- data/db/migrate/20161216102820_add_alignment_to_comments.rb +5 -0
- data/db/seeds.rb +11 -0
- data/lib/decidim/comments.rb +10 -0
- data/lib/decidim/comments/engine.rb +34 -0
- data/lib/decidim/comments/mutation_extensions.rb +36 -0
- data/lib/decidim/comments/query_extensions.rb +33 -0
- metadata +228 -0
@@ -0,0 +1,173 @@
|
|
1
|
+
/* eslint-disable no-unused-expressions */
|
2
|
+
import { shallow, mount } from 'enzyme';
|
3
|
+
|
4
|
+
import { AddCommentForm } from './add_comment_form.component';
|
5
|
+
|
6
|
+
import generateCurrentUserData from '../support/generate_current_user_data';
|
7
|
+
|
8
|
+
describe("<AddCommentForm />", () => {
|
9
|
+
let currentUser = null;
|
10
|
+
const commentableId = "1";
|
11
|
+
const commentableType = "Decidim::ParticipatoryProcess";
|
12
|
+
const addCommentStub = () => {
|
13
|
+
return null;
|
14
|
+
}
|
15
|
+
|
16
|
+
beforeEach(() => {
|
17
|
+
currentUser = generateCurrentUserData();
|
18
|
+
});
|
19
|
+
|
20
|
+
it("should render a div with class add-comment", () => {
|
21
|
+
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} currentUser={currentUser} commentableId={commentableId} commentableType={commentableType} />);
|
22
|
+
expect(wrapper.find('div.add-comment')).to.present();
|
23
|
+
});
|
24
|
+
|
25
|
+
it("should have a reference to body textarea", () => {
|
26
|
+
const wrapper = mount(<AddCommentForm addComment={addCommentStub} currentUser={currentUser} commentableId={commentableId} commentableType={commentableType} />);
|
27
|
+
expect(wrapper.instance().bodyTextArea).to.be.ok;
|
28
|
+
});
|
29
|
+
|
30
|
+
it("should initialize with a state property disabled as true", () => {
|
31
|
+
const wrapper = mount(<AddCommentForm addComment={addCommentStub} currentUser={currentUser} commentableId={commentableId} commentableType={commentableType} />);
|
32
|
+
expect(wrapper).to.have.state('disabled', true);
|
33
|
+
});
|
34
|
+
|
35
|
+
it("should have a default prop showTitle as true", () => {
|
36
|
+
const wrapper = mount(<AddCommentForm addComment={addCommentStub} currentUser={currentUser} commentableId={commentableId} commentableType={commentableType} />);
|
37
|
+
expect(wrapper).to.have.prop('showTitle').equal(true);
|
38
|
+
});
|
39
|
+
|
40
|
+
it("should not render the title if prop showTitle is false", () => {
|
41
|
+
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} currentUser={currentUser} commentableId={commentableId} commentableType={commentableType} showTitle={false} />);
|
42
|
+
expect(wrapper.find('h5.section-heading')).not.to.be.present();
|
43
|
+
});
|
44
|
+
|
45
|
+
it("should have a default prop submitButtonClassName as 'button button--sc'", () => {
|
46
|
+
const wrapper = mount(<AddCommentForm addComment={addCommentStub} currentUser={currentUser} commentableId={commentableId} commentableType={commentableType} />);
|
47
|
+
expect(wrapper).to.have.prop('submitButtonClassName').equal('button button--sc');
|
48
|
+
});
|
49
|
+
|
50
|
+
it("should use prop submitButtonClassName as a className prop for submit button", () => {
|
51
|
+
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} currentUser={currentUser} commentableId={commentableId} commentableType={commentableType} submitButtonClassName="button small hollow" />);
|
52
|
+
expect(wrapper.find('input[type="submit"]')).to.have.className('button');
|
53
|
+
expect(wrapper.find('input[type="submit"]')).to.have.className('small');
|
54
|
+
expect(wrapper.find('input[type="submit"]')).to.have.className('hollow');
|
55
|
+
});
|
56
|
+
|
57
|
+
it("should enable the submit button if textarea is not blank", () => {
|
58
|
+
const wrapper = mount(<AddCommentForm addComment={addCommentStub} currentUser={currentUser} commentableId={commentableId} commentableType={commentableType} />);
|
59
|
+
wrapper.find('textarea').simulate('change', {
|
60
|
+
target: {
|
61
|
+
value: 'This is a comment'
|
62
|
+
}
|
63
|
+
});
|
64
|
+
expect(wrapper.find('input[type="submit"]')).not.to.be.disabled();
|
65
|
+
});
|
66
|
+
|
67
|
+
it("should disable the submit button if textarea is blank", () => {
|
68
|
+
const wrapper = mount(<AddCommentForm addComment={addCommentStub} currentUser={currentUser} commentableId={commentableId} commentableType={commentableType} />);
|
69
|
+
wrapper.find('textarea').simulate('change', {
|
70
|
+
target: {
|
71
|
+
value: 'This will be deleted'
|
72
|
+
}
|
73
|
+
});
|
74
|
+
wrapper.find('textarea').simulate('change', {
|
75
|
+
target: {
|
76
|
+
value: ''
|
77
|
+
}
|
78
|
+
});
|
79
|
+
expect(wrapper.find('input[type="submit"]')).to.be.disabled();
|
80
|
+
});
|
81
|
+
|
82
|
+
it("should not render a div with class 'opinion-toggle'", () => {
|
83
|
+
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} currentUser={currentUser} commentableId={commentableId} commentableType={commentableType} />);
|
84
|
+
expect(wrapper.find('.opinion-toggle')).not.to.be.present();
|
85
|
+
});
|
86
|
+
|
87
|
+
describe("submitting the form", () => {
|
88
|
+
let addComment = null;
|
89
|
+
let onCommentAdded = null;
|
90
|
+
let wrapper = null;
|
91
|
+
let message = null;
|
92
|
+
|
93
|
+
beforeEach(() => {
|
94
|
+
addComment = sinon.spy();
|
95
|
+
onCommentAdded = sinon.spy();
|
96
|
+
wrapper = mount(<AddCommentForm addComment={addComment} currentUser={currentUser} commentableId={commentableId} commentableType={commentableType} onCommentAdded={onCommentAdded} />);
|
97
|
+
message = 'This will be submitted';
|
98
|
+
wrapper.instance().bodyTextArea.value = message;
|
99
|
+
});
|
100
|
+
|
101
|
+
it("should call addComment prop with the textarea value and state property alignment", () => {
|
102
|
+
wrapper.find('form').simulate('submit');
|
103
|
+
expect(addComment).to.calledWith({ body: message, alignment: 0 });
|
104
|
+
});
|
105
|
+
|
106
|
+
it("should reset textarea", () => {
|
107
|
+
wrapper.find('form').simulate('submit');
|
108
|
+
expect(wrapper.find('textarea')).to.have.value('');
|
109
|
+
});
|
110
|
+
|
111
|
+
it("should prevent default form submission", () => {
|
112
|
+
const preventDefault = sinon.spy();
|
113
|
+
wrapper.find('form').simulate('submit', { preventDefault });
|
114
|
+
expect(preventDefault).to.have.been.called;
|
115
|
+
});
|
116
|
+
|
117
|
+
it("should call the prop onCommentAdded function", () => {
|
118
|
+
wrapper.find('form').simulate('submit');
|
119
|
+
expect(onCommentAdded).to.have.been.called;
|
120
|
+
});
|
121
|
+
});
|
122
|
+
|
123
|
+
it("should initialize state with a property alignment and value 0", () => {
|
124
|
+
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} currentUser={currentUser} commentableId={commentableId} commentableType={commentableType} arguable />);
|
125
|
+
expect(wrapper).to.have.state('alignment').equal(0);
|
126
|
+
});
|
127
|
+
|
128
|
+
describe("when receiving an optional prop arguable with value true", () => {
|
129
|
+
it("should render a div with class 'opinion-toggle'", () => {
|
130
|
+
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} currentUser={currentUser} commentableId={commentableId} commentableType={commentableType} arguable />);
|
131
|
+
expect(wrapper.find('.opinion-toggle')).to.be.present();
|
132
|
+
});
|
133
|
+
|
134
|
+
it("should set state alignment to 1 if user clicks ok button and change its class", () => {
|
135
|
+
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} currentUser={currentUser} commentableId={commentableId} commentableType={commentableType} arguable />);
|
136
|
+
wrapper.find('.opinion-toggle--ok').simulate('click');
|
137
|
+
expect(wrapper.find('.opinion-toggle--ok')).to.have.className('is-active');
|
138
|
+
expect(wrapper).to.have.state('alignment').equal(1);
|
139
|
+
});
|
140
|
+
|
141
|
+
it("should set state alignment to -11 if user clicks ko button and change its class", () => {
|
142
|
+
const wrapper = shallow(<AddCommentForm addComment={addCommentStub} currentUser={currentUser} commentableId={commentableId} commentableType={commentableType} arguable />);
|
143
|
+
wrapper.find('.opinion-toggle--ko').simulate('click');
|
144
|
+
expect(wrapper.find('.opinion-toggle--ko')).to.have.className('is-active');
|
145
|
+
expect(wrapper).to.have.state('alignment').equal(-1);
|
146
|
+
});
|
147
|
+
|
148
|
+
describe("submitting the form", () => {
|
149
|
+
let addComment = null;
|
150
|
+
let wrapper = null;
|
151
|
+
let message = null;
|
152
|
+
|
153
|
+
beforeEach(() => {
|
154
|
+
addComment = sinon.spy();
|
155
|
+
wrapper = mount(<AddCommentForm addComment={addComment} currentUser={currentUser} commentableId={commentableId} commentableType={commentableType} arguable />);
|
156
|
+
message = 'This will be submitted';
|
157
|
+
wrapper.instance().bodyTextArea.value = message;
|
158
|
+
});
|
159
|
+
|
160
|
+
it("should call addComment prop with the state's property alignment", () => {
|
161
|
+
wrapper.find('button.opinion-toggle--ko').simulate('click');
|
162
|
+
wrapper.find('form').simulate('submit');
|
163
|
+
expect(addComment).to.calledWith({ body: message, alignment: -1 });
|
164
|
+
});
|
165
|
+
|
166
|
+
it("should reset the state to its initial state", () => {
|
167
|
+
wrapper.find('button.opinion-toggle--ok').simulate('click');
|
168
|
+
wrapper.find('form').simulate('submit');
|
169
|
+
expect(wrapper).to.have.state('alignment').eq(0);
|
170
|
+
});
|
171
|
+
});
|
172
|
+
});
|
173
|
+
});
|
@@ -0,0 +1,8 @@
|
|
1
|
+
mutation addComment($commentableId: String!, $commentableType: String!, $body: String!, $alignment: Int) {
|
2
|
+
addComment(commentableId: $commentableId, commentableType: $commentableType, body: $body, alignment: $alignment) {
|
3
|
+
...CommentData
|
4
|
+
replies {
|
5
|
+
id
|
6
|
+
}
|
7
|
+
}
|
8
|
+
}
|
@@ -0,0 +1,202 @@
|
|
1
|
+
import { Component, PropTypes } from 'react';
|
2
|
+
import { propType } from 'graphql-anywhere';
|
3
|
+
import gql from 'graphql-tag';
|
4
|
+
import moment from 'moment';
|
5
|
+
import { I18n } from 'react-i18nify';
|
6
|
+
import classnames from 'classnames';
|
7
|
+
|
8
|
+
import AddCommentForm from './add_comment_form.component';
|
9
|
+
|
10
|
+
import commentFragment from './comment.fragment.graphql';
|
11
|
+
import commentDataFragment from './comment_data.fragment.graphql';
|
12
|
+
|
13
|
+
/**
|
14
|
+
* A single comment component with the author info and the comment's body
|
15
|
+
* @class
|
16
|
+
* @augments Component
|
17
|
+
*/
|
18
|
+
class Comment extends Component {
|
19
|
+
constructor(props) {
|
20
|
+
super(props);
|
21
|
+
|
22
|
+
this.state = {
|
23
|
+
showReplyForm: false
|
24
|
+
};
|
25
|
+
}
|
26
|
+
|
27
|
+
render() {
|
28
|
+
const { comment: { id, author, body, createdAt }, articleClassName } = this.props;
|
29
|
+
|
30
|
+
const formattedCreatedAt = ` ${moment(createdAt, "YYYY-MM-DD HH:mm:ss z").format("LLL")}`;
|
31
|
+
|
32
|
+
return (
|
33
|
+
<article id={`comment_${id}`} className={articleClassName}>
|
34
|
+
<div className="comment__header">
|
35
|
+
<div className="author-data">
|
36
|
+
<div className="author-data__main">
|
37
|
+
<div className="author author--inline">
|
38
|
+
<a className="author__avatar">
|
39
|
+
<img src={author.avatarUrl} alt="author-avatar" />
|
40
|
+
</a>
|
41
|
+
<a className="author__name">{author.name}</a>
|
42
|
+
<time dateTime={createdAt}>{formattedCreatedAt}</time>
|
43
|
+
</div>
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
</div>
|
47
|
+
<div className="comment__content">
|
48
|
+
<p>
|
49
|
+
{ this._renderAlignmentBadge() }
|
50
|
+
{ body }
|
51
|
+
</p>
|
52
|
+
</div>
|
53
|
+
{this._renderReplies()}
|
54
|
+
<div className="comment__footer">
|
55
|
+
{this._renderReplyButton()}
|
56
|
+
</div>
|
57
|
+
{this._renderReplyForm()}
|
58
|
+
</article>
|
59
|
+
);
|
60
|
+
}
|
61
|
+
|
62
|
+
/**
|
63
|
+
* Render reply button if user can reply the comment
|
64
|
+
* @private
|
65
|
+
* @returns {Void|DOMElement} - Render the reply button or not if user can reply
|
66
|
+
*/
|
67
|
+
_renderReplyButton() {
|
68
|
+
const { comment: { canHaveReplies }, currentUser } = this.props;
|
69
|
+
const { showReplyForm } = this.state;
|
70
|
+
|
71
|
+
if (currentUser && canHaveReplies) {
|
72
|
+
return (
|
73
|
+
<button
|
74
|
+
className="comment__reply muted-link"
|
75
|
+
aria-controls="comment1-reply"
|
76
|
+
onClick={() => this.setState({ showReplyForm: !showReplyForm })}
|
77
|
+
>
|
78
|
+
{ I18n.t("components.comment.reply") }
|
79
|
+
</button>
|
80
|
+
);
|
81
|
+
}
|
82
|
+
|
83
|
+
return <div> </div>;
|
84
|
+
}
|
85
|
+
|
86
|
+
/**
|
87
|
+
* Render comment replies alternating the css class
|
88
|
+
* @private
|
89
|
+
* @returns {Void|DomElement} - A wrapper element with comment replies inside
|
90
|
+
*/
|
91
|
+
_renderReplies() {
|
92
|
+
const { comment: { id, replies }, currentUser, articleClassName } = this.props;
|
93
|
+
let replyArticleClassName = 'comment comment--nested';
|
94
|
+
|
95
|
+
if (articleClassName === 'comment comment--nested') {
|
96
|
+
replyArticleClassName = `${replyArticleClassName} comment--nested--alt`;
|
97
|
+
}
|
98
|
+
|
99
|
+
if (replies) {
|
100
|
+
return (
|
101
|
+
<div>
|
102
|
+
{
|
103
|
+
replies.map((reply) => (
|
104
|
+
<Comment
|
105
|
+
key={`comment_${id}_reply_${reply.id}`}
|
106
|
+
comment={reply}
|
107
|
+
currentUser={currentUser}
|
108
|
+
articleClassName={replyArticleClassName}
|
109
|
+
/>
|
110
|
+
))
|
111
|
+
}
|
112
|
+
</div>
|
113
|
+
);
|
114
|
+
}
|
115
|
+
|
116
|
+
return null;
|
117
|
+
}
|
118
|
+
|
119
|
+
/**
|
120
|
+
* Render reply form based on the current component state
|
121
|
+
* @private
|
122
|
+
* @returns {Void|ReactElement} - Render the AddCommentForm component or not
|
123
|
+
*/
|
124
|
+
_renderReplyForm() {
|
125
|
+
const { currentUser, comment } = this.props;
|
126
|
+
const { showReplyForm } = this.state;
|
127
|
+
|
128
|
+
if (showReplyForm) {
|
129
|
+
return (
|
130
|
+
<AddCommentForm
|
131
|
+
commentableId={comment.id}
|
132
|
+
commentableType="Decidim::Comments::Comment"
|
133
|
+
currentUser={currentUser}
|
134
|
+
showTitle={false}
|
135
|
+
submitButtonClassName="button small hollow"
|
136
|
+
onCommentAdded={() => this.setState({ showReplyForm: false })}
|
137
|
+
/>
|
138
|
+
);
|
139
|
+
}
|
140
|
+
|
141
|
+
return null;
|
142
|
+
}
|
143
|
+
|
144
|
+
/**
|
145
|
+
* Render alignment badge if comment's alignment is 0 or -1
|
146
|
+
* @private
|
147
|
+
* @returns {Void|DOMElement} - The alignment's badge or not
|
148
|
+
*/
|
149
|
+
_renderAlignmentBadge() {
|
150
|
+
const { comment: { alignment } } = this.props;
|
151
|
+
const spanClassName = classnames('label', {
|
152
|
+
success: alignment === 1,
|
153
|
+
alert: alignment === -1
|
154
|
+
});
|
155
|
+
|
156
|
+
let label = '';
|
157
|
+
|
158
|
+
if (alignment === 1) {
|
159
|
+
label = I18n.t('components.comment.alignment.in_favor');
|
160
|
+
} else {
|
161
|
+
label = I18n.t('components.comment.alignment.against');
|
162
|
+
}
|
163
|
+
|
164
|
+
if (alignment === 1 || alignment === -1) {
|
165
|
+
return (
|
166
|
+
<span>
|
167
|
+
<span className={spanClassName}>{ label }</span>
|
168
|
+
|
169
|
+
</span>
|
170
|
+
);
|
171
|
+
}
|
172
|
+
|
173
|
+
return null;
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
Comment.fragments = {
|
178
|
+
comment: gql`
|
179
|
+
${commentFragment}
|
180
|
+
${commentDataFragment}
|
181
|
+
`,
|
182
|
+
commentData: gql`
|
183
|
+
${commentDataFragment}
|
184
|
+
`
|
185
|
+
};
|
186
|
+
|
187
|
+
Comment.defaultProps = {
|
188
|
+
articleClassName: 'comment'
|
189
|
+
};
|
190
|
+
|
191
|
+
Comment.propTypes = {
|
192
|
+
comment: PropTypes.oneOfType([
|
193
|
+
propType(Comment.fragments.comment).isRequired,
|
194
|
+
propType(Comment.fragments.commentData).isRequired
|
195
|
+
]).isRequired,
|
196
|
+
currentUser: PropTypes.shape({
|
197
|
+
name: PropTypes.string.isRequired
|
198
|
+
}),
|
199
|
+
articleClassName: PropTypes.string.isRequired
|
200
|
+
};
|
201
|
+
|
202
|
+
export default Comment;
|
@@ -0,0 +1,125 @@
|
|
1
|
+
import { shallow, mount } from 'enzyme';
|
2
|
+
import { filter } from 'graphql-anywhere';
|
3
|
+
import gql from 'graphql-tag';
|
4
|
+
|
5
|
+
import Comment from './comment.component';
|
6
|
+
import AddCommentForm from './add_comment_form.component';
|
7
|
+
|
8
|
+
import commentFragment from './comment.fragment.graphql';
|
9
|
+
import commentDataFragment from './comment_data.fragment.graphql';
|
10
|
+
|
11
|
+
import stubComponent from '../support/stub_component';
|
12
|
+
import generateCommentsData from '../support/generate_comments_data';
|
13
|
+
import generateCurrentUserData from '../support/generate_current_user_data';
|
14
|
+
|
15
|
+
describe("<Comment />", () => {
|
16
|
+
let comment = {};
|
17
|
+
let currentUser = null;
|
18
|
+
|
19
|
+
stubComponent(AddCommentForm);
|
20
|
+
|
21
|
+
beforeEach(() => {
|
22
|
+
let commentsData = generateCommentsData(1);
|
23
|
+
commentsData[0].replies = generateCommentsData(3);
|
24
|
+
const currentUserData = generateCurrentUserData();
|
25
|
+
|
26
|
+
const fragment = gql`
|
27
|
+
${commentFragment}
|
28
|
+
${commentDataFragment}
|
29
|
+
`;
|
30
|
+
|
31
|
+
comment = filter(fragment, commentsData[0]);
|
32
|
+
currentUser = currentUserData;
|
33
|
+
});
|
34
|
+
|
35
|
+
it("should render an article with class comment", () => {
|
36
|
+
const wrapper = shallow(<Comment comment={comment} currentUser={currentUser} />);
|
37
|
+
expect(wrapper.find('article.comment')).to.present();
|
38
|
+
});
|
39
|
+
|
40
|
+
it("should render a time tag with comment's created at", () => {
|
41
|
+
const wrapper = shallow(<Comment comment={comment} currentUser={currentUser} />);
|
42
|
+
expect(wrapper.find('time')).to.have.text(comment.created_at);
|
43
|
+
});
|
44
|
+
|
45
|
+
it("should render author's name in a link with class author__name", () => {
|
46
|
+
const wrapper = shallow(<Comment comment={comment} currentUser={currentUser} />);
|
47
|
+
expect(wrapper.find('a.author__name')).to.have.text(comment.author.name);
|
48
|
+
});
|
49
|
+
|
50
|
+
it("should render author's avatar as a image tag", () => {
|
51
|
+
const wrapper = shallow(<Comment comment={comment} currentUser={currentUser} />);
|
52
|
+
expect(wrapper.find('a.author__avatar img')).to.have.attr('src').equal(comment.author.avatarUrl);
|
53
|
+
});
|
54
|
+
|
55
|
+
it("should render comment's body on a div with class comment__content", () => {
|
56
|
+
const wrapper = shallow(<Comment comment={comment} currentUser={currentUser} />);
|
57
|
+
expect(wrapper.find('div.comment__content')).to.have.text(comment.body);
|
58
|
+
});
|
59
|
+
|
60
|
+
it("should initialize with a state property showReplyForm as false", () => {
|
61
|
+
const wrapper = shallow(<Comment comment={comment} currentUser={currentUser} />);
|
62
|
+
expect(wrapper).to.have.state('showReplyForm', false);
|
63
|
+
});
|
64
|
+
|
65
|
+
it("should render a AddCommentForm component with the correct props when clicking the reply button", () => {
|
66
|
+
const wrapper = shallow(<Comment comment={comment} currentUser={currentUser} />);
|
67
|
+
expect(wrapper.find(AddCommentForm)).not.to.be.present();
|
68
|
+
wrapper.find('button.comment__reply').simulate('click');
|
69
|
+
expect(wrapper.find(AddCommentForm)).to.have.prop('currentUser').deep.equal(currentUser);
|
70
|
+
expect(wrapper.find(AddCommentForm)).to.have.prop('commentableId').equal(comment.id);
|
71
|
+
expect(wrapper.find(AddCommentForm)).to.have.prop('commentableType').equal("Decidim::Comments::Comment");
|
72
|
+
expect(wrapper.find(AddCommentForm)).to.have.prop('showTitle').equal(false);
|
73
|
+
expect(wrapper.find(AddCommentForm)).to.have.prop('submitButtonClassName').equal('button small hollow');
|
74
|
+
});
|
75
|
+
|
76
|
+
it("should not render the reply button if the comment cannot have replies", () => {
|
77
|
+
comment.canHaveReplies = false;
|
78
|
+
const wrapper = shallow(<Comment comment={comment} currentUser={currentUser} />);
|
79
|
+
expect(wrapper.find('button.comment__reply')).not.to.be.present();
|
80
|
+
});
|
81
|
+
|
82
|
+
it("should render comment replies a separate Comment components", () => {
|
83
|
+
const wrapper = shallow(<Comment comment={comment} currentUser={currentUser} />);
|
84
|
+
wrapper.find(Comment).forEach((node, idx) => {
|
85
|
+
expect(node).to.have.prop("comment").deep.equal(comment.replies[idx]);
|
86
|
+
expect(node).to.have.prop("currentUser").deep.equal(currentUser);
|
87
|
+
expect(node).to.have.prop("articleClassName").equal("comment comment--nested")
|
88
|
+
});
|
89
|
+
});
|
90
|
+
|
91
|
+
it("should render comment replies with articleClassName as 'comment comment--nested comment--nested--alt' when articleClassName is 'comment comment--nested'", () => {
|
92
|
+
const wrapper = shallow(<Comment comment={comment} currentUser={currentUser} articleClassName="comment comment--nested" />);
|
93
|
+
wrapper.find(Comment).forEach((node) => {
|
94
|
+
expect(node).to.have.prop("articleClassName").equal("comment comment--nested comment--nested--alt")
|
95
|
+
});
|
96
|
+
});
|
97
|
+
|
98
|
+
it("should have a default prop articleClassName with value 'comment'", () => {
|
99
|
+
const wrapper = mount(<Comment comment={comment} currentUser={currentUser} />);
|
100
|
+
expect(wrapper).to.have.prop("articleClassName").equal("comment");
|
101
|
+
});
|
102
|
+
|
103
|
+
describe("when user is not logged in", () => {
|
104
|
+
beforeEach(() => {
|
105
|
+
currentUser = null;
|
106
|
+
});
|
107
|
+
|
108
|
+
it("should not render reply button", () => {
|
109
|
+
const wrapper = shallow(<Comment comment={comment} currentUser={currentUser} />);
|
110
|
+
expect(wrapper.find('button.comment__reply')).not.to.be.present();
|
111
|
+
});
|
112
|
+
});
|
113
|
+
|
114
|
+
it("should render a 'in favor' badge if comment's alignment is 1", () => {
|
115
|
+
comment.alignment = 1;
|
116
|
+
const wrapper = shallow(<Comment comment={comment} currentUser={currentUser} />);
|
117
|
+
expect(wrapper.find('span.success.label')).to.have.text('In favor');
|
118
|
+
});
|
119
|
+
|
120
|
+
it("should render a 'against' badge if comment's alignment is -1", () => {
|
121
|
+
comment.alignment = -1;
|
122
|
+
const wrapper = shallow(<Comment comment={comment} currentUser={currentUser} />);
|
123
|
+
expect(wrapper.find('span.alert.label')).to.have.text('Against');
|
124
|
+
});
|
125
|
+
});
|