diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-10-24 18:12:41 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-10-24 18:12:41 +0300 |
commit | 40a4f37126bb1a1dd6b6f4b3c0ebb414a3e3908a (patch) | |
tree | ff6b0774cbd1ab71b69d9e9bf9fa0e0b3d1ad799 /spec | |
parent | a19e3ec8e8545d5a6b275bab3e5ea8b0cc707449 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
62 files changed, 925 insertions, 560 deletions
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index 60343c822af..8dbdd8db99b 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -66,51 +66,69 @@ RSpec.describe Admin::ApplicationSettingsController, :do_not_mock_admin_mode_set sign_in(admin) end + context 'when there are NO recent ServicePing reports' do + it 'return 404' do + get :usage_data, format: :json + + expect(response).to have_gitlab_http_status(:not_found) + end + end + context 'when there are recent ServicePing reports' do - it 'attempts to use prerecorded data' do + before do create(:raw_usage_data) + end + it 'does not trigger ServicePing generation' do expect(Gitlab::Usage::ServicePingReport).not_to receive(:for) get :usage_data, format: :json end - end - context 'when there are NO recent ServicePing reports' do - it 'calculates data on the fly' do - allow(Gitlab::Usage::ServicePingReport).to receive(:for).and_call_original + it 'check cached data if present' do + expect(Rails.cache).to receive(:fetch).with(Gitlab::Usage::ServicePingReport::CACHE_KEY).and_return({ test: 1 }) + expect(::RawUsageData).not_to receive(:for_current_reporting_cycle) get :usage_data, format: :json - - expect(Gitlab::Usage::ServicePingReport).to have_received(:for) end - end - it 'returns HTML data' do - get :usage_data, format: :html + context 'if no cached data available' do + before do + allow(Rails.cache).to receive(:fetch).and_return(nil) + end - expect(response.body).to start_with('<span') - expect(response).to have_gitlab_http_status(:ok) - end + it 'returns latest RawUsageData' do + expect(::RawUsageData).to receive_message_chain(:for_current_reporting_cycle, :first, :payload) - it 'returns JSON data' do - get :usage_data, format: :json + get :usage_data, format: :json + end + end - body = json_response - expect(body["version"]).to eq(Gitlab::VERSION) - expect(body).to include('counts') - expect(response).to have_gitlab_http_status(:ok) - end + it 'returns HTML data' do + get :usage_data, format: :html - describe 'usage data counter' do - let(:counter) { Gitlab::UsageDataCounters::ServiceUsageDataCounter } + expect(response.body).to start_with('<span') + expect(response).to have_gitlab_http_status(:ok) + end - it 'incremented when json generated' do - expect { get :usage_data, format: :json }.to change { counter.read(:download_payload_click) }.by(1) + it 'returns JSON data' do + get :usage_data, format: :json + + expect(json_response).to be_present + expect(json_response['test']).to include('test') + expect(response).to have_gitlab_http_status(:ok) end - it 'not incremented when html format requested' do - expect { get :usage_data }.not_to change { counter.read(:download_payload_click) } + describe 'usage data counter' do + let(:counter) { Gitlab::UsageDataCounters::ServiceUsageDataCounter } + + it 'incremented when json generated' do + expect { get :usage_data, format: :json }.to change { counter.read(:download_payload_click) }.by(1) + end + + it 'not incremented when html format requested' do + expect { get :usage_data }.not_to change { counter.read(:download_payload_click) } + end end end end @@ -524,35 +542,37 @@ RSpec.describe Admin::ApplicationSettingsController, :do_not_mock_admin_mode_set end end - describe 'GET #service_usage_data', feature_category: :service_ping do + describe 'GET #metrics_and_profiling', feature_category: :service_ping do before do stub_usage_data_connections stub_database_flavor_check sign_in(admin) end - it 'assigns truthy value if there are recent ServicePing reports in database' do + it 'assigns service_ping_data if there are recent ServicePing reports in database' do create(:raw_usage_data) - get :service_usage_data, format: :html + get :metrics_and_profiling, format: :html - expect(assigns(:service_ping_data_present)).to be_truthy + expect(assigns(:service_ping_data)).to be_present expect(response).to have_gitlab_http_status(:ok) end - it 'assigns truthy value if there are recent ServicePing reports in cache', :use_clean_rails_memory_store_caching do - Rails.cache.write('usage_data', true) + it 'assigns service_ping_data if there are recent ServicePing reports in cache', :use_clean_rails_memory_store_caching do + create(:raw_usage_data) + cached_data = { testKey: "testValue" } + Rails.cache.write('usage_data', cached_data) - get :service_usage_data, format: :html + get :metrics_and_profiling, format: :html - expect(assigns(:service_ping_data_present)).to be_truthy + expect(assigns(:service_ping_data)).to eq(cached_data) expect(response).to have_gitlab_http_status(:ok) end - it 'assigns falsey value if there are NO recent ServicePing reports' do - get :service_usage_data, format: :html + it 'does not assign service_ping_data value if there are NO recent ServicePing reports' do + get :metrics_and_profiling, format: :html - expect(assigns(:service_ping_data_present)).to be_falsey + expect(assigns(:service_ping_data)).not_to be_present expect(response).to have_gitlab_http_status(:ok) end end diff --git a/spec/features/admin/admin_jobs_spec.rb b/spec/features/admin/admin_jobs_spec.rb index b125974532b..b3e21d02354 100644 --- a/spec/features/admin/admin_jobs_spec.rb +++ b/spec/features/admin/admin_jobs_spec.rb @@ -132,7 +132,7 @@ RSpec.describe 'Admin Jobs', :js, feature_category: :continuous_integration do within_testid('jobs-table') do expect(page).to have_selector('[data-testid="jobs-table-row"]', count: 1) - expect(find_by_testid('ci-badge-text')).to have_content('Failed') + expect(find_by_testid('ci-icon-text')).to have_content('Failed') end end end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 1b10ea81333..aae41cab21f 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -666,28 +666,47 @@ RSpec.describe 'Admin updates settings', feature_category: :shared do expect(find_field('Allow access to members of the following group').value).to be_nil end - it 'loads togglable usage ping payload on click', :js do - allow(Gitlab::Usage::ServicePingReport).to receive(:for).and_return({ uuid: '12345678', hostname: '127.0.0.1' }) + context 'Service usage data', :with_license do + before do + stub_usage_data_connections + stub_database_flavor_check + end - stub_usage_data_connections - stub_database_flavor_check + context 'when service data cached' do + before_all do + create(:raw_usage_data) + end - page.within('#js-usage-settings') do - expected_payload_content = /(?=.*"uuid")(?=.*"hostname")/m + it 'loads usage ping payload on click', :js do + expected_payload_content = /(?=.*"test")/m - expect(page).not_to have_content expected_payload_content + expect(page).not_to have_content expected_payload_content - click_button('Preview payload') + click_button('Preview payload') - wait_for_requests + wait_for_requests + + expect(page).to have_button 'Hide payload' + expect(page).to have_content expected_payload_content + end - expect(page).to have_selector '.js-service-ping-payload' - expect(page).to have_button 'Hide payload' - expect(page).to have_content expected_payload_content + it 'generates usage ping payload on button click', :js do + expect_next_instance_of(Admin::ApplicationSettingsController) do |instance| + expect(instance).to receive(:usage_data).and_call_original + end + + click_button('Download payload') + + wait_for_requests + end + end - click_button('Hide payload') + context 'when service data not cached' do + it 'renders missing cache information' do + visit metrics_and_profiling_admin_application_settings_path - expect(page).not_to have_content expected_payload_content + expect(page).to have_text('Service Ping payload not found in the application cache') + end end end end @@ -998,61 +1017,6 @@ RSpec.describe 'Admin updates settings', feature_category: :shared do end end end - - context 'Service usage data page', :with_license do - before do - stub_usage_data_connections - stub_database_flavor_check - end - - context 'when service data cached', :use_clean_rails_memory_store_caching do - let(:usage_data) { { uuid: "1111", hostname: "localhost", counts: { issue: 0 } }.deep_stringify_keys } - - before do - # We are mocking Gitlab::Usage::ServicePingReport because this dataset generation - # takes a very long time, and is not what we're testing in this context. - # - # See https://gitlab.com/gitlab-org/gitlab/-/issues/414929 - allow(Gitlab::UsageData).to receive(:data).and_return(usage_data) - allow(Gitlab::Usage::ServicePingReport).to receive(:with_instrumentation_classes) - .with(usage_data, :with_value).and_return(usage_data) - - visit usage_data_admin_application_settings_path - visit service_usage_data_admin_application_settings_path - end - - it 'loads usage ping payload on click', :js do - expected_payload_content = /(?=.*"uuid")(?=.*"hostname")/m - - expect(page).not_to have_content expected_payload_content - - click_button('Preview payload') - - wait_for_requests - - expect(page).to have_button 'Hide payload' - expect(page).to have_content expected_payload_content - end - - it 'generates usage ping payload on button click', :js do - expect_next_instance_of(Admin::ApplicationSettingsController) do |instance| - expect(instance).to receive(:usage_data).and_call_original - end - - click_button('Download payload') - - wait_for_requests - end - end - - context 'when service data not cached' do - it 'renders missing cache information' do - visit service_usage_data_admin_application_settings_path - - expect(page).to have_text('Service Ping payload not found in the application cache') - end - end - end end context 'application setting :admin_mode is disabled' do diff --git a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb index 69eb6b0dc17..5e683ddf7ba 100644 --- a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb +++ b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb @@ -86,7 +86,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', it 'sees branch pipelines and detached merge request pipelines in correct order' do page.within('.ci-table') do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Created', count: 2) + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Created', count: 2) expect(first('[data-testid="pipeline-url-link"]')).to have_content("##{detached_merge_request_pipeline.id}") end end @@ -122,7 +122,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', it 'sees branch pipelines and detached merge request pipelines in correct order' do page.within('.ci-table') do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Pending', count: 4) + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Pending', count: 4) expect(all('[data-testid="pipeline-url-link"]')[0]) .to have_content("##{detached_merge_request_pipeline_2.id}") @@ -220,7 +220,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', it 'sees a branch pipeline in pipeline tab' do page.within('.ci-table') do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Created', count: 1) + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Created', count: 1) expect(first('[data-testid="pipeline-url-link"]')).to have_content("##{push_pipeline.id}") end end @@ -273,7 +273,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', it 'sees branch pipelines and detached merge request pipelines in correct order' do page.within('.ci-table') do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Pending', count: 2) + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Pending', count: 2) expect(first('[data-testid="pipeline-url-link"]')).to have_content("##{detached_merge_request_pipeline.id}") end end @@ -289,7 +289,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', it 'sees pipeline list in forked project' do visit project_pipelines_path(forked_project) - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Pending', count: 2) + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Pending', count: 2) end context 'when a user updated a merge request from a forked project to the parent project' do @@ -315,7 +315,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', it 'sees branch pipelines and detached merge request pipelines in correct order' do page.within('.ci-table') do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Pending', count: 4) + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Pending', count: 4) expect(all('[data-testid="pipeline-url-link"]')[0]) .to have_content("##{detached_merge_request_pipeline_2.id}") @@ -358,7 +358,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', it 'sees pipeline list in forked project' do visit project_pipelines_path(forked_project) - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Pending', count: 4) + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Pending', count: 4) end end diff --git a/spec/features/merge_request/user_sees_pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb index a68b3c444fe..c21c9043f01 100644 --- a/spec/features/merge_request/user_sees_pipelines_spec.rb +++ b/spec/features/merge_request/user_sees_pipelines_spec.rb @@ -49,7 +49,7 @@ RSpec.describe 'Merge request > User sees pipelines', :js, feature_category: :co wait_for_requests page.within(find('[data-testid="pipeline-table-row"]', match: :first)) do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Passed') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Passed') expect(page).to have_content(pipeline.id) expect(page).to have_content('API') expect(page).to have_css('[data-testid="pipeline-mini-graph"]') diff --git a/spec/features/projects/commit/user_sees_pipelines_tab_spec.rb b/spec/features/projects/commit/user_sees_pipelines_tab_spec.rb index 00cb5474ea0..56a3a252f3e 100644 --- a/spec/features/projects/commit/user_sees_pipelines_tab_spec.rb +++ b/spec/features/projects/commit/user_sees_pipelines_tab_spec.rb @@ -36,7 +36,7 @@ RSpec.describe 'Commit > Pipelines tab', :js, feature_category: :source_code_man wait_for_requests page.within('[data-testid="pipeline-table-row"]') do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Passed') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Passed') expect(page).to have_content(pipeline.id) expect(page).to have_content('API') expect(page).to have_css('[data-testid="pipeline-mini-graph"]') diff --git a/spec/features/projects/jobs/user_browses_jobs_spec.rb b/spec/features/projects/jobs/user_browses_jobs_spec.rb index fc67d7dedcc..115b3dda5b2 100644 --- a/spec/features/projects/jobs/user_browses_jobs_spec.rb +++ b/spec/features/projects/jobs/user_browses_jobs_spec.rb @@ -72,7 +72,7 @@ RSpec.describe 'User browses jobs', feature_category: :groups_and_projects do wait_for_requests - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Canceled') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Canceled') expect(page).not_to have_selector('[data-testid="jobs-table-error-alert"]') end end @@ -93,7 +93,7 @@ RSpec.describe 'User browses jobs', feature_category: :groups_and_projects do wait_for_requests - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Pending') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Pending') end end @@ -133,7 +133,7 @@ RSpec.describe 'User browses jobs', feature_category: :groups_and_projects do wait_for_requests - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Pending') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Pending') end it 'unschedules a job successfully' do @@ -141,7 +141,7 @@ RSpec.describe 'User browses jobs', feature_category: :groups_and_projects do wait_for_requests - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Manual') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Manual') end end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 12ed2558712..050ed4e0e4c 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -66,7 +66,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state, feature_category: :grou wait_for_requests - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Passed') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Passed') end it 'shows commit`s data', :js do diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index f042a12884c..ae0b02dac07 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -224,7 +224,7 @@ RSpec.describe 'Pipeline', :js, feature_category: :continuous_integration do expect(page).not_to have_content('Retry job') within('[data-testid="pipeline-details-header"]') do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Running') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Running') end end end @@ -278,7 +278,7 @@ RSpec.describe 'Pipeline', :js, feature_category: :continuous_integration do expect(page).not_to have_content('Retry job') within('[data-testid="pipeline-details-header"]') do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Running') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Running') end end @@ -312,7 +312,7 @@ RSpec.describe 'Pipeline', :js, feature_category: :continuous_integration do expect(page).not_to have_content('Play job') within('[data-testid="pipeline-details-header"]') do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Running') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Running') end end end @@ -537,7 +537,7 @@ RSpec.describe 'Pipeline', :js, feature_category: :continuous_integration do it 'shows running status in pipeline header', :sidekiq_might_not_need_inline do within('[data-testid="pipeline-details-header"]') do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Running') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Running') end end end @@ -900,7 +900,7 @@ RSpec.describe 'Pipeline', :js, feature_category: :continuous_integration do subject within('[data-testid="pipeline-details-header"]') do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Pending') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Pending') end within('.js-pipeline-graph') do @@ -925,7 +925,7 @@ RSpec.describe 'Pipeline', :js, feature_category: :continuous_integration do subject within('[data-testid="pipeline-details-header"]') do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Running') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Running') end within('.js-pipeline-graph') do @@ -954,7 +954,7 @@ RSpec.describe 'Pipeline', :js, feature_category: :continuous_integration do subject within('[data-testid="pipeline-details-header"]') do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Waiting') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Waiting') end within('.js-pipeline-graph') do @@ -974,7 +974,7 @@ RSpec.describe 'Pipeline', :js, feature_category: :continuous_integration do subject within('[data-testid="pipeline-details-header"]') do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Running') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Running') end within('.js-pipeline-graph') do @@ -1002,7 +1002,7 @@ RSpec.describe 'Pipeline', :js, feature_category: :continuous_integration do subject within('[data-testid="pipeline-details-header"]') do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Waiting') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Waiting') end within('.js-pipeline-graph') do diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 73ff4222b02..be9e99be36f 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -115,7 +115,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :continuous_integration do it 'indicates that pipeline can be canceled' do expect(page).to have_selector('.js-pipelines-cancel-button') - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Running') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Running') end context 'when canceling' do @@ -127,7 +127,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :continuous_integration do it 'indicated that pipelines was canceled', :sidekiq_might_not_need_inline do expect(page).not_to have_selector('.js-pipelines-cancel-button') - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Canceled') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Canceled') end end end @@ -144,7 +144,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :continuous_integration do it 'indicates that pipeline can be retried' do expect(page).to have_selector('.js-pipelines-retry-button') - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Failed') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Failed') end context 'when retrying' do @@ -155,7 +155,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :continuous_integration do it 'shows running pipeline that is not retryable' do expect(page).not_to have_selector('.js-pipelines-retry-button') - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Running') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Running') end end end @@ -396,7 +396,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :continuous_integration do end it 'shows the pipeline as preparing' do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Preparing') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Preparing') end end @@ -417,7 +417,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :continuous_integration do end it 'has pipeline running' do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Running') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Running') end context 'when canceling' do @@ -428,7 +428,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :continuous_integration do it 'indicates that pipeline was canceled', :sidekiq_might_not_need_inline do expect(page).not_to have_selector('.js-pipelines-cancel-button') - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Canceled') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Canceled') end end end @@ -450,7 +450,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :continuous_integration do end it 'has failed pipeline', :sidekiq_might_not_need_inline do - expect(page).to have_selector('[data-testid="ci-badge-link"]', text: 'Failed') + expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Failed') end end end diff --git a/spec/frontend/analytics/shared/components/metric_tile_spec.js b/spec/frontend/analytics/shared/components/metric_tile_spec.js index 9da5ed0fb07..262357a35e4 100644 --- a/spec/frontend/analytics/shared/components/metric_tile_spec.js +++ b/spec/frontend/analytics/shared/components/metric_tile_spec.js @@ -32,8 +32,7 @@ describe('MetricTile', () => { }; wrapper = createComponent({ metric }); - const singleStat = findSingleStat(); - singleStat.vm.$emit('click'); + findSingleStat().vm.$emit('click'); expect(redirectTo).toHaveBeenCalledWith('foo/bar'); // eslint-disable-line import/no-deprecated }); @@ -41,27 +40,31 @@ describe('MetricTile', () => { const metric = { identifier: 'deploys', value: '10', label: 'Deploys' }; wrapper = createComponent({ metric }); - const singleStat = findSingleStat(); - singleStat.vm.$emit('click'); + findSingleStat().vm.$emit('click'); expect(redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated }); }); - describe('decimal places', () => { + describe('number formatting', () => { it(`will render 0 decimal places for an integer value`, () => { const metric = { identifier: 'deploys', value: '10', label: 'Deploys' }; wrapper = createComponent({ metric }); - const singleStat = findSingleStat(); - expect(singleStat.props('animationDecimalPlaces')).toBe(0); + expect(findSingleStat().props('animationDecimalPlaces')).toBe(0); }); it(`will render 1 decimal place for a float value`, () => { const metric = { identifier: 'deploys', value: '10.5', label: 'Deploys' }; wrapper = createComponent({ metric }); - const singleStat = findSingleStat(); - expect(singleStat.props('animationDecimalPlaces')).toBe(1); + expect(findSingleStat().props('animationDecimalPlaces')).toBe(1); + }); + + it('will render using delimiters', () => { + const metric = { identifier: 'deploys', value: '10000', label: 'Deploys' }; + wrapper = createComponent({ metric }); + + expect(findSingleStat().props('useDelimiters')).toBe(true); }); }); diff --git a/spec/frontend/blob/components/blob_header_spec.js b/spec/frontend/blob/components/blob_header_spec.js index 922d6a0211b..e7b2ee74940 100644 --- a/spec/frontend/blob/components/blob_header_spec.js +++ b/spec/frontend/blob/components/blob_header_spec.js @@ -116,13 +116,22 @@ describe('Blob Header Default Actions', () => { }); }); + it.each([[{ showBlameToggle: true }], [{ showBlameToggle: false }]])( + 'passes the `showBlameToggle` prop to the viewer switcher', + (propsData) => { + createComponent({ propsData }); + + expect(findViewSwitcher().props('showBlameToggle')).toBe(propsData.showBlameToggle); + }, + ); + it('does not render viewer switcher if the blob has only the simple viewer', () => { createComponent({ blobProps: { richViewer: null, }, }); - expect(findViewSwitcher().exists()).toBe(false); + expect(findViewSwitcher().props('showViewerToggles')).toBe(false); }); it('does not render viewer switcher if a corresponding prop is passed', () => { diff --git a/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js b/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js index 2ef87f6664b..25d9642acb0 100644 --- a/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js +++ b/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js @@ -1,6 +1,6 @@ import { GlButtonGroup, GlButton } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; import { nextTick } from 'vue'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import BlobHeaderViewerSwitcher from '~/blob/components/blob_header_viewer_switcher.vue'; import { RICH_BLOB_VIEWER, @@ -12,14 +12,15 @@ import { describe('Blob Header Viewer Switcher', () => { let wrapper; - function createComponent(propsData = {}) { - wrapper = mount(BlobHeaderViewerSwitcher, { + function createComponent(propsData = { showViewerToggles: true }) { + wrapper = mountExtended(BlobHeaderViewerSwitcher, { propsData, }); } const findSimpleViewerButton = () => wrapper.findComponent('[data-viewer="simple"]'); const findRichViewerButton = () => wrapper.findComponent('[data-viewer="rich"]'); + const findBlameButton = () => wrapper.findByText('Blame'); describe('intiialization', () => { it('is initialized with simple viewer as active', () => { @@ -74,7 +75,7 @@ describe('Blob Header Viewer Switcher', () => { }); it('emits an event when a Simple Viewer button is clicked', async () => { - createComponent({ value: RICH_BLOB_VIEWER }); + createComponent({ value: RICH_BLOB_VIEWER, showViewerToggles: true }); findSimpleViewerButton().vm.$emit('click'); await nextTick(); @@ -82,4 +83,28 @@ describe('Blob Header Viewer Switcher', () => { expect(wrapper.emitted('input')).toEqual([[SIMPLE_BLOB_VIEWER]]); }); }); + + it('does not render simple and rich viewer buttons if `showViewerToggles` is `false`', async () => { + createComponent({ showViewerToggles: false }); + await nextTick(); + + expect(findSimpleViewerButton().exists()).toBe(false); + expect(findRichViewerButton().exists()).toBe(false); + }); + + it('does not render a Blame button if `showBlameToggle` is `false`', async () => { + createComponent({ showBlameToggle: false }); + await nextTick(); + + expect(findBlameButton().exists()).toBe(false); + }); + + it('emits an event when the Blame button is clicked', async () => { + createComponent({ showBlameToggle: true }); + + findBlameButton().trigger('click'); + await nextTick(); + + expect(wrapper.emitted('blame')).toHaveLength(1); + }); }); diff --git a/spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js b/spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js index 3628af31aa1..ba77d90f4e2 100644 --- a/spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js +++ b/spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js @@ -2,7 +2,7 @@ import { GlLoadingIcon, GlTable, GlLink, GlPagination, GlModal, GlFormCheckbox } import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import getJobArtifactsResponse from 'test_fixtures/graphql/ci/artifacts/graphql/queries/get_job_artifacts.query.graphql.json'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import waitForPromises from 'helpers/wait_for_promises'; import JobArtifactsTable from '~/ci/artifacts/components/job_artifacts_table.vue'; import ArtifactsTableRowDetails from '~/ci/artifacts/components/artifacts_table_row_details.vue'; @@ -51,7 +51,7 @@ describe('JobArtifactsTable component', () => { const findStatuses = () => wrapper.findAllByTestId('job-artifacts-job-status'); const findSuccessfulJobStatus = () => findStatuses().at(0); - const findCiBadgeLink = () => findSuccessfulJobStatus().findComponent(CiBadgeLink); + const findCiIcon = () => findSuccessfulJobStatus().findComponent(CiIcon); const findLinks = () => wrapper.findAllComponents(GlLink); const findJobLink = () => findLinks().at(0); @@ -201,12 +201,11 @@ describe('JobArtifactsTable component', () => { }); it('shows the job status as an icon for a successful job', () => { - expect(findCiBadgeLink().props()).toMatchObject({ + expect(findCiIcon().props()).toMatchObject({ status: { group: 'success', }, - size: 'sm', - showText: false, + showStatusText: false, }); }); diff --git a/spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js b/spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js index 6ab9520508d..c061332ba13 100644 --- a/spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js +++ b/spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js @@ -2,8 +2,8 @@ import { GlAvatar, GlAvatarLink, GlBadge } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import CiResourceHeader from '~/ci/catalog/components/details/ci_resource_header.vue'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; import CiResourceAbout from '~/ci/catalog/components/details/ci_resource_about.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import { catalogSharedDataMock, catalogAdditionalDetailsMock } from '../../mock'; describe('CiResourceHeader', () => { @@ -24,7 +24,7 @@ describe('CiResourceHeader', () => { const findAvatar = () => wrapper.findComponent(GlAvatar); const findAvatarLink = () => wrapper.findComponent(GlAvatarLink); const findVersionBadge = () => wrapper.findComponent(GlBadge); - const findPipelineStatusBadge = () => wrapper.findComponent(CiBadgeLink); + const findPipelineStatusBadge = () => wrapper.findComponent(CiIcon); const createComponent = ({ props = {} } = {}) => { wrapper = shallowMountExtended(CiResourceHeader, { @@ -126,8 +126,7 @@ describe('CiResourceHeader', () => { expect(findPipelineStatusBadge().exists()).toBe(hasPipelineBadge); if (hasPipelineBadge) { expect(findPipelineStatusBadge().props()).toEqual({ - showText: true, - size: 'sm', + showStatusText: true, status: pipelineStatus, showTooltip: true, useLink: true, diff --git a/spec/frontend/ci/common/pipelines_table_spec.js b/spec/frontend/ci/common/pipelines_table_spec.js index 6cf391d72ca..f6d3121109f 100644 --- a/spec/frontend/ci/common/pipelines_table_spec.js +++ b/spec/frontend/ci/common/pipelines_table_spec.js @@ -16,7 +16,7 @@ import { TRACKING_CATEGORIES, } from '~/ci/constants'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; describe('Pipelines Table', () => { let wrapper; @@ -58,7 +58,7 @@ describe('Pipelines Table', () => { }; const findGlTableLite = () => wrapper.findComponent(GlTableLite); - const findCiBadgeLink = () => wrapper.findComponent(CiBadgeLink); + const findCiIcon = () => wrapper.findComponent(CiIcon); const findPipelineInfo = () => wrapper.findComponent(PipelineUrl); const findTriggerer = () => wrapper.findComponent(PipelineTriggerer); const findLegacyPipelineMiniGraph = () => wrapper.findComponent(LegacyPipelineMiniGraph); @@ -96,7 +96,7 @@ describe('Pipelines Table', () => { describe('status cell', () => { it('should render a status badge', () => { - expect(findCiBadgeLink().exists()).toBe(true); + expect(findCiIcon().exists()).toBe(true); }); }); @@ -265,7 +265,7 @@ describe('Pipelines Table', () => { }); it('tracks status badge click', () => { - findCiBadgeLink().vm.$emit('ciStatusBadgeClick'); + findCiIcon().vm.$emit('ciStatusBadgeClick'); expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_ci_status_badge', { label: TRACKING_CATEGORIES.table, diff --git a/spec/frontend/ci/job_details/components/job_header_spec.js b/spec/frontend/ci/job_details/components/job_header_spec.js index 609369316f5..d12267807ac 100644 --- a/spec/frontend/ci/job_details/components/job_header_spec.js +++ b/spec/frontend/ci/job_details/components/job_header_spec.js @@ -1,7 +1,7 @@ import { GlButton, GlAvatarLink, GlTooltip } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import JobHeader from '~/ci/job_details/components/job_header.vue'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; @@ -29,7 +29,7 @@ describe('Header CI Component', () => { shouldRenderTriggeredLabel: true, }; - const findCiBadgeLink = () => wrapper.findComponent(CiBadgeLink); + const findCiIcon = () => wrapper.findComponent(CiIcon); const findTimeAgo = () => wrapper.findComponent(TimeagoTooltip); const findUserLink = () => wrapper.findComponent(GlAvatarLink); const findSidebarToggleBtn = () => wrapper.findComponent(GlButton); @@ -57,7 +57,7 @@ describe('Header CI Component', () => { }); it('should render status badge', () => { - expect(findCiBadgeLink().exists()).toBe(true); + expect(findCiIcon().exists()).toBe(true); }); it('should render timeago date', () => { diff --git a/spec/frontend/ci/job_details/components/sidebar/stages_dropdown_spec.js b/spec/frontend/ci/job_details/components/sidebar/stages_dropdown_spec.js index e007896c81e..54c5a73f757 100644 --- a/spec/frontend/ci/job_details/components/sidebar/stages_dropdown_spec.js +++ b/spec/frontend/ci/job_details/components/sidebar/stages_dropdown_spec.js @@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils'; import { Mousetrap } from '~/lib/mousetrap'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import StagesDropdown from '~/ci/job_details/components/sidebar/stages_dropdown.vue'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import * as copyToClipboard from '~/behaviors/copy_to_clipboard'; import { mockPipelineWithoutRef, @@ -15,7 +15,7 @@ import { describe('Stages Dropdown', () => { let wrapper; - const findStatus = () => wrapper.findComponent(CiBadgeLink); + const findStatus = () => wrapper.findComponent(CiIcon); const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown); const findSelectedStageText = () => findDropdown().props('toggleText'); @@ -47,7 +47,6 @@ describe('Stages Dropdown', () => { it('renders pipeline status', () => { expect(findStatus().props('status')).toBe(mockPipelineWithoutMR.details.status); - expect(findStatus().props('size')).toBe('sm'); }); it('renders dropdown with stages', () => { diff --git a/spec/frontend/ci/jobs_page/components/jobs_table_spec.js b/spec/frontend/ci/jobs_page/components/jobs_table_spec.js index d4e0ce92bc2..d14afe7dd3e 100644 --- a/spec/frontend/ci/jobs_page/components/jobs_table_spec.js +++ b/spec/frontend/ci/jobs_page/components/jobs_table_spec.js @@ -3,7 +3,7 @@ import { mount } from '@vue/test-utils'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import JobsTable from '~/ci/jobs_page/components/jobs_table.vue'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import { DEFAULT_FIELDS_ADMIN } from '~/ci/admin/jobs_table/constants'; import ProjectCell from '~/ci/admin/jobs_table/components/cells/project_cell.vue'; import RunnerCell from '~/ci/admin/jobs_table/components/cells/runner_cell.vue'; @@ -13,7 +13,7 @@ describe('Jobs Table', () => { let wrapper; const findTable = () => wrapper.findComponent(GlTable); - const findCiBadgeLink = () => wrapper.findComponent(CiBadgeLink); + const findCiIcon = () => wrapper.findComponent(CiIcon); const findTableRows = () => wrapper.findAllByTestId('jobs-table-row'); const findJobStage = () => wrapper.findByTestId('job-stage-name'); const findJobName = () => wrapper.findByTestId('job-name'); @@ -45,7 +45,7 @@ describe('Jobs Table', () => { }); it('displays job status', () => { - expect(findCiBadgeLink().exists()).toBe(true); + expect(findCiIcon().exists()).toBe(true); }); it('displays the job stage, id and name', () => { diff --git a/spec/frontend/ci/pipeline_details/graph/components/job_item_spec.js b/spec/frontend/ci/pipeline_details/graph/components/job_item_spec.js index de9ee8a16bf..cda84a9e4d3 100644 --- a/spec/frontend/ci/pipeline_details/graph/components/job_item_spec.js +++ b/spec/frontend/ci/pipeline_details/graph/components/job_item_spec.js @@ -5,7 +5,7 @@ import JobItem from '~/ci/pipeline_details/graph/components/job_item.vue'; import axios from '~/lib/utils/axios_utils'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import ActionComponent from '~/ci/common/private/job_action_component.vue'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { @@ -31,7 +31,7 @@ describe('pipeline graph job item', () => { const findActionComponent = () => wrapper.findByTestId('ci-action-button'); const findBadge = () => wrapper.findByTestId('job-bridge-badge'); const findJobLink = () => wrapper.findByTestId('job-with-link'); - const findJobCiBadge = () => wrapper.findComponent(CiBadgeLink); + const findJobCiIcon = () => wrapper.findComponent(CiIcon); const findModal = () => wrapper.findComponent(GlModal); const clickOnModalPrimaryBtn = () => findModal().vm.$emit('primary'); @@ -60,7 +60,7 @@ describe('pipeline graph job item', () => { ...mocks, }, stubs: { - CiBadgeLink, + CiIcon, }, }); }; @@ -86,8 +86,8 @@ describe('pipeline graph job item', () => { expect(link.attributes('title')).toBe(`${mockJob.name} - ${mockJob.status.label}`); - expect(findJobCiBadge().exists()).toBe(true); - expect(findJobCiBadge().find('.ci-status-icon-success').exists()).toBe(true); + expect(findJobCiIcon().exists()).toBe(true); + expect(findJobCiIcon().find('.ci-status-icon-success').exists()).toBe(true); expect(wrapper.text()).toBe(mockJob.name); }); @@ -105,8 +105,8 @@ describe('pipeline graph job item', () => { }); it('should render status and name', () => { - expect(findJobCiBadge().exists()).toBe(true); - expect(findJobCiBadge().find('.ci-status-icon-success').exists()).toBe(true); + expect(findJobCiIcon().exists()).toBe(true); + expect(findJobCiIcon().find('.ci-status-icon-success').exists()).toBe(true); expect(findJobLink().exists()).toBe(false); expect(wrapper.text()).toBe(mockJobWithoutDetails.name); @@ -117,12 +117,12 @@ describe('pipeline graph job item', () => { }); }); - describe('CiBadgeLink', () => { + describe('CiIcon', () => { it('should not render a link', () => { createWrapper(); - expect(findJobCiBadge().exists()).toBe(true); - expect(findJobCiBadge().props('useLink')).toBe(false); + expect(findJobCiIcon().exists()).toBe(true); + expect(findJobCiIcon().props('useLink')).toBe(false); }); }); diff --git a/spec/frontend/ci/pipeline_details/header/pipeline_details_header_spec.js b/spec/frontend/ci/pipeline_details/header/pipeline_details_header_spec.js index 6e13658a773..6b8a525a15a 100644 --- a/spec/frontend/ci/pipeline_details/header/pipeline_details_header_spec.js +++ b/spec/frontend/ci/pipeline_details/header/pipeline_details_header_spec.js @@ -7,7 +7,7 @@ import waitForPromises from 'helpers/wait_for_promises'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import PipelineDetailsHeader from '~/ci/pipeline_details/header/pipeline_details_header.vue'; import { BUTTON_TOOLTIP_RETRY, BUTTON_TOOLTIP_CANCEL } from '~/ci/constants'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import cancelPipelineMutation from '~/ci/pipeline_details/graphql/mutations/cancel_pipeline.mutation.graphql'; import deletePipelineMutation from '~/ci/pipeline_details/graphql/mutations/delete_pipeline.mutation.graphql'; import retryPipelineMutation from '~/ci/pipeline_details/graphql/mutations/retry_pipeline.mutation.graphql'; @@ -56,7 +56,7 @@ describe('Pipeline details header', () => { .mockResolvedValue(pipelineDeleteMutationResponseFailed); const findAlert = () => wrapper.findComponent(GlAlert); - const findStatus = () => wrapper.findComponent(CiBadgeLink); + const findStatus = () => wrapper.findComponent(CiIcon); const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findAllBadges = () => wrapper.findAllComponents(GlBadge); const findDeleteModal = () => wrapper.findComponent(GlModal); diff --git a/spec/frontend/ci/pipeline_mini_graph/legacy_pipeline_stage_spec.js b/spec/frontend/ci/pipeline_mini_graph/legacy_pipeline_stage_spec.js index 4b357a9fc7c..87df7676bf1 100644 --- a/spec/frontend/ci/pipeline_mini_graph/legacy_pipeline_stage_spec.js +++ b/spec/frontend/ci/pipeline_mini_graph/legacy_pipeline_stage_spec.js @@ -2,7 +2,7 @@ import { GlDropdown } from '@gitlab/ui'; import { nextTick } from 'vue'; import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import axios from '~/lib/utils/axios_utils'; import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import LegacyPipelineStage from '~/ci/pipeline_mini_graph/legacy_pipeline_stage.vue'; @@ -52,7 +52,7 @@ describe('Pipelines stage component', () => { }); const findCiActionBtn = () => wrapper.find('.js-ci-action'); - const findCiIcon = () => wrapper.findComponent(CiBadgeLink); + const findCiIcon = () => wrapper.findComponent(CiIcon); const findDropdown = () => wrapper.findComponent(GlDropdown); const findDropdownToggle = () => wrapper.find('button.dropdown-toggle'); const findDropdownMenu = () => diff --git a/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js b/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js index ae069145292..b79e7c6e251 100644 --- a/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js +++ b/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js @@ -1,5 +1,5 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import PipelineScheduleLastPipeline from '~/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline.vue'; import { mockPipelineScheduleNodes } from '../../../mock_data'; @@ -18,16 +18,14 @@ describe('Pipeline schedule last pipeline', () => { }); }; - const findCIBadgeLink = () => wrapper.findComponent(CiBadgeLink); + const findCiIcon = () => wrapper.findComponent(CiIcon); const findStatusText = () => wrapper.findByTestId('pipeline-schedule-status-text'); it('displays pipeline status', () => { createComponent(); - expect(findCIBadgeLink().exists()).toBe(true); - expect(findCIBadgeLink().props('status')).toBe( - defaultProps.schedule.lastPipeline.detailedStatus, - ); + expect(findCiIcon().exists()).toBe(true); + expect(findCiIcon().props('status')).toBe(defaultProps.schedule.lastPipeline.detailedStatus); expect(findStatusText().exists()).toBe(false); }); @@ -35,6 +33,6 @@ describe('Pipeline schedule last pipeline', () => { createComponent({ schedule: mockPipelineScheduleNodes[0] }); expect(findStatusText().text()).toBe('None'); - expect(findCIBadgeLink().exists()).toBe(false); + expect(findCiIcon().exists()).toBe(false); }); }); diff --git a/spec/frontend/commit/components/commit_box_pipeline_status_spec.js b/spec/frontend/commit/components/commit_box_pipeline_status_spec.js index 844a2d81832..008a1b2c068 100644 --- a/spec/frontend/commit/components/commit_box_pipeline_status_spec.js +++ b/spec/frontend/commit/components/commit_box_pipeline_status_spec.js @@ -5,7 +5,7 @@ import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert } from '~/alert'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import CommitBoxPipelineStatus from '~/projects/commit_box/info/components/commit_box_pipeline_status.vue'; import { COMMIT_BOX_POLL_INTERVAL, @@ -32,7 +32,7 @@ describe('Commit box pipeline status', () => { const failedHandler = jest.fn().mockRejectedValue(new Error('GraphQL error')); const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); - const findCiBadgeLink = () => wrapper.findComponent(CiBadgeLink); + const findCiIcon = () => wrapper.findComponent(CiIcon); const advanceToNextFetch = () => { jest.advanceTimersByTime(COMMIT_BOX_POLL_INTERVAL); @@ -50,7 +50,7 @@ describe('Commit box pipeline status', () => { ...mockProvide, }, stubs: { - CiBadgeLink, + CiIcon, }, apolloProvider: createMockApolloProvider(handler), }); @@ -61,7 +61,7 @@ describe('Commit box pipeline status', () => { createComponent(); expect(findLoadingIcon().exists()).toBe(true); - expect(findCiBadgeLink().exists()).toBe(false); + expect(findCiIcon().exists()).toBe(false); }); }); @@ -73,7 +73,7 @@ describe('Commit box pipeline status', () => { }); it('should display pipeline status after the query is resolved successfully', () => { - expect(findCiBadgeLink().exists()).toBe(true); + expect(findCiIcon().exists()).toBe(true); expect(findLoadingIcon().exists()).toBe(false); expect(createAlert).toHaveBeenCalledTimes(0); @@ -90,7 +90,7 @@ describe('Commit box pipeline status', () => { }, } = mockPipelineStatusResponse; - expect(findCiBadgeLink().attributes('href')).toBe(detailsPath); + expect(findCiIcon().attributes('href')).toBe(detailsPath); }); }); diff --git a/spec/frontend/helpers/help_page_helper_spec.js b/spec/frontend/helpers/help_page_helper_spec.js index 09c1a113a96..44a7300097f 100644 --- a/spec/frontend/helpers/help_page_helper_spec.js +++ b/spec/frontend/helpers/help_page_helper_spec.js @@ -3,6 +3,7 @@ import { helpPagePath } from '~/helpers/help_page_helper'; describe('help page helper', () => { it.each` relative_url_root | path | anchor | expected + ${undefined} | ${undefined} | ${undefined} | ${'/help'} ${undefined} | ${'administration/index'} | ${undefined} | ${'/help/administration/index'} ${''} | ${'administration/index'} | ${undefined} | ${'/help/administration/index'} ${'/'} | ${'administration/index'} | ${undefined} | ${'/help/administration/index'} diff --git a/spec/frontend/ide/components/jobs/detail/description_spec.js b/spec/frontend/ide/components/jobs/detail/description_spec.js index 2bb0f3fccf4..5465615bf38 100644 --- a/spec/frontend/ide/components/jobs/detail/description_spec.js +++ b/spec/frontend/ide/components/jobs/detail/description_spec.js @@ -23,10 +23,6 @@ describe('IDE job description', () => { expect(wrapper.find('.ci-status-icon').findComponent(GlIcon).exists()).toBe(true); }); - it('renders a borderless CI icon', () => { - expect(wrapper.find('.borderless').findComponent(GlIcon).exists()).toBe(true); - }); - it('renders bridge job details without the job link', () => { wrapper = mount(Description, { propsData: { diff --git a/spec/frontend/ide/components/jobs/item_spec.js b/spec/frontend/ide/components/jobs/item_spec.js index ab442a27817..dc0f732d11e 100644 --- a/spec/frontend/ide/components/jobs/item_spec.js +++ b/spec/frontend/ide/components/jobs/item_spec.js @@ -18,7 +18,7 @@ describe('IDE jobs item', () => { }); it('renders CI icon', () => { - expect(wrapper.find('[data-testid="status_success_borderless-icon"]').exists()).toBe(true); + expect(wrapper.find('[data-testid="status_success-icon"]').exists()).toBe(true); }); it('does not render view logs button if not started', async () => { diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions_spec.js index 40ea6058c70..efe89100e90 100644 --- a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions_spec.js +++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions_spec.js @@ -1,15 +1,23 @@ +import { nextTick } from 'vue'; import { shallowMount } from '@vue/test-utils'; -import { GlButton, GlLink } from '@gitlab/ui'; +import { GlButton, GlFormCheckbox, GlLink } from '@gitlab/ui'; -import { OAUTH_SELF_MANAGED_DOC_LINK } from '~/jira_connect/subscriptions/constants'; +import { + PREREQUISITES_DOC_LINK, + OAUTH_SELF_MANAGED_DOC_LINK, + SET_UP_INSTANCE_DOC_LINK, +} from '~/jira_connect/subscriptions/constants'; import SetupInstructions from '~/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions.vue'; describe('SetupInstructions', () => { let wrapper; - const findGlLink = () => wrapper.findComponent(GlLink); + const findPrerequisitesGlLink = () => wrapper.findAllComponents(GlLink).at(0); + const findOAuthGlLink = () => wrapper.findAllComponents(GlLink).at(1); + const findSetUpInstanceGlLink = () => wrapper.findAllComponents(GlLink).at(2); const findBackButton = () => wrapper.findAllComponents(GlButton).at(0); const findNextButton = () => wrapper.findAllComponents(GlButton).at(1); + const findCheckboxAtIndex = (index) => wrapper.findAllComponents(GlFormCheckbox).at(index); const createComponent = () => { wrapper = shallowMount(SetupInstructions); @@ -20,8 +28,34 @@ describe('SetupInstructions', () => { createComponent(); }); - it('renders "Learn more" link to documentation', () => { - expect(findGlLink().attributes('href')).toBe(OAUTH_SELF_MANAGED_DOC_LINK); + it('renders "Prerequisites" link to documentation', () => { + expect(findPrerequisitesGlLink().attributes('href')).toBe(PREREQUISITES_DOC_LINK); + }); + + it('renders "Set up OAuth authentication" link to documentation', () => { + expect(findOAuthGlLink().attributes('href')).toBe(OAUTH_SELF_MANAGED_DOC_LINK); + }); + + it('renders "Set up your instance" link to documentation', () => { + expect(findSetUpInstanceGlLink().attributes('href')).toBe(SET_UP_INSTANCE_DOC_LINK); + }); + + describe('NextButton', () => { + it('emits next event when clicked and all steps checked', async () => { + createComponent(); + + findCheckboxAtIndex(0).vm.$emit('input', true); + findCheckboxAtIndex(1).vm.$emit('input', true); + findCheckboxAtIndex(2).vm.$emit('input', true); + + await nextTick(); + + expect(findNextButton().attributes('disabled')).toBeUndefined(); + }); + + it('disables button when not all steps are checked', () => { + expect(findNextButton().attributes('disabled')).toBe('true'); + }); }); describe('when "Next" button is clicked', () => { diff --git a/spec/frontend/observability/client_spec.js b/spec/frontend/observability/client_spec.js index e61153fb5e5..7da84b18a91 100644 --- a/spec/frontend/observability/client_spec.js +++ b/spec/frontend/observability/client_spec.js @@ -53,13 +53,13 @@ describe('buildClient', () => { }); }); - describe('isTracingEnabled', () => { + describe('isObservabilityEnabled', () => { it('returns true if requests succeedes', async () => { axiosMock.onGet(provisioningUrl).reply(200, { status: 'ready', }); - const enabled = await client.isTracingEnabled(); + const enabled = await client.isObservabilityEnabled(); expect(enabled).toBe(true); }); @@ -67,7 +67,7 @@ describe('buildClient', () => { it('returns false if response is 404', async () => { axiosMock.onGet(provisioningUrl).reply(404); - const enabled = await client.isTracingEnabled(); + const enabled = await client.isObservabilityEnabled(); expect(enabled).toBe(false); }); @@ -79,7 +79,7 @@ describe('buildClient', () => { status: 'not ready', }); - const enabled = await client.isTracingEnabled(); + const enabled = await client.isObservabilityEnabled(); expect(enabled).toBe(true); }); @@ -88,7 +88,7 @@ describe('buildClient', () => { axiosMock.onGet(provisioningUrl).reply(500); const e = 'Request failed with status code 500'; - await expect(client.isTracingEnabled()).rejects.toThrow(e); + await expect(client.isObservabilityEnabled()).rejects.toThrow(e); expect(Sentry.captureException).toHaveBeenCalledWith(new Error(e)); }); @@ -96,12 +96,12 @@ describe('buildClient', () => { axiosMock.onGet(provisioningUrl).reply(200, {}); const e = 'Failed to check provisioning'; - await expect(client.isTracingEnabled()).rejects.toThrow(e); + await expect(client.isObservabilityEnabled()).rejects.toThrow(e); expect(Sentry.captureException).toHaveBeenCalledWith(new Error(e)); }); }); - describe('enableTraces', () => { + describe('enableObservability', () => { it('makes a PUT request to the provisioning URL', async () => { let putConfig; axiosMock.onPut(provisioningUrl).reply((config) => { @@ -109,7 +109,7 @@ describe('buildClient', () => { return [200]; }); - await client.enableTraces(); + await client.enableObservability(); expect(putConfig.withCredentials).toBe(true); }); @@ -119,7 +119,7 @@ describe('buildClient', () => { const e = 'Request failed with status code 401'; - await expect(client.enableTraces()).rejects.toThrow(e); + await expect(client.enableObservability()).rejects.toThrow(e); expect(Sentry.captureException).toHaveBeenCalledWith(new Error(e)); }); }); diff --git a/spec/frontend/observability/observability_container_spec.js b/spec/frontend/observability/observability_container_spec.js index 5d838756308..1e954b7db0d 100644 --- a/spec/frontend/observability/observability_container_spec.js +++ b/spec/frontend/observability/observability_container_spec.js @@ -19,10 +19,12 @@ describe('ObservabilityContainer', () => { const SERVICES_URL = 'https://example.com/services'; const OPERATIONS_URL = 'https://example.com/operations'; + const mockClient = { mock: 'client' }; + beforeEach(() => { jest.spyOn(console, 'error').mockImplementation(); - buildClient.mockReturnValue({}); + buildClient.mockReturnValue(mockClient); wrapper = shallowMountExtended(ObservabilityContainer, { propsData: { @@ -43,12 +45,6 @@ describe('ObservabilityContainer', () => { h(`<div>mockedComponent</div>`); }, name: 'MockComponent', - props: { - observabilityClient: { - type: Object, - required: true, - }, - }, }, }, }); @@ -85,22 +81,38 @@ describe('ObservabilityContainer', () => { expect(findSlotComponent().exists()).toBe(false); }); - it('renders the slot content and removes the iframe on oauth success message', async () => { - dispatchMessageEvent('success'); + it('should not emit observability-client-ready', () => { + expect(wrapper.emitted('observability-client-ready')).toBeUndefined(); + }); - await nextTick(); + describe('on oauth success message', () => { + beforeEach(async () => { + dispatchMessageEvent('success'); - expect(mockSkeletonOnContentLoaded).toHaveBeenCalledTimes(1); + await nextTick(); + }); - const slotComponent = findSlotComponent(); - expect(slotComponent.exists()).toBe(true); - expect(buildClient).toHaveBeenCalledWith({ - provisioningUrl: PROVISIONING_URL, - tracingUrl: TRACING_URL, - servicesUrl: SERVICES_URL, - operationsUrl: OPERATIONS_URL, + it('renders invoke onContentLoaded on the skeleton', () => { + expect(mockSkeletonOnContentLoaded).toHaveBeenCalledTimes(1); + }); + + it('renders the slot content', () => { + const slotComponent = findSlotComponent(); + expect(slotComponent.exists()).toBe(true); + }); + + it('build the observability client', () => { + expect(buildClient).toHaveBeenCalledWith({ + provisioningUrl: PROVISIONING_URL, + tracingUrl: TRACING_URL, + servicesUrl: SERVICES_URL, + operationsUrl: OPERATIONS_URL, + }); + }); + + it('emits observability-client-ready', () => { + expect(wrapper.emitted('observability-client-ready')).toEqual([[mockClient]]); }); - expect(findIframe().exists()).toBe(false); }); it('does not render the slot content and removes the iframe on oauth error message', async () => { diff --git a/spec/frontend/observability/observability_empty_state_spec.js b/spec/frontend/observability/observability_empty_state_spec.js new file mode 100644 index 00000000000..ce420dd076d --- /dev/null +++ b/spec/frontend/observability/observability_empty_state_spec.js @@ -0,0 +1,36 @@ +import { GlButton, GlEmptyState } from '@gitlab/ui'; +import ObservabilityEmptyState from '~/observability/components/observability_empty_state.vue'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; + +describe('ObservabilityEmptyState', () => { + let wrapper; + + const findEnableButton = () => wrapper.findComponent(GlButton); + + beforeEach(() => { + wrapper = shallowMountExtended(ObservabilityEmptyState); + }); + + it('passes the correct title', () => { + expect(wrapper.findComponent(GlEmptyState).props('title')).toBe( + 'Get started with GitLab Observability', + ); + }); + + it('displays the correct description', () => { + const description = wrapper.find('span').text(); + expect(description).toBe('Monitor your applications with GitLab Observability.'); + }); + + it('displays the enable button', () => { + const enableButton = findEnableButton(); + expect(enableButton.exists()).toBe(true); + expect(enableButton.text()).toBe('Enable'); + }); + + it('emits enable-tracing when enable button is clicked', () => { + findEnableButton().vm.$emit('click'); + + expect(wrapper.emitted('enable-observability')).toHaveLength(1); + }); +}); diff --git a/spec/frontend/observability/provisioned_observability_container_spec.js b/spec/frontend/observability/provisioned_observability_container_spec.js new file mode 100644 index 00000000000..7d84c1e80e7 --- /dev/null +++ b/spec/frontend/observability/provisioned_observability_container_spec.js @@ -0,0 +1,151 @@ +import { GlLoadingIcon } from '@gitlab/ui'; +import ProvisionedObservabilityContainer from '~/observability/components/provisioned_observability_container.vue'; +import ObservabilityContainer from '~/observability/components/observability_container.vue'; +import ObservabilityEmptyState from '~/observability/components/observability_empty_state.vue'; +import waitForPromises from 'helpers/wait_for_promises'; + +import { createAlert } from '~/alert'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; + +jest.mock('~/alert'); + +describe('ProvisionedObservabilityContainer', () => { + let wrapper; + let mockClient; + + const mockClientReady = async () => { + await wrapper + .findComponent(ObservabilityContainer) + .vm.$emit('observability-client-ready', mockClient); + }; + + const mockClientReadyAndWait = async () => { + await wrapper + .findComponent(ObservabilityContainer) + .vm.$emit('observability-client-ready', mockClient); + await waitForPromises(); + }; + + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findEmptyState = () => wrapper.findComponent(ObservabilityEmptyState); + const findSlotComponent = () => wrapper.findComponent({ name: 'MockComponent' }); + + const props = { + oauthUrl: 'https://example.com/oauth', + tracingUrl: 'https://example.com/tracing', + servicesUrl: 'https://example.com/services', + provisioningUrl: 'https://example.com/provisioning', + operationsUrl: 'https://example.com/operations', + }; + + beforeEach(() => { + mockClient = { + isObservabilityEnabled: jest.fn().mockResolvedValue(true), + enableObservability: jest.fn().mockResolvedValue(true), + }; + wrapper = shallowMountExtended(ProvisionedObservabilityContainer, { + propsData: props, + slots: { + default: { + render(h) { + h(`<div>mockedComponent</div>`); + }, + name: 'MockComponent', + }, + }, + }); + }); + + it('renders the observability-container', () => { + const observabilityContainer = wrapper.findComponent(ObservabilityContainer); + expect(observabilityContainer.exists()).toBe(true); + expect(observabilityContainer.props()).toMatchObject(props); + }); + + describe('when the client is ready', () => { + it('renders the loading indicator while checking if observability is enabled', async () => { + await mockClientReady(); + + expect(findLoadingIcon().exists()).toBe(true); + expect(findEmptyState().exists()).toBe(false); + expect(findSlotComponent().exists()).toBe(false); + expect(mockClient.isObservabilityEnabled).toHaveBeenCalledTimes(1); + }); + + describe('if observability is enabled', () => { + beforeEach(async () => { + mockClient.isObservabilityEnabled.mockResolvedValue(true); + await mockClientReadyAndWait(); + }); + + it('renders the content slot', () => { + expect(findLoadingIcon().exists()).toBe(false); + expect(findEmptyState().exists()).toBe(false); + expect(findSlotComponent().exists()).toBe(true); + }); + }); + + describe('if observability is not enabled', () => { + beforeEach(async () => { + mockClient.isObservabilityEnabled.mockResolvedValue(false); + await mockClientReadyAndWait(); + }); + + it('renders the empty state', () => { + expect(findLoadingIcon().exists()).toBe(false); + expect(findEmptyState().exists()).toBe(true); + expect(findSlotComponent().exists()).toBe(false); + }); + + describe('when empty-state emits enable-observability', () => { + it('shows the loading icon', async () => { + await findEmptyState().vm.$emit('enable-observability'); + + expect(findLoadingIcon().exists()).toBe(true); + }); + + it('enable observability', async () => { + await findEmptyState().vm.$emit('enable-observability'); + + expect(mockClient.enableObservability).toHaveBeenCalledTimes(1); + }); + + it('shows the content slot', async () => { + await findEmptyState().vm.$emit('enable-observability'); + await waitForPromises(); + + expect(findLoadingIcon().exists()).toBe(false); + expect(findEmptyState().exists()).toBe(false); + expect(findSlotComponent().exists()).toBe(true); + }); + }); + }); + }); + + describe('error handling', () => { + it('shows an alert if checking if observability is enabled fails', async () => { + mockClient.isObservabilityEnabled.mockRejectedValue(new Error('error')); + + await mockClientReadyAndWait(); + + expect(findLoadingIcon().exists()).toBe(false); + expect(findEmptyState().exists()).toBe(false); + expect(findSlotComponent().exists()).toBe(false); + expect(createAlert).toHaveBeenLastCalledWith({ message: 'Failed to load page.' }); + }); + + it('shows an alert when checking if observability is enabled fails', async () => { + mockClient.isObservabilityEnabled.mockResolvedValue(false); + mockClient.enableObservability.mockRejectedValue(new Error('error')); + + await mockClientReadyAndWait(); + + await findEmptyState().vm.$emit('enable-observability'); + await waitForPromises(); + + expect(createAlert).toHaveBeenLastCalledWith({ + message: 'Failed to enable GitLab Observability.', + }); + }); + }); +}); diff --git a/spec/frontend/projects/pipelines/charts/components/__snapshots__/statistics_list_spec.js.snap b/spec/frontend/projects/pipelines/charts/components/__snapshots__/statistics_list_spec.js.snap index 16d291804cc..a2877a9f17c 100644 --- a/spec/frontend/projects/pipelines/charts/components/__snapshots__/statistics_list_spec.js.snap +++ b/spec/frontend/projects/pipelines/charts/components/__snapshots__/statistics_list_spec.js.snap @@ -7,7 +7,7 @@ exports[`StatisticsList displays the counts data with labels 1`] = ` Total: </span> <strong> - 4 pipelines + 40,000 pipelines </strong> </li> <li> @@ -15,7 +15,7 @@ exports[`StatisticsList displays the counts data with labels 1`] = ` Successful: </span> <strong> - 2 pipelines + 20,000 pipelines </strong> </li> <li> @@ -25,7 +25,7 @@ exports[`StatisticsList displays the counts data with labels 1`] = ` <gl-link-stub href="/flightjs/Flight/-/pipelines?page=1&scope=all&status=failed" > - 2 pipelines + 20,000 pipelines </gl-link-stub> </li> <li> diff --git a/spec/frontend/projects/pipelines/charts/mock_data.js b/spec/frontend/projects/pipelines/charts/mock_data.js index 04971b5b20e..0e3e43835d0 100644 --- a/spec/frontend/projects/pipelines/charts/mock_data.js +++ b/spec/frontend/projects/pipelines/charts/mock_data.js @@ -1,7 +1,7 @@ export const counts = { - failed: 2, - success: 2, - total: 4, + failed: 20000, + success: 20000, + total: 40000, successRatio: 50, totalDuration: 116158, }; diff --git a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap index 44b9b44fc77..99cfc4f418f 100644 --- a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap +++ b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap @@ -10,12 +10,10 @@ exports[`Repository last commit component renders commit widget 1`] = ` <div class="gl-ml-5" > - <ci-badge-link-stub + <ci-icon-stub aria-label="Pipeline: failed" class="js-commit-pipeline" - details-path="https://test.com/pipeline" showtooltip="true" - size="md" status="[object Object]" uselink="true" /> diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js index cc077e20e0b..e0d2984893b 100644 --- a/spec/frontend/repository/components/blob_content_viewer_spec.js +++ b/spec/frontend/repository/components/blob_content_viewer_spec.js @@ -18,6 +18,7 @@ import { loadViewer } from '~/repository/components/blob_viewers'; import DownloadViewer from '~/repository/components/blob_viewers/download_viewer.vue'; import EmptyViewer from '~/repository/components/blob_viewers/empty_viewer.vue'; import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vue'; +import SourceViewerNew from '~/vue_shared/components/source_viewer/source_viewer_new.vue'; import blobInfoQuery from 'shared_queries/repository/blob_info.query.graphql'; import projectInfoQuery from '~/repository/queries/project_info.query.graphql'; import CodeIntelligence from '~/code_navigation/components/app.vue'; @@ -137,6 +138,7 @@ const createComponent = async (mockData = {}, mountFn = shallowMount, mockRoute ...inject, glFeatures: { highlightJsWorker: false, + blobBlameInfo: true, }, }, }), @@ -157,6 +159,7 @@ describe('Blob content viewer component', () => { const findForkSuggestion = () => wrapper.findComponent(ForkSuggestion); const findCodeIntelligence = () => wrapper.findComponent(CodeIntelligence); const findSourceViewer = () => wrapper.findComponent(SourceViewer); + const findSourceViewerNew = () => wrapper.findComponent(SourceViewerNew); beforeEach(() => { jest.spyOn(window, 'requestIdleCallback').mockImplementation(execImmediately); @@ -179,14 +182,43 @@ describe('Blob content viewer component', () => { expect(findBlobHeader().props('activeViewerType')).toEqual(SIMPLE_BLOB_VIEWER); expect(findBlobHeader().props('hasRenderError')).toEqual(false); - expect(findBlobHeader().props('hideViewerSwitcher')).toEqual(true); + expect(findBlobHeader().props('hideViewerSwitcher')).toEqual(false); expect(findBlobHeader().props('blob')).toEqual(simpleViewerMock); expect(findBlobHeader().props('showForkSuggestion')).toEqual(false); + expect(findBlobHeader().props('showBlameToggle')).toEqual(false); expect(findBlobHeader().props('projectPath')).toEqual(propsMock.projectPath); expect(findBlobHeader().props('projectId')).toEqual(projectMock.id); expect(mockRouterPush).not.toHaveBeenCalled(); }); + describe('blame toggle', () => { + const triggerBlame = async () => { + findBlobHeader().vm.$emit('blame'); + await nextTick(); + }; + + it('renders a blame toggle for JSON files', async () => { + await createComponent({ blob: { ...simpleViewerMock, language: 'json' } }); + + expect(findBlobHeader().props('showBlameToggle')).toEqual(true); + }); + + it('adds blame param to the URL and passes `showBlame` to the SourceViewer', async () => { + loadViewer.mockReturnValueOnce(SourceViewerNew); + await createComponent({ blob: { ...simpleViewerMock, language: 'json' } }); + + await triggerBlame(); + + expect(mockRouterPush).toHaveBeenCalledWith({ query: { blame: '1' } }); + expect(findSourceViewerNew().props('showBlame')).toBe(true); + + await triggerBlame(); + + expect(mockRouterPush).toHaveBeenCalledWith({ query: { blame: '0' } }); + expect(findSourceViewerNew().props('showBlame')).toBe(false); + }); + }); + it('creates an alert when the BlobHeader component emits an error', async () => { await createComponent(); diff --git a/spec/frontend/super_sidebar/components/global_search/components/global_search_default_places_spec.js b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_places_spec.js index f91c8034fe9..d1bec8f8662 100644 --- a/spec/frontend/super_sidebar/components/global_search/components/global_search_default_places_spec.js +++ b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_places_spec.js @@ -88,6 +88,19 @@ describe('GlobalSearchDefaultPlaces', () => { 'data-qa-places-item': 'Admin area', }, }, + { + text: 'Leave admin mode', + href: '/admin/session/destroy', + extraAttrs: { + 'data-track-action': 'click_command_palette_item', + 'data-track-extra': '{"title":"Leave admin mode"}', + 'data-track-label': 'item_without_id', + 'data-track-property': 'nav_panel_unknown', + 'data-testid': 'places-item-link', + 'data-qa-places-item': 'Leave admin mode', + 'data-method': 'post', + }, + }, ]); }); }); diff --git a/spec/frontend/super_sidebar/mock_data.js b/spec/frontend/super_sidebar/mock_data.js index d464ce372ed..d2d2faedbf8 100644 --- a/spec/frontend/super_sidebar/mock_data.js +++ b/spec/frontend/super_sidebar/mock_data.js @@ -74,6 +74,7 @@ export const mergeRequestMenuGroup = [ export const contextSwitcherLinks = [ { title: 'Explore', link: '/explore', icon: 'compass', link_classes: 'persistent-link-class' }, { title: 'Admin area', link: '/admin', icon: 'admin' }, + { title: 'Leave admin mode', link: '/admin/session/destroy', data_method: 'post' }, ]; export const sidebarData = { diff --git a/spec/frontend/vue_shared/components/ci_badge_link_spec.js b/spec/frontend/vue_shared/components/ci_badge_link_spec.js deleted file mode 100644 index e1660225a5c..00000000000 --- a/spec/frontend/vue_shared/components/ci_badge_link_spec.js +++ /dev/null @@ -1,158 +0,0 @@ -import { GlBadge } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; - -jest.mock('~/lib/utils/url_utility', () => ({ - visitUrl: jest.fn(), -})); - -describe('CI Badge Link Component', () => { - let wrapper; - - const statuses = { - canceled: { - text: 'canceled', - label: 'canceled', - group: 'canceled', - icon: 'status_canceled', - details_path: 'status/canceled', - }, - created: { - text: 'created', - label: 'created', - group: 'created', - icon: 'status_created', - details_path: 'status/created', - }, - failed: { - text: 'failed', - label: 'failed', - group: 'failed', - icon: 'status_failed', - details_path: 'status/failed', - }, - manual: { - text: 'manual', - label: 'manual action', - group: 'manual', - icon: 'status_manual', - details_path: 'status/manual', - }, - pending: { - text: 'pending', - label: 'pending', - group: 'pending', - icon: 'status_pending', - details_path: 'status/pending', - }, - preparing: { - text: 'preparing', - label: 'preparing', - group: 'preparing', - icon: 'status_preparing', - details_path: 'status/preparing', - }, - running: { - text: 'running', - label: 'running', - group: 'running', - icon: 'status_running', - details_path: 'status/running', - }, - scheduled: { - text: 'scheduled', - label: 'scheduled', - group: 'scheduled', - icon: 'status_scheduled', - details_path: 'status/scheduled', - }, - skipped: { - text: 'skipped', - label: 'skipped', - group: 'skipped', - icon: 'status_skipped', - details_path: 'status/skipped', - }, - success_warining: { - text: 'warning', - label: 'passed with warnings', - group: 'success-with-warnings', - icon: 'status_warning', - details_path: 'status/warning', - }, - success: { - text: 'passed', - label: 'passed', - group: 'passed', - icon: 'status_success', - details_path: 'status/passed', - }, - }; - - const findIcon = () => wrapper.findComponent(CiIcon); - const findBadge = () => wrapper.findComponent(GlBadge); - const findBadgeText = () => wrapper.find('[data-testid="ci-badge-text"'); - - const createComponent = (propsData) => { - wrapper = shallowMount(CiBadgeLink, { propsData }); - }; - - it.each(Object.keys(statuses))('should render badge for status: %s', (status) => { - createComponent({ status: statuses[status] }); - - expect(wrapper.attributes('href')).toBe(statuses[status].details_path); - expect(wrapper.text()).toBe(statuses[status].text); - expect(findBadge().props('size')).toBe('md'); - expect(findIcon().exists()).toBe(true); - }); - - it.each` - status | textColor | variant - ${statuses.success} | ${'gl-text-green-700'} | ${'success'} - ${statuses.success_warining} | ${'gl-text-orange-700'} | ${'warning'} - ${statuses.failed} | ${'gl-text-red-700'} | ${'danger'} - ${statuses.running} | ${'gl-text-blue-700'} | ${'info'} - ${statuses.pending} | ${'gl-text-orange-700'} | ${'warning'} - ${statuses.preparing} | ${'gl-text-gray-600'} | ${'muted'} - ${statuses.canceled} | ${'gl-text-gray-700'} | ${'neutral'} - ${statuses.scheduled} | ${'gl-text-gray-600'} | ${'muted'} - ${statuses.skipped} | ${'gl-text-gray-600'} | ${'muted'} - ${statuses.manual} | ${'gl-text-gray-700'} | ${'neutral'} - ${statuses.created} | ${'gl-text-gray-600'} | ${'muted'} - `( - 'should contain correct badge class and variant for status: $status.text', - ({ status, textColor, variant }) => { - createComponent({ status }); - - expect(findBadgeText().classes()).toContain(textColor); - expect(findBadge().props('variant')).toBe(variant); - }, - ); - - it('should not render label', () => { - createComponent({ status: statuses.canceled, showText: false }); - - expect(wrapper.text()).toBe(''); - }); - - it('should emit ciStatusBadgeClick event', () => { - createComponent({ status: statuses.success }); - - findBadge().vm.$emit('click'); - - expect(wrapper.emitted('ciStatusBadgeClick')).toEqual([[]]); - }); - - it('should render dynamic badge size', () => { - createComponent({ status: statuses.success, size: 'lg' }); - - expect(findBadge().props('size')).toBe('lg'); - }); - - it('should have class `gl-px-2` when `showText` is false', () => { - createComponent({ status: statuses.success, size: 'md', showText: false }); - - expect(findBadge().classes()).toContain('gl-px-2'); - }); -}); diff --git a/spec/frontend/vue_shared/components/ci_icon_spec.js b/spec/frontend/vue_shared/components/ci_icon_spec.js index c907b776b91..1dc46538ec4 100644 --- a/spec/frontend/vue_shared/components/ci_icon_spec.js +++ b/spec/frontend/vue_shared/components/ci_icon_spec.js @@ -25,46 +25,6 @@ describe('CI Icon component', () => { expect(wrapper.findComponent(GlIcon).exists()).toBe(true); }); - describe.each` - isActive - ${true} - ${false} - `('when isActive is $isActive', ({ isActive }) => { - it(`"active" class is ${isActive ? 'not ' : ''}added`, () => { - wrapper = shallowMount(CiIcon, { - propsData: { - status: { - group: 'success', - icon: 'status_success', - }, - isActive, - }, - }); - - expect(wrapper.classes('active')).toBe(isActive); - }); - }); - - describe.each` - isInteractive - ${true} - ${false} - `('when isInteractive is $isInteractive', ({ isInteractive }) => { - it(`"interactive" class is ${isInteractive ? 'not ' : ''}added`, () => { - wrapper = shallowMount(CiIcon, { - propsData: { - status: { - group: 'success', - icon: 'status_success', - }, - isInteractive, - }, - }); - - expect(wrapper.classes('interactive')).toBe(isInteractive); - }); - }); - describe('rendering a status', () => { it.each` icon | group | cssClass @@ -87,7 +47,7 @@ describe('CI Icon component', () => { }, }); - expect(wrapper.classes()).toContain(cssClass); + expect(wrapper.find('.ci-icon-wrapper').classes()).toContain(cssClass); }); }); }); diff --git a/spec/frontend/vue_shared/components/source_viewer/mock_data.js b/spec/frontend/vue_shared/components/source_viewer/mock_data.js index b3516f7ed72..f03d6ee8aee 100644 --- a/spec/frontend/vue_shared/components/source_viewer/mock_data.js +++ b/spec/frontend/vue_shared/components/source_viewer/mock_data.js @@ -24,17 +24,19 @@ export const CHUNK_2 = { }; export const SOURCE_CODE_CONTENT_MOCK = ` -<div class="content"> - <div> - <div id="L1">1</div> - <div id="L2">2</div> - <div id="L3">3</div> - </div> +<div class="blob-viewer"> + <div class="content"> + <div> + <div id="L1">1</div> + <div id="L2">2</div> + <div id="L3">3</div> + </div> - <div> - <div id="LC1">Content 1</div> - <div id="LC2">Content 2</div> - <div id="LC3">Content 3</div> + <div> + <div id="LC1">Content 1</div> + <div id="LC2">Content 2</div> + <div id="LC3">Content 3</div> + </div> </div> </div>`; @@ -43,3 +45,42 @@ export const BLAME_DATA_MOCK = [ { lineno: 2, commit: { author: 'Sarah' }, index: 1 }, { lineno: 3, commit: { author: 'Peter' }, index: 2 }, ]; + +export const BLAME_DATA_QUERY_RESPONSE_MOCK = { + data: { + project: { + id: 'gid://gitlab/Project/278964', + __typename: 'Project', + repository: { + __typename: 'Repository', + blobs: { + __typename: 'BlobConnection', + nodes: [ + { + id: 'gid://gitlab/Blob/f0c77e4b621df72719ce2b500ea6228559f6bc09', + blame: { + firstLine: '1', + groups: [ + { + lineno: 1, + span: 3, + commit: { + id: 'gid://gitlab/CommitPresenter/13b0aca4142d1d55931577f69289a792f216f805', + titleHtml: 'Upload New File', + message: 'Upload New File', + authoredDate: '2022-10-31T10:38:30+00:00', + authorGravatar: 'path/to/gravatar', + webPath: '/commit/1234', + author: {}, + sha: '13b0aca4142d1d55931577f69289a792f216f805', + }, + }, + ], + }, + }, + ], + }, + }, + }, + }, +}; diff --git a/spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js b/spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js index 1a498d0c5b1..b0b2978ecd1 100644 --- a/spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js +++ b/spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js @@ -1,3 +1,6 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer_new.vue'; import Chunk from '~/vue_shared/components/source_viewer/components/chunk_new.vue'; @@ -5,7 +8,19 @@ import { EVENT_ACTION, EVENT_LABEL_VIEWER } from '~/vue_shared/components/source import Tracking from '~/tracking'; import LineHighlighter from '~/blob/line_highlighter'; import addBlobLinksTracking from '~/blob/blob_links_tracking'; -import { BLOB_DATA_MOCK, CHUNK_1, CHUNK_2, LANGUAGE_MOCK } from './mock_data'; +import waitForPromises from 'helpers/wait_for_promises'; +import blameDataQuery from '~/vue_shared/components/source_viewer/queries/blame_data.query.graphql'; +import Blame from '~/vue_shared/components/source_viewer/components/blame_info.vue'; + +import { + BLOB_DATA_MOCK, + CHUNK_1, + CHUNK_2, + LANGUAGE_MOCK, + BLAME_DATA_QUERY_RESPONSE_MOCK, +} from './mock_data'; + +Vue.use(VueApollo); const lineHighlighter = new LineHighlighter(); jest.mock('~/blob/line_highlighter', () => @@ -17,17 +32,29 @@ jest.mock('~/blob/blob_links_tracking'); describe('Source Viewer component', () => { let wrapper; + let fakeApollo; const CHUNKS_MOCK = [CHUNK_1, CHUNK_2]; const hash = '#L142'; - const createComponent = () => { + const blameDataQueryHandlerSuccess = jest.fn().mockResolvedValue(BLAME_DATA_QUERY_RESPONSE_MOCK); + + const createComponent = ({ showBlame = true } = {}) => { + fakeApollo = createMockApollo([[blameDataQuery, blameDataQueryHandlerSuccess]]); + wrapper = shallowMountExtended(SourceViewer, { + apolloProvider: fakeApollo, mocks: { $route: { hash } }, - propsData: { blob: BLOB_DATA_MOCK, chunks: CHUNKS_MOCK }, + propsData: { + blob: BLOB_DATA_MOCK, + chunks: CHUNKS_MOCK, + projectPath: 'test', + showBlame, + }, }); }; const findChunks = () => wrapper.findAllComponents(Chunk); + const findBlameComponents = () => wrapper.findAllComponents(Blame); beforeEach(() => { jest.spyOn(Tracking, 'event'); @@ -50,6 +77,46 @@ describe('Source Viewer component', () => { }); describe('rendering', () => { + it('does not render a Blame component if the respective chunk for the blame has not appeared', async () => { + await waitForPromises(); + expect(findBlameComponents()).toHaveLength(0); + }); + + describe('Blame information', () => { + const triggerChunkAppear = async () => { + findChunks().at(0).vm.$emit('appear'); + await waitForPromises(); + }; + beforeEach(async () => {}); + + it('renders a Blame component when a chunk appears', async () => { + await triggerChunkAppear(); + const blameData = + BLAME_DATA_QUERY_RESPONSE_MOCK.data.project.repository.blobs.nodes[0].blame.groups; + + expect(findBlameComponents().at(0).exists()).toBe(true); + expect(findBlameComponents().at(0).props()).toMatchObject({ blameData }); + }); + + it('calls the query only once per chunk', async () => { + jest.spyOn(wrapper.vm.$apollo, 'query'); + + // We trigger the `appear` event multiple times here in order to simulate the user scrolling past the chunk more than once. + // In this scenario we only want to query the backend once. + await triggerChunkAppear(); + await triggerChunkAppear(); + + expect(wrapper.vm.$apollo.query).toHaveBeenCalledTimes(1); + }); + + it('does not render a Blame component when `showBlame: false`', async () => { + createComponent({ showBlame: false }); + await triggerChunkAppear(); + + expect(findBlameComponents()).toHaveLength(0); + }); + }); + it('renders a Chunk component for each chunk', () => { expect(findChunks().at(0).props()).toMatchObject(CHUNK_1); expect(findChunks().at(1).props()).toMatchObject(CHUNK_2); diff --git a/spec/graphql/types/analytics/cycle_analytics/value_stream_type_spec.rb b/spec/graphql/types/analytics/cycle_analytics/value_stream_type_spec.rb index c4c0d0824b3..5e2638210d3 100644 --- a/spec/graphql/types/analytics/cycle_analytics/value_stream_type_spec.rb +++ b/spec/graphql/types/analytics/cycle_analytics/value_stream_type_spec.rb @@ -7,5 +7,5 @@ RSpec.describe Types::Analytics::CycleAnalytics::ValueStreamType, feature_catego specify { expect(described_class).to require_graphql_authorizations(:read_cycle_analytics) } - specify { expect(described_class).to have_graphql_fields(:name, :namespace, :project) } + specify { expect(described_class).to have_graphql_fields(:id, :name, :namespace, :project) } end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index e295014a2a6..7b4bcf4b1b0 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -39,7 +39,7 @@ RSpec.describe GitlabSchema.types['Project'] do recent_issue_boards ci_config_path_or_default packages_cleanup_policy ci_variables timelog_categories fork_targets branch_rules ci_config_variables pipeline_schedules languages incident_management_timeline_event_tags visible_forks inherited_ci_variables autocomplete_users - ci_cd_settings + ci_cd_settings detailed_import_status ] expect(described_class).to include_graphql_fields(*expected_fields) @@ -932,4 +932,93 @@ RSpec.describe GitlabSchema.types['Project'] do end end end + + describe 'detailed_import_status' do + let_it_be_with_reload(:project) { create(:project, :with_import_url) } + + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + detailedImportStatus { + status + url + lastError + } + } + } + ) + end + + subject { GitlabSchema.execute(query, context: { current_user: current_user }).as_json } + + let(:detailed_import_status) do + subject.dig('data', 'project', 'detailedImportStatus') + end + + context 'when project is not imported' do + let(:current_user) { create(:user) } + + before do + project.add_developer(current_user) + project.import_state.destroy! + end + + it 'returns nil' do + expect(detailed_import_status).to be_nil + end + end + + context 'when current_user is not set' do + let(:current_user) { nil } + + it 'returns nil' do + expect(detailed_import_status).to be_nil + end + end + + context 'when current_user has no permission' do + let(:current_user) { create(:user) } + + it 'returns nil' do + expect(detailed_import_status).to be_nil + end + end + + context 'when current_user has limited permission' do + let(:current_user) { create(:user) } + + before do + project.add_developer(current_user) + project.import_state.last_error = 'Some error' + project.import_state.save! + end + + it 'returns detailed information' do + expect(detailed_import_status).to include( + 'status' => project.import_state.status, + 'url' => project.safe_import_url, + 'lastError' => nil + ) + end + end + + context 'when current_user has permission' do + let(:current_user) { create(:user) } + + before do + project.add_maintainer(current_user) + project.import_state.last_error = 'Some error' + project.import_state.save! + end + + it 'returns detailed information' do + expect(detailed_import_status).to include( + 'status' => project.import_state.status, + 'url' => project.safe_import_url, + 'lastError' => 'Some error' + ) + end + end + end end diff --git a/spec/graphql/types/projects/detailed_import_status_type_spec.rb b/spec/graphql/types/projects/detailed_import_status_type_spec.rb new file mode 100644 index 00000000000..cfdf06e4ab5 --- /dev/null +++ b/spec/graphql/types/projects/detailed_import_status_type_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['DetailedImportStatus'], feature_category: :importers do + include GraphqlHelpers + + let(:fields) do + %w[ + id + status + url + last_error + last_update_at + last_update_started_at + last_successful_update_at + ] + end + + it { expect(described_class.graphql_name).to eq('DetailedImportStatus') } + it { expect(described_class).to have_graphql_fields(fields) } + it { expect(described_class).to require_graphql_authorizations(:read_project) } +end diff --git a/spec/lib/gitlab/bitbucket_import/importers/issue_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/issue_importer_spec.rb index 8f79390d2d9..8732c787657 100644 --- a/spec/lib/gitlab/bitbucket_import/importers/issue_importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importers/issue_importer_spec.rb @@ -99,5 +99,13 @@ RSpec.describe Gitlab::BitbucketImport::Importers::IssueImporter, :clean_gitlab_ importer.execute end + + it 'increments the issue counter' do + expect_next_instance_of(Gitlab::Import::Metrics) do |metrics| + expect(metrics).to receive_message_chain(:issues_counter, :increment) + end + + importer.execute + end end end diff --git a/spec/lib/gitlab/bitbucket_import/importers/issues_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/issues_importer_spec.rb index a361a9343dd..af5a929683e 100644 --- a/spec/lib/gitlab/bitbucket_import/importers/issues_importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importers/issues_importer_spec.rb @@ -12,22 +12,37 @@ RSpec.describe Gitlab::BitbucketImport::Importers::IssuesImporter, feature_categ ) end + let(:client) { Bitbucket::Client.new(project.import_data.credentials) } + + before do + allow(Bitbucket::Client).to receive(:new).and_return(client) + allow(client).to receive(:repo).and_return(Bitbucket::Representation::Repo.new({ 'has_issues' => true })) + allow(client).to receive(:last_issue).and_return(Bitbucket::Representation::Issue.new({ 'id' => 2 })) + allow(client).to receive(:issues).and_return( + [ + Bitbucket::Representation::Issue.new({ 'id' => 1 }), + Bitbucket::Representation::Issue.new({ 'id' => 2 }) + ], + [] + ) + end + subject(:importer) { described_class.new(project) } describe '#execute', :clean_gitlab_redis_cache do - before do - allow_next_instance_of(Bitbucket::Client) do |client| - allow(client).to receive(:issues).and_return( - [ - Bitbucket::Representation::Issue.new({ 'id' => 1 }), - Bitbucket::Representation::Issue.new({ 'id' => 2 }) - ], - [] - ) + context 'when the repo does not have issue tracking enabled' do + before do + allow(client).to receive(:repo).and_return(Bitbucket::Representation::Repo.new({ 'has_issues' => false })) + end + + it 'does not import issues' do + expect(Gitlab::BitbucketImport::ImportIssueWorker).not_to receive(:perform_in) + + importer.execute end end - it 'imports each issue in parallel', :aggregate_failures do + it 'imports each issue in parallel' do expect(Gitlab::BitbucketImport::ImportIssueWorker).to receive(:perform_in).twice waiter = importer.execute @@ -38,11 +53,15 @@ RSpec.describe Gitlab::BitbucketImport::Importers::IssuesImporter, feature_categ .to match_array(%w[1 2]) end + it 'allocates internal ids' do + expect(Issue).to receive(:track_namespace_iid!).with(project.project_namespace, 2) + + importer.execute + end + context 'when the client raises an error' do before do - allow_next_instance_of(Bitbucket::Client) do |client| - allow(client).to receive(:issues).and_raise(StandardError) - end + allow(client).to receive(:issues).and_raise(StandardError) end it 'tracks the failure and does not fail' do @@ -57,7 +76,7 @@ RSpec.describe Gitlab::BitbucketImport::Importers::IssuesImporter, feature_categ Gitlab::Cache::Import::Caching.set_add(importer.already_enqueued_cache_key, 1) end - it 'does not schedule job for enqueued issues', :aggregate_failures do + it 'does not schedule job for enqueued issues' do expect(Gitlab::BitbucketImport::ImportIssueWorker).to receive(:perform_in).once waiter = importer.execute diff --git a/spec/lib/gitlab/bitbucket_import/importers/issues_notes_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/issues_notes_importer_spec.rb index 1466b91fa8b..a04543b0511 100644 --- a/spec/lib/gitlab/bitbucket_import/importers/issues_notes_importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importers/issues_notes_importer_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Gitlab::BitbucketImport::Importers::IssuesNotesImporter, feature_ subject(:importer) { described_class.new(project) } describe '#execute', :clean_gitlab_redis_cache do - it 'imports the notes from each issue in parallel', :aggregate_failures do + it 'imports the notes from each issue in parallel' do expect(Gitlab::BitbucketImport::ImportIssueNotesWorker).to receive(:perform_in).twice waiter = importer.execute @@ -38,7 +38,7 @@ RSpec.describe Gitlab::BitbucketImport::Importers::IssuesNotesImporter, feature_ Gitlab::Cache::Import::Caching.set_add(importer.already_enqueued_cache_key, 2) end - it 'does not schedule job for enqueued issues', :aggregate_failures do + it 'does not schedule job for enqueued issues' do expect(Gitlab::BitbucketImport::ImportIssueNotesWorker).to receive(:perform_in).once waiter = importer.execute diff --git a/spec/lib/gitlab/bitbucket_import/importers/pull_request_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/pull_request_importer_spec.rb index 2eca6bb47d6..1f36a353724 100644 --- a/spec/lib/gitlab/bitbucket_import/importers/pull_request_importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importers/pull_request_importer_spec.rb @@ -162,5 +162,13 @@ RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestImporter, :clean_g importer.execute end + + it 'increments the merge requests counter' do + expect_next_instance_of(Gitlab::Import::Metrics) do |metrics| + expect(metrics).to receive_message_chain(:merge_requests_counter, :increment) + end + + importer.execute + end end end diff --git a/spec/lib/gitlab/bitbucket_import/importers/pull_requests_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/pull_requests_importer_spec.rb index 46bf099de0c..eba7ec92aba 100644 --- a/spec/lib/gitlab/bitbucket_import/importers/pull_requests_importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importers/pull_requests_importer_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestsImporter, feature end end - it 'imports each pull request in parallel', :aggregate_failures do + it 'imports each pull request in parallel' do expect(Gitlab::BitbucketImport::ImportPullRequestWorker).to receive(:perform_in).exactly(3).times waiter = importer.execute @@ -58,7 +58,7 @@ RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestsImporter, feature Gitlab::Cache::Import::Caching.set_add(importer.already_enqueued_cache_key, 1) end - it 'does not schedule job for enqueued pull requests', :aggregate_failures do + it 'does not schedule job for enqueued pull requests' do expect(Gitlab::BitbucketImport::ImportPullRequestWorker).to receive(:perform_in).twice waiter = importer.execute diff --git a/spec/lib/gitlab/bitbucket_import/importers/pull_requests_notes_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/pull_requests_notes_importer_spec.rb index c44fc259c3b..78a08accf82 100644 --- a/spec/lib/gitlab/bitbucket_import/importers/pull_requests_notes_importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importers/pull_requests_notes_importer_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestsNotesImporter, fe subject(:importer) { described_class.new(project) } describe '#execute', :clean_gitlab_redis_cache do - it 'imports the notes from each merge request in parallel', :aggregate_failures do + it 'imports the notes from each merge request in parallel' do expect(Gitlab::BitbucketImport::ImportPullRequestNotesWorker).to receive(:perform_in).twice waiter = importer.execute @@ -38,7 +38,7 @@ RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestsNotesImporter, fe Gitlab::Cache::Import::Caching.set_add(importer.already_enqueued_cache_key, 2) end - it 'does not schedule job for enqueued merge requests', :aggregate_failures do + it 'does not schedule job for enqueued merge requests' do expect(Gitlab::BitbucketImport::ImportPullRequestNotesWorker).to receive(:perform_in).once waiter = importer.execute diff --git a/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb index d0787d8b673..816b59b96ae 100644 --- a/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb +++ b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb @@ -3,12 +3,12 @@ require 'spec_helper' RSpec.describe Gitlab::GitalyClient::ConflictFilesStitcher do + let_it_be(:target_project) { create(:project, :repository) } + let_it_be(:target_repository) { target_project.repository.raw } + let_it_be(:target_gitaly_repository) { target_repository.gitaly_repository } + describe 'enumeration' do it 'combines segregated ConflictFile messages together' do - target_project = create(:project, :repository) - target_repository = target_project.repository.raw - target_gitaly_repository = target_repository.gitaly_repository - ancestor_path_1 = 'ancestor/path/1' our_path_1 = 'our/path/1' their_path_1 = 'their/path/1' @@ -69,5 +69,49 @@ RSpec.describe Gitlab::GitalyClient::ConflictFilesStitcher do expect(conflict_files[1].repository).to eq(target_repository) expect(conflict_files[1].commit_oid).to eq(commit_oid_2) end + + it 'handles non-latin character names' do + ancestor_path_1_utf8 = "ancestor/テスト.txt" + our_path_1_utf8 = "our/テスト.txt" + their_path_1_utf8 = "their/テスト.txt" + + ancestor_path_1 = String.new('ancestor/テスト.txt', encoding: Encoding::US_ASCII) + our_path_1 = String.new('our/テスト.txt', encoding: Encoding::US_ASCII) + their_path_1 = String.new('their/テスト.txt', encoding: Encoding::US_ASCII) + our_mode_1 = 0744 + commit_oid_1 = 'f00' + content_1 = 'content of the first file' + + header_1 = double( + repository: target_gitaly_repository, + commit_oid: commit_oid_1, + ancestor_path: ancestor_path_1.dup, + our_path: our_path_1.dup, + their_path: their_path_1.dup, + our_mode: our_mode_1 + ) + + messages = [ + double(files: [double(header: header_1), double(header: nil, content: content_1[0..5])]), + double(files: [double(header: nil, content: content_1[6..])]) + ] + + conflict_files = described_class.new(messages, target_repository.gitaly_repository).to_a + + expect(conflict_files.size).to be(1) + + expect(conflict_files[0].content).to eq(content_1) + expect(conflict_files[0].ancestor_path).to eq(ancestor_path_1_utf8) + expect(conflict_files[0].their_path).to eq(their_path_1_utf8) + expect(conflict_files[0].our_path).to eq(our_path_1_utf8) + expect(conflict_files[0].our_mode).to be(our_mode_1) + expect(conflict_files[0].repository).to eq(target_repository) + expect(conflict_files[0].commit_oid).to eq(commit_oid_1) + + # Doesn't equal the ASCII version + expect(conflict_files[0].ancestor_path).not_to eq(ancestor_path_1) + expect(conflict_files[0].their_path).not_to eq(their_path_1) + expect(conflict_files[0].our_path).not_to eq(our_path_1) + end end end diff --git a/spec/lib/sidebars/projects/menus/security_compliance_menu_spec.rb b/spec/lib/sidebars/projects/menus/security_compliance_menu_spec.rb index 4b4706bd311..c7c0586c2f1 100644 --- a/spec/lib/sidebars/projects/menus/security_compliance_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/security_compliance_menu_spec.rb @@ -21,9 +21,12 @@ RSpec.describe Sidebars::Projects::Menus::SecurityComplianceMenu do context 'when user is authenticated' do context 'when the Security and Compliance is disabled' do + let_it_be(:project) { create(:project, :security_and_compliance_disabled) } + before do allow(Ability).to receive(:allowed?).with(user, :access_security_and_compliance, project).and_return(false) allow(Ability).to receive(:allowed?).with(user, :read_security_resource, project).and_return(false) + allow(project).to receive(:security_and_compliance_enabled?).and_return(false) end it { is_expected.to be_falsey } diff --git a/spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb b/spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb index e5c5204e0b4..3f8a146f040 100644 --- a/spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb +++ b/spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb @@ -16,6 +16,7 @@ RSpec.describe Sidebars::Projects::SuperSidebarMenus::MonitorMenu, feature_categ expect(items.map(&:class).uniq).to eq([Sidebars::NilMenuItem]) expect(items.map(&:item_id)).to eq([ :tracing, + :metrics, :error_tracking, :alert_management, :incidents, diff --git a/spec/services/bulk_imports/process_service_spec.rb b/spec/services/bulk_imports/process_service_spec.rb index 5398e76cb67..7dbd3153357 100644 --- a/spec/services/bulk_imports/process_service_spec.rb +++ b/spec/services/bulk_imports/process_service_spec.rb @@ -229,7 +229,7 @@ RSpec.describe BulkImports::ProcessService, feature_category: :importers do bulk_import_entity_type: entity.source_type, source_full_path: entity.source_full_path, importer: 'gitlab_migration', - pipeline_name: 'PipelineClass4', + pipeline_class: 'PipelineClass4', minimum_source_version: '15.1.0', maximum_source_version: nil, source_version: '15.0.0' @@ -242,7 +242,7 @@ RSpec.describe BulkImports::ProcessService, feature_category: :importers do bulk_import_entity_type: entity.source_type, source_full_path: entity.source_full_path, importer: 'gitlab_migration', - pipeline_name: 'PipelineClass5', + pipeline_class: 'PipelineClass5', minimum_source_version: '16.0.0', maximum_source_version: nil, source_version: '15.0.0' diff --git a/spec/services/bulk_imports/relation_batch_export_service_spec.rb b/spec/services/bulk_imports/relation_batch_export_service_spec.rb index 8548e01a6aa..7729c17098a 100644 --- a/spec/services/bulk_imports/relation_batch_export_service_spec.rb +++ b/spec/services/bulk_imports/relation_batch_export_service_spec.rb @@ -10,7 +10,7 @@ RSpec.describe BulkImports::RelationBatchExportService, feature_category: :impor let_it_be(:batch) { create(:bulk_import_export_batch, export: export) } let_it_be(:cache_key) { BulkImports::BatchedRelationExportService.cache_key(export.id, batch.id) } - subject(:service) { described_class.new(user.id, batch.id) } + subject(:service) { described_class.new(user, batch) } before_all do Gitlab::Cache::Import::Caching.set_add(cache_key, label.id) diff --git a/spec/services/ci/cancel_pipeline_service_spec.rb b/spec/services/ci/cancel_pipeline_service_spec.rb index c4a1e1c26d1..256d2db1ed2 100644 --- a/spec/services/ci/cancel_pipeline_service_spec.rb +++ b/spec/services/ci/cancel_pipeline_service_spec.rb @@ -12,12 +12,12 @@ RSpec.describe Ci::CancelPipelineService, :aggregate_failures, feature_category: pipeline: pipeline, current_user: current_user, cascade_to_children: cascade_to_children, - auto_canceled_by_pipeline_id: auto_canceled_by_pipeline_id, + auto_canceled_by_pipeline: auto_canceled_by_pipeline, execute_async: execute_async) end let(:cascade_to_children) { true } - let(:auto_canceled_by_pipeline_id) { nil } + let(:auto_canceled_by_pipeline) { nil } let(:execute_async) { true } shared_examples 'force_execute' do @@ -58,14 +58,19 @@ RSpec.describe Ci::CancelPipelineService, :aggregate_failures, feature_category: expect(pipeline.all_jobs.pluck(:status)).to match_array(%w[canceled canceled success]) end - context 'when auto_canceled_by_pipeline_id is provided' do - let(:auto_canceled_by_pipeline_id) { create(:ci_pipeline).id } + context 'when auto_canceled_by_pipeline is provided' do + let(:auto_canceled_by_pipeline) { create(:ci_pipeline) } it 'updates the pipeline and jobs with it' do subject - expect(pipeline.auto_canceled_by_id).to eq(auto_canceled_by_pipeline_id) - expect(pipeline.all_jobs.canceled.pluck(:auto_canceled_by_id).uniq).to eq([auto_canceled_by_pipeline_id]) + expect(pipeline.auto_canceled_by_id).to eq(auto_canceled_by_pipeline.id) + + expect(pipeline.all_jobs.canceled.pluck(:auto_canceled_by_id).uniq) + .to eq([auto_canceled_by_pipeline.id]) + + expect(pipeline.all_jobs.canceled.pluck(:auto_canceled_by_partition_id).uniq) + .to eq([auto_canceled_by_pipeline.partition_id]) end end diff --git a/spec/support/helpers/database/duplicate_indexes.yml b/spec/support/helpers/database/duplicate_indexes.yml index 02efdabd70b..1e78b8fc774 100644 --- a/spec/support/helpers/database/duplicate_indexes.yml +++ b/spec/support/helpers/database/duplicate_indexes.yml @@ -213,9 +213,6 @@ sbom_occurrences: - index_sbom_occurrences_on_project_id index_sbom_occurrences_on_project_id_and_package_manager: - index_sbom_occurrences_on_project_id -scan_result_policies: - index_scan_result_policies_on_position_in_configuration: - - index_scan_result_policies_on_policy_configuration_id search_namespace_index_assignments: index_search_namespace_index_assignments_uniqueness_index_type: - index_search_namespace_index_assignments_on_namespace_id diff --git a/spec/support/shared_examples/requests/api/graphql/remote_development_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/remote_development_shared_examples.rb deleted file mode 100644 index 83e22945361..00000000000 --- a/spec/support/shared_examples/requests/api/graphql/remote_development_shared_examples.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples 'workspaces query in licensed environment and with feature flag on' do - describe 'when licensed and remote_development_feature_flag feature flag is enabled' do - before do - stub_licensed_features(remote_development: true) - - post_graphql(query, current_user: current_user) - end - - it_behaves_like 'a working graphql query' - - it { is_expected.to match_array(a_hash_including('name' => workspace.name)) } - - context 'when user is not authorized' do - let(:current_user) { create(:user) } - - it { is_expected.to eq([]) } - end - end -end - -RSpec.shared_examples 'workspaces query in unlicensed environment and with feature flag off' do - describe 'when remote_development feature is unlicensed' do - before do - stub_licensed_features(remote_development: false) - post_graphql(query, current_user: current_user) - end - - it 'returns an error' do - expect(subject).to be_nil - expect_graphql_errors_to_include(/'remote_development' licensed feature is not available/) - end - end - - describe 'when remote_development_feature_flag feature flag is disabled' do - before do - stub_licensed_features(remote_development: true) - stub_feature_flags(remote_development_feature_flag: false) - post_graphql(query, current_user: current_user) - end - - it 'returns an error' do - expect(subject).to be_nil - expect_graphql_errors_to_include(/'remote_development_feature_flag' feature flag is disabled/) - end - end -end diff --git a/spec/workers/bulk_imports/pipeline_worker_spec.rb b/spec/workers/bulk_imports/pipeline_worker_spec.rb index 2042300edf0..37205026a90 100644 --- a/spec/workers/bulk_imports/pipeline_worker_spec.rb +++ b/spec/workers/bulk_imports/pipeline_worker_spec.rb @@ -44,7 +44,7 @@ RSpec.describe BulkImports::PipelineWorker, feature_category: :importers do .to receive(:info) .with( hash_including( - 'pipeline_name' => 'FakePipeline', + 'pipeline_class' => 'FakePipeline', 'bulk_import_id' => entity.bulk_import_id, 'bulk_import_entity_id' => entity.id, 'bulk_import_entity_type' => entity.source_type, @@ -94,7 +94,7 @@ RSpec.describe BulkImports::PipelineWorker, feature_category: :importers do .to receive(:error) .with( hash_including( - 'pipeline_name' => 'FakePipeline', + 'pipeline_class' => 'FakePipeline', 'bulk_import_entity_id' => entity.id, 'bulk_import_id' => entity.bulk_import_id, 'bulk_import_entity_type' => entity.source_type, @@ -118,7 +118,7 @@ RSpec.describe BulkImports::PipelineWorker, feature_category: :importers do 'bulk_import_id' => entity.bulk_import.id, 'bulk_import_entity_type' => entity.source_type, 'source_full_path' => entity.source_full_path, - 'pipeline_name' => pipeline_tracker.pipeline_name, + 'pipeline_class' => pipeline_tracker.pipeline_name, 'importer' => 'gitlab_migration', 'source_version' => entity.bulk_import.source_version_info.to_s ) @@ -160,7 +160,7 @@ RSpec.describe BulkImports::PipelineWorker, feature_category: :importers do 'bulk_import_entity_type' => entity.source_type, 'pipeline_tracker_id' => pipeline_tracker.id, 'pipeline_tracker_state' => status, - 'pipeline_name' => pipeline_tracker.pipeline_name, + 'pipeline_class' => pipeline_tracker.pipeline_name, 'source_full_path' => entity.source_full_path, 'source_version' => entity.bulk_import.source_version_info.to_s, 'importer' => 'gitlab_migration', @@ -233,7 +233,7 @@ RSpec.describe BulkImports::PipelineWorker, feature_category: :importers do .to receive(:info) .with( hash_including( - 'pipeline_name' => 'FakePipeline', + 'pipeline_class' => 'FakePipeline', 'bulk_import_entity_id' => entity.id, 'bulk_import_id' => entity.bulk_import_id, 'bulk_import_entity_type' => entity.source_type, @@ -283,7 +283,7 @@ RSpec.describe BulkImports::PipelineWorker, feature_category: :importers do .to receive(:info) .with( hash_including( - 'pipeline_name' => 'FakePipeline', + 'pipeline_class' => 'FakePipeline', 'bulk_import_entity_id' => entity.id, 'bulk_import_id' => entity.bulk_import_id, 'bulk_import_entity_type' => entity.source_type, @@ -421,7 +421,7 @@ RSpec.describe BulkImports::PipelineWorker, feature_category: :importers do .to receive(:error) .with( hash_including( - 'pipeline_name' => 'NdjsonPipeline', + 'pipeline_class' => 'NdjsonPipeline', 'bulk_import_entity_id' => entity.id, 'bulk_import_id' => entity.bulk_import_id, 'bulk_import_entity_type' => entity.source_type, @@ -476,7 +476,7 @@ RSpec.describe BulkImports::PipelineWorker, feature_category: :importers do .to receive(:error) .with( hash_including( - 'pipeline_name' => 'NdjsonPipeline', + 'pipeline_class' => 'NdjsonPipeline', 'bulk_import_entity_id' => entity.id, 'bulk_import_id' => entity.bulk_import_id, 'bulk_import_entity_type' => entity.source_type, diff --git a/spec/workers/bulk_imports/relation_batch_export_worker_spec.rb b/spec/workers/bulk_imports/relation_batch_export_worker_spec.rb index 4a2c8d48742..e27e2dc0ac8 100644 --- a/spec/workers/bulk_imports/relation_batch_export_worker_spec.rb +++ b/spec/workers/bulk_imports/relation_batch_export_worker_spec.rb @@ -15,7 +15,7 @@ RSpec.describe BulkImports::RelationBatchExportWorker, feature_category: :import expect(BulkImports::RelationBatchExportService) .to receive(:new) - .with(user.id, batch.id) + .with(user, batch) .twice.and_return(service) expect(service).to receive(:execute).twice diff --git a/spec/workers/ci/cancel_pipeline_worker_spec.rb b/spec/workers/ci/cancel_pipeline_worker_spec.rb index 13a9c0affe7..8e8f9a78132 100644 --- a/spec/workers/ci/cancel_pipeline_worker_spec.rb +++ b/spec/workers/ci/cancel_pipeline_worker_spec.rb @@ -11,14 +11,13 @@ RSpec.describe Ci::CancelPipelineWorker, :aggregate_failures, feature_category: let(:cancel_service) { instance_double(::Ci::CancelPipelineService) } it 'cancels the pipeline' do - allow(::Ci::Pipeline).to receive(:find_by_id).and_return(pipeline) + allow(::Ci::Pipeline).to receive(:find_by_id).twice.and_return(pipeline) expect(::Ci::CancelPipelineService) .to receive(:new) .with( pipeline: pipeline, current_user: nil, - auto_canceled_by_pipeline_id: - pipeline.id, + auto_canceled_by_pipeline: pipeline, cascade_to_children: false) .and_return(cancel_service) @@ -28,7 +27,7 @@ RSpec.describe Ci::CancelPipelineWorker, :aggregate_failures, feature_category: end context 'if pipeline is deleted' do - subject(:perform) { described_class.new.perform(non_existing_record_id, non_existing_record_id) } + subject(:perform) { described_class.new.perform(non_existing_record_id, pipeline.id) } it 'does not error' do expect(::Ci::CancelPipelineService).not_to receive(:new) @@ -37,6 +36,23 @@ RSpec.describe Ci::CancelPipelineWorker, :aggregate_failures, feature_category: end end + context 'when auto_canceled_by_pipeline is deleted' do + subject(:perform) { described_class.new.perform(pipeline.id, non_existing_record_id) } + + it 'does not error' do + expect(::Ci::CancelPipelineService) + .to receive(:new) + .with( + pipeline: an_instance_of(::Ci::Pipeline), + current_user: nil, + auto_canceled_by_pipeline: nil, + cascade_to_children: false) + .and_call_original + + perform + end + end + describe 'with builds and state transition side effects', :sidekiq_inline do let!(:build) { create(:ci_build, :running, pipeline: pipeline) } |