Index: app/controllers/issue_categories_controller.rb =================================================================== --- app/controllers/issue_categories_controller.rb (revision 11475) +++ app/controllers/issue_categories_controller.rb (working copy) @@ -95,7 +95,7 @@ if @issue_count == 0 || params[:todo] || api_request? reassign_to = nil if params[:reassign_to_id] && (params[:todo] == 'reassign' || params[:todo].blank?) - reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id]) + reassign_to = IssueCategory.find_by_id(params[:reassign_to_id]) end @category.destroy(reassign_to) respond_to do |format| @@ -104,7 +104,16 @@ end return end - @categories = @project.issue_categories - [@category] + old = nil + @categories = @project.inherited_categories(true).reject { |cat| + if cat.id == @category.id then + true + else + cr = (old == cat.name) + old = cat.name + cr + end + } end private Index: app/controllers/projects_controller.rb =================================================================== --- app/controllers/projects_controller.rb (revision 11475) +++ app/controllers/projects_controller.rb (working copy) @@ -182,7 +182,12 @@ def update @project.safe_attributes = params[:project] if validate_parent_id && @project.save - @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') + new_inherit = params[:project].has_key?('inherit') ? params[:project]['inherit'] : nil + if new_inherit == "1" then new_inherit = true + elsif new_inherit == "0" then new_inherit = false + end + new_parent = params[:project].has_key?('parent_id') ? params[:project]['parent_id'] : @project.parent + @project.set_allowed_parent!(new_parent, new_inherit) if (params[:project].has_key?('parent_id') || params[:project].has_key?('inherit')) respond_to do |format| format.html { flash[:notice] = l(:notice_successful_update) Index: app/helpers/application_helper.rb =================================================================== --- app/helpers/application_helper.rb (revision 11475) +++ app/helpers/application_helper.rb (working copy) @@ -1284,4 +1284,14 @@ def link_to_content_update(text, url_params = {}, html_options = {}) link_to(text, url_params, html_options) end + + def format_project(proj, list) + resul = '' + list.reverse.each { |p| + break if p == proj + resul += "#{p.name} » " + } + resul += proj.name + end + end Index: app/helpers/issue_categories_helper.rb =================================================================== --- app/helpers/issue_categories_helper.rb (revision 11475) +++ app/helpers/issue_categories_helper.rb (working copy) @@ -18,4 +18,14 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module IssueCategoriesHelper + def options_for_reassign(categ, categs, proj) + projs = proj.inherited_projects + cats = categs.sort { |a, b| + (a.project_id == b.project_id) ? (a.name <=> b.name) : (a.project.rgt <=> b.project.rgt) + } + return options_for_select(cats.map { |cat| + ["#{cat.name} (#{format_project cat.project, projs})", cat.id] + }) + end + end Index: app/models/issue_category.rb =================================================================== --- app/models/issue_category.rb (revision 11475) +++ app/models/issue_category.rb (working copy) @@ -34,7 +34,7 @@ # Destroy the category # If a category is specified, issues are reassigned to this category def destroy(reassign_to = nil) - if reassign_to && reassign_to.is_a?(IssueCategory) && reassign_to.project == self.project + if reassign_to && reassign_to.is_a?(IssueCategory) && (self.project.inherited_projects.include?(reassign_to.project) || (self.project == reassign_to.project)) Issue.update_all("category_id = #{reassign_to.id}", "category_id = #{id}") end destroy_without_reassign Index: app/models/project.rb =================================================================== --- app/models/project.rb (revision 11475) +++ app/models/project.rb (working copy) @@ -127,6 +127,34 @@ end end + # walks ancestors up to the first that doesn't inherit from his parent + def inherited_projects + l = [self] + self_and_ancestors.reverse.each { |p| + if p.inherit and ! p.parent.nil? + l << p.parent + else + break + end + } + return l + end + + # get inherited categories (all = false) or all categories from inherited + # projects if all is true + def inherited_categories(all = false) + categs = IssueCategory.find(:all , + :joins => :project, + :conditions => { :project_id => inherited_projects }, + :order => "name, #{Project.table_name}.rgt") + old = nil + # a category from a deep project masks categories of same name from others + return all ? categs : categs.reject { |c| cr = (c.name == old) + old = c.name + cr + } + end + def identifier=(identifier) super unless identifier_frozen? end @@ -350,7 +378,7 @@ end # Sets the parent of the project with authorization check - def set_allowed_parent!(p) + def set_allowed_parent!(p, i = nil) unless p.nil? || p.is_a?(Project) if p.to_s.blank? p = nil @@ -366,12 +394,13 @@ elsif !allowed_parents.include?(p) return false end - set_parent!(p) + set_parent!(p, i) end # Sets the parent of the project # Argument can be either a Project, a String, a Fixnum or nil - def set_parent!(p) + def set_parent!(p, i = nil) + i = self.inherit if i.nil? unless p.nil? || p.is_a?(Project) if p.to_s.blank? p = nil @@ -380,10 +409,16 @@ return false unless p end end + i = false if p.nil? if p == parent && !p.nil? - # Nothing to do + adjust_inheritance((self.inherit ? self.parent : nil), i ? p : nil) + Project.update(self.id, :inherit => i) unless (i == self.inherit) + self.inherit = i true elsif p.nil? || (p.active? && move_possible?(p)) + adjust_inheritance((self.inherit ? self.parent : nil), i ? p : nil) + Project.update(self.id, :inherit => i) unless (i == self.inherit) + self.inherit = i set_or_update_position_under(p) Issue.update_versions_from_hierarchy_change(self) true @@ -973,4 +1008,32 @@ move_to_child_of(target_parent) end end + + # adjusts inherited issue categories when inherit is set to false or + # when parent is changed - new and old are direct inherited projects + # they are nil if inherit is false + # For each lost category, a categorie of same name is created in current + # project, and issues are reaffected + def adjust_inheritance(old, new) + if (inherit && new.nil?) then + self.inherit = false + Project.update(id, {:inherit => false}) + end + return if old.nil? + lost_categories = old.inherited_categories + lost_categories -= new.inherited_categories if ! new.nil? + subprojs = self_and_descendants + Issue.transaction do + lost_categories.each { |cat| + if ! Issue.find(:all, + :joins => :project, + :conditions => [ "category_id = ? and #{Project.table_name}.lft > ? and #{Project.table_name}.rgt < ?", cat.id, self.lft, self.rgt] + ).empty? then + newcat=IssueCategory.create!(:name => cat.name, :project_id => self.id) + n = Issue.update_all({:category_id => newcat.id}, + { :category_id => cat, :project_id => subprojs}) + end + } + end + end end Index: app/views/issue_categories/destroy.html.erb =================================================================== --- app/views/issue_categories/destroy.html.erb (revision 11475) +++ app/views/issue_categories/destroy.html.erb (working copy) @@ -7,7 +7,10 @@ <% if @categories.size > 0 %> : <%= label_tag "reassign_to_id", l(:description_issue_category_reassign), :class => "hidden-for-sighted" %> -<%= select_tag 'reassign_to_id', options_from_collection_for_select(@categories, 'id', 'name') %>

+ +<%= select_tag 'reassign_to_id', options_for_reassign(@category, @categories, @project) %> +

+ <% end %> Index: app/views/issues/_attributes.html.erb =================================================================== --- app/views/issues/_attributes.html.erb (revision 11475) +++ app/views/issues/_attributes.html.erb (working copy) @@ -18,8 +18,8 @@

<%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true, :required => @issue.required_attribute?('assigned_to_id') %>

<% end %> -<% if @issue.safe_attribute?('category_id') && @issue.project.issue_categories.any? %> -

<%= f.select :category_id, (@issue.project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true, :required => @issue.required_attribute?('category_id') %> +<% if @issue.safe_attribute?('category_id') && @issue.project.inherited_categories.any? %> +

<%= f.select :category_id, (@issue.project.inherited_categories.collect {|c| [c.name, c.id]}), :include_blank => true, :required => @issue.required_attribute?('category_id') %> <%= link_to(image_tag('add.png', :style => 'vertical-align: middle;'), new_project_issue_category_path(@issue.project), :remote => true, Index: app/views/projects/_form.html.erb =================================================================== --- app/views/projects/_form.html.erb (revision 11475) +++ app/views/projects/_form.html.erb (working copy) @@ -6,6 +6,7 @@ <% unless @project.allowed_parents.compact.empty? %>

<%= label(:project, :parent_id, l(:field_parent)) %><%= parent_project_select_tag(@project) %>

+

<%= f.check_box :inherit %>

<% end %>

<%= f.text_area :description, :rows => 5, :class => 'wiki-edit' %>

Index: app/views/projects/settings/_issue_categories.html.erb =================================================================== --- app/views/projects/settings/_issue_categories.html.erb (revision 11475) +++ app/views/projects/settings/_issue_categories.html.erb (working copy) @@ -25,5 +25,32 @@ <% else %>

<%= l(:label_no_data) %>

<% end %> +

<%= link_to l(:label_issue_category_new), new_project_issue_category_path(@project), :class => 'icon icon-add' if User.current.allowed_to?(:manage_categories, @project) %>

+<% strict_inherited = (@project.inherited_categories(true) - @project.issue_categories) %> +<% if ! strict_inherited.empty? %> + <% masked = false %> + <% inherited = @project.inherited_categories %> +

<%= l(:text_issue_categories_inherited) %>

+ + + + + + <% projs = @project.inherited_projects; projs.shift %> + <% for p in projs %> + <% for c in p.issue_categories %> + + + + + <% end %> + <% end %> + +
<%= l(:label_project) %><%= l(:label_issue_category) %>
<%= format_project(p, projs) %><%= c.name %> + <% if ! inherited.include?(c) %>*<% masked = true %><% end %> +
+ <% if masked %> +

* : <%= l(:text_issue_category_masked) %>

+ <% end %> +<% end %> -

<%= link_to l(:label_issue_category_new), new_project_issue_category_path(@project), :class => 'icon icon-add' if User.current.allowed_to?(:manage_categories, @project) %>

Index: config/locales/en.yml =================================================================== --- config/locales/en.yml (revision 11475) +++ config/locales/en.yml (working copy) @@ -332,7 +332,9 @@ field_timeout: "Timeout (in seconds)" field_board_parent: Parent forum field_private_notes: Private notes + field_inherit: Inherits categories from parent + setting_app_title: Application title setting_app_subtitle: Application subtitle setting_welcome_text: Welcome text @@ -1032,7 +1034,10 @@ text_account_destroy_confirmation: "Are you sure you want to proceed?\nYour account will be permanently deleted, with no way to reactivate it." text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." text_project_closed: This project is closed and read-only. + text_issue_categories_inherited: "Categories from inherited projects" + text_issue_category_masked: "category is masked by one with same name in a deeper project" + default_role_manager: Manager default_role_developer: Developer default_role_reporter: Reporter Index: db/migrate/20120830171625_add_inherit_to_project.rb =================================================================== --- db/migrate/20120830171625_add_inherit_to_project.rb (revision 0) +++ db/migrate/20120830171625_add_inherit_to_project.rb (working copy) @@ -0,0 +1,5 @@ +class AddInheritToProject < ActiveRecord::Migration + def change + add_column :projects, :inherit, :boolean + end +end Index: test/functional/issue_categories_controller_inheritance_test.rb =================================================================== --- test/functional/issue_categories_controller_inheritance_test.rb (revision 0) +++ test/functional/issue_categories_controller_inheritance_test.rb (working copy) @@ -0,0 +1,95 @@ +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.expand_path('../../test_helper', __FILE__) +require 'projects_controller' + +class IssueCategoriesControllerInheritanceTest < ActionController::TestCase + fixtures :trackers, :issue_statuses, :enumerations, :workflows, :users + + def setup + @controller = IssueCategoriesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + User.current = nil + @request.session[:user_id] = 1 + + wt = WorkflowTransition.find(:first) + @tracker = wt.tracker + @status = wt.old_status + + @pa = Project.create!(:name => 'A', :identifier => 'a') + @pb = Project.create!(:name => 'B', :identifier => 'b', :trackers => [@tracker]) + @pc = Project.create!(:name => 'C', :identifier => 'c', :parent_id => @pb.id, :inherit => true, :trackers => [@tracker]) + @pd = Project.create!(:name => 'D', :identifier => 'd', :parent_id => @pc.id, :inherit => true, :trackers => [@tracker]) + @pe = Project.create!(:name => 'E', :identifier => 'e', :parent_id => @pd.id, :inherit => true, :trackers => [@tracker]) + @pf = Project.create!(:name => 'F', :identifier => 'f', :parent_id => @pb.id, :inherit => true, :trackers => [@tracker]) + @pg = Project.create!(:name => 'G', :identifier => 'g', :trackers => [@tracker]) + @pa.reload + @pb.reload + @pc.reload + @pd.reload + @pd.reload + @ca1 = IssueCategory.create!(:project => @pa, :name => 'ca1') + @cb1 = IssueCategory.create!(:project => @pb, :name => 'cb1') + @cb2 = IssueCategory.create!(:project => @pb, :name => 'cb2') + @cd1 = IssueCategory.create!(:project => @pd, :name => 'cd1') + @cd2 = IssueCategory.create!(:project => @pd, :name => 'cb2') + end + + def test_destroy_no_reassign + is1 = Issue.create! :tracker => @tracker, :project => @pe, :status => @status, :author_id => 1, :subject => 'IS1', :category => @cd1 + delete :destroy, :id => @cd1.id, :todo => "nil" + is1.reload + assert_nil is1.category + end + + def test_destroy_reassign_same_project + is1 = Issue.create! :tracker => @tracker, :project => @pe, :status => @status, :author_id => 1, :subject => 'IS1', :category => @cd1 + delete :destroy, :id => @cd1.id, :todo => 'reassign', :reassign_to_id => @cd2.id + is1.reload + assert_equal @cd2, is1.category + end + + def test_destroy_reassign_upper_project + is1 = Issue.create! :tracker => @tracker, :project => @pe, :status => @status, :author_id => 1, :subject => 'IS1', :category => @cd1 + delete :destroy, :id => @cd1.id, :todo => 'reassign', :reassign_to_id => @cb1.id + is1.reload + assert_equal @cb1, is1.category + end + + def test_destroy_reassign_demasked_category + is1 = Issue.create! :tracker => @tracker, :project => @pe, :status => @status, :author_id => 1, :subject => 'IS1', :category => @cd2 + delete :destroy, :id => @cd2.id, :todo => 'reassign', :reassign_to_id => @cb2.id + is1.reload + assert_equal @cb2, is1.category + end + + def test_categories_list_no_mask + is1 = Issue.create! :tracker => @tracker, :project => @pe, :status => @status, :author_id => 1, :subject => 'IS1', :category => @cd2 + delete :destroy, :id => @cd2.id + assert_response(:success) + assert assigns.has_key?('categories') + assert_equal [@cb1, @cb2, @cd1], assigns(:categories) + end + + def test_categories_list_with_mask + is1 = Issue.create! :tracker => @tracker, :project => @pe, :status => @status, :author_id => 1, :subject => 'IS1', :category => @cd1 + delete :destroy, :id => @cd1.id + assert_response(:success) + assert assigns.has_key?('categories') + assert_equal [@cb1, @cd2], assigns(:categories) + end + +end Index: test/functional/projects_controller_inheritance_test.rb =================================================================== --- test/functional/projects_controller_inheritance_test.rb (revision 0) +++ test/functional/projects_controller_inheritance_test.rb (working copy) @@ -0,0 +1,131 @@ +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.expand_path('../../test_helper', __FILE__) +require 'projects_controller' + +class ProjectsControllerInheritanceTest < ActionController::TestCase + fixtures :trackers, :issue_statuses, :enumerations, :workflows, :users + + def setup + @controller = ProjectsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + User.current = nil + @request.session[:user_id] = 1 + + wt = WorkflowTransition.find(:first) + @tracker = wt.tracker + @status = wt.old_status + + @pa = Project.create!(:name => 'A', :identifier => 'a') + @pb = Project.create!(:name => 'B', :identifier => 'b', :trackers => [@tracker]) + @pc = Project.create!(:name => 'C', :identifier => 'c', :parent_id => @pb.id, :inherit => true, :trackers => [@tracker]) + @pd = Project.create!(:name => 'D', :identifier => 'd', :parent_id => @pc.id, :inherit => true, :trackers => [@tracker]) + @pe = Project.create!(:name => 'E', :identifier => 'e', :parent_id => @pd.id, :inherit => true, :trackers => [@tracker]) + @pf = Project.create!(:name => 'F', :identifier => 'f', :parent_id => @pb.id, :inherit => true, :trackers => [@tracker]) + @pg = Project.create!(:name => 'G', :identifier => 'g', :trackers => [@tracker]) + @pa.reload + @pb.reload + @pc.reload + @pd.reload + @pd.reload + @ca1 = IssueCategory.create!(:project => @pa, :name => 'ca1') + @cb1 = IssueCategory.create!(:project => @pb, :name => 'cb1') + @cb2 = IssueCategory.create!(:project => @pb, :name => 'cb2') + @cd1 = IssueCategory.create!(:project => @pd, :name => 'cd1') + @cd2 = IssueCategory.create!(:project => @pd, :name => 'cb2') + end + + def test_adjust_set_inherit_to_false + is1 = Issue.create!(:project => @pe, :tracker => @tracker, :status => @status, :author_id => 1, :subject => 'issueb', :category => @cb1) + is2 = Issue.create!(:project => @pe, :tracker => @tracker, :status => @status, :author_id => 1, :subject => 'issued', :category => @cd1) + post :update, :id => @pc.identifier, :project => {:inherit => "0"} + assert_response(:redirect) + assert_redirected_to "/projects/#{@pc.identifier}/settings" + @pc.reload + assert ! @pc.inherit + assert_equal @pb, @pc.parent + is1.reload + is2.reload + assert_equal @cd1, is2.category + assert_equal 1, @pc.issue_categories.size + cat = @pc.issue_categories[0] + assert_equal cat, is1.category + assert_equal @cb1.name, cat.name + end + + def test_adjust_set_parent_nil + is1 = Issue.create!(:project => @pe, :tracker => @tracker, :status => @status, :author_id => 1, :subject => 'issueb', :category => @cb1) + is2 = Issue.create!(:project => @pe, :tracker => @tracker, :status => @status, :author_id => 1, :subject => 'issued', :category => @cd1) + post :update, :id => @pc.identifier, :project => {:parent_id => ""} + assert_response(:redirect) + assert_redirected_to "/projects/#{@pc.identifier}/settings" + @pc.reload + assert ! @pc.inherit + assert_nil @pc.parent + is1.reload + is2.reload + assert_equal @cd1, is2.category + assert_equal 1, @pc.issue_categories.size + cat = @pc.issue_categories[0] + assert_equal cat, is1.category + assert_equal @cb1.name, cat.name + end + + def test_adjust_inheritance_with_loss + is1 = Issue.create!(:project => @pe, :tracker => @tracker, :status => @status, :author_id => 1, :subject => 'issueb', :category => @cb1) + is2 = Issue.create!(:project => @pe, :tracker => @tracker, :status => @status, :author_id => 1, :subject => 'issued', :category => @cd1) + post :update, :id => @pc.identifier, :project => {:parent_id => @pa.id} + assert_response(:redirect) + assert_redirected_to "/projects/#{@pc.identifier}/settings" + @pc.reload + assert @pc.inherit + assert_equal @pa, @pc.parent + is1.reload + is2.reload + assert_equal @cd1, is2.category + assert_equal 1, @pc.issue_categories.size + cat = @pc.issue_categories[0] + assert_equal cat, is1.category + assert_equal @cb1.name, cat.name + end + + def test_adjust_inheritance_no_loss + is1 = Issue.create!(:project => @pe, :tracker => @tracker, :status => @status, :author_id => 1, :subject => 'issueb', :category => @cb1) + is2 = Issue.create!(:project => @pe, :tracker => @tracker, :status => @status, :author_id => 1, :subject => 'issued', :category => @cd1) + post :update, :id => @pd.identifier, :project => {:parent_id => @pf.id} + assert_redirected_to "/projects/#{@pd.identifier}/settings" + @pd.reload + assert @pd.inherit + assert_equal @pf, @pd.parent + is1.reload + is2.reload + assert_equal @cd1, is2.category + assert_equal 2, @pd.issue_categories.size + assert_equal @cb1, is1.category + end + + def test_set_inherit + @pf.inherit = false + @pf.save! + @pf.reload + assert ! @pf.inherit + post :update, :id => @pf.identifier, :project => {:inherit => true} + assert_redirected_to "/projects/#{@pf.identifier}/settings" + @pf.reload + assert @pf.inherit + end + +end Index: test/unit/helpers/issue_categories_helper_test.rb =================================================================== --- test/unit/helpers/issue_categories_helper_test.rb (revision 0) +++ test/unit/helpers/issue_categories_helper_test.rb (working copy) @@ -0,0 +1,74 @@ +# encoding: utf-8 + +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.expand_path('../../../test_helper', __FILE__) + +class IssueCategoryHelperTest < ActionView::TestCase + include IssueCategoriesHelper + + fixtures :trackers, :issue_statuses, :enumerations, :users, :workflows + + def setup + super + User.current = nil + wt = WorkflowTransition.find(:first) + @tracker = wt.tracker + @status = wt.old_status + + @pa = Project.create!(:name => 'A', :identifier => 'a') + @pb = Project.create!(:name => 'B', :identifier => 'b', :trackers => [@tracker]) + @pc = Project.create!(:name => 'C', :identifier => 'c', :parent_id => @pb.id, :inherit => true, :trackers => [@tracker]) + @pd = Project.create!(:name => 'D', :identifier => 'd', :parent_id => @pc.id, :inherit => true, :trackers => [@tracker]) + @pe = Project.create!(:name => 'E', :identifier => 'e', :parent_id => @pd.id, :inherit => true, :trackers => [@tracker]) + @pf = Project.create!(:name => 'F', :identifier => 'f', :parent_id => @pb.id, :inherit => true, :trackers => [@tracker]) + @pg = Project.create!(:name => 'G', :identifier => 'g', :trackers => [@tracker]) + @pa.reload + @pb.reload + @pc.reload + @pd.reload + @pd.reload + @ca1 = IssueCategory.create!(:project => @pa, :name => 'ca1') + @cb1 = IssueCategory.create!(:project => @pb, :name => 'cb1') + @cb2 = IssueCategory.create!(:project => @pb, :name => 'cb2') + @cd1 = IssueCategory.create!(:project => @pd, :name => 'cd1') + @cd2 = IssueCategory.create!(:project => @pd, :name => 'cb2') + end + + def test_format_project + list = @pe.inherited_projects + assert_equal 'B » C » D', format_project(@pd, list) + assert_equal 'B', format_project(@pb, list) + assert_equal 'B » C » D » E', format_project(@pe, list) + end + + def test_options_for_reassign_no_inheritance + @pd.inherit = false + @pd.save + categs = @pd.inherited_categories - [@cd1] + assert_equal "", options_for_reassign(@cd1, categs, @pd) + end + + def test_options_for_reassign_with_masked_categ + categs = @pd.inherited_categories - [ @cd1 ] + assert_equal "\n", options_for_reassign(@cd1, categs, @pd) + end + + def test_options_for_reassign_no_masked_categ + categs = (@pd.issue_categories | @pc.inherited_categories) - [ @cd2 ] + assert_equal "\n\n", options_for_reassign(@cd1, categs, @pd) + end + +end Index: test/unit/inherited_categories_test.rb =================================================================== --- test/unit/inherited_categories_test.rb (revision 0) +++ test/unit/inherited_categories_test.rb (working copy) @@ -0,0 +1,105 @@ +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.expand_path('../../test_helper', __FILE__) + +class InheritedCategoriesTest < ActiveSupport::TestCase + fixtures :trackers, :issue_statuses, :enumerations, :workflows, :users + + def setup + wt = WorkflowTransition.find(:first) + @tracker = wt.tracker + @status = wt.old_status + + @pa = Project.create!(:name => 'A', :identifier => 'a') + @pb = Project.create!(:name => 'B', :identifier => 'b', :trackers => [@tracker]) + @pc = Project.create!(:name => 'C', :identifier => 'c', :parent_id => @pb.id, :inherit => true, :trackers => [@tracker]) + @pd = Project.create!(:name => 'D', :identifier => 'd', :parent_id => @pc.id, :inherit => true, :trackers => [@tracker]) + @pe = Project.create!(:name => 'E', :identifier => 'e', :parent_id => @pd.id, :inherit => true, :trackers => [@tracker]) + @pf = Project.create!(:name => 'F', :identifier => 'f', :parent_id => @pb.id, :inherit => true, :trackers => [@tracker]) + @pg = Project.create!(:name => 'G', :identifier => 'g', :trackers => [@tracker]) + @pa.reload + @pb.reload + @pc.reload + @pd.reload + @pd.reload + @ca1 = IssueCategory.create!(:project => @pa, :name => 'ca1') + @cb1 = IssueCategory.create!(:project => @pb, :name => 'cb1') + @cb2 = IssueCategory.create!(:project => @pb, :name => 'cb2') + @cd1 = IssueCategory.create!(:project => @pd, :name => 'cd1') + @cd2 = IssueCategory.create!(:project => @pd, :name => 'cb2') + end + + def test_inherited_project_self + assert_equal [ @pb ], @pb.inherited_projects + assert_equal [ @pa ], @pa.inherited_projects + end + + def test_inherited_project + assert_equal [ @pd, @pc, @pb ], @pd.inherited_projects + end + + def test_inherited_categories_self + assert_equal @pa.issue_categories, @pa.inherited_categories + assert_equal @pb.issue_categories, @pb.inherited_categories + assert_equal @pg.issue_categories, @pg.inherited_categories + end + + def test_inherited_categories + assert_equal [ @cb1, @cd2, @cd1 ], @pd.inherited_categories + assert_equal [ @cb1, @cd2, @cd1 ], @pe.inherited_categories + assert @pg.inherited_categories.empty? + end + + def test_adjust_inheritance_to_nil + is1 = Issue.create!(:project => @pe, :tracker => @tracker, :status => @status, :author_id => 1, :subject => 'issueb', :category => @cb1) + is2 = Issue.create!(:project => @pe, :tracker => @tracker, :status => @status, :author_id => 1, :subject => 'issued', :category => @cd1) + @pc.send :adjust_inheritance, @pb, nil + assert ! @pc.inherit + @pc.reload + assert ! @pc.inherit + is1.reload + is2.reload + assert_equal @cd1, is2.category + assert_equal 1, @pc.issue_categories.size + cat = @pc.issue_categories[0] + assert_equal cat, is1.category + assert_equal @cb1.name, cat.name + end + + def test_adjust_inheritance_with_loss + is1 = Issue.create!(:project => @pd, :tracker => @tracker, :status => @status, :author_id => 1, :subject => 'issueb', :category => @cb1) + is2 = Issue.create!(:project => @pd, :tracker => @tracker, :status => @status, :author_id => 1, :subject => 'issued', :category => @cd1) + @pc.send :adjust_inheritance, @pb, @pa + is1.reload + is2.reload + assert_equal @cd1, is2.category + assert_equal 1, @pc.issue_categories.size + cat = @pc.issue_categories[0] + assert_equal cat, is1.category + assert_equal @cb1.name, cat.name + end + + def test_adjust_inheritance_no_loss + is1 = Issue.create!(:project => @pe, :tracker => @tracker, :status => @status, :author_id => 1, :subject => 'issueb', :category => @cb1) + is2 = Issue.create!(:project => @pe, :tracker => @tracker, :status => @status, :author_id => 1, :subject => 'issued', :category => @cd1) + @pd.send :adjust_inheritance, @pc, @pf + is1.reload + is2.reload + assert_equal @cd1, is2.category + assert @pc.issue_categories.empty? + assert_equal @cb1, is1.category + end + +end Index: test/unit/inherited_issue_category_test.rb =================================================================== --- test/unit/inherited_issue_category_test.rb (revision 0) +++ test/unit/inherited_issue_category_test.rb (working copy) @@ -0,0 +1,81 @@ +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.expand_path('../../test_helper', __FILE__) + +class InheritedIssueCategoryTest < ActiveSupport::TestCase + fixtures :trackers, :issue_statuses, :enumerations, :workflows, :users + + def setup + wt = WorkflowTransition.find(:first) + @tracker = wt.tracker + @status = wt.old_status + + @pa = Project.create!(:name => 'A', :identifier => 'a') + @pb = Project.create!(:name => 'B', :identifier => 'b', :trackers => [@tracker]) + @pc = Project.create!(:name => 'C', :identifier => 'c', :parent_id => @pb.id, :inherit => true, :trackers => [@tracker]) + @pd = Project.create!(:name => 'D', :identifier => 'd', :parent_id => @pc.id, :inherit => true, :trackers => [@tracker]) + @pe = Project.create!(:name => 'E', :identifier => 'e', :parent_id => @pd.id, :inherit => true, :trackers => [@tracker]) + @pf = Project.create!(:name => 'F', :identifier => 'f', :parent_id => @pb.id, :inherit => true, :trackers => [@tracker]) + @pg = Project.create!(:name => 'G', :identifier => 'g', :trackers => [@tracker]) + @pa.reload + @pb.reload + @pc.reload + @pd.reload + @pd.reload + @ca1 = IssueCategory.create!(:project => @pa, :name => 'ca1') + @cb1 = IssueCategory.create!(:project => @pb, :name => 'cb1') + @cb2 = IssueCategory.create!(:project => @pb, :name => 'cb2') + @cd1 = IssueCategory.create!(:project => @pd, :name => 'cd1') + @cd2 = IssueCategory.create!(:project => @pd, :name => 'cb2') + end + + def test_destroy_no_reassign + is1 = Issue.create! :tracker => @tracker, :project => @pe, :status => @status, :author_id => 1, :subject => 'IS1', :category => @cd1 + @cd1.destroy + is1.reload + assert_nil is1.category + end + + def test_destroy_inaccessible_reassign + @pc.inherit = false + @pc.save + is1 = Issue.create! :tracker => @tracker, :project => @pe, :status => @status, :author_id => 1, :subject => 'IS1', :category => @cd1 + @cd1.destroy(@cb1) + is1.reload + assert_nil is1.category + end + + def test_destroy_reassign_same_project + is1 = Issue.create! :tracker => @tracker, :project => @pe, :status => @status, :author_id => 1, :subject => 'IS1', :category => @cd1 + @cd1.destroy @cd2 + is1.reload + assert_equal @cd2, is1.category + end + + def test_destroy_reassign_upper_project + is1 = Issue.create! :tracker => @tracker, :project => @pe, :status => @status, :author_id => 1, :subject => 'IS1', :category => @cd1 + @cd1.destroy @cb1 + is1.reload + assert_equal @cb1, is1.category + end + + def test_destroy_reassign_demasked_category + is1 = Issue.create! :tracker => @tracker, :project => @pe, :status => @status, :author_id => 1, :subject => 'IS1', :category => @cd2 + @cd2.destroy @cb2 + is1.reload + assert_equal @cb2, is1.category + end + +end