diff --git a/Gemfile b/Gemfile
index b9585134d0..7ca736a95c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -182,7 +182,7 @@ group :test do
# Test prof provides factories from code
# and other niceties
- gem 'test-prof'
+ gem 'test-prof', '~> 0.4.0'
gem 'cucumber', '~> 3.0.0'
gem 'cucumber-rails', '~> 1.5.0', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 7de20ef095..a22ec28ceb 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -564,7 +564,7 @@ GEM
syck (1.3.0)
sys-filesystem (1.1.8)
ffi
- test-prof (0.1.0)
+ test-prof (0.4.8)
thin (1.7.2)
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0, >= 1.0.4)
@@ -724,7 +724,7 @@ DEPENDENCIES
svg-graph (~> 2.1.0)
syck (~> 1.3.0)
sys-filesystem (~> 1.1.4)
- test-prof
+ test-prof (~> 0.4.0)
thin (~> 1.7.2)
timecop (~> 0.9.0)
transactional_lock!
diff --git a/app/assets/stylesheets/content/_forms.sass b/app/assets/stylesheets/content/_forms.sass
index e8202ccf1f..bc4291724d 100644
--- a/app/assets/stylesheets/content/_forms.sass
+++ b/app/assets/stylesheets/content/_forms.sass
@@ -163,6 +163,9 @@ $form--field-types: (text-field, text-area, select, check-box, radio-button, ran
.form--space
padding-top: 10px
+ &.-big
+ padding-top: 20px
+
.form--row
@include grid-block
@include grid-visible-overflow
@@ -377,6 +380,10 @@ fieldset.form--fieldset
line-height: $base-line-height
padding-right: 0.325rem
+ &.-top
+ align-self: flex-start
+ line-height: 2.15rem
+
.form--label-required
@include default-transition
@include varprop(color, primary-color-dark)
@@ -580,6 +587,56 @@ input[readonly].-clickable
.form &
margin-bottom: 0rem
+.form--select-autocompleter
+ max-height: 400px
+ overflow-y: auto
+
+.form--selected-value
+ width: calc(100% - 42px)
+ padding: 3px
+ line-height: 2
+
+.form--selected-value--container
+ display: inline-block
+ border: 1px solid transparent
+ border-radius: 2px
+ overflow: visible
+ width: 100%
+ position: relative
+
+ &:last-of-type
+ margin-bottom: 0.5rem
+
+ &:hover,
+ &:focus,
+ &.-focus
+ text-decoration: none
+ color: $body-font-color
+ border-color: $inplace-edit--border-color
+
+ .form--selected-value--remover
+ visibility: visible
+
+ a.form--selected-value--remover
+ text-decoration: none
+ color: $body-font-color
+
+.form--selected-value--remover
+ position: absolute
+ height: 100%
+ right: 0
+ text-align: center
+ width: 32px
+ background: #F8F8F8
+ border-left: 1px solid #ddd
+ color: $body-font-color
+ visibility: hidden
+ line-height: 2rem
+
+.form--selected-value--list
+ margin-left: 0rem
+ margin-bottom: 0rem
+
.form--text-area
@extend %input-style
diff --git a/app/assets/stylesheets/content/_hidden.sass b/app/assets/stylesheets/content/_hidden.sass
new file mode 100644
index 0000000000..fc57dc72a8
--- /dev/null
+++ b/app/assets/stylesheets/content/_hidden.sass
@@ -0,0 +1,36 @@
+//-- copyright
+// OpenProject is a project management system.
+// Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
+//++
+
+// Overriding the browser's default [hidden] rule to force hiding the element as having a
+// more specific rule (and almost everything will be more specific than [hidden]) will prevent using
+// the hidden-attribute otherwise:
+// "Note: Changing the value of the CSS display property on an element with the hidden attribute overrides the behavior.
+// For instance, elements styled display: flex will be displayed despite the hidden attribute's presence."
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/hidden
+[hidden]
+ display: none !important
diff --git a/app/assets/stylesheets/content/_hide_section.sass b/app/assets/stylesheets/content/_hide_until_initialized.sass
similarity index 96%
rename from app/assets/stylesheets/content/_hide_section.sass
rename to app/assets/stylesheets/content/_hide_until_initialized.sass
index 8bde0f346b..ea5f50aaae 100644
--- a/app/assets/stylesheets/content/_hide_section.sass
+++ b/app/assets/stylesheets/content/_hide_until_initialized.sass
@@ -26,7 +26,8 @@
// See docs/COPYRIGHT.rdoc for more details.
//++
-hide-section
+hide-section,
+autocomplete-select-decoration
display: none
&.-initialized
diff --git a/app/assets/stylesheets/content/_index.sass b/app/assets/stylesheets/content/_index.sass
index 54a566c199..812dea17b5 100644
--- a/app/assets/stylesheets/content/_index.sass
+++ b/app/assets/stylesheets/content/_index.sass
@@ -63,6 +63,7 @@
@import content/on_off_status
@import content/custom_actions
@import content/user_mention
-@import content/hide_section
+@import content/hide_until_initialized
+@import content/hidden
@import content/menus/_project_autocompletion
diff --git a/app/assets/stylesheets/content/work_packages/inplace_editing/_edit_fields.sass b/app/assets/stylesheets/content/work_packages/inplace_editing/_edit_fields.sass
index a8febe7b66..006723db0c 100644
--- a/app/assets/stylesheets/content/work_packages/inplace_editing/_edit_fields.sass
+++ b/app/assets/stylesheets/content/work_packages/inplace_editing/_edit_fields.sass
@@ -39,6 +39,9 @@
font-size: 14px
margin-bottom: 0
+ &:last-of-type
+ margin-bottom: 0
+
.read-value--html
*
// Adjust size of all members in the html
@@ -46,3 +49,6 @@
p
// Add some margin to paragraphs in long-text fields
margin-bottom: 1em
+
+.wp-edit-field--text
+ width: 100%
diff --git a/app/assets/stylesheets/openproject/_accessibility.sass b/app/assets/stylesheets/openproject/_accessibility.sass
index b19797fb79..5761fdf0c4 100644
--- a/app/assets/stylesheets/openproject/_accessibility.sass
+++ b/app/assets/stylesheets/openproject/_accessibility.sass
@@ -35,9 +35,6 @@
height: 1px
overflow: hidden
-[hidden]
- display: none
-
// -------------------- Rules for accessibility mode --------------------
body.accessibility-mode
// -------------------- General --------------------
diff --git a/app/models/custom_actions/actions/strategies/validate_in_range.rb b/app/models/custom_actions/actions/strategies/validate_in_range.rb
index 26da0f95fc..cf8698a5b6 100644
--- a/app/models/custom_actions/actions/strategies/validate_in_range.rb
+++ b/app/models/custom_actions/actions/strategies/validate_in_range.rb
@@ -43,7 +43,7 @@ module CustomActions::Actions::Strategies::ValidateInRange
private
def validate_in_interval(errors)
- return unless values.length == 1
+ return unless values.compact.length == 1
validate_greater_than_minimum(errors)
validate_smaller_than_maximum(errors)
diff --git a/app/models/custom_actions/conditions/base.rb b/app/models/custom_actions/conditions/base.rb
index 5c02af86ac..f3eba67831 100644
--- a/app/models/custom_actions/conditions/base.rb
+++ b/app/models/custom_actions/conditions/base.rb
@@ -44,7 +44,6 @@ class CustomActions::Conditions::Base
def allowed_values
associated
.map { |value, label| { value: value, label: label } }
- .unshift(value: nil, label: I18n.t('placeholders.default'))
end
def human_name
diff --git a/app/models/queries/filter_serializer.rb b/app/models/queries/filter_serializer.rb
index cf309c6f33..15b65b4d8f 100644
--- a/app/models/queries/filter_serializer.rb
+++ b/app/models/queries/filter_serializer.rb
@@ -34,7 +34,11 @@ module Queries::FilterSerializer
def self.load(serialized_filter_hash)
return [] if serialized_filter_hash.nil?
- (YAML.load(serialized_filter_hash) || {}).each_with_object([]) do |(field, options), array|
+ # yeah, dunno, but apparently '=' may have been serialized as a Syck::DefaultKey instance...
+ yaml = serialized_filter_hash
+ .gsub('!ruby/object:Syck::DefaultKey {}', '"="')
+
+ (YAML.load(yaml) || {}).each_with_object([]) do |(field, options), array|
options = options.with_indifferent_access
filter = filter_for(field, true)
filter.operator = options['operator']
diff --git a/app/views/custom_actions/_form.html.erb b/app/views/custom_actions/_form.html.erb
index fbffbac90e..ee9056baa0 100644
--- a/app/views/custom_actions/_form.html.erb
+++ b/app/views/custom_actions/_form.html.erb
@@ -15,16 +15,20 @@
<% @custom_action.all_conditions.each do |condition| %>
+
<% end %>
@@ -37,17 +41,21 @@
<% @custom_action.all_actions.each do |action| %>
-
-
-
-
-
+
+
+
+
- {{ attachment.fileName || attachment.customName || attachment.name }}
-
-
-
-
-
-
-
+ {{ attachment.fileName || attachment.customName || attachment.name }}
+
+
-
+
+
+
+
diff --git a/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.html b/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.html
index f289deee0b..7298006131 100644
--- a/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.html
+++ b/frontend/app/components/wp-attachments/wp-attachment-list/wp-attachment-list.html
@@ -1,14 +1,10 @@
diff --git a/frontend/app/components/wp-edit/field-types/wp-edit-boolean-field.directive.html b/frontend/app/components/wp-edit/field-types/wp-edit-boolean-field.directive.html
index ce40c7fcb6..8a87f816e1 100644
--- a/frontend/app/components/wp-edit/field-types/wp-edit-boolean-field.directive.html
+++ b/frontend/app/components/wp-edit/field-types/wp-edit-boolean-field.directive.html
@@ -6,5 +6,6 @@
ng-change="vm.handleUserSubmit()"
ng-focus="vm.handleUserFocus()"
ng-blur="vm.handleUserBlur()"
+ ng-keydown="vm.handleUserKeydown($event)"
ng-disabled="vm.field.inFlight"
ng-attr-id="{{vm.htmlId}}" />
diff --git a/frontend/app/components/wp-single-view-tabs/watchers-tab/watchers-tab.html b/frontend/app/components/wp-single-view-tabs/watchers-tab/watchers-tab.html
index b3ab270c7e..b08cb1d567 100644
--- a/frontend/app/components/wp-single-view-tabs/watchers-tab/watchers-tab.html
+++ b/frontend/app/components/wp-single-view-tabs/watchers-tab/watchers-tab.html
@@ -6,7 +6,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/lib/tasks/shared/legacy_attachment.rb b/lib/tasks/shared/legacy_attachment.rb
index 9fc185a7aa..b88d163941 100644
--- a/lib/tasks/shared/legacy_attachment.rb
+++ b/lib/tasks/shared/legacy_attachment.rb
@@ -27,10 +27,14 @@ module Tasks
module_function
def migrate_attachment(attachment)
- file = legacy_file_name attachment.disk_filename
- new_file = strip_timestamp_from_filename(file)
+ file_name = attachment.disk_filename.presence
- if File.readable? file
+ if file_name
+ file = legacy_file_name file_name
+ new_file = strip_timestamp_from_filename(file)
+ end
+
+ if file_name && File.readable?(file)
FileUtils.move file, new_file
attachment.file = File.open(new_file)
attachment.filename = ''
diff --git a/spec/features/custom_fields/custom_fields_spec.rb b/spec/features/custom_fields/custom_fields_spec.rb
index e8846c40b4..36903bc061 100644
--- a/spec/features/custom_fields/custom_fields_spec.rb
+++ b/spec/features/custom_fields/custom_fields_spec.rb
@@ -78,8 +78,10 @@ describe 'custom fields', js: true do
with_enterprise_token(:multiselect_custom_fields)
cf_page.visit!
+ expect_angular_frontend_initialized
click_on custom_field.name
+ expect_angular_frontend_initialized
end
it "adds new options" do
diff --git a/spec/features/work_packages/bulk/copy_work_package_spec.rb b/spec/features/work_packages/bulk/copy_work_package_spec.rb
new file mode 100644
index 0000000000..821981085d
--- /dev/null
+++ b/spec/features/work_packages/bulk/copy_work_package_spec.rb
@@ -0,0 +1,118 @@
+require 'spec_helper'
+require 'features/page_objects/notification'
+
+describe 'Copy work packages through Rails view', js: true do
+ let(:dev_role) do
+ FactoryGirl.create :role,
+ permissions: %i[view_work_packages]
+ end
+ let(:mover_role) do
+ FactoryGirl.create :role,
+ permissions: %i[view_work_packages copy_work_packages move_work_packages manage_subtasks add_work_packages]
+ end
+ let(:dev) do
+ FactoryGirl.create :user,
+ firstname: 'Dev',
+ lastname: 'Guy',
+ member_in_project: project,
+ member_through_role: dev_role
+ end
+ let(:mover) do
+ FactoryGirl.create :admin,
+ firstname: 'Manager',
+ lastname: 'Guy',
+ member_in_project: project,
+ member_through_role: mover_role
+ end
+
+ let(:type) { FactoryGirl.create :type, name: 'Bug' }
+ let(:type2) { FactoryGirl.create :type, name: 'Risk' }
+
+ let!(:project) { FactoryGirl.create(:project, name: 'Source', types: [type, type2]) }
+ let!(:project2) { FactoryGirl.create(:project, name: 'Target', types: [type, type2]) }
+
+ let!(:work_package) {
+ FactoryGirl.create(:work_package,
+ author: dev,
+ project: project,
+ type: type)
+ }
+ let!(:work_package2) {
+ FactoryGirl.create(:work_package,
+ author: dev,
+ project: project,
+ type: type)
+ }
+
+ let(:status) { work_package.status }
+ let!(:status2) { FactoryGirl.create :default_status }
+ let!(:workflow) do
+ FactoryGirl.create :workflow,
+ type_id: type2.id,
+ old_status: work_package.status,
+ new_status: status2,
+ role: mover_role
+ end
+
+ let(:wp_table) { ::Pages::WorkPackagesTable.new(project) }
+ let(:context_menu) { Components::WorkPackages::ContextMenu.new }
+
+ before do
+ login_as current_user
+ wp_table.visit!
+ expect_angular_frontend_initialized
+ wp_table.expect_work_package_listed work_package, work_package2
+
+ # Select all work packages
+ find('body').send_keys [:control, 'a']
+ end
+
+ describe 'copying work packages' do
+ context 'with permission' do
+ let(:current_user) { mover }
+
+ before do
+ context_menu.open_for work_package
+ context_menu.choose 'Bulk copy'
+
+ # On work packages move page
+ expect(page).to have_selector('#new_project_id')
+ select 'Target', from: 'new_project_id'
+ click_on 'Copy and follow'
+ end
+
+ it 'moves parent and child wp to a new project' do
+ expect_angular_frontend_initialized
+ wp_table.expect_work_package_count 2
+ expect(page).to have_selector('#projects-menu', text: 'Target')
+
+ # Should not move the sources
+ work_package2.reload
+ work_package.reload
+ expect(work_package.project_id).to eq(project.id)
+ expect(work_package2.project_id).to eq(project.id)
+
+ # Check project of last two created wps
+ copied_wps = WorkPackage.last(2)
+ expect(copied_wps.map(&:project_id)).to eq([project2.id, project2.id])
+ end
+
+ context 'when the target project does not have the type' do
+ let!(:project2) { FactoryGirl.create(:project, name: 'Target', types: [type2]) }
+
+ it 'does moves the work package and changes the type' do
+ expect(page).to have_selector('.flash.error', text: "Failed to save 2 work package(s) on 2 selected:")
+ end
+ end
+ end
+
+ context 'without permission' do
+ let(:current_user) { dev }
+
+ it 'does not allow to copy' do
+ context_menu.open_for work_package
+ context_menu.expect_no_options 'Bulk copy'
+ end
+ end
+ end
+end
diff --git a/spec/features/work_packages/bulk/move_work_package_spec.rb b/spec/features/work_packages/bulk/move_work_package_spec.rb
new file mode 100644
index 0000000000..1844751d10
--- /dev/null
+++ b/spec/features/work_packages/bulk/move_work_package_spec.rb
@@ -0,0 +1,123 @@
+require 'spec_helper'
+require 'features/page_objects/notification'
+
+describe 'Moving a work package through Rails view', js: true do
+ let(:dev_role) do
+ FactoryGirl.create :role,
+ permissions: %i[view_work_packages add_work_packages]
+ end
+ let(:mover_role) do
+ FactoryGirl.create :role,
+ permissions: %i[view_work_packages move_work_packages manage_subtasks add_work_packages]
+ end
+ let(:dev) do
+ FactoryGirl.create :user,
+ firstname: 'Dev',
+ lastname: 'Guy',
+ member_in_project: project,
+ member_through_role: dev_role
+ end
+ let(:mover) do
+ FactoryGirl.create :admin,
+ firstname: 'Manager',
+ lastname: 'Guy',
+ member_in_project: project,
+ member_through_role: mover_role
+ end
+
+ let(:type) { FactoryGirl.create :type, name: 'Bug' }
+ let(:type2) { FactoryGirl.create :type, name: 'Risk' }
+
+ let!(:project) { FactoryGirl.create(:project, name: 'Source', types: [type, type2]) }
+ let!(:project2) { FactoryGirl.create(:project, name: 'Target', types: [type, type2]) }
+
+ let!(:work_package) {
+ FactoryGirl.create(:work_package,
+ author: dev,
+ project: project,
+ type: type)
+ }
+ let!(:child_wp) {
+ FactoryGirl.create(:work_package,
+ author: dev,
+ parent: work_package,
+ project: project,
+ type: type)
+ }
+
+ let(:status) { work_package.status }
+ let!(:status2) { FactoryGirl.create :default_status }
+ let!(:workflow) do
+ FactoryGirl.create :workflow,
+ type_id: type2.id,
+ old_status: work_package.status,
+ new_status: status2,
+ role: mover_role
+ end
+
+ let(:wp_table) { ::Pages::WorkPackagesTable.new(project) }
+ let(:context_menu) { Components::WorkPackages::ContextMenu.new }
+
+ before do
+ login_as current_user
+ wp_table.visit!
+ expect_angular_frontend_initialized
+ wp_table.expect_work_package_listed work_package, child_wp
+ end
+
+ describe 'moving a work package and its children' do
+ context 'with permission' do
+ let(:current_user) { mover }
+
+ before do
+ expect(child_wp.project_id).to eq(project.id)
+
+ context_menu.open_for work_package
+ context_menu.choose 'Move'
+
+ # On work packages move page
+ expect(page).to have_selector('#new_project_id')
+ select 'Target', from: 'new_project_id'
+ click_on 'Move and follow'
+ end
+
+
+ it 'moves parent and child wp to a new project' do
+ expect_angular_frontend_initialized
+ expect(page).to have_selector('.wp-edit-field.subject', text: work_package.subject, wait: 10)
+ expect(page).to have_selector('#projects-menu', text: 'Target')
+
+ # Should move its children
+ child_wp.reload
+ expect(child_wp.project_id).to eq(project2.id)
+ end
+
+ context 'when the target project does not have the type' do
+ let!(:project2) { FactoryGirl.create(:project, name: 'Target', types: [type2]) }
+
+ it 'does moves the work package and changes the type' do
+ expect_angular_frontend_initialized
+ expect(page).to have_selector('.wp-edit-field.subject', text: work_package.subject, wait: 10)
+ expect(page).to have_selector('#projects-menu', text: 'Target')
+
+ # Should NOT have moved
+ child_wp.reload
+ work_package.reload
+ expect(work_package.project_id).to eq(project2.id)
+ expect(work_package.type_id).to eq(type2.id)
+ expect(child_wp.project_id).to eq(project2.id)
+ expect(child_wp.type_id).to eq(type2.id)
+ end
+ end
+ end
+
+ context 'without permission' do
+ let(:current_user) { dev }
+
+ it 'does not allow to move' do
+ context_menu.open_for work_package
+ context_menu.expect_no_options 'Move'
+ end
+ end
+ end
+end
diff --git a/spec/features/work_packages/cancel_editing_spec.rb b/spec/features/work_packages/cancel_editing_spec.rb
index dfefb3b7b8..b3aaec8c52 100644
--- a/spec/features/work_packages/cancel_editing_spec.rb
+++ b/spec/features/work_packages/cancel_editing_spec.rb
@@ -52,9 +52,8 @@ describe 'Cancel editing work package', js: true do
def expect_active_edit(path)
visit path
- loading_indicator_saveguard
-
- expect(page).to have_selector('#wp-new-inline-edit--field-subject')
+ expect_angular_frontend_initialized
+ expect(page).to have_selector('#wp-new-inline-edit--field-subject', wait: 10)
end
def expect_subject(val)
diff --git a/spec/features/work_packages/tabs/watcher_tab_spec.rb b/spec/features/work_packages/tabs/watcher_tab_spec.rb
index 83330dc6c3..251ce01758 100644
--- a/spec/features/work_packages/tabs/watcher_tab_spec.rb
+++ b/spec/features/work_packages/tabs/watcher_tab_spec.rb
@@ -67,8 +67,8 @@ describe 'Watcher tab', js: true, selenium: true do
expect_button_is_watching
# Remove watcher from list
- page.find('.watcher-element', text: user.name).hover
- page.find('.remove-watcher-btn').click
+ page.find('wp-watcher-entry', text: user.name).hover
+ page.find('.form--selected-value--remover').click
# Expect the removal of the user to toggle WP watch button
expect(page).to have_no_selector('.work-package--watcher-name')
diff --git a/spec/models/custom_actions/conditions/project_spec.rb b/spec/models/custom_actions/conditions/project_spec.rb
index 9c756e8026..2964a67a5a 100644
--- a/spec/models/custom_actions/conditions/project_spec.rb
+++ b/spec/models/custom_actions/conditions/project_spec.rb
@@ -41,8 +41,7 @@ describe CustomActions::Conditions::Project, type: :model do
.and_return(projects)
expect(instance.allowed_values)
- .to eql([{ value: nil, label: '-' },
- { value: projects.first.id, label: projects.first.name },
+ .to eql([{ value: projects.first.id, label: projects.first.name },
{ value: projects.last.id, label: projects.last.name }])
end
end
diff --git a/spec/models/custom_actions/conditions/role_spec.rb b/spec/models/custom_actions/conditions/role_spec.rb
index 861d200097..ea831f7858 100644
--- a/spec/models/custom_actions/conditions/role_spec.rb
+++ b/spec/models/custom_actions/conditions/role_spec.rb
@@ -42,8 +42,7 @@ describe CustomActions::Conditions::Role, type: :model do
.and_return(roles)
expect(instance.allowed_values)
- .to eql([{ value: nil, label: '-' },
- { value: roles.first.id, label: roles.first.name },
+ .to eql([{ value: roles.first.id, label: roles.first.name },
{ value: roles.last.id, label: roles.last.name }])
end
end
diff --git a/spec/models/custom_actions/conditions/status_spec.rb b/spec/models/custom_actions/conditions/status_spec.rb
index 8087655ed6..093c5dddb0 100644
--- a/spec/models/custom_actions/conditions/status_spec.rb
+++ b/spec/models/custom_actions/conditions/status_spec.rb
@@ -38,11 +38,10 @@ describe CustomActions::Conditions::Status, type: :model do
FactoryGirl.build_stubbed(:status)]
allow(Status)
.to receive_message_chain(:select, :order)
- .and_return(statuses)
+ .and_return(statuses)
expect(instance.allowed_values)
- .to eql([{ value: nil, label: '-' },
- { value: statuses.first.id, label: statuses.first.name },
+ .to eql([{ value: statuses.first.id, label: statuses.first.name },
{ value: statuses.last.id, label: statuses.last.name }])
end
end
diff --git a/spec/models/custom_actions/conditions/type_spec.rb b/spec/models/custom_actions/conditions/type_spec.rb
index d1977e2163..9eaddee411 100644
--- a/spec/models/custom_actions/conditions/type_spec.rb
+++ b/spec/models/custom_actions/conditions/type_spec.rb
@@ -41,8 +41,7 @@ describe CustomActions::Conditions::Type, type: :model do
.and_return(types)
expect(instance.allowed_values)
- .to eql([{ value: nil, label: '-' },
- { value: types.first.id, label: types.first.name },
+ .to eql([{ value: types.first.id, label: types.first.name },
{ value: types.last.id, label: types.last.name }])
end
end
diff --git a/spec/requests/api/v3/work_packages/form/work_package_form_resource_spec.rb b/spec/requests/api/v3/work_packages/form/work_package_form_resource_spec.rb
index e69478b3d8..b7d5dcfd34 100644
--- a/spec/requests/api/v3/work_packages/form/work_package_form_resource_spec.rb
+++ b/spec/requests/api/v3/work_packages/form/work_package_form_resource_spec.rb
@@ -35,8 +35,8 @@ describe 'API v3 Work package form resource', type: :request do
include API::V3::Utilities::PathHelper
shared_let(:project) { FactoryGirl.create(:project, is_public: false) }
- shared_let(:work_package, reload: true) { FactoryGirl.create(:work_package, project: @project) }
- shared_let(:authorized_user) { FactoryGirl.create(:user, member_in_project: @project) }
+ shared_let(:work_package, reload: true) { FactoryGirl.create(:work_package, project: project) }
+ shared_let(:authorized_user) { FactoryGirl.create(:user, member_in_project: project) }
shared_let(:unauthorized_user) { FactoryGirl.create(:user) }
describe '#post' do
diff --git a/spec/routing/repositories_routing_spec.rb b/spec/routing/repositories_routing_spec.rb
index dad3661398..113bc9765f 100644
--- a/spec/routing/repositories_routing_spec.rb
+++ b/spec/routing/repositories_routing_spec.rb
@@ -34,6 +34,7 @@ describe RepositoriesController, type: :routing do
expect(get('/projects/testproject/repository'))
.to route_to(controller: 'repositories',
action: 'show',
+ format: 'html',
project_id: 'testproject')
}
@@ -41,6 +42,7 @@ describe RepositoriesController, type: :routing do
expect(get('/projects/testproject/repository/path/to/file.c'))
.to route_to(controller: 'repositories',
action: 'show',
+ format: 'html',
project_id: 'testproject',
path: 'path/to/file.c')
}
@@ -49,6 +51,7 @@ describe RepositoriesController, type: :routing do
expect(get('/projects/testproject/repository/folder%20with%20spaces'))
.to route_to(controller: 'repositories',
action: 'show',
+ format: 'html',
project_id: 'testproject',
path: 'folder with spaces')
}
@@ -57,11 +60,57 @@ describe RepositoriesController, type: :routing do
expect(get('/projects/testproject/repository/revisions/5'))
.to route_to(controller: 'repositories',
action: 'show',
+ format: 'html',
rev: '5',
project_id: 'testproject')
}
end
+ describe 'changes with js file (regression #24960)' do
+ it {
+ expect(get('/projects/testproject/repository/revisions/my-branch/changes/assets/test.js'))
+ .to route_to(controller: 'repositories',
+ action: 'changes',
+ path: 'assets/test.js',
+ rev: 'my-branch',
+ format: 'html',
+ project_id: 'testproject')
+ }
+ end
+
+ describe 'show with git tags (regression test #27230)' do
+ it {
+ expect(get('/projects/testproject/repository/sub?rev=mytags%2Ffoo&branch=&tag=mytags%2Ffoo'))
+ .to route_to(controller: 'repositories',
+ action: 'show',
+ path: 'sub',
+ branch: '',
+ rev: 'mytags/foo',
+ tag: 'mytags/foo',
+ format: 'html',
+ project_id: 'testproject')
+ }
+ it {
+ expect(get('/projects/testproject/repository?rev=FSubCommit-a&branch=master&tag=FSubCommit-a'))
+ .to route_to(controller: 'repositories',
+ action: 'show',
+ branch: 'master',
+ rev: 'FSubCommit-a',
+ tag: 'FSubCommit-a',
+ format: 'html',
+ project_id: 'testproject')
+ }
+ it {
+ expect(get('/projects/testproject/repository/revisions/FSubCommit-a/sub'))
+ .to route_to(controller: 'repositories',
+ action: 'show',
+ path: 'sub',
+ rev: 'FSubCommit-a',
+ format: 'html',
+ project_id: 'testproject')
+ }
+ end
+
describe 'edit' do
it {
expect(get('/projects/testproject/repository/edit'))
@@ -171,6 +220,7 @@ describe RepositoriesController, type: :routing do
expect(get('/projects/testproject/repository/browse/path/to/file.c'))
.to route_to(controller: 'repositories',
action: 'browse',
+ format: 'html',
project_id: 'testproject',
path: 'path/to/file.c')
}
@@ -181,6 +231,7 @@ describe RepositoriesController, type: :routing do
expect(get('/projects/testproject/repository/entry/path/to/file.c'))
.to route_to(controller: 'repositories',
action: 'entry',
+ format: 'html',
project_id: 'testproject',
path: 'path/to/file.c')
}
@@ -189,6 +240,7 @@ describe RepositoriesController, type: :routing do
expect(get('/projects/testproject/repository/revisions/2/entry/path/to/file.c'))
.to route_to(controller: 'repositories',
action: 'entry',
+ format: 'html',
project_id: 'testproject',
path: 'path/to/file.c',
rev: '2')
@@ -219,6 +271,7 @@ describe RepositoriesController, type: :routing do
expect(get('/projects/testproject/repository/annotate/path/to/file.c'))
.to route_to(controller: 'repositories',
action: 'annotate',
+ format: 'html',
project_id: 'testproject',
path: 'path/to/file.c')
}
@@ -226,6 +279,7 @@ describe RepositoriesController, type: :routing do
expect(get('/projects/testproject/repository/revisions/5/annotate/path/to/file.c'))
.to route_to(controller: 'repositories',
action: 'annotate',
+ format: 'html',
project_id: 'testproject',
path: 'path/to/file.c',
rev: '5')
@@ -237,6 +291,7 @@ describe RepositoriesController, type: :routing do
expect(get('/projects/testproject/repository/changes/path/to/file.c'))
.to route_to(controller: 'repositories',
action: 'changes',
+ format: 'html',
project_id: 'testproject',
path: 'path/to/file.c')
}
@@ -245,6 +300,7 @@ describe RepositoriesController, type: :routing do
expect(get('/projects/testproject/repository/revisions/5/changes/path/to/file.c'))
.to route_to(controller: 'repositories',
action: 'changes',
+ format: 'html',
project_id: 'testproject',
path: 'path/to/file.c',
rev: '5')
diff --git a/spec/support/angular.rb b/spec/support/angular.rb
index a2221de521..b894d27fa1 100644
--- a/spec/support/angular.rb
+++ b/spec/support/angular.rb
@@ -29,7 +29,7 @@
##
# Wait for the angular bootstrap to have happened
def expect_angular_frontend_initialized
- expect(page).to have_selector('.__ng2-bootstrap-has-run')
- expect(page).to have_selector('.__ng-bootstrap-has-run')
+ expect(page).to have_selector('.__ng2-bootstrap-has-run', wait: 20)
+ expect(page).to have_selector('.__ng-bootstrap-has-run', wait: 20)
end
diff --git a/spec/support/pages/admin/custom_actions/form.rb b/spec/support/pages/admin/custom_actions/form.rb
index 9ff52f88b8..b84ca52af1 100644
--- a/spec/support/pages/admin/custom_actions/form.rb
+++ b/spec/support/pages/admin/custom_actions/form.rb
@@ -41,17 +41,9 @@ module Pages
end
def add_action(name, value)
- within '#custom-actions-form--actions' do
- select name, from: 'Add'
+ select name, from: 'Add'
- if page.has_select?(name)
- Array(value).each do |val|
- select val, from: name
- end
- else
- fill_in name, with: value
- end
- end
+ set_action_value(name, value)
end
def remove_action(name)
@@ -63,20 +55,39 @@ module Pages
end
def set_action(name, value)
- within '#custom-actions-form--active-actions' do
- field = find('.form--field', text: name)
- within field do
- select value, from: name
- end
- end
+ set_action_value(name, value)
rescue Capybara::ElementNotFound
add_action(name, value)
end
def set_condition(name, value)
- within '#custom-actions-form--conditions' do
- Array(value).each do |val|
- select val, from: name
+ Array(value).each do |val|
+ fill_in name, with: val
+ find('.ui-menu-item', text: val).click
+ end
+ end
+
+ private
+
+ def set_action_value(name, value)
+ field = find('#custom-actions-form--active-actions .form--field', text: name)
+
+ autocomplete = false
+
+ Array(value).each do |val|
+ within field do
+ if has_selector?('.form--selected-value--container')
+ find('.form--selected-value--container').click
+ autocomplete = true
+ elsif has_selector?('.form--input.-autocomplete')
+ autocomplete = true
+ end
+
+ fill_in name, with: val
+ end
+
+ if autocomplete
+ find('.ui-menu-item', text: val).click
end
end
end
diff --git a/spec/support/pages/work_packages_table.rb b/spec/support/pages/work_packages_table.rb
index a138a1b8a8..6833fedfbb 100644
--- a/spec/support/pages/work_packages_table.rb
+++ b/spec/support/pages/work_packages_table.rb
@@ -54,6 +54,12 @@ module Pages
end
end
+ def expect_work_package_count(n)
+ within(table_container) do
+ expect(page).to have_selector(".wp--row", count: n, wait: 20)
+ end
+ end
+
def expect_work_package_not_listed(*work_packages, wait: 3)
within(table_container) do
work_packages.each do |wp|
@@ -70,7 +76,9 @@ module Pages
end
def has_work_packages_listed?(work_packages)
- work_packages.all? { |wp| has_text? wp.subject }
+ work_packages.all? do |wp|
+ has_selector?(".wp-row-#{wp.id} td.subject", text: wp.subject, wait: 20)
+ end
end
def expect_no_work_package_listed
diff --git a/spec/support/shared/scroll_element_into_view.rb b/spec/support/shared/scroll_into_view_helpers.rb
similarity index 100%
rename from spec/support/shared/scroll_element_into_view.rb
rename to spec/support/shared/scroll_into_view_helpers.rb
diff --git a/spec/support/shared_let.rb b/spec/support/shared_let.rb
index 61c2bb404b..c94f38c721 100644
--- a/spec/support/shared_let.rb
+++ b/spec/support/shared_let.rb
@@ -35,20 +35,11 @@
# Caveats: Set +reload: true+ if you plan to modify this value, otherwise Rails may still
# have cached the local value. This will perform a database update, but is much faster
# than creating new records (especially, work packages).
-def shared_let(key, reload: false, &block)
- var = "@#{key}"
-
- before_all do
- # Use instance_eval so following blocks may reference earlier ones
- result = instance_eval &block
- instance_variable_set(var, result)
- end
-
- let(key) do
- value = instance_variable_get(var)
- value.reload if reload
+#
+# Since test-prof added `let_it_be` this is only a wrapper for it
+require 'test_prof/recipes/rspec/let_it_be'
- value
- end
+def shared_let(key, reload: false, refind: false, &block)
+ let_it_be(key, reload: reload, refind: refind, &block)
end