Feature #13919 » 0001-Allow-users-to-be-mentioned-using.patch
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) |