Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/models/work_item_spec.rb')
-rw-r--r--spec/models/work_item_spec.rb244
1 files changed, 236 insertions, 8 deletions
diff --git a/spec/models/work_item_spec.rb b/spec/models/work_item_spec.rb
index 6aacaa3c119..5a525d83c3b 100644
--- a/spec/models/work_item_spec.rb
+++ b/spec/models/work_item_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe WorkItem, feature_category: :portfolio_management do
+ using RSpec::Parameterized::TableSyntax
+
let_it_be(:reusable_project) { create(:project) }
describe 'associations' do
@@ -99,15 +101,28 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
end
end
- describe '#supports_assignee?' do
- let(:work_item) { build(:work_item, :task) }
+ describe '#get_widget' do
+ let(:work_item) { build(:work_item, description: 'foo') }
- before do
- allow(work_item.work_item_type).to receive(:supports_assignee?).and_return(false)
+ it 'returns widget object' do
+ expect(work_item.get_widget(:description)).to be_an_instance_of(WorkItems::Widgets::Description)
end
- it 'delegates the call to its work item type' do
- expect(work_item.supports_assignee?).to be(false)
+ context 'when widget does not exist' do
+ it 'returns nil' do
+ expect(work_item.get_widget(:nop)).to be_nil
+ end
+ end
+ end
+
+ describe '#supports_assignee?' do
+ Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter::WIDGETS_FOR_TYPE.each_pair do |base_type, widgets|
+ specify do
+ work_item = build(:work_item, base_type)
+ supports_assignee = widgets.include?(:assignees)
+
+ expect(work_item.supports_assignee?).to eq(supports_assignee)
+ end
end
end
@@ -117,7 +132,7 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
subject { work_item.supported_quick_action_commands }
it 'returns quick action commands supported for all work items' do
- is_expected.to include(:title, :reopen, :close, :cc, :tableflip, :shrug)
+ is_expected.to include(:title, :reopen, :close, :cc, :tableflip, :shrug, :type)
end
context 'when work item supports the assignee widget' do
@@ -127,7 +142,7 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
end
context 'when work item does not the assignee widget' do
- let(:work_item) { build(:work_item, :incident) }
+ let(:work_item) { build(:work_item, :test_case) }
it 'omits assignee related quick action commands' do
is_expected.not_to include(:assign, :unassign, :reassign)
@@ -163,6 +178,30 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
end
end
+ describe 'transform_quick_action_params' do
+ let(:work_item) { build(:work_item, :task) }
+
+ subject(:transformed_params) do
+ work_item.transform_quick_action_params({
+ title: 'bar',
+ assignee_ids: ['foo']
+ })
+ end
+
+ it 'correctly separates widget params from regular params' do
+ expect(transformed_params).to eq({
+ common: {
+ title: 'bar'
+ },
+ widgets: {
+ assignees_widget: {
+ assignee_ids: ['foo']
+ }
+ }
+ })
+ end
+ end
+
describe 'callbacks' do
describe 'record_create_action' do
it 'records the creation action after saving' do
@@ -290,6 +329,20 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
end
end
+ describe '#link_reference_pattern' do
+ let(:match_data) { described_class.link_reference_pattern.match(link_reference_url) }
+
+ context 'with work item url' do
+ let(:link_reference_url) { 'http://localhost/namespace/project/-/work_items/1' }
+
+ it 'matches with expected attributes' do
+ expect(match_data['namespace']).to eq('namespace')
+ expect(match_data['project']).to eq('project')
+ expect(match_data['work_item']).to eq('1')
+ end
+ end
+ end
+
context 'with hierarchy' do
let_it_be(:type1) { create(:work_item_type, namespace: reusable_project.namespace) }
let_it_be(:type2) { create(:work_item_type, namespace: reusable_project.namespace) }
@@ -344,4 +397,179 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
end
end
end
+
+ describe '#allowed_work_item_type_change' do
+ let_it_be(:all_types) { WorkItems::Type::BASE_TYPES.keys }
+
+ it 'is possible to change between all types', :aggregate_failures do
+ all_types.each do |type|
+ work_item = build(:work_item, type, project: reusable_project)
+
+ (all_types - [type]).each do |new_type|
+ work_item.work_item_type_id = WorkItems::Type.default_by_type(new_type).id
+
+ expect(work_item).to be_valid, "#{type} to #{new_type}"
+ end
+ end
+ end
+
+ context 'with ParentLink relation' do
+ let_it_be(:old_type) { create(:work_item_type) }
+ let_it_be(:new_type) { create(:work_item_type) }
+
+ context 'with hierarchy restrictions' do
+ let_it_be(:child_type) { create(:work_item_type) }
+
+ let_it_be_with_reload(:parent) { create(:work_item, work_item_type: old_type, project: reusable_project) }
+ let_it_be_with_reload(:child) { create(:work_item, work_item_type: child_type, project: reusable_project) }
+
+ let_it_be(:hierarchy_restriction) do
+ create(:hierarchy_restriction, parent_type: old_type, child_type: child_type)
+ end
+
+ let_it_be(:link) { create(:parent_link, work_item_parent: parent, work_item: child) }
+
+ context 'when child items restrict the type change' do
+ before do
+ parent.work_item_type = new_type
+ end
+
+ context 'when child items are compatible with the new type' do
+ let_it_be(:hierarchy_restriction_new_type) do
+ create(:hierarchy_restriction, parent_type: new_type, child_type: child_type)
+ end
+
+ it 'allows to change types' do
+ expect(parent).to be_valid
+ expect(parent.errors).to be_empty
+ end
+ end
+
+ context 'when child items are not compatible with the new type' do
+ it 'does not allow to change types' do
+ expect(parent).not_to be_valid
+ expect(parent.errors[:work_item_type_id])
+ .to include("cannot be changed to #{new_type.name} with these child item types.")
+ end
+ end
+ end
+
+ context 'when the parent restricts the type change' do
+ before do
+ child.work_item_type = new_type
+ end
+
+ it 'does not allow to change types' do
+ expect(child.valid?).to eq(false)
+ expect(child.errors[:work_item_type_id])
+ .to include("cannot be changed to #{new_type.name} with #{parent.work_item_type.name} as parent type.")
+ end
+ end
+ end
+
+ context 'with hierarchy depth restriction' do
+ let_it_be_with_reload(:item1) { create(:work_item, work_item_type: new_type, project: reusable_project) }
+ let_it_be_with_reload(:item2) { create(:work_item, work_item_type: new_type, project: reusable_project) }
+ let_it_be_with_reload(:item3) { create(:work_item, work_item_type: new_type, project: reusable_project) }
+ let_it_be_with_reload(:item4) { create(:work_item, work_item_type: new_type, project: reusable_project) }
+
+ let_it_be(:hierarchy_restriction1) do
+ create(:hierarchy_restriction, parent_type: old_type, child_type: new_type)
+ end
+
+ let_it_be(:hierarchy_restriction2) do
+ create(:hierarchy_restriction, parent_type: new_type, child_type: old_type)
+ end
+
+ let_it_be_with_reload(:hierarchy_restriction3) do
+ create(:hierarchy_restriction, parent_type: new_type, child_type: new_type, maximum_depth: 4)
+ end
+
+ let_it_be(:link1) { create(:parent_link, work_item_parent: item1, work_item: item2) }
+ let_it_be(:link2) { create(:parent_link, work_item_parent: item2, work_item: item3) }
+ let_it_be(:link3) { create(:parent_link, work_item_parent: item3, work_item: item4) }
+
+ before do
+ hierarchy_restriction3.update!(maximum_depth: maximum_depth)
+ end
+
+ shared_examples 'validates the depth correctly' do
+ before do
+ work_item.update!(work_item_type: old_type)
+ end
+
+ context 'when it is valid' do
+ let(:maximum_depth) { 4 }
+
+ it 'allows to change types' do
+ work_item.work_item_type = new_type
+
+ expect(work_item).to be_valid
+ end
+ end
+
+ context 'when it is not valid' do
+ let(:maximum_depth) { 3 }
+
+ it 'does not allow to change types' do
+ work_item.work_item_type = new_type
+
+ expect(work_item).not_to be_valid
+ expect(work_item.errors[:work_item_type_id]).to include("reached maximum depth")
+ end
+ end
+ end
+
+ context 'with the highest ancestor' do
+ let_it_be_with_reload(:work_item) { item1 }
+
+ it_behaves_like 'validates the depth correctly'
+ end
+
+ context 'with a child item' do
+ let_it_be_with_reload(:work_item) { item2 }
+
+ it_behaves_like 'validates the depth correctly'
+ end
+
+ context 'with the last child item' do
+ let_it_be_with_reload(:work_item) { item4 }
+
+ it_behaves_like 'validates the depth correctly'
+ end
+
+ context 'when ancestor is still the old type' do
+ let_it_be(:hierarchy_restriction4) do
+ create(:hierarchy_restriction, parent_type: old_type, child_type: old_type)
+ end
+
+ before do
+ item1.update!(work_item_type: old_type)
+ item2.update!(work_item_type: old_type)
+ end
+
+ context 'when it exceeds maximum depth' do
+ let(:maximum_depth) { 2 }
+
+ it 'does not allow to change types' do
+ item2.work_item_type = new_type
+
+ expect(item2).not_to be_valid
+ expect(item2.errors[:work_item_type_id]).to include("reached maximum depth")
+ end
+ end
+
+ context 'when it does not exceed maximum depth' do
+ let(:maximum_depth) { 3 }
+
+ it 'does allow to change types' do
+ item2.work_item_type = new_type
+
+ expect(item2).to be_valid
+ end
+ end
+ end
+ end
+ end
+ end
end