Chris@0
|
1 # Redmine - project management software
|
Chris@1494
|
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
|
Chris@0
|
3 #
|
Chris@0
|
4 # This program is free software; you can redistribute it and/or
|
Chris@0
|
5 # modify it under the terms of the GNU General Public License
|
Chris@0
|
6 # as published by the Free Software Foundation; either version 2
|
Chris@0
|
7 # of the License, or (at your option) any later version.
|
Chris@909
|
8 #
|
Chris@0
|
9 # This program is distributed in the hope that it will be useful,
|
Chris@0
|
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Chris@0
|
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
Chris@0
|
12 # GNU General Public License for more details.
|
Chris@909
|
13 #
|
Chris@0
|
14 # You should have received a copy of the GNU General Public License
|
Chris@0
|
15 # along with this program; if not, write to the Free Software
|
Chris@0
|
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
Chris@0
|
17
|
Chris@119
|
18 require File.expand_path('../../test_helper', __FILE__)
|
Chris@0
|
19
|
Chris@0
|
20 class ProjectNestedSetTest < ActiveSupport::TestCase
|
Chris@909
|
21
|
Chris@1115
|
22 def setup
|
Chris@1115
|
23 Project.delete_all
|
Chris@119
|
24
|
Chris@1115
|
25 @a = Project.create!(:name => 'A', :identifier => 'projecta')
|
Chris@1115
|
26 @a1 = Project.create!(:name => 'A1', :identifier => 'projecta1')
|
Chris@1115
|
27 @a1.set_parent!(@a)
|
Chris@1115
|
28 @a2 = Project.create!(:name => 'A2', :identifier => 'projecta2')
|
Chris@1115
|
29 @a2.set_parent!(@a)
|
Chris@909
|
30
|
Chris@1115
|
31 @c = Project.create!(:name => 'C', :identifier => 'projectc')
|
Chris@1115
|
32 @c1 = Project.create!(:name => 'C1', :identifier => 'projectc1')
|
Chris@1115
|
33 @c1.set_parent!(@c)
|
Chris@909
|
34
|
Chris@1115
|
35 @b = Project.create!(:name => 'B', :identifier => 'projectb')
|
Chris@1115
|
36 @b2 = Project.create!(:name => 'B2', :identifier => 'projectb2')
|
Chris@1115
|
37 @b2.set_parent!(@b)
|
Chris@1115
|
38 @b1 = Project.create!(:name => 'B1', :identifier => 'projectb1')
|
Chris@1115
|
39 @b1.set_parent!(@b)
|
Chris@1115
|
40 @b11 = Project.create!(:name => 'B11', :identifier => 'projectb11')
|
Chris@1115
|
41 @b11.set_parent!(@b1)
|
Chris@909
|
42
|
Chris@1115
|
43 @a, @a1, @a2, @b, @b1, @b11, @b2, @c, @c1 = *(Project.all.sort_by(&:name))
|
Chris@1115
|
44 end
|
Chris@1115
|
45
|
Chris@1115
|
46 def test_valid_tree
|
Chris@1115
|
47 assert_valid_nested_set
|
Chris@1115
|
48 end
|
Chris@1115
|
49
|
Chris@1115
|
50 def test_rebuild_should_build_valid_tree
|
Chris@1115
|
51 Project.update_all "lft = NULL, rgt = NULL"
|
Chris@1115
|
52
|
Chris@1115
|
53 Project.rebuild!
|
Chris@1115
|
54 assert_valid_nested_set
|
Chris@1115
|
55 end
|
Chris@1115
|
56
|
Chris@1115
|
57 def test_rebuild_tree_should_build_valid_tree_even_with_valid_lft_rgt_values
|
Chris@1115
|
58 Project.update_all "name = 'YY'", {:id => @a.id }
|
Chris@1115
|
59 # lft and rgt values are still valid (Project.rebuild! would not update anything)
|
Chris@1115
|
60 # but projects are not ordered properly (YY is in the first place)
|
Chris@1115
|
61
|
Chris@1115
|
62 Project.rebuild_tree!
|
Chris@1115
|
63 assert_valid_nested_set
|
Chris@1115
|
64 end
|
Chris@1115
|
65
|
Chris@1115
|
66 def test_moving_a_child_to_a_different_parent_should_keep_valid_tree
|
Chris@1115
|
67 assert_no_difference 'Project.count' do
|
Chris@1115
|
68 Project.find_by_name('B1').set_parent!(Project.find_by_name('A2'))
|
Chris@119
|
69 end
|
Chris@1115
|
70 assert_valid_nested_set
|
Chris@1115
|
71 end
|
Chris@909
|
72
|
Chris@1115
|
73 def test_renaming_a_root_to_first_position_should_update_nested_set_order
|
Chris@1115
|
74 @c.name = '1'
|
Chris@1115
|
75 @c.save!
|
Chris@1115
|
76 assert_valid_nested_set
|
Chris@1115
|
77 end
|
Chris@1115
|
78
|
Chris@1115
|
79 def test_renaming_a_root_to_middle_position_should_update_nested_set_order
|
Chris@1115
|
80 @a.name = 'BA'
|
Chris@1115
|
81 @a.save!
|
Chris@1115
|
82 assert_valid_nested_set
|
Chris@1115
|
83 end
|
Chris@1115
|
84
|
Chris@1115
|
85 def test_renaming_a_root_to_last_position_should_update_nested_set_order
|
Chris@1115
|
86 @a.name = 'D'
|
Chris@1115
|
87 @a.save!
|
Chris@1115
|
88 assert_valid_nested_set
|
Chris@1115
|
89 end
|
Chris@1115
|
90
|
Chris@1115
|
91 def test_renaming_a_root_to_same_position_should_update_nested_set_order
|
Chris@1115
|
92 @c.name = 'D'
|
Chris@1115
|
93 @c.save!
|
Chris@1115
|
94 assert_valid_nested_set
|
Chris@1115
|
95 end
|
Chris@1115
|
96
|
Chris@1115
|
97 def test_renaming_a_child_should_update_nested_set_order
|
Chris@1115
|
98 @a1.name = 'A3'
|
Chris@1115
|
99 @a1.save!
|
Chris@1115
|
100 assert_valid_nested_set
|
Chris@1115
|
101 end
|
Chris@1115
|
102
|
Chris@1115
|
103 def test_renaming_a_child_with_child_should_update_nested_set_order
|
Chris@1115
|
104 @b1.name = 'B3'
|
Chris@1115
|
105 @b1.save!
|
Chris@1115
|
106 assert_valid_nested_set
|
Chris@1115
|
107 end
|
Chris@1115
|
108
|
Chris@1115
|
109 def test_adding_a_root_to_first_position_should_update_nested_set_order
|
Chris@1115
|
110 project = Project.create!(:name => '1', :identifier => 'projectba')
|
Chris@1115
|
111 assert_valid_nested_set
|
Chris@1115
|
112 end
|
Chris@1115
|
113
|
Chris@1115
|
114 def test_adding_a_root_to_middle_position_should_update_nested_set_order
|
Chris@1115
|
115 project = Project.create!(:name => 'BA', :identifier => 'projectba')
|
Chris@1115
|
116 assert_valid_nested_set
|
Chris@1115
|
117 end
|
Chris@1115
|
118
|
Chris@1115
|
119 def test_adding_a_root_to_last_position_should_update_nested_set_order
|
Chris@1115
|
120 project = Project.create!(:name => 'Z', :identifier => 'projectba')
|
Chris@1115
|
121 assert_valid_nested_set
|
Chris@1115
|
122 end
|
Chris@1115
|
123
|
Chris@1115
|
124 def test_destroying_a_root_with_children_should_keep_valid_tree
|
Chris@1115
|
125 assert_difference 'Project.count', -4 do
|
Chris@1115
|
126 Project.find_by_name('B').destroy
|
Chris@119
|
127 end
|
Chris@1115
|
128 assert_valid_nested_set
|
Chris@1115
|
129 end
|
Chris@909
|
130
|
Chris@1115
|
131 def test_destroying_a_child_with_children_should_keep_valid_tree
|
Chris@1115
|
132 assert_difference 'Project.count', -2 do
|
Chris@1115
|
133 Project.find_by_name('B1').destroy
|
Chris@119
|
134 end
|
Chris@1115
|
135 assert_valid_nested_set
|
Chris@1115
|
136 end
|
Chris@909
|
137
|
Chris@1115
|
138 private
|
Chris@909
|
139
|
Chris@119
|
140 def assert_nested_set_values(h)
|
Chris@119
|
141 assert Project.valid?
|
Chris@119
|
142 h.each do |project, expected|
|
Chris@119
|
143 project.reload
|
Chris@119
|
144 assert_equal expected, [project.parent_id, project.lft, project.rgt], "Unexpected nested set values for #{project.name}"
|
Chris@0
|
145 end
|
Chris@0
|
146 end
|
Chris@1115
|
147
|
Chris@1115
|
148 def assert_valid_nested_set
|
Chris@1115
|
149 projects = Project.all
|
Chris@1115
|
150 lft_rgt = projects.map {|p| [p.lft, p.rgt]}.flatten
|
Chris@1115
|
151 assert_equal projects.size * 2, lft_rgt.uniq.size
|
Chris@1115
|
152 assert_equal 1, lft_rgt.min
|
Chris@1115
|
153 assert_equal projects.size * 2, lft_rgt.max
|
Chris@1115
|
154
|
Chris@1115
|
155 projects.each do |project|
|
Chris@1115
|
156 # lft should always be < rgt
|
Chris@1115
|
157 assert project.lft < project.rgt, "lft=#{project.lft} was not < rgt=#{project.rgt} for project #{project.name}"
|
Chris@1115
|
158 if project.parent_id
|
Chris@1115
|
159 # child lft/rgt values must be greater/lower
|
Chris@1115
|
160 assert_not_nil project.parent, "parent was nil for project #{project.name}"
|
Chris@1115
|
161 assert project.lft > project.parent.lft, "lft=#{project.lft} was not > parent.lft=#{project.parent.lft} for project #{project.name}"
|
Chris@1115
|
162 assert project.rgt < project.parent.rgt, "rgt=#{project.rgt} was not < parent.rgt=#{project.parent.rgt} for project #{project.name}"
|
Chris@1115
|
163 end
|
Chris@1115
|
164 # no overlapping lft/rgt values
|
Chris@1115
|
165 overlapping = projects.detect {|other|
|
Chris@1115
|
166 other != project && (
|
Chris@1115
|
167 (other.lft > project.lft && other.lft < project.rgt && other.rgt > project.rgt) ||
|
Chris@1115
|
168 (other.rgt > project.lft && other.rgt < project.rgt && other.lft < project.lft)
|
Chris@1115
|
169 )
|
Chris@1115
|
170 }
|
Chris@1115
|
171 assert_nil overlapping, (overlapping && "Project #{overlapping.name} (#{overlapping.lft}/#{overlapping.rgt}) overlapped #{project.name} (#{project.lft}/#{project.rgt})")
|
Chris@1115
|
172 end
|
Chris@1115
|
173
|
Chris@1115
|
174 # root projects sorted alphabetically
|
Chris@1115
|
175 assert_equal Project.roots.map(&:name).sort, Project.roots.sort_by(&:lft).map(&:name), "Root projects were not properly sorted"
|
Chris@1115
|
176 projects.each do |project|
|
Chris@1115
|
177 if project.children.any?
|
Chris@1115
|
178 # sibling projects sorted alphabetically
|
Chris@1115
|
179 assert_equal project.children.map(&:name).sort, project.children.order('lft').map(&:name), "Project #{project.name}'s children were not properly sorted"
|
Chris@1115
|
180 end
|
Chris@1115
|
181 end
|
Chris@1115
|
182 end
|
Chris@119
|
183 end
|