decidim-comments 0.23.6 → 0.24.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/decidim/comments/bundle.js.map +1 -1
  3. data/app/assets/javascripts/decidim/comments/comments.component.js.es6 +292 -0
  4. data/app/assets/javascripts/decidim/comments/comments.component.test.js +581 -0
  5. data/app/assets/javascripts/decidim/comments/comments.js.erb +1 -1
  6. data/app/cells/decidim/comments/comment/actions.erb +7 -0
  7. data/app/cells/decidim/comments/comment/alignment_badge.erb +4 -0
  8. data/app/cells/decidim/comments/comment/author.erb +1 -0
  9. data/app/cells/decidim/comments/comment/show.erb +40 -0
  10. data/app/cells/decidim/comments/comment/utilities.erb +13 -0
  11. data/app/cells/decidim/comments/comment/votes.erb +25 -0
  12. data/app/cells/decidim/comments/comment_card_cell.rb +22 -0
  13. data/app/cells/decidim/comments/comment_cell.rb +142 -8
  14. data/app/cells/decidim/comments/comment_form/comment_as.erb +10 -0
  15. data/app/cells/decidim/comments/comment_form/show.erb +24 -0
  16. data/app/cells/decidim/comments/comment_form_cell.rb +94 -0
  17. data/app/cells/decidim/comments/comment_m/top.erb +1 -1
  18. data/app/cells/decidim/comments/comment_thread/show.erb +6 -0
  19. data/app/cells/decidim/comments/comment_thread/title.erb +3 -0
  20. data/app/cells/decidim/comments/comment_thread_cell.rb +30 -0
  21. data/app/cells/decidim/comments/comments/add_comment.erb +30 -0
  22. data/app/cells/decidim/comments/comments/blocked_comments_warning.erb +3 -0
  23. data/app/cells/decidim/comments/comments/order_control.erb +37 -0
  24. data/app/cells/decidim/comments/comments/show.erb +32 -0
  25. data/app/cells/decidim/comments/comments/single_comment_warning.erb +9 -0
  26. data/app/cells/decidim/comments/comments/user_comments_blocked_warning.erb +3 -0
  27. data/app/cells/decidim/comments/comments_cell.rb +134 -0
  28. data/app/commands/decidim/comments/vote_comment.rb +34 -10
  29. data/app/controllers/decidim/comments/application_controller.rb +16 -0
  30. data/app/controllers/decidim/comments/comments_controller.rb +122 -0
  31. data/app/controllers/decidim/comments/votes_controller.rb +41 -0
  32. data/app/events/decidim/comments/comment_created_event.rb +1 -1
  33. data/app/events/decidim/comments/comment_downvoted_event.rb +8 -0
  34. data/app/events/decidim/comments/comment_upvoted_event.rb +8 -0
  35. data/app/events/decidim/comments/comment_voted_event.rb +26 -0
  36. data/app/forms/decidim/comments/comment_form.rb +6 -2
  37. data/app/models/decidim/comments/comment.rb +22 -8
  38. data/app/models/decidim/comments/seed.rb +1 -1
  39. data/app/permissions/decidim/comments/permissions.rb +59 -0
  40. data/app/queries/decidim/comments/metrics/comment_participants_metric_measure.rb +2 -2
  41. data/app/queries/decidim/comments/metrics/comments_metric_manage.rb +5 -6
  42. data/app/queries/decidim/comments/sorted_comments.rb +18 -14
  43. data/app/views/decidim/comments/comments/_comment.html.erb +5 -0
  44. data/app/views/decidim/comments/comments/_comments.html.erb +1 -0
  45. data/app/views/decidim/comments/comments/create.js.erb +16 -0
  46. data/app/views/decidim/comments/comments/error.js.erb +1 -0
  47. data/app/views/decidim/comments/comments/index.js.erb +24 -0
  48. data/app/views/decidim/comments/comments/reload.js.erb +21 -0
  49. data/app/views/decidim/comments/votes/create.js.erb +23 -0
  50. data/app/views/decidim/comments/votes/error.js.erb +1 -0
  51. data/config/locales/ar.yml +0 -2
  52. data/config/locales/ca.yml +7 -2
  53. data/config/locales/cs.yml +24 -7
  54. data/config/locales/de.yml +17 -2
  55. data/config/locales/el.yml +0 -2
  56. data/config/locales/en.yml +17 -2
  57. data/config/locales/es-MX.yml +7 -2
  58. data/config/locales/es-PY.yml +7 -2
  59. data/config/locales/es.yml +7 -2
  60. data/config/locales/eu.yml +0 -2
  61. data/config/locales/fi-plain.yml +17 -2
  62. data/config/locales/fi.yml +17 -2
  63. data/config/locales/fr-CA.yml +17 -2
  64. data/config/locales/fr.yml +17 -2
  65. data/config/locales/gl.yml +7 -2
  66. data/config/locales/hu.yml +0 -2
  67. data/config/locales/id-ID.yml +0 -2
  68. data/config/locales/is-IS.yml +0 -1
  69. data/config/locales/it.yml +4 -2
  70. data/config/locales/ja.yml +8 -4
  71. data/config/locales/lv.yml +0 -2
  72. data/config/locales/nl.yml +0 -2
  73. data/config/locales/no.yml +0 -2
  74. data/config/locales/pl.yml +19 -2
  75. data/config/locales/pt-BR.yml +0 -2
  76. data/config/locales/pt.yml +0 -2
  77. data/config/locales/ro-RO.yml +0 -3
  78. data/config/locales/ru.yml +0 -2
  79. data/config/locales/sk.yml +0 -2
  80. data/config/locales/sv.yml +7 -2
  81. data/config/locales/tr-TR.yml +7 -2
  82. data/config/locales/uk.yml +0 -1
  83. data/config/locales/zh-CN.yml +0 -2
  84. data/lib/decidim/api/add_comment_type.rb +13 -0
  85. data/lib/decidim/api/comment_mutation_type.rb +22 -0
  86. data/lib/decidim/api/comment_type.rb +89 -0
  87. data/lib/decidim/api/commentable_interface.rb +50 -0
  88. data/lib/decidim/api/commentable_mutation_type.rb +30 -0
  89. data/{app/types/decidim/comments → lib/decidim/api}/commentable_type.rb +2 -5
  90. data/lib/decidim/comments/api.rb +12 -0
  91. data/lib/decidim/comments/comments_helper.rb +10 -52
  92. data/lib/decidim/comments/engine.rb +9 -7
  93. data/lib/decidim/comments/mutation_extensions.rb +22 -22
  94. data/lib/decidim/comments/query_extensions.rb +12 -14
  95. data/lib/decidim/comments/test/shared_examples/comment_voted_event.rb +65 -0
  96. data/lib/decidim/comments/test.rb +1 -0
  97. data/lib/decidim/comments/version.rb +1 -1
  98. data/lib/decidim/comments.rb +1 -3
  99. metadata +67 -66
  100. data/app/assets/javascripts/decidim/comments/bundle.js +0 -268
  101. data/app/frontend/application/apollo_client.ts +0 -12
  102. data/app/frontend/application/application.component.test.tsx +0 -23
  103. data/app/frontend/application/application.component.tsx +0 -35
  104. data/app/frontend/application/icon.component.test.tsx +0 -38
  105. data/app/frontend/application/icon.component.tsx +0 -46
  106. data/app/frontend/comments/add_comment_form.component.test.tsx +0 -265
  107. data/app/frontend/comments/add_comment_form.component.tsx +0 -482
  108. data/app/frontend/comments/comment.component.test.tsx +0 -490
  109. data/app/frontend/comments/comment.component.tsx +0 -677
  110. data/app/frontend/comments/comment_order_selector.component.test.tsx +0 -29
  111. data/app/frontend/comments/comment_order_selector.component.tsx +0 -106
  112. data/app/frontend/comments/comment_thread.component.test.tsx +0 -82
  113. data/app/frontend/comments/comment_thread.component.tsx +0 -81
  114. data/app/frontend/comments/comments.component.test.tsx +0 -150
  115. data/app/frontend/comments/comments.component.tsx +0 -289
  116. data/app/frontend/comments/down_vote_button.component.test.tsx +0 -59
  117. data/app/frontend/comments/down_vote_button.component.tsx +0 -133
  118. data/app/frontend/comments/up_vote_button.component.test.tsx +0 -59
  119. data/app/frontend/comments/up_vote_button.component.tsx +0 -133
  120. data/app/frontend/comments/vote_button.component.tsx +0 -50
  121. data/app/frontend/comments/vote_button_component.test.tsx +0 -64
  122. data/app/frontend/entry.ts +0 -38
  123. data/app/frontend/entry_test.ts +0 -6
  124. data/app/frontend/fragments/add_comment_form_commentable.fragment.graphql +0 -4
  125. data/app/frontend/fragments/add_comment_form_session.fragment.graphql +0 -6
  126. data/app/frontend/fragments/comment.fragment.graphql +0 -14
  127. data/app/frontend/fragments/comment_data.fragment.graphql +0 -27
  128. data/app/frontend/fragments/comment_thread.fragment.graphql +0 -6
  129. data/app/frontend/fragments/down_vote_button.fragment.graphql +0 -6
  130. data/app/frontend/fragments/up_vote_button.fragment.graphql +0 -6
  131. data/app/frontend/mutations/add_comment.mutation.graphql +0 -9
  132. data/app/frontend/mutations/down_vote.mutation.graphql +0 -9
  133. data/app/frontend/mutations/up_vote.mutation.graphql +0 -9
  134. data/app/frontend/queries/comments.query.graphql +0 -26
  135. data/app/frontend/support/asset_url.ts +0 -11
  136. data/app/frontend/support/generate_comments_data.ts +0 -49
  137. data/app/frontend/support/generate_user_data.ts +0 -14
  138. data/app/frontend/support/generate_user_group_data.ts +0 -14
  139. data/app/frontend/support/graphql_transformer.js +0 -32
  140. data/app/frontend/support/load_translations.ts +0 -48
  141. data/app/frontend/support/require_all.ts +0 -10
  142. data/app/frontend/support/resolve_graphql_query.ts +0 -37
  143. data/app/frontend/support/schema.ts +0 -2026
  144. data/app/types/decidim/comments/commentable_interface.rb +0 -61
  145. data/app/types/decidim/comments/commentable_mutation_type.rb +0 -33
  146. data/lib/decidim/comments/api/add_comment_type.rb +0 -13
  147. data/lib/decidim/comments/api/comment_mutation_type.rb +0 -20
  148. data/lib/decidim/comments/api/comment_type.rb +0 -89
@@ -0,0 +1,581 @@
1
+ /* eslint-disable id-length, max-lines */
2
+ /* global spyOn, jest */
3
+ const $ = require("jquery");
4
+
5
+ // Ability to spy on the jQuery methods inside the component in order to test
6
+ // the sub-elements correctly. Needs to be defined before the modules are loaded
7
+ // in order for them to define the $ variable correctly.
8
+ window.$ = jest.fn().mockImplementation((...args) => $(...args));
9
+ window.$.ajax = jest.fn().mockImplementation((...args) => $.ajax(...args));
10
+
11
+ // Quill is expected by the input character counter
12
+ window.Quill = require("../../../../../../decidim-core/vendor/assets/javascripts/quill.min.js");
13
+
14
+ // Fake timers for testing polling
15
+ jest.useFakeTimers();
16
+
17
+ require("../../../../../../decidim-core/app/assets/javascripts/decidim/input_character_counter.js.es6");
18
+ require("./comments.component.js.es6");
19
+
20
+ const { Decidim: { CommentsComponent, createCharacterCounter } } = window;
21
+
22
+ // Create a dummy foundation jQuery method for the comments component to call
23
+ $.fn.foundation = () => {};
24
+
25
+ describe("CommentsComponent", () => {
26
+ const selector = "#comments-for-Dummy-123";
27
+ let subject = null;
28
+ let $doc = null;
29
+ let $container = null;
30
+ let addComment = null;
31
+ let orderLinks = null;
32
+ let allToggles = null;
33
+ let allTextareas = null;
34
+ let allForms = null;
35
+
36
+ const spyOnAddComment = (methodToSpy) => {
37
+ addComment.each((i) => {
38
+ addComment[i].$ = $(addComment[i]);
39
+ addComment[i].opinionToggles = $(".opinion-toggle .button", addComment[i].$);
40
+ addComment[i].commentForm = $("form", addComment[i].$);
41
+ addComment[i].commentTextarea = $("textarea", addComment[i].commentForm);
42
+
43
+ if (methodToSpy) {
44
+ spyOn(addComment[i].opinionToggles, methodToSpy);
45
+ spyOn(addComment[i].commentForm, methodToSpy);
46
+ spyOn(addComment[i].commentTextarea, methodToSpy);
47
+ }
48
+ });
49
+
50
+ spyOn(window, "$").mockImplementation((...args) => {
51
+ const jqSelector = args[0];
52
+ const parent = args[1];
53
+
54
+ if (jqSelector === document) {
55
+ return $doc;
56
+ } else if (jqSelector === ".order-by__dropdown .is-submenu-item a" && parent.is(subject.$element)) {
57
+ return orderLinks;
58
+ } else if (jqSelector === ".add-comment" && parent.is(subject.$element)) {
59
+ return addComment;
60
+ } else if (jqSelector === ".add-comment .opinion-toggle .button" && parent.is(subject.$element)) {
61
+ return allToggles;
62
+ } else if (jqSelector === ".add-comment textarea" && parent.is(subject.$element)) {
63
+ return allTextareas;
64
+ } else if (jqSelector === ".add-comment form" && parent.is(subject.$element)) {
65
+ return allForms;
66
+ }
67
+ const addCommentsArray = addComment.toArray();
68
+ for (let i = 0; i < addCommentsArray.length; i += 1) {
69
+ if (jqSelector === ".opinion-toggle .button" && parent.is(addCommentsArray[i].$)) {
70
+ return addCommentsArray[i].opinionToggles;
71
+ } else if (jqSelector === "form" && parent.is(addCommentsArray[i].$)) {
72
+ return addCommentsArray[i].commentForm;
73
+ } else if (jqSelector === "textarea" && parent.is(addCommentsArray[i].commentForm)) {
74
+ return addCommentsArray[i].commentTextarea;
75
+ }
76
+ }
77
+ return $(...args);
78
+ });
79
+ }
80
+
81
+ const generateCommentForm = (modelName, modelId) => {
82
+ return `
83
+ <form class="new_comment" id="new_comment_for_${modelName}_${modelId}" action="/comments?order=older" accept-charset="UTF-8" data-remote="true" method="post">
84
+ <input name="utf8" type="hidden" value="✓" />
85
+ <input type="hidden" value="commentable-gid" name="comment[commentable_gid]" />
86
+ <input class="alignment-input" type="hidden" value="0" name="comment[alignment]" />
87
+ <div class="field">
88
+ <label for="add-comment-${modelName}-${modelId}-user-group-id">
89
+ Comment as
90
+ </label>
91
+ <select id="add-comment-${modelName}-${modelId}-user-group-id" name="comment[user_group_id]">
92
+ <option value="">Dwain Oberbrunner</option>
93
+ <option value="4">Schmidt, Adams and Cassin</option>
94
+ </select>
95
+ </div>
96
+
97
+ <div class="field">
98
+ <label class="show-for-sr" for="add-comment-${modelName}-${modelId}">
99
+ Comment
100
+ </label>
101
+ <div class="hashtags__container">
102
+ <label for="comment_body">
103
+ Body
104
+ <span
105
+ title=""
106
+ data-tooltip="true"
107
+ data-disable-hover="false"
108
+ data-keep-on-hover="true"
109
+ aria-haspopup="true"
110
+ class="label-required has-tip"
111
+ >
112
+ <span aria-hidden="true">*</span><span class="show-for-sr">Required field</span>
113
+ </span>
114
+ <textarea
115
+ required="required"
116
+ maxlength="1000"
117
+ id="add-comment-Comment-${modelId}"
118
+ rows="4"
119
+ placeholder="What do you think about this?"
120
+ data-tribute="true"
121
+ data-remaining-characters="#add-comment-${modelName}-${modelId}-remaining-characters"
122
+ name="comment[body]"
123
+ ></textarea>
124
+ <span class="form-error">There's an error in this field.</span>
125
+ </label>
126
+ </div>
127
+ <button type="submit" class="button button--sc" disabled="disabled">Send</button>
128
+ <span id="add-comment-${modelName}-${modelId}-remaining-characters" class="remaining-character-count">1000 characters left</span>
129
+ </div>
130
+ </form>
131
+ `;
132
+ };
133
+
134
+ const generateSingleComment = (commentId, content, replies = "") => {
135
+ return `
136
+ <div id="comment_${commentId}" class="comment comment--nested" data-comment-id="${commentId}">
137
+ <div class="comment__header">
138
+ <div class="author-data">
139
+ <div class="author-data__main">
140
+ <div class="author author--inline">
141
+ <a href="/profiles/eusebio_rempel">
142
+ <span class="author__avatar">
143
+ <img alt="Avatar" src="/assets/decidim/default-avatar-123.svg" />
144
+ </span>
145
+ <span
146
+ class="author__name m-none has-tip"
147
+ data-position="top"
148
+ data-show-on="medium"
149
+ data-alignment="center"
150
+ data-click-open="false"
151
+ data-keep-on-hover="true"
152
+ data-allow-html="true"
153
+ data-template-classes="light expanded"
154
+ data-tip-text="Tip text"
155
+ title=""
156
+ >Dwain Oberbrunner</span>
157
+ </a>
158
+ </div>
159
+
160
+ <span>
161
+ <time datetime="2020-09-28T17:15:08Z">28/09/2020 17:15</time>
162
+ </span>
163
+ </div>
164
+ <div class="author-data__extra">
165
+ <button type="button" class="link-alt" data-open="flagModalComment${commentId}" title="Report inappropriate content" aria-controls="flagModalComment${commentId}" aria-haspopup="true" tabindex="0">
166
+ <svg role="none presentation" aria-hidden="true" class="icon--flag icon icon--small">
167
+ <title></title>
168
+ <use role="none presentation" href="/assets/decidim/icons-123.svg#icon-flag"></use>
169
+ </svg>
170
+ <span class="show-for-sr">Report inappropriate content</span>
171
+ </button>
172
+
173
+ <a title="Get link to single comment" href="/path/to/dummy/123?commentId=${commentId}#comment_${commentId}">
174
+ <span class="show-for-sr">Get link to single comment</span>
175
+ <svg role="none presentation" class="icon--link-intact icon icon--small">
176
+ <title></title>
177
+ <use role="none presentation" href="/assets/decidim/icons-123.svg#icon-link-intact"></use>
178
+ </svg>
179
+ </a>
180
+ </div>
181
+ </div>
182
+ </div>
183
+ <div class="comment__content">
184
+ <p>${content}</p>
185
+ </div>
186
+ <div class="comment__footer">
187
+ <div class="comment__actions">
188
+ <button class="comment__reply muted-link" aria-controls="comment${commentId}-reply" data-toggle="comment${commentId}-reply" aria-expanded="true">
189
+ <svg role="none presentation" class="icon--pencil icon icon--small">
190
+ <title></title>
191
+ <use role="none presentation" href="/assets/decidim/icons-123.svg#icon-pencil"></use>
192
+ </svg>
193
+ &nbsp;Reply
194
+ </button>
195
+ </div>
196
+
197
+ <div class="comment__votes">
198
+ <form class="button_to" method="post" action="/comments/${commentId}/votes?weight=1" data-remote="true">
199
+ <button class="comment__votes--up" title="I agree with this comment" type="submit">
200
+ <span class="show-for-sr">I agree with this comment</span>
201
+ <svg role="none presentation" class="icon--chevron-top icon icon--small">
202
+ <title></title>
203
+ <use role="none presentation" href="/assets/decidim/icons-123.svg#icon-chevron-top"></use>
204
+ </svg>
205
+ <span class="comment__votes--count">0</span>
206
+ </button>
207
+ <input type="hidden" name="authenticity_token" value="xyz123" />
208
+ </form>
209
+
210
+ <form class="button_to" method="post" action="/comments/${commentId}/votes?weight=-1" data-remote="true">
211
+ <button class="comment__votes--down" title="I disagree with this comment" type="submit">
212
+ <span class="show-for-sr">I disagree with this comment</span>
213
+ <svg role="none presentation" class="icon--chevron-bottom icon icon--small">
214
+ <title></title>
215
+ <use role="none presentation" href="/assets/decidim/icons-123.svg#icon-chevron-bottom"></use>
216
+ </svg>
217
+ <span class="comment__votes--count">0</span>
218
+ </button>
219
+ <input type="hidden" name="authenticity_token" value="xyz123" />
220
+ </form>
221
+ </div>
222
+ </div>
223
+ <div id="comment-${commentId}-replies">${replies}</div>
224
+
225
+ <div class="comment__additionalreply hide">
226
+ <button class="comment__reply muted-link" aria-controls="comment${commentId}-reply" data-toggle="comment${commentId}-reply" aria-expanded="true">
227
+ <svg role="none presentation" class="icon--pencil icon icon--small">
228
+ <title></title>
229
+ <use role="none presentation" href="/assets/decidim/icons-123.svg#icon-pencil"></use>
230
+ </svg>
231
+ &nbsp;Reply
232
+ </button>
233
+ </div>
234
+
235
+ <div class="add-comment hide" id="comment${commentId}-reply" data-toggler=".hide">
236
+ ${generateCommentForm("Comment", commentId)}
237
+ </div>
238
+ </div>
239
+ `;
240
+ };
241
+
242
+ const generateCommentThread = (commentId, content, replies = "") => {
243
+ return `
244
+ <div>
245
+ <div class="comment-thread">
246
+ ${generateSingleComment(commentId, content, replies)}
247
+ </div>
248
+ </div>
249
+ `
250
+ }
251
+
252
+ beforeEach(() => {
253
+ let orderSelector = `
254
+ <ul class="dropdown menu" data-dropdown-menu="data-dropdown-menu" data-autoclose="false" data-disable-hover="true" data-click-open="true" data-close-on-click="true" tabindex="-1" role="menubar">
255
+ <li class="is-dropdown-submenu-parent opens-right" tabindex="-1" role="none">
256
+ <a href="#" id="comments-order-menu-control" aria-label="Order by:" aria-controls="comments-order-menu" aria-haspopup="true" role="menuitem">Older</a>
257
+ <ul class="menu is-dropdown-submenu submenu first-sub vertical" id="comments-order-chooser-menu" role="menu" aria-labelledby="comments-order-menu-control" tabindex="-1" data-submenu="">
258
+ <li role="none" class="is-submenu-item is-dropdown-submenu-item">
259
+ <a tabindex="-1" role="menuitem" data-remote="true" href="/comments?commentable_gid=commentable-gid&amp;order=best_rated&amp;reload=1">
260
+ Best rated
261
+ </a>
262
+ </li>
263
+ <li role="none" class="is-submenu-item is-dropdown-submenu-item">
264
+ <a tabindex="-1" role="menuitem" data-remote="true" href="/comments?commentable_gid=commentable-gid&amp;order=recent&amp;reload=1">
265
+ Recent
266
+ </a>
267
+ </li>
268
+ <li role="none" class="is-submenu-item is-dropdown-submenu-item">
269
+ <a tabindex="-1" role="menuitem" data-remote="true" href="/comments?commentable_gid=commentable-gid&amp;order=older&amp;reload=1">
270
+ Older
271
+ </a>
272
+ </li>
273
+ <li role="none" class="is-submenu-item is-dropdown-submenu-item">
274
+ <a tabindex="-1" role="menuitem" data-remote="true" href="/comments?commentable_gid=commentable-gid&amp;order=most_discussed&amp;reload=1">
275
+ Most discussed
276
+ </a>
277
+ </li>
278
+ </ul>
279
+ </li>
280
+ </ul>
281
+ `;
282
+
283
+ let reply = generateSingleComment(451, "This is a reply.");
284
+ let firstThread = generateCommentThread(450, "This is the first comment thread.", reply);
285
+ let secondThread = generateCommentThread(452, "This is another comment thread.");
286
+
287
+ let comments = `
288
+ <div id="comments-for-Dummy-123" data-decidim-comments='{"singleComment":false,"toggleTranslations":false,"commentableGid":"commentable-gid","commentsUrl":"/comments","rootDepth":0,"lastCommentId":456,"order":"older"}'>
289
+ <div class="columns large-9 comments-container" id="comments">
290
+ <div class="comments">
291
+ <div class="row collapse order-by">
292
+ <h2 class="order-by__text section-heading">3 comments</h2>
293
+ <div class="order-by__dropdown order-by__dropdown--right">
294
+ <span class="order-by__text">Order by:</span>
295
+ ${orderSelector}
296
+ </div>
297
+ </div>
298
+
299
+ <div class="comment-threads">
300
+ ${firstThread}
301
+ ${secondThread}
302
+ </div>
303
+ <div class="add-comment">
304
+ <h4 class="section-heading">Add your comment</h4>
305
+
306
+ <div class="opinion-toggle button-group">
307
+ <button class="button tiny button--muted opinion-toggle--ok">
308
+ <svg role="none presentation" class="icon--thumb-up icon">
309
+ <title></title>
310
+ <use role="none presentation" href="/assets/decidim/icons-2ba788b32e181c1a7197f7a54a0f03101c146dd434b9e56191690c7c2d7bdae3.svg#icon-thumb-up"></use>
311
+ </svg>
312
+ </button>
313
+ <button class="button tiny button--muted opinion-toggle--meh is-active">
314
+ Neutral
315
+ </button>
316
+ <button class="button tiny button--muted opinion-toggle--ko">
317
+ <svg role="none presentation" class="icon--thumb-down icon">
318
+ <title></title>
319
+ <use role="none presentation" href="/assets/decidim/icons-2ba788b32e181c1a7197f7a54a0f03101c146dd434b9e56191690c7c2d7bdae3.svg#icon-thumb-down"></use>
320
+ </svg>
321
+ </button>
322
+ </div>
323
+
324
+ ${generateCommentForm("Dummy", 123)}
325
+ </div>
326
+ </div>
327
+ <div class="callout primary loading-comments hide">
328
+ <p>Loading comments ...</p>
329
+ </div>
330
+ </div>
331
+ </div>
332
+ `;
333
+ $("body").append(comments);
334
+
335
+ $container = $(document).find(selector);
336
+ subject = new CommentsComponent($container, {
337
+ commentableGid: "commentable-gid",
338
+ commentsUrl: "/comments",
339
+ rootDepth: 0,
340
+ order: "older",
341
+ lastCommentId: 456,
342
+ pollingInterval: 1000
343
+ });
344
+ $("textarea[maxlength]", $container).each((_i, elem) => {
345
+ createCharacterCounter($(elem));
346
+ });
347
+
348
+ $doc = $(document);
349
+ addComment = $(".add-comment", subject.$element);
350
+ orderLinks = $(".order-by__dropdown .is-submenu-item a", subject.$element);
351
+
352
+ allToggles = $(".add-comment .opinion-toggle .button", subject.$element);
353
+ allTextareas = $(".add-comment textarea", subject.$element);
354
+ allForms = $(".add-comment form", subject.$element);
355
+ });
356
+
357
+ it("exists", () => {
358
+ expect(CommentsComponent).toBeDefined();
359
+ });
360
+
361
+ it("initializes unmounted", () => {
362
+ expect(subject.mounted).toBeFalsy();
363
+ });
364
+
365
+ it("initializes the comments element with the given selector", () => {
366
+ expect(subject.$element).toEqual($(selector));
367
+ });
368
+
369
+ it("starts polling for new comments", () => {
370
+ subject.mountComponent();
371
+
372
+ expect(window.setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
373
+
374
+ jest.advanceTimersByTime(1000);
375
+
376
+ expect(window.$.ajax).toHaveBeenCalledWith({
377
+ url: "/comments",
378
+ method: "GET",
379
+ contentType: "application/javascript",
380
+ data: {
381
+ "commentable_gid": "commentable-gid",
382
+ "root_depth": 0,
383
+ order: "older",
384
+ after: 456
385
+ }
386
+ });
387
+ });
388
+
389
+ describe("when mounted", () => {
390
+ beforeEach(() => {
391
+ spyOnAddComment("on");
392
+ spyOn(orderLinks, "on");
393
+ spyOn($doc, "trigger");
394
+
395
+ subject.mountComponent();
396
+ });
397
+
398
+ afterEach(() => {
399
+ subject.unmountComponent();
400
+ });
401
+
402
+ it("mounts the component", () => {
403
+ expect(subject.mounted).toBeTruthy();
404
+ });
405
+
406
+ it("binds the order selector events", () => {
407
+ expect(orderLinks.on).toHaveBeenCalledWith(
408
+ "click.decidim-comments",
409
+ expect.any(Function)
410
+ );
411
+ });
412
+
413
+ it("binds the add comment element events", () => {
414
+ addComment.each((i) => {
415
+ expect(addComment[i].opinionToggles.on).toHaveBeenCalledWith(
416
+ "click.decidim-comments",
417
+ subject._onToggleOpinion
418
+ );
419
+ expect(addComment[i].commentTextarea.on).toHaveBeenCalledWith(
420
+ "input.decidim-comments",
421
+ subject._onTextInput
422
+ );
423
+ expect(addComment[i].commentForm.on).toHaveBeenCalledWith(
424
+ "submit.decidim-comments",
425
+ expect.any(Function)
426
+ );
427
+ });
428
+ });
429
+
430
+ it("attaches the mentions elements to the text fields", () => {
431
+ addComment.each((i) => {
432
+ expect($doc.trigger).toHaveBeenCalledWith(
433
+ "attach-mentions-element",
434
+ [addComment[i].commentTextarea[0]]
435
+ );
436
+ });
437
+ });
438
+ });
439
+
440
+ describe("when interacting", () => {
441
+ beforeEach(() => {
442
+ spyOnAddComment();
443
+ subject.mountComponent();
444
+ });
445
+
446
+ afterEach(() => {
447
+ subject.unmountComponent();
448
+ });
449
+
450
+ describe("form and input events", () => {
451
+ let commentSection = null;
452
+ let commentForm = null;
453
+ let commentText = null;
454
+
455
+ beforeEach(() => {
456
+ commentSection = addComment[addComment.length - 1];
457
+ commentForm = commentSection.commentForm;
458
+ commentText = commentSection.commentTextarea;
459
+
460
+ // Avoid not implemented error
461
+ commentForm[0].submit = jest.fn();
462
+ });
463
+
464
+ it("enables the submit button when comment is entered", () => {
465
+ commentText.html("This is a test comment")
466
+ commentText.trigger("input");
467
+
468
+ expect(
469
+ $("button[type='submit']", commentSection.commentForm).is(":enabled")
470
+ ).toBeTruthy();
471
+ });
472
+
473
+ it("disables the submit button on submit and stops polling", () => {
474
+ spyOn(window, "clearTimeout");
475
+
476
+ commentText.html("This is a test comment")
477
+ commentText.trigger("input");
478
+ commentForm.trigger("submit");
479
+
480
+ expect(
481
+ $("button[type='submit']", commentSection.commentForm).is(":disabled")
482
+ ).toBeTruthy();
483
+
484
+ expect(window.clearTimeout).toHaveBeenCalledWith(subject.pollTimeout);
485
+ });
486
+ });
487
+
488
+ describe("opinion toggles", () => {
489
+ let commentSection = null;
490
+ let toggles = null;
491
+
492
+ beforeEach(() => {
493
+ commentSection = addComment[addComment.length - 1];
494
+ toggles = commentSection.opinionToggles;
495
+ });
496
+
497
+ it("adds the correct alignment on positive toggle", () => {
498
+ $(toggles[0]).trigger("click");
499
+
500
+ expect($(".alignment-input", commentSection).val()).toEqual("1");
501
+ });
502
+
503
+ it("adds the correct alignment on neutral toggle", () => {
504
+ $(toggles[0]).trigger("click");
505
+ $(toggles[1]).trigger("click");
506
+
507
+ expect($(".alignment-input", commentSection).val()).toEqual("0");
508
+ });
509
+
510
+ it("adds the correct alignment on negative toggle", () => {
511
+ $(toggles[2]).trigger("click");
512
+
513
+ expect($(".alignment-input", commentSection).val()).toEqual("-1");
514
+ });
515
+ });
516
+ });
517
+
518
+ describe("when adding comments", () => {
519
+ beforeEach(() => {
520
+ subject.mountComponent();
521
+ });
522
+
523
+ afterEach(() => {
524
+ subject.unmountComponent();
525
+ });
526
+
527
+ describe("addThread", () => {
528
+ it("adds a new comment thread", () => {
529
+ const newThread = generateCommentThread(999, "This is a dynamically added comment");
530
+ subject.addThread(newThread);
531
+
532
+ expect(subject.$element.html()).toEqual(expect.stringContaining(
533
+ "This is a dynamically added comment"
534
+ ));
535
+ });
536
+ });
537
+
538
+ describe("addReply", () => {
539
+ it("adds a new reply to an existing thread", () => {
540
+ const newThread = generateSingleComment(999, "This is a dynamically added reply");
541
+ subject.addReply(450, newThread);
542
+
543
+ expect(subject.$element.html()).toEqual(expect.stringContaining(
544
+ "This is a dynamically added reply"
545
+ ));
546
+ });
547
+ });
548
+ });
549
+
550
+ describe("when unmounted", () => {
551
+ beforeEach(() => {
552
+ spyOn(orderLinks, "off");
553
+ spyOn(allToggles, "off");
554
+ spyOn(allTextareas, "off");
555
+ spyOn(allForms, "off");
556
+
557
+ subject.mountComponent();
558
+ subject.unmountComponent();
559
+ });
560
+
561
+ it("mounts the component", () => {
562
+ expect(subject.mounted).toBeFalsy();
563
+ });
564
+
565
+ it("unbinds the order selector events", () => {
566
+ expect(orderLinks.off).toHaveBeenCalledWith(
567
+ "click.decidim-comments"
568
+ );
569
+ });
570
+
571
+ it("unbinds the add comment element events", () => {
572
+ expect(allToggles.off).toHaveBeenCalledWith("click.decidim-comments");
573
+ expect(allTextareas.off).toHaveBeenCalledWith("input.decidim-comments");
574
+ expect(allForms.off).toHaveBeenCalledWith("submit.decidim-comments");
575
+ });
576
+ });
577
+
578
+ afterEach(() => {
579
+ $(selector).remove();
580
+ })
581
+ });
@@ -1,5 +1,5 @@
1
1
  // = require_self
2
- // = require ./bundle
2
+ // = require decidim/comments/comments.component
3
3
 
4
4
  window.DecidimComments = window.DecidimComments || {};
5
5
 
@@ -0,0 +1,7 @@
1
+ <div class="comment__actions">
2
+ <% if can_reply? %>
3
+ <button class="comment__reply muted-link" aria-controls="<%= reply_id %>" data-toggle="<%= reply_id %>">
4
+ <%= icon "pencil", class: "icon--small", role: "none presentation" %>&nbsp;<%= t("decidim.components.comment.reply") %>
5
+ </button>
6
+ <% end %>
7
+ </div>
@@ -0,0 +1,4 @@
1
+ <span>
2
+ <span class="<%= alignment_badge_classes %>"><%= alignment_badge_label %></span>
3
+ &nbsp;
4
+ </span>
@@ -0,0 +1 @@
1
+ <%== cell("decidim/author", author_presenter).profile %>
@@ -0,0 +1,40 @@
1
+ <%= content_tag :div, id: "comment_#{model.id}", class: comment_classes, data: { comment_id: model.id } do %>
2
+ <div class="comment__header">
3
+ <div class="author-data">
4
+ <div class="author-data__main">
5
+ <%= render :author %>
6
+ <span>
7
+ <%= time_tag created_at, l(created_at, format: :decidim_short) %>
8
+ </span>
9
+ </div>
10
+ <div class="author-data__extra">
11
+ <%= render :utilities %>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ <div class="comment__content">
16
+ <%= alignment_badge %>
17
+ <%= comment_body %>
18
+ </div>
19
+ <div class="comment__footer">
20
+ <%= render :actions %>
21
+ <%= votes %>
22
+ </div>
23
+ <div id="comment-<%= model.id %>-replies">
24
+ <% if has_replies? %>
25
+ <% replies.each do |reply| %>
26
+ <%= cell("decidim/comments/comment", reply, root_depth: root_depth, order: order) %>
27
+ <% end %>
28
+ <% end %>
29
+ </div>
30
+ <% if can_reply? %>
31
+ <div class="comment__additionalreply<%= " hide" unless has_replies? %>">
32
+ <button class="comment__reply muted-link" aria-controls="<%= reply_id %>" data-toggle="<%= reply_id %>">
33
+ <%= icon "pencil", class: "icon--small", role: "none presentation" %>&nbsp;<%= t("decidim.components.comment.reply") %>
34
+ </button>
35
+ </div>
36
+ <div class="add-comment hide" id="<%= reply_id %>" data-toggler=".hide">
37
+ <%== cell("decidim/comments/comment_form", model, root_depth: root_depth, order: order) %>
38
+ </div>
39
+ <% end %>
40
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <button type="button" class="link-alt" data-open="<%= current_user.present? ? "flagModalComment#{model.id}" : "loginModal" %>" title="<%= t("decidim.components.comment.report.title") %>" aria-controls="<%= current_user.present? ? "flagModalComment#{model.id}" : "loginModal" %>" aria-haspopup="true" tabindex="0">
2
+ <%= icon "flag", aria_hidden: true, class: "icon--small", role: "none presentation", "aria-hidden": true %>
3
+ <span class="show-for-sr"><%= t("decidim.components.comment.report.title") %></span>
4
+ </button>
5
+
6
+ <%= link_to "#{commentable_path("commentId" => model.id)}#comment_#{model.id}", title: t("decidim.components.comment.single_comment_link_title") do %>
7
+ <span class="show-for-sr"><%= t("decidim.components.comment.single_comment_link_title") %></span>
8
+ <%= icon "link-intact", class: "icon--small", role: "none presentation" %>
9
+ <% end %>
10
+
11
+ <% if current_user.present? %>
12
+ <%= cell("decidim/flag_modal", model, modal_id: "flagModalComment#{model.id}") %>
13
+ <% end %>