message_train 0.7.6 → 1.0.0

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 (171) hide show
  1. checksums.yaml +5 -5
  2. data/.gitattributes +1 -0
  3. data/.github/workflows/ci.yml +86 -0
  4. data/.gitignore +64 -0
  5. data/.rubocop.yml +9 -0
  6. data/.rubocop_todo.yml +854 -2
  7. data/.ruby-version +1 -1
  8. data/.simplecov +2 -4
  9. data/CHANGELOG.md +128 -0
  10. data/CLAUDE.md +5 -0
  11. data/Gemfile +29 -31
  12. data/LICENSE.txt +1 -1
  13. data/README.md +106 -35
  14. data/Rakefile +4 -18
  15. data/app/assets/stylesheets/message_train.scss +405 -67
  16. data/app/controllers/concerns/message_train_support.rb +2 -2
  17. data/app/controllers/message_train/boxes_controller.rb +4 -2
  18. data/app/controllers/message_train/conversations_controller.rb +1 -0
  19. data/app/helpers/message_train/application_helper.rb +68 -0
  20. data/app/helpers/message_train/attachments_helper.rb +7 -4
  21. data/app/helpers/message_train/conversations_helper.rb +16 -16
  22. data/app/helpers/message_train/messages_helper.rb +23 -10
  23. data/app/javascript/controllers/message_train/bulk_actions_controller.js +36 -0
  24. data/app/javascript/controllers/message_train/lightbox_controller.js +25 -0
  25. data/app/javascript/controllers/message_train/nested_form_controller.js +36 -0
  26. data/app/javascript/controllers/message_train/recipient_autocomplete_controller.js +31 -0
  27. data/app/mailers/message_train/previews/receipt_mailer_preview.rb +7 -5
  28. data/app/models/message_train/attachment.rb +54 -42
  29. data/app/models/message_train/box.rb +2 -2
  30. data/app/models/message_train/conversation.rb +28 -2
  31. data/app/models/message_train/ignore.rb +2 -2
  32. data/app/models/message_train/message.rb +10 -4
  33. data/app/models/message_train/receipt.rb +27 -4
  34. data/app/models/message_train/unsubscribe.rb +1 -1
  35. data/app/views/application/404.html.haml +1 -3
  36. data/app/views/kaminari/_first_page.html.haml +1 -1
  37. data/app/views/kaminari/_last_page.html.haml +1 -1
  38. data/app/views/kaminari/_next_page.html.haml +1 -1
  39. data/app/views/kaminari/_page.html.haml +3 -3
  40. data/app/views/kaminari/_paginator.html.haml +12 -11
  41. data/app/views/kaminari/_prev_page.html.haml +1 -1
  42. data/app/views/message_train/application/_attachment_fields.html.haml +10 -7
  43. data/app/views/message_train/application/_attachment_link.html.haml +2 -2
  44. data/app/views/message_train/application/_widget.html.haml +2 -4
  45. data/app/views/message_train/boxes/_widget.html.haml +5 -5
  46. data/app/views/message_train/boxes/show.html.haml +28 -45
  47. data/app/views/message_train/collectives/_widget.html.haml +4 -4
  48. data/app/views/message_train/conversations/_conversation.html.haml +11 -11
  49. data/app/views/message_train/conversations/_deleted_toggle.html.haml +1 -1
  50. data/app/views/message_train/conversations/_ignored_toggle.html.haml +2 -2
  51. data/app/views/message_train/conversations/_read_toggle.html.haml +2 -2
  52. data/app/views/message_train/conversations/_toggle.html.haml +6 -4
  53. data/app/views/message_train/conversations/_trashed_toggle.html.haml +2 -2
  54. data/app/views/message_train/conversations/show.html.haml +8 -6
  55. data/app/views/message_train/messages/_deleted_toggle.html.haml +1 -1
  56. data/app/views/message_train/messages/_form.html.haml +22 -30
  57. data/app/views/message_train/messages/_message.html.haml +27 -45
  58. data/app/views/message_train/messages/_read_toggle.html.haml +2 -2
  59. data/app/views/message_train/messages/_toggle.html.haml +5 -1
  60. data/app/views/message_train/messages/_trashed_toggle.html.haml +2 -2
  61. data/app/views/message_train/unsubscribes/index.html.haml +12 -15
  62. data/badges/coverage.json +1 -0
  63. data/config/importmap.rb +2 -0
  64. data/config/locales/en.yml +6 -3
  65. data/db/migrate/20150721145319_create_message_train_conversations.rb +1 -1
  66. data/db/migrate/20150721160322_create_message_train_messages.rb +1 -1
  67. data/db/migrate/20150721161144_create_message_train_attachments.rb +5 -2
  68. data/db/migrate/20150721161940_create_message_train_receipts.rb +1 -1
  69. data/db/migrate/20150721163838_create_message_train_ignores.rb +1 -1
  70. data/db/migrate/20150901183458_add_received_through_to_message_train_receipts.rb +1 -1
  71. data/db/migrate/20151004184347_add_unique_index_to_receipts.rb +1 -1
  72. data/db/migrate/20151124000820_create_message_train_unsubscribes.rb +1 -1
  73. data/db/migrate/20260827095938_remove_paperclip_columns_from_message_train_attachments.rb +8 -0
  74. data/db/migrate/20260827171549_remove_body_from_message_train_messages.rb +5 -0
  75. data/db/migrate/20260827180000_add_marking_indexes.rb +9 -0
  76. data/db/migrate/20260827200000_add_body_back_to_message_train_messages.rb +5 -0
  77. data/docs/screenshots/inbox-desktop.png +0 -0
  78. data/docs/screenshots/inbox-mobile.png +0 -0
  79. data/lib/generators/message_train/install/install_generator.rb +1 -1
  80. data/lib/message_train/engine.rb +11 -0
  81. data/lib/message_train/instance_methods.rb +13 -7
  82. data/lib/message_train/localization.rb +4 -3
  83. data/lib/message_train/version.rb +1 -1
  84. data/lib/message_train.rb +0 -2
  85. data/message_train.gemspec +57 -389
  86. data/spec/controllers/message_train/boxes_controller_spec.rb +13 -8
  87. data/spec/controllers/message_train/concerns_spec.rb +2 -2
  88. data/spec/controllers/message_train/conversations_controller_spec.rb +36 -7
  89. data/spec/controllers/message_train/messages_controller_spec.rb +4 -4
  90. data/spec/dummy/app/assets/stylesheets/application.scss +141 -12
  91. data/spec/dummy/app/controllers/application_controller.rb +1 -1
  92. data/spec/dummy/app/javascript/application.js +4 -0
  93. data/spec/dummy/app/javascript/controllers/application.js +9 -0
  94. data/spec/dummy/app/javascript/controllers/index.js +4 -0
  95. data/spec/dummy/app/models/role.rb +1 -1
  96. data/spec/dummy/app/views/active_storage/blobs/_blob.html.erb +14 -0
  97. data/spec/dummy/app/views/layouts/_top_navigation.html.haml +18 -7
  98. data/spec/dummy/app/views/layouts/application.html.haml +22 -26
  99. data/spec/dummy/bin/importmap +4 -0
  100. data/spec/dummy/config/application.rb +7 -20
  101. data/spec/dummy/config/environments/development.rb +17 -21
  102. data/spec/dummy/config/environments/production.rb +11 -63
  103. data/spec/dummy/config/environments/test.rb +21 -16
  104. data/spec/dummy/config/importmap.rb +7 -0
  105. data/spec/dummy/config/initializers/assets.rb +1 -6
  106. data/spec/dummy/config/routes.rb +1 -1
  107. data/spec/dummy/config/storage.yml +7 -0
  108. data/spec/dummy/db/migrate/20150721140013_devise_create_users.rb +1 -1
  109. data/spec/dummy/db/migrate/20150721141009_rolify_create_roles.rb +1 -1
  110. data/spec/dummy/db/migrate/20150721141128_create_groups.rb +1 -1
  111. data/spec/dummy/db/migrate/20150721150307_add_display_name_column_to_users.rb +1 -1
  112. data/spec/dummy/db/migrate/20160207190402_create_message_train_conversations.message_train.rb +1 -1
  113. data/spec/dummy/db/migrate/20160207190403_create_message_train_messages.message_train.rb +1 -1
  114. data/spec/dummy/db/migrate/20160207190404_create_message_train_attachments.message_train.rb +5 -2
  115. data/spec/dummy/db/migrate/20160207190405_create_message_train_receipts.message_train.rb +1 -1
  116. data/spec/dummy/db/migrate/20160207190406_create_message_train_ignores.message_train.rb +1 -1
  117. data/spec/dummy/db/migrate/20160207190407_add_received_through_to_message_train_receipts.message_train.rb +1 -1
  118. data/spec/dummy/db/migrate/20160207190408_add_unique_index_to_receipts.message_train.rb +1 -1
  119. data/spec/dummy/db/migrate/20160207190409_create_message_train_unsubscribes.message_train.rb +1 -1
  120. data/spec/dummy/db/migrate/20260827095939_remove_paperclip_columns_from_message_train_attachments.message_train.rb +9 -0
  121. data/spec/dummy/db/migrate/20260827161141_create_active_storage_tables.active_storage.rb +57 -0
  122. data/spec/dummy/db/migrate/20260827171550_remove_body_from_message_train_messages.message_train.rb +6 -0
  123. data/spec/dummy/db/migrate/20260827225116_add_marking_indexes.message_train.rb +10 -0
  124. data/spec/dummy/db/migrate/20260828034830_add_body_back_to_message_train_messages.message_train.rb +6 -0
  125. data/spec/dummy/db/migrate/20260828040000_drop_action_text_tables.rb +5 -0
  126. data/spec/dummy/db/schema.rb +106 -72
  127. data/spec/dummy/db/seeds/conversations.seeds.rb +22 -22
  128. data/spec/dummy/db/seeds/development/conversations.seeds.rb +2 -2
  129. data/spec/dummy/db/seeds/groups.seeds.rb +4 -4
  130. data/spec/dummy/db/seeds/test/attachments.seeds.rb +4 -4
  131. data/spec/dummy/db/seeds/test/conversations.seeds.rb +2 -2
  132. data/spec/dummy/db/seeds/users.seeds.rb +8 -8
  133. data/spec/dummy/vendor/javascript/.keep +0 -0
  134. data/spec/factories/attachment.rb +5 -6
  135. data/spec/factories/group.rb +2 -2
  136. data/spec/factories/message.rb +7 -7
  137. data/spec/factories/user.rb +3 -3
  138. data/spec/features/boxes_spec.rb +38 -34
  139. data/spec/features/conversations_spec.rb +13 -6
  140. data/spec/features/messages_spec.rb +20 -20
  141. data/spec/features/unsubscribes_spec.rb +9 -9
  142. data/spec/helpers/message_train/application_helper_spec.rb +16 -18
  143. data/spec/helpers/message_train/attachment_helper_spec.rb +8 -18
  144. data/spec/helpers/message_train/boxes_helper_spec.rb +6 -6
  145. data/spec/helpers/message_train/collectives_helper_spec.rb +10 -10
  146. data/spec/helpers/message_train/conversations_helper_spec.rb +43 -85
  147. data/spec/helpers/message_train/messages_helper_spec.rb +41 -61
  148. data/spec/models/message_train/attachment_spec.rb +26 -36
  149. data/spec/models/message_train/box_spec.rb +2 -2
  150. data/spec/models/message_train/conversation_spec.rb +15 -0
  151. data/spec/models/message_train/message_spec.rb +5 -1
  152. data/spec/models/message_train/receipt_spec.rb +1 -1
  153. data/spec/models/message_train/unsubscribe_spec.rb +1 -1
  154. data/spec/rails_helper.rb +14 -13
  155. data/spec/support/controller_behaviors.rb +13 -13
  156. data/spec/support/factory_bot.rb +3 -0
  157. data/spec/support/feature_behaviors.rb +14 -14
  158. data/spec/support/loaded_site/attachments.rb +6 -2
  159. data/spec/support/utilities.rb +49 -20
  160. metadata +260 -213
  161. data/.travis.yml +0 -12
  162. data/VERSION +0 -1
  163. data/app/assets/javascripts/ckeditor/config.js +0 -49
  164. data/app/assets/javascripts/message_train.js +0 -149
  165. data/spec/dummy/app/assets/javascripts/application.js +0 -17
  166. data/spec/dummy/app/assets/stylesheets/bootstrap-everything.scss +0 -54
  167. data/spec/dummy/config/initializers/bootstrap_leather.rb +0 -7
  168. data/spec/dummy/config/initializers/paperclip.rb +0 -5
  169. data/spec/dummy/config/secrets.yml +0 -22
  170. data/spec/dummy/db/test.sqlite3 +0 -0
  171. data/spec/support/factory_girl.rb +0 -3
@@ -6,7 +6,7 @@ describe MessageTrain::ConversationsHelper do
6
6
 
7
7
  before do
8
8
  view.extend MessageTrain::BoxesHelper
9
- view.extend BootstrapLeather::ApplicationHelper
9
+ view.extend MessageTrain::ApplicationHelper
10
10
  login_user first_user
11
11
  assign(:box_user, first_user)
12
12
  end
@@ -120,17 +120,14 @@ describe MessageTrain::ConversationsHelper do
120
120
  subject { helper.conversation_trashed_toggle(unread_conversation) }
121
121
  it do
122
122
  should have_tag(
123
- :a,
123
+ :button,
124
124
  with: {
125
- 'rel' => 'nofollow',
126
125
  'title' => 'Mark as Trashed',
127
- 'data-confirm' => 'Are you sure?',
128
- 'data-method' => 'put',
129
- 'data-remote' => 'true'
126
+ 'data-turbo-confirm' => 'Are you sure?'
130
127
  }
131
128
  )
132
129
  end
133
- it { should have_tag(:span, with: { class: 'glyphicon-trash' }) }
130
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
134
131
  end
135
132
  context 'when the conversation is all trashed' do
136
133
  before do
@@ -139,16 +136,13 @@ describe MessageTrain::ConversationsHelper do
139
136
  subject { helper.conversation_trashed_toggle(trashed_conversation) }
140
137
  it do
141
138
  should have_tag(
142
- :a,
139
+ :button,
143
140
  with: {
144
- 'rel' => 'nofollow',
145
- 'title' => 'Mark as Untrashed',
146
- 'data-method' => 'put',
147
- 'data-remote' => 'true'
141
+ 'title' => 'Mark as Untrashed'
148
142
  }
149
143
  )
150
144
  end
151
- it { should have_tag(:span, with: { class: 'glyphicon-inbox' }) }
145
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
152
146
  end
153
147
  end
154
148
  describe 'in a collective box' do
@@ -161,17 +155,14 @@ describe MessageTrain::ConversationsHelper do
161
155
  end
162
156
  it do
163
157
  should have_tag(
164
- :a,
158
+ :button,
165
159
  with: {
166
- 'rel' => 'nofollow',
167
160
  'title' => 'Mark as Trashed',
168
- 'data-confirm' => 'Are you sure?',
169
- 'data-method' => 'put',
170
- 'data-remote' => 'true'
161
+ 'data-turbo-confirm' => 'Are you sure?'
171
162
  }
172
163
  )
173
164
  end
174
- it { should have_tag(:span, with: { class: 'glyphicon-trash' }) }
165
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
175
166
  end
176
167
  context 'when the conversation is all trashed' do
177
168
  before do
@@ -184,16 +175,13 @@ describe MessageTrain::ConversationsHelper do
184
175
  end
185
176
  it do
186
177
  should have_tag(
187
- :a,
178
+ :button,
188
179
  with: {
189
- 'rel' => 'nofollow',
190
- 'title' => 'Mark as Untrashed',
191
- 'data-method' => 'put',
192
- 'data-remote' => 'true'
180
+ 'title' => 'Mark as Untrashed'
193
181
  }
194
182
  )
195
183
  end
196
- it { should have_tag(:span, with: { class: 'glyphicon-inbox' }) }
184
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
197
185
  end
198
186
  end
199
187
  end
@@ -207,16 +195,13 @@ describe MessageTrain::ConversationsHelper do
207
195
  subject { helper.conversation_read_toggle(unread_conversation) }
208
196
  it do
209
197
  should have_tag(
210
- :a,
198
+ :button,
211
199
  with: {
212
- 'rel' => 'nofollow',
213
- 'title' => 'Mark as Read',
214
- 'data-method' => 'put',
215
- 'data-remote' => 'true'
200
+ 'title' => 'Mark as Read'
216
201
  }
217
202
  )
218
203
  end
219
- it { should have_tag(:span, with: { class: 'glyphicon-eye-open' }) }
204
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
220
205
  end
221
206
  context 'when the conversation is all read' do
222
207
  before do
@@ -225,16 +210,13 @@ describe MessageTrain::ConversationsHelper do
225
210
  subject { helper.conversation_read_toggle(read_conversation) }
226
211
  it do
227
212
  should have_tag(
228
- :a,
213
+ :button,
229
214
  with: {
230
- 'rel' => 'nofollow',
231
- 'title' => 'Mark as Unread',
232
- 'data-method' => 'put',
233
- 'data-remote' => 'true'
215
+ 'title' => 'Mark as Unread'
234
216
  }
235
217
  )
236
218
  end
237
- it { should have_tag(:span, with: { class: 'glyphicon-eye-close' }) }
219
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
238
220
  end
239
221
  end
240
222
  describe 'in a collective box' do
@@ -245,16 +227,13 @@ describe MessageTrain::ConversationsHelper do
245
227
  subject { helper.conversation_read_toggle(membered_group_conversation) }
246
228
  it do
247
229
  should have_tag(
248
- :a,
230
+ :button,
249
231
  with: {
250
- 'rel' => 'nofollow',
251
- 'title' => 'Mark as Read',
252
- 'data-method' => 'put',
253
- 'data-remote' => 'true'
232
+ 'title' => 'Mark as Read'
254
233
  }
255
234
  )
256
235
  end
257
- it { should have_tag(:span, with: { class: 'glyphicon-eye-open' }) }
236
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
258
237
  end
259
238
  context 'when the conversation is all read' do
260
239
  before do
@@ -265,16 +244,13 @@ describe MessageTrain::ConversationsHelper do
265
244
  end
266
245
  it do
267
246
  should have_tag(
268
- :a,
247
+ :button,
269
248
  with: {
270
- 'rel' => 'nofollow',
271
- 'title' => 'Mark as Unread',
272
- 'data-method' => 'put',
273
- 'data-remote' => 'true'
249
+ 'title' => 'Mark as Unread'
274
250
  }
275
251
  )
276
252
  end
277
- it { should have_tag(:span, with: { class: 'glyphicon-eye-close' }) }
253
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
278
254
  end
279
255
  end
280
256
  end
@@ -288,17 +264,14 @@ describe MessageTrain::ConversationsHelper do
288
264
  subject { helper.conversation_ignored_toggle(unread_conversation) }
289
265
  it do
290
266
  should have_tag(
291
- :a,
267
+ :button,
292
268
  with: {
293
- 'rel' => 'nofollow',
294
269
  'title' => 'Mark as Ignored',
295
- 'data-confirm' => 'Are you sure?',
296
- 'data-method' => 'delete',
297
- 'data-remote' => 'true'
270
+ 'data-turbo-confirm' => 'Are you sure?'
298
271
  }
299
272
  )
300
273
  end
301
- it { should have_tag(:span, with: { class: 'glyphicon-volume-off' }) }
274
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
302
275
  end
303
276
  context 'when the conversation is all ignored' do
304
277
  before do
@@ -307,16 +280,13 @@ describe MessageTrain::ConversationsHelper do
307
280
  subject { helper.conversation_ignored_toggle(ignored_conversation) }
308
281
  it do
309
282
  should have_tag(
310
- :a,
283
+ :button,
311
284
  with: {
312
- 'rel' => 'nofollow',
313
- 'title' => 'Mark as Unignored',
314
- 'data-method' => 'delete',
315
- 'data-remote' => 'true'
285
+ 'title' => 'Mark as Unignored'
316
286
  }
317
287
  )
318
288
  end
319
- it { should have_tag(:span, with: { class: 'glyphicon-volume-up' }) }
289
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
320
290
  end
321
291
  end
322
292
  describe 'in a collective box' do
@@ -329,17 +299,14 @@ describe MessageTrain::ConversationsHelper do
329
299
  end
330
300
  it do
331
301
  should have_tag(
332
- :a,
302
+ :button,
333
303
  with: {
334
- 'rel' => 'nofollow',
335
304
  'title' => 'Mark as Ignored',
336
- 'data-confirm' => 'Are you sure?',
337
- 'data-method' => 'delete',
338
- 'data-remote' => 'true'
305
+ 'data-turbo-confirm' => 'Are you sure?'
339
306
  }
340
307
  )
341
308
  end
342
- it { should have_tag(:span, with: { class: 'glyphicon-volume-off' }) }
309
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
343
310
  end
344
311
  context 'when the conversation is all ignored' do
345
312
  before do
@@ -352,16 +319,13 @@ describe MessageTrain::ConversationsHelper do
352
319
  end
353
320
  it do
354
321
  should have_tag(
355
- :a,
322
+ :button,
356
323
  with: {
357
- 'rel' => 'nofollow',
358
- 'title' => 'Mark as Unignored',
359
- 'data-method' => 'delete',
360
- 'data-remote' => 'true'
324
+ 'title' => 'Mark as Unignored'
361
325
  }
362
326
  )
363
327
  end
364
- it { should have_tag(:span, with: { class: 'glyphicon-volume-up' }) }
328
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
365
329
  end
366
330
  end
367
331
  end
@@ -375,17 +339,14 @@ describe MessageTrain::ConversationsHelper do
375
339
  subject { helper.conversation_deleted_toggle(unread_conversation) }
376
340
  it do
377
341
  should have_tag(
378
- :a,
342
+ :button,
379
343
  with: {
380
- 'rel' => 'nofollow',
381
344
  'title' => 'Mark as Deleted',
382
- 'data-confirm' => 'Delete forever? This cannot be undone.',
383
- 'data-method' => 'put',
384
- 'data-remote' => 'true'
345
+ 'data-turbo-confirm' => 'Delete forever? This cannot be undone.'
385
346
  }
386
347
  )
387
348
  end
388
- it { should have_tag(:span, with: { class: 'glyphicon-remove' }) }
349
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
389
350
  end
390
351
  end
391
352
  describe 'in a collective box' do
@@ -398,17 +359,14 @@ describe MessageTrain::ConversationsHelper do
398
359
  end
399
360
  it do
400
361
  should have_tag(
401
- :a,
362
+ :button,
402
363
  with: {
403
- 'rel' => 'nofollow',
404
364
  'title' => 'Mark as Deleted',
405
- 'data-confirm' => 'Delete forever? This cannot be undone.',
406
- 'data-method' => 'put',
407
- 'data-remote' => 'true'
365
+ 'data-turbo-confirm' => 'Delete forever? This cannot be undone.'
408
366
  }
409
367
  )
410
368
  end
411
- it { should have_tag(:span, with: { class: 'glyphicon-remove' }) }
369
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
412
370
  end
413
371
  end
414
372
  end
@@ -6,7 +6,7 @@ describe MessageTrain::MessagesHelper do
6
6
 
7
7
  before do
8
8
  view.extend MessageTrain::BoxesHelper
9
- view.extend BootstrapLeather::ApplicationHelper
9
+ view.extend MessageTrain::ApplicationHelper
10
10
  login_user first_user
11
11
  assign(:box_user, first_user)
12
12
  end
@@ -64,37 +64,33 @@ describe MessageTrain::MessagesHelper do
64
64
  assign(:box, first_user.box(:in))
65
65
  end
66
66
  subject { helper.message_trashed_toggle(unread_message) }
67
+ before { assign(:conversation, unread_message.conversation) }
67
68
  it do
68
69
  should have_tag(
69
- :a,
70
+ :button,
70
71
  with: {
71
- 'rel' => 'nofollow',
72
72
  'title' => 'Mark as Trashed',
73
- 'data-confirm' => 'Are you sure?',
74
- 'data-method' => 'put',
75
- 'data-remote' => 'true'
73
+ 'data-turbo-confirm' => 'Are you sure?'
76
74
  }
77
75
  )
78
76
  end
79
- it { should have_tag(:span, with: { class: 'glyphicon-trash' }) }
77
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
80
78
  end
81
79
  context 'when the message is trashed' do
82
80
  before do
83
81
  assign(:box, first_user.box(:trash))
84
82
  end
85
83
  subject { helper.message_trashed_toggle(trashed_message) }
84
+ before { assign(:conversation, trashed_message.conversation) }
86
85
  it do
87
86
  should have_tag(
88
- :a,
87
+ :button,
89
88
  with: {
90
- 'rel' => 'nofollow',
91
- 'title' => 'Mark as Untrashed',
92
- 'data-method' => 'put',
93
- 'data-remote' => 'true'
89
+ 'title' => 'Mark as Untrashed'
94
90
  }
95
91
  )
96
92
  end
97
- it { should have_tag(:span, with: { class: 'glyphicon-inbox' }) }
93
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
98
94
  end
99
95
  end
100
96
  describe 'in a collective box' do
@@ -103,19 +99,17 @@ describe MessageTrain::MessagesHelper do
103
99
  assign(:box, membered_group.box(:in, first_user))
104
100
  end
105
101
  subject { helper.message_trashed_toggle(membered_group_message) }
102
+ before { assign(:conversation, membered_group_message.conversation) }
106
103
  it do
107
104
  should have_tag(
108
- :a,
105
+ :button,
109
106
  with: {
110
- 'rel' => 'nofollow',
111
107
  'title' => 'Mark as Trashed',
112
- 'data-confirm' => 'Are you sure?',
113
- 'data-method' => 'put',
114
- 'data-remote' => 'true'
108
+ 'data-turbo-confirm' => 'Are you sure?'
115
109
  }
116
110
  )
117
111
  end
118
- it { should have_tag(:span, with: { class: 'glyphicon-trash' }) }
112
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
119
113
  end
120
114
  context 'when the message is trashed' do
121
115
  before do
@@ -124,18 +118,16 @@ describe MessageTrain::MessagesHelper do
124
118
  subject do
125
119
  helper.message_trashed_toggle(membered_group_trashed_message)
126
120
  end
121
+ before { assign(:conversation, membered_group_trashed_message.conversation) }
127
122
  it do
128
123
  should have_tag(
129
- :a,
124
+ :button,
130
125
  with: {
131
- 'rel' => 'nofollow',
132
- 'title' => 'Mark as Untrashed',
133
- 'data-method' => 'put',
134
- 'data-remote' => 'true'
126
+ 'title' => 'Mark as Untrashed'
135
127
  }
136
128
  )
137
129
  end
138
- it { should have_tag(:span, with: { class: 'glyphicon-inbox' }) }
130
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
139
131
  end
140
132
  end
141
133
  end
@@ -147,36 +139,32 @@ describe MessageTrain::MessagesHelper do
147
139
  assign(:box, first_user.box(:in))
148
140
  end
149
141
  subject { helper.message_read_toggle(unread_message) }
142
+ before { assign(:conversation, unread_message.conversation) }
150
143
  it do
151
144
  should have_tag(
152
- :a,
145
+ :button,
153
146
  with: {
154
- 'rel' => 'nofollow',
155
- 'title' => 'Mark as Read',
156
- 'data-method' => 'put',
157
- 'data-remote' => 'true'
147
+ 'title' => 'Mark as Read'
158
148
  }
159
149
  )
160
150
  end
161
- it { should have_tag(:span, with: { class: 'glyphicon-eye-open' }) }
151
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
162
152
  end
163
153
  context 'when the message is read' do
164
154
  before do
165
155
  assign(:box, first_user.box(:trash))
166
156
  end
167
157
  subject { helper.message_read_toggle(read_message) }
158
+ before { assign(:conversation, read_message.conversation) }
168
159
  it do
169
160
  should have_tag(
170
- :a,
161
+ :button,
171
162
  with: {
172
- 'rel' => 'nofollow',
173
- 'title' => 'Mark as Unread',
174
- 'data-method' => 'put',
175
- 'data-remote' => 'true'
163
+ 'title' => 'Mark as Unread'
176
164
  }
177
165
  )
178
166
  end
179
- it { should have_tag(:span, with: { class: 'glyphicon-eye-close' }) }
167
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
180
168
  end
181
169
  end
182
170
  describe 'in a collective box' do
@@ -185,36 +173,32 @@ describe MessageTrain::MessagesHelper do
185
173
  assign(:box, membered_group.box(:in, first_user))
186
174
  end
187
175
  subject { helper.message_read_toggle(membered_group_message) }
176
+ before { assign(:conversation, membered_group_message.conversation) }
188
177
  it do
189
178
  should have_tag(
190
- :a,
179
+ :button,
191
180
  with: {
192
- 'rel' => 'nofollow',
193
- 'title' => 'Mark as Read',
194
- 'data-method' => 'put',
195
- 'data-remote' => 'true'
181
+ 'title' => 'Mark as Read'
196
182
  }
197
183
  )
198
184
  end
199
- it { should have_tag(:span, with: { class: 'glyphicon-eye-open' }) }
185
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
200
186
  end
201
187
  context 'when the message is read' do
202
188
  before do
203
189
  assign(:box, membered_group.box(:trash, first_user))
204
190
  end
205
191
  subject { helper.message_read_toggle(membered_group_read_message) }
192
+ before { assign(:conversation, membered_group_read_message.conversation) }
206
193
  it do
207
194
  should have_tag(
208
- :a,
195
+ :button,
209
196
  with: {
210
- 'rel' => 'nofollow',
211
- 'title' => 'Mark as Unread',
212
- 'data-method' => 'put',
213
- 'data-remote' => 'true'
197
+ 'title' => 'Mark as Unread'
214
198
  }
215
199
  )
216
200
  end
217
- it { should have_tag(:span, with: { class: 'glyphicon-eye-close' }) }
201
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
218
202
  end
219
203
  end
220
204
  end
@@ -226,19 +210,17 @@ describe MessageTrain::MessagesHelper do
226
210
  assign(:box, first_user.box(:trash))
227
211
  end
228
212
  subject { helper.message_deleted_toggle(unread_message) }
213
+ before { assign(:conversation, unread_message.conversation) }
229
214
  it do
230
215
  should have_tag(
231
- :a,
216
+ :button,
232
217
  with: {
233
- 'rel' => 'nofollow',
234
218
  'title' => 'Mark as Deleted',
235
- 'data-confirm' => 'Delete forever? This cannot be undone.',
236
- 'data-method' => 'put',
237
- 'data-remote' => 'true'
219
+ 'data-turbo-confirm' => 'Delete forever? This cannot be undone.'
238
220
  }
239
221
  )
240
222
  end
241
- it { should have_tag(:span, with: { class: 'glyphicon-remove' }) }
223
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
242
224
  end
243
225
  end
244
226
  describe 'in a collective box' do
@@ -247,19 +229,17 @@ describe MessageTrain::MessagesHelper do
247
229
  assign(:box, membered_group.box(:trash, first_user))
248
230
  end
249
231
  subject { helper.message_deleted_toggle(membered_group_message) }
232
+ before { assign(:conversation, membered_group_message.conversation) }
250
233
  it do
251
234
  should have_tag(
252
- :a,
235
+ :button,
253
236
  with: {
254
- 'rel' => 'nofollow',
255
237
  'title' => 'Mark as Deleted',
256
- 'data-confirm' => 'Delete forever? This cannot be undone.',
257
- 'data-method' => 'put',
258
- 'data-remote' => 'true'
238
+ 'data-turbo-confirm' => 'Delete forever? This cannot be undone.'
259
239
  }
260
240
  )
261
241
  end
262
- it { should have_tag(:span, with: { class: 'glyphicon-remove' }) }
242
+ it { should have_tag(:span, with: { class: 'message-train-icon' }) }
263
243
  end
264
244
  end
265
245
  end
@@ -5,52 +5,42 @@ module MessageTrain
5
5
  include_context 'loaded site'
6
6
 
7
7
  describe 'Model' do
8
- it { should belong_to :message }
9
- it { should have_attached_file :attachment }
10
- it { should validate_attachment_presence :attachment }
11
- it do
12
- should validate_attachment_content_type(:attachment).allowing(
13
- 'application/pdf',
14
- 'application/vnd.ms-excel',
15
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
16
- 'application/msword',
17
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.'\
18
- 'document',
19
- 'application/rtf',
20
- 'text/plain',
21
- 'image/bmp',
22
- 'image/gif',
23
- 'image/jpeg',
24
- 'image/pjpeg',
25
- 'image/png',
26
- 'image/x-png',
27
- 'application/bmp',
28
- 'application/gif',
29
- 'application/jpeg',
30
- 'application/pjpeg',
31
- 'application/png',
32
- 'application/x-png',
33
- 'x-application/bmp',
34
- 'x-application/gif',
35
- 'x-application/jpeg',
36
- 'x-application/pjpeg',
37
- 'x-application/png',
38
- 'x-application/x-png'
39
- ).rejecting(
40
- 'image/tiff',
41
- 'image/svg'
8
+ it { is_expected.to belong_to :message }
9
+
10
+ it 'has an attached file' do
11
+ expect(image_attachment.attachment).to be_attached
12
+ end
13
+
14
+ it 'is invalid without an attached file' do
15
+ attachment = build(:attachment, message: build(:message), attachment: nil)
16
+ expect(attachment).not_to be_valid
17
+ expect(attachment.errors[:attachment]).to be_present
18
+ end
19
+
20
+ it 'is valid for an allowed content type' do
21
+ expect(image_attachment).to be_valid
22
+ end
23
+
24
+ it 'is invalid for a disallowed content type' do
25
+ attachment = build(:attachment, message: build(:message))
26
+ attachment.attachment.attach(
27
+ io: StringIO.new('bogus'),
28
+ filename: 'bogus.svg',
29
+ content_type: 'image/svg'
42
30
  )
31
+ expect(attachment).not_to be_valid
32
+ expect(attachment.errors[:attachment]).to be_present
43
33
  end
44
34
  end
45
35
  describe 'Scopes and Methods' do
46
36
  describe '#image?' do
47
37
  context 'when it is an image' do
48
38
  subject { image_attachment.image? }
49
- it { should eq true }
39
+ it { is_expected.to eq true }
50
40
  end
51
41
  context 'when it is not an image' do
52
42
  subject { pdf_attachment.image? }
53
- it { should eq false }
43
+ it { is_expected.to eq false }
54
44
  end
55
45
  end
56
46
  end
@@ -290,7 +290,7 @@ module MessageTrain
290
290
  should eq [
291
291
  {
292
292
  css_id: 'box',
293
- path: '/collectives/groups:first-group/box/in',
293
+ path: '/messages/collectives/groups:first-group/box/in',
294
294
  message: 'Cannot mark with Time'
295
295
  }
296
296
  ]
@@ -512,7 +512,7 @@ module MessageTrain
512
512
  {
513
513
  css_id: 'message_train_message_'\
514
514
  "#{membered_group_message.id}",
515
- path: '/collectives/groups:membered-group/box/in/'\
515
+ path: '/messages/collectives/groups:membered-group/box/in/'\
516
516
  "messages/#{membered_group_message.id}",
517
517
  message: 'Access to Message '\
518
518
  "#{membered_group_message.id} denied"
@@ -53,6 +53,21 @@ module MessageTrain
53
53
  it { should_not include third_user }
54
54
  end
55
55
 
56
+ describe '#new_reply' do
57
+ subject(:reply) { sent_conversation.new_reply(box: first_user.box(:in)) }
58
+
59
+ it 'prefixes the subject with Re:' do
60
+ expect(reply.subject).to eq("Re: #{sent_conversation.subject}")
61
+ end
62
+
63
+ it 'quotes the previous message as a markdown blockquote' do
64
+ quoted = sent_conversation.messages.last.body.each_line.map do |line|
65
+ "> #{line.chomp}"
66
+ end.join("\n")
67
+ expect(reply.body).to eq("#{quoted}\n\n")
68
+ end
69
+ end
70
+
56
71
  describe '#participant_ignore and #participant_ignored?' do
57
72
  subject { ignored_conversation.participant_ignored?(first_user) }
58
73
  it { should be true }
@@ -6,7 +6,11 @@ module MessageTrain
6
6
  let(:user_in_box) { first_user.box(:in) }
7
7
  describe 'Model' do
8
8
  # Relationships
9
- it { should belong_to :conversation }
9
+ # `conversation` has no association-level `required`/presence
10
+ # validation because `create_conversation_if_blank` (a
11
+ # before_validation callback) always fills it in before validation
12
+ # runs, so it's never actually blank at that point.
13
+ it { should belong_to(:conversation).optional }
10
14
  it { should belong_to :sender }
11
15
  it { should have_many :attachments }
12
16
  it { should have_many :receipts }