Merge remote-tracking branch 'origin/dev' into feature/return_all_users_from_api_3451

pull/853/head
Hagen Schink 11 years ago
commit 60d8a67dde
  1. 2
      .travis.yml
  2. 16
      app/controllers/activities_controller.rb
  3. 3
      app/controllers/work_packages/bulk_controller.rb
  4. 7
      app/controllers/work_packages/context_menus_controller.rb
  5. 4
      app/helpers/timelines_helper.rb
  6. 6
      app/helpers/work_packages_helper.rb
  7. 2
      app/models/mail_handler.rb
  8. 27
      app/models/project.rb
  9. 4
      app/models/queries/work_packages/available_filter_options.rb
  10. 2
      app/models/query.rb
  11. 28
      app/models/work_package.rb
  12. 1
      app/views/activities/index.html.erb
  13. 2
      app/views/categories/_form.html.erb
  14. 2
      app/views/projects/form/attributes/_responsible_id.html.erb
  15. 1
      app/views/user_mailer/_issue_details.html.erb
  16. 1
      app/views/user_mailer/_issue_details.text.erb
  17. 6
      app/views/work_packages/bulk/edit.html.erb
  18. 14
      app/views/work_packages/context_menus/index.html.erb
  19. 4
      app/views/work_packages/moves/new.html.erb
  20. 2
      config/locales/de.yml
  21. 2
      config/locales/en.yml
  22. 7
      doc/CHANGELOG.md
  23. 6
      features/step_definitions/work_package_steps.rb
  24. 105
      features/work_packages/bulk.feature
  25. 22
      features/work_packages/update.feature
  26. 2
      lib/redmine.rb
  27. 67
      spec/controllers/activities_controller_spec.rb
  28. 46
      spec/controllers/work_packages/bulk_controller_spec.rb
  29. 25
      spec/controllers/work_packages/context_menus_controller_spec.rb
  30. 42
      spec/controllers/work_packages_controller_spec.rb
  31. 33
      spec/models/work_package_spec.rb

@ -28,7 +28,7 @@
language: ruby
rvm:
- 2.1.0
- 2.0.0
branches:
only:
- dev

@ -47,8 +47,8 @@ class ActivitiesController < ApplicationController
@activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
:with_subprojects => @with_subprojects,
:author => @author)
@activity.scope_select {|t| !params["show_#{t}"].nil?}
@activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
set_activity_scope
events = @activity.events(@date_from, @date_to)
censor_events_from_projects_with_disabled_activity!(events) unless @project
@ -100,4 +100,16 @@ class ActivitiesController < ApplicationController
event.project_id.nil? || allowed_project_ids.include?(event.project_id)
end
end
def set_activity_scope
if params[:apply]
@activity.scope_select {|t| !params["show_#{t}"].nil?}
elsif session[:activity]
@activity.scope = session[:activity]
else
@activity.scope = (@author.nil? ? :default : :all)
end
session[:activity] = @activity.scope
end
end

@ -43,7 +43,8 @@ class WorkPackages::BulkController < ApplicationController
@work_packages.sort!
@available_statuses = @projects.map{|p|Workflow.available_statuses(p)}.inject{|memo,w|memo & w}
@custom_fields = @projects.map{|p|p.all_work_package_custom_fields}.inject{|memo,c|memo & c}
@assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
@assignables = @projects.map(&:possible_assignees).inject{|memo,a| memo & a}
@responsibles = @projects.map(&:possible_responsibles).inject{|memo,a| memo & a}
@types = @projects.map(&:types).inject{|memo,t| memo & t}
end

@ -56,12 +56,15 @@ class WorkPackages::ContextMenusController < ApplicationController
:delete => User.current.allowed_to?(:delete_work_packages, @projects)
}
if @project
@assignables = @project.assignable_users
@assignables = @project.possible_assignees
@assignables << @work_package.assigned_to if @work_package && @work_package.assigned_to && !@assignables.include?(@work_package.assigned_to)
@responsibles = @project.possible_responsibles
@responsibles << @work_package.responsible if @work_package && @work_package.responsible && !@responsibles.include?(@work_package.responsible)
@types = @project.types
else
#when multiple projects, we only keep the intersection of each set
@assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
@assignables = @projects.map(&:possible_assignees).inject{|memo,a| memo & a}
@responsibles = @projects.map(&:possible_responsibles).inject{|memo,a| memo & a}
@types = @projects.map(&:types).inject{|memo,t| memo & t}
end

@ -85,10 +85,6 @@ module TimelinesHelper
ProjectType.all.map { |t| [t.name, t.id] }
end
def options_for_responsible(project)
project.users.map { |u| [u.name, u.id] }
end
def visible_parent_project(project)
parent = project.parent

@ -509,12 +509,12 @@ module WorkPackagesHelper
def work_package_form_assignee_attribute(form, work_package, locals = {})
WorkPackageAttribute.new(:assignee,
form.select(:assigned_to_id, (work_package.assignable_users.map {|m| [m.name, m.id]}), :include_blank => true))
form.select(:assigned_to_id, (work_package.assignable_assignees.map {|m| [m.name, m.id]}), :include_blank => true))
end
def work_package_form_responsible_attribute(form, work_package, locals = {})
WorkPackageAttribute.new(:assignee,
form.select(:responsible_id, options_for_responsible(locals[:project]), :include_blank => true))
WorkPackageAttribute.new(:responsible,
form.select(:responsible_id, work_package.assignable_responsibles.map {|m| [m.name, m.id]}, :include_blank => true))
end
def work_package_form_category_attribute(form, work_package, locals = {})

@ -412,7 +412,7 @@ class MailHandler < ActionMailer::Base
def find_assignee_from_keyword(keyword, issue)
keyword = keyword.to_s.downcase
assignable = issue.assignable_users
assignable = issue.assignable_assignees
assignee = nil
assignee ||= assignable.detect {|a|
a.mail.to_s.downcase == keyword ||

@ -46,10 +46,14 @@ class Project < ActiveRecord::Base
# Specific overidden Activities
has_many :time_entry_activities
has_many :members, :include => [:user, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUSES[:active]}"
has_many :assignable_members,
has_many :possible_assignee_members,
:class_name => 'Member',
:include => [:principal, :roles],
:conditions => Proc.new { self.class.assignable_members_condition }
:conditions => Proc.new { self.class.possible_assignees_condition }
has_many :possible_responsible_members,
:class_name => 'Member',
:include => [:principal, :roles],
:conditions => Proc.new { self.class.possible_responsibles_condition }
has_many :memberships, :class_name => 'Member'
has_many :member_principals, :class_name => 'Member',
:include => :principal,
@ -597,8 +601,13 @@ class Project < ActiveRecord::Base
end
# Users/groups a work_package can be assigned to
def assignable_users
assignable_members.map(&:principal).compact.sort
def possible_assignees
possible_assignee_members.map(&:principal).compact.sort
end
# Users who can become responsible for a work_package
def possible_responsibles
possible_responsible_members.map(&:principal).compact.sort
end
# Returns the mail adresses of users that should be always notified on project events
@ -916,7 +925,7 @@ class Project < ActiveRecord::Base
protected
def self.assignable_members_condition
def self.possible_assignees_condition
condition = Setting.work_package_group_assignment? ?
["(#{Principal.table_name}.type=? OR #{Principal.table_name}.type=?)", 'User', 'Group'] :
@ -928,4 +937,12 @@ class Project < ActiveRecord::Base
sanitize_sql_array condition
end
def self.possible_responsibles_condition
condition = ["(#{Principal.table_name}.type=? AND #{User.table_name}.status=? AND roles.assignable = ?)",
'User', User::STATUSES[:active], true]
sanitize_sql_array condition
end
end

@ -112,7 +112,9 @@ module Queries::WorkPackages::AvailableFilterOptions
role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
@available_work_package_filters["assigned_to_role"] = { type: :list_optional, order: 7, values: role_values, name: I18n.t('query_fields.assigned_to_role') } unless role_values.empty?
@available_work_package_filters["responsible_id"] = { type: :list_optional, order: 4, values: assigned_to_values } unless assigned_to_values.empty?
responsible_values = user_values.dup
responsible_values = [["<< #{l(:label_me)} >>", "me"]] + responsible_values if User.current.logged?
@available_work_package_filters["responsible_id"] = { type: :list_optional, order: 4, values: responsible_values } unless responsible_values.empty?
# watcher filters
if User.current.logged?

@ -62,7 +62,7 @@ class Query < ActiveRecord::Base
QueryColumn.new(:subject, :sortable => "#{WorkPackage.table_name}.subject"),
QueryColumn.new(:author),
QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
QueryColumn.new(:responsible, sortable: ["responsible.lastname", "responsible.firstname", "responsible.id"], groupable: "#{WorkPackage.table_name}.responsible_id", :join => "LEFT OUTER JOIN users as responsible ON (#{WorkPackage.table_name}.responsible_id = responsible.id)"),
QueryColumn.new(:responsible, sortable: ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], groupable: "#{WorkPackage.table_name}.responsible_id", :join => "LEFT OUTER JOIN users as responsible ON (#{WorkPackage.table_name}.responsible_id = responsible.id)"),
QueryColumn.new(:updated_at, :sortable => "#{WorkPackage.table_name}.updated_at", :default_order => 'desc'),
QueryColumn.new(:category, :sortable => "#{Category.table_name}.name", :groupable => true),
QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),

@ -371,8 +371,15 @@ class WorkPackage < ActiveRecord::Base
end
end
# Users/groups the work_package can be assigned to
def assignable_assignees
project.possible_assignees
end
# Users the work_package can be assigned to
delegate :assignable_users, :to => :project
def assignable_responsibles
project.possible_responsibles
end
# Versions that the work_package can be assigned to
# A work_package can be assigned to:
@ -917,7 +924,7 @@ class WorkPackage < ActiveRecord::Base
end
def add_time_entry_for(user, attributes)
return if attributes.nil? || attributes.values.all?(&:blank?)
return if time_entry_blank?(attributes)
attributes.reverse_merge!({ :user => user,
:spent_on => Date.today })
@ -925,6 +932,23 @@ class WorkPackage < ActiveRecord::Base
time_entries.build(attributes)
end
##
# Checks if the time entry defined by the given attributes is blank.
# A time entry counts as blank despite a selected activity if that activity
# is simply the default activity and all other attributes are blank.
def time_entry_blank?(attributes)
return true if attributes.nil?
key = "activity_id"
id = attributes[key]
default_id = if id && !id.blank?
Enumeration.exists? :id => id, :is_default => true, :type => 'TimeEntryActivity'
else
true
end
default_id && attributes.except(key).values.all?(&:blank?)
end
# >>> issues.rb >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# this removes all attachments separately before destroying the issue
# avoids getting a ActiveRecord::StaleObjectError when deleting an issue

@ -93,6 +93,7 @@ See doc/COPYRIGHT.rdoc for more details.
<p><label><%= check_box_tag 'with_subprojects', 1, @with_subprojects %> <%=l(:label_subproject_plural)%></label></p>
<% end %>
<%= hidden_field_tag('user_id', params[:user_id]) unless params[:user_id].blank? %>
<%= hidden_field_tag('apply', true) %>
<p><%= submit_tag l(:button_apply), :class => 'button-small', :name => nil %></p>
<% end %>
<% end %>

@ -32,6 +32,6 @@ See doc/COPYRIGHT.rdoc for more details.
<div class="box">
<p><%= f.text_field :name, :size => 30, :required => true %></p>
<p>
<%= f.select :assigned_to_id, @project.assignable_users.sort.collect{|u| [u.name, u.id]}, :include_blank => true %>
<%= f.select :assigned_to_id, @project.possible_assignees.sort.collect{|u| [u.name, u.id]}, :include_blank => true %>
</p>
</div>

@ -30,7 +30,7 @@ See doc/COPYRIGHT.rdoc for more details.
<p>
<% if project && project.persisted? %>
<% if User.current.impaired? %>
<%= form.select :responsible_id, options_for_responsible(project), :include_blank => true %>
<%= form.select :responsible_id, project.possible_responsibles.map {|m| [m.name, m.id]}, :include_blank => true %>
<% else %>
<% options = { :'data-ajaxURL' => url_for({:controller => "/members",
:action => "paginate_users" }),

@ -34,6 +34,7 @@ See doc/COPYRIGHT.rdoc for more details.
<li><%= WorkPackage.human_attribute_name(:status) %>: <%= issue.status %></li>
<li><%= WorkPackage.human_attribute_name(:priority) %>: <%= issue.priority %></li>
<li><%= WorkPackage.human_attribute_name(:assigned_to) %>: <%= issue.assigned_to %></li>
<li><%= WorkPackage.human_attribute_name(:responsible) %>: <%= issue.responsible %></li>
<li><%= WorkPackage.human_attribute_name(:category) %>: <%= issue.category %></li>
<li><%= WorkPackage.human_attribute_name(:fixed_version) %>: <%= issue.fixed_version %></li>

@ -34,6 +34,7 @@ See doc/COPYRIGHT.rdoc for more details.
<%= WorkPackage.human_attribute_name(:status) %>: <%= issue.status %>
<%= WorkPackage.human_attribute_name(:priority) %>: <%= issue.priority %>
<%= WorkPackage.human_attribute_name(:assigned_to) %>: <%= issue.assigned_to %>
<%= WorkPackage.human_attribute_name(:responsible) %>: <%= issue.responsible %>
<%= WorkPackage.human_attribute_name(:category) %>: <%= issue.category %>
<%= WorkPackage.human_attribute_name(:fixed_version) %>: <%= issue.fixed_version %>

@ -59,6 +59,12 @@ See doc/COPYRIGHT.rdoc for more details.
content_tag('option', l(:label_nobody), :value => 'none') +
options_from_collection_for_select(@assignables, :id, :name)) %>
</p>
<p>
<label for='work_package_responsible_id'><%= WorkPackage.human_attribute_name(:responsible) %></label>
<%= select_tag('work_package[responsible_id]', content_tag('option', l(:label_no_change_option), :value => '') +
content_tag('option', l(:label_nobody), :value => 'none') +
options_from_collection_for_select(@responsibles, :id, :name)) %>
</p>
<% if @project %>
<p>
<label for='category_id'><%= WorkPackage.human_attribute_name(:category) %></label>

@ -81,7 +81,7 @@ See doc/COPYRIGHT.rdoc for more details.
<% end %>
<% if @assignables.present? -%>
<% assignables = @assignables.dup << [nil, l(:label_none)] %>
<% assignables = @assignables.dup << ["none", l(:label_none)] %>
<% params = default_params.merge(:collection => assignables,
:attribute => 'assigned_to',
:selected => lambda { |user| @work_package && user == @work_package.assigned_to },
@ -89,8 +89,18 @@ See doc/COPYRIGHT.rdoc for more details.
<%= context_menu_entry(params) %>
<% end %>
<% if @responsibles.present? -%>
<% responsibles = @responsibles.dup << ["none", l(:label_none)] %>
<% params = default_params.merge(:collection => responsibles,
:attribute => 'responsible',
:selected => lambda { |user| @work_package && user == @work_package.responsible },
:disabled => lambda { |user| !@can[:update] }) %>
<%= context_menu_entry(params) %>
<% end %>
<% unless @project.nil? || (categories = @project.categories.to_a).empty? -%>
<% categories << [nil, l(:label_none)] %>
<% categories << ["none", l(:label_none)] %>
<% params = default_params.merge(:collection => categories,
:attribute => 'category',
:selected => lambda { |category| @work_package && category == @work_package.category },

@ -78,14 +78,14 @@ See doc/COPYRIGHT.rdoc for more details.
<%= select_tag('assigned_to_id',
content_tag('option', l(:label_no_change_option), :value => '') +
content_tag('option', l(:label_nobody), :value => 'none') +
options_from_collection_for_select(@target_project.assignable_users, :id, :name)) %>
options_from_collection_for_select(@target_project.possible_assignees, :id, :name)) %>
</p>
<p>
<label for='responsible_id'><%= WorkPackage.human_attribute_name(:responsible) %></label>
<%= select_tag('responsible_id',
content_tag('option', l(:label_no_change_option), :value => '') +
content_tag('option', l(:label_nobody), :value => 'none') +
options_from_collection_for_select(@target_project.assignable_users, :id, :name)) %>
options_from_collection_for_select(@target_project.possible_responsibles, :id, :name)) %>
</p>
</div>

@ -249,7 +249,7 @@ de:
group: "Gruppe"
issue: "Ticket"
category: "Kategorie"
status: "Ticket-Status"
status: "Arbeitspaket-Status"
member: "Mitglied"
news: "News"
project: "Projekt"

@ -247,7 +247,7 @@ en:
group: "Group"
issue: "Issue"
category: "Category"
status: "Issue status"
status: "Work package status"
member: "Member"
news: "News"
project: "Project"

@ -31,9 +31,16 @@ See doc/COPYRIGHT.rdoc for more details.
* `#2018` Cleanup journal tables
* `#2244` Fix: [Accessibility] correctly label document language - custom fields
* `#2594` Fix: [Activity] Too many filter selects than necessary
* `#3332` [CodeClimate] Mass Assignment AuthSourcesController
* `#3333` [CodeClimate] Mass Assignment RolesController
* `#3438` Activity default value makes log time required
* `#3451` API references hidden users
* `#3481` Fix: [Activity] Not possible to unselect all filters
* `#3730` Setting responsible via bulk edit
* `#3731` Setting responsible via context menu
* `#3844` Fixed Work Package status translation
* `#3854` Move function and Query filters allows to select groups as responsible
* `#3974` [Timelines] Typo at creating timelines
## 3.0.0pre43

@ -127,3 +127,9 @@ Then /^the work package should be shown with the following values:$/ do |table|
should have_css(".description", :text => table.rows_hash["Description"])
end
end
Then(/^the attribute "(.*?)" of work package "(.*?)" should be "(.*?)"$/) do |attribute, wp_name, value|
wp = WorkPackage.find_by_subject(wp_name)
wp ||= WorkPackages.where("subject like ?", wp_name).to_sql
wp.send(attribute).to_s.should == value
end

@ -0,0 +1,105 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2013 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
Feature: Updating work packages
Background:
Given there is 1 user with:
| login | manager |
| firstname | the |
| lastname | manager |
And there are the following types:
| Name | Is milestone |
| Phase1 | false |
| Phase2 | false |
And there are the following project types:
| Name |
| Standard Project |
And there is 1 project with the following:
| identifier | ecookbook |
| name | ecookbook |
And the project named "ecookbook" is of the type "Standard Project"
And the following types are enabled for projects of type "Standard Project"
| Phase1 |
| Phase2 |
And there is a role "manager"
And the role "manager" may have the following rights:
| edit_work_packages |
| view_work_packages |
| manage_subtasks |
And I am working in project "ecookbook"
And the user "manager" is a "manager"
And there are the following priorities:
| name | default |
| prio1 | true |
| prio2 | |
And there are the following status:
| name | default |
| status1 | true |
| status2 | |
And the project "ecookbook" has 1 version with the following:
| name | version1 |
And the type "Phase1" has the default workflow for the role "manager"
And the type "Phase2" has the default workflow for the role "manager"
And there are the following work packages in project "ecookbook":
| subject | type | status | fixed_version | assigned_to |
| pe1 | Phase1 | status1 | version1 | manager |
| pe2 | | | | manager |
And I am already logged in as "manager"
@javascript
Scenario: Bulk updating the fixed version of several work packages
When I go to the work package index page of the project called "ecookbook"
And I open the context menu on the work packages:
| pe1 |
| pe2 |
And I hover over ".fixed_version .context_item"
And I follow "none" within "#context-menu"
Then I should see "Successful update"
And I follow "pe1"
And I should see "deleted (version1)"
@javascript
Scenario: Bulk updating several work packages without back url should return index
When I go to the work package index page of the project called "ecookbook"
And I open the context menu on the work packages:
| pe1 |
| pe2 |
And I follow "Edit" within "#context-menu"
And I press "Submit"
Then I should see "Work packages" within "#content"
@javascript
Scenario: Bulk updating the fixed version of several work packages
When I go to the work package index page of the project called "ecookbook"
And I open the context menu on the work packages:
| pe1 |
| pe2 |
And I hover over ".assigned_to .context_item"
And I follow "none" within "#context-menu"
Then I should see "Successful update"
Then the attribute "assigned_to" of work package "pe1" should be ""

@ -127,25 +127,3 @@ Feature: Updating work packages
Then I should be on the page of the work package "pe1"
And I should see a journal with the following:
| Notes | Note message |
@javascript
Scenario: Bulk updating the fixed version of several work packages
When I go to the work package index page of the project called "ecookbook"
And I open the context menu on the work packages:
| pe1 |
| pe2 |
And I hover over ".fixed_version .context_item"
And I follow "none" within "#context-menu"
Then I should see "Successful update"
And I follow "pe1"
And I should see "deleted (version1)"
@javascript
Scenario: Bulk updating several work packages without back url should return index
When I go to the work package index page of the project called "ecookbook"
And I open the context menu on the work packages:
| pe1 |
| pe2 |
And I follow "Edit" within "#context-menu"
And I press "Submit"
Then I should see "Work packages" within "#content"

@ -364,7 +364,7 @@ end
Redmine::Activity.map do |activity|
activity.register :work_packages, class_name: 'Activity::WorkPackageActivityProvider'
activity.register :changesets, class_name: 'Activity::ChangesetActivityProvider'
activity.register :news, class_name: 'Activity::NewsActivityProvider'
activity.register :news, class_name: 'Activity::NewsActivityProvider', default: false
activity.register :wiki_edits, class_name: 'Activity::WikiContentActivityProvider', default: false
activity.register :messages, class_name: 'Activity::MessageActivityProvider', default: false
activity.register :time_entries, class_name: 'Activity::TimeEntryActivityProvider', default: false

@ -39,6 +39,12 @@ describe ActivitiesController do
end
describe 'index' do
shared_examples_for 'valid index response' do
it { expect(response).to be_success }
it { expect(response).to render_template 'index' }
end
describe 'global' do
let(:work_package) { FactoryGirl.create(:work_package) }
let!(:journal) { FactoryGirl.create(:work_package_journal,
@ -53,11 +59,9 @@ describe ActivitiesController do
before { get 'index' }
it { expect(response).to be_success }
it { expect(response).to render_template 'index' }
it_behaves_like 'valid index response'
it { expect(assigns(:event_by_day)).to be_nil }
it { expect(assigns(:events_by_day)).not_to be_empty }
describe 'view' do
render_views
@ -72,6 +76,14 @@ describe ActivitiesController do
:content => /#{ERB::Util.html_escape(work_package.subject)}/ } } }
end
end
describe 'empty filter selection' do
before { get 'index', apply: true }
it_behaves_like 'valid index response'
it { expect(assigns(:events_by_day)).to be_empty }
end
end
describe 'with activated activity module' do
@ -96,6 +108,12 @@ describe ActivitiesController do
end
end
shared_context 'index with params' do
let(:session_values) { defined?(session_hash) ? session_hash : {} }
before { get :index, params, session_values }
end
describe :atom_feed do
let(:user) { FactoryGirl.create(:user) }
let(:project) { FactoryGirl.create(:project) }
@ -129,7 +147,7 @@ describe ActivitiesController do
let(:params) { { project_id: project.id,
format: :atom } }
before { get :index, params }
include_context 'index with params'
it { expect(assigns(:items).count).to eq(2) }
@ -145,15 +163,52 @@ describe ActivitiesController do
let!(:message_2) { FactoryGirl.create(:message,
board: board) }
let(:params) { { project_id: project.id,
apply: true,
show_messages: 1,
format: :atom } }
before { get :index, params }
include_context 'index with params'
it { expect(assigns(:items).count).to eq(2) }
it { expect(response).to render_template("common/feed") }
end
end
describe 'user selection' do
describe 'first activity request' do
let(:default_scope) { ['work_packages', 'changesets'] }
let(:params) { {} }
include_context 'index with params'
it { expect(assigns(:activity).scope).to match_array(default_scope) }
it { expect(session[:activity]).to match_array(default_scope) }
end
describe 'subsequent activity requests' do
let(:scope) { [] }
let(:params) { {} }
let(:session_hash) { { activity: [] } }
include_context 'index with params'
it { expect(assigns(:activity).scope).to match_array(scope) }
it { expect(session[:activity]).to match_array(scope) }
end
describe 'selection with apply' do
let(:scope) { [] }
let(:params) { { apply: true } }
include_context 'index with params'
it { expect(assigns(:activity).scope).to match_array(scope) }
it { expect(session[:activity]).to match_array(scope) }
end
end
end
end

@ -30,6 +30,7 @@ require 'spec_helper'
describe WorkPackages::BulkController do
let(:user) { FactoryGirl.create(:user) }
let(:user2) { FactoryGirl.create(:user)}
let(:custom_field_value) { '125' }
let(:custom_field_1) { FactoryGirl.create(:work_package_custom_field,
field_format: 'string',
@ -47,17 +48,22 @@ describe WorkPackages::BulkController do
permissions: [:edit_work_packages,
:view_work_packages,
:manage_subtasks]) }
let(:member_1) { FactoryGirl.create(:member,
let(:member1_p1) { FactoryGirl.create(:member,
project: project_1,
principal: user,
roles: [role]) }
let(:member_2) { FactoryGirl.create(:member,
let(:member2_p1) { FactoryGirl.create(:member,
project: project_1,
principal: user2,
roles: [role]) }
let(:member1_p2) { FactoryGirl.create(:member,
project: project_2,
principal: user,
roles: [role]) }
let(:work_package_1) { FactoryGirl.create(:work_package,
author: user,
assigned_to: user,
responsible: user2,
type: type,
status: status,
custom_field_values: { custom_field_1.id => custom_field_value },
@ -65,6 +71,7 @@ describe WorkPackages::BulkController do
let(:work_package_2) { FactoryGirl.create(:work_package,
author: user,
assigned_to: user,
responsible: user2,
type: type,
status: status,
custom_field_values: { custom_field_1.id => custom_field_value },
@ -80,7 +87,8 @@ describe WorkPackages::BulkController do
before do
custom_field_1
member_1
member1_p1
member2_p1
User.stub(:current).and_return user
end
@ -122,7 +130,7 @@ describe WorkPackages::BulkController do
context "different projects" do
before do
member_2
member1_p2
get :edit, ids: [work_package_1.id, work_package_2.id, work_package_3.id]
end
@ -156,6 +164,7 @@ describe WorkPackages::BulkController do
let(:work_packages) { WorkPackage.find_all_by_id(work_package_ids) }
let(:priority) { FactoryGirl.create(:priority_immediate) }
let(:group_id) { '' }
let(:responsible_id) {''}
describe :redirect do
context "in host" do
@ -205,8 +214,8 @@ describe WorkPackages::BulkController do
before do
# create user memberships to allow the user to watch work packages
member_1
member_2
member1_p1
member1_p2
# let other_user perform the bulk update
User.stub(:current).and_return other_user
put :update, ids: work_package_ids, work_package: work_package_params
@ -233,6 +242,7 @@ describe WorkPackages::BulkController do
notes: 'Bulk editing',
work_package: { priority_id: priority.id,
assigned_to_id: group_id,
responsible_id: responsible_id,
custom_field_values: { custom_field_1.id.to_s => '' },
send_notification: send_notification }
end
@ -302,7 +312,7 @@ describe WorkPackages::BulkController do
let(:work_package_ids) { [work_package_1.id, work_package_2.id, work_package_3.id] }
context "with permission" do
before { member_2 }
before { member1_p2 }
include_context :update_request
@ -338,6 +348,16 @@ describe WorkPackages::BulkController do
it { should =~ [group_id] }
end
describe :responsible do
let(:responsible_id) { user.id }
include_context :update_request
subject { work_packages.collect {|w| w.responsible_id }.uniq }
it { should =~ [responsible_id] }
end
describe :status do
let(:closed_status) { FactoryGirl.create(:closed_status) }
let(:workflow) { FactoryGirl.create(:workflow,
@ -402,6 +422,18 @@ describe WorkPackages::BulkController do
it { should =~ [nil] }
end
describe :delete_responsible do
before do
put :update,
ids: work_package_ids,
work_package: { responsible_id: 'none' }
end
subject { work_packages.collect(&:responsible_id).uniq }
it { should =~ [nil] }
end
describe :version do
describe "set fixed_version_id attribute to some version" do
let(:version) { FactoryGirl.create(:version,

@ -181,20 +181,29 @@ describe WorkPackages::ContextMenusController do
end
end
shared_examples_for :assigned_to do
let(:assigned_to_link) { "/work_packages/bulk?#{ids_link}"\
"&amp;work_package%5Bassigned_to_id%5D=#{user.id}" }
shared_examples_for :assignee_or_responsible do
let(:link) { "/work_packages/bulk?#{ids_link}"\
"&amp;work_package%5B#{assignee_or_responsible}_id%5D=#{user.id}" }
before { get :index, ids: ids }
it do
assert_tag tag: 'a',
content: user.name,
attributes: { href: assigned_to_link,
attributes: { href: link,
:class => '' }
end
end
shared_examples_for :assigned_to do
let(:assignee_or_responsible) { "assigned_to"}
include_examples :assignee_or_responsible
end
shared_examples_for :responsible do
let(:assignee_or_responsible) { "responsible"}
include_examples :assignee_or_responsible
end
shared_examples_for :duplicate do
let(:duplicate_link) { "/projects/#{project_1.identifier}/work_packages"\
"/new?copy_from=#{ids.first}" }
@ -262,6 +271,8 @@ describe WorkPackages::ContextMenusController do
it_behaves_like :assigned_to
it_behaves_like :responsible
it_behaves_like :duplicate
it_behaves_like :copy
@ -300,6 +311,8 @@ describe WorkPackages::ContextMenusController do
it_behaves_like :assigned_to
it_behaves_like :responsible
it_behaves_like :copy
it_behaves_like :move
@ -329,6 +342,8 @@ describe WorkPackages::ContextMenusController do
it_behaves_like :assigned_to
it_behaves_like :responsible
it_behaves_like :delete
end

@ -411,6 +411,48 @@ describe WorkPackagesController do
end
end
describe 'update w/ a time entry' do
render_views
let(:admin) { FactoryGirl.create(:admin) }
let(:work_package) { FactoryGirl.create(:work_package) }
let(:default_activity) { FactoryGirl.create(:default_activity) }
let(:activity) { FactoryGirl.create(:activity) }
let(:params) do
lambda do |work_package_id, activity_id|
{
:id => work_package_id,
:work_package => {
:time_entry => {
:hours => '',
:comments => '',
:activity_id => activity_id
}
}
}
end
end
before do
User.stub(:current).and_return admin
end
it 'should not try to create a time entry if blank' do
# default activity counts as blank as long as everything else is blank too
put 'update', params.call(work_package.id, default_activity.id)
expect(response.status).to eq(200)
expect(response.body).to have_content("Successful update")
end
it 'should still give an error for a non-blank time entry' do
put 'update', params.call(work_package.id, activity.id)
expect(response.status).to eq(200) # shouldn't this be 400 or similar?
expect(response.body).to have_content("Log time is invalid")
end
end
describe 'update.html' do
let(:wp_params) { { :wp_attribute => double('wp_attribute') } }
let(:params) { { :id => stub_work_package.id, :work_package => wp_params } }

@ -167,15 +167,15 @@ describe WorkPackage do
it { should eq(category.assigned_to) }
end
describe :assignable_users do
describe :assignable_assignees do
let(:user) { FactoryGirl.build_stubbed(:user) }
context "single user" do
before { stub_work_package.project.stub(:assignable_users).and_return([user]) }
before { stub_work_package.project.stub(:possible_assignees).and_return([user]) }
subject { stub_work_package.assignable_users }
subject { stub_work_package.assignable_assignees }
it 'should return all users the project deems to be assignable' do
it 'should return all users the project deems to be possible assignees' do
should include(user)
end
end
@ -189,7 +189,7 @@ describe WorkPackage do
work_package.project.add_member! group, FactoryGirl.create(:role)
end
subject { work_package.assignable_users }
subject { work_package.assignable_assignees }
it { should include(group) }
end
@ -202,21 +202,36 @@ describe WorkPackage do
work_package.project.add_member! group, FactoryGirl.create(:role)
end
subject { work_package.assignable_users }
subject { work_package.assignable_assignees }
it { should_not include(group) }
end
context "multiple users" do
let(:user_2) { FactoryGirl.build_stubbed(:user) }
before { stub_work_package.project.stub(:assignable_users).and_return([user, user_2]) }
before { stub_work_package.project.stub(:assignable_assignees).and_return([user, user_2]) }
subject { stub_work_package.assignable_users.uniq }
subject { stub_work_package.assignable_assignees.uniq }
it { should eq(stub_work_package.assignable_users) }
it { should eq(stub_work_package.assignable_assignees) }
end
end
describe :assignable_responsibles do
let(:user) { FactoryGirl.create(:user) }
let(:group) { FactoryGirl.create(:group) }
before do
work_package.project.add_member! user, FactoryGirl.create(:role)
work_package.project.add_member! group, FactoryGirl.create(:role)
end
subject { work_package.assignable_responsibles }
it { should_not include(group) }
it { should include(user) }
end
describe :assignable_versions do
def stub_shared_versions(v = nil)
versions = v ? [v] : []

Loading…
Cancel
Save