diff options
author | Matija Čupić <matteeyah@gmail.com> | 2017-12-21 18:30:34 +0100 |
---|---|---|
committer | Matija Čupić <matteeyah@gmail.com> | 2017-12-21 18:30:34 +0100 |
commit | 305bce8d246d2c6e88b5f22439c0ce0833eba1a3 (patch) | |
tree | e043cb4041c121957610f81d6a65790e91f84fb9 /spec | |
parent | 614c0e0bf9c404ba43f835166183a2f1883071d1 (diff) | |
parent | b8d79cc479200ff714f89dc43a3bbec18af3c5b5 (diff) | |
download | gitlab-ce-305bce8d246d2c6e88b5f22439c0ce0833eba1a3.tar.gz |
Merge branch 'master' into 39957-redirect-to-gpc-page-if-users-try-to-create-a-cluster-but-the-account-is-not-enabled
Diffstat (limited to 'spec')
290 files changed, 4339 insertions, 1879 deletions
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index c5d08cb0b9d..a2ef937609b 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -874,7 +874,7 @@ describe Projects::IssuesController do end it 'delegates the update of the todos count cache to TodoService' do - expect_any_instance_of(TodoService).to receive(:destroy_issuable).with(issue, owner).once + expect_any_instance_of(TodoService).to receive(:destroy_target).with(issue).once delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 51d5d6a52b3..45c424af8c4 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -91,11 +91,19 @@ describe Projects::MergeRequestsController do end end - context 'without basic serializer param' do - it 'renders the merge request in the json format' do - go(format: :json) + context 'with widget serializer param' do + it 'renders widget MR entity as json' do + go(serializer: 'widget', format: :json) - expect(response).to match_response_schema('entities/merge_request') + expect(response).to match_response_schema('entities/merge_request_widget') + end + end + + context 'when no serialiser was passed' do + it 'renders widget MR entity as json' do + go(serializer: nil, format: :json) + + expect(response).to match_response_schema('entities/merge_request_widget') end end end @@ -468,7 +476,7 @@ describe Projects::MergeRequestsController do end it 'delegates the update of the todos count cache to TodoService' do - expect_any_instance_of(TodoService).to receive(:destroy_issuable).with(merge_request, owner).once + expect_any_instance_of(TodoService).to receive(:destroy_target).with(merge_request).once delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid end diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb index 4e52e261920..966ffdf6996 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -3,10 +3,12 @@ require 'spec_helper' describe Projects::PipelineSchedulesController do include AccessMatchersForController - set(:project) { create(:project, :public) } - let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) } + set(:project) { create(:project, :public, :repository) } + set(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) } describe 'GET #index' do + render_views + let(:scope) { nil } let!(:inactive_pipeline_schedule) do create(:ci_pipeline_schedule, :inactive, project: project) @@ -96,7 +98,7 @@ describe Projects::PipelineSchedulesController do end end - context 'when variables_attributes has two variables and duplicted' do + context 'when variables_attributes has two variables and duplicated' do let(:schedule) do basic_param.merge({ variables_attributes: [{ key: 'AAA', value: 'AAA123' }, { key: 'AAA', value: 'BBB123' }] @@ -364,6 +366,65 @@ describe Projects::PipelineSchedulesController do end end + describe 'POST #play', :clean_gitlab_redis_cache do + set(:user) { create(:user) } + let(:ref) { 'master' } + + before do + project.add_developer(user) + + sign_in(user) + end + + context 'when an anonymous user makes the request' do + before do + sign_out(user) + end + + it 'does not allow pipeline to be executed' do + expect(RunPipelineScheduleWorker).not_to receive(:perform_async) + + post :play, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'when a developer makes the request' do + it 'executes a new pipeline' do + expect(RunPipelineScheduleWorker).to receive(:perform_async).with(pipeline_schedule.id, user.id).and_return('job-123') + + post :play, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + + expect(flash[:notice]).to start_with 'Successfully scheduled a pipeline to run' + expect(response).to have_gitlab_http_status(302) + end + + it 'prevents users from scheduling the same pipeline repeatedly' do + 2.times do + post :play, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id + end + + expect(flash.to_a.size).to eq(2) + expect(flash[:alert]).to eq 'You cannot play this scheduled pipeline at the moment. Please wait a minute.' + expect(response).to have_gitlab_http_status(302) + end + end + + context 'when a developer attempts to schedule a protected ref' do + it 'does not allow pipeline to be executed' do + create(:protected_branch, project: project, name: ref) + protected_schedule = create(:ci_pipeline_schedule, project: project, ref: ref) + + expect(RunPipelineScheduleWorker).not_to receive(:perform_async) + + post :play, namespace_id: project.namespace.to_param, project_id: project, id: protected_schedule.id + + expect(response).to have_gitlab_http_status(404) + end + end + end + describe 'DELETE #destroy' do set(:user) { create(:user) } diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index 1604a2da485..35ac999cc65 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -17,13 +17,10 @@ describe Projects::PipelinesController do describe 'GET index.json' do before do - branch_head = project.commit - parent = branch_head.parent - - create(:ci_empty_pipeline, status: 'pending', project: project, sha: branch_head.id) - create(:ci_empty_pipeline, status: 'running', project: project, sha: branch_head.id) - create(:ci_empty_pipeline, status: 'created', project: project, sha: parent.id) - create(:ci_empty_pipeline, status: 'success', project: project, sha: parent.id) + %w(pending running created success).each_with_index do |status, index| + sha = project.commit("HEAD~#{index}") + create(:ci_empty_pipeline, status: status, project: project, sha: sha) + end end subject do @@ -46,7 +43,7 @@ describe Projects::PipelinesController do context 'when performing gitaly calls', :request_store do it 'limits the Gitaly requests' do - expect { subject }.to change { Gitlab::GitalyClient.get_request_count }.by(8) + expect { subject }.to change { Gitlab::GitalyClient.get_request_count }.by(3) end end end diff --git a/spec/factories/abuse_reports.rb b/spec/factories/abuse_reports.rb index 8f6422a7825..021971ac421 100644 --- a/spec/factories/abuse_reports.rb +++ b/spec/factories/abuse_reports.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :abuse_report do reporter factory: :user user diff --git a/spec/factories/appearances.rb b/spec/factories/appearances.rb index 860973024c9..5f9c57c0c8d 100644 --- a/spec/factories/appearances.rb +++ b/spec/factories/appearances.rb @@ -1,6 +1,6 @@ -# Read about factories at https://github.com/thoughtbot/factory_girl +# Read about factories at https://github.com/thoughtbot/factory_bot -FactoryGirl.define do +FactoryBot.define do factory :appearance do title "MepMep" description "This is my Community Edition instance" diff --git a/spec/factories/application_settings.rb b/spec/factories/application_settings.rb index aef65e724c2..3ecc90b6573 100644 --- a/spec/factories/application_settings.rb +++ b/spec/factories/application_settings.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :application_setting do end end diff --git a/spec/factories/award_emoji.rb b/spec/factories/award_emoji.rb index 4b858df52c9..a0abbbce686 100644 --- a/spec/factories/award_emoji.rb +++ b/spec/factories/award_emoji.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :award_emoji do name "thumbsup" user diff --git a/spec/factories/boards.rb b/spec/factories/boards.rb index 1ec042a6ab4..1e125237ae8 100644 --- a/spec/factories/boards.rb +++ b/spec/factories/boards.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :board do project diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb index c2fdf89213a..9a65e7f8a3f 100644 --- a/spec/factories/broadcast_messages.rb +++ b/spec/factories/broadcast_messages.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :broadcast_message do message "MyText" starts_at 1.day.ago diff --git a/spec/factories/chat_names.rb b/spec/factories/chat_names.rb index 9a0be1a4598..56993e5da18 100644 --- a/spec/factories/chat_names.rb +++ b/spec/factories/chat_names.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :chat_name, class: ChatName do user factory: :user service factory: :service diff --git a/spec/factories/chat_teams.rb b/spec/factories/chat_teams.rb index ffedf69a69b..d048c46d6b6 100644 --- a/spec/factories/chat_teams.rb +++ b/spec/factories/chat_teams.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :chat_team, class: ChatTeam do sequence(:team_id) { |n| "abcdefghijklm#{n}" } namespace factory: :group diff --git a/spec/factories/ci/build_trace_section_names.rb b/spec/factories/ci/build_trace_section_names.rb index 1c16225f0e5..ce07e716dde 100644 --- a/spec/factories/ci/build_trace_section_names.rb +++ b/spec/factories/ci/build_trace_section_names.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :ci_build_trace_section_name, class: Ci::BuildTraceSectionName do sequence(:name) { |n| "section_#{n}" } project factory: :project diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index c868525cbc0..dc1d88c92dc 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -1,6 +1,6 @@ include ActionDispatch::TestProcess -FactoryGirl.define do +FactoryBot.define do factory :ci_build, class: Ci::Build do name 'test' stage 'test' diff --git a/spec/factories/ci/group_variables.rb b/spec/factories/ci/group_variables.rb index 565ced9eb1a..64716842b12 100644 --- a/spec/factories/ci/group_variables.rb +++ b/spec/factories/ci/group_variables.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :ci_group_variable, class: Ci::GroupVariable do sequence(:key) { |n| "VARIABLE_#{n}" } value 'VARIABLE_VALUE' diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb index 538dc422832..46afba2953c 100644 --- a/spec/factories/ci/job_artifacts.rb +++ b/spec/factories/ci/job_artifacts.rb @@ -1,6 +1,6 @@ include ActionDispatch::TestProcess -FactoryGirl.define do +FactoryBot.define do factory :ci_job_artifact, class: Ci::JobArtifact do job factory: :ci_build file_type :archive diff --git a/spec/factories/ci/pipeline_schedule.rb b/spec/factories/ci/pipeline_schedule.rb index 564fef6833b..b2b79807429 100644 --- a/spec/factories/ci/pipeline_schedule.rb +++ b/spec/factories/ci/pipeline_schedule.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :ci_pipeline_schedule, class: Ci::PipelineSchedule do cron '0 1 * * *' cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE diff --git a/spec/factories/ci/pipeline_schedule_variables.rb b/spec/factories/ci/pipeline_schedule_variables.rb index ca64d1aada0..8d29118e310 100644 --- a/spec/factories/ci/pipeline_schedule_variables.rb +++ b/spec/factories/ci/pipeline_schedule_variables.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :ci_pipeline_schedule_variable, class: Ci::PipelineScheduleVariable do sequence(:key) { |n| "VARIABLE_#{n}" } value 'VARIABLE_VALUE' diff --git a/spec/factories/ci/pipeline_variables.rb b/spec/factories/ci/pipeline_variables.rb index 7c1a7faec08..b18055d7b3a 100644 --- a/spec/factories/ci/pipeline_variables.rb +++ b/spec/factories/ci/pipeline_variables.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :ci_pipeline_variable, class: Ci::PipelineVariable do sequence(:key) { |n| "VARIABLE_#{n}" } value 'VARIABLE_VALUE' diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb index f994c2df821..51a767e5b93 100644 --- a/spec/factories/ci/pipelines.rb +++ b/spec/factories/ci/pipelines.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :ci_empty_pipeline, class: Ci::Pipeline do source :push ref 'master' diff --git a/spec/factories/ci/runner_projects.rb b/spec/factories/ci/runner_projects.rb index fa76d0ecd8c..f605e90ceed 100644 --- a/spec/factories/ci/runner_projects.rb +++ b/spec/factories/ci/runner_projects.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :ci_runner_project, class: Ci::RunnerProject do runner factory: :ci_runner project diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb index 88bb755d068..34b8b246d0f 100644 --- a/spec/factories/ci/runners.rb +++ b/spec/factories/ci/runners.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :ci_runner, class: Ci::Runner do sequence(:description) { |n| "My runner#{n}" } diff --git a/spec/factories/ci/stages.rb b/spec/factories/ci/stages.rb index b2ded945738..25309033571 100644 --- a/spec/factories/ci/stages.rb +++ b/spec/factories/ci/stages.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :ci_stage, class: Ci::LegacyStage do skip_create diff --git a/spec/factories/ci/trigger_requests.rb b/spec/factories/ci/trigger_requests.rb index 40b8848920e..0e9fc3d0014 100644 --- a/spec/factories/ci/trigger_requests.rb +++ b/spec/factories/ci/trigger_requests.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :ci_trigger_request, class: Ci::TriggerRequest do trigger factory: :ci_trigger end diff --git a/spec/factories/ci/triggers.rb b/spec/factories/ci/triggers.rb index 3734c7040c0..742d9efba2d 100644 --- a/spec/factories/ci/triggers.rb +++ b/spec/factories/ci/triggers.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :ci_trigger_without_token, class: Ci::Trigger do owner diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb index d8fd513ffcf..3d014b9b54f 100644 --- a/spec/factories/ci/variables.rb +++ b/spec/factories/ci/variables.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :ci_variable, class: Ci::Variable do sequence(:key) { |n| "VARIABLE_#{n}" } value 'VARIABLE_VALUE' diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb index fab37195113..d82fa8e34aa 100644 --- a/spec/factories/clusters/applications/helm.rb +++ b/spec/factories/clusters/applications/helm.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :cluster_applications_helm, class: Clusters::Applications::Helm do cluster factory: %i(cluster provided_by_gcp) diff --git a/spec/factories/clusters/applications/ingress.rb b/spec/factories/clusters/applications/ingress.rb index b103a980655..85f54a9d744 100644 --- a/spec/factories/clusters/applications/ingress.rb +++ b/spec/factories/clusters/applications/ingress.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :cluster_applications_ingress, class: Clusters::Applications::Ingress do cluster factory: %i(cluster provided_by_gcp) diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb index 9e73a19e856..20d5580f0c2 100644 --- a/spec/factories/clusters/clusters.rb +++ b/spec/factories/clusters/clusters.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :cluster, class: Clusters::Cluster do user name 'test-cluster' diff --git a/spec/factories/clusters/platforms/kubernetes.rb b/spec/factories/clusters/platforms/kubernetes.rb index 8b3e6ff35fa..89f6ddebf6a 100644 --- a/spec/factories/clusters/platforms/kubernetes.rb +++ b/spec/factories/clusters/platforms/kubernetes.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :cluster_platform_kubernetes, class: Clusters::Platforms::Kubernetes do cluster namespace nil diff --git a/spec/factories/clusters/providers/gcp.rb b/spec/factories/clusters/providers/gcp.rb index a815410512a..a002ab28519 100644 --- a/spec/factories/clusters/providers/gcp.rb +++ b/spec/factories/clusters/providers/gcp.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :cluster_provider_gcp, class: Clusters::Providers::Gcp do cluster gcp_project_id 'test-gcp-project' diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb index abbe37df90e..ce5fbc343ee 100644 --- a/spec/factories/commit_statuses.rb +++ b/spec/factories/commit_statuses.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :commit_status, class: CommitStatus do name 'default' stage 'test' diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb index f4f12a095fc..84a8bc56640 100644 --- a/spec/factories/commits.rb +++ b/spec/factories/commits.rb @@ -1,16 +1,29 @@ require_relative '../support/repo_helpers' -FactoryGirl.define do +FactoryBot.define do factory :commit do - git_commit RepoHelpers.sample_commit + transient do + author nil + end + + git_commit do + commit = RepoHelpers.sample_commit + + if author + commit.author_email = author.email + commit.author_name = author.name + end + + commit + end project initialize_with do new(git_commit, project) end - after(:build) do |commit| - allow(commit).to receive(:author).and_return build(:author) + after(:build) do |commit, evaluator| + allow(commit).to receive(:author).and_return(evaluator.author || build(:author)) end trait :without_author do diff --git a/spec/factories/container_repositories.rb b/spec/factories/container_repositories.rb index 3fcad9fd4b3..62a89a12ef5 100644 --- a/spec/factories/container_repositories.rb +++ b/spec/factories/container_repositories.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :container_repository do name 'test_container_image' project diff --git a/spec/factories/conversational_development_index_metrics.rb b/spec/factories/conversational_development_index_metrics.rb index 3806c43ba15..abf37fb861e 100644 --- a/spec/factories/conversational_development_index_metrics.rb +++ b/spec/factories/conversational_development_index_metrics.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :conversational_development_index_metric, class: ConversationalDevelopmentIndex::Metric do leader_issues 9.256 instance_issues 1.234 diff --git a/spec/factories/deploy_keys_projects.rb b/spec/factories/deploy_keys_projects.rb index 27cece487bd..30a6d468ed3 100644 --- a/spec/factories/deploy_keys_projects.rb +++ b/spec/factories/deploy_keys_projects.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :deploy_keys_project do deploy_key project diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb index 0dd1238d6e2..9d7d5e56611 100644 --- a/spec/factories/deployments.rb +++ b/spec/factories/deployments.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :deployment, class: Deployment do sha '97de212e80737a608d939f648d959671fb0a0142' ref 'master' diff --git a/spec/factories/emails.rb b/spec/factories/emails.rb index c9ab87a15aa..4dc7961060a 100644 --- a/spec/factories/emails.rb +++ b/spec/factories/emails.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :email do user email { generate(:email_alias) } diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb index 9034476d094..b5db57d5148 100644 --- a/spec/factories/environments.rb +++ b/spec/factories/environments.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :environment, class: Environment do sequence(:name) { |n| "environment#{n}" } diff --git a/spec/factories/events.rb b/spec/factories/events.rb index ad9f7e2caef..ed275243ac9 100644 --- a/spec/factories/events.rb +++ b/spec/factories/events.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :event do project author factory: :user diff --git a/spec/factories/file_uploaders.rb b/spec/factories/file_uploaders.rb index 622571390d2..8404985bfea 100644 --- a/spec/factories/file_uploaders.rb +++ b/spec/factories/file_uploaders.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :file_uploader do skip_create diff --git a/spec/factories/fork_network_members.rb b/spec/factories/fork_network_members.rb index 509c4e1fa1c..850e3f158f1 100644 --- a/spec/factories/fork_network_members.rb +++ b/spec/factories/fork_network_members.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :fork_network_member do association :project association :fork_network diff --git a/spec/factories/fork_networks.rb b/spec/factories/fork_networks.rb index f42d36f3d19..813b1943eb2 100644 --- a/spec/factories/fork_networks.rb +++ b/spec/factories/fork_networks.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :fork_network do association :root_project, factory: :project end diff --git a/spec/factories/forked_project_links.rb b/spec/factories/forked_project_links.rb index 9b34651a4ae..bc59fea81ec 100644 --- a/spec/factories/forked_project_links.rb +++ b/spec/factories/forked_project_links.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :forked_project_link do association :forked_to_project, factory: [:project, :repository] association :forked_from_project, factory: [:project, :repository] diff --git a/spec/factories/gitaly/commit.rb b/spec/factories/gitaly/commit.rb index e7966cee77b..5034c3d0e48 100644 --- a/spec/factories/gitaly/commit.rb +++ b/spec/factories/gitaly/commit.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do sequence(:gitaly_commit_id) { Digest::SHA1.hexdigest(Time.now.to_f.to_s) } factory :gitaly_commit, class: Gitaly::GitCommit do diff --git a/spec/factories/gitaly/commit_author.rb b/spec/factories/gitaly/commit_author.rb index 341873a2002..aaf634ce08b 100644 --- a/spec/factories/gitaly/commit_author.rb +++ b/spec/factories/gitaly/commit_author.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :gitaly_commit_author, class: Gitaly::CommitAuthor do skip_create diff --git a/spec/factories/gpg_key_subkeys.rb b/spec/factories/gpg_key_subkeys.rb index 66ecb44d84b..57eaaee345f 100644 --- a/spec/factories/gpg_key_subkeys.rb +++ b/spec/factories/gpg_key_subkeys.rb @@ -1,6 +1,6 @@ require_relative '../support/gpg_helpers' -FactoryGirl.define do +FactoryBot.define do factory :gpg_key_subkey do gpg_key diff --git a/spec/factories/gpg_keys.rb b/spec/factories/gpg_keys.rb index 93218e5763e..b8aabf74221 100644 --- a/spec/factories/gpg_keys.rb +++ b/spec/factories/gpg_keys.rb @@ -1,6 +1,6 @@ require_relative '../support/gpg_helpers' -FactoryGirl.define do +FactoryBot.define do factory :gpg_key do key GpgHelpers::User1.public_key user diff --git a/spec/factories/gpg_signature.rb b/spec/factories/gpg_signature.rb index e9798ff6a41..4620caff823 100644 --- a/spec/factories/gpg_signature.rb +++ b/spec/factories/gpg_signature.rb @@ -1,6 +1,6 @@ require_relative '../support/gpg_helpers' -FactoryGirl.define do +FactoryBot.define do factory :gpg_signature do commit_sha { Digest::SHA1.hexdigest(SecureRandom.hex) } project diff --git a/spec/factories/group_custom_attributes.rb b/spec/factories/group_custom_attributes.rb index 7ff5f376e8b..d2f45d5d3ce 100644 --- a/spec/factories/group_custom_attributes.rb +++ b/spec/factories/group_custom_attributes.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :group_custom_attribute do group sequence(:key) { |n| "key#{n}" } diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb index 32cbfe28a60..1c2214e9481 100644 --- a/spec/factories/group_members.rb +++ b/spec/factories/group_members.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :group_member do access_level { GroupMember::OWNER } group diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb index 52f76b094a3..1512f5a0e58 100644 --- a/spec/factories/groups.rb +++ b/spec/factories/groups.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :group, class: Group, parent: :namespace do sequence(:name) { |n| "group#{n}" } path { name.downcase.gsub(/\s/, '_') } diff --git a/spec/factories/identities.rb b/spec/factories/identities.rb index 26ef6f18698..122d0d42938 100644 --- a/spec/factories/identities.rb +++ b/spec/factories/identities.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :identity do provider 'ldapmain' extern_uid 'my-ldap-id' diff --git a/spec/factories/instance_configuration.rb b/spec/factories/instance_configuration.rb index 406c7c3caf1..31866a9c221 100644 --- a/spec/factories/instance_configuration.rb +++ b/spec/factories/instance_configuration.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :instance_configuration do skip_create end diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb index 7c3b80198f9..5ed6b017dee 100644 --- a/spec/factories/issues.rb +++ b/spec/factories/issues.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :issue do title { generate(:title) } author diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb index 3f7c794b14a..e6eb76f71d3 100644 --- a/spec/factories/keys.rb +++ b/spec/factories/keys.rb @@ -1,6 +1,6 @@ require_relative '../support/helpers/key_generator_helper' -FactoryGirl.define do +FactoryBot.define do factory :key do title key { Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate + ' dummy@gitlab.com' } diff --git a/spec/factories/label_links.rb b/spec/factories/label_links.rb index 3580174e873..007847d9cf4 100644 --- a/spec/factories/label_links.rb +++ b/spec/factories/label_links.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :label_link do label target factory: :issue diff --git a/spec/factories/label_priorities.rb b/spec/factories/label_priorities.rb index 7430466fc57..c4824faad53 100644 --- a/spec/factories/label_priorities.rb +++ b/spec/factories/label_priorities.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :label_priority do project label diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index 416317d677b..f759b6d499d 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do trait :base_label do title { generate(:label_title) } color "#990000" diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb index 477fab9e964..8eb709022ce 100644 --- a/spec/factories/lfs_objects.rb +++ b/spec/factories/lfs_objects.rb @@ -1,6 +1,6 @@ include ActionDispatch::TestProcess -FactoryGirl.define do +FactoryBot.define do factory :lfs_object do sequence(:oid) { |n| "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a%05x" % n } size 499013 diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb index 1ed0355c8e4..c225387a5de 100644 --- a/spec/factories/lfs_objects_projects.rb +++ b/spec/factories/lfs_objects_projects.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :lfs_objects_project do lfs_object project diff --git a/spec/factories/lists.rb b/spec/factories/lists.rb index 48142d3c49b..210c58b21e9 100644 --- a/spec/factories/lists.rb +++ b/spec/factories/lists.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :list do board label diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index cc6cef63b47..40558c88d15 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :merge_request do title { generate(:title) } author diff --git a/spec/factories/merge_requests_closing_issues.rb b/spec/factories/merge_requests_closing_issues.rb index fdbdc00cad7..ee0606a72b6 100644 --- a/spec/factories/merge_requests_closing_issues.rb +++ b/spec/factories/merge_requests_closing_issues.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :merge_requests_closing_issues do issue merge_request diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb index b5298b2f969..f95632e7187 100644 --- a/spec/factories/milestones.rb +++ b/spec/factories/milestones.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :milestone do title diff --git a/spec/factories/namespaces.rb b/spec/factories/namespaces.rb index 1b1fc4ce80d..f94b09cff15 100644 --- a/spec/factories/namespaces.rb +++ b/spec/factories/namespaces.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :namespace do sequence(:name) { |n| "namespace#{n}" } path { name.downcase.gsub(/\s/, '_') } diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 471bfb3213a..707ecbd6be5 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -2,7 +2,7 @@ require_relative '../support/repo_helpers' include ActionDispatch::TestProcess -FactoryGirl.define do +FactoryBot.define do factory :note do project note { generate(:title) } diff --git a/spec/factories/notification_settings.rb b/spec/factories/notification_settings.rb index e9171528d86..5116ef33f5d 100644 --- a/spec/factories/notification_settings.rb +++ b/spec/factories/notification_settings.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :notification_setting do source factory: :project user diff --git a/spec/factories/oauth_access_grants.rb b/spec/factories/oauth_access_grants.rb index 543b3e99274..9e6af24c4eb 100644 --- a/spec/factories/oauth_access_grants.rb +++ b/spec/factories/oauth_access_grants.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :oauth_access_grant do resource_owner_id { create(:user).id } application diff --git a/spec/factories/oauth_access_tokens.rb b/spec/factories/oauth_access_tokens.rb index a46bc1d8ce8..eabfd6cd830 100644 --- a/spec/factories/oauth_access_tokens.rb +++ b/spec/factories/oauth_access_tokens.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :oauth_access_token do resource_owner application diff --git a/spec/factories/oauth_applications.rb b/spec/factories/oauth_applications.rb index c7ede40f240..4427da1d6c7 100644 --- a/spec/factories/oauth_applications.rb +++ b/spec/factories/oauth_applications.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :oauth_application, class: 'Doorkeeper::Application', aliases: [:application] do sequence(:name) { |n| "OAuth App #{n}" } uid { Doorkeeper::OAuth::Helpers::UniqueToken.generate } diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb index 6d2e45f41ba..61b04708da2 100644 --- a/spec/factories/pages_domains.rb +++ b/spec/factories/pages_domains.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :pages_domain, class: 'PagesDomain' do domain 'my.domain.com' diff --git a/spec/factories/personal_access_tokens.rb b/spec/factories/personal_access_tokens.rb index 06acaff6cd0..1b12f84d7b8 100644 --- a/spec/factories/personal_access_tokens.rb +++ b/spec/factories/personal_access_tokens.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :personal_access_token do user token { SecureRandom.hex(50) } diff --git a/spec/factories/project_auto_devops.rb b/spec/factories/project_auto_devops.rb index 8d124dc2381..5ce1988c76f 100644 --- a/spec/factories/project_auto_devops.rb +++ b/spec/factories/project_auto_devops.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :project_auto_devops do project enabled true diff --git a/spec/factories/project_custom_attributes.rb b/spec/factories/project_custom_attributes.rb index 5eedeb86304..099d2d7ff19 100644 --- a/spec/factories/project_custom_attributes.rb +++ b/spec/factories/project_custom_attributes.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :project_custom_attribute do project sequence(:key) { |n| "key#{n}" } diff --git a/spec/factories/project_group_links.rb b/spec/factories/project_group_links.rb index e73cc05f9d7..d5ace9425a0 100644 --- a/spec/factories/project_group_links.rb +++ b/spec/factories/project_group_links.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :project_group_link do project group diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb index accae636a3a..493b7bc021c 100644 --- a/spec/factories/project_hooks.rb +++ b/spec/factories/project_hooks.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :project_hook do url { generate(:url) } enable_ssl_verification false diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb index 9cf3a1e8e8a..4260f52498d 100644 --- a/spec/factories/project_members.rb +++ b/spec/factories/project_members.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :project_member do user project diff --git a/spec/factories/project_statistics.rb b/spec/factories/project_statistics.rb index 6c2ed7c6581..2d0f698475d 100644 --- a/spec/factories/project_statistics.rb +++ b/spec/factories/project_statistics.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :project_statistics do project diff --git a/spec/factories/project_wikis.rb b/spec/factories/project_wikis.rb index 38fcab7466d..89d8248f9f4 100644 --- a/spec/factories/project_wikis.rb +++ b/spec/factories/project_wikis.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :project_wiki do skip_create diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 4034e7905ad..d0f3911f730 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -1,6 +1,6 @@ require_relative '../support/test_env' -FactoryGirl.define do +FactoryBot.define do # Project without repository # # Project does not have bare repository. diff --git a/spec/factories/protected_branches.rb b/spec/factories/protected_branches.rb index fe0cbfc4444..39460834d06 100644 --- a/spec/factories/protected_branches.rb +++ b/spec/factories/protected_branches.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :protected_branch do name project diff --git a/spec/factories/protected_tags.rb b/spec/factories/protected_tags.rb index 225588b23cc..df9c8b3cb63 100644 --- a/spec/factories/protected_tags.rb +++ b/spec/factories/protected_tags.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :protected_tag do name project diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb index 74497dc82c0..d80c65cf8bb 100644 --- a/spec/factories/releases.rb +++ b/spec/factories/releases.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :release do tag "v1.1.0" description "Awesome release" diff --git a/spec/factories/sent_notifications.rb b/spec/factories/sent_notifications.rb index c2febf5b438..80872067233 100644 --- a/spec/factories/sent_notifications.rb +++ b/spec/factories/sent_notifications.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :sent_notification do project recipient factory: :user diff --git a/spec/factories/sequences.rb b/spec/factories/sequences.rb index c0232ba5bf6..f2b6e7a11f9 100644 --- a/spec/factories/sequences.rb +++ b/spec/factories/sequences.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do sequence(:username) { |n| "user#{n}" } sequence(:name) { |n| "John Doe#{n}" } sequence(:email) { |n| "user#{n}@example.org" } diff --git a/spec/factories/service_hooks.rb b/spec/factories/service_hooks.rb index e3f88ab8fcc..c907862b4f6 100644 --- a/spec/factories/service_hooks.rb +++ b/spec/factories/service_hooks.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :service_hook do url { generate(:url) } service diff --git a/spec/factories/services.rb b/spec/factories/services.rb index ccf63f3ffa4..4b0377967c7 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :service do project type 'Service' diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb index 075bccd7f94..2ab9a56d255 100644 --- a/spec/factories/snippets.rb +++ b/spec/factories/snippets.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :snippet do author title { generate(:title) } diff --git a/spec/factories/spam_logs.rb b/spec/factories/spam_logs.rb index e369f9f13e9..a467f850a80 100644 --- a/spec/factories/spam_logs.rb +++ b/spec/factories/spam_logs.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :spam_log do user sequence(:source_ip) { |n| "42.42.42.#{n % 255}" } diff --git a/spec/factories/subscriptions.rb b/spec/factories/subscriptions.rb index 1ae7fc9f384..a4bc4e87b0a 100644 --- a/spec/factories/subscriptions.rb +++ b/spec/factories/subscriptions.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :subscription do user project diff --git a/spec/factories/system_hooks.rb b/spec/factories/system_hooks.rb index 841e1e293e8..9e00eeb6ef1 100644 --- a/spec/factories/system_hooks.rb +++ b/spec/factories/system_hooks.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :system_hook do url { generate(:url) } end diff --git a/spec/factories/system_note_metadata.rb b/spec/factories/system_note_metadata.rb index f487a2d7e4a..e913068da40 100644 --- a/spec/factories/system_note_metadata.rb +++ b/spec/factories/system_note_metadata.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :system_note_metadata do note action 'merge' diff --git a/spec/factories/timelogs.rb b/spec/factories/timelogs.rb index 6f1545418eb..af34b0681e2 100644 --- a/spec/factories/timelogs.rb +++ b/spec/factories/timelogs.rb @@ -1,6 +1,6 @@ -# Read about factories at https://github.com/thoughtbot/factory_girl +# Read about factories at https://github.com/thoughtbot/factory_bot -FactoryGirl.define do +FactoryBot.define do factory :timelog do time_spent 3600 user diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb index 4975befbfe3..6a6de665dd1 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :todo do project author diff --git a/spec/factories/trending_project.rb b/spec/factories/trending_project.rb index 246176611dc..f7c634fd21f 100644 --- a/spec/factories/trending_project.rb +++ b/spec/factories/trending_project.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do # TrendingProject factory :trending_project, class: 'TrendingProject' do project diff --git a/spec/factories/u2f_registrations.rb b/spec/factories/u2f_registrations.rb index df92b079581..26090b08966 100644 --- a/spec/factories/u2f_registrations.rb +++ b/spec/factories/u2f_registrations.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :u2f_registration do certificate { FFaker::BaconIpsum.characters(728) } key_handle { FFaker::BaconIpsum.characters(86) } diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb index e18f1a6bd4a..c39500faea1 100644 --- a/spec/factories/uploads.rb +++ b/spec/factories/uploads.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :upload do model { build(:project) } path { "uploads/-/system/project/avatar/avatar.jpg" } diff --git a/spec/factories/user_agent_details.rb b/spec/factories/user_agent_details.rb index 9763cc0cf15..7183a8e1140 100644 --- a/spec/factories/user_agent_details.rb +++ b/spec/factories/user_agent_details.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :user_agent_detail do ip_address '127.0.0.1' user_agent 'AppleWebKit/537.36' diff --git a/spec/factories/user_custom_attributes.rb b/spec/factories/user_custom_attributes.rb index 278cf290d4f..a184a2e0f17 100644 --- a/spec/factories/user_custom_attributes.rb +++ b/spec/factories/user_custom_attributes.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :user_custom_attribute do user sequence(:key) { |n| "key#{n}" } diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 8ace424f8af..e62e0b263ca 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :user, aliases: [:author, :assignee, :recipient, :owner, :resource_owner] do email { generate(:email) } name { generate(:name) } diff --git a/spec/factories/web_hook_log.rb b/spec/factories/web_hook_log.rb index 230b3f6b26e..17837260a4b 100644 --- a/spec/factories/web_hook_log.rb +++ b/spec/factories/web_hook_log.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :web_hook_log do web_hook factory: :project_hook trigger 'push_hooks' diff --git a/spec/factories/wiki_directories.rb b/spec/factories/wiki_directories.rb index 3b4cfc380b8..b105c82b19d 100644 --- a/spec/factories/wiki_directories.rb +++ b/spec/factories/wiki_directories.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :wiki_directory do skip_create diff --git a/spec/factories/wiki_pages.rb b/spec/factories/wiki_pages.rb index 4105f59e289..2335b5118dd 100644 --- a/spec/factories/wiki_pages.rb +++ b/spec/factories/wiki_pages.rb @@ -1,6 +1,6 @@ require 'ostruct' -FactoryGirl.define do +FactoryBot.define do factory :wiki_page do transient do attrs do diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb index 09b3c0b0994..66b71d0f556 100644 --- a/spec/factories_spec.rb +++ b/spec/factories_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'factories' do - FactoryGirl.factories.each do |factory| + FactoryBot.factories.each do |factory| describe "#{factory.name} factory" do it 'does not raise error when built' do expect { build(factory.name) }.not_to raise_error diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index e3bb16af38a..c1c54177167 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -13,8 +13,8 @@ describe "Admin Runners" do context "when there are runners" do before do - runner = FactoryGirl.create(:ci_runner, contacted_at: Time.now) - FactoryGirl.create(:ci_build, pipeline: pipeline, runner_id: runner.id) + runner = FactoryBot.create(:ci_runner, contacted_at: Time.now) + FactoryBot.create(:ci_build, pipeline: pipeline, runner_id: runner.id) visit admin_runners_path end @@ -25,8 +25,8 @@ describe "Admin Runners" do describe 'search' do before do - FactoryGirl.create :ci_runner, description: 'runner-foo' - FactoryGirl.create :ci_runner, description: 'runner-bar' + FactoryBot.create :ci_runner, description: 'runner-foo' + FactoryBot.create :ci_runner, description: 'runner-bar' end it 'shows correct runner when description matches' do @@ -62,11 +62,11 @@ describe "Admin Runners" do end describe "Runner show page" do - let(:runner) { FactoryGirl.create :ci_runner } + let(:runner) { FactoryBot.create :ci_runner } before do - @project1 = FactoryGirl.create(:project) - @project2 = FactoryGirl.create(:project) + @project1 = FactoryBot.create(:project) + @project2 = FactoryBot.create(:project) visit admin_runner_path(runner) end diff --git a/spec/features/admin/admin_system_info_spec.rb b/spec/features/admin/admin_system_info_spec.rb index 1fd1cda694a..5a989319d5b 100644 --- a/spec/features/admin/admin_system_info_spec.rb +++ b/spec/features/admin/admin_system_info_spec.rb @@ -18,8 +18,8 @@ describe 'Admin System Info' do it 'shows system info page' do expect(page).to have_content 'CPU 2 cores' - expect(page).to have_content 'Memory 4 GB / 16 GB' - expect(page).to have_content 'Disks' + expect(page).to have_content 'Memory Usage 4 GB / 16 GB' + expect(page).to have_content 'Disk Usage' expect(page).to have_content 'Uptime' end end @@ -33,8 +33,8 @@ describe 'Admin System Info' do it 'shows system info page with no CPU info' do expect(page).to have_content 'CPU Unable to collect CPU info' - expect(page).to have_content 'Memory 4 GB / 16 GB' - expect(page).to have_content 'Disks' + expect(page).to have_content 'Memory Usage 4 GB / 16 GB' + expect(page).to have_content 'Disk Usage' expect(page).to have_content 'Uptime' end end @@ -48,8 +48,8 @@ describe 'Admin System Info' do it 'shows system info page with no CPU info' do expect(page).to have_content 'CPU 2 cores' - expect(page).to have_content 'Memory Unable to collect memory info' - expect(page).to have_content 'Disks' + expect(page).to have_content 'Memory Usage Unable to collect memory info' + expect(page).to have_content 'Disk Usage' expect(page).to have_content 'Uptime' end end diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb index a9530becb65..70faf28e09d 100644 --- a/spec/features/calendar_spec.rb +++ b/spec/features/calendar_spec.rb @@ -12,7 +12,7 @@ feature 'Contributions Calendar', :js do issue_params = { title: issue_title } def get_cell_color_selector(contributions) - activity_colors = %w[#ededed #acd5f2 #7fa8c9 #527ba0 #254e77] + activity_colors = ["#ededed", "rgb(172, 213, 242)", "rgb(127, 168, 201)", "rgb(82, 123, 160)", "rgb(37, 78, 119)"] # We currently don't actually test the cases with contributions >= 20 activity_colors_index = if contributions > 0 && contributions < 10 diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index c870910c8ea..77dcdf89f37 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -197,7 +197,7 @@ describe 'Commits' do commits = project.repository.commits(branch_name) commits.each do |commit| - expect(page).to have_content("committed #{commit.committed_date.strftime("%b %d, %Y")}") + expect(page).to have_content("authored #{commit.authored_date.strftime("%b %d, %Y")}") end end diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb index d2d0be35f1c..e9b375f4c94 100644 --- a/spec/features/group_variables_spec.rb +++ b/spec/features/group_variables_spec.rb @@ -24,7 +24,7 @@ feature 'Group variables', :js do expect(find(".variable-value")).to have_content('******') expect(find(".variable-protected")).to have_content('Yes') end - click_on 'Reveal Values' + click_on 'Reveal value' page.within('.variables-table') do expect(find(".variable-value")).to have_content('AAA123') end diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb index 93be3b066ee..0d04ed612c2 100644 --- a/spec/features/help_pages_spec.rb +++ b/spec/features/help_pages_spec.rb @@ -32,12 +32,30 @@ describe 'Help Pages' do it_behaves_like 'help page', prefix: '/gitlab' end + + context 'quick link shortcuts', :js do + before do + visit help_path + end + + it 'focuses search bar' do + find('.js-trigger-search-bar').click + + expect(page).to have_selector('#search:focus') + end + + it 'opens shortcuts help dialog' do + find('.js-trigger-shortcut').click + + expect(page).to have_selector('#modal-shortcuts') + end + end end context 'in a production environment with version check enabled', :js do before do allow(Rails.env).to receive(:production?) { true } - allow_any_instance_of(ApplicationSetting).to receive(:version_check_enabled) { true } + stub_application_setting(version_check_enabled: true) allow_any_instance_of(VersionCheck).to receive(:url) { '/version-check-url' } sign_in(create(:user)) @@ -56,9 +74,9 @@ describe 'Help Pages' do describe 'when help page is customized' do before do - allow_any_instance_of(ApplicationSetting).to receive(:help_page_hide_commercial_content?) { true } - allow_any_instance_of(ApplicationSetting).to receive(:help_page_text) { "My Custom Text" } - allow_any_instance_of(ApplicationSetting).to receive(:help_page_support_url) { "http://example.com/help" } + stub_application_setting(help_page_hide_commercial_content: true, + help_page_text: 'My Custom Text', + help_page_support_url: 'http://example.com/help') sign_in(create(:user)) visit help_path diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb index 4224a8fe5d4..babb0285590 100644 --- a/spec/features/issues/issue_detail_spec.rb +++ b/spec/features/issues/issue_detail_spec.rb @@ -24,7 +24,7 @@ feature 'Issue Detail', :js do visit project_issue_path(project, issue) wait_for_requests - click_link 'Edit' + page.find('.js-issuable-edit').click fill_in 'issuable-title', with: 'issue title' click_button 'Save' wait_for_requests diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 852d9e368aa..d1ff057a0c6 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -8,729 +8,753 @@ describe 'Issues' do let(:user) { create(:user) } let(:project) { create(:project, :public) } - before do - sign_in(user) - user2 = create(:user) - - project.team << [[user, user2], :developer] - end + describe 'while user is signed out' do + describe 'empty state' do + it 'user sees empty state' do + visit project_issues_path(project) - describe 'Edit issue' do - let!(:issue) do - create(:issue, - author: user, - assignees: [user], - project: project) + expect(page).to have_content('Register / Sign In') + expect(page).to have_content('The Issue Tracker is the place to add things that need to be improved or solved in a project.') + expect(page).to have_content('You can register or sign in to create issues for this project.') + end end + end + describe 'while user is signed in' do before do - visit edit_project_issue_path(project, issue) - find('.js-zen-enter').click - end - - it 'opens new issue popup' do - expect(page).to have_content("Issue ##{issue.iid}") - end - end + sign_in(user) + user2 = create(:user) - describe 'Editing issue assignee' do - let!(:issue) do - create(:issue, - author: user, - assignees: [user], - project: project) + project.team << [[user, user2], :developer] end - it 'allows user to select unassigned', :js do - visit edit_project_issue_path(project, issue) - - expect(page).to have_content "Assignee #{user.name}" + describe 'empty state' do + it 'user sees empty state' do + visit project_issues_path(project) - first('.js-user-search').click - click_link 'Unassigned' + expect(page).to have_content('The Issue Tracker is the place to add things that need to be improved or solved in a project') + expect(page).to have_content('Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.') + expect(page).to have_content('New issue') + end + end - click_button 'Save changes' + describe 'Edit issue' do + let!(:issue) do + create(:issue, + author: user, + assignees: [user], + project: project) + end - page.within('.assignee') do - expect(page).to have_content 'No assignee - assign yourself' + before do + visit edit_project_issue_path(project, issue) + find('.js-zen-enter').click end - expect(issue.reload.assignees).to be_empty + it 'opens new issue popup' do + expect(page).to have_content("Issue ##{issue.iid}") + end end - end - describe 'due date', :js do - context 'on new form' do - before do - visit new_project_issue_path(project) + describe 'Editing issue assignee' do + let!(:issue) do + create(:issue, + author: user, + assignees: [user], + project: project) end - it 'saves with due date' do - date = Date.today.at_beginning_of_month - - fill_in 'issue_title', with: 'bug 345' - fill_in 'issue_description', with: 'bug description' - find('#issuable-due-date').click + it 'allows user to select unassigned', :js do + visit edit_project_issue_path(project, issue) - page.within '.pika-single' do - click_button date.day - end + expect(page).to have_content "Assignee #{user.name}" - expect(find('#issuable-due-date').value).to eq date.to_s + first('.js-user-search').click + click_link 'Unassigned' - click_button 'Submit issue' + click_button 'Save changes' - page.within '.issuable-sidebar' do - expect(page).to have_content date.to_s(:medium) + page.within('.assignee') do + expect(page).to have_content 'No assignee - assign yourself' end + + expect(issue.reload.assignees).to be_empty end end - context 'on edit form' do - let(:issue) { create(:issue, author: user, project: project, due_date: Date.today.at_beginning_of_month.to_s) } + describe 'due date', :js do + context 'on new form' do + before do + visit new_project_issue_path(project) + end - before do - visit edit_project_issue_path(project, issue) - end + it 'saves with due date' do + date = Date.today.at_beginning_of_month - it 'saves with due date' do - date = Date.today.at_beginning_of_month + fill_in 'issue_title', with: 'bug 345' + fill_in 'issue_description', with: 'bug description' + find('#issuable-due-date').click - expect(find('#issuable-due-date').value).to eq date.to_s + page.within '.pika-single' do + click_button date.day + end - date = date.tomorrow + expect(find('#issuable-due-date').value).to eq date.to_s - fill_in 'issue_title', with: 'bug 345' - fill_in 'issue_description', with: 'bug description' - find('#issuable-due-date').click + click_button 'Submit issue' - page.within '.pika-single' do - click_button date.day + page.within '.issuable-sidebar' do + expect(page).to have_content date.to_s(:medium) + end end + end - expect(find('#issuable-due-date').value).to eq date.to_s + context 'on edit form' do + let(:issue) { create(:issue, author: user, project: project, due_date: Date.today.at_beginning_of_month.to_s) } - click_button 'Save changes' - - page.within '.issuable-sidebar' do - expect(page).to have_content date.to_s(:medium) + before do + visit edit_project_issue_path(project, issue) end - end - it 'warns about version conflict' do - issue.update(title: "New title") + it 'saves with due date' do + date = Date.today.at_beginning_of_month - fill_in 'issue_title', with: 'bug 345' - fill_in 'issue_description', with: 'bug description' + expect(find('#issuable-due-date').value).to eq date.to_s - click_button 'Save changes' + date = date.tomorrow - expect(page).to have_content 'Someone edited the issue the same time you did' - end - end - end + fill_in 'issue_title', with: 'bug 345' + fill_in 'issue_description', with: 'bug description' + find('#issuable-due-date').click + + page.within '.pika-single' do + click_button date.day + end - describe 'Issue info' do - it 'links to current issue in breadcrubs' do - issue = create(:issue, project: project) + expect(find('#issuable-due-date').value).to eq date.to_s - visit project_issue_path(project, issue) + click_button 'Save changes' - expect(find('.breadcrumbs-sub-title a')[:href]).to end_with(issue_path(issue)) - end + page.within '.issuable-sidebar' do + expect(page).to have_content date.to_s(:medium) + end + end - it 'excludes award_emoji from comment count' do - issue = create(:issue, author: user, assignees: [user], project: project, title: 'foobar') - create(:award_emoji, awardable: issue) + it 'warns about version conflict' do + issue.update(title: "New title") - visit project_issues_path(project, assignee_id: user.id) + fill_in 'issue_title', with: 'bug 345' + fill_in 'issue_description', with: 'bug description' - expect(page).to have_content 'foobar' - expect(page.all('.no-comments').first.text).to eq "0" - end - end + click_button 'Save changes' - describe 'Filter issue' do - before do - %w(foobar barbaz gitlab).each do |title| - create(:issue, - author: user, - assignees: [user], - project: project, - title: title) + expect(page).to have_content 'Someone edited the issue the same time you did' + end end - - @issue = Issue.find_by(title: 'foobar') - @issue.milestone = create(:milestone, project: project) - @issue.assignees = [] - @issue.save end - let(:issue) { @issue } + describe 'Issue info' do + it 'links to current issue in breadcrubs' do + issue = create(:issue, project: project) - it 'allows filtering by issues with no specified assignee' do - visit project_issues_path(project, assignee_id: IssuableFinder::NONE) + visit project_issue_path(project, issue) - expect(page).to have_content 'foobar' - expect(page).not_to have_content 'barbaz' - expect(page).not_to have_content 'gitlab' - end + expect(find('.breadcrumbs-sub-title a')[:href]).to end_with(issue_path(issue)) + end - it 'allows filtering by a specified assignee' do - visit project_issues_path(project, assignee_id: user.id) + it 'excludes award_emoji from comment count' do + issue = create(:issue, author: user, assignees: [user], project: project, title: 'foobar') + create(:award_emoji, awardable: issue) - expect(page).not_to have_content 'foobar' - expect(page).to have_content 'barbaz' - expect(page).to have_content 'gitlab' - end - end + visit project_issues_path(project, assignee_id: user.id) - describe 'filter issue' do - titles = %w[foo bar baz] - titles.each_with_index do |title, index| - let!(title.to_sym) do - create(:issue, title: title, - project: project, - created_at: Time.now - (index * 60)) + expect(page).to have_content 'foobar' + expect(page.all('.no-comments').first.text).to eq "0" end end - let(:newer_due_milestone) { create(:milestone, due_date: '2013-12-11') } - let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') } - it 'sorts by newest' do - visit project_issues_path(project, sort: sort_value_created_date) + describe 'Filter issue' do + before do + %w(foobar barbaz gitlab).each do |title| + create(:issue, + author: user, + assignees: [user], + project: project, + title: title) + end - expect(first_issue).to include('foo') - expect(last_issue).to include('baz') - end + @issue = Issue.find_by(title: 'foobar') + @issue.milestone = create(:milestone, project: project) + @issue.assignees = [] + @issue.save + end - it 'sorts by most recently updated' do - baz.updated_at = Time.now + 100 - baz.save - visit project_issues_path(project, sort: sort_value_recently_updated) + let(:issue) { @issue } - expect(first_issue).to include('baz') - end + it 'allows filtering by issues with no specified assignee' do + visit project_issues_path(project, assignee_id: IssuableFinder::NONE) - describe 'sorting by due date' do - before do - foo.update(due_date: 1.day.from_now) - bar.update(due_date: 6.days.from_now) + expect(page).to have_content 'foobar' + expect(page).not_to have_content 'barbaz' + expect(page).not_to have_content 'gitlab' end - it 'sorts by due date' do - visit project_issues_path(project, sort: sort_value_due_date) + it 'allows filtering by a specified assignee' do + visit project_issues_path(project, assignee_id: user.id) - expect(first_issue).to include('foo') + expect(page).not_to have_content 'foobar' + expect(page).to have_content 'barbaz' + expect(page).to have_content 'gitlab' end + end - it 'sorts by due date by excluding nil due dates' do - bar.update(due_date: nil) + describe 'filter issue' do + titles = %w[foo bar baz] + titles.each_with_index do |title, index| + let!(title.to_sym) do + create(:issue, title: title, + project: project, + created_at: Time.now - (index * 60)) + end + end + let(:newer_due_milestone) { create(:milestone, due_date: '2013-12-11') } + let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') } - visit project_issues_path(project, sort: sort_value_due_date) + it 'sorts by newest' do + visit project_issues_path(project, sort: sort_value_created_date) expect(first_issue).to include('foo') + expect(last_issue).to include('baz') end - context 'with a filter on labels' do - let(:label) { create(:label, project: project) } + it 'sorts by most recently updated' do + baz.updated_at = Time.now + 100 + baz.save + visit project_issues_path(project, sort: sort_value_recently_updated) + expect(first_issue).to include('baz') + end + + describe 'sorting by due date' do before do - create(:label_link, label: label, target: foo) + foo.update(due_date: 1.day.from_now) + bar.update(due_date: 6.days.from_now) + end + + it 'sorts by due date' do + visit project_issues_path(project, sort: sort_value_due_date) + + expect(first_issue).to include('foo') end - it 'sorts by least recently due date by excluding nil due dates' do + it 'sorts by due date by excluding nil due dates' do bar.update(due_date: nil) - visit project_issues_path(project, label_names: [label.name], sort: sort_value_due_date_later) + visit project_issues_path(project, sort: sort_value_due_date) expect(first_issue).to include('foo') end - end - end - describe 'filtering by due date' do - before do - foo.update(due_date: 1.day.from_now) - bar.update(due_date: 6.days.from_now) - end + context 'with a filter on labels' do + let(:label) { create(:label, project: project) } + + before do + create(:label_link, label: label, target: foo) + end + + it 'sorts by least recently due date by excluding nil due dates' do + bar.update(due_date: nil) - it 'filters by none' do - visit project_issues_path(project, due_date: Issue::NoDueDate.name) + visit project_issues_path(project, label_names: [label.name], sort: sort_value_due_date_later) - page.within '.issues-holder' do - expect(page).not_to have_content('foo') - expect(page).not_to have_content('bar') - expect(page).to have_content('baz') + expect(first_issue).to include('foo') + end end end - it 'filters by any' do - visit project_issues_path(project, due_date: Issue::AnyDueDate.name) + describe 'filtering by due date' do + before do + foo.update(due_date: 1.day.from_now) + bar.update(due_date: 6.days.from_now) + end + + it 'filters by none' do + visit project_issues_path(project, due_date: Issue::NoDueDate.name) - page.within '.issues-holder' do - expect(page).to have_content('foo') - expect(page).to have_content('bar') - expect(page).to have_content('baz') + page.within '.issues-holder' do + expect(page).not_to have_content('foo') + expect(page).not_to have_content('bar') + expect(page).to have_content('baz') + end + end + + it 'filters by any' do + visit project_issues_path(project, due_date: Issue::AnyDueDate.name) + + page.within '.issues-holder' do + expect(page).to have_content('foo') + expect(page).to have_content('bar') + expect(page).to have_content('baz') + end end - end - it 'filters by due this week' do - foo.update(due_date: Date.today.beginning_of_week + 2.days) - bar.update(due_date: Date.today.end_of_week) - baz.update(due_date: Date.today - 8.days) + it 'filters by due this week' do + foo.update(due_date: Date.today.beginning_of_week + 2.days) + bar.update(due_date: Date.today.end_of_week) + baz.update(due_date: Date.today - 8.days) - visit project_issues_path(project, due_date: Issue::DueThisWeek.name) + visit project_issues_path(project, due_date: Issue::DueThisWeek.name) - page.within '.issues-holder' do - expect(page).to have_content('foo') - expect(page).to have_content('bar') - expect(page).not_to have_content('baz') + page.within '.issues-holder' do + expect(page).to have_content('foo') + expect(page).to have_content('bar') + expect(page).not_to have_content('baz') + end end - end - it 'filters by due this month' do - foo.update(due_date: Date.today.beginning_of_month + 2.days) - bar.update(due_date: Date.today.end_of_month) - baz.update(due_date: Date.today - 50.days) + it 'filters by due this month' do + foo.update(due_date: Date.today.beginning_of_month + 2.days) + bar.update(due_date: Date.today.end_of_month) + baz.update(due_date: Date.today - 50.days) - visit project_issues_path(project, due_date: Issue::DueThisMonth.name) + visit project_issues_path(project, due_date: Issue::DueThisMonth.name) - page.within '.issues-holder' do - expect(page).to have_content('foo') - expect(page).to have_content('bar') - expect(page).not_to have_content('baz') + page.within '.issues-holder' do + expect(page).to have_content('foo') + expect(page).to have_content('bar') + expect(page).not_to have_content('baz') + end end - end - it 'filters by overdue' do - foo.update(due_date: Date.today + 2.days) - bar.update(due_date: Date.today + 20.days) - baz.update(due_date: Date.yesterday) + it 'filters by overdue' do + foo.update(due_date: Date.today + 2.days) + bar.update(due_date: Date.today + 20.days) + baz.update(due_date: Date.yesterday) - visit project_issues_path(project, due_date: Issue::Overdue.name) + visit project_issues_path(project, due_date: Issue::Overdue.name) - page.within '.issues-holder' do - expect(page).not_to have_content('foo') - expect(page).not_to have_content('bar') - expect(page).to have_content('baz') + page.within '.issues-holder' do + expect(page).not_to have_content('foo') + expect(page).not_to have_content('bar') + expect(page).to have_content('baz') + end end end - end - describe 'sorting by milestone' do - before do - foo.milestone = newer_due_milestone - foo.save - bar.milestone = later_due_milestone - bar.save - end + describe 'sorting by milestone' do + before do + foo.milestone = newer_due_milestone + foo.save + bar.milestone = later_due_milestone + bar.save + end - it 'sorts by milestone' do - visit project_issues_path(project, sort: sort_value_milestone) + it 'sorts by milestone' do + visit project_issues_path(project, sort: sort_value_milestone) - expect(first_issue).to include('foo') - expect(last_issue).to include('baz') + expect(first_issue).to include('foo') + expect(last_issue).to include('baz') + end end - end - describe 'combine filter and sort' do - let(:user2) { create(:user) } + describe 'combine filter and sort' do + let(:user2) { create(:user) } - before do - foo.assignees << user2 - foo.save - bar.assignees << user2 - bar.save - end + before do + foo.assignees << user2 + foo.save + bar.assignees << user2 + bar.save + end - it 'sorts with a filter applied' do - visit project_issues_path(project, sort: sort_value_created_date, assignee_id: user2.id) + it 'sorts with a filter applied' do + visit project_issues_path(project, sort: sort_value_created_date, assignee_id: user2.id) - expect(first_issue).to include('foo') - expect(last_issue).to include('bar') - expect(page).not_to have_content('baz') + expect(first_issue).to include('foo') + expect(last_issue).to include('bar') + expect(page).not_to have_content('baz') + end end end - end - describe 'when I want to reset my incoming email token' do - let(:project1) { create(:project, namespace: user.namespace) } - let!(:issue) { create(:issue, project: project1) } + describe 'when I want to reset my incoming email token' do + let(:project1) { create(:project, namespace: user.namespace) } + let!(:issue) { create(:issue, project: project1) } - before do - stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") - project1.team << [user, :master] - visit namespace_project_issues_path(user.namespace, project1) - end + before do + stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") + project1.team << [user, :master] + visit namespace_project_issues_path(user.namespace, project1) + end - it 'changes incoming email address token', :js do - find('.issuable-email-modal-btn').click - previous_token = find('input#issuable_email').value - find('.incoming-email-token-reset').click + it 'changes incoming email address token', :js do + find('.issuable-email-modal-btn').click + previous_token = find('input#issuable_email').value + find('.incoming-email-token-reset').click - wait_for_requests + wait_for_requests - expect(page).to have_no_field('issuable_email', with: previous_token) - new_token = project1.new_issuable_address(user.reload, 'issue') - expect(page).to have_field( - 'issuable_email', - with: new_token - ) + expect(page).to have_no_field('issuable_email', with: previous_token) + new_token = project1.new_issuable_address(user.reload, 'issue') + expect(page).to have_field( + 'issuable_email', + with: new_token + ) + end end - end - describe 'update labels from issue#show', :js do - let(:issue) { create(:issue, project: project, author: user, assignees: [user]) } - let!(:label) { create(:label, project: project) } + describe 'update labels from issue#show', :js do + let(:issue) { create(:issue, project: project, author: user, assignees: [user]) } + let!(:label) { create(:label, project: project) } - before do - visit project_issue_path(project, issue) - end + before do + visit project_issue_path(project, issue) + end - it 'will not send ajax request when no data is changed' do - page.within '.labels' do - click_link 'Edit' + it 'will not send ajax request when no data is changed' do + page.within '.labels' do + click_link 'Edit' - find('.dropdown-menu-close', match: :first).click + find('.dropdown-menu-close', match: :first).click - expect(page).not_to have_selector('.block-loading') + expect(page).not_to have_selector('.block-loading') + end end end - end - describe 'update assignee from issue#show' do - let(:issue) { create(:issue, project: project, author: user, assignees: [user]) } + describe 'update assignee from issue#show' do + let(:issue) { create(:issue, project: project, author: user, assignees: [user]) } - context 'by authorized user' do - it 'allows user to select unassigned', :js do - visit project_issue_path(project, issue) + context 'by authorized user' do + it 'allows user to select unassigned', :js do + visit project_issue_path(project, issue) - page.within('.assignee') do - expect(page).to have_content "#{user.name}" + page.within('.assignee') do + expect(page).to have_content "#{user.name}" - click_link 'Edit' - click_link 'Unassigned' - first('.title').click - expect(page).to have_content 'No assignee' - end + click_link 'Edit' + click_link 'Unassigned' + first('.title').click + expect(page).to have_content 'No assignee' + end - # wait_for_requests does not work with vue-resource at the moment - sleep 1 + # wait_for_requests does not work with vue-resource at the moment + sleep 1 - expect(issue.reload.assignees).to be_empty - end + expect(issue.reload.assignees).to be_empty + end - it 'allows user to select an assignee', :js do - issue2 = create(:issue, project: project, author: user) - visit project_issue_path(project, issue2) + it 'allows user to select an assignee', :js do + issue2 = create(:issue, project: project, author: user) + visit project_issue_path(project, issue2) - page.within('.assignee') do - expect(page).to have_content "No assignee" - end + page.within('.assignee') do + expect(page).to have_content "No assignee" + end - page.within '.assignee' do - click_link 'Edit' - end + page.within '.assignee' do + click_link 'Edit' + end - page.within '.dropdown-menu-user' do - click_link user.name - end + page.within '.dropdown-menu-user' do + click_link user.name + end - page.within('.assignee') do - expect(page).to have_content user.name + page.within('.assignee') do + expect(page).to have_content user.name + end end - end - it 'allows user to unselect themselves', :js do - issue2 = create(:issue, project: project, author: user) - visit project_issue_path(project, issue2) + it 'allows user to unselect themselves', :js do + issue2 = create(:issue, project: project, author: user) + visit project_issue_path(project, issue2) - page.within '.assignee' do - click_link 'Edit' - click_link user.name + page.within '.assignee' do + click_link 'Edit' + click_link user.name - page.within '.value .author' do - expect(page).to have_content user.name - end + page.within '.value .author' do + expect(page).to have_content user.name + end - click_link 'Edit' - click_link user.name + click_link 'Edit' + click_link user.name - page.within '.value .assign-yourself' do - expect(page).to have_content "No assignee" + page.within '.value .assign-yourself' do + expect(page).to have_content "No assignee" + end end end end - end - context 'by unauthorized user' do - let(:guest) { create(:user) } + context 'by unauthorized user' do + let(:guest) { create(:user) } - before do - project.team << [[guest], :guest] - end + before do + project.team << [[guest], :guest] + end - it 'shows assignee text', :js do - sign_out(:user) - sign_in(guest) + it 'shows assignee text', :js do + sign_out(:user) + sign_in(guest) - visit project_issue_path(project, issue) - expect(page).to have_content issue.assignees.first.name + visit project_issue_path(project, issue) + expect(page).to have_content issue.assignees.first.name + end end end - end - describe 'update milestone from issue#show' do - let!(:issue) { create(:issue, project: project, author: user) } - let!(:milestone) { create(:milestone, project: project) } + describe 'update milestone from issue#show' do + let!(:issue) { create(:issue, project: project, author: user) } + let!(:milestone) { create(:milestone, project: project) } - context 'by authorized user' do - it 'allows user to select unassigned', :js do - visit project_issue_path(project, issue) + context 'by authorized user' do + it 'allows user to select unassigned', :js do + visit project_issue_path(project, issue) - page.within('.milestone') do - expect(page).to have_content "None" - end + page.within('.milestone') do + expect(page).to have_content "None" + end - find('.block.milestone .edit-link').click - sleep 2 # wait for ajax stuff to complete - first('.dropdown-content li').click - sleep 2 - page.within('.milestone') do - expect(page).to have_content 'None' + find('.block.milestone .edit-link').click + sleep 2 # wait for ajax stuff to complete + first('.dropdown-content li').click + sleep 2 + page.within('.milestone') do + expect(page).to have_content 'None' + end + + expect(issue.reload.milestone).to be_nil end - expect(issue.reload.milestone).to be_nil - end + it 'allows user to de-select milestone', :js do + visit project_issue_path(project, issue) - it 'allows user to de-select milestone', :js do - visit project_issue_path(project, issue) + page.within('.milestone') do + click_link 'Edit' + click_link milestone.title - page.within('.milestone') do - click_link 'Edit' - click_link milestone.title - - page.within '.value' do - expect(page).to have_content milestone.title - end + page.within '.value' do + expect(page).to have_content milestone.title + end - click_link 'Edit' - click_link milestone.title + click_link 'Edit' + click_link milestone.title - page.within '.value' do - expect(page).to have_content 'None' + page.within '.value' do + expect(page).to have_content 'None' + end end end end - end - context 'by unauthorized user' do - let(:guest) { create(:user) } + context 'by unauthorized user' do + let(:guest) { create(:user) } - before do - project.team << [guest, :guest] - issue.milestone = milestone - issue.save - end + before do + project.team << [guest, :guest] + issue.milestone = milestone + issue.save + end - it 'shows milestone text', :js do - sign_out(:user) - sign_in(guest) + it 'shows milestone text', :js do + sign_out(:user) + sign_in(guest) - visit project_issue_path(project, issue) - expect(page).to have_content milestone.title + visit project_issue_path(project, issue) + expect(page).to have_content milestone.title + end end end - end - describe 'new issue' do - let!(:issue) { create(:issue, project: project) } + describe 'new issue' do + let!(:issue) { create(:issue, project: project) } - context 'by unauthenticated user' do - before do - sign_out(:user) - end + context 'by unauthenticated user' do + before do + sign_out(:user) + end - it 'redirects to signin then back to new issue after signin' do - visit project_issues_path(project) + it 'redirects to signin then back to new issue after signin' do + visit project_issues_path(project) - page.within '.nav-controls' do - click_link 'New issue' - end + page.within '.nav-controls' do + click_link 'New issue' + end - expect(current_path).to eq new_user_session_path + expect(current_path).to eq new_user_session_path - gitlab_sign_in(create(:user)) + gitlab_sign_in(create(:user)) - expect(current_path).to eq new_project_issue_path(project) + expect(current_path).to eq new_project_issue_path(project) + end end - end - context 'dropzone upload file', :js do - before do - visit new_project_issue_path(project) - end + context 'dropzone upload file', :js do + before do + visit new_project_issue_path(project) + end - it 'uploads file when dragging into textarea' do - dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') + it 'uploads file when dragging into textarea' do + dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') - expect(page.find_field("issue_description").value).to have_content 'banana_sample' - end + expect(page.find_field("issue_description").value).to have_content 'banana_sample' + end - it "doesn't add double newline to end of a single attachment markdown" do - dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') + it "doesn't add double newline to end of a single attachment markdown" do + dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') - expect(page.find_field("issue_description").value).not_to match /\n\n$/ - end + expect(page.find_field("issue_description").value).not_to match /\n\n$/ + end - it "cancels a file upload correctly" do - slow_requests do - dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false) + it "cancels a file upload correctly" do + slow_requests do + dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false) - click_button 'Cancel' - end + click_button 'Cancel' + end - expect(page).to have_button('Attach a file') - expect(page).not_to have_button('Cancel') - expect(page).not_to have_selector('.uploading-progress-container', visible: true) + expect(page).to have_button('Attach a file') + expect(page).not_to have_button('Cancel') + expect(page).not_to have_selector('.uploading-progress-container', visible: true) + end end - end - context 'form filled by URL parameters' do - let(:project) { create(:project, :public, :repository) } + context 'form filled by URL parameters' do + let(:project) { create(:project, :public, :repository) } - before do - project.repository.create_file( - user, - '.gitlab/issue_templates/bug.md', - 'this is a test "bug" template', - message: 'added issue template', - branch_name: 'master') - - visit new_project_issue_path(project, issuable_template: 'bug') - end + before do + project.repository.create_file( + user, + '.gitlab/issue_templates/bug.md', + 'this is a test "bug" template', + message: 'added issue template', + branch_name: 'master') + + visit new_project_issue_path(project, issuable_template: 'bug') + end - it 'fills in template' do - expect(find('.js-issuable-selector .dropdown-toggle-text')).to have_content('bug') + it 'fills in template' do + expect(find('.js-issuable-selector .dropdown-toggle-text')).to have_content('bug') + end end end - end - describe 'new issue by email' do - shared_examples 'show the email in the modal' do - let(:issue) { create(:issue, project: project) } + describe 'new issue by email' do + shared_examples 'show the email in the modal' do + let(:issue) { create(:issue, project: project) } - before do - project.issues << issue - stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") + before do + project.issues << issue + stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") - visit project_issues_path(project) - click_button('Email a new issue') - end + visit project_issues_path(project) + click_button('Email a new issue') + end - it 'click the button to show modal for the new email' do - page.within '#issuable-email-modal' do - email = project.new_issuable_address(user, 'issue') + it 'click the button to show modal for the new email' do + page.within '#issuable-email-modal' do + email = project.new_issuable_address(user, 'issue') - expect(page).to have_selector("input[value='#{email}']") + expect(page).to have_selector("input[value='#{email}']") + end end end - end - context 'with existing issues' do - let!(:issue) { create(:issue, project: project, author: user) } + context 'with existing issues' do + let!(:issue) { create(:issue, project: project, author: user) } - it_behaves_like 'show the email in the modal' - end + it_behaves_like 'show the email in the modal' + end - context 'without existing issues' do - it_behaves_like 'show the email in the modal' + context 'without existing issues' do + it_behaves_like 'show the email in the modal' + end end - end - describe 'due date' do - context 'update due on issue#show', :js do - let(:issue) { create(:issue, project: project, author: user, assignees: [user]) } + describe 'due date' do + context 'update due on issue#show', :js do + let(:issue) { create(:issue, project: project, author: user, assignees: [user]) } - before do - visit project_issue_path(project, issue) - end + before do + visit project_issue_path(project, issue) + end - it 'adds due date to issue' do - date = Date.today.at_beginning_of_month + 2.days + it 'adds due date to issue' do + date = Date.today.at_beginning_of_month + 2.days - page.within '.due_date' do - click_link 'Edit' + page.within '.due_date' do + click_link 'Edit' - page.within '.pika-single' do - click_button date.day - end + page.within '.pika-single' do + click_button date.day + end - wait_for_requests + wait_for_requests - expect(find('.value').text).to have_content date.strftime('%b %-d, %Y') + expect(find('.value').text).to have_content date.strftime('%b %-d, %Y') + end end - end - it 'removes due date from issue' do - date = Date.today.at_beginning_of_month + 2.days + it 'removes due date from issue' do + date = Date.today.at_beginning_of_month + 2.days - page.within '.due_date' do - click_link 'Edit' + page.within '.due_date' do + click_link 'Edit' - page.within '.pika-single' do - click_button date.day - end + page.within '.pika-single' do + click_button date.day + end - wait_for_requests + wait_for_requests - expect(page).to have_no_content 'No due date' + expect(page).to have_no_content 'No due date' - click_link 'remove due date' - expect(page).to have_content 'No due date' + click_link 'remove due date' + expect(page).to have_content 'No due date' + end end end end - end - describe 'title issue#show', :js do - it 'updates the title', :js do - issue = create(:issue, author: user, assignees: [user], project: project, title: 'new title') + describe 'title issue#show', :js do + it 'updates the title', :js do + issue = create(:issue, author: user, assignees: [user], project: project, title: 'new title') - visit project_issue_path(project, issue) + visit project_issue_path(project, issue) - expect(page).to have_text("new title") + expect(page).to have_text("new title") - issue.update(title: "updated title") + issue.update(title: "updated title") - wait_for_requests - expect(page).to have_text("updated title") + wait_for_requests + expect(page).to have_text("updated title") + end end - end - describe 'confidential issue#show', :js do - it 'shows confidential sibebar information as confidential and can be turned off' do - issue = create(:issue, :confidential, project: project) + describe 'confidential issue#show', :js do + it 'shows confidential sibebar information as confidential and can be turned off' do + issue = create(:issue, :confidential, project: project) - visit project_issue_path(project, issue) + visit project_issue_path(project, issue) - expect(page).to have_css('.issuable-note-warning') - expect(find('.issuable-sidebar-item.confidentiality')).to have_css('.is-active') - expect(find('.issuable-sidebar-item.confidentiality')).not_to have_css('.not-active') + expect(page).to have_css('.issuable-note-warning') + expect(find('.issuable-sidebar-item.confidentiality')).to have_css('.is-active') + expect(find('.issuable-sidebar-item.confidentiality')).not_to have_css('.not-active') - find('.confidential-edit').click - expect(page).to have_css('.sidebar-item-warning-message') + find('.confidential-edit').click + expect(page).to have_css('.sidebar-item-warning-message') - within('.sidebar-item-warning-message') do - find('.btn-close').click - end + within('.sidebar-item-warning-message') do + find('.btn-close').click + end - wait_for_requests + wait_for_requests - visit project_issue_path(project, issue) + visit project_issue_path(project, issue) - expect(page).not_to have_css('.is-active') + expect(page).not_to have_css('.is-active') + end end end end diff --git a/spec/features/merge_requests/image_diff_notes.rb b/spec/features/merge_requests/image_diff_notes_spec.rb index 021c4e03428..b53570835cb 100644 --- a/spec/features/merge_requests/image_diff_notes.rb +++ b/spec/features/merge_requests/image_diff_notes_spec.rb @@ -10,11 +10,10 @@ feature 'image diff notes', :js do project.team << [user, :master] sign_in user - page.driver.set_cookie('sidebar_collapsed', 'true') - # Stub helper to return any blob file as image from public app folder. # This is necessary to run this specs since we don't display repo images in capybara. - allow_any_instance_of(DiffHelper).to receive(:diff_file_blob_raw_path).and_return('/apple-touch-icon.png') + allow_any_instance_of(DiffHelper).to receive(:diff_file_blob_raw_url).and_return('/apple-touch-icon.png') + allow_any_instance_of(DiffHelper).to receive(:diff_file_old_blob_raw_url).and_return('/favicon.ico') end context 'create commit diff notes' do @@ -141,13 +140,13 @@ feature 'image diff notes', :js do end it 'allows expanding/collapsing the discussion notes' do - page.all('.js-diff-notes-toggle')[0].trigger('click') - page.all('.js-diff-notes-toggle')[1].trigger('click') + page.all('.js-diff-notes-toggle')[0].click + page.all('.js-diff-notes-toggle')[1].click expect(page).not_to have_content('image diff test comment') - page.all('.js-diff-notes-toggle')[0].trigger('click') - page.all('.js-diff-notes-toggle')[1].trigger('click') + page.all('.js-diff-notes-toggle')[0].click + page.all('.js-diff-notes-toggle')[1].click expect(page).to have_content('image diff test comment') end @@ -196,13 +195,31 @@ feature 'image diff notes', :js do expect(find('.onion-skin-frame')['style']).to match('width: 228px; height: 240px;') end + + it 'resets onion skin view mode opacity when toggling between view modes' do + find('.view-modes-menu .onion-skin').click + + # Simulate dragging onion-skin slider + drag_and_drop_by(find('.dragger'), -30, 0) + + expect(find('.onion-skin-frame .frame.added', visible: false)['style']).not_to match('opacity: 1;') + + find('.view-modes-menu .swipe').click + find('.view-modes-menu .onion-skin').click + + expect(find('.onion-skin-frame .frame.added', visible: false)['style']).to match('opacity: 1;') + end end -end -def create_image_diff_note - find('.js-add-image-diff-note-button', match: :first).click - page.all('.js-add-image-diff-note-button')[0].trigger('click') - find('.diff-content .note-textarea').native.send_keys('image diff test comment') - click_button 'Comment' - wait_for_requests + def drag_and_drop_by(element, right_by, down_by) + page.driver.browser.action.drag_and_drop_by(element.native, right_by, down_by).perform + end + + def create_image_diff_note + find('.js-add-image-diff-note-button', match: :first).click + page.all('.js-add-image-diff-note-button')[0].click + find('.diff-content .note-textarea').native.send_keys('image diff test comment') + click_button 'Comment' + wait_for_requests + end end diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_requests/mini_pipeline_graph_spec.rb index 93c5e945453..a7e7c0eeff6 100644 --- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb +++ b/spec/features/merge_requests/mini_pipeline_graph_spec.rb @@ -15,8 +15,8 @@ feature 'Mini Pipeline Graph', :js do visit_merge_request end - def visit_merge_request(format = :html) - visit project_merge_request_path(project, merge_request, format: format) + def visit_merge_request(format: :html, serializer: nil) + visit project_merge_request_path(project, merge_request, format: format, serializer: serializer) end it 'should display a mini pipeline graph' do @@ -33,12 +33,12 @@ feature 'Mini Pipeline Graph', :js do end it 'avoids repeated database queries' do - before = ActiveRecord::QueryRecorder.new { visit_merge_request(:json) } + before = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') } create(:ci_build, pipeline: pipeline, legacy_artifacts_file: artifacts_file2) create(:ci_build, pipeline: pipeline, when: 'manual') - after = ActiveRecord::QueryRecorder.new { visit_merge_request(:json) } + after = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') } expect(before.count).to eq(after.count) expect(before.cached_count).to eq(after.cached_count) diff --git a/spec/features/merge_requests/user_posts_notes_spec.rb b/spec/features/merge_requests/user_posts_notes_spec.rb index f4c75a2f265..e17e9c2ccf5 100644 --- a/spec/features/merge_requests/user_posts_notes_spec.rb +++ b/spec/features/merge_requests/user_posts_notes_spec.rb @@ -66,6 +66,21 @@ describe 'Merge requests > User posts notes', :js do end end + describe 'when previewing a note' do + it 'shows the toolbar buttons when editing a note' do + page.within('.js-main-target-form') do + expect(page).to have_css('.md-header-toolbar.active') + end + end + + it 'hides the toolbar buttons when previewing a note' do + find('.js-md-preview-button').click + page.within('.js-main-target-form') do + expect(page).not_to have_css('.md-header-toolbar.active') + end + end + end + describe 'when editing a note' do it 'there should be a hidden edit form' do is_expected.to have_css('.note-edit-form:not(.mr-note-edit-form)', visible: false, count: 1) diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb index 27efc32c95b..9f24193a2ac 100644 --- a/spec/features/milestone_spec.rb +++ b/spec/features/milestone_spec.rb @@ -82,9 +82,9 @@ feature 'Milestone' do milestone = create(:milestone, project: project, title: 8.7) issue1 = create(:issue, project: project, milestone: milestone) issue2 = create(:issue, project: project, milestone: milestone) - issue1.spend_time(duration: 3600, user: user) + issue1.spend_time(duration: 3600, user_id: user.id) issue1.save! - issue2.spend_time(duration: 7200, user: user) + issue2.spend_time(duration: 7200, user_id: user.id) issue2.save! visit project_milestone_path(project, milestone) diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index c60883911f7..0848857ed1e 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -25,7 +25,7 @@ describe 'Profile account page', :js do fill_in 'password', with: '12345678' - page.within '.popup-dialog' do + page.within '.modal' do click_button 'Delete account' end @@ -38,7 +38,7 @@ describe 'Profile account page', :js do fill_in 'password', with: 'testing123' - page.within '.popup-dialog' do + page.within '.modal' do click_button 'Delete account' end diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb index 6c616bf0456..8ac9821b879 100644 --- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb +++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb @@ -2,15 +2,15 @@ require 'spec_helper' feature 'project owner sees a link to create a license file in empty project', :js do let(:project_master) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project_empty_repo) } + background do - project.team << [project_master, :master] + project.add_master(project_master) sign_in(project_master) end scenario 'project master creates a license file from a template' do visit project_path(project) - click_link 'Create empty bare repository' click_on 'LICENSE' expect(page).to have_content('New file') @@ -26,8 +26,6 @@ feature 'project owner sees a link to create a license file in empty project', : expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}") fill_in :commit_message, with: 'Add a LICENSE file', visible: true - # Remove pre-receive hook so we can push without auth - FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive')) click_button 'Commit changes' expect(current_path).to eq( diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index 0257cd157c9..4319fc2746c 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -32,9 +32,7 @@ feature 'issuable templates', :js do message: 'added issue template', branch_name: 'master') visit project_issue_path project, issue - page.within('.js-issuable-actions') do - click_on 'Edit' - end + page.find('.js-issuable-edit').click fill_in :'issuable-title', with: 'test issue title' end @@ -77,9 +75,7 @@ feature 'issuable templates', :js do message: 'added issue template', branch_name: 'master') visit project_issue_path project, issue - page.within('.js-issuable-actions') do - click_on 'Edit' - end + page.find('.js-issuable-edit').click fill_in :'issuable-title', with: 'test issue title' fill_in :'issue-description', with: prior_description end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 888e290292b..3987cea0b4f 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -152,7 +152,7 @@ describe 'Pipeline', :js do end it 'shows counter in Jobs tab' do - expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s) + expect(page.find('.js-builds-counter').text).to eq(pipeline.total_size.to_s) end it 'shows Pipeline tab as active' do @@ -248,7 +248,7 @@ describe 'Pipeline', :js do end it 'shows counter in Jobs tab' do - expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s) + expect(page.find('.js-builds-counter').text).to eq(pipeline.total_size.to_s) end it 'shows Jobs tab as active' do diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb deleted file mode 100644 index 33ccbc1a29f..00000000000 --- a/spec/features/projects/ref_switcher_spec.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'rails_helper' - -feature 'Ref switcher', :js do - let(:user) { create(:user) } - let(:project) { create(:project, :public, :repository) } - - before do - project.team << [user, :master] - set_cookie('new_repo', 'true') - sign_in(user) - visit project_tree_path(project, 'master') - end - - it 'allow user to change ref by enter key' do - click_button 'master' - wait_for_requests - - page.within '.project-refs-form' do - input = find('input[type="search"]') - input.set 'binary' - wait_for_requests - - expect(find('.dropdown-content ul')).to have_selector('li', count: 7) - - page.within '.dropdown-content ul' do - input.native.send_keys :enter - end - end - - expect(page).to have_title 'add-pdf-text-binary' - end - - it "user selects ref with special characters" do - click_button 'master' - wait_for_requests - - page.within '.project-refs-form' do - page.fill_in 'Search branches and tags', with: "'test'" - click_link "'test'" - end - - expect(page).to have_title "'test'" - end - - context "create branch" do - let(:input) { find('.js-new-branch-name') } - - before do - click_button 'master' - wait_for_requests - - page.within '.project-refs-form' do - find(".dropdown-footer-list a").click - end - end - - it "shows error message for the invalid branch name" do - input.set 'foo bar' - click_button('Create') - wait_for_requests - expect(page).to have_content 'Branch name is invalid' - end - - it "should create new branch properly" do - input.set 'new-branch-name' - click_button('Create') - wait_for_requests - expect(find('.js-project-refs-dropdown')).to have_content 'new-branch-name' - end - - it "should create new branch by Enter key" do - input.set 'new-branch-name-2' - input.native.send_keys :enter - wait_for_requests - expect(find('.js-project-refs-dropdown')).to have_content 'new-branch-name-2' - end - end -end diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb index 156293289dd..3f6d16c8acf 100644 --- a/spec/features/projects/tree/create_directory_spec.rb +++ b/spec/features/projects/tree/create_directory_spec.rb @@ -13,6 +13,14 @@ feature 'Multi-file editor new directory', :js do visit project_tree_path(project, :master) wait_for_requests + + click_link('Multi Edit') + + wait_for_requests + end + + after do + set_cookie('new_repo', 'false') end it 'creates directory in current directory' do @@ -20,18 +28,30 @@ feature 'Multi-file editor new directory', :js do click_link('New directory') - page.within('.popup-dialog') do - find('.form-control').set('foldername') + page.within('.modal') do + find('.form-control').set('folder name') click_button('Create directory') end + find('.add-to-tree').click + + click_link('New file') + + page.within('.modal-dialog') do + find('.form-control').set('file name') + + click_button('Create file') + end + + wait_for_requests + find('.multi-file-commit-panel-collapse-btn').click - fill_in('commit-message', with: 'commit message') + fill_in('commit-message', with: 'commit message ide') click_button('Commit') - expect(page).to have_selector('td', text: 'commit message') + expect(page).to have_content('folder name') end end diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb index 8fb8476e631..ba71eef07f4 100644 --- a/spec/features/projects/tree/create_file_spec.rb +++ b/spec/features/projects/tree/create_file_spec.rb @@ -13,6 +13,14 @@ feature 'Multi-file editor new file', :js do visit project_tree_path(project, :master) wait_for_requests + + click_link('Multi Edit') + + wait_for_requests + end + + after do + set_cookie('new_repo', 'false') end it 'creates file in current directory' do @@ -20,18 +28,20 @@ feature 'Multi-file editor new file', :js do click_link('New file') - page.within('.popup-dialog') do - find('.form-control').set('filename') + page.within('.modal') do + find('.form-control').set('file name') click_button('Create file') end + wait_for_requests + find('.multi-file-commit-panel-collapse-btn').click - fill_in('commit-message', with: 'commit message') + fill_in('commit-message', with: 'commit message ide') click_button('Commit') - expect(page).to have_selector('td', text: 'commit message') + expect(page).to have_content('file name') end end diff --git a/spec/features/projects/tree/tree_show_spec.rb b/spec/features/projects/tree/tree_show_spec.rb new file mode 100644 index 00000000000..c8a17871508 --- /dev/null +++ b/spec/features/projects/tree/tree_show_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +feature 'Projects tree' do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + + before do + project.add_master(user) + sign_in(user) + + visit project_tree_path(project, 'master') + end + + it 'renders tree table' do + expect(page).to have_selector('.tree-item') + expect(page).not_to have_selector('.label-lfs', text: 'LFS') + end + + context 'LFS' do + before do + visit project_tree_path(project, File.join('master', 'files/lfs')) + end + + it 'renders LFS badge on blob item' do + expect(page).to have_selector('.label-lfs', text: 'LFS') + end + end +end diff --git a/spec/features/projects/tree/upload_file_spec.rb b/spec/features/projects/tree/upload_file_spec.rb index d4e57d1ecfa..9fbb1dbd0e8 100644 --- a/spec/features/projects/tree/upload_file_spec.rb +++ b/spec/features/projects/tree/upload_file_spec.rb @@ -15,6 +15,14 @@ feature 'Multi-file editor upload file', :js do visit project_tree_path(project, :master) wait_for_requests + + click_link('Multi Edit') + + wait_for_requests + end + + after do + set_cookie('new_repo', 'false') end it 'uploads text file' do @@ -41,6 +49,5 @@ feature 'Multi-file editor upload file', :js do expect(page).to have_selector('.multi-file-tab', text: 'dk.png') expect(page).not_to have_selector('.monaco-editor') - expect(page).to have_content('The source could not be displayed for this temporary file.') end end diff --git a/spec/features/tags/master_views_tags_spec.rb b/spec/features/tags/master_views_tags_spec.rb index 9edc7ced163..4662367d843 100644 --- a/spec/features/tags/master_views_tags_spec.rb +++ b/spec/features/tags/master_views_tags_spec.rb @@ -4,18 +4,17 @@ feature 'Master views tags' do let(:user) { create(:user) } before do - project.team << [user, :master] + project.add_master(user) sign_in(user) end context 'when project has no tags' do let(:project) { create(:project_empty_repo) } + before do visit project_path(project) click_on 'README' fill_in :commit_message, with: 'Add a README file', visible: true - # Remove pre-receive hook so we can push without auth - FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive')) click_button 'Commit changes' visit project_tags_path(project) end diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb index c78f7d0d9be..dde60c83536 100644 --- a/spec/features/variables_spec.rb +++ b/spec/features/variables_spec.rb @@ -65,14 +65,14 @@ describe 'Project variables', :js do expect(page).to have_content('******') end - click_button('Reveal Values') + click_button('Reveal values') page.within('.variables-table') do expect(page).to have_content('key') expect(page).to have_content('key value') end - click_button('Hide Values') + click_button('Hide values') page.within('.variables-table') do expect(page).to have_content('key') diff --git a/spec/finders/group_descendants_finder_spec.rb b/spec/finders/group_descendants_finder_spec.rb index 074914420a1..ae050f36b4a 100644 --- a/spec/finders/group_descendants_finder_spec.rb +++ b/spec/finders/group_descendants_finder_spec.rb @@ -73,6 +73,41 @@ describe GroupDescendantsFinder do expect(finder.execute).to contain_exactly(matching_project) end end + + context 'sorting by name' do + let!(:project1) { create(:project, namespace: group, name: 'a', path: 'project-a') } + let!(:project2) { create(:project, namespace: group, name: 'z', path: 'project-z') } + let(:params) do + { + sort: 'name_asc' + } + end + + it 'sorts elements by name' do + expect(subject.execute).to eq( + [ + project1, + project2 + ] + ) + end + + context 'with nested groups', :nested_groups do + let!(:subgroup1) { create(:group, parent: group, name: 'a', path: 'sub-a') } + let!(:subgroup2) { create(:group, parent: group, name: 'z', path: 'sub-z') } + + it 'sorts elements by name' do + expect(subject.execute).to eq( + [ + subgroup1, + subgroup2, + project1, + project2 + ] + ) + end + end + end end context 'with nested groups', :nested_groups do diff --git a/spec/fixtures/api/schemas/contributor.json b/spec/fixtures/api/schemas/contributor.json new file mode 100644 index 00000000000..e88470a2363 --- /dev/null +++ b/spec/fixtures/api/schemas/contributor.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "required" : [ + "name", + "email", + "commits", + "additions", + "deletions" + ], + "properties" : { + "name": { "type": "string" }, + "email": { "type": "string" }, + "commits": { "type": "integer" }, + "additions": { "type": "integer" }, + "deletions": { "type": "integer" } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/contributors.json b/spec/fixtures/api/schemas/contributors.json new file mode 100644 index 00000000000..a9f1d1ea64f --- /dev/null +++ b/spec/fixtures/api/schemas/contributors.json @@ -0,0 +1,4 @@ +{ + "type": "array", + "items": { "$ref": "contributor.json" } +} diff --git a/spec/fixtures/api/schemas/entities/merge_request.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json index ba094ba1657..342890c3dee 100644 --- a/spec/fixtures/api/schemas/entities/merge_request.json +++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json @@ -80,15 +80,15 @@ "target_branch_tree_path": { "type": "string" }, "source_branch_path": { "type": "string" }, "conflict_resolution_path": { "type": ["string", "null"] }, - "cancel_merge_when_pipeline_succeeds_path": { "type": "string" }, - "create_issue_to_resolve_discussions_path": { "type": "string" }, - "merge_path": { "type": "string" }, + "cancel_merge_when_pipeline_succeeds_path": { "type": ["string", "null"] }, + "create_issue_to_resolve_discussions_path": { "type": ["string", "null"] }, + "merge_path": { "type": ["string", "null"] }, "cherry_pick_in_fork_path": { "type": ["string", "null"] }, "revert_in_fork_path": { "type": ["string", "null"] }, "email_patches_path": { "type": "string" }, "plain_diff_path": { "type": "string" }, "status_path": { "type": "string" }, - "new_blob_path": { "type": "string" }, + "new_blob_path": { "type": ["string", "null"] }, "merge_check_path": { "type": "string" }, "ci_environments_status_path": { "type": "string" }, "merge_commit_message_with_description": { "type": "string" }, diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index 638cd8b07c8..71abb6da607 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -258,12 +258,23 @@ With inline diffs tags you can display {+ additions +} or [- deletions -]. The wrapping tags can be either curly braces or square brackets [+ additions +] or {- deletions -}. -However the wrapping tags can not be mixed as such - +Examples: +``` +- {+ additions +} +- [+ additions +] +- {- deletions -} +- [- deletions -] +``` + +However the wrapping tags cannot be mixed as such: + +``` - {+ additions +] - [+ additions +} -- {- delletions -] -- [- delletions -} +- {- deletions -] +- [- deletions -} +``` ### Videos diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb index cd15e27b497..36a44f8567a 100644 --- a/spec/helpers/notes_helper_spec.rb +++ b/spec/helpers/notes_helper_spec.rb @@ -41,6 +41,7 @@ describe NotesHelper do describe '#discussion_path' do let(:project) { create(:project, :repository) } + let(:anchor) { discussion.line_code } context 'for a merge request discusion' do let(:merge_request) { create(:merge_request, source_project: project, target_project: project, importing: true) } @@ -151,6 +152,15 @@ describe NotesHelper do expect(helper.discussion_path(discussion)).to be_nil end end + + context 'for a contextual commit discussion' do + let(:commit) { merge_request.commits.last } + let(:discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, commit_id: commit.id).to_discussion } + + it 'returns the merge request diff discussion scoped in the commit' do + expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, commit_id: commit.id, anchor: anchor)) + end + end end context 'for a commit discussion' do @@ -160,7 +170,7 @@ describe NotesHelper do let(:discussion) { create(:diff_note_on_commit, project: project).to_discussion } it 'returns the commit path with the line code' do - expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: discussion.line_code)) + expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: anchor)) end end @@ -168,7 +178,7 @@ describe NotesHelper do let(:discussion) { create(:legacy_diff_note_on_commit, project: project).to_discussion } it 'returns the commit path with the line code' do - expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: discussion.line_code)) + expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: anchor)) end end diff --git a/spec/helpers/runners_helper_spec.rb b/spec/helpers/runners_helper_spec.rb index 35f91b7decf..a4a483e68a8 100644 --- a/spec/helpers/runners_helper_spec.rb +++ b/spec/helpers/runners_helper_spec.rb @@ -2,17 +2,17 @@ require 'spec_helper' describe RunnersHelper do it "returns - not contacted yet" do - runner = FactoryGirl.build :ci_runner + runner = FactoryBot.build :ci_runner expect(runner_status_icon(runner)).to include("not connected yet") end it "returns offline text" do - runner = FactoryGirl.build(:ci_runner, contacted_at: 1.day.ago, active: true) + runner = FactoryBot.build(:ci_runner, contacted_at: 1.day.ago, active: true) expect(runner_status_icon(runner)).to include("Runner is offline") end it "returns online text" do - runner = FactoryGirl.build(:ci_runner, contacted_at: 1.second.ago, active: true) + runner = FactoryBot.build(:ci_runner, contacted_at: 1.second.ago, active: true) expect(runner_status_icon(runner)).to include("Runner is online") end end diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb index c358ccae9c3..d3b1be599dd 100644 --- a/spec/helpers/tree_helper_spec.rb +++ b/spec/helpers/tree_helper_spec.rb @@ -9,6 +9,7 @@ describe TreeHelper do before do @id = sha @project = project + @lfs_blob_ids = [] end it 'displays all entries without a warning' do diff --git a/spec/javascripts/activities_spec.js b/spec/javascripts/activities_spec.js index e8c5f721423..7a9c539e9d0 100644 --- a/spec/javascripts/activities_spec.js +++ b/spec/javascripts/activities_spec.js @@ -1,8 +1,7 @@ /* eslint-disable no-unused-expressions, no-prototype-builtins, no-new, no-shadow, max-len */ import 'vendor/jquery.endless-scroll'; -import '~/pager'; -import '~/activities'; +import Activities from '~/activities'; (() => { window.gon || (window.gon = {}); @@ -35,7 +34,7 @@ import '~/activities'; describe('Activities', () => { beforeEach(() => { loadFixtures(fixtureTemplate); - new gl.Activities(); + new Activities(); }); for (let i = 0; i < filters.length; i += 1) { diff --git a/spec/javascripts/behaviors/secret_values_spec.js b/spec/javascripts/behaviors/secret_values_spec.js new file mode 100644 index 00000000000..9eeae474e7d --- /dev/null +++ b/spec/javascripts/behaviors/secret_values_spec.js @@ -0,0 +1,146 @@ +import SecretValues from '~/behaviors/secret_values'; + +function generateFixtureMarkup(secrets, isRevealed) { + return ` + <div class="js-secret-container"> + ${secrets.map(secret => ` + <div class="js-secret-value-placeholder"> + *** + </div> + <div class="hide js-secret-value"> + ${secret} + </div> + `).join('')} + <button + class="js-secret-value-reveal-button" + data-secret-reveal-status="${isRevealed}" + > + ... + </button> + </div> + `; +} + +function setupSecretFixture(secrets, isRevealed) { + const wrapper = document.createElement('div'); + wrapper.innerHTML = generateFixtureMarkup(secrets, isRevealed); + + const secretValues = new SecretValues(wrapper.querySelector('.js-secret-container')); + secretValues.init(); + + return wrapper; +} + +describe('setupSecretValues', () => { + describe('with a single secret', () => { + const secrets = ['mysecret123']; + + it('should have correct "Reveal" label when values are hidden', () => { + const wrapper = setupSecretFixture(secrets, false); + const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); + + expect(revealButton.textContent).toEqual('Reveal value'); + }); + + it('should have correct "Hide" label when values are shown', () => { + const wrapper = setupSecretFixture(secrets, true); + const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); + + expect(revealButton.textContent).toEqual('Hide value'); + }); + + it('should value hidden initially', () => { + const wrapper = setupSecretFixture(secrets, false); + const values = wrapper.querySelectorAll('.js-secret-value'); + const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder'); + + expect(values.length).toEqual(1); + expect(values[0].classList.contains('hide')).toEqual(true); + expect(placeholders.length).toEqual(1); + expect(placeholders[0].classList.contains('hide')).toEqual(false); + }); + + it('should toggle value and placeholder', () => { + const wrapper = setupSecretFixture(secrets, false); + const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); + const values = wrapper.querySelectorAll('.js-secret-value'); + const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder'); + + revealButton.click(); + + expect(values.length).toEqual(1); + expect(values[0].classList.contains('hide')).toEqual(false); + expect(placeholders.length).toEqual(1); + expect(placeholders[0].classList.contains('hide')).toEqual(true); + + revealButton.click(); + + expect(values.length).toEqual(1); + expect(values[0].classList.contains('hide')).toEqual(true); + expect(placeholders.length).toEqual(1); + expect(placeholders[0].classList.contains('hide')).toEqual(false); + }); + }); + + describe('with a multiple secrets', () => { + const secrets = ['mysecret123', 'happygoat456', 'tanuki789']; + + it('should have correct "Reveal" label when values are hidden', () => { + const wrapper = setupSecretFixture(secrets, false); + const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); + + expect(revealButton.textContent).toEqual('Reveal values'); + }); + + it('should have correct "Hide" label when values are shown', () => { + const wrapper = setupSecretFixture(secrets, true); + const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); + + expect(revealButton.textContent).toEqual('Hide values'); + }); + + it('should have all values hidden initially', () => { + const wrapper = setupSecretFixture(secrets, false); + const values = wrapper.querySelectorAll('.js-secret-value'); + const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder'); + + expect(values.length).toEqual(3); + values.forEach((value) => { + expect(value.classList.contains('hide')).toEqual(true); + }); + expect(placeholders.length).toEqual(3); + placeholders.forEach((placeholder) => { + expect(placeholder.classList.contains('hide')).toEqual(false); + }); + }); + + it('should toggle values and placeholders', () => { + const wrapper = setupSecretFixture(secrets, false); + const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); + const values = wrapper.querySelectorAll('.js-secret-value'); + const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder'); + + revealButton.click(); + + expect(values.length).toEqual(3); + values.forEach((value) => { + expect(value.classList.contains('hide')).toEqual(false); + }); + expect(placeholders.length).toEqual(3); + placeholders.forEach((placeholder) => { + expect(placeholder.classList.contains('hide')).toEqual(true); + }); + + revealButton.click(); + + expect(values.length).toEqual(3); + values.forEach((value) => { + expect(value.classList.contains('hide')).toEqual(true); + }); + expect(placeholders.length).toEqual(3); + placeholders.forEach((placeholder) => { + expect(placeholder.classList.contains('hide')).toEqual(false); + }); + }); + }); +}); diff --git a/spec/javascripts/collapsed_sidebar_todo_spec.js b/spec/javascripts/collapsed_sidebar_todo_spec.js index 974815fe939..5026eaafaca 100644 --- a/spec/javascripts/collapsed_sidebar_todo_spec.js +++ b/spec/javascripts/collapsed_sidebar_todo_spec.js @@ -1,7 +1,6 @@ -/* global Sidebar */ /* eslint-disable no-new */ import _ from 'underscore'; -import '~/right_sidebar'; +import Sidebar from '~/right_sidebar'; describe('Issuable right sidebar collapsed todo toggle', () => { const fixtureName = 'issues/open-issue.html.raw'; diff --git a/spec/javascripts/commits_spec.js b/spec/javascripts/commits_spec.js index e5a5e3293b9..d0176520440 100644 --- a/spec/javascripts/commits_spec.js +++ b/spec/javascripts/commits_spec.js @@ -1,5 +1,4 @@ import 'vendor/jquery.endless-scroll'; -import '~/pager'; import CommitsList from '~/commits'; describe('Commits List', () => { diff --git a/spec/javascripts/datetime_utility_spec.js b/spec/javascripts/datetime_utility_spec.js index 0f7bf9ec712..2e5b65f5610 100644 --- a/spec/javascripts/datetime_utility_spec.js +++ b/spec/javascripts/datetime_utility_spec.js @@ -1,110 +1,108 @@ import * as datetimeUtility from '~/lib/utils/datetime_utility'; -(() => { - describe('Date time utils', () => { - describe('timeFor', () => { - it('returns `past due` when in past', () => { - const date = new Date(); - date.setFullYear(date.getFullYear() - 1); - - expect( - gl.utils.timeFor(date), - ).toBe('Past due'); - }); - - it('returns remaining time when in the future', () => { - const date = new Date(); - date.setFullYear(date.getFullYear() + 1); - - // Add a day to prevent a transient error. If date is even 1 second - // short of a full year, timeFor will return '11 months remaining' - date.setDate(date.getDate() + 1); - - expect( - gl.utils.timeFor(date), - ).toBe('1 year remaining'); - }); +describe('Date time utils', () => { + describe('timeFor', () => { + it('returns `past due` when in past', () => { + const date = new Date(); + date.setFullYear(date.getFullYear() - 1); + + expect( + datetimeUtility.timeFor(date), + ).toBe('Past due'); }); - describe('get day name', () => { - it('should return Sunday', () => { - const day = gl.utils.getDayName(new Date('07/17/2016')); - expect(day).toBe('Sunday'); - }); - - it('should return Monday', () => { - const day = gl.utils.getDayName(new Date('07/18/2016')); - expect(day).toBe('Monday'); - }); - - it('should return Tuesday', () => { - const day = gl.utils.getDayName(new Date('07/19/2016')); - expect(day).toBe('Tuesday'); - }); - - it('should return Wednesday', () => { - const day = gl.utils.getDayName(new Date('07/20/2016')); - expect(day).toBe('Wednesday'); - }); - - it('should return Thursday', () => { - const day = gl.utils.getDayName(new Date('07/21/2016')); - expect(day).toBe('Thursday'); - }); - - it('should return Friday', () => { - const day = gl.utils.getDayName(new Date('07/22/2016')); - expect(day).toBe('Friday'); - }); - - it('should return Saturday', () => { - const day = gl.utils.getDayName(new Date('07/23/2016')); - expect(day).toBe('Saturday'); - }); - }); + it('returns remaining time when in the future', () => { + const date = new Date(); + date.setFullYear(date.getFullYear() + 1); + + // Add a day to prevent a transient error. If date is even 1 second + // short of a full year, timeFor will return '11 months remaining' + date.setDate(date.getDate() + 1); - describe('get day difference', () => { - it('should return 7', () => { - const firstDay = new Date('07/01/2016'); - const secondDay = new Date('07/08/2016'); - const difference = gl.utils.getDayDifference(firstDay, secondDay); - expect(difference).toBe(7); - }); - - it('should return 31', () => { - const firstDay = new Date('07/01/2016'); - const secondDay = new Date('08/01/2016'); - const difference = gl.utils.getDayDifference(firstDay, secondDay); - expect(difference).toBe(31); - }); - - it('should return 365', () => { - const firstDay = new Date('07/02/2015'); - const secondDay = new Date('07/01/2016'); - const difference = gl.utils.getDayDifference(firstDay, secondDay); - expect(difference).toBe(365); - }); + expect( + datetimeUtility.timeFor(date), + ).toBe('1 year remaining'); }); }); - describe('timeIntervalInWords', () => { - it('should return string with number of minutes and seconds', () => { - expect(datetimeUtility.timeIntervalInWords(9.54)).toEqual('9 seconds'); - expect(datetimeUtility.timeIntervalInWords(1)).toEqual('1 second'); - expect(datetimeUtility.timeIntervalInWords(200)).toEqual('3 minutes 20 seconds'); - expect(datetimeUtility.timeIntervalInWords(6008)).toEqual('100 minutes 8 seconds'); + describe('get day name', () => { + it('should return Sunday', () => { + const day = datetimeUtility.getDayName(new Date('07/17/2016')); + expect(day).toBe('Sunday'); + }); + + it('should return Monday', () => { + const day = datetimeUtility.getDayName(new Date('07/18/2016')); + expect(day).toBe('Monday'); + }); + + it('should return Tuesday', () => { + const day = datetimeUtility.getDayName(new Date('07/19/2016')); + expect(day).toBe('Tuesday'); + }); + + it('should return Wednesday', () => { + const day = datetimeUtility.getDayName(new Date('07/20/2016')); + expect(day).toBe('Wednesday'); + }); + + it('should return Thursday', () => { + const day = datetimeUtility.getDayName(new Date('07/21/2016')); + expect(day).toBe('Thursday'); + }); + + it('should return Friday', () => { + const day = datetimeUtility.getDayName(new Date('07/22/2016')); + expect(day).toBe('Friday'); + }); + + it('should return Saturday', () => { + const day = datetimeUtility.getDayName(new Date('07/23/2016')); + expect(day).toBe('Saturday'); }); }); - describe('dateInWords', () => { - const date = new Date('07/01/2016'); + describe('get day difference', () => { + it('should return 7', () => { + const firstDay = new Date('07/01/2016'); + const secondDay = new Date('07/08/2016'); + const difference = datetimeUtility.getDayDifference(firstDay, secondDay); + expect(difference).toBe(7); + }); - it('should return date in words', () => { - expect(datetimeUtility.dateInWords(date)).toEqual('July 1, 2016'); + it('should return 31', () => { + const firstDay = new Date('07/01/2016'); + const secondDay = new Date('08/01/2016'); + const difference = datetimeUtility.getDayDifference(firstDay, secondDay); + expect(difference).toBe(31); }); - it('should return abbreviated month name', () => { - expect(datetimeUtility.dateInWords(date, true)).toEqual('Jul 1, 2016'); + it('should return 365', () => { + const firstDay = new Date('07/02/2015'); + const secondDay = new Date('07/01/2016'); + const difference = datetimeUtility.getDayDifference(firstDay, secondDay); + expect(difference).toBe(365); }); }); -})(); +}); + +describe('timeIntervalInWords', () => { + it('should return string with number of minutes and seconds', () => { + expect(datetimeUtility.timeIntervalInWords(9.54)).toEqual('9 seconds'); + expect(datetimeUtility.timeIntervalInWords(1)).toEqual('1 second'); + expect(datetimeUtility.timeIntervalInWords(200)).toEqual('3 minutes 20 seconds'); + expect(datetimeUtility.timeIntervalInWords(6008)).toEqual('100 minutes 8 seconds'); + }); +}); + +describe('dateInWords', () => { + const date = new Date('07/01/2016'); + + it('should return date in words', () => { + expect(datetimeUtility.dateInWords(date)).toEqual('July 1, 2016'); + }); + + it('should return abbreviated month name', () => { + expect(datetimeUtility.dateInWords(date, true)).toEqual('Jul 1, 2016'); + }); +}); diff --git a/spec/javascripts/deploy_keys/components/key_spec.js b/spec/javascripts/deploy_keys/components/key_spec.js index 5b64cbb2dfc..2f28c5bbf01 100644 --- a/spec/javascripts/deploy_keys/components/key_spec.js +++ b/spec/javascripts/deploy_keys/components/key_spec.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import DeployKeysStore from '~/deploy_keys/store'; import key from '~/deploy_keys/components/key.vue'; +import { getTimeago } from '~/lib/utils/datetime_utility'; describe('Deploy keys key', () => { let vm; @@ -37,7 +38,7 @@ describe('Deploy keys key', () => { it('renders human friendly formatted created date', () => { expect( vm.$el.querySelector('.key-created-at').textContent.trim(), - ).toBe(`created ${gl.utils.getTimeago().format(deployKey.created_at)}`); + ).toBe(`created ${getTimeago().format(deployKey.created_at)}`); }); it('shows edit button', () => { diff --git a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js index 861f26e162f..6599839a526 100644 --- a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js +++ b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js @@ -1,8 +1,10 @@ /* eslint-disable quotes, jasmine/no-suite-dupes, vars-on-top, no-var */ - -import d3 from 'd3'; +import { scaleLinear, scaleTime } from 'd3-scale'; +import { timeParse } from 'd3-time-format'; import { ContributorsGraph, ContributorsMasterGraph } from '~/graphs/stat_graph_contributors_graph'; +const d3 = { scaleLinear, scaleTime, timeParse }; + describe("ContributorsGraph", function () { describe("#set_x_domain", function () { it("set the x_domain", function () { @@ -53,7 +55,7 @@ describe("ContributorsGraph", function () { it("sets the instance's x domain using the prototype's x_domain", function () { ContributorsGraph.prototype.x_domain = 20; var instance = new ContributorsGraph(); - instance.x = d3.time.scale().range([0, 100]).clamp(true); + instance.x = d3.scaleTime().range([0, 100]).clamp(true); spyOn(instance.x, 'domain'); instance.set_x_domain(); expect(instance.x.domain).toHaveBeenCalledWith(20); @@ -64,7 +66,7 @@ describe("ContributorsGraph", function () { it("sets the instance's y domain using the prototype's y_domain", function () { ContributorsGraph.prototype.y_domain = 30; var instance = new ContributorsGraph(); - instance.y = d3.scale.linear().range([100, 0]).nice(); + instance.y = d3.scaleLinear().range([100, 0]).nice(); spyOn(instance.y, 'domain'); instance.set_y_domain(); expect(instance.y.domain).toHaveBeenCalledWith(30); @@ -118,7 +120,7 @@ describe("ContributorsMasterGraph", function () { describe("#parse_dates", function () { it("parses the dates", function () { var graph = new ContributorsMasterGraph(); - var parseDate = d3.time.format("%Y-%m-%d").parse; + var parseDate = d3.timeParse("%Y-%m-%d"); var data = [{ date: "2013-01-01" }, { date: "2012-12-15" }]; var correct = [{ date: parseDate(data[0].date) }, { date: parseDate(data[1].date) }]; graph.parse_dates(data); diff --git a/spec/javascripts/graphs/stat_graph_contributors_spec.js b/spec/javascripts/graphs/stat_graph_contributors_spec.js new file mode 100644 index 00000000000..962423462e7 --- /dev/null +++ b/spec/javascripts/graphs/stat_graph_contributors_spec.js @@ -0,0 +1,26 @@ +import ContributorsStatGraph from '~/graphs/stat_graph_contributors'; +import { ContributorsGraph } from '~/graphs/stat_graph_contributors_graph'; + +import { setLanguage } from '../helpers/locale_helper'; + +describe('ContributorsStatGraph', () => { + describe('change_date_header', () => { + beforeAll(() => { + setLanguage('de'); + }); + + afterAll(() => { + setLanguage(null); + }); + + it('uses the locale to display date ranges', () => { + ContributorsGraph.init_x_domain([{ date: '2013-01-31' }, { date: '2012-01-31' }]); + setFixtures('<div id="date_header"></div>'); + const graph = new ContributorsStatGraph(); + + graph.change_date_header(); + + expect(document.getElementById('date_header').innerText).toBe('31. Januar 2012 – 31. Januar 2013'); + }); + }); +}); diff --git a/spec/javascripts/groups/components/item_actions_spec.js b/spec/javascripts/groups/components/item_actions_spec.js index 2ce1a749a96..7a5c1da4d1d 100644 --- a/spec/javascripts/groups/components/item_actions_spec.js +++ b/spec/javascripts/groups/components/item_actions_spec.js @@ -36,27 +36,27 @@ describe('ItemActionsComponent', () => { describe('methods', () => { describe('onLeaveGroup', () => { - it('should change `dialogStatus` prop to `true` which shows confirmation dialog', () => { - expect(vm.dialogStatus).toBeFalsy(); + it('should change `modalStatus` prop to `true` which shows confirmation dialog', () => { + expect(vm.modalStatus).toBeFalsy(); vm.onLeaveGroup(); - expect(vm.dialogStatus).toBeTruthy(); + expect(vm.modalStatus).toBeTruthy(); }); }); describe('leaveGroup', () => { - it('should change `dialogStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => { + it('should change `modalStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => { spyOn(eventHub, '$emit'); - vm.dialogStatus = true; + vm.modalStatus = true; vm.leaveGroup(true); - expect(vm.dialogStatus).toBeFalsy(); + expect(vm.modalStatus).toBeFalsy(); expect(eventHub.$emit).toHaveBeenCalledWith('leaveGroup', vm.group, vm.parentGroup); }); - it('should change `dialogStatus` prop to `false` and should NOT emit `leaveGroup` event when called with `leaveConfirmed` as `false`', () => { + it('should change `modalStatus` prop to `false` and should NOT emit `leaveGroup` event when called with `leaveConfirmed` as `false`', () => { spyOn(eventHub, '$emit'); - vm.dialogStatus = true; + vm.modalStatus = true; vm.leaveGroup(false); - expect(vm.dialogStatus).toBeFalsy(); + expect(vm.modalStatus).toBeFalsy(); expect(eventHub.$emit).not.toHaveBeenCalled(); }); }); @@ -99,9 +99,9 @@ describe('ItemActionsComponent', () => { newVm.$destroy(); }); - it('should show modal dialog when `dialogStatus` is set to `true`', () => { - vm.dialogStatus = true; - const modalDialogEl = vm.$el.querySelector('.modal.popup-dialog'); + it('should show modal dialog when `modalStatus` is set to `true`', () => { + vm.modalStatus = true; + const modalDialogEl = vm.$el.querySelector('.modal'); expect(modalDialogEl).toBeDefined(); expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?'); expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave'); diff --git a/spec/javascripts/helpers/locale_helper.js b/spec/javascripts/helpers/locale_helper.js new file mode 100644 index 00000000000..99e6ce61234 --- /dev/null +++ b/spec/javascripts/helpers/locale_helper.js @@ -0,0 +1,11 @@ +/* eslint-disable import/prefer-default-export */ + +export const setLanguage = (languageCode) => { + const htmlElement = document.querySelector('html'); + + if (languageCode) { + htmlElement.setAttribute('lang', languageCode); + } else { + htmlElement.removeAttribute('lang'); + } +}; diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index 729c3c29f22..7159148f8fa 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -272,10 +272,10 @@ describe('Issuable output', () => { }); }); - it('opens recaptcha dialog if update rejected as spam', (done) => { + it('opens recaptcha modal if update rejected as spam', (done) => { function mockScriptSrc() { const recaptchaChild = vm.$children - .find(child => child.$options._componentTag === 'recaptcha-dialog'); // eslint-disable-line no-underscore-dangle + .find(child => child.$options._componentTag === 'recaptcha-modal'); // eslint-disable-line no-underscore-dangle recaptchaChild.scriptSrc = '//scriptsrc'; } @@ -302,7 +302,7 @@ describe('Issuable output', () => { .then(promise) .then(() => setTimeoutPromise()) .then(() => { - modal = vm.$el.querySelector('.js-recaptcha-dialog'); + modal = vm.$el.querySelector('.js-recaptcha-modal'); expect(modal.style.display).not.toEqual('none'); expect(modal.querySelector('.g-recaptcha').textContent).toEqual('recaptcha_html'); diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js index 2e000a1063f..0da25bdca9c 100644 --- a/spec/javascripts/issue_show/components/description_spec.js +++ b/spec/javascripts/issue_show/components/description_spec.js @@ -54,7 +54,7 @@ describe('Description component', () => { it('opens recaptcha dialog if update rejected as spam', (done) => { let modal; const recaptchaChild = vm.$children - .find(child => child.$options._componentTag === 'recaptcha-dialog'); // eslint-disable-line no-underscore-dangle + .find(child => child.$options._componentTag === 'recaptcha-modal'); // eslint-disable-line no-underscore-dangle recaptchaChild.scriptSrc = '//scriptsrc'; @@ -64,7 +64,7 @@ describe('Description component', () => { vm.$nextTick() .then(() => { - modal = vm.$el.querySelector('.js-recaptcha-dialog'); + modal = vm.$el.querySelector('.js-recaptcha-modal'); expect(modal.style.display).not.toEqual('none'); expect(modal.querySelector('.g-recaptcha').textContent).toEqual('recaptcha_html'); diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index 3636aac79a0..2cd2e63b15d 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -55,7 +55,7 @@ describe('Issue', function() { } function findElements(isIssueInitiallyOpen) { - $boxClosed = $('div.status-box-closed'); + $boxClosed = $('div.status-box-issue-closed'); expect($boxClosed).toExist(); expect($boxClosed).toHaveText('Closed'); diff --git a/spec/javascripts/lib/utils/tick_formats_spec.js b/spec/javascripts/lib/utils/tick_formats_spec.js new file mode 100644 index 00000000000..283989b4fc8 --- /dev/null +++ b/spec/javascripts/lib/utils/tick_formats_spec.js @@ -0,0 +1,40 @@ +import { dateTickFormat, initDateFormats } from '~/lib/utils/tick_formats'; + +import { setLanguage } from '../../helpers/locale_helper'; + +describe('tick formats', () => { + describe('dateTickFormat', () => { + beforeAll(() => { + setLanguage('de'); + initDateFormats(); + }); + + afterAll(() => { + setLanguage(null); + }); + + it('returns year for first of January', () => { + const tick = dateTickFormat(new Date('2001-01-01')); + + expect(tick).toBe('2001'); + }); + + it('returns month for first of February', () => { + const tick = dateTickFormat(new Date('2001-02-01')); + + expect(tick).toBe('Februar'); + }); + + it('returns day and month for second of February', () => { + const tick = dateTickFormat(new Date('2001-02-02')); + + expect(tick).toBe('2. Feb.'); + }); + + it('ignores time', () => { + const tick = dateTickFormat(new Date('2001-02-02 12:34:56')); + + expect(tick).toBe('2. Feb.'); + }); + }); +}); diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js index 645664a5219..89f4b85541d 100644 --- a/spec/javascripts/line_highlighter_spec.js +++ b/spec/javascripts/line_highlighter_spec.js @@ -1,7 +1,6 @@ /* eslint-disable space-before-function-paren, no-var, no-param-reassign, quotes, prefer-template, no-else-return, new-cap, dot-notation, no-return-assign, comma-dangle, no-new, one-var, one-var-declaration-per-line, jasmine/no-spec-dupes, no-underscore-dangle, max-len */ -/* global LineHighlighter */ -import '~/line_highlighter'; +import LineHighlighter from '~/line_highlighter'; (function() { describe('LineHighlighter', function() { diff --git a/spec/javascripts/locale/index_spec.js b/spec/javascripts/locale/index_spec.js new file mode 100644 index 00000000000..29b0b21eed7 --- /dev/null +++ b/spec/javascripts/locale/index_spec.js @@ -0,0 +1,35 @@ +import { createDateTimeFormat, languageCode } from '~/locale'; + +import { setLanguage } from '../helpers/locale_helper'; + +describe('locale', () => { + afterEach(() => { + setLanguage(null); + }); + + describe('languageCode', () => { + it('parses the lang attribute', () => { + setLanguage('ja'); + + expect(languageCode()).toBe('ja'); + }); + + it('falls back to English', () => { + setLanguage(null); + + expect(languageCode()).toBe('en'); + }); + }); + + describe('createDateTimeFormat', () => { + beforeEach(() => { + setLanguage('de'); + }); + + it('creates an instance of Intl.DateTimeFormat', () => { + const dateFormat = createDateTimeFormat({ year: 'numeric', month: 'long', day: 'numeric' }); + + expect(dateFormat.format(new Date(2015, 6, 3))).toBe('3. Juli 2015'); + }); + }); +}); diff --git a/spec/javascripts/merge_request_notes_spec.js b/spec/javascripts/merge_request_notes_spec.js index 6054b75d0b8..e983e4de3fc 100644 --- a/spec/javascripts/merge_request_notes_spec.js +++ b/spec/javascripts/merge_request_notes_spec.js @@ -1,11 +1,9 @@ -/* global Notes */ - import 'autosize'; import '~/gl_form'; import '~/lib/utils/text_utility'; import '~/render_gfm'; import '~/render_math'; -import '~/notes'; +import Notes from '~/notes'; const upArrowKeyCode = 38; diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js index 70ae63ba036..2f02c11482f 100644 --- a/spec/javascripts/merge_request_spec.js +++ b/spec/javascripts/merge_request_spec.js @@ -1,7 +1,6 @@ /* eslint-disable space-before-function-paren, no-return-assign */ -/* global MergeRequest */ -import '~/merge_request'; +import MergeRequest from '~/merge_request'; import CloseReopenReportToggle from '~/close_reopen_report_toggle'; import IssuablesHelper from '~/helpers/issuables_helper'; diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index 5076435e7a8..a6be474805b 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -1,13 +1,12 @@ /* eslint-disable no-var, comma-dangle, object-shorthand */ -/* global Notes */ import * as urlUtils from '~/lib/utils/url_utility'; -import '~/merge_request_tabs'; +import MergeRequestTabs from '~/merge_request_tabs'; import '~/commit/pipelines/pipelines_bundle'; import '~/breakpoints'; import '~/lib/utils/common_utils'; import Diff from '~/diff'; -import '~/notes'; +import Notes from '~/notes'; import 'vendor/jquery.scrollTo'; (function () { @@ -32,7 +31,7 @@ import 'vendor/jquery.scrollTo'; ); beforeEach(function () { - this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation }); + this.class = new MergeRequestTabs({ stubLocation: stubLocation }); setLocation(); this.spies = { @@ -279,8 +278,8 @@ import 'vendor/jquery.scrollTo'; loadFixtures('merge_requests/diff_comment.html.raw'); $('body').attr('data-page', 'projects:merge_requests:show'); window.gl.ImageFile = () => {}; - window.notes = new Notes('', []); - spyOn(window.notes, 'toggleDiffNote').and.callThrough(); + Notes.initialize('', []); + spyOn(Notes.instance, 'toggleDiffNote').and.callThrough(); }); afterEach(() => { @@ -291,15 +290,18 @@ import 'vendor/jquery.scrollTo'; $('body').removeAttr('data-page'); }); - it('requires an absolute pathname', function () { - spyOn($, 'ajax').and.callFake(function (options) { - expect(options.url).toEqual('/foo/bar/merge_requests/1/diffs.json'); + it('triggers Ajax request to JSON endpoint', function (done) { + const url = '/foo/bar/merge_requests/1/diffs'; + spyOn(this.class, 'ajaxGet').and.callFake((options) => { + expect(options.url).toEqual(`${url}.json`); + done(); }); - this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); + this.class.loadDiff(url); }); - it('triggers scroll event when diff already loaded', function () { + it('triggers scroll event when diff already loaded', function (done) { + spyOn(this.class, 'ajaxGet').and.callFake(() => done.fail()); spyOn(document, 'dispatchEvent'); this.class.diffsLoaded = true; @@ -308,6 +310,7 @@ import 'vendor/jquery.scrollTo'; expect( document.dispatchEvent, ).toHaveBeenCalledWith(new CustomEvent('scroll')); + done(); }); describe('with inline diff', () => { @@ -338,7 +341,7 @@ import 'vendor/jquery.scrollTo'; this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); expect(noteId.length).toBeGreaterThan(0); - expect(window.notes.toggleDiffNote).toHaveBeenCalledWith({ + expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({ target: jasmine.any(Object), lineType: 'old', forceShow: true, @@ -349,7 +352,7 @@ import 'vendor/jquery.scrollTo'; spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist'); this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); - expect(window.notes.toggleDiffNote).not.toHaveBeenCalled(); + expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled(); }); }); @@ -359,7 +362,7 @@ import 'vendor/jquery.scrollTo'; this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); expect(noteLineNumId.length).toBeGreaterThan(0); - expect(window.notes.toggleDiffNote).not.toHaveBeenCalled(); + expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled(); }); }); }); @@ -393,7 +396,7 @@ import 'vendor/jquery.scrollTo'; this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); expect(noteId.length).toBeGreaterThan(0); - expect(window.notes.toggleDiffNote).toHaveBeenCalledWith({ + expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({ target: jasmine.any(Object), lineType: 'new', forceShow: true, @@ -404,7 +407,7 @@ import 'vendor/jquery.scrollTo'; spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist'); this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); - expect(window.notes.toggleDiffNote).not.toHaveBeenCalled(); + expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled(); }); }); @@ -414,7 +417,7 @@ import 'vendor/jquery.scrollTo'; this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); expect(noteLineNumId.length).toBeGreaterThan(0); - expect(window.notes.toggleDiffNote).not.toHaveBeenCalled(); + expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled(); }); }); }); diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 2612c5fd7bc..167f074fb9b 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -1,12 +1,10 @@ /* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, max-len */ -/* global Notes */ - import * as urlUtils from '~/lib/utils/url_utility'; import 'autosize'; import '~/gl_form'; import '~/lib/utils/text_utility'; import '~/render_gfm'; -import '~/notes'; +import Notes from '~/notes'; (function() { window.gon || (window.gon = {}); @@ -222,7 +220,6 @@ import '~/notes'; notes.note_ids = []; notes.updatedNotesTrackingMap = {}; - spyOn(gl.utils, 'localTimeAgo'); spyOn(Notes, 'isNewNote').and.callThrough(); spyOn(Notes, 'isUpdatedNote').and.callThrough(); spyOn(Notes, 'animateAppendNote').and.callThrough(); @@ -349,7 +346,6 @@ import '~/notes'; ]); notes.note_ids = []; - spyOn(gl.utils, 'localTimeAgo'); spyOn(Notes, 'isNewNote'); spyOn(Notes, 'animateAppendNote'); Notes.isNewNote.and.returnValue(true); diff --git a/spec/javascripts/pager_spec.js b/spec/javascripts/pager_spec.js index fe3ea996eac..2fd87754238 100644 --- a/spec/javascripts/pager_spec.js +++ b/spec/javascripts/pager_spec.js @@ -1,15 +1,9 @@ /* global fixture */ import * as utils from '~/lib/utils/url_utility'; -import '~/pager'; +import Pager from '~/pager'; describe('pager', () => { - const Pager = window.Pager; - - it('is defined on window', () => { - expect(window.Pager).toBeDefined(); - }); - describe('init', () => { const originalHref = window.location.href; diff --git a/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js index f750061a6a1..c4d3866c922 100644 --- a/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js +++ b/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import listCollapsed from '~/repo/components/commit_sidebar/list_collapsed.vue'; +import store from '~/ide/stores'; +import listCollapsed from '~/ide/components/commit_sidebar/list_collapsed.vue'; import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; import { file } from '../../helpers'; diff --git a/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js index 18c9b46fcd9..fc7c9ae9dd7 100644 --- a/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js +++ b/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import listItem from '~/repo/components/commit_sidebar/list_item.vue'; +import listItem from '~/ide/components/commit_sidebar/list_item.vue'; import mountComponent from '../../../helpers/vue_mount_component_helper'; import { file } from '../../helpers'; diff --git a/spec/javascripts/repo/components/commit_sidebar/list_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_spec.js index df7e3c5de21..cb5240ad118 100644 --- a/spec/javascripts/repo/components/commit_sidebar/list_spec.js +++ b/spec/javascripts/repo/components/commit_sidebar/list_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import commitSidebarList from '~/repo/components/commit_sidebar/list.vue'; +import store from '~/ide/stores'; +import commitSidebarList from '~/ide/components/commit_sidebar/list.vue'; import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; import { file } from '../../helpers'; @@ -13,8 +13,11 @@ describe('Multi-file editor commit sidebar list', () => { vm = createComponentWithStore(Component, store, { title: 'Staged', fileList: [], - collapsed: false, - }).$mount(); + }); + + vm.$store.state.rightPanelCollapsed = false; + + vm.$mount(); }); afterEach(() => { @@ -43,30 +46,14 @@ describe('Multi-file editor commit sidebar list', () => { describe('collapsed', () => { beforeEach((done) => { - vm.collapsed = true; + vm.$store.state.rightPanelCollapsed = true; Vue.nextTick(done); }); - it('adds collapsed class', () => { - expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull(); - }); - it('hides list', () => { expect(vm.$el.querySelector('.list-unstyled')).toBeNull(); expect(vm.$el.querySelector('.help-block')).toBeNull(); }); - - it('hides collapse button', () => { - expect(vm.$el.querySelector('.multi-file-commit-panel-collapse-btn')).toBeNull(); - }); - }); - - it('clicking toggle collapse button emits toggle event', () => { - spyOn(vm, '$emit'); - - vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click(); - - expect(vm.$emit).toHaveBeenCalledWith('toggleCollapsed'); }); }); diff --git a/spec/javascripts/repo/components/ide_context_bar_spec.js b/spec/javascripts/repo/components/ide_context_bar_spec.js new file mode 100644 index 00000000000..3f8f37d2343 --- /dev/null +++ b/spec/javascripts/repo/components/ide_context_bar_spec.js @@ -0,0 +1,49 @@ +import Vue from 'vue'; +import store from '~/ide/stores'; +import ideContextBar from '~/ide/components/ide_context_bar.vue'; +import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; + +describe('Multi-file editor right context bar', () => { + let vm; + + beforeEach(() => { + const Component = Vue.extend(ideContextBar); + + vm = createComponentWithStore(Component, store); + + vm.$store.state.rightPanelCollapsed = false; + + vm.$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('collapsed', () => { + beforeEach((done) => { + vm.$store.state.rightPanelCollapsed = true; + + Vue.nextTick(done); + }); + + it('adds collapsed class', () => { + expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull(); + }); + + it('shows correct icon', () => { + expect(vm.currentIcon).toBe('angle-double-left'); + }); + }); + + it('clicking toggle collapse button collapses the bar', () => { + spyOn(vm, 'setPanelCollapsedStatus').and.returnValue(Promise.resolve()); + + vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click(); + + expect(vm.setPanelCollapsedStatus).toHaveBeenCalledWith({ + side: 'right', + collapsed: true, + }); + }); +}); diff --git a/spec/javascripts/repo/components/repo_sidebar_spec.js b/spec/javascripts/repo/components/ide_repo_tree_spec.js index df7cf8aabbb..b6f70f585cd 100644 --- a/spec/javascripts/repo/components/repo_sidebar_spec.js +++ b/spec/javascripts/repo/components/ide_repo_tree_spec.js @@ -1,20 +1,26 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import repoSidebar from '~/repo/components/repo_sidebar.vue'; +import store from '~/ide/stores'; +import ideRepoTree from '~/ide/components/ide_repo_tree.vue'; import { file, resetStore } from '../helpers'; -describe('RepoSidebar', () => { +describe('IdeRepoTree', () => { let vm; beforeEach(() => { - const RepoSidebar = Vue.extend(repoSidebar); + const IdeRepoTree = Vue.extend(ideRepoTree); - vm = new RepoSidebar({ + vm = new IdeRepoTree({ store, + propsData: { + treeId: 'abcproject/mybranch', + }, }); + vm.$store.state.currentBranch = 'master'; vm.$store.state.isRoot = true; - vm.$store.state.tree.push(file()); + vm.$store.state.trees['abcproject/mybranch'] = { + tree: [file()], + }; vm.$mount(); }); @@ -26,13 +32,9 @@ describe('RepoSidebar', () => { }); it('renders a sidebar', () => { - const thead = vm.$el.querySelector('thead'); const tbody = vm.$el.querySelector('tbody'); expect(vm.$el.classList.contains('sidebar-mini')).toBeFalsy(); - expect(thead.querySelector('.name').textContent.trim()).toEqual('Name'); - expect(thead.querySelector('.last-commit').textContent.trim()).toEqual('Last commit'); - expect(thead.querySelector('.last-update').textContent.trim()).toEqual('Last update'); expect(tbody.querySelector('.repo-file-options')).toBeFalsy(); expect(tbody.querySelector('.prev-directory')).toBeFalsy(); expect(tbody.querySelector('.loading-file')).toBeFalsy(); @@ -40,7 +42,6 @@ describe('RepoSidebar', () => { }); it('renders 5 loading files if tree is loading', (done) => { - vm.$store.state.tree = []; vm.$store.state.loading = true; Vue.nextTick(() => { diff --git a/spec/javascripts/repo/components/ide_side_bar_spec.js b/spec/javascripts/repo/components/ide_side_bar_spec.js new file mode 100644 index 00000000000..30e45169205 --- /dev/null +++ b/spec/javascripts/repo/components/ide_side_bar_spec.js @@ -0,0 +1,43 @@ +import Vue from 'vue'; +import store from '~/ide/stores'; +import ideSidebar from '~/ide/components/ide_side_bar.vue'; +import { resetStore } from '../helpers'; +import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; + +describe('IdeSidebar', () => { + let vm; + + beforeEach(() => { + const Component = Vue.extend(ideSidebar); + + vm = createComponentWithStore(Component, store).$mount(); + + vm.$store.state.leftPanelCollapsed = false; + }); + + afterEach(() => { + vm.$destroy(); + + resetStore(vm.$store); + }); + + it('renders a sidebar', () => { + expect(vm.$el.querySelector('.multi-file-commit-panel-inner')).not.toBeNull(); + }); + + describe('collapsed', () => { + beforeEach((done) => { + vm.$store.state.leftPanelCollapsed = true; + + Vue.nextTick(done); + }); + + it('adds collapsed class', () => { + expect(vm.$el.classList).toContain('is-collapsed'); + }); + + it('shows correct icon', () => { + expect(vm.currentIcon).toBe('angle-double-right'); + }); + }); +}); diff --git a/spec/javascripts/repo/components/repo_spec.js b/spec/javascripts/repo/components/ide_spec.js index b32d2c13af8..20b8dc25dcb 100644 --- a/spec/javascripts/repo/components/repo_spec.js +++ b/spec/javascripts/repo/components/ide_spec.js @@ -1,14 +1,14 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import repo from '~/repo/components/repo.vue'; +import store from '~/ide/stores'; +import ide from '~/ide/components/ide.vue'; import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; import { file, resetStore } from '../helpers'; -describe('repo component', () => { +describe('ide component', () => { let vm; beforeEach(() => { - const Component = Vue.extend(repo); + const Component = Vue.extend(ide); vm = createComponentWithStore(Component, store).$mount(); }); @@ -24,7 +24,9 @@ describe('repo component', () => { }); it('renders panel right when files are open', (done) => { - vm.$store.state.tree.push(file()); + vm.$store.state.trees['abcproject/mybranch'] = { + tree: [file()], + }; Vue.nextTick(() => { expect(vm.$el.querySelector('.panel-right')).toBeNull(); diff --git a/spec/javascripts/repo/components/new_branch_form_spec.js b/spec/javascripts/repo/components/new_branch_form_spec.js index 9a705a1f0ed..cd1d073ec18 100644 --- a/spec/javascripts/repo/components/new_branch_form_spec.js +++ b/spec/javascripts/repo/components/new_branch_form_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import newBranchForm from '~/repo/components/new_branch_form.vue'; +import store from '~/ide/stores'; +import newBranchForm from '~/ide/components/new_branch_form.vue'; import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; import { resetStore } from '../helpers'; diff --git a/spec/javascripts/repo/components/new_dropdown/index_spec.js b/spec/javascripts/repo/components/new_dropdown/index_spec.js index 93b10fc1fee..b001c1655b4 100644 --- a/spec/javascripts/repo/components/new_dropdown/index_spec.js +++ b/spec/javascripts/repo/components/new_dropdown/index_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import newDropdown from '~/repo/components/new_dropdown/index.vue'; +import store from '~/ide/stores'; +import newDropdown from '~/ide/components/new_dropdown/index.vue'; import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; import { resetStore } from '../../helpers'; @@ -10,8 +10,12 @@ describe('new dropdown component', () => { beforeEach(() => { const component = Vue.extend(newDropdown); - vm = createComponentWithStore(component, store); + vm = createComponentWithStore(component, store, { + branch: 'master', + path: '', + }); + vm.$store.state.currentProjectId = 'abcproject'; vm.$store.state.path = ''; vm.$mount(); @@ -23,9 +27,10 @@ describe('new dropdown component', () => { resetStore(vm.$store); }); - it('renders new file and new directory links', () => { + it('renders new file, upload and new directory links', () => { expect(vm.$el.querySelectorAll('a')[0].textContent.trim()).toBe('New file'); - expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe('New directory'); + expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe('Upload file'); + expect(vm.$el.querySelectorAll('a')[2].textContent.trim()).toBe('New directory'); }); describe('createNewItem', () => { @@ -36,7 +41,7 @@ describe('new dropdown component', () => { }); it('sets modalType to tree when new directory is clicked', () => { - vm.$el.querySelectorAll('a')[1].click(); + vm.$el.querySelectorAll('a')[2].click(); expect(vm.modalType).toBe('tree'); }); diff --git a/spec/javascripts/repo/components/new_dropdown/modal_spec.js b/spec/javascripts/repo/components/new_dropdown/modal_spec.js index 1ff7590ec79..233cca06ed0 100644 --- a/spec/javascripts/repo/components/new_dropdown/modal_spec.js +++ b/spec/javascripts/repo/components/new_dropdown/modal_spec.js @@ -1,12 +1,42 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import modal from '~/repo/components/new_dropdown/modal.vue'; +import store from '~/ide/stores'; +import service from '~/ide/services'; +import modal from '~/ide/components/new_dropdown/modal.vue'; import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; import { file, resetStore } from '../../helpers'; describe('new file modal component', () => { const Component = Vue.extend(modal); let vm; + let projectTree; + + beforeEach(() => { + spyOn(service, 'getProjectData').and.returnValue(Promise.resolve({ + data: { + id: '123', + }, + })); + + spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({ + commit: { + id: '123branch', + }, + })); + + spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({ + headers: { + 'page-title': 'test', + }, + json: () => Promise.resolve({ + last_commit_path: 'last_commit_path', + parent_tree_url: 'parent_tree_url', + path: '/', + trees: [{ name: 'tree' }], + blobs: [{ name: 'blob' }], + submodules: [{ name: 'submodule' }], + }), + })); + }); afterEach(() => { vm.$destroy(); @@ -17,12 +47,26 @@ describe('new file modal component', () => { ['tree', 'blob'].forEach((type) => { describe(type, () => { beforeEach(() => { + store.state.projects.abcproject = { + web_url: '', + }; + store.state.trees = []; + store.state.trees['abcproject/mybranch'] = { + tree: [], + }; + projectTree = store.state.trees['abcproject/mybranch']; + store.state.currentProjectId = 'abcproject'; + vm = createComponentWithStore(Component, store, { type, + branchId: 'master', path: '', - }).$mount(); + parent: projectTree, + }); vm.entryName = 'testing'; + + vm.$mount(); }); it(`sets modal title as ${type}`, () => { @@ -50,6 +94,9 @@ describe('new file modal component', () => { vm.createEntryInStore(); expect(vm.createTempEntry).toHaveBeenCalledWith({ + projectId: 'abcproject', + branchId: 'master', + parent: projectTree, name: 'testing', type, }); @@ -76,31 +123,18 @@ describe('new file modal component', () => { }); it('opens newly created file', (done) => { - vm.createEntryInStore(); - - setTimeout(() => { - expect(vm.$store.state.openFiles.length).toBe(1); - expect(vm.$store.state.openFiles[0].name).toBe(type === 'blob' ? 'testing' : '.gitkeep'); - - done(); - }); - }); - - it(`creates ${type} in the current stores path`, (done) => { - vm.$store.state.path = 'app'; - - vm.createEntryInStore(); - - setTimeout(() => { - expect(vm.$store.state.tree[0].path).toBe('app/testing'); - expect(vm.$store.state.tree[0].name).toBe('testing'); + if (type === 'blob') { + vm.createEntryInStore(); - if (type === 'tree') { - expect(vm.$store.state.tree[0].tree.length).toBe(1); - } + setTimeout(() => { + expect(vm.$store.state.openFiles.length).toBe(1); + expect(vm.$store.state.openFiles[0].name).toBe(type === 'blob' ? 'testing' : '.gitkeep'); + done(); + }); + } else { done(); - }); + } }); if (type === 'blob') { @@ -108,25 +142,27 @@ describe('new file modal component', () => { vm.createEntryInStore(); setTimeout(() => { - expect(vm.$store.state.tree.length).toBe(1); - expect(vm.$store.state.tree[0].name).toBe('testing'); - expect(vm.$store.state.tree[0].type).toBe('blob'); - expect(vm.$store.state.tree[0].tempFile).toBeTruthy(); + const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; + expect(baseTree.length).toBe(1); + expect(baseTree[0].name).toBe('testing'); + expect(baseTree[0].type).toBe('blob'); + expect(baseTree[0].tempFile).toBeTruthy(); done(); }); }); it('does not create temp file when file already exists', (done) => { - vm.$store.state.tree.push(file('testing', '1', type)); + const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; + baseTree.push(file('testing', '1', type)); vm.createEntryInStore(); setTimeout(() => { - expect(vm.$store.state.tree.length).toBe(1); - expect(vm.$store.state.tree[0].name).toBe('testing'); - expect(vm.$store.state.tree[0].type).toBe('blob'); - expect(vm.$store.state.tree[0].tempFile).toBeFalsy(); + expect(baseTree.length).toBe(1); + expect(baseTree[0].name).toBe('testing'); + expect(baseTree[0].type).toBe('blob'); + expect(baseTree[0].tempFile).toBeFalsy(); done(); }); @@ -135,48 +171,47 @@ describe('new file modal component', () => { it('creates new tree', () => { vm.createEntryInStore(); - expect(vm.$store.state.tree.length).toBe(1); - expect(vm.$store.state.tree[0].name).toBe('testing'); - expect(vm.$store.state.tree[0].type).toBe('tree'); - expect(vm.$store.state.tree[0].tempFile).toBeTruthy(); - expect(vm.$store.state.tree[0].tree.length).toBe(1); - expect(vm.$store.state.tree[0].tree[0].name).toBe('.gitkeep'); + const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; + expect(baseTree.length).toBe(1); + expect(baseTree[0].name).toBe('testing'); + expect(baseTree[0].type).toBe('tree'); + expect(baseTree[0].tempFile).toBeTruthy(); }); it('creates multiple trees when entryName has slashes', () => { vm.entryName = 'app/test'; vm.createEntryInStore(); - expect(vm.$store.state.tree.length).toBe(1); - expect(vm.$store.state.tree[0].name).toBe('app'); - expect(vm.$store.state.tree[0].tree[0].name).toBe('test'); - expect(vm.$store.state.tree[0].tree[0].tree[0].name).toBe('.gitkeep'); + const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; + expect(baseTree.length).toBe(1); + expect(baseTree[0].name).toBe('app'); }); it('creates tree in existing tree', () => { - vm.$store.state.tree.push(file('app', '1', 'tree')); + const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; + baseTree.push(file('app', '1', 'tree')); vm.entryName = 'app/test'; vm.createEntryInStore(); - expect(vm.$store.state.tree.length).toBe(1); - expect(vm.$store.state.tree[0].name).toBe('app'); - expect(vm.$store.state.tree[0].tempFile).toBeFalsy(); - expect(vm.$store.state.tree[0].tree[0].tempFile).toBeTruthy(); - expect(vm.$store.state.tree[0].tree[0].name).toBe('test'); - expect(vm.$store.state.tree[0].tree[0].tree[0].name).toBe('.gitkeep'); + expect(baseTree.length).toBe(1); + expect(baseTree[0].name).toBe('app'); + expect(baseTree[0].tempFile).toBeFalsy(); + expect(baseTree[0].tree[0].tempFile).toBeTruthy(); + expect(baseTree[0].tree[0].name).toBe('test'); }); it('does not create new tree when already exists', () => { - vm.$store.state.tree.push(file('app', '1', 'tree')); + const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; + baseTree.push(file('app', '1', 'tree')); vm.entryName = 'app'; vm.createEntryInStore(); - expect(vm.$store.state.tree.length).toBe(1); - expect(vm.$store.state.tree[0].name).toBe('app'); - expect(vm.$store.state.tree[0].tempFile).toBeFalsy(); - expect(vm.$store.state.tree[0].tree.length).toBe(0); + expect(baseTree.length).toBe(1); + expect(baseTree[0].name).toBe('app'); + expect(baseTree[0].tempFile).toBeFalsy(); + expect(baseTree[0].tree.length).toBe(0); }); } }); @@ -188,6 +223,8 @@ describe('new file modal component', () => { vm = createComponentWithStore(Component, store, { type: 'tree', + projectId: 'abcproject', + branchId: 'master', path: '', }).$mount('.js-test'); diff --git a/spec/javascripts/repo/components/new_dropdown/upload_spec.js b/spec/javascripts/repo/components/new_dropdown/upload_spec.js index bf7893029b1..788c08e5279 100644 --- a/spec/javascripts/repo/components/new_dropdown/upload_spec.js +++ b/spec/javascripts/repo/components/new_dropdown/upload_spec.js @@ -1,19 +1,61 @@ import Vue from 'vue'; -import upload from '~/repo/components/new_dropdown/upload.vue'; -import store from '~/repo/stores'; +import upload from '~/ide/components/new_dropdown/upload.vue'; +import store from '~/ide/stores'; +import service from '~/ide/services'; import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; import { resetStore } from '../../helpers'; describe('new dropdown upload', () => { let vm; + let projectTree; beforeEach(() => { + spyOn(service, 'getProjectData').and.returnValue(Promise.resolve({ + data: { + id: '123', + }, + })); + + spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({ + commit: { + id: '123branch', + }, + })); + + spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({ + headers: { + 'page-title': 'test', + }, + json: () => Promise.resolve({ + last_commit_path: 'last_commit_path', + parent_tree_url: 'parent_tree_url', + path: '/', + trees: [{ name: 'tree' }], + blobs: [{ name: 'blob' }], + submodules: [{ name: 'submodule' }], + }), + })); + const Component = Vue.extend(upload); + store.state.projects.abcproject = { + web_url: '', + }; + store.state.currentProjectId = 'abcproject'; + store.state.trees = []; + store.state.trees['abcproject/mybranch'] = { + tree: [], + }; + projectTree = store.state.trees['abcproject/mybranch']; + vm = createComponentWithStore(Component, store, { + branchId: 'master', path: '', + parent: projectTree, }); + vm.entryName = 'testing'; + vm.$mount(); }); @@ -65,23 +107,33 @@ describe('new dropdown upload', () => { vm.createFile(target, file, true); vm.$nextTick(() => { - expect(vm.$store.state.tree.length).toBe(1); - expect(vm.$store.state.tree[0].name).toBe(file.name); - expect(vm.$store.state.tree[0].content).toBe(target.result); + const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; + expect(baseTree.length).toBe(1); + expect(baseTree[0].name).toBe(file.name); + expect(baseTree[0].content).toBe(target.result); done(); }); }); it('creates new file in path', (done) => { - vm.$store.state.path = 'testing'; + const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; + const tree = { + type: 'tree', + name: 'testing', + path: 'testing', + tree: [], + }; + baseTree.push(tree); + + vm.parent = tree; vm.createFile(target, file, true); vm.$nextTick(() => { - expect(vm.$store.state.tree.length).toBe(1); - expect(vm.$store.state.tree[0].name).toBe(file.name); - expect(vm.$store.state.tree[0].content).toBe(target.result); - expect(vm.$store.state.tree[0].path).toBe(`testing/${file.name}`); + expect(baseTree.length).toBe(1); + expect(baseTree[0].tree[0].name).toBe(file.name); + expect(baseTree[0].tree[0].content).toBe(target.result); + expect(baseTree[0].tree[0].path).toBe(`testing/${file.name}`); done(); }); @@ -91,10 +143,11 @@ describe('new dropdown upload', () => { vm.createFile(binaryTarget, file, false); vm.$nextTick(() => { - expect(vm.$store.state.tree.length).toBe(1); - expect(vm.$store.state.tree[0].name).toBe(file.name); - expect(vm.$store.state.tree[0].content).toBe(binaryTarget.result.split('base64,')[1]); - expect(vm.$store.state.tree[0].base64).toBe(true); + const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; + expect(baseTree.length).toBe(1); + expect(baseTree[0].name).toBe(file.name); + expect(baseTree[0].content).toBe(binaryTarget.result.split('base64,')[1]); + expect(baseTree[0].base64).toBe(true); done(); }); diff --git a/spec/javascripts/repo/components/repo_commit_section_spec.js b/spec/javascripts/repo/components/repo_commit_section_spec.js index 72712e058e5..cd93fb3ccbf 100644 --- a/spec/javascripts/repo/components/repo_commit_section_spec.js +++ b/spec/javascripts/repo/components/repo_commit_section_spec.js @@ -1,8 +1,8 @@ import Vue from 'vue'; import * as urlUtils from '~/lib/utils/url_utility'; -import store from '~/repo/stores'; -import service from '~/repo/services'; -import repoCommitSection from '~/repo/components/repo_commit_section.vue'; +import store from '~/ide/stores'; +import service from '~/ide/services'; +import repoCommitSection from '~/ide/components/repo_commit_section.vue'; import getSetTimeoutPromise from '../../helpers/set_timeout_promise_helper'; import { file, resetStore } from '../helpers'; @@ -16,6 +16,18 @@ describe('RepoCommitSection', () => { store, }).$mount(); + comp.$store.state.currentProjectId = 'abcproject'; + comp.$store.state.currentBranchId = 'master'; + comp.$store.state.projects.abcproject = { + web_url: '', + branches: { + master: { + workingReference: '1', + }, + }, + }; + + comp.$store.state.rightPanelCollapsed = false; comp.$store.state.currentBranch = 'master'; comp.$store.state.openFiles = [file(), file()]; comp.$store.state.openFiles.forEach(f => Object.assign(f, { @@ -29,7 +41,19 @@ describe('RepoCommitSection', () => { beforeEach((done) => { vm = createComponent(); - vm.collapsed = false; + spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({ + headers: { + 'page-title': 'test', + }, + json: () => Promise.resolve({ + last_commit_path: 'last_commit_path', + parent_tree_url: 'parent_tree_url', + path: '/', + trees: [{ name: 'tree' }], + blobs: [{ name: 'blob' }], + submodules: [{ name: 'submodule' }], + }), + })); Vue.nextTick(done); }); @@ -45,7 +69,6 @@ describe('RepoCommitSection', () => { const submitCommit = vm.$el.querySelector('form .btn'); expect(vm.$el.querySelector('.multi-file-commit-form')).not.toBeNull(); - expect(vm.$el.querySelector('.multi-file-commit-panel-section header').textContent.trim()).toEqual('Staged'); expect(changedFileElements.length).toEqual(2); changedFileElements.forEach((changedFile, i) => { diff --git a/spec/javascripts/repo/components/repo_edit_button_spec.js b/spec/javascripts/repo/components/repo_edit_button_spec.js index 44018464b35..2895b794506 100644 --- a/spec/javascripts/repo/components/repo_edit_button_spec.js +++ b/spec/javascripts/repo/components/repo_edit_button_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import repoEditButton from '~/repo/components/repo_edit_button.vue'; +import store from '~/ide/stores'; +import repoEditButton from '~/ide/components/repo_edit_button.vue'; import { file, resetStore } from '../helpers'; describe('RepoEditButton', () => { @@ -32,7 +32,7 @@ describe('RepoEditButton', () => { vm.$mount(); expect(vm.$el.querySelector('.btn')).not.toBeNull(); - expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Edit'); + expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Cancel edit'); }); it('renders edit button with cancel text', () => { @@ -50,7 +50,7 @@ describe('RepoEditButton', () => { vm.$el.querySelector('.btn').click(); vm.$nextTick(() => { - expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Cancel edit'); + expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Edit'); done(); }); diff --git a/spec/javascripts/repo/components/repo_editor_spec.js b/spec/javascripts/repo/components/repo_editor_spec.js index 81158cad639..e7b2ed08acd 100644 --- a/spec/javascripts/repo/components/repo_editor_spec.js +++ b/spec/javascripts/repo/components/repo_editor_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import repoEditor from '~/repo/components/repo_editor.vue'; -import monacoLoader from '~/repo/monaco_loader'; +import store from '~/ide/stores'; +import repoEditor from '~/ide/components/repo_editor.vue'; +import monacoLoader from '~/ide/monaco_loader'; import { file, resetStore } from '../helpers'; describe('RepoEditor', () => { diff --git a/spec/javascripts/repo/components/repo_file_buttons_spec.js b/spec/javascripts/repo/components/repo_file_buttons_spec.js index d6e255e4810..115569a9117 100644 --- a/spec/javascripts/repo/components/repo_file_buttons_spec.js +++ b/spec/javascripts/repo/components/repo_file_buttons_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import repoFileButtons from '~/repo/components/repo_file_buttons.vue'; +import store from '~/ide/stores'; +import repoFileButtons from '~/ide/components/repo_file_buttons.vue'; import { file, resetStore } from '../helpers'; describe('RepoFileButtons', () => { diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js index bf9181fb09c..e8b370f97b4 100644 --- a/spec/javascripts/repo/components/repo_file_spec.js +++ b/spec/javascripts/repo/components/repo_file_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import repoFile from '~/repo/components/repo_file.vue'; +import store from '~/ide/stores'; +import repoFile from '~/ide/components/repo_file.vue'; import { file, resetStore } from '../helpers'; describe('RepoFile', () => { @@ -35,11 +35,10 @@ describe('RepoFile', () => { const fileIcon = vm.$el.querySelector('.file-icon'); expect(vm.$el.querySelector(`.${vm.file.icon}`).style.marginLeft).toEqual('0px'); - expect(name.href).toMatch(`/${vm.file.url}`); + expect(name.href).toMatch(''); expect(name.textContent.trim()).toEqual(vm.file.name); expect(fileIcon.classList.contains(vm.file.icon)).toBeTruthy(); expect(fileIcon.style.marginLeft).toEqual(`${vm.file.level * 10}px`); - expect(vm.$el.querySelectorAll('.animation-container').length).toBe(2); }); it('does render if hasFiles is true and is loading tree', () => { @@ -75,16 +74,16 @@ describe('RepoFile', () => { }); }); - it('fires clickedTreeRow when the link is clicked', () => { + it('fires clickFile when the link is clicked', () => { vm = createComponent({ file: file(), }); - spyOn(vm, 'clickedTreeRow'); + spyOn(vm, 'clickFile'); vm.$el.click(); - expect(vm.clickedTreeRow).toHaveBeenCalledWith(vm.file); + expect(vm.clickFile).toHaveBeenCalledWith(vm.file); }); describe('submodule', () => { diff --git a/spec/javascripts/repo/components/repo_loading_file_spec.js b/spec/javascripts/repo/components/repo_loading_file_spec.js index 031f2a9c0b2..18366fb89bc 100644 --- a/spec/javascripts/repo/components/repo_loading_file_spec.js +++ b/spec/javascripts/repo/components/repo_loading_file_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import repoLoadingFile from '~/repo/components/repo_loading_file.vue'; +import store from '~/ide/stores'; +import repoLoadingFile from '~/ide/components/repo_loading_file.vue'; import { resetStore } from '../helpers'; describe('RepoLoadingFile', () => { @@ -48,6 +48,7 @@ describe('RepoLoadingFile', () => { it('renders 1 column of animated LoC if isMini', (done) => { vm = createComponent(); + vm.$store.state.leftPanelCollapsed = true; vm.$store.state.openFiles.push('test'); vm.$nextTick(() => { diff --git a/spec/javascripts/repo/components/repo_prev_directory_spec.js b/spec/javascripts/repo/components/repo_prev_directory_spec.js index 7f82ae36a64..ff26cab2262 100644 --- a/spec/javascripts/repo/components/repo_prev_directory_spec.js +++ b/spec/javascripts/repo/components/repo_prev_directory_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import repoPrevDirectory from '~/repo/components/repo_prev_directory.vue'; +import store from '~/ide/stores'; +import repoPrevDirectory from '~/ide/components/repo_prev_directory.vue'; import { resetStore } from '../helpers'; describe('RepoPrevDirectory', () => { diff --git a/spec/javascripts/repo/components/repo_preview_spec.js b/spec/javascripts/repo/components/repo_preview_spec.js index 8d1a87494cf..e90837e4cb2 100644 --- a/spec/javascripts/repo/components/repo_preview_spec.js +++ b/spec/javascripts/repo/components/repo_preview_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import repoPreview from '~/repo/components/repo_preview.vue'; +import store from '~/ide/stores'; +import repoPreview from '~/ide/components/repo_preview.vue'; import { file, resetStore } from '../helpers'; describe('RepoPreview', () => { diff --git a/spec/javascripts/repo/components/repo_tab_spec.js b/spec/javascripts/repo/components/repo_tab_spec.js index 7d2174196c9..507bca983df 100644 --- a/spec/javascripts/repo/components/repo_tab_spec.js +++ b/spec/javascripts/repo/components/repo_tab_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import repoTab from '~/repo/components/repo_tab.vue'; +import store from '~/ide/stores'; +import repoTab from '~/ide/components/repo_tab.vue'; import { file, resetStore } from '../helpers'; describe('RepoTab', () => { @@ -31,16 +31,16 @@ describe('RepoTab', () => { expect(name.textContent.trim()).toEqual(vm.tab.name); }); - it('calls setFileActive when clicking tab', () => { + it('fires clickFile when the link is clicked', () => { vm = createComponent({ tab: file(), }); - spyOn(vm, 'setFileActive'); + spyOn(vm, 'clickFile'); vm.$el.click(); - expect(vm.setFileActive).toHaveBeenCalledWith(vm.tab); + expect(vm.clickFile).toHaveBeenCalledWith(vm.tab); }); it('calls closeFile when clicking close button', () => { diff --git a/spec/javascripts/repo/components/repo_tabs_spec.js b/spec/javascripts/repo/components/repo_tabs_spec.js index 1fb2242c051..0beaf643793 100644 --- a/spec/javascripts/repo/components/repo_tabs_spec.js +++ b/spec/javascripts/repo/components/repo_tabs_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import repoTabs from '~/repo/components/repo_tabs.vue'; +import store from '~/ide/stores'; +import repoTabs from '~/ide/components/repo_tabs.vue'; import { file, resetStore } from '../helpers'; describe('RepoTabs', () => { diff --git a/spec/javascripts/repo/helpers.js b/spec/javascripts/repo/helpers.js index 820a44992b4..ac43d221198 100644 --- a/spec/javascripts/repo/helpers.js +++ b/spec/javascripts/repo/helpers.js @@ -1,5 +1,5 @@ -import { decorateData } from '~/repo/stores/utils'; -import state from '~/repo/stores/state'; +import { decorateData } from '~/ide/stores/utils'; +import state from '~/ide/stores/state'; export const resetStore = (store) => { store.replaceState(state()); @@ -12,4 +12,5 @@ export const file = (name = 'name', id = name, type = '') => decorateData({ url: 'url', name, path: name, + lastCommit: {}, }); diff --git a/spec/javascripts/repo/lib/common/disposable_spec.js b/spec/javascripts/repo/lib/common/disposable_spec.js index 62c3913bf4d..af12ca15369 100644 --- a/spec/javascripts/repo/lib/common/disposable_spec.js +++ b/spec/javascripts/repo/lib/common/disposable_spec.js @@ -1,4 +1,4 @@ -import Disposable from '~/repo/lib/common/disposable'; +import Disposable from '~/ide/lib/common/disposable'; describe('Multi-file editor library disposable class', () => { let instance; diff --git a/spec/javascripts/repo/lib/common/model_manager_spec.js b/spec/javascripts/repo/lib/common/model_manager_spec.js index 8c134f178c0..563c2e33834 100644 --- a/spec/javascripts/repo/lib/common/model_manager_spec.js +++ b/spec/javascripts/repo/lib/common/model_manager_spec.js @@ -1,6 +1,6 @@ /* global monaco */ -import monacoLoader from '~/repo/monaco_loader'; -import ModelManager from '~/repo/lib/common/model_manager'; +import monacoLoader from '~/ide/monaco_loader'; +import ModelManager from '~/ide/lib/common/model_manager'; import { file } from '../../helpers'; describe('Multi-file editor library model manager', () => { diff --git a/spec/javascripts/repo/lib/common/model_spec.js b/spec/javascripts/repo/lib/common/model_spec.js index d41ade237ca..878a4a3f3fe 100644 --- a/spec/javascripts/repo/lib/common/model_spec.js +++ b/spec/javascripts/repo/lib/common/model_spec.js @@ -1,6 +1,6 @@ /* global monaco */ -import monacoLoader from '~/repo/monaco_loader'; -import Model from '~/repo/lib/common/model'; +import monacoLoader from '~/ide/monaco_loader'; +import Model from '~/ide/lib/common/model'; import { file } from '../../helpers'; describe('Multi-file editor library model', () => { diff --git a/spec/javascripts/repo/lib/decorations/controller_spec.js b/spec/javascripts/repo/lib/decorations/controller_spec.js index 2e32e8fa0bd..fea12d74dca 100644 --- a/spec/javascripts/repo/lib/decorations/controller_spec.js +++ b/spec/javascripts/repo/lib/decorations/controller_spec.js @@ -1,8 +1,8 @@ /* global monaco */ -import monacoLoader from '~/repo/monaco_loader'; -import editor from '~/repo/lib/editor'; -import DecorationsController from '~/repo/lib/decorations/controller'; -import Model from '~/repo/lib/common/model'; +import monacoLoader from '~/ide/monaco_loader'; +import editor from '~/ide/lib/editor'; +import DecorationsController from '~/ide/lib/decorations/controller'; +import Model from '~/ide/lib/common/model'; import { file } from '../../helpers'; describe('Multi-file editor library decorations controller', () => { diff --git a/spec/javascripts/repo/lib/diff/controller_spec.js b/spec/javascripts/repo/lib/diff/controller_spec.js index ed62e28d3a3..1d55c165260 100644 --- a/spec/javascripts/repo/lib/diff/controller_spec.js +++ b/spec/javascripts/repo/lib/diff/controller_spec.js @@ -1,10 +1,10 @@ /* global monaco */ -import monacoLoader from '~/repo/monaco_loader'; -import editor from '~/repo/lib/editor'; -import ModelManager from '~/repo/lib/common/model_manager'; -import DecorationsController from '~/repo/lib/decorations/controller'; -import DirtyDiffController, { getDiffChangeType, getDecorator } from '~/repo/lib/diff/controller'; -import { computeDiff } from '~/repo/lib/diff/diff'; +import monacoLoader from '~/ide/monaco_loader'; +import editor from '~/ide/lib/editor'; +import ModelManager from '~/ide/lib/common/model_manager'; +import DecorationsController from '~/ide/lib/decorations/controller'; +import DirtyDiffController, { getDiffChangeType, getDecorator } from '~/ide/lib/diff/controller'; +import { computeDiff } from '~/ide/lib/diff/diff'; import { file } from '../../helpers'; describe('Multi-file editor library dirty diff controller', () => { diff --git a/spec/javascripts/repo/lib/diff/diff_spec.js b/spec/javascripts/repo/lib/diff/diff_spec.js index 3269ec5d2c9..57f3ac3d365 100644 --- a/spec/javascripts/repo/lib/diff/diff_spec.js +++ b/spec/javascripts/repo/lib/diff/diff_spec.js @@ -1,4 +1,4 @@ -import { computeDiff } from '~/repo/lib/diff/diff'; +import { computeDiff } from '~/ide/lib/diff/diff'; describe('Multi-file editor library diff calculator', () => { describe('computeDiff', () => { diff --git a/spec/javascripts/repo/lib/editor_options_spec.js b/spec/javascripts/repo/lib/editor_options_spec.js index b4887d063ed..edbf5450dce 100644 --- a/spec/javascripts/repo/lib/editor_options_spec.js +++ b/spec/javascripts/repo/lib/editor_options_spec.js @@ -1,4 +1,4 @@ -import editorOptions from '~/repo/lib/editor_options'; +import editorOptions from '~/ide/lib/editor_options'; describe('Multi-file editor library editor options', () => { it('returns an array', () => { diff --git a/spec/javascripts/repo/lib/editor_spec.js b/spec/javascripts/repo/lib/editor_spec.js index cd32832a232..8d51d48a782 100644 --- a/spec/javascripts/repo/lib/editor_spec.js +++ b/spec/javascripts/repo/lib/editor_spec.js @@ -1,6 +1,6 @@ /* global monaco */ -import monacoLoader from '~/repo/monaco_loader'; -import editor from '~/repo/lib/editor'; +import monacoLoader from '~/ide/monaco_loader'; +import editor from '~/ide/lib/editor'; import { file } from '../helpers'; describe('Multi-file editor library', () => { diff --git a/spec/javascripts/repo/monaco_loader_spec.js b/spec/javascripts/repo/monaco_loader_spec.js index 887a80160fc..b8ac36972aa 100644 --- a/spec/javascripts/repo/monaco_loader_spec.js +++ b/spec/javascripts/repo/monaco_loader_spec.js @@ -1,5 +1,5 @@ import monacoContext from 'monaco-editor/dev/vs/loader'; -import monacoLoader from '~/repo/monaco_loader'; +import monacoLoader from '~/ide/monaco_loader'; describe('MonacoLoader', () => { it('calls require.config and exports require', () => { diff --git a/spec/javascripts/repo/stores/actions/branch_spec.js b/spec/javascripts/repo/stores/actions/branch_spec.js index af9d6835a67..00d16fd790d 100644 --- a/spec/javascripts/repo/stores/actions/branch_spec.js +++ b/spec/javascripts/repo/stores/actions/branch_spec.js @@ -1,5 +1,5 @@ -import store from '~/repo/stores'; -import service from '~/repo/services'; +import store from '~/ide/stores'; +import service from '~/ide/services'; import { resetStore } from '../../helpers'; describe('Multi-file store branch actions', () => { @@ -16,19 +16,25 @@ describe('Multi-file store branch actions', () => { })); spyOn(history, 'pushState'); - store.state.project.id = 2; - store.state.currentBranch = 'testing'; + store.state.currentProjectId = 'abcproject'; + store.state.currentBranchId = 'testing'; + store.state.projects.abcproject = { + branches: { + master: { + workingReference: '1', + }, + }, + }; }); it('creates new branch', (done) => { store.dispatch('createNewBranch', 'master') .then(() => { - expect(store.state.currentBranch).toBe('testing'); - expect(service.createBranch).toHaveBeenCalledWith(2, { + expect(store.state.currentBranchId).toBe('testing'); + expect(service.createBranch).toHaveBeenCalledWith('abcproject', { branch: 'master', ref: 'testing', }); - expect(history.pushState).toHaveBeenCalled(); done(); }) diff --git a/spec/javascripts/repo/stores/actions/file_spec.js b/spec/javascripts/repo/stores/actions/file_spec.js index 099c0556e71..8ce01d3bf12 100644 --- a/spec/javascripts/repo/stores/actions/file_spec.js +++ b/spec/javascripts/repo/stores/actions/file_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import store from '~/repo/stores'; -import service from '~/repo/services'; +import store from '~/ide/stores'; +import service from '~/ide/services'; import { file, resetStore } from '../../helpers'; describe('Multi-file store file actions', () => { @@ -24,8 +24,6 @@ describe('Multi-file store file actions', () => { localFile.parentTreeUrl = 'parentTreeUrl'; store.state.openFiles.push(localFile); - - spyOn(history, 'pushState'); }); afterEach(() => { @@ -82,15 +80,6 @@ describe('Multi-file store file actions', () => { }).catch(done.fail); }); - it('calls pushState when no open files are left', (done) => { - store.dispatch('closeFile', { file: localFile }) - .then(() => { - expect(history.pushState).toHaveBeenCalledWith(jasmine.anything(), '', 'parentTreeUrl'); - - done(); - }).catch(done.fail); - }); - it('sets next file as active', (done) => { const f = file(); store.state.openFiles.push(f); @@ -322,8 +311,26 @@ describe('Multi-file store file actions', () => { }); describe('createTempFile', () => { + let projectTree; + beforeEach(() => { document.body.innerHTML += '<div class="flash-container"></div>'; + + store.state.currentProjectId = 'abcproject'; + store.state.currentBranchId = 'master'; + store.state.projects.abcproject = { + branches: { + master: { + workingReference: '1', + }, + }, + }; + + store.state.trees['abcproject/mybranch'] = { + tree: [], + }; + + projectTree = store.state.trees['abcproject/mybranch']; }); afterEach(() => { @@ -332,11 +339,13 @@ describe('Multi-file store file actions', () => { it('creates temp file', (done) => { store.dispatch('createTempFile', { - tree: store.state, name: 'test', + projectId: 'abcproject', + branchId: 'mybranch', + parent: projectTree, }).then((f) => { expect(f.tempFile).toBeTruthy(); - expect(store.state.tree.length).toBe(1); + expect(store.state.trees['abcproject/mybranch'].tree.length).toBe(1); done(); }).catch(done.fail); @@ -344,8 +353,10 @@ describe('Multi-file store file actions', () => { it('adds tmp file to open files', (done) => { store.dispatch('createTempFile', { - tree: store.state, name: 'test', + projectId: 'abcproject', + branchId: 'mybranch', + parent: projectTree, }).then((f) => { expect(store.state.openFiles.length).toBe(1); expect(store.state.openFiles[0].name).toBe(f.name); @@ -356,8 +367,10 @@ describe('Multi-file store file actions', () => { it('sets tmp file as active', (done) => { store.dispatch('createTempFile', { - tree: store.state, name: 'test', + projectId: 'abcproject', + branchId: 'mybranch', + parent: projectTree, }).then((f) => { expect(f.active).toBeTruthy(); @@ -367,8 +380,10 @@ describe('Multi-file store file actions', () => { it('enters edit mode if file is not base64', (done) => { store.dispatch('createTempFile', { - tree: store.state, name: 'test', + projectId: 'abcproject', + branchId: 'mybranch', + parent: projectTree, }).then(() => { expect(store.state.editMode).toBeTruthy(); @@ -376,24 +391,14 @@ describe('Multi-file store file actions', () => { }).catch(done.fail); }); - it('does not enter edit mode if file is base64', (done) => { - store.dispatch('createTempFile', { - tree: store.state, - name: 'test', - base64: true, - }).then(() => { - expect(store.state.editMode).toBeFalsy(); - - done(); - }).catch(done.fail); - }); - it('creates flash message is file already exists', (done) => { - store.state.tree.push(file('test', '1', 'blob')); + store.state.trees['abcproject/mybranch'].tree.push(file('test', '1', 'blob')); store.dispatch('createTempFile', { - tree: store.state, name: 'test', + projectId: 'abcproject', + branchId: 'mybranch', + parent: projectTree, }).then(() => { expect(document.querySelector('.flash-alert')).not.toBeNull(); @@ -402,11 +407,13 @@ describe('Multi-file store file actions', () => { }); it('increases level of file', (done) => { - store.state.level = 1; + store.state.trees['abcproject/mybranch'].level = 1; store.dispatch('createTempFile', { - tree: store.state, name: 'test', + projectId: 'abcproject', + branchId: 'mybranch', + parent: projectTree, }).then((f) => { expect(f.level).toBe(2); diff --git a/spec/javascripts/repo/stores/actions/tree_spec.js b/spec/javascripts/repo/stores/actions/tree_spec.js index 2bbc49d5a9f..65351dbb7d9 100644 --- a/spec/javascripts/repo/stores/actions/tree_spec.js +++ b/spec/javascripts/repo/stores/actions/tree_spec.js @@ -1,10 +1,30 @@ import Vue from 'vue'; -import * as urlUtils from '~/lib/utils/url_utility'; -import store from '~/repo/stores'; -import service from '~/repo/services'; +import store from '~/ide/stores'; +import service from '~/ide/services'; import { file, resetStore } from '../../helpers'; describe('Multi-file store tree actions', () => { + let projectTree; + + const basicCallParameters = { + endpoint: 'rootEndpoint', + projectId: 'abcproject', + branch: 'master', + }; + + beforeEach(() => { + store.state.currentProjectId = 'abcproject'; + store.state.currentBranchId = 'master'; + store.state.projects.abcproject = { + web_url: '', + branches: { + master: { + workingReference: '1', + }, + }, + }; + }); + afterEach(() => { resetStore(store); }); @@ -24,38 +44,32 @@ describe('Multi-file store tree actions', () => { submodules: [{ name: 'submodule' }], }), })); - spyOn(history, 'pushState'); - - Object.assign(store.state.endpoints, { - rootEndpoint: 'rootEndpoint', - }); }); it('calls service getTreeData', (done) => { - store.dispatch('getTreeData') - .then(() => { - expect(service.getTreeData).toHaveBeenCalledWith('rootEndpoint'); + store.dispatch('getTreeData', basicCallParameters) + .then(() => { + expect(service.getTreeData).toHaveBeenCalledWith('rootEndpoint'); - done(); - }).catch(done.fail); + done(); + }).catch(done.fail); }); it('adds data into tree', (done) => { - store.dispatch('getTreeData') - .then(Vue.nextTick) + store.dispatch('getTreeData', basicCallParameters) .then(() => { - expect(store.state.tree.length).toBe(3); - expect(store.state.tree[0].type).toBe('tree'); - expect(store.state.tree[1].type).toBe('submodule'); - expect(store.state.tree[2].type).toBe('blob'); + projectTree = store.state.trees['abcproject/master']; + expect(projectTree.tree.length).toBe(3); + expect(projectTree.tree[0].type).toBe('tree'); + expect(projectTree.tree[1].type).toBe('submodule'); + expect(projectTree.tree[2].type).toBe('blob'); done(); }).catch(done.fail); }); it('sets parent tree URL', (done) => { - store.dispatch('getTreeData') - .then(Vue.nextTick) + store.dispatch('getTreeData', basicCallParameters) .then(() => { expect(store.state.parentTreeUrl).toBe('parent_tree_url'); @@ -64,10 +78,9 @@ describe('Multi-file store tree actions', () => { }); it('sets last commit path', (done) => { - store.dispatch('getTreeData') - .then(Vue.nextTick) + store.dispatch('getTreeData', basicCallParameters) .then(() => { - expect(store.state.lastCommitPath).toBe('last_commit_path'); + expect(store.state.trees['abcproject/master'].lastCommitPath).toBe('last_commit_path'); done(); }).catch(done.fail); @@ -76,8 +89,7 @@ describe('Multi-file store tree actions', () => { it('sets root if not currently at root', (done) => { store.state.isInitialRoot = false; - store.dispatch('getTreeData') - .then(Vue.nextTick) + store.dispatch('getTreeData', basicCallParameters) .then(() => { expect(store.state.isInitialRoot).toBeTruthy(); expect(store.state.isRoot).toBeTruthy(); @@ -87,7 +99,7 @@ describe('Multi-file store tree actions', () => { }); it('sets page title', (done) => { - store.dispatch('getTreeData') + store.dispatch('getTreeData', basicCallParameters) .then(() => { expect(document.title).toBe('test'); @@ -95,40 +107,15 @@ describe('Multi-file store tree actions', () => { }).catch(done.fail); }); - it('toggles loading', (done) => { - store.dispatch('getTreeData') - .then(() => { - expect(store.state.loading).toBeTruthy(); - - return Vue.nextTick(); - }) - .then(() => { - expect(store.state.loading).toBeFalsy(); - - done(); - }).catch(done.fail); - }); - - it('calls pushState with endpoint', (done) => { - store.dispatch('getTreeData') - .then(Vue.nextTick) - .then(() => { - expect(history.pushState).toHaveBeenCalledWith(jasmine.anything(), '', 'rootEndpoint'); - - done(); - }).catch(done.fail); - }); - it('calls getLastCommitData if prevLastCommitPath is not null', (done) => { const getLastCommitDataSpy = jasmine.createSpy('getLastCommitData'); const oldGetLastCommitData = store._actions.getLastCommitData; // eslint-disable-line store._actions.getLastCommitData = [getLastCommitDataSpy]; // eslint-disable-line store.state.prevLastCommitPath = 'test'; - store.dispatch('getTreeData') - .then(Vue.nextTick) + store.dispatch('getTreeData', basicCallParameters) .then(() => { - expect(getLastCommitDataSpy).toHaveBeenCalledWith(store.state); + expect(getLastCommitDataSpy).toHaveBeenCalledWith(projectTree); store._actions.getLastCommitData = oldGetLastCommitData; // eslint-disable-line @@ -149,6 +136,8 @@ describe('Multi-file store tree actions', () => { store._actions.getTreeData = [getTreeDataSpy]; // eslint-disable-line tree = { + projectId: 'abcproject', + branchId: 'master', opened: false, tree: [], }; @@ -175,10 +164,11 @@ describe('Multi-file store tree actions', () => { tree, }).then(() => { expect(getTreeDataSpy).toHaveBeenCalledWith({ + projectId: 'abcproject', + branch: 'master', endpoint: 'test', tree, }); - expect(store.state.previousUrl).toBe('test'); done(); }).catch(done.fail); @@ -199,155 +189,29 @@ describe('Multi-file store tree actions', () => { done(); }).catch(done.fail); }); - - it('pushes new state', (done) => { - spyOn(history, 'pushState'); - Object.assign(tree, { - opened: true, - parentTreeUrl: 'testing', - }); - - store.dispatch('toggleTreeOpen', { - endpoint: 'test', - tree, - }).then(() => { - expect(history.pushState).toHaveBeenCalledWith(jasmine.anything(), '', 'testing'); - - done(); - }).catch(done.fail); - }); - }); - - describe('clickedTreeRow', () => { - describe('tree', () => { - let toggleTreeOpenSpy; - let oldToggleTreeOpen; - - beforeEach(() => { - toggleTreeOpenSpy = jasmine.createSpy('toggleTreeOpen'); - - oldToggleTreeOpen = store._actions.toggleTreeOpen; // eslint-disable-line - store._actions.toggleTreeOpen = [toggleTreeOpenSpy]; // eslint-disable-line - }); - - afterEach(() => { - store._actions.toggleTreeOpen = oldToggleTreeOpen; // eslint-disable-line - }); - - it('opens tree', (done) => { - const tree = { - url: 'a', - type: 'tree', - }; - - store.dispatch('clickedTreeRow', tree) - .then(() => { - expect(toggleTreeOpenSpy).toHaveBeenCalledWith({ - endpoint: tree.url, - tree, - }); - - done(); - }).catch(done.fail); - }); - }); - - describe('submodule', () => { - let row; - - beforeEach(() => { - spyOn(urlUtils, 'visitUrl'); - - row = { - url: 'submoduleurl', - type: 'submodule', - loading: false, - }; - }); - - it('toggles loading for row', (done) => { - store.dispatch('clickedTreeRow', row) - .then(() => { - expect(row.loading).toBeTruthy(); - - done(); - }).catch(done.fail); - }); - - it('opens submodule URL', (done) => { - store.dispatch('clickedTreeRow', row) - .then(() => { - expect(urlUtils.visitUrl).toHaveBeenCalledWith('submoduleurl'); - - done(); - }).catch(done.fail); - }); - }); - - describe('blob', () => { - let row; - - beforeEach(() => { - row = { - type: 'blob', - opened: false, - }; - }); - - it('calls getFileData', (done) => { - const getFileDataSpy = jasmine.createSpy('getFileData'); - const oldGetFileData = store._actions.getFileData; // eslint-disable-line - store._actions.getFileData = [getFileDataSpy]; // eslint-disable-line - - store.dispatch('clickedTreeRow', row) - .then(() => { - expect(getFileDataSpy).toHaveBeenCalledWith(row); - - store._actions.getFileData = oldGetFileData; // eslint-disable-line - - done(); - }).catch(done.fail); - }); - - it('calls setFileActive when file is opened', (done) => { - const setFileActiveSpy = jasmine.createSpy('setFileActive'); - const oldSetFileActive = store._actions.setFileActive; // eslint-disable-line - store._actions.setFileActive = [setFileActiveSpy]; // eslint-disable-line - - row.opened = true; - - store.dispatch('clickedTreeRow', row) - .then(() => { - expect(setFileActiveSpy).toHaveBeenCalledWith(row); - - store._actions.setFileActive = oldSetFileActive; // eslint-disable-line - - done(); - }).catch(done.fail); - }); - }); }); describe('createTempTree', () => { - it('creates temp tree', (done) => { - store.dispatch('createTempTree', 'test') - .then(() => { - expect(store.state.tree[0].tempFile).toBeTruthy(); - expect(store.state.tree[0].name).toBe('test'); - expect(store.state.tree[0].type).toBe('tree'); - - done(); - }).catch(done.fail); + beforeEach(() => { + store.state.trees['abcproject/mybranch'] = { + tree: [], + }; + projectTree = store.state.trees['abcproject/mybranch']; }); - it('creates .gitkeep file in temp tree', (done) => { - store.dispatch('createTempTree', 'test') - .then(() => { - expect(store.state.tree[0].tree[0].tempFile).toBeTruthy(); - expect(store.state.tree[0].tree[0].name).toBe('.gitkeep'); + it('creates temp tree', (done) => { + store.dispatch('createTempTree', { + projectId: store.state.currentProjectId, + branchId: store.state.currentBranchId, + name: 'test', + parent: projectTree, + }) + .then(() => { + expect(projectTree.tree[0].name).toBe('test'); + expect(projectTree.tree[0].type).toBe('tree'); - done(); - }).catch(done.fail); + done(); + }).catch(done.fail); }); it('creates new folder inside another tree', (done) => { @@ -357,35 +221,46 @@ describe('Multi-file store tree actions', () => { tree: [], }; - store.state.tree.push(tree); + projectTree.tree.push(tree); - store.dispatch('createTempTree', 'testing/test') - .then(() => { - expect(store.state.tree[0].name).toBe('testing'); - expect(store.state.tree[0].tree[0].tempFile).toBeTruthy(); - expect(store.state.tree[0].tree[0].name).toBe('test'); - expect(store.state.tree[0].tree[0].type).toBe('tree'); + store.dispatch('createTempTree', { + projectId: store.state.currentProjectId, + branchId: store.state.currentBranchId, + name: 'testing/test', + parent: projectTree, + }) + .then(() => { + expect(projectTree.tree[0].name).toBe('testing'); + expect(projectTree.tree[0].tree[0].tempFile).toBeTruthy(); + expect(projectTree.tree[0].tree[0].name).toBe('test'); + expect(projectTree.tree[0].tree[0].type).toBe('tree'); - done(); - }).catch(done.fail); + done(); + }).catch(done.fail); }); it('does not create new tree if already exists', (done) => { const tree = { type: 'tree', name: 'testing', + endpoint: 'test', tree: [], }; - store.state.tree.push(tree); + projectTree.tree.push(tree); - store.dispatch('createTempTree', 'testing/test') - .then(() => { - expect(store.state.tree[0].name).toBe('testing'); - expect(store.state.tree[0].tempFile).toBeUndefined(); + store.dispatch('createTempTree', { + projectId: store.state.currentProjectId, + branchId: store.state.currentBranchId, + name: 'testing/test', + parent: projectTree, + }) + .then(() => { + expect(projectTree.tree[0].name).toBe('testing'); + expect(projectTree.tree[0].tempFile).toBeUndefined(); - done(); - }).catch(done.fail); + done(); + }).catch(done.fail); }); }); @@ -405,12 +280,17 @@ describe('Multi-file store tree actions', () => { }]), })); - store.state.tree.push(file('testing', '1', 'tree')); - store.state.lastCommitPath = 'lastcommitpath'; + store.state.trees['abcproject/mybranch'] = { + tree: [], + }; + + projectTree = store.state.trees['abcproject/mybranch']; + projectTree.tree.push(file('testing', '1', 'tree')); + projectTree.lastCommitPath = 'lastcommitpath'; }); it('calls service with lastCommitPath', (done) => { - store.dispatch('getLastCommitData') + store.dispatch('getLastCommitData', projectTree) .then(() => { expect(service.getTreeLastCommit).toHaveBeenCalledWith('lastcommitpath'); @@ -419,22 +299,22 @@ describe('Multi-file store tree actions', () => { }); it('updates trees last commit data', (done) => { - store.dispatch('getLastCommitData') - .then(Vue.nextTick) + store.dispatch('getLastCommitData', projectTree) + .then(Vue.nextTick) .then(() => { - expect(store.state.tree[0].lastCommit.message).toBe('commit message'); + expect(projectTree.tree[0].lastCommit.message).toBe('commit message'); done(); }).catch(done.fail); }); it('does not update entry if not found', (done) => { - store.state.tree[0].name = 'a'; + projectTree.tree[0].name = 'a'; - store.dispatch('getLastCommitData') + store.dispatch('getLastCommitData', projectTree) .then(Vue.nextTick) .then(() => { - expect(store.state.tree[0].lastCommit.message).not.toBe('commit message'); + expect(projectTree.tree[0].lastCommit.message).not.toBe('commit message'); done(); }).catch(done.fail); diff --git a/spec/javascripts/repo/stores/actions_spec.js b/spec/javascripts/repo/stores/actions_spec.js index 21d87e46216..0b0d34f072a 100644 --- a/spec/javascripts/repo/stores/actions_spec.js +++ b/spec/javascripts/repo/stores/actions_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import * as urlUtils from '~/lib/utils/url_utility'; -import store from '~/repo/stores'; -import service from '~/repo/services'; +import store from '~/ide/stores'; +import service from '~/ide/services'; import { resetStore, file } from '../helpers'; describe('Multi-file store actions', () => { @@ -110,6 +110,7 @@ describe('Multi-file store actions', () => { it('can force closed if there are changed files', (done) => { store.state.editMode = true; + store.state.openFiles.push(file()); store.state.openFiles[0].changed = true; @@ -125,7 +126,6 @@ describe('Multi-file store actions', () => { it('discards file changes', (done) => { const f = file(); store.state.editMode = true; - store.state.tree.push(f); store.state.openFiles.push(f); f.changed = true; @@ -141,8 +141,6 @@ describe('Multi-file store actions', () => { describe('toggleBlobView', () => { it('sets edit mode view if in edit mode', (done) => { - store.state.editMode = true; - store.dispatch('toggleBlobView') .then(() => { expect(store.state.currentBlobView).toBe('repo-editor'); @@ -153,6 +151,8 @@ describe('Multi-file store actions', () => { }); it('sets preview mode view if not in edit mode', (done) => { + store.state.editMode = false; + store.dispatch('toggleBlobView') .then(() => { expect(store.state.currentBlobView).toBe('repo-preview'); @@ -165,9 +165,15 @@ describe('Multi-file store actions', () => { describe('checkCommitStatus', () => { beforeEach(() => { - store.state.project.id = 2; - store.state.currentBranch = 'master'; - store.state.currentRef = '1'; + store.state.currentProjectId = 'abcproject'; + store.state.currentBranchId = 'master'; + store.state.projects.abcproject = { + branches: { + master: { + workingReference: '1', + }, + }, + }; }); it('calls service', (done) => { @@ -177,7 +183,7 @@ describe('Multi-file store actions', () => { store.dispatch('checkCommitStatus') .then(() => { - expect(service.getBranchData).toHaveBeenCalledWith(2, 'master'); + expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master'); done(); }) @@ -221,7 +227,17 @@ describe('Multi-file store actions', () => { document.body.innerHTML += '<div class="flash-container"></div>'; - store.state.project.id = 123; + store.state.currentProjectId = 'abcproject'; + store.state.currentBranchId = 'master'; + store.state.projects.abcproject = { + web_url: 'webUrl', + branches: { + master: { + workingReference: '1', + }, + }, + }; + payload = { branch: 'master', }; @@ -248,7 +264,7 @@ describe('Multi-file store actions', () => { it('calls service', (done) => { store.dispatch('commitChanges', { payload, newMr: false }) .then(() => { - expect(service.commit).toHaveBeenCalledWith(123, payload); + expect(service.commit).toHaveBeenCalledWith('abcproject', payload); done(); }).catch(done.fail); @@ -284,17 +300,6 @@ describe('Multi-file store actions', () => { }).catch(done.fail); }); - it('toggles edit mode', (done) => { - store.state.editMode = true; - - store.dispatch('commitChanges', { payload, newMr: false }) - .then(() => { - expect(store.state.editMode).toBeFalsy(); - - done(); - }).catch(done.fail); - }); - it('closes all files', (done) => { store.state.openFiles.push(file()); store.state.openFiles[0].opened = true; @@ -317,23 +322,12 @@ describe('Multi-file store actions', () => { }).catch(done.fail); }); - it('updates commit ref', (done) => { - store.dispatch('commitChanges', { payload, newMr: false }) - .then(() => { - expect(store.state.currentRef).toBe('123456'); - - done(); - }).catch(done.fail); - }); - it('redirects to new merge request page', (done) => { spyOn(urlUtils, 'visitUrl'); - store.state.endpoints.newMergeRequestUrl = 'newMergeRequestUrl?branch='; - store.dispatch('commitChanges', { payload, newMr: true }) .then(() => { - expect(urlUtils.visitUrl).toHaveBeenCalledWith('newMergeRequestUrl?branch=master'); + expect(urlUtils.visitUrl).toHaveBeenCalledWith('webUrl/merge_requests/new?merge_request%5Bsource_branch%5D=master'); done(); }).catch(done.fail); @@ -363,15 +357,30 @@ describe('Multi-file store actions', () => { }); describe('createTempEntry', () => { + beforeEach(() => { + store.state.trees['abcproject/mybranch'] = { + tree: [], + }; + store.state.projects.abcproject = { + web_url: '', + }; + }); + it('creates a temp tree', (done) => { + const projectTree = store.state.trees['abcproject/mybranch']; + store.dispatch('createTempEntry', { + projectId: 'abcproject', + branchId: 'mybranch', + parent: projectTree, name: 'test', type: 'tree', }) .then(() => { - expect(store.state.tree.length).toBe(1); - expect(store.state.tree[0].tempFile).toBeTruthy(); - expect(store.state.tree[0].type).toBe('tree'); + const baseTree = projectTree.tree; + expect(baseTree.length).toBe(1); + expect(baseTree[0].tempFile).toBeTruthy(); + expect(baseTree[0].type).toBe('tree'); done(); }) @@ -379,14 +388,20 @@ describe('Multi-file store actions', () => { }); it('creates temp file', (done) => { + const projectTree = store.state.trees['abcproject/mybranch']; + store.dispatch('createTempEntry', { + projectId: 'abcproject', + branchId: 'mybranch', + parent: projectTree, name: 'test', type: 'blob', }) .then(() => { - expect(store.state.tree.length).toBe(1); - expect(store.state.tree[0].tempFile).toBeTruthy(); - expect(store.state.tree[0].type).toBe('blob'); + const baseTree = projectTree.tree; + expect(baseTree.length).toBe(1); + expect(baseTree[0].tempFile).toBeTruthy(); + expect(baseTree[0].type).toBe('blob'); done(); }) diff --git a/spec/javascripts/repo/stores/getters_spec.js b/spec/javascripts/repo/stores/getters_spec.js index 952b8ec3a59..d0d5934f29a 100644 --- a/spec/javascripts/repo/stores/getters_spec.js +++ b/spec/javascripts/repo/stores/getters_spec.js @@ -1,5 +1,5 @@ -import * as getters from '~/repo/stores/getters'; -import state from '~/repo/stores/state'; +import * as getters from '~/ide/stores/getters'; +import state from '~/ide/stores/state'; import { file } from '../helpers'; describe('Multi-file store getters', () => { @@ -9,20 +9,6 @@ describe('Multi-file store getters', () => { localState = state(); }); - describe('treeList', () => { - it('returns flat tree list', () => { - localState.tree.push(file('1')); - localState.tree[0].tree.push(file('2')); - localState.tree[0].tree[0].tree.push(file('3')); - - const treeList = getters.treeList(localState); - - expect(treeList.length).toBe(3); - expect(treeList[1].name).toBe(localState.tree[0].tree[0].name); - expect(treeList[2].name).toBe(localState.tree[0].tree[0].tree[0].name); - }); - }); - describe('changedFiles', () => { it('returns a list of changed opened files', () => { localState.openFiles.push(file()); @@ -49,7 +35,7 @@ describe('Multi-file store getters', () => { localState.openFiles.push(file()); localState.openFiles.push(file('active')); - expect(getters.activeFile(localState)).toBeUndefined(); + expect(getters.activeFile(localState)).toBeNull(); }); }); @@ -67,18 +53,6 @@ describe('Multi-file store getters', () => { }); }); - describe('isCollapsed', () => { - it('returns true if state has open files', () => { - localState.openFiles.push(file()); - - expect(getters.isCollapsed(localState)).toBeTruthy(); - }); - - it('returns false if state has no open files', () => { - expect(getters.isCollapsed(localState)).toBeFalsy(); - }); - }); - describe('canEditFile', () => { beforeEach(() => { localState.onTopOfBranch = true; @@ -109,12 +83,6 @@ describe('Multi-file store getters', () => { expect(getters.canEditFile(localState)).toBeFalsy(); }); - - it('returns false if user can commit but on a branch', () => { - localState.onTopOfBranch = false; - - expect(getters.canEditFile(localState)).toBeFalsy(); - }); }); describe('modifiedFiles', () => { diff --git a/spec/javascripts/repo/stores/mutations/branch_spec.js b/spec/javascripts/repo/stores/mutations/branch_spec.js index 3c06794d5e3..a7167537ef2 100644 --- a/spec/javascripts/repo/stores/mutations/branch_spec.js +++ b/spec/javascripts/repo/stores/mutations/branch_spec.js @@ -1,5 +1,5 @@ -import mutations from '~/repo/stores/mutations/branch'; -import state from '~/repo/stores/state'; +import mutations from '~/ide/stores/mutations/branch'; +import state from '~/ide/stores/state'; describe('Multi-file store branch mutations', () => { let localState; @@ -12,7 +12,7 @@ describe('Multi-file store branch mutations', () => { it('sets currentBranch', () => { mutations.SET_CURRENT_BRANCH(localState, 'master'); - expect(localState.currentBranch).toBe('master'); + expect(localState.currentBranchId).toBe('master'); }); }); }); diff --git a/spec/javascripts/repo/stores/mutations/file_spec.js b/spec/javascripts/repo/stores/mutations/file_spec.js index 2f2835dde1f..947a60587df 100644 --- a/spec/javascripts/repo/stores/mutations/file_spec.js +++ b/spec/javascripts/repo/stores/mutations/file_spec.js @@ -1,5 +1,5 @@ -import mutations from '~/repo/stores/mutations/file'; -import state from '~/repo/stores/state'; +import mutations from '~/ide/stores/mutations/file'; +import state from '~/ide/stores/state'; import { file } from '../../helpers'; describe('Multi-file store file mutations', () => { diff --git a/spec/javascripts/repo/stores/mutations/tree_spec.js b/spec/javascripts/repo/stores/mutations/tree_spec.js index 1c76cfed9c8..cf1248ba28b 100644 --- a/spec/javascripts/repo/stores/mutations/tree_spec.js +++ b/spec/javascripts/repo/stores/mutations/tree_spec.js @@ -1,5 +1,5 @@ -import mutations from '~/repo/stores/mutations/tree'; -import state from '~/repo/stores/state'; +import mutations from '~/ide/stores/mutations/tree'; +import state from '~/ide/stores/state'; import { file } from '../../helpers'; describe('Multi-file store tree mutations', () => { diff --git a/spec/javascripts/repo/stores/mutations_spec.js b/spec/javascripts/repo/stores/mutations_spec.js index d1c9885e01d..5fd8ad94972 100644 --- a/spec/javascripts/repo/stores/mutations_spec.js +++ b/spec/javascripts/repo/stores/mutations_spec.js @@ -1,5 +1,5 @@ -import mutations from '~/repo/stores/mutations'; -import state from '~/repo/stores/state'; +import mutations from '~/ide/stores/mutations'; +import state from '~/ide/stores/state'; import { file } from '../helpers'; describe('Multi-file store mutations', () => { @@ -65,11 +65,11 @@ describe('Multi-file store mutations', () => { it('toggles editMode', () => { mutations.TOGGLE_EDIT_MODE(localState); - expect(localState.editMode).toBeTruthy(); + expect(localState.editMode).toBeFalsy(); mutations.TOGGLE_EDIT_MODE(localState); - expect(localState.editMode).toBeFalsy(); + expect(localState.editMode).toBeTruthy(); }); }); @@ -85,14 +85,6 @@ describe('Multi-file store mutations', () => { }); }); - describe('SET_COMMIT_REF', () => { - it('sets currentRef', () => { - mutations.SET_COMMIT_REF(localState, '123'); - - expect(localState.currentRef).toBe('123'); - }); - }); - describe('SET_ROOT', () => { it('sets isRoot & initialRoot', () => { mutations.SET_ROOT(localState, true); @@ -107,11 +99,27 @@ describe('Multi-file store mutations', () => { }); }); - describe('SET_PREVIOUS_URL', () => { - it('sets previousUrl', () => { - mutations.SET_PREVIOUS_URL(localState, 'testing'); + describe('SET_LEFT_PANEL_COLLAPSED', () => { + it('sets left panel collapsed', () => { + mutations.SET_LEFT_PANEL_COLLAPSED(localState, true); + + expect(localState.leftPanelCollapsed).toBeTruthy(); + + mutations.SET_LEFT_PANEL_COLLAPSED(localState, false); + + expect(localState.leftPanelCollapsed).toBeFalsy(); + }); + }); + + describe('SET_RIGHT_PANEL_COLLAPSED', () => { + it('sets right panel collapsed', () => { + mutations.SET_RIGHT_PANEL_COLLAPSED(localState, true); + + expect(localState.rightPanelCollapsed).toBeTruthy(); + + mutations.SET_RIGHT_PANEL_COLLAPSED(localState, false); - expect(localState.previousUrl).toBe('testing'); + expect(localState.rightPanelCollapsed).toBeFalsy(); }); }); }); diff --git a/spec/javascripts/repo/stores/utils_spec.js b/spec/javascripts/repo/stores/utils_spec.js index 37287c587d7..89745a2029e 100644 --- a/spec/javascripts/repo/stores/utils_spec.js +++ b/spec/javascripts/repo/stores/utils_spec.js @@ -1,4 +1,6 @@ -import * as utils from '~/repo/stores/utils'; +import * as utils from '~/ide/stores/utils'; +import state from '~/ide/stores/state'; +import { file } from '../helpers'; describe('Multi-file store utils', () => { describe('setPageTitle', () => { @@ -9,13 +11,28 @@ describe('Multi-file store utils', () => { }); }); - describe('pushState', () => { - it('calls history.pushState', () => { - spyOn(history, 'pushState'); + describe('treeList', () => { + let localState; - utils.pushState('test'); + beforeEach(() => { + localState = state(); + }); + + it('returns flat tree list', () => { + localState.trees = []; + localState.trees['abcproject/mybranch'] = { + tree: [], + }; + const baseTree = localState.trees['abcproject/mybranch'].tree; + baseTree.push(file('1')); + baseTree[0].tree.push(file('2')); + baseTree[0].tree[0].tree.push(file('3')); + + const treeList = utils.treeList(localState, 'abcproject/mybranch'); - expect(history.pushState).toHaveBeenCalledWith({ url: 'test' }, '', 'test'); + expect(treeList.length).toBe(3); + expect(treeList[1].name).toBe(baseTree[0].tree[0].name); + expect(treeList[2].name).toBe(baseTree[0].tree[0].tree[0].name); }); }); @@ -52,10 +69,10 @@ describe('Multi-file store utils', () => { }); describe('findIndexOfFile', () => { - let state; + let localState; beforeEach(() => { - state = [{ + localState = [{ path: '1', }, { path: '2', @@ -63,7 +80,7 @@ describe('Multi-file store utils', () => { }); it('finds in the index of an entry by path', () => { - const index = utils.findIndexOfFile(state, { + const index = utils.findIndexOfFile(localState, { path: '2', }); @@ -72,10 +89,10 @@ describe('Multi-file store utils', () => { }); describe('findEntry', () => { - let state; + let localState; beforeEach(() => { - state = { + localState = { tree: [{ type: 'tree', name: 'test', @@ -87,14 +104,14 @@ describe('Multi-file store utils', () => { }); it('returns an entry found by name', () => { - const foundEntry = utils.findEntry(state, 'tree', 'test'); + const foundEntry = utils.findEntry(localState.tree, 'tree', 'test'); expect(foundEntry.type).toBe('tree'); expect(foundEntry.name).toBe('test'); }); it('returns undefined when no entry found', () => { - const foundEntry = utils.findEntry(state, 'blob', 'test'); + const foundEntry = utils.findEntry(localState.tree, 'blob', 'test'); expect(foundEntry).toBeUndefined(); }); diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index 72790eb215a..3267e29585b 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -1,8 +1,7 @@ /* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, new-parens, no-return-assign, new-cap, vars-on-top, max-len */ -/* global Sidebar */ import '~/commons/bootstrap'; -import '~/right_sidebar'; +import Sidebar from '~/right_sidebar'; (function() { var $aside, $icon, $labelsIcon, $page, $toggle, assertSidebarState; diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js index d5b8947c86f..db7d083065b 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import * as urlUtils from '~/lib/utils/url_utility'; import deploymentComponent from '~/vue_merge_request_widget/components/mr_widget_deployment'; import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; +import { getTimeago } from '~/lib/utils/datetime_utility'; const deploymentMockData = [ { @@ -49,7 +50,7 @@ describe('MRWidgetDeployment', () => { describe('formatDate', () => { it('should work', () => { - const readable = gl.utils.getTimeago().format(deployment.deployed_at); + const readable = getTimeago().format(deployment.deployed_at); expect(vm.formatDate(deployment.deployed_at)).toEqual(readable); }); }); diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index 9e6d0aa472c..74b343c573e 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import mrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options'; import eventHub from '~/vue_merge_request_widget/event_hub'; import notify from '~/lib/utils/notify'; +import { stateKey } from '~/vue_merge_request_widget/stores/state_maps'; import mockData from './mock_data'; import mountComponent from '../helpers/vue_mount_component_helper'; @@ -344,4 +345,31 @@ describe('mrWidgetOptions', () => { expect(comps['mr-widget-merge-when-pipeline-succeeds']).toBeDefined(); }); }); + + describe('rendering relatedLinks', () => { + beforeEach((done) => { + vm.mr.relatedLinks = { + assignToMe: null, + closing: ` + <a class="close-related-link" href="#'> + Close + </a> + `, + mentioned: '', + }; + Vue.nextTick(done); + }); + + it('renders if there are relatedLinks', () => { + expect(vm.$el.querySelector('.close-related-link')).toBeDefined(); + }); + + it('does not render if state is nothingToMerge', (done) => { + vm.mr.state = stateKey.nothingToMerge; + Vue.nextTick(() => { + expect(vm.$el.querySelector('.close-related-link')).toBeNull(); + done(); + }); + }); + }); }); diff --git a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js index 8e5614b20f0..33d052aceb2 100644 --- a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js +++ b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js @@ -1,4 +1,5 @@ import MergeRequestStore from '~/vue_merge_request_widget/stores/mr_widget_store'; +import { stateKey } from '~/vue_merge_request_widget/stores/state_maps'; import mockData from '../mock_data'; describe('MergeRequestStore', () => { @@ -52,5 +53,17 @@ describe('MergeRequestStore', () => { expect(store.isPipelineSkipped).toBe(false); }); }); + + describe('isNothingToMergeState', () => { + it('returns true when nothingToMerge', () => { + store.state = stateKey.nothingToMerge; + expect(store.isNothingToMergeState).toEqual(true); + }); + + it('returns false when not nothingToMerge', () => { + store.state = 'state'; + expect(store.isNothingToMergeState).toEqual(false); + }); + }); }); }); diff --git a/spec/javascripts/vue_shared/components/popup_dialog_spec.js b/spec/javascripts/vue_shared/components/modal_spec.js index 5c1d2a196f4..721f4044659 100644 --- a/spec/javascripts/vue_shared/components/popup_dialog_spec.js +++ b/spec/javascripts/vue_shared/components/modal_spec.js @@ -1,11 +1,11 @@ import Vue from 'vue'; -import PopupDialog from '~/vue_shared/components/popup_dialog.vue'; +import modal from '~/vue_shared/components/modal.vue'; import mountComponent from '../../helpers/vue_mount_component_helper'; -describe('PopupDialog', () => { +describe('Modal', () => { it('does not render a primary button if no primaryButtonLabel', () => { - const popupDialog = Vue.extend(PopupDialog); - const vm = mountComponent(popupDialog); + const modalComponent = Vue.extend(modal); + const vm = mountComponent(modalComponent); expect(vm.$el.querySelector('.js-primary-button')).toBeNull(); }); diff --git a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js index b4c1f70ed1e..b4fb568f1d4 100644 --- a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js +++ b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import timeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; -import '~/lib/utils/datetime_utility'; +import { formatDate, getTimeago } from '~/lib/utils/datetime_utility'; describe('Time ago with tooltip component', () => { let TimeagoTooltip; @@ -24,10 +24,10 @@ describe('Time ago with tooltip component', () => { expect(vm.$el.tagName).toEqual('TIME'); expect( vm.$el.getAttribute('data-original-title'), - ).toEqual(gl.utils.formatDate('2017-05-08T14:57:39.781Z')); + ).toEqual(formatDate('2017-05-08T14:57:39.781Z')); expect(vm.$el.getAttribute('data-placement')).toEqual('top'); - const timeago = gl.utils.getTimeago(); + const timeago = getTimeago(); expect(vm.$el.textContent.trim()).toEqual(timeago.format('2017-05-08T14:57:39.781Z')); }); diff --git a/spec/javascripts/vue_shared/components/toggle_button_spec.js b/spec/javascripts/vue_shared/components/toggle_button_spec.js index 447d74d4e08..859995d33fa 100644 --- a/spec/javascripts/vue_shared/components/toggle_button_spec.js +++ b/spec/javascripts/vue_shared/components/toggle_button_spec.js @@ -30,9 +30,9 @@ describe('Toggle Button', () => { expect(vm.$el.querySelector('input').getAttribute('value')).toEqual('true'); }); - it('renders Enabled and Disabled text data attributes', () => { - expect(vm.$el.querySelector('button').getAttribute('data-enabled-text')).toEqual('Enabled'); - expect(vm.$el.querySelector('button').getAttribute('data-disabled-text')).toEqual('Disabled'); + it('renders input status icon', () => { + expect(vm.$el.querySelectorAll('span.toggle-icon').length).toEqual(1); + expect(vm.$el.querySelectorAll('svg.s16.toggle-icon-svg').length).toEqual(1); }); }); @@ -49,6 +49,14 @@ describe('Toggle Button', () => { expect(vm.$el.querySelector('button').classList.contains('is-checked')).toEqual(true); }); + it('sets aria-label representing toggle state', () => { + vm.value = true; + expect(vm.ariaLabel).toEqual('Toggle Status: ON'); + + vm.value = false; + expect(vm.ariaLabel).toEqual('Toggle Status: OFF'); + }); + it('emits change event when clicked', () => { vm.$el.querySelector('button').click(); diff --git a/spec/lib/gitlab/action_rate_limiter_spec.rb b/spec/lib/gitlab/action_rate_limiter_spec.rb new file mode 100644 index 00000000000..542fc03e555 --- /dev/null +++ b/spec/lib/gitlab/action_rate_limiter_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe Gitlab::ActionRateLimiter do + let(:redis) { double('redis') } + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:key) { [user, project] } + let(:cache_key) { "action_rate_limiter:test_action:user:#{user.id}:project:#{project.id}" } + + subject { described_class.new(action: :test_action, expiry_time: 100) } + + before do + allow(Gitlab::Redis::Cache).to receive(:with).and_yield(redis) + end + + it 'increases the throttle count and sets the expire time' do + expect(redis).to receive(:incr).with(cache_key).and_return(1) + expect(redis).to receive(:expire).with(cache_key, 100) + + expect(subject.throttled?(key, 1)).to be false + end + + it 'returns true if the key is throttled' do + expect(redis).to receive(:incr).with(cache_key).and_return(2) + expect(redis).not_to receive(:expire) + + expect(subject.throttled?(key, 1)).to be true + end +end diff --git a/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb b/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb index 79d2c071446..e1c4f9cfea7 100644 --- a/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb +++ b/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb @@ -2,19 +2,20 @@ require 'spec_helper' describe Gitlab::BackgroundMigration::CreateForkNetworkMembershipsRange, :migration, schema: 20170929131201 do let(:migration) { described_class.new } + let(:projects) { table(:projects) } - let(:base1) { create(:project) } - let(:base1_fork1) { create(:project) } - let(:base1_fork2) { create(:project) } + let(:base1) { projects.create } + let(:base1_fork1) { projects.create } + let(:base1_fork2) { projects.create } - let(:base2) { create(:project) } - let(:base2_fork1) { create(:project) } - let(:base2_fork2) { create(:project) } + let(:base2) { projects.create } + let(:base2_fork1) { projects.create } + let(:base2_fork2) { projects.create } - let(:fork_of_fork) { create(:project) } - let(:fork_of_fork2) { create(:project) } - let(:second_level_fork) { create(:project) } - let(:third_level_fork) { create(:project) } + let(:fork_of_fork) { projects.create } + let(:fork_of_fork2) { projects.create } + let(:second_level_fork) { projects.create } + let(:third_level_fork) { projects.create } let(:fork_network1) { fork_networks.find_by(root_project_id: base1.id) } let(:fork_network2) { fork_networks.find_by(root_project_id: base2.id) } @@ -97,7 +98,7 @@ describe Gitlab::BackgroundMigration::CreateForkNetworkMembershipsRange, :migrat end it 'does not miss members for forks of forks for which the root was deleted' do - forked_project_links.create(id: 9, forked_from_project_id: base1_fork1.id, forked_to_project_id: create(:project).id) + forked_project_links.create(id: 9, forked_from_project_id: base1_fork1.id, forked_to_project_id: projects.create.id) base1.destroy expect(migration.missing_members?(7, 10)).to be_falsy @@ -105,8 +106,8 @@ describe Gitlab::BackgroundMigration::CreateForkNetworkMembershipsRange, :migrat context 'with more forks' do before do - forked_project_links.create(id: 9, forked_from_project_id: fork_of_fork.id, forked_to_project_id: create(:project).id) - forked_project_links.create(id: 10, forked_from_project_id: fork_of_fork.id, forked_to_project_id: create(:project).id) + forked_project_links.create(id: 9, forked_from_project_id: fork_of_fork.id, forked_to_project_id: projects.create.id) + forked_project_links.create(id: 10, forked_from_project_id: fork_of_fork.id, forked_to_project_id: projects.create.id) end it 'only processes a single batch of links at a time' do diff --git a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb index cb52d971047..7351d45336a 100644 --- a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb @@ -225,9 +225,10 @@ describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migrati let(:user_class) { table(:users) } let(:author) { build(:user).becomes(user_class).tap(&:save!).becomes(User) } let(:namespace) { create(:namespace, owner: author) } - let(:project) { create(:project_empty_repo, namespace: namespace, creator: author) } + let(:projects) { table(:projects) } + let(:project) { projects.create(namespace_id: namespace.id, creator_id: author.id) } - # We can not rely on FactoryGirl as the state of Event may change in ways that + # We can not rely on FactoryBot as the state of Event may change in ways that # the background migration does not expect, hence we use the Event class of # the migration itself. def create_push_event(project, author, data = nil) diff --git a/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb b/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb index e52baf8dde7..8582af96199 100644 --- a/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb @@ -2,10 +2,11 @@ require 'spec_helper' describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, schema: 20170929131201 do let(:migration) { described_class.new } - let(:base1) { create(:project) } + let(:projects) { table(:projects) } + let(:base1) { projects.create } - let(:base2) { create(:project) } - let(:base2_fork1) { create(:project) } + let(:base2) { projects.create } + let(:base2_fork1) { projects.create } let!(:forked_project_links) { table(:forked_project_links) } let!(:fork_networks) { table(:fork_networks) } @@ -18,10 +19,10 @@ describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, sch # A normal fork link forked_project_links.create(id: 1, forked_from_project_id: base1.id, - forked_to_project_id: create(:project).id) + forked_to_project_id: projects.create.id) forked_project_links.create(id: 2, forked_from_project_id: base1.id, - forked_to_project_id: create(:project).id) + forked_to_project_id: projects.create.id) forked_project_links.create(id: 3, forked_from_project_id: base2.id, forked_to_project_id: base2_fork1.id) @@ -29,10 +30,10 @@ describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, sch # create a fork of a fork forked_project_links.create(id: 4, forked_from_project_id: base2_fork1.id, - forked_to_project_id: create(:project).id) + forked_to_project_id: projects.create.id) forked_project_links.create(id: 5, - forked_from_project_id: create(:project).id, - forked_to_project_id: create(:project).id) + forked_from_project_id: projects.create.id, + forked_to_project_id: projects.create.id) # Stub out the calls to the other migrations allow(BackgroundMigrationWorker).to receive(:perform_in) @@ -63,7 +64,7 @@ describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, sch end it 'creates a fork network for the fork of which the source was deleted' do - fork = create(:project) + fork = projects.create forked_project_links.create(id: 6, forked_from_project_id: 99999, forked_to_project_id: fork.id) migration.perform(5, 8) diff --git a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb index 0560c47f03f..3fe0493ed9b 100644 --- a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb @@ -23,6 +23,8 @@ describe Gitlab::CycleAnalytics::BaseEventFetcher do allow_any_instance_of(described_class).to receive(:serialize) do |event| event end + allow_any_instance_of(described_class) + .to receive(:allowed_ids).and_return(nil) stub_const('Gitlab::CycleAnalytics::BaseEventFetcher::MAX_EVENTS', max_events) diff --git a/spec/lib/gitlab/git/gitlab_projects_spec.rb b/spec/lib/gitlab/git/gitlab_projects_spec.rb new file mode 100644 index 00000000000..24da9589458 --- /dev/null +++ b/spec/lib/gitlab/git/gitlab_projects_spec.rb @@ -0,0 +1,311 @@ +require 'spec_helper' + +describe Gitlab::Git::GitlabProjects do + after do + TestEnv.clean_test_path + end + + let(:project) { create(:project, :repository) } + + if $VERBOSE + let(:logger) { Logger.new(STDOUT) } + else + let(:logger) { double('logger').as_null_object } + end + + let(:tmp_repos_path) { TestEnv.repos_path } + let(:repo_name) { project.disk_path + '.git' } + let(:tmp_repo_path) { File.join(tmp_repos_path, repo_name) } + let(:gl_projects) { build_gitlab_projects(tmp_repos_path, repo_name) } + + describe '#initialize' do + it { expect(gl_projects.shard_path).to eq(tmp_repos_path) } + it { expect(gl_projects.repository_relative_path).to eq(repo_name) } + it { expect(gl_projects.repository_absolute_path).to eq(File.join(tmp_repos_path, repo_name)) } + it { expect(gl_projects.logger).to eq(logger) } + end + + describe '#mv_project' do + let(:new_repo_path) { File.join(tmp_repos_path, 'repo.git') } + + it 'moves a repo directory' do + expect(File.exist?(tmp_repo_path)).to be_truthy + + message = "Moving repository from <#{tmp_repo_path}> to <#{new_repo_path}>." + expect(logger).to receive(:info).with(message) + + expect(gl_projects.mv_project('repo.git')).to be_truthy + + expect(File.exist?(tmp_repo_path)).to be_falsy + expect(File.exist?(new_repo_path)).to be_truthy + end + + it "fails if the source path doesn't exist" do + expected_source_path = File.join(tmp_repos_path, 'bad-src.git') + expect(logger).to receive(:error).with("mv-project failed: source path <#{expected_source_path}> does not exist.") + + result = build_gitlab_projects(tmp_repos_path, 'bad-src.git').mv_project('repo.git') + expect(result).to be_falsy + end + + it 'fails if the destination path already exists' do + FileUtils.mkdir_p(File.join(tmp_repos_path, 'already-exists.git')) + + expected_distination_path = File.join(tmp_repos_path, 'already-exists.git') + message = "mv-project failed: destination path <#{expected_distination_path}> already exists." + expect(logger).to receive(:error).with(message) + + expect(gl_projects.mv_project('already-exists.git')).to be_falsy + end + end + + describe '#rm_project' do + it 'removes a repo directory' do + expect(File.exist?(tmp_repo_path)).to be_truthy + expect(logger).to receive(:info).with("Removing repository <#{tmp_repo_path}>.") + + expect(gl_projects.rm_project).to be_truthy + + expect(File.exist?(tmp_repo_path)).to be_falsy + end + end + + describe '#push_branches' do + let(:remote_name) { 'remote-name' } + let(:branch_name) { 'master' } + let(:cmd) { %W(git push -- #{remote_name} #{branch_name}) } + let(:force) { false } + + subject { gl_projects.push_branches(remote_name, 600, force, [branch_name]) } + + it 'executes the command' do + stub_spawn(cmd, 600, tmp_repo_path, success: true) + + is_expected.to be_truthy + end + + it 'fails' do + stub_spawn(cmd, 600, tmp_repo_path, success: false) + + is_expected.to be_falsy + end + + context 'with --force' do + let(:cmd) { %W(git push --force -- #{remote_name} #{branch_name}) } + let(:force) { true } + + it 'executes the command' do + stub_spawn(cmd, 600, tmp_repo_path, success: true) + + is_expected.to be_truthy + end + end + end + + describe '#fetch_remote' do + let(:remote_name) { 'remote-name' } + let(:branch_name) { 'master' } + let(:force) { false } + let(:tags) { true } + let(:args) { { force: force, tags: tags }.merge(extra_args) } + let(:extra_args) { {} } + let(:cmd) { %W(git fetch #{remote_name} --prune --quiet --tags) } + + subject { gl_projects.fetch_remote(remote_name, 600, args) } + + def stub_tempfile(name, filename, opts = {}) + chmod = opts.delete(:chmod) + file = StringIO.new + + allow(file).to receive(:close!) + allow(file).to receive(:path).and_return(name) + + expect(Tempfile).to receive(:new).with(filename).and_return(file) + expect(file).to receive(:chmod).with(chmod) if chmod + + file + end + + context 'with default args' do + it 'executes the command' do + stub_spawn(cmd, 600, tmp_repo_path, {}, success: true) + + is_expected.to be_truthy + end + + it 'fails' do + stub_spawn(cmd, 600, tmp_repo_path, {}, success: false) + + is_expected.to be_falsy + end + end + + context 'with --force' do + let(:force) { true } + let(:cmd) { %W(git fetch #{remote_name} --prune --quiet --force --tags) } + + it 'executes the command with forced option' do + stub_spawn(cmd, 600, tmp_repo_path, {}, success: true) + + is_expected.to be_truthy + end + end + + context 'with --no-tags' do + let(:tags) { false } + let(:cmd) { %W(git fetch #{remote_name} --prune --quiet --no-tags) } + + it 'executes the command' do + stub_spawn(cmd, 600, tmp_repo_path, {}, success: true) + + is_expected.to be_truthy + end + end + + describe 'with an SSH key' do + let(:extra_args) { { ssh_key: 'SSH KEY' } } + + it 'sets GIT_SSH to a custom script' do + script = stub_tempfile('scriptFile', 'gitlab-shell-ssh-wrapper', chmod: 0o755) + key = stub_tempfile('/tmp files/keyFile', 'gitlab-shell-key-file', chmod: 0o400) + + stub_spawn(cmd, 600, tmp_repo_path, { 'GIT_SSH' => 'scriptFile' }, success: true) + + is_expected.to be_truthy + + expect(script.string).to eq("#!/bin/sh\nexec ssh '-oIdentityFile=\"/tmp files/keyFile\"' '-oIdentitiesOnly=\"yes\"' \"$@\"") + expect(key.string).to eq('SSH KEY') + end + end + + describe 'with known_hosts data' do + let(:extra_args) { { known_hosts: 'KNOWN HOSTS' } } + + it 'sets GIT_SSH to a custom script' do + script = stub_tempfile('scriptFile', 'gitlab-shell-ssh-wrapper', chmod: 0o755) + key = stub_tempfile('/tmp files/knownHosts', 'gitlab-shell-known-hosts', chmod: 0o400) + + stub_spawn(cmd, 600, tmp_repo_path, { 'GIT_SSH' => 'scriptFile' }, success: true) + + is_expected.to be_truthy + + expect(script.string).to eq("#!/bin/sh\nexec ssh '-oStrictHostKeyChecking=\"yes\"' '-oUserKnownHostsFile=\"/tmp files/knownHosts\"' \"$@\"") + expect(key.string).to eq('KNOWN HOSTS') + end + end + end + + describe '#import_project' do + let(:project) { create(:project) } + let(:import_url) { TestEnv.factory_repo_path_bare } + let(:cmd) { %W(git clone --bare -- #{import_url} #{tmp_repo_path}) } + let(:timeout) { 600 } + + subject { gl_projects.import_project(import_url, timeout) } + + context 'success import' do + it 'imports a repo' do + expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy + + message = "Importing project from <#{import_url}> to <#{tmp_repo_path}>." + expect(logger).to receive(:info).with(message) + + is_expected.to be_truthy + + expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_truthy + end + end + + context 'already exists' do + it "doesn't import" do + FileUtils.mkdir_p(tmp_repo_path) + + is_expected.to be_falsy + end + end + + context 'timeout' do + it 'does not import a repo' do + stub_spawn_timeout(cmd, timeout, nil) + + message = "Importing project from <#{import_url}> to <#{tmp_repo_path}> failed." + expect(logger).to receive(:error).with(message) + + is_expected.to be_falsy + + expect(gl_projects.output).to eq("Timed out\n") + expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy + end + end + end + + describe '#fork_repository' do + let(:dest_repos_path) { tmp_repos_path } + let(:dest_repo_name) { File.join('@hashed', 'aa', 'bb', 'xyz.git') } + let(:dest_repo) { File.join(dest_repos_path, dest_repo_name) } + let(:dest_namespace) { File.dirname(dest_repo) } + + subject { gl_projects.fork_repository(dest_repos_path, dest_repo_name) } + + before do + FileUtils.mkdir_p(dest_repos_path) + end + + after do + FileUtils.rm_rf(dest_repos_path) + end + + it 'forks the repository' do + message = "Forking repository from <#{tmp_repo_path}> to <#{dest_repo}>." + expect(logger).to receive(:info).with(message) + + is_expected.to be_truthy + + expect(File.exist?(dest_repo)).to be_truthy + expect(File.exist?(File.join(dest_repo, 'hooks', 'pre-receive'))).to be_truthy + expect(File.exist?(File.join(dest_repo, 'hooks', 'post-receive'))).to be_truthy + end + + it 'does not fork if a project of the same name already exists' do + # create a fake project at the intended destination + FileUtils.mkdir_p(dest_repo) + + # trying to fork again should fail as the repo already exists + message = "fork-repository failed: destination repository <#{dest_repo}> already exists." + expect(logger).to receive(:error).with(message) + + is_expected.to be_falsy + end + + context 'different storages' do + let(:dest_repos_path) { File.join(File.dirname(tmp_repos_path), 'alternative') } + + it 'forks the repo' do + is_expected.to be_truthy + + expect(File.exist?(dest_repo)).to be_truthy + expect(File.exist?(File.join(dest_repo, 'hooks', 'pre-receive'))).to be_truthy + expect(File.exist?(File.join(dest_repo, 'hooks', 'post-receive'))).to be_truthy + end + end + end + + def build_gitlab_projects(*args) + described_class.new( + *args, + global_hooks_path: Gitlab.config.gitlab_shell.hooks_path, + logger: logger + ) + end + + def stub_spawn(*args, success: true) + exitstatus = success ? 0 : nil + expect(gl_projects).to receive(:popen_with_timeout).with(*args) + .and_return(["output", exitstatus]) + end + + def stub_spawn_timeout(*args) + expect(gl_projects).to receive(:popen_with_timeout).with(*args) + .and_raise(Timeout::Error) + end +end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index e6845420f7d..03a9cc488ca 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -19,6 +19,51 @@ describe Gitlab::Git::Repository, seed_helper: true do let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } + describe '.create_hooks' do + let(:repo_path) { File.join(TestEnv.repos_path, 'hook-test.git') } + let(:hooks_dir) { File.join(repo_path, 'hooks') } + let(:target_hooks_dir) { Gitlab.config.gitlab_shell.hooks_path } + let(:existing_target) { File.join(repo_path, 'foobar') } + + before do + FileUtils.rm_rf(repo_path) + FileUtils.mkdir_p(repo_path) + end + + context 'hooks is a directory' do + let(:existing_file) { File.join(hooks_dir, 'my-file') } + + before do + FileUtils.mkdir_p(hooks_dir) + FileUtils.touch(existing_file) + described_class.create_hooks(repo_path, target_hooks_dir) + end + + it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) } + it { expect(Dir[File.join(repo_path, "hooks.old.*/my-file")].count).to eq(1) } + end + + context 'hooks is a valid symlink' do + before do + FileUtils.mkdir_p existing_target + File.symlink(existing_target, hooks_dir) + described_class.create_hooks(repo_path, target_hooks_dir) + end + + it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) } + end + + context 'hooks is a broken symlink' do + before do + FileUtils.rm_f(existing_target) + File.symlink(existing_target, hooks_dir) + described_class.create_hooks(repo_path, target_hooks_dir) + end + + it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) } + end + end + describe "Respond to" do subject { repository } diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb index 168e5d07504..46a57e08963 100644 --- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb @@ -70,7 +70,7 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do describe '#execute' do it 'imports the repository and wiki' do - expect(repository) + expect(project) .to receive(:empty_repo?) .and_return(true) @@ -93,7 +93,7 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do end it 'does not import the repository if it already exists' do - expect(repository) + expect(project) .to receive(:empty_repo?) .and_return(false) @@ -115,7 +115,7 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do end it 'does not import the wiki if it is disabled' do - expect(repository) + expect(project) .to receive(:empty_repo?) .and_return(true) @@ -137,7 +137,7 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do end it 'does not import the wiki if the repository could not be imported' do - expect(repository) + expect(project) .to receive(:empty_repo?) .and_return(true) diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb index 260df6e4dae..048caa38fcf 100644 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -38,7 +38,6 @@ describe Gitlab::LDAP::User do it "does not mark existing ldap user as changed" do create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', provider: 'ldapmain') - ldap_user.gl_user.user_synced_attributes_metadata(provider: 'ldapmain', email: true) expect(ldap_user.changed?).to be_falsey end end @@ -144,11 +143,15 @@ describe Gitlab::LDAP::User do expect(ldap_user.gl_user.email).to eq(info[:email]) end - it "has user_synced_attributes_metadata email set to true" do + it "has email set as synced" do expect(ldap_user.gl_user.user_synced_attributes_metadata.email_synced).to be_truthy end - it "has synced_attribute_provider set to ldapmain" do + it "has email set as read-only" do + expect(ldap_user.gl_user.read_only_attribute?(:email)).to be_truthy + end + + it "has synced attributes provider set to ldapmain" do expect(ldap_user.gl_user.user_synced_attributes_metadata.provider).to eql 'ldapmain' end end @@ -162,9 +165,13 @@ describe Gitlab::LDAP::User do expect(ldap_user.gl_user.temp_oauth_email?).to be_truthy end - it "has synced attribute email set to false" do + it "has email set as not synced" do expect(ldap_user.gl_user.user_synced_attributes_metadata.email_synced).to be_falsey end + + it "does not have email set as read-only" do + expect(ldap_user.gl_user.read_only_attribute?(:email)).to be_falsey + end end end diff --git a/spec/lib/gitlab/metrics/method_call_spec.rb b/spec/lib/gitlab/metrics/method_call_spec.rb index 5341addf911..41a9d1d9c90 100644 --- a/spec/lib/gitlab/metrics/method_call_spec.rb +++ b/spec/lib/gitlab/metrics/method_call_spec.rb @@ -20,9 +20,39 @@ describe Gitlab::Metrics::MethodCall do context 'prometheus instrumentation is enabled' do before do + allow(Feature.get(:prometheus_metrics_method_instrumentation)).to receive(:enabled?).and_call_original + described_class.measurement_enabled_cache_expires_at.value = Time.now.to_i - 1 Feature.get(:prometheus_metrics_method_instrumentation).enable end + around do |example| + Timecop.freeze do + example.run + end + end + + it 'caches subsequent invocations of feature check' do + 10.times do + method_call.measure { 'foo' } + end + + expect(Feature.get(:prometheus_metrics_method_instrumentation)).to have_received(:enabled?).once + end + + it 'expires feature check cache after 1 minute' do + method_call.measure { 'foo' } + + Timecop.travel(1.minute.from_now) do + method_call.measure { 'foo' } + end + + Timecop.travel(1.minute.from_now + 1.second) do + method_call.measure { 'foo' } + end + + expect(Feature.get(:prometheus_metrics_method_instrumentation)).to have_received(:enabled?).twice + end + it 'observes the performance of the supplied block' do expect(described_class.call_duration_histogram) .to receive(:observe) @@ -34,6 +64,8 @@ describe Gitlab::Metrics::MethodCall do context 'prometheus instrumentation is disabled' do before do + described_class.measurement_enabled_cache_expires_at.value = Time.now.to_i - 1 + Feature.get(:prometheus_metrics_method_instrumentation).disable end @@ -64,14 +96,17 @@ describe Gitlab::Metrics::MethodCall do describe '#to_metric' do it 'returns a Metric instance' do + expect(method_call).to receive(:real_time).and_return(4.0001) + expect(method_call).to receive(:cpu_time).and_return(3.0001) + method_call.measure { 'foo' } metric = method_call.to_metric expect(metric).to be_an_instance_of(Gitlab::Metrics::Metric) expect(metric.series).to eq('rails_method_calls') - expect(metric.values[:duration]).to be_a_kind_of(Numeric) - expect(metric.values[:cpu_duration]).to be_a_kind_of(Numeric) + expect(metric.values[:duration]).to eq(4000) + expect(metric.values[:cpu_duration]).to eq(3000) expect(metric.values[:call_count]).to be_an(Integer) expect(metric.tags).to eq({ method: 'Foo#bar' }) @@ -84,13 +119,13 @@ describe Gitlab::Metrics::MethodCall do end it 'returns false when the total call time is not above the threshold' do - expect(method_call).to receive(:real_time).and_return(9) + expect(method_call).to receive(:real_time).and_return(0.009) expect(method_call.above_threshold?).to eq(false) end it 'returns true when the total call time is above the threshold' do - expect(method_call).to receive(:real_time).and_return(9000) + expect(method_call).to receive(:real_time).and_return(9) expect(method_call.above_threshold?).to eq(true) end diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb index 4d94d8705fb..14afcdf5daa 100644 --- a/spec/lib/gitlab/metrics/system_spec.rb +++ b/spec/lib/gitlab/metrics/system_spec.rb @@ -29,19 +29,19 @@ describe Gitlab::Metrics::System do describe '.cpu_time' do it 'returns a Fixnum' do - expect(described_class.cpu_time).to be_an(Integer) + expect(described_class.cpu_time).to be_an(Float) end end describe '.real_time' do it 'returns a Fixnum' do - expect(described_class.real_time).to be_an(Integer) + expect(described_class.real_time).to be_an(Float) end end describe '.monotonic_time' do - it 'returns a Fixnum' do - expect(described_class.monotonic_time).to be_an(Integer) + it 'returns a Float' do + expect(described_class.monotonic_time).to be_an(Float) end end end diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb index 2f19fb7312d..6334bcd0156 100644 --- a/spec/lib/gitlab/o_auth/user_spec.rb +++ b/spec/lib/gitlab/o_auth/user_spec.rb @@ -202,11 +202,13 @@ describe Gitlab::OAuth::User do end context "and no account for the LDAP user" do - it "creates a user with dual LDAP and omniauth identities" do + before do allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) oauth_user.save + end + it "creates a user with dual LDAP and omniauth identities" do expect(gl_user).to be_valid expect(gl_user.username).to eql uid expect(gl_user.email).to eql 'johndoe@example.com' @@ -219,6 +221,18 @@ describe Gitlab::OAuth::User do ] ) end + + it "has email set as synced" do + expect(gl_user.user_synced_attributes_metadata.email_synced).to be_truthy + end + + it "has email set as read-only" do + expect(gl_user.read_only_attribute?(:email)).to be_truthy + end + + it "has synced attributes provider set to ldapmain" do + expect(gl_user.user_synced_attributes_metadata.provider).to eql 'ldapmain' + end end context "and LDAP user has an account already" do @@ -440,11 +454,15 @@ describe Gitlab::OAuth::User do expect(gl_user.email).to eq(info_hash[:email]) end - it "has external_attributes set to true" do - expect(gl_user.user_synced_attributes_metadata).not_to be_nil + it "has email set as synced" do + expect(gl_user.user_synced_attributes_metadata.email_synced).to be_truthy + end + + it "has email set as read-only" do + expect(gl_user.read_only_attribute?(:email)).to be_truthy end - it "has attributes_provider set to my-provider" do + it "has synced attributes provider set to my-provider" do expect(gl_user.user_synced_attributes_metadata.provider).to eql 'my-provider' end end @@ -458,10 +476,13 @@ describe Gitlab::OAuth::User do expect(gl_user.email).not_to eq(info_hash[:email]) end - it "has user_synced_attributes_metadata set to nil" do - expect(gl_user.user_synced_attributes_metadata.provider).to eql 'my-provider' + it "has email set as not synced" do expect(gl_user.user_synced_attributes_metadata.email_synced).to be_falsey end + + it "does not have email set as read-only" do + expect(gl_user.read_only_attribute?(:email)).to be_falsey + end end end @@ -508,11 +529,15 @@ describe Gitlab::OAuth::User do expect(gl_user.email).to eq(info_hash[:email]) end - it "has email_synced_attribute set to true" do + it "has email set as synced" do expect(gl_user.user_synced_attributes_metadata.email_synced).to be(true) end - it "has my-provider as attributes_provider" do + it "has email set as read-only" do + expect(gl_user.read_only_attribute?(:email)).to be_truthy + end + + it "has synced attributes provider set to my-provider" do expect(gl_user.user_synced_attributes_metadata.provider).to eql 'my-provider' end end @@ -524,7 +549,14 @@ describe Gitlab::OAuth::User do it "does not update the user email" do expect(gl_user.email).not_to eq(info_hash[:email]) - expect(gl_user.user_synced_attributes_metadata.email_synced).to be(false) + end + + it "has email set as not synced" do + expect(gl_user.user_synced_attributes_metadata.email_synced).to be_falsey + end + + it "does not have email set as read-only" do + expect(gl_user.read_only_attribute?(:email)).to be_falsey end end end diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index eec6858a5de..dd779b04741 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -2,12 +2,19 @@ require 'spec_helper' require 'stringio' describe Gitlab::Shell do - let(:project) { double('Project', id: 7, path: 'diaspora') } + set(:project) { create(:project, :repository) } + let(:gitlab_shell) { described_class.new } let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } } + let(:gitlab_projects) { double('gitlab_projects') } + let(:timeout) { Gitlab.config.gitlab_shell.git_timeout } before do allow(Project).to receive(:find).and_return(project) + + allow(gitlab_shell).to receive(:gitlab_projects) + .with(project.repository_storage_path, project.disk_path + '.git') + .and_return(gitlab_projects) end it { is_expected.to respond_to :add_key } @@ -44,38 +51,6 @@ describe Gitlab::Shell do end end - describe 'projects commands' do - let(:gitlab_shell_path) { File.expand_path('tmp/tests/gitlab-shell') } - let(:projects_path) { File.join(gitlab_shell_path, 'bin/gitlab-projects') } - let(:gitlab_shell_hooks_path) { File.join(gitlab_shell_path, 'hooks') } - - before do - allow(Gitlab.config.gitlab_shell).to receive(:path).and_return(gitlab_shell_path) - allow(Gitlab.config.gitlab_shell).to receive(:hooks_path).and_return(gitlab_shell_hooks_path) - allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800) - end - - describe '#mv_repository' do - it 'executes the command' do - expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with( - [projects_path, 'mv-project', 'storage/path', 'project/path.git', 'new/path.git'] - ) - gitlab_shell.mv_repository('storage/path', 'project/path', 'new/path') - end - end - - describe '#add_key' do - it 'removes trailing garbage' do - allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) - expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with( - [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar'] - ) - - gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') - end - end - end - describe Gitlab::Shell::KeyAdder do describe '#add_key' do it 'removes trailing garbage' do @@ -121,6 +96,17 @@ describe Gitlab::Shell do allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800) end + describe '#add_key' do + it 'removes trailing garbage' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with( + [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar'] + ) + + gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') + end + end + describe '#add_repository' do shared_examples '#add_repository' do let(:repository_storage) { 'default' } @@ -162,83 +148,76 @@ describe Gitlab::Shell do end describe '#remove_repository' do + subject { gitlab_shell.remove_repository(project.repository_storage_path, project.disk_path) } + it 'returns true when the command succeeds' do - expect(Gitlab::Popen).to receive(:popen) - .with([projects_path, 'rm-project', 'current/storage', 'project/path.git'], - nil, popen_vars).and_return([nil, 0]) + expect(gitlab_projects).to receive(:rm_project) { true } - expect(gitlab_shell.remove_repository('current/storage', 'project/path')).to be true + is_expected.to be_truthy end it 'returns false when the command fails' do - expect(Gitlab::Popen).to receive(:popen) - .with([projects_path, 'rm-project', 'current/storage', 'project/path.git'], - nil, popen_vars).and_return(["error", 1]) + expect(gitlab_projects).to receive(:rm_project) { false } - expect(gitlab_shell.remove_repository('current/storage', 'project/path')).to be false + is_expected.to be_falsy end end describe '#mv_repository' do it 'returns true when the command succeeds' do - expect(Gitlab::Popen).to receive(:popen) - .with([projects_path, 'mv-project', 'current/storage', 'project/path.git', 'project/newpath.git'], - nil, popen_vars).and_return([nil, 0]) + expect(gitlab_projects).to receive(:mv_project).with('project/newpath.git') { true } - expect(gitlab_shell.mv_repository('current/storage', 'project/path', 'project/newpath')).to be true + expect(gitlab_shell.mv_repository(project.repository_storage_path, project.disk_path, 'project/newpath')).to be_truthy end it 'returns false when the command fails' do - expect(Gitlab::Popen).to receive(:popen) - .with([projects_path, 'mv-project', 'current/storage', 'project/path.git', 'project/newpath.git'], - nil, popen_vars).and_return(["error", 1]) + expect(gitlab_projects).to receive(:mv_project).with('project/newpath.git') { false } - expect(gitlab_shell.mv_repository('current/storage', 'project/path', 'project/newpath')).to be false + expect(gitlab_shell.mv_repository(project.repository_storage_path, project.disk_path, 'project/newpath')).to be_falsy end end describe '#fork_repository' do + subject do + gitlab_shell.fork_repository( + project.repository_storage_path, + project.disk_path, + 'new/storage', + 'fork/path' + ) + end + it 'returns true when the command succeeds' do - expect(Gitlab::Popen).to receive(:popen) - .with([projects_path, 'fork-repository', 'current/storage', 'project/path.git', 'new/storage', 'fork/path.git'], - nil, popen_vars).and_return([nil, 0]) + expect(gitlab_projects).to receive(:fork_repository).with('new/storage', 'fork/path.git') { true } - expect(gitlab_shell.fork_repository('current/storage', 'project/path', 'new/storage', 'fork/path')).to be true + is_expected.to be_truthy end it 'return false when the command fails' do - expect(Gitlab::Popen).to receive(:popen) - .with([projects_path, 'fork-repository', 'current/storage', 'project/path.git', 'new/storage', 'fork/path.git'], - nil, popen_vars).and_return(["error", 1]) + expect(gitlab_projects).to receive(:fork_repository).with('new/storage', 'fork/path.git') { false } - expect(gitlab_shell.fork_repository('current/storage', 'project/path', 'new/storage', 'fork/path')).to be false + is_expected.to be_falsy end end shared_examples 'fetch_remote' do |gitaly_on| - let(:project2) { create(:project, :repository) } - let(:repository) { project2.repository } + let(:repository) { project.repository } def fetch_remote(ssh_auth = nil) - gitlab_shell.fetch_remote(repository.raw_repository, 'new/storage', ssh_auth: ssh_auth) + gitlab_shell.fetch_remote(repository.raw_repository, 'remote-name', ssh_auth: ssh_auth) end - def expect_popen(fail = false, vars = {}) - popen_args = [ - projects_path, - 'fetch-remote', - TestEnv.repos_path, - repository.relative_path, - 'new/storage', - Gitlab.config.gitlab_shell.git_timeout.to_s - ] - - return_value = fail ? ["error", 1] : [nil, 0] + def expect_gitlab_projects(fail = false, options = {}) + expect(gitlab_projects).to receive(:fetch_remote).with( + 'remote-name', + timeout, + options + ).and_return(!fail) - expect(Gitlab::Popen).to receive(:popen).with(popen_args, nil, popen_vars.merge(vars)).and_return(return_value) + allow(gitlab_projects).to receive(:output).and_return('error') if fail end - def expect_gitaly_call(fail, vars = {}) + def expect_gitaly_call(fail, options = {}) receive_fetch_remote = if fail receive(:fetch_remote).and_raise(GRPC::NotFound) @@ -250,12 +229,12 @@ describe Gitlab::Shell do end if gitaly_on - def expect_call(fail, vars = {}) - expect_gitaly_call(fail, vars) + def expect_call(fail, options = {}) + expect_gitaly_call(fail, options) end else - def expect_call(fail, vars = {}) - expect_popen(fail, vars) + def expect_call(fail, options = {}) + expect_gitlab_projects(fail, options) end end @@ -271,20 +250,27 @@ describe Gitlab::Shell do end it 'returns true when the command succeeds' do - expect_call(false) + expect_call(false, force: false, tags: true) expect(fetch_remote).to be_truthy end it 'raises an exception when the command fails' do - expect_call(true) + expect_call(true, force: false, tags: true) expect { fetch_remote }.to raise_error(Gitlab::Shell::Error) end + it 'allows forced and no_tags to be changed' do + expect_call(false, force: true, tags: false) + + result = gitlab_shell.fetch_remote(repository.raw_repository, 'remote-name', forced: true, no_tags: true) + expect(result).to be_truthy + end + context 'SSH auth' do it 'passes the SSH key if specified' do - expect_call(false, 'GITLAB_SHELL_SSH_KEY' => 'foo') + expect_call(false, force: false, tags: true, ssh_key: 'foo') ssh_auth = build_ssh_auth(ssh_key_auth?: true, ssh_private_key: 'foo') @@ -292,7 +278,7 @@ describe Gitlab::Shell do end it 'does not pass an empty SSH key' do - expect_call(false) + expect_call(false, force: false, tags: true) ssh_auth = build_ssh_auth(ssh_key_auth: true, ssh_private_key: '') @@ -300,7 +286,7 @@ describe Gitlab::Shell do end it 'does not pass the key unless SSH key auth is to be used' do - expect_call(false) + expect_call(false, force: false, tags: true) ssh_auth = build_ssh_auth(ssh_key_auth: false, ssh_private_key: 'foo') @@ -308,7 +294,7 @@ describe Gitlab::Shell do end it 'passes the known_hosts data if specified' do - expect_call(false, 'GITLAB_SHELL_KNOWN_HOSTS' => 'foo') + expect_call(false, force: false, tags: true, known_hosts: 'foo') ssh_auth = build_ssh_auth(ssh_known_hosts: 'foo') @@ -316,7 +302,7 @@ describe Gitlab::Shell do end it 'does not pass empty known_hosts data' do - expect_call(false) + expect_call(false, force: false, tags: true) ssh_auth = build_ssh_auth(ssh_known_hosts: '') @@ -324,7 +310,7 @@ describe Gitlab::Shell do end it 'does not pass known_hosts data unless SSH is to be used' do - expect_call(false, popen_vars) + expect_call(false, force: false, tags: true) ssh_auth = build_ssh_auth(ssh_import?: false, ssh_known_hosts: 'foo') @@ -342,20 +328,79 @@ describe Gitlab::Shell do end describe '#import_repository' do + let(:import_url) { 'https://gitlab.com/gitlab-org/gitlab-ce.git' } + it 'returns true when the command succeeds' do - expect(Gitlab::Popen).to receive(:popen) - .with([projects_path, 'import-project', 'current/storage', 'project/path.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git', "800"], - nil, popen_vars).and_return([nil, 0]) + expect(gitlab_projects).to receive(:import_project).with(import_url, timeout) { true } - expect(gitlab_shell.import_repository('current/storage', 'project/path', 'https://gitlab.com/gitlab-org/gitlab-ce.git')).to be true + result = gitlab_shell.import_repository(project.repository_storage_path, project.disk_path, import_url) + + expect(result).to be_truthy end it 'raises an exception when the command fails' do - expect(Gitlab::Popen).to receive(:popen) - .with([projects_path, 'import-project', 'current/storage', 'project/path.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git', "800"], - nil, popen_vars).and_return(["error", 1]) + allow(gitlab_projects).to receive(:output) { 'error' } + expect(gitlab_projects).to receive(:import_project) { false } + + expect do + gitlab_shell.import_repository(project.repository_storage_path, project.disk_path, import_url) + end.to raise_error(Gitlab::Shell::Error, "error") + end + end + + describe '#push_remote_branches' do + subject(:result) do + gitlab_shell.push_remote_branches( + project.repository_storage_path, + project.disk_path, + 'downstream-remote', + ['master'] + ) + end + + it 'executes the command' do + expect(gitlab_projects).to receive(:push_branches) + .with('downstream-remote', timeout, true, ['master']) + .and_return(true) + + is_expected.to be_truthy + end + + it 'fails to execute the command' do + allow(gitlab_projects).to receive(:output) { 'error' } + expect(gitlab_projects).to receive(:push_branches) + .with('downstream-remote', timeout, true, ['master']) + .and_return(false) + + expect { result }.to raise_error(Gitlab::Shell::Error, 'error') + end + end + + describe '#delete_remote_branches' do + subject(:result) do + gitlab_shell.delete_remote_branches( + project.repository_storage_path, + project.disk_path, + 'downstream-remote', + ['master'] + ) + end + + it 'executes the command' do + expect(gitlab_projects).to receive(:delete_remote_branches) + .with('downstream-remote', ['master']) + .and_return(true) + + is_expected.to be_truthy + end + + it 'fails to execute the command' do + allow(gitlab_projects).to receive(:output) { 'error' } + expect(gitlab_projects).to receive(:delete_remote_branches) + .with('downstream-remote', ['master']) + .and_return(false) - expect { gitlab_shell.import_repository('current/storage', 'project/path', 'https://gitlab.com/gitlab-org/gitlab-ce.git') }.to raise_error(Gitlab::Shell::Error, "error") + expect { result }.to raise_error(Gitlab::Shell::Error, 'error') end end end diff --git a/spec/lib/gitlab/sidekiq_config_spec.rb b/spec/lib/gitlab/sidekiq_config_spec.rb index 09f95be2213..0c66d764851 100644 --- a/spec/lib/gitlab/sidekiq_config_spec.rb +++ b/spec/lib/gitlab/sidekiq_config_spec.rb @@ -16,9 +16,30 @@ describe Gitlab::SidekiqConfig do expect(queues).to include('post_receive') expect(queues).to include('merge') - expect(queues).to include('cronjob') + expect(queues).to include('cronjob:stuck_import_jobs') expect(queues).to include('mailers') expect(queues).to include('default') end end + + describe '.expand_queues' do + it 'expands queue namespaces to concrete queue names' do + queues = described_class.expand_queues(%w[cronjob]) + + expect(queues).to include('cronjob:stuck_import_jobs') + expect(queues).to include('cronjob:stuck_merge_jobs') + end + + it 'lets concrete queue names pass through' do + queues = described_class.expand_queues(%w[post_receive]) + + expect(queues).to include('post_receive') + end + + it 'lets unknown queues pass through' do + queues = described_class.expand_queues(%w[unknown]) + + expect(queues).to include('unknown') + end + end end diff --git a/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb b/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb new file mode 100644 index 00000000000..7debf70a16f --- /dev/null +++ b/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe Gitlab::SidekiqVersioning::Manager do + before do + Sidekiq::Manager.prepend described_class + end + + describe '#initialize' do + it 'listens on all expanded queues' do + manager = Sidekiq::Manager.new(queues: %w[post_receive repository_fork cronjob unknown]) + + queues = manager.options[:queues] + + expect(queues).to include('post_receive') + expect(queues).to include('repository_fork') + expect(queues).to include('cronjob') + expect(queues).to include('cronjob:stuck_import_jobs') + expect(queues).to include('cronjob:stuck_merge_jobs') + expect(queues).to include('unknown') + end + end +end diff --git a/spec/lib/gitlab/sidekiq_versioning_spec.rb b/spec/lib/gitlab/sidekiq_versioning_spec.rb new file mode 100644 index 00000000000..fa6d42e730d --- /dev/null +++ b/spec/lib/gitlab/sidekiq_versioning_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe Gitlab::SidekiqVersioning, :sidekiq, :redis do + let(:foo_worker) do + Class.new do + def self.name + 'FooWorker' + end + + include ApplicationWorker + end + end + + let(:bar_worker) do + Class.new do + def self.name + 'BarWorker' + end + + include ApplicationWorker + end + end + + before do + allow(Gitlab::SidekiqConfig).to receive(:workers).and_return([foo_worker, bar_worker]) + allow(Gitlab::SidekiqConfig).to receive(:worker_queues).and_return([foo_worker.queue, bar_worker.queue]) + end + + describe '.install!' do + it 'prepends SidekiqVersioning::Manager into Sidekiq::Manager' do + described_class.install! + + expect(Sidekiq::Manager).to include(Gitlab::SidekiqVersioning::Manager) + end + + it 'registers all versionless and versioned queues with Redis' do + described_class.install! + + queues = Sidekiq::Queue.all.map(&:name) + expect(queues).to include('foo') + expect(queues).to include('bar') + end + end +end diff --git a/spec/lib/gitlab/tcp_checker_spec.rb b/spec/lib/gitlab/tcp_checker_spec.rb new file mode 100644 index 00000000000..4acf0334496 --- /dev/null +++ b/spec/lib/gitlab/tcp_checker_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe Gitlab::TcpChecker do + before do + @server = TCPServer.new('localhost', 0) + _, @port, _, @ip = @server.addr + end + + after do + @server.close + end + + subject(:checker) { described_class.new(@ip, @port) } + + describe '#check' do + subject { checker.check } + + it 'can connect to an open port' do + is_expected.to be_truthy + + expect(checker.error).to be_nil + end + + it 'fails to connect to a closed port' do + @server.close + + is_expected.to be_falsy + + expect(checker.error).to be_a(Errno::ECONNREFUSED) + end + end +end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index e1d71a9573b..4d0a3942996 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -342,6 +342,46 @@ describe Notify do end end + context 'for issue notes' do + let(:host) { Gitlab.config.gitlab.host } + + context 'in discussion' do + set(:first_note) { create(:discussion_note_on_issue) } + set(:second_note) { create(:discussion_note_on_issue, in_reply_to: first_note) } + set(:third_note) { create(:discussion_note_on_issue, in_reply_to: second_note) } + + subject { described_class.note_issue_email(recipient.id, third_note.id) } + + it 'has In-Reply-To header pointing to previous note in discussion' do + expect(subject.header['In-Reply-To'].message_ids).to eq(["note_#{second_note.id}@#{host}"]) + end + + it 'has References header including the notes and issue of the discussion' do + expect(subject.header['References'].message_ids).to include("issue_#{first_note.noteable.id}@#{host}", + "note_#{first_note.id}@#{host}", + "note_#{second_note.id}@#{host}") + end + + it 'has X-GitLab-Discussion-ID header' do + expect(subject.header['X-GitLab-Discussion-ID'].value).to eq(third_note.discussion.id) + end + end + + context 'individual issue comments' do + set(:note) { create(:note_on_issue) } + + subject { described_class.note_issue_email(recipient.id, note.id) } + + it 'has In-Reply-To header pointing to the issue' do + expect(subject.header['In-Reply-To'].message_ids).to eq(["issue_#{note.noteable.id}@#{host}"]) + end + + it 'has References header including the notes and issue of the discussion' do + expect(subject.header['References'].message_ids).to include("issue_#{note.noteable.id}@#{host}") + end + end + end + context 'for snippet notes' do let(:project_snippet) { create(:project_snippet, project: project) } let(:project_snippet_note) { create(:note_on_project_snippet, project: project, noteable: project_snippet) } diff --git a/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb b/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb index 05f281fffff..57ee2adaaff 100644 --- a/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb +++ b/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb @@ -2,9 +2,10 @@ require 'spec_helper' require Rails.root.join('db', 'post_migrate', '20171013104327_migrate_gcp_clusters_to_new_clusters_architectures.rb') describe MigrateGcpClustersToNewClustersArchitectures, :migration do - let(:project) { create(:project) } + let(:projects) { table(:projects) } + let(:project) { projects.create } let(:user) { create(:user) } - let(:service) { create(:kubernetes_service, project: project) } + let(:service) { create(:kubernetes_service, project_id: project.id) } context 'when cluster is being created' do let(:project_id) { project.id } @@ -56,8 +57,7 @@ describe MigrateGcpClustersToNewClustersArchitectures, :migration do expect(cluster.provider_type).to eq('gcp') expect(cluster.platform_type).to eq('kubernetes') - expect(cluster.project).to eq(project) - expect(project.clusters).to include(cluster) + expect(cluster.project_ids).to include(project.id) expect(cluster.provider_gcp.cluster).to eq(cluster) expect(cluster.provider_gcp.status).to eq(status) @@ -133,8 +133,7 @@ describe MigrateGcpClustersToNewClustersArchitectures, :migration do expect(cluster.provider_type).to eq('gcp') expect(cluster.platform_type).to eq('kubernetes') - expect(cluster.project).to eq(project) - expect(project.clusters).to include(cluster) + expect(cluster.project_ids).to include(project.id) expect(cluster.provider_gcp.cluster).to eq(cluster) expect(cluster.provider_gcp.status).to eq(status) diff --git a/spec/models/blob_viewer/package_json_spec.rb b/spec/models/blob_viewer/package_json_spec.rb index 0f8330e91c1..5ed2f4400bc 100644 --- a/spec/models/blob_viewer/package_json_spec.rb +++ b/spec/models/blob_viewer/package_json_spec.rb @@ -22,4 +22,51 @@ describe BlobViewer::PackageJson do expect(subject.package_name).to eq('module-name') end end + + describe '#package_url' do + it 'returns the package URL' do + expect(subject).to receive(:prepare!) + + expect(subject.package_url).to eq("https://www.npmjs.com/package/#{subject.package_name}") + end + end + + describe '#package_type' do + it 'returns "package"' do + expect(subject).to receive(:prepare!) + + expect(subject.package_type).to eq('package') + end + end + + context 'when package.json has "private": true' do + let(:data) do + <<-SPEC.strip_heredoc + { + "name": "module-name", + "version": "10.3.1", + "private": true, + "homepage": "myawesomepackage.com" + } + SPEC + end + let(:blob) { fake_blob(path: 'package.json', data: data) } + subject { described_class.new(blob) } + + describe '#package_url' do + it 'returns homepage if any' do + expect(subject).to receive(:prepare!) + + expect(subject.package_url).to eq('myawesomepackage.com') + end + end + + describe '#package_type' do + it 'returns "private package"' do + expect(subject).to receive(:prepare!) + + expect(subject.package_type).to eq('private package') + end + end + end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index c5e23532aa5..871e8b47650 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1861,9 +1861,9 @@ describe Ci::Build do describe 'state transition: any => [:running]' do shared_examples 'validation is active' do context 'when depended job has not been completed yet' do - let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) } + let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) } - it { expect { job.run! }.to raise_error(Ci::Build::MissingDependenciesError) } + it { expect { job.run! }.not_to raise_error(Ci::Build::MissingDependenciesError) } end context 'when artifacts of depended job has been expired' do @@ -1885,11 +1885,10 @@ describe Ci::Build do shared_examples 'validation is not active' do context 'when depended job has not been completed yet' do - let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) } + let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) } it { expect { job.run! }.not_to raise_error } end - context 'when artifacts of depended job has been expired' do let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) } diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index bb89e093890..a1f63a2534b 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -116,7 +116,7 @@ describe Ci::Pipeline, :mailer do end it "calculates average when there is one build without coverage" do - FactoryGirl.create(:ci_build, pipeline: pipeline) + FactoryBot.create(:ci_build, pipeline: pipeline) expect(pipeline.coverage).to be_nil end end @@ -435,7 +435,7 @@ describe Ci::Pipeline, :mailer do describe 'merge request metrics' do let(:project) { create(:project, :repository) } - let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) } + let(:pipeline) { FactoryBot.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) } let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) } before do @@ -1530,4 +1530,16 @@ describe Ci::Pipeline, :mailer do expect(query_count).to eq(1) end end + + describe '#total_size' do + let!(:build_job1) { create(:ci_build, pipeline: pipeline, stage_idx: 0) } + let!(:build_job2) { create(:ci_build, pipeline: pipeline, stage_idx: 0) } + let!(:test_job_failed_and_retried) { create(:ci_build, :failed, :retried, pipeline: pipeline, stage_idx: 1) } + let!(:second_test_job) { create(:ci_build, pipeline: pipeline, stage_idx: 1) } + let!(:deploy_job) { create(:ci_build, pipeline: pipeline, stage_idx: 2) } + + it 'returns all jobs (including failed and retried)' do + expect(pipeline.total_size).to eq(5) + end + end end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index a93e7e233a8..b2b64e6ff48 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -51,24 +51,24 @@ describe Ci::Runner do describe '#display_name' do it 'returns the description if it has a value' do - runner = FactoryGirl.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448') + runner = FactoryBot.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448') expect(runner.display_name).to eq 'Linux/Ruby-1.9.3-p448' end it 'returns the token if it does not have a description' do - runner = FactoryGirl.create(:ci_runner) + runner = FactoryBot.create(:ci_runner) expect(runner.display_name).to eq runner.description end it 'returns the token if the description is an empty string' do - runner = FactoryGirl.build(:ci_runner, description: '', token: 'token') + runner = FactoryBot.build(:ci_runner, description: '', token: 'token') expect(runner.display_name).to eq runner.token end end describe '#assign_to' do - let!(:project) { FactoryGirl.create :project } - let!(:shared_runner) { FactoryGirl.create(:ci_runner, :shared) } + let!(:project) { FactoryBot.create :project } + let!(:shared_runner) { FactoryBot.create(:ci_runner, :shared) } before do shared_runner.assign_to(project) @@ -83,15 +83,15 @@ describe Ci::Runner do subject { described_class.online } before do - @runner1 = FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.year.ago) - @runner2 = FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.second.ago) + @runner1 = FactoryBot.create(:ci_runner, :shared, contacted_at: 1.year.ago) + @runner2 = FactoryBot.create(:ci_runner, :shared, contacted_at: 1.second.ago) end it { is_expected.to eq([@runner2])} end describe '#online?' do - let(:runner) { FactoryGirl.create(:ci_runner, :shared) } + let(:runner) { FactoryBot.create(:ci_runner, :shared) } subject { runner.online? } @@ -268,7 +268,7 @@ describe Ci::Runner do end describe '#status' do - let(:runner) { FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.second.ago) } + let(:runner) { FactoryBot.create(:ci_runner, :shared, contacted_at: 1.second.ago) } subject { runner.status } @@ -442,9 +442,9 @@ describe Ci::Runner do describe "belongs_to_one_project?" do it "returns false if there are two projects runner assigned to" do - runner = FactoryGirl.create(:ci_runner) - project = FactoryGirl.create(:project) - project1 = FactoryGirl.create(:project) + runner = FactoryBot.create(:ci_runner) + project = FactoryBot.create(:project) + project1 = FactoryBot.create(:project) project.runners << runner project1.runners << runner @@ -452,8 +452,8 @@ describe Ci::Runner do end it "returns true" do - runner = FactoryGirl.create(:ci_runner) - project = FactoryGirl.create(:project) + runner = FactoryBot.create(:ci_runner) + project = FactoryBot.create(:project) project.runners << runner expect(runner.belongs_to_one_project?).to be_truthy diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index d18a5c9dfa6..cd955a5eb69 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -13,6 +13,45 @@ describe Commit do it { is_expected.to include_module(StaticModel) } end + describe '.lazy' do + set(:project) { create(:project, :repository) } + + context 'when the commits are found' do + let(:oids) do + %w( + 498214de67004b1da3d820901307bed2a68a8ef6 + c642fe9b8b9f28f9225d7ea953fe14e74748d53b + 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + 048721d90c449b244b7b4c53a9186b04330174ec + 281d3a76f31c812dbf48abce82ccf6860adedd81 + ) + end + + subject { oids.map { |oid| described_class.lazy(project, oid) } } + + it 'batches requests for commits' do + expect(project.repository).to receive(:commits_by).once.and_call_original + + subject.first.title + subject.last.title + end + + it 'maintains ordering' do + subject.each_with_index do |commit, i| + expect(commit.id).to eq(oids[i]) + end + end + end + + context 'when not found' do + it 'returns nil as commit' do + commit = described_class.lazy(project, 'deadbeef').__sync + + expect(commit).to be_nil + end + end + end + describe '#author' do it 'looks up the author in a case-insensitive way' do user = create(:user, email: commit.author_email.upcase) diff --git a/spec/models/concerns/blocks_json_serialization_spec.rb b/spec/models/concerns/blocks_json_serialization_spec.rb new file mode 100644 index 00000000000..5906b588d0e --- /dev/null +++ b/spec/models/concerns/blocks_json_serialization_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +describe BlocksJsonSerialization do + DummyModel = Class.new do + include BlocksJsonSerialization + end + + it 'blocks as_json' do + expect { DummyModel.new.as_json } + .to raise_error(described_class::JsonSerializationError, /DummyModel/) + end + + it 'blocks to_json' do + expect { DummyModel.new.to_json } + .to raise_error(described_class::JsonSerializationError, /DummyModel/) + end +end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 9df26f06a11..4b217df2e8f 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -291,7 +291,7 @@ describe Issuable do context 'total_time_spent is updated' do before do - issue.spend_time(duration: 2, user: user, spent_at: Time.now) + issue.spend_time(duration: 2, user_id: user.id, spent_at: Time.now) issue.save expect(Gitlab::HookData::IssuableBuilder) .to receive(:new).with(issue).and_return(builder) @@ -485,7 +485,7 @@ describe Issuable do let(:issue) { create(:issue) } def spend_time(seconds) - issue.spend_time(duration: seconds, user: user) + issue.spend_time(duration: seconds, user_id: user.id) issue.save! end diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index 9048da0c73d..673c609f534 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -189,9 +189,9 @@ describe Milestone, 'Milestoneish' do describe '#total_issue_time_spent' do it 'calculates total issue time spent' do - closed_issue_1.spend_time(duration: 300, user: author) + closed_issue_1.spend_time(duration: 300, user_id: author.id) closed_issue_1.save! - closed_issue_2.spend_time(duration: 600, user: assignee) + closed_issue_2.spend_time(duration: 600, user_id: assignee.id) closed_issue_2.save! expect(milestone.total_issue_time_spent).to eq(900) diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index c5708e70ef9..ba8aa13d5ad 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -126,7 +126,7 @@ describe Deployment do subject { deployment.stop_action } context 'when no other actions' do - let(:deployment) { FactoryGirl.build(:deployment, deployable: build) } + let(:deployment) { FactoryBot.build(:deployment, deployable: build) } it { is_expected.to be_nil } end @@ -135,13 +135,13 @@ describe Deployment do let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') } context 'when matching action is defined' do - let(:deployment) { FactoryGirl.build(:deployment, deployable: build, on_stop: 'close_other_app') } + let(:deployment) { FactoryBot.build(:deployment, deployable: build, on_stop: 'close_other_app') } it { is_expected.to be_nil } end context 'when no matching action is defined' do - let(:deployment) { FactoryGirl.build(:deployment, deployable: build, on_stop: 'close_app') } + let(:deployment) { FactoryBot.build(:deployment, deployable: build, on_stop: 'close_app') } it { is_expected.to eq(close_action) } end @@ -159,7 +159,7 @@ describe Deployment do context 'when matching action is defined' do let(:build) { create(:ci_build) } - let(:deployment) { FactoryGirl.build(:deployment, deployable: build, on_stop: 'close_app') } + let(:deployment) { FactoryBot.build(:deployment, deployable: build, on_stop: 'close_app') } let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') } it { is_expected.to be_truthy } diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb index a45a6088831..7c66c98231b 100644 --- a/spec/models/identity_spec.rb +++ b/spec/models/identity_spec.rb @@ -44,4 +44,31 @@ describe Identity do end end end + + context 'callbacks' do + context 'before_save' do + describe 'normalizes extern uid' do + let!(:ldap_identity) { create(:identity, provider: 'ldapmain', extern_uid: 'uid=john smith,ou=people,dc=example,dc=com') } + + it 'if extern_uid changes' do + expect(ldap_identity).not_to receive(:ensure_normalized_extern_uid) + ldap_identity.save + end + + it 'if current_uid is nil' do + expect(ldap_identity).to receive(:ensure_normalized_extern_uid) + + ldap_identity.update(extern_uid: nil) + + expect(ldap_identity.extern_uid).to be_nil + end + + it 'if extern_uid changed and not nil' do + ldap_identity.update(extern_uid: 'uid=john1,ou=PEOPLE,dc=example,dc=com') + + expect(ldap_identity.extern_uid).to eq 'uid=john1,ou=people,dc=example,dc=com' + end + end + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 6b98d013ded..bb63abd167b 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -601,30 +601,30 @@ describe MergeRequest do end describe '#can_remove_source_branch?' do - let(:user) { create(:user) } - let(:user2) { create(:user) } + set(:user) { create(:user) } + set(:merge_request) { create(:merge_request, :simple) } - before do - subject.source_project.team << [user, :master] + subject { merge_request } - subject.source_branch = "feature" - subject.target_branch = "master" - subject.save! + before do + subject.source_project.add_master(user) end it "can't be removed when its a protected branch" do allow(ProtectedBranch).to receive(:protected?).and_return(true) + expect(subject.can_remove_source_branch?(user)).to be_falsey end it "can't remove a root ref" do - subject.source_branch = "master" - subject.target_branch = "feature" + subject.update(source_branch: 'master', target_branch: 'feature') expect(subject.can_remove_source_branch?(user)).to be_falsey end it "is unable to remove the source branch for a project the user cannot push to" do + user2 = create(:user) + expect(subject.can_remove_source_branch?(user2)).to be_falsey end @@ -635,6 +635,7 @@ describe MergeRequest do end it "cannot be removed if the last commit is not also the head of the source branch" do + subject.clear_memoized_shas subject.source_branch = "lfs" expect(subject.can_remove_source_branch?(user)).to be_falsey @@ -1405,6 +1406,16 @@ describe MergeRequest do subject.reload_diff end + + context 'when using the after_update hook to update' do + context 'when the branches are updated' do + it 'uses the new heads to generate the diff' do + expect { subject.update!(source_branch: subject.target_branch, target_branch: subject.source_branch) } + .to change { subject.merge_request_diff.start_commit_sha } + .and change { subject.merge_request_diff.head_commit_sha } + end + end + end end describe '#update_diff_discussion_positions' do @@ -1857,4 +1868,20 @@ describe MergeRequest do it_behaves_like 'throttled touch' do subject { create(:merge_request, updated_at: 1.hour.ago) } end + + context 'state machine transitions' do + describe '#unlock_mr' do + subject { create(:merge_request, state: 'locked', merge_jid: 123) } + + it 'updates merge request head pipeline and sets merge_jid to nil' do + pipeline = create(:ci_empty_pipeline, project: subject.project, ref: subject.source_branch, sha: subject.source_branch_sha) + + subject.unlock_mr + + subject.reload + expect(subject.head_pipeline).to eq(pipeline) + expect(subject.merge_jid).to be_nil + end + end + end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index e1a0c55b6a6..cefbf60b28c 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -756,6 +756,28 @@ describe Note do end end + describe '#references' do + context 'when part of a discussion' do + it 'references all earlier notes in the discussion' do + first_note = create(:discussion_note_on_issue) + second_note = create(:discussion_note_on_issue, in_reply_to: first_note) + third_note = create(:discussion_note_on_issue, in_reply_to: second_note) + create(:discussion_note_on_issue, in_reply_to: third_note) + + expect(third_note.references).to eq([first_note.noteable, first_note, second_note]) + end + end + + context 'when not part of a discussion' do + subject { create(:note) } + let(:note) { create(:note, in_reply_to: subject) } + + it 'returns the noteable' do + expect(note.references).to eq([note.noteable]) + end + end + end + describe 'expiring ETag cache' do let(:note) { build(:note_on_issue) } diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index ad22fb2a386..c9b3c6cf602 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -395,6 +395,26 @@ describe JiraService do end end + describe 'additional cookies' do + let(:project) { create(:project) } + + context 'provides additional cookies to allow basic auth with oracle webgate' do + before do + @service = project.create_jira_service( + active: true, properties: { url: 'http://jira.com' }) + end + + after do + @service.destroy! + end + + it 'is initialized' do + expect(@service.options[:use_cookies]).to eq(true) + expect(@service.options[:additional_cookies]).to eq(["OBBasicAuth=fromDialog"]) + end + end + end + describe 'project and issue urls' do let(:project) { create(:project) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index dd9e8498519..cbeac2f05d3 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -289,12 +289,12 @@ describe Project do describe 'project token' do it 'sets an random token if none provided' do - project = FactoryGirl.create :project, runners_token: '' + project = FactoryBot.create :project, runners_token: '' expect(project.runners_token).not_to eq('') end it 'does not set an random token if one provided' do - project = FactoryGirl.create :project, runners_token: 'my-token' + project = FactoryBot.create :project, runners_token: 'my-token' expect(project.runners_token).to eq('my-token') end end @@ -1863,11 +1863,10 @@ describe Project do project.change_head(project.default_branch) end - it 'creates the new reference' do - expect(project.repository.raw_repository).to receive(:write_ref).with('HEAD', + it 'creates the new reference with rugged' do + expect(project.repository.rugged.references).to receive(:create).with('HEAD', "refs/heads/#{project.default_branch}", force: true) - project.change_head(project.default_branch) end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 129fce74f45..9a68ae086ea 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -59,12 +59,18 @@ describe Repository do end describe 'tags_sorted_by' do - context 'name' do - subject { repository.tags_sorted_by('name').map(&:name) } + context 'name_desc' do + subject { repository.tags_sorted_by('name_desc').map(&:name) } it { is_expected.to eq(['v1.1.0', 'v1.0.0']) } end + context 'name_asc' do + subject { repository.tags_sorted_by('name_asc').map(&:name) } + + it { is_expected.to eq(['v1.0.0', 'v1.1.0']) } + end + context 'updated' do let(:tag_a) { repository.find_tag('v1.0.0') } let(:tag_b) { repository.find_tag('v1.1.0') } @@ -233,6 +239,54 @@ describe Repository do end end + describe '#commits_by' do + set(:project) { create(:project, :repository) } + + shared_examples 'batch commits fetching' do + let(:oids) { TestEnv::BRANCH_SHA.values } + + subject { project.repository.commits_by(oids: oids) } + + it 'finds each commit' do + expect(subject).not_to include(nil) + expect(subject.size).to eq(oids.size) + end + + it 'returns only Commit instances' do + expect(subject).to all( be_a(Commit) ) + end + + context 'when some commits are not found ' do + let(:oids) do + ['deadbeef'] + TestEnv::BRANCH_SHA.values.first(10) + end + + it 'returns only found commits' do + expect(subject).not_to include(nil) + expect(subject.size).to eq(10) + end + end + + context 'when no oids are passed' do + let(:oids) { [] } + + it 'does not call #batch_by_oid' do + expect(Gitlab::Git::Commit).not_to receive(:batch_by_oid) + + subject + end + end + end + + context 'when Gitaly list_commits_by_oid is enabled' do + it_behaves_like 'batch commits fetching' + end + + context 'when Gitaly list_commits_by_oid is enabled', :disable_gitaly do + it_behaves_like 'batch commits fetching' + end + end + describe '#find_commits_by_message' do shared_examples 'finding commits by message' do it 'returns commits with messages containing a given string' do @@ -1007,7 +1061,7 @@ describe Repository do it 'runs without errors' do # old_rev is an ancestor of new_rev - expect(repository.rugged.merge_base(old_rev, new_rev)).to eq(old_rev) + expect(repository.merge_base(old_rev, new_rev)).to eq(old_rev) # old_rev is not a direct ancestor (parent) of new_rev expect(repository.rugged.lookup(new_rev).parent_ids).not_to include(old_rev) @@ -1029,7 +1083,7 @@ describe Repository do it 'raises an exception' do # The 'master' branch is NOT an ancestor of new_rev. - expect(repository.rugged.merge_base(old_rev, new_rev)).not_to eq(old_rev) + expect(repository.merge_base(old_rev, new_rev)).not_to eq(old_rev) # Updating 'master' to new_rev would lose the commits on 'master' that # are not contained in new_rev. This should not be allowed. @@ -1157,6 +1211,15 @@ describe Repository do end end + describe '#tag_exists?' do + it 'uses tag_names' do + allow(repository).to receive(:tag_names).and_return(['foobar']) + + expect(repository.tag_exists?('foobar')).to eq(true) + expect(repository.tag_exists?('master')).to eq(false) + end + end + describe '#branch_names', :use_clean_rails_memory_store_caching do let(:fake_branch_names) { ['foobar'] } @@ -1916,23 +1979,6 @@ describe Repository do File.delete(path) end - - it "attempting to call keep_around when exists a lock does not fail" do - ref = repository.send(:keep_around_ref_name, sample_commit.id) - path = File.join(repository.path, ref) - lock_path = "#{path}.lock" - - FileUtils.mkdir_p(File.dirname(path)) - File.open(lock_path, 'w') { |f| f.write('') } - - begin - expect { repository.keep_around(sample_commit.id) }.not_to raise_error(Gitlab::Git::Repository::GitError) - - expect(File.exist?(lock_path)).to be_falsey - ensure - File.delete(path) - end - end end describe '#update_ref' do @@ -2360,4 +2406,111 @@ describe Repository do end end end + + describe '#contributors' do + let(:author_a) { build(:author, email: 'tiagonbotelho@hotmail.com', name: 'tiagonbotelho') } + let(:author_b) { build(:author, email: 'gitlab@winniehell.de', name: 'Winnie') } + let(:author_c) { build(:author, email: 'douwe@gitlab.com', name: 'Douwe Maan') } + let(:stubbed_commits) do + [build(:commit, author: author_a), + build(:commit, author: author_a), + build(:commit, author: author_b), + build(:commit, author: author_c), + build(:commit, author: author_c), + build(:commit, author: author_c)] + end + let(:order_by) { nil } + let(:sort) { nil } + + before do + allow(repository).to receive(:commits).with(nil, limit: 2000, offset: 0, skip_merges: true).and_return(stubbed_commits) + end + + subject { repository.contributors(order_by: order_by, sort: sort) } + + def expect_contributors(*contributors) + expect(subject.map(&:email)).to eq(contributors.map(&:email)) + end + + it 'returns the array of Gitlab::Contributor for the repository' do + expect_contributors(author_a, author_b, author_c) + end + + context 'order_by email' do + let(:order_by) { 'email' } + + context 'asc' do + let(:sort) { 'asc' } + + it 'returns all the contributors ordered by email asc case insensitive' do + expect_contributors(author_c, author_b, author_a) + end + end + + context 'desc' do + let(:sort) { 'desc' } + + it 'returns all the contributors ordered by email desc case insensitive' do + expect_contributors(author_a, author_b, author_c) + end + end + end + + context 'order_by name' do + let(:order_by) { 'name' } + + context 'asc' do + let(:sort) { 'asc' } + + it 'returns all the contributors ordered by name asc case insensitive' do + expect_contributors(author_c, author_a, author_b) + end + end + + context 'desc' do + let(:sort) { 'desc' } + + it 'returns all the contributors ordered by name desc case insensitive' do + expect_contributors(author_b, author_a, author_c) + end + end + end + + context 'order_by commits' do + let(:order_by) { 'commits' } + + context 'asc' do + let(:sort) { 'asc' } + + it 'returns all the contributors ordered by commits asc' do + expect_contributors(author_b, author_a, author_c) + end + end + + context 'desc' do + let(:sort) { 'desc' } + + it 'returns all the contributors ordered by commits desc' do + expect_contributors(author_c, author_a, author_b) + end + end + end + + context 'invalid ordering' do + let(:order_by) { 'unknown' } + + it 'returns the contributors unsorted' do + expect_contributors(author_a, author_b, author_c) + end + end + + context 'invalid sorting' do + let(:order_by) { 'name' } + let(:sort) { 'unknown' } + + it 'returns the contributors unsorted' do + expect_contributors(author_a, author_b, author_c) + end + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 4687d9dfa00..e58e7588df0 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -12,6 +12,7 @@ describe User do it { is_expected.to include_module(Referable) } it { is_expected.to include_module(Sortable) } it { is_expected.to include_module(TokenAuthenticatable) } + it { is_expected.to include_module(BlocksJsonSerialization) } end describe 'delegations' do diff --git a/spec/policies/ci/pipeline_schedule_policy_spec.rb b/spec/policies/ci/pipeline_schedule_policy_spec.rb new file mode 100644 index 00000000000..1b0e9fac355 --- /dev/null +++ b/spec/policies/ci/pipeline_schedule_policy_spec.rb @@ -0,0 +1,92 @@ +require 'spec_helper' + +describe Ci::PipelineSchedulePolicy, :models do + set(:user) { create(:user) } + set(:project) { create(:project, :repository) } + set(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project) } + + let(:policy) do + described_class.new(user, pipeline_schedule) + end + + describe 'rules' do + describe 'rules for protected ref' do + before do + project.add_developer(user) + end + + context 'when no one can push or merge to the branch' do + before do + create(:protected_branch, :no_one_can_push, + name: pipeline_schedule.ref, project: project) + end + + it 'does not include ability to play pipeline schedule' do + expect(policy).to be_disallowed :play_pipeline_schedule + end + end + + context 'when developers can push to the branch' do + before do + create(:protected_branch, :developers_can_merge, + name: pipeline_schedule.ref, project: project) + end + + it 'includes ability to update pipeline' do + expect(policy).to be_allowed :play_pipeline_schedule + end + end + + context 'when no one can create the tag' do + let(:tag) { 'v1.0.0' } + + before do + pipeline_schedule.update(ref: tag) + + create(:protected_tag, :no_one_can_create, + name: pipeline_schedule.ref, project: project) + end + + it 'does not include ability to play pipeline schedule' do + expect(policy).to be_disallowed :play_pipeline_schedule + end + end + + context 'when no one can create the tag but it is not a tag' do + before do + create(:protected_tag, :no_one_can_create, + name: pipeline_schedule.ref, project: project) + end + + it 'includes ability to play pipeline schedule' do + expect(policy).to be_allowed :play_pipeline_schedule + end + end + end + + describe 'rules for owner of schedule' do + before do + project.add_developer(user) + pipeline_schedule.update(owner: user) + end + + it 'includes abilities to do do all operations on pipeline schedule' do + expect(policy).to be_allowed :play_pipeline_schedule + expect(policy).to be_allowed :update_pipeline_schedule + expect(policy).to be_allowed :admin_pipeline_schedule + end + end + + describe 'rules for a master' do + before do + project.add_master(user) + end + + it 'includes abilities to do do all operations on pipeline schedule' do + expect(policy).to be_allowed :play_pipeline_schedule + expect(policy).to be_allowed :update_pipeline_schedule + expect(policy).to be_allowed :admin_pipeline_schedule + end + end + end +end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 91616da6d9a..60dbd74d59d 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -150,6 +150,26 @@ describe API::MergeRequests do expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(merge_request3.id) end + + context 'search params' do + before do + merge_request.update(title: 'Search title', description: 'Search description') + end + + it 'returns merge requests matching given search string for title' do + get api("/merge_requests", user), search: merge_request.title + + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(merge_request.id) + end + + it 'returns merge requests for project matching given search string for description' do + get api("/merge_requests", user), project_id: project.id, search: merge_request.description + + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(merge_request.id) + end + end end end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 9f2ff3b5af6..741800ff61d 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -378,6 +378,28 @@ describe API::Repositories do expect(first_contributor['additions']).to eq(0) expect(first_contributor['deletions']).to eq(0) end + + context 'using sorting' do + context 'by commits desc' do + it 'returns the repository contribuors sorted by commits desc' do + get api(route, current_user), { order_by: 'commits', sort: 'desc' } + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('contributors') + expect(json_response.first['commits']).to be > json_response.last['commits'] + end + end + + context 'by name desc' do + it 'returns the repository contribuors sorted by name asc case insensitive' do + get api(route, current_user), { order_by: 'name', sort: 'asc' } + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('contributors') + expect(json_response.first['name'].downcase).to be < json_response.last['name'].downcase + end + end + end end context 'when unauthenticated', 'and project is public' do diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index 0bf7863bdc8..e2b19ad59f9 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -16,6 +16,44 @@ describe API::Tags do describe 'GET /projects/:id/repository/tags' do let(:route) { "/projects/#{project_id}/repository/tags" } + context 'sorting' do + let(:current_user) { user } + + it 'sorts by descending order by default' do + get api(route, current_user) + + desc_order_tags = project.repository.tags.sort_by { |tag| tag.dereferenced_target.committed_date } + desc_order_tags.reverse!.map! { |tag| tag.dereferenced_target.id } + + expect(json_response.map { |tag| tag['commit']['id'] }).to eq(desc_order_tags) + end + + it 'sorts by ascending order if specified' do + get api("#{route}?sort=asc", current_user) + + asc_order_tags = project.repository.tags.sort_by { |tag| tag.dereferenced_target.committed_date } + asc_order_tags.map! { |tag| tag.dereferenced_target.id } + + expect(json_response.map { |tag| tag['commit']['id'] }).to eq(asc_order_tags) + end + + it 'sorts by name in descending order when requested' do + get api("#{route}?order_by=name", current_user) + + ordered_by_name = project.repository.tags.map { |tag| tag.name }.sort.reverse + + expect(json_response.map { |tag| tag['name'] }).to eq(ordered_by_name) + end + + it 'sorts by name in ascending order when requested' do + get api("#{route}?order_by=name&sort=asc", current_user) + + ordered_by_name = project.repository.tags.map { |tag| tag.name }.sort + + expect(json_response.map { |tag| tag['name'] }).to eq(ordered_by_name) + end + end + shared_examples_for 'repository tags' do it 'returns the repository tags' do get api(route, current_user) diff --git a/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb b/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb new file mode 100644 index 00000000000..1fd40653f79 --- /dev/null +++ b/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb @@ -0,0 +1,157 @@ +require 'spec_helper' +require 'rubocop' +require 'rubocop/rspec/support' +require_relative '../../../../rubocop/cop/gitlab/module_with_instance_variables' + +describe RuboCop::Cop::Gitlab::ModuleWithInstanceVariables do + include CopHelper + + subject(:cop) { described_class.new } + + shared_examples('registering offense') do |options| + let(:offending_lines) { options[:offending_lines] } + + it 'registers an offense when instance variable is used in a module' do + inspect_source(cop, source) + + aggregate_failures do + expect(cop.offenses.size).to eq(offending_lines.size) + expect(cop.offenses.map(&:line)).to eq(offending_lines) + end + end + end + + shared_examples('not registering offense') do + it 'does not register offenses' do + inspect_source(cop, source) + + expect(cop.offenses).to be_empty + end + end + + context 'when source is a regular module' do + it_behaves_like 'registering offense', offending_lines: [3] do + let(:source) do + <<~RUBY + module M + def f + @f = true + end + end + RUBY + end + end + end + + context 'when source is a nested module' do + it_behaves_like 'registering offense', offending_lines: [4] do + let(:source) do + <<~RUBY + module N + module M + def f + @f = true + end + end + end + RUBY + end + end + end + + context 'when source is a nested module with multiple offenses' do + it_behaves_like 'registering offense', offending_lines: [4, 12] do + let(:source) do + <<~RUBY + module N + module M + def f + @f = true + end + + def g + true + end + + def h + @h = true + end + end + end + RUBY + end + end + end + + context 'when source is using simple or ivar assignment' do + it_behaves_like 'not registering offense' do + let(:source) do + <<~RUBY + module M + def f + @f ||= true + end + end + RUBY + end + end + end + + context 'when source is using simple ivar' do + it_behaves_like 'not registering offense' do + let(:source) do + <<~RUBY + module M + def f? + @f + end + end + RUBY + end + end + end + + context 'when source is defining initialize' do + it_behaves_like 'not registering offense' do + let(:source) do + <<~RUBY + module M + def initialize + @a = 1 + @b = 2 + end + end + RUBY + end + end + end + + context 'when source is using simple or ivar assignment with other ivar' do + it_behaves_like 'registering offense', offending_lines: [3] do + let(:source) do + <<~RUBY + module M + def f + @f ||= g(@g) + end + end + RUBY + end + end + end + + context 'when source is using or ivar assignment with something else' do + it_behaves_like 'registering offense', offending_lines: [3, 4] do + let(:source) do + <<~RUBY + module M + def f + @f ||= true + @f.to_s + end + end + RUBY + end + end + end +end diff --git a/spec/rubocop/cop/include_sidekiq_worker_spec.rb b/spec/rubocop/cop/include_sidekiq_worker_spec.rb new file mode 100644 index 00000000000..7f406535dda --- /dev/null +++ b/spec/rubocop/cop/include_sidekiq_worker_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' +require 'rubocop' +require 'rubocop/rspec/support' +require_relative '../../../rubocop/cop/include_sidekiq_worker' + +describe RuboCop::Cop::IncludeSidekiqWorker do + include CopHelper + + subject(:cop) { described_class.new } + + context 'when `Sidekiq::Worker` is included' do + let(:source) { 'include Sidekiq::Worker' } + let(:correct_source) { 'include ApplicationWorker' } + + it 'registers an offense ' do + inspect_source(cop, source) + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + expect(cop.highlights).to eq(['Sidekiq::Worker']) + end + end + + it 'autocorrects to the right version' do + autocorrected = autocorrect_source(cop, source) + + expect(autocorrected).to eq(correct_source) + end + end +end diff --git a/spec/rubocop/cop/sidekiq_options_queue_spec.rb b/spec/rubocop/cop/sidekiq_options_queue_spec.rb new file mode 100644 index 00000000000..a31de381631 --- /dev/null +++ b/spec/rubocop/cop/sidekiq_options_queue_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' +require 'rubocop' +require 'rubocop/rspec/support' +require_relative '../../../rubocop/cop/sidekiq_options_queue' + +describe RuboCop::Cop::SidekiqOptionsQueue do + include CopHelper + + subject(:cop) { described_class.new } + + it 'registers an offense when `sidekiq_options` is used with the `queue` option' do + inspect_source(cop, 'sidekiq_options queue: "some_queue"') + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + expect(cop.highlights).to eq(['queue: "some_queue"']) + end + end + + it 'does not register an offense when `sidekiq_options` is used with another option' do + inspect_source(cop, 'sidekiq_options retry: false') + + expect(cop.offenses).to be_empty + end +end diff --git a/spec/serializers/merge_request_serializer_spec.rb b/spec/serializers/merge_request_serializer_spec.rb index e3abefa6d63..b259cb92962 100644 --- a/spec/serializers/merge_request_serializer_spec.rb +++ b/spec/serializers/merge_request_serializer_spec.rb @@ -1,37 +1,43 @@ require 'spec_helper' describe MergeRequestSerializer do - let(:user) { build_stubbed(:user) } - let(:merge_request) { build_stubbed(:merge_request) } - - let(:serializer) do + let(:user) { create(:user) } + let(:resource) { create(:merge_request) } + let(:json_entity) do described_class.new(current_user: user) + .represent(resource, serializer: serializer) + .with_indifferent_access end - describe '#represent' do - let(:opts) { { serializer: serializer_entity } } - subject { serializer.represent(merge_request, serializer: serializer_entity) } + context 'widget merge request serialization' do + let(:serializer) { 'widget' } - context 'when passing basic serializer param' do - let(:serializer_entity) { 'basic' } + it 'matches issue json schema' do + expect(json_entity).to match_schema('entities/merge_request_widget') + end + end - it 'calls super class #represent with correct params' do - expect_any_instance_of(BaseSerializer).to receive(:represent) - .with(merge_request, opts, MergeRequestBasicEntity) + context 'sidebar merge request serialization' do + let(:serializer) { 'sidebar' } - subject - end + it 'matches basic merge request json schema' do + expect(json_entity).to match_schema('entities/merge_request_basic') end + end - context 'when serializer param is falsy' do - let(:serializer_entity) { nil } + context 'basic merge request serialization' do + let(:serializer) { 'basic' } + + it 'matches basic merge request json schema' do + expect(json_entity).to match_schema('entities/merge_request_basic') + end + end - it 'calls super class #represent with correct params' do - expect_any_instance_of(BaseSerializer).to receive(:represent) - .with(merge_request, opts, MergeRequestEntity) + context 'no serializer' do + let(:serializer) { nil } - subject - end + it 'falls back to the widget entity' do + expect(json_entity).to match_schema('entities/merge_request_widget') end end end diff --git a/spec/serializers/merge_request_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index 1ad672fd355..a5924a8589c 100644 --- a/spec/serializers/merge_request_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe MergeRequestEntity do +describe MergeRequestWidgetEntity do let(:project) { create :project, :repository } let(:resource) { create(:merge_request, source_project: project, target_project: project) } let(:user) { create(:user) } @@ -35,33 +35,6 @@ describe MergeRequestEntity do end end - it 'includes issues_links' do - issues_links = subject[:issues_links] - - expect(issues_links).to include(:closing, :mentioned_but_not_closing, - :assign_to_closing) - end - - it 'has Issuable attributes' do - expect(subject).to include(:id, :iid, :author_id, :description, :lock_version, :milestone_id, - :title, :updated_by_id, :created_at, :updated_at, :milestone, :labels) - end - - it 'has time estimation attributes' do - expect(subject).to include(:time_estimate, :total_time_spent, :human_time_estimate, :human_total_time_spent) - end - - it 'has important MergeRequest attributes' do - expect(subject).to include(:state, :deleted_at, :diff_head_sha, :merge_commit_message, - :has_conflicts, :has_ci, :merge_path, - :conflict_resolution_path, - :cancel_merge_when_pipeline_succeeds_path, - :create_issue_to_resolve_discussions_path, - :source_branch_path, :target_branch_commits_path, - :target_branch_tree_path, :commits_count, :merge_ongoing, - :ff_only_enabled) - end - it 'has email_patches_path' do expect(subject[:email_patches_path]) .to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}.patch") @@ -116,18 +89,6 @@ describe MergeRequestEntity do end end - it 'includes merge_event' do - create(:event, :merged, author: user, project: resource.project, target: resource) - - expect(subject[:merge_event]).to include(:author, :updated_at) - end - - it 'includes closed_event' do - create(:event, :closed, author: user, project: resource.project, target: resource) - - expect(subject[:closed_event]).to include(:author, :updated_at) - end - describe 'diverged_commits_count' do context 'when MR open and its diverging' do it 'returns diverged commits count' do diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 88d347322a6..c38795ad1a1 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe PipelineSerializer do + set(:project) { create(:project, :repository) } set(:user) { create(:user) } let(:serializer) do @@ -16,7 +17,7 @@ describe PipelineSerializer do end context 'when a single object is being serialized' do - let(:resource) { create(:ci_empty_pipeline) } + let(:resource) { create(:ci_empty_pipeline, project: project) } it 'serializers the pipeline object' do expect(subject[:id]).to eq resource.id @@ -24,7 +25,7 @@ describe PipelineSerializer do end context 'when multiple objects are being serialized' do - let(:resource) { create_list(:ci_pipeline, 2) } + let(:resource) { create_list(:ci_pipeline, 2, project: project) } it 'serializers the array of pipelines' do expect(subject).not_to be_empty @@ -100,7 +101,6 @@ describe PipelineSerializer do context 'number of queries' do let(:resource) { Ci::Pipeline.all } - let(:project) { create(:project) } before do # Since RequestStore.active? is true we have to allow the diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 41ce81e0651..267258b33a8 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -64,6 +64,18 @@ describe Ci::CreatePipelineService do create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project) end + context 'when related merge request is already merged' do + let!(:merged_merge_request) do + create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project, state: 'merged') + end + + it 'does not schedule update head pipeline job' do + expect(UpdateHeadPipelineForMergeRequestWorker).not_to receive(:perform_async).with(merged_merge_request.id) + + execute_service + end + end + context 'when the head pipeline sha equals merge request sha' do it 'updates head pipeline of each merge request' do merge_request_1 @@ -77,13 +89,13 @@ describe Ci::CreatePipelineService do end context 'when the head pipeline sha does not equal merge request sha' do - it 'raises the ArgumentError error from worker and does not update the head piepeline of MRs' do + it 'does not update the head piepeline of MRs' do merge_request_1 merge_request_2 allow_any_instance_of(Ci::Pipeline).to receive(:latest?).and_return(true) - expect { execute_service(after: 'ae73cb07c9eeaf35924a10f713b364d32b2dd34f') }.to raise_error(ArgumentError) + expect { execute_service(after: 'ae73cb07c9eeaf35924a10f713b364d32b2dd34f') }.not_to raise_error last_pipeline = Ci::Pipeline.last diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb index 22fb7ed7215..97a563c1ce1 100644 --- a/spec/services/ci/register_job_service_spec.rb +++ b/spec/services/ci/register_job_service_spec.rb @@ -2,11 +2,11 @@ require 'spec_helper' module Ci describe RegisterJobService do - let!(:project) { FactoryGirl.create :project, shared_runners_enabled: false } - let!(:pipeline) { FactoryGirl.create :ci_pipeline, project: project } - let!(:pending_job) { FactoryGirl.create :ci_build, pipeline: pipeline } - let!(:shared_runner) { FactoryGirl.create(:ci_runner, is_shared: true) } - let!(:specific_runner) { FactoryGirl.create(:ci_runner, is_shared: false) } + let!(:project) { FactoryBot.create :project, shared_runners_enabled: false } + let!(:pipeline) { FactoryBot.create :ci_pipeline, project: project } + let!(:pending_job) { FactoryBot.create :ci_build, pipeline: pipeline } + let!(:shared_runner) { FactoryBot.create(:ci_runner, is_shared: true) } + let!(:specific_runner) { FactoryBot.create(:ci_runner, is_shared: false) } before do specific_runner.assign_to(project) @@ -74,11 +74,11 @@ module Ci let!(:project3) { create :project, shared_runners_enabled: true } let!(:pipeline3) { create :ci_pipeline, project: project3 } let!(:build1_project1) { pending_job } - let!(:build2_project1) { FactoryGirl.create :ci_build, pipeline: pipeline } - let!(:build3_project1) { FactoryGirl.create :ci_build, pipeline: pipeline } - let!(:build1_project2) { FactoryGirl.create :ci_build, pipeline: pipeline2 } - let!(:build2_project2) { FactoryGirl.create :ci_build, pipeline: pipeline2 } - let!(:build1_project3) { FactoryGirl.create :ci_build, pipeline: pipeline3 } + let!(:build2_project1) { FactoryBot.create :ci_build, pipeline: pipeline } + let!(:build3_project1) { FactoryBot.create :ci_build, pipeline: pipeline } + let!(:build1_project2) { FactoryBot.create :ci_build, pipeline: pipeline2 } + let!(:build2_project2) { FactoryBot.create :ci_build, pipeline: pipeline2 } + let!(:build1_project3) { FactoryBot.create :ci_build, pipeline: pipeline3 } it 'prefers projects without builds first' do # it gets for one build from each of the projects @@ -287,9 +287,9 @@ module Ci shared_examples 'validation is active' do context 'when depended job has not been completed yet' do - let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) } + let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) } - it_behaves_like 'not pick' + it { expect(subject).to eq(pending_job) } end context 'when artifacts of depended job has been expired' do @@ -309,7 +309,7 @@ module Ci end context 'when job object is staled' do - let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) } + let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) } before do allow_any_instance_of(Ci::Build).to receive(:drop!) @@ -324,11 +324,10 @@ module Ci shared_examples 'validation is not active' do context 'when depended job has not been completed yet' do - let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) } + let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) } it { expect(subject).to eq(pending_job) } end - context 'when artifacts of depended job has been expired' do let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) } diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index d48a44fa57f..a06397a0782 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -40,7 +40,7 @@ describe Ci::RetryBuildService do description: 'my-job', stage: 'test', pipeline: pipeline, auto_canceled_by: create(:ci_empty_pipeline, project: project)) do |build| ## - # TODO, workaround for FactoryGirl limitation when having both + # TODO, workaround for FactoryBot limitation when having both # stage (text) and stage_id (integer) columns in the table. build.stage_id = stage.id end diff --git a/spec/services/files/delete_service_spec.rb b/spec/services/files/delete_service_spec.rb new file mode 100644 index 00000000000..e9f8f0efe6b --- /dev/null +++ b/spec/services/files/delete_service_spec.rb @@ -0,0 +1,64 @@ +require "spec_helper" + +describe Files::DeleteService do + subject { described_class.new(project, user, commit_params) } + + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + let(:file_path) { 'files/ruby/popen.rb' } + let(:branch_name) { project.default_branch } + let(:last_commit_sha) { nil } + + let(:commit_params) do + { + file_path: file_path, + commit_message: "Delete File", + last_commit_sha: last_commit_sha, + start_project: project, + start_branch: project.default_branch, + branch_name: branch_name + } + end + + shared_examples 'successfully deletes the file' do + it 'returns a hash with the :success status' do + results = subject.execute + + expect(results[:status]).to match(:success) + end + + it 'deletes the file' do + subject.execute + + blob = project.repository.blob_at_branch(project.default_branch, file_path) + + expect(blob).to be_nil + end + end + + before do + project.team << [user, :master] + end + + describe "#execute" do + context "when the file's last commit sha does not match the supplied last_commit_sha" do + let(:last_commit_sha) { "foo" } + + it "returns a hash with the correct error message and a :error status " do + expect { subject.execute } + .to raise_error(Files::UpdateService::FileChangedError, + "You are attempting to delete a file that has been previously updated.") + end + end + + context "when the file's last commit sha does match the supplied last_commit_sha" do + let(:last_commit_sha) { Gitlab::Git::Commit.last_for_path(project.repository, project.default_branch, file_path).sha } + + it_behaves_like 'successfully deletes the file' + end + + context "when the last_commit_sha is not supplied" do + it_behaves_like 'successfully deletes the file' + end + end +end diff --git a/spec/services/files/multi_service_spec.rb b/spec/services/files/multi_service_spec.rb new file mode 100644 index 00000000000..085a28d267f --- /dev/null +++ b/spec/services/files/multi_service_spec.rb @@ -0,0 +1,144 @@ +require "spec_helper" + +describe Files::MultiService do + subject { described_class.new(project, user, commit_params) } + + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + let(:branch_name) { project.default_branch } + let(:original_file_path) { 'files/ruby/popen.rb' } + let(:new_file_path) { 'files/ruby/popen.rb' } + let(:action) { 'update' } + + let!(:original_commit_id) do + Gitlab::Git::Commit.last_for_path(project.repository, branch_name, original_file_path).sha + end + + let(:actions) do + [ + { + action: action, + file_path: new_file_path, + previous_path: original_file_path, + content: 'New content', + last_commit_id: original_commit_id + } + ] + end + + let(:commit_params) do + { + commit_message: "Update File", + branch_name: branch_name, + start_branch: branch_name, + actions: actions + } + end + + before do + project.team << [user, :master] + end + + describe '#execute' do + context 'with a valid action' do + it 'returns a hash with the :success status ' do + results = subject.execute + + expect(results[:status]).to eq(:success) + end + end + + context 'with an invalid action' do + let(:action) { 'rename' } + + it 'returns a hash with the :error status ' do + results = subject.execute + + expect(results[:status]).to eq(:error) + expect(results[:message]).to match(/Unknown action/) + end + end + + describe 'Updating files' do + context 'when the file has been previously updated' do + before do + update_file(original_file_path) + end + + it 'rejects the commit' do + results = subject.execute + + expect(results[:status]).to eq(:error) + expect(results[:message]).to match(new_file_path) + end + end + + context 'when the file have not been modified' do + it 'accepts the commit' do + results = subject.execute + + expect(results[:status]).to eq(:success) + end + end + end + + context 'when moving a file' do + let(:action) { 'move' } + let(:new_file_path) { 'files/ruby/new_popen.rb' } + + context 'when original file has been updated' do + before do + update_file(original_file_path) + end + + it 'rejects the commit' do + results = subject.execute + + expect(results[:status]).to eq(:error) + expect(results[:message]).to match(original_file_path) + end + end + + context 'when original file have not been updated' do + it 'moves the file' do + results = subject.execute + blob = project.repository.blob_at_branch(branch_name, new_file_path) + + expect(results[:status]).to eq(:success) + expect(blob).to be_present + end + end + end + + context 'when file status validation is skipped' do + let(:action) { 'create' } + let(:new_file_path) { 'files/ruby/new_file.rb' } + + it 'does not check the last commit' do + expect(Gitlab::Git::Commit).not_to receive(:last_for_path) + + subject.execute + end + + it 'creates the file' do + subject.execute + + blob = project.repository.blob_at_branch(branch_name, new_file_path) + + expect(blob).to be_present + end + end + end + + def update_file(path) + params = { + file_path: path, + start_branch: branch_name, + branch_name: branch_name, + commit_message: 'Update file', + file_content: 'New content' + } + + Files::UpdateService.new(project, user, params).execute + end +end diff --git a/spec/services/issuable/destroy_service_spec.rb b/spec/services/issuable/destroy_service_spec.rb index d74d98c6079..0a3647a814f 100644 --- a/spec/services/issuable/destroy_service_spec.rb +++ b/spec/services/issuable/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Issuable::DestroyService do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :public) } subject(:service) { described_class.new(project, user) } @@ -19,6 +19,13 @@ describe Issuable::DestroyService do service.execute(issue) end + + it 'updates the todo caches for users with todos on the issue' do + create(:todo, target: issue, user: user, author: user, project: project) + + expect { service.execute(issue) } + .to change { user.todos_pending_count }.from(1).to(0) + end end context 'when issuable is a merge request' do @@ -33,6 +40,13 @@ describe Issuable::DestroyService do service.execute(merge_request) end + + it 'updates the todo caches for users with todos on the merge request' do + create(:todo, target: merge_request, user: user, author: user, project: project) + + expect { service.execute(merge_request) } + .to change { user.todos_pending_count }.from(1).to(0) + end end end end diff --git a/spec/services/merge_requests/conflicts/resolve_service_spec.rb b/spec/services/merge_requests/conflicts/resolve_service_spec.rb index 5376083e7f5..e28d8d7ae5c 100644 --- a/spec/services/merge_requests/conflicts/resolve_service_spec.rb +++ b/spec/services/merge_requests/conflicts/resolve_service_spec.rb @@ -213,7 +213,7 @@ describe MergeRequests::Conflicts::ResolveService do MergeRequests::Conflicts::ListService.new(merge_request).conflicts.resolver end let(:regex_conflict) do - resolver.conflict_for_path('files/ruby/regex.rb', 'files/ruby/regex.rb') + resolver.conflict_for_path(resolver.conflicts, 'files/ruby/regex.rb', 'files/ruby/regex.rb') end let(:invalid_params) do diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index f86f1ac2443..c38ddf4612b 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -1,14 +1,14 @@ require 'spec_helper' describe MergeRequests::MergeService do - let(:user) { create(:user) } - let(:user2) { create(:user) } + set(:user) { create(:user) } + set(:user2) { create(:user) } let(:merge_request) { create(:merge_request, :simple, author: user2, assignee: user2) } let(:project) { merge_request.project } before do - project.team << [user, :master] - project.team << [user2, :developer] + project.add_master(user) + project.add_developer(user2) end describe '#execute' do diff --git a/spec/services/notes/destroy_service_spec.rb b/spec/services/notes/destroy_service_spec.rb index c9a99a43edb..64445be560e 100644 --- a/spec/services/notes/destroy_service_spec.rb +++ b/spec/services/notes/destroy_service_spec.rb @@ -1,15 +1,25 @@ require 'spec_helper' describe Notes::DestroyService do + set(:project) { create(:project, :public) } + set(:issue) { create(:issue, project: project) } + let(:user) { issue.author } + describe '#execute' do it 'deletes a note' do - project = create(:project) - issue = create(:issue, project: project) note = create(:note, project: project, noteable: issue) - described_class.new(project, note.author).execute(note) + described_class.new(project, user).execute(note) expect(project.issues.find(issue.id).notes).not_to include(note) end + + it 'updates the todo counts for users with todos for the note' do + note = create(:note, project: project, noteable: issue) + create(:todo, note: note, target: issue, user: user, author: user, project: project) + + expect { described_class.new(project, user).execute(note) } + .to change { user.todos_pending_count }.from(1).to(0) + end end end diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb index 2bba71fef4f..3ec6139bfa6 100644 --- a/spec/services/projects/unlink_fork_service_spec.rb +++ b/spec/services/projects/unlink_fork_service_spec.rb @@ -62,6 +62,26 @@ describe Projects::UnlinkForkService do expect(source.forks_count).to be_zero end + context 'when the source has LFS objects' do + let(:lfs_object) { create(:lfs_object) } + + before do + lfs_object.projects << project + end + + it 'links the fork to the lfs object before unlinking' do + subject.execute + + expect(lfs_object.projects).to include(forked_project) + end + + it 'does not fail if the lfs objects were already linked' do + lfs_object.projects << forked_project + + expect { subject.execute }.not_to raise_error + end + end + context 'when the original project was deleted' do it 'does not fail when the original project is deleted' do source = forked_project.forked_from_project diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index c35177f6ebc..eb46480fa54 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -209,7 +209,7 @@ describe QuickActions::InterpretService do expect(updates).to eq(spend_time: { duration: 3600, - user: developer, + user_id: developer.id, spent_at: DateTime.now.to_date }) end @@ -221,7 +221,7 @@ describe QuickActions::InterpretService do expect(updates).to eq(spend_time: { duration: -1800, - user: developer, + user_id: developer.id, spent_at: DateTime.now.to_date }) end @@ -233,7 +233,7 @@ describe QuickActions::InterpretService do expect(updates).to eq(spend_time: { duration: 1800, - user: developer, + user_id: developer.id, spent_at: Date.parse(date) }) end @@ -267,7 +267,7 @@ describe QuickActions::InterpretService do it 'populates spend_time: :reset if content contains /remove_time_spent' do _, updates = service.execute(content, issuable) - expect(updates).to eq(spend_time: { duration: :reset, user: developer }) + expect(updates).to eq(spend_time: { duration: :reset, user_id: developer.id }) end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 47412110b4b..9025589ae0b 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -927,7 +927,7 @@ describe SystemNoteService do # We need a custom noteable in order to the shared examples to be green. let(:noteable) do mr = create(:merge_request, source_project: project) - mr.spend_time(duration: 360000, user: author) + mr.spend_time(duration: 360000, user_id: author.id) mr.save! mr end @@ -965,7 +965,7 @@ describe SystemNoteService do end def spend_time!(seconds) - noteable.spend_time(duration: seconds, user: author) + noteable.spend_time(duration: seconds, user_id: author.id) noteable.save! end end diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index dc2673abc73..88013acae0a 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -248,11 +248,26 @@ describe TodoService do end end - describe '#destroy_issuable' do - it 'refresh the todos count cache for the user' do - expect(john_doe).to receive(:update_todos_count_cache).and_call_original + describe '#destroy_target' do + it 'refreshes the todos count cache for users with todos on the target' do + create(:todo, target: issue, user: john_doe, author: john_doe, project: issue.project) - service.destroy_issuable(issue, john_doe) + expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original + + service.destroy_target(issue) { } + end + + it 'does not refresh the todos count cache for users with only done todos on the target' do + create(:todo, :done, target: issue, user: john_doe, author: john_doe, project: issue.project) + + expect_any_instance_of(User).not_to receive(:update_todos_count_cache) + + service.destroy_target(issue) { } + end + + it 'yields the target to the caller' do + expect { |b| service.destroy_target(issue, &b) } + .to yield_with_args(issue) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f94fb8733d5..f51bb44086b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -183,7 +183,7 @@ RSpec::Matchers.define :match_asset_path do |expected| end end -FactoryGirl::SyntaxRunner.class_eval do +FactoryBot::SyntaxRunner.class_eval do include RSpec::Mocks::ExampleMethods end diff --git a/spec/support/api/time_tracking_shared_examples.rb b/spec/support/api/time_tracking_shared_examples.rb index af1083f4bfd..dd3089d22e5 100644 --- a/spec/support/api/time_tracking_shared_examples.rb +++ b/spec/support/api/time_tracking_shared_examples.rb @@ -79,7 +79,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| context 'when subtracting time' do it 'subtracts time of the total spent time' do - issuable.update_attributes!(spend_time: { duration: 7200, user: user }) + issuable.update_attributes!(spend_time: { duration: 7200, user_id: user.id }) post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), duration: '-1h' @@ -91,7 +91,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| context 'when time to subtract is greater than the total spent time' do it 'does not modify the total time spent' do - issuable.update_attributes!(spend_time: { duration: 7200, user: user }) + issuable.update_attributes!(spend_time: { duration: 7200, user_id: user.id }) post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), duration: '-1w' @@ -119,7 +119,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| describe "GET /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_stats" do it "returns the time stats for #{issuable_name}" do - issuable.update_attributes!(spend_time: { duration: 1800, user: user }, + issuable.update_attributes!(spend_time: { duration: 1800, user_id: user.id }, time_estimate: 3600) get api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_stats", user) diff --git a/spec/support/api/v3/time_tracking_shared_examples.rb b/spec/support/api/v3/time_tracking_shared_examples.rb index afe0f4cecda..f27a2d06c83 100644 --- a/spec/support/api/v3/time_tracking_shared_examples.rb +++ b/spec/support/api/v3/time_tracking_shared_examples.rb @@ -75,7 +75,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| context 'when subtracting time' do it 'subtracts time of the total spent time' do - issuable.update_attributes!(spend_time: { duration: 7200, user: user }) + issuable.update_attributes!(spend_time: { duration: 7200, user_id: user.id }) post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user), duration: '-1h' @@ -87,7 +87,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| context 'when time to subtract is greater than the total spent time' do it 'does not modify the total time spent' do - issuable.update_attributes!(spend_time: { duration: 7200, user: user }) + issuable.update_attributes!(spend_time: { duration: 7200, user_id: user.id }) post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user), duration: '-1w' @@ -115,7 +115,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| describe "GET /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_stats" do it "returns the time stats for #{issuable_name}" do - issuable.update_attributes!(spend_time: { duration: 1800, user: user }, + issuable.update_attributes!(spend_time: { duration: 1800, user_id: user.id }, time_estimate: 3600) get v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_stats", user) diff --git a/spec/support/batch_loader.rb b/spec/support/batch_loader.rb new file mode 100644 index 00000000000..bb790e660a6 --- /dev/null +++ b/spec/support/batch_loader.rb @@ -0,0 +1,5 @@ +RSpec.configure do |config| + config.after do + BatchLoader::Executor.clear_current + end +end diff --git a/spec/support/factory_girl.rb b/spec/support/factory_girl.rb index eec437fb3aa..c7890e49c66 100644 --- a/spec/support/factory_girl.rb +++ b/spec/support/factory_girl.rb @@ -1,3 +1,3 @@ RSpec.configure do |config| - config.include FactoryGirl::Syntax::Methods + config.include FactoryBot::Syntax::Methods end diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb index c90359d7cfa..a0d854d3641 100644 --- a/spec/support/markdown_feature.rb +++ b/spec/support/markdown_feature.rb @@ -8,7 +8,7 @@ # The class renders `spec/fixtures/markdown.md.erb` using ERB, allowing for # reference to the factory-created objects. class MarkdownFeature - include FactoryGirl::Syntax::Methods + include FactoryBot::Syntax::Methods def user @user ||= create(:user) diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb index b36cf3c544c..9f08c139322 100644 --- a/spec/support/stub_configuration.rb +++ b/spec/support/stub_configuration.rb @@ -7,6 +7,9 @@ module StubConfiguration allow_any_instance_of(ApplicationSetting).to receive_messages(to_settings(messages)) allow(Gitlab::CurrentSettings.current_application_settings) .to receive_messages(to_settings(messages)) + + # Ensure that we don't use the Markdown cache when stubbing these values + allow_any_instance_of(ApplicationSetting).to receive(:cached_html_up_to_date?).and_return(false) end def stub_not_protect_default_branch diff --git a/spec/support/stub_env.rb b/spec/support/stub_env.rb index 19fbe572930..f621463e621 100644 --- a/spec/support/stub_env.rb +++ b/spec/support/stub_env.rb @@ -17,6 +17,7 @@ module StubENV def add_stubbed_value(key, value) allow(ENV).to receive(:[]).with(key).and_return(value) + allow(ENV).to receive(:key?).with(key).and_return(true) allow(ENV).to receive(:fetch).with(key).and_return(value) allow(ENV).to receive(:fetch).with(key, anything()) do |_, default_val| value || default_val @@ -29,6 +30,7 @@ module StubENV def init_stub allow(ENV).to receive(:[]).and_call_original + allow(ENV).to receive(:key?).and_call_original allow(ENV).to receive(:fetch).and_call_original add_stubbed_value(STUBBED_KEY, true) end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index b300b493f86..ffc051a3fff 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -82,10 +82,10 @@ module TestEnv setup_gitaly - # Create repository for FactoryGirl.create(:project) + # Create repository for FactoryBot.create(:project) setup_factory_repo - # Create repository for FactoryGirl.create(:forked_project_with_submodules) + # Create repository for FactoryBot.create(:forked_project_with_submodules) setup_forked_repo end diff --git a/spec/uploaders/records_uploads_spec.rb b/spec/uploaders/records_uploads_spec.rb index bb32ee62ccb..7ef7fb7d758 100644 --- a/spec/uploaders/records_uploads_spec.rb +++ b/spec/uploaders/records_uploads_spec.rb @@ -8,7 +8,7 @@ describe RecordsUploads do storage :file def model - FactoryGirl.build_stubbed(:user) + FactoryBot.build_stubbed(:user) end end diff --git a/spec/views/events/event/_push.html.haml_spec.rb b/spec/views/events/event/_push.html.haml_spec.rb new file mode 100644 index 00000000000..f5634de4916 --- /dev/null +++ b/spec/views/events/event/_push.html.haml_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe 'events/event/_push.html.haml' do + let(:event) { build_stubbed(:push_event) } + + context 'with a branch' do + let(:payload) { build_stubbed(:push_event_payload, event: event) } + + before do + allow(event).to receive(:push_event_payload).and_return(payload) + end + + it 'links to the branch' do + allow(event.project.repository).to receive(:branch_exists?).with(event.ref_name).and_return(true) + link = project_commits_path(event.project, event.ref_name) + + render partial: 'events/event/push', locals: { event: event } + + expect(rendered).to have_link(event.ref_name, href: link) + end + + context 'that has been deleted' do + it 'does not link to the branch' do + render partial: 'events/event/push', locals: { event: event } + + expect(rendered).not_to have_link(event.ref_name) + end + end + end + + context 'with a tag' do + let(:payload) { build_stubbed(:push_event_payload, event: event, ref_type: :tag, ref: 'v0.1.0') } + + before do + allow(event).to receive(:push_event_payload).and_return(payload) + end + + it 'links to the tag' do + allow(event.project.repository).to receive(:tag_exists?).with(event.ref_name).and_return(true) + link = project_commits_path(event.project, event.ref_name) + + render partial: 'events/event/push', locals: { event: event } + + expect(rendered).to have_link(event.ref_name, href: link) + end + + context 'that has been deleted' do + it 'does not link to the tag' do + render partial: 'events/event/push', locals: { event: event } + + expect(rendered).not_to have_link(event.ref_name) + end + end + end +end diff --git a/spec/views/projects/jobs/show.html.haml_spec.rb b/spec/views/projects/jobs/show.html.haml_spec.rb index 6139529013f..6a67da79ec5 100644 --- a/spec/views/projects/jobs/show.html.haml_spec.rb +++ b/spec/views/projects/jobs/show.html.haml_spec.rb @@ -187,7 +187,7 @@ describe 'projects/jobs/show' do context 'when incomplete trigger_request is used' do before do - build.trigger_request = FactoryGirl.build(:ci_trigger_request, trigger: nil) + build.trigger_request = FactoryBot.build(:ci_trigger_request, trigger: nil) end it 'test should not render token block' do @@ -199,7 +199,7 @@ describe 'projects/jobs/show' do context 'when complete trigger_request is used' do before do - build.trigger_request = FactoryGirl.build(:ci_trigger_request) + build.trigger_request = FactoryBot.build(:ci_trigger_request) end it 'should render token' do diff --git a/spec/views/projects/tree/_blob_item.html.haml_spec.rb b/spec/views/projects/tree/_blob_item.html.haml_spec.rb new file mode 100644 index 00000000000..6a477c712ff --- /dev/null +++ b/spec/views/projects/tree/_blob_item.html.haml_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe 'projects/tree/_blob_item' do + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + let(:blob_item) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID, 'files/ruby').first } + + before do + assign(:project, project) + assign(:repository, repository) + assign(:id, File.join('master', '')) + assign(:lfs_blob_ids, []) + end + + it 'renders blob item' do + render_partial(blob_item) + + expect(rendered).to have_content(blob_item.name) + expect(rendered).not_to have_selector('.label-lfs', text: 'LFS') + end + + describe 'LFS blob' do + before do + assign(:lfs_blob_ids, [blob_item].map(&:id)) + + render_partial(blob_item) + end + + it 'renders LFS badge' do + expect(rendered).to have_selector('.label-lfs', text: 'LFS') + end + end + + def render_partial(blob_item) + render partial: 'projects/tree/blob_item', locals: { + blob_item: blob_item, + type: 'blob' + } + end +end diff --git a/spec/views/projects/tree/show.html.haml_spec.rb b/spec/views/projects/tree/show.html.haml_spec.rb index 3c25e341b39..44b32df0395 100644 --- a/spec/views/projects/tree/show.html.haml_spec.rb +++ b/spec/views/projects/tree/show.html.haml_spec.rb @@ -9,6 +9,7 @@ describe 'projects/tree/show' do before do assign(:project, project) assign(:repository, repository) + assign(:lfs_blob_ids, []) allow(view).to receive(:can?).and_return(true) allow(view).to receive(:can_collaborate_with_project?).and_return(true) diff --git a/spec/workers/concerns/application_worker_spec.rb b/spec/workers/concerns/application_worker_spec.rb index 0145563e0ed..901d77178bc 100644 --- a/spec/workers/concerns/application_worker_spec.rb +++ b/spec/workers/concerns/application_worker_spec.rb @@ -17,6 +17,14 @@ describe ApplicationWorker do end end + describe '.queue_namespace' do + it 'sets the queue name based on the class name' do + worker.queue_namespace :some_namespace + + expect(worker.queue).to eq('some_namespace:foo_bar_dummy') + end + end + describe '.queue' do it 'returns the queue name' do worker.sidekiq_options queue: :some_queue diff --git a/spec/workers/concerns/cluster_queue_spec.rb b/spec/workers/concerns/cluster_queue_spec.rb index 5049886b55c..4118b9aa194 100644 --- a/spec/workers/concerns/cluster_queue_spec.rb +++ b/spec/workers/concerns/cluster_queue_spec.rb @@ -14,6 +14,6 @@ describe ClusterQueue do it 'sets a default pipelines queue automatically' do expect(worker.sidekiq_options['queue']) - .to eq :gcp_cluster + .to eq 'gcp_cluster:dummy' end end diff --git a/spec/workers/concerns/cronjob_queue_spec.rb b/spec/workers/concerns/cronjob_queue_spec.rb index 3ae1c5f54d8..c042a52f41f 100644 --- a/spec/workers/concerns/cronjob_queue_spec.rb +++ b/spec/workers/concerns/cronjob_queue_spec.rb @@ -13,7 +13,7 @@ describe CronjobQueue do end it 'sets the queue name of a worker' do - expect(worker.sidekiq_options['queue'].to_s).to eq('cronjob') + expect(worker.sidekiq_options['queue'].to_s).to eq('cronjob:dummy') end it 'disables retrying of failed jobs' do diff --git a/spec/workers/concerns/gitlab/github_import/queue_spec.rb b/spec/workers/concerns/gitlab/github_import/queue_spec.rb index 9c69ee32da1..a96f583aff7 100644 --- a/spec/workers/concerns/gitlab/github_import/queue_spec.rb +++ b/spec/workers/concerns/gitlab/github_import/queue_spec.rb @@ -11,6 +11,6 @@ describe Gitlab::GithubImport::Queue do include Gitlab::GithubImport::Queue end - expect(worker.sidekiq_options['queue']).to eq('github_importer') + expect(worker.sidekiq_options['queue']).to eq('github_importer:dummy') end end diff --git a/spec/workers/concerns/pipeline_queue_spec.rb b/spec/workers/concerns/pipeline_queue_spec.rb index dd911760948..a312b307fce 100644 --- a/spec/workers/concerns/pipeline_queue_spec.rb +++ b/spec/workers/concerns/pipeline_queue_spec.rb @@ -14,15 +14,6 @@ describe PipelineQueue do it 'sets a default pipelines queue automatically' do expect(worker.sidekiq_options['queue']) - .to eq 'pipeline_default' - end - - describe '.enqueue_in' do - it 'sets a custom sidekiq queue with prefix and group' do - worker.enqueue_in(group: :processing) - - expect(worker.sidekiq_options['queue']) - .to eq 'pipeline_processing' - end + .to eq 'pipeline_default:dummy' end end diff --git a/spec/workers/concerns/project_import_options_spec.rb b/spec/workers/concerns/project_import_options_spec.rb new file mode 100644 index 00000000000..b6c111df8b9 --- /dev/null +++ b/spec/workers/concerns/project_import_options_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe ProjectImportOptions do + let(:project) { create(:project, :import_started) } + let(:job) { { 'args' => [project.id, nil, nil], 'jid' => '123' } } + let(:worker_class) do + Class.new do + include Sidekiq::Worker + include ProjectImportOptions + end + end + + it 'sets default retry limit' do + expect(worker_class.sidekiq_options['retry']).to eq(ProjectImportOptions::IMPORT_RETRY_COUNT) + end + + it 'sets default status expiration' do + expect(worker_class.sidekiq_options['status_expiration']).to eq(StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION) + end + + describe '.sidekiq_retries_exhausted' do + it 'marks fork as failed' do + expect { worker_class.sidekiq_retries_exhausted_block.call(job) }.to change { project.reload.import_status }.from("started").to("failed") + end + + it 'logs the appropriate error message for forked projects' do + allow_any_instance_of(Project).to receive(:forked?).and_return(true) + + worker_class.sidekiq_retries_exhausted_block.call(job) + + expect(project.reload.import_error).to include("fork") + end + + it 'logs the appropriate error message for forked projects' do + worker_class.sidekiq_retries_exhausted_block.call(job) + + expect(project.reload.import_error).to include("import") + end + end +end diff --git a/spec/workers/concerns/repository_check_queue_spec.rb b/spec/workers/concerns/repository_check_queue_spec.rb index fdbbfcc90a5..d2eeecfc9a8 100644 --- a/spec/workers/concerns/repository_check_queue_spec.rb +++ b/spec/workers/concerns/repository_check_queue_spec.rb @@ -13,7 +13,7 @@ describe RepositoryCheckQueue do end it 'sets the queue name of a worker' do - expect(worker.sidekiq_options['queue'].to_s).to eq('repository_check') + expect(worker.sidekiq_options['queue'].to_s).to eq('repository_check:dummy') end it 'disables retrying of failed jobs' do diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb index 7ee0a51a263..9e3b99b3502 100644 --- a/spec/workers/every_sidekiq_worker_spec.rb +++ b/spec/workers/every_sidekiq_worker_spec.rb @@ -1,21 +1,36 @@ require 'spec_helper' describe 'Every Sidekiq worker' do - it 'includes ApplicationWorker' do - expect(Gitlab::SidekiqConfig.workers).to all(include(ApplicationWorker)) - end - it 'does not use the default queue' do expect(Gitlab::SidekiqConfig.workers.map(&:queue)).not_to include('default') end it 'uses the cronjob queue when the worker runs as a cronjob' do - expect(Gitlab::SidekiqConfig.cron_workers.map(&:queue)).to all(eq('cronjob')) + expect(Gitlab::SidekiqConfig.cron_workers.map(&:queue)).to all(start_with('cronjob:')) + end + + it 'has its queue in app/workers/all_queues.yml', :aggregate_failures do + file_worker_queues = Gitlab::SidekiqConfig.worker_queues.to_set + + worker_queues = Gitlab::SidekiqConfig.workers.map(&:queue).to_set + worker_queues << ActionMailer::DeliveryJob.queue_name + worker_queues << 'default' + + missing_from_file = worker_queues - file_worker_queues + expect(missing_from_file).to be_empty, "expected #{missing_from_file.to_a.inspect} to be in app/workers/all_queues.yml" + + unncessarily_in_file = file_worker_queues - worker_queues + expect(unncessarily_in_file).to be_empty, "expected #{unncessarily_in_file.to_a.inspect} not to be in app/workers/all_queues.yml" end - it 'defines the queue in the Sidekiq configuration file' do - config_queue_names = Gitlab::SidekiqConfig.config_queues.to_set + it 'has its queue or namespace in config/sidekiq_queues.yml', :aggregate_failures do + config_queues = Gitlab::SidekiqConfig.config_queues.to_set + + Gitlab::SidekiqConfig.workers.each do |worker| + queue = worker.queue + queue_namespace = queue.split(':').first - expect(Gitlab::SidekiqConfig.worker_queues).to all(be_in(config_queue_names)) + expect(config_queues).to include(queue).or(include(queue_namespace)) + end end end diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index 74c85848b7e..31598586f59 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -1,17 +1,21 @@ require 'spec_helper' describe RepositoryForkWorker do - let(:project) { create(:project, :repository) } - let(:fork_project) { create(:project, :repository, :import_scheduled, forked_from_project: project) } - let(:shell) { Gitlab::Shell.new } - - subject { described_class.new } - - before do - allow(subject).to receive(:gitlab_shell).and_return(shell) + describe 'modules' do + it 'includes ProjectImportOptions' do + expect(described_class).to include_module(ProjectImportOptions) + end end describe "#perform" do + let(:project) { create(:project, :repository) } + let(:fork_project) { create(:project, :repository, :import_scheduled, forked_from_project: project) } + let(:shell) { Gitlab::Shell.new } + + before do + allow(subject).to receive(:gitlab_shell).and_return(shell) + end + def perform! subject.perform(fork_project.id, '/test/path', project.disk_path) end @@ -60,14 +64,7 @@ describe RepositoryForkWorker do expect_fork_repository.and_return(false) - expect { perform! }.to raise_error(RepositoryForkWorker::ForkError, error_message) - end - - it 'handles unexpected error' do - expect_fork_repository.and_raise(RuntimeError) - - expect { perform! }.to raise_error(RepositoryForkWorker::ForkError) - expect(fork_project.reload.import_status).to eq('failed') + expect { perform! }.to raise_error(StandardError, error_message) end end end diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb index 0af537647ad..85ac14eb347 100644 --- a/spec/workers/repository_import_worker_spec.rb +++ b/spec/workers/repository_import_worker_spec.rb @@ -1,11 +1,15 @@ require 'spec_helper' describe RepositoryImportWorker do - let(:project) { create(:project, :import_scheduled) } - - subject { described_class.new } + describe 'modules' do + it 'includes ProjectImportOptions' do + expect(described_class).to include_module(ProjectImportOptions) + end + end describe '#perform' do + let(:project) { create(:project, :import_scheduled) } + context 'when worker was reset without cleanup' do let(:jid) { '12345678' } let(:started_project) { create(:project, :import_started, import_jid: jid) } @@ -44,22 +48,11 @@ describe RepositoryImportWorker do expect do subject.perform(project.id) - end.to raise_error(RepositoryImportWorker::ImportError, error) + end.to raise_error(StandardError, error) expect(project.reload.import_jid).not_to be_nil end end - context 'with unexpected error' do - it 'marks import as failed' do - allow_any_instance_of(Projects::ImportService).to receive(:execute).and_raise(RuntimeError) - - expect do - subject.perform(project.id) - end.to raise_error(RepositoryImportWorker::ImportError) - expect(project.reload.import_status).to eq('failed') - end - end - context 'when using an asynchronous importer' do it 'does not mark the import process as finished' do service = double(:service) diff --git a/spec/workers/run_pipeline_schedule_worker_spec.rb b/spec/workers/run_pipeline_schedule_worker_spec.rb new file mode 100644 index 00000000000..481a84837f9 --- /dev/null +++ b/spec/workers/run_pipeline_schedule_worker_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe RunPipelineScheduleWorker do + describe '#perform' do + set(:project) { create(:project) } + set(:user) { create(:user) } + set(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project ) } + let(:worker) { described_class.new } + + context 'when a project not found' do + it 'does not call the Service' do + expect(Ci::CreatePipelineService).not_to receive(:new) + expect(worker).not_to receive(:run_pipeline_schedule) + + worker.perform(100000, user.id) + end + end + + context 'when a user not found' do + it 'does not call the Service' do + expect(Ci::CreatePipelineService).not_to receive(:new) + expect(worker).not_to receive(:run_pipeline_schedule) + + worker.perform(pipeline_schedule.id, 10000) + end + end + + context 'when everything is ok' do + let(:create_pipeline_service) { instance_double(Ci::CreatePipelineService) } + + it 'calls the Service' do + expect(Ci::CreatePipelineService).to receive(:new).with(project, user, ref: pipeline_schedule.ref).and_return(create_pipeline_service) + expect(create_pipeline_service).to receive(:execute).with(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: pipeline_schedule) + + worker.perform(pipeline_schedule.id, user.id) + end + end + end +end diff --git a/spec/workers/stuck_merge_jobs_worker_spec.rb b/spec/workers/stuck_merge_jobs_worker_spec.rb index f8b55e873df..c2c2a5f9121 100644 --- a/spec/workers/stuck_merge_jobs_worker_spec.rb +++ b/spec/workers/stuck_merge_jobs_worker_spec.rb @@ -14,7 +14,6 @@ describe StuckMergeJobsWorker do mr_with_sha.reload mr_without_sha.reload - expect(mr_with_sha).to be_merged expect(mr_without_sha).to be_opened expect(mr_with_sha.merge_jid).to be_present @@ -24,10 +23,13 @@ describe StuckMergeJobsWorker do it 'updates merge request to opened when locked but has not been merged' do allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(%w(123)) merge_request = create(:merge_request, :locked, merge_jid: '123', state: :locked) + pipeline = create(:ci_empty_pipeline, project: merge_request.project, ref: merge_request.source_branch, sha: merge_request.source_branch_sha) worker.perform - expect(merge_request.reload).to be_opened + merge_request.reload + expect(merge_request).to be_opened + expect(merge_request.head_pipeline).to eq(pipeline) end it 'logs updated stuck merge job ids' do diff --git a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb index 522e1566271..9adde5fc21a 100644 --- a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb +++ b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb @@ -22,7 +22,7 @@ describe UpdateHeadPipelineForMergeRequestWorker do end it 'does not update head_pipeline_id' do - expect { subject.perform(merge_request.id) }.to raise_error(ArgumentError) + expect { subject.perform(merge_request.id) }.not_to raise_error expect(merge_request.reload.head_pipeline_id).to eq(nil) end |