Chris@0: # Redmine - project management software Chris@1494: # Copyright (C) 2006-2014 Jean-Philippe Lang Chris@0: # Chris@0: # This program is free software; you can redistribute it and/or Chris@0: # modify it under the terms of the GNU General Public License Chris@0: # as published by the Free Software Foundation; either version 2 Chris@0: # of the License, or (at your option) any later version. Chris@909: # Chris@0: # This program is distributed in the hope that it will be useful, Chris@0: # but WITHOUT ANY WARRANTY; without even the implied warranty of Chris@0: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Chris@0: # GNU General Public License for more details. Chris@909: # Chris@0: # You should have received a copy of the GNU General Public License Chris@0: # along with this program; if not, write to the Free Software Chris@0: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Chris@0: Chris@119: require File.expand_path('../../test_helper', __FILE__) Chris@0: Chris@0: class ProjectNestedSetTest < ActiveSupport::TestCase Chris@909: Chris@1115: def setup Chris@1115: Project.delete_all Chris@119: Chris@1115: @a = Project.create!(:name => 'A', :identifier => 'projecta') Chris@1115: @a1 = Project.create!(:name => 'A1', :identifier => 'projecta1') Chris@1115: @a1.set_parent!(@a) Chris@1115: @a2 = Project.create!(:name => 'A2', :identifier => 'projecta2') Chris@1115: @a2.set_parent!(@a) Chris@909: Chris@1115: @c = Project.create!(:name => 'C', :identifier => 'projectc') Chris@1115: @c1 = Project.create!(:name => 'C1', :identifier => 'projectc1') Chris@1115: @c1.set_parent!(@c) Chris@909: Chris@1115: @b = Project.create!(:name => 'B', :identifier => 'projectb') Chris@1115: @b2 = Project.create!(:name => 'B2', :identifier => 'projectb2') Chris@1115: @b2.set_parent!(@b) Chris@1115: @b1 = Project.create!(:name => 'B1', :identifier => 'projectb1') Chris@1115: @b1.set_parent!(@b) Chris@1115: @b11 = Project.create!(:name => 'B11', :identifier => 'projectb11') Chris@1115: @b11.set_parent!(@b1) Chris@909: Chris@1115: @a, @a1, @a2, @b, @b1, @b11, @b2, @c, @c1 = *(Project.all.sort_by(&:name)) Chris@1115: end Chris@1115: Chris@1115: def test_valid_tree Chris@1115: assert_valid_nested_set Chris@1115: end Chris@1115: Chris@1115: def test_rebuild_should_build_valid_tree Chris@1115: Project.update_all "lft = NULL, rgt = NULL" Chris@1115: Chris@1517: Project.rebuild_tree! Chris@1115: assert_valid_nested_set Chris@1115: end Chris@1115: Chris@1115: def test_rebuild_tree_should_build_valid_tree_even_with_valid_lft_rgt_values Chris@1517: Project.where({:id => @a.id }).update_all("name = 'YY'") Chris@1115: # lft and rgt values are still valid (Project.rebuild! would not update anything) Chris@1115: # but projects are not ordered properly (YY is in the first place) Chris@1115: Chris@1115: Project.rebuild_tree! Chris@1115: assert_valid_nested_set Chris@1115: end Chris@1115: Chris@1115: def test_moving_a_child_to_a_different_parent_should_keep_valid_tree Chris@1115: assert_no_difference 'Project.count' do Chris@1115: Project.find_by_name('B1').set_parent!(Project.find_by_name('A2')) Chris@119: end Chris@1115: assert_valid_nested_set Chris@1115: end Chris@909: Chris@1115: def test_renaming_a_root_to_first_position_should_update_nested_set_order Chris@1115: @c.name = '1' Chris@1115: @c.save! Chris@1115: assert_valid_nested_set Chris@1115: end Chris@1115: Chris@1115: def test_renaming_a_root_to_middle_position_should_update_nested_set_order Chris@1115: @a.name = 'BA' Chris@1115: @a.save! Chris@1115: assert_valid_nested_set Chris@1115: end Chris@1115: Chris@1115: def test_renaming_a_root_to_last_position_should_update_nested_set_order Chris@1115: @a.name = 'D' Chris@1115: @a.save! Chris@1115: assert_valid_nested_set Chris@1115: end Chris@1115: Chris@1115: def test_renaming_a_root_to_same_position_should_update_nested_set_order Chris@1115: @c.name = 'D' Chris@1115: @c.save! Chris@1115: assert_valid_nested_set Chris@1115: end Chris@1115: Chris@1115: def test_renaming_a_child_should_update_nested_set_order Chris@1115: @a1.name = 'A3' Chris@1115: @a1.save! Chris@1115: assert_valid_nested_set Chris@1115: end Chris@1115: Chris@1115: def test_renaming_a_child_with_child_should_update_nested_set_order Chris@1115: @b1.name = 'B3' Chris@1115: @b1.save! Chris@1115: assert_valid_nested_set Chris@1115: end Chris@1115: Chris@1115: def test_adding_a_root_to_first_position_should_update_nested_set_order Chris@1115: project = Project.create!(:name => '1', :identifier => 'projectba') Chris@1115: assert_valid_nested_set Chris@1115: end Chris@1115: Chris@1115: def test_adding_a_root_to_middle_position_should_update_nested_set_order Chris@1115: project = Project.create!(:name => 'BA', :identifier => 'projectba') Chris@1115: assert_valid_nested_set Chris@1115: end Chris@1115: Chris@1115: def test_adding_a_root_to_last_position_should_update_nested_set_order Chris@1115: project = Project.create!(:name => 'Z', :identifier => 'projectba') Chris@1115: assert_valid_nested_set Chris@1115: end Chris@1115: Chris@1115: def test_destroying_a_root_with_children_should_keep_valid_tree Chris@1115: assert_difference 'Project.count', -4 do Chris@1115: Project.find_by_name('B').destroy Chris@119: end Chris@1115: assert_valid_nested_set Chris@1115: end Chris@909: Chris@1115: def test_destroying_a_child_with_children_should_keep_valid_tree Chris@1115: assert_difference 'Project.count', -2 do Chris@1115: Project.find_by_name('B1').destroy Chris@119: end Chris@1115: assert_valid_nested_set Chris@1115: end Chris@909: Chris@1115: private Chris@909: Chris@119: def assert_nested_set_values(h) Chris@119: assert Project.valid? Chris@119: h.each do |project, expected| Chris@119: project.reload Chris@119: assert_equal expected, [project.parent_id, project.lft, project.rgt], "Unexpected nested set values for #{project.name}" Chris@0: end Chris@0: end Chris@1115: Chris@1115: def assert_valid_nested_set Chris@1115: projects = Project.all Chris@1115: lft_rgt = projects.map {|p| [p.lft, p.rgt]}.flatten Chris@1115: assert_equal projects.size * 2, lft_rgt.uniq.size Chris@1115: assert_equal 1, lft_rgt.min Chris@1115: assert_equal projects.size * 2, lft_rgt.max Chris@1115: Chris@1115: projects.each do |project| Chris@1115: # lft should always be < rgt Chris@1115: assert project.lft < project.rgt, "lft=#{project.lft} was not < rgt=#{project.rgt} for project #{project.name}" Chris@1115: if project.parent_id Chris@1115: # child lft/rgt values must be greater/lower Chris@1115: assert_not_nil project.parent, "parent was nil for project #{project.name}" Chris@1115: assert project.lft > project.parent.lft, "lft=#{project.lft} was not > parent.lft=#{project.parent.lft} for project #{project.name}" Chris@1115: assert project.rgt < project.parent.rgt, "rgt=#{project.rgt} was not < parent.rgt=#{project.parent.rgt} for project #{project.name}" Chris@1115: end Chris@1115: # no overlapping lft/rgt values Chris@1115: overlapping = projects.detect {|other| Chris@1115: other != project && ( Chris@1115: (other.lft > project.lft && other.lft < project.rgt && other.rgt > project.rgt) || Chris@1115: (other.rgt > project.lft && other.rgt < project.rgt && other.lft < project.lft) Chris@1115: ) Chris@1115: } Chris@1115: assert_nil overlapping, (overlapping && "Project #{overlapping.name} (#{overlapping.lft}/#{overlapping.rgt}) overlapped #{project.name} (#{project.lft}/#{project.rgt})") Chris@1115: end Chris@1115: Chris@1115: # root projects sorted alphabetically Chris@1115: assert_equal Project.roots.map(&:name).sort, Project.roots.sort_by(&:lft).map(&:name), "Root projects were not properly sorted" Chris@1115: projects.each do |project| Chris@1115: if project.children.any? Chris@1115: # sibling projects sorted alphabetically Chris@1115: assert_equal project.children.map(&:name).sort, project.children.order('lft').map(&:name), "Project #{project.name}'s children were not properly sorted" Chris@1115: end Chris@1115: end Chris@1115: end Chris@119: end