##// END OF EJS Templates
Overrides IssueRelation#to_s....
Jean-Philippe Lang -
r13181:5326eb233e39
parent child
Show More
@@ -1,205 +1,215
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssueRelation < ActiveRecord::Base
18 class IssueRelation < ActiveRecord::Base
19 # Class used to represent the relations of an issue
19 # Class used to represent the relations of an issue
20 class Relations < Array
20 class Relations < Array
21 include Redmine::I18n
21 include Redmine::I18n
22
22
23 def initialize(issue, *args)
23 def initialize(issue, *args)
24 @issue = issue
24 @issue = issue
25 super(*args)
25 super(*args)
26 end
26 end
27
27
28 def to_s(*args)
28 def to_s(*args)
29 map {|relation| "#{l(relation.label_for(@issue))} ##{relation.other_issue(@issue).id}"}.join(', ')
29 map {|relation| "#{l(relation.label_for(@issue))} ##{relation.other_issue(@issue).id}"}.join(', ')
30 end
30 end
31 end
31 end
32
32
33 belongs_to :issue_from, :class_name => 'Issue'
33 belongs_to :issue_from, :class_name => 'Issue'
34 belongs_to :issue_to, :class_name => 'Issue'
34 belongs_to :issue_to, :class_name => 'Issue'
35
35
36 TYPE_RELATES = "relates"
36 TYPE_RELATES = "relates"
37 TYPE_DUPLICATES = "duplicates"
37 TYPE_DUPLICATES = "duplicates"
38 TYPE_DUPLICATED = "duplicated"
38 TYPE_DUPLICATED = "duplicated"
39 TYPE_BLOCKS = "blocks"
39 TYPE_BLOCKS = "blocks"
40 TYPE_BLOCKED = "blocked"
40 TYPE_BLOCKED = "blocked"
41 TYPE_PRECEDES = "precedes"
41 TYPE_PRECEDES = "precedes"
42 TYPE_FOLLOWS = "follows"
42 TYPE_FOLLOWS = "follows"
43 TYPE_COPIED_TO = "copied_to"
43 TYPE_COPIED_TO = "copied_to"
44 TYPE_COPIED_FROM = "copied_from"
44 TYPE_COPIED_FROM = "copied_from"
45
45
46 TYPES = {
46 TYPES = {
47 TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to,
47 TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to,
48 :order => 1, :sym => TYPE_RELATES },
48 :order => 1, :sym => TYPE_RELATES },
49 TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by,
49 TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by,
50 :order => 2, :sym => TYPE_DUPLICATED },
50 :order => 2, :sym => TYPE_DUPLICATED },
51 TYPE_DUPLICATED => { :name => :label_duplicated_by, :sym_name => :label_duplicates,
51 TYPE_DUPLICATED => { :name => :label_duplicated_by, :sym_name => :label_duplicates,
52 :order => 3, :sym => TYPE_DUPLICATES, :reverse => TYPE_DUPLICATES },
52 :order => 3, :sym => TYPE_DUPLICATES, :reverse => TYPE_DUPLICATES },
53 TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by,
53 TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by,
54 :order => 4, :sym => TYPE_BLOCKED },
54 :order => 4, :sym => TYPE_BLOCKED },
55 TYPE_BLOCKED => { :name => :label_blocked_by, :sym_name => :label_blocks,
55 TYPE_BLOCKED => { :name => :label_blocked_by, :sym_name => :label_blocks,
56 :order => 5, :sym => TYPE_BLOCKS, :reverse => TYPE_BLOCKS },
56 :order => 5, :sym => TYPE_BLOCKS, :reverse => TYPE_BLOCKS },
57 TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows,
57 TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows,
58 :order => 6, :sym => TYPE_FOLLOWS },
58 :order => 6, :sym => TYPE_FOLLOWS },
59 TYPE_FOLLOWS => { :name => :label_follows, :sym_name => :label_precedes,
59 TYPE_FOLLOWS => { :name => :label_follows, :sym_name => :label_precedes,
60 :order => 7, :sym => TYPE_PRECEDES, :reverse => TYPE_PRECEDES },
60 :order => 7, :sym => TYPE_PRECEDES, :reverse => TYPE_PRECEDES },
61 TYPE_COPIED_TO => { :name => :label_copied_to, :sym_name => :label_copied_from,
61 TYPE_COPIED_TO => { :name => :label_copied_to, :sym_name => :label_copied_from,
62 :order => 8, :sym => TYPE_COPIED_FROM },
62 :order => 8, :sym => TYPE_COPIED_FROM },
63 TYPE_COPIED_FROM => { :name => :label_copied_from, :sym_name => :label_copied_to,
63 TYPE_COPIED_FROM => { :name => :label_copied_from, :sym_name => :label_copied_to,
64 :order => 9, :sym => TYPE_COPIED_TO, :reverse => TYPE_COPIED_TO }
64 :order => 9, :sym => TYPE_COPIED_TO, :reverse => TYPE_COPIED_TO }
65 }.freeze
65 }.freeze
66
66
67 validates_presence_of :issue_from, :issue_to, :relation_type
67 validates_presence_of :issue_from, :issue_to, :relation_type
68 validates_inclusion_of :relation_type, :in => TYPES.keys
68 validates_inclusion_of :relation_type, :in => TYPES.keys
69 validates_numericality_of :delay, :allow_nil => true
69 validates_numericality_of :delay, :allow_nil => true
70 validates_uniqueness_of :issue_to_id, :scope => :issue_from_id
70 validates_uniqueness_of :issue_to_id, :scope => :issue_from_id
71 validate :validate_issue_relation
71 validate :validate_issue_relation
72
72
73 attr_protected :issue_from_id, :issue_to_id
73 attr_protected :issue_from_id, :issue_to_id
74 before_save :handle_issue_order
74 before_save :handle_issue_order
75 after_create :call_issues_relation_added_callback
75 after_create :call_issues_relation_added_callback
76 after_destroy :call_issues_relation_removed_callback
76 after_destroy :call_issues_relation_removed_callback
77
77
78 def visible?(user=User.current)
78 def visible?(user=User.current)
79 (issue_from.nil? || issue_from.visible?(user)) && (issue_to.nil? || issue_to.visible?(user))
79 (issue_from.nil? || issue_from.visible?(user)) && (issue_to.nil? || issue_to.visible?(user))
80 end
80 end
81
81
82 def deletable?(user=User.current)
82 def deletable?(user=User.current)
83 visible?(user) &&
83 visible?(user) &&
84 ((issue_from.nil? || user.allowed_to?(:manage_issue_relations, issue_from.project)) ||
84 ((issue_from.nil? || user.allowed_to?(:manage_issue_relations, issue_from.project)) ||
85 (issue_to.nil? || user.allowed_to?(:manage_issue_relations, issue_to.project)))
85 (issue_to.nil? || user.allowed_to?(:manage_issue_relations, issue_to.project)))
86 end
86 end
87
87
88 def initialize(attributes=nil, *args)
88 def initialize(attributes=nil, *args)
89 super
89 super
90 if new_record?
90 if new_record?
91 if relation_type.blank?
91 if relation_type.blank?
92 self.relation_type = IssueRelation::TYPE_RELATES
92 self.relation_type = IssueRelation::TYPE_RELATES
93 end
93 end
94 end
94 end
95 end
95 end
96
96
97 def validate_issue_relation
97 def validate_issue_relation
98 if issue_from && issue_to
98 if issue_from && issue_to
99 errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id
99 errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id
100 unless issue_from.project_id == issue_to.project_id ||
100 unless issue_from.project_id == issue_to.project_id ||
101 Setting.cross_project_issue_relations?
101 Setting.cross_project_issue_relations?
102 errors.add :issue_to_id, :not_same_project
102 errors.add :issue_to_id, :not_same_project
103 end
103 end
104 # detect circular dependencies depending wether the relation should be reversed
104 # detect circular dependencies depending wether the relation should be reversed
105 if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse]
105 if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse]
106 errors.add :base, :circular_dependency if issue_from.all_dependent_issues.include? issue_to
106 errors.add :base, :circular_dependency if issue_from.all_dependent_issues.include? issue_to
107 else
107 else
108 errors.add :base, :circular_dependency if issue_to.all_dependent_issues.include? issue_from
108 errors.add :base, :circular_dependency if issue_to.all_dependent_issues.include? issue_from
109 end
109 end
110 if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to)
110 if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to)
111 errors.add :base, :cant_link_an_issue_with_a_descendant
111 errors.add :base, :cant_link_an_issue_with_a_descendant
112 end
112 end
113 end
113 end
114 end
114 end
115
115
116 def other_issue(issue)
116 def other_issue(issue)
117 (self.issue_from_id == issue.id) ? issue_to : issue_from
117 (self.issue_from_id == issue.id) ? issue_to : issue_from
118 end
118 end
119
119
120 # Returns the relation type for +issue+
120 # Returns the relation type for +issue+
121 def relation_type_for(issue)
121 def relation_type_for(issue)
122 if TYPES[relation_type]
122 if TYPES[relation_type]
123 if self.issue_from_id == issue.id
123 if self.issue_from_id == issue.id
124 relation_type
124 relation_type
125 else
125 else
126 TYPES[relation_type][:sym]
126 TYPES[relation_type][:sym]
127 end
127 end
128 end
128 end
129 end
129 end
130
130
131 def label_for(issue)
131 def label_for(issue)
132 TYPES[relation_type] ?
132 TYPES[relation_type] ?
133 TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] :
133 TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] :
134 :unknow
134 :unknow
135 end
135 end
136
136
137 def to_s(issue=nil)
138 issue ||= issue_from
139 issue_text = block_given? ? yield(other_issue(issue)) : "##{other_issue(issue).try(:id)}"
140 s = []
141 s << l(label_for(issue))
142 s << "(#{l('datetime.distance_in_words.x_days', :count => delay)})" if delay && delay != 0
143 s << issue_text
144 s.join(' ')
145 end
146
137 def css_classes_for(issue)
147 def css_classes_for(issue)
138 "rel-#{relation_type_for(issue)}"
148 "rel-#{relation_type_for(issue)}"
139 end
149 end
140
150
141 def handle_issue_order
151 def handle_issue_order
142 reverse_if_needed
152 reverse_if_needed
143
153
144 if TYPE_PRECEDES == relation_type
154 if TYPE_PRECEDES == relation_type
145 self.delay ||= 0
155 self.delay ||= 0
146 else
156 else
147 self.delay = nil
157 self.delay = nil
148 end
158 end
149 set_issue_to_dates
159 set_issue_to_dates
150 end
160 end
151
161
152 def set_issue_to_dates
162 def set_issue_to_dates
153 soonest_start = self.successor_soonest_start
163 soonest_start = self.successor_soonest_start
154 if soonest_start && issue_to
164 if soonest_start && issue_to
155 issue_to.reschedule_on!(soonest_start)
165 issue_to.reschedule_on!(soonest_start)
156 end
166 end
157 end
167 end
158
168
159 def successor_soonest_start
169 def successor_soonest_start
160 if (TYPE_PRECEDES == self.relation_type) && delay && issue_from &&
170 if (TYPE_PRECEDES == self.relation_type) && delay && issue_from &&
161 (issue_from.start_date || issue_from.due_date)
171 (issue_from.start_date || issue_from.due_date)
162 (issue_from.due_date || issue_from.start_date) + 1 + delay
172 (issue_from.due_date || issue_from.start_date) + 1 + delay
163 end
173 end
164 end
174 end
165
175
166 def <=>(relation)
176 def <=>(relation)
167 r = TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order]
177 r = TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order]
168 r == 0 ? id <=> relation.id : r
178 r == 0 ? id <=> relation.id : r
169 end
179 end
170
180
171 def init_journals(user)
181 def init_journals(user)
172 issue_from.init_journal(user) if issue_from
182 issue_from.init_journal(user) if issue_from
173 issue_to.init_journal(user) if issue_to
183 issue_to.init_journal(user) if issue_to
174 end
184 end
175
185
176 private
186 private
177
187
178 # Reverses the relation if needed so that it gets stored in the proper way
188 # Reverses the relation if needed so that it gets stored in the proper way
179 # Should not be reversed before validation so that it can be displayed back
189 # Should not be reversed before validation so that it can be displayed back
180 # as entered on new relation form
190 # as entered on new relation form
181 def reverse_if_needed
191 def reverse_if_needed
182 if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse]
192 if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse]
183 issue_tmp = issue_to
193 issue_tmp = issue_to
184 self.issue_to = issue_from
194 self.issue_to = issue_from
185 self.issue_from = issue_tmp
195 self.issue_from = issue_tmp
186 self.relation_type = TYPES[relation_type][:reverse]
196 self.relation_type = TYPES[relation_type][:reverse]
187 end
197 end
188 end
198 end
189
199
190 def call_issues_relation_added_callback
200 def call_issues_relation_added_callback
191 call_issues_callback :relation_added
201 call_issues_callback :relation_added
192 end
202 end
193
203
194 def call_issues_relation_removed_callback
204 def call_issues_relation_removed_callback
195 call_issues_callback :relation_removed
205 call_issues_callback :relation_removed
196 end
206 end
197
207
198 def call_issues_callback(name)
208 def call_issues_callback(name)
199 [issue_from, issue_to].each do |issue|
209 [issue_from, issue_to].each do |issue|
200 if issue
210 if issue
201 issue.send name, self
211 issue.send name, self
202 end
212 end
203 end
213 end
204 end
214 end
205 end
215 end
@@ -1,44 +1,41
1 <div class="contextual">
1 <div class="contextual">
2 <% if User.current.allowed_to?(:manage_issue_relations, @project) %>
2 <% if User.current.allowed_to?(:manage_issue_relations, @project) %>
3 <%= toggle_link l(:button_add), 'new-relation-form', {:focus => 'relation_issue_to_id'} %>
3 <%= toggle_link l(:button_add), 'new-relation-form', {:focus => 'relation_issue_to_id'} %>
4 <% end %>
4 <% end %>
5 </div>
5 </div>
6
6
7 <p><strong><%=l(:label_related_issues)%></strong></p>
7 <p><strong><%=l(:label_related_issues)%></strong></p>
8
8
9 <% if @relations.present? %>
9 <% if @relations.present? %>
10 <form>
10 <form>
11 <table class="list issues">
11 <table class="list issues">
12 <% @relations.each do |relation| %>
12 <% @relations.each do |relation| %>
13 <% other_issue = relation.other_issue(@issue) -%>
13 <% other_issue = relation.other_issue(@issue) -%>
14 <tr class="issue hascontextmenu" id="relation-<%= relation.id %>">
14 <tr class="issue hascontextmenu" id="relation-<%= relation.id %>">
15 <td class="checkbox"><%= check_box_tag("ids[]", other_issue.id, false, :id => nil) %></td>
15 <td class="checkbox"><%= check_box_tag("ids[]", other_issue.id, false, :id => nil) %></td>
16 <td class="subject">
16 <td class="subject">
17 <%= l(relation.label_for(@issue)) %>
17 <%= relation.to_s(@issue) {|other| link_to_issue(other, :truncate => 60, :project => Setting.cross_project_issue_relations?)}.html_safe %>
18 <%= "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)})" if relation.delay && relation.delay != 0 %>
19 <%= h(other_issue.project) + ' - ' if Setting.cross_project_issue_relations? %>
20 <%= link_to_issue(other_issue, :truncate => 60) %>
21 </td>
18 </td>
22 <td class="status"><%=h other_issue.status.name %></td>
19 <td class="status"><%=h other_issue.status.name %></td>
23 <td class="start_date"><%= format_date(other_issue.start_date) %></td>
20 <td class="start_date"><%= format_date(other_issue.start_date) %></td>
24 <td class="due_date"><%= format_date(other_issue.due_date) %></td>
21 <td class="due_date"><%= format_date(other_issue.due_date) %></td>
25 <td class="buttons"><%= link_to image_tag('link_break.png'),
22 <td class="buttons"><%= link_to image_tag('link_break.png'),
26 relation_path(relation),
23 relation_path(relation),
27 :remote => true,
24 :remote => true,
28 :method => :delete,
25 :method => :delete,
29 :data => {:confirm => l(:text_are_you_sure)},
26 :data => {:confirm => l(:text_are_you_sure)},
30 :title => l(:label_relation_delete) if User.current.allowed_to?(:manage_issue_relations, @project) %></td>
27 :title => l(:label_relation_delete) if User.current.allowed_to?(:manage_issue_relations, @project) %></td>
31 </tr>
28 </tr>
32 <% end %>
29 <% end %>
33 </table>
30 </table>
34 </form>
31 </form>
35 <% end %>
32 <% end %>
36
33
37 <%= form_for @relation, {
34 <%= form_for @relation, {
38 :as => :relation, :remote => true,
35 :as => :relation, :remote => true,
39 :url => issue_relations_path(@issue),
36 :url => issue_relations_path(@issue),
40 :method => :post,
37 :method => :post,
41 :html => {:id => 'new-relation-form', :style => 'display: none;'}
38 :html => {:id => 'new-relation-form', :style => 'display: none;'}
42 } do |f| %>
39 } do |f| %>
43 <%= render :partial => 'issue_relations/form', :locals => {:f => f}%>
40 <%= render :partial => 'issue_relations/form', :locals => {:f => f}%>
44 <% end %>
41 <% end %>
@@ -1,217 +1,236
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class IssueRelationTest < ActiveSupport::TestCase
20 class IssueRelationTest < ActiveSupport::TestCase
21 fixtures :projects,
21 fixtures :projects,
22 :users,
22 :users,
23 :roles,
23 :roles,
24 :members,
24 :members,
25 :member_roles,
25 :member_roles,
26 :issues,
26 :issues,
27 :issue_statuses,
27 :issue_statuses,
28 :issue_relations,
28 :issue_relations,
29 :enabled_modules,
29 :enabled_modules,
30 :enumerations,
30 :enumerations,
31 :trackers,
31 :trackers,
32 :projects_trackers
32 :projects_trackers
33
33
34 include Redmine::I18n
34 include Redmine::I18n
35
35
36 def test_create
36 def test_create
37 from = Issue.find(1)
37 from = Issue.find(1)
38 to = Issue.find(2)
38 to = Issue.find(2)
39
39
40 relation = IssueRelation.new :issue_from => from, :issue_to => to,
40 relation = IssueRelation.new :issue_from => from, :issue_to => to,
41 :relation_type => IssueRelation::TYPE_PRECEDES
41 :relation_type => IssueRelation::TYPE_PRECEDES
42 assert relation.save
42 assert relation.save
43 relation.reload
43 relation.reload
44 assert_equal IssueRelation::TYPE_PRECEDES, relation.relation_type
44 assert_equal IssueRelation::TYPE_PRECEDES, relation.relation_type
45 assert_equal from, relation.issue_from
45 assert_equal from, relation.issue_from
46 assert_equal to, relation.issue_to
46 assert_equal to, relation.issue_to
47 end
47 end
48
48
49 def test_create_minimum
49 def test_create_minimum
50 relation = IssueRelation.new :issue_from => Issue.find(1), :issue_to => Issue.find(2)
50 relation = IssueRelation.new :issue_from => Issue.find(1), :issue_to => Issue.find(2)
51 assert relation.save
51 assert relation.save
52 assert_equal IssueRelation::TYPE_RELATES, relation.relation_type
52 assert_equal IssueRelation::TYPE_RELATES, relation.relation_type
53 end
53 end
54
54
55 def test_follows_relation_should_be_reversed
55 def test_follows_relation_should_be_reversed
56 from = Issue.find(1)
56 from = Issue.find(1)
57 to = Issue.find(2)
57 to = Issue.find(2)
58
58
59 relation = IssueRelation.new :issue_from => from, :issue_to => to,
59 relation = IssueRelation.new :issue_from => from, :issue_to => to,
60 :relation_type => IssueRelation::TYPE_FOLLOWS
60 :relation_type => IssueRelation::TYPE_FOLLOWS
61 assert relation.save
61 assert relation.save
62 relation.reload
62 relation.reload
63 assert_equal IssueRelation::TYPE_PRECEDES, relation.relation_type
63 assert_equal IssueRelation::TYPE_PRECEDES, relation.relation_type
64 assert_equal to, relation.issue_from
64 assert_equal to, relation.issue_from
65 assert_equal from, relation.issue_to
65 assert_equal from, relation.issue_to
66 end
66 end
67
67
68 def test_follows_relation_should_not_be_reversed_if_validation_fails
68 def test_follows_relation_should_not_be_reversed_if_validation_fails
69 from = Issue.find(1)
69 from = Issue.find(1)
70 to = Issue.find(2)
70 to = Issue.find(2)
71
71
72 relation = IssueRelation.new :issue_from => from, :issue_to => to,
72 relation = IssueRelation.new :issue_from => from, :issue_to => to,
73 :relation_type => IssueRelation::TYPE_FOLLOWS,
73 :relation_type => IssueRelation::TYPE_FOLLOWS,
74 :delay => 'xx'
74 :delay => 'xx'
75 assert !relation.save
75 assert !relation.save
76 assert_equal IssueRelation::TYPE_FOLLOWS, relation.relation_type
76 assert_equal IssueRelation::TYPE_FOLLOWS, relation.relation_type
77 assert_equal from, relation.issue_from
77 assert_equal from, relation.issue_from
78 assert_equal to, relation.issue_to
78 assert_equal to, relation.issue_to
79 end
79 end
80
80
81 def test_relation_type_for
81 def test_relation_type_for
82 from = Issue.find(1)
82 from = Issue.find(1)
83 to = Issue.find(2)
83 to = Issue.find(2)
84
84
85 relation = IssueRelation.new :issue_from => from, :issue_to => to,
85 relation = IssueRelation.new :issue_from => from, :issue_to => to,
86 :relation_type => IssueRelation::TYPE_PRECEDES
86 :relation_type => IssueRelation::TYPE_PRECEDES
87 assert_equal IssueRelation::TYPE_PRECEDES, relation.relation_type_for(from)
87 assert_equal IssueRelation::TYPE_PRECEDES, relation.relation_type_for(from)
88 assert_equal IssueRelation::TYPE_FOLLOWS, relation.relation_type_for(to)
88 assert_equal IssueRelation::TYPE_FOLLOWS, relation.relation_type_for(to)
89 end
89 end
90
90
91 def test_set_issue_to_dates_without_issue_to
91 def test_set_issue_to_dates_without_issue_to
92 r = IssueRelation.new(:issue_from => Issue.new(:start_date => Date.today),
92 r = IssueRelation.new(:issue_from => Issue.new(:start_date => Date.today),
93 :relation_type => IssueRelation::TYPE_PRECEDES,
93 :relation_type => IssueRelation::TYPE_PRECEDES,
94 :delay => 1)
94 :delay => 1)
95 assert_nil r.set_issue_to_dates
95 assert_nil r.set_issue_to_dates
96 end
96 end
97
97
98 def test_set_issue_to_dates_without_issues
98 def test_set_issue_to_dates_without_issues
99 r = IssueRelation.new(:relation_type => IssueRelation::TYPE_PRECEDES, :delay => 1)
99 r = IssueRelation.new(:relation_type => IssueRelation::TYPE_PRECEDES, :delay => 1)
100 assert_nil r.set_issue_to_dates
100 assert_nil r.set_issue_to_dates
101 end
101 end
102
102
103 def test_validates_circular_dependency
103 def test_validates_circular_dependency
104 IssueRelation.delete_all
104 IssueRelation.delete_all
105 assert IssueRelation.create!(
105 assert IssueRelation.create!(
106 :issue_from => Issue.find(1), :issue_to => Issue.find(2),
106 :issue_from => Issue.find(1), :issue_to => Issue.find(2),
107 :relation_type => IssueRelation::TYPE_PRECEDES
107 :relation_type => IssueRelation::TYPE_PRECEDES
108 )
108 )
109 assert IssueRelation.create!(
109 assert IssueRelation.create!(
110 :issue_from => Issue.find(2), :issue_to => Issue.find(3),
110 :issue_from => Issue.find(2), :issue_to => Issue.find(3),
111 :relation_type => IssueRelation::TYPE_PRECEDES
111 :relation_type => IssueRelation::TYPE_PRECEDES
112 )
112 )
113 r = IssueRelation.new(
113 r = IssueRelation.new(
114 :issue_from => Issue.find(3), :issue_to => Issue.find(1),
114 :issue_from => Issue.find(3), :issue_to => Issue.find(1),
115 :relation_type => IssueRelation::TYPE_PRECEDES
115 :relation_type => IssueRelation::TYPE_PRECEDES
116 )
116 )
117 assert !r.save
117 assert !r.save
118 assert_not_equal [], r.errors[:base]
118 assert_not_equal [], r.errors[:base]
119 end
119 end
120
120
121 def test_validates_circular_dependency_of_subtask
121 def test_validates_circular_dependency_of_subtask
122 set_language_if_valid 'en'
122 set_language_if_valid 'en'
123 issue1 = Issue.generate!
123 issue1 = Issue.generate!
124 issue2 = Issue.generate!
124 issue2 = Issue.generate!
125 IssueRelation.create!(
125 IssueRelation.create!(
126 :issue_from => issue1, :issue_to => issue2,
126 :issue_from => issue1, :issue_to => issue2,
127 :relation_type => IssueRelation::TYPE_PRECEDES
127 :relation_type => IssueRelation::TYPE_PRECEDES
128 )
128 )
129 child = Issue.generate!(:parent_issue_id => issue2.id)
129 child = Issue.generate!(:parent_issue_id => issue2.id)
130 issue1.reload
130 issue1.reload
131 child.reload
131 child.reload
132
132
133 r = IssueRelation.new(
133 r = IssueRelation.new(
134 :issue_from => child, :issue_to => issue1,
134 :issue_from => child, :issue_to => issue1,
135 :relation_type => IssueRelation::TYPE_PRECEDES
135 :relation_type => IssueRelation::TYPE_PRECEDES
136 )
136 )
137 assert !r.save
137 assert !r.save
138 assert_include 'This relation would create a circular dependency', r.errors.full_messages
138 assert_include 'This relation would create a circular dependency', r.errors.full_messages
139 end
139 end
140
140
141 def test_subtasks_should_allow_precedes_relation
141 def test_subtasks_should_allow_precedes_relation
142 parent = Issue.generate!
142 parent = Issue.generate!
143 child1 = Issue.generate!(:parent_issue_id => parent.id)
143 child1 = Issue.generate!(:parent_issue_id => parent.id)
144 child2 = Issue.generate!(:parent_issue_id => parent.id)
144 child2 = Issue.generate!(:parent_issue_id => parent.id)
145
145
146 r = IssueRelation.new(
146 r = IssueRelation.new(
147 :issue_from => child1, :issue_to => child2,
147 :issue_from => child1, :issue_to => child2,
148 :relation_type => IssueRelation::TYPE_PRECEDES
148 :relation_type => IssueRelation::TYPE_PRECEDES
149 )
149 )
150 assert r.valid?
150 assert r.valid?
151 assert r.save
151 assert r.save
152 end
152 end
153
153
154 def test_validates_circular_dependency_on_reverse_relations
154 def test_validates_circular_dependency_on_reverse_relations
155 IssueRelation.delete_all
155 IssueRelation.delete_all
156 assert IssueRelation.create!(
156 assert IssueRelation.create!(
157 :issue_from => Issue.find(1), :issue_to => Issue.find(3),
157 :issue_from => Issue.find(1), :issue_to => Issue.find(3),
158 :relation_type => IssueRelation::TYPE_BLOCKS
158 :relation_type => IssueRelation::TYPE_BLOCKS
159 )
159 )
160 assert IssueRelation.create!(
160 assert IssueRelation.create!(
161 :issue_from => Issue.find(1), :issue_to => Issue.find(2),
161 :issue_from => Issue.find(1), :issue_to => Issue.find(2),
162 :relation_type => IssueRelation::TYPE_BLOCKED
162 :relation_type => IssueRelation::TYPE_BLOCKED
163 )
163 )
164 r = IssueRelation.new(
164 r = IssueRelation.new(
165 :issue_from => Issue.find(2), :issue_to => Issue.find(1),
165 :issue_from => Issue.find(2), :issue_to => Issue.find(1),
166 :relation_type => IssueRelation::TYPE_BLOCKED
166 :relation_type => IssueRelation::TYPE_BLOCKED
167 )
167 )
168 assert !r.save
168 assert !r.save
169 assert_not_equal [], r.errors[:base]
169 assert_not_equal [], r.errors[:base]
170 end
170 end
171
171
172 def test_create_with_initialized_journals_should_create_journals
172 def test_create_with_initialized_journals_should_create_journals
173 from = Issue.find(1)
173 from = Issue.find(1)
174 to = Issue.find(2)
174 to = Issue.find(2)
175 from_journals = from.journals.size
175 from_journals = from.journals.size
176 to_journals = to.journals.size
176 to_journals = to.journals.size
177 relation = IssueRelation.new(:issue_from => from, :issue_to => to,
177 relation = IssueRelation.new(:issue_from => from, :issue_to => to,
178 :relation_type => IssueRelation::TYPE_PRECEDES)
178 :relation_type => IssueRelation::TYPE_PRECEDES)
179 relation.init_journals User.find(1)
179 relation.init_journals User.find(1)
180 assert relation.save
180 assert relation.save
181 from.reload
181 from.reload
182 to.reload
182 to.reload
183 relation.reload
183 relation.reload
184 assert_equal from.journals.size, (from_journals + 1)
184 assert_equal from.journals.size, (from_journals + 1)
185 assert_equal to.journals.size, (to_journals + 1)
185 assert_equal to.journals.size, (to_journals + 1)
186 assert_equal 'relation', from.journals.last.details.last.property
186 assert_equal 'relation', from.journals.last.details.last.property
187 assert_equal 'precedes', from.journals.last.details.last.prop_key
187 assert_equal 'precedes', from.journals.last.details.last.prop_key
188 assert_equal '2', from.journals.last.details.last.value
188 assert_equal '2', from.journals.last.details.last.value
189 assert_nil from.journals.last.details.last.old_value
189 assert_nil from.journals.last.details.last.old_value
190 assert_equal 'relation', to.journals.last.details.last.property
190 assert_equal 'relation', to.journals.last.details.last.property
191 assert_equal 'follows', to.journals.last.details.last.prop_key
191 assert_equal 'follows', to.journals.last.details.last.prop_key
192 assert_equal '1', to.journals.last.details.last.value
192 assert_equal '1', to.journals.last.details.last.value
193 assert_nil to.journals.last.details.last.old_value
193 assert_nil to.journals.last.details.last.old_value
194 end
194 end
195
195
196 def test_destroy_with_initialized_journals_should_create_journals
196 def test_destroy_with_initialized_journals_should_create_journals
197 relation = IssueRelation.find(1)
197 relation = IssueRelation.find(1)
198 from = relation.issue_from
198 from = relation.issue_from
199 to = relation.issue_to
199 to = relation.issue_to
200 from_journals = from.journals.size
200 from_journals = from.journals.size
201 to_journals = to.journals.size
201 to_journals = to.journals.size
202 relation.init_journals User.find(1)
202 relation.init_journals User.find(1)
203 assert relation.destroy
203 assert relation.destroy
204 from.reload
204 from.reload
205 to.reload
205 to.reload
206 assert_equal from.journals.size, (from_journals + 1)
206 assert_equal from.journals.size, (from_journals + 1)
207 assert_equal to.journals.size, (to_journals + 1)
207 assert_equal to.journals.size, (to_journals + 1)
208 assert_equal 'relation', from.journals.last.details.last.property
208 assert_equal 'relation', from.journals.last.details.last.property
209 assert_equal 'blocks', from.journals.last.details.last.prop_key
209 assert_equal 'blocks', from.journals.last.details.last.prop_key
210 assert_equal '9', from.journals.last.details.last.old_value
210 assert_equal '9', from.journals.last.details.last.old_value
211 assert_nil from.journals.last.details.last.value
211 assert_nil from.journals.last.details.last.value
212 assert_equal 'relation', to.journals.last.details.last.property
212 assert_equal 'relation', to.journals.last.details.last.property
213 assert_equal 'blocked', to.journals.last.details.last.prop_key
213 assert_equal 'blocked', to.journals.last.details.last.prop_key
214 assert_equal '10', to.journals.last.details.last.old_value
214 assert_equal '10', to.journals.last.details.last.old_value
215 assert_nil to.journals.last.details.last.value
215 assert_nil to.journals.last.details.last.value
216 end
216 end
217
218 def test_to_s_should_return_the_relation_string
219 set_language_if_valid 'en'
220 relation = IssueRelation.find(1)
221 assert_equal "Blocks #9", relation.to_s(relation.issue_from)
222 assert_equal "Blocked by #10", relation.to_s(relation.issue_to)
223 end
224
225 def test_to_s_without_argument_should_return_the_relation_string_for_issue_from
226 set_language_if_valid 'en'
227 relation = IssueRelation.find(1)
228 assert_equal "Blocks #9", relation.to_s
229 end
230
231 def test_to_s_should_accept_a_block_as_custom_issue_formatting
232 set_language_if_valid 'en'
233 relation = IssueRelation.find(1)
234 assert_equal "Blocks Bug #9", relation.to_s {|issue| "#{issue.tracker} ##{issue.id}"}
235 end
217 end
236 end
General Comments 0
You need to be logged in to leave comments. Login now