You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

project_nested_set_test.rb 6.5KB

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