decidim-core 0.19.1 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of decidim-core might be problematic. Click here for more details.

Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -0
  3. data/app/assets/javascripts/decidim.js.es6 +2 -1
  4. data/app/assets/javascripts/decidim/input_hashtags.js.es6 +1 -1
  5. data/app/assets/javascripts/decidim/input_mentions.js.es6 +112 -50
  6. data/app/assets/stylesheets/decidim/modules/_signup.scss +57 -0
  7. data/app/cells/decidim/coauthorships_cell.rb +2 -6
  8. data/app/cells/decidim/diff_cell.rb +3 -7
  9. data/app/controllers/decidim/application_controller.rb +0 -8
  10. data/app/controllers/decidim/devise/unlocks_controller.rb +25 -0
  11. data/app/controllers/decidim/searches_controller.rb +0 -1
  12. data/app/jobs/decidim/export_participatory_space_job.rb +20 -0
  13. data/app/models/decidim/category.rb +2 -0
  14. data/app/models/decidim/component.rb +2 -0
  15. data/app/models/decidim/user.rb +9 -2
  16. data/app/models/decidim/user_group.rb +7 -0
  17. data/app/presenters/decidim/attachment_presenter.rb +21 -0
  18. data/app/resolvers/decidim/core/user_resolver.rb +61 -0
  19. data/app/scrubbers/decidim/user_input_scrubber.rb +2 -2
  20. data/app/serializers/decidim/exporters/participatory_space_components_serializer.rb +46 -0
  21. data/{lib → app/serializers}/decidim/exporters/serializer.rb +0 -0
  22. data/app/serializers/decidim/importers/importer.rb +25 -0
  23. data/app/serializers/decidim/importers/participatory_space_components_importer.rb +67 -0
  24. data/app/services/decidim/open_data_exporter.rb +1 -1
  25. data/app/uploaders/decidim/banner_image_uploader.rb +0 -1
  26. data/app/views/decidim/devise/invitations/edit.html.erb +4 -2
  27. data/app/views/decidim/devise/registrations/new.html.erb +2 -2
  28. data/app/views/decidim/devise/unlocks/new.html.erb +33 -0
  29. data/app/views/decidim/searches/_filters.html.erb +3 -19
  30. data/app/views/decidim/searches/_resources_filter_block.html.erb +20 -0
  31. data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
  32. data/app/views/layouts/decidim/_main_footer.html.erb +26 -0
  33. data/app/views/layouts/decidim/_mini_footer.html.erb +21 -0
  34. data/app/views/layouts/decidim/_wrapper.html.erb +7 -50
  35. data/config/initializers/devise.rb +6 -6
  36. data/config/locales/en.yml +0 -10
  37. data/config/routes.rb +1 -0
  38. data/db/migrate/20191028135718_add_lockable_to_users.rb +10 -0
  39. data/db/migrate/20191118120529_add_weight_to_categories.rb +7 -0
  40. data/db/migrate/20191212102051_remove_continuity_badges.rb +13 -0
  41. data/db/seeds.rb +18 -0
  42. data/lib/decidim/acts_as_author.rb +21 -0
  43. data/lib/decidim/component_manifest.rb +45 -4
  44. data/lib/decidim/core.rb +1 -0
  45. data/lib/decidim/core/engine.rb +0 -4
  46. data/lib/decidim/core/test.rb +3 -0
  47. data/lib/decidim/core/test/factories.rb +5 -0
  48. data/lib/decidim/core/test/shared_examples/acts_as_author_examples.rb +12 -0
  49. data/lib/decidim/core/test/shared_examples/comments_examples.rb +41 -2
  50. data/lib/decidim/core/test/shared_examples/searchable_participatory_space_examples.rb +145 -0
  51. data/lib/decidim/core/test/shared_examples/searchable_resources_shared_context.rb +12 -0
  52. data/lib/decidim/core/version.rb +1 -1
  53. data/lib/decidim/exporters.rb +0 -1
  54. data/lib/decidim/exporters/export_manifest.rb +72 -0
  55. data/lib/decidim/faker/localized.rb +10 -0
  56. data/lib/decidim/form_builder.rb +2 -3
  57. data/lib/decidim/participatory_space_manifest.rb +33 -0
  58. data/lib/decidim/participatory_space_resourceable.rb +8 -0
  59. data/lib/decidim/query_extensions.rb +16 -0
  60. data/lib/decidim/resourceable.rb +1 -1
  61. data/lib/decidim/searchable.rb +19 -1
  62. data/vendor/assets/javascripts/tribute.js +1683 -1621
  63. metadata +29 -13
  64. data/app/assets/images/decidim/gamification/badges/continuity.svg +0 -73
  65. data/app/models/decidim/continuity_badge_status.rb +0 -9
  66. data/app/services/decidim/continuity_badge_tracker.rb +0 -64
  67. data/lib/decidim/components/export_manifest.rb +0 -63
@@ -0,0 +1,26 @@
1
+ <div class="main-footer">
2
+ <% if current_organization.official_img_footer? %>
3
+ <%= link_to current_organization.official_url, class: "main-footer__badge" do %>
4
+ <%= image_tag current_organization.official_img_footer.url.to_s , alt: current_organization.name %>
5
+ <% end %>
6
+ <% end %>
7
+ <div class="row">
8
+ <div class="medium-8 large-6 large-offset-3 column main__footer__nav">
9
+ <ul class="footer-nav">
10
+ <% if current_organization.static_pages.any? %>
11
+ <% current_organization.static_page_topics.where(show_in_footer: true).each do |page_topic| %>
12
+ <% if page_topic.pages.any? %>
13
+ <li><%= link_to translated_attribute(page_topic.title), decidim.page_path(page_topic.pages.first) %></li>
14
+ <% end %>
15
+ <% end %>
16
+
17
+ <% current_organization.static_pages.where(show_in_footer: true).each do |page| %>
18
+ <li><%= link_to translated_attribute(page.title), decidim.page_path(page) %></li>
19
+ <% end %>
20
+ <% end %>
21
+ <li><%= link_to t("layouts.decidim.footer.download_open_data"), decidim.open_data_download_path %></li>
22
+ </ul>
23
+ </div>
24
+ <%= render partial: "layouts/decidim/social_media_links" %>
25
+ </div>
26
+ </div>
@@ -0,0 +1,21 @@
1
+ <div class="mini-footer">
2
+ <div class="row">
3
+ <div class="medium-3 large-4 column">
4
+ <a rel="license" class="cc-badge"
5
+ href="http://creativecommons.org/licenses/by-sa/4.0/"
6
+ target="_blank" rel="noopener">
7
+ <%= image_tag("decidim/cc-badge.png", alt: "Creative Commons License" ) %>
8
+ </a>
9
+ <%= t("layouts.decidim.footer.made_with_open_source").html_safe %>
10
+ </div>
11
+ <div class="medium-3 large-2 column">
12
+ <div class="decidim-logo">
13
+ <a rel="decidim"
14
+ href="https://decidim.org/"
15
+ target="_blank" rel="noopener">
16
+ <%= image_tag("decidim/decidim-logo.svg", alt: "Decidim Logo" ) %>
17
+ </a>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </div>
@@ -24,6 +24,8 @@ end
24
24
  <div class="hide-for-medium" data-set="nav-holder"></div>
25
25
  <div class="hide-for-medium usermenu-off-canvas-holder"
26
26
  data-set="nav-login-holder"></div>
27
+ <div class="hide-for-medium mt-s ml-s mr-s search-off-canvas-holder"
28
+ data-set="nav-search-holder"></div>
27
29
  </div>
28
30
  <div class="off-canvas-content" data-off-canvas-content>
29
31
  <div class="footer-separator">
@@ -95,54 +97,9 @@ end
95
97
  <%= display_flash_messages %>
96
98
  <%= yield %>
97
99
  </main>
98
- </div><!-- /.footer-separator -->
99
- <div class="main-footer">
100
- <% if current_organization.official_img_footer? %>
101
- <%= link_to current_organization.official_url, class: "main-footer__badge" do %>
102
- <%= image_tag current_organization.official_img_footer.url.to_s , alt: current_organization.name %>
103
- <% end %>
104
- <% end %>
105
- <div class="row">
106
- <div class="medium-8 large-6 large-offset-3 column main__footer__nav">
107
- <ul class="footer-nav">
108
- <% if current_organization.static_pages.any? %>
109
- <% current_organization.static_page_topics.where(show_in_footer: true).each do |page_topic| %>
110
- <% if page_topic.pages.any? %>
111
- <li><%= link_to translated_attribute(page_topic.title), decidim.page_path(page_topic.pages.first) %></li>
112
- <% end %>
113
- <% end %>
114
-
115
- <% current_organization.static_pages.where(show_in_footer: true).each do |page| %>
116
- <li><%= link_to translated_attribute(page.title), decidim.page_path(page) %></li>
117
- <% end %>
118
- <% end %>
119
- <li><%= link_to t("layouts.decidim.footer.download_open_data"), decidim.open_data_download_path %></li>
120
- </ul>
121
- </div>
122
- <%= render partial: "layouts/decidim/social_media_links" %>
123
- </div>
124
100
  </div>
125
- <div class="mini-footer">
126
- <div class="row">
127
- <div class="medium-3 large-4 column">
128
- <a rel="license" class="cc-badge"
129
- href="http://creativecommons.org/licenses/by-sa/4.0/"
130
- target="_blank" rel="noopener">
131
- <%= image_tag("decidim/cc-badge.png", alt: "Creative Commons License" ) %>
132
- </a>
133
- <%= t("layouts.decidim.footer.made_with_open_source").html_safe %>
134
- </div>
135
- <div class="medium-3 large-2 column">
136
- <div class="decidim-logo">
137
- <a rel="decidim"
138
- href="https://decidim.org/"
139
- target="_blank" rel="noopener">
140
- <%= image_tag("decidim/decidim-logo.svg", alt: "Decidim Logo" ) %>
141
- </a>
142
- </div>
143
- </div>
144
- </div>
145
- </div>
146
- </div><!--/.off-canvas-content-->
147
- </div><!--/.off-canvas-wrapper-inner-->
148
- </div><!--/.off-canvas-wrapper-->
101
+ <%= render partial: "layouts/decidim/main_footer" %>
102
+ <%= render partial: "layouts/decidim/mini_footer" %>
103
+ </div>
104
+ </div>
105
+ </div>
@@ -223,27 +223,27 @@ Devise.setup do |config|
223
223
  # Defines which strategy will be used to lock an account.
224
224
  # :failed_attempts = Locks an account after a number of failed attempts to sign in.
225
225
  # :none = No lock strategy. You should handle locking by yourself.
226
- # config.lock_strategy = :failed_attempts
226
+ config.lock_strategy = :failed_attempts
227
227
 
228
228
  # Defines which key will be used when locking and unlocking an account
229
- # config.unlock_keys = [:email]
229
+ config.unlock_keys = [:email]
230
230
 
231
231
  # Defines which strategy will be used to unlock an account.
232
232
  # :email = Sends an unlock link to the user email
233
233
  # :time = Re-enables login after a certain amount of time (see :unlock_in below)
234
234
  # :both = Enables both strategies
235
235
  # :none = No unlock strategy. You should handle unlocking by yourself.
236
- # config.unlock_strategy = :both
236
+ config.unlock_strategy = :both
237
237
 
238
238
  # Number of authentication tries before locking an account if lock_strategy
239
239
  # is failed attempts.
240
- # config.maximum_attempts = 20
240
+ config.maximum_attempts = 20
241
241
 
242
242
  # Time interval to unlock the account if :time is enabled as unlock_strategy.
243
- # config.unlock_in = 1.hour
243
+ config.unlock_in = 1.hour
244
244
 
245
245
  # Warn on the last attempt before the account is locked.
246
- # config.last_attempt_warning = true
246
+ config.last_attempt_warning = true
247
247
 
248
248
  # ==> Configuration for :recoverable
249
249
  #
@@ -600,16 +600,6 @@ en:
600
600
  gamification:
601
601
  all_badges_link: See all available badges.
602
602
  badges:
603
- continuity:
604
- conditions:
605
- - Come here often
606
- description: This badge is granted when you visit the platform in a regular way. We like having you around here!
607
- description_another: This participant has connected for %{score} consecutive days at some point.
608
- description_own: You have connected for %{score} consecutive days at some point.
609
- name: Continuity
610
- next_level_in: Connect for %{score} consecutive days to get to the next level!
611
- unearned_another: This participant hasn't signed in for more than one consecutive day.
612
- unearned_own: You haven't connected for more than one consecutive day.
613
603
  followers:
614
604
  conditions:
615
605
  - Being active and following other people will surely make other people follow you.
@@ -13,6 +13,7 @@ Decidim::Core::Engine.routes.draw do
13
13
  confirmations: "decidim/devise/confirmations",
14
14
  registrations: "decidim/devise/registrations",
15
15
  passwords: "decidim/devise/passwords",
16
+ unlocks: "decidim/devise/unlocks",
16
17
  omniauth_callbacks: "decidim/devise/omniauth_registrations"
17
18
  }
18
19
 
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddLockableToUsers < ActiveRecord::Migration[5.2]
4
+ def change
5
+ add_column :decidim_users, :failed_attempts, :integer, default: 0, null: false # Only if lock strategy is :failed_attempts
6
+ add_column :decidim_users, :unlock_token, :string # Only if unlock strategy is :email or :both
7
+ add_column :decidim_users, :locked_at, :datetime
8
+ add_index :decidim_users, :unlock_token, unique: true
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddWeightToCategories < ActiveRecord::Migration[5.2]
4
+ def change
5
+ add_column :decidim_categories, :weight, :integer, null: false, default: 0
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RemoveContinuityBadges < ActiveRecord::Migration[5.2]
4
+ class BadgeScore < ApplicationRecord
5
+ self.table_name = :decidim_gamification_badge_scores
6
+ end
7
+
8
+ def change
9
+ drop_table :decidim_continuity_badge_statuses
10
+
11
+ BadgeScore.where(badge_name: :continuity).delete_all
12
+ end
13
+ end
@@ -139,6 +139,24 @@ if !Rails.env.production? || ENV["SEED"]
139
139
  accepted_tos_version: organization.tos_version
140
140
  )
141
141
 
142
+ locked_user = Decidim::User.find_or_initialize_by(email: "locked_user@example.org")
143
+
144
+ locked_user.update!(
145
+ name: Faker::Name.name,
146
+ nickname: Faker::Twitter.unique.screen_name,
147
+ password: "decidim123456",
148
+ password_confirmation: "decidim123456",
149
+ confirmed_at: Time.current,
150
+ locale: I18n.default_locale,
151
+ organization: organization,
152
+ tos_agreement: true,
153
+ personal_url: Faker::Internet.url,
154
+ about: Faker::Lorem.paragraph(2),
155
+ accepted_tos_version: organization.tos_version
156
+ )
157
+
158
+ locked_user.lock_access!
159
+
142
160
  Decidim::Messaging::Conversation.start!(
143
161
  originator: admin,
144
162
  interlocutors: [regular_user],
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # This concern contains the logic related to being an author.
5
+ #
6
+ # it mainly declares abstract methods to be implemented by artifacts
7
+ # including it in its inheritance hierarchy.
8
+ #
9
+ module ActsAsAuthor
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ # Authors of Authorables must provide its presenters.
14
+ #
15
+ # Return: The presenter for the current author.
16
+ def presenter
17
+ raise NotImlementedError, "Authors must return an instance of its Presenter via this method."
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "decidim/settings_manifest"
4
- require "decidim/components/export_manifest"
4
+ require "decidim/exporters/export_manifest"
5
5
 
6
6
  module Decidim
7
7
  # This class handles all the logic associated to configuring a component
@@ -58,6 +58,25 @@ module Decidim
58
58
  # probably have the form of `Decidim::<MyComponent>::Permissions`.
59
59
  attribute :permissions_class_name, String, default: "Decidim::DefaultPermissions"
60
60
 
61
+ # Does this component have specific data to serialize and import?
62
+ # Beyond the attributes in decidim_component table.
63
+ attribute :serializes_specific_data, Boolean, default: false
64
+
65
+ # The class to be used to serialize specific data for the current component.
66
+ # Should be a kind of `Decidim::Exporters::Serializer`.
67
+ #
68
+ # Note that this class will be initialized with the component as argument.
69
+ # Then it makes no sense to use the base Decidim::Exporters::Serializer because it
70
+ # will serialize the component itself, not the specific data depending on it.
71
+ # Thus you will always be setting a subclass of `Decidim::Exporters::Serializer`.
72
+ #
73
+ attribute :specific_data_serializer_class_name, String
74
+
75
+ # The class to be used to import specific data for the current component.
76
+ # Should be a kind of `Decidim::Importers::Importer`.
77
+ #
78
+ attribute :specific_data_importer_class_name, String
79
+
61
80
  validates :name, presence: true
62
81
 
63
82
  # Public: Registers a hook to this manifest. Hooks get fired when some
@@ -134,7 +153,7 @@ module Decidim
134
153
  end
135
154
 
136
155
  # Public: Registers an export artifact with a name and its properties
137
- # defined in `Decidim::Components::ExportManifest`.
156
+ # defined in `Decidim::Exporters::ExportManifest`.
138
157
  #
139
158
  # Export artifacts provide an unified way for components to register
140
159
  # exportable collections serialized via a `Serializer` than eventually
@@ -156,15 +175,19 @@ module Decidim
156
175
  # Pubic: Returns a collection of previously registered export manifests
157
176
  # for this component.
158
177
  #
159
- # Returns an Array<Decidim::Components::ExportManifest>.
178
+ # Returns an Array<Decidim::Exporters::ExportManifest>.
160
179
  def export_manifests
161
180
  @export_manifests ||= Array(@exports).map do |(name, block)|
162
- Decidim::Components::ExportManifest.new(name, self).tap do |manifest|
181
+ Decidim::Exporters::ExportManifest.new(name, self).tap do |manifest|
163
182
  block.call(manifest)
164
183
  end
165
184
  end
166
185
  end
167
186
 
187
+ def serializes_specific_data?
188
+ serializes_specific_data
189
+ end
190
+
168
191
  # Public: Stores an instance of StatsRegistry
169
192
  def stats
170
193
  @stats ||= StatsRegistry.new
@@ -192,6 +215,24 @@ module Decidim
192
215
  permissions_class_name&.constantize
193
216
  end
194
217
 
218
+ # Public: Finds the specific data serializer class from its name, using the
219
+ # `specific_data_serializer_class_name` attribute. If the class does not exist,
220
+ # it raises an exception. If the class name is not set, it returns nil.
221
+ #
222
+ # Returns a Decidim::Exporters::Serializer subclass or nil.
223
+ def specific_data_serializer_class
224
+ specific_data_serializer_class_name&.constantize
225
+ end
226
+
227
+ # Public: Finds the specific data importer class from its name, using the
228
+ # `specific_data_importerer_class_name` attribute. If the class does not exist,
229
+ # it raises an exception. If the class name is not set, it returns nil.
230
+ #
231
+ # Returns a Decidim::Importers::Importer subclass or nil.
232
+ def specific_data_importer_class
233
+ specific_data_importer_class_name&.constantize
234
+ end
235
+
195
236
  # Public: Registers a resource. Exposes a DSL defined by
196
237
  # `Decidim::ResourceManifest`. Automatically sets the component manifest
197
238
  # for that resource to the current one.
@@ -5,6 +5,7 @@ require "decidim/core/api"
5
5
  require "decidim/core/version"
6
6
  # Decidim configuration.
7
7
  module Decidim
8
+ autoload :ActsAsAuthor, "decidim/acts_as_author"
8
9
  autoload :TranslatableAttributes, "decidim/translatable_attributes"
9
10
  autoload :JsonbAttributes, "decidim/jsonb_attributes"
10
11
  autoload :FormBuilder, "decidim/form_builder"
@@ -379,10 +379,6 @@ module Decidim
379
379
  badge.levels = [1, 15, 30, 60, 100]
380
380
  badge.reset = ->(user) { user.followers.count }
381
381
  end
382
-
383
- Decidim::Gamification.register_badge(:continuity) do |badge|
384
- badge.levels = [2, 10, 30, 60, 180, 365]
385
- end
386
382
  end
387
383
 
388
384
  initializer "nbspw" do
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "decidim/core/test/shared_examples/acts_as_author_examples"
3
4
  require "decidim/core/test/shared_examples/authorable"
4
5
  require "decidim/core/test/shared_examples/coauthorable"
5
6
  require "decidim/core/test/shared_examples/publicable"
@@ -42,4 +43,6 @@ require "decidim/core/test/shared_examples/amendable/amendment_accepted_event_ex
42
43
  require "decidim/core/test/shared_examples/amendable/amendment_rejected_event_examples"
43
44
  require "decidim/core/test/shared_examples/amendable/amendment_promoted_event_examples"
44
45
  require "decidim/core/test/shared_examples/uncommentable_component_examples"
46
+ require "decidim/core/test/shared_examples/searchable_resources_shared_context"
47
+ require "decidim/core/test/shared_examples/searchable_participatory_space_examples"
45
48
  require "decidim/core/test/shared_examples/has_private_users"
@@ -55,6 +55,7 @@ FactoryBot.define do
55
55
  factory :category, class: "Decidim::Category" do
56
56
  name { generate_localized_title }
57
57
  description { Decidim::Faker::Localized.wrapped("<p>", "</p>") { generate_localized_title } }
58
+ weight { 0 }
58
59
 
59
60
  association :participatory_space, factory: :participatory_process
60
61
  end
@@ -339,6 +340,10 @@ FactoryBot.define do
339
340
  }
340
341
  end
341
342
  end
343
+
344
+ trait :with_permissions do
345
+ settings { { Random.rand => Random.new.bytes(5) } }
346
+ end
342
347
  end
343
348
 
344
349
  factory :scope_type, class: "Decidim::ScopeType" do
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # users of this test should delare the `subject` variable.
6
+ shared_examples "acts as author" do
7
+ describe "presenter" do
8
+ it "returns an instance of the presenter for this author" do
9
+ expect(subject.presenter).to be_present
10
+ end
11
+ end
12
+ end
@@ -193,13 +193,12 @@ shared_examples "comments" do
193
193
  end
194
194
  end
195
195
 
196
- describe "mentions" do
196
+ describe "mentions drop-down", :slow do
197
197
  before do
198
198
  visit resource_path
199
199
 
200
200
  within ".add-comment form" do
201
201
  fill_in "add-comment-#{commentable.commentable_type}-#{commentable.id}", with: content
202
- click_button "Send"
203
202
  end
204
203
  end
205
204
 
@@ -207,6 +206,46 @@ shared_examples "comments" do
207
206
  let!(:mentioned_user) { create(:user, :confirmed, organization: organization) }
208
207
  let(:content) { "A valid user mention: @#{mentioned_user.nickname}" }
209
208
 
209
+ context "when text finish with a mention" do
210
+ it "shows the tribute container" do
211
+ expect(page).to have_selector(".tribute-container", text: mentioned_user.name)
212
+ end
213
+ end
214
+
215
+ context "when text contains a mention" do
216
+ let(:content) { "A valid user mention: @#{mentioned_user.nickname}." }
217
+
218
+ it "shows the tribute container" do
219
+ expect(page).not_to have_selector(".tribute-container", text: mentioned_user.name)
220
+ end
221
+ end
222
+ end
223
+
224
+ context "when mentioning a non valid user" do
225
+ let!(:mentioned_user) { create(:user, organization: organization) }
226
+ let(:content) { "A unconfirmed user mention: @#{mentioned_user.nickname}" }
227
+
228
+ it "do not show the tribute container" do
229
+ expect(page).not_to have_selector(".tribute-container")
230
+ end
231
+ end
232
+ end
233
+
234
+ describe "mentions", :slow do
235
+ before do
236
+ visit resource_path
237
+
238
+ within ".add-comment form" do
239
+ fill_in "add-comment-#{commentable.commentable_type}-#{commentable.id}", with: content
240
+ click_button "Send"
241
+ end
242
+ end
243
+
244
+ context "when mentioning a valid user" do
245
+ let!(:mentioned_user) { create(:user, :confirmed, organization: organization) }
246
+ # do not finish with the mention to avoid trigger the drop-down
247
+ let(:content) { "A valid user mention: @#{mentioned_user.nickname}." }
248
+
210
249
  it "replaces the mention with a link to the user's profile" do
211
250
  expect(page).to have_comment_from(user, "A valid user mention: @#{mentioned_user.nickname}")
212
251
  expect(page).to have_link "@#{mentioned_user.nickname}", href: "/profiles/#{mentioned_user.nickname}"