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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +42 -0
  3. data/Rakefile +2 -0
  4. data/app/assets/config/decidim_comments_manifest.js +1 -0
  5. data/app/assets/javascripts/decidim/comments/bundle.js +504 -0
  6. data/app/assets/javascripts/decidim/comments/bundle.js.map +1 -0
  7. data/app/assets/javascripts/decidim/comments/comments.js.erb +8 -0
  8. data/app/commands/decidim/comments/create_comment.rb +40 -0
  9. data/app/forms/decidim/comments/comment_form.rb +16 -0
  10. data/app/frontend/application/apollo_client.js +16 -0
  11. data/app/frontend/application/application.component.jsx +37 -0
  12. data/app/frontend/application/application.component.test.jsx +33 -0
  13. data/app/frontend/application/icon.component.jsx +21 -0
  14. data/app/frontend/application/icon.component.test.jsx +43 -0
  15. data/app/frontend/comments/add_comment_form.component.jsx +250 -0
  16. data/app/frontend/comments/add_comment_form.component.test.jsx +173 -0
  17. data/app/frontend/comments/add_comment_form.mutation.graphql +8 -0
  18. data/app/frontend/comments/comment.component.jsx +202 -0
  19. data/app/frontend/comments/comment.component.test.jsx +125 -0
  20. data/app/frontend/comments/comment.fragment.graphql +12 -0
  21. data/app/frontend/comments/comment_data.fragment.graphql +11 -0
  22. data/app/frontend/comments/comment_order_selector.component.jsx +28 -0
  23. data/app/frontend/comments/comment_order_selector.component.test.jsx +9 -0
  24. data/app/frontend/comments/comment_thread.component.jsx +64 -0
  25. data/app/frontend/comments/comment_thread.component.test.jsx +71 -0
  26. data/app/frontend/comments/comment_thread.fragment.graphql +9 -0
  27. data/app/frontend/comments/comments.component.jsx +139 -0
  28. data/app/frontend/comments/comments.component.test.jsx +106 -0
  29. data/app/frontend/comments/comments.query.graphql +10 -0
  30. data/app/frontend/comments/featured_comment.component.jsx +23 -0
  31. data/app/frontend/comments/featured_comment.component.test.jsx +15 -0
  32. data/app/frontend/entry.js +24 -0
  33. data/app/frontend/entry.test.js +29 -0
  34. data/app/frontend/support/asset_url.js +11 -0
  35. data/app/frontend/support/generate_comments_data.js +29 -0
  36. data/app/frontend/support/generate_current_user_data.js +13 -0
  37. data/app/frontend/support/load_translations.js +23 -0
  38. data/app/frontend/support/require_all.js +10 -0
  39. data/app/frontend/support/resolve_graphql_query.js +37 -0
  40. data/app/frontend/support/stub_component.js +29 -0
  41. data/app/helpers/decidim/comments/comments_helper.rb +51 -0
  42. data/app/models/decidim/comments/comment.rb +55 -0
  43. data/app/types/decidim/comments/add_comment_type.rb +12 -0
  44. data/app/types/decidim/comments/author_type.rb +15 -0
  45. data/app/types/decidim/comments/comment_type.rb +28 -0
  46. data/config/i18n-tasks.yml +124 -0
  47. data/config/locales/ca.yml +35 -0
  48. data/config/locales/en.yml +36 -0
  49. data/config/locales/es.yml +35 -0
  50. data/db/migrate/20161130143508_create_comments.rb +11 -0
  51. data/db/migrate/20161214082645_add_depth_to_comments.rb +5 -0
  52. data/db/migrate/20161216102820_add_alignment_to_comments.rb +5 -0
  53. data/db/seeds.rb +11 -0
  54. data/lib/decidim/comments.rb +10 -0
  55. data/lib/decidim/comments/engine.rb +34 -0
  56. data/lib/decidim/comments/mutation_extensions.rb +36 -0
  57. data/lib/decidim/comments/query_extensions.rb +33 -0
  58. 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>&nbsp;</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
+ &nbsp;
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
+ });