diff options
author | Shinya Maeda <shinya@gitlab.com> | 2018-05-28 20:01:56 +0900 |
---|---|---|
committer | Shinya Maeda <shinya@gitlab.com> | 2018-05-28 20:01:56 +0900 |
commit | 1d20679e9c8b1ba16bebaf982255946e7207b4d4 (patch) | |
tree | 128685af66ca56c751b505a8314ca79dd3b54b34 /spec | |
parent | 8e92e25b62ca108de775362e6d2981e54535f094 (diff) | |
parent | 014f5f6a69f63ee42bd94454108268f189b62b18 (diff) | |
download | gitlab-ce-1d20679e9c8b1ba16bebaf982255946e7207b4d4.tar.gz |
Merge branch 'master' into per-project-pipeline-iid
Diffstat (limited to 'spec')
115 files changed, 2220 insertions, 595 deletions
diff --git a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb index 2d7647a6e12..397cc79bde4 100644 --- a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb @@ -5,7 +5,7 @@ describe Projects::MergeRequests::ConflictsController do let(:user) { project.owner } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } let(:merge_request_with_conflicts) do - create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) do |mr| + create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project, merge_status: :unchecked) do |mr| mr.mark_as_unmergeable end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index c8cc6b374f6..d3042be9e8b 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -7,7 +7,7 @@ describe Projects::MergeRequestsController do let(:user) { project.owner } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } let(:merge_request_with_conflicts) do - create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) do |mr| + create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project, merge_status: :unchecked) do |mr| mr.mark_as_unmergeable end end diff --git a/spec/controllers/projects/prometheus/metrics_controller_spec.rb b/spec/controllers/projects/prometheus/metrics_controller_spec.rb index b2b245dba90..871dcf5c796 100644 --- a/spec/controllers/projects/prometheus/metrics_controller_spec.rb +++ b/spec/controllers/projects/prometheus/metrics_controller_spec.rb @@ -12,44 +12,67 @@ describe Projects::Prometheus::MetricsController do end describe 'GET #active_common' do - before do - allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter) - end + context 'when prometheus_adapter can query' do + before do + allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter) + end - context 'when prometheus metrics are enabled' do - context 'when data is not present' do - before do - allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return({}) - end + context 'when prometheus metrics are enabled' do + context 'when data is not present' do + before do + allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return({}) + end - it 'returns no content response' do - get :active_common, project_params(format: :json) + it 'returns no content response' do + get :active_common, project_params(format: :json) - expect(response).to have_gitlab_http_status(204) + expect(response).to have_gitlab_http_status(204) + end end - end - context 'when data is available' do - let(:sample_response) { { some_data: 1 } } + context 'when data is available' do + let(:sample_response) { { some_data: 1 } } + + before do + allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return(sample_response) + end - before do - allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return(sample_response) + it 'returns no content response' do + get :active_common, project_params(format: :json) + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to eq(sample_response.deep_stringify_keys) + end end - it 'returns no content response' do - get :active_common, project_params(format: :json) + context 'when requesting non json response' do + it 'returns not found response' do + get :active_common, project_params - expect(response).to have_gitlab_http_status(200) - expect(json_response).to eq(sample_response.deep_stringify_keys) + expect(response).to have_gitlab_http_status(404) + end end end + end - context 'when requesting non json response' do - it 'returns not found response' do - get :active_common, project_params + context 'when prometheus_adapter cannot query' do + it 'renders 404' do + prometheus_adapter = double('prometheus_adapter', can_query?: false) - expect(response).to have_gitlab_http_status(404) - end + allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter) + allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return({}) + + get :active_common, project_params(format: :json) + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'when prometheus_adapter is disabled' do + it 'renders 404' do + get :active_common, project_params(format: :json) + + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb index 766cd4b0090..d8fcdebfc6d 100644 --- a/spec/features/admin/admin_abuse_reports_spec.rb +++ b/spec/features/admin/admin_abuse_reports_spec.rb @@ -45,7 +45,7 @@ describe "Admin::AbuseReports", :js do visit admin_abuse_reports_path expect(page).to have_selector('.pagination') - expect(page).to have_selector('.pagination .page', count: (report_count.to_f / AbuseReport.default_per_page).ceil) + expect(page).to have_selector('.pagination .js-pagination-page', count: (report_count.to_f / AbuseReport.default_per_page).ceil) end end end diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index 6d8350e99f1..328e8f25f89 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -34,7 +34,7 @@ describe "Admin::Projects" do expect(page).to have_content(project.name) expect(page).to have_content(archived_project.name) - expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived') + expect(page).to have_xpath("//span[@class='badge badge-warning']", text: 'archived') end it 'renders only archived projects', :js do diff --git a/spec/features/admin/admin_requests_profiles_spec.rb b/spec/features/admin/admin_requests_profiles_spec.rb index 380cd5d7703..2503fc9067d 100644 --- a/spec/features/admin/admin_requests_profiles_spec.rb +++ b/spec/features/admin/admin_requests_profiles_spec.rb @@ -33,12 +33,12 @@ describe 'Admin::RequestsProfilesController' do visit admin_requests_profiles_path - within('.panel', text: '/gitlab-org/gitlab-ce') do + within('.card', text: '/gitlab-org/gitlab-ce') do expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile1)}']", text: time1.to_s(:long)) expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile2)}']", text: time2.to_s(:long)) end - within('.panel', text: '/gitlab-com/infrastructure') do + within('.card', text: '/gitlab-com/infrastructure') do expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile3)}']", text: time3.to_s(:long)) end end diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index 3465ccfc423..c33014cbb31 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -68,7 +68,7 @@ describe "Admin Runners" do visit admin_runners_path within "#runner_#{runner.id}" do - expect(page).to have_selector '.label', text: 'group' + expect(page).to have_selector '.badge', text: 'group' expect(page).to have_text 'n/a' end end @@ -81,7 +81,7 @@ describe "Admin Runners" do visit admin_runners_path within "#runner_#{runner.id}" do - expect(page).to have_selector '.label', text: 'shared' + expect(page).to have_selector '.badge', text: 'shared' expect(page).to have_text 'n/a' end end @@ -95,7 +95,7 @@ describe "Admin Runners" do visit admin_runners_path within "#runner_#{runner.id}" do - expect(page).to have_selector '.label', text: 'specific' + expect(page).to have_selector '.badge', text: 'specific' expect(page).to have_text '1' end end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index f2f9b734c39..dc025d82937 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -152,7 +152,7 @@ feature 'Admin updates settings' do scenario 'Change CI/CD settings' do page.within('.as-ci-cd') do - check 'Enabled Auto DevOps (Beta) for projects by default' + check 'Enabled Auto DevOps for projects by default' fill_in 'Auto devops domain', with: 'domain.com' click_button 'Save changes' end diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 8fc57f4b4c3..9e3221577c7 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -283,14 +283,14 @@ describe "Admin::Users" do end it "lists group projects" do - within(:css, '.append-bottom-default + .panel') do + within(:css, '.append-bottom-default + .card') do expect(page).to have_content 'Group projects' expect(page).to have_link group.name, href: admin_group_path(group) end end it 'allows navigation to the group details' do - within(:css, '.append-bottom-default + .panel') do + within(:css, '.append-bottom-default + .card') do click_link group.name end within(:css, 'h3.page-title') do @@ -300,7 +300,7 @@ describe "Admin::Users" do end it 'shows the group access level' do - within(:css, '.append-bottom-default + .panel') do + within(:css, '.append-bottom-default + .card') do expect(page).to have_content 'Developer' end end diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb index 18901a954cc..7a14a441088 100644 --- a/spec/features/boards/add_issues_modal_spec.rb +++ b/spec/features/boards/add_issues_modal_spec.rb @@ -81,7 +81,7 @@ describe 'Issue Boards add issue modal', :js do expect(page).to have_content('2') end - expect(page).to have_selector('.card', count: 2) + expect(page).to have_selector('.board-card', count: 2) end end @@ -89,7 +89,7 @@ describe 'Issue Boards add issue modal', :js do page.within('.add-issues-modal') do click_link 'Selected issues' - expect(page).not_to have_selector('.card') + expect(page).not_to have_selector('.board-card') end end @@ -122,7 +122,7 @@ describe 'Issue Boards add issue modal', :js do wait_for_requests - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end @@ -133,7 +133,7 @@ describe 'Issue Boards add issue modal', :js do wait_for_requests - expect(page).not_to have_selector('.card') + expect(page).not_to have_selector('.board-card') expect(page).not_to have_content("You haven't added any issues to your project yet") end end @@ -142,7 +142,7 @@ describe 'Issue Boards add issue modal', :js do context 'selecing issues' do it 'selects single issue' do page.within('.add-issues-modal') do - first('.card .card-number').click + first('.board-card .board-card-number').click page.within('.nav-links') do expect(page).to have_content('Selected issues 1') @@ -152,7 +152,7 @@ describe 'Issue Boards add issue modal', :js do it 'changes button text' do page.within('.add-issues-modal') do - first('.card .card-number').click + first('.board-card .board-card-number').click expect(first('.add-issues-footer .btn')).to have_content('Add 1 issue') end @@ -160,7 +160,7 @@ describe 'Issue Boards add issue modal', :js do it 'changes button text with plural' do page.within('.add-issues-modal') do - all('.card .card-number').each do |el| + all('.board-card .board-card-number').each do |el| el.click end @@ -170,11 +170,11 @@ describe 'Issue Boards add issue modal', :js do it 'shows only selected issues on selected tab' do page.within('.add-issues-modal') do - first('.card .card-number').click + first('.board-card .board-card-number').click click_link 'Selected issues' - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end @@ -200,7 +200,7 @@ describe 'Issue Boards add issue modal', :js do it 'selects all that arent already selected' do page.within('.add-issues-modal') do - first('.card .card-number').click + first('.board-card .board-card-number').click expect(page).to have_selector('.is-active', count: 1) @@ -212,11 +212,11 @@ describe 'Issue Boards add issue modal', :js do it 'unselects from selected tab' do page.within('.add-issues-modal') do - first('.card .card-number').click + first('.board-card .board-card-number').click click_link 'Selected issues' - first('.card .card-number').click + first('.board-card .board-card-number').click expect(page).not_to have_selector('.is-active') end @@ -226,19 +226,19 @@ describe 'Issue Boards add issue modal', :js do context 'adding issues' do it 'adds to board' do page.within('.add-issues-modal') do - first('.card .card-number').click + first('.board-card .board-card-number').click click_button 'Add 1 issue' end page.within(find('.board:nth-child(2)')) do - expect(page).to have_selector('.card') + expect(page).to have_selector('.board-card') end end it 'adds to second list' do page.within('.add-issues-modal') do - first('.card .card-number').click + first('.board-card .board-card-number').click click_button planning.title @@ -248,7 +248,7 @@ describe 'Issue Boards add issue modal', :js do end page.within(find('.board:nth-child(3)')) do - expect(page).to have_selector('.card') + expect(page).to have_selector('.board-card') end end end diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 52ff47d57f9..e414345ac23 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -92,9 +92,9 @@ describe 'Issue Boards', :js do wait_for_requests expect(page).to have_selector('.board', count: 4) - expect(find('.board:nth-child(2)')).to have_selector('.card') - expect(find('.board:nth-child(3)')).to have_selector('.card') - expect(find('.board:nth-child(4)')).to have_selector('.card') + expect(find('.board:nth-child(2)')).to have_selector('.board-card') + expect(find('.board:nth-child(3)')).to have_selector('.board-card') + expect(find('.board:nth-child(4)')).to have_selector('.board-card') end it 'shows description tooltip on list title' do @@ -120,9 +120,9 @@ describe 'Issue Boards', :js do wait_for_requests - expect(find('.board:nth-child(2)')).to have_selector('.card', count: 0) - expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0) - expect(find('.board:nth-child(4)')).to have_selector('.card', count: 1) + expect(find('.board:nth-child(2)')).to have_selector('.board-card', count: 0) + expect(find('.board:nth-child(3)')).to have_selector('.board-card', count: 0) + expect(find('.board:nth-child(4)')).to have_selector('.board-card', count: 1) end it 'search list' do @@ -131,9 +131,9 @@ describe 'Issue Boards', :js do wait_for_requests - expect(find('.board:nth-child(2)')).to have_selector('.card', count: 1) - expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0) - expect(find('.board:nth-child(4)')).to have_selector('.card', count: 0) + expect(find('.board:nth-child(2)')).to have_selector('.board-card', count: 1) + expect(find('.board:nth-child(3)')).to have_selector('.board-card', count: 0) + expect(find('.board:nth-child(4)')).to have_selector('.board-card', count: 0) end it 'allows user to delete board' do @@ -171,21 +171,21 @@ describe 'Issue Boards', :js do page.within(find('.board:nth-child(2)')) do expect(page.find('.board-header')).to have_content('58') - expect(page).to have_selector('.card', count: 20) + expect(page).to have_selector('.board-card', count: 20) expect(page).to have_content('Showing 20 of 58 issues') find('.board .board-list') evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") wait_for_requests - expect(page).to have_selector('.card', count: 40) + expect(page).to have_selector('.board-card', count: 40) expect(page).to have_content('Showing 40 of 58 issues') find('.board .board-list') evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") wait_for_requests - expect(page).to have_selector('.card', count: 58) + expect(page).to have_selector('.board-card', count: 58) expect(page).to have_content('Showing all issues') end end @@ -204,7 +204,7 @@ describe 'Issue Boards', :js do wait_for_board_cards(4, 2) expect(find('.board:nth-child(2)')).not_to have_content(issue9.title) - expect(find('.board:nth-child(4)')).to have_selector('.card', count: 2) + expect(find('.board:nth-child(4)')).to have_selector('.board-card', count: 2) expect(find('.board:nth-child(4)')).to have_content(issue9.title) expect(find('.board:nth-child(4)')).not_to have_content(planning.title) end @@ -242,7 +242,7 @@ describe 'Issue Boards', :js do wait_for_board_cards(4, 1) expect(find('.board:nth-child(3)')).to have_content(issue6.title) - expect(find('.board:nth-child(3)').all('.card').last).to have_content(development.title) + expect(find('.board:nth-child(3)').all('.board-card').last).to have_content(development.title) end it 'issue moves between lists' do @@ -253,7 +253,7 @@ describe 'Issue Boards', :js do wait_for_board_cards(4, 1) expect(find('.board:nth-child(2)')).to have_content(issue7.title) - expect(find('.board:nth-child(2)').all('.card').first).to have_content(planning.title) + expect(find('.board:nth-child(2)').all('.board-card').first).to have_content(planning.title) end it 'issue moves from closed' do @@ -335,7 +335,7 @@ describe 'Issue Boards', :js do wait_for_requests - expect(page).to have_css('#js-add-list.open') + expect(page).to have_css('#js-add-list.show') end it 'creates new list from a new label' do @@ -425,12 +425,12 @@ describe 'Issue Boards', :js do page.within(find('.board:nth-child(2)')) do expect(page.find('.board-header')).to have_content('1') - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end page.within(find('.board:nth-child(3)')) do expect(page.find('.board-header')).to have_content('0') - expect(page).to have_selector('.card', count: 0) + expect(page).to have_selector('.board-card', count: 0) end end @@ -460,19 +460,19 @@ describe 'Issue Boards', :js do page.within(find('.board:nth-child(2)')) do expect(page.find('.board-header')).to have_content('51') - expect(page).to have_selector('.card', count: 20) + expect(page).to have_selector('.board-card', count: 20) expect(page).to have_content('Showing 20 of 51 issues') find('.board .board-list') evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") - expect(page).to have_selector('.card', count: 40) + expect(page).to have_selector('.board-card', count: 40) expect(page).to have_content('Showing 40 of 51 issues') find('.board .board-list') evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") - expect(page).to have_selector('.card', count: 51) + expect(page).to have_selector('.board-card', count: 51) expect(page).to have_content('Showing all issues') end end @@ -494,8 +494,8 @@ describe 'Issue Boards', :js do it 'filters by clicking label button on issue' do page.within(find('.board:nth-child(2)')) do - expect(page).to have_selector('.card', count: 8) - expect(find('.card', match: :first)).to have_content(bug.title) + expect(page).to have_selector('.board-card', count: 8) + expect(find('.board-card', match: :first)).to have_content(bug.title) click_button(bug.title) wait_for_requests end @@ -512,13 +512,13 @@ describe 'Issue Boards', :js do it 'removes label filter by clicking label button on issue' do page.within(find('.board:nth-child(2)')) do - page.within(find('.card', match: :first)) do + page.within(find('.board-card', match: :first)) do click_button(bug.title) end wait_for_requests - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end wait_for_requests @@ -589,7 +589,7 @@ describe 'Issue Boards', :js do def wait_for_board_cards(board_number, expected_cards) page.within(find(".board:nth-child(#{board_number})")) do expect(page.find('.board-header')).to have_content(expected_cards.to_s) - expect(page).to have_selector('.card', count: expected_cards) + expect(page).to have_selector('.board-card', count: expected_cards) end end diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb index 5abd02dbb48..193b1dfabbd 100644 --- a/spec/features/boards/issue_ordering_spec.rb +++ b/spec/features/boards/issue_ordering_spec.rb @@ -30,7 +30,7 @@ describe 'Issue Boards', :js do it 'has un-ordered issue as last issue' do page.within(find('.board:nth-child(2)')) do - expect(all('.card').last).to have_content(issue4.title) + expect(all('.board-card').last).to have_content(issue4.title) end end @@ -40,7 +40,7 @@ describe 'Issue Boards', :js do wait_for_requests page.within(find('.board:nth-child(2)')) do - expect(first('.card')).to have_content(issue4.title) + expect(first('.board-card')).to have_content(issue4.title) end end end @@ -58,7 +58,7 @@ describe 'Issue Boards', :js do wait_for_requests - expect(first('.card')).to have_content(issue2.title) + expect(first('.board-card')).to have_content(issue2.title) end it 'moves from middle to bottom' do @@ -66,7 +66,7 @@ describe 'Issue Boards', :js do wait_for_requests - expect(all('.card').last).to have_content(issue2.title) + expect(all('.board-card').last).to have_content(issue2.title) end it 'moves from top to bottom' do @@ -74,7 +74,7 @@ describe 'Issue Boards', :js do wait_for_requests - expect(all('.card').last).to have_content(issue3.title) + expect(all('.board-card').last).to have_content(issue3.title) end it 'moves from bottom to top' do @@ -82,7 +82,7 @@ describe 'Issue Boards', :js do wait_for_requests - expect(first('.card')).to have_content(issue1.title) + expect(first('.board-card')).to have_content(issue1.title) end it 'moves from top to middle' do @@ -90,7 +90,7 @@ describe 'Issue Boards', :js do wait_for_requests - expect(first('.card')).to have_content(issue2.title) + expect(first('.board-card')).to have_content(issue2.title) end it 'moves from bottom to middle' do @@ -98,7 +98,7 @@ describe 'Issue Boards', :js do wait_for_requests - expect(all('.card').last).to have_content(issue2.title) + expect(all('.board-card').last).to have_content(issue2.title) end end @@ -121,11 +121,11 @@ describe 'Issue Boards', :js do wait_for_requests - expect(find('.board:nth-child(2)')).to have_selector('.card', count: 2) - expect(all('.board')[2]).to have_selector('.card', count: 4) + expect(find('.board:nth-child(2)')).to have_selector('.board-card', count: 2) + expect(all('.board')[2]).to have_selector('.board-card', count: 4) page.within(all('.board')[2]) do - expect(first('.card')).to have_content(issue3.title) + expect(first('.board-card')).to have_content(issue3.title) end end @@ -134,11 +134,11 @@ describe 'Issue Boards', :js do wait_for_requests - expect(find('.board:nth-child(2)')).to have_selector('.card', count: 2) - expect(all('.board')[2]).to have_selector('.card', count: 4) + expect(find('.board:nth-child(2)')).to have_selector('.board-card', count: 2) + expect(all('.board')[2]).to have_selector('.board-card', count: 4) page.within(all('.board')[2]) do - expect(all('.card').last).to have_content(issue3.title) + expect(all('.board-card').last).to have_content(issue3.title) end end @@ -147,11 +147,11 @@ describe 'Issue Boards', :js do wait_for_requests - expect(find('.board:nth-child(2)')).to have_selector('.card', count: 2) - expect(all('.board')[2]).to have_selector('.card', count: 4) + expect(find('.board:nth-child(2)')).to have_selector('.board-card', count: 2) + expect(all('.board')[2]).to have_selector('.board-card', count: 4) page.within(all('.board')[2]) do - expect(all('.card')[1]).to have_content(issue3.title) + expect(all('.board-card')[1]).to have_content(issue3.title) end end end diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb index 5907bb0840f..be9c6a51c29 100644 --- a/spec/features/boards/modal_filter_spec.rb +++ b/spec/features/boards/modal_filter_spec.rb @@ -38,7 +38,7 @@ describe 'Issue Boards add issue modal filtering', :js do page.within('.add-issues-modal') do wait_for_requests - expect(page).to have_selector('.card', count: 0) + expect(page).to have_selector('.board-card', count: 0) click_button 'Cancel' end @@ -48,7 +48,7 @@ describe 'Issue Boards add issue modal filtering', :js do page.within('.add-issues-modal') do wait_for_requests - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end @@ -62,13 +62,13 @@ describe 'Issue Boards add issue modal filtering', :js do page.within('.add-issues-modal') do wait_for_requests - expect(page).to have_selector('.card', count: 0) + expect(page).to have_selector('.board-card', count: 0) find('.clear-search').click wait_for_requests - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end @@ -90,7 +90,7 @@ describe 'Issue Boards add issue modal filtering', :js do wait_for_requests expect(page).to have_selector('.js-visual-token', text: user2.name) - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end end @@ -113,7 +113,7 @@ describe 'Issue Boards add issue modal filtering', :js do wait_for_requests expect(page).to have_selector('.js-visual-token', text: 'none') - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end @@ -126,7 +126,7 @@ describe 'Issue Boards add issue modal filtering', :js do wait_for_requests expect(page).to have_selector('.js-visual-token', text: user2.name) - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end end @@ -148,7 +148,7 @@ describe 'Issue Boards add issue modal filtering', :js do wait_for_requests expect(page).to have_selector('.js-visual-token', text: 'upcoming') - expect(page).to have_selector('.card', count: 0) + expect(page).to have_selector('.board-card', count: 0) end end @@ -161,7 +161,7 @@ describe 'Issue Boards add issue modal filtering', :js do wait_for_requests expect(page).to have_selector('.js-visual-token', text: milestone.name) - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end end @@ -183,7 +183,7 @@ describe 'Issue Boards add issue modal filtering', :js do wait_for_requests expect(page).to have_selector('.js-visual-token', text: 'none') - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end @@ -196,7 +196,7 @@ describe 'Issue Boards add issue modal filtering', :js do wait_for_requests expect(page).to have_selector('.js-visual-token', text: label.title) - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end end diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index e880f0096c1..7a95f5cf871 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -64,7 +64,7 @@ describe 'Issue Boards new issue', :js do expect(page).to have_content('1') end - page.within(first('.card')) do + page.within(first('.board-card')) do issue = project.issues.find_by_title('bug') expect(page).to have_content(issue.to_reference) diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index 4d31123a699..a03aa681827 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -15,7 +15,7 @@ describe 'Issue Boards', :js do let!(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch], relative_position: 1) } let(:board) { create(:board, project: project) } let!(:list) { create(:list, board: board, label: development, position: 0) } - let(:card) { find('.board:nth-child(2)').first('.card') } + let(:card) { find('.board:nth-child(2)').first('.board-card') } around do |example| Timecop.freeze { example.run } @@ -75,7 +75,7 @@ describe 'Issue Boards', :js do wait_for_requests page.within(find('.board:nth-child(2)')) do - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end @@ -86,11 +86,11 @@ describe 'Issue Boards', :js do visit project_board_path(project, board) wait_for_requests - click_card(find('.board:nth-child(1)').first('.card')) + click_card(find('.board:nth-child(1)').first('.board-card')) expect(find('.issue-boards-sidebar')).not_to have_button 'Remove from board' - click_card(find('.board:nth-child(3)').first('.card')) + click_card(find('.board:nth-child(3)').first('.board-card')) expect(find('.issue-boards-sidebar')).not_to have_button 'Remove from board' end @@ -117,7 +117,7 @@ describe 'Issue Boards', :js do end it 'removes the assignee' do - card_two = find('.board:nth-child(2)').find('.card:nth-child(2)') + card_two = find('.board:nth-child(2)').find('.board-card:nth-child(2)') click_card(card_two) page.within('.assignee') do @@ -171,7 +171,7 @@ describe 'Issue Boards', :js do end page.within(find('.board:nth-child(2)')) do - find('.card:nth-child(2)').click + find('.board-card:nth-child(2)').click end page.within('.assignee') do @@ -246,7 +246,7 @@ describe 'Issue Boards', :js do wait_for_requests page.within('.value') do - expect(page).to have_selector('.label', count: 2) + expect(page).to have_selector('.badge', count: 2) expect(page).to have_content(development.title) expect(page).to have_content(stretch.title) end @@ -268,12 +268,12 @@ describe 'Issue Boards', :js do find('.dropdown-menu-close-icon').click page.within('.value') do - expect(page).to have_selector('.label', count: 3) + expect(page).to have_selector('.badge', count: 3) expect(page).to have_content(bug.title) end end - expect(card).to have_selector('.label', count: 3) + expect(card).to have_selector('.badge', count: 3) expect(card).to have_content(bug.title) end @@ -293,13 +293,13 @@ describe 'Issue Boards', :js do find('.dropdown-menu-close-icon').click page.within('.value') do - expect(page).to have_selector('.label', count: 4) + expect(page).to have_selector('.badge', count: 4) expect(page).to have_content(bug.title) expect(page).to have_content(regression.title) end end - expect(card).to have_selector('.label', count: 4) + expect(card).to have_selector('.badge', count: 4) expect(card).to have_content(bug.title) expect(card).to have_content(regression.title) end @@ -321,12 +321,12 @@ describe 'Issue Boards', :js do find('.dropdown-menu-close-icon').click page.within('.value') do - expect(page).to have_selector('.label', count: 1) + expect(page).to have_selector('.badge', count: 1) expect(page).not_to have_content(stretch.title) end end - expect(card).to have_selector('.label', count: 1) + expect(card).to have_selector('.badge', count: 1) expect(card).not_to have_content(stretch.title) end diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb index 5fdb8044db2..271c610dcc8 100644 --- a/spec/features/boards/sub_group_project_spec.rb +++ b/spec/features/boards/sub_group_project_spec.rb @@ -20,7 +20,7 @@ describe 'Sub-group project issue boards', :js do end it 'creates new label from sidebar' do - find('.card').click + find('.board-card').click page.within '.labels' do click_link 'Edit' diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb index 94133c62b5c..5ed20b02a6e 100644 --- a/spec/features/dashboard/todos/todos_spec.rb +++ b/spec/features/dashboard/todos/todos_spec.rb @@ -246,7 +246,7 @@ feature 'Dashboard Todos' do it 'is has the right number of pages' do visit dashboard_todos_path - expect(page).to have_selector('.gl-pagination .page', count: 2) + expect(page).to have_selector('.gl-pagination .js-pagination-page', count: 2) end describe 'mark all as done', :js do diff --git a/spec/features/explore/new_menu_spec.rb b/spec/features/explore/new_menu_spec.rb index 8d5233d0c0f..2516db5422f 100644 --- a/spec/features/explore/new_menu_spec.rb +++ b/spec/features/explore/new_menu_spec.rb @@ -66,7 +66,7 @@ feature 'Top Plus Menu', :js do page.within '.header-content' do find('.header-new-dropdown-toggle').click - expect(page).to have_selector('.header-new.dropdown.open', count: 1) + expect(page).to have_selector('.header-new.dropdown.show', count: 1) find('.header-new-project-snippet a').click end @@ -88,7 +88,7 @@ feature 'Top Plus Menu', :js do page.within '.header-content' do find('.header-new-dropdown-toggle').click - expect(page).to have_selector('.header-new.dropdown.open', count: 1) + expect(page).to have_selector('.header-new.dropdown.show', count: 1) find('.header-new-group-project a').click end @@ -156,7 +156,7 @@ feature 'Top Plus Menu', :js do def click_topmenuitem(item_name) page.within '.header-content' do find('.header-new-dropdown-toggle').click - expect(page).to have_selector('.header-new.dropdown.open', count: 1) + expect(page).to have_selector('.header-new.dropdown.show', count: 1) click_link item_name end end diff --git a/spec/features/groups/members/search_members_spec.rb b/spec/features/groups/members/search_members_spec.rb index 31fbbcf562c..e7efdf7dfef 100644 --- a/spec/features/groups/members/search_members_spec.rb +++ b/spec/features/groups/members/search_members_spec.rb @@ -22,7 +22,7 @@ describe 'Search group member' do find('.member-search-btn').click end - group_members_list = find(".panel .content-list") + group_members_list = find(".card .content-list") expect(group_members_list).to have_content(member.name) expect(group_members_list).not_to have_content(user.name) end diff --git a/spec/features/groups/settings/group_badges_spec.rb b/spec/features/groups/settings/group_badges_spec.rb index 92217294446..a99da4a119b 100644 --- a/spec/features/groups/settings/group_badges_spec.rb +++ b/spec/features/groups/settings/group_badges_spec.rb @@ -21,7 +21,7 @@ feature 'Group Badges' do page.within '.badge-settings' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 expect(rows[0]).to have_content badge_1.link_url expect(rows[1]).to have_content badge_2.link_url @@ -48,7 +48,7 @@ feature 'Group Badges' do click_button 'Add badge' wait_for_requests - within '.panel-body' do + within '.card-body' do expect(find('a')[:href]).to eq badge_link_url expect(find('a img')[:src]).to eq badge_image_url end @@ -60,7 +60,7 @@ feature 'Group Badges' do it 'form is shown when clicking edit button in list' do page.within '.badge-settings' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 rows[1].find('[aria-label="Edit"]').click @@ -74,7 +74,7 @@ feature 'Group Badges' do it 'updates a badge when submitting the edit form' do page.within '.badge-settings' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 rows[1].find('[aria-label="Edit"]').click within 'form' do @@ -85,7 +85,7 @@ feature 'Group Badges' do wait_for_requests end - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 expect(rows[1]).to have_content badge_link_url end @@ -99,7 +99,7 @@ feature 'Group Badges' do it 'shows a modal when deleting a badge' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 click_delete_button(rows[1]) @@ -109,14 +109,14 @@ feature 'Group Badges' do it 'deletes a badge when confirming the modal' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 click_delete_button(rows[1]) find('.modal .btn-danger').click wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 1 expect(rows[0]).to have_content badge_1.link_url end diff --git a/spec/features/groups/user_browse_projects_group_page_spec.rb b/spec/features/groups/user_browse_projects_group_page_spec.rb index e81c3180e78..916363c41dd 100644 --- a/spec/features/groups/user_browse_projects_group_page_spec.rb +++ b/spec/features/groups/user_browse_projects_group_page_spec.rb @@ -21,7 +21,7 @@ describe 'User browse group projects page' do visit projects_group_path(group) expect(page).to have_link project.name - expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived') + expect(page).to have_xpath("//span[@class='badge badge-warning']", text: 'archived') end end end diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index 08ba91a2682..483122ae463 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -248,7 +248,7 @@ describe 'Filter issues', :js do context 'issue label clicked' do it 'filters and displays in search bar' do - find('.issues-list .issue .issue-main-info .issuable-info a .label', text: multiple_words_label.title).click + find('.issues-list .issue .issue-main-info .issuable-info a .badge', text: multiple_words_label.title).click expect_issues_list_count(1) expect_tokens([label_token("\"#{multiple_words_label.title}\"")]) diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb index 539d7e9ff01..3dfcbc2fcb8 100644 --- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb +++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb @@ -139,8 +139,8 @@ describe 'User creates branch and merge request on issue page', :js do end it 'disables the create branch button' do - expect(page).to have_css('.create-mr-dropdown-wrap .unavailable:not(.hide)') - expect(page).to have_css('.create-mr-dropdown-wrap .available.hide', visible: false) + expect(page).to have_css('.create-mr-dropdown-wrap .unavailable:not(.hidden)') + expect(page).to have_css('.create-mr-dropdown-wrap .available.hidden', visible: false) expect(page).to have_content /1 Related Merge Request/ end end diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb index ae41f611ddc..4700ada1aae 100644 --- a/spec/features/labels_hierarchy_spec.rb +++ b/spec/features/labels_hierarchy_spec.rb @@ -34,7 +34,7 @@ feature 'Labels Hierarchy', :js, :nested_groups do wait_for_requests - expect(page).to have_selector('span.label', text: label.title) + expect(page).to have_selector('span.badge', text: label.title) end end @@ -45,7 +45,7 @@ feature 'Labels Hierarchy', :js, :nested_groups do wait_for_requests - expect(page).not_to have_selector('span.label', text: child_group_label.title) + expect(page).not_to have_selector('span.badge', text: child_group_label.title) end end @@ -57,7 +57,7 @@ feature 'Labels Hierarchy', :js, :nested_groups do wait_for_requests if board - expect(page).to have_selector('.card-title') do |card| + expect(page).to have_selector('.board-card-title') do |card| expect(card).to have_selector('a', text: labeled_issue.title) end else @@ -96,11 +96,11 @@ feature 'Labels Hierarchy', :js, :nested_groups do wait_for_requests if board - expect(page).to have_selector('.card-title') do |card| + expect(page).to have_selector('.board-card-title') do |card| expect(card).to have_selector('a', text: labeled_issue.title) end - expect(page).to have_selector('.card-title') do |card| + expect(page).to have_selector('.board-card-title') do |card| expect(card).to have_selector('a', text: labeled_issue_2.title) end else @@ -118,11 +118,11 @@ feature 'Labels Hierarchy', :js, :nested_groups do select_label_on_dropdown(group_label_3.title) if board - expect(page).to have_selector('.card-title') do |card| + expect(page).to have_selector('.board-card-title') do |card| expect(card).not_to have_selector('a', text: labeled_issue_2.title) end - expect(page).to have_selector('.card-title') do |card| + expect(page).to have_selector('.board-card-title') do |card| expect(card).to have_selector('a', text: labeled_issue_3.title) end else @@ -159,9 +159,9 @@ feature 'Labels Hierarchy', :js, :nested_groups do find('.btn-create').click expect(page.find('.issue-details h2.title')).to have_content('new created issue') - expect(page).to have_selector('span.label', text: grandparent_group_label.title) - expect(page).to have_selector('span.label', text: parent_group_label.title) - expect(page).to have_selector('span.label', text: project_label_1.title) + expect(page).to have_selector('span.badge', text: grandparent_group_label.title) + expect(page).to have_selector('span.badge', text: parent_group_label.title) + expect(page).to have_selector('span.badge', text: project_label_1.title) end end @@ -188,7 +188,7 @@ feature 'Labels Hierarchy', :js, :nested_groups do wait_for_requests - find('.card').click + find('.board-card').click end it_behaves_like 'assigning labels from sidebar' @@ -204,7 +204,7 @@ feature 'Labels Hierarchy', :js, :nested_groups do wait_for_requests - find('.card').click + find('.board-card').click end it_behaves_like 'assigning labels from sidebar' diff --git a/spec/features/merge_request/user_resolves_conflicts_spec.rb b/spec/features/merge_request/user_resolves_conflicts_spec.rb index 19995559fae..59aa90fc86f 100644 --- a/spec/features/merge_request/user_resolves_conflicts_spec.rb +++ b/spec/features/merge_request/user_resolves_conflicts_spec.rb @@ -10,7 +10,7 @@ describe 'Merge request > User resolves conflicts', :js do end def create_merge_request(source_branch) - create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start', source_project: project) do |mr| + create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start', source_project: project, merge_status: :unchecked) do |mr| mr.mark_as_unmergeable end end @@ -27,7 +27,7 @@ describe 'Merge request > User resolves conflicts', :js do end end - find_button('Commit conflict resolution').send_keys(:return) + find_button('Commit to source branch').send_keys(:return) expect(page).to have_content('All merge conflicts were resolved') merge_request.reload_diff @@ -71,7 +71,7 @@ describe 'Merge request > User resolves conflicts', :js do execute_script('ace.edit($(".files-wrapper .diff-file pre")[1]).setValue("Gregor Samsa woke from troubled dreams");') end - find_button('Commit conflict resolution').send_keys(:return) + find_button('Commit to source branch').send_keys(:return) expect(page).to have_content('All merge conflicts were resolved') merge_request.reload_diff @@ -145,7 +145,7 @@ describe 'Merge request > User resolves conflicts', :js do execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("Gregor Samsa woke from troubled dreams");') end - click_button 'Commit conflict resolution' + click_button 'Commit to source branch' expect(page).to have_content('All merge conflicts were resolved') diff --git a/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb new file mode 100644 index 00000000000..c40c720d168 --- /dev/null +++ b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb @@ -0,0 +1,24 @@ +require 'rails_helper' + +describe 'Merge request > User sees Check out branch modal', :js do + let(:project) { create(:project, :public, :repository) } + let(:user) { project.creator } + let(:merge_request) { create(:merge_request, source_project: project) } + + before do + sign_in(user) + visit project_merge_request_path(project, merge_request) + wait_for_requests + click_button('Check out branch') + end + + it 'shows the check out branch modal' do + expect(page).to have_content('Check out, review, and merge locally') + end + + it 'closes the check out branch model with Escape keypress' do + find('#modal_merge_info').send_keys(:escape) + + expect(page).not_to have_content('Check out, review, and merge locally') + end +end diff --git a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb index 42c279af117..ed6e29335d1 100644 --- a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb +++ b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb @@ -4,6 +4,12 @@ describe 'Merge request > User selects branches for new MR', :js do let(:project) { create(:project, :public, :repository) } let(:user) { project.creator } + def select_source_branch(branch_name) + find('.js-source-branch', match: :first).click + find('.js-source-branch-dropdown .dropdown-input-field').native.send_keys branch_name + find('.js-source-branch-dropdown .dropdown-content a', text: branch_name, match: :first).click + end + before do project.add_master(user) sign_in(user) @@ -43,8 +49,7 @@ describe 'Merge request > User selects branches for new MR', :js do it 'generates a diff for an orphaned branch' do visit project_new_merge_request_path(project) - find('.js-source-branch', match: :first).click - find('.js-source-branch-dropdown .dropdown-content a', text: 'orphaned-branch', match: :first).click + select_source_branch('orphaned-branch') click_button "Compare branches" click_link "Changes" @@ -169,4 +174,31 @@ describe 'Merge request > User selects branches for new MR', :js do end end end + + context 'with special characters in branch names' do + it 'escapes quotes in branch names' do + special_branch_name = '"with-quotes"' + CreateBranchService.new(project, user) + .execute(special_branch_name, 'add-pdf-file') + + visit project_new_merge_request_path(project) + select_source_branch(special_branch_name) + + source_branch_input = find('[name="merge_request[source_branch]"]', visible: false) + expect(source_branch_input.value).to eq special_branch_name + end + + it 'does not escape unicode in branch names' do + special_branch_name = 'ʕ•ᴥ•ʔ' + CreateBranchService.new(project, user) + .execute(special_branch_name, 'add-pdf-file') + + visit project_new_merge_request_path(project) + select_source_branch(special_branch_name) + + click_button "Compare branches" + + expect(page).to have_button("Submit merge request") + end + end end diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb index 6c51e4bbe26..b0db6870ddf 100644 --- a/spec/features/milestone_spec.rb +++ b/spec/features/milestone_spec.rb @@ -119,7 +119,7 @@ feature 'Milestone' do find('.milestone-deprecation-message .js-popover-link').click - expect(page).to have_selector('.milestone-deprecation-message .popover') + expect(page).to have_selector('.popover') end end end diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index ac82f869f0f..e7b305925f7 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -14,6 +14,8 @@ feature 'File blob', :js do context 'Ruby file' do before do visit_blob('files/ruby/popen.rb') + + wait_for_requests end it 'displays the blob' do @@ -48,6 +50,8 @@ feature 'File blob', :js do context 'visiting directly' do before do visit_blob('files/markdown/ruby-style-guide.md') + + wait_for_requests end it 'displays the blob using the rich viewer' do @@ -159,6 +163,8 @@ feature 'File blob', :js do project.update_attribute(:lfs_enabled, true) visit_blob('files/lfs/file.md') + + wait_for_requests end it 'displays an error' do @@ -207,6 +213,8 @@ feature 'File blob', :js do context 'when LFS is disabled on the project' do before do visit_blob('files/lfs/file.md') + + wait_for_requests end it 'displays the blob' do @@ -242,6 +250,8 @@ feature 'File blob', :js do ).execute visit_blob('files/test.pdf') + + wait_for_requests end it 'displays the blob' do @@ -268,6 +278,8 @@ feature 'File blob', :js do project.update_attribute(:lfs_enabled, true) visit_blob('files/lfs/lfs_object.iso') + + wait_for_requests end it 'displays the blob' do @@ -290,6 +302,8 @@ feature 'File blob', :js do context 'when LFS is disabled on the project' do before do visit_blob('files/lfs/lfs_object.iso') + + wait_for_requests end it 'displays the blob' do @@ -313,6 +327,8 @@ feature 'File blob', :js do context 'ZIP file' do before do visit_blob('Gemfile.zip') + + wait_for_requests end it 'displays the blob' do @@ -347,6 +363,8 @@ feature 'File blob', :js do ).execute visit_blob('files/empty.md') + + wait_for_requests end it 'displays an error' do diff --git a/spec/features/projects/issues/user_sorts_issues_spec.rb b/spec/features/projects/issues/user_sorts_issues_spec.rb index c3d63000dac..db5936a30cb 100644 --- a/spec/features/projects/issues/user_sorts_issues_spec.rb +++ b/spec/features/projects/issues/user_sorts_issues_spec.rb @@ -18,7 +18,7 @@ describe "User sorts issues" do it "sorts by popularity" do find("button.dropdown-toggle").click - page.within(".content ul.dropdown-menu.dropdown-menu-align-right li") do + page.within(".content ul.dropdown-menu.dropdown-menu-right li") do click_link("Popularity") end diff --git a/spec/features/projects/merge_requests/user_sorts_merge_requests_spec.rb b/spec/features/projects/merge_requests/user_sorts_merge_requests_spec.rb index d8d9f7e2a8c..305658f1b5d 100644 --- a/spec/features/projects/merge_requests/user_sorts_merge_requests_spec.rb +++ b/spec/features/projects/merge_requests/user_sorts_merge_requests_spec.rb @@ -18,7 +18,7 @@ describe 'User sorts merge requests' do it 'keeps the sort option' do find('button.dropdown-toggle').click - page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do + page.within('.content ul.dropdown-menu.dropdown-menu-right li') do click_link('Last updated') end @@ -43,7 +43,7 @@ describe 'User sorts merge requests' do it 'sorts by popularity' do find('button.dropdown-toggle').click - page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do + page.within('.content ul.dropdown-menu.dropdown-menu-right li') do click_link('Popularity') end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index a29c21f6fef..af2a9567a47 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -165,7 +165,7 @@ describe 'Pipeline', :js do end it 'shows Pipeline tab as active' do - expect(page).to have_css('.js-pipeline-tab-link.active') + expect(page).to have_css('.js-pipeline-tab-link .active') end context 'without permission to access builds' do @@ -271,7 +271,7 @@ describe 'Pipeline', :js do end it 'shows Jobs tab as active' do - expect(page).to have_css('li.js-builds-tab-link.active') + expect(page).to have_css('li.js-builds-tab-link .active') end end diff --git a/spec/features/projects/settings/project_badges_spec.rb b/spec/features/projects/settings/project_badges_spec.rb index cc3551a4c21..4893bef8884 100644 --- a/spec/features/projects/settings/project_badges_spec.rb +++ b/spec/features/projects/settings/project_badges_spec.rb @@ -22,7 +22,7 @@ feature 'Project Badges' do page.within '.badge-settings' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 expect(rows[0]).to have_content group_badge.link_url expect(rows[1]).to have_content project_badge.link_url @@ -49,7 +49,7 @@ feature 'Project Badges' do click_button 'Add badge' wait_for_requests - within '.panel-body' do + within '.card-body' do expect(find('a')[:href]).to eq badge_link_url expect(find('a img')[:src]).to eq badge_image_url end @@ -61,7 +61,7 @@ feature 'Project Badges' do it 'form is shown when clicking edit button in list' do page.within '.badge-settings' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 rows[1].find('[aria-label="Edit"]').click @@ -75,7 +75,7 @@ feature 'Project Badges' do it 'updates a badge when submitting the edit form' do page.within '.badge-settings' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 rows[1].find('[aria-label="Edit"]').click within 'form' do @@ -86,7 +86,7 @@ feature 'Project Badges' do wait_for_requests end - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 expect(rows[1]).to have_content badge_link_url end @@ -100,7 +100,7 @@ feature 'Project Badges' do it 'shows a modal when deleting a badge' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 click_delete_button(rows[1]) @@ -110,14 +110,14 @@ feature 'Project Badges' do it 'deletes a badge when confirming the modal' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 click_delete_button(rows[1]) find('.modal .btn-danger').click wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 1 expect(rows[0]).to have_content group_badge.link_url end diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index 43cabd3b9f2..0c28a853b54 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -68,8 +68,10 @@ feature 'Protected Branches', :js do within form do find(".js-allowed-to-merge").click + wait_for_requests click_link 'No one' find(".js-allowed-to-push").click + wait_for_requests click_link 'Developers + Masters' end diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb index 6088b831c14..db141ef7096 100644 --- a/spec/features/signed_commits_spec.rb +++ b/spec/features/signed_commits_spec.rb @@ -96,10 +96,11 @@ describe 'GPG signed commits', :js do within(find('.commit', text: 'signed commit by bette cartwright')) do click_on 'Unverified' - within '.popover' do - expect(page).to have_content 'This commit was signed with an unverified signature.' - expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}" - end + end + + within '.popover' do + expect(page).to have_content 'This commit was signed with an unverified signature.' + expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}" end end @@ -110,12 +111,13 @@ describe 'GPG signed commits', :js do within(find('.commit', text: 'signed and authored commit by bette cartwright, different email')) do click_on 'Unverified' - within '.popover' do - expect(page).to have_content 'This commit was signed with a verified signature, but the committer email is not verified to belong to the same user.' - expect(page).to have_content 'Bette Cartwright' - expect(page).to have_content '@bette.cartwright' - expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}" - end + end + + within '.popover' do + expect(page).to have_content 'This commit was signed with a verified signature, but the committer email is not verified to belong to the same user.' + expect(page).to have_content 'Bette Cartwright' + expect(page).to have_content '@bette.cartwright' + expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}" end end @@ -126,12 +128,13 @@ describe 'GPG signed commits', :js do within(find('.commit', text: 'signed commit by bette cartwright')) do click_on 'Unverified' - within '.popover' do - expect(page).to have_content "This commit was signed with a different user's verified signature." - expect(page).to have_content 'Bette Cartwright' - expect(page).to have_content '@bette.cartwright' - expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}" - end + end + + within '.popover' do + expect(page).to have_content "This commit was signed with a different user's verified signature." + expect(page).to have_content 'Bette Cartwright' + expect(page).to have_content '@bette.cartwright' + expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}" end end @@ -142,12 +145,13 @@ describe 'GPG signed commits', :js do within(find('.commit', text: 'signed and authored commit by nannie bernhard')) do click_on 'Verified' - within '.popover' do - expect(page).to have_content 'This commit was signed with a verified signature and the committer email is verified to belong to the same user.' - expect(page).to have_content 'Nannie Bernhard' - expect(page).to have_content '@nannie.bernhard' - expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}" - end + end + + within '.popover' do + expect(page).to have_content 'This commit was signed with a verified signature and the committer email is verified to belong to the same user.' + expect(page).to have_content 'Nannie Bernhard' + expect(page).to have_content '@nannie.bernhard' + expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}" end end @@ -167,12 +171,13 @@ describe 'GPG signed commits', :js do within(find('.commit', text: 'signed and authored commit by nannie bernhard')) do click_on 'Verified' - within '.popover' do - expect(page).to have_content 'This commit was signed with a verified signature and the committer email is verified to belong to the same user.' - expect(page).to have_content 'Nannie Bernhard' - expect(page).to have_content 'nannie.bernhard@example.com' - expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}" - end + end + + within '.popover' do + expect(page).to have_content 'This commit was signed with a verified signature and the committer email is verified to belong to the same user.' + expect(page).to have_content 'Nannie Bernhard' + expect(page).to have_content 'nannie.bernhard@example.com' + expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}" end end end diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb index 19784120108..6be2606fd0d 100644 --- a/spec/features/triggers_spec.rb +++ b/spec/features/triggers_spec.rb @@ -23,7 +23,7 @@ feature 'Triggers', :js do click_button 'Add trigger' # See if input has error due to empty value - expect(page.find('form.gl-show-field-errors .gl-field-error')['style']).to eq 'display: block;' + expect(page.find('form.gl-show-field-errors .gl-field-error')).to be_visible end scenario 'adds new trigger with description' do diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index 6f968a2c590..1f8d31a5c88 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -394,7 +394,7 @@ feature 'Login' do end def ensure_one_active_tab - expect(page).to have_selector('ul.new-session-tabs > li.active', count: 1) + expect(page).to have_selector('ul.new-session-tabs > li > a.active', count: 1) end def ensure_one_active_pane diff --git a/spec/fixtures/api/schemas/public_api/v4/projects.json b/spec/fixtures/api/schemas/public_api/v4/projects.json index d89eeea89a5..17ad8d8c48d 100644 --- a/spec/fixtures/api/schemas/public_api/v4/projects.json +++ b/spec/fixtures/api/schemas/public_api/v4/projects.json @@ -20,6 +20,7 @@ "ssh_url_to_repo": { "type": "string" }, "http_url_to_repo": { "type": "string" }, "web_url": { "type": "string" }, + "readme_url": { "type": ["string", "null"] }, "avatar_url": { "type": ["string", "null"] }, "star_count": { "type": "integer" }, "forks_count": { "type": "integer" }, diff --git a/spec/fixtures/emails/valid_new_issue_with_quote.eml b/spec/fixtures/emails/valid_new_issue_with_quote.eml new file mode 100644 index 00000000000..0caf8ed4e9e --- /dev/null +++ b/spec/fixtures/emails/valid_new_issue_with_quote.eml @@ -0,0 +1,25 @@ +Return-Path: <jake@adventuretime.ooo> +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog <jake@adventuretime.ooo> +To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo +Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com> +Subject: New Issue by email +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +The reply by email functionality should be extended to allow creating a new issue by email. +even when the email is forwarded to the project which may include lines that begin with ">" + +there should be a quote below this line: + +> this is a quote
\ No newline at end of file diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index 8de615ad8c2..a3e010c3206 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -258,13 +258,19 @@ describe BlobHelper do it 'returns full IDE path' do Rails.application.routes.default_url_options[:script_name] = nil - expect(helper.ide_edit_path(project, "master", "")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master/") + expect(helper.ide_edit_path(project, "master", "")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master") + end + + it 'returns full IDE path with second -' do + Rails.application.routes.default_url_options[:script_name] = nil + + expect(helper.ide_edit_path(project, "testing/slashes", "readme.md")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/testing/slashes/-/readme.md") end it 'returns IDE path without relative_url_root' do Rails.application.routes.default_url_options[:script_name] = "/gitlab" - expect(helper.ide_edit_path(project, "master", "")).to eq("/gitlab/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master/") + expect(helper.ide_edit_path(project, "master", "")).to eq("/gitlab/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master") end end end diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 7b59fde999d..cddb49b320f 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -45,22 +45,22 @@ describe IssuablesHelper do it 'returns "Open" when state is :opened' do expect(helper.issuables_state_counter_text(:issues, :opened, true)) - .to eq('<span>Open</span> <span class="badge">42</span>') + .to eq('<span>Open</span> <span class="badge badge-pill">42</span>') end it 'returns "Closed" when state is :closed' do expect(helper.issuables_state_counter_text(:issues, :closed, true)) - .to eq('<span>Closed</span> <span class="badge">42</span>') + .to eq('<span>Closed</span> <span class="badge badge-pill">42</span>') end it 'returns "Merged" when state is :merged' do expect(helper.issuables_state_counter_text(:merge_requests, :merged, true)) - .to eq('<span>Merged</span> <span class="badge">42</span>') + .to eq('<span>Merged</span> <span class="badge badge-pill">42</span>') end it 'returns "All" when state is :all' do expect(helper.issuables_state_counter_text(:merge_requests, :all, true)) - .to eq('<span>All</span> <span class="badge">42</span>') + .to eq('<span>All</span> <span class="badge badge-pill">42</span>') end end end diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js index 3d7ccf432be..e8435116221 100644 --- a/spec/javascripts/api_spec.js +++ b/spec/javascripts/api_spec.js @@ -341,4 +341,25 @@ describe('Api', () => { .catch(done.fail); }); }); + + describe('commitPipelines', () => { + it('fetches pipelines for a given commit', done => { + const projectId = 'example/foobar'; + const commitSha = 'abc123def'; + const expectedUrl = `${dummyUrlRoot}/${projectId}/commit/${commitSha}/pipelines`; + mock.onGet(expectedUrl).reply(200, [ + { + name: 'test', + }, + ]); + + Api.commitPipelines(projectId, commitSha) + .then(({ data }) => { + expect(data.length).toBe(1); + expect(data[0].name).toBe('test'); + }) + .then(done) + .catch(done.fail); + }); + }); }); diff --git a/spec/javascripts/badges/components/badge_list_spec.js b/spec/javascripts/badges/components/badge_list_spec.js index 9439c578973..02e59ae0843 100644 --- a/spec/javascripts/badges/components/badge_list_spec.js +++ b/spec/javascripts/badges/components/badge_list_spec.js @@ -33,7 +33,7 @@ describe('BadgeList component', () => { }); it('renders a header with the badge count', () => { - const header = vm.$el.querySelector('.panel-heading'); + const header = vm.$el.querySelector('.card-header'); expect(header).toHaveText(new RegExp(`Your badges\\s+${numberOfDummyBadges}`)); }); diff --git a/spec/javascripts/badges/components/badge_settings_spec.js b/spec/javascripts/badges/components/badge_settings_spec.js index 3db02982ad4..59367c85125 100644 --- a/spec/javascripts/badges/components/badge_settings_spec.js +++ b/spec/javascripts/badges/components/badge_settings_spec.js @@ -60,7 +60,7 @@ describe('BadgeSettings component', () => { }); it('displays badge list', () => { - const badgeListElement = vm.$el.querySelector('.panel'); + const badgeListElement = vm.$el.querySelector('.card'); expect(badgeListElement).not.toBe(null); expect(badgeListElement).toBeVisible(); expect(badgeListElement).toContainText('Your badges'); @@ -87,7 +87,7 @@ describe('BadgeSettings component', () => { }); it('displays no badge list', () => { - const badgeListElement = vm.$el.querySelector('.panel'); + const badgeListElement = vm.$el.querySelector('.card'); expect(badgeListElement).toBeHidden(); }); }); diff --git a/spec/javascripts/behaviors/secret_values_spec.js b/spec/javascripts/behaviors/secret_values_spec.js index 38d9bba6868..95122fcf30f 100644 --- a/spec/javascripts/behaviors/secret_values_spec.js +++ b/spec/javascripts/behaviors/secret_values_spec.js @@ -9,7 +9,7 @@ function generateValueMarkup( <div class="${placeholderClass}"> *** </div> - <div class="hide ${valueClass}"> + <div class="hidden ${valueClass}"> ${secret} </div> `; diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js index aa87956109f..cb0f2ba686d 100644 --- a/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js +++ b/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js @@ -257,9 +257,9 @@ describe('BalsamiqViewer', () => { name = 'name'; resource = 'resource'; template = ` - <div class="panel panel-default"> - <div class="panel-heading">name</div> - <div class="panel-body"> + <div class="card"> + <div class="card-header">name</div> + <div class="card-body"> <img class="img-thumbnail" src="data:image/png;base64,image"/> </div> </div> diff --git a/spec/javascripts/blob/sketch/index_spec.js b/spec/javascripts/blob/sketch/index_spec.js index 79f40559817..e062a068a92 100644 --- a/spec/javascripts/blob/sketch/index_spec.js +++ b/spec/javascripts/blob/sketch/index_spec.js @@ -82,7 +82,7 @@ describe('Sketch viewer', () => { const img = document.querySelector('#js-sketch-viewer img'); expect(img).not.toBeNull(); - expect(img.classList.contains('img-responsive')).toBeTruthy(); + expect(img.classList.contains('img-fluid')).toBeTruthy(); }); it('renders link to image', () => { diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js index 03df6c06691..c06b2f60813 100644 --- a/spec/javascripts/boards/board_list_spec.js +++ b/spec/javascripts/boards/board_list_spec.js @@ -83,13 +83,13 @@ describe('Board list component', () => { it('renders issues', () => { expect( - component.$el.querySelectorAll('.card').length, + component.$el.querySelectorAll('.board-card').length, ).toBe(1); }); it('sets data attribute with issue id', () => { expect( - component.$el.querySelector('.card').getAttribute('data-issue-id'), + component.$el.querySelector('.board-card').getAttribute('data-issue-id'), ).toBe('1'); }); diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js index be1ea0b57b4..abeef272c68 100644 --- a/spec/javascripts/boards/issue_card_spec.js +++ b/spec/javascripts/boards/issue_card_spec.js @@ -70,19 +70,19 @@ describe('Issue card component', () => { it('renders issue title', () => { expect( - component.$el.querySelector('.card-title').textContent, + component.$el.querySelector('.board-card-title').textContent, ).toContain(issue.title); }); it('includes issue base in link', () => { expect( - component.$el.querySelector('.card-title a').getAttribute('href'), + component.$el.querySelector('.board-card-title a').getAttribute('href'), ).toContain('/test'); }); it('includes issue title on link', () => { expect( - component.$el.querySelector('.card-title a').getAttribute('title'), + component.$el.querySelector('.board-card-title a').getAttribute('title'), ).toBe(issue.title); }); @@ -105,14 +105,14 @@ describe('Issue card component', () => { it('renders issue ID with #', () => { expect( - component.$el.querySelector('.card-number').textContent, + component.$el.querySelector('.board-card-number').textContent, ).toContain(`#${issue.id}`); }); describe('assignee', () => { it('does not render assignee', () => { expect( - component.$el.querySelector('.card-assignee .avatar'), + component.$el.querySelector('.board-card-assignee .avatar'), ).toBeNull(); }); @@ -125,25 +125,25 @@ describe('Issue card component', () => { it('renders assignee', () => { expect( - component.$el.querySelector('.card-assignee .avatar'), + component.$el.querySelector('.board-card-assignee .avatar'), ).not.toBeNull(); }); it('sets title', () => { expect( - component.$el.querySelector('.card-assignee img').getAttribute('data-original-title'), + component.$el.querySelector('.board-card-assignee img').getAttribute('data-original-title'), ).toContain(`Assigned to ${user.name}`); }); it('sets users path', () => { expect( - component.$el.querySelector('.card-assignee a').getAttribute('href'), + component.$el.querySelector('.board-card-assignee a').getAttribute('href'), ).toBe('/test'); }); it('renders avatar', () => { expect( - component.$el.querySelector('.card-assignee img'), + component.$el.querySelector('.board-card-assignee img'), ).not.toBeNull(); }); }); @@ -161,10 +161,10 @@ describe('Issue card component', () => { it('displays defaults avatar if users avatar is null', () => { expect( - component.$el.querySelector('.card-assignee img'), + component.$el.querySelector('.board-card-assignee img'), ).not.toBeNull(); expect( - component.$el.querySelector('.card-assignee img').getAttribute('src'), + component.$el.querySelector('.board-card-assignee img').getAttribute('src'), ).toBe('default_avatar'); }); }); @@ -197,7 +197,7 @@ describe('Issue card component', () => { }); it('renders all four assignees', () => { - expect(component.$el.querySelectorAll('.card-assignee .avatar').length).toEqual(4); + expect(component.$el.querySelectorAll('.board-card-assignee .avatar').length).toEqual(4); }); describe('more than four assignees', () => { @@ -213,11 +213,11 @@ describe('Issue card component', () => { }); it('renders more avatar counter', () => { - expect(component.$el.querySelector('.card-assignee .avatar-counter').innerText).toEqual('+2'); + expect(component.$el.querySelector('.board-card-assignee .avatar-counter').innerText).toEqual('+2'); }); it('renders three assignees', () => { - expect(component.$el.querySelectorAll('.card-assignee .avatar').length).toEqual(3); + expect(component.$el.querySelectorAll('.board-card-assignee .avatar').length).toEqual(3); }); it('renders 99+ avatar counter', (done) => { @@ -232,7 +232,7 @@ describe('Issue card component', () => { } Vue.nextTick(() => { - expect(component.$el.querySelector('.card-assignee .avatar-counter').innerText).toEqual('99+'); + expect(component.$el.querySelector('.board-card-assignee .avatar-counter').innerText).toEqual('99+'); done(); }); }); @@ -248,13 +248,13 @@ describe('Issue card component', () => { it('renders list label', () => { expect( - component.$el.querySelectorAll('.label').length, + component.$el.querySelectorAll('.badge').length, ).toBe(2); }); it('renders label', () => { const nodes = []; - component.$el.querySelectorAll('.label').forEach((label) => { + component.$el.querySelectorAll('.badge').forEach((label) => { nodes.push(label.title); }); @@ -265,13 +265,13 @@ describe('Issue card component', () => { it('sets label description as title', () => { expect( - component.$el.querySelector('.label').getAttribute('title'), + component.$el.querySelector('.badge').getAttribute('title'), ).toContain(label1.description); }); it('sets background color of button', () => { const nodes = []; - component.$el.querySelectorAll('.label').forEach((label) => { + component.$el.querySelectorAll('.badge').forEach((label) => { nodes.push(label.style.backgroundColor); }); @@ -288,7 +288,7 @@ describe('Issue card component', () => { Vue.nextTick() .then(() => { expect( - component.$el.querySelectorAll('.label').length, + component.$el.querySelectorAll('.badge').length, ).toBe(2); expect( component.$el.textContent, diff --git a/spec/javascripts/fixtures/event_filter.html.haml b/spec/javascripts/fixtures/event_filter.html.haml index 5477c6075f0..aa7af61c7eb 100644 --- a/spec/javascripts/fixtures/event_filter.html.haml +++ b/spec/javascripts/fixtures/event_filter.html.haml @@ -1,4 +1,4 @@ -%ul.nav-links.event-filter.scrolling-tabs +%ul.nav-links.event-filter.scrolling-tabs.nav.nav-tabs %li.active %a.event-filter-link{ id: "all_event_filter", title: "Filter by all", href: "/dashboard/activity"} %span diff --git a/spec/javascripts/fixtures/issue_sidebar_label.html.haml b/spec/javascripts/fixtures/issue_sidebar_label.html.haml index 397bdc85c67..06ce248dc9c 100644 --- a/spec/javascripts/fixtures/issue_sidebar_label.html.haml +++ b/spec/javascripts/fixtures/issue_sidebar_label.html.haml @@ -1,7 +1,7 @@ .block.labels .sidebar-collapsed-icon.js-sidebar-labels-tooltip .title.hide-collapsed - %a.edit-link.pull-right{ href: "#" } + %a.edit-link.float-right{ href: "#" } Edit .selectbox.hide-collapsed{ style: "display: none;" } .dropdown diff --git a/spec/javascripts/fixtures/linked_tabs.html.haml b/spec/javascripts/fixtures/linked_tabs.html.haml index c38fe8b1f25..632606e0536 100644 --- a/spec/javascripts/fixtures/linked_tabs.html.haml +++ b/spec/javascripts/fixtures/linked_tabs.html.haml @@ -1,9 +1,9 @@ -%ul.nav-links.new-session-tabs.linked-tabs - %li - %a{ href: 'foo/bar/1', data: { target: 'div#tab1', action: 'tab1', toggle: 'tab' } } +%ul.nav.nav-tabs.new-session-tabs.linked-tabs + %li.nav-item + %a.nav-link{ href: 'foo/bar/1', data: { target: 'div#tab1', action: 'tab1', toggle: 'tab' } } Tab 1 - %li - %a{ href: 'foo/bar/1/context', data: { target: 'div#tab2', action: 'tab2', toggle: 'tab' } } + %li.nav-item + %a.nav-link{ href: 'foo/bar/1/context', data: { target: 'div#tab2', action: 'tab2', toggle: 'tab' } } Tab 2 .tab-content diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js index 7f9c4811fba..175f386b60e 100644 --- a/spec/javascripts/gl_dropdown_spec.js +++ b/spec/javascripts/gl_dropdown_spec.js @@ -70,9 +70,9 @@ describe('glDropdown', function describeDropdown() { it('should open on click', () => { initDropDown.call(this, false); - expect(this.dropdownContainerElement).not.toHaveClass('open'); + expect(this.dropdownContainerElement).not.toHaveClass('show'); this.dropdownButtonElement.click(); - expect(this.dropdownContainerElement).toHaveClass('open'); + expect(this.dropdownContainerElement).toHaveClass('show'); }); it('escapes HTML as text', () => { @@ -134,12 +134,12 @@ describe('glDropdown', function describeDropdown() { }); it('should click the selected item on ENTER keypress', () => { - expect(this.dropdownContainerElement).toHaveClass('open'); + expect(this.dropdownContainerElement).toHaveClass('show'); const randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0; navigateWithKeys('down', randomIndex, () => { const visitUrl = spyOnDependency(GLDropdown, 'visitUrl').and.stub(); navigateWithKeys('enter', null, () => { - expect(this.dropdownContainerElement).not.toHaveClass('open'); + expect(this.dropdownContainerElement).not.toHaveClass('show'); const link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement); expect(link).toHaveClass('is-active'); const linkedLocation = link.attr('href'); @@ -149,13 +149,13 @@ describe('glDropdown', function describeDropdown() { }); it('should close on ESC keypress', () => { - expect(this.dropdownContainerElement).toHaveClass('open'); + expect(this.dropdownContainerElement).toHaveClass('show'); this.dropdownContainerElement.trigger({ type: 'keyup', which: ARROW_KEYS.ESC, keyCode: ARROW_KEYS.ESC }); - expect(this.dropdownContainerElement).not.toHaveClass('open'); + expect(this.dropdownContainerElement).not.toHaveClass('show'); }); }); diff --git a/spec/javascripts/helpers/vuex_action_helper.js b/spec/javascripts/helpers/vuex_action_helper.js index 83f29d1b0c2..d6ab0aeeed7 100644 --- a/spec/javascripts/helpers/vuex_action_helper.js +++ b/spec/javascripts/helpers/vuex_action_helper.js @@ -55,7 +55,7 @@ export default (action, payload, state, expectedMutations, expectedActions, done }; // call the action with mocked store and arguments - action({ commit, state, dispatch }, payload); + action({ commit, state, dispatch, rootState: state }, payload); // check if no mutations should have been dispatched if (expectedMutations.length === 0) { diff --git a/spec/javascripts/ide/components/ide_spec.js b/spec/javascripts/ide/components/ide_spec.js index 6f580e1f7af..045a60e56a0 100644 --- a/spec/javascripts/ide/components/ide_spec.js +++ b/spec/javascripts/ide/components/ide_spec.js @@ -107,5 +107,11 @@ describe('ide component', () => { vm.mousetrapStopCallback(null, vm.$el.querySelector('.dropdown-input-field'), 't'), ).toBe(true); }); + + it('stops callback in monaco editor', () => { + setFixtures('<div class="inputarea"></div>'); + + expect(vm.mousetrapStopCallback(null, document.querySelector('.inputarea'), 't')).toBe(true); + }); }); }); diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js index ff500acd849..d3f80e6f9c0 100644 --- a/spec/javascripts/ide/components/repo_editor_spec.js +++ b/spec/javascripts/ide/components/repo_editor_spec.js @@ -346,4 +346,24 @@ describe('RepoEditor', () => { }); }); }); + + it('calls removePendingTab when old file is pending', done => { + spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true); + spyOn(vm, 'removePendingTab'); + + vm.file.pending = true; + + vm + .$nextTick() + .then(() => { + vm.file = file('testing'); + + return vm.$nextTick(); + }) + .then(() => { + expect(vm.removePendingTab).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); }); diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js index c059862b9d1..c68ae050641 100644 --- a/spec/javascripts/ide/mock_data.js +++ b/spec/javascripts/ide/mock_data.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/prefer-default-export export const projectData = { id: 1, name: 'abcproject', @@ -14,3 +13,83 @@ export const projectData = { mergeRequests: {}, merge_requests_enabled: true, }; + +export const pipelines = [ + { + id: 1, + ref: 'master', + sha: '123', + status: 'failed', + }, + { + id: 2, + ref: 'master', + sha: '213', + status: 'success', + }, +]; + +export const jobs = [ + { + id: 1, + name: 'test', + status: 'failed', + stage: 'test', + duration: 1, + }, + { + id: 2, + name: 'test 2', + status: 'failed', + stage: 'test', + duration: 1, + }, + { + id: 3, + name: 'test 3', + status: 'failed', + stage: 'test', + duration: 1, + }, + { + id: 4, + name: 'test 3', + status: 'failed', + stage: 'build', + duration: 1, + }, +]; + +export const fullPipelinesResponse = { + data: { + count: { + all: 2, + }, + pipelines: [ + { + id: '51', + commit: { + id: 'xxxxxxxxxxxxxxxxxxxx', + }, + details: { + status: { + icon: 'status_failed', + text: 'failed', + }, + }, + }, + { + id: '50', + commit: { + id: 'abc123def456ghi789jkl', + }, + details: { + status: { + icon: 'status_passed', + text: 'passed', + }, + }, + }, + ], + }, +}; diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js index ebd08d95810..8e078ae7138 100644 --- a/spec/javascripts/ide/stores/actions/project_spec.js +++ b/spec/javascripts/ide/stores/actions/project_spec.js @@ -1,14 +1,33 @@ -import { - refreshLastCommitData, -} from '~/ide/stores/actions'; +import Visibility from 'visibilityjs'; +import MockAdapter from 'axios-mock-adapter'; +import { refreshLastCommitData, pollSuccessCallBack } from '~/ide/stores/actions'; import store from '~/ide/stores'; import service from '~/ide/services'; +import axios from '~/lib/utils/axios_utils'; +import { fullPipelinesResponse } from '../../mock_data'; import { resetStore } from '../../helpers'; import testAction from '../../../helpers/vuex_action_helper'; describe('IDE store project actions', () => { + const setProjectState = () => { + store.state.currentProjectId = 'abc/def'; + store.state.currentBranchId = 'master'; + store.state.projects['abc/def'] = { + id: 4, + path_with_namespace: 'abc/def', + branches: { + master: { + commit: { + id: 'abc123def456ghi789jkl', + title: 'example', + }, + }, + }, + }; + }; + beforeEach(() => { - store.state.projects.abcproject = {}; + store.state.projects['abc/def'] = {}; }); afterEach(() => { @@ -17,18 +36,16 @@ describe('IDE store project actions', () => { describe('refreshLastCommitData', () => { beforeEach(() => { - store.state.currentProjectId = 'abcproject'; + store.state.currentProjectId = 'abc/def'; store.state.currentBranchId = 'master'; - store.state.projects.abcproject = { + store.state.projects['abc/def'] = { + id: 4, branches: { master: { commit: null, }, }, }; - }); - - it('calls the service', done => { spyOn(service, 'getBranchData').and.returnValue( Promise.resolve({ data: { @@ -36,14 +53,16 @@ describe('IDE store project actions', () => { }, }), ); + }); + it('calls the service', done => { store .dispatch('refreshLastCommitData', { projectId: store.state.currentProjectId, branchId: store.state.currentBranchId, }) .then(() => { - expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master'); + expect(service.getBranchData).toHaveBeenCalledWith('abc/def', 'master'); done(); }) @@ -53,16 +72,118 @@ describe('IDE store project actions', () => { it('commits getBranchData', done => { testAction( refreshLastCommitData, - {}, - {}, - [{ - type: 'SET_BRANCH_COMMIT', - payload: { - projectId: 'abcproject', - branchId: 'master', - commit: { id: '123' }, + { + projectId: store.state.currentProjectId, + branchId: store.state.currentBranchId, + }, + store.state, + [ + { + type: 'SET_BRANCH_COMMIT', + payload: { + projectId: 'abc/def', + branchId: 'master', + commit: { id: '123' }, + }, + }, + ], // mutations + [ + { + type: 'getLastCommitPipeline', + payload: { + projectId: 'abc/def', + projectIdNumber: store.state.projects['abc/def'].id, + branchId: 'master', + }, + }, + ], // action + done, + ); + }); + }); + + describe('pipelinePoll', () => { + let mock; + + beforeEach(() => { + setProjectState(); + jasmine.clock().install(); + mock = new MockAdapter(axios); + mock + .onGet('/abc/def/commit/abc123def456ghi789jkl/pipelines') + .reply(200, { data: { foo: 'bar' } }, { 'poll-interval': '10000' }); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + mock.restore(); + store.dispatch('stopPipelinePolling'); + }); + + it('calls service periodically', done => { + spyOn(axios, 'get').and.callThrough(); + spyOn(Visibility, 'hidden').and.returnValue(false); + + store + .dispatch('pipelinePoll') + .then(() => { + jasmine.clock().tick(1000); + + expect(axios.get).toHaveBeenCalled(); + expect(axios.get.calls.count()).toBe(1); + }) + .then(() => new Promise(resolve => requestAnimationFrame(resolve))) + .then(() => { + jasmine.clock().tick(10000); + expect(axios.get.calls.count()).toBe(2); + }) + .then(() => new Promise(resolve => requestAnimationFrame(resolve))) + .then(() => { + jasmine.clock().tick(10000); + expect(axios.get.calls.count()).toBe(3); + }) + .then(() => new Promise(resolve => requestAnimationFrame(resolve))) + .then(() => { + jasmine.clock().tick(10000); + expect(axios.get.calls.count()).toBe(4); + }) + + .then(done) + .catch(done.fail); + }); + }); + + describe('pollSuccessCallBack', () => { + beforeEach(() => { + setProjectState(); + }); + + it('commits correct pipeline', done => { + testAction( + pollSuccessCallBack, + fullPipelinesResponse, + store.state, + [ + { + type: 'SET_LAST_COMMIT_PIPELINE', + payload: { + projectId: 'abc/def', + branchId: 'master', + pipeline: { + id: '50', + commit: { + id: 'abc123def456ghi789jkl', + }, + details: { + status: { + icon: 'status_passed', + text: 'passed', + }, + }, + }, + }, }, - }], // mutations + ], // mutations [], // action done, ); diff --git a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js new file mode 100644 index 00000000000..85fbcf8084b --- /dev/null +++ b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js @@ -0,0 +1,289 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import actions, { + requestLatestPipeline, + receiveLatestPipelineError, + receiveLatestPipelineSuccess, + fetchLatestPipeline, + requestJobs, + receiveJobsError, + receiveJobsSuccess, + fetchJobs, +} from '~/ide/stores/modules/pipelines/actions'; +import state from '~/ide/stores/modules/pipelines/state'; +import * as types from '~/ide/stores/modules/pipelines/mutation_types'; +import testAction from '../../../../helpers/vuex_action_helper'; +import { pipelines, jobs } from '../../../mock_data'; + +describe('IDE pipelines actions', () => { + let mockedState; + let mock; + + beforeEach(() => { + mockedState = state(); + mock = new MockAdapter(axios); + + gon.api_version = 'v4'; + mockedState.currentProjectId = 'test/project'; + }); + + afterEach(() => { + mock.restore(); + }); + + describe('requestLatestPipeline', () => { + it('commits request', done => { + testAction( + requestLatestPipeline, + null, + mockedState, + [{ type: types.REQUEST_LATEST_PIPELINE }], + [], + done, + ); + }); + }); + + describe('receiveLatestPipelineError', () => { + it('commits error', done => { + testAction( + receiveLatestPipelineError, + null, + mockedState, + [{ type: types.RECEIVE_LASTEST_PIPELINE_ERROR }], + [], + done, + ); + }); + + it('creates flash message', () => { + const flashSpy = spyOnDependency(actions, 'flash'); + + receiveLatestPipelineError({ commit() {} }); + + expect(flashSpy).toHaveBeenCalled(); + }); + }); + + describe('receiveLatestPipelineSuccess', () => { + it('commits pipeline', done => { + testAction( + receiveLatestPipelineSuccess, + pipelines[0], + mockedState, + [{ type: types.RECEIVE_LASTEST_PIPELINE_SUCCESS, payload: pipelines[0] }], + [], + done, + ); + }); + }); + + describe('fetchLatestPipeline', () => { + describe('success', () => { + beforeEach(() => { + mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines(.*)/).replyOnce(200, pipelines); + }); + + it('dispatches request', done => { + testAction( + fetchLatestPipeline, + '123', + mockedState, + [], + [{ type: 'requestLatestPipeline' }, { type: 'receiveLatestPipelineSuccess' }], + done, + ); + }); + + it('dispatches success with latest pipeline', done => { + testAction( + fetchLatestPipeline, + '123', + mockedState, + [], + [ + { type: 'requestLatestPipeline' }, + { type: 'receiveLatestPipelineSuccess', payload: pipelines[0] }, + ], + done, + ); + }); + + it('calls axios with correct params', () => { + const apiSpy = spyOn(axios, 'get').and.callThrough(); + + fetchLatestPipeline({ dispatch() {}, rootState: state }, '123'); + + expect(apiSpy).toHaveBeenCalledWith(jasmine.anything(), { + params: { + sha: '123', + per_page: '1', + }, + }); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines(.*)/).replyOnce(500); + }); + + it('dispatches error', done => { + testAction( + fetchLatestPipeline, + '123', + mockedState, + [], + [{ type: 'requestLatestPipeline' }, { type: 'receiveLatestPipelineError' }], + done, + ); + }); + }); + }); + + describe('requestJobs', () => { + it('commits request', done => { + testAction(requestJobs, null, mockedState, [{ type: types.REQUEST_JOBS }], [], done); + }); + }); + + describe('receiveJobsError', () => { + it('commits error', done => { + testAction( + receiveJobsError, + null, + mockedState, + [{ type: types.RECEIVE_JOBS_ERROR }], + [], + done, + ); + }); + + it('creates flash message', () => { + const flashSpy = spyOnDependency(actions, 'flash'); + + receiveJobsError({ commit() {} }); + + expect(flashSpy).toHaveBeenCalled(); + }); + }); + + describe('receiveJobsSuccess', () => { + it('commits jobs', done => { + testAction( + receiveJobsSuccess, + jobs, + mockedState, + [{ type: types.RECEIVE_JOBS_SUCCESS, payload: jobs }], + [], + done, + ); + }); + }); + + describe('fetchJobs', () => { + let page = ''; + + beforeEach(() => { + mockedState.latestPipeline = pipelines[0]; + }); + + describe('success', () => { + beforeEach(() => { + mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines\/(.*)\/jobs/).replyOnce(() => [ + 200, + jobs, + { + 'x-next-page': page, + }, + ]); + }); + + it('dispatches request', done => { + testAction( + fetchJobs, + null, + mockedState, + [], + [{ type: 'requestJobs' }, { type: 'receiveJobsSuccess' }], + done, + ); + }); + + it('dispatches success with latest pipeline', done => { + testAction( + fetchJobs, + null, + mockedState, + [], + [{ type: 'requestJobs' }, { type: 'receiveJobsSuccess', payload: jobs }], + done, + ); + }); + + it('dispatches twice for both pages', done => { + page = '2'; + + testAction( + fetchJobs, + null, + mockedState, + [], + [ + { type: 'requestJobs' }, + { type: 'receiveJobsSuccess', payload: jobs }, + { type: 'fetchJobs', payload: '2' }, + { type: 'requestJobs' }, + { type: 'receiveJobsSuccess', payload: jobs }, + ], + done, + ); + }); + + it('calls axios with correct URL', () => { + const apiSpy = spyOn(axios, 'get').and.callThrough(); + + fetchJobs({ dispatch() {}, state: mockedState, rootState: mockedState }); + + expect(apiSpy).toHaveBeenCalledWith('/api/v4/projects/test%2Fproject/pipelines/1/jobs', { + params: { page: '1' }, + }); + }); + + it('calls axios with page next page', () => { + const apiSpy = spyOn(axios, 'get').and.callThrough(); + + fetchJobs({ dispatch() {}, state: mockedState, rootState: mockedState }); + + expect(apiSpy).toHaveBeenCalledWith('/api/v4/projects/test%2Fproject/pipelines/1/jobs', { + params: { page: '1' }, + }); + + page = '2'; + + fetchJobs({ dispatch() {}, state: mockedState, rootState: mockedState }, page); + + expect(apiSpy).toHaveBeenCalledWith('/api/v4/projects/test%2Fproject/pipelines/1/jobs', { + params: { page: '2' }, + }); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines(.*)/).replyOnce(500); + }); + + it('dispatches error', done => { + testAction( + fetchJobs, + null, + mockedState, + [], + [{ type: 'requestJobs' }, { type: 'receiveJobsError' }], + done, + ); + }); + }); + }); +}); diff --git a/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js b/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js new file mode 100644 index 00000000000..b2a7e8a9025 --- /dev/null +++ b/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js @@ -0,0 +1,71 @@ +import * as getters from '~/ide/stores/modules/pipelines/getters'; +import state from '~/ide/stores/modules/pipelines/state'; + +describe('IDE pipeline getters', () => { + let mockedState; + + beforeEach(() => { + mockedState = state(); + }); + + describe('hasLatestPipeline', () => { + it('returns false when loading is true', () => { + mockedState.isLoadingPipeline = true; + + expect(getters.hasLatestPipeline(mockedState)).toBe(false); + }); + + it('returns false when pipelines is null', () => { + mockedState.latestPipeline = null; + + expect(getters.hasLatestPipeline(mockedState)).toBe(false); + }); + + it('returns false when loading is true & pipelines is null', () => { + mockedState.latestPipeline = null; + mockedState.isLoadingPipeline = true; + + expect(getters.hasLatestPipeline(mockedState)).toBe(false); + }); + + it('returns true when loading is false & pipelines is an object', () => { + mockedState.latestPipeline = { + id: 1, + }; + mockedState.isLoadingPipeline = false; + + expect(getters.hasLatestPipeline(mockedState)).toBe(true); + }); + }); + + describe('failedJobs', () => { + it('returns array of failed jobs', () => { + mockedState.stages = [ + { + title: 'test', + jobs: [{ id: 1, status: 'failed' }, { id: 2, status: 'success' }], + }, + { + title: 'build', + jobs: [{ id: 3, status: 'failed' }, { id: 4, status: 'failed' }], + }, + ]; + + expect(getters.failedJobs(mockedState).length).toBe(3); + expect(getters.failedJobs(mockedState)).toEqual([ + { + id: 1, + status: jasmine.anything(), + }, + { + id: 3, + status: jasmine.anything(), + }, + { + id: 4, + status: jasmine.anything(), + }, + ]); + }); + }); +}); diff --git a/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js new file mode 100644 index 00000000000..8262e916243 --- /dev/null +++ b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js @@ -0,0 +1,120 @@ +import mutations from '~/ide/stores/modules/pipelines/mutations'; +import state from '~/ide/stores/modules/pipelines/state'; +import * as types from '~/ide/stores/modules/pipelines/mutation_types'; +import { pipelines, jobs } from '../../../mock_data'; + +describe('IDE pipelines mutations', () => { + let mockedState; + + beforeEach(() => { + mockedState = state(); + }); + + describe(types.REQUEST_LATEST_PIPELINE, () => { + it('sets loading to true', () => { + mutations[types.REQUEST_LATEST_PIPELINE](mockedState); + + expect(mockedState.isLoadingPipeline).toBe(true); + }); + }); + + describe(types.RECEIVE_LASTEST_PIPELINE_ERROR, () => { + it('sets loading to false', () => { + mutations[types.RECEIVE_LASTEST_PIPELINE_ERROR](mockedState); + + expect(mockedState.isLoadingPipeline).toBe(false); + }); + }); + + describe(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, () => { + it('sets loading to false on success', () => { + mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, pipelines[0]); + + expect(mockedState.isLoadingPipeline).toBe(false); + }); + + it('sets latestPipeline', () => { + mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, pipelines[0]); + + expect(mockedState.latestPipeline).toEqual({ + id: pipelines[0].id, + status: pipelines[0].status, + }); + }); + + it('does not set latest pipeline if pipeline is null', () => { + mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, null); + + expect(mockedState.latestPipeline).toEqual(null); + }); + }); + + describe(types.REQUEST_JOBS, () => { + it('sets jobs loading to true', () => { + mutations[types.REQUEST_JOBS](mockedState); + + expect(mockedState.isLoadingJobs).toBe(true); + }); + }); + + describe(types.RECEIVE_JOBS_ERROR, () => { + it('sets jobs loading to false', () => { + mutations[types.RECEIVE_JOBS_ERROR](mockedState); + + expect(mockedState.isLoadingJobs).toBe(false); + }); + }); + + describe(types.RECEIVE_JOBS_SUCCESS, () => { + it('sets jobs loading to false on success', () => { + mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs); + + expect(mockedState.isLoadingJobs).toBe(false); + }); + + it('sets stages', () => { + mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs); + + expect(mockedState.stages.length).toBe(2); + expect(mockedState.stages).toEqual([ + { + title: 'test', + jobs: jasmine.anything(), + }, + { + title: 'build', + jobs: jasmine.anything(), + }, + ]); + }); + + it('sets jobs in stages', () => { + mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs); + + expect(mockedState.stages[0].jobs.length).toBe(3); + expect(mockedState.stages[1].jobs.length).toBe(1); + expect(mockedState.stages).toEqual([ + { + title: jasmine.anything(), + jobs: jobs.filter(job => job.stage === 'test').map(job => ({ + id: job.id, + name: job.name, + status: job.status, + stage: job.stage, + duration: job.duration, + })), + }, + { + title: jasmine.anything(), + jobs: jobs.filter(job => job.stage === 'build').map(job => ({ + id: job.id, + name: job.name, + status: job.status, + stage: job.stage, + duration: job.duration, + })), + }, + ]); + }); + }); +}); diff --git a/spec/javascripts/ide/stores/mutations/branch_spec.js b/spec/javascripts/ide/stores/mutations/branch_spec.js index 29eb859ddaf..f2f1f2a9a2e 100644 --- a/spec/javascripts/ide/stores/mutations/branch_spec.js +++ b/spec/javascripts/ide/stores/mutations/branch_spec.js @@ -37,4 +37,40 @@ describe('Multi-file store branch mutations', () => { expect(localState.projects.Example.branches.master.commit.title).toBe('Example commit'); }); }); + + describe('SET_LAST_COMMIT_PIPELINE', () => { + it('sets the pipeline for the last commit on current project', () => { + localState.projects = { + Example: { + branches: { + master: { + commit: {}, + }, + }, + }, + }; + + mutations.SET_LAST_COMMIT_PIPELINE(localState, { + projectId: 'Example', + branchId: 'master', + pipeline: { + id: '50', + details: { + status: { + icon: 'status_passed', + text: 'passed', + }, + }, + }, + }); + + expect(localState.projects.Example.branches.master.commit.pipeline.id).toBe('50'); + expect(localState.projects.Example.branches.master.commit.pipeline.details.status.text).toBe( + 'passed', + ); + expect(localState.projects.Example.branches.master.commit.pipeline.details.status.icon).toBe( + 'status_passed', + ); + }); + }); }); diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 0952356c2f4..648fb3e9bd3 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -974,7 +974,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; ).toBeFalsy(); expect( $tempNoteHeader - .find('.hidden-xs') + .find('.d-none.d-sm-block') .text() .trim(), ).toEqual(currentUserFullname); @@ -1020,7 +1020,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; const $tempNoteHeader = $tempNote.find('.note-header'); expect( $tempNoteHeader - .find('.hidden-xs') + .find('.d-none.d-sm-block') .text() .trim(), ).toEqual('Foo <script>alert("XSS")</script>'); diff --git a/spec/javascripts/pipelines/empty_state_spec.js b/spec/javascripts/pipelines/empty_state_spec.js index 71f77e5f42e..1e41a7bfe7c 100644 --- a/spec/javascripts/pipelines/empty_state_spec.js +++ b/spec/javascripts/pipelines/empty_state_spec.js @@ -29,7 +29,7 @@ describe('Pipelines Empty State', () => { expect( component.$el.querySelector('p').innerHTML.trim().replace(/\n+\s+/m, ' ').replace(/\s\s+/g, ' '), - ).toContain('Continous Integration can help catch bugs by running your tests automatically,'); + ).toContain('Continuous Integration can help catch bugs by running your tests automatically,'); expect( component.$el.querySelector('p').innerHTML.trim().replace(/\n+\s+/m, ' ').replace(/\s\s+/g, ' '), diff --git a/spec/javascripts/pipelines/mock_data.js b/spec/javascripts/pipelines/mock_data.js index a5a200973d7..03ead6cd8ba 100644 --- a/spec/javascripts/pipelines/mock_data.js +++ b/spec/javascripts/pipelines/mock_data.js @@ -217,7 +217,7 @@ export const pipelineWithStages = { browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411442/artifacts/browse', }, { - name: 'codequality', + name: 'code_quality', expired: false, expire_at: '2018-04-18T14:16:24.484Z', path: '/gitlab-org/gitlab-ee/-/jobs/62411441/artifacts/download', diff --git a/spec/javascripts/pipelines/pipelines_table_row_spec.js b/spec/javascripts/pipelines/pipelines_table_row_spec.js index 05ca4cb9044..68043b91bd0 100644 --- a/spec/javascripts/pipelines/pipelines_table_row_spec.js +++ b/spec/javascripts/pipelines/pipelines_table_row_spec.js @@ -182,7 +182,16 @@ describe('Pipelines Table Row', () => { }); component.$el.querySelector('.js-pipelines-cancel-button').click(); - expect(component.isCancelling).toEqual(true); + }); + + it('renders a loading icon when `cancelingPipeline` matches pipeline id', done => { + component.cancelingPipeline = pipeline.id; + component.$nextTick() + .then(() => { + expect(component.isCancelling).toEqual(true); + }) + .then(done) + .catch(done.fail); }); }); }); diff --git a/spec/javascripts/projects_dropdown/components/search_spec.js b/spec/javascripts/projects_dropdown/components/search_spec.js index 601264258c2..427f5024e3a 100644 --- a/spec/javascripts/projects_dropdown/components/search_spec.js +++ b/spec/javascripts/projects_dropdown/components/search_spec.js @@ -92,7 +92,6 @@ describe('SearchComponent', () => { const inputEl = vm.$el.querySelector('input.form-control'); expect(vm.$el.classList.contains('search-input-container')).toBeTruthy(); - expect(vm.$el.classList.contains('hidden-xs')).toBeTruthy(); expect(inputEl).not.toBe(null); expect(inputEl.getAttribute('placeholder')).toBe('Search your projects'); expect(vm.$el.querySelector('.search-icon')).toBeDefined(); diff --git a/spec/javascripts/sidebar/sidebar_move_issue_spec.js b/spec/javascripts/sidebar/sidebar_move_issue_spec.js index 00847df4b60..8f35b9ca437 100644 --- a/spec/javascripts/sidebar/sidebar_move_issue_spec.js +++ b/spec/javascripts/sidebar/sidebar_move_issue_spec.js @@ -14,7 +14,9 @@ describe('SidebarMoveIssue', function () { this.$content = $(` <div class="dropdown"> <div class="js-toggle"></div> - <div class="dropdown-content"></div> + <div class="dropdown-menu"> + <div class="dropdown-content"></div> + </div> <div class="js-confirm-button"></div> </div> `); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js index db27aa144d6..00f4f2d7c39 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js @@ -1,12 +1,12 @@ import Vue from 'vue'; -import authorComponent from '~/vue_merge_request_widget/components/mr_widget_author.vue'; +import MrWidgetAuthor from '~/vue_merge_request_widget/components/mr_widget_author.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; -describe('MRWidgetAuthor', () => { +describe('MrWidgetAuthor', () => { let vm; beforeEach(() => { - const Component = Vue.extend(authorComponent); + const Component = Vue.extend(MrWidgetAuthor); vm = mountComponent(Component, { author: { diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb index a547988d631..c73c6023b60 100644 --- a/spec/lib/api/helpers/pagination_spec.rb +++ b/spec/lib/api/helpers/pagination_spec.rb @@ -7,7 +7,203 @@ describe API::Helpers::Pagination do Class.new.include(described_class).new end - describe '#paginate' do + describe '#paginate (keyset pagination)' do + let(:value) { spy('return value') } + + before do + allow(value).to receive(:to_query).and_return(value) + + allow(subject).to receive(:header).and_return(value) + allow(subject).to receive(:params).and_return(value) + allow(subject).to receive(:request).and_return(value) + end + + context 'when resource can be paginated' do + let!(:projects) do + [ + create(:project, name: 'One'), + create(:project, name: 'Two'), + create(:project, name: 'Three') + ].sort_by { |e| -e.id } # sort by id desc (this is the default sort order for the API) + end + + describe 'first page' do + before do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', per_page: 2 }) + end + + it 'returns appropriate amount of resources' do + expect(subject.paginate(resource).count).to eq 2 + end + + it 'returns the first two records (by id desc)' do + expect(subject.paginate(resource)).to eq(projects[0..1]) + end + + it 'adds appropriate headers' do + expect_header('X-Per-Page', '2') + expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[1].id}&pagination=keyset&per_page=2") + + expect_header('Link', anything) do |_key, val| + expect(val).to include('rel="next"') + end + + subject.paginate(resource) + end + end + + describe 'second page' do + before do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', per_page: 2, ks_prev_id: projects[1].id }) + end + + it 'returns appropriate amount of resources' do + expect(subject.paginate(resource).count).to eq 1 + end + + it 'returns the third record' do + expect(subject.paginate(resource)).to eq(projects[2..2]) + end + + it 'adds appropriate headers' do + expect_header('X-Per-Page', '2') + expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[2].id}&pagination=keyset&per_page=2") + + expect_header('Link', anything) do |_key, val| + expect(val).to include('rel="next"') + end + + subject.paginate(resource) + end + end + + describe 'third page' do + before do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', per_page: 2, ks_prev_id: projects[2].id }) + end + + it 'returns appropriate amount of resources' do + expect(subject.paginate(resource).count).to eq 0 + end + + it 'adds appropriate headers' do + expect_header('X-Per-Page', '2') + expect(subject).not_to receive(:header).with('Link') + + subject.paginate(resource) + end + end + + context 'if order' do + context 'is not present' do + before do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', per_page: 2 }) + end + + it 'is not present it adds default order(:id) desc' do + resource.order_values = [] + + paginated_relation = subject.paginate(resource) + + expect(resource.order_values).to be_empty + expect(paginated_relation.order_values).to be_present + expect(paginated_relation.order_values.size).to eq(1) + expect(paginated_relation.order_values.first).to be_descending + expect(paginated_relation.order_values.first.expr.name).to eq :id + end + end + + context 'is present' do + let(:resource) { Project.all.order(name: :desc) } + let!(:projects) do + [ + create(:project, name: 'One'), + create(:project, name: 'Two'), + create(:project, name: 'Three'), + create(:project, name: 'Three'), # Note the duplicate name + create(:project, name: 'Four'), + create(:project, name: 'Five'), + create(:project, name: 'Six') + ] + + # if we sort this by name descending, id descending, this yields: + # { + # 2 => "Two", + # 4 => "Three", + # 3 => "Three", + # 7 => "Six", + # 1 => "One", + # 5 => "Four", + # 6 => "Five" + # } + # + # (key is the id) + end + + it 'it also orders by primary key' do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', per_page: 2 }) + paginated_relation = subject.paginate(resource) + + expect(paginated_relation.order_values).to be_present + expect(paginated_relation.order_values.size).to eq(2) + expect(paginated_relation.order_values.first).to be_descending + expect(paginated_relation.order_values.first.expr.name).to eq :name + expect(paginated_relation.order_values.second).to be_descending + expect(paginated_relation.order_values.second.expr.name).to eq :id + end + + it 'it returns the right records (first page)' do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', per_page: 2 }) + result = subject.paginate(resource) + + expect(result.first).to eq(projects[1]) + expect(result.second).to eq(projects[3]) + end + + it 'it returns the right records (second page)' do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', ks_prev_id: projects[3].id, ks_prev_name: projects[3].name, per_page: 2 }) + result = subject.paginate(resource) + + expect(result.first).to eq(projects[2]) + expect(result.second).to eq(projects[6]) + end + + it 'it returns the right records (third page), note increased per_page' do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', ks_prev_id: projects[6].id, ks_prev_name: projects[6].name, per_page: 5 }) + result = subject.paginate(resource) + + expect(result.size).to eq(3) + expect(result.first).to eq(projects[0]) + expect(result.second).to eq(projects[4]) + expect(result.last).to eq(projects[5]) + end + + it 'it returns the right link to the next page' do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', ks_prev_id: projects[3].id, ks_prev_name: projects[3].name, per_page: 2 }) + expect_header('X-Per-Page', '2') + expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[6].id}&ks_prev_name=#{projects[6].name}&pagination=keyset&per_page=2") + + expect_header('Link', anything) do |_key, val| + expect(val).to include('rel="next"') + end + + subject.paginate(resource) + end + end + end + end + end + + describe '#paginate (default offset-based pagination)' do let(:value) { spy('return value') } before do @@ -146,14 +342,14 @@ describe API::Helpers::Pagination do end end end + end - def expect_header(*args, &block) - expect(subject).to receive(:header).with(*args, &block) - end + def expect_header(*args, &block) + expect(subject).to receive(:header).with(*args, &block) + end - def expect_message(method) - expect(subject).to receive(method) - .at_least(:once).and_return(value) - end + def expect_message(method) + expect(subject).to receive(method) + .at_least(:once).and_return(value) end end diff --git a/spec/lib/api/helpers/related_resources_helpers_spec.rb b/spec/lib/api/helpers/related_resources_helpers_spec.rb index b918301f1cb..66af7f81535 100644 --- a/spec/lib/api/helpers/related_resources_helpers_spec.rb +++ b/spec/lib/api/helpers/related_resources_helpers_spec.rb @@ -9,9 +9,9 @@ describe API::Helpers::RelatedResourcesHelpers do let(:path) { '/api/v4/awesome_endpoint' } subject(:url) { helpers.expose_url(path) } - def stub_default_url_options(protocol: 'http', host: 'example.com', port: nil) + def stub_default_url_options(protocol: 'http', host: 'example.com', port: nil, script_name: '') expect(Gitlab::Application.routes).to receive(:default_url_options) - .and_return(protocol: protocol, host: host, port: port) + .and_return(protocol: protocol, host: host, port: port, script_name: script_name) end it 'respects the protocol if it is HTTP' do @@ -37,5 +37,17 @@ describe API::Helpers::RelatedResourcesHelpers do is_expected.to start_with('http://example.com:8080/') end + + it 'includes the relative_url before the path if it is set' do + stub_default_url_options(script_name: '/gitlab') + + is_expected.to start_with('http://example.com/gitlab/api/v4') + end + + it 'includes the path after the host' do + stub_default_url_options + + is_expected.to start_with('http://example.com/api/v4') + end end end diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb index 84688845fa5..23c04a1a101 100644 --- a/spec/lib/backup/manager_spec.rb +++ b/spec/lib/backup/manager_spec.rb @@ -5,6 +5,8 @@ describe Backup::Manager do let(:progress) { StringIO.new } + subject { described_class.new(progress) } + before do allow(progress).to receive(:puts) allow(progress).to receive(:print) diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb index b1ea9c0b622..a44243ac82d 100644 --- a/spec/lib/backup/repository_spec.rb +++ b/spec/lib/backup/repository_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe Backup::Repository do let(:progress) { StringIO.new } let!(:project) { create(:project, :wiki_repo) } + subject { described_class.new(progress) } before do allow(progress).to receive(:puts) @@ -24,14 +25,12 @@ describe Backup::Repository do end it 'does not raise error' do - expect { described_class.new.dump }.not_to raise_error + expect { subject.dump }.not_to raise_error end end end describe '#restore' do - subject { described_class.new } - let(:timestamp) { Time.utc(2017, 3, 22) } let(:temp_dirs) do Gitlab.config.repositories.storages.map do |name, storage| @@ -49,14 +48,14 @@ describe Backup::Repository do describe 'command failure' do before do - allow(Gitlab::Popen).to receive(:popen).and_return(['error', 1]) + allow_any_instance_of(Gitlab::Shell).to receive(:create_repository).and_return(false) end context 'hashed storage' do it 'shows the appropriate error' do subject.restore - expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} (#{project.disk_path}) - error") + expect(progress).to have_received(:puts).with("[Failed] restoring #{project.full_path} repository") end end @@ -66,33 +65,10 @@ describe Backup::Repository do it 'shows the appropriate error' do subject.restore - expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} - error") + expect(progress).to have_received(:puts).with("[Failed] restoring #{project.full_path} repository") end end end - - describe 'folders without permissions' do - before do - allow(FileUtils).to receive(:mv).and_raise(Errno::EACCES) - end - - it 'shows error message' do - expect(subject).to receive(:access_denied_error) - subject.restore - end - end - - describe 'folder that is a mountpoint' do - before do - allow(FileUtils).to receive(:mv).and_raise(Errno::EBUSY) - end - - it 'shows error message' do - expect(subject).to receive(:resource_busy_error).and_call_original - - expect { subject.restore }.to raise_error(/is a mountpoint/) - end - end end describe '#empty_repo?' do @@ -102,20 +78,20 @@ describe Backup::Repository do it 'invalidates the emptiness cache' do expect(wiki.repository).to receive(:expire_emptiness_caches).once - described_class.new.send(:empty_repo?, wiki) + subject.send(:empty_repo?, wiki) end context 'wiki repo has content' do let!(:wiki_page) { create(:wiki_page, wiki: wiki) } it 'returns true, regardless of bad cache value' do - expect(described_class.new.send(:empty_repo?, wiki)).to be(false) + expect(subject.send(:empty_repo?, wiki)).to be(false) end end context 'wiki repo does not have content' do it 'returns true, regardless of bad cache value' do - expect(described_class.new.send(:empty_repo?, wiki)).to be_truthy + expect(subject.send(:empty_repo?, wiki)).to be_truthy end end end diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 392905076dc..b30f3661e70 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -59,7 +59,7 @@ describe Banzai::Filter::LabelReferenceFilter do describe 'label span element' do it 'includes default classes' do doc = reference_filter("Label #{reference}") - expect(doc.css('a span').first.attr('class')).to eq 'label color-label has-tooltip' + expect(doc.css('a span').first.attr('class')).to eq 'badge color-label has-tooltip' end it 'includes a style attribute' do diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index 4ddcbd7eb66..19028495f52 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -1,17 +1,15 @@ require 'spec_helper' describe Gitlab::CurrentSettings do - include StubENV - before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') end - describe '#current_application_settings' do + describe '#current_application_settings', :use_clean_rails_memory_store_caching do it 'allows keys to be called directly' do db_settings = create(:application_setting, - home_page_url: 'http://mydomain.com', - signup_enabled: false) + home_page_url: 'http://mydomain.com', + signup_enabled: false) expect(described_class.home_page_url).to eq(db_settings.home_page_url) expect(described_class.signup_enabled?).to be_falsey @@ -19,46 +17,54 @@ describe Gitlab::CurrentSettings do expect(described_class.metrics_sample_interval).to be(15) end - context 'with DB available' do + context 'when ENV["IN_MEMORY_APPLICATION_SETTINGS"] is true' do before do - # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(true)` causes issues - # during the initialization phase of the test suite, so instead let's mock the internals of it - allow(ActiveRecord::Base.connection).to receive(:active?).and_return(true) - allow(ActiveRecord::Base.connection).to receive(:table_exists?).and_call_original - allow(ActiveRecord::Base.connection).to receive(:table_exists?).with('application_settings').and_return(true) + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'true') end - it 'attempts to use cached values first' do - expect(ApplicationSetting).to receive(:cached) + it 'returns an in-memory ApplicationSetting object' do + expect(ApplicationSetting).not_to receive(:current) expect(described_class.current_application_settings).to be_a(ApplicationSetting) + expect(described_class.current_application_settings).not_to be_persisted end + end - it 'falls back to DB if Redis returns an empty value' do - expect(ApplicationSetting).to receive(:cached).and_return(nil) - expect(ApplicationSetting).to receive(:last).and_call_original.twice - - expect(described_class.current_application_settings).to be_a(ApplicationSetting) + context 'with DB unavailable' do + before do + # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(false)` causes issues + # during the initialization phase of the test suite, so instead let's mock the internals of it + allow(ActiveRecord::Base.connection).to receive(:active?).and_return(false) end - it 'falls back to DB if Redis fails' do - db_settings = ApplicationSetting.create!(ApplicationSetting.defaults) + it 'returns an in-memory ApplicationSetting object' do + expect(ApplicationSetting).not_to receive(:current) - expect(ApplicationSetting).to receive(:cached).and_raise(::Redis::BaseError) - expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(Redis::BaseError) + expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings) + end + end - expect(described_class.current_application_settings).to eq(db_settings) + context 'with DB available' do + # This method returns the ::ApplicationSetting.defaults hash + # but with respect of custom attribute accessors of ApplicationSetting model + def settings_from_defaults + ar_wrapped_defaults = ::ApplicationSetting.build_from_defaults.attributes + ar_wrapped_defaults.slice(*::ApplicationSetting.defaults.keys) end - it 'creates default ApplicationSettings if none are present' do - expect(ApplicationSetting).to receive(:cached).and_raise(::Redis::BaseError) - expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(Redis::BaseError) + before do + # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(true)` causes issues + # during the initialization phase of the test suite, so instead let's mock the internals of it + allow(ActiveRecord::Base.connection).to receive(:active?).and_return(true) + allow(ActiveRecord::Base.connection).to receive(:cached_table_exists?).with('application_settings').and_return(true) + end + it 'creates default ApplicationSettings if none are present' do settings = described_class.current_application_settings expect(settings).to be_a(ApplicationSetting) expect(settings).to be_persisted - expect(settings).to have_attributes(ApplicationSetting.defaults) + expect(settings).to have_attributes(settings_from_defaults) end context 'with migrations pending' do @@ -69,7 +75,7 @@ describe Gitlab::CurrentSettings do it 'returns an in-memory ApplicationSetting object' do settings = described_class.current_application_settings - expect(settings).to be_a(OpenStruct) + expect(settings).to be_a(Gitlab::FakeApplicationSettings) expect(settings.sign_in_enabled?).to eq(settings.sign_in_enabled) expect(settings.sign_up_enabled?).to eq(settings.sign_up_enabled) end @@ -81,7 +87,7 @@ describe Gitlab::CurrentSettings do settings = described_class.current_application_settings app_defaults = ApplicationSetting.last - expect(settings).to be_a(OpenStruct) + expect(settings).to be_a(Gitlab::FakeApplicationSettings) expect(settings.home_page_url).to eq(db_settings.home_page_url) expect(settings.signup_enabled?).to be_falsey expect(settings.signup_enabled).to be_falsey @@ -91,34 +97,29 @@ describe Gitlab::CurrentSettings do settings.each { |key, _| expect(settings[key]).to eq(app_defaults[key]) } end end - end - - context 'with DB unavailable' do - before do - # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(false)` causes issues - # during the initialization phase of the test suite, so instead let's mock the internals of it - allow(ActiveRecord::Base.connection).to receive(:active?).and_return(false) - end - it 'returns an in-memory ApplicationSetting object' do - expect(ApplicationSetting).not_to receive(:current) - expect(ApplicationSetting).not_to receive(:last) + context 'when ApplicationSettings.current is present' do + it 'returns the existing application settings' do + expect(ApplicationSetting).to receive(:current).and_return(:current_settings) - expect(described_class.current_application_settings).to be_a(OpenStruct) + expect(described_class.current_application_settings).to eq(:current_settings) + end end - end - context 'when ENV["IN_MEMORY_APPLICATION_SETTINGS"] is true' do - before do - stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'true') + context 'when the application_settings table does not exists' do + it 'returns an in-memory ApplicationSetting object' do + expect(ApplicationSetting).to receive(:create_from_defaults).and_raise(ActiveRecord::StatementInvalid) + + expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings) + end end - it 'returns an in-memory ApplicationSetting object' do - expect(ApplicationSetting).not_to receive(:current) - expect(ApplicationSetting).not_to receive(:last) + context 'when the application_settings table is not fully migrated' do + it 'returns an in-memory ApplicationSetting object' do + expect(ApplicationSetting).to receive(:create_from_defaults).and_raise(ActiveRecord::UnknownAttributeError) - expect(described_class.current_application_settings).to be_a(ApplicationSetting) - expect(described_class.current_application_settings).not_to be_persisted + expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings) + end end end end diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb index 452249210b0..154ab4b3856 100644 --- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb @@ -46,6 +46,20 @@ describe Gitlab::Email::Handler::CreateIssueHandler do expect(issue.description).to eq('') end end + + context "when there are quotes in email" do + let(:email_raw) { fixture_file("emails/valid_new_issue_with_quote.eml") } + + it "creates a new issue" do + expect { receiver.execute }.to change { project.issues.count }.by(1) + issue = project.issues.last + + expect(issue.author).to eq(user) + expect(issue.title).to eq('New Issue by email') + expect(issue.description).to include('reply by email') + expect(issue.description).to include('> this is a quote') + end + end end context "something is wrong" do diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb index e21a998adfe..0989188f7ee 100644 --- a/spec/lib/gitlab/email/reply_parser_spec.rb +++ b/spec/lib/gitlab/email/reply_parser_spec.rb @@ -3,8 +3,8 @@ require "spec_helper" # Inspired in great part by Discourse's Email::Receiver describe Gitlab::Email::ReplyParser do describe '#execute' do - def test_parse_body(mail_string) - described_class.new(Mail::Message.new(mail_string)).execute + def test_parse_body(mail_string, params = {}) + described_class.new(Mail::Message.new(mail_string), params).execute end it "returns an empty string if the message is blank" do @@ -212,5 +212,19 @@ describe Gitlab::Email::ReplyParser do it "does not wrap links with no href in unnecessary brackets" do expect(test_parse_body(fixture_file("emails/html_empty_link.eml"))).to eq("no brackets!") end + + it "does not trim reply if trim_reply option is false" do + expect(test_parse_body(fixture_file("emails/valid_new_issue_with_quote.eml"), { trim_reply: false })) + .to eq( + <<-BODY.strip_heredoc.chomp + The reply by email functionality should be extended to allow creating a new issue by email. + even when the email is forwarded to the project which may include lines that begin with ">" + + there should be a quote below this line: + + > this is a quote + BODY + ) + end end end diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 6d63749296e..4f64f2bd6b4 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -6760,26 +6760,6 @@ "wiki_page_events": true }, { - "id": 92, - "title": "Gemnasium", - "project_id": 5, - "created_at": "2016-06-14T15:01:51.202Z", - "updated_at": "2016-06-14T15:01:51.202Z", - "active": false, - "properties": {}, - "template": false, - "push_events": true, - "issues_events": true, - "merge_requests_events": true, - "tag_push_events": true, - "note_events": true, - "job_events": true, - "type": "GemnasiumService", - "category": "common", - "default": false, - "wiki_page_events": true - }, - { "id": 91, "title": "Flowdock", "project_id": 5, diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index da146e24893..d63f448883b 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -8,6 +8,66 @@ describe Gitlab do expect(described_class.root).to eq(Pathname.new(File.expand_path('../..', __dir__))) end end + describe '.revision' do + let(:cmd) { %W[#{described_class.config.git.bin_path} log --pretty=format:%h -n 1] } + + around do |example| + described_class.instance_variable_set(:@_revision, nil) + example.run + described_class.instance_variable_set(:@_revision, nil) + end + + context 'when a REVISION file exists' do + before do + expect(File).to receive(:exist?) + .with(described_class.root.join('REVISION')) + .and_return(true) + end + + it 'returns the actual Git revision' do + expect(File).to receive(:read) + .with(described_class.root.join('REVISION')) + .and_return("abc123\n") + + expect(described_class.revision).to eq('abc123') + end + + it 'memoizes the revision' do + expect(File).to receive(:read) + .once + .with(described_class.root.join('REVISION')) + .and_return("abc123\n") + + 2.times { described_class.revision } + end + end + + context 'when no REVISION file exist' do + context 'when the Git command succeeds' do + before do + expect(Gitlab::Popen).to receive(:popen_with_detail) + .with(cmd) + .and_return(Gitlab::Popen::Result.new(cmd, 'abc123', '', double(success?: true))) + end + + it 'returns the actual Git revision' do + expect(described_class.revision).to eq('abc123') + end + end + + context 'when the Git command fails' do + before do + expect(Gitlab::Popen).to receive(:popen_with_detail) + .with(cmd) + .and_return(Gitlab::Popen::Result.new(cmd, '', 'fatal: Not a git repository', double('Process::Status', success?: false))) + end + + it 'returns "Unknown"' do + expect(described_class.revision).to eq('Unknown') + end + end + end + end describe '.com?' do it 'is true when on GitLab.com' do diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 8a52c151cc4..f310a6854d5 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -390,6 +390,46 @@ describe Notify do end end + describe 'that are unmergeable' do + set(:merge_request) do + create(:merge_request, :conflict, + source_project: project, + target_project: project, + author: current_user, + assignee: assignee, + description: 'Awesome description') + end + + subject { described_class.merge_request_unmergeable_email(recipient.id, merge_request.id) } + + it_behaves_like 'a multiple recipients email' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { merge_request } + end + it_behaves_like 'it should show Gmail Actions View Merge request link' + it_behaves_like 'an unsubscribeable thread' + + it 'is sent as the merge request author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(merge_request.author.name) + expect(sender.address).to eq(gitlab_sender) + end + + it 'has the correct subject and body' do + reasons = %w[foo bar] + + allow_any_instance_of(MergeRequestPresenter).to receive(:unmergeable_reasons).and_return(reasons) + aggregate_failures do + is_expected.to have_referable_subject(merge_request, reply: true) + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) + is_expected.to have_body_text('reasons:') + reasons.each do |reason| + is_expected.to have_body_text(reason) + end + end + end + end + shared_examples 'a push to an existing merge request' do let(:push_user) { create(:user) } diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb index 5489c17bd82..77b07cf1ac9 100644 --- a/spec/models/appearance_spec.rb +++ b/spec/models/appearance_spec.rb @@ -3,34 +3,11 @@ require 'rails_helper' describe Appearance do subject { build(:appearance) } - it { is_expected.to be_valid } + it { include(CacheableAttributes) } + it { expect(described_class.current_without_cache).to eq(described_class.first) } it { is_expected.to have_many(:uploads) } - describe '.current', :use_clean_rails_memory_store_caching do - let!(:appearance) { create(:appearance) } - - it 'returns the current appearance row' do - expect(described_class.current).to eq(appearance) - end - - it 'caches the result' do - expect(described_class).to receive(:first).once - - 2.times { described_class.current } - end - end - - describe '#flush_redis_cache' do - it 'flushes the cache in Redis' do - appearance = create(:appearance) - - expect(Rails.cache).to receive(:delete).with(described_class::CACHE_KEY) - - appearance.flush_redis_cache - end - end - describe '#single_appearance_row' do it 'adds an error when more than 1 row exists' do create(:appearance) diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 10d6109cae7..7e47043a1cb 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -3,6 +3,9 @@ require 'spec_helper' describe ApplicationSetting do let(:setting) { described_class.create_from_defaults } + it { include(CacheableAttributes) } + it { expect(described_class.current_without_cache).to eq(described_class.last) } + it { expect(setting).to be_valid } it { expect(setting.uuid).to be_present } it { expect(setting).to have_db_column(:auto_devops_enabled) } @@ -318,33 +321,6 @@ describe ApplicationSetting do end end - describe '.current' do - context 'redis unavailable' do - it 'returns an ApplicationSetting' do - allow(Rails.cache).to receive(:fetch).and_call_original - allow(described_class).to receive(:last).and_return(:last) - expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(ArgumentError) - - expect(described_class.current).to eq(:last) - end - end - - context 'when an ApplicationSetting is not yet present' do - it 'does not cache nil object' do - # when missing settings a nil object is returned, but not cached - allow(described_class).to receive(:last).and_return(nil).twice - expect(described_class.current).to be_nil - - # when the settings are set the method returns a valid object - allow(described_class).to receive(:last).and_return(:last) - expect(described_class.current).to eq(:last) - - # subsequent calls get everything from cache - expect(described_class.current).to eq(:last) - end - end - end - context 'restrict creating duplicates' do before do described_class.create_from_defaults diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 67919a0d6b4..c89655f4058 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1546,7 +1546,7 @@ describe Ci::Build do { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true }, { key: 'CI_SERVER_NAME', value: 'GitLab', public: true }, { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true }, - { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true }, + { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true }, { key: 'CI_JOB_NAME', value: 'test', public: true }, { key: 'CI_JOB_STAGE', value: 'test', public: true }, { key: 'CI_COMMIT_SHA', value: build.sha, public: true }, diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index 407e2fc598a..d2302583ac8 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -79,7 +79,7 @@ describe Clusters::Applications::Prometheus do end it 'creates proper url' do - expect(subject.prometheus_client.url).to eq('http://example.com/api/v1/proxy/namespaces/gitlab-managed-apps/service/prometheus-prometheus-server:80') + expect(subject.prometheus_client.url).to eq('http://example.com/api/v1/namespaces/gitlab-managed-apps/service/prometheus-prometheus-server:80/proxy') end it 'copies options and headers from kube client to proxy client' do diff --git a/spec/models/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb new file mode 100644 index 00000000000..49e4b23ebc7 --- /dev/null +++ b/spec/models/concerns/cacheable_attributes_spec.rb @@ -0,0 +1,153 @@ +require 'spec_helper' + +describe CacheableAttributes do + let(:minimal_test_class) do + Class.new do + include ActiveModel::Model + extend ActiveModel::Callbacks + define_model_callbacks :commit + include CacheableAttributes + + def self.name + 'TestClass' + end + + def self.first + @_first ||= new('foo' => 'a') + end + + def self.last + @_last ||= new('foo' => 'a', 'bar' => 'b') + end + + attr_accessor :attributes + + def initialize(attrs = {}) + @attributes = attrs + end + end + end + + shared_context 'with defaults' do + before do + minimal_test_class.define_singleton_method(:defaults) do + { foo: 'a', bar: 'b', baz: 'c' } + end + end + end + + describe '.current_without_cache' do + it 'defaults to last' do + expect(minimal_test_class.current_without_cache).to eq(minimal_test_class.last) + end + + it 'can be overriden' do + minimal_test_class.define_singleton_method(:current_without_cache) do + first + end + + expect(minimal_test_class.current_without_cache).to eq(minimal_test_class.first) + end + end + + describe '.cache_key' do + it 'excludes cache attributes' do + expect(minimal_test_class.cache_key).to eq("TestClass:#{Gitlab::VERSION}:#{Gitlab.migrations_hash}:json") + end + end + + describe '.defaults' do + it 'defaults to {}' do + expect(minimal_test_class.defaults).to eq({}) + end + + context 'with defaults defined' do + include_context 'with defaults' + + it 'can be overriden' do + expect(minimal_test_class.defaults).to eq({ foo: 'a', bar: 'b', baz: 'c' }) + end + end + end + + describe '.build_from_defaults' do + include_context 'with defaults' + + context 'without any attributes given' do + it 'intializes a new object with the defaults' do + expect(minimal_test_class.build_from_defaults).not_to be_persisted + end + end + + context 'without attributes given' do + it 'intializes a new object with the given attributes merged into the defaults' do + expect(minimal_test_class.build_from_defaults(foo: 'd').attributes[:foo]).to eq('d') + end + end + end + + describe '.current', :use_clean_rails_memory_store_caching do + context 'redis unavailable' do + it 'returns an uncached record' do + allow(minimal_test_class).to receive(:last).and_return(:last) + expect(Rails.cache).to receive(:read).and_raise(Redis::BaseError) + + expect(minimal_test_class.current).to eq(:last) + end + end + + context 'when a record is not yet present' do + it 'does not cache nil object' do + # when missing settings a nil object is returned, but not cached + allow(minimal_test_class).to receive(:last).twice.and_return(nil) + + expect(minimal_test_class.current).to be_nil + expect(Rails.cache.exist?(minimal_test_class.cache_key)).to be(false) + end + + it 'cache non-nil object' do + # when the settings are set the method returns a valid object + allow(minimal_test_class).to receive(:last).and_call_original + + expect(minimal_test_class.current).to eq(minimal_test_class.last) + expect(Rails.cache.exist?(minimal_test_class.cache_key)).to be(true) + + # subsequent calls retrieve the record from the cache + last_record = minimal_test_class.last + expect(minimal_test_class).not_to receive(:last) + expect(minimal_test_class.current.attributes).to eq(last_record.attributes) + end + end + end + + describe '.cached', :use_clean_rails_memory_store_caching do + context 'when cache is cold' do + it 'returns nil' do + expect(minimal_test_class.cached).to be_nil + end + end + + context 'when cached settings do not include the latest defaults' do + before do + Rails.cache.write(minimal_test_class.cache_key, { bar: 'b', baz: 'c' }.to_json) + minimal_test_class.define_singleton_method(:defaults) do + { foo: 'a', bar: 'b', baz: 'c' } + end + end + + it 'includes attributes from defaults' do + expect(minimal_test_class.cached.attributes[:foo]).to eq(minimal_test_class.defaults[:foo]) + end + end + end + + describe '#cache!', :use_clean_rails_memory_store_caching do + let(:appearance_record) { create(:appearance) } + + it 'caches the attributes' do + appearance_record.cache! + + expect(Rails.cache.read(Appearance.cache_key)).to eq(appearance_record.attributes.to_json) + end + end +end diff --git a/spec/models/concerns/resolvable_note_spec.rb b/spec/models/concerns/resolvable_note_spec.rb index 91591017587..fcb5250278e 100644 --- a/spec/models/concerns/resolvable_note_spec.rb +++ b/spec/models/concerns/resolvable_note_spec.rb @@ -326,4 +326,12 @@ describe Note, ResolvableNote do end end end + + describe "#potentially_resolvable?" do + it 'returns false if noteable could not be found' do + allow(subject).to receive(:noteable).and_return(nil) + + expect(subject.potentially_resolvable?).to be_falsey + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c2ab1259ebf..aa968dda41f 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1246,38 +1246,50 @@ describe MergeRequest do describe '#check_if_can_be_merged' do let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) } - subject { create(:merge_request, source_project: project, merge_status: :unchecked) } + shared_examples 'checking if can be merged' do + context 'when it is not broken and has no conflicts' do + before do + allow(subject).to receive(:broken?) { false } + allow(project.repository).to receive(:can_be_merged?).and_return(true) + end - context 'when it is not broken and has no conflicts' do - before do - allow(subject).to receive(:broken?) { false } - allow(project.repository).to receive(:can_be_merged?).and_return(true) + it 'is marked as mergeable' do + expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged') + end end - it 'is marked as mergeable' do - expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged') - end - end + context 'when broken' do + before do + allow(subject).to receive(:broken?) { true } + end - context 'when broken' do - before do - allow(subject).to receive(:broken?) { true } + it 'becomes unmergeable' do + expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') + end end - it 'becomes unmergeable' do - expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') + context 'when it has conflicts' do + before do + allow(subject).to receive(:broken?) { false } + allow(project.repository).to receive(:can_be_merged?).and_return(false) + end + + it 'becomes unmergeable' do + expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') + end end end - context 'when it has conflicts' do - before do - allow(subject).to receive(:broken?) { false } - allow(project.repository).to receive(:can_be_merged?).and_return(false) - end + context 'when merge_status is unchecked' do + subject { create(:merge_request, source_project: project, merge_status: :unchecked) } - it 'becomes unmergeable' do - expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') - end + it_behaves_like 'checking if can be merged' + end + + context 'when merge_status is unchecked' do + subject { create(:merge_request, source_project: project, merge_status: :cannot_be_merged_recheck) } + + it_behaves_like 'checking if can be merged' end end @@ -2065,6 +2077,53 @@ describe MergeRequest do expect(subject.merge_jid).to be_nil end end + + describe 'transition to cannot_be_merged' do + let(:notification_service) { double(:notification_service) } + let(:todo_service) { double(:todo_service) } + + subject { create(:merge_request, merge_status: :unchecked) } + + before do + allow(NotificationService).to receive(:new).and_return(notification_service) + allow(TodoService).to receive(:new).and_return(todo_service) + end + + it 'notifies, but does not notify again if rechecking still results in cannot_be_merged' do + expect(notification_service).to receive(:merge_request_unmergeable).with(subject).once + expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).once + + subject.mark_as_unmergeable + subject.mark_as_unchecked + subject.mark_as_unmergeable + end + + it 'notifies whenever merge request is newly unmergeable' do + expect(notification_service).to receive(:merge_request_unmergeable).with(subject).twice + expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).twice + + subject.mark_as_unmergeable + subject.mark_as_unchecked + subject.mark_as_mergeable + subject.mark_as_unchecked + subject.mark_as_unmergeable + end + end + + describe 'check_state?' do + it 'indicates whether MR is still checking for mergeability' do + state_machine = described_class.state_machines[:merge_status] + check_states = [:unchecked, :cannot_be_merged_recheck] + + check_states.each do |merge_status| + expect(state_machine.check_state?(merge_status)).to be true + end + + (state_machine.states.map(&:name) - check_states).each do |merge_status| + expect(state_machine.check_state?(merge_status)).to be false + end + end + end end describe '#should_be_rebased?' do @@ -2196,4 +2255,39 @@ describe MergeRequest do expect(merge_request.can_allow_maintainer_to_push?(user)).to be_truthy end end + + describe '#merge_participants' do + it 'contains author' do + expect(subject.merge_participants).to eq([subject.author]) + end + + describe 'when merge_when_pipeline_succeeds? is true' do + describe 'when merge user is author' do + let(:user) { create(:user) } + subject do + create(:merge_request, + merge_when_pipeline_succeeds: true, + merge_user: user, + author: user) + end + + it 'contains author only' do + expect(subject.merge_participants).to eq([subject.author]) + end + end + + describe 'when merge user and author are different users' do + let(:merge_user) { create(:user) } + subject do + create(:merge_request, + merge_when_pipeline_succeeds: true, + merge_user: merge_user) + end + + it 'contains author and merge user' do + expect(subject.merge_participants).to eq([subject.author, merge_user]) + end + end + end + end end diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb index 4c61bc0af95..6e417323e40 100644 --- a/spec/models/project_services/gemnasium_service_spec.rb +++ b/spec/models/project_services/gemnasium_service_spec.rb @@ -26,24 +26,49 @@ describe GemnasiumService do end end + describe "deprecated?" do + let(:project) { create(:project, :repository) } + let(:gemnasium_service) { described_class.new } + + before do + allow(gemnasium_service).to receive_messages( + project_id: project.id, + project: project, + service_hook: true, + token: 'verySecret', + api_key: 'GemnasiumUserApiKey' + ) + end + + it "is true" do + expect(gemnasium_service.deprecated?).to be true + end + + it "can't create a new service" do + expect(gemnasium_service.save).to be false + expect(gemnasium_service.errors[:base].first) + .to eq('Gemnasium has been acquired by GitLab in January 2018. Since May 15, 2018, the service provided by Gemnasium is no longer available.') + end + end + describe "Execute" do let(:user) { create(:user) } let(:project) { create(:project, :repository) } + let(:gemnasium_service) { described_class.new } + let(:sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) } before do - @gemnasium_service = described_class.new - allow(@gemnasium_service).to receive_messages( + allow(gemnasium_service).to receive_messages( project_id: project.id, project: project, service_hook: true, token: 'verySecret', api_key: 'GemnasiumUserApiKey' ) - @sample_data = Gitlab::DataBuilder::Push.build_sample(project, user) end it "calls Gemnasium service" do expect(Gemnasium::GitlabService).to receive(:execute).with(an_instance_of(Hash)).once - @gemnasium_service.execute(@sample_data) + gemnasium_service.execute(sample_data) end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 39625b559eb..af2240f4f89 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -469,6 +469,34 @@ describe Project do end end + describe "#readme_url" do + let(:project) { create(:project, :repository, path: "somewhere") } + + context 'with a non-existing repository' do + it 'returns nil' do + allow(project.repository).to receive(:tree).with(:head).and_return(nil) + + expect(project.readme_url).to be_nil + end + end + + context 'with an existing repository' do + context 'when no README exists' do + it 'returns nil' do + allow_any_instance_of(Tree).to receive(:readme).and_return(nil) + + expect(project.readme_url).to be_nil + end + end + + context 'when a README exists' do + it 'returns the README' do + expect(project.readme_url).to eql("#{Gitlab.config.gitlab.url}/#{project.namespace.full_path}/somewhere/blob/master/README.md") + end + end + end + end + describe "#new_issuable_address" do let(:project) { create(:project, path: "somewhere") } let(:user) { create(:user) } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 6bc148a1392..0ccf55bd895 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -2031,27 +2031,27 @@ describe Repository do describe '#xcode_project?' do before do - allow(repository).to receive(:tree).with(:head).and_return(double(:tree, blobs: [blob])) + allow(repository).to receive(:tree).with(:head).and_return(double(:tree, trees: [tree])) end - context 'when the root contains a *.xcodeproj file' do - let(:blob) { double(:blob, path: 'Foo.xcodeproj') } + context 'when the root contains a *.xcodeproj directory' do + let(:tree) { double(:tree, path: 'Foo.xcodeproj') } it 'returns true' do expect(repository.xcode_project?).to be_truthy end end - context 'when the root contains a *.xcworkspace file' do - let(:blob) { double(:blob, path: 'Foo.xcworkspace') } + context 'when the root contains a *.xcworkspace directory' do + let(:tree) { double(:tree, path: 'Foo.xcworkspace') } it 'returns true' do expect(repository.xcode_project?).to be_truthy end end - context 'when the root contains no XCode config file' do - let(:blob) { double(:blob, path: 'subdir/Foo.xcworkspace') } + context 'when the root contains no Xcode config directory' do + let(:tree) { double(:tree, path: 'Foo') } it 'returns false' do expect(repository.xcode_project?).to be_falsey diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 684fa030baf..6a2f4a39f09 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -393,24 +393,6 @@ describe User do end describe 'after commit hook' do - describe '.update_invalid_gpg_signatures' do - let(:user) do - create(:user, email: 'tula.torphy@abshire.ca').tap do |user| - user.skip_reconfirmation! - end - end - - it 'does nothing when the name is updated' do - expect(user).not_to receive(:update_invalid_gpg_signatures) - user.update_attributes!(name: 'Bette') - end - - it 'synchronizes the gpg keys when the email is updated' do - expect(user).to receive(:update_invalid_gpg_signatures).at_most(:twice) - user.update_attributes!(email: 'shawnee.ritchie@denesik.com') - end - end - describe '#update_emails_with_primary_email' do before do @user = create(:user, email: 'primary@example.com').tap do |user| @@ -450,6 +432,76 @@ describe User do expect(@user.emails.first.confirmed_at).not_to eq nil end end + + describe '#update_notification_email' do + # Regression: https://gitlab.com/gitlab-org/gitlab-ce/issues/22846 + context 'when changing :email' do + let(:user) { create(:user) } + let(:new_email) { 'new-email@example.com' } + + it 'sets :unconfirmed_email' do + expect do + user.tap { |u| u.update!(email: new_email) }.reload + end.to change(user, :unconfirmed_email).to(new_email) + end + + it 'does not change :notification_email' do + expect do + user.tap { |u| u.update!(email: new_email) }.reload + end.not_to change(user, :notification_email) + end + + it 'updates :notification_email to the new email once confirmed' do + user.update!(email: new_email) + + expect do + user.tap(&:confirm).reload + end.to change(user, :notification_email).to eq(new_email) + end + + context 'and :notification_email is set to a secondary email' do + let!(:email_attrs) { attributes_for(:email, :confirmed, user: user) } + let(:secondary) { create(:email, :confirmed, email: 'secondary@example.com', user: user) } + + before do + user.emails.create(email_attrs) + user.tap { |u| u.update!(notification_email: email_attrs[:email]) }.reload + end + + it 'does not change :notification_email to :email' do + expect do + user.tap { |u| u.update!(email: new_email) }.reload + end.not_to change(user, :notification_email) + end + + it 'does not change :notification_email to :email once confirmed' do + user.update!(email: new_email) + + expect do + user.tap(&:confirm).reload + end.not_to change(user, :notification_email) + end + end + end + end + + describe '#update_invalid_gpg_signatures' do + let(:user) do + create(:user, email: 'tula.torphy@abshire.ca').tap do |user| + user.skip_reconfirmation! + end + end + + it 'does nothing when the name is updated' do + expect(user).not_to receive(:update_invalid_gpg_signatures) + user.update_attributes!(name: 'Bette') + end + + it 'synchronizes the gpg keys when the email is updated' do + expect(user).to receive(:update_invalid_gpg_signatures).at_most(:twice) + user.update_attributes!(email: 'shawnee.ritchie@denesik.com') + end + end end describe '#update_tracked_fields!', :clean_gitlab_redis_shared_state do diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index e3b37739e8e..d5fb4a7742c 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -70,6 +70,41 @@ describe MergeRequestPresenter do end end + describe "#unmergeable_reasons" do + let(:presenter) { described_class.new(resource, current_user: user) } + + before do + # Mergeable base state + allow(resource).to receive(:has_no_commits?).and_return(false) + allow(resource).to receive(:source_branch_exists?).and_return(true) + allow(resource).to receive(:target_branch_exists?).and_return(true) + allow(resource.project.repository).to receive(:can_be_merged?).and_return(true) + end + + it "handles mergeable request" do + expect(presenter.unmergeable_reasons).to eq([]) + end + + it "handles no commit" do + allow(resource).to receive(:has_no_commits?).and_return(true) + + expect(presenter.unmergeable_reasons).to eq(["no commits"]) + end + + it "handles branches missing" do + allow(resource).to receive(:source_branch_exists?).and_return(false) + allow(resource).to receive(:target_branch_exists?).and_return(false) + + expect(presenter.unmergeable_reasons).to eq(["source branch is missing", "target branch is missing"]) + end + + it "handles merge conflict" do + allow(resource.project.repository).to receive(:can_be_merged?).and_return(false) + + expect(presenter.unmergeable_reasons).to eq(["has merge conflicts"]) + end + end + describe '#conflict_resolution_path' do let(:project) { create :project } let(:user) { create :user } diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index 53d48a91007..fdddca5d0ef 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -15,7 +15,7 @@ describe API::Environments do it 'returns project environments' do project_data_keys = %w( id description default_branch tag_list - ssh_url_to_repo http_url_to_repo web_url + ssh_url_to_repo http_url_to_repo web_url readme_url name name_with_namespace path path_with_namespace star_count forks_count diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 85a571b8f0e..9b7c3205c1f 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -18,7 +18,7 @@ describe API::Projects do let(:user2) { create(:user) } let(:user3) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:project, :repository, namespace: user.namespace) } let(:project2) { create(:project, namespace: user.namespace) } let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') } let(:project_member) { create(:project_member, :developer, user: user3, project: project) } @@ -220,7 +220,7 @@ describe API::Projects do it 'returns a simplified version of all the projects' do expected_keys = %w( id description default_branch tag_list - ssh_url_to_repo http_url_to_repo web_url + ssh_url_to_repo http_url_to_repo web_url readme_url name name_with_namespace path path_with_namespace star_count forks_count @@ -854,6 +854,7 @@ describe API::Projects do expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds) expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved) expect(json_response['merge_method']).to eq(project.merge_method.to_s) + expect(json_response['readme_url']).to eq(project.readme_url) end it 'returns a project by path name' do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index e8196980a8c..05637eb0729 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -488,10 +488,6 @@ describe API::Users do describe "PUT /users/:id" do let!(:admin_user) { create(:admin) } - before do - admin - end - it "updates user with new bio" do put api("/users/#{user.id}", admin), { bio: 'new test bio' } @@ -525,27 +521,28 @@ describe API::Users do expect(json_response['avatar_url']).to include(user.avatar_path) end - it 'updates user with his own email' do - put api("/users/#{user.id}", admin), email: user.email - - expect(response).to have_gitlab_http_status(200) - expect(json_response['email']).to eq(user.email) - expect(user.reload.email).to eq(user.email) - end - it 'updates user with a new email' do + old_email = user.email + old_notification_email = user.notification_email put api("/users/#{user.id}", admin), email: 'new@email.com' + user.reload + expect(response).to have_gitlab_http_status(200) - expect(user.reload.notification_email).to eq('new@email.com') + expect(user).to be_confirmed + expect(user.email).to eq(old_email) + expect(user.notification_email).to eq(old_notification_email) + expect(user.unconfirmed_email).to eq('new@email.com') end it 'skips reconfirmation when requested' do - put api("/users/#{user.id}", admin), { skip_reconfirmation: true } + put api("/users/#{user.id}", admin), email: 'new@email.com', skip_reconfirmation: true user.reload - expect(user.confirmed_at).to be_present + expect(response).to have_gitlab_http_status(200) + expect(user).to be_confirmed + expect(user.email).to eq('new@email.com') end it 'updates user with his own username' do diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb index 4c25bd935c6..158ddf171bc 100644 --- a/spec/requests/api/v3/projects_spec.rb +++ b/spec/requests/api/v3/projects_spec.rb @@ -82,7 +82,7 @@ describe API::V3::Projects do it 'returns a simplified version of all the projects' do expected_keys = %w( id description default_branch tag_list - ssh_url_to_repo http_url_to_repo web_url + ssh_url_to_repo http_url_to_repo web_url readme_url name name_with_namespace path path_with_namespace star_count forks_count diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb index 7bbf34422b8..38b618191fb 100644 --- a/spec/requests/api/version_spec.rb +++ b/spec/requests/api/version_spec.rb @@ -18,7 +18,7 @@ describe API::Version do expect(response).to have_gitlab_http_status(200) expect(json_response['version']).to eq(Gitlab::VERSION) - expect(json_response['revision']).to eq(Gitlab::REVISION) + expect(json_response['revision']).to eq(Gitlab.revision) end end end diff --git a/spec/services/clusters/applications/schedule_installation_service_spec.rb b/spec/services/clusters/applications/schedule_installation_service_spec.rb index 473dbcbb692..bca1e71bef2 100644 --- a/spec/services/clusters/applications/schedule_installation_service_spec.rb +++ b/spec/services/clusters/applications/schedule_installation_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Clusters::Applications::ScheduleInstallationService do def count_scheduled - application_class&.with_status(:scheduled)&.count || 0 + application&.class&.with_status(:scheduled)&.count || 0 end shared_examples 'a failing service' do @@ -10,45 +10,42 @@ describe Clusters::Applications::ScheduleInstallationService do expect(ClusterInstallAppWorker).not_to receive(:perform_async) count_before = count_scheduled - expect { service.execute }.to raise_error(StandardError) + expect { service.execute(application) }.to raise_error(StandardError) expect(count_scheduled).to eq(count_before) end end describe '#execute' do - let(:application_class) { Clusters::Applications::Helm } - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } - let(:service) { described_class.new(project, nil, cluster: cluster, application_class: application_class) } + let(:project) { double(:project) } + let(:service) { described_class.new(project, nil) } - it 'creates a new application' do - allow(ClusterInstallAppWorker).to receive(:perform_async) + context 'when application is installable' do + let(:application) { create(:clusters_applications_helm, :installable) } - expect { service.execute }.to change { application_class.count }.by(1) - end - - it 'make the application scheduled' do - expect(ClusterInstallAppWorker).to receive(:perform_async).with(application_class.application_name, kind_of(Numeric)).once + it 'make the application scheduled' do + expect(ClusterInstallAppWorker).to receive(:perform_async).with(application.name, kind_of(Numeric)).once - expect { service.execute }.to change { application_class.with_status(:scheduled).count }.by(1) + expect { service.execute(application) }.to change { application.class.with_status(:scheduled).count }.by(1) + end end context 'when installation is already in progress' do let(:application) { create(:clusters_applications_helm, :installing) } - let(:cluster) { application.cluster } it_behaves_like 'a failing service' end - context 'when application_class is nil' do - let(:application_class) { nil } + context 'when application is nil' do + let(:application) { nil } it_behaves_like 'a failing service' end context 'when application cannot be persisted' do + let(:application) { create(:clusters_applications_helm) } + before do - expect_any_instance_of(application_class).to receive(:make_scheduled!).once.and_raise(ActiveRecord::RecordInvalid) + expect(application).to receive(:make_scheduled!).once.and_raise(ActiveRecord::RecordInvalid) end it_behaves_like 'a failing service' diff --git a/spec/services/merge_requests/conflicts/list_service_spec.rb b/spec/services/merge_requests/conflicts/list_service_spec.rb index 837b8a56d12..d57852615d9 100644 --- a/spec/services/merge_requests/conflicts/list_service_spec.rb +++ b/spec/services/merge_requests/conflicts/list_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe MergeRequests::Conflicts::ListService do describe '#can_be_resolved_in_ui?' do def create_merge_request(source_branch) - create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start') do |mr| + create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start', merge_status: :unchecked) do |mr| mr.mark_as_unmergeable end end diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb index 240aa638f79..8838742a637 100644 --- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb @@ -112,32 +112,6 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do service.trigger(unrelated_pipeline) end end - - context 'when the merge request is not mergeable' do - let(:mr_conflict) do - create(:merge_request, merge_when_pipeline_succeeds: true, merge_user: user, - source_branch: 'master', target_branch: 'feature-conflict', - source_project: project, target_project: project) - end - - let(:conflict_pipeline) do - create(:ci_pipeline, project: project, ref: mr_conflict.source_branch, - sha: mr_conflict.diff_head_sha, status: 'success', - head_pipeline_of: mr_conflict) - end - - it 'does not merge the merge request' do - expect(MergeWorker).not_to receive(:perform_async) - - service.trigger(conflict_pipeline) - end - - it 'creates todos for unmergeability' do - expect_any_instance_of(TodoService).to receive(:merge_request_became_unmergeable).with(mr_conflict) - - service.trigger(conflict_pipeline) - end - end end describe "#cancel" do diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 5f28bc123f3..831678b949d 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -1224,6 +1224,32 @@ describe NotificationService, :mailer do end end + describe '#merge_request_unmergeable' do + it "sends email to merge request author" do + notification.merge_request_unmergeable(merge_request) + + should_email(merge_request.author) + expect(email_recipients.size).to eq(1) + end + + describe 'when merge_when_pipeline_succeeds is true' do + before do + merge_request.update_attributes( + merge_when_pipeline_succeeds: true, + merge_user: create(:user) + ) + end + + it "sends email to merge request author and merge_user" do + notification.merge_request_unmergeable(merge_request) + + should_email(merge_request.author) + should_email(merge_request.merge_user) + expect(email_recipients.size).to eq(2) + end + end + end + describe '#closed_merge_request' do before do update_custom_notification(:close_merge_request, @u_guest_custom, resource: project) diff --git a/spec/services/projects/open_issues_count_service_spec.rb b/spec/services/projects/open_issues_count_service_spec.rb index f964f9972cd..06b470849b3 100644 --- a/spec/services/projects/open_issues_count_service_spec.rb +++ b/spec/services/projects/open_issues_count_service_spec.rb @@ -2,20 +2,53 @@ require 'spec_helper' describe Projects::OpenIssuesCountService do describe '#count' do - it 'returns the number of open issues' do - project = create(:project) - create(:issue, :opened, project: project) + let(:project) { create(:project) } - expect(described_class.new(project).count).to eq(1) + context 'when user is nil' do + it 'does not include confidential issues in the issue count' do + create(:issue, :opened, project: project) + create(:issue, :opened, confidential: true, project: project) + + expect(described_class.new(project).count).to eq(1) + end end - it 'does not include confidential issues in the issue count' do - project = create(:project) + context 'when user is provided' do + let(:user) { create(:user) } + + context 'when user can read confidential issues' do + before do + project.add_reporter(user) + end + + it 'returns the right count with confidential issues' do + create(:issue, :opened, project: project) + create(:issue, :opened, confidential: true, project: project) + + expect(described_class.new(project, user).count).to eq(2) + end + + it 'uses total_open_issues_count cache key' do + expect(described_class.new(project, user).cache_key_name).to eq('total_open_issues_count') + end + end + + context 'when user cannot read confidential issues' do + before do + project.add_guest(user) + end + + it 'does not include confidential issues' do + create(:issue, :opened, project: project) + create(:issue, :opened, confidential: true, project: project) - create(:issue, :opened, project: project) - create(:issue, :opened, confidential: true, project: project) + expect(described_class.new(project, user).count).to eq(1) + end - expect(described_class.new(project).count).to eq(1) + it 'uses public_open_issues_count cache key' do + expect(described_class.new(project, user).cache_key_name).to eq('public_open_issues_count') + end + end end end end diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 562b89e6767..9a51c873b30 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -721,17 +721,18 @@ describe TodoService do end describe '#merge_request_build_failed' do - it 'creates a pending todo for the merge request author' do - service.merge_request_build_failed(mr_unassigned) + let(:merge_participants) { [mr_unassigned.author, admin] } - should_create_todo(user: author, target: mr_unassigned, action: Todo::BUILD_FAILED) + before do + allow(mr_unassigned).to receive(:merge_participants).and_return(merge_participants) end - it 'creates a pending todo for merge_user' do - mr_unassigned.update(merge_when_pipeline_succeeds: true, merge_user: admin) + it 'creates a pending todo for each merge_participant' do service.merge_request_build_failed(mr_unassigned) - should_create_todo(user: admin, author: admin, target: mr_unassigned, action: Todo::BUILD_FAILED) + merge_participants.each do |participant| + should_create_todo(user: participant, author: participant, target: mr_unassigned, action: Todo::BUILD_FAILED) + end end end @@ -747,11 +748,19 @@ describe TodoService do end describe '#merge_request_became_unmergeable' do - it 'creates a pending todo for a merge_user' do + let(:merge_participants) { [admin, create(:user)] } + + before do + allow(mr_unassigned).to receive(:merge_participants).and_return(merge_participants) + end + + it 'creates a pending todo for each merge_participant' do mr_unassigned.update(merge_when_pipeline_succeeds: true, merge_user: admin) service.merge_request_became_unmergeable(mr_unassigned) - should_create_todo(user: admin, author: admin, target: mr_unassigned, action: Todo::UNMERGEABLE) + merge_participants.each do |participant| + should_create_todo(user: participant, author: participant, target: mr_unassigned, action: Todo::UNMERGEABLE) + end end end diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb index 0388f110d71..a15189db35f 100644 --- a/spec/support/api/milestones_shared_examples.rb +++ b/spec/support/api/milestones_shared_examples.rb @@ -196,6 +196,12 @@ shared_examples_for 'group and project milestones' do |route_definition| expect(json_response['state']).to eq('closed') end + + it 'updates milestone with only start date' do + put api(resource_route, user), start_date: Date.tomorrow + + expect(response).to have_gitlab_http_status(200) + end end describe "GET #{route_definition}/:milestone_id/issues" do diff --git a/spec/support/helpers/board_helpers.rb b/spec/support/helpers/board_helpers.rb index 507d0432d7f..b85fde222ea 100644 --- a/spec/support/helpers/board_helpers.rb +++ b/spec/support/helpers/board_helpers.rb @@ -1,7 +1,7 @@ module BoardHelpers def click_card(card) within card do - first('.card-number').click + first('.board-card-number').click end wait_for_sidebar diff --git a/spec/support/helpers/features/sorting_helpers.rb b/spec/support/helpers/features/sorting_helpers.rb index 50457b64745..ad0053ec5cf 100644 --- a/spec/support/helpers/features/sorting_helpers.rb +++ b/spec/support/helpers/features/sorting_helpers.rb @@ -15,7 +15,7 @@ module Spec def sort_by(value) find('button.dropdown-toggle').click - page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do + page.within('.content ul.dropdown-menu.dropdown-menu-right li') do click_link(value) end end diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb index 72e5c2d66dd..f7b71bf42e3 100644 --- a/spec/support/helpers/login_helpers.rb +++ b/spec/support/helpers/login_helpers.rb @@ -132,6 +132,14 @@ module LoginHelpers env['omniauth.auth'] = OmniAuth.config.mock_auth[provider.to_sym] end + def stub_omniauth_failure(strategy, message_key, exception = nil) + env = @request.env + + env['omniauth.error'] = exception + env['omniauth.error.type'] = message_key.to_sym + env['omniauth.error.strategy'] = strategy + end + def stub_omniauth_saml_config(messages) set_devise_mapping(context: Rails.application) Rails.application.routes.disable_clear_and_finalize = true diff --git a/spec/support/helpers/mobile_helpers.rb b/spec/support/helpers/mobile_helpers.rb index 3b9eb84e824..9dc1f1de436 100644 --- a/spec/support/helpers/mobile_helpers.rb +++ b/spec/support/helpers/mobile_helpers.rb @@ -1,10 +1,10 @@ module MobileHelpers def resize_screen_xs - resize_window(767, 768) + resize_window(575, 768) end def resize_screen_sm - resize_window(900, 768) + resize_window(767, 768) end def restore_window_size diff --git a/spec/support/helpers/routes_helpers.rb b/spec/support/helpers/routes_helpers.rb new file mode 100644 index 00000000000..c4129606418 --- /dev/null +++ b/spec/support/helpers/routes_helpers.rb @@ -0,0 +1,7 @@ +module RoutesHelpers + def fake_routes(&block) + @routes = @routes.dup + @routes.formatter.clear + ActionDispatch::Routing::Mapper.new(@routes).instance_exec(&block) + end +end diff --git a/spec/support/helpers/sorting_helper.rb b/spec/support/helpers/sorting_helper.rb index 577518d726c..9496a94d8f4 100644 --- a/spec/support/helpers/sorting_helper.rb +++ b/spec/support/helpers/sorting_helper.rb @@ -11,7 +11,7 @@ module SortingHelper def sorting_by(value) find('button.dropdown-toggle').click - page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do + page.within('.content ul.dropdown-menu.dropdown-menu-right li') do click_link value end end diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb index 17f319f49e9..5241c0fa6f1 100644 --- a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb +++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb @@ -10,7 +10,7 @@ shared_examples "protected branches > access control > CE" do unless allowed_to_push_button.text == access_type_name allowed_to_push_button.click - within(".dropdown.open .dropdown-menu") { click_on access_type_name } + within(".dropdown.show .dropdown-menu") { click_on access_type_name } end end @@ -55,7 +55,7 @@ shared_examples "protected branches > access control > CE" do unless allowed_to_merge_button.text == access_type_name allowed_to_merge_button.click - within(".dropdown.open .dropdown-menu") { click_on access_type_name } + within(".dropdown.show .dropdown-menu") { click_on access_type_name } end end diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index a2e5642a72c..c9252bebb2e 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -125,6 +125,16 @@ describe 'gitlab:app namespace rake task' do expect(Dir.entries(File.join(project.repository.path, 'custom_hooks'))).to include("dummy.txt") end end + + context 'specific backup tasks' do + let(:task_list) { %w(db repo uploads builds artifacts pages lfs registry) } + + it 'prints a progress message to stdout' do + task_list.each do |task| + expect { run_rake_task("gitlab:backup:#{task}:create") }.to output(/Dumping /).to_stdout + end + end + end end context 'tar creation' do diff --git a/spec/views/admin/dashboard/index.html.haml_spec.rb b/spec/views/admin/dashboard/index.html.haml_spec.rb index 099baacf019..59c777ea338 100644 --- a/spec/views/admin/dashboard/index.html.haml_spec.rb +++ b/spec/views/admin/dashboard/index.html.haml_spec.rb @@ -22,6 +22,6 @@ describe 'admin/dashboard/index.html.haml' do it "includes revision of GitLab" do render - expect(rendered).to have_content "#{Gitlab::VERSION} (#{Gitlab::REVISION})" + expect(rendered).to have_content "#{Gitlab::VERSION} (#{Gitlab.revision})" end end diff --git a/spec/views/help/index.html.haml_spec.rb b/spec/views/help/index.html.haml_spec.rb index 0a78606171d..836d452304c 100644 --- a/spec/views/help/index.html.haml_spec.rb +++ b/spec/views/help/index.html.haml_spec.rb @@ -39,7 +39,7 @@ describe 'help/index' do def stub_version(version, revision) stub_const('Gitlab::VERSION', version) - stub_const('Gitlab::REVISION', revision) + allow(Gitlab).to receive(:revision).and_return(revision) end def stub_helpers diff --git a/spec/workers/update_merge_requests_worker_spec.rb b/spec/workers/update_merge_requests_worker_spec.rb index 0fa19ac84bb..80137815d2b 100644 --- a/spec/workers/update_merge_requests_worker_spec.rb +++ b/spec/workers/update_merge_requests_worker_spec.rb @@ -18,8 +18,13 @@ describe UpdateMergeRequestsWorker do end it 'executes MergeRequests::RefreshService with expected values' do - expect(MergeRequests::RefreshService).to receive(:new).with(project, user).and_call_original - expect_any_instance_of(MergeRequests::RefreshService).to receive(:execute).with(oldrev, newrev, ref) + expect(MergeRequests::RefreshService).to receive(:new) + .with(project, user).and_wrap_original do |method, *args| + method.call(*args).tap do |refresh_service| + expect(refresh_service) + .to receive(:execute).with(oldrev, newrev, ref) + end + end perform end |