decidim-comments 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
});
|