Project

General

Profile

Feature #13919 » 0001-Allow-users-to-be-mentioned-using.patch

Marius BĂLTEANU, 2022-01-12 19:18

View differences:

app/controllers/auto_completes_controller.rb
60 60
    render json: format_wiki_pages_json(wiki_pages)
61 61
  end
62 62

  
63
  def users
64
    users = []
65
    q = (params[:q] || params[:term]).to_s.strip
66
    scope = nil
67
    if params[:q].blank? && @project.present?
68
      scope = @project.users
69
    else
70
      scope = User.all.limit(10)
71
    end
72
    users = scope.active.visible.sorted.like(params[:q]).to_a
73
    render :json => format_users_json(users)
74
  end
75

  
63 76
  private
64 77

  
65 78
  def find_project
......
89 102
      }
90 103
    end
91 104
  end
105

  
106
  def format_users_json(users)
107
    users.map {|user| {
108
        'firstname' => user.firstname,
109
        'lastname' => user.lastname,
110
        'name' => user.name,
111
        'login' => user.login
112
      }
113
    }
114
  end
92 115
end
app/helpers/application_helper.rb
1822 1822
  def autocomplete_data_sources(project)
1823 1823
    {
1824 1824
      issues: auto_complete_issues_path(:project_id => project, :q => ''),
1825
      wiki_pages: auto_complete_wiki_pages_path(:project_id => project, :q => '')
1825
      wiki_pages: auto_complete_wiki_pages_path(:project_id => project, :q => ''),
1826
      users: auto_complete_users_path(:project_id => @project, :q => '')
1826 1827
    }
1827 1828
  end
1828 1829

  
app/models/issue.rb
54 54
  acts_as_activity_provider :scope => proc {preload(:project, :author, :tracker, :status)},
55 55
                            :author_key => :author_id
56 56

  
57
  acts_as_mentionable :attributes => ['description']
58

  
57 59
  DONE_RATIO_OPTIONS = %w(issue_field issue_status)
58 60

  
59 61
  attr_reader :transition_warning
app/models/journal.rb
58 58
                  " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')").distinct
59 59
      end
60 60
  )
61
  acts_as_mentionable :attributes => ['notes']
61 62
  before_create :split_private_notes
62 63
  after_create_commit :send_notification
63 64

  
......
172 173

  
173 174
  def notified_watchers
174 175
    notified = journalized.notified_watchers
175
    if private_notes?
176
      notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
177
    end
176
    notified = select_journal_visible_user(notified)
177
    notified
178
  end
179

  
180
  def notified_mentions
181
    notified = super
182
    notified = select_journal_visible_user(notified)
178 183
    notified
179 184
  end
180 185

  
......
337 342
      Mailer.deliver_issue_edit(self)
338 343
    end
339 344
  end
345

  
346
  def select_journal_visible_user(notified)
347
    if private_notes?
348
      notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
349
    end
350
    notified
351
  end
340 352
end
app/models/mailer.rb
94 94
  # Example:
95 95
  #   Mailer.deliver_issue_add(issue)
96 96
  def self.deliver_issue_add(issue)
97
    users = issue.notified_users | issue.notified_watchers
97
    users = issue.notified_users | issue.notified_watchers | issue.notified_mentions
98 98
    users.each do |user|
99 99
      issue_add(user, issue).deliver_later
100 100
    end
......
129 129
  # Example:
130 130
  #   Mailer.deliver_issue_edit(journal)
131 131
  def self.deliver_issue_edit(journal)
132
    users  = journal.notified_users | journal.notified_watchers
132
    users  = journal.notified_users | journal.notified_watchers | journal.notified_mentions | journal.journalized.notified_mentions
133 133
    users.select! do |user|
134 134
      journal.notes? || journal.visible_details(user).any?
135 135
    end
......
222 222
  # Example:
223 223
  #   Mailer.deliver_news_added(news)
224 224
  def self.deliver_news_added(news)
225
    users = news.notified_users | news.notified_watchers_for_added_news
225
    users = news.notified_users | news.notified_watchers_for_added_news | news.notified_mentions
226 226
    users.each do |user|
227 227
      news_added(user, news).deliver_later
228 228
    end
......
306 306
  # Example:
307 307
  #   Mailer.deliver_wiki_content_added(wiki_content)
308 308
  def self.deliver_wiki_content_added(wiki_content)
309
    users = wiki_content.notified_users | wiki_content.page.wiki.notified_watchers
309
    users = wiki_content.notified_users | wiki_content.page.wiki.notified_watchers | wiki_content.notified_mentions
310 310
    users.each do |user|
311 311
      wiki_content_added(user, wiki_content).deliver_later
312 312
    end
......
343 343
    users  = wiki_content.notified_users
344 344
    users |= wiki_content.page.notified_watchers
345 345
    users |= wiki_content.page.wiki.notified_watchers
346
    users |= wiki_content.notified_mentions
346 347

  
347 348
    users.each do |user|
348 349
      wiki_content_updated(user, wiki_content).deliver_later
app/models/news.rb
35 35
  acts_as_activity_provider :scope => proc {preload(:project, :author)},
36 36
                            :author_key => :author_id
37 37
  acts_as_watchable
38
  acts_as_mentionable :attributes => ['description']
38 39

  
39 40
  after_create :add_author_as_watcher
40 41
  after_create_commit :send_notification
app/models/wiki_content.rb
24 24
  belongs_to :page, :class_name => 'WikiPage'
25 25
  belongs_to :author, :class_name => 'User'
26 26
  has_many :versions, :class_name => 'WikiContentVersion', :dependent => :delete_all
27

  
28
  acts_as_mentionable :attributes => ['text']
29

  
27 30
  validates_presence_of :text
28 31
  validates_length_of :comments, :maximum => 1024, :allow_nil => true
29 32

  
config/routes.rb
46 46
  post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
47 47
  post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy'
48 48

  
49
  # Auto complate routes
49
  # Auto complete routes
50 50
  match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues'
51 51
  match '/wiki_pages/auto_complete', :to => 'auto_completes#wiki_pages', :via => :get, :as => 'auto_complete_wiki_pages'
52
  match '/users/auto_complete', :to => 'auto_completes#users', :via => :get, :as => 'auto_complete_users'
52 53

  
53 54
  # Misc issue routes. TODO: move into resources
54 55
  match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post]
lib/redmine.rb
36 36
end
37 37

  
38 38
module Redmine
39

  
39 40
end
lib/redmine/acts/mentionable.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-2022 Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
module Redmine
21
  module Acts
22
    module Mentionable
23
      def self.included(base)
24
        base.extend ClassMethods
25
      end
26

  
27
      module ClassMethods
28
        def acts_as_mentionable(options = {})
29
          class_attribute :mentionable_attributes
30
          self.mentionable_attributes = options[:attributes]
31

  
32
          attr_accessor :mentioned_users
33

  
34
          send :include, Redmine::Acts::Mentionable::InstanceMethods
35

  
36
          after_save :parse_mentions
37
        end
38
      end
39

  
40
      module InstanceMethods
41
        def self.included(base)
42
          base.extend ClassMethods
43
        end
44

  
45
        def notified_mentions
46
          notified = mentioned_users.to_a
47
          notified.reject! {|user| user.mail.blank? || user.mail_notification == 'none'}
48
          if respond_to?(:visible?)
49
            notified.reject! {|user| !visible?(user)}
50
          end
51
          notified
52
        end
53

  
54
        private
55

  
56
        def parse_mentions
57
          mentionable_attrs = self.mentionable_attributes
58
          saved_mentionable_attrs = self.saved_changes.select{|a| mentionable_attrs.include?(a)}
59

  
60
          saved_mentionable_attrs.each do |key, attr|
61
            old_value, new_value =  attr
62
            get_mentioned_users(old_value, new_value)
63
          end
64
        end
65

  
66
        def get_mentioned_users(old_content, new_content)
67
          self.mentioned_users = []
68

  
69
          previous_matches =  scan_for_mentioned_users(old_content)
70
          current_matches = scan_for_mentioned_users(new_content)
71
          new_matches = (current_matches - previous_matches).flatten
72

  
73
          if new_matches.any?
74
            self.mentioned_users = User.visible.active.where(login: new_matches)
75
          end
76
        end
77

  
78
        def scan_for_mentioned_users(content)
79
          return [] if content.nil?
80

  
81
          # remove quoted text
82
          content = content.gsub(%r{\r\n(?:\>\s)+(.*?)\r\n}m, '')
83

  
84
          text_formatting = Setting.text_formatting
85
          # Remove text wrapped in pre tags based on text formatting
86
          if text_formatting == 'textile'
87
            content = content.gsub(%r{<pre>(.*?)</pre>}m, '')
88
          elsif text_formatting == 'markdown' || text_formatting == 'common_mark'
89
            content = content.gsub(%r{(~~~|```)(.*?)(~~~|```)}m, '')
90
          end
91

  
92
          users = content.scan(MentionPattern).flatten
93
        end
94

  
95
        MentionPattern = /
96
          (?:^|\W)                    # beginning of string or non-word char
97
          @((?>[a-z0-9][a-z0-9-]*))   # @username
98
          (?!\/)                      # without a trailing slash
99
          (?=
100
            \.+[ \t\W]|               # dots followed by space or non-word character
101
            \.+$|                     # dots at end of line
102
            [^0-9a-zA-Z_.]|           # non-word character except dot
103
            $                         # end of line
104
          )
105
        /ix
106
      end
107
    end
108
  end
109
end
lib/redmine/preparation.rb
21 21
  module Preparation
22 22
    def self.prepare
23 23
      ActiveRecord::Base.include Redmine::Acts::Positioned
24
      ActiveRecord::Base.include Redmine::Acts::Mentionable
24 25
      ActiveRecord::Base.include Redmine::I18n
25 26

  
26 27
      Scm::Base.add "Subversion"
public/javascripts/application.js
1193 1193
          noMatchTemplate: function () {
1194 1194
            return '<span style:"visibility: hidden;"></span>';
1195 1195
          }
1196
        },
1197
        {
1198
          trigger: '@',
1199
          lookup: function (user, mentionText) {
1200
            return user.name + user.firstname + user.lastname + user.login;
1201
          },
1202
          values: function (text, cb) {
1203
            remoteSearch(getDataSource('users') + text, function (users) {
1204
              return cb(users);
1205
            });
1206
          },
1207
          menuItemTemplate: function (user) {
1208
            return user.original.name;
1209
          },
1210
          selectTemplate: function (user) {
1211
            return '@' + user.original.login;
1212
          }
1196 1213
        }
1197 1214
      ]
1198 1215
    });
test/functional/auto_completes_controller_test.rb
79 79
    assert_include "Bug #13", response.body
80 80
  end
81 81

  
82
  def test_auto_complete_with_scope_all_should_search_other_projects
82
  def test_issues_with_scope_all_should_search_other_projects
83 83
    get(
84 84
      :issues,
85 85
      :params => {
......
92 92
    assert_include "Bug #13", response.body
93 93
  end
94 94

  
95
  def test_auto_complete_without_project_should_search_all_projects
95
  def test_issues_without_project_should_search_all_projects
96 96
    get(:issues, :params => {:q => '13'})
97 97
    assert_response :success
98 98
    assert_include "Bug #13", response.body
99 99
  end
100 100

  
101
  def test_auto_complete_without_scope_all_should_not_search_other_projects
101
  def test_issues_without_scope_all_should_not_search_other_projects
102 102
    get(
103 103
      :issues,
104 104
      :params => {
......
128 128
    assert_equal 'Bug #13: Subproject issue two', issue['label']
129 129
  end
130 130

  
131
  def test_auto_complete_with_status_o_should_return_open_issues_only
131
  def test_issues_with_status_o_should_return_open_issues_only
132 132
    get(
133 133
      :issues,
134 134
      :params => {
......
142 142
    assert_not_include "closed", response.body
143 143
  end
144 144

  
145
  def test_auto_complete_with_status_c_should_return_closed_issues_only
145
  def test_issues_with_status_c_should_return_closed_issues_only
146 146
    get(
147 147
      :issues,
148 148
      :params => {
......
156 156
    assert_not_include "Issue due today", response.body
157 157
  end
158 158

  
159
  def test_auto_complete_with_issue_id_should_not_return_that_issue
159
  def test_issues_with_issue_id_should_not_return_that_issue
160 160
    get(
161 161
      :issues,
162 162
      :params => {
......
182 182
    assert_include 'application/json', response.headers['Content-Type']
183 183
  end
184 184

  
185
  def test_auto_complete_without_term_should_return_last_10_issues
185
  def test_issue_without_term_should_return_last_10_issues
186 186
    # There are 9 issues generated by fixtures
187 187
    # and we need two more to test the 10 limit
188 188
    %w(1..2).each do
......
269 269
    assert_equal 10, json.count
270 270
    assert_equal wiki.pages[-2].id, json.first['id'].to_i
271 271
  end
272

  
273
  def test_users_should_accept_case_insensitive_term_param
274
    get :users, :params => {
275
        :q => 'JoHN'
276
    }
277
    assert_response :success
278
    assert_include "John Smith", response.body
279
  end
280

  
281
  def test_users_should_return_json
282
    get :users, :params => {
283
        :q => 'john'
284
    }
285
    assert_response :success
286
    json = ActiveSupport::JSON.decode(response.body)
287
    assert_kind_of Array, json
288
    user = json.first
289
    assert_kind_of Hash, user
290
    assert_equal 'John', user['firstname']
291
    assert_equal 'Smith', user['lastname']
292
    assert_equal 'John Smith', user['name']
293
    assert_equal 'jsmith', user['login']
294
  end
295

  
296
  def test_users_with_project_and_without_term_should_return_project_users
297
    get :users, :params => {
298
        :project_id => 'onlinestore'
299
    }
300
    assert_response :success
301
    json = ActiveSupport::JSON.decode(response.body)
302
    assert_equal 2, json.count
303
    assert_equal 'jsmith', json.first['login']
304
    assert_equal 'miscuser8', json.last['login']
305
  end
306

  
307
  def test_users_without_project_and_with_term_should_search_in_all_users
308
    get :users, :params => {
309
        :q => 'm'
310
    }
311
    assert_response :success
312
    assert_include "Redmine Admin", response.body
313
    assert_include "Dave Lopper", response.body
314
  end
272 315
end
test/unit/journal_test.rb
236 236
      assert_equal "image#{i}.png", attachment.filename
237 237
    end
238 238
  end
239

  
240
  def test_notified_mentions_should_not_include_users_who_cannot_view_private_notes
241
    journal = Journal.generate!(journalized: Issue.find(2), user: User.find(1), private_notes: true, notes: 'Hello @dlopper, @jsmith and @admin.')
242

  
243
    # User "dlopper" has "Developer" role on project "eCookbook"
244
    # Role "Developer" does not have the "View private notes" permission
245
    assert_equal [1, 2], journal.notified_mentions.map(&:id)
246
  end
239 247
end
test/unit/lib/redmine/acts/mentionable_test.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-2022  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
require File.expand_path('../../../../../test_helper', __FILE__)
21

  
22
class Redmine::Acts::MentionableTest < ActiveSupport::TestCase
23
  fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles,
24
         :groups_users,
25
         :trackers, :projects_trackers,
26
         :enabled_modules,
27
         :issue_statuses, :issue_categories, :issue_relations, :workflows,
28
         :enumerations,
29
         :issues
30

  
31
  def test_mentioned_users_with_user_mention
32
    issue = Issue.generate!(project_id: 1, description: '@dlopper')
33

  
34
    assert_equal [User.find(3)], issue.mentioned_users
35
  end
36

  
37
  def test_mentioned_users_with_multiple_mentions
38
    issue = Issue.generate!(project_id: 1, description: 'Hello @dlopper, @jsmith.')
39

  
40
    assert_equal [User.find(2), User.find(3)], issue.mentioned_users
41
  end
42

  
43
  def test_mentioned_users_should_not_mention_same_user_multiple_times
44
    issue = Issue.generate!(project_id: 1, description: '@dlopper @jsmith @dlopper')
45

  
46
    assert_equal [User.find(2), User.find(3)], issue.mentioned_users
47
  end
48

  
49
  def test_mentioned_users_should_include_only_active_users
50
    # disable dlopper account
51
    user = User.find(3)
52
    user.status = User::STATUS_LOCKED
53
    user.save
54

  
55
    issue = Issue.generate!(project_id: 1, description: '@dlopper @jsmith')
56

  
57
    assert_equal [User.find(2)], issue.mentioned_users
58
  end
59

  
60
  def test_mentioned_users_should_include_only_visible_users
61
    User.current = nil
62
    Role.non_member.update! users_visibility: 'members_of_visible_projects'
63
    Role.anonymous.update! users_visibility: 'members_of_visible_projects'
64
    user = User.generate!
65

  
66
    issue = Issue.generate!(project_id: 1, description: "@jsmith @#{user.login}")
67

  
68
    assert_equal [User.find(2)], issue.mentioned_users
69
  end
70

  
71
  def test_mentioned_users_should_not_include_mentioned_users_in_existing_content
72
    issue = Issue.generate!(project_id: 1, description: 'Hello @dlopper')
73

  
74
    assert issue.save
75
    assert_equal [User.find(3)], issue.mentioned_users
76

  
77
    issue.description = 'Hello @dlopper and @jsmith'
78
    issue.save
79

  
80
    assert_equal [User.find(2)], issue.mentioned_users
81
  end
82

  
83
  def test_mentioned_users_should_not_include_users_wrapped_in_pre_tags_for_textile
84
    description = <<~STR
85
      <pre>
86
      Hello @jsmith
87
      </pre>
88
    STR
89

  
90
    with_settings text_formatting: 'textile' do
91
      issue = Issue.generate!(project_id: 1, description: description)
92

  
93
      assert_equal [], issue.mentioned_users
94
    end
95
  end
96

  
97
  def test_mentioned_users_should_not_include_users_wrapped_in_pre_tags_for_markdown
98
    description = <<~STR
99
      ```
100
      Hello @jsmith
101
      ```
102
    STR
103

  
104
    with_settings text_formatting: 'markdown' do
105
      issue = Issue.generate!(project_id: 1, description: description)
106

  
107
      assert_equal [], issue.mentioned_users
108
    end
109
  end
110

  
111
  def test_mentioned_users_should_not_include_users_wrapped_in_pre_tags_for_common_mark
112
    description = <<~STR
113
      ```
114
      Hello @jsmith
115
      ```
116
    STR
117

  
118
    with_settings text_formatting: 'common_mark' do
119
      issue = Issue.generate!(project_id: 1, description: description)
120

  
121
      assert_equal [], issue.mentioned_users
122
    end
123
  end
124

  
125
  def test_notified_mentions
126
    issue = Issue.generate!(project_id: 1, description: 'Hello @dlopper, @jsmith.')
127

  
128
    assert_equal [User.find(2), User.find(3)], issue.notified_mentions
129
  end
130

  
131
  def test_notified_mentions_should_not_include_users_who_out_of_all_email
132
    User.find(3).update!(mail_notification: :none)
133
    issue = Issue.generate!(project_id: 1, description: "Hello @dlopper, @jsmith.")
134

  
135
    assert_equal [User.find(2)], issue.notified_mentions
136
  end
137

  
138
  def test_notified_mentions_should_not_include_users_who_cannot_view_the_object
139
    user = User.find(3)
140

  
141
    # User dlopper does not have access to project "Private child of eCookbook"
142
    issue = Issue.generate!(project_id: 5, description: "Hello @dlopper, @jsmith.")
143

  
144
    assert !issue.notified_mentions.include?(user)
145
  end
146
end
test/unit/mailer_test.rb
464 464
    assert_not_include user.mail, recipients
465 465
  end
466 466

  
467
  def test_issue_add_should_notify_mentioned_users_in_issue_description
468
    User.find(1).mail_notification = 'only_my_events'
469

  
470
    issue = Issue.generate!(project_id: 1, description: 'Hello @dlopper and @admin.')
471

  
472
    assert Mailer.deliver_issue_add(issue)
473
    # @jsmith and @dlopper are members of the project
474
    # admin is mentioned
475
    # @dlopper won't receive duplicated notifications
476
    assert_equal 3, ActionMailer::Base.deliveries.size
477
    assert_include User.find(1).mail, recipients
478
  end
479

  
467 480
  def test_issue_add_should_include_enabled_fields
468 481
    issue = Issue.find(2)
469 482
    assert Mailer.deliver_issue_add(issue)
......
608 621
    end
609 622
  end
610 623

  
624
  def test_issue_edit_should_notify_mentioned_users_in_issue_updated_description
625
    User.find(1).mail_notification = 'only_my_events'
626

  
627
    issue = Issue.find(3)
628
    issue.init_journal(User.current)
629
    issue.update(description: "Hello @admin")
630
    journal = issue.journals.last
631

  
632
    ActionMailer::Base.deliveries.clear
633
    Mailer.deliver_issue_edit(journal)
634

  
635
    # @jsmith and @dlopper are members of the project
636
    # admin is mentioned in the updated description
637
    # @dlopper won't receive duplicated notifications
638
    assert_equal 3, ActionMailer::Base.deliveries.size
639
    assert_include User.find(1).mail, recipients
640
  end
641

  
642
  def test_issue_edit_should_notify_mentioned_users_in_notes
643
    User.find(1).mail_notification = 'only_my_events'
644

  
645
    journal = Journal.generate!(journalized: Issue.find(3), user: User.find(1), notes: 'Hello @admin.')
646

  
647
    ActionMailer::Base.deliveries.clear
648
    Mailer.deliver_issue_edit(journal)
649

  
650
    # @jsmith and @dlopper are members of the project
651
    # admin is mentioned in the notes
652
    # @dlopper won't receive duplicated notifications
653
    assert_equal 3, ActionMailer::Base.deliveries.size
654
    assert_include User.find(1).mail, recipients
655
  end
656

  
611 657
  def test_issue_should_send_email_notification_with_suppress_empty_fields
612 658
    ActionMailer::Base.deliveries.clear
613 659
    with_settings :notified_events => %w(issue_added) do
......
691 737
    end
692 738
  end
693 739

  
740
  def test_news_added_should_notify_mentioned_users_in_news_description
741
    news = News.new(title: 'Test news', description: 'Hello @admin.', author: User.first, project_id: 1)
742
    news.save!
743

  
744
    ActionMailer::Base.deliveries.clear
745
    Mailer.deliver_news_added(news)
746

  
747
    # @jsmith and @dlopper are members of the project
748
    # admin is mentioned in the notes
749
    # @dlopper won't receive duplicated notifications
750
    assert_equal 3, ActionMailer::Base.deliveries.size
751
    assert_include User.find(1).mail, recipients
752
  end
753

  
694 754
  def test_wiki_content_added
695 755
    content = WikiContent.find(1)
696 756
    assert_difference 'ActionMailer::Base.deliveries.size', 2 do
......
703 763
    end
704 764
  end
705 765

  
766
  def test_wiki_content_added_should_notify_mentioned_users_in_content
767
    content = WikiContent.new(text: 'Hello @admin.', author_id: 1, page_id: 1)
768
    content.save!
769

  
770
    ActionMailer::Base.deliveries.clear
771
    Mailer.deliver_wiki_content_added(content)
772

  
773
    # @jsmith and @dlopper are members of the project
774
    # admin is mentioned in the notes
775
    # @dlopper won't receive duplicated notifications
776
    assert_equal 3, ActionMailer::Base.deliveries.size
777
    assert_include User.find(1).mail, recipients
778
  end
779

  
706 780
  def test_wiki_content_updated
707 781
    content = WikiContent.find(1)
708 782
    assert Mailer.deliver_wiki_content_updated(content)
......
713 787
    end
714 788
  end
715 789

  
790
  def test_wiki_content_updated_should_notify_mentioned_users_in_updated_content
791
    content = WikiContent.find(1)
792
    content.update(text: 'Hello @admin.')
793
    content.save!
794

  
795
    ActionMailer::Base.deliveries.clear
796
    Mailer.deliver_wiki_content_updated(content)
797

  
798
    # @jsmith and @dlopper are members of the project
799
    # admin is mentioned in the notes
800
    # @dlopper won't receive duplicated notifications
801
    assert_equal 3, ActionMailer::Base.deliveries.size
802
    assert_include User.find(1).mail, recipients
803
  end
804

  
716 805
  def test_register
717 806
    token = Token.find(1)
718 807
    assert Mailer.deliver_register(token.user, token)
(10-10/12)