summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-11-14 12:08:03 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-11-14 12:08:03 +0000
commit61a82b8ec062d6f122dadd38783c7754cef7ce2b (patch)
tree071d1ded4f507d77bac97156aa1fa85c95c0cba5 /spec
parent3ed578edf525bce3167860b84f6b43bab5065cf5 (diff)
downloadgitlab-ce-61a82b8ec062d6f122dadd38783c7754cef7ce2b.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/concerns/issuable_actions_spec.rb13
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb4
-rw-r--r--spec/factories/experiment_subjects.rb9
-rw-r--r--spec/factories/experiments.rb7
-rw-r--r--spec/factories/projects/wiki_repositories.rb7
-rw-r--r--spec/features/merge_request/user_accepts_merge_request_spec.rb2
-rw-r--r--spec/features/projects/network_graph_spec.rb138
-rw-r--r--spec/features/users/active_sessions_spec.rb4
-rw-r--r--spec/finders/branches_finder_spec.rb2
-rw-r--r--spec/frontend/boards/components/board_filtered_search_spec.js35
-rw-r--r--spec/frontend/boards/mock_data.js18
-rw-r--r--spec/frontend/fixtures/merge_requests.rb14
-rw-r--r--spec/frontend/issues/list/components/issues_list_app_spec.js24
-rw-r--r--spec/frontend/issues/list/mock_data.js112
-rw-r--r--spec/frontend/observability/observability_app_spec.js73
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js425
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap196
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js182
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed_spec.js60
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js342
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_missing_branch_spec.js43
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js70
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_wip_spec.js109
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js14
-rw-r--r--spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js50
-rw-r--r--spec/frontend/vue_merge_request_widget/stores/mr_widget_store_spec.js13
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/plugins/link_dependencies_spec.js8
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/plugins/mock_data.js17
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/plugins/utils/podspec_json_linker_spec.js14
-rw-r--r--spec/graphql/mutations/ci/runner/bulk_delete_spec.rb3
-rw-r--r--spec/graphql/types/issue_type_enum_spec.rb4
-rw-r--r--spec/helpers/groups/observability_helper_spec.rb92
-rw-r--r--spec/lib/gitlab/cluster/lifecycle_events_spec.rb59
-rw-r--r--spec/lib/gitlab/experimentation/group_types_spec.rb13
-rw-r--r--spec/lib/gitlab/git/tree_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/json_spec.rb15
-rw-r--r--spec/lib/gitlab/metrics/method_call_spec.rb47
-rw-r--r--spec/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled_spec.rb4
-rw-r--r--spec/lib/gitlab/redis/multi_store_spec.rb14
-rw-r--r--spec/models/active_session_spec.rb22
-rw-r--r--spec/models/ci/build_spec.rb11
-rw-r--r--spec/models/ci/processable_spec.rb2
-rw-r--r--spec/models/experiment_spec.rb150
-rw-r--r--spec/models/experiment_subject_spec.rb72
-rw-r--r--spec/models/integrations/bamboo_spec.rb2
-rw-r--r--spec/models/integrations/base_chat_notification_spec.rb4
-rw-r--r--spec/models/integrations/base_issue_tracker_spec.rb4
-rw-r--r--spec/models/integrations/base_third_party_wiki_spec.rb4
-rw-r--r--spec/models/integrations/buildkite_spec.rb2
-rw-r--r--spec/models/integrations/drone_ci_spec.rb2
-rw-r--r--spec/models/integrations/jenkins_spec.rb2
-rw-r--r--spec/models/integrations/mock_ci_spec.rb2
-rw-r--r--spec/models/integrations/prometheus_spec.rb2
-rw-r--r--spec/models/integrations/teamcity_spec.rb2
-rw-r--r--spec/models/merge_request_diff_file_spec.rb10
-rw-r--r--spec/models/network/graph_spec.rb20
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/models/projects/wiki_repository_spec.rb16
-rw-r--r--spec/requests/api/maven_packages_spec.rb10
-rw-r--r--spec/requests/api/npm_project_packages_spec.rb33
-rw-r--r--spec/requests/groups/observability_controller_spec.rb175
-rw-r--r--spec/routing/group_routing_spec.rb12
-rw-r--r--spec/serializers/merge_request_poll_cached_widget_entity_spec.rb16
-rw-r--r--spec/serializers/merge_request_poll_widget_entity_spec.rb6
-rw-r--r--spec/services/issuable/discussions_list_service_spec.rb27
-rw-r--r--spec/services/issues/relative_position_rebalancing_service_spec.rb8
-rw-r--r--spec/spec_helper.rb4
-rw-r--r--spec/support/helpers/navbar_structure_helper.rb6
-rw-r--r--spec/support/shared_examples/models/integrations/base_ci_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/models/integrations/base_monitoring_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/api/discussions_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/issuable/discussions_list_shared_examples.rb112
-rw-r--r--spec/views/groups/observability.html.haml_spec.rb18
-rw-r--r--spec/views/groups/observability/observability.html.haml_spec.rb18
-rw-r--r--spec/workers/merge_requests/delete_branch_worker_spec.rb30
-rw-r--r--spec/workers/merge_requests/delete_source_branch_worker_spec.rb26
80 files changed, 1510 insertions, 1614 deletions
diff --git a/spec/controllers/concerns/issuable_actions_spec.rb b/spec/controllers/concerns/issuable_actions_spec.rb
index c3fef591b91..37d9dc080e1 100644
--- a/spec/controllers/concerns/issuable_actions_spec.rb
+++ b/spec/controllers/concerns/issuable_actions_spec.rb
@@ -6,8 +6,8 @@ RSpec.describe IssuableActions do
let(:project) { double('project') }
let(:user) { double('user') }
let(:issuable) { double('issuable') }
- let(:finder_params_for_issuable) { {} }
- let(:notes_result) { double('notes_result') }
+ let(:finder_params_for_issuable) { { project: project, target: issuable } }
+ let(:notes_result) { [] }
let(:discussion_serializer) { double('discussion_serializer') }
let(:controller) do
@@ -55,13 +55,20 @@ RSpec.describe IssuableActions do
end
it 'instantiates and calls NotesFinder as expected' do
+ expect(issuable).to receive(:to_ability_name).and_return('issue')
+ expect(issuable).to receive(:project).and_return(project)
+ expect(Ability).to receive(:allowed?).at_least(1).and_return(true)
expect(Discussion).to receive(:build_collection).and_return([])
expect(DiscussionSerializer).to receive(:new).and_return(discussion_serializer)
expect(NotesFinder).to receive(:new).with(user, finder_params_for_issuable).and_call_original
expect_any_instance_of(NotesFinder).to receive(:execute).and_return(notes_result)
- expect(notes_result).to receive_messages(inc_relations_for_view: notes_result, includes: notes_result, fresh: notes_result)
+ expect(notes_result).to receive_messages(
+ with_web_entity_associations: notes_result,
+ inc_relations_for_view: notes_result,
+ fresh: notes_result
+ )
controller.discussions
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index ceff86456f7..026cf19bde5 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -1780,7 +1780,7 @@ RSpec.describe Projects::MergeRequestsController do
end
it 'renders MergeRequest as JSON' do
- expect(json_response.keys).to include('id', 'iid', 'title', 'has_ci', 'merge_status', 'can_be_merged', 'current_user')
+ expect(json_response.keys).to include('id', 'iid', 'title', 'has_ci', 'current_user')
end
end
@@ -1814,7 +1814,7 @@ RSpec.describe Projects::MergeRequestsController do
it 'renders MergeRequest as JSON' do
subject
- expect(json_response.keys).to include('id', 'iid', 'title', 'has_ci', 'merge_status', 'can_be_merged', 'current_user')
+ expect(json_response.keys).to include('id', 'iid', 'title', 'has_ci', 'current_user')
end
end
diff --git a/spec/factories/experiment_subjects.rb b/spec/factories/experiment_subjects.rb
deleted file mode 100644
index c35bc370bad..00000000000
--- a/spec/factories/experiment_subjects.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-FactoryBot.define do
- factory :experiment_subject do
- experiment
- user
- variant { :control }
- end
-end
diff --git a/spec/factories/experiments.rb b/spec/factories/experiments.rb
deleted file mode 100644
index 2c51a6585f4..00000000000
--- a/spec/factories/experiments.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-FactoryBot.define do
- factory :experiment do
- name { generate(:title) }
- end
-end
diff --git a/spec/factories/projects/wiki_repositories.rb b/spec/factories/projects/wiki_repositories.rb
new file mode 100644
index 00000000000..78e02ff297b
--- /dev/null
+++ b/spec/factories/projects/wiki_repositories.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :project_wiki_repository, class: 'Projects::WikiRepository' do
+ project
+ end
+end
diff --git a/spec/features/merge_request/user_accepts_merge_request_spec.rb b/spec/features/merge_request/user_accepts_merge_request_spec.rb
index 159306b28d8..b50e6779e07 100644
--- a/spec/features/merge_request/user_accepts_merge_request_spec.rb
+++ b/spec/features/merge_request/user_accepts_merge_request_spec.rb
@@ -18,6 +18,8 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli
click_button('Merge')
+ puts merge_request.short_merged_commit_sha
+
expect(page).to have_content("Changes merged into #{merge_request.target_branch} with #{merge_request.short_merged_commit_sha}")
end
diff --git a/spec/features/projects/network_graph_spec.rb b/spec/features/projects/network_graph_spec.rb
index 1ee0ea51e53..97b743b4d73 100644
--- a/spec/features/projects/network_graph_spec.rb
+++ b/spec/features/projects/network_graph_spec.rb
@@ -13,98 +13,110 @@ RSpec.describe 'Project Network Graph', :js do
allow(Network::Graph).to receive(:max_count).and_return(10)
end
- context 'when branch is master' do
- def switch_ref_to(ref_name)
- first('.js-project-refs-dropdown').click
-
- page.within '.project-refs-form' do
- click_link ref_name
+ shared_examples 'network graph' do
+ context 'when branch is master' do
+ def switch_ref_to(ref_name)
+ first('.js-project-refs-dropdown').click
+
+ page.within '.project-refs-form' do
+ click_link ref_name
+ end
end
- end
- def click_show_only_selected_branch_checkbox
- find('#filter_ref').click
- end
+ def click_show_only_selected_branch_checkbox
+ find('#filter_ref').click
+ end
- before do
- visit project_network_path(project, 'master')
- end
+ before do
+ visit project_network_path(project, 'master')
+ end
- it 'renders project network' do
- expect(page).to have_selector ".network-graph"
- expect(page).to have_selector '.dropdown-menu-toggle', text: "master"
- page.within '.network-graph' do
- expect(page).to have_content 'master'
+ it 'renders project network' do
+ expect(page).to have_selector ".network-graph"
+ expect(page).to have_selector '.dropdown-menu-toggle', text: "master"
+ page.within '.network-graph' do
+ expect(page).to have_content 'master'
+ end
end
- end
- it 'switches ref to branch' do
- switch_ref_to('feature')
+ it 'switches ref to branch' do
+ switch_ref_to('feature')
- expect(page).to have_selector '.dropdown-menu-toggle', text: 'feature'
- page.within '.network-graph' do
- expect(page).to have_content 'feature'
+ expect(page).to have_selector '.dropdown-menu-toggle', text: 'feature'
+ page.within '.network-graph' do
+ expect(page).to have_content 'feature'
+ end
end
- end
- it 'switches ref to tag' do
- switch_ref_to('v1.0.0')
+ it 'switches ref to tag' do
+ switch_ref_to('v1.0.0')
- expect(page).to have_selector '.dropdown-menu-toggle', text: 'v1.0.0'
- page.within '.network-graph' do
- expect(page).to have_content 'v1.0.0'
+ expect(page).to have_selector '.dropdown-menu-toggle', text: 'v1.0.0'
+ page.within '.network-graph' do
+ expect(page).to have_content 'v1.0.0'
+ end
end
- end
- it 'renders by commit sha of "v1.0.0"' do
- page.within ".network-form" do
- fill_in 'extended_sha1', with: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9'
- find('button').click
+ it 'renders by commit sha of "v1.0.0"' do
+ page.within ".network-form" do
+ fill_in 'extended_sha1', with: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9'
+ find('button').click
+ end
+
+ expect(page).to have_selector ".network-graph"
+ expect(page).to have_selector '.dropdown-menu-toggle', text: "master"
+ page.within '.network-graph' do
+ expect(page).to have_content 'v1.0.0'
+ end
end
- expect(page).to have_selector ".network-graph"
- expect(page).to have_selector '.dropdown-menu-toggle', text: "master"
- page.within '.network-graph' do
- expect(page).to have_content 'v1.0.0'
- end
- end
+ it 'filters select tag' do
+ switch_ref_to('v1.0.0')
- it 'filters select tag' do
- switch_ref_to('v1.0.0')
+ expect(page).to have_css 'title', text: 'Graph · v1.0.0', visible: false
+ page.within '.network-graph' do
+ expect(page).to have_content 'Change some files'
+ end
- expect(page).to have_css 'title', text: 'Graph · v1.0.0', visible: false
- page.within '.network-graph' do
- expect(page).to have_content 'Change some files'
- end
+ click_show_only_selected_branch_checkbox
- click_show_only_selected_branch_checkbox
+ page.within '.network-graph' do
+ expect(page).not_to have_content 'Change some files'
+ end
- page.within '.network-graph' do
- expect(page).not_to have_content 'Change some files'
+ click_show_only_selected_branch_checkbox
+
+ page.within '.network-graph' do
+ expect(page).to have_content 'Change some files'
+ end
end
- click_show_only_selected_branch_checkbox
+ it 'renders error message when sha commit not exists' do
+ page.within ".network-form" do
+ fill_in 'extended_sha1', with: ';'
+ find('button').click
+ end
- page.within '.network-graph' do
- expect(page).to have_content 'Change some files'
+ expect(page).to have_selector '[data-testid="alert-danger"]', text: "Git revision ';' does not exist."
end
end
- it 'renders error message when sha commit not exists' do
- page.within ".network-form" do
- fill_in 'extended_sha1', with: ';'
- find('button').click
- end
+ it 'renders project network with test branch' do
+ visit project_network_path(project, "'test'")
- expect(page).to have_selector '[data-testid="alert-danger"]', text: "Git revision ';' does not exist."
+ page.within '.network-graph' do
+ expect(page).to have_content "'test'"
+ end
end
end
- it 'renders project network with test branch' do
- visit project_network_path(project, "'test'")
+ it_behaves_like 'network graph'
- page.within '.network-graph' do
- expect(page).to have_content "'test'"
+ context 'when disable_network_graph_notes_count is disabled' do
+ before do
+ stub_feature_flags(disable_network_graph_notes_count: false)
end
+
+ it_behaves_like 'network graph'
end
end
diff --git a/spec/features/users/active_sessions_spec.rb b/spec/features/users/active_sessions_spec.rb
index c722a4ec05c..e2ee78a7cc5 100644
--- a/spec/features/users/active_sessions_spec.rb
+++ b/spec/features/users/active_sessions_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe 'Active user sessions', :clean_gitlab_redis_sessions do
user = create(:user)
Gitlab::Redis::Sessions.with do |redis|
- redis.sadd("session:lookup:user:gitlab:#{user.id}", '59822c7d9fcdfa03725eff41782ad97d')
+ redis.sadd?("session:lookup:user:gitlab:#{user.id}", '59822c7d9fcdfa03725eff41782ad97d')
end
gitlab_sign_in(user)
@@ -45,7 +45,7 @@ RSpec.describe 'Active user sessions', :clean_gitlab_redis_sessions do
personal_access_token = create(:personal_access_token, user: user)
Gitlab::Redis::Sessions.with do |redis|
- redis.sadd("session:lookup:user:gitlab:#{user.id}", '59822c7d9fcdfa03725eff41782ad97d')
+ redis.sadd?("session:lookup:user:gitlab:#{user.id}", '59822c7d9fcdfa03725eff41782ad97d')
end
visit user_path(user, :atom, private_token: personal_access_token.token)
diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb
index 9314f616c44..f14c60c4b8f 100644
--- a/spec/finders/branches_finder_spec.rb
+++ b/spec/finders/branches_finder_spec.rb
@@ -211,7 +211,7 @@ RSpec.describe BranchesFinder do
it 'raises an error' do
expect do
subject
- end.to raise_error(Gitlab::Git::CommandError, '13:could not find page token.')
+ end.to raise_error(Gitlab::Git::CommandError, /could not find page token/)
end
end
diff --git a/spec/frontend/boards/components/board_filtered_search_spec.js b/spec/frontend/boards/components/board_filtered_search_spec.js
index f1113ee3073..6f17e4193a3 100644
--- a/spec/frontend/boards/components/board_filtered_search_spec.js
+++ b/spec/frontend/boards/components/board_filtered_search_spec.js
@@ -6,6 +6,15 @@ import * as urlUtility from '~/lib/utils/url_utility';
import {
TOKEN_TITLE_AUTHOR,
TOKEN_TITLE_LABEL,
+ TOKEN_TYPE_ASSIGNEE,
+ TOKEN_TYPE_AUTHOR,
+ TOKEN_TYPE_HEALTH,
+ TOKEN_TYPE_ITERATION,
+ TOKEN_TYPE_LABEL,
+ TOKEN_TYPE_MILESTONE,
+ TOKEN_TYPE_RELEASE,
+ TOKEN_TYPE_TYPE,
+ TOKEN_TYPE_WEIGHT,
} from '~/vue_shared/components/filtered_search_bar/constants';
import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
@@ -120,16 +129,16 @@ describe('BoardFilteredSearch', () => {
it('sets the url params to the correct results', async () => {
const mockFilters = [
- { type: 'author', value: { data: 'root', operator: '=' } },
- { type: 'assignee', value: { data: 'root', operator: '=' } },
- { type: 'label', value: { data: 'label', operator: '=' } },
- { type: 'label', value: { data: 'label&2', operator: '=' } },
- { type: 'milestone', value: { data: 'New Milestone', operator: '=' } },
- { type: 'type', value: { data: 'INCIDENT', operator: '=' } },
- { type: 'weight', value: { data: '2', operator: '=' } },
- { type: 'iteration', value: { data: 'Any&3', operator: '=' } },
- { type: 'release', value: { data: 'v1.0.0', operator: '=' } },
- { type: 'health_status', value: { data: 'onTrack', operator: '=' } },
+ { type: TOKEN_TYPE_AUTHOR, value: { data: 'root', operator: '=' } },
+ { type: TOKEN_TYPE_ASSIGNEE, value: { data: 'root', operator: '=' } },
+ { type: TOKEN_TYPE_LABEL, value: { data: 'label', operator: '=' } },
+ { type: TOKEN_TYPE_LABEL, value: { data: 'label&2', operator: '=' } },
+ { type: TOKEN_TYPE_MILESTONE, value: { data: 'New Milestone', operator: '=' } },
+ { type: TOKEN_TYPE_TYPE, value: { data: 'INCIDENT', operator: '=' } },
+ { type: TOKEN_TYPE_WEIGHT, value: { data: '2', operator: '=' } },
+ { type: TOKEN_TYPE_ITERATION, value: { data: 'Any&3', operator: '=' } },
+ { type: TOKEN_TYPE_RELEASE, value: { data: 'v1.0.0', operator: '=' } },
+ { type: TOKEN_TYPE_HEALTH, value: { data: 'onTrack', operator: '=' } },
];
jest.spyOn(urlUtility, 'updateHistory');
findFilteredSearch().vm.$emit('onFilter', mockFilters);
@@ -173,9 +182,9 @@ describe('BoardFilteredSearch', () => {
it('passes the correct props to FilterSearchBar', () => {
expect(findFilteredSearch().props('initialFilterValue')).toEqual([
- { type: 'author', value: { data: 'root', operator: '=' } },
- { type: 'label', value: { data: 'label', operator: '=' } },
- { type: 'health_status', value: { data: 'Any', operator: '=' } },
+ { type: TOKEN_TYPE_AUTHOR, value: { data: 'root', operator: '=' } },
+ { type: TOKEN_TYPE_LABEL, value: { data: 'label', operator: '=' } },
+ { type: TOKEN_TYPE_HEALTH, value: { data: 'Any', operator: '=' } },
]);
});
});
diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js
index a18b79d00a0..3c26fa97338 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -10,6 +10,12 @@ import {
TOKEN_TITLE_MILESTONE,
TOKEN_TITLE_RELEASE,
TOKEN_TITLE_TYPE,
+ TOKEN_TYPE_ASSIGNEE,
+ TOKEN_TYPE_AUTHOR,
+ TOKEN_TYPE_LABEL,
+ TOKEN_TYPE_MILESTONE,
+ TOKEN_TYPE_RELEASE,
+ TOKEN_TYPE_TYPE,
} from '~/vue_shared/components/filtered_search_bar/constants';
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
import EmojiToken from '~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue';
@@ -752,7 +758,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, isSignedI
{
icon: 'user',
title: TOKEN_TITLE_ASSIGNEE,
- type: 'assignee',
+ type: TOKEN_TYPE_ASSIGNEE,
operators: OPERATOR_IS_AND_IS_NOT,
token: AuthorToken,
unique: true,
@@ -762,7 +768,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, isSignedI
{
icon: 'pencil',
title: TOKEN_TITLE_AUTHOR,
- type: 'author',
+ type: TOKEN_TYPE_AUTHOR,
operators: OPERATOR_IS_AND_IS_NOT,
symbol: '@',
token: AuthorToken,
@@ -773,7 +779,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, isSignedI
{
icon: 'labels',
title: TOKEN_TITLE_LABEL,
- type: 'label',
+ type: TOKEN_TYPE_LABEL,
operators: OPERATOR_IS_AND_IS_NOT,
token: LabelToken,
unique: false,
@@ -785,7 +791,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, isSignedI
icon: 'clock',
title: TOKEN_TITLE_MILESTONE,
symbol: '%',
- type: 'milestone',
+ type: TOKEN_TYPE_MILESTONE,
shouldSkipSort: true,
token: MilestoneToken,
unique: true,
@@ -794,7 +800,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, isSignedI
{
icon: 'issues',
title: TOKEN_TITLE_TYPE,
- type: 'type',
+ type: TOKEN_TYPE_TYPE,
token: GlFilteredSearchToken,
unique: true,
options: [
@@ -803,7 +809,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, isSignedI
],
},
{
- type: 'release',
+ type: TOKEN_TYPE_RELEASE,
title: TOKEN_TITLE_RELEASE,
icon: 'rocket',
token: ReleaseToken,
diff --git a/spec/frontend/fixtures/merge_requests.rb b/spec/frontend/fixtures/merge_requests.rb
index 4ab3d56a13f..18f89fbc5e5 100644
--- a/spec/frontend/fixtures/merge_requests.rb
+++ b/spec/frontend/fixtures/merge_requests.rb
@@ -147,6 +147,20 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type:
expect_graphql_errors_to_be_empty
end
end
+
+ context 'merge request in state getState query' do
+ base_input_path = 'vue_merge_request_widget/queries/'
+ base_output_path = 'graphql/merge_requests/'
+ query_name = 'get_state.query.graphql'
+
+ it "#{base_output_path}#{query_name}.json" do
+ query = get_graphql_query_as_string("#{base_input_path}#{query_name}")
+
+ post_graphql(query, current_user: user, variables: { projectPath: project.full_path, iid: merge_request.iid.to_s })
+
+ expect_graphql_errors_to_be_empty
+ end
+ end
end
private
diff --git a/spec/frontend/issues/list/components/issues_list_app_spec.js b/spec/frontend/issues/list/components/issues_list_app_spec.js
index a5488941791..d0c93c896b3 100644
--- a/spec/frontend/issues/list/components/issues_list_app_spec.js
+++ b/spec/frontend/issues/list/components/issues_list_app_spec.js
@@ -33,16 +33,6 @@ import {
CREATED_DESC,
RELATIVE_POSITION,
RELATIVE_POSITION_ASC,
- TOKEN_TYPE_ASSIGNEE,
- TOKEN_TYPE_AUTHOR,
- TOKEN_TYPE_CONFIDENTIAL,
- TOKEN_TYPE_CONTACT,
- TOKEN_TYPE_LABEL,
- TOKEN_TYPE_MILESTONE,
- TOKEN_TYPE_MY_REACTION,
- TOKEN_TYPE_ORGANIZATION,
- TOKEN_TYPE_RELEASE,
- TOKEN_TYPE_TYPE,
urlSortParams,
} from '~/issues/list/constants';
import eventHub from '~/issues/list/eventhub';
@@ -57,7 +47,19 @@ import {
WORK_ITEM_TYPE_ENUM_TASK,
WORK_ITEM_TYPE_ENUM_TEST_CASE,
} from '~/work_items/constants';
-import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
+import {
+ FILTERED_SEARCH_TERM,
+ TOKEN_TYPE_ASSIGNEE,
+ TOKEN_TYPE_AUTHOR,
+ TOKEN_TYPE_CONFIDENTIAL,
+ TOKEN_TYPE_CONTACT,
+ TOKEN_TYPE_LABEL,
+ TOKEN_TYPE_MILESTONE,
+ TOKEN_TYPE_MY_REACTION,
+ TOKEN_TYPE_ORGANIZATION,
+ TOKEN_TYPE_RELEASE,
+ TOKEN_TYPE_TYPE,
+} from '~/vue_shared/components/filtered_search_bar/constants';
import('~/issuable/bulk_update_sidebar');
import('~/users_select');
diff --git a/spec/frontend/issues/list/mock_data.js b/spec/frontend/issues/list/mock_data.js
index e80191a95c8..62fcbf7aad0 100644
--- a/spec/frontend/issues/list/mock_data.js
+++ b/spec/frontend/issues/list/mock_data.js
@@ -1,7 +1,21 @@
import {
+ FILTERED_SEARCH_TERM,
OPERATOR_IS,
OPERATOR_IS_NOT,
OPERATOR_OR,
+ TOKEN_TYPE_ASSIGNEE,
+ TOKEN_TYPE_AUTHOR,
+ TOKEN_TYPE_CONFIDENTIAL,
+ TOKEN_TYPE_CONTACT,
+ TOKEN_TYPE_EPIC,
+ TOKEN_TYPE_ITERATION,
+ TOKEN_TYPE_LABEL,
+ TOKEN_TYPE_MILESTONE,
+ TOKEN_TYPE_MY_REACTION,
+ TOKEN_TYPE_ORGANIZATION,
+ TOKEN_TYPE_RELEASE,
+ TOKEN_TYPE_TYPE,
+ TOKEN_TYPE_WEIGHT,
} from '~/vue_shared/components/filtered_search_bar/constants';
export const getIssuesQueryResponse = {
@@ -169,58 +183,58 @@ export const locationSearchWithSpecialValues = [
].join('&');
export const filteredTokens = [
- { type: 'author_username', value: { data: 'homer', operator: OPERATOR_IS } },
- { type: 'author_username', value: { data: 'marge', operator: OPERATOR_IS_NOT } },
- { type: 'assignee_username', value: { data: 'bart', operator: OPERATOR_IS } },
- { type: 'assignee_username', value: { data: 'lisa', operator: OPERATOR_IS } },
- { type: 'assignee_username', value: { data: '5', operator: OPERATOR_IS } },
- { type: 'assignee_username', value: { data: 'patty', operator: OPERATOR_IS_NOT } },
- { type: 'assignee_username', value: { data: 'selma', operator: OPERATOR_IS_NOT } },
- { type: 'assignee_username', value: { data: 'carl', operator: OPERATOR_OR } },
- { type: 'assignee_username', value: { data: 'lenny', operator: OPERATOR_OR } },
- { type: 'milestone', value: { data: 'season 3', operator: OPERATOR_IS } },
- { type: 'milestone', value: { data: 'season 4', operator: OPERATOR_IS } },
- { type: 'milestone', value: { data: 'season 20', operator: OPERATOR_IS_NOT } },
- { type: 'milestone', value: { data: 'season 30', operator: OPERATOR_IS_NOT } },
- { type: 'labels', value: { data: 'cartoon', operator: OPERATOR_IS } },
- { type: 'labels', value: { data: 'tv', operator: OPERATOR_IS } },
- { type: 'labels', value: { data: 'live action', operator: OPERATOR_IS_NOT } },
- { type: 'labels', value: { data: 'drama', operator: OPERATOR_IS_NOT } },
- { type: 'release', value: { data: 'v3', operator: OPERATOR_IS } },
- { type: 'release', value: { data: 'v4', operator: OPERATOR_IS } },
- { type: 'release', value: { data: 'v20', operator: OPERATOR_IS_NOT } },
- { type: 'release', value: { data: 'v30', operator: OPERATOR_IS_NOT } },
- { type: 'type', value: { data: 'issue', operator: OPERATOR_IS } },
- { type: 'type', value: { data: 'feature', operator: OPERATOR_IS } },
- { type: 'type', value: { data: 'bug', operator: OPERATOR_IS_NOT } },
- { type: 'type', value: { data: 'incident', operator: OPERATOR_IS_NOT } },
- { type: 'my_reaction_emoji', value: { data: 'thumbsup', operator: OPERATOR_IS } },
- { type: 'my_reaction_emoji', value: { data: 'thumbsdown', operator: OPERATOR_IS_NOT } },
- { type: 'confidential', value: { data: 'yes', operator: OPERATOR_IS } },
- { type: 'iteration', value: { data: '4', operator: OPERATOR_IS } },
- { type: 'iteration', value: { data: '12', operator: OPERATOR_IS } },
- { type: 'iteration', value: { data: '20', operator: OPERATOR_IS_NOT } },
- { type: 'iteration', value: { data: '42', operator: OPERATOR_IS_NOT } },
- { type: 'epic_id', value: { data: '12', operator: OPERATOR_IS } },
- { type: 'epic_id', value: { data: '34', operator: OPERATOR_IS_NOT } },
- { type: 'weight', value: { data: '1', operator: OPERATOR_IS } },
- { type: 'weight', value: { data: '3', operator: OPERATOR_IS_NOT } },
- { type: 'crm_contact', value: { data: '123', operator: OPERATOR_IS } },
- { type: 'crm_organization', value: { data: '456', operator: OPERATOR_IS } },
- { type: 'filtered-search-term', value: { data: 'find' } },
- { type: 'filtered-search-term', value: { data: 'issues' } },
+ { type: TOKEN_TYPE_AUTHOR, value: { data: 'homer', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_AUTHOR, value: { data: 'marge', operator: OPERATOR_IS_NOT } },
+ { type: TOKEN_TYPE_ASSIGNEE, value: { data: 'bart', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_ASSIGNEE, value: { data: 'lisa', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_ASSIGNEE, value: { data: '5', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_ASSIGNEE, value: { data: 'patty', operator: OPERATOR_IS_NOT } },
+ { type: TOKEN_TYPE_ASSIGNEE, value: { data: 'selma', operator: OPERATOR_IS_NOT } },
+ { type: TOKEN_TYPE_ASSIGNEE, value: { data: 'carl', operator: OPERATOR_OR } },
+ { type: TOKEN_TYPE_ASSIGNEE, value: { data: 'lenny', operator: OPERATOR_OR } },
+ { type: TOKEN_TYPE_MILESTONE, value: { data: 'season 3', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_MILESTONE, value: { data: 'season 4', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_MILESTONE, value: { data: 'season 20', operator: OPERATOR_IS_NOT } },
+ { type: TOKEN_TYPE_MILESTONE, value: { data: 'season 30', operator: OPERATOR_IS_NOT } },
+ { type: TOKEN_TYPE_LABEL, value: { data: 'cartoon', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_LABEL, value: { data: 'tv', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_LABEL, value: { data: 'live action', operator: OPERATOR_IS_NOT } },
+ { type: TOKEN_TYPE_LABEL, value: { data: 'drama', operator: OPERATOR_IS_NOT } },
+ { type: TOKEN_TYPE_RELEASE, value: { data: 'v3', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_RELEASE, value: { data: 'v4', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_RELEASE, value: { data: 'v20', operator: OPERATOR_IS_NOT } },
+ { type: TOKEN_TYPE_RELEASE, value: { data: 'v30', operator: OPERATOR_IS_NOT } },
+ { type: TOKEN_TYPE_TYPE, value: { data: 'issue', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_TYPE, value: { data: 'feature', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_TYPE, value: { data: 'bug', operator: OPERATOR_IS_NOT } },
+ { type: TOKEN_TYPE_TYPE, value: { data: 'incident', operator: OPERATOR_IS_NOT } },
+ { type: TOKEN_TYPE_MY_REACTION, value: { data: 'thumbsup', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_MY_REACTION, value: { data: 'thumbsdown', operator: OPERATOR_IS_NOT } },
+ { type: TOKEN_TYPE_CONFIDENTIAL, value: { data: 'yes', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_ITERATION, value: { data: '4', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_ITERATION, value: { data: '12', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_ITERATION, value: { data: '20', operator: OPERATOR_IS_NOT } },
+ { type: TOKEN_TYPE_ITERATION, value: { data: '42', operator: OPERATOR_IS_NOT } },
+ { type: TOKEN_TYPE_EPIC, value: { data: '12', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_EPIC, value: { data: '34', operator: OPERATOR_IS_NOT } },
+ { type: TOKEN_TYPE_WEIGHT, value: { data: '1', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_WEIGHT, value: { data: '3', operator: OPERATOR_IS_NOT } },
+ { type: TOKEN_TYPE_CONTACT, value: { data: '123', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_ORGANIZATION, value: { data: '456', operator: OPERATOR_IS } },
+ { type: FILTERED_SEARCH_TERM, value: { data: 'find' } },
+ { type: FILTERED_SEARCH_TERM, value: { data: 'issues' } },
];
export const filteredTokensWithSpecialValues = [
- { type: 'assignee_username', value: { data: '123', operator: OPERATOR_IS } },
- { type: 'assignee_username', value: { data: 'bart', operator: OPERATOR_IS } },
- { type: 'my_reaction_emoji', value: { data: 'None', operator: OPERATOR_IS } },
- { type: 'iteration', value: { data: 'Current', operator: OPERATOR_IS } },
- { type: 'labels', value: { data: 'None', operator: OPERATOR_IS } },
- { type: 'release', value: { data: 'None', operator: OPERATOR_IS } },
- { type: 'milestone', value: { data: 'Upcoming', operator: OPERATOR_IS } },
- { type: 'epic_id', value: { data: 'None', operator: OPERATOR_IS } },
- { type: 'weight', value: { data: 'None', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_ASSIGNEE, value: { data: '123', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_ASSIGNEE, value: { data: 'bart', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_MY_REACTION, value: { data: 'None', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_ITERATION, value: { data: 'Current', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_LABEL, value: { data: 'None', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_RELEASE, value: { data: 'None', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_MILESTONE, value: { data: 'Upcoming', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_EPIC, value: { data: 'None', operator: OPERATOR_IS } },
+ { type: TOKEN_TYPE_WEIGHT, value: { data: 'None', operator: OPERATOR_IS } },
];
export const apiParams = {
diff --git a/spec/frontend/observability/observability_app_spec.js b/spec/frontend/observability/observability_app_spec.js
new file mode 100644
index 00000000000..f0b318e69ec
--- /dev/null
+++ b/spec/frontend/observability/observability_app_spec.js
@@ -0,0 +1,73 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import ObservabilityApp from '~/observability/components/observability_app.vue';
+
+describe('Observability root app', () => {
+ let wrapper;
+ const replace = jest.fn();
+ const $router = {
+ replace,
+ };
+ const $route = {
+ pathname: 'https://gitlab.com/gitlab-org/',
+ query: { otherQuery: 100 },
+ };
+
+ const findIframe = () => wrapper.findByTestId('observability-ui-iframe');
+
+ const TEST_IFRAME_SRC = 'https://observe.gitlab.com/9970/?groupId=14485840';
+
+ const mountComponent = (route = $route) => {
+ wrapper = shallowMountExtended(ObservabilityApp, {
+ propsData: {
+ observabilityIframeSrc: TEST_IFRAME_SRC,
+ },
+ mocks: {
+ $router,
+ $route: route,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should render an iframe with observabilityIframeSrc as src', () => {
+ mountComponent();
+ const iframe = findIframe();
+ expect(iframe.exists()).toBe(true);
+ expect(iframe.attributes('src')).toBe(TEST_IFRAME_SRC);
+ });
+
+ it('should not call replace method from vue router if message event does not have url', () => {
+ mountComponent();
+ wrapper.vm.messageHandler({ data: 'some other data' });
+ expect(replace).not.toHaveBeenCalled();
+ });
+
+ it.each`
+ condition | origin | observability_path | url
+ ${'message origin is different from iframe source origin'} | ${'https://example.com'} | ${'/'} | ${'/explore'}
+ ${'path is same as before (observability_path)'} | ${'https://observe.gitlab.com'} | ${'/foo?bar=test'} | ${'/foo?bar=test'}
+ `(
+ 'should not call replace method from vue router if $condition',
+ async ({ origin, observability_path, url }) => {
+ mountComponent({ ...$route, query: { observability_path } });
+ wrapper.vm.messageHandler({ data: { url }, origin });
+ expect(replace).not.toHaveBeenCalled();
+ },
+ );
+
+ it('should call replace method from vue router on messageHandle call', () => {
+ mountComponent();
+ wrapper.vm.messageHandler({ data: { url: '/explore' }, origin: 'https://observe.gitlab.com' });
+ expect(replace).toHaveBeenCalled();
+ expect(replace).toHaveBeenCalledWith({
+ name: 'https://gitlab.com/gitlab-org/',
+ query: {
+ otherQuery: 100,
+ observability_path: '/explore',
+ },
+ });
+ });
+});
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js
index 05c259de370..7b52773e92d 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js
@@ -8,7 +8,7 @@ jest.mock('~/vue_shared/plugins/global_toast');
let wrapper;
-function createWrapper(propsData, mergeRequestWidgetGraphql) {
+function createWrapper(propsData) {
wrapper = mount(WidgetRebase, {
propsData,
data() {
@@ -22,7 +22,6 @@ function createWrapper(propsData, mergeRequestWidgetGraphql) {
},
};
},
- provide: { glFeatures: { mergeRequestWidgetGraphql } },
mocks: {
$apollo: {
queries: {
@@ -43,276 +42,244 @@ describe('Merge request widget rebase component', () => {
wrapper.destroy();
wrapper = null;
});
-
- [true, false].forEach((mergeRequestWidgetGraphql) => {
- describe(`widget graphql is ${mergeRequestWidgetGraphql ? 'enabled' : 'disabled'}`, () => {
- describe('while rebasing', () => {
- it('should show progress message', () => {
- createWrapper(
- {
- mr: { rebaseInProgress: true },
- service: {},
- },
- mergeRequestWidgetGraphql,
- );
-
- expect(findRebaseMessageText()).toContain('Rebase in progress');
- });
+ describe('while rebasing', () => {
+ it('should show progress message', () => {
+ createWrapper({
+ mr: { rebaseInProgress: true },
+ service: {},
});
- describe('with permissions', () => {
- const rebaseMock = jest.fn().mockResolvedValue();
- const pollMock = jest.fn().mockResolvedValue({});
+ expect(findRebaseMessageText()).toContain('Rebase in progress');
+ });
+ });
+
+ describe('with permissions', () => {
+ const rebaseMock = jest.fn().mockResolvedValue();
+ const pollMock = jest.fn().mockResolvedValue({});
- it('renders the warning message', () => {
- createWrapper(
- {
- mr: {
- rebaseInProgress: false,
- canPushToSourceBranch: true,
- },
- service: {
- rebase: rebaseMock,
- poll: pollMock,
- },
- },
- mergeRequestWidgetGraphql,
- );
+ it('renders the warning message', () => {
+ createWrapper({
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: true,
+ },
+ service: {
+ rebase: rebaseMock,
+ poll: pollMock,
+ },
+ });
- const text = findRebaseMessageText();
+ const text = findRebaseMessageText();
- expect(text).toContain('Merge blocked');
- expect(text.replace(/\s\s+/g, ' ')).toContain(
- 'the source branch must be rebased onto the target branch',
- );
- });
+ expect(text).toContain('Merge blocked');
+ expect(text.replace(/\s\s+/g, ' ')).toContain(
+ 'the source branch must be rebased onto the target branch',
+ );
+ });
- it('renders an error message when rebasing has failed', async () => {
- createWrapper(
- {
- mr: {
- rebaseInProgress: false,
- canPushToSourceBranch: true,
- },
- service: {
- rebase: rebaseMock,
- poll: pollMock,
- },
- },
- mergeRequestWidgetGraphql,
- );
+ it('renders an error message when rebasing has failed', async () => {
+ createWrapper({
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: true,
+ },
+ service: {
+ rebase: rebaseMock,
+ poll: pollMock,
+ },
+ });
+
+ // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
+ // eslint-disable-next-line no-restricted-syntax
+ wrapper.setData({ rebasingError: 'Something went wrong!' });
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ rebasingError: 'Something went wrong!' });
+ await nextTick();
+ expect(findRebaseMessageText()).toContain('Something went wrong!');
+ });
- await nextTick();
- expect(findRebaseMessageText()).toContain('Something went wrong!');
+ describe('Rebase buttons', () => {
+ beforeEach(() => {
+ createWrapper({
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: true,
+ },
+ service: {
+ rebase: rebaseMock,
+ poll: pollMock,
+ },
});
+ });
- describe('Rebase buttons', () => {
- beforeEach(() => {
- createWrapper(
- {
- mr: {
- rebaseInProgress: false,
- canPushToSourceBranch: true,
- },
- service: {
- rebase: rebaseMock,
- poll: pollMock,
- },
- },
- mergeRequestWidgetGraphql,
- );
- });
+ it('renders both buttons', () => {
+ expect(findRebaseWithoutCiButton().exists()).toBe(true);
+ expect(findStandardRebaseButton().exists()).toBe(true);
+ });
- it('renders both buttons', () => {
- expect(findRebaseWithoutCiButton().exists()).toBe(true);
- expect(findStandardRebaseButton().exists()).toBe(true);
- });
+ it('starts the rebase when clicking', async () => {
+ findStandardRebaseButton().vm.$emit('click');
- it('starts the rebase when clicking', async () => {
- findStandardRebaseButton().vm.$emit('click');
+ await nextTick();
- await nextTick();
+ expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false });
+ });
- expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false });
- });
+ it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => {
+ findRebaseWithoutCiButton().vm.$emit('click');
- it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => {
- findRebaseWithoutCiButton().vm.$emit('click');
+ await nextTick();
- await nextTick();
+ expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true });
+ });
+ });
- expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true });
- });
+ describe('Rebase when pipelines must succeed is enabled', () => {
+ beforeEach(() => {
+ createWrapper({
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: true,
+ onlyAllowMergeIfPipelineSucceeds: true,
+ },
+ service: {
+ rebase: rebaseMock,
+ poll: pollMock,
+ },
});
+ });
- describe('Rebase when pipelines must succeed is enabled', () => {
- beforeEach(() => {
- createWrapper(
- {
- mr: {
- rebaseInProgress: false,
- canPushToSourceBranch: true,
- onlyAllowMergeIfPipelineSucceeds: true,
- },
- service: {
- rebase: rebaseMock,
- poll: pollMock,
- },
- },
- mergeRequestWidgetGraphql,
- );
- });
+ it('renders only the rebase button', () => {
+ expect(findRebaseWithoutCiButton().exists()).toBe(false);
+ expect(findStandardRebaseButton().exists()).toBe(true);
+ });
- it('renders only the rebase button', () => {
- expect(findRebaseWithoutCiButton().exists()).toBe(false);
- expect(findStandardRebaseButton().exists()).toBe(true);
- });
+ it('starts the rebase when clicking', async () => {
+ findStandardRebaseButton().vm.$emit('click');
- it('starts the rebase when clicking', async () => {
- findStandardRebaseButton().vm.$emit('click');
+ await nextTick();
- await nextTick();
+ expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false });
+ });
+ });
- expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false });
- });
+ describe('Rebase when pipelines must succeed and skipped pipelines are considered successful are enabled', () => {
+ beforeEach(() => {
+ createWrapper({
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: true,
+ onlyAllowMergeIfPipelineSucceeds: true,
+ allowMergeOnSkippedPipeline: true,
+ },
+ service: {
+ rebase: rebaseMock,
+ poll: pollMock,
+ },
});
+ });
- describe('Rebase when pipelines must succeed and skipped pipelines are considered successful are enabled', () => {
- beforeEach(() => {
- createWrapper(
- {
- mr: {
- rebaseInProgress: false,
- canPushToSourceBranch: true,
- onlyAllowMergeIfPipelineSucceeds: true,
- allowMergeOnSkippedPipeline: true,
- },
- service: {
- rebase: rebaseMock,
- poll: pollMock,
- },
- },
- mergeRequestWidgetGraphql,
- );
- });
+ it('renders both rebase buttons', () => {
+ expect(findRebaseWithoutCiButton().exists()).toBe(true);
+ expect(findStandardRebaseButton().exists()).toBe(true);
+ });
+
+ it('starts the rebase when clicking', async () => {
+ findStandardRebaseButton().vm.$emit('click');
- it('renders both rebase buttons', () => {
- expect(findRebaseWithoutCiButton().exists()).toBe(true);
- expect(findStandardRebaseButton().exists()).toBe(true);
- });
+ await nextTick();
- it('starts the rebase when clicking', async () => {
- findStandardRebaseButton().vm.$emit('click');
+ expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false });
+ });
- await nextTick();
+ it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => {
+ findRebaseWithoutCiButton().vm.$emit('click');
- expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false });
- });
+ await nextTick();
- it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => {
- findRebaseWithoutCiButton().vm.$emit('click');
+ expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true });
+ });
+ });
+ });
- await nextTick();
+ describe('without permissions', () => {
+ const exampleTargetBranch = 'fake-branch-to-test-with';
- expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true });
- });
+ describe('UI text', () => {
+ beforeEach(() => {
+ createWrapper({
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: false,
+ targetBranch: exampleTargetBranch,
+ },
+ service: {},
});
});
- describe('without permissions', () => {
- const exampleTargetBranch = 'fake-branch-to-test-with';
-
- describe('UI text', () => {
- beforeEach(() => {
- createWrapper(
- {
- mr: {
- rebaseInProgress: false,
- canPushToSourceBranch: false,
- targetBranch: exampleTargetBranch,
- },
- service: {},
- },
- mergeRequestWidgetGraphql,
- );
- });
-
- it('renders a message explaining user does not have permissions', () => {
- const text = findRebaseMessageText();
-
- expect(text).toContain(
- 'Merge blocked: the source branch must be rebased onto the target branch.',
- );
- expect(text).toContain('the source branch must be rebased');
- });
-
- it('renders the correct target branch name', () => {
- const elem = findRebaseMessage();
-
- expect(elem.text()).toContain(
- 'Merge blocked: the source branch must be rebased onto the target branch.',
- );
- });
- });
+ it('renders a message explaining user does not have permissions', () => {
+ const text = findRebaseMessageText();
- it('does render the "Rebase without pipeline" button', () => {
- createWrapper(
- {
- mr: {
- rebaseInProgress: false,
- canPushToSourceBranch: false,
- targetBranch: exampleTargetBranch,
- },
- service: {},
- },
- mergeRequestWidgetGraphql,
- );
+ expect(text).toContain(
+ 'Merge blocked: the source branch must be rebased onto the target branch.',
+ );
+ expect(text).toContain('the source branch must be rebased');
+ });
- expect(findRebaseWithoutCiButton().exists()).toBe(true);
- });
+ it('renders the correct target branch name', () => {
+ const elem = findRebaseMessage();
+
+ expect(elem.text()).toContain(
+ 'Merge blocked: the source branch must be rebased onto the target branch.',
+ );
+ });
+ });
+
+ it('does render the "Rebase without pipeline" button', () => {
+ createWrapper({
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: false,
+ targetBranch: exampleTargetBranch,
+ },
+ service: {},
});
- describe('methods', () => {
- it('checkRebaseStatus', async () => {
- jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- createWrapper(
- {
- mr: {},
- service: {
- rebase() {
- return Promise.resolve();
- },
- poll() {
- return Promise.resolve({
- data: {
- rebase_in_progress: false,
- should_be_rebased: false,
- merge_error: null,
- },
- });
- },
+ expect(findRebaseWithoutCiButton().exists()).toBe(true);
+ });
+ });
+
+ describe('methods', () => {
+ it('checkRebaseStatus', async () => {
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ createWrapper({
+ mr: {},
+ service: {
+ rebase() {
+ return Promise.resolve();
+ },
+ poll() {
+ return Promise.resolve({
+ data: {
+ rebase_in_progress: false,
+ should_be_rebased: false,
+ merge_error: null,
},
- },
- mergeRequestWidgetGraphql,
- );
+ });
+ },
+ },
+ });
- wrapper.vm.rebase();
+ wrapper.vm.rebase();
- // Wait for the rebase request
- await nextTick();
- // Wait for the polling request
- await nextTick();
- // Wait for the eventHub to be called
- await nextTick();
+ // Wait for the rebase request
+ await nextTick();
+ // Wait for the polling request
+ await nextTick();
+ // Wait for the eventHub to be called
+ await nextTick();
- expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetRebaseSuccess');
- expect(toast).toHaveBeenCalledWith('Rebase completed');
- });
- });
+ expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetRebaseSuccess');
+ expect(toast).toHaveBeenCalledWith('Rebase completed');
});
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap
index 8c3a4978bb8..bd40a968392 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap
+++ b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`MRWidgetAutoMergeEnabled when graphql is disabled template should have correct elements 1`] = `
+exports[`MRWidgetAutoMergeEnabled template should have correct elements 1`] = `
<div
class="mr-widget-body media mr-widget-body-line-height-1 gl-line-height-normal"
>
@@ -51,199 +51,7 @@ exports[`MRWidgetAutoMergeEnabled when graphql is disabled template should have
class="gl-mr-3"
data-testid="statusText"
>
- Set by
- <a
- class="author-link inline"
- >
- <img
- class="avatar avatar-inline s16"
- src="no_avatar.png"
- />
-
- <span
- class="author"
- >
-
- </span>
- </a>
- to be merged automatically when the pipeline succeeds
- </h4>
-
- <div
- class="gl-display-flex gl-font-size-0 gl-ml-auto gl-gap-3"
- >
- <div
- class="gl-display-flex gl-align-items-flex-start"
- >
- <div
- class="dropdown b-dropdown gl-new-dropdown gl-display-block gl-md-display-none! btn-group"
- lazy=""
- no-caret=""
- title="Options"
- >
- <!---->
- <button
- aria-expanded="false"
- aria-haspopup="true"
- class="btn dropdown-toggle btn-default btn-sm gl-p-2! gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret"
- type="button"
- >
- <!---->
-
- <svg
- aria-hidden="true"
- class="dropdown-icon gl-icon s16"
- data-testid="ellipsis_v-icon"
- role="img"
- >
- <use
- href="#ellipsis_v"
- />
- </svg>
-
- <span
- class="gl-new-dropdown-button-text gl-sr-only"
- >
-
- </span>
-
- <svg
- aria-hidden="true"
- class="gl-button-icon dropdown-chevron gl-icon s16"
- data-testid="chevron-down-icon"
- role="img"
- >
- <use
- href="#chevron-down"
- />
- </svg>
- </button>
- <ul
- class="dropdown-menu dropdown-menu-right"
- role="menu"
- tabindex="-1"
- >
- <!---->
- </ul>
- </div>
-
- <button
- class="btn gl-display-none gl-md-display-block gl-float-left btn-confirm btn-sm gl-button btn-confirm-tertiary js-cancel-auto-merge"
- data-qa-selector="cancel_auto_merge_button"
- data-testid="cancelAutomaticMergeButton"
- type="button"
- >
- <!---->
-
- <!---->
-
- <span
- class="gl-button-text"
- >
-
- Cancel auto-merge
-
- </span>
- </button>
- </div>
- </div>
- </div>
-
- <div
- class="gl-md-display-none gl-border-l-1 gl-border-l-solid gl-border-gray-100 gl-ml-3 gl-pl-3 gl-h-6 gl-mt-1"
- >
- <button
- class="btn gl-vertical-align-top btn-default btn-sm gl-button btn-default-tertiary btn-icon"
- title="Collapse merge details"
- type="button"
- >
- <!---->
-
- <svg
- aria-hidden="true"
- class="gl-button-icon gl-icon s16"
- data-testid="chevron-lg-up-icon"
- role="img"
- >
- <use
- href="#chevron-lg-up"
- />
- </svg>
-
- <!---->
- </button>
- </div>
- </div>
-</div>
-`;
-
-exports[`MRWidgetAutoMergeEnabled when graphql is enabled template should have correct elements 1`] = `
-<div
- class="mr-widget-body media mr-widget-body-line-height-1 gl-line-height-normal"
->
- <div
- class="gl-w-6 gl-h-6 gl-display-flex gl-align-self-start gl-mr-3"
- >
- <div
- class="gl-display-flex gl-m-auto"
- >
- <div
- class="gl-mr-3 gl-p-2 gl-m-0! gl-text-blue-500 gl-w-6 gl-p-2"
- >
- <div
- class="gl-rounded-full gl-relative gl-display-flex mr-widget-extension-icon"
- >
- <div
- class="gl-absolute gl-top-half gl-left-50p gl-translate-x-n50 gl-display-flex gl-m-auto"
- >
- <div
- class="gl-display-flex gl-m-auto gl-translate-y-n50"
- >
- <svg
- aria-label="Scheduled "
- class="gl-display-block gl-icon s12"
- data-qa-selector="status_scheduled_icon"
- data-testid="status-scheduled-icon"
- role="img"
- >
- <use
- href="#status-scheduled"
- />
- </svg>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
-
- <div
- class="gl-display-flex gl-w-full"
- >
- <div
- class="media-body gl-display-flex gl-align-items-center"
- >
-
- <h4
- class="gl-mr-3"
- data-testid="statusText"
- >
- Set by
- <a
- class="author-link inline"
- >
- <img
- class="avatar avatar-inline s16"
- src="no_avatar.png"
- />
-
- <span
- class="author"
- >
-
- </span>
- </a>
- to be merged automatically when the pipeline succeeds
+ Set by to be merged automatically when the pipeline succeeds
</h4>
<div
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js
index 28182793683..5b9f30dfb86 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js
@@ -9,7 +9,6 @@ import eventHub from '~/vue_merge_request_widget/event_hub';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
let wrapper;
-let mergeRequestWidgetGraphqlEnabled = false;
function convertPropsToGraphqlState(props) {
return {
@@ -30,12 +29,6 @@ function convertPropsToGraphqlState(props) {
}
function factory(propsData, stateOverride = {}) {
- let state = {};
-
- if (mergeRequestWidgetGraphqlEnabled) {
- state = { ...convertPropsToGraphqlState(propsData), ...stateOverride };
- }
-
wrapper = extendedWrapper(
mount(autoMergeEnabledComponent, {
propsData: {
@@ -43,9 +36,8 @@ function factory(propsData, stateOverride = {}) {
service: new MRWidgetService({}),
},
data() {
- return { state };
+ return { ...convertPropsToGraphqlState(propsData), ...stateOverride };
},
- provide: { glFeatures: { mergeRequestWidgetGraphql: mergeRequestWidgetGraphqlEnabled } },
mocks: {
$apollo: {
queries: {
@@ -95,130 +87,88 @@ describe('MRWidgetAutoMergeEnabled', () => {
wrapper = null;
});
- [true, false].forEach((mergeRequestWidgetGraphql) => {
- describe(`when graphql is ${mergeRequestWidgetGraphql ? 'enabled' : 'disabled'}`, () => {
- beforeEach(() => {
- mergeRequestWidgetGraphqlEnabled = mergeRequestWidgetGraphql;
+ describe('computed', () => {
+ describe('cancelButtonText', () => {
+ it('should return "Cancel" if MWPS is selected', () => {
+ factory({
+ ...defaultMrProps(),
+ autoMergeStrategy: MWPS_MERGE_STRATEGY,
+ });
+
+ expect(wrapper.findByTestId('cancelAutomaticMergeButton').text()).toBe('Cancel auto-merge');
});
+ });
+ });
- describe('computed', () => {
- describe('cancelButtonText', () => {
- it('should return "Cancel" if MWPS is selected', () => {
- factory({
- ...defaultMrProps(),
- autoMergeStrategy: MWPS_MERGE_STRATEGY,
+ describe('methods', () => {
+ describe('cancelAutomaticMerge', () => {
+ it('should set flag and call service then tell main component to update the widget with data', async () => {
+ factory({
+ ...defaultMrProps(),
+ });
+ const mrObj = {
+ is_new_mr_data: true,
+ };
+ jest.spyOn(wrapper.vm.service, 'cancelAutomaticMerge').mockReturnValue(
+ new Promise((resolve) => {
+ resolve({
+ data: mrObj,
});
+ }),
+ );
- expect(wrapper.findByTestId('cancelAutomaticMergeButton').text()).toBe(
- 'Cancel auto-merge',
- );
- });
- });
- });
+ wrapper.vm.cancelAutomaticMerge();
- describe('methods', () => {
- describe('cancelAutomaticMerge', () => {
- it('should set flag and call service then tell main component to update the widget with data', async () => {
- factory({
- ...defaultMrProps(),
- });
- const mrObj = {
- is_new_mr_data: true,
- };
- jest.spyOn(wrapper.vm.service, 'cancelAutomaticMerge').mockReturnValue(
- new Promise((resolve) => {
- resolve({
- data: mrObj,
- });
- }),
- );
-
- wrapper.vm.cancelAutomaticMerge();
-
- await waitForPromises();
-
- expect(wrapper.vm.isCancellingAutoMerge).toBe(true);
- if (mergeRequestWidgetGraphql) {
- expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
- } else {
- expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
- }
- });
- });
+ await waitForPromises();
- describe('removeSourceBranch', () => {
- it('should set flag and call service then request main component to update the widget', async () => {
- factory({
- ...defaultMrProps(),
- });
- jest.spyOn(wrapper.vm.service, 'merge').mockReturnValue(
- Promise.resolve({
- data: {
- status: MWPS_MERGE_STRATEGY,
- },
- }),
- );
-
- wrapper.vm.removeSourceBranch();
-
- await waitForPromises();
-
- expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
- expect(wrapper.vm.service.merge).toHaveBeenCalledWith({
- sha,
- auto_merge_strategy: MWPS_MERGE_STRATEGY,
- should_remove_source_branch: true,
- });
- });
- });
+ expect(wrapper.vm.isCancellingAutoMerge).toBe(true);
+ expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
});
+ });
+ });
- describe('template', () => {
- it('should have correct elements', () => {
- factory({
- ...defaultMrProps(),
- });
+ describe('template', () => {
+ it('should have correct elements', () => {
+ factory({
+ ...defaultMrProps(),
+ });
- expect(wrapper.element).toMatchSnapshot();
- });
+ expect(wrapper.element).toMatchSnapshot();
+ });
- it('should disable cancel auto merge button when the action is in progress', async () => {
- factory({
- ...defaultMrProps(),
- });
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- isCancellingAutoMerge: true,
- });
+ it('should disable cancel auto merge button when the action is in progress', async () => {
+ factory({
+ ...defaultMrProps(),
+ });
+ // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
+ // eslint-disable-next-line no-restricted-syntax
+ wrapper.setData({
+ isCancellingAutoMerge: true,
+ });
- await nextTick();
+ await nextTick();
- expect(wrapper.find('.js-cancel-auto-merge').props('loading')).toBe(true);
- });
+ expect(wrapper.find('.js-cancel-auto-merge').props('loading')).toBe(true);
+ });
- it('should render the status text as "...to merged automatically" if MWPS is selected', () => {
- factory({
- ...defaultMrProps(),
- autoMergeStrategy: MWPS_MERGE_STRATEGY,
- });
+ it('should render the status text as "...to merged automatically" if MWPS is selected', () => {
+ factory({
+ ...defaultMrProps(),
+ autoMergeStrategy: MWPS_MERGE_STRATEGY,
+ });
- expect(getStatusText()).toContain(
- 'to be merged automatically when the pipeline succeeds',
- );
- });
+ expect(getStatusText()).toContain('to be merged automatically when the pipeline succeeds');
+ });
- it('should render the cancel button as "Cancel" if MWPS is selected', () => {
- factory({
- ...defaultMrProps(),
- autoMergeStrategy: MWPS_MERGE_STRATEGY,
- });
+ it('should render the cancel button as "Cancel" if MWPS is selected', () => {
+ factory({
+ ...defaultMrProps(),
+ autoMergeStrategy: MWPS_MERGE_STRATEGY,
+ });
- const cancelButtonText = trimText(wrapper.find('.js-cancel-auto-merge').text());
+ const cancelButtonText = trimText(wrapper.find('.js-cancel-auto-merge').text());
- expect(cancelButtonText).toBe('Cancel auto-merge');
- });
- });
+ expect(cancelButtonText).toBe('Cancel auto-merge');
});
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed_spec.js
index 398a3912882..826f708069c 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed_spec.js
@@ -9,18 +9,11 @@ describe('MRWidgetAutoMergeFailed', () => {
const mergeError = 'This is the merge error';
const findButton = () => wrapper.findComponent(GlButton);
- const createComponent = (props = {}, mergeRequestWidgetGraphql = false) => {
+ const createComponent = (props = {}) => {
wrapper = mount(AutoMergeFailedComponent, {
propsData: { ...props },
data() {
- if (mergeRequestWidgetGraphql) {
- return { mergeError: props.mr?.mergeError };
- }
-
- return {};
- },
- provide: {
- glFeatures: { mergeRequestWidgetGraphql },
+ return { mergeError: props.mr?.mergeError };
},
});
};
@@ -29,40 +22,33 @@ describe('MRWidgetAutoMergeFailed', () => {
wrapper.destroy();
});
- [true, false].forEach((mergeRequestWidgetGraphql) => {
- describe(`when graphql is ${mergeRequestWidgetGraphql ? 'enabled' : 'dislabed'}`, () => {
- beforeEach(() => {
- createComponent(
- {
- mr: { mergeError },
- },
- mergeRequestWidgetGraphql,
- );
- });
+ beforeEach(() => {
+ createComponent({
+ mr: { mergeError },
+ });
+ });
- it('renders failed message', () => {
- expect(wrapper.text()).toContain('This merge request failed to be merged automatically');
- });
+ it('renders failed message', () => {
+ expect(wrapper.text()).toContain('This merge request failed to be merged automatically');
+ });
- it('renders merge error provided', () => {
- expect(wrapper.text()).toContain(mergeError);
- });
+ it('renders merge error provided', () => {
+ expect(wrapper.text()).toContain(mergeError);
+ });
- it('render refresh button', () => {
- expect(findButton().text()).toBe('Refresh');
- });
+ it('render refresh button', () => {
+ expect(findButton().text()).toBe('Refresh');
+ });
- it('emits event and shows loading icon when button is clicked', async () => {
- jest.spyOn(eventHub, '$emit');
- findButton().vm.$emit('click');
+ it('emits event and shows loading icon when button is clicked', async () => {
+ jest.spyOn(eventHub, '$emit');
+ findButton().vm.$emit('click');
- expect(eventHub.$emit.mock.calls[0][0]).toBe('MRWidgetUpdateRequested');
+ expect(eventHub.$emit.mock.calls[0][0]).toBe('MRWidgetUpdateRequested');
- await nextTick();
+ await nextTick();
- expect(findButton().attributes('disabled')).toBe('disabled');
- expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
- });
- });
+ expect(findButton().attributes('disabled')).toBe('disabled');
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js
index 7a9fd5b002d..a16e4d4a6ea 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js
@@ -7,7 +7,6 @@ import ConflictsComponent from '~/vue_merge_request_widget/components/states/mr_
describe('MRWidgetConflicts', () => {
let wrapper;
- let mergeRequestWidgetGraphql = null;
const path = '/conflicts';
const findResolveButton = () => wrapper.findByTestId('resolve-conflicts-button');
@@ -25,10 +24,17 @@ describe('MRWidgetConflicts', () => {
wrapper = extendedWrapper(
mount(ConflictsComponent, {
propsData,
- provide: {
- glFeatures: {
- mergeRequestWidgetGraphql,
- },
+ data() {
+ return {
+ userPermissions: {
+ canMerge: propsData.mr.canMerge,
+ pushToSourceBranch: propsData.mr.canPushToSourceBranch,
+ },
+ state: {
+ shouldBeRebased: propsData.mr.shouldBeRebased,
+ sourceBranchProtected: propsData.mr.sourceBranchProtected,
+ },
+ };
},
mocks: {
$apollo: {
@@ -41,212 +47,188 @@ describe('MRWidgetConflicts', () => {
}),
);
- if (mergeRequestWidgetGraphql) {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- userPermissions: {
- canMerge: propsData.mr.canMerge,
- pushToSourceBranch: propsData.mr.canPushToSourceBranch,
- },
- stateData: {
- shouldBeRebased: propsData.mr.shouldBeRebased,
- sourceBranchProtected: propsData.mr.sourceBranchProtected,
- },
- });
- }
-
await nextTick();
}
afterEach(() => {
- mergeRequestWidgetGraphql = null;
wrapper.destroy();
});
- [false, true].forEach((featureEnabled) => {
- describe(`with GraphQL feature flag ${featureEnabled ? 'enabled' : 'disabled'}`, () => {
- beforeEach(() => {
- mergeRequestWidgetGraphql = featureEnabled;
+ // There are two permissions we need to consider:
+ //
+ // 1. Is the user allowed to merge to the target branch?
+ // 2. Is the user allowed to push to the source branch?
+ //
+ // This yields 4 possible permutations that we need to test, and
+ // we test them below. A user who can push to the source
+ // branch should be allowed to resolve conflicts. This is
+ // consistent with what the backend does.
+ describe('when allowed to merge but not allowed to push to source branch', () => {
+ beforeEach(async () => {
+ await createComponent({
+ mr: {
+ canMerge: true,
+ canPushToSourceBranch: false,
+ conflictResolutionPath: path,
+ conflictsDocsPath: '',
+ },
});
+ });
- // There are two permissions we need to consider:
- //
- // 1. Is the user allowed to merge to the target branch?
- // 2. Is the user allowed to push to the source branch?
- //
- // This yields 4 possible permutations that we need to test, and
- // we test them below. A user who can push to the source
- // branch should be allowed to resolve conflicts. This is
- // consistent with what the backend does.
- describe('when allowed to merge but not allowed to push to source branch', () => {
- beforeEach(async () => {
- await createComponent({
- mr: {
- canMerge: true,
- canPushToSourceBranch: false,
- conflictResolutionPath: path,
- conflictsDocsPath: '',
- },
- });
- });
+ it('should tell you about conflicts without bothering other people', () => {
+ expect(wrapper.text()).toContain(mergeConflictsText);
+ expect(wrapper.text()).not.toContain(userCannotMergeText);
+ });
- it('should tell you about conflicts without bothering other people', () => {
- expect(wrapper.text()).toContain(mergeConflictsText);
- expect(wrapper.text()).not.toContain(userCannotMergeText);
- });
+ it('should not allow you to resolve the conflicts', () => {
+ expect(wrapper.text()).not.toContain(resolveConflictsBtnText);
+ });
- it('should not allow you to resolve the conflicts', () => {
- expect(wrapper.text()).not.toContain(resolveConflictsBtnText);
- });
+ it('should have merge buttons', () => {
+ expect(findMergeLocalButton().text()).toContain(mergeLocallyBtnText);
+ });
+ });
- it('should have merge buttons', () => {
- expect(findMergeLocalButton().text()).toContain(mergeLocallyBtnText);
- });
+ describe('when not allowed to merge but allowed to push to source branch', () => {
+ beforeEach(async () => {
+ await createComponent({
+ mr: {
+ canMerge: false,
+ canPushToSourceBranch: true,
+ conflictResolutionPath: path,
+ conflictsDocsPath: '',
+ },
});
+ });
- describe('when not allowed to merge but allowed to push to source branch', () => {
- beforeEach(async () => {
- await createComponent({
- mr: {
- canMerge: false,
- canPushToSourceBranch: true,
- conflictResolutionPath: path,
- conflictsDocsPath: '',
- },
- });
- });
-
- it('should tell you about conflicts', () => {
- expect(wrapper.text()).toContain(mergeConflictsText);
- expect(wrapper.text()).toContain(userCannotMergeText);
- });
-
- it('should allow you to resolve the conflicts', () => {
- expect(findResolveButton().text()).toContain(resolveConflictsBtnText);
- expect(findResolveButton().attributes('href')).toEqual(path);
- });
-
- it('should not have merge buttons', () => {
- expect(wrapper.text()).not.toContain(mergeLocallyBtnText);
- });
+ it('should tell you about conflicts', () => {
+ expect(wrapper.text()).toContain(mergeConflictsText);
+ expect(wrapper.text()).toContain(userCannotMergeText);
+ });
+
+ it('should allow you to resolve the conflicts', () => {
+ expect(findResolveButton().text()).toContain(resolveConflictsBtnText);
+ expect(findResolveButton().attributes('href')).toEqual(path);
+ });
+
+ it('should not have merge buttons', () => {
+ expect(wrapper.text()).not.toContain(mergeLocallyBtnText);
+ });
+ });
+
+ describe('when allowed to merge and push to source branch', () => {
+ beforeEach(async () => {
+ await createComponent({
+ mr: {
+ canMerge: true,
+ canPushToSourceBranch: true,
+ conflictResolutionPath: path,
+ conflictsDocsPath: '',
+ },
});
+ });
- describe('when allowed to merge and push to source branch', () => {
- beforeEach(async () => {
- await createComponent({
- mr: {
- canMerge: true,
- canPushToSourceBranch: true,
- conflictResolutionPath: path,
- conflictsDocsPath: '',
- },
- });
- });
-
- it('should tell you about conflicts without bothering other people', () => {
- expect(wrapper.text()).toContain(mergeConflictsText);
- expect(wrapper.text()).not.toContain(userCannotMergeText);
- });
-
- it('should allow you to resolve the conflicts', () => {
- expect(findResolveButton().text()).toContain(resolveConflictsBtnText);
- expect(findResolveButton().attributes('href')).toEqual(path);
- });
-
- it('should have merge buttons', () => {
- expect(findMergeLocalButton().text()).toContain(mergeLocallyBtnText);
- });
+ it('should tell you about conflicts without bothering other people', () => {
+ expect(wrapper.text()).toContain(mergeConflictsText);
+ expect(wrapper.text()).not.toContain(userCannotMergeText);
+ });
+
+ it('should allow you to resolve the conflicts', () => {
+ expect(findResolveButton().text()).toContain(resolveConflictsBtnText);
+ expect(findResolveButton().attributes('href')).toEqual(path);
+ });
+
+ it('should have merge buttons', () => {
+ expect(findMergeLocalButton().text()).toContain(mergeLocallyBtnText);
+ });
+ });
+
+ describe('when user does not have permission to push to source branch', () => {
+ it('should show proper message', async () => {
+ await createComponent({
+ mr: {
+ canMerge: false,
+ canPushToSourceBranch: false,
+ conflictsDocsPath: '',
+ },
});
- describe('when user does not have permission to push to source branch', () => {
- it('should show proper message', async () => {
- await createComponent({
- mr: {
- canMerge: false,
- canPushToSourceBranch: false,
- conflictsDocsPath: '',
- },
- });
+ expect(wrapper.text().trim().replace(/\s\s+/g, ' ')).toContain(userCannotMergeText);
+ });
- expect(wrapper.text().trim().replace(/\s\s+/g, ' ')).toContain(userCannotMergeText);
- });
+ it('should not have action buttons', async () => {
+ await createComponent({
+ mr: {
+ canMerge: false,
+ canPushToSourceBranch: false,
+ conflictsDocsPath: '',
+ },
+ });
- it('should not have action buttons', async () => {
- await createComponent({
- mr: {
- canMerge: false,
- canPushToSourceBranch: false,
- conflictsDocsPath: '',
- },
- });
-
- expect(findResolveButton().exists()).toBe(false);
- expect(findMergeLocalButton().exists()).toBe(false);
- });
-
- it('should not have resolve button when no conflict resolution path', async () => {
- await createComponent({
- mr: {
- canMerge: true,
- conflictResolutionPath: null,
- conflictsDocsPath: '',
- },
- });
+ expect(findResolveButton().exists()).toBe(false);
+ expect(findMergeLocalButton().exists()).toBe(false);
+ });
- expect(findResolveButton().exists()).toBe(false);
- });
+ it('should not have resolve button when no conflict resolution path', async () => {
+ await createComponent({
+ mr: {
+ canMerge: true,
+ conflictResolutionPath: null,
+ conflictsDocsPath: '',
+ },
});
- describe('when fast-forward or semi-linear merge enabled', () => {
- it('should tell you to rebase locally', async () => {
- await createComponent({
- mr: {
- shouldBeRebased: true,
- conflictsDocsPath: '',
- },
- });
+ expect(findResolveButton().exists()).toBe(false);
+ });
+ });
- expect(removeBreakLine(wrapper.text()).trim()).toContain(fastForwardMergeText);
- });
+ describe('when fast-forward or semi-linear merge enabled', () => {
+ it('should tell you to rebase locally', async () => {
+ await createComponent({
+ mr: {
+ shouldBeRebased: true,
+ conflictsDocsPath: '',
+ },
});
- describe('when source branch protected', () => {
- beforeEach(async () => {
- await createComponent({
- mr: {
- canMerge: true,
- canPushToSourceBranch: true,
- conflictResolutionPath: TEST_HOST,
- sourceBranchProtected: true,
- conflictsDocsPath: '',
- },
- });
- });
+ expect(removeBreakLine(wrapper.text()).trim()).toContain(fastForwardMergeText);
+ });
+ });
- it('should allow you to resolve the conflicts', () => {
- expect(findResolveButton().exists()).toBe(true);
- });
+ describe('when source branch protected', () => {
+ beforeEach(async () => {
+ await createComponent({
+ mr: {
+ canMerge: true,
+ canPushToSourceBranch: true,
+ conflictResolutionPath: TEST_HOST,
+ sourceBranchProtected: true,
+ conflictsDocsPath: '',
+ },
});
+ });
- describe('when source branch not protected', () => {
- beforeEach(async () => {
- await createComponent({
- mr: {
- canMerge: true,
- canPushToSourceBranch: true,
- conflictResolutionPath: TEST_HOST,
- sourceBranchProtected: false,
- conflictsDocsPath: '',
- },
- });
- });
+ it('should not allow you to resolve the conflicts', () => {
+ expect(findResolveButton().exists()).toBe(false);
+ });
+ });
- it('should allow you to resolve the conflicts', () => {
- expect(findResolveButton().text()).toContain(resolveConflictsBtnText);
- expect(findResolveButton().attributes('href')).toEqual(TEST_HOST);
- });
+ describe('when source branch not protected', () => {
+ beforeEach(async () => {
+ await createComponent({
+ mr: {
+ canMerge: true,
+ canPushToSourceBranch: true,
+ conflictResolutionPath: TEST_HOST,
+ sourceBranchProtected: false,
+ conflictsDocsPath: '',
+ },
});
});
+
+ it('should allow you to resolve the conflicts', () => {
+ expect(findResolveButton().text()).toContain(resolveConflictsBtnText);
+ expect(findResolveButton().attributes('href')).toEqual(TEST_HOST);
+ });
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_missing_branch_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_missing_branch_spec.js
index ddce07954ab..f29cf55f7ce 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_missing_branch_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_missing_branch_spec.js
@@ -1,26 +1,17 @@
import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
import MissingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue';
let wrapper;
-async function factory(sourceBranchRemoved, mergeRequestWidgetGraphql) {
+function factory(sourceBranchRemoved) {
wrapper = shallowMount(MissingBranchComponent, {
propsData: {
mr: { sourceBranchRemoved },
},
- provide: {
- glFeatures: { mergeRequestWidgetGraphql },
+ data() {
+ return { state: { sourceBranchExists: !sourceBranchRemoved } };
},
});
-
- if (mergeRequestWidgetGraphql) {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ state: { sourceBranchExists: !sourceBranchRemoved } });
- }
-
- await nextTick();
}
describe('MRWidgetMissingBranch', () => {
@@ -28,22 +19,16 @@ describe('MRWidgetMissingBranch', () => {
wrapper.destroy();
});
- [true, false].forEach((mergeRequestWidgetGraphql) => {
- describe(`widget GraphQL feature flag is ${
- mergeRequestWidgetGraphql ? 'enabled' : 'disabled'
- }`, () => {
- it.each`
- sourceBranchRemoved | branchName
- ${true} | ${'source'}
- ${false} | ${'target'}
- `(
- 'should set missing branch name as $branchName when sourceBranchRemoved is $sourceBranchRemoved',
- async ({ sourceBranchRemoved, branchName }) => {
- await factory(sourceBranchRemoved, mergeRequestWidgetGraphql);
+ it.each`
+ sourceBranchRemoved | branchName
+ ${true} | ${'source'}
+ ${false} | ${'target'}
+ `(
+ 'should set missing branch name as $branchName when sourceBranchRemoved is $sourceBranchRemoved',
+ ({ sourceBranchRemoved, branchName }) => {
+ factory(sourceBranchRemoved);
- expect(wrapper.find('[data-testid="widget-content"]').text()).toContain(branchName);
- },
- );
- });
- });
+ expect(wrapper.find('[data-testid="widget-content"]').text()).toContain(branchName);
+ },
+ );
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
index 48d3f15560b..407bd60b2b7 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -60,6 +60,11 @@ const createTestMr = (customConfig) => {
translateStateToMachine: () => this.transitionStateMachine(),
state: 'open',
canMerge: true,
+ mergeable: true,
+ userPermissions: {
+ removeSourceBranch: true,
+ canMerge: true,
+ },
};
Object.assign(mr, customConfig.mr);
@@ -68,7 +73,7 @@ const createTestMr = (customConfig) => {
};
const createTestService = () => ({
- merge: jest.fn(),
+ merge: jest.fn().mockResolvedValue(),
poll: jest.fn().mockResolvedValue(),
});
@@ -87,21 +92,24 @@ const createReadyToMergeResponse = (customMr) => {
});
};
-const createComponent = (
- customConfig = {},
- mergeRequestWidgetGraphql = false,
- restructuredMrWidget = true,
-) => {
+const createComponent = (customConfig = {}, createState = true) => {
wrapper = shallowMount(ReadyToMerge, {
propsData: {
mr: createTestMr(customConfig),
service: createTestService(),
},
- provide: {
- glFeatures: {
- mergeRequestWidgetGraphql,
- restructuredMrWidget,
- },
+ data() {
+ if (createState) {
+ return {
+ loading: false,
+ state: {
+ ...createTestMr(customConfig),
+ },
+ };
+ }
+ return {
+ loading: true,
+ };
},
stubs: {
CommitEdit,
@@ -136,7 +144,7 @@ describe('ReadyToMerge', () => {
describe('computed', () => {
describe('isAutoMergeAvailable', () => {
it('should return true when at least one merge strategy is available', () => {
- createComponent();
+ createComponent({});
expect(wrapper.vm.isAutoMergeAvailable).toBe(true);
});
@@ -168,14 +176,14 @@ describe('ReadyToMerge', () => {
});
it('returns pending when pipeline is active', () => {
- createComponent({ mr: { pipeline: {}, isPipelineActive: true } });
+ createComponent({ mr: { pipeline: { active: true }, isPipelineActive: true } });
expect(wrapper.vm.status).toEqual('pending');
});
it('returns failed when pipeline is failed', () => {
createComponent({
- mr: { pipeline: {}, isPipelineFailed: true, availableAutoMergeStrategies: [] },
+ mr: { pipeline: { status: 'FAILED' }, availableAutoMergeStrategies: [], hasCI: true },
});
expect(wrapper.vm.status).toEqual('failed');
@@ -185,7 +193,7 @@ describe('ReadyToMerge', () => {
describe('Merge Button Variant', () => {
it('defaults to confirm class', () => {
createComponent({
- mr: { availableAutoMergeStrategies: [] },
+ mr: { availableAutoMergeStrategies: [], mergeable: true },
});
expect(findMergeButton().attributes('variant')).toBe('confirm');
@@ -194,19 +202,19 @@ describe('ReadyToMerge', () => {
describe('status icon', () => {
it('defaults to tick icon', () => {
- createComponent();
+ createComponent({ mr: { mergeable: true } });
expect(wrapper.vm.iconClass).toEqual('success');
});
it('shows tick for success status', () => {
- createComponent({ mr: { pipeline: true } });
+ createComponent({ mr: { pipeline: { status: 'SUCCESS' }, mergeable: true } });
expect(wrapper.vm.iconClass).toEqual('success');
});
it('shows tick for pending status', () => {
- createComponent({ mr: { pipeline: {}, isPipelineActive: true } });
+ createComponent({ mr: { pipeline: { active: true }, mergeable: true } });
expect(wrapper.vm.iconClass).toEqual('success');
});
@@ -266,7 +274,7 @@ describe('ReadyToMerge', () => {
describe('isMergeButtonDisabled', () => {
it('should return false with initial data', () => {
- createComponent({ mr: { isMergeAllowed: true } });
+ createComponent({ mr: { isMergeAllowed: true, mergeable: false } });
expect(wrapper.vm.isMergeButtonDisabled).toBe(false);
});
@@ -283,6 +291,7 @@ describe('ReadyToMerge', () => {
isMergeAllowed: false,
availableAutoMergeStrategies: [],
onlyAllowMergeIfPipelineSucceeds: true,
+ mergeable: false,
},
});
@@ -544,7 +553,15 @@ describe('ReadyToMerge', () => {
describe('Remove source branch checkbox', () => {
describe('when user can merge but cannot delete branch', () => {
it('should be disabled in the rendered output', () => {
- createComponent();
+ createComponent({
+ mr: {
+ mergeable: true,
+ userPermissions: {
+ removeSourceBranch: false,
+ canMerge: true,
+ },
+ },
+ });
expect(wrapper.find('#remove-source-branch-input').exists()).toBe(false);
});
@@ -553,7 +570,7 @@ describe('ReadyToMerge', () => {
describe('when user can merge and can delete branch', () => {
beforeEach(() => {
createComponent({
- mr: { canRemoveSourceBranch: true },
+ mr: { canRemoveSourceBranch: true, mergeable: true },
});
});
@@ -567,7 +584,7 @@ describe('ReadyToMerge', () => {
describe('squash checkbox', () => {
it('should be rendered when squash before merge is enabled and there is more than 1 commit', () => {
createComponent({
- mr: { commitsCount: 2, enableSquashBeforeMerge: true },
+ mr: { commitsCount: 2, enableSquashBeforeMerge: true, mergeable: true },
});
expect(findCheckboxElement().exists()).toBe(true);
@@ -665,6 +682,7 @@ describe('ReadyToMerge', () => {
squashIsSelected: true,
enableSquashBeforeMerge: true,
commitsCount: 2,
+ mergeRequestsFfOnlyEnabled: true,
},
});
@@ -795,7 +813,9 @@ describe('ReadyToMerge', () => {
});
it('shows the diverged commits text when the source branch is behind the target', () => {
- createComponent({ mr: { divergedCommitsCount: 9001, canMerge: false } });
+ createComponent({
+ mr: { divergedCommitsCount: 9001, userPermissions: { canMerge: false }, canMerge: false },
+ });
expect(wrapper.text()).toEqual(
expect.stringContaining('The source branch is 9001 commits behind the target branch'),
@@ -807,7 +827,7 @@ describe('ReadyToMerge', () => {
describe('Merge button when pipeline has failed', () => {
beforeEach(() => {
createComponent({
- mr: { pipeline: {}, isPipelineFailed: true, availableAutoMergeStrategies: [] },
+ mr: { headPipeline: { status: 'FAILED' }, availableAutoMergeStrategies: [], hasCI: true },
});
});
@@ -830,7 +850,7 @@ describe('ReadyToMerge', () => {
const USER_COMMIT_MESSAGE = 'Merge message provided manually by user';
const createDefaultGqlComponent = () =>
- createComponent({ mr: { commitsCount: 2, enableSquashBeforeMerge: true } }, true);
+ createComponent({ mr: { commitsCount: 2, enableSquashBeforeMerge: true } }, false);
beforeEach(() => {
readyToMergeResponseSpy = jest
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_wip_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_wip_spec.js
index 7259f210b6e..82aeac1a47d 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_wip_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_wip_spec.js
@@ -1,101 +1,42 @@
-import Vue, { nextTick } from 'vue';
-import waitForPromises from 'helpers/wait_for_promises';
+import { mount } from '@vue/test-utils';
import WorkInProgress from '~/vue_merge_request_widget/components/states/work_in_progress.vue';
-import toast from '~/vue_shared/plugins/global_toast';
-import eventHub from '~/vue_merge_request_widget/event_hub';
-jest.mock('~/vue_shared/plugins/global_toast');
-
-const createComponent = () => {
- const Component = Vue.extend(WorkInProgress);
- const mr = {
- title: 'The best MR ever',
- removeWIPPath: '/path/to/remove/wip',
- };
- const service = {
- removeWIP() {},
- };
- return new Component({
- el: document.createElement('div'),
- propsData: { mr, service },
+let wrapper;
+
+const createComponent = (updateMergeRequest = true) => {
+ wrapper = mount(WorkInProgress, {
+ propsData: {
+ mr: {},
+ },
+ data() {
+ return {
+ userPermissions: {
+ updateMergeRequest,
+ },
+ };
+ },
});
};
-describe('Wip', () => {
- describe('props', () => {
- it('should have props', () => {
- const { mr, service } = WorkInProgress.props;
-
- expect(mr.type instanceof Object).toBe(true);
- expect(mr.required).toBe(true);
-
- expect(service.type instanceof Object).toBe(true);
- expect(service.required).toBe(true);
- });
- });
-
- describe('data', () => {
- it('should have default data', () => {
- const vm = createComponent();
-
- expect(vm.isMakingRequest).toBe(false);
- });
- });
-
- describe('methods', () => {
- const mrObj = {
- is_new_mr_data: true,
- };
-
- describe('handleRemoveDraft', () => {
- it('should make a request to service and handle response', async () => {
- const vm = createComponent();
-
- jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- jest.spyOn(vm.service, 'removeWIP').mockReturnValue(
- new Promise((resolve) => {
- resolve({
- data: mrObj,
- });
- }),
- );
-
- vm.handleRemoveDraft();
-
- await waitForPromises();
-
- expect(vm.isMakingRequest).toBe(true);
- expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
- expect(toast).toHaveBeenCalledWith('Marked as ready. Merging is now allowed.');
- });
- });
+describe('Merge request widget draft state component', () => {
+ afterEach(() => {
+ wrapper.destroy();
});
describe('template', () => {
- let vm;
- let el;
-
- beforeEach(() => {
- vm = createComponent();
- el = vm.$el;
- });
-
it('should have correct elements', () => {
- expect(el.classList.contains('mr-widget-body')).toBe(true);
- expect(el.innerText).toContain(
+ createComponent(true);
+
+ expect(wrapper.text()).toContain(
"Merge blocked: merge request must be marked as ready. It's still marked as draft.",
);
- expect(el.querySelector('.js-remove-draft').innerText.replace(/\s\s+/g, ' ')).toContain(
- 'Mark as ready',
- );
+ expect(wrapper.find('[data-testid="removeWipButton"]').text()).toContain('Mark as ready');
});
- it('should not show removeWIP button is user cannot update MR', async () => {
- vm.mr.removeWIPPath = '';
-
- await nextTick();
+ it('should not show removeWIP button is user cannot update MR', () => {
+ createComponent(false);
- expect(el.querySelector('.js-remove-draft')).toBeNull();
+ expect(wrapper.find('[data-testid="removeWipButton"]').exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap
index bf50ae42794..e9a34453930 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap
+++ b/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`~/vue_merge_request_widget/components/widget/dynamic_content.vue renders given data 1`] = `
-"<content-row-stub level=\\"2\\" statusiconname=\\"success\\" widgetname=\\"MyWidget\\" header=\\"This is a header,This is a subheader\\" helppopover=\\"[object Object]\\">
+"<content-row-stub level=\\"2\\" statusiconname=\\"success\\" widgetname=\\"MyWidget\\" header=\\"This is a header,This is a subheader\\" helppopover=\\"[object Object]\\" actionbuttons=\\"\\">
<div class=\\"gl-display-flex gl-flex-direction-column\\">
<div>
<p class=\\"gl-mb-0\\">Main text for the row</p>
@@ -15,7 +15,7 @@ exports[`~/vue_merge_request_widget/components/widget/dynamic_content.vue render
</div>
<ul class=\\"gl-m-0 gl-p-0 gl-list-style-none\\">
<li>
- <content-row-stub level=\\"3\\" statusiconname=\\"\\" widgetname=\\"MyWidget\\" header=\\"Child row header\\" data-qa-selector=\\"child_content\\">
+ <content-row-stub level=\\"3\\" statusiconname=\\"\\" widgetname=\\"MyWidget\\" header=\\"Child row header\\" actionbuttons=\\"\\" data-qa-selector=\\"child_content\\">
<div class=\\"gl-display-flex gl-flex-direction-column\\">
<div>
<p class=\\"gl-mb-0\\">This is recursive. It will be listed in level 3.</p>
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js
index 606f7696694..e4bee6b8652 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js
@@ -1,6 +1,7 @@
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import WidgetContentRow from '~/vue_merge_request_widget/components/widget/widget_content_row.vue';
import StatusIcon from '~/vue_merge_request_widget/components/widget/status_icon.vue';
+import ActionButtons from '~/vue_merge_request_widget/components/action_buttons.vue';
import HelpPopover from '~/vue_shared/components/help_popover.vue';
describe('~/vue_merge_request_widget/components/widget/widget_content_row.vue', () => {
@@ -8,6 +9,7 @@ describe('~/vue_merge_request_widget/components/widget/widget_content_row.vue',
const findStatusIcon = () => wrapper.findComponent(StatusIcon);
const findHelpPopover = () => wrapper.findComponent(HelpPopover);
+ const findActionButtons = () => wrapper.findComponent(ActionButtons);
const createComponent = ({ propsData, slots } = {}) => {
wrapper = shallowMountExtended(WidgetContentRow, {
@@ -84,5 +86,17 @@ describe('~/vue_merge_request_widget/components/widget/widget_content_row.vue',
createComponent({});
expect(findHelpPopover().exists()).toBe(false);
});
+
+ it('does not display action buttons if actionButtons is not provided', () => {
+ createComponent({});
+ expect(findActionButtons().exists()).toBe(false);
+ });
+
+ it('does display action buttons if actionButtons is provided', () => {
+ const actionButtons = [{ text: 'click-me', href: '#' }];
+
+ createComponent({ propsData: { actionButtons } });
+ expect(findActionButtons().props('tertiaryButtons')).toEqual(actionButtons);
+ });
});
});
diff --git a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
index 02454af7242..0f4637d18d9 100644
--- a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
@@ -4,6 +4,8 @@ import MockAdapter from 'axios-mock-adapter';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import * as Sentry from '@sentry/browser';
+import getStateQueryResponse from 'test_fixtures/graphql/merge_requests/get_state.query.graphql.json';
+import readyToMergeResponse from 'test_fixtures/graphql/merge_requests/states/ready_to_merge.query.graphql.json';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { securityReportMergeRequestDownloadPathsQueryResponse } from 'jest/vue_shared/security_reports/mock_data';
@@ -22,6 +24,10 @@ import eventHub from '~/vue_merge_request_widget/event_hub';
import MrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_icon.vue';
import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql';
+import getStateQuery from '~/vue_merge_request_widget/queries/get_state.query.graphql';
+import readyToMergeQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql';
+import userPermissionsQuery from '~/vue_merge_request_widget/queries/permissions.query.graphql';
+import conflictsStateQuery from '~/vue_merge_request_widget/queries/states/conflicts.query.graphql';
import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data';
import mockData from './mock_data';
import {
@@ -83,7 +89,39 @@ describe('MrWidgetOptions', () => {
propsData: {
mrData: { ...mrData },
},
+ data() {
+ return { loading: false };
+ },
+
...options,
+ apolloProvider: createMockApollo([
+ [
+ getStateQuery,
+ jest.fn().mockResolvedValue({
+ data: {
+ project: {
+ ...getStateQueryResponse.data.project,
+ mergeRequest: {
+ ...getStateQueryResponse.data.project.mergeRequest,
+ mergeError: mrData.mergeError || null,
+ },
+ },
+ },
+ }),
+ ],
+ [readyToMergeQuery, jest.fn().mockResolvedValue(readyToMergeResponse)],
+ [
+ userPermissionsQuery,
+ jest.fn().mockResolvedValue({
+ data: { project: { mergeRequest: { userPermissions: {} } } },
+ }),
+ ],
+ [
+ conflictsStateQuery,
+ jest.fn().mockResolvedValue({ data: { project: { mergeRequest: {} } } }),
+ ],
+ ...(options.apolloMock || []),
+ ]),
});
return axios.waitForAll();
@@ -769,12 +807,12 @@ describe('MrWidgetOptions', () => {
mock.onGet(mockData.merge_request_cached_widget_path).reply(() => [200, mrData]);
return createComponent(mrData, {
- apolloProvider: createMockApollo([
+ apolloMock: [
[
securityReportMergeRequestDownloadPathsQuery,
async () => ({ data: securityReportMergeRequestDownloadPathsQueryResponse }),
],
- ]),
+ ],
});
};
@@ -837,8 +875,10 @@ describe('MrWidgetOptions', () => {
${'closed'} | ${false} | ${'hides'}
${'merged'} | ${true} | ${'shows'}
${'open'} | ${true} | ${'shows'}
- `('$showText merge error when state is $state', ({ state, show }) => {
- createComponent({ ...mockData, state, merge_error: 'Error!' });
+ `('$showText merge error when state is $state', async ({ state, show }) => {
+ createComponent({ ...mockData, state, mergeError: 'Error!' });
+
+ await waitForPromises();
expect(wrapper.find('[data-testid="merge_error"]').exists()).toBe(show);
});
@@ -1069,7 +1109,7 @@ describe('MrWidgetOptions', () => {
await nextTick();
await waitForPromises();
- expect(Sentry.captureException).toHaveBeenCalledTimes(1);
+ expect(Sentry.captureException).toHaveBeenCalledTimes(2);
expect(Sentry.captureException).toHaveBeenCalledWith(new Error('Fetch error'));
expect(wrapper.findComponent(StatusIcon).props('iconName')).toBe('failed');
});
diff --git a/spec/frontend/vue_merge_request_widget/stores/mr_widget_store_spec.js b/spec/frontend/vue_merge_request_widget/stores/mr_widget_store_spec.js
index 3cdb4265ef0..37df041210c 100644
--- a/spec/frontend/vue_merge_request_widget/stores/mr_widget_store_spec.js
+++ b/spec/frontend/vue_merge_request_widget/stores/mr_widget_store_spec.js
@@ -21,22 +21,9 @@ describe('MergeRequestStore', () => {
});
describe('setData', () => {
- it('should set isSHAMismatch when the diff SHA changes', () => {
- store.setData({ ...mockData, diff_head_sha: 'a-different-string' });
-
- expect(store.isSHAMismatch).toBe(true);
- });
-
- it('should not set isSHAMismatch when other data changes', () => {
- store.setData({ ...mockData, work_in_progress: !mockData.work_in_progress });
-
- expect(store.isSHAMismatch).toBe(false);
- });
-
it('should update cached sha after rebasing', () => {
store.setData({ ...mockData, diff_head_sha: 'abc123' }, true);
- expect(store.isSHAMismatch).toBe(false);
expect(store.sha).toBe('abc123');
});
diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/link_dependencies_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/link_dependencies_spec.js
index cfd493663b7..266782a7478 100644
--- a/spec/frontend/vue_shared/components/source_viewer/plugins/link_dependencies_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/plugins/link_dependencies_spec.js
@@ -2,6 +2,7 @@ import packageJsonLinker from '~/vue_shared/components/source_viewer/plugins/uti
import godepsJsonLinker from '~/vue_shared/components/source_viewer/plugins/utils/godeps_json_linker';
import gemspecLinker from '~/vue_shared/components/source_viewer/plugins/utils/gemspec_linker';
import gemfileLinker from '~/vue_shared/components/source_viewer/plugins/utils/gemfile_linker';
+import podspecJsonLinker from '~/vue_shared/components/source_viewer/plugins/utils/podspec_json_linker';
import linkDependencies from '~/vue_shared/components/source_viewer/plugins/link_dependencies';
import {
PACKAGE_JSON_FILE_TYPE,
@@ -9,12 +10,14 @@ import {
GEMSPEC_FILE_TYPE,
GODEPS_JSON_FILE_TYPE,
GEMFILE_FILE_TYPE,
+ PODSPEC_JSON_FILE_TYPE,
} from './mock_data';
jest.mock('~/vue_shared/components/source_viewer/plugins/utils/package_json_linker');
jest.mock('~/vue_shared/components/source_viewer/plugins/utils/gemspec_linker');
jest.mock('~/vue_shared/components/source_viewer/plugins/utils/godeps_json_linker');
jest.mock('~/vue_shared/components/source_viewer/plugins/utils/gemfile_linker');
+jest.mock('~/vue_shared/components/source_viewer/plugins/utils/podspec_json_linker');
describe('Highlight.js plugin for linking dependencies', () => {
const hljsResultMock = { value: 'test' };
@@ -38,4 +41,9 @@ describe('Highlight.js plugin for linking dependencies', () => {
linkDependencies(hljsResultMock, GEMFILE_FILE_TYPE);
expect(gemfileLinker).toHaveBeenCalled();
});
+
+ it('calls podspecJsonLinker for podspec_json file types', () => {
+ linkDependencies(hljsResultMock, PODSPEC_JSON_FILE_TYPE);
+ expect(podspecJsonLinker).toHaveBeenCalled();
+ });
});
diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/mock_data.js b/spec/frontend/vue_shared/components/source_viewer/plugins/mock_data.js
index 4f390cebd37..8f3ff9a15bb 100644
--- a/spec/frontend/vue_shared/components/source_viewer/plugins/mock_data.js
+++ b/spec/frontend/vue_shared/components/source_viewer/plugins/mock_data.js
@@ -6,3 +6,20 @@ export const GEMSPEC_FILE_TYPE = 'gemspec';
export const GODEPS_JSON_FILE_TYPE = 'godeps_json';
export const GEMFILE_FILE_TYPE = 'gemfile';
+
+export const PODSPEC_JSON_FILE_TYPE = 'podspec_json';
+
+export const PODSPEC_JSON_CONTENT = `{
+ "dependencies": {
+ "MyCheckCore": [
+ ]
+ },
+ "subspecs": [
+ {
+ "dependencies": {
+ "AFNetworking/Security": [
+ ]
+ }
+ }
+ ]
+ }`;
diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js
index e1dbdf8a87d..66e2020da27 100644
--- a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js
@@ -1,7 +1,9 @@
import {
createLink,
generateHLJSOpenTag,
+ getObjectKeysByKeyName,
} from '~/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util';
+import { PODSPEC_JSON_CONTENT } from '../mock_data';
describe('createLink', () => {
it('generates a link with the correct attributes', () => {
@@ -32,3 +34,11 @@ describe('generateHLJSOpenTag', () => {
expect(generateHLJSOpenTag(type)).toBe(result);
});
});
+
+describe('getObjectKeysByKeyName method', () => {
+ it('gets all object keys within specified key', () => {
+ const acc = [];
+ const keys = getObjectKeysByKeyName(JSON.parse(PODSPEC_JSON_CONTENT), 'dependencies', acc);
+ expect(keys).toEqual(['MyCheckCore', 'AFNetworking/Security']);
+ });
+});
diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/podspec_json_linker_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/podspec_json_linker_spec.js
new file mode 100644
index 00000000000..0ef63de68c6
--- /dev/null
+++ b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/podspec_json_linker_spec.js
@@ -0,0 +1,14 @@
+import podspecJsonLinker from '~/vue_shared/components/source_viewer/plugins/utils/podspec_json_linker';
+import { PODSPEC_JSON_CONTENT } from '../mock_data';
+
+describe('Highlight.js plugin for linking podspec_json dependencies', () => {
+ it('mutates the input value by wrapping dependency names in anchors', () => {
+ const inputValue =
+ '<span class="hljs-attr">&quot;AFNetworking/Security&quot;</span><span class="hljs-punctuation">:</span><span class=""> </span><span class="hljs-punctuation">[';
+ const outputValue =
+ '<span class="hljs-attr">&quot;<a href="https://cocoapods.org/pods/AFNetworking" target="_blank" rel="nofollow noreferrer noopener">AFNetworking/Security</a>&quot;</span><span class="hljs-punctuation">:</span><span class=""> </span><span class="hljs-punctuation">[';
+ const hljsResultMock = { value: inputValue };
+ const output = podspecJsonLinker(hljsResultMock, PODSPEC_JSON_CONTENT);
+ expect(output).toBe(outputValue);
+ });
+});
diff --git a/spec/graphql/mutations/ci/runner/bulk_delete_spec.rb b/spec/graphql/mutations/ci/runner/bulk_delete_spec.rb
index 3f8ba5955d5..2eccfd3409f 100644
--- a/spec/graphql/mutations/ci/runner/bulk_delete_spec.rb
+++ b/spec/graphql/mutations/ci/runner/bulk_delete_spec.rb
@@ -19,8 +19,9 @@ RSpec.describe Mutations::Ci::Runner::BulkDelete do
end
context 'when user can delete runners' do
+ let_it_be(:group) { create(:group) }
+
let(:user) { admin_user }
- let(:group) { create(:group) }
let!(:runners) do
create_list(:ci_runner, 2, :group, groups: [group])
end
diff --git a/spec/graphql/types/issue_type_enum_spec.rb b/spec/graphql/types/issue_type_enum_spec.rb
index d462c26c6ac..cd1737c3ebb 100644
--- a/spec/graphql/types/issue_type_enum_spec.rb
+++ b/spec/graphql/types/issue_type_enum_spec.rb
@@ -5,9 +5,9 @@ require 'spec_helper'
RSpec.describe Types::IssueTypeEnum do
specify { expect(described_class.graphql_name).to eq('IssueType') }
- it 'exposes all the existing issue type values except objective & key_result' do
+ it 'exposes all the existing issue type values except key_result' do
expect(described_class.values.keys).to match_array(
- %w[ISSUE INCIDENT TEST_CASE REQUIREMENT TASK]
+ %w[ISSUE INCIDENT TEST_CASE REQUIREMENT TASK OBJECTIVE]
)
end
end
diff --git a/spec/helpers/groups/observability_helper_spec.rb b/spec/helpers/groups/observability_helper_spec.rb
new file mode 100644
index 00000000000..4393f4e9bec
--- /dev/null
+++ b/spec/helpers/groups/observability_helper_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Groups::ObservabilityHelper do
+ let(:group) { build_stubbed(:group) }
+ let(:observability_url) { Gitlab::Observability.observability_url }
+
+ describe '#observability_iframe_src' do
+ context 'if observability_path is missing from params' do
+ it 'returns the iframe src for action: dashboards' do
+ allow(helper).to receive(:params).and_return({ action: 'dashboards' })
+ expect(helper.observability_iframe_src(group)).to eq("#{observability_url}/#{group.id}/")
+ end
+
+ it 'returns the iframe src for action: manage' do
+ allow(helper).to receive(:params).and_return({ action: 'manage' })
+ expect(helper.observability_iframe_src(group)).to eq("#{observability_url}/#{group.id}/dashboards")
+ end
+
+ it 'returns the iframe src for action: explore' do
+ allow(helper).to receive(:params).and_return({ action: 'explore' })
+ expect(helper.observability_iframe_src(group)).to eq("#{observability_url}/#{group.id}/explore")
+ end
+ end
+
+ context 'if observability_path exists in params' do
+ context 'if observability_path is valid' do
+ it 'returns the iframe src by injecting the observability path' do
+ allow(helper).to receive(:params).and_return({ action: '/explore', observability_path: '/foo?bar=foobar' })
+ expect(helper.observability_iframe_src(group)).to eq("#{observability_url}/#{group.id}/foo?bar=foobar")
+ end
+ end
+
+ context 'if observability_path is not valid' do
+ it 'returns the iframe src by injecting the sanitised observability path' do
+ allow(helper).to receive(:params).and_return({
+ action: '/explore',
+ observability_path:
+ "/test?groupId=<script>alert('attack!')</script>"
+ })
+ expect(helper.observability_iframe_src(group)).to eq(
+ "#{observability_url}/#{group.id}/test?groupId=alert('attack!')"
+ )
+ end
+ end
+ end
+
+ context 'when observability ui is standalone' do
+ before do
+ stub_env('STANDALONE_OBSERVABILITY_UI', 'true')
+ end
+
+ it 'returns the iframe src without group.id for action: dashboards' do
+ allow(helper).to receive(:params).and_return({ action: 'dashboards' })
+ expect(helper.observability_iframe_src(group)).to eq("#{observability_url}/")
+ end
+
+ it 'returns the iframe src without group.id for action: manage' do
+ allow(helper).to receive(:params).and_return({ action: 'manage' })
+ expect(helper.observability_iframe_src(group)).to eq("#{observability_url}/dashboards")
+ end
+
+ it 'returns the iframe src without group.id for action: explore' do
+ allow(helper).to receive(:params).and_return({ action: 'explore' })
+ expect(helper.observability_iframe_src(group)).to eq("#{observability_url}/explore")
+ end
+ end
+ end
+
+ describe '#observability_page_title' do
+ it 'returns the title for action: dashboards' do
+ allow(helper).to receive(:params).and_return({ action: 'dashboards' })
+ expect(helper.observability_page_title).to eq("Dashboards")
+ end
+
+ it 'returns the title for action: manage' do
+ allow(helper).to receive(:params).and_return({ action: 'manage' })
+ expect(helper.observability_page_title).to eq("Manage Dashboards")
+ end
+
+ it 'returns the title for action: explore' do
+ allow(helper).to receive(:params).and_return({ action: 'explore' })
+ expect(helper.observability_page_title).to eq("Explore")
+ end
+
+ it 'returns the default title for unknown action' do
+ allow(helper).to receive(:params).and_return({ action: 'unknown' })
+ expect(helper.observability_page_title).to eq("Dashboards")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cluster/lifecycle_events_spec.rb b/spec/lib/gitlab/cluster/lifecycle_events_spec.rb
index 5eea78acd98..45becb8370c 100644
--- a/spec/lib/gitlab/cluster/lifecycle_events_spec.rb
+++ b/spec/lib/gitlab/cluster/lifecycle_events_spec.rb
@@ -3,38 +3,55 @@
require 'spec_helper'
RSpec.describe Gitlab::Cluster::LifecycleEvents do
+ using RSpec::Parameterized::TableSyntax
+
# we create a new instance to ensure that we do not touch existing hooks
let(:replica) { Class.new(described_class) }
- context 'hooks execution' do
- using RSpec::Parameterized::TableSyntax
+ before do
+ # disable blackout period to speed-up tests
+ stub_config(shutdown: { blackout_seconds: 0 })
+ end
- where(:method, :hook_names) do
- :do_worker_start | %i[worker_start_hooks]
- :do_before_fork | %i[before_fork_hooks]
- :do_before_graceful_shutdown | %i[master_blackout_period master_graceful_shutdown]
- :do_before_master_restart | %i[master_restart_hooks]
+ context 'outside of clustered environments' do
+ where(:hook, :was_executed_immediately) do
+ :on_worker_start | true
+ :on_before_fork | false
+ :on_before_graceful_shutdown | false
+ :on_before_master_restart | false
+ :on_worker_stop | false
end
- before do
- # disable blackout period to speed-up tests
- stub_config(shutdown: { blackout_seconds: 0 })
+ with_them do
+ it 'executes the given block immediately' do
+ was_executed = false
+ replica.public_send(hook, &proc { was_executed = true })
+
+ expect(was_executed).to eq(was_executed_immediately)
+ end
end
+ end
- with_them do
- subject { replica.public_send(method) }
+ context 'in clustered environments' do
+ before do
+ allow(Gitlab::Runtime).to receive(:puma?).and_return(true)
+ replica.set_puma_options(workers: 2)
+ end
- it 'executes all hooks' do
- hook_names.each do |hook_name|
- hook = double
- replica.instance_variable_set(:"@#{hook_name}", [hook])
+ where(:hook, :execution_helper) do
+ :on_worker_start | :do_worker_start
+ :on_before_fork | :do_before_fork
+ :on_before_graceful_shutdown | :do_before_graceful_shutdown
+ :on_before_master_restart | :do_before_master_restart
+ :on_worker_stop | :do_worker_stop
+ end
- # ensure that proper hooks are called
- expect(hook).to receive(:call)
- expect(replica).to receive(:call).with(hook_name, anything).and_call_original
- end
+ with_them do
+ it 'requires explicit execution via do_* helper' do
+ was_executed = false
+ replica.public_send(hook, &proc { was_executed = true })
- subject
+ expect { replica.public_send(execution_helper) }.to change { was_executed }.from(false).to(true)
end
end
end
diff --git a/spec/lib/gitlab/experimentation/group_types_spec.rb b/spec/lib/gitlab/experimentation/group_types_spec.rb
deleted file mode 100644
index 2b118d76fa4..00000000000
--- a/spec/lib/gitlab/experimentation/group_types_spec.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Experimentation::GroupTypes do
- it 'defines a GROUP_CONTROL constant' do
- expect(described_class.const_defined?(:GROUP_CONTROL)).to be_truthy
- end
-
- it 'defines a GROUP_EXPERIMENTAL constant' do
- expect(described_class.const_defined?(:GROUP_EXPERIMENTAL)).to be_truthy
- end
-end
diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb
index 7c84c737c00..17f802b9f66 100644
--- a/spec/lib/gitlab/git/tree_spec.rb
+++ b/spec/lib/gitlab/git/tree_spec.rb
@@ -239,7 +239,7 @@ RSpec.describe Gitlab::Git::Tree do
let(:pagination_params) { { limit: 5, page_token: 'aabbccdd' } }
it 'raises a command error' do
- expect { entries }.to raise_error(Gitlab::Git::CommandError, 'could not find starting OID: aabbccdd')
+ expect { entries }.to raise_error(Gitlab::Git::CommandError, /could not find starting OID: aabbccdd/)
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 18d4b33f2b7..e9dde1c6180 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -552,6 +552,7 @@ project:
- path_locks
- approver_groups
- repository_state
+- wiki_repository
- wiki_repository_state
- source_pipelines
- sourced_pipelines
diff --git a/spec/lib/gitlab/json_spec.rb b/spec/lib/gitlab/json_spec.rb
index e54356d94c1..cbfab7e8884 100644
--- a/spec/lib/gitlab/json_spec.rb
+++ b/spec/lib/gitlab/json_spec.rb
@@ -468,6 +468,21 @@ RSpec.describe Gitlab::Json do
expect(new_result).to eq(original_result)
end
+
+ it "behaves the same when processing invalid unicode data" do
+ invalid_obj = { test: "Gr\x80\x81e" }
+ default_encoder = ActiveSupport::JSON::Encoding::JSONGemEncoder
+
+ original_result = ActiveSupport::JSON::Encoding.use_encoder(default_encoder) do
+ expect { ActiveSupport::JSON.encode(invalid_obj) }.to raise_error(JSON::GeneratorError)
+ end
+
+ new_result = ActiveSupport::JSON::Encoding.use_encoder(described_class) do
+ expect { ActiveSupport::JSON.encode(invalid_obj) }.to raise_error(JSON::GeneratorError)
+ end
+
+ expect(new_result).to eq(original_result)
+ end
end
end
# rubocop: enable Gitlab/Json
diff --git a/spec/lib/gitlab/metrics/method_call_spec.rb b/spec/lib/gitlab/metrics/method_call_spec.rb
index 6aa89c7cb05..091f35bfbcc 100644
--- a/spec/lib/gitlab/metrics/method_call_spec.rb
+++ b/spec/lib/gitlab/metrics/method_call_spec.rb
@@ -24,47 +24,22 @@ RSpec.describe Gitlab::Metrics::MethodCall do
allow(method_call).to receive(:above_threshold?).and_return(true)
end
- context 'prometheus instrumentation is enabled' do
- before do
- stub_feature_flags(prometheus_metrics_method_instrumentation: true)
- end
-
- around do |example|
- freeze_time do
- example.run
- end
- end
-
- it 'metric is not a NullMetric' do
- method_call.measure { 'foo' }
- expect(::Gitlab::Metrics::WebTransaction.prometheus_metric(:gitlab_method_call_duration_seconds, :histogram)).not_to be_instance_of(Gitlab::Metrics::NullMetric)
- end
-
- it 'observes the performance of the supplied block' do
- expect(transaction)
- .to receive(:observe).with(:gitlab_method_call_duration_seconds, be_a_kind_of(Numeric), { method: "#bar", module: :Foo })
-
- method_call.measure { 'foo' }
+ around do |example|
+ freeze_time do
+ example.run
end
end
- context 'prometheus instrumentation is disabled' do
- before do
- stub_feature_flags(prometheus_metrics_method_instrumentation: false)
- end
-
- it 'observes the performance of the supplied block' do
- expect(transaction)
- .to receive(:observe).with(:gitlab_method_call_duration_seconds, be_a_kind_of(Numeric), { method: "#bar", module: :Foo })
-
- method_call.measure { 'foo' }
- end
+ it 'metric is not a NullMetric' do
+ method_call.measure { 'foo' }
+ expect(::Gitlab::Metrics::WebTransaction.prometheus_metric(:gitlab_method_call_duration_seconds, :histogram)).not_to be_instance_of(Gitlab::Metrics::NullMetric)
+ end
- it 'observes using NullMetric' do
- method_call.measure { 'foo' }
+ it 'observes the performance of the supplied block' do
+ expect(transaction)
+ .to receive(:observe).with(:gitlab_method_call_duration_seconds, be_a_kind_of(Numeric), { method: "#bar", module: :Foo })
- expect(::Gitlab::Metrics::WebTransaction.prometheus_metric(:gitlab_method_call_duration_seconds, :histogram)).to be_instance_of(Gitlab::Metrics::NullMetric)
- end
+ method_call.measure { 'foo' }
end
end
diff --git a/spec/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled_spec.rb b/spec/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled_spec.rb
index 05cdc5bb79b..d42cef8bcba 100644
--- a/spec/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled_spec.rb
+++ b/spec/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe Gitlab::PerformanceBar::RedisAdapterWhenPeekEnabled do
it 'stores request id and enqueues stats job' do
expect_to_obtain_exclusive_lease(GitlabPerformanceBarStatsWorker::LEASE_KEY, uuid)
expect(GitlabPerformanceBarStatsWorker).to receive(:perform_in).with(GitlabPerformanceBarStatsWorker::WORKER_DELAY, uuid)
- expect(client).to receive(:sadd).with(GitlabPerformanceBarStatsWorker::STATS_KEY, uuid)
+ expect(client).to receive(:sadd?).with(GitlabPerformanceBarStatsWorker::STATS_KEY, uuid)
expect(client).to receive(:expire).with(GitlabPerformanceBarStatsWorker::STATS_KEY, GitlabPerformanceBarStatsWorker::STATS_KEY_EXPIRE)
peek_adapter.new(client).save('foo')
@@ -56,7 +56,7 @@ RSpec.describe Gitlab::PerformanceBar::RedisAdapterWhenPeekEnabled do
it 'stores request id but does not enqueue any job' do
expect(GitlabPerformanceBarStatsWorker).not_to receive(:perform_in)
- expect(client).to receive(:sadd).with(GitlabPerformanceBarStatsWorker::STATS_KEY, uuid)
+ expect(client).to receive(:sadd?).with(GitlabPerformanceBarStatsWorker::STATS_KEY, uuid)
peek_adapter.new(client).save('foo')
end
diff --git a/spec/lib/gitlab/redis/multi_store_spec.rb b/spec/lib/gitlab/redis/multi_store_spec.rb
index c3b3d876779..6e3001b38b7 100644
--- a/spec/lib/gitlab/redis/multi_store_spec.rb
+++ b/spec/lib/gitlab/redis/multi_store_spec.rb
@@ -130,15 +130,13 @@ RSpec.describe Gitlab::Redis::MultiStore do
primary_store.multi do |multi|
multi.set(key1, value1)
multi.set(key2, value2)
- multi.sadd(skey, value1)
- multi.sadd(skey, value2)
+ multi.sadd(skey, [value1, value2])
end
secondary_store.multi do |multi|
multi.set(key1, value1)
multi.set(key2, value2)
- multi.sadd(skey, value1)
- multi.sadd(skey, value2)
+ multi.sadd(skey, [value1, value2])
end
end
@@ -332,8 +330,8 @@ RSpec.describe Gitlab::Redis::MultiStore do
let_it_be(:skey) { "redis:set:key" }
let_it_be(:svalues1) { [value2, value1] }
let_it_be(:svalues2) { [value1] }
- let_it_be(:skey_value1) { [skey, value1] }
- let_it_be(:skey_value2) { [skey, value2] }
+ let_it_be(:skey_value1) { [skey, [value1]] }
+ let_it_be(:skey_value2) { [skey, [value2]] }
let_it_be(:script) { %(redis.call("set", "#{key1}", "#{value1}")) }
where(:case_name, :name, :args, :expected_value, :verification_name, :verification_args) do
@@ -353,12 +351,12 @@ RSpec.describe Gitlab::Redis::MultiStore do
primary_store.multi do |multi|
multi.set(key2, value1)
- multi.sadd(skey, value1)
+ multi.sadd?(skey, value1)
end
secondary_store.multi do |multi|
multi.set(key2, value1)
- multi.sadd(skey, value1)
+ multi.sadd?(skey, value1)
end
end
diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb
index c85a72be54c..3665f13015e 100644
--- a/spec/models/active_session_spec.rb
+++ b/spec/models/active_session_spec.rb
@@ -260,7 +260,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_sessions do
redis.set("session:gitlab:#{rack_session.private_id}", '')
redis.set(session_key, serialized_session)
- redis.sadd(lookup_key, active_session_lookup_key)
+ redis.sadd?(lookup_key, active_session_lookup_key)
end
end
@@ -338,7 +338,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_sessions do
session_private_id = Rack::Session::SessionId.new(session_public_id).private_id
active_session = ActiveSession.new(session_private_id: session_private_id)
redis.set(key_name(user.id, session_private_id), dump_session(active_session))
- redis.sadd(lookup_key, session_private_id)
+ redis.sadd?(lookup_key, session_private_id)
end
# setup for unrelated user
@@ -347,7 +347,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_sessions do
active_session = ActiveSession.new(session_private_id: session_private_id)
redis.set(key_name(unrelated_user_id, session_private_id), dump_session(active_session))
- redis.sadd(described_class.lookup_key_name(unrelated_user_id), session_private_id)
+ redis.sadd?(described_class.lookup_key_name(unrelated_user_id), session_private_id)
end
end
@@ -372,7 +372,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_sessions do
Gitlab::Redis::Sessions.with do |redis|
redis.set(key_name(user.id, impersonated_session_id),
dump_session(ActiveSession.new(session_id: Rack::Session::SessionId.new(impersonated_session_id), is_impersonated: true)))
- redis.sadd(lookup_key, impersonated_session_id)
+ redis.sadd?(lookup_key, impersonated_session_id)
end
expect { ActiveSession.destroy_all_but_current(user, request.session) }.to change { ActiveSession.session_ids_for_user(user.id).size }.from(3).to(2)
@@ -418,8 +418,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_sessions do
it 'removes obsolete lookup entries' do
Gitlab::Redis::Sessions.with do |redis|
redis.set(session_key, '')
- redis.sadd(lookup_key, current_session_id)
- redis.sadd(lookup_key, '59822c7d9fcdfa03725eff41782ad97d')
+ redis.sadd(lookup_key, [current_session_id, '59822c7d9fcdfa03725eff41782ad97d'])
end
ActiveSession.cleanup(user)
@@ -445,7 +444,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_sessions do
key_name(user.id, number),
dump_session(ActiveSession.new(session_id: number.to_s, updated_at: number.days.ago))
)
- redis.sadd(lookup_key, number.to_s)
+ redis.sadd?(lookup_key, number.to_s)
end
end
end
@@ -477,7 +476,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_sessions do
it 'removes obsolete lookup entries even without active session' do
Gitlab::Redis::Sessions.with do |redis|
- redis.sadd(lookup_key, (max_number_of_sessions_plus_two + 1).to_s)
+ redis.sadd?(lookup_key, (max_number_of_sessions_plus_two + 1).to_s)
end
ActiveSession.cleanup(user)
@@ -534,7 +533,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_sessions do
key_name(user.id, number),
dump_session(ActiveSession.new(session_private_id: number.to_s, updated_at: number.days.ago))
)
- redis.sadd(lookup_key, number.to_s)
+ redis.sadd?(lookup_key, number.to_s)
end
end
end
@@ -601,11 +600,10 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_sessions do
dump_session(ActiveSession.new(session_id: number.to_s, updated_at: number.days.ago))
)
- redis.sadd(lookup_key, number.to_s)
+ redis.sadd?(lookup_key, number.to_s)
end
- redis.sadd(lookup_key, (active_count + 1).to_s)
- redis.sadd(lookup_key, (active_count + 2).to_s)
+ redis.sadd?(lookup_key, [(active_count + 1).to_s, (active_count + 2).to_s])
end
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index e1bb1195813..e31298e489d 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -25,6 +25,7 @@ RSpec.describe Ci::Build do
it { is_expected.to have_many(:needs) }
it { is_expected.to have_many(:sourced_pipelines) }
+ it { is_expected.to have_one(:sourced_pipeline) }
it { is_expected.to have_many(:job_variables) }
it { is_expected.to have_many(:report_results) }
it { is_expected.to have_many(:pages_deployments) }
@@ -2797,16 +2798,6 @@ RSpec.describe Ci::Build do
expect(environment_based_variables_collection).to be_empty
end
- context 'when ci_job_jwt feature flag is disabled' do
- before do
- stub_feature_flags(ci_job_jwt: false)
- end
-
- it 'CI_JOB_JWT is not included' do
- expect(subject.pluck(:key)).not_to include('CI_JOB_JWT')
- end
- end
-
context 'when CI_JOB_JWT generation fails' do
[
OpenSSL::PKey::RSAError,
diff --git a/spec/models/ci/processable_spec.rb b/spec/models/ci/processable_spec.rb
index 535999f211d..e62e5f84a6d 100644
--- a/spec/models/ci/processable_spec.rb
+++ b/spec/models/ci/processable_spec.rb
@@ -81,7 +81,7 @@ RSpec.describe Ci::Processable do
commit_id deployment erased_by_id project_id
runner_id tag_taggings taggings tags trigger_request_id
user_id auto_canceled_by_id retried failure_reason
- sourced_pipelines artifacts_file_store artifacts_metadata_store
+ sourced_pipelines sourced_pipeline artifacts_file_store artifacts_metadata_store
metadata runner_session trace_chunks upstream_pipeline_id
artifacts_file artifacts_metadata artifacts_size commands
resource resource_group_id processed security_scans author
diff --git a/spec/models/experiment_spec.rb b/spec/models/experiment_spec.rb
deleted file mode 100644
index dc740ce8b0f..00000000000
--- a/spec/models/experiment_spec.rb
+++ /dev/null
@@ -1,150 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Experiment do
- subject { build(:experiment) }
-
- describe 'associations' do
- it { is_expected.to have_many(:experiment_subjects) }
- end
-
- describe 'validations' do
- it { is_expected.to validate_presence_of(:name) }
- it { is_expected.to validate_uniqueness_of(:name) }
- it { is_expected.to validate_length_of(:name).is_at_most(255) }
- end
-
- describe '#record_conversion_event_for_subject' do
- let_it_be(:user) { create(:user) }
- let_it_be(:experiment) { create(:experiment) }
- let_it_be(:context) { { a: 42 } }
-
- subject(:record_conversion) { experiment.record_conversion_event_for_subject(user, context) }
-
- context 'when no existing experiment_subject record exists for the given user' do
- it 'does not update or create an experiment_subject record' do
- expect { record_conversion }.not_to change { ExperimentSubject.all.to_a }
- end
- end
-
- context 'when an existing experiment_subject exists for the given user' do
- context 'but it has already been converted' do
- let(:experiment_subject) { create(:experiment_subject, experiment: experiment, user: user, converted_at: 2.days.ago) }
-
- it 'does not update the converted_at value' do
- expect { record_conversion }.not_to change { experiment_subject.converted_at }
- end
- end
-
- context 'and it has not yet been converted' do
- let(:experiment_subject) { create(:experiment_subject, experiment: experiment, user: user) }
-
- it 'updates the converted_at value' do
- expect { record_conversion }.to change { experiment_subject.reload.converted_at }
- end
- end
-
- context 'with no existing context' do
- let(:experiment_subject) { create(:experiment_subject, experiment: experiment, user: user) }
-
- it 'updates the context' do
- expect { record_conversion }.to change { experiment_subject.reload.context }.to('a' => 42)
- end
- end
-
- context 'with an existing context' do
- let(:experiment_subject) { create(:experiment_subject, experiment: experiment, user: user, converted_at: 2.days.ago, context: { b: 1 }) }
-
- it 'merges the context' do
- expect { record_conversion }.to change { experiment_subject.reload.context }.to('a' => 42, 'b' => 1)
- end
- end
- end
- end
-
- describe '#record_subject_and_variant!' do
- let_it_be(:subject_to_record) { create(:group) }
- let_it_be(:variant) { :control }
- let_it_be(:experiment) { create(:experiment) }
-
- subject(:record_subject_and_variant!) { experiment.record_subject_and_variant!(subject_to_record, variant) }
-
- context 'when no existing experiment_subject record exists for the given subject' do
- it 'creates an experiment_subject record' do
- expect { record_subject_and_variant! }.to change(ExperimentSubject, :count).by(1)
- expect(ExperimentSubject.last.variant).to eq(variant.to_s)
- end
- end
-
- context 'when an existing experiment_subject exists for the given subject' do
- let_it_be(:experiment_subject) do
- create(:experiment_subject, experiment: experiment, namespace: subject_to_record, user: nil, variant: :experimental)
- end
-
- context 'when it belongs to the same variant' do
- let(:variant) { :experimental }
-
- it 'does not initiate a transaction' do
- expect(Experiment.connection).not_to receive(:transaction)
-
- subject
- end
- end
-
- context 'but it belonged to a different variant' do
- it 'updates the variant value' do
- expect { record_subject_and_variant! }.to change { experiment_subject.reload.variant }.to('control')
- end
- end
- end
-
- describe 'providing a subject to record' do
- context 'when given a group as subject' do
- it 'saves the namespace as the experiment subject' do
- expect(record_subject_and_variant!.namespace).to eq(subject_to_record)
- end
- end
-
- context 'when given a users namespace as subject' do
- let_it_be(:subject_to_record) { build(:namespace) }
-
- it 'saves the namespace as the experiment_subject' do
- expect(record_subject_and_variant!.namespace).to eq(subject_to_record)
- end
- end
-
- context 'when given a user as subject' do
- let_it_be(:subject_to_record) { build(:user) }
-
- it 'saves the user as experiment_subject user' do
- expect(record_subject_and_variant!.user).to eq(subject_to_record)
- end
- end
-
- context 'when given a project as subject' do
- let_it_be(:subject_to_record) { build(:project) }
-
- it 'saves the project as experiment_subject user' do
- expect(record_subject_and_variant!.project).to eq(subject_to_record)
- end
- end
-
- context 'when given no subject' do
- let_it_be(:subject_to_record) { nil }
-
- it 'raises an error' do
- expect { record_subject_and_variant! }.to raise_error('Incompatible subject provided!')
- end
- end
-
- context 'when given an incompatible subject' do
- let_it_be(:subject_to_record) { build(:ci_build) }
-
- it 'raises an error' do
- expect { record_subject_and_variant! }.to raise_error('Incompatible subject provided!')
- end
- end
- end
- end
-end
diff --git a/spec/models/experiment_subject_spec.rb b/spec/models/experiment_subject_spec.rb
deleted file mode 100644
index d86dc3cbf65..00000000000
--- a/spec/models/experiment_subject_spec.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe ExperimentSubject, type: :model do
- describe 'associations' do
- it { is_expected.to belong_to(:experiment) }
- it { is_expected.to belong_to(:user) }
- it { is_expected.to belong_to(:namespace) }
- it { is_expected.to belong_to(:project) }
- end
-
- describe 'validations' do
- it { is_expected.to validate_presence_of(:experiment) }
-
- describe 'must_have_one_subject_present' do
- let(:experiment_subject) { build(:experiment_subject, user: nil, namespace: nil, project: nil) }
- let(:error_message) { 'Must have exactly one of User, Namespace, or Project.' }
-
- it 'fails when no subject is present' do
- expect(experiment_subject).not_to be_valid
- expect(experiment_subject.errors[:base]).to include(error_message)
- end
-
- it 'passes when user subject is present' do
- experiment_subject.user = build(:user)
- expect(experiment_subject).to be_valid
- end
-
- it 'passes when namespace subject is present' do
- experiment_subject.namespace = build(:group)
- expect(experiment_subject).to be_valid
- end
-
- it 'passes when project subject is present' do
- experiment_subject.project = build(:project)
- expect(experiment_subject).to be_valid
- end
-
- it 'fails when more than one subject is present', :aggregate_failures do
- # two subjects
- experiment_subject.user = build(:user)
- experiment_subject.namespace = build(:group)
- expect(experiment_subject).not_to be_valid
- expect(experiment_subject.errors[:base]).to include(error_message)
-
- # three subjects
- experiment_subject.project = build(:project)
- expect(experiment_subject).not_to be_valid
- expect(experiment_subject.errors[:base]).to include(error_message)
- end
- end
- end
-
- describe '.valid_subject?' do
- subject(:valid_subject?) { described_class.valid_subject?(subject_class.new) }
-
- context 'when passing a Group, Namespace, User or Project' do
- [Group, Namespace, User, Project].each do |subject_class|
- let(:subject_class) { subject_class }
-
- it { is_expected.to be(true) }
- end
- end
-
- context 'when passing another object' do
- let(:subject_class) { Issue }
-
- it { is_expected.to be(false) }
- end
- end
-end
diff --git a/spec/models/integrations/bamboo_spec.rb b/spec/models/integrations/bamboo_spec.rb
index ac8ea52dd3e..1d2c90dad51 100644
--- a/spec/models/integrations/bamboo_spec.rb
+++ b/spec/models/integrations/bamboo_spec.rb
@@ -23,6 +23,8 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching do
)
end
+ it_behaves_like Integrations::BaseCi
+
it_behaves_like Integrations::ResetSecretFields
include_context Integrations::EnableSslVerification
diff --git a/spec/models/integrations/base_chat_notification_spec.rb b/spec/models/integrations/base_chat_notification_spec.rb
index 3875f0edec5..b959ead2cae 100644
--- a/spec/models/integrations/base_chat_notification_spec.rb
+++ b/spec/models/integrations/base_chat_notification_spec.rb
@@ -3,6 +3,10 @@
require 'spec_helper'
RSpec.describe Integrations::BaseChatNotification do
+ describe 'default values' do
+ it { expect(subject.category).to eq(:chat) }
+ end
+
describe 'validations' do
before do
allow(subject).to receive(:activated?).and_return(true)
diff --git a/spec/models/integrations/base_issue_tracker_spec.rb b/spec/models/integrations/base_issue_tracker_spec.rb
index 37f7d99717c..e1a764cd7cb 100644
--- a/spec/models/integrations/base_issue_tracker_spec.rb
+++ b/spec/models/integrations/base_issue_tracker_spec.rb
@@ -7,6 +7,10 @@ RSpec.describe Integrations::BaseIssueTracker do
let_it_be_with_refind(:project) { create :project }
+ describe 'default values' do
+ it { expect(subject.category).to eq(:issue_tracker) }
+ end
+
describe 'Validations' do
describe 'only one issue tracker per project' do
before do
diff --git a/spec/models/integrations/base_third_party_wiki_spec.rb b/spec/models/integrations/base_third_party_wiki_spec.rb
index 11e044c2a18..dbead636cb9 100644
--- a/spec/models/integrations/base_third_party_wiki_spec.rb
+++ b/spec/models/integrations/base_third_party_wiki_spec.rb
@@ -3,6 +3,10 @@
require 'spec_helper'
RSpec.describe Integrations::BaseThirdPartyWiki do
+ describe 'default values' do
+ it { expect(subject.category).to eq(:third_party_wiki) }
+ end
+
describe 'Validations' do
let_it_be_with_reload(:project) { create(:project) }
diff --git a/spec/models/integrations/buildkite_spec.rb b/spec/models/integrations/buildkite_spec.rb
index 5535a13db7f..5f62c68bd2b 100644
--- a/spec/models/integrations/buildkite_spec.rb
+++ b/spec/models/integrations/buildkite_spec.rb
@@ -18,6 +18,8 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do
)
end
+ it_behaves_like Integrations::BaseCi
+
it_behaves_like Integrations::ResetSecretFields
it_behaves_like Integrations::HasWebHook do
diff --git a/spec/models/integrations/drone_ci_spec.rb b/spec/models/integrations/drone_ci_spec.rb
index 59961b37c20..6ff6888e0d3 100644
--- a/spec/models/integrations/drone_ci_spec.rb
+++ b/spec/models/integrations/drone_ci_spec.rb
@@ -9,6 +9,8 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do
let_it_be(:project) { create(:project, :repository, name: 'project') }
+ it_behaves_like Integrations::BaseCi
+
it_behaves_like Integrations::ResetSecretFields do
let(:integration) { subject }
end
diff --git a/spec/models/integrations/jenkins_spec.rb b/spec/models/integrations/jenkins_spec.rb
index 4e787f958af..0264982f0dc 100644
--- a/spec/models/integrations/jenkins_spec.rb
+++ b/spec/models/integrations/jenkins_spec.rb
@@ -23,6 +23,8 @@ RSpec.describe Integrations::Jenkins do
}
end
+ it_behaves_like Integrations::BaseCi
+
it_behaves_like Integrations::ResetSecretFields do
let(:integration) { jenkins_integration }
end
diff --git a/spec/models/integrations/mock_ci_spec.rb b/spec/models/integrations/mock_ci_spec.rb
index d29c63b3a97..83954812bfe 100644
--- a/spec/models/integrations/mock_ci_spec.rb
+++ b/spec/models/integrations/mock_ci_spec.rb
@@ -7,6 +7,8 @@ RSpec.describe Integrations::MockCi do
subject(:integration) { described_class.new(project: project, mock_service_url: generate(:url)) }
+ it_behaves_like Integrations::BaseCi
+
include_context Integrations::EnableSslVerification
describe '#commit_status' do
diff --git a/spec/models/integrations/prometheus_spec.rb b/spec/models/integrations/prometheus_spec.rb
index f2b718a98d8..3c3850854b3 100644
--- a/spec/models/integrations/prometheus_spec.rb
+++ b/spec/models/integrations/prometheus_spec.rb
@@ -12,6 +12,8 @@ RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching,
let(:integration) { project.prometheus_integration }
+ it_behaves_like Integrations::BaseMonitoring
+
context 'redirects' do
it 'does not follow redirects' do
redirect_to = 'https://redirected.example.com'
diff --git a/spec/models/integrations/teamcity_spec.rb b/spec/models/integrations/teamcity_spec.rb
index 5160b410514..e32088a2f79 100644
--- a/spec/models/integrations/teamcity_spec.rb
+++ b/spec/models/integrations/teamcity_spec.rb
@@ -22,6 +22,8 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do
)
end
+ it_behaves_like Integrations::BaseCi
+
it_behaves_like Integrations::ResetSecretFields
include_context Integrations::EnableSslVerification do
diff --git a/spec/models/merge_request_diff_file_spec.rb b/spec/models/merge_request_diff_file_spec.rb
index f107a56c1b6..7e127caa649 100644
--- a/spec/models/merge_request_diff_file_spec.rb
+++ b/spec/models/merge_request_diff_file_spec.rb
@@ -203,16 +203,6 @@ RSpec.describe MergeRequestDiffFile do
end
end
- context 'when externally_stored_diffs_caching_export feature flag is disabled' do
- it 'calls #diff' do
- stub_feature_flags(externally_stored_diffs_caching_export: false)
-
- expect(file).to receive(:diff)
-
- file.utf8_diff
- end
- end
-
context 'when diff is not stored externally' do
it 'calls #diff' do
expect(file).to receive(:diff)
diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb
index 5274526e81c..16894bf28f1 100644
--- a/spec/models/network/graph_spec.rb
+++ b/spec/models/network/graph_spec.rb
@@ -6,10 +6,24 @@ RSpec.describe Network::Graph do
let(:project) { create(:project, :repository) }
let!(:note_on_commit) { create(:note_on_commit, project: project) }
- it '#initialize' do
- graph = described_class.new(project, 'refs/heads/master', project.repository.commit, nil)
+ describe '#initialize' do
+ let(:graph) do
+ described_class.new(project, 'refs/heads/master', project.repository.commit, nil)
+ end
+
+ it 'has initialized' do
+ expect(graph).to be_a(described_class)
+ end
- expect(graph.notes).to eq({ note_on_commit.commit_id => 1 })
+ context 'when disable_network_graph_note_counts is disabled' do
+ before do
+ stub_feature_flags(disable_network_graph_notes_count: false)
+ end
+
+ it 'initializes the notes hash' do
+ expect(graph.notes).to eq({ note_on_commit.commit_id => 1 })
+ end
+ end
end
describe '#commits' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 736e70d1efc..184500f3209 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -38,6 +38,7 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to have_many(:hooks) }
it { is_expected.to have_many(:protected_branches) }
it { is_expected.to have_many(:exported_protected_branches) }
+ it { is_expected.to have_one(:wiki_repository).class_name('Projects::WikiRepository').inverse_of(:project) }
it { is_expected.to have_one(:slack_integration) }
it { is_expected.to have_one(:microsoft_teams_integration) }
it { is_expected.to have_one(:mattermost_integration) }
diff --git a/spec/models/projects/wiki_repository_spec.rb b/spec/models/projects/wiki_repository_spec.rb
new file mode 100644
index 00000000000..6868e1f5fb9
--- /dev/null
+++ b/spec/models/projects/wiki_repository_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::WikiRepository do
+ subject { described_class.new(project: build(:project)) }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:project).inverse_of(:wiki_repository) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_uniqueness_of(:project) }
+ end
+end
diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb
index d771c1e2dcc..ac8c4aacdf2 100644
--- a/spec/requests/api/maven_packages_spec.rb
+++ b/spec/requests/api/maven_packages_spec.rb
@@ -720,6 +720,16 @@ RSpec.describe API::MavenPackages do
expect(response).to have_gitlab_http_status(:not_found)
end
+ context 'with access to package registry for everyone' do
+ subject { download_file(file_name: package_file.file_name) }
+
+ before do
+ project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC)
+ end
+
+ it_behaves_like 'successfully returning the file'
+ end
+
it_behaves_like 'downloads with a job token'
it_behaves_like 'downloads with a deploy token'
diff --git a/spec/requests/api/npm_project_packages_spec.rb b/spec/requests/api/npm_project_packages_spec.rb
index bdcd6e7278d..373327787a2 100644
--- a/spec/requests/api/npm_project_packages_spec.rb
+++ b/spec/requests/api/npm_project_packages_spec.rb
@@ -5,16 +5,29 @@ require 'spec_helper'
RSpec.describe API::NpmProjectPackages do
include_context 'npm api setup'
- describe 'GET /api/v4/projects/:id/packages/npm/*package_name' do
- it_behaves_like 'handling get metadata requests', scope: :project do
- let(:url) { api("/projects/#{project.id}/packages/npm/#{package_name}") }
+ shared_examples 'accept get request on private project with access to package registry for everyone' do
+ subject { get(url) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC)
end
+
+ it_behaves_like 'returning response status', :ok
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/npm/*package_name' do
+ let(:url) { api("/projects/#{project.id}/packages/npm/#{package_name}") }
+
+ it_behaves_like 'handling get metadata requests', scope: :project
+ it_behaves_like 'accept get request on private project with access to package registry for everyone'
end
describe 'GET /api/v4/projects/:id/packages/npm/-/package/*package_name/dist-tags' do
- it_behaves_like 'handling get dist tags requests', scope: :project do
- let(:url) { api("/projects/#{project.id}/packages/npm/-/package/#{package_name}/dist-tags") }
- end
+ let(:url) { api("/projects/#{project.id}/packages/npm/-/package/#{package_name}/dist-tags") }
+
+ it_behaves_like 'handling get dist tags requests', scope: :project
+ it_behaves_like 'accept get request on private project with access to package registry for everyone'
end
describe 'PUT /api/v4/projects/:id/packages/npm/-/package/*package_name/dist-tags/:tag' do
@@ -108,6 +121,14 @@ RSpec.describe API::NpmProjectPackages do
expect(response).to have_gitlab_http_status(:forbidden)
end
end
+
+ context 'with access to package registry for everyone' do
+ before do
+ project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC)
+ end
+
+ it_behaves_like 'successfully downloads the file'
+ end
end
context 'internal project' do
diff --git a/spec/requests/groups/observability_controller_spec.rb b/spec/requests/groups/observability_controller_spec.rb
index c6fb66506da..a08231fe939 100644
--- a/spec/requests/groups/observability_controller_spec.rb
+++ b/spec/requests/groups/observability_controller_spec.rb
@@ -9,13 +9,14 @@ RSpec.describe Groups::ObservabilityController do
let_it_be(:user) { create(:user) }
let(:observability_url) { Gitlab::Observability.observability_url }
+ let(:expected_observability_path) { "/" }
- subject do
- get group_observability_index_path(group)
- response
- end
+ shared_examples 'observability route request' do
+ subject do
+ get path
+ response
+ end
- describe 'GET #index' do
context 'when user is not authenticated' do
it 'returns 404' do
expect(subject).to have_gitlab_http_status(:not_found)
@@ -57,98 +58,112 @@ RSpec.describe Groups::ObservabilityController do
expect(subject).to render_template("layouts/fullscreen")
expect(subject).not_to render_template('layouts/nav/breadcrumbs')
expect(subject).to render_template("nav/sidebar/_group")
+ expect(subject).to render_template("groups/observability/observability")
end
- describe 'iframe' do
- subject do
- get group_observability_index_path(group)
- Nokogiri::HTML.parse(response.body).at_css('iframe#observability-ui-iframe')
- end
+ it 'renders the js-observability-app element correctly' do
+ element = Nokogiri::HTML.parse(subject.body).at_css('#js-observability-app')
+ expect(element.attributes['data-observability-iframe-src'].value).to eq(expected_observability_path)
+ end
+ end
+ end
- it 'sets the iframe src to the proper URL' do
- expected_url = "#{observability_url}/-/#{group.id}"
+ describe 'GET #dashboards' do
+ let(:path) { group_observability_dashboards_path(group) }
+ let(:expected_observability_path) { "#{observability_url}/#{group.id}/" }
- expect(subject.attributes['src'].value).to eq(expected_url)
- end
+ it_behaves_like 'observability route request'
+ end
+
+ describe 'GET #manage' do
+ let(:path) { group_observability_manage_path(group) }
+ let(:expected_observability_path) { "#{observability_url}/#{group.id}/dashboards" }
+
+ it_behaves_like 'observability route request'
+ end
+
+ describe 'GET #explore' do
+ let(:path) { group_observability_explore_path(group) }
+ let(:expected_observability_path) { "#{observability_url}/#{group.id}/explore" }
+
+ it_behaves_like 'observability route request'
+ end
+
+ describe 'CSP' do
+ before do
+ setup_csp_for_controller(described_class, csp)
+ end
+
+ subject do
+ get group_observability_dashboards_path(group)
+ response.headers['Content-Security-Policy']
+ end
+
+ context 'when there is no CSP config' do
+ let(:csp) { ActionDispatch::ContentSecurityPolicy.new }
+
+ it 'does not add any csp header' do
+ expect(subject).to be_blank
end
+ end
- describe 'CSP' do
- before do
- setup_csp_for_controller(described_class, csp)
+ context 'when frame-src exists in the CSP config' do
+ let(:csp) do
+ ActionDispatch::ContentSecurityPolicy.new do |p|
+ p.frame_src 'https://something.test'
end
+ end
- subject do
- get group_observability_index_path(group)
- response.headers['Content-Security-Policy']
+ it 'appends the proper url to frame-src CSP directives' do
+ expect(subject).to include(
+ "frame-src https://something.test #{observability_url} 'self'")
+ end
+ end
+
+ context 'when self is already present in the policy' do
+ let(:csp) do
+ ActionDispatch::ContentSecurityPolicy.new do |p|
+ p.frame_src "'self'"
end
+ end
- context 'when there is no CSP config' do
- let(:csp) { ActionDispatch::ContentSecurityPolicy.new }
+ it 'does not append self again' do
+ expect(subject).to include(
+ "frame-src 'self' #{observability_url};")
+ end
+ end
- it 'does not add any csp header' do
- expect(subject).to be_blank
- end
+ context 'when default-src exists in the CSP config' do
+ let(:csp) do
+ ActionDispatch::ContentSecurityPolicy.new do |p|
+ p.default_src 'https://something.test'
end
+ end
- context 'when frame-src exists in the CSP config' do
- let(:csp) do
- ActionDispatch::ContentSecurityPolicy.new do |p|
- p.frame_src 'https://something.test'
- end
- end
-
- it 'appends the proper url to frame-src CSP directives' do
- expect(subject).to include(
- "frame-src https://something.test #{observability_url} 'self'")
- end
- end
+ it 'does not change default-src' do
+ expect(subject).to include(
+ "default-src https://something.test;")
+ end
- context 'when self is already present in the policy' do
- let(:csp) do
- ActionDispatch::ContentSecurityPolicy.new do |p|
- p.frame_src "'self'"
- end
- end
-
- it 'does not append self again' do
- expect(subject).to include(
- "frame-src 'self' #{observability_url};")
- end
- end
+ it 'appends the proper url to frame-src CSP directives' do
+ expect(subject).to include(
+ "frame-src https://something.test #{observability_url} 'self'")
+ end
+ end
- context 'when default-src exists in the CSP config' do
- let(:csp) do
- ActionDispatch::ContentSecurityPolicy.new do |p|
- p.default_src 'https://something.test'
- end
- end
-
- it 'does not change default-src' do
- expect(subject).to include(
- "default-src https://something.test;")
- end
-
- it 'appends the proper url to frame-src CSP directives' do
- expect(subject).to include(
- "frame-src https://something.test #{observability_url} 'self'")
- end
+ context 'when frame-src and default-src exist in the CSP config' do
+ let(:csp) do
+ ActionDispatch::ContentSecurityPolicy.new do |p|
+ p.default_src 'https://something_default.test'
+ p.frame_src 'https://something.test'
end
+ end
- context 'when frame-src and default-src exist in the CSP config' do
- let(:csp) do
- ActionDispatch::ContentSecurityPolicy.new do |p|
- p.default_src 'https://something_default.test'
- p.frame_src 'https://something.test'
- end
- end
-
- it 'appends to frame-src CSP directives' do
- expect(subject).to include(
- "frame-src https://something.test #{observability_url} 'self'")
- expect(subject).to include(
- "default-src https://something_default.test")
- end
- end
+ it 'appends to frame-src CSP directives' do
+ expect(subject).to include(
+ "frame-src https://something.test #{observability_url} 'self'")
+ expect(subject).to include(
+ "default-src https://something_default.test")
end
end
end
diff --git a/spec/routing/group_routing_spec.rb b/spec/routing/group_routing_spec.rb
index ae69b222280..68e619e5246 100644
--- a/spec/routing/group_routing_spec.rb
+++ b/spec/routing/group_routing_spec.rb
@@ -72,8 +72,16 @@ RSpec.shared_examples 'groups routing' do
expect(get("groups/#{group_path}/-/harbor/repositories/test/artifacts/test/tags")).to route_to('groups/harbor/tags#index', group_id: group_path, repository_id: 'test', artifact_id: 'test')
end
- it 'routes to the observability controller' do
- expect(get("groups/#{group_path}/-/observability")).to route_to('groups/observability#index', group_id: group_path)
+ it 'routes to the observability controller dashboards method' do
+ expect(get("groups/#{group_path}/-/observability/dashboards")).to route_to('groups/observability#dashboards', group_id: group_path)
+ end
+
+ it 'routes to the observability controller explore method' do
+ expect(get("groups/#{group_path}/-/observability/explore")).to route_to('groups/observability#explore', group_id: group_path)
+ end
+
+ it 'routes to the observability controller manage method' do
+ expect(get("groups/#{group_path}/-/observability/manage")).to route_to('groups/observability#manage', group_id: group_path)
end
end
diff --git a/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb b/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb
index e9c1fe23855..702c6d9fe98 100644
--- a/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb
@@ -184,22 +184,6 @@ RSpec.describe MergeRequestPollCachedWidgetEntity do
end
end
- describe 'auto merge' do
- context 'when auto merge is enabled' do
- let(:resource) { create(:merge_request, :merge_when_pipeline_succeeds) }
-
- it 'returns auto merge related information' do
- expect(subject[:auto_merge_enabled]).to be_truthy
- end
- end
-
- context 'when auto merge is not enabled' do
- it 'returns auto merge related information' do
- expect(subject[:auto_merge_enabled]).to be_falsy
- end
- end
- end
-
describe 'squash defaults for projects' do
where(:squash_option, :value, :default, :readonly) do
'always' | true | true | true
diff --git a/spec/serializers/merge_request_poll_widget_entity_spec.rb b/spec/serializers/merge_request_poll_widget_entity_spec.rb
index 59ffba0e7a9..418f629a301 100644
--- a/spec/serializers/merge_request_poll_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_poll_widget_entity_spec.rb
@@ -184,10 +184,4 @@ RSpec.describe MergeRequestPollWidgetEntity do
end
end
end
-
- describe '#mergeable_discussions_state?' do
- it 'returns mergeable discussions state' do
- expect(subject[:mergeable_discussions_state]).to eq(true)
- end
- end
end
diff --git a/spec/services/issuable/discussions_list_service_spec.rb b/spec/services/issuable/discussions_list_service_spec.rb
new file mode 100644
index 00000000000..2ce47f42a72
--- /dev/null
+++ b/spec/services/issuable/discussions_list_service_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Issuable::DiscussionsListService do
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:group) { create(:group, :private) }
+ let_it_be(:project) { create(:project, :repository, :private, group: group) }
+ let_it_be(:milestone) { create(:milestone, project: project) }
+ let_it_be(:label) { create(:label, project: project) }
+
+ let(:finder_params_for_issuable) { {} }
+
+ subject(:discussions_service) { described_class.new(current_user, issuable, finder_params_for_issuable) }
+
+ describe 'fetching notes for issue' do
+ let_it_be(:issuable) { create(:issue, project: project) }
+
+ it_behaves_like 'listing issuable discussions', :guest, 1, 7
+ end
+
+ describe 'fetching notes for merge requests' do
+ let_it_be(:issuable) { create(:merge_request, source_project: project, target_project: project) }
+
+ it_behaves_like 'listing issuable discussions', :reporter, 0, 6
+ end
+end
diff --git a/spec/services/issues/relative_position_rebalancing_service_spec.rb b/spec/services/issues/relative_position_rebalancing_service_spec.rb
index 43c94ef07aa..27c0394ac8b 100644
--- a/spec/services/issues/relative_position_rebalancing_service_spec.rb
+++ b/spec/services/issues/relative_position_rebalancing_service_spec.rb
@@ -93,8 +93,12 @@ RSpec.describe Issues::RelativePositionRebalancingService, :clean_gitlab_redis_s
it 'resumes a started rebalance even if there are already too many rebalances running' do
Gitlab::Redis::SharedState.with do |redis|
- redis.sadd("gitlab:issues-position-rebalances:running_rebalances", "#{::Gitlab::Issues::Rebalancing::State::PROJECT}/#{project.id}")
- redis.sadd("gitlab:issues-position-rebalances:running_rebalances", "1/100")
+ redis.sadd("gitlab:issues-position-rebalances:running_rebalances",
+ [
+ "#{::Gitlab::Issues::Rebalancing::State::PROJECT}/#{project.id}",
+ "1/100"
+ ]
+ )
end
caching = service.send(:caching)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 9d169c50013..8e73073e68b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -255,10 +255,6 @@ RSpec.configure do |config|
# The survey popover can block the diffs causing specs to fail
stub_feature_flags(mr_experience_survey: false)
- # Merge request widget GraphQL requests are disabled in the tests
- # for now whilst we migrate as much as we can over the GraphQL
- # stub_feature_flags(merge_request_widget_graphql: false)
-
# Using FortiAuthenticator as OTP provider is disabled by default in
# tests, until we introduce it in user settings
stub_feature_flags(forti_authenticator: false)
diff --git a/spec/support/helpers/navbar_structure_helper.rb b/spec/support/helpers/navbar_structure_helper.rb
index b44552d6479..e1ed3ffacec 100644
--- a/spec/support/helpers/navbar_structure_helper.rb
+++ b/spec/support/helpers/navbar_structure_helper.rb
@@ -90,7 +90,11 @@ module NavbarStructureHelper
_('Kubernetes'),
new_nav_item: {
nav_item: _('Observability'),
- nav_sub_items: []
+ nav_sub_items: [
+ _('Dashboards'),
+ _('Explore'),
+ _('Manage Dashboards')
+ ]
}
)
end
diff --git a/spec/support/shared_examples/models/integrations/base_ci_shared_examples.rb b/spec/support/shared_examples/models/integrations/base_ci_shared_examples.rb
new file mode 100644
index 00000000000..08fab45e41b
--- /dev/null
+++ b/spec/support/shared_examples/models/integrations/base_ci_shared_examples.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Integrations::BaseCi do
+ describe 'default values' do
+ it { expect(subject.category).to eq(:ci) }
+ end
+end
diff --git a/spec/support/shared_examples/models/integrations/base_monitoring_shared_examples.rb b/spec/support/shared_examples/models/integrations/base_monitoring_shared_examples.rb
new file mode 100644
index 00000000000..5d7e7633a23
--- /dev/null
+++ b/spec/support/shared_examples/models/integrations/base_monitoring_shared_examples.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Integrations::BaseMonitoring do
+ describe 'default values' do
+ it { expect(subject.category).to eq(:monitoring) }
+ end
+end
diff --git a/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb
index e35ac9c0d0d..7dfdd24177e 100644
--- a/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb
+++ b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb
@@ -6,6 +6,10 @@ RSpec.shared_examples Integrations::BaseSlashCommands do
it { is_expected.to have_many :chat_names }
end
+ describe 'default values' do
+ it { expect(subject.category).to eq(:chat) }
+ end
+
describe '#valid_token?' do
subject { described_class.new }
diff --git a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
index 32562aef8d2..f577e2ad323 100644
--- a/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/discussions_shared_examples.rb
@@ -15,7 +15,7 @@ RSpec.shared_examples 'with cross-reference system notes' do
new_merge_request.project.add_developer(user)
hidden_merge_request = create(:merge_request)
- new_cross_reference = "test commit #{hidden_merge_request.project.commit}"
+ new_cross_reference = "test commit #{hidden_merge_request.project.commit.to_reference(project)}"
new_note = create(:system_note, noteable: merge_request, project: project, note: new_cross_reference)
create(:system_note_metadata, note: new_note, action: 'cross_reference')
end
diff --git a/spec/support/shared_examples/services/issuable/discussions_list_shared_examples.rb b/spec/support/shared_examples/services/issuable/discussions_list_shared_examples.rb
new file mode 100644
index 00000000000..c38ca6a3bf0
--- /dev/null
+++ b/spec/support/shared_examples/services/issuable/discussions_list_shared_examples.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'listing issuable discussions' do |user_role, internal_discussion_count, total_discussions_count|
+ before_all do
+ create_notes(issuable, "some user comment")
+ end
+
+ context 'when user cannot read issue' do
+ it "returns no notes" do
+ expect(discussions_service.execute).to be_empty
+ end
+ end
+
+ context 'when user can read issuable' do
+ before do
+ group.add_developer(current_user)
+ end
+
+ context 'with paginated results' do
+ let(:finder_params_for_issuable) { { per_page: 2 } }
+ let(:next_page_cursor) { { cursor: discussions_service.paginator.cursor_for_next_page } }
+
+ it "returns next page notes" do
+ next_page_discussions_service = described_class.new(current_user, issuable,
+ finder_params_for_issuable.merge(next_page_cursor))
+ discussions = next_page_discussions_service.execute
+
+ expect(discussions.count).to eq(2)
+ expect(discussions.first.notes.map(&:note)).to match_array(["added #{label.to_reference} label"])
+ expect(discussions.second.notes.map(&:note)).to match_array(["removed #{label.to_reference} label"])
+ end
+ end
+
+ # confidential notes are currently available only on issues and epics
+ context 'and cannot read confidential notes' do
+ before do
+ group.add_member(current_user, user_role)
+ end
+
+ it "returns non confidential notes" do
+ discussions = discussions_service.execute
+
+ non_conf_discussion_count = total_discussions_count - internal_discussion_count
+ expect(discussions.count).to eq(non_conf_discussion_count)
+ expect(discussions.count { |disc| disc.notes.any?(&:confidential) }).to eq(0)
+ expect(discussions.count { |disc| !disc.notes.any?(&:confidential) }).to eq(non_conf_discussion_count)
+ end
+ end
+
+ # confidential notes are currently available only on issues and epics
+ context 'and can read confidential notes' do
+ it "returns all notes" do
+ discussions = discussions_service.execute
+
+ expect(discussions.count).to eq(total_discussions_count)
+ expect(discussions.count { |disc| disc.notes.any?(&:confidential) }).to eq(internal_discussion_count)
+ non_conf_discussion_count = total_discussions_count - internal_discussion_count
+ expect(discussions.count { |disc| !disc.notes.any?(&:confidential) }).to eq(non_conf_discussion_count)
+ end
+ end
+
+ context 'and system notes only' do
+ let(:finder_params_for_issuable) { { notes_filter: UserPreference::NOTES_FILTERS[:only_activity] } }
+
+ it "returns system notes" do
+ discussions = discussions_service.execute
+
+ expect(discussions.count { |disc| disc.notes.any?(&:system) }).to be > 0
+ expect(discussions.count { |disc| !disc.notes.any?(&:system) }).to eq(0)
+ end
+ end
+
+ context 'and user comments only' do
+ let(:finder_params_for_issuable) { { notes_filter: UserPreference::NOTES_FILTERS[:only_comments] } }
+
+ it "returns user comments" do
+ discussions = discussions_service.execute
+
+ expect(discussions.count { |disc| disc.notes.any?(&:system) }).to eq(0)
+ expect(discussions.count { |disc| !disc.notes.any?(&:system) }).to be > 0
+ end
+ end
+ end
+end
+
+def create_notes(issuable, note_body)
+ assoc_name = issuable.to_ability_name
+
+ create(:note, system: true, project: issuable.project, noteable: issuable)
+
+ first_discussion = create(:discussion_note_on_issue, noteable: issuable, project: issuable.project, note: note_body)
+ create(:note,
+ discussion_id: first_discussion.discussion_id, noteable: issuable,
+ project: issuable.project, note: "reply on #{note_body}")
+
+ create(:resource_label_event, user: current_user, "#{assoc_name}": issuable, label: label, action: 'add')
+ create(:resource_label_event, user: current_user, "#{assoc_name}": issuable, label: label, action: 'remove')
+
+ unless issuable.is_a?(Epic)
+ create(:resource_milestone_event, "#{assoc_name}": issuable, milestone: milestone, action: 'add')
+ create(:resource_milestone_event, "#{assoc_name}": issuable, milestone: milestone, action: 'remove')
+ end
+
+ # confidential notes are currently available only on issues and epics
+ return unless issuable.is_a?(Issue) || issuable.is_a?(Epic)
+
+ first_internal_discussion = create(:discussion_note_on_issue, :confidential,
+ noteable: issuable, project: issuable.project, note: "confidential #{note_body}")
+ create(:note, :confidential,
+ discussion_id: first_internal_discussion.discussion_id, noteable: issuable,
+ project: issuable.project, note: "reply on confidential #{note_body}")
+end
diff --git a/spec/views/groups/observability.html.haml_spec.rb b/spec/views/groups/observability.html.haml_spec.rb
deleted file mode 100644
index db280d5a2ba..00000000000
--- a/spec/views/groups/observability.html.haml_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'groups/observability/index' do
- let_it_be(:iframe_url) { "foo.test" }
-
- before do
- assign(:observability_iframe_src, iframe_url)
- end
-
- it 'renders as expected' do
- render
- page = Capybara.string(rendered)
- iframe = page.find('iframe#observability-ui-iframe')
- expect(iframe['src']).to eq(iframe_url)
- end
-end
diff --git a/spec/views/groups/observability/observability.html.haml_spec.rb b/spec/views/groups/observability/observability.html.haml_spec.rb
new file mode 100644
index 00000000000..0561737cb39
--- /dev/null
+++ b/spec/views/groups/observability/observability.html.haml_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'groups/observability/observability.html.haml' do
+ let(:iframe_url) { "foo.test" }
+
+ before do
+ allow(view).to receive(:observability_iframe_src).and_return(iframe_url)
+ end
+
+ it 'renders as expected' do
+ render
+ page = Capybara.string(rendered)
+ div = page.find('#js-observability-app')
+ expect(div['data-observability-iframe-src']).to eq(iframe_url)
+ end
+end
diff --git a/spec/workers/merge_requests/delete_branch_worker_spec.rb b/spec/workers/merge_requests/delete_branch_worker_spec.rb
index f97e7d3b0bf..80ca8c061f5 100644
--- a/spec/workers/merge_requests/delete_branch_worker_spec.rb
+++ b/spec/workers/merge_requests/delete_branch_worker_spec.rb
@@ -55,36 +55,6 @@ RSpec.describe MergeRequests::DeleteBranchWorker do
worker.perform(merge_request.id, user.id, branch, retarget_branch)
end
end
-
- context 'when delete service returns an error' do
- let(:service_result) { ServiceResponse.error(message: 'placeholder') }
-
- it 'tracks the exception' do
- expect_next_instance_of(::Branches::DeleteService) do |instance|
- expect(instance).to receive(:execute).with(merge_request.source_branch).and_return(service_result)
- end
-
- expect(service_result).to receive(:track_exception).and_call_original
-
- worker.perform(merge_request.id, user.id, branch, retarget_branch)
- end
-
- context 'when track_delete_source_errors is disabled' do
- before do
- stub_feature_flags(track_delete_source_errors: false)
- end
-
- it 'does not track the exception' do
- expect_next_instance_of(::Branches::DeleteService) do |instance|
- expect(instance).to receive(:execute).with(merge_request.source_branch).and_return(service_result)
- end
-
- expect(service_result).not_to receive(:track_exception)
-
- worker.perform(merge_request.id, user.id, branch, retarget_branch)
- end
- end
- end
end
it_behaves_like 'an idempotent worker' do
diff --git a/spec/workers/merge_requests/delete_source_branch_worker_spec.rb b/spec/workers/merge_requests/delete_source_branch_worker_spec.rb
index 8dd302d81cf..2935d3ef5dc 100644
--- a/spec/workers/merge_requests/delete_source_branch_worker_spec.rb
+++ b/spec/workers/merge_requests/delete_source_branch_worker_spec.rb
@@ -102,32 +102,6 @@ RSpec.describe MergeRequests::DeleteSourceBranchWorker do
context 'when delete service returns an error' do
let(:service_result) { ServiceResponse.error(message: 'placeholder') }
- it 'tracks the exception' do
- expect_next_instance_of(::Branches::DeleteService) do |instance|
- expect(instance).to receive(:execute).with(merge_request.source_branch).and_return(service_result)
- end
-
- expect(service_result).to receive(:track_exception).and_call_original
-
- worker.perform(merge_request.id, sha, user.id)
- end
-
- context 'when track_delete_source_errors is disabled' do
- before do
- stub_feature_flags(track_delete_source_errors: false)
- end
-
- it 'does not track the exception' do
- expect_next_instance_of(::Branches::DeleteService) do |instance|
- expect(instance).to receive(:execute).with(merge_request.source_branch).and_return(service_result)
- end
-
- expect(service_result).not_to receive(:track_exception)
-
- worker.perform(merge_request.id, sha, user.id)
- end
- end
-
it 'still retargets the merge request' do
expect_next_instance_of(::Branches::DeleteService) do |instance|
expect(instance).to receive(:execute).with(merge_request.source_branch).and_return(service_result)