diff options
author | Mike Greiling <mike@pixelcog.com> | 2017-01-27 19:33:58 -0600 |
---|---|---|
committer | Mike Greiling <mike@pixelcog.com> | 2017-01-27 19:33:58 -0600 |
commit | 69e4072f89ad9aeebcc852373341f790c1b021e2 (patch) | |
tree | c68ad1ee38efe48707e8ea467db3e2759f1a88c0 /spec | |
parent | c5b7cc54e9bfceda7d48b1f15bcf064a0d96c07d (diff) | |
parent | 6ccc4eb42a05d4ce8b75773723305bd82305dfec (diff) | |
download | gitlab-ce-69e4072f89ad9aeebcc852373341f790c1b021e2.tar.gz |
Merge branch 'master' into go-go-gadget-webpack
* master: (389 commits)
Document "No gems fetched from git repositories" policy [ci skip]
Typos
Small gramatical tweaks
Typos
Added PHP & NPM doc
Use `:empty_project` where possible in request specs
Add caching of droplab ajax requests
Use `:empty_project` where possible in model specs
Revert 3f17f29a
Remove unused js response from refs controller
Add MR id to changelog entry
fixed small mini pipeline graph line glitch
Prevent form to be submitted twice
Fix Error 500 when repositories contain annotated tags pointing to blobs
Fix /explore sorting (trending)
Simplify wording in "adding an image" docs
Remove "official merge window" from CONTRIBUTING.md [ci skip]
Update repository check documentation
Fixed flexbox and wrap issues
Update two_factor_authentication.md
...
Diffstat (limited to 'spec')
393 files changed, 5482 insertions, 1673 deletions
diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb index 602de72d23f..84db26a958a 100644 --- a/spec/controllers/admin/groups_controller_spec.rb +++ b/spec/controllers/admin/groups_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Admin::GroupsController do let(:group) { create(:group) } - let(:project) { create(:project, namespace: group) } + let(:project) { create(:empty_project, namespace: group) } let(:admin) { create(:admin) } before do diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb index 8eaacef2024..2c35d394b74 100644 --- a/spec/controllers/admin/projects_controller_spec.rb +++ b/spec/controllers/admin/projects_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Admin::ProjectsController do - let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + let!(:project) { create(:empty_project, :public) } before do sign_in(create(:admin)) diff --git a/spec/controllers/admin/services_controller_spec.rb b/spec/controllers/admin/services_controller_spec.rb new file mode 100644 index 00000000000..e5cdd52307e --- /dev/null +++ b/spec/controllers/admin/services_controller_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe Admin::ServicesController do + let(:admin) { create(:admin) } + + before { sign_in(admin) } + + describe 'GET #edit' do + let!(:project) { create(:empty_project) } + + Service.available_services_names.each do |service_name| + context "#{service_name}" do + let!(:service) do + service_template = service_name.concat("_service").camelize.constantize + service_template.where(template: true).first_or_create + end + + it 'successfully displays the template' do + get :edit, id: service.id + + expect(response).to have_http_status(200) + end + end + end + end +end diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index ea2fd90a9b0..7d2f6dd9d0a 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe AutocompleteController do - let!(:project) { create(:project) } + let!(:project) { create(:empty_project) } let!(:user) { create(:user) } context 'GET users' do diff --git a/spec/controllers/blob_controller_spec.rb b/spec/controllers/blob_controller_spec.rb index 465013231f9..2fcb4a6a528 100644 --- a/spec/controllers/blob_controller_spec.rb +++ b/spec/controllers/blob_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::BlobController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/ci/projects_controller_spec.rb b/spec/controllers/ci/projects_controller_spec.rb index 5022a3e2c80..86f01f437a2 100644 --- a/spec/controllers/ci/projects_controller_spec.rb +++ b/spec/controllers/ci/projects_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Ci::ProjectsController do let(:visibility) { :public } - let!(:project) { create(:project, visibility, ci_id: 1) } + let!(:project) { create(:empty_project, visibility, ci_id: 1) } let(:ci_id) { project.ci_id } describe '#index' do diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb index 19fbc2f7748..79ef3a1adad 100644 --- a/spec/controllers/dashboard/todos_controller_spec.rb +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Dashboard::TodosController do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:todo_service) { TodoService.new } describe 'GET #index' do diff --git a/spec/controllers/explore/projects_controller_spec.rb b/spec/controllers/explore/projects_controller_spec.rb new file mode 100644 index 00000000000..6128091f543 --- /dev/null +++ b/spec/controllers/explore/projects_controller_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe Explore::ProjectsController do + let(:user) { create(:user) } + let(:visibility) { :public } + + describe 'GET #trending' do + let!(:project_1) { create(:project, visibility, ci_id: 1) } + let!(:project_2) { create(:project, visibility, ci_id: 2) } + + let!(:trending_project_1) { create(:trending_project, project: project_1) } + let!(:trending_project_2) { create(:trending_project, project: project_2) } + + before do + sign_in(user) + end + + context 'sorting by update date' do + it 'sorts by last updated' do + get :trending, sort: 'updated_desc' + expect(assigns(:projects)).to eq [project_2, project_1] + end + + it 'sorts by oldest updated' do + get :trending, sort: 'updated_asc' + expect(assigns(:projects)).to eq [project_1, project_2] + end + end + end +end diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index 8c52f615b8b..6e4b5f78e33 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Groups::MilestonesController do let(:group) { create(:group) } - let(:project) { create(:project, group: group) } + let(:project) { create(:empty_project, group: group) } let(:project2) { create(:empty_project, group: group) } let(:user) { create(:user) } let(:title) { '肯定不是中文的问题' } diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 98dfb3e5216..cad82a34fb0 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe GroupsController do let(:user) { create(:user) } let(:group) { create(:group) } - let(:project) { create(:project, namespace: group) } + let(:project) { create(:empty_project, namespace: group) } let!(:group_member) { create(:group_member, group: group, user: user) } describe 'GET #index' do diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb index 56ecf2bb644..cfe18dd4b6c 100644 --- a/spec/controllers/health_check_controller_spec.rb +++ b/spec/controllers/health_check_controller_spec.rb @@ -1,10 +1,16 @@ require 'spec_helper' describe HealthCheckController do + include StubENV + let(:token) { current_application_settings.health_check_access_token } let(:json_response) { JSON.parse(response.body) } let(:xml_response) { Hash.from_xml(response.body)['hash'] } + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') + end + describe 'GET #index' do context 'when services are up but NO access token' do it 'returns a not found page' do diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb index ce7c0b334ee..fa4cc0ebbe0 100644 --- a/spec/controllers/import/bitbucket_controller_spec.rb +++ b/spec/controllers/import/bitbucket_controller_spec.rb @@ -52,7 +52,7 @@ describe Import::BitbucketController do end it "assigns variables" do - @project = create(:project, import_type: 'bitbucket', creator_id: user.id) + @project = create(:empty_project, import_type: 'bitbucket', creator_id: user.id) allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo]) get :status @@ -63,7 +63,7 @@ describe Import::BitbucketController do end it "does not show already added project" do - @project = create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim') + @project = create(:empty_project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim') allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo]) get :status diff --git a/spec/controllers/import/fogbugz_controller_spec.rb b/spec/controllers/import/fogbugz_controller_spec.rb index 5f0f6dea821..fffbc805335 100644 --- a/spec/controllers/import/fogbugz_controller_spec.rb +++ b/spec/controllers/import/fogbugz_controller_spec.rb @@ -16,7 +16,7 @@ describe Import::FogbugzController do end it 'assigns variables' do - @project = create(:project, import_type: 'fogbugz', creator_id: user.id) + @project = create(:empty_project, import_type: 'fogbugz', creator_id: user.id) stub_client(repos: [@repo]) get :status @@ -26,7 +26,7 @@ describe Import::FogbugzController do end it 'does not show already added project' do - @project = create(:project, import_type: 'fogbugz', creator_id: user.id, import_source: 'vim') + @project = create(:empty_project, import_type: 'fogbugz', creator_id: user.id, import_source: 'vim') stub_client(repos: [@repo]) get :status diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb index 6f75ebb16c8..3f73ea000ae 100644 --- a/spec/controllers/import/gitlab_controller_spec.rb +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -36,7 +36,7 @@ describe Import::GitlabController do end it "assigns variables" do - @project = create(:project, import_type: 'gitlab', creator_id: user.id) + @project = create(:empty_project, import_type: 'gitlab', creator_id: user.id) stub_client(projects: [@repo]) get :status @@ -46,7 +46,7 @@ describe Import::GitlabController do end it "does not show already added project" do - @project = create(:project, import_type: 'gitlab', creator_id: user.id, import_source: 'asd/vim') + @project = create(:empty_project, import_type: 'gitlab', creator_id: user.id, import_source: 'asd/vim') stub_client(projects: [@repo]) get :status diff --git a/spec/controllers/import/google_code_controller_spec.rb b/spec/controllers/import/google_code_controller_spec.rb index 4241db6e771..c96fb90f70e 100644 --- a/spec/controllers/import/google_code_controller_spec.rb +++ b/spec/controllers/import/google_code_controller_spec.rb @@ -27,7 +27,7 @@ describe Import::GoogleCodeController do end it "assigns variables" do - @project = create(:project, import_type: 'google_code', creator_id: user.id) + @project = create(:empty_project, import_type: 'google_code', creator_id: user.id) stub_client(repos: [@repo], incompatible_repos: []) get :status @@ -38,7 +38,7 @@ describe Import::GoogleCodeController do end it "does not show already added project" do - @project = create(:project, import_type: 'google_code', creator_id: user.id, import_source: 'vim') + @project = create(:empty_project, import_type: 'google_code', creator_id: user.id, import_source: 'vim') stub_client(repos: [@repo], incompatible_repos: []) get :status diff --git a/spec/controllers/notification_settings_controller_spec.rb b/spec/controllers/notification_settings_controller_spec.rb index 79b819a1377..9e3a31e1a6b 100644 --- a/spec/controllers/notification_settings_controller_spec.rb +++ b/spec/controllers/notification_settings_controller_spec.rb @@ -93,7 +93,7 @@ describe NotificationSettingsController do end context 'not authorized' do - let(:private_project) { create(:project, :private) } + let(:private_project) { create(:empty_project, :private) } before { sign_in(user) } it 'returns 404' do diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb index f5ea097af8b..8b71d6518bb 100644 --- a/spec/controllers/projects/avatars_controller_spec.rb +++ b/spec/controllers/projects/avatars_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::AvatarsController do - let(:project) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } + let(:project) { create(:empty_project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/blame_controller_spec.rb b/spec/controllers/projects/blame_controller_spec.rb index 4402ca43c65..addc5e7ec33 100644 --- a/spec/controllers/projects/blame_controller_spec.rb +++ b/spec/controllers/projects/blame_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::BlameController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index f35c5d992d9..b36d0e69330 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' describe Projects::BlobController do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index b88586b8678..9de03876755 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::BranchesController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:developer) { create(:user) } diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index 646b097d74e..a95cfc5c6be 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -1,9 +1,10 @@ require 'spec_helper' describe Projects::CommitController do - let(:project) { create(:project) } - let(:user) { create(:user) } - let(:commit) { project.commit("master") } + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + let(:commit) { project.commit("master") } + let(:pipeline) { create(:ci_pipeline, project: project, commit: commit) } let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' } let(:master_pickable_commit) { project.commit(master_pickable_sha) } @@ -309,4 +310,35 @@ describe Projects::CommitController do end end end + + describe 'GET pipelines' do + def get_pipelines(extra_params = {}) + params = { + namespace_id: project.namespace.to_param, + project_id: project.to_param + } + + get :pipelines, params.merge(extra_params) + end + + context 'when the commit exists' do + context 'when the commit has one or more pipelines' do + it 'shows pipelines' do + get_pipelines(id: commit.id) + + expect(response).to be_ok + end + end + end + + context 'when the commit does not exist' do + before do + get_pipelines(id: 'e7a412c8da9f6d0081a633a4a402dde1c4694ebd') + end + + it 'returns a 404' do + expect(response).to have_http_status(404) + end + end + end end diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index 1ac7e03a2db..54b8d1108a5 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::CommitsController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index b03c4b52de6..e811c76fb31 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::CompareController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:ref_from) { "improve%2Fawesome" } let(:ref_to) { "feature" } diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb index a971adf0539..6a6d71a16ee 100644 --- a/spec/controllers/projects/cycle_analytics_controller_spec.rb +++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::CycleAnalyticsController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb index ff617fea847..79ab364a6f3 100644 --- a/spec/controllers/projects/discussions_controller_spec.rb +++ b/spec/controllers/projects/discussions_controller_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' describe Projects::DiscussionsController do - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:merge_request) { create(:merge_request, source_project: project) } + let(:user) { create(:user) } + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.source_project } let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) } let(:discussion) { note.discussion } diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb index 038dfeb8466..a4884256c92 100644 --- a/spec/controllers/projects/find_file_controller_spec.rb +++ b/spec/controllers/projects/find_file_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::FindFileController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb index 028ea067a97..a867668d97b 100644 --- a/spec/controllers/projects/forks_controller_spec.rb +++ b/spec/controllers/projects/forks_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::ForksController do let(:user) { create(:user) } - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:forked_project) { Projects::ForkService.new(project, user).execute } let(:group) { create(:group, owner: forked_project.creator) } diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb index 74e6603b0cb..bbe8e4bf6b2 100644 --- a/spec/controllers/projects/graphs_controller_spec.rb +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::GraphsController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb index 17dc101b7ee..a976a9c27ab 100644 --- a/spec/controllers/projects/group_links_controller_spec.rb +++ b/spec/controllers/projects/group_links_controller_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Projects::GroupLinksController do let(:group) { create(:group, :private) } let(:group2) { create(:group, :private) } - let(:project) { create(:project, :private, group: group2) } + let(:project) { create(:empty_project, :private, group: group2) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index b5987a83df0..5f27f336f72 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -98,7 +98,7 @@ describe Projects::IssuesController do end it 'fills in an issue for a merge request' do - project_with_repository = create(:project) + project_with_repository = create(:project, :repository) project_with_repository.team << [user, :developer] mr = create(:merge_request_with_diff_notes, source_project: project_with_repository) @@ -124,7 +124,7 @@ describe Projects::IssuesController do describe 'PUT #update' do context 'when moving issue to another private project' do - let(:another_project) { create(:project, :private) } + let(:another_project) { create(:empty_project, :private) } before do sign_in(user) @@ -466,7 +466,7 @@ describe Projects::IssuesController do context "when the user is owner" do let(:owner) { create(:user) } let(:namespace) { create(:namespace, owner: owner) } - let(:project) { create(:project, namespace: namespace) } + let(:project) { create(:empty_project, namespace: namespace) } before { sign_in(owner) } diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index 6d30d085056..14207bf6b7a 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::MilestonesController do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:user) { create(:user) } let(:milestone) { create(:milestone, project: project) } let(:issue) { create(:issue, project: project, milestone: milestone) } diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index 9f6d4ec6537..dc597202050 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::NotesController do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:issue) { create(:issue, project: project) } let(:note) { create(:note, noteable: issue, project: project) } @@ -16,6 +16,7 @@ describe Projects::NotesController do describe 'POST create' do let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.source_project } let(:request_params) do { note: { note: 'some note', noteable_id: merge_request.id, noteable_type: 'MergeRequest' }, @@ -88,6 +89,7 @@ describe Projects::NotesController do end describe "resolving and unresolving" do + let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, source_project: project) } let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) } diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 442f81187dc..416eaa0037e 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -143,7 +143,7 @@ describe Projects::ProjectMembersController do end context 'and is an owner' do - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:empty_project, namespace: user.namespace) } before { project.team << [user, :master] } @@ -234,7 +234,7 @@ describe Projects::ProjectMembersController do end describe 'POST apply_import' do - let(:another_project) { create(:project, :private) } + let(:another_project) { create(:empty_project, :private) } let(:member) { create(:user) } before do diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb index 04bd9a01f7b..b23d6e257ba 100644 --- a/spec/controllers/projects/raw_controller_spec.rb +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::RawController do - let(:public_project) { create(:project, :public) } + let(:public_project) { create(:project, :public, :repository) } describe "#show" do context 'regular filename' do diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb index abd45a74e2d..d8fb4667c67 100644 --- a/spec/controllers/projects/refs_controller_spec.rb +++ b/spec/controllers/projects/refs_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::RefsController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb index 9fd5c3b85f6..69fcc26c77e 100644 --- a/spec/controllers/projects/releases_controller_spec.rb +++ b/spec/controllers/projects/releases_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::ReleasesController do - let!(:project) { create(:project) } + let!(:project) { create(:project, :repository) } let!(:user) { create(:user) } let!(:release) { create(:release, project: project) } let!(:tag) { release.tag } diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index 38e02a46626..04e88879fb8 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe Projects::RepositoriesController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } describe "GET archive" do context 'as a guest' do diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 2e44b5128b4..16365642a34 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::ServicesController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:service) { create(:service, project: project) } @@ -54,6 +54,7 @@ describe Projects::ServicesController do context 'on successful update' do it 'sets the flash' do expect(service).to receive(:to_param).and_return('hipchat') + expect(service).to receive(:event_names).and_return(HipchatService.event_names) put :update, namespace_id: project.namespace.id, diff --git a/spec/controllers/projects/settings/integrations_controller_spec.rb b/spec/controllers/projects/settings/integrations_controller_spec.rb new file mode 100644 index 00000000000..65f7bb34f4a --- /dev/null +++ b/spec/controllers/projects/settings/integrations_controller_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Projects::Settings::IntegrationsController do + let(:project) { create(:empty_project, :public) } + let(:user) { create(:user) } + + before do + project.team << [user, :master] + sign_in(user) + end + + describe 'GET show' do + it 'renders show with 200 status code' do + get :show, namespace_id: project.namespace, project_id: project + + expect(response).to have_http_status(200) + expect(response).to render_template(:show) + end + end +end diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index 5e661c2c41d..c36a5fdd66c 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::TagsController do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let!(:release) { create(:release, project: project) } let!(:invalid_release) { create(:release, project: project, tag: 'does-not-exist') } diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb index 19a152bcb05..99d0bcfa8d1 100644 --- a/spec/controllers/projects/templates_controller_spec.rb +++ b/spec/controllers/projects/templates_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::TemplatesController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:user2) { create(:user) } let(:file_path_1) { '.gitlab/issue_templates/bug.md' } diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb index 1cc050247c6..b81645a3d2d 100644 --- a/spec/controllers/projects/tree_controller_spec.rb +++ b/spec/controllers/projects/tree_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::TreeController do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } before do diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb index 71d0e4be834..f1c8891e87a 100644 --- a/spec/controllers/projects/uploads_controller_spec.rb +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -1,7 +1,7 @@ require('spec_helper') describe Projects::UploadsController do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:user) { create(:user) } let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index d0a63aa9403..9323f723bdb 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -1,11 +1,11 @@ require('spec_helper') describe ProjectsController do - let(:project) { create(:project) } - let(:public_project) { create(:project, :public) } - let(:user) { create(:user) } - let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } - let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } + let(:project) { create(:empty_project) } + let(:public_project) { create(:empty_project, :public) } + let(:user) { create(:user) } + let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } + let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } describe 'GET index' do context 'as a user' do @@ -32,7 +32,7 @@ describe ProjectsController do before { sign_in(user) } context "user does not have access to project" do - let(:private_project) { create(:project, :private) } + let(:private_project) { create(:empty_project, :private) } it "does not initialize notification setting" do get :show, namespace_id: private_project.namespace.path, id: private_project.path @@ -146,6 +146,8 @@ describe ProjectsController do end context "rendering default project view" do + let(:public_project) { create(:project, :public, :repository) } + render_views it "renders the activity view" do @@ -190,25 +192,11 @@ describe ProjectsController do expect(assigns(:project)).to eq(public_project) expect(response).to redirect_to("/#{public_project.path_with_namespace}") end - - # MySQL queries are case insensitive by default, so this spec would fail. - if Gitlab::Database.postgresql? - context "when there is also a match with the same casing" do - let!(:other_project) { create(:project, :public, namespace: public_project.namespace, path: public_project.path.upcase) } - - it "loads the exactly matched project" do - get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase - - expect(assigns(:project)).to eq(other_project) - expect(response).to have_http_status(200) - end - end - end end end context "when the url contains .atom" do - let(:public_project_with_dot_atom) { build(:project, :public, name: 'my.atom', path: 'my.atom') } + let(:public_project_with_dot_atom) { build(:empty_project, :public, name: 'my.atom', path: 'my.atom') } it 'expects an error creating the project' do expect(public_project_with_dot_atom).not_to be_valid @@ -217,7 +205,7 @@ describe ProjectsController do context 'when the project is pending deletions' do it 'renders a 404 error' do - project = create(:project, pending_delete: true) + project = create(:empty_project, pending_delete: true) sign_in(user) get :show, namespace_id: project.namespace.path, id: project.path @@ -233,6 +221,7 @@ describe ProjectsController do let(:admin) { create(:admin) } it "sets the repository to the right path after a rename" do + project = create(:project, :repository) new_path = 'renamed_path' project_params = { path: new_path } controller.instance_variable_set(:@project, project) @@ -384,6 +373,8 @@ describe ProjectsController do end describe "GET refs" do + let(:public_project) { create(:project, :public) } + it "gets a list of branches and tags" do get :refs, namespace_id: public_project.namespace.path, id: public_project.path diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 69124ab06bf..570d9fa43f8 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -41,7 +41,7 @@ describe UploadsController do end context "when viewing a project avatar" do - let!(:project) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } + let!(:project) { create(:empty_project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } context "when the project is public" do before do diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 19a8b1fe524..bbe9aaf737f 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -73,7 +73,7 @@ describe UsersController do end context 'forked project' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:forked_project) { Projects::ForkService.new(project, user).execute } before do @@ -91,7 +91,7 @@ describe UsersController do end describe 'GET #calendar_activities' do - let!(:project) { create(:project) } + let!(:project) { create(:empty_project) } let!(:user) { create(:user) } before do diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb index ed4acca23f1..c3b4aff55ba 100644 --- a/spec/factories/ci/runners.rb +++ b/spec/factories/ci/runners.rb @@ -16,6 +16,10 @@ FactoryGirl.define do is_shared true end + trait :specific do + is_shared false + end + trait :inactive do active false end diff --git a/spec/factories/ci/stages.rb b/spec/factories/ci/stages.rb index ee3b17b8bf1..7f557b25ccb 100644 --- a/spec/factories/ci/stages.rb +++ b/spec/factories/ci/stages.rb @@ -3,11 +3,12 @@ FactoryGirl.define do transient do name 'test' status nil + warnings nil pipeline factory: :ci_empty_pipeline end initialize_with do - Ci::Stage.new(pipeline, name: name, status: status) + Ci::Stage.new(pipeline, name: name, status: status, warnings: warnings) end end end diff --git a/spec/factories/deploy_keys_projects.rb b/spec/factories/deploy_keys_projects.rb index 27cece487bd..75f8982ecd9 100644 --- a/spec/factories/deploy_keys_projects.rb +++ b/spec/factories/deploy_keys_projects.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :deploy_keys_project do deploy_key - project + project factory: :empty_project end end diff --git a/spec/factories/events.rb b/spec/factories/events.rb index 8820d527c61..bfe41f71b57 100644 --- a/spec/factories/events.rb +++ b/spec/factories/events.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :event do - project + project factory: :empty_project author factory: :user factory :closed_issue_event do diff --git a/spec/factories/file_uploader.rb b/spec/factories/file_uploader.rb index 1b36e21f2b0..bc74aeecc3b 100644 --- a/spec/factories/file_uploader.rb +++ b/spec/factories/file_uploader.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :file_uploader do - project + project factory: :empty_project secret nil transient do diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb index ece6beb9fa9..86f51ffca99 100644 --- a/spec/factories/groups.rb +++ b/spec/factories/groups.rb @@ -1,8 +1,9 @@ FactoryGirl.define do - factory :group do + factory :group, class: Group, parent: :namespace do sequence(:name) { |n| "group#{n}" } path { name.downcase.gsub(/\s/, '_') } type 'Group' + owner nil trait :public do visibility_level Gitlab::VisibilityLevel::PUBLIC diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb index 2b4670be468..7e09f1ba8ea 100644 --- a/spec/factories/issues.rb +++ b/spec/factories/issues.rb @@ -6,7 +6,7 @@ FactoryGirl.define do factory :issue do title author - project + project factory: :empty_project trait :confidential do confidential true diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index 3e8822faf97..5ba8443c62c 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -2,7 +2,7 @@ FactoryGirl.define do factory :label, class: ProjectLabel do sequence(:title) { |n| "label#{n}" } color "#990000" - project + project factory: :empty_project transient do priority nil diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 37eb49c94df..22f84150bb3 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -2,7 +2,7 @@ FactoryGirl.define do factory :merge_request do title author - source_project factory: :project + association :source_project, :repository, factory: :project target_project { source_project } # $ git log --pretty=oneline feature..master diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb index 84da71ed6dc..841ab3c73b8 100644 --- a/spec/factories/milestones.rb +++ b/spec/factories/milestones.rb @@ -1,7 +1,7 @@ FactoryGirl.define do factory :milestone do title - project + project factory: :empty_project trait :active do state "active" diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index a10ba629760..a21da7074f9 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -4,7 +4,7 @@ include ActionDispatch::TestProcess FactoryGirl.define do factory :note do - project + project factory: :empty_project note "Note" author on_issue @@ -13,12 +13,19 @@ FactoryGirl.define do factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note] factory :note_on_merge_request, traits: [:on_merge_request] factory :note_on_project_snippet, traits: [:on_project_snippet] + factory :note_on_personal_snippet, traits: [:on_personal_snippet] factory :system_note, traits: [:system] - factory :legacy_diff_note_on_commit, traits: [:on_commit, :legacy_diff_note], class: LegacyDiffNote - factory :legacy_diff_note_on_merge_request, traits: [:on_merge_request, :legacy_diff_note], class: LegacyDiffNote + factory :legacy_diff_note_on_commit, traits: [:on_commit, :legacy_diff_note], class: LegacyDiffNote do + association :project, :repository + end + + factory :legacy_diff_note_on_merge_request, traits: [:on_merge_request, :legacy_diff_note], class: LegacyDiffNote do + association :project, :repository + end factory :diff_note_on_merge_request, traits: [:on_merge_request], class: DiffNote do + association :project, :repository position do Gitlab::Diff::Position.new( old_path: "files/ruby/popen.rb", @@ -36,6 +43,7 @@ FactoryGirl.define do end factory :diff_note_on_commit, traits: [:on_commit], class: DiffNote do + association :project, :repository position do Gitlab::Diff::Position.new( old_path: "files/ruby/popen.rb", @@ -48,6 +56,7 @@ FactoryGirl.define do end trait :on_commit do + association :project, :repository noteable nil noteable_type 'Commit' noteable_id nil @@ -70,6 +79,11 @@ FactoryGirl.define do noteable { create(:project_snippet, project: project) } end + trait :on_personal_snippet do + noteable { create(:personal_snippet) } + project nil + end + trait :system do system true end diff --git a/spec/factories/project_group_links.rb b/spec/factories/project_group_links.rb index e73cc05f9d7..50341d943f5 100644 --- a/spec/factories/project_group_links.rb +++ b/spec/factories/project_group_links.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :project_group_link do - project + project factory: :empty_project group end end diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb index c21927640d1..d62799a5a47 100644 --- a/spec/factories/project_members.rb +++ b/spec/factories/project_members.rb @@ -1,7 +1,7 @@ FactoryGirl.define do factory :project_member do user - project + project factory: :empty_project master trait(:guest) { access_level ProjectMember::GUEST } diff --git a/spec/factories/project_snippets.rb b/spec/factories/project_snippets.rb index d681a2c8483..e0fe1b36fd3 100644 --- a/spec/factories/project_snippets.rb +++ b/spec/factories/project_snippets.rb @@ -1,5 +1,5 @@ FactoryGirl.define do factory :project_snippet, parent: :snippet, class: :ProjectSnippet do - project + project factory: :empty_project end end diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb index 74497dc82c0..6a6d6fa171f 100644 --- a/spec/factories/releases.rb +++ b/spec/factories/releases.rb @@ -2,6 +2,6 @@ FactoryGirl.define do factory :release do tag "v1.1.0" description "Awesome release" - project + project factory: :empty_project end end diff --git a/spec/factories/sent_notifications.rb b/spec/factories/sent_notifications.rb index 78eb929c6e7..6287c40afe9 100644 --- a/spec/factories/sent_notifications.rb +++ b/spec/factories/sent_notifications.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :sent_notification do - project + project factory: :empty_project recipient factory: :user noteable factory: :issue reply_key "0123456789abcdef" * 2 diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 9de78d68280..a14a46c803e 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -1,5 +1,5 @@ FactoryGirl.define do factory :service do - project + project factory: :empty_project end end diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb index 082b02116c0..91d6f39a5bf 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :todo do - project + project factory: :empty_project author user target factory: :issue @@ -28,6 +28,10 @@ FactoryGirl.define do action { Todo::APPROVAL_REQUIRED } end + trait :unmergeable do + action { Todo::UNMERGEABLE } + end + trait :done do state :done end diff --git a/spec/factories/trending_project.rb b/spec/factories/trending_project.rb new file mode 100644 index 00000000000..246176611dc --- /dev/null +++ b/spec/factories/trending_project.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + # TrendingProject + factory :trending_project, class: 'TrendingProject' do + project + end +end diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb index 66044b44495..e8e080ce3e2 100644 --- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb +++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb @@ -1,10 +1,13 @@ require 'rails_helper' feature 'Admin disables Git access protocol', feature: true do + include StubENV + let(:project) { create(:empty_project, :empty_repo) } let(:admin) { create(:admin) } background do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') login_as(admin) end diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb index dec2dedf2b5..f7e49a56deb 100644 --- a/spec/features/admin/admin_health_check_spec.rb +++ b/spec/features/admin/admin_health_check_spec.rb @@ -1,9 +1,11 @@ require 'spec_helper' feature "Admin Health Check", feature: true do + include StubENV include WaitForAjax before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') login_as :admin end @@ -12,11 +14,12 @@ feature "Admin Health Check", feature: true do visit admin_health_check_path end - it { page.has_text? 'Health Check' } - it { page.has_text? 'Health information can be retrieved' } - it 'has a health check access token' do + page.has_text? 'Health Check' + page.has_text? 'Health information can be retrieved' + token = current_application_settings.health_check_access_token + expect(page).to have_content("Access token is #{token}") expect(page).to have_selector('#health-check-token', text: token) end diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index a5b88812b75..87a8f62687a 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -10,7 +10,7 @@ describe "Admin::Projects", feature: true do end describe "GET /admin/projects" do - let!(:archived_project) { create :project, :public, archived: true } + let!(:archived_project) { create :project, :public, :archived } before do visit admin_projects_path diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index d92c66b689d..f05fbe3d062 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -1,7 +1,10 @@ require 'spec_helper' describe "Admin Runners" do + include StubENV + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') login_as :admin end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 47fa2f14307..de42ab81fac 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -1,7 +1,10 @@ require 'spec_helper' feature 'Admin updates settings', feature: true do - before(:each) do + include StubENV + + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') login_as :admin visit admin_application_settings_path end diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb index 661fb761809..855247de2ea 100644 --- a/spec/features/admin/admin_uses_repository_checks_spec.rb +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -1,7 +1,12 @@ require 'rails_helper' feature 'Admin uses repository checks', feature: true do - before { login_as :admin } + include StubENV + + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') + login_as :admin + end scenario 'to trigger a single check' do project = create(:empty_project) @@ -29,7 +34,7 @@ feature 'Admin uses repository checks', feature: true do scenario 'to clear all repository checks', js: true do visit admin_application_settings_path - + expect(RepositoryCheck::ClearWorker).to receive(:perform_async) click_link 'Clear all repository checks' diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb new file mode 100644 index 00000000000..f3a5b565122 --- /dev/null +++ b/spec/features/copy_as_gfm_spec.rb @@ -0,0 +1,432 @@ +require 'spec_helper' + +describe 'Copy as GFM', feature: true, js: true do + include GitlabMarkdownHelper + include ActionView::Helpers::JavaScriptHelper + + before do + @feat = MarkdownFeature.new + + # `markdown` helper expects a `@project` variable + @project = @feat.project + + visit namespace_project_issue_path(@project.namespace, @project, @feat.issue) + end + + # The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert GitLab Flavored Markdown (GFM) to HTML. + # The handlers defined in app/assets/javascripts/copy_as_gfm.js.es6 consequently convert that same HTML to GFM. + # To make sure these filters and handlers are properly aligned, this spec tests the GFM-to-HTML-to-GFM cycle + # by verifying (`html_to_gfm(gfm_to_html(gfm)) == gfm`) for a number of examples of GFM for every filter, using the `verify` helper. + + # These are all in a single `it` for performance reasons. + it 'works', :aggregate_failures do + verify( + 'nesting', + + '> 1. [x] **[$`2 + 2`$ {-=-}{+=+} 2^2 ~~:thumbsup:~~](http://google.com)**' + ) + + verify( + 'a real world example from the gitlab-ce README', + + <<-GFM.strip_heredoc + # GitLab + + [![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) + [![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) + [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) + [![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) + + ## Canonical source + + The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/). + + ## Open source software to collaborate on code + + To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/). + + + - Manage Git repositories with fine grained access controls that keep your code secure + + - Perform code reviews and enhance collaboration with merge requests + + - Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications + + - Each project can also have an issue tracker, issue board, and a wiki + + - Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises + + - Completely free and open source (MIT Expat license) + GFM + ) + + verify( + 'InlineDiffFilter', + + '{-Deleted text-}', + '{+Added text+}' + ) + + verify( + 'TaskListFilter', + + '- [ ] Unchecked task', + '- [x] Checked task', + '1. [ ] Unchecked numbered task', + '1. [x] Checked numbered task' + ) + + verify( + 'ReferenceFilter', + + # issue reference + @feat.issue.to_reference, + # full issue reference + @feat.issue.to_reference(full: true), + # issue URL + namespace_project_issue_url(@project.namespace, @project, @feat.issue), + # issue URL with note anchor + namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123'), + # issue link + "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue)})", + # issue link with note anchor + "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123')})", + ) + + verify( + 'AutolinkFilter', + + 'https://example.com' + ) + + verify( + 'TableOfContentsFilter', + + '[[_TOC_]]' + ) + + verify( + 'EmojiFilter', + + ':thumbsup:' + ) + + verify( + 'ImageLinkFilter', + + '![Image](https://example.com/image.png)' + ) + + verify( + 'VideoLinkFilter', + + '![Video](https://example.com/video.mp4)' + ) + + verify( + 'MathFilter: math as converted from GFM to HTML', + + '$`c = \pm\sqrt{a^2 + b^2}`$', + + # math block + <<-GFM.strip_heredoc + ```math + c = \pm\sqrt{a^2 + b^2} + ``` + GFM + ) + + aggregate_failures('MathFilter: math as transformed from HTML to KaTeX') do + gfm = '$`c = \pm\sqrt{a^2 + b^2}`$' + + html = <<-HTML.strip_heredoc + <span class="katex"> + <span class="katex-mathml"> + <math> + <semantics> + <mrow> + <mi>c</mi> + <mo>=</mo> + <mo>±</mo> + <msqrt> + <mrow> + <msup> + <mi>a</mi> + <mn>2</mn> + </msup> + <mo>+</mo> + <msup> + <mi>b</mi> + <mn>2</mn> + </msup> + </mrow> + </msqrt> + </mrow> + <annotation encoding="application/x-tex">c = \\pm\\sqrt{a^2 + b^2}</annotation> + </semantics> + </math> + </span> + <span class="katex-html" aria-hidden="true"> + <span class="strut" style="height: 0.913389em;"></span> + <span class="strut bottom" style="height: 1.04em; vertical-align: -0.126611em;"></span> + <span class="base textstyle uncramped"> + <span class="mord mathit">c</span> + <span class="mrel">=</span> + <span class="mord">±</span> + <span class="sqrt mord"><span class="sqrt-sign" style="top: -0.073389em;"> + <span class="style-wrap reset-textstyle textstyle uncramped">√</span> + </span> + <span class="vlist"> + <span class="" style="top: 0em;"> + <span class="fontsize-ensurer reset-size5 size5"> + <span class="" style="font-size: 1em;"></span> + </span> + <span class="mord textstyle cramped"> + <span class="mord"> + <span class="mord mathit">a</span> + <span class="msupsub"> + <span class="vlist"> + <span class="" style="top: -0.289em; margin-right: 0.05em;"> + <span class="fontsize-ensurer reset-size5 size5"> + <span class="" style="font-size: 0em;"></span> + </span> + <span class="reset-textstyle scriptstyle cramped"> + <span class="mord mathrm">2</span> + </span> + </span> + <span class="baseline-fix"> + <span class="fontsize-ensurer reset-size5 size5"> + <span class="" style="font-size: 0em;"></span> + </span> + </span> + </span> + </span> + </span> + <span class="mbin">+</span> + <span class="mord"> + <span class="mord mathit">b</span> + <span class="msupsub"> + <span class="vlist"> + <span class="" style="top: -0.289em; margin-right: 0.05em;"> + <span class="fontsize-ensurer reset-size5 size5"> + <span class="" style="font-size: 0em;"></span> + </span> + <span class="reset-textstyle scriptstyle cramped"> + <span class="mord mathrm">2</span> + </span> + </span> + <span class="baseline-fix"> + <span class="fontsize-ensurer reset-size5 size5"> + <span class="" style="font-size: 0em;"></span> + </span> + </span> + </span> + </span> + </span> + </span> + </span> + <span class="" style="top: -0.833389em;"> + <span class="fontsize-ensurer reset-size5 size5"> + <span class="" style="font-size: 1em;"></span> + </span> + <span class="reset-textstyle textstyle uncramped sqrt-line"></span> + </span> + <span class="baseline-fix"> + <span class="fontsize-ensurer reset-size5 size5"> + <span class="" style="font-size: 1em;"></span> + </span> + </span> + </span> + </span> + </span> + </span> + </span> + HTML + + output_gfm = html_to_gfm(html) + expect(output_gfm.strip).to eq(gfm.strip) + end + + verify( + 'SanitizationFilter', + + <<-GFM.strip_heredoc + <sub>sub</sub> + + <dl> + <dt>dt</dt> + <dd>dd</dd> + </dl> + + <kbd>kbd</kbd> + + <q>q</q> + + <samp>samp</samp> + + <var>var</var> + + <ruby>ruby</ruby> + + <rt>rt</rt> + + <rp>rp</rp> + + <abbr>abbr</abbr> + GFM + ) + + verify( + 'SanitizationFilter', + + <<-GFM.strip_heredoc, + ``` + Plain text + ``` + GFM + + <<-GFM.strip_heredoc, + ```ruby + def foo + bar + end + ``` + GFM + + <<-GFM.strip_heredoc + Foo + + This is an example of GFM + + ```js + Code goes here + ``` + GFM + ) + + verify( + 'MarkdownFilter', + + "Line with two spaces at the end \nto insert a linebreak", + + '`code`', + '`` code with ` ticks ``', + + '> Quote', + + # multiline quote + <<-GFM.strip_heredoc, + > Multiline + > Quote + > + > With multiple paragraphs + GFM + + '![Image](https://example.com/image.png)', + + '# Heading with no anchor link', + + '[Link](https://example.com)', + + '- List item', + + # multiline list item + <<-GFM.strip_heredoc, + - Multiline + List item + GFM + + # nested lists + <<-GFM.strip_heredoc, + - Nested + + + - Lists + GFM + + # list with blockquote + <<-GFM.strip_heredoc, + - List + + > Blockquote + GFM + + '1. Numbered list item', + + # multiline numbered list item + <<-GFM.strip_heredoc, + 1. Multiline + Numbered list item + GFM + + # nested numbered list + <<-GFM.strip_heredoc, + 1. Nested + + + 1. Numbered lists + GFM + + '# Heading', + '## Heading', + '### Heading', + '#### Heading', + '##### Heading', + '###### Heading', + + '**Bold**', + + '_Italics_', + + '~~Strikethrough~~', + + '2^2', + + '-----', + + # table + <<-GFM.strip_heredoc, + | Centered | Right | Left | + |:--------:|------:|------| + | Foo | Bar | **Baz** | + | Foo | Bar | **Baz** | + GFM + + # table with empty heading + <<-GFM.strip_heredoc, + | | x | y | + |---|---|---| + | a | 1 | 0 | + | b | 0 | 1 | + GFM + ) + end + + alias_method :gfm_to_html, :markdown + + def html_to_gfm(html) + js = <<-JS.strip_heredoc + (function(html) { + var node = document.createElement('div'); + node.innerHTML = html; + return window.gl.CopyAsGFM.nodeToGFM(node); + })("#{escape_javascript(html)}") + JS + page.evaluate_script(js) + end + + def verify(label, *gfms) + aggregate_failures(label) do + gfms.each do |gfm| + html = gfm_to_html(gfm) + output_gfm = html_to_gfm(html) + expect(output_gfm.strip).to eq(gfm.strip) + end + end + end + + # Fake a `current_user` helper + def current_user + @feat.user + end +end diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb index 30b80aa82b0..78a11ffee99 100644 --- a/spec/features/groups/merge_requests_spec.rb +++ b/spec/features/groups/merge_requests_spec.rb @@ -7,7 +7,7 @@ feature 'Group merge requests page', feature: true do include_examples 'project features apply to issuables', MergeRequest context 'archived issuable' do - let(:project_archived) { create(:project, group: group, merge_requests_access_level: ProjectFeature::ENABLED, archived: true) } + let(:project_archived) { create(:project, :archived, group: group, merge_requests_access_level: ProjectFeature::ENABLED) } let(:issuable_archived) { create(:merge_request, source_project: project_archived, target_project: project_archived, title: 'issuable of an archived project') } let(:access_level) { ProjectFeature::ENABLED } let(:user) { user_in_group } diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb index 6f6a2532c04..93763f092fb 100644 --- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -66,6 +66,12 @@ describe 'Dropdown assignee', js: true, feature: true do expect(dropdown_assignee_size).to eq(3) end + + it 'shows current user at top of dropdown' do + send_keys_to_filtered_search('assignee:') + + expect(first('#js-dropdown-assignee .filter-dropdown li')).to have_content(user.name) + end end describe 'filtering' do @@ -119,7 +125,7 @@ describe 'Dropdown assignee', js: true, feature: true do click_assignee(user_jacob.name) expect(page).to have_css(js_dropdown_assignee, visible: false) - expect(filtered_search.value).to eq("assignee:@#{user_jacob.username}") + expect(filtered_search.value).to eq("assignee:@#{user_jacob.username} ") end it 'fills in the assignee username when the assignee has been filtered' do @@ -127,14 +133,14 @@ describe 'Dropdown assignee', js: true, feature: true do click_assignee(user.name) expect(page).to have_css(js_dropdown_assignee, visible: false) - expect(filtered_search.value).to eq("assignee:@#{user.username}") + expect(filtered_search.value).to eq("assignee:@#{user.username} ") end it 'selects `no assignee`' do find('#js-dropdown-assignee .filter-dropdown-item', text: 'No Assignee').click expect(page).to have_css(js_dropdown_assignee, visible: false) - expect(filtered_search.value).to eq("assignee:none") + expect(filtered_search.value).to eq("assignee:none ") end end @@ -163,4 +169,22 @@ describe 'Dropdown assignee', js: true, feature: true do expect(page).to have_css(js_dropdown_assignee, visible: true) end end + + describe 'caching requests' do + it 'caches requests after the first load' do + filtered_search.set('assignee') + send_keys_to_filtered_search(':') + initial_size = dropdown_assignee_size + + expect(initial_size).to be > 0 + + new_user = create(:user) + project.team << [new_user, :master] + find('.filtered-search-input-container .clear-search').click + filtered_search.set('assignee') + send_keys_to_filtered_search(':') + + expect(dropdown_assignee_size).to eq(initial_size) + end + end end diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb index 60a86cc93d4..59e302f0e2d 100644 --- a/spec/features/issues/filtered_search/dropdown_author_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb @@ -66,6 +66,12 @@ describe 'Dropdown author', js: true, feature: true do expect(dropdown_author_size).to eq(3) end + + it 'shows current user at top of dropdown' do + send_keys_to_filtered_search('author:') + + expect(first('#js-dropdown-author li')).to have_content(user.name) + end end describe 'filtering' do @@ -115,14 +121,14 @@ describe 'Dropdown author', js: true, feature: true do click_author(user_jacob.name) expect(page).to have_css(js_dropdown_author, visible: false) - expect(filtered_search.value).to eq("author:@#{user_jacob.username}") + expect(filtered_search.value).to eq("author:@#{user_jacob.username} ") end it 'fills in the author username when the author has been filtered' do click_author(user.name) expect(page).to have_css(js_dropdown_author, visible: false) - expect(filtered_search.value).to eq("author:@#{user.username}") + expect(filtered_search.value).to eq("author:@#{user.username} ") end end @@ -151,4 +157,22 @@ describe 'Dropdown author', js: true, feature: true do expect(page).to have_css(js_dropdown_author, visible: true) end end + + describe 'caching requests' do + it 'caches requests after the first load' do + filtered_search.set('author') + send_keys_to_filtered_search(':') + initial_size = dropdown_author_size + + expect(initial_size).to be > 0 + + new_user = create(:user) + project.team << [new_user, :master] + find('.filtered-search-input-container .clear-search').click + filtered_search.set('author') + send_keys_to_filtered_search(':') + + expect(dropdown_author_size).to eq(initial_size) + end + end end diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb index 89c144141c9..5079eb8dd00 100644 --- a/spec/features/issues/filtered_search/dropdown_label_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb @@ -40,6 +40,16 @@ describe 'Dropdown label', js: true, feature: true do visit namespace_project_issues_path(project.namespace, project) end + describe 'keyboard navigation' do + it 'selects label' do + send_keys_to_filtered_search('label:') + + filtered_search.native.send_keys(:down, :down, :enter) + + expect(filtered_search.value).to eq("label:~#{special_label.name} ") + end + end + describe 'behavior' do it 'opens when the search bar has label:' do filtered_search.set('label:') @@ -159,7 +169,7 @@ describe 'Dropdown label', js: true, feature: true do click_label(bug_label.title) expect(page).to have_css(js_dropdown_label, visible: false) - expect(filtered_search.value).to eq("label:~#{bug_label.title}") + expect(filtered_search.value).to eq("label:~#{bug_label.title} ") end it 'fills in the label name when the label is partially filled' do @@ -167,49 +177,49 @@ describe 'Dropdown label', js: true, feature: true do click_label(bug_label.title) expect(page).to have_css(js_dropdown_label, visible: false) - expect(filtered_search.value).to eq("label:~#{bug_label.title}") + expect(filtered_search.value).to eq("label:~#{bug_label.title} ") end it 'fills in the label name that contains multiple words' do click_label(two_words_label.title) expect(page).to have_css(js_dropdown_label, visible: false) - expect(filtered_search.value).to eq("label:~\"#{two_words_label.title}\"") + expect(filtered_search.value).to eq("label:~\"#{two_words_label.title}\" ") end it 'fills in the label name that contains multiple words and is very long' do click_label(long_label.title) expect(page).to have_css(js_dropdown_label, visible: false) - expect(filtered_search.value).to eq("label:~\"#{long_label.title}\"") + expect(filtered_search.value).to eq("label:~\"#{long_label.title}\" ") end it 'fills in the label name that contains double quotes' do click_label(wont_fix_label.title) expect(page).to have_css(js_dropdown_label, visible: false) - expect(filtered_search.value).to eq("label:~'#{wont_fix_label.title}'") + expect(filtered_search.value).to eq("label:~'#{wont_fix_label.title}' ") end it 'fills in the label name with the correct capitalization' do click_label(uppercase_label.title) expect(page).to have_css(js_dropdown_label, visible: false) - expect(filtered_search.value).to eq("label:~#{uppercase_label.title}") + expect(filtered_search.value).to eq("label:~#{uppercase_label.title} ") end it 'fills in the label name with special characters' do click_label(special_label.title) expect(page).to have_css(js_dropdown_label, visible: false) - expect(filtered_search.value).to eq("label:~#{special_label.title}") + expect(filtered_search.value).to eq("label:~#{special_label.title} ") end it 'selects `no label`' do find('#js-dropdown-label .filter-dropdown-item', text: 'No Label').click expect(page).to have_css(js_dropdown_label, visible: false) - expect(filtered_search.value).to eq("label:none") + expect(filtered_search.value).to eq("label:none ") end end @@ -239,4 +249,21 @@ describe 'Dropdown label', js: true, feature: true do expect(page).to have_css(js_dropdown_label, visible: true) end end + + describe 'caching requests' do + it 'caches requests after the first load' do + filtered_search.set('label') + send_keys_to_filtered_search(':') + initial_size = dropdown_label_size + + expect(initial_size).to be > 0 + + create(:label, project: project) + find('.filtered-search-input-container .clear-search').click + filtered_search.set('label') + send_keys_to_filtered_search(':') + + expect(dropdown_label_size).to eq(initial_size) + end + end end diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index e5a271b663f..0ce16715b86 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -127,7 +127,7 @@ describe 'Dropdown milestone', js: true, feature: true do click_milestone(milestone.title) expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:%#{milestone.title}") + expect(filtered_search.value).to eq("milestone:%#{milestone.title} ") end it 'fills in the milestone name when the milestone is partially filled' do @@ -135,56 +135,56 @@ describe 'Dropdown milestone', js: true, feature: true do click_milestone(milestone.title) expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:%#{milestone.title}") + expect(filtered_search.value).to eq("milestone:%#{milestone.title} ") end it 'fills in the milestone name that contains multiple words' do click_milestone(two_words_milestone.title) expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:%\"#{two_words_milestone.title}\"") + expect(filtered_search.value).to eq("milestone:%\"#{two_words_milestone.title}\" ") end it 'fills in the milestone name that contains multiple words and is very long' do click_milestone(long_milestone.title) expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:%\"#{long_milestone.title}\"") + expect(filtered_search.value).to eq("milestone:%\"#{long_milestone.title}\" ") end it 'fills in the milestone name that contains double quotes' do click_milestone(wont_fix_milestone.title) expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:%'#{wont_fix_milestone.title}'") + expect(filtered_search.value).to eq("milestone:%'#{wont_fix_milestone.title}' ") end it 'fills in the milestone name with the correct capitalization' do click_milestone(uppercase_milestone.title) expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:%#{uppercase_milestone.title}") + expect(filtered_search.value).to eq("milestone:%#{uppercase_milestone.title} ") end it 'fills in the milestone name with special characters' do click_milestone(special_milestone.title) expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:%#{special_milestone.title}") + expect(filtered_search.value).to eq("milestone:%#{special_milestone.title} ") end it 'selects `no milestone`' do click_static_milestone('No Milestone') expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:none") + expect(filtered_search.value).to eq("milestone:none ") end it 'selects `upcoming milestone`' do click_static_milestone('Upcoming') expect(page).to have_css(js_dropdown_milestone, visible: false) - expect(filtered_search.value).to eq("milestone:upcoming") + expect(filtered_search.value).to eq("milestone:upcoming ") end end @@ -219,4 +219,21 @@ describe 'Dropdown milestone', js: true, feature: true do expect(page).to have_css(js_dropdown_milestone, visible: true) end end + + describe 'caching requests' do + it 'caches requests after the first load' do + filtered_search.set('milestone') + send_keys_to_filtered_search(':') + initial_size = dropdown_milestone_size + + expect(initial_size).to be > 0 + + create(:milestone, project: project) + find('.filtered-search-input-container .clear-search').click + filtered_search.set('milestone') + send_keys_to_filtered_search(':') + + expect(dropdown_milestone_size).to eq(initial_size) + end + end end diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index ead43d6784a..f48a0193545 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -19,9 +19,12 @@ describe 'Filter issues', js: true, feature: true do let!(:closed_issue) { create(:issue, title: 'bug that is closed', project: project, state: :closed) } let(:filtered_search) { find('.filtered-search') } - def input_filtered_search(search_term) + def input_filtered_search(search_term, submit: true) filtered_search.set(search_term) - filtered_search.send_keys(:enter) + + if submit + filtered_search.send_keys(:enter) + end end def expect_filtered_search_input(input) @@ -43,6 +46,10 @@ describe 'Filter issues', js: true, feature: true do end end + def select_search_at_index(pos) + evaluate_script("el = document.querySelector('.filtered-search'); el.focus(); el.setSelectionRange(#{pos}, #{pos});") + end + before do project.team << [user, :master] project.team << [user2, :master] @@ -522,6 +529,44 @@ describe 'Filter issues', js: true, feature: true do end end + describe 'overwrites selected filter' do + it 'changes author' do + input_filtered_search("author:@#{user.username}", submit: false) + + select_search_at_index(3) + + page.within '#js-dropdown-author' do + click_button user2.username + end + + expect(filtered_search.value).to eq("author:@#{user2.username} ") + end + + it 'changes label' do + input_filtered_search("author:@#{user.username} label:~#{bug_label.title}", submit: false) + + select_search_at_index(27) + + page.within '#js-dropdown-label' do + click_button label.name + end + + expect(filtered_search.value).to eq("author:@#{user.username} label:~#{label.name} ") + end + + it 'changes label correctly space is in previous label' do + input_filtered_search("label:~\"#{multiple_words_label.title}\"", submit: false) + + select_search_at_index(0) + + page.within '#js-dropdown-label' do + click_button label.name + end + + expect(filtered_search.value).to eq("label:~#{label.name} ") + end + end + describe 'filter issues by text' do context 'only text' do it 'filters issues by searched text' do diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb index 56b1d354eb0..90eb60eb337 100644 --- a/spec/features/issues/filtered_search/search_bar_spec.rb +++ b/spec/features/issues/filtered_search/search_bar_spec.rb @@ -20,6 +20,22 @@ describe 'Search bar', js: true, feature: true do left_style.to_s.gsub('left: ', '').to_f end + describe 'keyboard navigation' do + it 'makes item active' do + filtered_search.native.send_keys(:down) + + page.within '#js-dropdown-hint' do + expect(page).to have_selector('.dropdown-active') + end + end + + it 'selects item' do + filtered_search.native.send_keys(:down, :down, :enter) + + expect(filtered_search.value).to eq('author:') + end + end + describe 'clear search button' do it 'clears text' do search_text = 'search_text' diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index 8771cc8e157..741ca95f1ca 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -68,6 +68,22 @@ describe 'New/edit issue', feature: true, js: true do end end end + + it 'correctly updates the dropdown toggle when removing a label' do + click_button 'Labels' + + page.within '.dropdown-menu-labels' do + click_link label.title + end + + expect(find('.js-label-select')).to have_content(label.title) + + page.within '.dropdown-menu-labels' do + click_link label.title + end + + expect(find('.js-label-select')).to have_content('Labels') + end end context 'edit issue' do diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 82c9bd0e6e6..31156fcf994 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -33,6 +33,45 @@ feature 'GFM autocomplete', feature: true, js: true do expect(page).not_to have_selector('.atwho-view') end + it 'doesnt select the first item for non-assignee dropdowns' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('') + find('#note_note').native.send_keys(':') + end + + expect(page).to have_selector('.atwho-container') + + wait_for_ajax + + expect(find('#at-view-58')).not_to have_selector('.cur:first-of-type') + end + + it 'selects the first item for assignee dropdowns' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('') + find('#note_note').native.send_keys('@') + end + + expect(page).to have_selector('.atwho-container') + + wait_for_ajax + + expect(find('#at-view-64')).to have_selector('.cur:first-of-type') + end + + it 'selects the first item for non-assignee dropdowns if a query is entered' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('') + find('#note_note').native.send_keys(':1') + end + + expect(page).to have_selector('.atwho-container') + + wait_for_ajax + + expect(find('#at-view-58')).to have_selector('.cur:first-of-type') + end + context 'if a selected value has special characters' do it 'wraps the result in double quotes' do note = find('#note_note') diff --git a/spec/features/merge_requests/cherry_pick_spec.rb b/spec/features/merge_requests/cherry_pick_spec.rb index 82bc5226d07..dfe7c910a10 100644 --- a/spec/features/merge_requests/cherry_pick_spec.rb +++ b/spec/features/merge_requests/cherry_pick_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' describe 'Cherry-pick Merge Requests' do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user) } before do diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb index c46bd8d449f..cb3bc392903 100644 --- a/spec/features/merge_requests/edit_mr_spec.rb +++ b/spec/features/merge_requests/edit_mr_spec.rb @@ -40,5 +40,32 @@ feature 'Edit Merge Request', feature: true do expect(page).to have_content 'Remove source branch' end + + it 'should preserve description textarea height', js: true do + long_description = %q( + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam ac ornare ligula, ut tempus arcu. Etiam ultricies accumsan dolor vitae faucibus. Donec at elit lacus. Mauris orci ante, aliquam quis lorem eget, convallis faucibus arcu. Aenean at pulvinar lacus. Ut viverra quam massa, molestie ornare tortor dignissim a. Suspendisse tristique pellentesque tellus, id lacinia metus elementum id. Nam tristique, arcu rhoncus faucibus viverra, lacus ipsum sagittis ligula, vitae convallis odio lacus a nibh. Ut tincidunt est purus, ac vestibulum augue maximus in. Suspendisse vel erat et mi ultricies semper. Pellentesque volutpat pellentesque consequat. + + Cras congue nec ligula tristique viverra. Curabitur fringilla fringilla fringilla. Donec rhoncus dignissim orci ut accumsan. Ut rutrum urna a rhoncus varius. Maecenas blandit, mauris nec accumsan gravida, augue nibh finibus magna, sed maximus turpis libero nec neque. Suspendisse at semper est. Nunc imperdiet dapibus dui, varius sollicitudin erat luctus non. Sed pellentesque ligula eget posuere facilisis. Donec dictum commodo volutpat. Donec egestas dui ac magna sollicitudin bibendum. Vivamus purus neque, ullamcorper ac feugiat et, tempus sit amet metus. Praesent quis viverra neque. Sed bibendum viverra est, eu aliquam mi ornare vitae. Proin et dapibus ipsum. Nunc tortor diam, malesuada nec interdum vel, placerat quis justo. Ut viverra at erat eu laoreet. + + Pellentesque commodo, diam sit amet dignissim condimentum, tortor justo pretium est, non venenatis metus eros ut nunc. Etiam ut neque eget sem dapibus aliquam. Curabitur vel elit lorem. Nulla nec enim elit. Sed ut ex id justo facilisis convallis at ac augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nullam cursus egestas turpis non tristique. Suspendisse in erat sem. Fusce libero elit, fermentum gravida mauris id, auctor iaculis felis. Nullam vulputate tempor laoreet. + + Nam tempor et magna sed convallis. Fusce sit amet sollicitudin risus, a ullamcorper lacus. Morbi gravida quis sem eget porttitor. Donec eu egestas mauris, in elementum tortor. Sed eget ex mi. Mauris iaculis tortor ut est auctor, nec dignissim quam sagittis. Suspendisse vel metus non quam suscipit tincidunt. Cras molestie lacus non justo finibus sodales quis vitae erat. In a porttitor nisi, id sollicitudin urna. Ut at felis tellus. Suspendisse potenti. + + Maecenas leo ligula, varius at neque vitae, ornare maximus justo. Nullam convallis luctus risus et vulputate. Duis suscipit faucibus iaculis. Etiam quis tortor faucibus, tristique tellus sit amet, sodales neque. Nulla dapibus nisi vel aliquet consequat. Etiam faucibus, metus eget condimentum iaculis, enim urna lobortis sem, id efficitur eros sapien nec nisi. Aenean ut finibus ex. + ) + + fill_in 'merge_request_description', with: long_description + + height = get_textarea_height + find('.js-md-preview-button').click + find('.js-md-write-button').click + new_height = get_textarea_height + + expect(height).to eq(new_height) + end + + def get_textarea_height + page.evaluate_script('document.getElementById("merge_request_description").offsetHeight') + end end end diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb new file mode 100644 index 00000000000..f2f8f11ab28 --- /dev/null +++ b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +feature 'Merge immediately', :feature, :js do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + + let(:merge_request) do + create(:merge_request_with_diffs, source_project: project, + author: user, + title: 'Bug NS-04') + end + + let(:pipeline) do + create(:ci_pipeline, project: project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch) + end + + before { project.team << [user, :master] } + + context 'when there is active pipeline for merge request' do + background do + create(:ci_build, pipeline: pipeline) + end + + before do + login_as user + visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) + end + + it 'enables merge immediately' do + page.within '.mr-widget-body' do + find('.dropdown-toggle').click + + click_link 'Merge Immediately' + + expect(find('.js-merge-button')).to have_content('Merge in progress') + end + end + end +end diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb index aa24a905001..2ea9c317bd1 100644 --- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb @@ -32,19 +32,61 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do expect(page).to have_button "Merge When Pipeline Succeeds" end - context "Merge When Pipeline Succeeds enabled" do - before do - click_button "Merge When Pipeline Succeeds" + describe 'enabling Merge When Pipeline Succeeds' do + shared_examples 'Merge When Pipeline Succeeds activator' do + it 'activates the Merge When Pipeline Succeeds feature' do + click_button "Merge When Pipeline Succeeds" + + expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds." + expect(page).to have_content "The source branch will not be removed." + expect(page).to have_link "Cancel Automatic Merge" + visit_merge_request(merge_request) # Needed to refresh the page + expect(page).to have_content /enabled an automatic merge when the pipeline for \h{8} succeeds/i + end end - it 'activates Merge When Pipeline Succeeds feature' do - expect(page).to have_link "Cancel Automatic Merge" + context "when enabled immediately" do + it_behaves_like 'Merge When Pipeline Succeeds activator' + end + + context 'when enabled after pipeline status changed' do + before do + pipeline.run! - expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds." - expect(page).to have_content "The source branch will not be removed." + # We depend on merge request widget being reloaded + # so we have to wait for asynchronous call to reload it + # and have_content expectation handles that. + # + expect(page).to have_content "Pipeline ##{pipeline.id} running" + end + + it_behaves_like 'Merge When Pipeline Succeeds activator' + end + + context 'when enabled after it was previously canceled' do + before do + click_button "Merge When Pipeline Succeeds" + click_link "Cancel Automatic Merge" + end + + it_behaves_like 'Merge When Pipeline Succeeds activator' + end - visit_merge_request(merge_request) # Needed to refresh the page - expect(page).to have_content /enabled an automatic merge when the pipeline for \h{8} succeeds/i + context 'when it was enabled and then canceled' do + let(:merge_request) do + create(:merge_request_with_diffs, + :merge_when_build_succeeds, + source_project: project, + title: 'Bug NS-04', + author: user, + merge_user: user) + end + + before do + click_link "Cancel Automatic Merge" + end + + it_behaves_like 'Merge When Pipeline Succeeds activator' end end end diff --git a/spec/features/merge_requests/wip_message_spec.rb b/spec/features/merge_requests/wip_message_spec.rb new file mode 100644 index 00000000000..3311731b33b --- /dev/null +++ b/spec/features/merge_requests/wip_message_spec.rb @@ -0,0 +1,63 @@ +require 'spec_helper' + +feature 'Work In Progress help message', feature: true do + let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + let!(:user) { create(:user) } + + before do + project.team << [user, :master] + login_as(user) + end + + context 'with WIP commits' do + it 'shows a specific WIP hint' do + visit new_namespace_project_merge_request_path( + project.namespace, + project, + merge_request: { + source_project_id: project.id, + target_project_id: project.id, + source_branch: 'wip', + target_branch: 'master' + } + ) + + within_wip_explanation do + expect(page).to have_text( + 'It looks like you have some WIP commits in this branch' + ) + end + end + end + + context 'without WIP commits' do + it 'shows the regular WIP message' do + visit new_namespace_project_merge_request_path( + project.namespace, + project, + merge_request: { + source_project_id: project.id, + target_project_id: project.id, + source_branch: 'fix', + target_branch: 'master' + } + ) + + within_wip_explanation do + expect(page).not_to have_text( + 'It looks like you have some WIP commits in this branch' + ) + expect(page).to have_text( + "Start the title with WIP: to prevent a Work In Progress merge \ +request from being merged before it's ready" + ) + end + end + end + + def within_wip_explanation(&block) + page.within '.js-no-wip-explanation' do + yield + end + end +end diff --git a/spec/features/projects/commits/cherry_pick_spec.rb b/spec/features/projects/commits/cherry_pick_spec.rb index d46d9e9399e..7baf7913424 100644 --- a/spec/features/projects/commits/cherry_pick_spec.rb +++ b/spec/features/projects/commits/cherry_pick_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' include WaitForAjax describe 'Cherry-pick Commits' do - let(:project) { create(:project) } + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') } let(:master_pickable_merge) { project.commit('e56497bb5f03a90a51293fc6d516788730953899') } diff --git a/spec/features/projects/import_export/namespace_export_file_spec.rb b/spec/features/projects/import_export/namespace_export_file_spec.rb new file mode 100644 index 00000000000..d0bafc6168c --- /dev/null +++ b/spec/features/projects/import_export/namespace_export_file_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' + +feature 'Import/Export - Namespace export file cleanup', feature: true, js: true do + let(:export_path) { "#{Dir::tmpdir}/import_file_spec" } + let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys } + + let(:project) { create(:empty_project) } + + background do + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + end + + after do + FileUtils.rm_rf(export_path, secure: true) + end + + context 'admin user' do + before do + login_as(:admin) + end + + context 'moving the namespace' do + scenario 'removes the export file' do + setup_export_project + + old_export_path = project.export_path.dup + + expect(File).to exist(old_export_path) + + project.namespace.update(path: 'new_path') + + expect(File).not_to exist(old_export_path) + end + end + + context 'deleting the namespace' do + scenario 'removes the export file' do + setup_export_project + + old_export_path = project.export_path.dup + + expect(File).to exist(old_export_path) + + project.namespace.destroy + + expect(File).not_to exist(old_export_path) + end + end + + def setup_export_project + visit edit_namespace_project_path(project.namespace, project) + + expect(page).to have_content('Export project') + + click_link 'Export project' + + visit edit_namespace_project_path(project.namespace, project) + + expect(page).to have_content('Download export') + end + end +end diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb new file mode 100644 index 00000000000..b6728960fb8 --- /dev/null +++ b/spec/features/projects/merge_request_button_spec.rb @@ -0,0 +1,108 @@ +require 'spec_helper' + +feature 'Merge Request button', feature: true do + shared_examples 'Merge Request button only shown when allowed' do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:forked_project) { create(:project, :public, forked_from_project: project) } + + context 'not logged in' do + it 'does not show Create Merge Request button' do + visit url + + within("#content-body") do + expect(page).not_to have_link(label) + end + end + end + + context 'logged in as developer' do + before do + login_as(user) + project.team << [user, :developer] + end + + it 'shows Create Merge Request button' do + href = new_namespace_project_merge_request_path(project.namespace, + project, + merge_request: { source_branch: 'feature', + target_branch: 'master' }) + + visit url + + within("#content-body") do + expect(page).to have_link(label, href: href) + end + end + + context 'merge requests are disabled' do + before do + project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED) + end + + it 'does not show Create Merge Request button' do + visit url + + within("#content-body") do + expect(page).not_to have_link(label) + end + end + end + end + + context 'logged in as non-member' do + before do + login_as(user) + end + + it 'does not show Create Merge Request button' do + visit url + + within("#content-body") do + expect(page).not_to have_link(label) + end + end + + context 'on own fork of project' do + let(:user) { forked_project.owner } + + it 'shows Create Merge Request button' do + href = new_namespace_project_merge_request_path(forked_project.namespace, + forked_project, + merge_request: { source_branch: 'feature', + target_branch: 'master' }) + + visit fork_url + + within("#content-body") do + expect(page).to have_link(label, href: href) + end + end + end + end + end + + context 'on branches page' do + it_behaves_like 'Merge Request button only shown when allowed' do + let(:label) { 'Merge Request' } + let(:url) { namespace_project_branches_path(project.namespace, project) } + let(:fork_url) { namespace_project_branches_path(forked_project.namespace, forked_project) } + end + end + + context 'on compare page' do + it_behaves_like 'Merge Request button only shown when allowed' do + let(:label) { 'Create Merge Request' } + let(:url) { namespace_project_compare_path(project.namespace, project, from: 'master', to: 'feature') } + let(:fork_url) { namespace_project_compare_path(forked_project.namespace, forked_project, from: 'master', to: 'feature') } + end + end + + context 'on commits page' do + it_behaves_like 'Merge Request button only shown when allowed' do + let(:label) { 'Create Merge Request' } + let(:url) { namespace_project_commits_path(project.namespace, project, 'feature') } + let(:fork_url) { namespace_project_commits_path(forked_project.namespace, forked_project, 'feature') } + end + end +end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 14e009daba8..e673ece37c3 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -11,18 +11,42 @@ describe 'Pipeline', :feature, :js do project.team << [user, :developer] end + shared_context 'pipeline builds' do + let!(:build_passed) do + create(:ci_build, :success, + pipeline: pipeline, stage: 'build', name: 'build') + end + + let!(:build_failed) do + create(:ci_build, :failed, + pipeline: pipeline, stage: 'test', name: 'test', commands: 'test') + end + + let!(:build_running) do + create(:ci_build, :running, + pipeline: pipeline, stage: 'deploy', name: 'deploy') + end + + let!(:build_manual) do + create(:ci_build, :manual, + pipeline: pipeline, stage: 'deploy', name: 'manual-build') + end + + let!(:build_external) do + create(:generic_commit_status, status: 'success', + pipeline: pipeline, + name: 'jenkins', + stage: 'external', + target_url: 'http://gitlab.com/status') + end + end + describe 'GET /:project/pipelines/:id' do + include_context 'pipeline builds' + let(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) } - before do - @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build') - @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test') - @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy') - @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual-build') - @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external') - end - before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) } it 'shows the pipeline graph' do @@ -116,6 +140,7 @@ describe 'Pipeline', :feature, :js do it 'shows the success icon and the generic comit status build' do expect(page).to have_selector('.ci-status-icon-success') expect(page).to have_content('jenkins') + expect(page).to have_link('jenkins', href: 'http://gitlab.com/status') end end end @@ -157,26 +182,22 @@ describe 'Pipeline', :feature, :js do end describe 'GET /:project/pipelines/:id/builds' do + include_context 'pipeline builds' + let(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) } before do - @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build') - @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test') - @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy') - @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual-build') - @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external') + visit builds_namespace_project_pipeline_path(project.namespace, project, pipeline) end - before { visit builds_namespace_project_pipeline_path(project.namespace, project, pipeline)} - it 'shows a list of builds' do expect(page).to have_content('Test') - expect(page).to have_content(@success.id) + expect(page).to have_content(build_passed.id) expect(page).to have_content('Deploy') - expect(page).to have_content(@failed.id) - expect(page).to have_content(@running.id) - expect(page).to have_content(@external.id) + expect(page).to have_content(build_failed.id) + expect(page).to have_content(build_running.id) + expect(page).to have_content(build_external.id) expect(page).to have_content('Retry failed') expect(page).to have_content('Cancel running') expect(page).to have_link('Play') @@ -230,7 +251,7 @@ describe 'Pipeline', :feature, :js do end end - it { expect(@manual.reload).to be_pending } + it { expect(build_manual.reload).to be_pending } end end end diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb new file mode 100644 index 00000000000..cef315ac9cd --- /dev/null +++ b/spec/features/projects/settings/visibility_settings_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +feature 'Visibility settings', feature: true, js: true do + let(:user) { create(:user) } + let(:project) { create(:project, namespace: user.namespace, visibility_level: 20) } + + context 'as owner' do + before do + login_as(user) + visit edit_namespace_project_path(project.namespace, project) + end + + scenario 'project visibility select is available' do + visibility_select_container = find('.js-visibility-select') + + expect(visibility_select_container.find('.visibility-select').value).to eq project.visibility_level.to_s + expect(visibility_select_container).to have_content 'The project can be cloned without any authentication.' + end + + scenario 'project visibility description updates on change' do + visibility_select_container = find('.js-visibility-select') + visibility_select = visibility_select_container.find('.visibility-select') + visibility_select.select('Private') + + expect(visibility_select.value).to eq '0' + expect(visibility_select_container).to have_content 'Project access must be granted explicitly to each user.' + end + end + + context 'as master' do + let(:master_user) { create(:user) } + + before do + project.team << [master_user, :master] + login_as(master_user) + visit edit_namespace_project_path(project.namespace, project) + end + + scenario 'project visibility is locked' do + visibility_select_container = find('.js-visibility-select') + + expect(visibility_select_container).not_to have_select '.visibility-select' + expect(visibility_select_container).to have_content 'Public' + expect(visibility_select_container).to have_content 'The project can be cloned without any authentication.' + end + end +end diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index a05b83959fb..0fe5a897565 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -211,4 +211,44 @@ describe "Search", feature: true do end end end + + describe 'search for commits' do + before do + visit search_path(project_id: project.id) + end + + it 'redirects to commit page when search by sha and only commit found' do + fill_in 'search', with: '6d394385cf567f80a8fd85055db1ab4c5295806f' + + click_button 'Search' + + expect(page).to have_current_path(namespace_project_commit_path(project.namespace, project, '6d394385cf567f80a8fd85055db1ab4c5295806f')) + end + + it 'redirects to single commit regardless of query case' do + fill_in 'search', with: '6D394385cf' + + click_button 'Search' + + expect(page).to have_current_path(namespace_project_commit_path(project.namespace, project, '6d394385cf567f80a8fd85055db1ab4c5295806f')) + end + + it 'holds on /search page when the only commit is found by message' do + create_commit('Message referencing another sha: "deadbeef" ', project, user, 'master') + + fill_in 'search', with: 'deadbeef' + click_button 'Search' + + expect(page).to have_current_path('/search', only_path: true) + end + + it 'shows multiple matching commits' do + fill_in 'search', with: 'See merge request' + + click_button 'Search' + click_link 'Commits' + + expect(page).to have_selector('.commit-row-description', count: 9) + end + end end diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index ecebabefff8..92d5a2fbc48 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -262,8 +262,8 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_denied_for(:visitor) } end - describe "GET /:project_path/hooks" do - subject { namespace_project_hooks_path(project.namespace, project) } + describe "GET /:project_path/settings/integrations" do + subject { namespace_project_settings_integrations_path(project.namespace, project) } it { is_expected.to be_allowed_for(:admin) } it { is_expected.to be_allowed_for(:owner).of(project) } diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 9bc59a7c4f9..b616e488487 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -234,8 +234,8 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for(:visitor) } end - describe "GET /:project_path/hooks" do - subject { namespace_project_hooks_path(project.namespace, project) } + describe "GET /:project_path/namespace/hooks" do + subject { namespace_project_settings_integrations_path(project.namespace, project) } it { is_expected.to be_allowed_for(:admin) } it { is_expected.to be_allowed_for(:owner).of(project) } diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index a8d43b3d581..ded85e837f4 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -400,8 +400,8 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for(:visitor) } end - describe "GET /:project_path/hooks" do - subject { namespace_project_hooks_path(project.namespace, project) } + describe "GET /:project_path/settings/integrations" do + subject { namespace_project_settings_integrations_path(project.namespace, project) } it { is_expected.to be_allowed_for(:admin) } it { is_expected.to be_allowed_for(:owner).of(project) } diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index abb27c90e0a..a5d14aa19f1 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -36,6 +36,19 @@ feature 'Task Lists', feature: true do MARKDOWN end + let(:nested_tasks_markdown) do + <<-EOT.strip_heredoc + - [ ] Task a + - [x] Task a.1 + - [ ] Task a.2 + - [ ] Task b + + 1. [ ] Task 1 + 1. [ ] Task 1.1 + 1. [x] Task 1.2 + EOT + end + before do Warden.test_mode! @@ -123,6 +136,35 @@ feature 'Task Lists', feature: true do expect(page).to have_content("1 of 1 task completed") end end + + describe 'nested tasks', js: true do + let(:issue) { create(:issue, description: nested_tasks_markdown, author: user, project: project) } + + before { visit_issue(project, issue) } + + it 'renders' do + expect(page).to have_selector('ul.task-list', count: 2) + expect(page).to have_selector('li.task-list-item', count: 7) + expect(page).to have_selector('ul input[checked]', count: 1) + expect(page).to have_selector('ol input[checked]', count: 1) + end + + it 'solves tasks' do + expect(page).to have_content("2 of 7 tasks completed") + + page.find('li.task-list-item', text: 'Task b').find('input').click + page.find('li.task-list-item ul li.task-list-item', text: 'Task a.2').find('input').click + page.find('li.task-list-item ol li.task-list-item', text: 'Task 1.1').find('input').click + + expect(page).to have_content("5 of 7 tasks completed") + + visit_issue(project, issue) # reload to see new system notes + + expect(page).to have_content('marked the task Task b as complete') + expect(page).to have_content('marked the task Task a.2 as complete') + expect(page).to have_content('marked the task Task 1.1 as complete') + end + end end describe 'for Notes' do @@ -236,7 +278,7 @@ feature 'Task Lists', feature: true do expect(page).to have_content("2 of 6 tasks completed") end end - + describe 'single incomplete task' do let!(:merge) { create(:merge_request, :simple, description: singleIncompleteMarkdown, author: user, source_project: project) } diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb index 4bda0927692..3850e930b6d 100644 --- a/spec/features/todos/todos_spec.rb +++ b/spec/features/todos/todos_spec.rb @@ -165,7 +165,7 @@ describe 'Dashboard Todos', feature: true do end it 'shows the todo' do - expect(page).to have_content 'The build failed for your merge request' + expect(page).to have_content 'The build failed for merge request' end it 'links to the pipelines for the merge request' do diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb index db60c01db0d..91f34973ba5 100644 --- a/spec/finders/branches_finder_spec.rb +++ b/spec/finders/branches_finder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe BranchesFinder do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } describe '#execute' do diff --git a/spec/finders/contributed_projects_finder_spec.rb b/spec/finders/contributed_projects_finder_spec.rb index 65d7f14c721..ad2d456529a 100644 --- a/spec/finders/contributed_projects_finder_spec.rb +++ b/spec/finders/contributed_projects_finder_spec.rb @@ -6,8 +6,8 @@ describe ContributedProjectsFinder do let(:finder) { described_class.new(source_user) } - let!(:public_project) { create(:project, :public) } - let!(:private_project) { create(:project, :private) } + let!(:public_project) { create(:empty_project, :public) } + let!(:private_project) { create(:empty_project, :private) } before do private_project.team << [source_user, Gitlab::Access::MASTER] diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb index 00eec3f3f4c..ef97b061ca7 100644 --- a/spec/finders/group_projects_finder_spec.rb +++ b/spec/finders/group_projects_finder_spec.rb @@ -6,11 +6,11 @@ describe GroupProjectsFinder do let(:finder) { described_class.new(source_user) } - let!(:public_project) { create(:project, :public, group: group, path: '1') } - let!(:private_project) { create(:project, :private, group: group, path: '2') } - let!(:shared_project_1) { create(:project, :public, path: '3') } - let!(:shared_project_2) { create(:project, :private, path: '4') } - let!(:shared_project_3) { create(:project, :internal, path: '5') } + let!(:public_project) { create(:empty_project, :public, group: group, path: '1') } + let!(:private_project) { create(:empty_project, :private, group: group, path: '2') } + let!(:shared_project_1) { create(:empty_project, :public, path: '3') } + let!(:shared_project_2) { create(:empty_project, :private, path: '4') } + let!(:shared_project_3) { create(:empty_project, :internal, path: '5') } before do shared_project_1.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group) diff --git a/spec/finders/joined_groups_finder_spec.rb b/spec/finders/joined_groups_finder_spec.rb index 29a47e005a6..4c389746252 100644 --- a/spec/finders/joined_groups_finder_spec.rb +++ b/spec/finders/joined_groups_finder_spec.rb @@ -42,7 +42,7 @@ describe JoinedGroupsFinder do context 'if profile visitor is in one of the private group projects' do before do - project = create(:project, :private, group: private_group, name: 'B', path: 'B') + project = create(:empty_project, :private, group: private_group, name: 'B', path: 'B') project.add_user(profile_visitor, Gitlab::Access::DEVELOPER) end diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 88361e27102..3dcd7781e5b 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -4,9 +4,9 @@ describe MergeRequestsFinder do let(:user) { create :user } let(:user2) { create :user } - let(:project1) { create(:project) } - let(:project2) { create(:project, forked_from_project: project1) } - let(:project3) { create(:project, forked_from_project: project1, archived: true) } + let(:project1) { create(:empty_project) } + let(:project2) { create(:empty_project, forked_from_project: project1) } + let(:project3) { create(:empty_project, :archived, forked_from_project: project1) } let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) } let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') } diff --git a/spec/finders/move_to_project_finder_spec.rb b/spec/finders/move_to_project_finder_spec.rb index fdce4e714ff..dea87980e25 100644 --- a/spec/finders/move_to_project_finder_spec.rb +++ b/spec/finders/move_to_project_finder_spec.rb @@ -2,13 +2,13 @@ require 'spec_helper' describe MoveToProjectFinder do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } - let(:no_access_project) { create(:project) } - let(:guest_project) { create(:project) } - let(:reporter_project) { create(:project) } - let(:developer_project) { create(:project) } - let(:master_project) { create(:project) } + let(:no_access_project) { create(:empty_project) } + let(:guest_project) { create(:empty_project) } + let(:reporter_project) { create(:empty_project) } + let(:developer_project) { create(:empty_project) } + let(:master_project) { create(:empty_project) } subject { described_class.new(user) } @@ -36,8 +36,8 @@ describe MoveToProjectFinder do it 'does not return archived projects' do reporter_project.team << [user, :reporter] - reporter_project.update_attributes(archived: true) - other_reporter_project = create(:project) + reporter_project.archive! + other_reporter_project = create(:empty_project) other_reporter_project.team << [user, :reporter] expect(subject.execute(project).to_a).to eq([other_reporter_project]) @@ -46,7 +46,7 @@ describe MoveToProjectFinder do it 'does not return projects for which issues are disabled' do reporter_project.team << [user, :reporter] reporter_project.update_attributes(issues_enabled: false) - other_reporter_project = create(:project) + other_reporter_project = create(:empty_project) other_reporter_project.team << [user, :reporter] expect(subject.execute(project).to_a).to eq([other_reporter_project]) @@ -83,10 +83,10 @@ describe MoveToProjectFinder do end it 'returns projects matching a search query' do - foo_project = create(:project) + foo_project = create(:empty_project) foo_project.team << [user, :master] - wadus_project = create(:project, name: 'wadus') + wadus_project = create(:empty_project, name: 'wadus') wadus_project.team << [user, :master] expect(subject.execute(project).to_a).to eq([wadus_project, foo_project]) diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb index 4d21254323c..bac653ea451 100644 --- a/spec/finders/notes_finder_spec.rb +++ b/spec/finders/notes_finder_spec.rb @@ -28,7 +28,7 @@ describe NotesFinder do end it "excludes notes on commits the author can't download" do - project = create(:project, :private) + project = create(:project, :private, :repository) note = create(:note_on_commit, project: project) params = { target_type: 'commit', target_id: note.noteable.id } @@ -76,7 +76,7 @@ describe NotesFinder do end context 'for target' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:note1) { create :note_on_commit, project: project } let(:note2) { create :note_on_commit, project: project } let(:commit) { note1.noteable } diff --git a/spec/finders/personal_projects_finder_spec.rb b/spec/finders/personal_projects_finder_spec.rb index a4681fe59d8..e0e17af681a 100644 --- a/spec/finders/personal_projects_finder_spec.rb +++ b/spec/finders/personal_projects_finder_spec.rb @@ -4,14 +4,14 @@ describe PersonalProjectsFinder do let(:source_user) { create(:user) } let(:current_user) { create(:user) } let(:finder) { described_class.new(source_user) } - let!(:public_project) { create(:project, :public, namespace: source_user.namespace) } + let!(:public_project) { create(:empty_project, :public, namespace: source_user.namespace) } let!(:private_project) do - create(:project, :private, namespace: source_user.namespace, path: 'mepmep') + create(:empty_project, :private, namespace: source_user.namespace, path: 'mepmep') end let!(:internal_project) do - create(:project, :internal, namespace: source_user.namespace, path: 'C') + create(:empty_project, :internal, namespace: source_user.namespace, path: 'C') end before do diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb index b0811d134fa..fdc8215aa47 100644 --- a/spec/finders/pipelines_finder_spec.rb +++ b/spec/finders/pipelines_finder_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe PipelinesFinder do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let!(:tag_pipeline) { create(:ci_pipeline, project: project, ref: 'v1.0.0') } let!(:branch_pipeline) { create(:ci_pipeline, project: project) } diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index 13bda5f7c5a..e44e7434c80 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -6,19 +6,19 @@ describe ProjectsFinder do let(:group) { create(:group, :public) } let!(:private_project) do - create(:project, :private, name: 'A', path: 'A') + create(:empty_project, :private, name: 'A', path: 'A') end let!(:internal_project) do - create(:project, :internal, group: group, name: 'B', path: 'B') + create(:empty_project, :internal, group: group, name: 'B', path: 'B') end let!(:public_project) do - create(:project, :public, group: group, name: 'C', path: 'C') + create(:empty_project, :public, group: group, name: 'C', path: 'C') end let!(:shared_project) do - create(:project, :private, name: 'D', path: 'D') + create(:empty_project, :private, name: 'D', path: 'D') end let(:finder) { described_class.new } diff --git a/spec/finders/tags_finder_spec.rb b/spec/finders/tags_finder_spec.rb index 98b42e264dc..460e278e2d3 100644 --- a/spec/finders/tags_finder_spec.rb +++ b/spec/finders/tags_finder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe TagsFinder do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } describe '#execute' do diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 92053e5a7c6..8b201f348f1 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -55,7 +55,7 @@ describe ApplicationHelper do describe 'project_icon' do it 'returns an url for the avatar' do - project = create(:project, avatar: File.open(uploaded_image_temp_path)) + project = create(:empty_project, avatar: File.open(uploaded_image_temp_path)) avatar_url = "http://#{Gitlab.config.gitlab.host}/uploads/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s). @@ -63,7 +63,7 @@ describe ApplicationHelper do end it 'gives uploaded icon when present' do - project = create(:project) + project = create(:empty_project) allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true) diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index a43a7238c70..fa516f9903e 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -70,7 +70,7 @@ describe BlobHelper do describe "#edit_blob_link" do let(:namespace) { create(:namespace, name: 'gitlab' )} - let(:project) { create(:project, namespace: namespace) } + let(:project) { create(:project, :repository, namespace: namespace) } before do allow(self).to receive(:current_user).and_return(double) diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index 810311e2b1a..b8ec3521edb 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe GitlabMarkdownHelper do include ApplicationHelper - let!(:project) { create(:project) } + let!(:project) { create(:project, :repository) } let(:user) { create(:user, username: 'gfm') } let(:commit) { project.commit } @@ -55,18 +55,18 @@ describe GitlabMarkdownHelper do end describe '#link_to_gfm' do - let(:commit_path) { namespace_project_commit_path(project.namespace, project, commit) } - let(:issues) { create_list(:issue, 2, project: project) } + let(:link) { '/commits/0a1b2c3d' } + let(:issues) { create_list(:issue, 2, project: project) } it 'handles references nested in links with all the text' do - actual = helper.link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", commit_path) + actual = helper.link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", link) doc = Nokogiri::HTML.parse(actual) # Make sure we didn't create invalid markup expect(doc.errors).to be_empty # Leading commit link - expect(doc.css('a')[0].attr('href')).to eq commit_path + expect(doc.css('a')[0].attr('href')).to eq link expect(doc.css('a')[0].text).to eq 'This should finally fix ' # First issue link @@ -75,7 +75,7 @@ describe GitlabMarkdownHelper do expect(doc.css('a')[1].text).to eq issues[0].to_reference # Internal commit link - expect(doc.css('a')[2].attr('href')).to eq commit_path + expect(doc.css('a')[2].attr('href')).to eq link expect(doc.css('a')[2].text).to eq ' and ' # Second issue link @@ -84,12 +84,12 @@ describe GitlabMarkdownHelper do expect(doc.css('a')[3].text).to eq issues[1].to_reference # Trailing commit link - expect(doc.css('a')[4].attr('href')).to eq commit_path + expect(doc.css('a')[4].attr('href')).to eq link expect(doc.css('a')[4].text).to eq ' for real' end it 'forwards HTML options' do - actual = helper.link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo') + actual = helper.link_to_gfm("Fixed in #{commit.id}", link, class: 'foo') doc = Nokogiri::HTML.parse(actual) expect(doc.css('a')).to satisfy do |v| @@ -100,7 +100,7 @@ describe GitlabMarkdownHelper do it "escapes HTML passed in as the body" do actual = "This is a <h1>test</h1> - see #{issues[0].to_reference}" - expect(helper.link_to_gfm(actual, commit_path)). + expect(helper.link_to_gfm(actual, link)). to match('<h1>test</h1>') end diff --git a/spec/helpers/graph_helper_spec.rb b/spec/helpers/graph_helper_spec.rb index 51c49f0e587..400635abdde 100644 --- a/spec/helpers/graph_helper_spec.rb +++ b/spec/helpers/graph_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe GraphHelper do describe '#get_refs' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:commit) { project.commit("master") } let(:graph) { Network::Graph.new(project, 'master', commit, '') } diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 9c7e0ee2fe0..13fb9c1f1a7 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe IssuesHelper do - let(:project) { create :project } + let(:project) { create(:empty_project) } let(:issue) { create :issue, project: project } let(:ext_project) { create :redmine_project } diff --git a/spec/helpers/members_helper_spec.rb b/spec/helpers/members_helper_spec.rb index 33934cdf8b1..2b455571d52 100644 --- a/spec/helpers/members_helper_spec.rb +++ b/spec/helpers/members_helper_spec.rb @@ -46,7 +46,7 @@ describe MembersHelper do end describe '#leave_confirmation_message' do - let(:project) { build_stubbed(:project) } + let(:project) { build_stubbed(:empty_project) } let(:group) { build_stubbed(:group) } let(:user) { build_stubbed(:user) } diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb index 1f221487393..550b4a66a6a 100644 --- a/spec/helpers/merge_requests_helper_spec.rb +++ b/spec/helpers/merge_requests_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe MergeRequestsHelper do describe 'ci_build_details_path' do - let(:project) { create :project } + let(:project) { create(:empty_project) } let(:merge_request) { MergeRequest.new } let(:ci_service) { CiService.new } let(:last_commit) { Ci::Pipeline.new({}) } @@ -30,7 +30,7 @@ describe MergeRequestsHelper do it { is_expected.to eq('#1, #2, and #3') } context 'for JIRA issues' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:issues) do [ ExternalIssue.new('JIRA-123', project), @@ -52,8 +52,8 @@ describe MergeRequestsHelper do end describe 'within different projects' do - let(:project) { create(:project) } - let(:fork_project) { create(:project, forked_from_project: project) } + let(:project) { create(:empty_project) } + let(:fork_project) { create(:empty_project, forked_from_project: project) } let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: project) } subject { format_mr_branch_names(merge_request) } let(:source_title) { "#{fork_project.path_with_namespace}:#{merge_request.source_branch}" } @@ -64,8 +64,8 @@ describe MergeRequestsHelper do end describe 'mr_widget_refresh_url' do + let(:project) { create(:empty_project) } let(:merge_request) { create(:merge_request, source_project: project) } - let(:project) { create(:project) } it 'returns correct url for MR' do expected_url = "#{project.path_with_namespace}/merge_requests/#{merge_request.iid}/merge_widget_refresh" diff --git a/spec/helpers/milestones_helper_spec.rb b/spec/helpers/milestones_helper_spec.rb index ea744dbb629..14a95479339 100644 --- a/spec/helpers/milestones_helper_spec.rb +++ b/spec/helpers/milestones_helper_spec.rb @@ -21,24 +21,22 @@ describe MilestonesHelper do end describe '#milestone_counts' do - let(:project) { FactoryGirl.create(:project) } + let(:project) { create(:empty_project) } let(:counts) { helper.milestone_counts(project.milestones) } context 'when there are milestones' do - let!(:milestone_1) { FactoryGirl.create(:active_milestone, project: project) } - let!(:milestone_2) { FactoryGirl.create(:active_milestone, project: project) } - let!(:milestone_3) { FactoryGirl.create(:closed_milestone, project: project) } - it 'returns the correct counts' do + create_list(:active_milestone, 2, project: project) + create(:closed_milestone, project: project) + expect(counts).to eq(opened: 2, closed: 1, all: 3) end end context 'when there are only milestones of one type' do - let!(:milestone_1) { FactoryGirl.create(:active_milestone, project: project) } - let!(:milestone_2) { FactoryGirl.create(:active_milestone, project: project) } - it 'returns the correct counts' do + create_list(:active_milestone, 2, project: project) + expect(counts).to eq(opened: 2, closed: 0, all: 2) end end diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb index 77841e85223..1f02e06e312 100644 --- a/spec/helpers/preferences_helper_spec.rb +++ b/spec/helpers/preferences_helper_spec.rb @@ -110,7 +110,7 @@ describe PreferencesHelper do end context 'when repository is not empty' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } it 'returns readme if user has repository access' do allow(helper).to receive(:can?).with(nil, :download_code, project).and_return(true) diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 8113742923b..8d1570aa6f3 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -10,7 +10,7 @@ describe ProjectsHelper do end describe "can_change_visibility_level?" do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:project_member, :reporter, user: create(:user), project: project).user } let(:fork_project) { Projects::ForkService.new(project, user).execute } @@ -97,7 +97,7 @@ describe ProjectsHelper do end describe '#license_short_name' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'when project.repository has a license_key' do it 'returns the nickname of the license if present' do diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index 4b2ca3514f8..e51720f10ed 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -42,7 +42,7 @@ describe SearchHelper do end it "includes the user's projects" do - project = create(:project, namespace: create(:namespace, owner: user)) + project = create(:empty_project, namespace: create(:namespace, owner: user)) expect(search_autocomplete_opts(project.name).size).to eq(1) end @@ -52,7 +52,9 @@ describe SearchHelper do end context "with a current project" do - before { @project = create(:project) } + before do + @project = create(:project, :repository) + end it "includes project-specific sections" do expect(search_autocomplete_opts("Files").size).to eq(1) diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb index 37ac6a2699d..4da1569e59f 100644 --- a/spec/helpers/submodule_helper_spec.rb +++ b/spec/helpers/submodule_helper_spec.rb @@ -116,7 +116,7 @@ describe SubmoduleHelper do context 'submodules with relative links' do let(:group) { create(:group, name: "Master Project", path: "master-project") } - let(:project) { create(:project, group: group) } + let(:project) { create(:empty_project, group: group) } let(:commit_id) { sample_commit[:id] } before do @@ -145,7 +145,7 @@ describe SubmoduleHelper do context 'personal project' do let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:empty_project, namespace: user.namespace) } it 'one level down with personal project' do result = relative_self_links('../test.git', commit_id) diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb index 8d6537ba4b5..9523d0f4aa6 100644 --- a/spec/helpers/tree_helper_spec.rb +++ b/spec/helpers/tree_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe TreeHelper do describe 'flatten_tree' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do @repository = project.repository diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb index db3ad1b99e9..8942b00b128 100644 --- a/spec/helpers/visibility_level_helper_spec.rb +++ b/spec/helpers/visibility_level_helper_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe VisibilityLevelHelper do - let(:project) { build(:project) } + let(:project) { build(:empty_project) } let(:group) { build(:group) } let(:personal_snippet) { build(:personal_snippet) } let(:project_snippet) { build(:project_snippet) } @@ -60,8 +60,8 @@ describe VisibilityLevelHelper do describe "skip_level?" do describe "forks" do - let(:project) { create(:project, :internal) } - let(:fork_project) { create(:project, forked_from_project: project) } + let(:project) { create(:empty_project, :internal) } + let(:fork_project) { create(:empty_project, forked_from_project: project) } it "skips levels" do expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy @@ -71,7 +71,7 @@ describe VisibilityLevelHelper do end describe "non-forked project" do - let(:project) { create(:project, :internal) } + let(:project) { create(:empty_project, :internal) } it "skips levels" do expect(skip_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey diff --git a/spec/initializers/metrics_spec.rb b/spec/initializers/metrics_spec.rb new file mode 100644 index 00000000000..bb595162370 --- /dev/null +++ b/spec/initializers/metrics_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' +require_relative '../../config/initializers/metrics' + +describe 'instrument_classes', lib: true do + let(:config) { double(:config) } + + before do + allow(config).to receive(:instrument_method) + allow(config).to receive(:instrument_methods) + allow(config).to receive(:instrument_instance_methods) + end + + it 'can autoload and instrument all files' do + expect { instrument_classes(config) }.not_to raise_error + end +end diff --git a/spec/javascripts/.eslintrc b/spec/javascripts/.eslintrc index b3d191e15ab..fbd9bb9f0ff 100644 --- a/spec/javascripts/.eslintrc +++ b/spec/javascripts/.eslintrc @@ -23,6 +23,8 @@ "plugins": ["jasmine"], "rules": { "func-names": 0, + "jasmine/no-suite-dupes": [1, "branch"], + "jasmine/no-spec-dupes": [1, "branch"], "no-console": 0, "prefer-arrow-callback": 0 } diff --git a/spec/javascripts/abuse_reports_spec.js.es6 b/spec/javascripts/abuse_reports_spec.js.es6 index 6e23a7a0b56..76b370b345b 100644 --- a/spec/javascripts/abuse_reports_spec.js.es6 +++ b/spec/javascripts/abuse_reports_spec.js.es6 @@ -21,7 +21,6 @@ require('~/abuse_reports'); messages = $('.abuse-reports .message'); }); - it('should truncate long messages', () => { const $longMessage = findMessage('LONG MESSAGE'); expect($longMessage.data('original-message')).toEqual(jasmine.anything()); diff --git a/spec/javascripts/activities_spec.js.es6 b/spec/javascripts/activities_spec.js.es6 index aba16a03ce2..e6a6fc36ca1 100644 --- a/spec/javascripts/activities_spec.js.es6 +++ b/spec/javascripts/activities_spec.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable no-unused-expressions, comma-spacing, prefer-const, no-prototype-builtins, semi, no-new, keyword-spacing, no-plusplus, no-shadow, max-len */ +/* eslint-disable no-unused-expressions, no-prototype-builtins, no-new, no-shadow, max-len */ require('vendor/jquery.endless-scroll.js'); require('~/pager'); @@ -18,18 +18,18 @@ require('~/activities'); name: 'merge events', }, { id: 'comments', - },{ + }, { id: 'team', }]; function getEventName(index) { - let filter = filters[index]; + const filter = filters[index]; return filter.hasOwnProperty('name') ? filter.name : filter.id; } function getSelector(index) { - let filter = filters[index]; - return `#${filter.id}_event_filter` + const filter = filters[index]; + return `#${filter.id}_event_filter`; } describe('Activities', () => { @@ -38,17 +38,17 @@ require('~/activities'); new gl.Activities(); }); - for(let i = 0; i < filters.length; i++) { + for (let i = 0; i < filters.length; i += 1) { ((i) => { describe(`when selecting ${getEventName(i)}`, () => { beforeEach(() => { $(getSelector(i)).click(); }); - for(let x = 0; x < filters.length; x++) { + for (let x = 0; x < filters.length; x += 1) { ((x) => { - let shouldHighlight = i === x; - let testName = shouldHighlight ? 'should highlight' : 'should not highlight'; + const shouldHighlight = i === x; + const testName = shouldHighlight ? 'should highlight' : 'should not highlight'; it(`${testName} ${getEventName(x)}`, () => { expect($(getSelector(x)).parent().hasClass('active')).toEqual(shouldHighlight); diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index 672f6f33ad3..dcaf26c186f 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, new-parens, no-unused-vars, quotes, jasmine/no-spec-dupes, prefer-template, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, new-parens, no-unused-vars, quotes, jasmine/no-spec-dupes, prefer-template, max-len */ /* global AwardsHandler */ require('~/awards_handler'); @@ -229,5 +229,4 @@ require('./fixtures/emoji_menu'); }); }); }); - }).call(this); diff --git a/spec/javascripts/behaviors/autosize_spec.js b/spec/javascripts/behaviors/autosize_spec.js index 3b29579e70e..ac1fb098251 100644 --- a/spec/javascripts/behaviors/autosize_spec.js +++ b/spec/javascripts/behaviors/autosize_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, no-var, comma-dangle, no-return-assign, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-var, comma-dangle, no-return-assign, max-len */ require('~/behaviors/autosize'); @@ -18,5 +18,4 @@ require('~/behaviors/autosize'); return $(document).trigger('page:load'); }; }); - }).call(this); diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js index 247eb5f70ea..b84126c0e3d 100644 --- a/spec/javascripts/behaviors/quick_submit_spec.js +++ b/spec/javascripts/behaviors/quick_submit_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, no-var, no-return-assign, comma-dangle, jasmine/no-spec-dupes, new-cap, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-var, no-return-assign, comma-dangle, jasmine/no-spec-dupes, new-cap, max-len */ require('~/behaviors/quick_submit'); @@ -93,5 +93,4 @@ require('~/behaviors/quick_submit'); return $.Event('keydown', $.extend({}, defaults, options)); }; }); - }).call(this); diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js index fd098196e7d..3fa5c65c0a6 100644 --- a/spec/javascripts/behaviors/requires_input_spec.js +++ b/spec/javascripts/behaviors/requires_input_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, no-var, padded-blocks */ +/* eslint-disable space-before-function-paren, no-var */ require('~/behaviors/requires_input'); @@ -41,5 +41,4 @@ require('~/behaviors/requires_input'); return expect(spy).toHaveBeenCalled(); }); }); - }).call(this); diff --git a/spec/javascripts/boards/boards_store_spec.js.es6 b/spec/javascripts/boards/boards_store_spec.js.es6 index 8f8f6d22066..4f1d8968521 100644 --- a/spec/javascripts/boards/boards_store_spec.js.es6 +++ b/spec/javascripts/boards/boards_store_spec.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, one-var, no-unused-vars, indent */ +/* eslint-disable comma-dangle, one-var, no-unused-vars */ /* global Vue */ /* global BoardService */ /* global boardsMockInterceptor */ @@ -141,8 +141,8 @@ describe('Store', () => { }); it('moves the position of lists', () => { - const listOne = gl.issueBoards.BoardsStore.addList(listObj), - listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate); + const listOne = gl.issueBoards.BoardsStore.addList(listObj); + const listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate); expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2); @@ -152,8 +152,8 @@ describe('Store', () => { }); it('moves an issue from one list to another', (done) => { - const listOne = gl.issueBoards.BoardsStore.addList(listObj), - listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate); + const listOne = gl.issueBoards.BoardsStore.addList(listObj); + const listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate); expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2); diff --git a/spec/javascripts/bootstrap_linked_tabs_spec.js.es6 b/spec/javascripts/bootstrap_linked_tabs_spec.js.es6 index 54055b04f62..9f6db2b0723 100644 --- a/spec/javascripts/bootstrap_linked_tabs_spec.js.es6 +++ b/spec/javascripts/bootstrap_linked_tabs_spec.js.es6 @@ -3,7 +3,12 @@ require('~/lib/utils/bootstrap_linked_tabs'); (() => { // TODO: remove this hack! // PhantomJS causes spyOn to panic because replaceState isn't "writable" - const phantomjs = !Object.getOwnPropertyDescriptor(window.history, 'replaceState').writable; + let phantomjs; + try { + phantomjs = !Object.getOwnPropertyDescriptor(window.history, 'replaceState').writable; + } catch (err) { + phantomjs = false; + } describe('Linked Tabs', () => { preloadFixtures('static/linked_tabs.html.raw'); diff --git a/spec/javascripts/dashboard_spec.js.es6 b/spec/javascripts/dashboard_spec.js.es6 index 501380693d4..c0bdb89ed63 100644 --- a/spec/javascripts/dashboard_spec.js.es6 +++ b/spec/javascripts/dashboard_spec.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable no-new, padded-blocks */ +/* eslint-disable no-new */ require('~/sidebar'); require('~/lib/utils/text_utility'); @@ -34,5 +34,4 @@ require('~/lib/utils/text_utility'); expect(todosCountText()).toEqual('1,000,000'); }); }); - })(window.gl); diff --git a/spec/javascripts/diff_comments_store_spec.js.es6 b/spec/javascripts/diff_comments_store_spec.js.es6 index cf2f17de5ee..f956394ef53 100644 --- a/spec/javascripts/diff_comments_store_spec.js.es6 +++ b/spec/javascripts/diff_comments_store_spec.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable no-extra-semi, jasmine/no-global-setup, dot-notation, jasmine/no-expect-in-setup-teardown, max-len */ +/* eslint-disable jasmine/no-global-setup, dot-notation, jasmine/no-expect-in-setup-teardown, max-len */ /* global CommentsStore */ require('~/diff_notes/models/discussion'); @@ -8,7 +8,7 @@ require('~/diff_notes/stores/comments'); (() => { function createDiscussion(noteId = 1, resolved = true) { CommentsStore.create('a', noteId, true, resolved, 'test'); - }; + } beforeEach(() => { CommentsStore.state = {}; diff --git a/spec/javascripts/environments/environment_rollback_spec.js.es6 b/spec/javascripts/environments/environment_rollback_spec.js.es6 index 8c7e1e912b4..043b8708a6e 100644 --- a/spec/javascripts/environments/environment_rollback_spec.js.es6 +++ b/spec/javascripts/environments/environment_rollback_spec.js.es6 @@ -33,7 +33,6 @@ describe('Rollback Component', () => { expect(component.$el.querySelector('span').textContent).toContain('Re-deploy'); }); - it('Should render Rollback label when isLastDeployment is false', () => { const component = new window.gl.environmentsList.RollbackComponent({ el: document.querySelector('.test-dom-element'), diff --git a/spec/javascripts/environments/environment_spec.js.es6 b/spec/javascripts/environments/environment_spec.js.es6 new file mode 100644 index 00000000000..87eda136122 --- /dev/null +++ b/spec/javascripts/environments/environment_spec.js.es6 @@ -0,0 +1,125 @@ +/* global Vue, environment */ + +require('~/flash'); +require('~/environments/stores/environments_store'); +require('~/environments/components/environment'); +require('./mock_data'); + +describe('Environment', () => { + preloadFixtures('static/environments/environments.html.raw'); + + let component; + + beforeEach(() => { + loadFixtures('static/environments/environments.html.raw'); + }); + + describe('successfull request', () => { + describe('without environments', () => { + const environmentsEmptyResponseInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify([]), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(environmentsEmptyResponseInterceptor); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, environmentsEmptyResponseInterceptor, + ); + }); + + it('should render the empty state', (done) => { + component = new gl.environmentsList.EnvironmentsComponent({ + el: document.querySelector('#environments-list-view'), + propsData: { + store: gl.environmentsList.EnvironmentsStore.create(), + }, + }); + + setTimeout(() => { + expect( + component.$el.querySelector('.js-new-environment-button').textContent, + ).toContain('New Environment'); + + expect( + component.$el.querySelector('.js-blank-state-title').textContent, + ).toContain('You don\'t have any environments right now.'); + + done(); + }, 0); + }); + }); + + describe('with environments', () => { + const environmentsResponseInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify([environment]), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(environmentsResponseInterceptor); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, environmentsResponseInterceptor, + ); + }); + + it('should render a table with environments', (done) => { + component = new gl.environmentsList.EnvironmentsComponent({ + el: document.querySelector('#environments-list-view'), + propsData: { + store: gl.environmentsList.EnvironmentsStore.create(), + }, + }); + + setTimeout(() => { + expect( + component.$el.querySelectorAll('table tbody tr').length, + ).toEqual(1); + done(); + }, 0); + }); + }); + }); + + describe('unsuccessfull request', () => { + const environmentsErrorResponseInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify([]), { + status: 500, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(environmentsErrorResponseInterceptor); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, environmentsErrorResponseInterceptor, + ); + }); + + it('should render empty state', (done) => { + component = new gl.environmentsList.EnvironmentsComponent({ + el: document.querySelector('#environments-list-view'), + propsData: { + store: gl.environmentsList.EnvironmentsStore.create(), + }, + }); + + setTimeout(() => { + expect( + component.$el.querySelector('.js-blank-state-title').textContent, + ).toContain('You don\'t have any environments right now.'); + done(); + }, 0); + }); + }); +}); diff --git a/spec/javascripts/environments/mock_data.js.es6 b/spec/javascripts/environments/mock_data.js.es6 index 563414d0b1a..58f6fb96afb 100644 --- a/spec/javascripts/environments/mock_data.js.es6 +++ b/spec/javascripts/environments/mock_data.js.es6 @@ -1,4 +1,3 @@ -/* eslint-disable no-unused-vars */ const environmentsList = [ { @@ -136,3 +135,19 @@ const environmentsList = [ ]; window.environmentsList = environmentsList; + +const environment = { + id: 4, + name: 'production', + state: 'available', + external_url: 'http://production.', + environment_type: null, + last_deployment: {}, + 'stoppable?': false, + environment_path: '/root/review-app/environments/4', + stop_path: '/root/review-app/environments/4/stop', + created_at: '2016-12-16T11:51:04.690Z', + updated_at: '2016-12-16T12:04:51.133Z', +}; + +window.environment = environment; diff --git a/spec/javascripts/extensions/array_spec.js.es6 b/spec/javascripts/extensions/array_spec.js.es6 index 75372266808..ba5eb81defc 100644 --- a/spec/javascripts/extensions/array_spec.js.es6 +++ b/spec/javascripts/extensions/array_spec.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, no-var, padded-blocks */ +/* eslint-disable space-before-function-paren, no-var */ require('~/extensions/array'); @@ -42,5 +42,4 @@ require('~/extensions/array'); }); }); }); - }).call(this); diff --git a/spec/javascripts/extensions/jquery_spec.js b/spec/javascripts/extensions/jquery_spec.js index 298832f6985..c0bb0419814 100644 --- a/spec/javascripts/extensions/jquery_spec.js +++ b/spec/javascripts/extensions/jquery_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, no-var, padded-blocks */ +/* eslint-disable space-before-function-paren, no-var */ require('~/extensions/jquery'); @@ -39,5 +39,4 @@ require('~/extensions/jquery'); }); }); }); - }).call(this); diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6 b/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6 index 96f33427213..074a46349ab 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6 +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6 @@ -31,41 +31,68 @@ require('~/filtered_search/filtered_search_dropdown_manager'); }); describe('filterWithSymbol', () => { + let input; const item = { title: '@root', }; + beforeEach(() => { + setFixtures(` + <input type="text" id="test" /> + `); + + input = document.getElementById('test'); + }); + it('should filter without symbol', () => { - const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':roo'); + input.value = ':roo'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item); expect(updatedItem.droplab_hidden).toBe(false); }); it('should filter with symbol', () => { - const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':@roo'); + input.value = '@roo'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item); expect(updatedItem.droplab_hidden).toBe(false); }); it('should filter with colon', () => { - const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':'); + input.value = 'roo'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item); expect(updatedItem.droplab_hidden).toBe(false); }); }); describe('filterHint', () => { + let input; + + beforeEach(() => { + setFixtures(` + <input type="text" id="test" /> + `); + + input = document.getElementById('test'); + }); + it('should filter', () => { - let updatedItem = gl.DropdownUtils.filterHint({ + input.value = 'l'; + let updatedItem = gl.DropdownUtils.filterHint(input, { hint: 'label', - }, 'l'); + }); expect(updatedItem.droplab_hidden).toBe(false); - updatedItem = gl.DropdownUtils.filterHint({ + input.value = 'o'; + updatedItem = gl.DropdownUtils.filterHint(input, { hint: 'label', }, 'o'); expect(updatedItem.droplab_hidden).toBe(true); }); it('should return droplab_hidden false when item has no hint', () => { - const updatedItem = gl.DropdownUtils.filterHint({}, ''); + const updatedItem = gl.DropdownUtils.filterHint(input, {}, ''); expect(updatedItem.droplab_hidden).toBe(false); }); }); diff --git a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6 index 0bc6689eba5..ed0b0196ec4 100644 --- a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6 +++ b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6 @@ -31,7 +31,7 @@ require('~/filtered_search/filtered_search_dropdown_manager'); it('should add tokenName and tokenValue', () => { gl.FilteredSearchDropdownManager.addWordToInput('label', 'none'); - expect(getInputValue()).toBe('label:none'); + expect(getInputValue()).toBe('label:none '); }); }); @@ -45,13 +45,13 @@ require('~/filtered_search/filtered_search_dropdown_manager'); it('should replace tokenValue', () => { setInputValue('author:roo'); gl.FilteredSearchDropdownManager.addWordToInput('author', '@root'); - expect(getInputValue()).toBe('author:@root'); + expect(getInputValue()).toBe('author:@root '); }); it('should add tokenValues containing spaces', () => { setInputValue('label:~"test'); gl.FilteredSearchDropdownManager.addWordToInput('label', '~\'"test me"\''); - expect(getInputValue()).toBe('label:~\'"test me"\''); + expect(getInputValue()).toBe('label:~\'"test me"\' '); }); }); }); diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6 index 7df9d9ec1cb..cf409a7e509 100644 --- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6 +++ b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6 @@ -72,6 +72,12 @@ require('~/filtered_search/filtered_search_token_keys'); const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); expect(result).toEqual(tokenKeys[0]); }); + + it('should return alternative tokenKey when found by key param', () => { + const tokenKeys = gl.FilteredSearchTokenKeys.getAlternatives(); + const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); + expect(result).toEqual(tokenKeys[0]); + }); }); describe('searchByConditionUrl', () => { diff --git a/spec/javascripts/fixtures/emoji_menu.js b/spec/javascripts/fixtures/emoji_menu.js index 3d776bb9277..2ef242901e8 100644 --- a/spec/javascripts/fixtures/emoji_menu.js +++ b/spec/javascripts/fixtures/emoji_menu.js @@ -1,5 +1,4 @@ -/* eslint-disable space-before-function-paren, padded-blocks */ +/* eslint-disable space-before-function-paren */ (function() { window.emojiMenu = "<div class='emoji-menu'>\n <input type=\"text\" name=\"emoji_search\" id=\"emoji_search\" value=\"\" class=\"emoji-search search-input form-control\" />\n <div class='emoji-menu-content'>\n <h5 class='emoji-menu-title'>\n Emoticons\n </h5>\n <ul class='clearfix emoji-menu-list'>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47D\" title=\"alien\" data-aliases=\"\" data-emoji=\"alien\" data-unicode-name=\"1F47D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47C\" title=\"angel\" data-aliases=\"\" data-emoji=\"angel\" data-unicode-name=\"1F47C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A2\" title=\"anger\" data-aliases=\"\" data-emoji=\"anger\" data-unicode-name=\"1F4A2\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F620\" title=\"angry\" data-aliases=\"\" data-emoji=\"angry\" data-unicode-name=\"1F620\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F627\" title=\"anguished\" data-aliases=\"\" data-emoji=\"anguished\" data-unicode-name=\"1F627\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F632\" title=\"astonished\" data-aliases=\"\" data-emoji=\"astonished\" data-unicode-name=\"1F632\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45F\" title=\"athletic_shoe\" data-aliases=\"\" data-emoji=\"athletic_shoe\" data-unicode-name=\"1F45F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F476\" title=\"baby\" data-aliases=\"\" data-emoji=\"baby\" data-unicode-name=\"1F476\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F459\" title=\"bikini\" data-aliases=\"\" data-emoji=\"bikini\" data-unicode-name=\"1F459\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F499\" title=\"blue_heart\" data-aliases=\"\" data-emoji=\"blue_heart\" data-unicode-name=\"1F499\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60A\" title=\"blush\" data-aliases=\"\" data-emoji=\"blush\" data-unicode-name=\"1F60A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A5\" title=\"boom\" data-aliases=\"\" data-emoji=\"boom\" data-unicode-name=\"1F4A5\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F462\" title=\"boot\" data-aliases=\"\" data-emoji=\"boot\" data-unicode-name=\"1F462\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F647\" title=\"bow\" data-aliases=\"\" data-emoji=\"bow\" data-unicode-name=\"1F647\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F466\" title=\"boy\" data-aliases=\"\" data-emoji=\"boy\" data-unicode-name=\"1F466\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F470\" title=\"bride_with_veil\" data-aliases=\"\" data-emoji=\"bride_with_veil\" data-unicode-name=\"1F470\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4BC\" title=\"briefcase\" data-aliases=\"\" data-emoji=\"briefcase\" data-unicode-name=\"1F4BC\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F494\" title=\"broken_heart\" data-aliases=\"\" data-emoji=\"broken_heart\" data-unicode-name=\"1F494\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F464\" title=\"bust_in_silhouette\" data-aliases=\"\" data-emoji=\"bust_in_silhouette\" data-unicode-name=\"1F464\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F465\" title=\"busts_in_silhouette\" data-aliases=\"\" data-emoji=\"busts_in_silhouette\" data-unicode-name=\"1F465\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44F\" title=\"clap\" data-aliases=\"\" data-emoji=\"clap\" data-unicode-name=\"1F44F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F302\" title=\"closed_umbrella\" data-aliases=\"\" data-emoji=\"closed_umbrella\" data-unicode-name=\"1F302\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F630\" title=\"cold_sweat\" data-aliases=\"\" data-emoji=\"cold_sweat\" data-unicode-name=\"1F630\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F616\" title=\"confounded\" data-aliases=\"\" data-emoji=\"confounded\" data-unicode-name=\"1F616\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F615\" title=\"confused\" data-aliases=\"\" data-emoji=\"confused\" data-unicode-name=\"1F615\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F477\" title=\"construction_worker\" data-aliases=\"\" data-emoji=\"construction_worker\" data-unicode-name=\"1F477\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46E\" title=\"cop\" data-aliases=\"\" data-emoji=\"cop\" data-unicode-name=\"1F46E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46B\" title=\"couple\" data-aliases=\"\" data-emoji=\"couple\" data-unicode-name=\"1F46B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F491\" title=\"couple_with_heart\" data-aliases=\"\" data-emoji=\"couple_with_heart\" data-unicode-name=\"1F491\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48F\" title=\"couplekiss\" data-aliases=\"\" data-emoji=\"couplekiss\" data-unicode-name=\"1F48F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F451\" title=\"crown\" data-aliases=\"\" data-emoji=\"crown\" data-unicode-name=\"1F451\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F622\" title=\"cry\" data-aliases=\"\" data-emoji=\"cry\" data-unicode-name=\"1F622\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63F\" title=\"crying_cat_face\" data-aliases=\"\" data-emoji=\"crying_cat_face\" data-unicode-name=\"1F63F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F498\" title=\"cupid\" data-aliases=\"\" data-emoji=\"cupid\" data-unicode-name=\"1F498\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F483\" title=\"dancer\" data-aliases=\"\" data-emoji=\"dancer\" data-unicode-name=\"1F483\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46F\" title=\"dancers\" data-aliases=\"\" data-emoji=\"dancers\" data-unicode-name=\"1F46F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A8\" title=\"dash\" data-aliases=\"\" data-emoji=\"dash\" data-unicode-name=\"1F4A8\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61E\" title=\"disappointed\" data-aliases=\"\" data-emoji=\"disappointed\" data-unicode-name=\"1F61E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F625\" title=\"disappointed_relieved\" data-aliases=\"\" data-emoji=\"disappointed_relieved\" data-unicode-name=\"1F625\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AB\" title=\"dizzy\" data-aliases=\"\" data-emoji=\"dizzy\" data-unicode-name=\"1F4AB\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F635\" title=\"dizzy_face\" data-aliases=\"\" data-emoji=\"dizzy_face\" data-unicode-name=\"1F635\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F457\" title=\"dress\" data-aliases=\"\" data-emoji=\"dress\" data-unicode-name=\"1F457\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A7\" title=\"droplet\" data-aliases=\"\" data-emoji=\"droplet\" data-unicode-name=\"1F4A7\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F442\" title=\"ear\" data-aliases=\"\" data-emoji=\"ear\" data-unicode-name=\"1F442\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F611\" title=\"expressionless\" data-aliases=\"\" data-emoji=\"expressionless\" data-unicode-name=\"1F611\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F453\" title=\"eyeglasses\" data-aliases=\"\" data-emoji=\"eyeglasses\" data-unicode-name=\"1F453\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F440\" title=\"eyes\" data-aliases=\"\" data-emoji=\"eyes\" data-unicode-name=\"1F440\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46A\" title=\"family\" data-aliases=\"\" data-emoji=\"family\" data-unicode-name=\"1F46A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F628\" title=\"fearful\" data-aliases=\"\" data-emoji=\"fearful\" data-unicode-name=\"1F628\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F525\" title=\"fire\" data-aliases=\":flame:\" data-emoji=\"fire\" data-unicode-name=\"1F525\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270A\" title=\"fist\" data-aliases=\"\" data-emoji=\"fist\" data-unicode-name=\"270A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F633\" title=\"flushed\" data-aliases=\"\" data-emoji=\"flushed\" data-unicode-name=\"1F633\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F463\" title=\"footprints\" data-aliases=\"\" data-emoji=\"footprints\" data-unicode-name=\"1F463\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F626\" title=\"frowning\" data-aliases=\":anguished:\" data-emoji=\"frowning\" data-unicode-name=\"1F626\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48E\" title=\"gem\" data-aliases=\"\" data-emoji=\"gem\" data-unicode-name=\"1F48E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F467\" title=\"girl\" data-aliases=\"\" data-emoji=\"girl\" data-unicode-name=\"1F467\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49A\" title=\"green_heart\" data-aliases=\"\" data-emoji=\"green_heart\" data-unicode-name=\"1F49A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62C\" title=\"grimacing\" data-aliases=\"\" data-emoji=\"grimacing\" data-unicode-name=\"1F62C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F601\" title=\"grin\" data-aliases=\"\" data-emoji=\"grin\" data-unicode-name=\"1F601\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F600\" title=\"grinning\" data-aliases=\"\" data-emoji=\"grinning\" data-unicode-name=\"1F600\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F482\" title=\"guardsman\" data-aliases=\"\" data-emoji=\"guardsman\" data-unicode-name=\"1F482\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F487\" title=\"haircut\" data-aliases=\"\" data-emoji=\"haircut\" data-unicode-name=\"1F487\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45C\" title=\"handbag\" data-aliases=\"\" data-emoji=\"handbag\" data-unicode-name=\"1F45C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F649\" title=\"hear_no_evil\" data-aliases=\"\" data-emoji=\"hear_no_evil\" data-unicode-name=\"1F649\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-2764\" title=\"heart\" data-aliases=\"\" data-emoji=\"heart\" data-unicode-name=\"2764\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60D\" title=\"heart_eyes\" data-aliases=\"\" data-emoji=\"heart_eyes\" data-unicode-name=\"1F60D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63B\" title=\"heart_eyes_cat\" data-aliases=\"\" data-emoji=\"heart_eyes_cat\" data-unicode-name=\"1F63B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F493\" title=\"heartbeat\" data-aliases=\"\" data-emoji=\"heartbeat\" data-unicode-name=\"1F493\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F497\" title=\"heartpulse\" data-aliases=\"\" data-emoji=\"heartpulse\" data-unicode-name=\"1F497\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F460\" title=\"high_heel\" data-aliases=\"\" data-emoji=\"high_heel\" data-unicode-name=\"1F460\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62F\" title=\"hushed\" data-aliases=\"\" data-emoji=\"hushed\" data-unicode-name=\"1F62F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47F\" title=\"imp\" data-aliases=\"\" data-emoji=\"imp\" data-unicode-name=\"1F47F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F481\" title=\"information_desk_person\" data-aliases=\"\" data-emoji=\"information_desk_person\" data-unicode-name=\"1F481\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F607\" title=\"innocent\" data-aliases=\"\" data-emoji=\"innocent\" data-unicode-name=\"1F607\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47A\" title=\"japanese_goblin\" data-aliases=\"\" data-emoji=\"japanese_goblin\" data-unicode-name=\"1F47A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F479\" title=\"japanese_ogre\" data-aliases=\"\" data-emoji=\"japanese_ogre\" data-unicode-name=\"1F479\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F456\" title=\"jeans\" data-aliases=\"\" data-emoji=\"jeans\" data-unicode-name=\"1F456\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F602\" title=\"joy\" data-aliases=\"\" data-emoji=\"joy\" data-unicode-name=\"1F602\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F639\" title=\"joy_cat\" data-aliases=\"\" data-emoji=\"joy_cat\" data-unicode-name=\"1F639\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F458\" title=\"kimono\" data-aliases=\"\" data-emoji=\"kimono\" data-unicode-name=\"1F458\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48B\" title=\"kiss\" data-aliases=\"\" data-emoji=\"kiss\" data-unicode-name=\"1F48B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F617\" title=\"kissing\" data-aliases=\"\" data-emoji=\"kissing\" data-unicode-name=\"1F617\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63D\" title=\"kissing_cat\" data-aliases=\"\" data-emoji=\"kissing_cat\" data-unicode-name=\"1F63D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61A\" title=\"kissing_closed_eyes\" data-aliases=\"\" data-emoji=\"kissing_closed_eyes\" data-unicode-name=\"1F61A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F618\" title=\"kissing_heart\" data-aliases=\"\" data-emoji=\"kissing_heart\" data-unicode-name=\"1F618\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F619\" title=\"kissing_smiling_eyes\" data-aliases=\"\" data-emoji=\"kissing_smiling_eyes\" data-unicode-name=\"1F619\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F606\" title=\"laughing\" data-aliases=\":satisfied:\" data-emoji=\"laughing\" data-unicode-name=\"1F606\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F444\" title=\"lips\" data-aliases=\"\" data-emoji=\"lips\" data-unicode-name=\"1F444\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F484\" title=\"lipstick\" data-aliases=\"\" data-emoji=\"lipstick\" data-unicode-name=\"1F484\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48C\" title=\"love_letter\" data-aliases=\"\" data-emoji=\"love_letter\" data-unicode-name=\"1F48C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F468\" title=\"man\" data-aliases=\"\" data-emoji=\"man\" data-unicode-name=\"1F468\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F472\" title=\"man_with_gua_pi_mao\" data-aliases=\"\" data-emoji=\"man_with_gua_pi_mao\" data-unicode-name=\"1F472\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F473\" title=\"man_with_turban\" data-aliases=\"\" data-emoji=\"man_with_turban\" data-unicode-name=\"1F473\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45E\" title=\"mans_shoe\" data-aliases=\"\" data-emoji=\"mans_shoe\" data-unicode-name=\"1F45E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F637\" title=\"mask\" data-aliases=\"\" data-emoji=\"mask\" data-unicode-name=\"1F637\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F486\" title=\"massage\" data-aliases=\"\" data-emoji=\"massage\" data-unicode-name=\"1F486\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AA\" title=\"muscle\" data-aliases=\"\" data-emoji=\"muscle\" data-unicode-name=\"1F4AA\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F485\" title=\"nail_care\" data-aliases=\"\" data-emoji=\"nail_care\" data-unicode-name=\"1F485\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F454\" title=\"necktie\" data-aliases=\"\" data-emoji=\"necktie\" data-unicode-name=\"1F454\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F610\" title=\"neutral_face\" data-aliases=\"\" data-emoji=\"neutral_face\" data-unicode-name=\"1F610\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F645\" title=\"no_good\" data-aliases=\"\" data-emoji=\"no_good\" data-unicode-name=\"1F645\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F636\" title=\"no_mouth\" data-aliases=\"\" data-emoji=\"no_mouth\" data-unicode-name=\"1F636\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F443\" title=\"nose\" data-aliases=\"\" data-emoji=\"nose\" data-unicode-name=\"1F443\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44C\" title=\"ok_hand\" data-aliases=\"\" data-emoji=\"ok_hand\" data-unicode-name=\"1F44C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F646\" title=\"ok_woman\" data-aliases=\"\" data-emoji=\"ok_woman\" data-unicode-name=\"1F646\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F474\" title=\"older_man\" data-aliases=\"\" data-emoji=\"older_man\" data-unicode-name=\"1F474\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F475\" title=\"older_woman\" data-aliases=\":grandma:\" data-emoji=\"older_woman\" data-unicode-name=\"1F475\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F450\" title=\"open_hands\" data-aliases=\"\" data-emoji=\"open_hands\" data-unicode-name=\"1F450\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62E\" title=\"open_mouth\" data-aliases=\"\" data-emoji=\"open_mouth\" data-unicode-name=\"1F62E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F614\" title=\"pensive\" data-aliases=\"\" data-emoji=\"pensive\" data-unicode-name=\"1F614\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F623\" title=\"persevere\" data-aliases=\"\" data-emoji=\"persevere\" data-unicode-name=\"1F623\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64D\" title=\"person_frowning\" data-aliases=\"\" data-emoji=\"person_frowning\" data-unicode-name=\"1F64D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F471\" title=\"person_with_blond_hair\" data-aliases=\"\" data-emoji=\"person_with_blond_hair\" data-unicode-name=\"1F471\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64E\" title=\"person_with_pouting_face\" data-aliases=\"\" data-emoji=\"person_with_pouting_face\" data-unicode-name=\"1F64E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F447\" title=\"point_down\" data-aliases=\"\" data-emoji=\"point_down\" data-unicode-name=\"1F447\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F448\" title=\"point_left\" data-aliases=\"\" data-emoji=\"point_left\" data-unicode-name=\"1F448\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F449\" title=\"point_right\" data-aliases=\"\" data-emoji=\"point_right\" data-unicode-name=\"1F449\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-261D\" title=\"point_up\" data-aliases=\"\" data-emoji=\"point_up\" data-unicode-name=\"261D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F446\" title=\"point_up_2\" data-aliases=\"\" data-emoji=\"point_up_2\" data-unicode-name=\"1F446\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A9\" title=\"poop\" data-aliases=\":shit: :hankey: :poo:\" data-emoji=\"poop\" data-unicode-name=\"1F4A9\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45D\" title=\"pouch\" data-aliases=\"\" data-emoji=\"pouch\" data-unicode-name=\"1F45D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63E\" title=\"pouting_cat\" data-aliases=\"\" data-emoji=\"pouting_cat\" data-unicode-name=\"1F63E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64F\" title=\"pray\" data-aliases=\"\" data-emoji=\"pray\" data-unicode-name=\"1F64F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F478\" title=\"princess\" data-aliases=\"\" data-emoji=\"princess\" data-unicode-name=\"1F478\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44A\" title=\"punch\" data-aliases=\"\" data-emoji=\"punch\" data-unicode-name=\"1F44A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49C\" title=\"purple_heart\" data-aliases=\"\" data-emoji=\"purple_heart\" data-unicode-name=\"1F49C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45B\" title=\"purse\" data-aliases=\"\" data-emoji=\"purse\" data-unicode-name=\"1F45B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F621\" title=\"rage\" data-aliases=\"\" data-emoji=\"rage\" data-unicode-name=\"1F621\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270B\" title=\"raised_hand\" data-aliases=\"\" data-emoji=\"raised_hand\" data-unicode-name=\"270B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64C\" title=\"raised_hands\" data-aliases=\"\" data-emoji=\"raised_hands\" data-unicode-name=\"1F64C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64B\" title=\"raising_hand\" data-aliases=\"\" data-emoji=\"raising_hand\" data-unicode-name=\"1F64B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-263A\" title=\"relaxed\" data-aliases=\"\" data-emoji=\"relaxed\" data-unicode-name=\"263A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60C\" title=\"relieved\" data-aliases=\"\" data-emoji=\"relieved\" data-unicode-name=\"1F60C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49E\" title=\"revolving_hearts\" data-aliases=\"\" data-emoji=\"revolving_hearts\" data-unicode-name=\"1F49E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F380\" title=\"ribbon\" data-aliases=\"\" data-emoji=\"ribbon\" data-unicode-name=\"1F380\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48D\" title=\"ring\" data-aliases=\"\" data-emoji=\"ring\" data-unicode-name=\"1F48D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3C3\" title=\"runner\" data-aliases=\"\" data-emoji=\"runner\" data-unicode-name=\"1F3C3\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3BD\" title=\"running_shirt_with_sash\" data-aliases=\"\" data-emoji=\"running_shirt_with_sash\" data-unicode-name=\"1F3BD\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F461\" title=\"sandal\" data-aliases=\"\" data-emoji=\"sandal\" data-unicode-name=\"1F461\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F631\" title=\"scream\" data-aliases=\"\" data-emoji=\"scream\" data-unicode-name=\"1F631\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F640\" title=\"scream_cat\" data-aliases=\"\" data-emoji=\"scream_cat\" data-unicode-name=\"1F640\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F648\" title=\"see_no_evil\" data-aliases=\"\" data-emoji=\"see_no_evil\" data-unicode-name=\"1F648\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F455\" title=\"shirt\" data-aliases=\"\" data-emoji=\"shirt\" data-unicode-name=\"1F455\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F480\" title=\"skull\" data-aliases=\":skeleton:\" data-emoji=\"skull\" data-unicode-name=\"1F480\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F634\" title=\"sleeping\" data-aliases=\"\" data-emoji=\"sleeping\" data-unicode-name=\"1F634\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62A\" title=\"sleepy\" data-aliases=\"\" data-emoji=\"sleepy\" data-unicode-name=\"1F62A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F604\" title=\"smile\" data-aliases=\"\" data-emoji=\"smile\" data-unicode-name=\"1F604\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F638\" title=\"smile_cat\" data-aliases=\"\" data-emoji=\"smile_cat\" data-unicode-name=\"1F638\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F603\" title=\"smiley\" data-aliases=\"\" data-emoji=\"smiley\" data-unicode-name=\"1F603\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63A\" title=\"smiley_cat\" data-aliases=\"\" data-emoji=\"smiley_cat\" data-unicode-name=\"1F63A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F608\" title=\"smiling_imp\" data-aliases=\"\" data-emoji=\"smiling_imp\" data-unicode-name=\"1F608\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60F\" title=\"smirk\" data-aliases=\"\" data-emoji=\"smirk\" data-unicode-name=\"1F60F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63C\" title=\"smirk_cat\" data-aliases=\"\" data-emoji=\"smirk_cat\" data-unicode-name=\"1F63C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62D\" title=\"sob\" data-aliases=\"\" data-emoji=\"sob\" data-unicode-name=\"1F62D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-2728\" title=\"sparkles\" data-aliases=\"\" data-emoji=\"sparkles\" data-unicode-name=\"2728\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F496\" title=\"sparkling_heart\" data-aliases=\"\" data-emoji=\"sparkling_heart\" data-unicode-name=\"1F496\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64A\" title=\"speak_no_evil\" data-aliases=\"\" data-emoji=\"speak_no_evil\" data-unicode-name=\"1F64A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AC\" title=\"speech_balloon\" data-aliases=\"\" data-emoji=\"speech_balloon\" data-unicode-name=\"1F4AC\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F31F\" title=\"star2\" data-aliases=\"\" data-emoji=\"star2\" data-unicode-name=\"1F31F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61B\" title=\"stuck_out_tongue\" data-aliases=\"\" data-emoji=\"stuck_out_tongue\" data-unicode-name=\"1F61B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61D\" title=\"stuck_out_tongue_closed_eyes\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_closed_eyes\" data-unicode-name=\"1F61D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61C\" title=\"stuck_out_tongue_winking_eye\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_winking_eye\" data-unicode-name=\"1F61C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60E\" title=\"sunglasses\" data-aliases=\"\" data-emoji=\"sunglasses\" data-unicode-name=\"1F60E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F613\" title=\"sweat\" data-aliases=\"\" data-emoji=\"sweat\" data-unicode-name=\"1F613\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A6\" title=\"sweat_drops\" data-aliases=\"\" data-emoji=\"sweat_drops\" data-unicode-name=\"1F4A6\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F605\" title=\"sweat_smile\" data-aliases=\"\" data-emoji=\"sweat_smile\" data-unicode-name=\"1F605\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AD\" title=\"thought_balloon\" data-aliases=\"\" data-emoji=\"thought_balloon\" data-unicode-name=\"1F4AD\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44E\" title=\"thumbsdown\" data-aliases=\":-1:\" data-emoji=\"thumbsdown\" data-unicode-name=\"1F44E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44D\" title=\"thumbsup\" data-aliases=\":+1:\" data-emoji=\"thumbsup\" data-unicode-name=\"1F44D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62B\" title=\"tired_face\" data-aliases=\"\" data-emoji=\"tired_face\" data-unicode-name=\"1F62B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F445\" title=\"tongue\" data-aliases=\"\" data-emoji=\"tongue\" data-unicode-name=\"1F445\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3A9\" title=\"tophat\" data-aliases=\"\" data-emoji=\"tophat\" data-unicode-name=\"1F3A9\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F624\" title=\"triumph\" data-aliases=\"\" data-emoji=\"triumph\" data-unicode-name=\"1F624\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F495\" title=\"two_hearts\" data-aliases=\"\" data-emoji=\"two_hearts\" data-unicode-name=\"1F495\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46C\" title=\"two_men_holding_hands\" data-aliases=\"\" data-emoji=\"two_men_holding_hands\" data-unicode-name=\"1F46C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46D\" title=\"two_women_holding_hands\" data-aliases=\"\" data-emoji=\"two_women_holding_hands\" data-unicode-name=\"1F46D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F612\" title=\"unamused\" data-aliases=\"\" data-emoji=\"unamused\" data-unicode-name=\"1F612\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270C\" title=\"v\" data-aliases=\"\" data-emoji=\"v\" data-unicode-name=\"270C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F6B6\" title=\"walking\" data-aliases=\"\" data-emoji=\"walking\" data-unicode-name=\"1F6B6\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44B\" title=\"wave\" data-aliases=\"\" data-emoji=\"wave\" data-unicode-name=\"1F44B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F629\" title=\"weary\" data-aliases=\"\" data-emoji=\"weary\" data-unicode-name=\"1F629\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F609\" title=\"wink\" data-aliases=\"\" data-emoji=\"wink\" data-unicode-name=\"1F609\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F469\" title=\"woman\" data-aliases=\"\" data-emoji=\"woman\" data-unicode-name=\"1F469\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45A\" title=\"womans_clothes\" data-aliases=\"\" data-emoji=\"womans_clothes\" data-unicode-name=\"1F45A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F452\" title=\"womans_hat\" data-aliases=\"\" data-emoji=\"womans_hat\" data-unicode-name=\"1F452\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61F\" title=\"worried\" data-aliases=\"\" data-emoji=\"worried\" data-unicode-name=\"1F61F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49B\" title=\"yellow_heart\" data-aliases=\"\" data-emoji=\"yellow_heart\" data-unicode-name=\"1F49B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60B\" title=\"yum\" data-aliases=\"\" data-emoji=\"yum\" data-unicode-name=\"1F60B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A4\" title=\"zzz\" data-aliases=\"\" data-emoji=\"zzz\" data-unicode-name=\"1F4A4\"></div>\n </button>\n </li>\n </ul>\n </div>\n</div>"; - }).call(this); diff --git a/spec/javascripts/fixtures/environments/environments.html.haml b/spec/javascripts/fixtures/environments/environments.html.haml index d89bc50c1f0..e6000fbb553 100644 --- a/spec/javascripts/fixtures/environments/environments.html.haml +++ b/spec/javascripts/fixtures/environments/environments.html.haml @@ -1,5 +1,5 @@ %div - #environments-list-view{ data: { environments_data: "https://gitlab.com/foo/environments", + #environments-list-view{ data: { environments_data: "foo/environments", "can-create-deployment" => "true", "can-read-environment" => "true", "can-create-environment" => "true", diff --git a/spec/javascripts/fixtures/mini_dropdown_graph.html.haml b/spec/javascripts/fixtures/mini_dropdown_graph.html.haml index e9bf7568e95..29370b974af 100644 --- a/spec/javascripts/fixtures/mini_dropdown_graph.html.haml +++ b/spec/javascripts/fixtures/mini_dropdown_graph.html.haml @@ -1,8 +1,9 @@ -%div.js-builds-dropdown-tests - %button.dropdown.js-builds-dropdown-button{'data-stage-endpoint' => 'foobar'} +%div.js-builds-dropdown-tests.dropdown.dropdown.js-mini-pipeline-graph + %button.js-builds-dropdown-button{'data-stage-endpoint' => 'foobar', data: { toggle: 'dropdown'} } Dropdown - %div.js-builds-dropdown-container - %div.js-builds-dropdown-list - %div.js-builds-dropdown-loading.builds-dropdown-loading.hidden + %ul.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container + .js-builds-dropdown-list.scrollable-menu + + .js-builds-dropdown-loading.builds-dropdown-loading.hidden %span.fa.fa-spinner.fa-spin diff --git a/spec/javascripts/gfm_auto_complete_spec.js.es6 b/spec/javascripts/gfm_auto_complete_spec.js.es6 new file mode 100644 index 00000000000..c61c32f8a13 --- /dev/null +++ b/spec/javascripts/gfm_auto_complete_spec.js.es6 @@ -0,0 +1,91 @@ +require('~/gfm_auto_complete'); +require('vendor/jquery.caret'); +require('vendor/jquery.atwho'); + +const global = window.gl || (window.gl = {}); +const GfmAutoComplete = global.GfmAutoComplete; + +describe('GfmAutoComplete', function () { + describe('DefaultOptions.sorter', function () { + describe('assets loading', function () { + beforeEach(function () { + spyOn(GfmAutoComplete, 'isLoading').and.returnValue(true); + + this.atwhoInstance = { setting: {} }; + this.items = []; + + this.sorterValue = GfmAutoComplete.DefaultOptions.sorter + .call(this.atwhoInstance, '', this.items); + }); + + it('should disable highlightFirst', function () { + expect(this.atwhoInstance.setting.highlightFirst).toBe(false); + }); + + it('should return the passed unfiltered items', function () { + expect(this.sorterValue).toEqual(this.items); + }); + }); + + describe('assets finished loading', function () { + beforeEach(function () { + spyOn(GfmAutoComplete, 'isLoading').and.returnValue(false); + spyOn($.fn.atwho.default.callbacks, 'sorter'); + }); + + it('should enable highlightFirst if alwaysHighlightFirst is set', function () { + const atwhoInstance = { setting: { alwaysHighlightFirst: true } }; + + GfmAutoComplete.DefaultOptions.sorter.call(atwhoInstance); + + expect(atwhoInstance.setting.highlightFirst).toBe(true); + }); + + it('should enable highlightFirst if a query is present', function () { + const atwhoInstance = { setting: {} }; + + GfmAutoComplete.DefaultOptions.sorter.call(atwhoInstance, 'query'); + + expect(atwhoInstance.setting.highlightFirst).toBe(true); + }); + + it('should call the default atwho sorter', function () { + const atwhoInstance = { setting: {} }; + + const query = 'query'; + const items = []; + const searchKey = 'searchKey'; + + GfmAutoComplete.DefaultOptions.sorter.call(atwhoInstance, query, items, searchKey); + + expect($.fn.atwho.default.callbacks.sorter).toHaveBeenCalledWith(query, items, searchKey); + }); + }); + }); + + describe('isLoading', function () { + it('should be true with loading data object item', function () { + expect(GfmAutoComplete.isLoading({ name: 'loading' })).toBe(true); + }); + + it('should be true with loading data array', function () { + expect(GfmAutoComplete.isLoading(['loading'])).toBe(true); + }); + + it('should be true with loading data object array', function () { + expect(GfmAutoComplete.isLoading([{ name: 'loading' }])).toBe(true); + }); + + it('should be false with actual array data', function () { + expect(GfmAutoComplete.isLoading([ + { title: 'Foo' }, + { title: 'Bar' }, + { title: 'Qux' }, + ])).toBe(false); + }); + + it('should be false with actual data item', function () { + expect(GfmAutoComplete.isLoading({ title: 'Foo' })).toBe(false); + }); + }); +}); diff --git a/spec/javascripts/gl_dropdown_spec.js.es6 b/spec/javascripts/gl_dropdown_spec.js.es6 index 83e1638cdc6..a3b81b663e0 100644 --- a/spec/javascripts/gl_dropdown_spec.js.es6 +++ b/spec/javascripts/gl_dropdown_spec.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, prefer-const, no-param-reassign, no-plusplus, semi, no-unused-expressions, arrow-spacing, max-len */ +/* eslint-disable comma-dangle, no-param-reassign, no-unused-expressions, max-len */ /* global Turbolinks */ require('~/gl_dropdown'); @@ -20,7 +20,7 @@ require('~/lib/utils/type_utility'); let remoteCallback; - let navigateWithKeys = function navigateWithKeys(direction, steps, cb, i) { + const navigateWithKeys = function navigateWithKeys(direction, steps, cb, i) { i = i || 0; if (!i) direction = direction.toUpperCase(); $('body').trigger({ @@ -28,7 +28,7 @@ require('~/lib/utils/type_utility'); which: ARROW_KEYS[direction], keyCode: ARROW_KEYS[direction] }); - i++; + i += 1; if (i <= steps) { navigateWithKeys(direction, steps, cb, i); } else { @@ -36,9 +36,9 @@ require('~/lib/utils/type_utility'); } }; - let remoteMock = function remoteMock(data, term, callback) { + const remoteMock = function remoteMock(data, term, callback) { remoteCallback = callback.bind({}, data); - } + }; describe('Dropdown', function describeDropdown() { preloadFixtures('static/gl_dropdown.html.raw'); @@ -88,7 +88,7 @@ require('~/lib/utils/type_utility'); it('should select a following item on DOWN keypress', () => { expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(0); - let randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 1)) + 0); + const randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 1)) + 0); navigateWithKeys('down', randomIndex, () => { expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1); expect($(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement)).toHaveClass('is-focused'); @@ -99,7 +99,7 @@ require('~/lib/utils/type_utility'); expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(0); navigateWithKeys('down', (this.projectsData.length - 1), () => { expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1); - let randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 2)) + 0); + const randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 2)) + 0); navigateWithKeys('up', randomIndex, () => { expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1); expect($(`${ITEM_SELECTOR}:eq(${((this.projectsData.length - 2) - randomIndex)}) a`, this.$dropdownMenuElement)).toHaveClass('is-focused'); @@ -108,15 +108,15 @@ require('~/lib/utils/type_utility'); }); it('should click the selected item on ENTER keypress', () => { - expect(this.dropdownContainerElement).toHaveClass('open') - let randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0 + expect(this.dropdownContainerElement).toHaveClass('open'); + const randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0; navigateWithKeys('down', randomIndex, () => { spyOn(Turbolinks, 'visit').and.stub(); navigateWithKeys('enter', null, () => { expect(this.dropdownContainerElement).not.toHaveClass('open'); - let link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement); + const link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement); expect(link).toHaveClass('is-active'); - let linkedLocation = link.attr('href'); + const linkedLocation = link.attr('href'); if (linkedLocation && linkedLocation !== '#') expect(Turbolinks.visit).toHaveBeenCalledWith(linkedLocation); }); }); @@ -139,18 +139,18 @@ require('~/lib/utils/type_utility'); this.dropdownButtonElement.click(); }); - it('should not focus search input while remote task is not complete', ()=> { + it('should not focus search input while remote task is not complete', () => { expect($(document.activeElement)).not.toEqual($(SEARCH_INPUT_SELECTOR)); remoteCallback(); expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR)); }); - it('should focus search input after remote task is complete', ()=> { + it('should focus search input after remote task is complete', () => { remoteCallback(); expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR)); }); - it('should focus on input when opening for the second time', ()=> { + it('should focus on input when opening for the second time', () => { remoteCallback(); this.dropdownContainerElement.trigger({ type: 'keyup', @@ -163,16 +163,15 @@ require('~/lib/utils/type_utility'); }); describe('input focus with array data', () => { - it('should focus input when passing array data to drop down', ()=> { + it('should focus input when passing array data to drop down', () => { initDropDown.call(this, false, true); this.dropdownButtonElement.click(); expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR)); }); }); - it('should still have input value on close and restore', () => { - let $searchInput = $(SEARCH_INPUT_SELECTOR); + const $searchInput = $(SEARCH_INPUT_SELECTOR); initDropDown.call(this, false, true); $searchInput .trigger('focus') diff --git a/spec/javascripts/gl_field_errors_spec.js.es6 b/spec/javascripts/gl_field_errors_spec.js.es6 index 51ba59df671..733023481f5 100644 --- a/spec/javascripts/gl_field_errors_spec.js.es6 +++ b/spec/javascripts/gl_field_errors_spec.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, arrow-body-style, indent, padded-blocks */ +/* eslint-disable space-before-function-paren, arrow-body-style */ require('~/gl_field_errors'); @@ -27,7 +27,7 @@ require('~/gl_field_errors'); expect(customErrorElem.length).toBe(1); const customErrors = this.fieldErrors.state.inputs.filter((input) => { - return input.inputElement.hasClass(customErrorFlag); + return input.inputElement.hasClass(customErrorFlag); }); expect(customErrors.length).toBe(0); }); @@ -106,7 +106,5 @@ require('~/gl_field_errors'); expect(noTitleErrorElem.text()).toBe('This field is required.'); expect(hasTitleErrorElem.text()).toBe('Please provide a valid email address.'); }); - }); - })(window.gl || (window.gl = {})); diff --git a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js index 88aaaa0471b..a954bb60560 100644 --- a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js +++ b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable quotes, indent, semi, object-curly-spacing, jasmine/no-suite-dupes, vars-on-top, no-var, padded-blocks, spaced-comment, max-len */ +/* eslint-disable quotes, jasmine/no-suite-dupes, vars-on-top, no-var, max-len */ /* global d3 */ /* global ContributorsGraph */ /* global ContributorsMasterGraph */ @@ -8,126 +8,123 @@ require('~/graphs/stat_graph_contributors_graph'); describe("ContributorsGraph", function () { describe("#set_x_domain", function () { it("set the x_domain", function () { - ContributorsGraph.set_x_domain(20) - expect(ContributorsGraph.prototype.x_domain).toEqual(20) - }) - }) + ContributorsGraph.set_x_domain(20); + expect(ContributorsGraph.prototype.x_domain).toEqual(20); + }); + }); describe("#set_y_domain", function () { it("sets the y_domain", function () { - ContributorsGraph.set_y_domain([{commits: 30}]) - expect(ContributorsGraph.prototype.y_domain).toEqual([0, 30]) - }) - }) + ContributorsGraph.set_y_domain([{ commits: 30 }]); + expect(ContributorsGraph.prototype.y_domain).toEqual([0, 30]); + }); + }); describe("#init_x_domain", function () { it("sets the initial x_domain", function () { - ContributorsGraph.init_x_domain([{date: "2013-01-31"}, {date: "2012-01-31"}]) - expect(ContributorsGraph.prototype.x_domain).toEqual(["2012-01-31", "2013-01-31"]) - }) - }) + ContributorsGraph.init_x_domain([{ date: "2013-01-31" }, { date: "2012-01-31" }]); + expect(ContributorsGraph.prototype.x_domain).toEqual(["2012-01-31", "2013-01-31"]); + }); + }); describe("#init_y_domain", function () { it("sets the initial y_domain", function () { - ContributorsGraph.init_y_domain([{commits: 30}]) - expect(ContributorsGraph.prototype.y_domain).toEqual([0, 30]) - }) - }) + ContributorsGraph.init_y_domain([{ commits: 30 }]); + expect(ContributorsGraph.prototype.y_domain).toEqual([0, 30]); + }); + }); describe("#init_domain", function () { it("calls init_x_domain and init_y_domain", function () { - spyOn(ContributorsGraph, "init_x_domain") - spyOn(ContributorsGraph, "init_y_domain") - ContributorsGraph.init_domain() - expect(ContributorsGraph.init_x_domain).toHaveBeenCalled() - expect(ContributorsGraph.init_y_domain).toHaveBeenCalled() - }) - }) + spyOn(ContributorsGraph, "init_x_domain"); + spyOn(ContributorsGraph, "init_y_domain"); + ContributorsGraph.init_domain(); + expect(ContributorsGraph.init_x_domain).toHaveBeenCalled(); + expect(ContributorsGraph.init_y_domain).toHaveBeenCalled(); + }); + }); describe("#set_dates", function () { it("sets the dates", function () { - ContributorsGraph.set_dates("2013-12-01") - expect(ContributorsGraph.prototype.dates).toEqual("2013-12-01") - }) - }) + ContributorsGraph.set_dates("2013-12-01"); + expect(ContributorsGraph.prototype.dates).toEqual("2013-12-01"); + }); + }); describe("#set_x_domain", 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) - spyOn(instance.x, 'domain') - instance.set_x_domain() - expect(instance.x.domain).toHaveBeenCalledWith(20) - }) - }) + ContributorsGraph.prototype.x_domain = 20; + var instance = new ContributorsGraph(); + instance.x = d3.time.scale().range([0, 100]).clamp(true); + spyOn(instance.x, 'domain'); + instance.set_x_domain(); + expect(instance.x.domain).toHaveBeenCalledWith(20); + }); + }); describe("#set_y_domain", 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() - spyOn(instance.y, 'domain') - instance.set_y_domain() - expect(instance.y.domain).toHaveBeenCalledWith(30) - }) - }) + ContributorsGraph.prototype.y_domain = 30; + var instance = new ContributorsGraph(); + instance.y = d3.scale.linear().range([100, 0]).nice(); + spyOn(instance.y, 'domain'); + instance.set_y_domain(); + expect(instance.y.domain).toHaveBeenCalledWith(30); + }); + }); describe("#set_domain", function () { it("calls set_x_domain and set_y_domain", function () { - var instance = new ContributorsGraph() - spyOn(instance, 'set_x_domain') - spyOn(instance, 'set_y_domain') - instance.set_domain() - expect(instance.set_x_domain).toHaveBeenCalled() - expect(instance.set_y_domain).toHaveBeenCalled() - }) - }) + var instance = new ContributorsGraph(); + spyOn(instance, 'set_x_domain'); + spyOn(instance, 'set_y_domain'); + instance.set_domain(); + expect(instance.set_x_domain).toHaveBeenCalled(); + expect(instance.set_y_domain).toHaveBeenCalled(); + }); + }); describe("#set_data", function () { it("sets the data", function () { - var instance = new ContributorsGraph() - instance.set_data("20") - expect(instance.data).toEqual("20") - }) - }) -}) + var instance = new ContributorsGraph(); + instance.set_data("20"); + expect(instance.data).toEqual("20"); + }); + }); +}); describe("ContributorsMasterGraph", function () { - // TODO: fix or remove - //describe("#process_dates", function () { - //it("gets and parses dates", function () { - //var graph = new ContributorsMasterGraph() - //var data = 'random data here' - //spyOn(graph, 'parse_dates') - //spyOn(graph, 'get_dates').andReturn("get") - //spyOn(ContributorsGraph,'set_dates').andCallThrough() - //graph.process_dates(data) - //expect(graph.parse_dates).toHaveBeenCalledWith(data) - //expect(graph.get_dates).toHaveBeenCalledWith(data) - //expect(ContributorsGraph.set_dates).toHaveBeenCalledWith("get") - //}) - //}) + // describe("#process_dates", function () { + // it("gets and parses dates", function () { + // var graph = new ContributorsMasterGraph(); + // var data = 'random data here'; + // spyOn(graph, 'parse_dates'); + // spyOn(graph, 'get_dates').andReturn("get"); + // spyOn(ContributorsGraph,'set_dates').andCallThrough(); + // graph.process_dates(data); + // expect(graph.parse_dates).toHaveBeenCalledWith(data); + // expect(graph.get_dates).toHaveBeenCalledWith(data); + // expect(ContributorsGraph.set_dates).toHaveBeenCalledWith("get"); + // }); + // }); describe("#get_dates", function () { it("plucks the date field from data collection", function () { - var graph = new ContributorsMasterGraph() - var data = [{date: "2013-01-01"}, {date: "2012-12-15"}] - expect(graph.get_dates(data)).toEqual(["2013-01-01", "2012-12-15"]) - }) - }) + var graph = new ContributorsMasterGraph(); + var data = [{ date: "2013-01-01" }, { date: "2012-12-15" }]; + expect(graph.get_dates(data)).toEqual(["2013-01-01", "2012-12-15"]); + }); + }); describe("#parse_dates", function () { it("parses the dates", function () { - var graph = new ContributorsMasterGraph() - var parseDate = d3.time.format("%Y-%m-%d").parse - 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) - expect(data).toEqual(correct) - }) - }) - - -}) + var graph = new ContributorsMasterGraph(); + var parseDate = d3.time.format("%Y-%m-%d").parse; + 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); + expect(data).toEqual(correct); + }); + }); +}); diff --git a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js index 671b0ae391c..b15764abe8c 100644 --- a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js +++ b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js @@ -1,215 +1,218 @@ -/* eslint-disable quotes, padded-blocks, no-var, camelcase, object-curly-spacing, semi, indent, object-property-newline, comma-dangle, comma-spacing, spaced-comment, max-len, key-spacing, vars-on-top, quote-props, no-multi-spaces */ +/* eslint-disable quotes, no-var, camelcase, object-property-newline, comma-dangle, max-len, vars-on-top, quote-props */ /* global ContributorsStatGraphUtil */ require('~/graphs/stat_graph_contributors_util'); describe("ContributorsStatGraphUtil", function () { - describe("#parse_log", function () { it("returns a correctly parsed log", function () { var fake_log = [ - {author_email: "karlo@email.com", author_name: "Karlo Soriano", date: "2013-05-09", additions: 471}, - {author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 6, deletions: 1}, - {author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 19, deletions: 3}, - {author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 29, deletions: 3}] + { author_email: "karlo@email.com", author_name: "Karlo Soriano", date: "2013-05-09", additions: 471 }, + { author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 6, deletions: 1 }, + { author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 19, deletions: 3 }, + { author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 29, deletions: 3 } + ]; var correct_parsed_log = { total: [ - {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}, - {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}], - by_author: - [ - { - author_name: "Karlo Soriano", author_email: "karlo@email.com", - "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1} - }, - { - author_name: "Dmitriy Zaporozhets",author_email: "dzaporozhets@email.com", - "2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3} - } + { date: "2013-05-09", additions: 471, deletions: 0, commits: 1 }, + { date: "2013-05-08", additions: 54, deletions: 7, commits: 3 } + ], + by_author: [ + { + author_name: "Karlo Soriano", author_email: "karlo@email.com", + "2013-05-09": { date: "2013-05-09", additions: 471, deletions: 0, commits: 1 } + }, + { + author_name: "Dmitriy Zaporozhets", author_email: "dzaporozhets@email.com", + "2013-05-08": { date: "2013-05-08", additions: 54, deletions: 7, commits: 3 } + } ] - } - expect(ContributorsStatGraphUtil.parse_log(fake_log)).toEqual(correct_parsed_log) - }) - }) + }; + expect(ContributorsStatGraphUtil.parse_log(fake_log)).toEqual(correct_parsed_log); + }); + }); describe("#store_data", function () { - - var fake_entry = {author: "Karlo Soriano", date: "2013-05-09", additions: 471} - var fake_total = {} - var fake_by_author = {} + var fake_entry = { author: "Karlo Soriano", date: "2013-05-09", additions: 471 }; + var fake_total = {}; + var fake_by_author = {}; it("calls #store_commits", function () { - spyOn(ContributorsStatGraphUtil, 'store_commits') - ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author) - expect(ContributorsStatGraphUtil.store_commits).toHaveBeenCalled() - }) + spyOn(ContributorsStatGraphUtil, 'store_commits'); + ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author); + expect(ContributorsStatGraphUtil.store_commits).toHaveBeenCalled(); + }); it("calls #store_additions", function () { - spyOn(ContributorsStatGraphUtil, 'store_additions') - ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author) - expect(ContributorsStatGraphUtil.store_additions).toHaveBeenCalled() - }) + spyOn(ContributorsStatGraphUtil, 'store_additions'); + ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author); + expect(ContributorsStatGraphUtil.store_additions).toHaveBeenCalled(); + }); it("calls #store_deletions", function () { - spyOn(ContributorsStatGraphUtil, 'store_deletions') - ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author) - expect(ContributorsStatGraphUtil.store_deletions).toHaveBeenCalled() - }) - - }) + spyOn(ContributorsStatGraphUtil, 'store_deletions'); + ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author); + expect(ContributorsStatGraphUtil.store_deletions).toHaveBeenCalled(); + }); + }); // TODO: fix or remove - //describe("#store_commits", function () { - //var fake_total = "fake_total" - //var fake_by_author = "fake_by_author" - - //it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { - //spyOn(ContributorsStatGraphUtil, 'add') - //ContributorsStatGraphUtil.store_commits(fake_total, fake_by_author) - //expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "commits", 1], ["fake_by_author", "commits", 1]]) - //}) - //}) + // describe("#store_commits", function () { + // var fake_total = "fake_total"; + // var fake_by_author = "fake_by_author"; + // + // it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { + // spyOn(ContributorsStatGraphUtil, 'add'); + // ContributorsStatGraphUtil.store_commits(fake_total, fake_by_author); + // expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "commits", 1], ["fake_by_author", "commits", 1]]); + // }); + // }); describe("#add", function () { it("adds 1 to current test_field in collection", function () { - var fake_collection = {test_field: 10} - ContributorsStatGraphUtil.add(fake_collection, "test_field", 1) - expect(fake_collection.test_field).toEqual(11) - }) + var fake_collection = { test_field: 10 }; + ContributorsStatGraphUtil.add(fake_collection, "test_field", 1); + expect(fake_collection.test_field).toEqual(11); + }); it("inits and adds 1 if test_field in collection is not defined", function () { - var fake_collection = {} - ContributorsStatGraphUtil.add(fake_collection, "test_field", 1) - expect(fake_collection.test_field).toEqual(1) - }) - }) + var fake_collection = {}; + ContributorsStatGraphUtil.add(fake_collection, "test_field", 1); + expect(fake_collection.test_field).toEqual(1); + }); + }); // TODO: fix or remove - //describe("#store_additions", function () { - //var fake_entry = {additions: 10} - //var fake_total= "fake_total" - //var fake_by_author = "fake_by_author" - //it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { - //spyOn(ContributorsStatGraphUtil, 'add') - //ContributorsStatGraphUtil.store_additions(fake_entry, fake_total, fake_by_author) - //expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "additions", 10], ["fake_by_author", "additions", 10]]) - //}) - //}) + // describe("#store_additions", function () { + // var fake_entry = {additions: 10}; + // var fake_total= "fake_total"; + // var fake_by_author = "fake_by_author"; + // it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { + // spyOn(ContributorsStatGraphUtil, 'add'); + // ContributorsStatGraphUtil.store_additions(fake_entry, fake_total, fake_by_author); + // expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "additions", 10], ["fake_by_author", "additions", 10]]); + // }); + // }); // TODO: fix or remove - //describe("#store_deletions", function () { - //var fake_entry = {deletions: 10} - //var fake_total= "fake_total" - //var fake_by_author = "fake_by_author" - //it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { - //spyOn(ContributorsStatGraphUtil, 'add') - //ContributorsStatGraphUtil.store_deletions(fake_entry, fake_total, fake_by_author) - //expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "deletions", 10], ["fake_by_author", "deletions", 10]]) - //}) - //}) + // describe("#store_deletions", function () { + // var fake_entry = {deletions: 10}; + // var fake_total= "fake_total"; + // var fake_by_author = "fake_by_author"; + // it("calls #add twice with arguments fake_total and fake_by_author respectively", function () { + // spyOn(ContributorsStatGraphUtil, 'add'); + // ContributorsStatGraphUtil.store_deletions(fake_entry, fake_total, fake_by_author); + // expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "deletions", 10], ["fake_by_author", "deletions", 10]]); + // }); + // }); describe("#add_date", function () { it("adds a date field to the collection", function () { - var fake_date = "2013-10-02" - var fake_collection = {} - ContributorsStatGraphUtil.add_date(fake_date, fake_collection) - expect(fake_collection[fake_date].date).toEqual("2013-10-02") - }) - }) + var fake_date = "2013-10-02"; + var fake_collection = {}; + ContributorsStatGraphUtil.add_date(fake_date, fake_collection); + expect(fake_collection[fake_date].date).toEqual("2013-10-02"); + }); + }); describe("#add_author", function () { it("adds an author field to the collection", function () { - var fake_author = { author_name: "Author", author_email: 'fake@email.com' } - var fake_author_collection = {} - var fake_email_collection = {} - ContributorsStatGraphUtil.add_author(fake_author, fake_author_collection, fake_email_collection) - expect(fake_author_collection[fake_author.author_name].author_name).toEqual("Author") - expect(fake_email_collection[fake_author.author_email].author_name).toEqual("Author") - }) - }) + var fake_author = { author_name: "Author", author_email: 'fake@email.com' }; + var fake_author_collection = {}; + var fake_email_collection = {}; + ContributorsStatGraphUtil.add_author(fake_author, fake_author_collection, fake_email_collection); + expect(fake_author_collection[fake_author.author_name].author_name).toEqual("Author"); + expect(fake_email_collection[fake_author.author_email].author_name).toEqual("Author"); + }); + }); describe("#get_total_data", function () { it("returns the collection sorted via specified field", function () { var fake_parsed_log = { - total: [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1}, - {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}], - by_author:[ - { - author: "Karlo Soriano", - "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1} - }, - { - author: "Dmitriy Zaporozhets", - "2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3} - } - ]}; - var correct_total_data = [{date: "2013-05-08", commits: 3}, - {date: "2013-05-09", commits: 1}]; - expect(ContributorsStatGraphUtil.get_total_data(fake_parsed_log, "commits")).toEqual(correct_total_data) - }) - }) + total: [ + { date: "2013-05-09", additions: 471, deletions: 0, commits: 1 }, + { date: "2013-05-08", additions: 54, deletions: 7, commits: 3 } + ], + by_author: [ + { + author: "Karlo Soriano", + "2013-05-09": { date: "2013-05-09", additions: 471, deletions: 0, commits: 1 } + }, + { + author: "Dmitriy Zaporozhets", + "2013-05-08": { date: "2013-05-08", additions: 54, deletions: 7, commits: 3 } + } + ] + }; + var correct_total_data = [ + { date: "2013-05-08", commits: 3 }, + { date: "2013-05-09", commits: 1 } + ]; + expect(ContributorsStatGraphUtil.get_total_data(fake_parsed_log, "commits")).toEqual(correct_total_data); + }); + }); describe("#pick_field", function () { it("returns the collection with only the specified field and date", function () { - var fake_parsed_log_total = [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1}, - {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}]; - ContributorsStatGraphUtil.pick_field(fake_parsed_log_total, "commits") - var correct_pick_field_data = [{date: "2013-05-09", commits: 1},{date: "2013-05-08", commits: 3}]; - expect(ContributorsStatGraphUtil.pick_field(fake_parsed_log_total, "commits")).toEqual(correct_pick_field_data) - }) - }) + var fake_parsed_log_total = [ + { date: "2013-05-09", additions: 471, deletions: 0, commits: 1 }, + { date: "2013-05-08", additions: 54, deletions: 7, commits: 3 } + ]; + ContributorsStatGraphUtil.pick_field(fake_parsed_log_total, "commits"); + var correct_pick_field_data = [{ date: "2013-05-09", commits: 1 }, { date: "2013-05-08", commits: 3 }]; + expect(ContributorsStatGraphUtil.pick_field(fake_parsed_log_total, "commits")).toEqual(correct_pick_field_data); + }); + }); describe("#get_author_data", function () { it("returns the log by author sorted by specified field", function () { var fake_parsed_log = { total: [ - {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}, - {date: "2013-05-08", additions: 54, deletions: 7, commits: 3} + { date: "2013-05-09", additions: 471, deletions: 0, commits: 1 }, + { date: "2013-05-08", additions: 54, deletions: 7, commits: 3 } ], by_author: [ { author_name: "Karlo Soriano", author_email: "karlo@email.com", - "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1} + "2013-05-09": { date: "2013-05-09", additions: 471, deletions: 0, commits: 1 } }, { author_name: "Dmitriy Zaporozhets", author_email: "dzaporozhets@email.com", - "2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3} + "2013-05-08": { date: "2013-05-08", additions: 54, deletions: 7, commits: 3 } } ] - } + }; var correct_author_data = [ - {author_name:"Dmitriy Zaporozhets",author_email:"dzaporozhets@email.com",dates:{"2013-05-08":3},deletions:7,additions:54,"commits":3}, - {author_name:"Karlo Soriano",author_email:"karlo@email.com",dates:{"2013-05-09":1},deletions:0,additions:471,commits:1} - ] - expect(ContributorsStatGraphUtil.get_author_data(fake_parsed_log, "commits")).toEqual(correct_author_data) - }) - }) + { author_name: "Dmitriy Zaporozhets", author_email: "dzaporozhets@email.com", dates: { "2013-05-08": 3 }, deletions: 7, additions: 54, "commits": 3 }, + { author_name: "Karlo Soriano", author_email: "karlo@email.com", dates: { "2013-05-09": 1 }, deletions: 0, additions: 471, commits: 1 } + ]; + expect(ContributorsStatGraphUtil.get_author_data(fake_parsed_log, "commits")).toEqual(correct_author_data); + }); + }); describe("#parse_log_entry", function () { it("adds the corresponding info from the log entry to the author", function () { - var fake_log_entry = { author_name: "Karlo Soriano", author_email: "karlo@email.com", - "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1} - } - var correct_parsed_log = {author_name:"Karlo Soriano",author_email:"karlo@email.com",dates:{"2013-05-09":1},deletions:0,additions:471,commits:1} - expect(ContributorsStatGraphUtil.parse_log_entry(fake_log_entry, 'commits', null)).toEqual(correct_parsed_log) - }) - }) + var fake_log_entry = { author_name: "Karlo Soriano", author_email: "karlo@email.com", + "2013-05-09": { date: "2013-05-09", additions: 471, deletions: 0, commits: 1 } + }; + var correct_parsed_log = { author_name: "Karlo Soriano", author_email: "karlo@email.com", dates: { "2013-05-09": 1 }, deletions: 0, additions: 471, commits: 1 }; + expect(ContributorsStatGraphUtil.parse_log_entry(fake_log_entry, 'commits', null)).toEqual(correct_parsed_log); + }); + }); describe("#in_range", function () { - var date = "2013-05-09" + var date = "2013-05-09"; it("returns true if date_range is null", function () { - expect(ContributorsStatGraphUtil.in_range(date, null)).toEqual(true) - }) + expect(ContributorsStatGraphUtil.in_range(date, null)).toEqual(true); + }); it("returns true if date is in range", function () { - var date_range = [new Date("2013-01-01"), new Date("2013-12-12")] - expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(true) - }) + var date_range = [new Date("2013-01-01"), new Date("2013-12-12")]; + expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(true); + }); it("returns false if date is not in range", function () { - var date_range = [new Date("1999-12-01"), new Date("2000-12-01")] - expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(false) - }) - }) - - -}) + var date_range = [new Date("1999-12-01"), new Date("2000-12-01")]; + expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(false); + }); + }); +}); diff --git a/spec/javascripts/graphs/stat_graph_spec.js b/spec/javascripts/graphs/stat_graph_spec.js index 5b3b7c9222a..876c23361bc 100644 --- a/spec/javascripts/graphs/stat_graph_spec.js +++ b/spec/javascripts/graphs/stat_graph_spec.js @@ -1,10 +1,9 @@ -/* eslint-disable quotes, padded-blocks, semi */ +/* eslint-disable quotes */ /* global StatGraph */ require('~/graphs/stat_graph'); describe("StatGraph", function () { - describe("#get_log", function () { it("returns log", function () { StatGraph.log = "test"; @@ -16,7 +15,6 @@ describe("StatGraph", function () { it("sets the log", function () { StatGraph.set_log("test"); expect(StatGraph.log).toBe("test"); - }) - }) - + }); + }); }); diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js index a281502b6ba..cecebb0b038 100644 --- a/spec/javascripts/header_spec.js +++ b/spec/javascripts/header_spec.js @@ -1,10 +1,9 @@ -/* eslint-disable space-before-function-paren, padded-blocks, no-var */ +/* eslint-disable space-before-function-paren, no-var */ require('~/header'); require('~/lib/utils/text_utility'); (function() { - describe('Header', function() { var todosPendingCount = '.todos-pending-count'; var fixtureTemplate = 'static/header.html.raw'; @@ -51,5 +50,4 @@ require('~/lib/utils/text_utility'); }); }); }); - }).call(this); diff --git a/spec/javascripts/helpers/class_spec_helper.js.es6 b/spec/javascripts/helpers/class_spec_helper.js.es6 new file mode 100644 index 00000000000..d3c37d39431 --- /dev/null +++ b/spec/javascripts/helpers/class_spec_helper.js.es6 @@ -0,0 +1,9 @@ +class ClassSpecHelper { + static itShouldBeAStaticMethod(base, method) { + return it('should be a static method', () => { + expect(Object.prototype.hasOwnProperty.call(base, method)).toBeTruthy(); + }); + } +} + +window.ClassSpecHelper = ClassSpecHelper; diff --git a/spec/javascripts/helpers/class_spec_helper_spec.js.es6 b/spec/javascripts/helpers/class_spec_helper_spec.js.es6 new file mode 100644 index 00000000000..0a61e561640 --- /dev/null +++ b/spec/javascripts/helpers/class_spec_helper_spec.js.es6 @@ -0,0 +1,36 @@ +/* global ClassSpecHelper */ + +require('./class_spec_helper'); + +describe('ClassSpecHelper', () => { + describe('.itShouldBeAStaticMethod', function () { + beforeEach(() => { + class TestClass { + instanceMethod() { this.prop = 'val'; } + static staticMethod() {} + } + + this.TestClass = TestClass; + }); + + ClassSpecHelper.itShouldBeAStaticMethod(ClassSpecHelper, 'itShouldBeAStaticMethod'); + + it('should have a defined spec', () => { + expect(ClassSpecHelper.itShouldBeAStaticMethod(this.TestClass, 'staticMethod').description).toBe('should be a static method'); + }); + + it('should pass for a static method', () => { + const spec = ClassSpecHelper.itShouldBeAStaticMethod(this.TestClass, 'staticMethod'); + expect(spec.status()).toBe('passed'); + }); + + it('should fail for an instance method', (done) => { + const spec = ClassSpecHelper.itShouldBeAStaticMethod(this.TestClass, 'instanceMethod'); + spec.resultCallback = (result) => { + expect(result.status).toBe('failed'); + done(); + }; + spec.execute(); + }); + }); +}); diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index d1d6d5e22cb..5b0b7aa7903 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-use-before-define, indent, no-trailing-spaces, comma-dangle, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */ /* global Issue */ require('~/lib/utils/text_utility'); @@ -42,21 +42,21 @@ require('~/issue'); } function findElements() { - $boxClosed = $('div.status-box-closed'); - expect($boxClosed).toExist(); - expect($boxClosed).toHaveText('Closed'); + $boxClosed = $('div.status-box-closed'); + expect($boxClosed).toExist(); + expect($boxClosed).toHaveText('Closed'); - $boxOpen = $('div.status-box-open'); - expect($boxOpen).toExist(); - expect($boxOpen).toHaveText('Open'); + $boxOpen = $('div.status-box-open'); + expect($boxOpen).toExist(); + expect($boxOpen).toHaveText('Open'); - $btnClose = $('.btn-close.btn-grouped'); - expect($btnClose).toExist(); - expect($btnClose).toHaveText('Close issue'); + $btnClose = $('.btn-close.btn-grouped'); + expect($btnClose).toExist(); + expect($btnClose).toHaveText('Close issue'); - $btnReopen = $('.btn-reopen.btn-grouped'); - expect($btnReopen).toExist(); - expect($btnReopen).toHaveText('Reopen issue'); + $btnReopen = $('.btn-reopen.btn-grouped'); + expect($btnReopen).toExist(); + expect($btnReopen).toHaveText('Reopen issue'); } describe('Issue', function() { @@ -161,5 +161,4 @@ require('~/issue'); expect($btnReopen).toHaveProp('disabled', false); }); }); - }).call(this); diff --git a/spec/javascripts/labels_issue_sidebar_spec.js.es6 b/spec/javascripts/labels_issue_sidebar_spec.js.es6 index 61ccef42cd8..37e038c16da 100644 --- a/spec/javascripts/labels_issue_sidebar_spec.js.es6 +++ b/spec/javascripts/labels_issue_sidebar_spec.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable no-new, no-plusplus, object-curly-spacing, prefer-const, semi */ +/* eslint-disable no-new */ /* global IssuableContext */ /* global LabelsSelect */ @@ -24,18 +24,18 @@ require('~/labels_select'); spyOn(jQuery, 'ajax').and.callFake((req) => { const d = $.Deferred(); - let LABELS_DATA = [] + let LABELS_DATA = []; if (req.url === '/root/test/labels.json') { - for (let i = 0; i < 10; i++) { - LABELS_DATA.push({id: i, title: `test ${i}`, color: '#5CB85C'}); + for (let i = 0; i < 10; i += 1) { + LABELS_DATA.push({ id: i, title: `test ${i}`, color: '#5CB85C' }); } } else if (req.url === '/root/test/issues/2.json') { - let tmp = [] - for (let i = 0; i < saveLabelCount; i++) { - tmp.push({id: i, title: `test ${i}`, color: '#5CB85C'}); + const tmp = []; + for (let i = 0; i < saveLabelCount; i += 1) { + tmp.push({ id: i, title: `test ${i}`, color: '#5CB85C' }); } - LABELS_DATA = {labels: tmp}; + LABELS_DATA = { labels: tmp }; } d.resolve(LABELS_DATA); diff --git a/spec/javascripts/lib/utils/common_utils_spec.js.es6 b/spec/javascripts/lib/utils/common_utils_spec.js.es6 index 9e8456c03aa..fbb06f3948b 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js.es6 +++ b/spec/javascripts/lib/utils/common_utils_spec.js.es6 @@ -56,5 +56,22 @@ require('~/lib/utils/common_utils'); expect(value).toBe(null); }); }); + + describe('gl.utils.normalizedHeaders', () => { + it('should upperCase all the header keys to keep them consistent', () => { + const apiHeaders = { + 'X-Something-Workhorse': { workhorse: 'ok' }, + 'x-something-nginx': { nginx: 'ok' }, + }; + + const normalized = gl.utils.normalizeHeaders(apiHeaders); + + const WORKHORSE = 'X-SOMETHING-WORKHORSE'; + const NGINX = 'X-SOMETHING-NGINX'; + + expect(normalized[WORKHORSE].workhorse).toBe('ok'); + expect(normalized[NGINX].nginx).toBe('ok'); + }); + }); }); })(); diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js index be80e06af53..8b196f7720f 100644 --- a/spec/javascripts/line_highlighter_spec.js +++ b/spec/javascripts/line_highlighter_spec.js @@ -1,4 +1,4 @@ -/* 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, no-plusplus, jasmine/no-spec-dupes, no-underscore-dangle, padded-blocks, max-len */ +/* 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 */ require('~/line_highlighter'); @@ -33,11 +33,11 @@ require('~/line_highlighter'); return expect($('#LC13')).toHaveClass(this.css); }); it('highlights a range of lines given in the URL hash', function() { - var i, line, results; + var line, results; new LineHighlighter('#L5-25'); expect($("." + this.css).length).toBe(21); results = []; - for (line = i = 5; i <= 25; line = ++i) { + for (line = 5; line <= 25; line += 1) { results.push(expect($("#LC" + line)).toHaveClass(this.css)); } return results; @@ -124,27 +124,27 @@ require('~/line_highlighter'); }); describe('with existing single-line highlight', function() { it('uses existing line as last line when target is lesser', function() { - var i, line, results; + var line, results; clickLine(20); clickLine(15, { shiftKey: true }); expect($("." + this.css).length).toBe(6); results = []; - for (line = i = 15; i <= 20; line = ++i) { + for (line = 15; line <= 20; line += 1) { results.push(expect($("#LC" + line)).toHaveClass(this.css)); } return results; }); return it('uses existing line as first line when target is greater', function() { - var i, line, results; + var line, results; clickLine(5); clickLine(10, { shiftKey: true }); expect($("." + this.css).length).toBe(6); results = []; - for (line = i = 5; i <= 10; line = ++i) { + for (line = 5; line <= 10; line += 1) { results.push(expect($("#LC" + line)).toHaveClass(this.css)); } return results; @@ -160,25 +160,25 @@ require('~/line_highlighter'); }); }); it('uses target as first line when it is less than existing first line', function() { - var i, line, results; + var line, results; clickLine(5, { shiftKey: true }); expect($("." + this.css).length).toBe(6); results = []; - for (line = i = 5; i <= 10; line = ++i) { + for (line = 5; line <= 10; line += 1) { results.push(expect($("#LC" + line)).toHaveClass(this.css)); } return results; }); return it('uses target as last line when it is greater than existing first line', function() { - var i, line, results; + var line, results; clickLine(15, { shiftKey: true }); expect($("." + this.css).length).toBe(6); results = []; - for (line = i = 10; i <= 15; line = ++i) { + for (line = 10; line <= 15; line += 1) { results.push(expect($("#LC" + line)).toHaveClass(this.css)); } return results; @@ -227,5 +227,4 @@ require('~/line_highlighter'); }); }); }); - }).call(this); diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js index f87e87f4204..25cfa9e9479 100644 --- a/spec/javascripts/merge_request_spec.js +++ b/spec/javascripts/merge_request_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, no-return-assign, padded-blocks */ +/* eslint-disable space-before-function-paren, no-return-assign */ /* global MergeRequest */ require('~/merge_request'); @@ -26,5 +26,4 @@ require('~/merge_request'); }); }); }); - }).call(this); diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index 377acd5a3aa..5b52b0036a9 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -8,7 +8,12 @@ require('vendor/jquery.scrollTo'); (function () { // TODO: remove this hack! // PhantomJS causes spyOn to panic because replaceState isn't "writable" - const phantomjs = !Object.getOwnPropertyDescriptor(window.history, 'replaceState').writable; + var phantomjs; + try { + phantomjs = !Object.getOwnPropertyDescriptor(window.history, 'replaceState').writable; + } catch (err) { + phantomjs = false; + } describe('MergeRequestTabs', function () { var stubLocation = {}; diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js index c09cea28696..001850a4b9c 100644 --- a/spec/javascripts/merge_request_widget_spec.js +++ b/spec/javascripts/merge_request_widget_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, quotes, comma-dangle, dot-notation, indent, quote-props, no-var, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, quotes, comma-dangle, dot-notation, quote-props, no-var, max-len */ require('~/merge_request_widget'); require('~/lib/utils/datetime_utility'); @@ -42,17 +42,17 @@ require('~/lib/utils/datetime_utility'); }); it('should call renderEnvironments when the environments property is set', function() { - const spy = spyOn(this.class, 'renderEnvironments').and.stub(); - this.class.getCIEnvironmentsStatus(); - expect(spy).toHaveBeenCalledWith(this.ciEnvironmentsStatusData); - }); - - it('should not call renderEnvironments when the environments property is not set', function() { - this.ciEnvironmentsStatusData = null; - const spy = spyOn(this.class, 'renderEnvironments').and.stub(); - this.class.getCIEnvironmentsStatus(); - expect(spy).not.toHaveBeenCalled(); - }); + const spy = spyOn(this.class, 'renderEnvironments').and.stub(); + this.class.getCIEnvironmentsStatus(); + expect(spy).toHaveBeenCalledWith(this.ciEnvironmentsStatusData); + }); + + it('should not call renderEnvironments when the environments property is not set', function() { + this.ciEnvironmentsStatusData = null; + const spy = spyOn(this.class, 'renderEnvironments').and.stub(); + this.class.getCIEnvironmentsStatus(); + expect(spy).not.toHaveBeenCalled(); + }); }); describe('renderEnvironments', function() { @@ -107,16 +107,16 @@ require('~/lib/utils/datetime_utility'); }); describe('mergeInProgress', function() { - it('should display error with h4 tag', function() { - spyOn(this.class.$widgetBody, 'html').and.callFake(function(html) { - expect(html).toBe('<h4>Sorry, something went wrong.</h4>'); - }); - spyOn($, 'ajax').and.callFake(function(e) { - e.success({ merge_error: 'Sorry, something went wrong.' }); - }); - this.class.mergeInProgress(null); + it('should display error with h4 tag', function() { + spyOn(this.class.$widgetBody, 'html').and.callFake(function(html) { + expect(html).toBe('<h4>Sorry, something went wrong.</h4>'); + }); + spyOn($, 'ajax').and.callFake(function(e) { + e.success({ merge_error: 'Sorry, something went wrong.' }); }); + this.class.mergeInProgress(null); }); + }); return describe('getCIStatus', function() { beforeEach(function() { @@ -167,5 +167,4 @@ require('~/lib/utils/datetime_utility'); }); }); }); - }).call(this); diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js index 0fdfa4d037b..9b657868523 100644 --- a/spec/javascripts/new_branch_spec.js +++ b/spec/javascripts/new_branch_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, quotes, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, quotes, max-len */ /* global NewBranchForm */ require('jquery-ui/ui/autocomplete'); @@ -166,5 +166,4 @@ require('~/new_branch_form'); }); }); }); - }).call(this); diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 295b44a3e74..af495787c54 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, semi, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, max-len */ /* global Notes */ require('~/notes'); @@ -72,8 +72,7 @@ require('~/lib/utils/text_utility'); $('.js-comment-button').click(); expect(this.autoSizeSpy).toHaveBeenTriggered(); - }) + }); }); }); - }).call(this); diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js index fa59a937c8e..e0b52f767e4 100644 --- a/spec/javascripts/project_title_spec.js +++ b/spec/javascripts/project_title_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, no-unused-expressions, no-return-assign, no-param-reassign, no-var, new-cap, wrap-iife, no-unused-vars, quotes, jasmine/no-expect-in-setup-teardown, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-unused-expressions, no-return-assign, no-param-reassign, no-var, new-cap, wrap-iife, no-unused-vars, quotes, jasmine/no-expect-in-setup-teardown, max-len */ /* global Project */ require('select2/select2.js'); @@ -49,5 +49,4 @@ require('~/project'); }); }); }); - }).call(this); diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index 3b00f15795c..f7636865aa1 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -1,4 +1,4 @@ -/* 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, semi, padded-blocks, max-len */ +/* 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 */ require('~/right_sidebar'); @@ -77,7 +77,6 @@ require('~/extensions/jquery.js'); $('.js-issuable-todo').click(); expect(todoToggleSpy.calls.count()).toEqual(1); - }) + }); }); - }).call(this); diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index 3be8f88f87b..05f96ef5802 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, max-len, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, comma-dangle, object-shorthand, prefer-template, quotes, new-parens, vars-on-top, new-cap, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, max-len, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, comma-dangle, object-shorthand, prefer-template, quotes, new-parens, vars-on-top, new-cap, max-len */ require('~/gl_dropdown'); require('~/search_autocomplete'); @@ -170,5 +170,4 @@ require('vendor/fuzzaldrin-plus'); expect(enterKeyEvent.isDefaultPrevented()).toBe(true); }); }); - }).call(this); diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js index 3f7f6cf0113..331f36ccc44 100644 --- a/spec/javascripts/shortcuts_issuable_spec.js +++ b/spec/javascripts/shortcuts_issuable_spec.js @@ -1,6 +1,7 @@ -/* eslint-disable space-before-function-paren, no-return-assign, no-var, quotes, padded-blocks */ +/* eslint-disable space-before-function-paren, no-return-assign, no-var, quotes */ /* global ShortcutsIssuable */ +require('~/copy_as_gfm'); require('~/shortcuts_issuable'); (function() { @@ -14,10 +15,12 @@ require('~/shortcuts_issuable'); }); return describe('#replyWithSelectedText', function() { var stubSelection; - // Stub window.getSelection to return the provided String. - stubSelection = function(text) { - return window.getSelection = function() { - return text; + // Stub window.gl.utils.getSelectedFragment to return a node with the provided HTML. + stubSelection = function(html) { + window.gl.utils.getSelectedFragment = function() { + var node = document.createElement('div'); + node.innerHTML = html; + return node; }; }; beforeEach(function() { @@ -32,13 +35,13 @@ require('~/shortcuts_issuable'); }); describe('with any selection', function() { beforeEach(function() { - return stubSelection('Selected text.'); + return stubSelection('<p>Selected text.</p>'); }); it('leaves existing input intact', function() { $(this.selector).val('This text was already here.'); expect($(this.selector).val()).toBe('This text was already here.'); this.shortcut.replyWithSelectedText(); - return expect($(this.selector).val()).toBe("This text was already here.\n> Selected text.\n\n"); + return expect($(this.selector).val()).toBe("This text was already here.\n\n> Selected text.\n\n"); }); it('triggers `input`', function() { var triggered; @@ -56,19 +59,18 @@ require('~/shortcuts_issuable'); }); describe('with a one-line selection', function() { return it('quotes the selection', function() { - stubSelection('This text has been selected.'); + stubSelection('<p>This text has been selected.</p>'); this.shortcut.replyWithSelectedText(); return expect($(this.selector).val()).toBe("> This text has been selected.\n\n"); }); }); return describe('with a multi-line selection', function() { return it('quotes the selected lines as a group', function() { - stubSelection("Selected line one.\n\nSelected line two.\nSelected line three.\n"); + stubSelection("<p>Selected line one.</p>\n\n<p>Selected line two.</p>\n\n<p>Selected line three.</p>"); this.shortcut.replyWithSelectedText(); - return expect($(this.selector).val()).toBe("> Selected line one.\n> Selected line two.\n> Selected line three.\n\n"); + return expect($(this.selector).val()).toBe("> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n"); }); }); }); }); - }).call(this); diff --git a/spec/javascripts/subbable_resource_spec.js.es6 b/spec/javascripts/subbable_resource_spec.js.es6 index ef1b32c2d19..454386697f5 100644 --- a/spec/javascripts/subbable_resource_spec.js.es6 +++ b/spec/javascripts/subbable_resource_spec.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable max-len, arrow-parens, comma-dangle, no-plusplus */ +/* eslint-disable max-len, arrow-parens, comma-dangle */ require('~/subbable_resource'); @@ -50,7 +50,7 @@ require('~/subbable_resource'); this.MockResource.subscribe(callbacks.two); this.MockResource.subscribe(callbacks.three); - state.myprop++; + state.myprop += 1; this.MockResource.publish(state); diff --git a/spec/javascripts/syntax_highlight_spec.js b/spec/javascripts/syntax_highlight_spec.js index 6c953f1b71c..c0c3837d1f4 100644 --- a/spec/javascripts/syntax_highlight_spec.js +++ b/spec/javascripts/syntax_highlight_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, no-var, no-return-assign, quotes, padded-blocks */ +/* eslint-disable space-before-function-paren, no-var, no-return-assign, quotes */ require('~/syntax_highlight'); @@ -41,5 +41,4 @@ require('~/syntax_highlight'); }); }); }); - }).call(this); diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js index ddbc1455057..cba1af4daa4 100644 --- a/spec/javascripts/u2f/authenticate_spec.js +++ b/spec/javascripts/u2f/authenticate_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, new-parens, quotes, comma-dangle, no-var, one-var, one-var-declaration-per-line, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, new-parens, quotes, comma-dangle, no-var, one-var, one-var-declaration-per-line, max-len */ /* global MockU2FDevice */ /* global U2FAuthenticate */ @@ -69,5 +69,4 @@ require('./mock_u2f_device'); }); }); }); - }).call(this); diff --git a/spec/javascripts/u2f/mock_u2f_device.js b/spec/javascripts/u2f/mock_u2f_device.js index 1459f968c3d..287bfb4138b 100644 --- a/spec/javascripts/u2f/mock_u2f_device.js +++ b/spec/javascripts/u2f/mock_u2f_device.js @@ -1,6 +1,7 @@ -/* eslint-disable space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-unused-expressions, no-return-assign, no-param-reassign, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-expressions, no-return-assign, no-param-reassign, max-len */ + (function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.MockU2FDevice = (function() { function MockU2FDevice() { @@ -28,7 +29,5 @@ }; return MockU2FDevice; - })(); - }).call(this); diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js index 50522ff2391..10578c2c4b5 100644 --- a/spec/javascripts/u2f/register_spec.js +++ b/spec/javascripts/u2f/register_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, new-parens, quotes, no-var, one-var, one-var-declaration-per-line, comma-dangle, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, new-parens, quotes, no-var, one-var, one-var-declaration-per-line, comma-dangle, max-len */ /* global MockU2FDevice */ /* global U2FRegister */ @@ -74,5 +74,4 @@ require('./mock_u2f_device'); }); }); }); - }).call(this); diff --git a/spec/javascripts/visibility_select_spec.js.es6 b/spec/javascripts/visibility_select_spec.js.es6 new file mode 100644 index 00000000000..9727c03c91e --- /dev/null +++ b/spec/javascripts/visibility_select_spec.js.es6 @@ -0,0 +1,100 @@ +require('~/visibility_select'); + +(() => { + const VisibilitySelect = gl.VisibilitySelect; + + describe('VisibilitySelect', function () { + const lockedElement = document.createElement('div'); + lockedElement.dataset.helpBlock = 'lockedHelpBlock'; + + const checkedElement = document.createElement('div'); + checkedElement.dataset.description = 'checkedDescription'; + + const mockElements = { + container: document.createElement('div'), + select: document.createElement('div'), + '.help-block': document.createElement('div'), + '.js-locked': lockedElement, + 'option:checked': checkedElement, + }; + + beforeEach(function () { + spyOn(Element.prototype, 'querySelector').and.callFake(selector => mockElements[selector]); + }); + + describe('#constructor', function () { + beforeEach(function () { + this.visibilitySelect = new VisibilitySelect(mockElements.container); + }); + + it('sets the container member', function () { + expect(this.visibilitySelect.container).toEqual(mockElements.container); + }); + + it('queries and sets the helpBlock member', function () { + expect(Element.prototype.querySelector).toHaveBeenCalledWith('.help-block'); + expect(this.visibilitySelect.helpBlock).toEqual(mockElements['.help-block']); + }); + + it('queries and sets the select member', function () { + expect(Element.prototype.querySelector).toHaveBeenCalledWith('select'); + expect(this.visibilitySelect.select).toEqual(mockElements.select); + }); + + describe('if there is no container element provided', function () { + it('throws an error', function () { + expect(() => new VisibilitySelect()).toThrowError('VisibilitySelect requires a container element as argument 1'); + }); + }); + }); + + describe('#init', function () { + describe('if there is a select', function () { + beforeEach(function () { + this.visibilitySelect = new VisibilitySelect(mockElements.container); + }); + + it('calls updateHelpText', function () { + spyOn(VisibilitySelect.prototype, 'updateHelpText'); + this.visibilitySelect.init(); + expect(this.visibilitySelect.updateHelpText).toHaveBeenCalled(); + }); + + it('adds a change event listener', function () { + spyOn(this.visibilitySelect.select, 'addEventListener'); + this.visibilitySelect.init(); + expect(this.visibilitySelect.select.addEventListener.calls.argsFor(0)).toContain('change'); + }); + }); + + describe('if there is no select', function () { + beforeEach(function () { + mockElements.select = undefined; + this.visibilitySelect = new VisibilitySelect(mockElements.container); + this.visibilitySelect.init(); + }); + + it('updates the helpBlock text to the locked `data-help-block` messaged', function () { + expect(this.visibilitySelect.helpBlock.textContent) + .toEqual(lockedElement.dataset.helpBlock); + }); + + afterEach(function () { + mockElements.select = document.createElement('div'); + }); + }); + }); + + describe('#updateHelpText', function () { + beforeEach(function () { + this.visibilitySelect = new VisibilitySelect(mockElements.container); + this.visibilitySelect.init(); + }); + + it('updates the helpBlock text to the selected options `data-description`', function () { + expect(this.visibilitySelect.helpBlock.textContent) + .toEqual(checkedElement.dataset.description); + }); + }); + }); +})(); diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js index 7a68356376f..ce33a6814aa 100644 --- a/spec/javascripts/zen_mode_spec.js +++ b/spec/javascripts/zen_mode_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-return-assign, new-cap, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-return-assign, new-cap, max-len */ /* global Dropzone */ /* global Mousetrap */ /* global ZenMode */ @@ -76,5 +76,4 @@ require('~/zen_mode'); keyCode: 27 })); }; - }).call(this); diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb index 9703e2315b8..deadc36524c 100644 --- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do include FilterSpecHelper - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:commit1) { project.commit("HEAD~2") } let(:commit2) { project.commit } @@ -99,7 +99,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do end context 'cross-project / cross-namespace complete reference' do - let(:project2) { create(:project, :public) } + let(:project2) { create(:project, :public, :repository) } let(:reference) { "#{project2.path_with_namespace}@#{commit1.id}...#{commit2.id}" } it 'links to a valid reference' do @@ -133,8 +133,8 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do context 'cross-project / same-namespace complete reference' do let(:namespace) { create(:namespace) } - let(:project) { create(:project, :public, namespace: namespace) } - let(:project2) { create(:project, :public, path: "same-namespace", namespace: namespace) } + let(:project) { create(:project, :public, :repository, namespace: namespace) } + let(:project2) { create(:project, :public, :repository, path: "same-namespace", namespace: namespace) } let(:reference) { "#{project2.path}@#{commit1.id}...#{commit2.id}" } it 'links to a valid reference' do @@ -168,8 +168,8 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do context 'cross-project shorthand reference' do let(:namespace) { create(:namespace) } - let(:project) { create(:project, :public, namespace: namespace) } - let(:project2) { create(:project, :public, path: "same-namespace", namespace: namespace) } + let(:project) { create(:project, :public, :repository, namespace: namespace) } + let(:project2) { create(:project, :public, :repository, path: "same-namespace", namespace: namespace) } let(:reference) { "#{project2.path}@#{commit1.id}...#{commit2.id}" } it 'links to a valid reference' do @@ -203,7 +203,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do context 'cross-project URL reference' do let(:namespace) { create(:namespace) } - let(:project2) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, :repository, namespace: namespace) } let(:range) { CommitRange.new("#{commit1.id}...master", project) } let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') } diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb index 2e6dcc3a434..a19aac61229 100644 --- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Filter::CommitReferenceFilter, lib: true do include FilterSpecHelper - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:commit) { project.commit } it 'requires project context' do @@ -96,7 +96,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do context 'cross-project / cross-namespace complete reference' do let(:namespace) { create(:namespace) } - let(:project2) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, :repository, namespace: namespace) } let(:commit) { project2.commit } let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" } @@ -122,7 +122,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do context 'cross-project / same-namespace complete reference' do let(:namespace) { create(:namespace) } let(:project) { create(:empty_project, namespace: namespace) } - let(:project2) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, :repository, namespace: namespace) } let(:commit) { project2.commit } let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" } @@ -148,7 +148,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do context 'cross-project shorthand reference' do let(:namespace) { create(:namespace) } let(:project) { create(:empty_project, namespace: namespace) } - let(:project2) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, :repository, namespace: namespace) } let(:commit) { project2.commit } let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" } @@ -173,7 +173,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do context 'cross-project URL reference' do let(:namespace) { create(:namespace) } - let(:project2) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, :repository, namespace: namespace) } let(:commit) { project2.commit } let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) } diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb index fe2ce092e6b..082c0d4dd0d 100644 --- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb +++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Filter::GollumTagsFilter, lib: true do include FilterSpecHelper - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:user) { double } let(:project_wiki) { ProjectWiki.new(project, user) } diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb index 275010c1a2c..3d3d36061f4 100644 --- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb @@ -188,7 +188,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do context 'cross-project URL reference' do let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:empty_project, :public, namespace: namespace) } let(:merge) { create(:merge_request, source_project: project2, target_project: project2) } let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, project2, merge) + '/diffs#note_123' } diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index 73b5edb99b3..a317c751d32 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Filter::MilestoneReferenceFilter, lib: true do include FilterSpecHelper - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:milestone) { create(:milestone, project: project) } let(:reference) { milestone.to_reference } diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb index df2dd173b57..1957ba739e2 100644 --- a/spec/lib/banzai/filter/relative_link_filter_spec.rb +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -25,7 +25,7 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do %(<a href="#{path}">#{path}</a>) end - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:project_path) { project.path_with_namespace } let(:ref) { 'markdown' } let(:commit) { project.commit(ref) } diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb index d265d29ee86..69e3c52b35a 100644 --- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb +++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb @@ -6,21 +6,21 @@ describe Banzai::Filter::SyntaxHighlightFilter, lib: true do context "when no language is specified" do it "highlights as plaintext" do result = filter('<pre><code>def fun end</code></pre>') - expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" v-pre="true"><code>def fun end</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code>def fun end</code></pre>') end end context "when a valid language is specified" do it "highlights as that language" do result = filter('<pre><code class="ruby">def fun end</code></pre>') - expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby" v-pre="true"><code><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby" lang="ruby" v-pre="true"><code><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></code></pre>') end end context "when an invalid language is specified" do it "highlights as plaintext" do result = filter('<pre><code class="gnuplot">This is a test</code></pre>') - expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" v-pre="true"><code>This is a test</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code>This is a test</code></pre>') end end @@ -31,7 +31,7 @@ describe Banzai::Filter::SyntaxHighlightFilter, lib: true do it "highlights as plaintext" do result = filter('<pre><code class="ruby">This is a test</code></pre>') - expect(result.to_html).to eq('<pre class="code highlight" v-pre="true"><code>This is a test</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight" lang="" v-pre="true"><code>This is a test</code></pre>') end end end diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb index 8b76c1d73c9..639cac6406a 100644 --- a/spec/lib/banzai/filter/upload_link_filter_spec.rb +++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb @@ -29,7 +29,7 @@ describe Banzai::Filter::UploadLinkFilter, lib: true do %(<div><a href="#{path}">#{path}</a></div>) end - let(:project) { create(:project) } + let(:project) { create(:empty_project) } shared_examples :preserve_unchanged do it 'does not modify any relative URL in anchor' do diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index 5bfeb82e738..3e1ac9fb2b2 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -152,6 +152,30 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do end end + context 'when a project is not specified' do + let(:project) { nil } + + it 'does not link a User' do + doc = reference_filter("Hey #{reference}") + + expect(doc).not_to include('a') + end + + context 'when skip_project_check set to true' do + it 'links to a User' do + doc = reference_filter("Hey #{reference}", skip_project_check: true) + + expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) + end + + it 'does not link users using @all reference' do + doc = reference_filter("Hey #{User.reference_prefix}all", skip_project_check: true) + + expect(doc).not_to include('a') + end + end + end + describe '#namespaces' do it 'returns a Hash containing all Namespaces' do document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>") diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb index 6ab1be9ccb7..00494f545a3 100644 --- a/spec/lib/banzai/filter/video_link_filter_spec.rb +++ b/spec/lib/banzai/filter/video_link_filter_spec.rb @@ -13,7 +13,7 @@ describe Banzai::Filter::VideoLinkFilter, lib: true do %(<img src="#{path}" />) end - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } context 'when the element src has a video extension' do UploaderHelper::VIDEO_EXT.each do |ext| diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb index fafc2cec546..31ca9d27b0b 100644 --- a/spec/lib/banzai/reference_parser/user_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb @@ -147,7 +147,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do describe '#nodes_user_can_reference' do context 'when the link has a data-author attribute' do it 'returns the nodes when the user is a member of the project' do - other_project = create(:project) + other_project = create(:empty_project) other_project.team << [user, :developer] link['data-project'] = other_project.id.to_s @@ -164,7 +164,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do end it 'returns an empty Array when the user could not be found' do - other_project = create(:project) + other_project = create(:empty_project) link['data-project'] = other_project.id.to_s link['data-author'] = '' @@ -173,7 +173,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do end it 'returns an empty Array when the user is not a team member' do - other_project = create(:project) + other_project = create(:empty_project) link['data-project'] = other_project.id.to_s link['data-author'] = user.id.to_s diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb index 94266f6653b..a5251e9a8c2 100644 --- a/spec/lib/constraints/project_url_constrainer_spec.rb +++ b/spec/lib/constraints/project_url_constrainer_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ProjectUrlConstrainer, lib: true do - let!(:project) { create(:project) } + let!(:project) { create(:empty_project) } let!(:namespace) { project.namespace } describe '#matches?' do diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb index ec2f66b1136..e3066311b7d 100644 --- a/spec/lib/event_filter_spec.rb +++ b/spec/lib/event_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe EventFilter, lib: true do describe '#apply_filter' do let(:source_user) { create(:user) } - let!(:public_project) { create(:project, :public) } + let!(:public_project) { create(:empty_project, :public) } let!(:push_event) { create(:event, action: Event::PUSHED, project: public_project, target: public_project, author: source_user) } let!(:merged_event) { create(:event, action: Event::MERGED, project: public_project, target: public_project, author: source_user) } diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index 0e85e302f29..29c07655ae8 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -24,7 +24,7 @@ describe ExtractsPath, lib: true do let(:params) { { path: sample_commit[:line_code_path], ref: ref } } before do - @project = create(:project) + @project = create(:project, :repository) end it "log tree path has no escape sequences" do diff --git a/spec/lib/gitlab/badge/build/metadata_spec.rb b/spec/lib/gitlab/badge/build/metadata_spec.rb index d678e522721..9df96ea04eb 100644 --- a/spec/lib/gitlab/badge/build/metadata_spec.rb +++ b/spec/lib/gitlab/badge/build/metadata_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' require 'lib/gitlab/badge/shared/metadata' describe Gitlab::Badge::Build::Metadata do - let(:badge) { double(project: create(:project), ref: 'feature') } + let(:badge) { double(project: create(:empty_project), ref: 'feature') } let(:metadata) { described_class.new(badge) } it_behaves_like 'badge metadata' diff --git a/spec/lib/gitlab/badge/build/status_spec.rb b/spec/lib/gitlab/badge/build/status_spec.rb index 70f03021d36..3c5414701a7 100644 --- a/spec/lib/gitlab/badge/build/status_spec.rb +++ b/spec/lib/gitlab/badge/build/status_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Badge::Build::Status do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:sha) { project.commit.sha } let(:branch) { 'master' } let(:badge) { described_class.new(project, branch) } diff --git a/spec/lib/gitlab/badge/coverage/metadata_spec.rb b/spec/lib/gitlab/badge/coverage/metadata_spec.rb index 74eaf7eaf8b..5e93935ea37 100644 --- a/spec/lib/gitlab/badge/coverage/metadata_spec.rb +++ b/spec/lib/gitlab/badge/coverage/metadata_spec.rb @@ -3,7 +3,7 @@ require 'lib/gitlab/badge/shared/metadata' describe Gitlab::Badge::Coverage::Metadata do let(:badge) do - double(project: create(:project), ref: 'feature', job: 'test') + double(project: create(:empty_project), ref: 'feature', job: 'test') end let(:metadata) { described_class.new(badge) } diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index 72b1ba36b58..0a2fe5af2c3 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -52,7 +52,7 @@ describe Gitlab::BitbucketImport::Importer, lib: true do let(:project) do create( - :project, + :empty_project, import_source: project_identifier, import_data: ProjectImportData.new(credentials: data) ) diff --git a/spec/lib/gitlab/blame_spec.rb b/spec/lib/gitlab/blame_spec.rb index 89245761b6f..26b1baf75be 100644 --- a/spec/lib/gitlab/blame_spec.rb +++ b/spec/lib/gitlab/blame_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Blame, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:path) { 'files/ruby/popen.rb' } let(:commit) { project.commit('master') } let(:blob) { project.repository.blob_at(commit.id, path) } diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb index a2d84977f58..1e81eaef18c 100644 --- a/spec/lib/gitlab/chat_commands/command_spec.rb +++ b/spec/lib/gitlab/chat_commands/command_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::ChatCommands::Command, service: true do context 'when no command is available' do let(:params) { { text: 'issue show 1' } } - let(:project) { create(:project, has_external_issue_tracker: true) } + let(:project) { create(:empty_project, has_external_issue_tracker: true) } it 'displays 404 messages' do expect(subject[:response_type]).to be(:ephemeral) diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb index 98effecdbbc..cadfbadca10 100644 --- a/spec/lib/gitlab/checks/change_access_spec.rb +++ b/spec/lib/gitlab/checks/change_access_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Checks::ChangeAccess, lib: true do describe '#exec' do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user_access) { Gitlab::UserAccess.new(user, project: project) } let(:changes) do { diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb index f6288011494..7a84bbebd02 100644 --- a/spec/lib/gitlab/checks/force_push_spec.rb +++ b/spec/lib/gitlab/checks/force_push_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Checks::ChangeAccess, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } context "exit code checking" do it "does not raise a runtime error if the `popen` call to git returns a zero exit code" do diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index dccb29b5ef6..0c40fca0c1a 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -3,15 +3,23 @@ require 'spec_helper' describe Gitlab::Ci::Status::Build::Factory do let(:user) { create(:user) } let(:project) { build.project } - - subject { described_class.new(build, user) } - let(:status) { subject.fabricate! } + let(:status) { factory.fabricate! } + let(:factory) { described_class.new(build, user) } before { project.team << [user, :developer] } context 'when build is successful' do let(:build) { create(:ci_build, :success) } + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Success + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Retryable] + end + it 'fabricates a retryable build status' do expect(status).to be_a Gitlab::Ci::Status::Build::Retryable end @@ -26,24 +34,72 @@ describe Gitlab::Ci::Status::Build::Factory do end context 'when build is failed' do - let(:build) { create(:ci_build, :failed) } + context 'when build is not allowed to fail' do + let(:build) { create(:ci_build, :failed) } - it 'fabricates a retryable build status' do - expect(status).to be_a Gitlab::Ci::Status::Build::Retryable + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Retryable] + end + + it 'fabricates a retryable build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Retryable + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'failed' + expect(status.icon).to eq 'icon_status_failed' + expect(status.label).to eq 'failed' + expect(status).to have_details + expect(status).to have_action + end end - it 'fabricates status with correct details' do - expect(status.text).to eq 'failed' - expect(status.icon).to eq 'icon_status_failed' - expect(status.label).to eq 'failed' - expect(status).to have_details - expect(status).to have_action + context 'when build is allowed to fail' do + let(:build) { create(:ci_build, :failed, :allowed_to_fail) } + + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Retryable, + Gitlab::Ci::Status::Build::FailedAllowed] + end + + it 'fabricates a failed but allowed build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::FailedAllowed + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'failed' + expect(status.icon).to eq 'icon_status_warning' + expect(status.label).to eq 'failed (allowed to fail)' + expect(status).to have_details + expect(status).to have_action + expect(status.action_title).to include 'Retry' + expect(status.action_path).to include 'retry' + end end end context 'when build is a canceled' do let(:build) { create(:ci_build, :canceled) } + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Canceled + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Retryable] + end + it 'fabricates a retryable build status' do expect(status).to be_a Gitlab::Ci::Status::Build::Retryable end @@ -60,6 +116,15 @@ describe Gitlab::Ci::Status::Build::Factory do context 'when build is running' do let(:build) { create(:ci_build, :running) } + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Running + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Cancelable] + end + it 'fabricates a canceable build status' do expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable end @@ -76,6 +141,15 @@ describe Gitlab::Ci::Status::Build::Factory do context 'when build is pending' do let(:build) { create(:ci_build, :pending) } + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Pending + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Cancelable] + end + it 'fabricates a cancelable build status' do expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable end @@ -92,6 +166,14 @@ describe Gitlab::Ci::Status::Build::Factory do context 'when build is skipped' do let(:build) { create(:ci_build, :skipped) } + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped + end + + it 'does not match extended statuses' do + expect(factory.extended_statuses).to be_empty + end + it 'fabricates a core skipped status' do expect(status).to be_a Gitlab::Ci::Status::Skipped end @@ -109,6 +191,15 @@ describe Gitlab::Ci::Status::Build::Factory do context 'when build is a play action' do let(:build) { create(:ci_build, :playable) } + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Play] + end + it 'fabricates a core skipped status' do expect(status).to be_a Gitlab::Ci::Status::Build::Play end @@ -119,12 +210,22 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.label).to eq 'manual play action' expect(status).to have_details expect(status).to have_action + expect(status.action_path).to include 'play' end end context 'when build is an environment stop action' do let(:build) { create(:ci_build, :playable, :teardown_environment) } + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Stop] + end + it 'fabricates a core skipped status' do expect(status).to be_a Gitlab::Ci::Status::Build::Stop end diff --git a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb new file mode 100644 index 00000000000..20f71459738 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb @@ -0,0 +1,110 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::FailedAllowed do + let(:status) { double('core status') } + let(:user) { double('user') } + + subject do + described_class.new(status) + end + + describe '#text' do + it 'does not override status text' do + expect(status).to receive(:text) + + subject.text + end + end + + describe '#icon' do + it 'returns a warning icon' do + expect(subject.icon).to eq 'icon_status_warning' + end + end + + describe '#label' do + it 'returns information about failed but allowed to fail status' do + expect(subject.label).to eq 'failed (allowed to fail)' + end + end + + describe '#group' do + it 'returns status failed with warnings status group' do + expect(subject.group).to eq 'failed_with_warnings' + end + end + + describe 'action details' do + describe '#has_action?' do + it 'does not decorate action details' do + expect(status).to receive(:has_action?) + + subject.has_action? + end + end + + describe '#action_path' do + it 'does not decorate action path' do + expect(status).to receive(:action_path) + + subject.action_path + end + end + + describe '#action_icon' do + it 'does not decorate action icon' do + expect(status).to receive(:action_icon) + + subject.action_icon + end + end + + describe '#action_title' do + it 'does not decorate action title' do + expect(status).to receive(:action_title) + + subject.action_title + end + end + end + + describe '.matches?' do + subject { described_class.matches?(build, user) } + + context 'when build is failed' do + context 'when build is allowed to fail' do + let(:build) { create(:ci_build, :failed, :allowed_to_fail) } + + it 'is a correct match' do + expect(subject).to be true + end + end + + context 'when build is not allowed to fail' do + let(:build) { create(:ci_build, :failed) } + + it 'is not a correct match' do + expect(subject).not_to be true + end + end + end + + context 'when build did not fail' do + context 'when build is allowed to fail' do + let(:build) { create(:ci_build, :success, :allowed_to_fail) } + + it 'is not a correct match' do + expect(subject).not_to be true + end + end + + context 'when build is not allowed to fail' do + let(:build) { create(:ci_build, :success) } + + it 'is not a correct match' do + expect(subject).not_to be true + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/status/external/common_spec.rb b/spec/lib/gitlab/ci/status/external/common_spec.rb new file mode 100644 index 00000000000..5a97d98b55f --- /dev/null +++ b/spec/lib/gitlab/ci/status/external/common_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::External::Common do + let(:user) { create(:user) } + let(:project) { external_status.project } + let(:external_target_url) { 'http://example.gitlab.com/status' } + + let(:external_status) do + create(:generic_commit_status, target_url: external_target_url) + end + + subject do + Gitlab::Ci::Status::Core + .new(external_status, user) + .extend(described_class) + end + + describe '#has_action?' do + it { is_expected.not_to have_action } + end + + describe '#has_details?' do + context 'when user has access to read commit status' do + before { project.team << [user, :developer] } + + it { is_expected.to have_details } + end + + context 'when user does not have access to read commit status' do + it { is_expected.not_to have_details } + end + end + + describe '#details_path' do + it 'links to the external target URL' do + expect(subject.details_path).to eq external_target_url + end + end +end diff --git a/spec/lib/gitlab/ci/status/external/factory_spec.rb b/spec/lib/gitlab/ci/status/external/factory_spec.rb new file mode 100644 index 00000000000..c96fd53e730 --- /dev/null +++ b/spec/lib/gitlab/ci/status/external/factory_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::External::Factory do + let(:user) { create(:user) } + let(:project) { resource.project } + let(:status) { factory.fabricate! } + let(:factory) { described_class.new(resource, user) } + let(:external_url) { 'http://gitlab.com/status' } + + before do + project.team << [user, :developer] + end + + context 'when external status has a simple core status' do + HasStatus::AVAILABLE_STATUSES.each do |simple_status| + context "when core status is #{simple_status}" do + let(:resource) do + create(:generic_commit_status, status: simple_status, + target_url: external_url) + end + + let(:expected_status) do + Gitlab::Ci::Status.const_get(simple_status.capitalize) + end + + it "fabricates a core status #{simple_status}" do + expect(status).to be_a expected_status + end + + it 'extends core status with common methods' do + expect(status).to have_details + expect(status).not_to have_action + expect(status.details_path).to eq external_url + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb index f92a1c149bf..bbf9c7c83a3 100644 --- a/spec/lib/gitlab/ci/status/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/factory_spec.rb @@ -1,24 +1,135 @@ require 'spec_helper' describe Gitlab::Ci::Status::Factory do - subject do - described_class.new(resource, user) + let(:user) { create(:user) } + let(:fabricated_status) { factory.fabricate! } + let(:factory) { described_class.new(resource, user) } + + context 'when object has a core status' do + HasStatus::AVAILABLE_STATUSES.each do |simple_status| + context "when simple core status is #{simple_status}" do + let(:resource) { double('resource', status: simple_status) } + + let(:expected_status) do + Gitlab::Ci::Status.const_get(simple_status.capitalize) + end + + it "fabricates a core status #{simple_status}" do + expect(fabricated_status).to be_a expected_status + end + + it "matches a valid core status for #{simple_status}" do + expect(factory.core_status).to be_a expected_status + end + + it "does not match any extended statuses for #{simple_status}" do + expect(factory.extended_statuses).to be_empty + end + end + end end - let(:user) { create(:user) } + context 'when resource supports multiple extended statuses' do + let(:resource) { double('resource', status: :success) } - let(:status) { subject.fabricate! } + let(:first_extended_status) do + Class.new(SimpleDelegator) do + def first_method + 'first return value' + end - context 'when object has a core status' do - HasStatus::AVAILABLE_STATUSES.each do |core_status| - context "when core status is #{core_status}" do - let(:resource) { double(status: core_status) } + def second_method + 'second return value' + end + + def self.matches?(*) + true + end + end + end - it "fabricates a core status #{core_status}" do - expect(status).to be_a( - Gitlab::Ci::Status.const_get(core_status.capitalize)) + let(:second_extended_status) do + Class.new(SimpleDelegator) do + def first_method + 'decorated return value' end + + def third_method + 'third return value' + end + + def self.matches?(*) + true + end + end + end + + shared_examples 'compound decorator factory' do + it 'fabricates compound decorator' do + expect(fabricated_status.first_method).to eq 'decorated return value' + expect(fabricated_status.second_method).to eq 'second return value' + expect(fabricated_status.third_method).to eq 'third return value' end + + it 'delegates to core status' do + expect(fabricated_status.text).to eq 'passed' + end + + it 'latest matches status becomes a status name' do + expect(fabricated_status.class).to eq second_extended_status + end + + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Success + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [first_extended_status, second_extended_status] + end + end + + context 'when exclusive statuses are matches' do + before do + allow(described_class).to receive(:extended_statuses) + .and_return([[first_extended_status, second_extended_status]]) + end + + it 'does not fabricate compound decorator' do + expect(fabricated_status.first_method).to eq 'first return value' + expect(fabricated_status.second_method).to eq 'second return value' + expect(fabricated_status).not_to respond_to(:third_method) + end + + it 'delegates to core status' do + expect(fabricated_status.text).to eq 'passed' + end + + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Success + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses).to eq [first_extended_status] + end + end + + context 'when exclusive statuses are not matched' do + before do + allow(described_class).to receive(:extended_statuses) + .and_return([[first_extended_status], [second_extended_status]]) + end + + it_behaves_like 'compound decorator factory' + end + + context 'when using simplified status grouping' do + before do + allow(described_class).to receive(:extended_statuses) + .and_return([first_extended_status, second_extended_status]) + end + + it_behaves_like 'compound decorator factory' end end end diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb index d4a2dc7fcc1..b10a447c27a 100644 --- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb @@ -3,29 +3,32 @@ require 'spec_helper' describe Gitlab::Ci::Status::Pipeline::Factory do let(:user) { create(:user) } let(:project) { pipeline.project } - - subject do - described_class.new(pipeline, user) - end - - let(:status) do - subject.fabricate! - end + let(:status) { factory.fabricate! } + let(:factory) { described_class.new(pipeline, user) } before do project.team << [user, :developer] end context 'when pipeline has a core status' do - HasStatus::AVAILABLE_STATUSES.each do |core_status| - context "when core status is #{core_status}" do - let(:pipeline) do - create(:ci_pipeline, status: core_status) + HasStatus::AVAILABLE_STATUSES.each do |simple_status| + context "when core status is #{simple_status}" do + let(:pipeline) { create(:ci_pipeline, status: simple_status) } + + let(:expected_status) do + Gitlab::Ci::Status.const_get(simple_status.capitalize) + end + + it "matches correct core status for #{simple_status}" do + expect(factory.core_status).to be_a expected_status end - it "fabricates a core status #{core_status}" do - expect(status).to be_a( - Gitlab::Ci::Status.const_get(core_status.capitalize)) + it 'does not matche extended statuses' do + expect(factory.extended_statuses).to be_empty + end + + it "fabricates a core status #{simple_status}" do + expect(status).to be_a expected_status end it 'extends core status with common pipeline methods' do @@ -47,13 +50,22 @@ describe Gitlab::Ci::Status::Pipeline::Factory do create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline) end + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Success + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::SuccessWarning] + end + it 'fabricates extended "success with warnings" status' do - expect(status) - .to be_a Gitlab::Ci::Status::Pipeline::SuccessWithWarnings + expect(status).to be_a Gitlab::Ci::Status::SuccessWarning end - it 'extends core status with common pipeline methods' do + it 'extends core status with common pipeline method' do expect(status).to have_details + expect(status.details_path).to include "pipelines/#{pipeline.id}" end end end diff --git a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb deleted file mode 100644 index 979160eb9c4..00000000000 --- a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb +++ /dev/null @@ -1,69 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do - subject do - described_class.new(double('status')) - end - - describe '#test' do - it { expect(subject.text).to eq 'passed' } - end - - describe '#label' do - it { expect(subject.label).to eq 'passed with warnings' } - end - - describe '#icon' do - it { expect(subject.icon).to eq 'icon_status_warning' } - end - - describe '#group' do - it { expect(subject.group).to eq 'success_with_warnings' } - end - - describe '.matches?' do - context 'when pipeline is successful' do - let(:pipeline) do - create(:ci_pipeline, status: :success) - end - - context 'when pipeline has warnings' do - before do - allow(pipeline).to receive(:has_warnings?).and_return(true) - end - - it 'is a correct match' do - expect(described_class.matches?(pipeline, double)).to eq true - end - end - - context 'when pipeline does not have warnings' do - it 'does not match' do - expect(described_class.matches?(pipeline, double)).to eq false - end - end - end - - context 'when pipeline is not successful' do - let(:pipeline) do - create(:ci_pipeline, status: :skipped) - end - - context 'when pipeline has warnings' do - before do - allow(pipeline).to receive(:has_warnings?).and_return(true) - end - - it 'does not match' do - expect(described_class.matches?(pipeline, double)).to eq false - end - end - - context 'when pipeline does not have warnings' do - it 'does not match' do - expect(described_class.matches?(pipeline, double)).to eq false - end - end - end - end -end diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb index 6f8721d30c2..bbb40e2c1ab 100644 --- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb @@ -43,4 +43,25 @@ describe Gitlab::Ci::Status::Stage::Factory do end end end + + context 'when stage has warnings' do + let(:stage) do + build(:ci_stage, name: 'test', status: :success, pipeline: pipeline) + end + + before do + create(:ci_build, :allowed_to_fail, :failed, + stage: 'test', pipeline: stage.pipeline) + end + + it 'fabricates extended "success with warnings" status' do + expect(status) + .to be_a Gitlab::Ci::Status::SuccessWarning + end + + it 'extends core status with common stage method' do + expect(status).to have_details + expect(status.details_path).to include "pipelines/#{pipeline.id}##{stage.name}" + end + end end diff --git a/spec/lib/gitlab/ci/status/success_warning_spec.rb b/spec/lib/gitlab/ci/status/success_warning_spec.rb new file mode 100644 index 00000000000..7e2269397c6 --- /dev/null +++ b/spec/lib/gitlab/ci/status/success_warning_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::SuccessWarning do + subject do + described_class.new(double('status')) + end + + describe '#test' do + it { expect(subject.text).to eq 'passed' } + end + + describe '#label' do + it { expect(subject.label).to eq 'passed with warnings' } + end + + describe '#icon' do + it { expect(subject.icon).to eq 'icon_status_warning' } + end + + describe '#group' do + it { expect(subject.group).to eq 'success_with_warnings' } + end + + describe '.matches?' do + let(:matchable) { double('matchable') } + + context 'when matchable subject is successful' do + before do + allow(matchable).to receive(:success?).and_return(true) + end + + context 'when matchable subject has warnings' do + before do + allow(matchable).to receive(:has_warnings?).and_return(true) + end + + it 'is a correct match' do + expect(described_class.matches?(matchable, double)).to eq true + end + end + + context 'when matchable subject does not have warnings' do + before do + allow(matchable).to receive(:has_warnings?).and_return(false) + end + + it 'does not match' do + expect(described_class.matches?(matchable, double)).to eq false + end + end + end + + context 'when matchable subject is not successful' do + before do + allow(matchable).to receive(:success?).and_return(false) + end + + context 'when matchable subject has warnings' do + before do + allow(matchable).to receive(:has_warnings?).and_return(true) + end + + it 'does not match' do + expect(described_class.matches?(matchable, double)).to eq false + end + end + + context 'when matchable subject does not have warnings' do + it 'does not match' do + expect(described_class.matches?(matchable, double)).to eq false + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/trace_reader_spec.rb b/spec/lib/gitlab/ci/trace_reader_spec.rb index f06d78694d6..ff5551bf703 100644 --- a/spec/lib/gitlab/ci/trace_reader_spec.rb +++ b/spec/lib/gitlab/ci/trace_reader_spec.rb @@ -11,13 +11,25 @@ describe Gitlab::Ci::TraceReader do last_lines = random_lines expected = lines.last(last_lines).join + result = subject.read(last_lines: last_lines) - expect(subject.read(last_lines: last_lines)).to eq(expected) + expect(result).to eq(expected) + expect(result.encoding).to eq(Encoding.default_external) end end it 'returns everything if trying to get too many lines' do - expect(build_subject.read(last_lines: lines.size * 2)).to eq(lines.join) + result = build_subject.read(last_lines: lines.size * 2) + + expect(result).to eq(lines.join) + expect(result.encoding).to eq(Encoding.default_external) + end + + it 'returns all contents if last_lines is not specified' do + result = build_subject.read + + expect(result).to eq(lines.join) + expect(result.encoding).to eq(Encoding.default_external) end it 'raises an error if not passing an integer for last_lines' do diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index 1bbaca0739a..97af1c2523d 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -1,11 +1,11 @@ require 'spec_helper' describe Gitlab::ClosingIssueExtractor, lib: true do - let(:project) { create(:project) } - let(:project2) { create(:project) } + let(:project) { create(:empty_project) } + let(:project2) { create(:empty_project) } let(:forked_project) { Projects::ForkService.new(project, project.creator).execute } - let(:issue) { create(:issue, project: project) } - let(:issue2) { create(:issue, project: project2) } + let(:issue) { create(:issue, project: project) } + let(:issue2) { create(:issue, project: project2) } let(:reference) { issue.to_reference } let(:cross_reference) { issue2.to_reference(project) } let(:fork_cross_reference) { issue.to_reference(forked_project) } diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb index 648d342ecf8..fbf679c5215 100644 --- a/spec/lib/gitlab/conflict/file_spec.rb +++ b/spec/lib/gitlab/conflict/file_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Conflict::File, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:rugged) { repository.rugged } let(:their_commit) { rugged.branches['conflict-start'].target } diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index 004341ffd02..b01c4805a34 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -1,36 +1,64 @@ require 'spec_helper' describe Gitlab::CurrentSettings do + include StubENV + + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') + end + describe '#current_application_settings' do - it 'attempts to use cached values first' do - allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) - expect(ApplicationSetting).to receive(:current).and_return(::ApplicationSetting.create_from_defaults) - expect(ApplicationSetting).not_to receive(:last) + context 'with DB available' do + before do + allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) + end - expect(current_application_settings).to be_a(ApplicationSetting) - end + it 'attempts to use cached values first' do + expect(ApplicationSetting).to receive(:current) + expect(ApplicationSetting).not_to receive(:last) + + expect(current_application_settings).to be_a(ApplicationSetting) + end - it 'does not attempt to connect to DB or Redis' do - allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(false) - expect(ApplicationSetting).not_to receive(:current) - expect(ApplicationSetting).not_to receive(:last) + it 'falls back to DB if Redis returns an empty value' do + expect(ApplicationSetting).to receive(:last).and_call_original - expect(current_application_settings).to eq fake_application_settings + expect(current_application_settings).to be_a(ApplicationSetting) + end + + it 'falls back to DB if Redis fails' do + expect(ApplicationSetting).to receive(:current).and_raise(::Redis::BaseError) + expect(ApplicationSetting).to receive(:last).and_call_original + + expect(current_application_settings).to be_a(ApplicationSetting) + end end - it 'falls back to DB if Redis returns an empty value' do - allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) - expect(ApplicationSetting).to receive(:last).and_call_original + context 'with DB unavailable' do + before do + allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(false) + end - expect(current_application_settings).to be_a(ApplicationSetting) + it 'returns an in-memory ApplicationSetting object' do + expect(ApplicationSetting).not_to receive(:current) + expect(ApplicationSetting).not_to receive(:last) + + expect(current_application_settings).to be_a(OpenStruct) + end end - it 'falls back to DB if Redis fails' do - allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) - expect(ApplicationSetting).to receive(:current).and_raise(::Redis::BaseError) - expect(ApplicationSetting).to receive(:last).and_call_original + context 'when ENV["IN_MEMORY_APPLICATION_SETTINGS"] is true' do + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'true') + end + + it 'returns an in-memory ApplicationSetting object' do + expect(ApplicationSetting).not_to receive(:current) + expect(ApplicationSetting).not_to receive(:last) - expect(current_application_settings).to be_a(ApplicationSetting) + expect(current_application_settings).to be_a(ApplicationSetting) + expect(current_application_settings).not_to be_persisted + end end end end diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb index fb6b6c4a8d2..3dd76ba5b8a 100644 --- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::CycleAnalytics::StageSummary, models: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from) { 1.day.ago } let(:user) { create(:user, :admin) } subject { described_class.new(project, from: Time.now, current_user: user).data } @@ -15,7 +15,7 @@ describe Gitlab::CycleAnalytics::StageSummary, models: true do end it "doesn't find issues from other projects" do - Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project)) } + Timecop.freeze(5.days.from_now) { create(:issue, project: create(:empty_project)) } expect(subject.first[:value]).to eq(0) end @@ -30,7 +30,7 @@ describe Gitlab::CycleAnalytics::StageSummary, models: true do end it "doesn't find commits from other projects" do - Timecop.freeze(5.days.from_now) { create_commit("Test message", create(:project), user, 'master') } + Timecop.freeze(5.days.from_now) { create_commit("Test message", create(:project, :repository), user, 'master') } expect(subject.second[:value]).to eq(0) end @@ -51,7 +51,9 @@ describe Gitlab::CycleAnalytics::StageSummary, models: true do end it "doesn't find commits from other projects" do - Timecop.freeze(5.days.from_now) { create(:deployment, project: create(:project)) } + Timecop.freeze(5.days.from_now) do + create(:deployment, project: create(:project, :repository)) + end expect(subject.third[:value]).to eq(0) end diff --git a/spec/lib/gitlab/data_builder/note_spec.rb b/spec/lib/gitlab/data_builder/note_spec.rb index 9a4dec91e56..04ec34492e1 100644 --- a/spec/lib/gitlab/data_builder/note_spec.rb +++ b/spec/lib/gitlab/data_builder/note_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::DataBuilder::Note, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:data) { described_class.build(note, user) } let(:fixed_time) { Time.at(1425600000) } # Avoid time precision errors diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb index a68f5943a6a..f13041e498c 100644 --- a/spec/lib/gitlab/data_builder/pipeline_spec.rb +++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::DataBuilder::Pipeline do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb index a379f798a16..dbcfb9b7400 100644 --- a/spec/lib/gitlab/data_builder/push_spec.rb +++ b/spec/lib/gitlab/data_builder/push_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::DataBuilder::Push, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } describe '.build_sample' do diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index 38475792d93..050689b7c9a 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Diff::File, lib: true do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:commit) { project.commit(sample_commit.id) } let(:diff) { commit.raw_diffs.first } let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) } diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb index 1c2ddeed692..1e21270d928 100644 --- a/spec/lib/gitlab/diff/highlight_spec.rb +++ b/spec/lib/gitlab/diff/highlight_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Diff::Highlight, lib: true do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:commit) { project.commit(sample_commit.id) } let(:diff) { commit.raw_diffs.first } let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) } diff --git a/spec/lib/gitlab/diff/line_mapper_spec.rb b/spec/lib/gitlab/diff/line_mapper_spec.rb index 4b943fa382d..2c7ecd1907e 100644 --- a/spec/lib/gitlab/diff/line_mapper_spec.rb +++ b/spec/lib/gitlab/diff/line_mapper_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Diff::LineMapper, lib: true do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:commit) { project.commit(sample_commit.id) } let(:diffs) { commit.raw_diffs } diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb index af18d3c25a6..fe5fa048413 100644 --- a/spec/lib/gitlab/diff/parallel_diff_spec.rb +++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Diff::ParallelDiff, lib: true do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:commit) { project.commit(sample_commit.id) } let(:diffs) { commit.raw_diffs } diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb index 6e8fff6f516..cdf0af6d7ef 100644 --- a/spec/lib/gitlab/diff/position_spec.rb +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Diff::Position, lib: true do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } describe "position for an added file" do let(:commit) { project.commit("2ea1f3dec713d940208fb5ce4a38765ecb5d3f73") } diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb index c268f84c759..f5822fed37c 100644 --- a/spec/lib/gitlab/diff/position_tracer_spec.rb +++ b/spec/lib/gitlab/diff/position_tracer_spec.rb @@ -51,7 +51,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:current_user) { project.owner } let(:repository) { project.repository } let(:file_name) { "test-file" } diff --git a/spec/lib/gitlab/email/email_shared_blocks.rb b/spec/lib/gitlab/email/email_shared_blocks.rb index 19298e261e3..9d806fc524d 100644 --- a/spec/lib/gitlab/email/email_shared_blocks.rb +++ b/spec/lib/gitlab/email/email_shared_blocks.rb @@ -18,7 +18,7 @@ shared_context :email_shared_context do end end -shared_examples :email_shared_examples do +shared_examples :reply_processing_shared_examples do context "when the user could not be found" do before do user.destroy diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb index cb3651e3845..4a9c9a7fe34 100644 --- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb @@ -3,7 +3,7 @@ require_relative '../email_shared_blocks' describe Gitlab::Email::Handler::CreateIssueHandler, lib: true do include_context :email_shared_context - it_behaves_like :email_shared_examples + it_behaves_like :reply_processing_shared_examples before do stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo") @@ -13,7 +13,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler, lib: true do let(:email_raw) { fixture_file('emails/valid_new_issue.eml') } let(:namespace) { create(:namespace, path: 'gitlabhq') } - let!(:project) { create(:project, :public, namespace: namespace) } + let!(:project) { create(:project, :public, :repository, namespace: namespace) } let!(:user) do create( :user, diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb index 48660d1dd1b..17a4ef25210 100644 --- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb @@ -3,7 +3,7 @@ require_relative '../email_shared_blocks' describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do include_context :email_shared_context - it_behaves_like :email_shared_examples + it_behaves_like :reply_processing_shared_examples before do stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo") @@ -11,7 +11,7 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do end let(:email_raw) { fixture_file('emails/valid_reply.eml') } - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:user) { create(:user) } let(:note) { create(:diff_note_on_merge_request, project: project) } let(:noteable) { note.noteable } diff --git a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb new file mode 100644 index 00000000000..0939e6c4514 --- /dev/null +++ b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' +require_relative '../email_shared_blocks' + +describe Gitlab::Email::Handler::UnsubscribeHandler, lib: true do + include_context :email_shared_context + + before do + stub_incoming_email_setting(enabled: true, address: 'reply+%{key}@appmail.adventuretime.ooo') + stub_config_setting(host: 'localhost') + end + + let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "#{mail_key}+unsubscribe") } + let(:project) { create(:empty_project, :public) } + let(:user) { create(:user) } + let(:noteable) { create(:issue, project: project) } + + let!(:sent_notification) { SentNotification.record(noteable, user.id, mail_key) } + + context 'when notification concerns a commit' do + let(:commit) { create(:commit, project: project) } + let!(:sent_notification) { SentNotification.record(commit, user.id, mail_key) } + + it 'handler does not raise an error' do + expect { receiver.execute }.not_to raise_error + end + end + + context 'user is unsubscribed' do + it 'leaves user unsubscribed' do + expect { receiver.execute }.not_to change { noteable.subscribed?(user) }.from(false) + end + end + + context 'user is subscribed' do + before do + noteable.subscribe(user) + end + + it 'unsubscribes user from notable' do + expect { receiver.execute }.to change { noteable.subscribed?(user) }.from(true).to(false) + end + end + + context 'when the noteable could not be found' do + before do + noteable.destroy + end + + it 'raises a NoteableNotFoundError' do + expect { receiver.execute }.to raise_error(Gitlab::Email::NoteableNotFoundError) + end + end + + context 'when no sent notification for the mail key could be found' do + let(:email_raw) { fixture_file('emails/wrong_mail_key.eml') } + + it 'raises a SentNotificationNotFoundError' do + expect { receiver.execute }.to raise_error(Gitlab::Email::SentNotificationNotFoundError) + end + end +end diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb index 5b966bddb6a..7b3291b8315 100644 --- a/spec/lib/gitlab/email/message/repository_push_spec.rb +++ b/spec/lib/gitlab/email/message/repository_push_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::Email::Message::RepositoryPush do include RepoHelpers let!(:group) { create(:group, name: 'my_group') } - let!(:project) { create(:project, name: 'my_project', namespace: group) } + let!(:project) { create(:project, :repository, name: 'my_project', namespace: group) } let!(:author) { create(:author, name: 'Author') } let(:message) do diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb index f4703dc704f..5d416c9eec3 100644 --- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Gitlab::Gfm::ReferenceRewriter do let(:text) { 'some text' } - let(:old_project) { create(:project, name: 'old') } - let(:new_project) { create(:project, name: 'new') } + let(:old_project) { create(:empty_project, name: 'old-project') } + let(:new_project) { create(:empty_project, name: 'new-project') } let(:user) { create(:user) } before { old_project.team << [user, :reporter] } diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb index 6eca33f9fee..c3016f63ebf 100644 --- a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Gitlab::Gfm::UploadsRewriter do let(:user) { create(:user) } - let(:old_project) { create(:project) } - let(:new_project) { create(:project) } + let(:old_project) { create(:empty_project) } + let(:new_project) { create(:empty_project) } let(:rewriter) { described_class.new(text, old_project, user) } context 'text contains links to uploads' do diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb index d1f947b6850..3f279c21865 100644 --- a/spec/lib/gitlab/git/hook_spec.rb +++ b/spec/lib/gitlab/git/hook_spec.rb @@ -3,7 +3,7 @@ require 'fileutils' describe Gitlab::Git::Hook, lib: true do describe "#trigger" do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } def create_hook(name) diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb index 1f9c987be0b..d48629a296d 100644 --- a/spec/lib/gitlab/git/rev_list_spec.rb +++ b/spec/lib/gitlab/git/rev_list_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Git::RevList, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } context "validations" do described_class::ALLOWED_VARIABLES.each do |var| diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 44b84afde47..b080be62b34 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::GitAccess, lib: true do let(:access) { Gitlab::GitAccess.new(actor, project, 'web', authentication_abilities: authentication_abilities) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:actor) { user } let(:authentication_abilities) do @@ -88,7 +88,7 @@ describe Gitlab::GitAccess, lib: true do end context 'when project is public' do - let(:public_project) { create(:project, :public) } + let(:public_project) { create(:project, :public, :repository) } let(:guest_access) { Gitlab::GitAccess.new(nil, public_project, 'web', authentication_abilities: []) } subject { guest_access.check('git-upload-pack', '_any') } @@ -124,19 +124,19 @@ describe Gitlab::GitAccess, lib: true do context 'when unauthorized' do context 'from public project' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } it { expect(subject).to be_allowed } end context 'from internal project' do - let(:project) { create(:project, :internal) } + let(:project) { create(:project, :internal, :repository) } it { expect(subject).not_to be_allowed } end context 'from private project' do - let(:project) { create(:project, :private) } + let(:project) { create(:project, :private, :repository) } it { expect(subject).not_to be_allowed } end @@ -148,7 +148,7 @@ describe Gitlab::GitAccess, lib: true do let(:authentication_abilities) { build_authentication_abilities } describe 'owner' do - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:project, :repository, namespace: user.namespace) } context 'pull code' do it { expect(subject).to be_allowed } @@ -364,19 +364,19 @@ describe Gitlab::GitAccess, lib: true do context 'when unauthorized' do context 'to public project' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } it { expect(subject).not_to be_allowed } end context 'to internal project' do - let(:project) { create(:project, :internal) } + let(:project) { create(:project, :internal, :repository) } it { expect(subject).not_to be_allowed } end context 'to private project' do - let(:project) { create(:project) } + let(:project) { create(:project, :private, :repository) } it { expect(subject).not_to be_allowed } end diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index a5d172233cc..4a0cdc6887e 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::GitAccessWiki, lib: true do let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:authentication_abilities) do [ diff --git a/spec/lib/gitlab/github_import/branch_formatter_spec.rb b/spec/lib/gitlab/github_import/branch_formatter_spec.rb index 462caa5b5fe..36e7d739f7e 100644 --- a/spec/lib/gitlab/github_import/branch_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/branch_formatter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GithubImport::BranchFormatter, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:commit) { create(:commit, project: project) } let(:repo) { double } let(:raw) do diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb index c520a9c53ad..e6e33d3686a 100644 --- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GithubImport::CommentFormatter, lib: true do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:octocat) { double(id: 123456, login: 'octocat') } let(:created_at) { DateTime.strptime('2013-04-10T20:09:31Z') } let(:updated_at) { DateTime.strptime('2014-03-03T18:58:10Z') } diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb index e31ed9c1fa0..eec1fabab54 100644 --- a/spec/lib/gitlab/github_import/issue_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GithubImport::IssueFormatter, lib: true do - let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) } + let!(:project) { create(:empty_project, namespace: create(:namespace, path: 'octocat')) } let(:octocat) { double(id: 123456, login: 'octocat') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') } diff --git a/spec/lib/gitlab/github_import/label_formatter_spec.rb b/spec/lib/gitlab/github_import/label_formatter_spec.rb index 8098754d735..10449ef5fcb 100644 --- a/spec/lib/gitlab/github_import/label_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/label_formatter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GithubImport::LabelFormatter, lib: true do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:raw) { double(name: 'improvements', color: 'e6e6e6') } subject { described_class.new(project, raw) } diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb index 2b3256edcb2..90947ff4707 100644 --- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GithubImport::PullRequestFormatter, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:source_sha) { create(:commit, project: project).id } let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id } let(:repository) { double(id: 1, fork: false) } diff --git a/spec/lib/gitlab/github_import/release_formatter_spec.rb b/spec/lib/gitlab/github_import/release_formatter_spec.rb index 793128c6ab9..13b15e669ab 100644 --- a/spec/lib/gitlab/github_import/release_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/release_formatter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GithubImport::ReleaseFormatter, lib: true do - let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) } + let!(:project) { create(:empty_project, namespace: create(:namespace, path: 'octocat')) } let(:octocat) { double(id: 123456, login: 'octocat') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb index 097861fd34d..ccaa88a5c79 100644 --- a/spec/lib/gitlab/google_code_import/importer_spec.rb +++ b/spec/lib/gitlab/google_code_import/importer_spec.rb @@ -10,7 +10,7 @@ describe Gitlab::GoogleCodeImport::Importer, lib: true do 'user_map' => { 'thilo...' => "@#{mapped_user.username}" } } end - let(:project) { create(:project) } + let(:project) { create(:empty_project) } subject { described_class.new(project) } diff --git a/spec/lib/gitlab/graphs/commits_spec.rb b/spec/lib/gitlab/graphs/commits_spec.rb index f5c064303ad..abb5a26060f 100644 --- a/spec/lib/gitlab/graphs/commits_spec.rb +++ b/spec/lib/gitlab/graphs/commits_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Graphs::Commits, lib: true do - let!(:project) { create(:project, :public, :empty_repo) } + let!(:project) { create(:empty_project, :public) } let!(:commit1) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: Time.now) } let!(:commit1_yesterday) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: 1.day.ago)} diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb index fc021416d92..fadfe4d378e 100644 --- a/spec/lib/gitlab/highlight_spec.rb +++ b/spec/lib/gitlab/highlight_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Highlight, lib: true do include RepoHelpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:commit) { project.commit(sample_commit.id) } diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb index d6409a29550..53f7d244d88 100644 --- a/spec/lib/gitlab/import_export/import_export_spec.rb +++ b/spec/lib/gitlab/import_export/import_export_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::ImportExport, services: true do describe 'export filename' do - let(:project) { create(:project, :public, path: 'project-path') } + let(:project) { create(:empty_project, :public, path: 'project-path') } it 'contains the project path' do expect(described_class.export_filename(project: project)).to include(project.path) diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb index 1cb02f8e318..0b7984d6ca9 100644 --- a/spec/lib/gitlab/import_export/members_mapper_spec.rb +++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Gitlab::ImportExport::MembersMapper, services: true do describe 'map members' do - let(:user) { create(:user, authorized_projects_populated: true) } - let(:project) { create(:project, :public, name: 'searchable_project') } + let(:user) { create(:admin, authorized_projects_populated: true) } + let(:project) { create(:empty_project, :public, name: 'searchable_project') } let(:user2) { create(:user, authorized_projects_populated: true) } let(:exported_user_id) { 99 } let(:exported_members) do @@ -24,7 +24,7 @@ describe Gitlab::ImportExport::MembersMapper, services: true do { "id" => exported_user_id, "email" => user2.email, - "username" => user2.username + "username" => 'test' } }, { @@ -48,6 +48,10 @@ describe Gitlab::ImportExport::MembersMapper, services: true do exported_members: exported_members, user: user, project: project) end + it 'includes the exported user ID in the map' do + expect(members_mapper.map.keys).to include(exported_user_id) + end + it 'maps a project member' do expect(members_mapper.map[exported_user_id]).to eq(user2.id) end @@ -56,12 +60,6 @@ describe Gitlab::ImportExport::MembersMapper, services: true do expect(members_mapper.map[-1]).to eq(user.id) end - it 'updates missing author IDs on missing project member' do - members_mapper.map[-1] - - expect(members_mapper.missing_author_ids.first).to eq(-1) - end - it 'has invited members with no user' do members_mapper.map @@ -74,5 +72,25 @@ describe Gitlab::ImportExport::MembersMapper, services: true do expect(user.authorized_project?(project)).to be true expect(user2.authorized_project?(project)).to be true end + + context 'user is not an admin' do + let(:user) { create(:user, authorized_projects_populated: true) } + + it 'does not map a project member' do + expect(members_mapper.map[exported_user_id]).to eq(user.id) + end + + it 'defaults to importer project member if it does not exist' do + expect(members_mapper.map[-1]).to eq(user.id) + end + end + + context 'chooses the one with an email first' do + let(:user3) { create(:user, username: 'test') } + + it 'maps the project member that has a matching email first' do + expect(members_mapper.map[exported_user_id]).to eq(user2.id) + end + end end end diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index c8bba553558..d480c3821ec 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -151,6 +151,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do project = create(:project, :public, + :repository, issues: [issue], snippets: [snippet], releases: [release], diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb index 3aa492a8ab1..db0084d6823 100644 --- a/spec/lib/gitlab/import_export/relation_factory_spec.rb +++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport::RelationFactory, lib: true do let(:project) { create(:empty_project) } let(:members_mapper) { double('members_mapper').as_null_object } - let(:user) { create(:user) } + let(:user) { create(:admin) } let(:created_object) do described_class.create(relation_sym: relation_sym, relation_hash: relation_hash, @@ -122,4 +122,60 @@ describe Gitlab::ImportExport::RelationFactory, lib: true do expect(created_object.values).not_to include(99) end end + + context 'Notes user references' do + let(:relation_sym) { :notes } + let(:new_user) { create(:user) } + let(:exported_member) do + { + "id" => 111, + "access_level" => 30, + "source_id" => 1, + "source_type" => "Project", + "user_id" => 3, + "notification_level" => 3, + "created_at" => "2016-11-18T09:29:42.634Z", + "updated_at" => "2016-11-18T09:29:42.634Z", + "user" => { + "id" => 999, + "email" => new_user.email, + "username" => new_user.username + } + } + end + + let(:relation_hash) do + { + "id" => 4947, + "note" => "merged", + "noteable_type" => "MergeRequest", + "author_id" => 999, + "created_at" => "2016-11-18T09:29:42.634Z", + "updated_at" => "2016-11-18T09:29:42.634Z", + "project_id" => 1, + "attachment" => { + "url" => nil + }, + "noteable_id" => 377, + "system" => true, + "author" => { + "name" => "Administrator" + }, + "events" => [ + + ] + } + end + + let(:members_mapper) do + Gitlab::ImportExport::MembersMapper.new( + exported_members: [exported_member], + user: user, + project: project) + end + + it 'maps the right author to the imported note' do + expect(created_object.author).to eq(new_user) + end + end end diff --git a/spec/lib/gitlab/import_export/repo_bundler_spec.rb b/spec/lib/gitlab/import_export/repo_bundler_spec.rb index 135e99bc953..d39ea60ff7f 100644 --- a/spec/lib/gitlab/import_export/repo_bundler_spec.rb +++ b/spec/lib/gitlab/import_export/repo_bundler_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport::RepoSaver, services: true do describe 'bundle a project Git repo' do let(:user) { create(:user) } - let!(:project) { create(:project, :public, name: 'searchable_project') } + let!(:project) { create(:empty_project, :public, name: 'searchable_project') } let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) } let(:bundler) { described_class.new(project: project, shared: shared) } diff --git a/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb index b628da0f3e8..47d5d2fc150 100644 --- a/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport::WikiRepoSaver, services: true do describe 'bundle a wiki Git repo' do let(:user) { create(:user) } - let!(:project) { create(:project, :public, name: 'searchable_project') } + let!(:project) { create(:empty_project, :public, name: 'searchable_project') } let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) } let(:wiki_bundler) { described_class.new(project: project, shared: shared) } diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb index 1dcf2c0668b..7e951e3fcdd 100644 --- a/spec/lib/gitlab/incoming_email_spec.rb +++ b/spec/lib/gitlab/incoming_email_spec.rb @@ -23,6 +23,48 @@ describe Gitlab::IncomingEmail, lib: true do end end + describe 'self.supports_wildcard?' do + context 'address contains the wildard placeholder' do + before do + stub_incoming_email_setting(address: 'replies+%{key}@example.com') + end + + it 'confirms that wildcard is supported' do + expect(described_class.supports_wildcard?).to be_truthy + end + end + + context "address doesn't contain the wildcard placeholder" do + before do + stub_incoming_email_setting(address: 'replies@example.com') + end + + it 'returns that wildcard is not supported' do + expect(described_class.supports_wildcard?).to be_falsey + end + end + + context 'address is not set' do + before do + stub_incoming_email_setting(address: nil) + end + + it 'returns that wildard is not supported' do + expect(described_class.supports_wildcard?).to be_falsey + end + end + end + + context 'self.unsubscribe_address' do + before do + stub_incoming_email_setting(address: 'replies+%{key}@example.com') + end + + it 'returns the address with interpolated reply key and unsubscribe suffix' do + expect(described_class.unsubscribe_address('key')).to eq('replies+key+unsubscribe@example.com') + end + end + context "self.reply_address" do before do stub_incoming_email_setting(address: "replies+%{key}@example.com") diff --git a/spec/lib/gitlab/job_waiter_spec.rb b/spec/lib/gitlab/job_waiter_spec.rb new file mode 100644 index 00000000000..780f5b1f8d7 --- /dev/null +++ b/spec/lib/gitlab/job_waiter_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe Gitlab::JobWaiter do + describe '#wait' do + let(:waiter) { described_class.new(%w(a)) } + it 'returns when all jobs have been completed' do + expect(Gitlab::SidekiqStatus).to receive(:all_completed?).with(%w(a)). + and_return(true) + + expect(waiter).not_to receive(:sleep) + + waiter.wait + end + + it 'sleeps between checking the job statuses' do + expect(Gitlab::SidekiqStatus).to receive(:all_completed?). + with(%w(a)). + and_return(false, true) + + expect(waiter).to receive(:sleep).with(described_class::INTERVAL) + + waiter.wait + end + + it 'returns when timing out' do + expect(waiter).not_to receive(:sleep) + waiter.wait(0) + end + end +end diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index 14ee386dba6..92e3624a8d8 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::ProjectSearchResults, lib: true do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:query) { 'hello world' } describe 'initialize with empty ref' do @@ -22,6 +22,7 @@ describe Gitlab::ProjectSearchResults, lib: true do end describe 'blob search' do + let(:project) { create(:project, :repository) } let(:results) { described_class.new(user, project, 'files').objects('blobs') } it 'finds by name' do @@ -74,6 +75,7 @@ describe Gitlab::ProjectSearchResults, lib: true do end describe 'confidential issues' do + let(:project) { create(:empty_project) } let(:query) { 'issue' } let(:author) { create(:user) } let(:assignee) { create(:user) } @@ -178,4 +180,119 @@ describe Gitlab::ProjectSearchResults, lib: true do expect(results.objects('notes')).not_to include note end end + + # Examples for commit access level test + # + # params: + # * search_phrase + # * commit + # + shared_examples 'access restricted commits' do + context 'when project is internal' do + let(:project) { create(:project, :internal, :repository) } + + it 'does not search if user is not authenticated' do + commits = described_class.new(nil, project, search_phrase).objects('commits') + + expect(commits).to be_empty + end + + it 'searches if user is authenticated' do + commits = described_class.new(user, project, search_phrase).objects('commits') + + expect(commits).to contain_exactly commit + end + end + + context 'when project is private' do + let!(:creator) { create(:user, username: 'private-project-author') } + let!(:private_project) { create(:project, :private, :repository, creator: creator, namespace: creator.namespace) } + let(:team_master) do + user = create(:user, username: 'private-project-master') + private_project.team << [user, :master] + user + end + let(:team_reporter) do + user = create(:user, username: 'private-project-reporter') + private_project.team << [user, :reporter] + user + end + + it 'does not show commit to stranger' do + commits = described_class.new(nil, private_project, search_phrase).objects('commits') + + expect(commits).to be_empty + end + + context 'team access' do + it 'shows commit to creator' do + commits = described_class.new(creator, private_project, search_phrase).objects('commits') + + expect(commits).to contain_exactly commit + end + + it 'shows commit to master' do + commits = described_class.new(team_master, private_project, search_phrase).objects('commits') + + expect(commits).to contain_exactly commit + end + + it 'shows commit to reporter' do + commits = described_class.new(team_reporter, private_project, search_phrase).objects('commits') + + expect(commits).to contain_exactly commit + end + end + end + end + + describe 'commit search' do + context 'by commit message' do + let(:project) { create(:project, :public, :repository) } + let(:commit) { project.repository.commit('59e29889be61e6e0e5e223bfa9ac2721d31605b8') } + let(:message) { 'Sorry, I did a mistake' } + + it 'finds commit by message' do + commits = described_class.new(user, project, message).objects('commits') + + expect(commits).to contain_exactly commit + end + + it 'handles when no commit match' do + commits = described_class.new(user, project, 'not really an existing description').objects('commits') + + expect(commits).to be_empty + end + + it_behaves_like 'access restricted commits' do + let(:search_phrase) { message } + let(:commit) { project.repository.commit('59e29889be61e6e0e5e223bfa9ac2721d31605b8') } + end + end + + context 'by commit hash' do + let(:project) { create(:project, :public, :repository) } + let(:commit) { project.repository.commit('0b4bc9a') } + commit_hashes = { short: '0b4bc9a', full: '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } + + commit_hashes.each do |type, commit_hash| + it "shows commit by #{type} hash id" do + commits = described_class.new(user, project, commit_hash).objects('commits') + + expect(commits).to contain_exactly commit + end + end + + it 'handles not existing commit hash correctly' do + commits = described_class.new(user, project, 'deadbeef').objects('commits') + + expect(commits).to be_empty + end + + it_behaves_like 'access restricted commits' do + let(:search_phrase) { '0b4bc9a49' } + let(:commit) { project.repository.commit('0b4bc9a') } + end + end + end end diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index bf0ab9635fd..6b689c41ef6 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -1,9 +1,11 @@ require 'spec_helper' describe Gitlab::ReferenceExtractor, lib: true do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } - before { project.team << [project.creator, :developer] } + before do + project.team << [project.creator, :developer] + end subject { Gitlab::ReferenceExtractor.new(project, project.creator) } @@ -78,22 +80,27 @@ describe Gitlab::ReferenceExtractor, lib: true do end it 'accesses valid commits' do + project = create(:project, :repository) { |p| p.add_developer(p.creator) } commit = project.commit('master') - subject.analyze("this references commits #{commit.sha[0..6]} and 012345") - extracted = subject.commits + extractor = described_class.new(project, project.creator) + extractor.analyze("this references commits #{commit.sha[0..6]} and 012345") + extracted = extractor.commits + expect(extracted.size).to eq(1) expect(extracted[0].sha).to eq(commit.sha) expect(extracted[0].message).to eq(commit.message) end it 'accesses valid commit ranges' do + project = create(:project, :repository) { |p| p.add_developer(p.creator) } commit = project.commit('master') earlier_commit = project.commit('master~2') - subject.analyze("this references commits #{earlier_commit.sha[0..6]}...#{commit.sha[0..6]}") + extractor = described_class.new(project, project.creator) + extractor.analyze("this references commits #{earlier_commit.sha[0..6]}...#{commit.sha[0..6]}") + extracted = extractor.commit_ranges - extracted = subject.commit_ranges expect(extracted.size).to eq(1) expect(extracted.first).to be_kind_of(CommitRange) expect(extracted.first.commit_from).to eq earlier_commit @@ -102,7 +109,6 @@ describe Gitlab::ReferenceExtractor, lib: true do context 'with an external issue tracker' do let(:project) { create(:jira_project) } - subject { described_class.new(project, project.creator) } it 'returns JIRA issues for a JIRA-integrated project' do subject.analyze('JIRA-123 and FOOBAR-4567') @@ -112,7 +118,7 @@ describe Gitlab::ReferenceExtractor, lib: true do end context 'with a project with an underscore' do - let(:other_project) { create(:project, path: 'test_project') } + let(:other_project) { create(:empty_project, path: 'test_project') } let(:issue) { create(:issue, project: other_project) } before do diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index 9614aad3e73..847fb977400 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::SearchResults do let(:user) { create(:user) } - let!(:project) { create(:project, name: 'foo') } + let!(:project) { create(:empty_project, name: 'foo') } let!(:issue) { create(:issue, project: project, title: 'foo') } let!(:merge_request) do diff --git a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb new file mode 100644 index 00000000000..287bf62d9bd --- /dev/null +++ b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe Gitlab::SidekiqStatus::ClientMiddleware do + describe '#call' do + it 'tracks the job in Redis' do + expect(Gitlab::SidekiqStatus).to receive(:set).with('123') + + described_class.new. + call('Foo', { 'jid' => '123' }, double(:queue), double(:pool)) { nil } + end + end +end diff --git a/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb new file mode 100644 index 00000000000..80728197b8c --- /dev/null +++ b/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe Gitlab::SidekiqStatus::ServerMiddleware do + describe '#call' do + it 'stops tracking of a job upon completion' do + expect(Gitlab::SidekiqStatus).to receive(:unset).with('123') + + ret = described_class.new. + call(double(:worker), { 'jid' => '123' }, double(:queue)) { 10 } + + expect(ret).to eq(10) + end + end +end diff --git a/spec/lib/gitlab/sidekiq_status_spec.rb b/spec/lib/gitlab/sidekiq_status_spec.rb new file mode 100644 index 00000000000..0aa36a3416b --- /dev/null +++ b/spec/lib/gitlab/sidekiq_status_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe Gitlab::SidekiqStatus do + describe '.set', :redis do + it 'stores the job ID' do + described_class.set('123') + + key = described_class.key_for('123') + + Sidekiq.redis do |redis| + expect(redis.exists(key)).to eq(true) + expect(redis.ttl(key) > 0).to eq(true) + end + end + end + + describe '.unset', :redis do + it 'removes the job ID' do + described_class.set('123') + described_class.unset('123') + + key = described_class.key_for('123') + + Sidekiq.redis do |redis| + expect(redis.exists(key)).to eq(false) + end + end + end + + describe '.all_completed?', :redis do + it 'returns true if all jobs have been completed' do + expect(described_class.all_completed?(%w(123))).to eq(true) + end + + it 'returns false if a job has not yet been completed' do + described_class.set('123') + + expect(described_class.all_completed?(%w(123 456))).to eq(false) + end + end + + describe '.key_for' do + it 'returns the key for a job ID' do + key = described_class.key_for('123') + + expect(key).to be_an_instance_of(String) + expect(key).to include('123') + end + end +end diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb index d2d334e6413..45cec65a284 100644 --- a/spec/lib/gitlab/template/issue_template_spec.rb +++ b/spec/lib/gitlab/template/issue_template_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::Template::IssueTemplate do subject { described_class } let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:file_path_1) { '.gitlab/issue_templates/bug.md' } let(:file_path_2) { '.gitlab/issue_templates/template_test.md' } let(:file_path_3) { '.gitlab/issue_templates/feature_proposal.md' } diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb index ddf68c4cf78..ae51b79be22 100644 --- a/spec/lib/gitlab/template/merge_request_template_spec.rb +++ b/spec/lib/gitlab/template/merge_request_template_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::Template::MergeRequestTemplate do subject { described_class } let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:file_path_1) { '.gitlab/merge_request_templates/bug.md' } let(:file_path_2) { '.gitlab/merge_request_templates/template_test.md' } let(:file_path_3) { '.gitlab/merge_request_templates/feature_proposal.md' } diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb index a826b24419a..3fe8cf43934 100644 --- a/spec/lib/gitlab/url_builder_spec.rb +++ b/spec/lib/gitlab/url_builder_spec.rb @@ -99,7 +99,7 @@ describe Gitlab::UrlBuilder, lib: true do context 'on another object' do it 'returns a proper URL' do - project = build_stubbed(:project) + project = build_stubbed(:empty_project) expect { described_class.build(project) }. to raise_error(NotImplementedError, 'No URL builder defined for Project') diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb index d3c3b800b94..369e55f61f1 100644 --- a/spec/lib/gitlab/user_access_spec.rb +++ b/spec/lib/gitlab/user_access_spec.rb @@ -66,7 +66,8 @@ describe Gitlab::UserAccess, lib: true do end describe 'push to protected branch' do - let(:branch) { create :protected_branch, project: project } + let(:branch) { create :protected_branch, project: project, name: "test" } + let(:not_existing_branch) { create :protected_branch, :developers_can_merge, project: project } it 'returns true if user is a master' do project.team << [user, :master] @@ -85,6 +86,12 @@ describe Gitlab::UserAccess, lib: true do expect(access.can_push_to_branch?(branch.name)).to be_falsey end + + it 'returns true if branch does not exist and user has permission to merge' do + project.team << [user, :developer] + + expect(access.can_push_to_branch?(not_existing_branch.name)).to be_truthy + end end describe 'push to protected branch if allowed for developers' do diff --git a/spec/lib/gitlab/view/presenter/base_spec.rb b/spec/lib/gitlab/view/presenter/base_spec.rb new file mode 100644 index 00000000000..f2c152cdcd4 --- /dev/null +++ b/spec/lib/gitlab/view/presenter/base_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe Gitlab::View::Presenter::Base do + let(:project) { double(:project) } + let(:presenter_class) do + Struct.new(:subject).include(described_class) + end + + describe '.presenter?' do + it 'returns true' do + presenter = presenter_class.new(project) + + expect(presenter.class).to be_presenter + end + end + + describe '.presents' do + it 'exposes #subject with the given keyword' do + presenter_class.presents(:foo) + presenter = presenter_class.new(project) + + expect(presenter.foo).to eq(project) + end + end + + describe '#can?' do + context 'user is not allowed' do + it 'returns false' do + presenter = presenter_class.new(build_stubbed(:empty_project)) + + expect(presenter.can?(nil, :read_project)).to be_falsy + end + end + + context 'user is allowed' do + it 'returns true' do + presenter = presenter_class.new(build_stubbed(:empty_project, :public)) + + expect(presenter.can?(nil, :read_project)).to be_truthy + end + end + + context 'subject is overriden' do + it 'returns true' do + presenter = presenter_class.new(build_stubbed(:empty_project, :public)) + + expect(presenter.can?(nil, :read_project, build_stubbed(:empty_project))).to be_falsy + end + end + end +end diff --git a/spec/lib/gitlab/view/presenter/delegated_spec.rb b/spec/lib/gitlab/view/presenter/delegated_spec.rb new file mode 100644 index 00000000000..e9d4af54389 --- /dev/null +++ b/spec/lib/gitlab/view/presenter/delegated_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe Gitlab::View::Presenter::Delegated do + let(:project) { double(:project, user: 'John Doe') } + let(:presenter_class) do + Class.new(described_class) + end + + it 'includes Gitlab::View::Presenter::Base' do + expect(described_class).to include(Gitlab::View::Presenter::Base) + end + + describe '#initialize' do + it 'takes arbitrary key/values and exposes them' do + presenter = presenter_class.new(project, current_user: 'Jane Doe') + + expect(presenter.current_user).to eq('Jane Doe') + end + + it 'raise an error if the presentee already respond to method' do + expect { presenter_class.new(project, user: 'Jane Doe') }. + to raise_error Gitlab::View::Presenter::CannotOverrideMethodError + end + end + + describe 'delegation' do + it 'forwards missing methods to subject' do + presenter = presenter_class.new(project) + + expect(presenter.user).to eq('John Doe') + end + end +end diff --git a/spec/lib/gitlab/view/presenter/factory_spec.rb b/spec/lib/gitlab/view/presenter/factory_spec.rb new file mode 100644 index 00000000000..70d2e22b48f --- /dev/null +++ b/spec/lib/gitlab/view/presenter/factory_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe Gitlab::View::Presenter::Factory do + let(:build) { Ci::Build.new } + + describe '#initialize' do + context 'without optional parameters' do + it 'takes a subject and optional params' do + presenter = described_class.new(build) + + expect { presenter }.not_to raise_error + end + end + + context 'with optional parameters' do + it 'takes a subject and optional params' do + presenter = described_class.new(build, user: 'user') + + expect { presenter }.not_to raise_error + end + end + end + + describe '#fabricate!' do + it 'detects the presenter based on the given subject' do + presenter = described_class.new(build).fabricate! + + expect(presenter).to be_a(Ci::BuildPresenter) + end + end +end diff --git a/spec/lib/gitlab/view/presenter/simple_spec.rb b/spec/lib/gitlab/view/presenter/simple_spec.rb new file mode 100644 index 00000000000..1795ed2405b --- /dev/null +++ b/spec/lib/gitlab/view/presenter/simple_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe Gitlab::View::Presenter::Simple do + let(:project) { double(:project, user: 'John Doe') } + let(:presenter_class) do + Class.new(described_class) + end + + it 'includes Gitlab::View::Presenter::Base' do + expect(described_class).to include(Gitlab::View::Presenter::Base) + end + + describe '#initialize' do + it 'takes arbitrary key/values and exposes them' do + presenter = presenter_class.new(project, current_user: 'Jane Doe') + + expect(presenter.current_user).to eq('Jane Doe') + end + + it 'override the presentee attributes' do + presenter = presenter_class.new(project, user: 'Jane Doe') + + expect(presenter.user).to eq('Jane Doe') + end + end + + describe 'delegation' do + it 'does not forward missing methods to subject' do + presenter = presenter_class.new(project) + + expect { presenter.user }.to raise_error(NoMethodError) + end + end +end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 61da91dcbd3..7dd4d76d1a3 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Workhorse, lib: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } def decode_workhorse_header(array) @@ -174,4 +174,27 @@ describe Gitlab::Workhorse, lib: true do described_class.verify_api_request!(headers) end end + + describe '.git_http_ok' do + let(:user) { create(:user) } + + subject { described_class.git_http_ok(repository, user) } + + it { expect(subject).to eq({ GL_ID: "user-#{user.id}", RepoPath: repository.path_to_repo }) } + + context 'when Gitaly socket path is present' do + let(:gitaly_socket_path) { '/tmp/gitaly.sock' } + + before do + allow(Gitlab.config.gitaly).to receive(:socket_path).and_return(gitaly_socket_path) + end + + it 'includes Gitaly params in the returned value' do + expect(subject).to include({ + GitalyResourcePath: "/projects/#{repository.project.id}/git-http/info-refs", + GitalySocketPath: gitaly_socket_path, + }) + end + end + end end diff --git a/spec/lib/light_url_builder_spec.rb b/spec/lib/light_url_builder_spec.rb index a826b24419a..3fe8cf43934 100644 --- a/spec/lib/light_url_builder_spec.rb +++ b/spec/lib/light_url_builder_spec.rb @@ -99,7 +99,7 @@ describe Gitlab::UrlBuilder, lib: true do context 'on another object' do it 'returns a proper URL' do - project = build_stubbed(:project) + project = build_stubbed(:empty_project) expect { described_class.build(project) }. to raise_error(NotImplementedError, 'No URL builder defined for Project') diff --git a/spec/lib/repository_cache_spec.rb b/spec/lib/repository_cache_spec.rb index f227926f39c..5892f3481a4 100644 --- a/spec/lib/repository_cache_spec.rb +++ b/spec/lib/repository_cache_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe RepositoryCache, lib: true do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:backend) { double('backend').as_null_object } let(:cache) { RepositoryCache.new('example', project.id, backend) } diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 1bdf005c823..2f4a33a1868 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Ability, lib: true do describe '.can_edit_note?' do let(:project) { create(:empty_project) } - let!(:note) { create(:note_on_issue, project: project) } + let(:note) { create(:note_on_issue, project: project) } context 'using an anonymous user' do it 'returns false' do @@ -60,7 +60,7 @@ describe Ability, lib: true do describe '.users_that_can_read_project' do context 'using a public project' do it 'returns all the users' do - project = create(:project, :public) + project = create(:empty_project, :public) user = build(:user) expect(described_class.users_that_can_read_project([user], project)). @@ -69,7 +69,7 @@ describe Ability, lib: true do end context 'using an internal project' do - let(:project) { create(:project, :internal) } + let(:project) { create(:empty_project, :internal) } it 'returns users that are administrators' do user = build(:user, admin: true) @@ -120,7 +120,7 @@ describe Ability, lib: true do end context 'using a private project' do - let(:project) { create(:project, :private) } + let(:project) { create(:empty_project, :private) } it 'returns users that are administrators' do user = build(:user, admin: true) @@ -171,6 +171,33 @@ describe Ability, lib: true do end end + describe '.users_that_can_read_personal_snippet' do + def users_for_snippet(snippet) + described_class.users_that_can_read_personal_snippet(users, snippet) + end + + let(:users) { create_list(:user, 3) } + let(:author) { users[0] } + + it 'private snippet is readable only by its author' do + snippet = create(:personal_snippet, :private, author: author) + + expect(users_for_snippet(snippet)).to match_array([author]) + end + + it 'internal snippet is readable by all registered users' do + snippet = create(:personal_snippet, :public, author: author) + + expect(users_for_snippet(snippet)).to match_array(users) + end + + it 'public snippet is readable by all users' do + snippet = create(:personal_snippet, :public, author: author) + + expect(users_for_snippet(snippet)).to match_array(users) + end + end + describe '.issues_readable_by_user' do context 'with an admin user' do it 'returns all given issues' do @@ -220,7 +247,7 @@ describe Ability, lib: true do end describe '.project_disabled_features_rules' do - let(:project) { create(:project, wiki_access_level: ProjectFeature::DISABLED) } + let(:project) { create(:empty_project, wiki_access_level: ProjectFeature::DISABLED) } subject { described_class.allowed(project.owner, project) } diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 3309a7fff9f..47cd5075a7d 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Ci::Build, :models do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:build) { create(:ci_build, pipeline: pipeline) } let(:test_trace) { 'This is a test' } @@ -1401,4 +1401,14 @@ describe Ci::Build, :models do it { is_expected.to eq(%w[predefined project pipeline yaml secret]) } end end + + describe 'State transition: any => [:pending]' do + let(:build) { create(:ci_build, :created) } + + it 'queues BuildQueueWorker' do + expect(BuildQueueWorker).to receive(:perform_async).with(build.id) + + build.enqueue + end + end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index d1aee27057a..426be74cd02 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -122,55 +122,80 @@ describe Ci::Pipeline, models: true do end end - describe '#stages' do + describe 'pipeline stages' do before do - create(:commit_status, pipeline: pipeline, stage: 'build', name: 'linux', stage_idx: 0, status: 'success') - create(:commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'failed') - create(:commit_status, pipeline: pipeline, stage: 'deploy', name: 'staging', stage_idx: 2, status: 'running') - create(:commit_status, pipeline: pipeline, stage: 'test', name: 'rspec', stage_idx: 1, status: 'success') - end - - subject { pipeline.stages } - - context 'stages list' do - it 'returns ordered list of stages' do - expect(subject.map(&:name)).to eq(%w[build test deploy]) + create(:commit_status, pipeline: pipeline, + stage: 'build', + name: 'linux', + stage_idx: 0, + status: 'success') + + create(:commit_status, pipeline: pipeline, + stage: 'build', + name: 'mac', + stage_idx: 0, + status: 'failed') + + create(:commit_status, pipeline: pipeline, + stage: 'deploy', + name: 'staging', + stage_idx: 2, + status: 'running') + + create(:commit_status, pipeline: pipeline, + stage: 'test', + name: 'rspec', + stage_idx: 1, + status: 'success') + end + + describe '#stages' do + subject { pipeline.stages } + + context 'stages list' do + it 'returns ordered list of stages' do + expect(subject.map(&:name)).to eq(%w[build test deploy]) + end end - end - it 'returns a valid number of stages' do - expect(pipeline.stages_count).to eq(3) - end + context 'stages with statuses' do + let(:statuses) do + subject.map { |stage| [stage.name, stage.status] } + end - it 'returns a valid names of stages' do - expect(pipeline.stages_name).to eq(['build', 'test', 'deploy']) - end + it 'returns list of stages with correct statuses' do + expect(statuses).to eq([['build', 'failed'], + ['test', 'success'], + ['deploy', 'running']]) + end - context 'stages with statuses' do - let(:statuses) do - subject.map do |stage| - [stage.name, stage.status] + context 'when commit status is retried' do + before do + create(:commit_status, pipeline: pipeline, + stage: 'build', + name: 'mac', + stage_idx: 0, + status: 'success') + end + + it 'ignores the previous state' do + expect(statuses).to eq([['build', 'success'], + ['test', 'success'], + ['deploy', 'running']]) + end end end + end - it 'returns list of stages with statuses' do - expect(statuses).to eq([['build', 'failed'], - ['test', 'success'], - ['deploy', 'running'] - ]) + describe '#stages_count' do + it 'returns a valid number of stages' do + expect(pipeline.stages_count).to eq(3) end + end - context 'when build is retried' do - before do - create(:commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'success') - end - - it 'ignores the previous state' do - expect(statuses).to eq([['build', 'success'], - ['test', 'success'], - ['deploy', 'running'] - ]) - end + describe '#stages_name' do + it 'returns a valid names of stages' do + expect(pipeline.stages_name).to eq(['build', 'test', 'deploy']) end end end @@ -259,7 +284,7 @@ describe Ci::Pipeline, models: true do end describe 'merge request metrics' do - let(:project) { FactoryGirl.create :project } + 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!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) } @@ -314,7 +339,7 @@ describe Ci::Pipeline, models: true do end context 'with non-empty project' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, @@ -865,7 +890,7 @@ describe Ci::Pipeline, models: true do end describe "#merge_requests" do - let(:project) { FactoryGirl.create :project } + let(:project) { create(:project, :repository) } let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) } it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do @@ -931,7 +956,7 @@ describe Ci::Pipeline, models: true do end describe 'notifications when pipeline success or failed' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index ef65eb99328..3f32248e52b 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -91,8 +91,7 @@ describe Ci::Runner, models: true do end describe '#can_pick?' do - let(:project) { create(:project) } - let(:pipeline) { create(:ci_pipeline, project: project) } + let(:pipeline) { create(:ci_pipeline) } let(:build) { create(:ci_build, pipeline: pipeline) } let(:runner) { create(:ci_runner) } @@ -263,10 +262,66 @@ describe Ci::Runner, models: true do end end + describe '#tick_runner_queue' do + let(:runner) { create(:ci_runner) } + + it 'returns a new last_update value' do + expect(runner.tick_runner_queue).not_to be_empty + end + end + + describe '#ensure_runner_queue_value' do + let(:runner) { create(:ci_runner) } + + it 'sets a new last_update value when it is called the first time' do + last_update = runner.ensure_runner_queue_value + + expect_value_in_redis.to eq(last_update) + end + + it 'does not change if it is not expired and called again' do + last_update = runner.ensure_runner_queue_value + + expect(runner.ensure_runner_queue_value).to eq(last_update) + expect_value_in_redis.to eq(last_update) + end + + context 'updates runner queue after changing editable value' do + let!(:last_update) { runner.ensure_runner_queue_value } + + before do + runner.update(description: 'new runner') + end + + it 'sets a new last_update value' do + expect_value_in_redis.not_to eq(last_update) + end + end + + context 'does not update runner value after save' do + let!(:last_update) { runner.ensure_runner_queue_value } + + before do + runner.touch + end + + it 'has an old last_update value' do + expect_value_in_redis.to eq(last_update) + end + end + + def expect_value_in_redis + Gitlab::Redis.with do |redis| + runner_queue_key = runner.send(:runner_queue_key) + expect(redis.get(runner_queue_key)) + end + end + end + describe '.assignable_for' do let(:runner) { create(:ci_runner) } - let(:project) { create(:project) } - let(:another_project) { create(:project) } + let(:project) { create(:empty_project) } + let(:another_project) { create(:empty_project) } before do project.runners << runner diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index 742bedb37e4..c4a9743a4e2 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -142,6 +142,78 @@ describe Ci::Stage, models: true do end end + describe '#success?' do + context 'when stage is successful' do + before do + create_job(:ci_build, status: :success) + create_job(:generic_commit_status, status: :success) + end + + it 'is successful' do + expect(stage).to be_success + end + end + + context 'when stage is not successful' do + before do + create_job(:ci_build, status: :failed) + create_job(:generic_commit_status, status: :success) + end + + it 'is not successful' do + expect(stage).not_to be_success + end + end + end + + describe '#has_warnings?' do + context 'when stage has warnings' do + context 'when using memoized warnings flag' do + context 'when there are warnings' do + let(:stage) { build(:ci_stage, warnings: true) } + + it 'has memoized warnings' do + expect(stage).not_to receive(:statuses) + expect(stage).to have_warnings + end + end + + context 'when there are no warnings' do + let(:stage) { build(:ci_stage, warnings: false) } + + it 'has memoized warnings' do + expect(stage).not_to receive(:statuses) + expect(stage).not_to have_warnings + end + end + end + + context 'when calculating warnings from statuses' do + before do + create(:ci_build, :failed, :allowed_to_fail, + stage: stage_name, pipeline: pipeline) + end + + it 'has warnings calculated from statuses' do + expect(stage).to receive(:statuses).and_call_original + expect(stage).to have_warnings + end + end + end + + context 'when stage does not have warnings' do + before do + create(:ci_build, :success, stage: stage_name, + pipeline: pipeline) + end + + it 'does not have warnings calculated from statuses' do + expect(stage).to receive(:statuses).and_call_original + expect(stage).not_to have_warnings + end + end + end + def create_job(type, status: 'success', stage: stage_name) create(type, pipeline: pipeline, stage: stage, status: status) end diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb index 30782ca75a0..e4bddf67096 100644 --- a/spec/models/commit_range_spec.rb +++ b/spec/models/commit_range_spec.rb @@ -7,7 +7,7 @@ describe CommitRange, models: true do it { is_expected.to include_module(Referable) } end - let!(:project) { create(:project, :public) } + let!(:project) { create(:project, :public, :repository) } let!(:commit1) { project.commit("HEAD~2") } let!(:commit2) { project.commit } diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 74b50d2908d..32f9366a14c 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Commit, models: true do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:commit) { project.commit } describe 'modules' do @@ -34,7 +34,7 @@ describe Commit, models: true do end describe '#to_reference' do - let(:project) { create(:project, path: 'sample-project') } + let(:project) { create(:project, :repository, path: 'sample-project') } let(:commit) { project.commit } it 'returns a String reference to the object' do @@ -42,13 +42,13 @@ describe Commit, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:project, :repository, name: 'another-project', namespace: project.namespace) expect(commit.to_reference(another_project)).to eq "sample-project@#{commit.id}" end end describe '#reference_link_text' do - let(:project) { create(:project, path: 'sample-project') } + let(:project) { create(:project, :repository, path: 'sample-project') } let(:commit) { project.commit } it 'returns a String reference to the object' do @@ -56,7 +56,7 @@ describe Commit, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:project, :repository, name: 'another-project', namespace: project.namespace) expect(commit.reference_link_text(another_project)).to eq "sample-project@#{commit.short_id}" end end @@ -131,7 +131,7 @@ eos describe '#closes_issues' do let(:issue) { create :issue, project: project } - let(:other_project) { create :project, :public } + let(:other_project) { create(:empty_project, :public) } let(:other_issue) { create :issue, project: other_project } let(:commiter) { create :user } @@ -154,7 +154,7 @@ eos end it_behaves_like 'a mentionable' do - subject { create(:project).commit } + subject { create(:project, :repository).commit } let(:author) { create(:user, email: subject.author_email) } let(:backref_text) { "commit #{subject.id}" } @@ -323,4 +323,50 @@ eos expect(new_commit.message).to eq(commit.message) end end + + describe '#work_in_progress?' do + ['squash! ', 'fixup! ', 'wip: ', 'WIP: ', '[WIP] '].each do |wip_prefix| + it "detects the '#{wip_prefix}' prefix" do + commit.message = "#{wip_prefix}#{commit.message}" + + expect(commit).to be_work_in_progress + end + end + + it "detects WIP for a commit just saying 'wip'" do + commit.message = "wip" + + expect(commit).to be_work_in_progress + end + + it "doesn't detect WIP for a commit that begins with 'FIXUP! '" do + commit.message = "FIXUP! #{commit.message}" + + expect(commit).not_to be_work_in_progress + end + + it "doesn't detect WIP for words starting with WIP" do + commit.message = "Wipout #{commit.message}" + + expect(commit).not_to be_work_in_progress + end + end + + describe '.valid_hash?' do + it 'checks hash contents' do + expect(described_class.valid_hash?('abcdef01239ABCDEF')).to be true + expect(described_class.valid_hash?("abcdef01239ABCD\nEF")).to be false + expect(described_class.valid_hash?(' abcdef01239ABCDEF ')).to be false + expect(described_class.valid_hash?('Gabcdef01239ABCDEF')).to be false + expect(described_class.valid_hash?('gabcdef01239ABCDEF')).to be false + expect(described_class.valid_hash?('-abcdef01239ABCDEF')).to be false + end + + it 'checks hash length' do + expect(described_class.valid_hash?('a' * 6)).to be false + expect(described_class.valid_hash?('a' * 7)).to be true + expect(described_class.valid_hash?('a' * 40)).to be true + expect(described_class.valid_hash?('a' * 41)).to be false + end + end end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 64ea607eb95..bf4394f7d5b 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe CommitStatus, models: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, project: project, sha: project.commit.id) diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb index 49ab3c4b6e9..da003dbf794 100644 --- a/spec/models/compare_spec.rb +++ b/spec/models/compare_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Compare, models: true do include RepoHelpers - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:commit) { project.commit } let(:start_commit) { sample_image_commit } diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb index 4d0f51fe82a..dbfe3cd2d36 100644 --- a/spec/models/concerns/has_status_spec.rb +++ b/spec/models/concerns/has_status_spec.rb @@ -219,4 +219,10 @@ describe HasStatus do end end end + + describe '::DEFAULT_STATUS' do + it 'is a status created' do + expect(described_class::DEFAULT_STATUS).to eq 'created' + end + end end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index d7d31892e12..545a11912e3 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -301,7 +301,7 @@ describe Issue, "Issuable" do end describe '#labels_array' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:bug) { create(:label, project: project, title: 'bug') } let(:issue) { create(:issue, project: project) } @@ -315,7 +315,7 @@ describe Issue, "Issuable" do end describe '#user_notes_count' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:issue1) { create(:issue, project: project) } let(:issue2) { create(:issue, project: project) } @@ -359,7 +359,7 @@ describe Issue, "Issuable" do end describe ".with_label" do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:bug) { create(:label, project: project, title: 'bug') } let(:feature) { create(:label, project: project, title: 'feature') } let(:enhancement) { create(:label, project: project, title: 'enhancement') } diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index 132858950d5..2092576e981 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -13,7 +13,7 @@ describe Mentionable do end describe 'references' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:mentionable) { Example.new } it 'excludes JIRA references' do @@ -30,12 +30,20 @@ describe Issue, "Mentionable" do describe '#mentioned_users' do let!(:user) { create(:user, username: 'stranger') } let!(:user2) { create(:user, username: 'john') } - let!(:issue) { create(:issue, description: "#{user.to_reference} mentioned") } + let!(:user3) { create(:user, username: 'jim') } + let(:issue) { create(:issue, description: "#{user.to_reference} mentioned") } subject { issue.mentioned_users } - it { is_expected.to include(user) } - it { is_expected.not_to include(user2) } + it { expect(subject).to contain_exactly(user) } + + context 'when a note on personal snippet' do + let!(:note) { create(:note_on_personal_snippet, note: "#{user.to_reference} mentioned #{user3.to_reference}") } + + subject { note.mentioned_users } + + it { expect(subject).to contain_exactly(user, user3) } + end end describe '#referenced_mentionables' do @@ -75,13 +83,13 @@ describe Issue, "Mentionable" do end describe '#create_cross_references!' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:author) { build(:user) } let(:commit) { project.commit } let(:commit2) { project.commit } let!(:issue) do - create(:issue, project: project, description: commit.to_reference) + create(:issue, project: project, description: "See #{commit.to_reference}") end it 'correctly removes already-mentioned Commits' do @@ -92,7 +100,7 @@ describe Issue, "Mentionable" do end describe '#create_new_cross_references!' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:author) { create(:author) } let(:issues) { create_list(:issue, 2, project: project, author: author) } @@ -138,6 +146,16 @@ describe Issue, "Mentionable" do issue.update_attributes(description: issues[1].to_reference) issue.create_new_cross_references! end + + it 'notifies new references from project snippet note' do + snippet = create(:snippet, project: project) + note = create(:note, note: issues[0].to_reference, noteable: snippet, project: project, author: author) + + expect(SystemNoteService).to receive(:cross_reference).with(issues[1], any_args) + + note.update_attributes(note: issues[1].to_reference) + note.create_new_cross_references! + end end def create_issue(description:) diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index 0e097559b59..ad703a6c8bb 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -7,7 +7,7 @@ describe Milestone, 'Milestoneish' do let(:member) { create(:user) } let(:guest) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:milestone) { create(:milestone, project: project) } let!(:issue) { create(:issue, project: project, milestone: milestone) } let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone) } diff --git a/spec/models/concerns/presentable_spec.rb b/spec/models/concerns/presentable_spec.rb new file mode 100644 index 00000000000..941647a79fb --- /dev/null +++ b/spec/models/concerns/presentable_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Presentable do + let(:build) { Ci::Build.new } + + describe '#present' do + it 'returns a presenter' do + expect(build.present).to be_a(Ci::BuildPresenter) + end + + it 'takes optional attributes' do + expect(build.present(foo: 'bar').foo).to eq('bar') + end + end +end diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb index 9041690023f..6cf5877424d 100644 --- a/spec/models/concerns/project_features_compatibility_spec.rb +++ b/spec/models/concerns/project_features_compatibility_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ProjectFeaturesCompatibility do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:features) { %w(issues wiki builds merge_requests snippets) } # We had issues_enabled, snippets_enabled, builds_enabled, merge_requests_enabled and issues_enabled fields on projects table diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index b556135532f..30443534cca 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -68,4 +68,14 @@ describe Group, 'Routable' do end end end + + describe '.member_descendants' do + let!(:user) { create(:user) } + let!(:nested_group) { create(:group, parent: group) } + + before { group.add_owner(user) } + subject { described_class.member_descendants(user.id) } + + it { is_expected.to eq([nested_group]) } + end end diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb index 70f985afefb..3b7cc7d9e2e 100644 --- a/spec/models/cycle_analytics/code_spec.rb +++ b/spec/models/cycle_analytics/code_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#code', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb index e4b6a8f4518..5c73edbbc53 100644 --- a/spec/models/cycle_analytics/issue_spec.rb +++ b/spec/models/cycle_analytics/issue_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#issue', models: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb index dc5b04852d6..55483fc876a 100644 --- a/spec/models/cycle_analytics/plan_spec.rb +++ b/spec/models/cycle_analytics/plan_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#plan', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb index 5e99188f318..591bbdddf55 100644 --- a/spec/models/cycle_analytics/production_spec.rb +++ b/spec/models/cycle_analytics/production_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#production', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb index 45baa5f7006..33d2c0a7416 100644 --- a/spec/models/cycle_analytics/review_spec.rb +++ b/spec/models/cycle_analytics/review_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#review', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb index 77625aad580..00693d67475 100644 --- a/spec/models/cycle_analytics/staging_spec.rb +++ b/spec/models/cycle_analytics/staging_spec.rb @@ -3,9 +3,10 @@ require 'spec_helper' describe 'CycleAnalytics#staging', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } + subject { CycleAnalytics.new(project, from: from_date) } generate_cycle_analytics_spec( diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb index 27a117d2d76..f857ea6cbec 100644 --- a/spec/models/cycle_analytics/test_spec.rb +++ b/spec/models/cycle_analytics/test_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#test', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb index 8a1e337c1a3..aacc178a19e 100644 --- a/spec/models/deploy_keys_project_spec.rb +++ b/spec/models/deploy_keys_project_spec.rb @@ -12,7 +12,7 @@ describe DeployKeysProject, models: true do end describe "Destroying" do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } subject { create(:deploy_keys_project, project: project) } let(:deploy_key) { subject.deploy_key } @@ -39,7 +39,7 @@ describe DeployKeysProject, models: true do end context "when the deploy key is used by more than one project" do - let!(:other_project) { create(:project) } + let!(:other_project) { create(:empty_project) } before do other_project.deploy_keys << deploy_key diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index ca594a320c0..fc4435a2f64 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -17,7 +17,7 @@ describe Deployment, models: true do it { is_expected.to validate_presence_of(:sha) } describe '#includes_commit?' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:environment) { create(:environment, project: project) } let(:deployment) do create(:deployment, environment: environment, sha: project.commit.id) diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index 3db5937a4f3..9ea3a4b7020 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe DiffNote, models: true do include RepoHelpers - let(:project) { create(:project) } - let(:merge_request) { create(:merge_request, source_project: project) } + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.project } let(:commit) { project.commit(sample_commit.id) } let(:path) { "files/ruby/popen.rb" } diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 96efe1696c3..b2e06541e66 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -32,7 +32,7 @@ describe Environment, models: true do end describe '#includes_commit?' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } context 'without a last deployment' do it "returns false" do @@ -81,7 +81,7 @@ describe Environment, models: true do end describe '#first_deployment_for' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let!(:deployment) { create(:deployment, environment: environment, ref: commit.parent.id) } let!(:deployment1) { create(:deployment, environment: environment, ref: commit.id) } let(:head_commit) { project.commit } diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index f8660da031d..349474bb656 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -27,7 +27,7 @@ describe Event, models: true do end describe "Push event" do - let(:project) { create(:project, :private) } + let(:project) { create(:empty_project, :private) } let(:user) { project.owner } let(:event) { create_event(project, user) } @@ -187,7 +187,7 @@ describe Event, models: true do end context 'merge request diff note event' do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:merge_request) { create(:merge_request, source_project: project, author: author, assignee: assignee) } let(:note_on_merge_request) { create(:legacy_diff_note_on_merge_request, noteable: merge_request, project: project) } let(:target) { note_on_merge_request } @@ -202,7 +202,7 @@ describe Event, models: true do end context 'private project' do - let(:project) { create(:project, :private) } + let(:project) { create(:empty_project, :private) } it do expect(event.visible_to_user?(non_member)).to eq false diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb index 1863581f57b..454550c9710 100644 --- a/spec/models/forked_project_link_spec.rb +++ b/spec/models/forked_project_link_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ForkedProjectLink, "add link on fork" do - let(:project_from) { create(:project) } + let(:project_from) { create(:project, :repository) } let(:namespace) { create(:namespace) } let(:user) { create(:user, namespace: namespace) } @@ -21,7 +21,7 @@ end describe '#forked?' do let(:forked_project_link) { build(:forked_project_link) } - let(:project_from) { create(:project) } + let(:project_from) { create(:project, :repository) } let(:project_to) { create(:project, forked_project_link: forked_project_link) } before :each do diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index 6004bfdb7b7..f4c3e6d503f 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -1,10 +1,20 @@ require 'spec_helper' describe GenericCommitStatus, models: true do - let(:pipeline) { create(:ci_pipeline) } + let(:project) { create(:empty_project) } + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:external_url) { 'http://example.gitlab.com/status' } let(:generic_commit_status) do - create(:generic_commit_status, pipeline: pipeline) + create(:generic_commit_status, pipeline: pipeline, + target_url: external_url) + end + + describe 'validations' do + it { is_expected.to validate_length_of(:target_url).is_at_most(255) } + it { is_expected.to allow_value(nil).for(:target_url) } + it { is_expected.to allow_value('http://gitlab.com/s').for(:target_url) } + it { is_expected.not_to allow_value('javascript:alert(1)').for(:target_url) } end describe '#context' do @@ -22,10 +32,25 @@ describe GenericCommitStatus, models: true do describe '#detailed_status' do let(:user) { create(:user) } + let(:status) { generic_commit_status.detailed_status(user) } it 'returns detailed status object' do - expect(generic_commit_status.detailed_status(user)) - .to be_a Gitlab::Ci::Status::Success + expect(status).to be_a Gitlab::Ci::Status::Success + end + + context 'when user has ability to see datails' do + before { project.team << [user, :developer] } + + it 'details path points to an external URL' do + expect(status).to have_details + expect(status.details_path).to eq external_url + end + end + + context 'when user should not see details' do + it 'does not have details' do + expect(status).not_to have_details + end end end diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb index d87684fd49e..cacbab8bcb1 100644 --- a/spec/models/global_milestone_spec.rb +++ b/spec/models/global_milestone_spec.rb @@ -4,9 +4,9 @@ describe GlobalMilestone, models: true do let(:user) { create(:user) } let(:user2) { create(:user) } let(:group) { create(:group) } - let(:project1) { create(:project, group: group) } - let(:project2) { create(:project, path: 'gitlab-ci', group: group) } - let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) } + let(:project1) { create(:empty_project, group: group) } + let(:project2) { create(:empty_project, path: 'gitlab-ci', group: group) } + let(:project3) { create(:empty_project, path: 'cookbook-gitlab', group: group) } describe '.build_collection' do let(:milestone1_due_date) { 2.weeks.from_now.to_date } diff --git a/spec/models/group_milestone_spec.rb b/spec/models/group_milestone_spec.rb index 601167c3bd3..916afb7aaf5 100644 --- a/spec/models/group_milestone_spec.rb +++ b/spec/models/group_milestone_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe GroupMilestone, models: true do let(:group) { create(:group) } - let(:project) { create(:project, group: group) } + let(:project) { create(:empty_project, group: group) } let(:project_milestone) do create(:milestone, title: "Milestone v1.2", project: project) end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 45fe927202b..9ca50555191 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -81,13 +81,19 @@ describe Group, models: true do describe 'public_only' do subject { described_class.public_only.to_a } - it{ is_expected.to eq([group]) } + it { is_expected.to eq([group]) } end describe 'public_and_internal_only' do subject { described_class.public_and_internal_only.to_a } - it{ is_expected.to match_array([group, internal_group]) } + it { is_expected.to match_array([group, internal_group]) } + end + + describe 'non_public_only' do + subject { described_class.non_public_only.to_a } + + it { is_expected.to match_array([private_group, internal_group]) } end end @@ -269,6 +275,12 @@ describe Group, models: true do it 'returns the canonical URL' do expect(group.web_url).to include("groups/#{group.name}") end + + context 'nested group' do + let(:nested_group) { create(:group, :nested) } + + it { expect(nested_group.web_url).to include("groups/#{nested_group.full_path}") } + end end describe 'nested group' do diff --git a/spec/models/guest_spec.rb b/spec/models/guest_spec.rb index d79f929f7a1..582b54c0712 100644 --- a/spec/models/guest_spec.rb +++ b/spec/models/guest_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' describe Guest, lib: true do - let(:public_project) { create(:project, :public) } - let(:private_project) { create(:project, :private) } - let(:internal_project) { create(:project, :internal) } + let(:public_project) { build_stubbed(:empty_project, :public) } + let(:private_project) { build_stubbed(:empty_project, :private) } + let(:internal_project) { build_stubbed(:empty_project, :internal) } describe '.can_pull?' do context 'when project is private' do diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index ad2b710041a..e8caad00c44 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -4,7 +4,7 @@ describe SystemHook, models: true do describe "execute" do let(:system_hook) { create(:system_hook) } let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:empty_project, namespace: user.namespace) } let(:group) { create(:group) } before do diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index e52b9d75cef..9d4db1bfb52 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -25,7 +25,7 @@ describe WebHook, models: true do end describe "execute" do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:project_hook) { create(:project_hook) } before(:each) do diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb index 2459a49f095..08712f2a768 100644 --- a/spec/models/issue/metrics_spec.rb +++ b/spec/models/issue/metrics_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Issue::Metrics, models: true do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } subject { create(:issue, project: project) } diff --git a/spec/models/issue_collection_spec.rb b/spec/models/issue_collection_spec.rb index d742c814680..d8aed25c041 100644 --- a/spec/models/issue_collection_spec.rb +++ b/spec/models/issue_collection_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe IssueCollection do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:issue1) { create(:issue, project: project) } let(:issue2) { create(:issue, project: project) } let(:collection) { described_class.new([issue1, issue2]) } diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index bfa36d92ac3..40c0a75c364 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -35,7 +35,7 @@ describe Issue, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:empty_project, name: 'another-project', namespace: project.namespace) expect(issue.to_reference(another_project)).to eq "sample-project#1" end end @@ -60,9 +60,9 @@ describe Issue, models: true do end describe '#closed_by_merge_requests' do - let(:project) { create(:project) } - let(:issue) { create(:issue, project: project, state: "opened")} - let(:closed_issue) { build(:issue, project: project, state: "closed")} + let(:project) { create(:project, :repository) } + let(:issue) { create(:issue, project: project)} + let(:closed_issue) { build(:issue, :closed, project: project)} let(:mr) do opts = { @@ -104,7 +104,7 @@ describe Issue, models: true do describe '#referenced_merge_requests' do it 'returns the referenced merge requests' do - project = create(:project, :public) + project = create(:empty_project, :public) mr1 = create(:merge_request, source_project: project, @@ -137,7 +137,7 @@ describe Issue, models: true do end context 'user is reporter in project issue belongs to' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:issue) { create(:issue, project: project) } before { project.team << [user, :reporter] } @@ -151,7 +151,7 @@ describe Issue, models: true do context 'checking destination project also' do subject { issue.can_move?(user, to_project) } - let(:to_project) { create(:project) } + let(:to_project) { create(:empty_project) } context 'destination project allowed' do before { to_project.team << [user, :reporter] } @@ -217,7 +217,7 @@ describe Issue, models: true do end it_behaves_like 'an editable mentionable' do - subject { create(:issue) } + subject { create(:issue, project: create(:project, :repository)) } let(:backref_text) { "issue #{subject.to_reference}" } let(:set_mentionable_text) { ->(txt){ subject.description = txt } } @@ -246,7 +246,7 @@ describe Issue, models: true do describe '#participants' do context 'using a public project' do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:issue) { create(:issue, project: project) } let!(:note1) do @@ -268,7 +268,7 @@ describe Issue, models: true do context 'using a private project' do it 'does not include mentioned users that do not have access to the project' do - project = create(:project) + project = create(:empty_project) user = create(:user) issue = create(:issue, project: project) diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index 5eaddd822be..7c40cfd8253 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -30,11 +30,30 @@ describe Key, models: true do end describe "#update_last_used_at" do - it "enqueues a UseKeyWorker job" do - key = create(:key) + let(:key) { create(:key) } + + context 'when key was not updated during the last day' do + before do + allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain). + and_return('000000') + end + + it 'enqueues a UseKeyWorker job' do + expect(UseKeyWorker).to receive(:perform_async).with(key.id) + key.update_last_used_at + end + end + + context 'when key was updated during the last day' do + before do + allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain). + and_return(false) + end - expect(UseKeyWorker).to receive(:perform_async).with(key.id) - key.update_last_used_at + it 'does not enqueue a UseKeyWorker job' do + expect(UseKeyWorker).not_to receive(:perform_async) + key.update_last_used_at + end end end end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 4f7c8a36cb5..16e2144d6a1 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -481,7 +481,7 @@ describe Member, models: true do describe "destroying a record", truncate: true do it "refreshes user's authorized projects" do - project = create(:project, :private) + project = create(:empty_project, :private) user = create(:user) member = project.team << [user, :reporter] diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 68f72f5c86e..90d14c2c0b9 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -83,8 +83,8 @@ describe ProjectMember, models: true do describe '.import_team' do before do - @project_1 = create :project - @project_2 = create :project + @project_1 = create(:empty_project) + @project_2 = create(:empty_project) @user_1 = create :user @user_2 = create :user @@ -131,8 +131,8 @@ describe ProjectMember, models: true do describe '.truncate_teams' do before do - @project_1 = create :project - @project_2 = create :project + @project_1 = create(:empty_project) + @project_2 = create(:empty_project) @user_1 = create :user @user_2 = create :user diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb index 255db41cb19..9afed311e27 100644 --- a/spec/models/merge_request/metrics_spec.rb +++ b/spec/models/merge_request/metrics_spec.rb @@ -1,9 +1,7 @@ require 'spec_helper' describe MergeRequest::Metrics, models: true do - let(:project) { create(:project) } - - subject { create(:merge_request, source_project: project) } + subject { create(:merge_request) } describe "when recording the default set of metrics on merge request save" do it "records the merge time" do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 861426acbc3..32ed1e96749 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -65,7 +65,7 @@ describe MergeRequest, models: true do end describe '#target_branch_sha' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } subject { create(:merge_request, source_project: project, target_project: project) } @@ -150,7 +150,7 @@ describe MergeRequest, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:empty_project, name: 'another-project', namespace: project.namespace) expect(merge_request.to_reference(another_project)).to eq "sample-project!1" end @@ -245,8 +245,8 @@ describe MergeRequest, models: true do describe '#for_fork?' do it 'returns true if the merge request is for a fork' do - subject.source_project = create(:project, namespace: create(:group)) - subject.target_project = create(:project, namespace: create(:group)) + subject.source_project = build_stubbed(:empty_project, namespace: create(:group)) + subject.target_project = build_stubbed(:empty_project, namespace: create(:group)) expect(subject.for_fork?).to be_truthy end @@ -501,8 +501,8 @@ describe MergeRequest, models: true do end describe '#diverged_commits_count' do - let(:project) { create(:project) } - let(:fork_project) { create(:project, forked_from_project: project) } + let(:project) { create(:project, :repository) } + let(:fork_project) { create(:project, :repository, forked_from_project: project) } context 'when the target branch does not exist anymore' do subject { create(:merge_request, source_project: project, target_project: project) } @@ -727,7 +727,7 @@ describe MergeRequest, models: true do end describe '#participants' do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:mr) do create(:merge_request, source_project: project, target_project: project) @@ -768,15 +768,17 @@ describe MergeRequest, models: true do end describe '#check_if_can_be_merged' do - let(:project) { create(:project, only_allow_merge_if_build_succeeds: true) } + let(:project) { create(:empty_project, only_allow_merge_if_build_succeeds: true) } subject { create(:merge_request, source_project: project, merge_status: :unchecked) } context 'when it is not broken and has no conflicts' do - it 'is marked as mergeable' do + before do allow(subject).to receive(:broken?) { false } allow(project.repository).to receive(:can_be_merged?).and_return(true) + end + it 'is marked as mergeable' do expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged') end end @@ -787,6 +789,12 @@ describe MergeRequest, models: true do it 'becomes unmergeable' do expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') end + + it 'creates Todo on unmergeability' do + expect_any_instance_of(TodoService).to receive(:merge_request_became_unmergeable).with(subject) + + subject.check_if_can_be_merged + end end context 'when it has conflicts' do @@ -802,7 +810,7 @@ describe MergeRequest, models: true do end describe '#mergeable?' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } subject { create(:merge_request, source_project: project) } @@ -822,7 +830,7 @@ describe MergeRequest, models: true do end describe '#mergeable_state?' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } subject { create(:merge_request, source_project: project) } @@ -949,7 +957,7 @@ describe MergeRequest, models: true do let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project) } context 'when project.only_allow_merge_if_all_discussions_are_resolved == true' do - let(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: true) } + let(:project) { create(:project, :repository, only_allow_merge_if_all_discussions_are_resolved: true) } context 'with all discussions resolved' do before do @@ -983,7 +991,7 @@ describe MergeRequest, models: true do end context 'when project.only_allow_merge_if_all_discussions_are_resolved == false' do - let(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: false) } + let(:project) { create(:project, :repository, only_allow_merge_if_all_discussions_are_resolved: false) } context 'with unresolved discussions' do before do @@ -998,7 +1006,7 @@ describe MergeRequest, models: true do end describe "#environments" do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, source_project: project) } context 'with multiple environments' do @@ -1016,7 +1024,7 @@ describe MergeRequest, models: true do context 'with environments on source project' do let(:source_project) do - create(:project) do |fork_project| + create(:project, :repository) do |fork_project| fork_project.create_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) end end @@ -1393,8 +1401,8 @@ describe MergeRequest, models: true do end describe "#source_project_missing?" do - let(:project) { create(:project) } - let(:fork_project) { create(:project, forked_from_project: project) } + let(:project) { create(:empty_project) } + let(:fork_project) { create(:empty_project, forked_from_project: project) } let(:user) { create(:user) } let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } @@ -1431,8 +1439,8 @@ describe MergeRequest, models: true do end describe "#closed_without_fork?" do - let(:project) { create(:project) } - let(:fork_project) { create(:project, forked_from_project: project) } + let(:project) { create(:empty_project) } + let(:fork_project) { create(:empty_project, forked_from_project: project) } let(:user) { create(:user) } let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } @@ -1477,9 +1485,9 @@ describe MergeRequest, models: true do end context 'forked project' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:user) { create(:user) } - let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) } + let(:fork_project) { create(:empty_project, forked_from_project: project, namespace: user.namespace) } let!(:merge_request) do create(:closed_merge_request, @@ -1523,7 +1531,7 @@ describe MergeRequest, models: true do status: status) end - let(:project) { create(:project, :public, only_allow_merge_if_build_succeeds: true) } + let(:project) { create(:project, :public, :repository, only_allow_merge_if_build_succeeds: true) } let(:developer) { create(:user) } let(:user) { create(:user) } let(:merge_request) { create(:merge_request, source_project: project) } diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 064f29d2d66..3cee2b7714f 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -24,7 +24,7 @@ describe Milestone, models: true do it { is_expected.to have_many(:issues) } end - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:milestone) { create(:milestone, project: project) } let(:issue) { create(:issue, project: project) } let(:user) { create(:user) } @@ -44,7 +44,7 @@ describe Milestone, models: true do end it "accepts the same title in another project" do - project = build(:project) + project = build(:empty_project) new_milestone = Milestone.new(project: project, title: milestone.title) expect(new_milestone).to be_valid @@ -257,7 +257,7 @@ describe Milestone, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:empty_project, name: 'another-project', namespace: project.namespace) expect(milestone.to_reference(another_project)).to eq "sample-project%1" end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 600538ff5f4..4e96f19eb6f 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -5,6 +5,8 @@ describe Namespace, models: true do it { is_expected.to have_many :projects } it { is_expected.to have_many :project_statistics } + it { is_expected.to belong_to :parent } + it { is_expected.to have_many :children } it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) } @@ -105,7 +107,7 @@ describe Namespace, models: true do describe '#move_dir' do before do @namespace = create :namespace - @project = create :project, namespace: @namespace + @project = create(:empty_project, namespace: @namespace) allow(@namespace).to receive(:path_changed?).and_return(true) end @@ -117,6 +119,7 @@ describe Namespace, models: true do new_path = @namespace.path + "_new" allow(@namespace).to receive(:path_was).and_return(@namespace.path) allow(@namespace).to receive(:path).and_return(new_path) + expect(@namespace).to receive(:remove_exports!) expect(@namespace.move_dir).to be_truthy end @@ -136,14 +139,20 @@ describe Namespace, models: true do end describe :rm_dir do - let!(:project) { create(:project, namespace: namespace) } + let!(:project) { create(:empty_project, namespace: namespace) } let!(:path) { File.join(Gitlab.config.repositories.storages.default, namespace.path) } - before { namespace.destroy } - it "removes its dirs when deleted" do + namespace.destroy + expect(File.exist?(path)).to be(false) end + + it 'removes the exports folder' do + expect(namespace).to receive(:remove_exports!) + + namespace.destroy + end end describe '.find_by_path_or_name' do @@ -182,17 +191,31 @@ describe Namespace, models: true do it { expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}") } end - describe '#parents' do + describe '#ancestors' do let(:group) { create(:group) } let(:nested_group) { create(:group, parent: group) } let(:deep_nested_group) { create(:group, parent: nested_group) } let(:very_deep_nested_group) { create(:group, parent: deep_nested_group) } - it 'returns the correct parents' do - expect(very_deep_nested_group.parents).to eq([group, nested_group, deep_nested_group]) - expect(deep_nested_group.parents).to eq([group, nested_group]) - expect(nested_group.parents).to eq([group]) - expect(group.parents).to eq([]) + it 'returns the correct ancestors' do + expect(very_deep_nested_group.ancestors).to eq([group, nested_group, deep_nested_group]) + expect(deep_nested_group.ancestors).to eq([group, nested_group]) + expect(nested_group.ancestors).to eq([group]) + expect(group.ancestors).to eq([]) + end + end + + describe '#descendants' do + let!(:group) { create(:group) } + let!(:nested_group) { create(:group, parent: group) } + let!(:deep_nested_group) { create(:group, parent: nested_group) } + let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) } + + it 'returns the correct descendants' do + expect(very_deep_nested_group.descendants.to_a).to eq([]) + expect(deep_nested_group.descendants.to_a).to eq([very_deep_nested_group]) + expect(nested_group.descendants.to_a).to eq([deep_nested_group, very_deep_nested_group]) + expect(group.descendants.to_a).to eq([nested_group, deep_nested_group, very_deep_nested_group]) end end end diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb index b76513d2a3c..492c4e01bd8 100644 --- a/spec/models/network/graph_spec.rb +++ b/spec/models/network/graph_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Network::Graph, models: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let!(:note_on_commit) { create(:note_on_commit, project: project) } it '#initialize' do diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 310fecd8a5c..1cde9e04951 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -42,7 +42,7 @@ describe Note, models: true do context 'when noteable and note project differ' do subject do build(:note, noteable: build_stubbed(:issue), - project: build_stubbed(:project)) + project: build_stubbed(:empty_project)) end it { is_expected.to be_invalid } @@ -52,6 +52,19 @@ describe Note, models: true do subject { create(:note) } it { is_expected.to be_valid } end + + context 'when project is missing for a project related note' do + subject { build(:note, project: nil, noteable: build_stubbed(:issue)) } + it { is_expected.to be_invalid } + end + + context 'when noteable is a personal snippet' do + subject { build(:note_on_personal_snippet) } + + it 'is valid without project' do + is_expected.to be_valid + end + end end describe "Commit notes" do @@ -80,8 +93,8 @@ describe Note, models: true do describe 'authorization' do before do - @p1 = create(:project) - @p2 = create(:project) + @p1 = create(:empty_project) + @p2 = create(:empty_project) @u1 = create(:user) @u2 = create(:user) @u3 = create(:user) @@ -125,7 +138,7 @@ describe Note, models: true do it_behaves_like 'an editable mentionable' do subject { create :note, noteable: issue, project: issue.project } - let(:issue) { create :issue } + let(:issue) { create(:issue, project: create(:project, :repository)) } let(:backref_text) { issue.gfm_reference } let(:set_mentionable_text) { ->(txt) { subject.note = txt } } end @@ -139,6 +152,7 @@ describe Note, models: true do with([{ text: note1.note, context: { + skip_project_check: false, pipeline: :note, cache_key: [note1, "note"], project: note1.project, @@ -150,6 +164,7 @@ describe Note, models: true do with([{ text: note2.note, context: { + skip_project_check: false, pipeline: :note, cache_key: [note2, "note"], project: note2.project, @@ -176,10 +191,10 @@ describe Note, models: true do describe "cross_reference_not_visible_for?" do let(:private_user) { create(:user) } - let(:private_project) { create(:project, namespace: private_user.namespace).tap { |p| p.team << [private_user, :master] } } + let(:private_project) { create(:empty_project, namespace: private_user.namespace) { |p| p.team << [private_user, :master] } } let(:private_issue) { create(:issue, project: private_project) } - let(:ext_proj) { create(:project, :public) } + let(:ext_proj) { create(:empty_project, :public) } let(:ext_issue) { create(:issue, project: ext_proj) } let(:note) do @@ -222,7 +237,7 @@ describe Note, models: true do describe '#participants' do it 'includes the note author' do - project = create(:project, :public) + project = create(:empty_project, :public) issue = create(:issue, project: project) note = create(:note_on_issue, noteable: issue, project: project) @@ -306,4 +321,70 @@ describe Note, models: true do end end end + + describe '#for_personal_snippet?' do + it 'returns false for a project snippet note' do + expect(build(:note_on_project_snippet).for_personal_snippet?).to be_falsy + end + + it 'returns true for a personal snippet note' do + expect(build(:note_on_personal_snippet).for_personal_snippet?).to be_truthy + end + end + + describe '#to_ability_name' do + it 'returns snippet for a project snippet note' do + expect(build(:note_on_project_snippet).to_ability_name).to eq('snippet') + end + + it 'returns personal_snippet for a personal snippet note' do + expect(build(:note_on_personal_snippet).to_ability_name).to eq('personal_snippet') + end + + it 'returns merge_request for an MR note' do + expect(build(:note_on_merge_request).to_ability_name).to eq('merge_request') + end + + it 'returns issue for an issue note' do + expect(build(:note_on_issue).to_ability_name).to eq('issue') + end + + it 'returns issue for a commit note' do + expect(build(:note_on_commit).to_ability_name).to eq('commit') + end + end + + describe '#cache_markdown_field' do + let(:html) { '<p>some html</p>'} + + context 'note for a project snippet' do + let(:note) { build(:note_on_project_snippet) } + + before do + expect(Banzai::Renderer).to receive(:cacheless_render_field). + with(note, :note, { skip_project_check: false }).and_return(html) + + note.save + end + + it 'creates a note' do + expect(note.note_html).to eq(html) + end + end + + context 'note for a personal snippet' do + let(:note) { build(:note_on_personal_snippet) } + + before do + expect(Banzai::Renderer).to receive(:cacheless_render_field). + with(note, :note, { skip_project_check: true }).and_return(html) + + note.save + end + + it 'creates a note' do + expect(note.note_html).to eq(html) + end + end + end end diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb index a55d43ab2f9..8589f1eb712 100644 --- a/spec/models/project_feature_spec.rb +++ b/spec/models/project_feature_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ProjectFeature do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:user) { create(:user) } describe '#feature_available?' do @@ -35,7 +35,7 @@ describe ProjectFeature do it "returns true when user is a member of project group" do group = create(:group) - project = create(:project, namespace: group) + project = create(:empty_project, namespace: group) group.add_developer(user) features.each do |feature| diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb index 47397a822c1..59a4ae1b799 100644 --- a/spec/models/project_group_link_spec.rb +++ b/spec/models/project_group_link_spec.rb @@ -17,7 +17,7 @@ describe ProjectGroupLink do describe "destroying a record", truncate: true do it "refreshes group users' authorized projects" do - project = create(:project, :private) + project = create(:empty_project, :private) group = create(:group) reporter = create(:user) group_users = group.users diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index 4d538cac007..9cdbfa44e5b 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -100,7 +100,7 @@ describe ProjectLabel, models: true do end context 'cross project reference' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'using name' do it 'returns cross reference with label name' do diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb index 8e5145e824b..48aef3a93f2 100644 --- a/spec/models/project_services/asana_service_spec.rb +++ b/spec/models/project_services/asana_service_spec.rb @@ -18,7 +18,7 @@ describe AsanaService, models: true do describe 'Execute' do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } def create_data_for_commits(*messages) { diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb index 4c5acb7990b..96f00af898e 100644 --- a/spec/models/project_services/assembla_service_spec.rb +++ b/spec/models/project_services/assembla_service_spec.rb @@ -8,7 +8,7 @@ describe AssemblaService, models: true do describe "Execute" do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do @assembla_service = AssemblaService.new diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb index a3b9d084a75..953e664fb66 100644 --- a/spec/models/project_services/campfire_service_spec.rb +++ b/spec/models/project_services/campfire_service_spec.rb @@ -22,7 +22,7 @@ describe CampfireService, models: true do describe "#execute" do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do @campfire_service = CampfireService.new diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb index 42c2ed668bc..f9307d6de7b 100644 --- a/spec/models/project_services/drone_ci_service_spec.rb +++ b/spec/models/project_services/drone_ci_service_spec.rb @@ -27,7 +27,7 @@ describe DroneCiService, models: true, caching: true do shared_context :drone_ci_service do let(:drone) { DroneCiService.new } - let(:project) { create(:project, name: 'project') } + let(:project) { create(:project, :repository, name: 'project') } let(:path) { "#{project.namespace.path}/#{project.path}" } let(:drone_url) { 'http://drone.example.com' } let(:sha) { '2ab7834c' } diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb index 342d86aeca9..bdeea1db1e3 100644 --- a/spec/models/project_services/external_wiki_service_spec.rb +++ b/spec/models/project_services/external_wiki_service_spec.rb @@ -23,7 +23,7 @@ describe ExternalWikiService, models: true do end describe 'External wiki' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'when it is active' do before do diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb index d6db02d6e76..a97e8c6e4ce 100644 --- a/spec/models/project_services/flowdock_service_spec.rb +++ b/spec/models/project_services/flowdock_service_spec.rb @@ -22,7 +22,7 @@ describe FlowdockService, models: true do describe "Execute" do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do @flowdock_service = FlowdockService.new diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb index 529044d1d8b..a13fbae03eb 100644 --- a/spec/models/project_services/gemnasium_service_spec.rb +++ b/spec/models/project_services/gemnasium_service_spec.rb @@ -24,7 +24,7 @@ describe GemnasiumService, models: true do describe "Execute" do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do @gemnasium_service = GemnasiumService.new diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb index 9b80f0e7296..dcb70ee28a8 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -8,21 +8,21 @@ describe GitlabIssueTrackerService, models: true do describe 'Validations' do context 'when service is active' do - subject { described_class.new(project: create(:project), active: true) } + subject { described_class.new(project: create(:empty_project), active: true) } it { is_expected.to validate_presence_of(:issues_url) } it_behaves_like 'issue tracker service URL attribute', :issues_url end context 'when service is inactive' do - subject { described_class.new(project: create(:project), active: false) } + subject { described_class.new(project: create(:empty_project), active: false) } it { is_expected.not_to validate_presence_of(:issues_url) } end end describe 'project and issue urls' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'with absolute urls' do before do diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index 2da3a9cb09f..bf422ac7ce1 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -22,8 +22,8 @@ describe HipchatService, models: true do describe "Execute" do let(:hipchat) { HipchatService.new } - let(:user) { create(:user, username: 'username') } - let(:project) { create(:project, name: 'project') } + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' } let(:project_name) { project.name_with_namespace.gsub(/\s/, '') } let(:token) { 'verySecret' } @@ -165,7 +165,7 @@ describe HipchatService, models: true do context "Note events" do let(:user) { create(:user) } - let(:project) { create(:project, creator_id: user.id) } + let(:project) { create(:project, :repository, creator: user) } context 'when commit comment event triggered' do let(:commit_note) do diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb index f8c45b37561..b9fb6f3f6f4 100644 --- a/spec/models/project_services/irker_service_spec.rb +++ b/spec/models/project_services/irker_service_spec.rb @@ -25,7 +25,7 @@ describe IrkerService, models: true do describe 'Execute' do let(:irker) { IrkerService.new } let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:sample_data) do Gitlab::DataBuilder::Push.build_sample(project, user) end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 862e3a72a73..2f6b159d76e 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -71,7 +71,7 @@ describe JiraService, models: true do describe '#close_issue' do let(:custom_base_url) { 'http://custom_url' } let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:merge_request) { create(:merge_request) } before do @@ -207,12 +207,12 @@ describe JiraService, models: true do end describe "Stored password invalidation" do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context "when a password was previously set" do before do @jira_service = JiraService.create!( - project: create(:project), + project: project, properties: { url: 'http://jira.example.com/rest/api/2', username: 'mic', @@ -252,7 +252,7 @@ describe JiraService, models: true do context "when no password was previously set" do before do @jira_service = JiraService.create( - project: create(:project), + project: project, properties: { url: 'http://jira.example.com/rest/api/2', username: 'mic' @@ -281,7 +281,7 @@ describe JiraService, models: true do end describe 'description and title' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'when it is not set' do before do @@ -316,7 +316,7 @@ describe JiraService, models: true do end describe 'project and issue urls' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'when gitlab.yml was initialized' do before do diff --git a/spec/models/project_services/pipeline_email_service_spec.rb b/spec/models/project_services/pipeline_email_service_spec.rb index 7c8824485f5..03932895b0e 100644 --- a/spec/models/project_services/pipeline_email_service_spec.rb +++ b/spec/models/project_services/pipeline_email_service_spec.rb @@ -7,7 +7,7 @@ describe PipelinesEmailService do create(:ci_pipeline, project: project, sha: project.commit('master').sha) end - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:recipient) { 'test@gitlab.com' } let(:data) do diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb index 8fc92a9ab51..a7e7594a7d5 100644 --- a/spec/models/project_services/pushover_service_spec.rb +++ b/spec/models/project_services/pushover_service_spec.rb @@ -27,7 +27,7 @@ describe PushoverService, models: true do describe 'Execute' do let(:pushover) { PushoverService.new } let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:sample_data) do Gitlab::DataBuilder::Push.build_sample(project, user) end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e93a4e62244..646a1311462 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -73,9 +73,7 @@ describe Project, models: true do context 'after initialized' do it "has a project_feature" do - project = FactoryGirl.build(:project) - - expect(project.project_feature.present?).to be_present + expect(Project.new.project_feature).to be_present end end @@ -129,7 +127,7 @@ describe Project, models: true do end describe 'validation' do - let!(:project) { create(:project) } + let!(:project) { create(:empty_project) } it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) } @@ -148,7 +146,7 @@ describe Project, models: true do it { is_expected.to validate_presence_of(:repository_storage) } it 'does not allow new projects beyond user limits' do - project2 = build(:project) + project2 = build(:empty_project) allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object) expect(project2).not_to be_valid expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/) @@ -157,7 +155,7 @@ describe Project, models: true do describe 'wiki path conflict' do context "when the new path has been used by the wiki of other Project" do it 'has an error on the name attribute' do - new_project = build_stubbed(:project, namespace_id: project.namespace_id, path: "#{project.path}.wiki") + new_project = build_stubbed(:empty_project, namespace_id: project.namespace_id, path: "#{project.path}.wiki") expect(new_project).not_to be_valid expect(new_project.errors[:name].first).to eq('has already been taken') @@ -166,8 +164,8 @@ describe Project, models: true do context "when the new wiki path has been used by the path of other Project" do it 'has an error on the name attribute' do - project_with_wiki_suffix = create(:project, path: 'foo.wiki') - new_project = build_stubbed(:project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo') + project_with_wiki_suffix = create(:empty_project, path: 'foo.wiki') + new_project = build_stubbed(:empty_project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo') expect(new_project).not_to be_valid expect(new_project.errors[:name].first).to eq('has already been taken') @@ -176,7 +174,7 @@ describe Project, models: true do end context 'repository storages inclussion' do - let(:project2) { build(:project, repository_storage: 'missing') } + let(:project2) { build(:empty_project, repository_storage: 'missing') } before do storages = { 'custom' => 'tmp/tests/custom_repositories' } @@ -352,7 +350,7 @@ describe Project, models: true do end describe '#repository_storage_path' do - let(:project) { create(:project, repository_storage: 'custom') } + let(:project) { create(:empty_project, repository_storage: 'custom') } before do FileUtils.mkdir('tmp/tests/custom_repositories') @@ -412,7 +410,7 @@ describe Project, models: true do describe 'last_activity methods' do let(:timestamp) { 2.hours.ago } # last_activity_at gets set to created_at upon creation - let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) } + let(:project) { create(:empty_project, created_at: timestamp, updated_at: timestamp) } describe 'last_activity' do it 'alias last_activity to last_event' do @@ -496,7 +494,7 @@ describe Project, models: true do context 'with namespace' do before do @group = create :group, name: 'gitlab' - @project = create(:project, name: 'gitlabhq', namespace: @group) + @project = create(:empty_project, name: 'gitlabhq', namespace: @group) end it { expect(@project.to_param).to eq('gitlabhq') } @@ -522,7 +520,7 @@ describe Project, models: true do end describe '#repository' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } it 'returns valid repo' do expect(project.repository).to be_kind_of(Repository) @@ -530,20 +528,22 @@ describe Project, models: true do end describe '#default_issues_tracker?' do - let(:project) { create(:project) } - let(:ext_project) { create(:redmine_project) } - it "is true if used internal tracker" do + project = build(:empty_project) + expect(project.default_issues_tracker?).to be_truthy end it "is false if used other tracker" do - expect(ext_project.default_issues_tracker?).to be_falsey + # NOTE: The current nature of this factory requires persistence + project = create(:redmine_project) + + expect(project.default_issues_tracker?).to be_falsey end end describe '#external_issue_tracker' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:ext_project) { create(:redmine_project) } context 'on existing projects with no value for has_external_issue_tracker' do @@ -578,7 +578,7 @@ describe Project, models: true do end describe '#cache_has_external_issue_tracker' do - let(:project) { create(:project, has_external_issue_tracker: nil) } + let(:project) { create(:empty_project, has_external_issue_tracker: nil) } it 'stores true if there is any external_issue_tracker' do services = double(:service, external_issue_trackers: [RedmineService.new]) @@ -600,9 +600,9 @@ describe Project, models: true do end describe '#has_wiki?' do - let(:no_wiki_project) { create(:project, wiki_access_level: ProjectFeature::DISABLED, has_external_wiki: false) } - let(:wiki_enabled_project) { create(:project) } - let(:external_wiki_project) { create(:project, has_external_wiki: true) } + let(:no_wiki_project) { create(:empty_project, wiki_access_level: ProjectFeature::DISABLED, has_external_wiki: false) } + let(:wiki_enabled_project) { create(:empty_project) } + let(:external_wiki_project) { create(:empty_project, has_external_wiki: true) } it 'returns true if project is wiki enabled or has external wiki' do expect(wiki_enabled_project).to have_wiki @@ -612,7 +612,7 @@ describe Project, models: true do end describe '#external_wiki' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'with an active external wiki' do before do @@ -663,7 +663,7 @@ describe Project, models: true do end describe '#open_branches' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do project.protected_branches.create(name: 'master') @@ -685,7 +685,7 @@ describe Project, models: true do it 'counts stars from multiple users' do user1 = create :user user2 = create :user - project = create :project, :public + project = create(:empty_project, :public) expect(project.star_count).to eq(0) @@ -707,8 +707,8 @@ describe Project, models: true do it 'counts stars on the right project' do user = create :user - project1 = create :project, :public - project2 = create :project, :public + project1 = create(:empty_project, :public) + project2 = create(:empty_project, :public) expect(project1.star_count).to eq(0) expect(project2.star_count).to eq(0) @@ -740,7 +740,7 @@ describe Project, models: true do end describe '#avatar_type' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } it 'is true if avatar is image' do project.update_attribute(:avatar, 'uploads/avatar.png') @@ -756,7 +756,7 @@ describe Project, models: true do describe '#avatar_url' do subject { project.avatar_url } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'When avatar file is uploaded' do before do @@ -791,7 +791,7 @@ describe Project, models: true do end describe '#pipeline_for' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let!(:pipeline) { create_pipeline } shared_examples 'giving the correct pipeline' do @@ -825,13 +825,33 @@ describe Project, models: true do end describe '#builds_enabled' do - let(:project) { create :project } + let(:project) { create(:empty_project) } subject { project.builds_enabled } it { expect(project.builds_enabled?).to be_truthy } end + describe '.with_shared_runners' do + subject { Project.with_shared_runners } + + context 'when shared runners are enabled for project' do + let!(:project) { create(:empty_project, shared_runners_enabled: true) } + + it "returns a project" do + is_expected.to eq([project]) + end + end + + context 'when shared runners are disabled for project' do + let!(:project) { create(:empty_project, shared_runners_enabled: false) } + + it "returns an empty array" do + is_expected.to be_empty + end + end + end + describe '.cached_count', caching: true do let(:group) { create(:group, :public) } let!(:project1) { create(:empty_project, :public, group: group) } @@ -877,7 +897,7 @@ describe Project, models: true do end describe '.visible_to_user' do - let!(:project) { create(:project, :private) } + let!(:project) { create(:empty_project, :private) } let!(:user) { create(:user) } subject { described_class.visible_to_user(user) } @@ -974,8 +994,30 @@ describe Project, models: true do end end + describe '#shared_runners' do + let!(:runner) { create(:ci_runner, :shared) } + + subject { project.shared_runners } + + context 'when shared runners are enabled for project' do + let!(:project) { create(:empty_project, shared_runners_enabled: true) } + + it "returns a list of shared runners" do + is_expected.to eq([runner]) + end + end + + context 'when shared runners are disabled for project' do + let!(:project) { create(:empty_project, shared_runners_enabled: false) } + + it "returns a empty list" do + is_expected.to be_empty + end + end + end + describe '#visibility_level_allowed?' do - let(:project) { create(:project, :internal) } + let(:project) { create(:empty_project, :internal) } context 'when checking on non-forked project' do it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy } @@ -984,8 +1026,8 @@ describe Project, models: true do end context 'when checking on forked project' do - let(:project) { create(:project, :internal) } - let(:forked_project) { create(:project, forked_from_project: project) } + let(:project) { create(:empty_project, :internal) } + let(:forked_project) { create(:empty_project, forked_from_project: project) } it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy } it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy } @@ -994,7 +1036,7 @@ describe Project, models: true do end describe '.search' do - let(:project) { create(:project, description: 'kitten mittens') } + let(:project) { create(:empty_project, description: 'kitten mittens') } it 'returns projects with a matching name' do expect(described_class.search(project.name)).to eq([project]) @@ -1052,7 +1094,7 @@ describe Project, models: true do end describe '#rename_repo' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:gitlab_shell) { Gitlab::Shell.new } before do @@ -1102,7 +1144,7 @@ describe Project, models: true do end describe '#expire_caches_before_rename' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repo) { double(:repo, exists?: true) } let(:wiki) { double(:wiki, exists?: true) } @@ -1123,7 +1165,7 @@ describe Project, models: true do end describe '.search_by_title' do - let(:project) { create(:project, name: 'kittens') } + let(:project) { create(:empty_project, name: 'kittens') } it 'returns projects with a matching name' do expect(described_class.search_by_title(project.name)).to eq([project]) @@ -1142,8 +1184,8 @@ describe Project, models: true do let(:private_group) { create(:group, visibility_level: 0) } let(:internal_group) { create(:group, visibility_level: 10) } - let(:private_project) { create :project, :private, group: private_group } - let(:internal_project) { create :project, :internal, group: internal_group } + let(:private_project) { create :empty_project, :private, group: private_group } + let(:internal_project) { create :empty_project, :internal, group: internal_group } context 'when group is private project can not be internal' do it { expect(private_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_falsey } @@ -1155,7 +1197,7 @@ describe Project, models: true do end describe '#create_repository' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:shell) { Gitlab::Shell.new } before do @@ -1197,7 +1239,7 @@ describe Project, models: true do describe '#protected_branch?' do context 'existing project' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } it 'returns true when the branch matches a protected branch via direct match' do create(:protected_branch, project: project, name: "foo") @@ -1381,7 +1423,7 @@ describe Project, models: true do name: name) end - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) { create_pipeline } context 'with many builds' do @@ -1461,7 +1503,7 @@ describe Project, models: true do end context 'not forked' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } it 'schedules a RepositoryImportWorker job' do expect(RepositoryImportWorker).to receive(:perform_async).with(project.id) @@ -1472,19 +1514,19 @@ describe Project, models: true do end describe '#gitlab_project_import?' do - subject(:project) { build(:project, import_type: 'gitlab_project') } + subject(:project) { build(:empty_project, import_type: 'gitlab_project') } it { expect(project.gitlab_project_import?).to be true } end describe '#gitea_import?' do - subject(:project) { build(:project, import_type: 'gitea') } + subject(:project) { build(:empty_project, import_type: 'gitea') } it { expect(project.gitea_import?).to be true } end describe '#lfs_enabled?' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } shared_examples 'project overrides group' do it 'returns true when enabled in project' do @@ -1546,7 +1588,7 @@ describe Project, models: true do end describe '#change_head' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } it 'calls the before_change_head and after_change_head methods' do expect(project.repository).to receive(:before_change_head) @@ -1574,7 +1616,7 @@ describe Project, models: true do end describe '#pushes_since_gc' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } after do project.reset_pushes_since_gc @@ -1596,7 +1638,7 @@ describe Project, models: true do end describe '#increment_pushes_since_gc' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } after do project.reset_pushes_since_gc @@ -1610,7 +1652,7 @@ describe Project, models: true do end describe '#reset_pushes_since_gc' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } after do project.reset_pushes_since_gc @@ -1626,7 +1668,7 @@ describe Project, models: true do end describe '#environments_for' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:environment) { create(:environment, project: project) } context 'tagged deployment' do @@ -1678,7 +1720,7 @@ describe Project, models: true do end describe '#environments_recently_updated_on_branch' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:environment) { create(:environment, project: project) } context 'when last deployment to environment is the most recent one' do diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 0475cecaa2d..942eeab251d 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -265,10 +265,10 @@ describe ProjectTeam, models: true do let(:group) { create(:group) } let(:developer) { create(:user) } let(:master) { create(:user) } - let(:personal_project) { create(:project, namespace: developer.namespace) } - let(:group_project) { create(:project, namespace: group) } - let(:members_project) { create(:project) } - let(:shared_project) { create(:project) } + let(:personal_project) { create(:empty_project, namespace: developer.namespace) } + let(:group_project) { create(:empty_project, namespace: group) } + let(:members_project) { create(:empty_project) } + let(:shared_project) { create(:empty_project) } before do group.add_master(master) @@ -330,7 +330,7 @@ describe ProjectTeam, models: true do reporter = create(:user) promoted_guest = create(:user) guest = create(:user) - project = create(:project) + project = create(:empty_project) project.add_master(master) project.add_reporter(reporter) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 99ca53938c8..829b69093c9 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -4,7 +4,7 @@ describe Repository, models: true do include RepoHelpers TestBlob = Struct.new(:name) - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:user) { create(:user) } @@ -90,6 +90,30 @@ describe Repository, models: true do it { is_expected.to eq(['v1.1.0', 'v1.0.0']) } end + + context 'annotated tag pointing to a blob' do + let(:annotated_tag_name) { 'annotated-tag' } + + subject { repository.tags_sorted_by('updated_asc').map(&:name) } + + before do + options = { message: 'test tag message\n', + tagger: { name: 'John Smith', email: 'john@gmail.com' } } + repository.rugged.tags.create(annotated_tag_name, 'a48e4fc218069f68ef2e769dd8dfea3991362175', options) + + double_first = double(committed_date: Time.now - 1.second) + double_last = double(committed_date: Time.now) + + allow(tag_a).to receive(:dereferenced_target).and_return(double_last) + allow(tag_b).to receive(:dereferenced_target).and_return(double_first) + end + + it { is_expected.to eq(['v1.1.0', 'v1.0.0', annotated_tag_name]) } + + after do + repository.rugged.tags.delete(annotated_tag_name) + end + end end end diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb index 8481a9bef16..dd2a5109abc 100644 --- a/spec/models/route_spec.rb +++ b/spec/models/route_spec.rb @@ -14,7 +14,7 @@ describe Route, models: true do it { is_expected.to validate_uniqueness_of(:path) } end - describe '#rename_children' do + describe '#rename_descendants' do let!(:nested_group) { create(:group, path: "test", parent: group) } let!(:deep_nested_group) { create(:group, path: "foo", parent: nested_group) } let!(:similar_group) { create(:group, path: 'gitlab-org') } diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 691511cd93f..0e2f07e945f 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -12,7 +12,7 @@ describe Service, models: true do end describe "Testable" do - let(:project) { create :project } + let(:project) { create(:project, :repository) } before do allow(@service).to receive(:project).and_return(project) @@ -35,7 +35,7 @@ describe Service, models: true do end describe "With commits" do - let(:project) { create :project } + let(:project) { create(:project, :repository) } before do allow(@service).to receive(:project).and_return(project) @@ -60,7 +60,7 @@ describe Service, models: true do api_key: '123456789' }) end - let(:project) { create(:project) } + let(:project) { create(:empty_project) } describe 'is prefilled for projects pushover service' do it "has all fields prefilled" do @@ -79,7 +79,7 @@ describe Service, models: true do describe "{property}_changed?" do let(:service) do BambooService.create( - project: create(:project), + project: create(:empty_project), properties: { bamboo_url: 'http://gitlab.com', username: 'mic', @@ -119,7 +119,7 @@ describe Service, models: true do describe "{property}_touched?" do let(:service) do BambooService.create( - project: create(:project), + project: create(:empty_project), properties: { bamboo_url: 'http://gitlab.com', username: 'mic', @@ -159,7 +159,7 @@ describe Service, models: true do describe "{property}_was" do let(:service) do BambooService.create( - project: create(:project), + project: create(:empty_project), properties: { bamboo_url: 'http://gitlab.com', username: 'mic', @@ -199,7 +199,7 @@ describe Service, models: true do describe 'initialize service with no properties' do let(:service) do GitlabIssueTrackerService.create( - project: create(:project), + project: create(:empty_project), title: 'random title' ) end @@ -214,7 +214,7 @@ describe Service, models: true do end describe "callbacks" do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let!(:service) do RedmineService.new( project: project, diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 7425a897769..219ab1989ea 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -42,7 +42,7 @@ describe Snippet, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:empty_project, name: 'another-project', namespace: project.namespace) expect(snippet.to_reference(another_project)).to eq "sample-project$1" end end @@ -55,7 +55,7 @@ describe Snippet, models: true do end it 'still returns shortest reference when project arg present' do - another_project = build(:project, name: 'another-project') + another_project = build(:empty_project, name: 'another-project') expect(snippet.to_reference(another_project)).to eq "$1" end end @@ -173,7 +173,7 @@ describe Snippet, models: true do end describe '#participants' do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:snippet) { create(:snippet, content: 'foo', project: project) } let!(:note1) do diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb index 623b82c01d8..8017d1c3324 100644 --- a/spec/models/todo_spec.rb +++ b/spec/models/todo_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe Todo, models: true do - let(:project) { create(:project) } - let(:commit) { project.commit } let(:issue) { create(:issue) } describe 'relationships' do @@ -82,6 +80,9 @@ describe Todo, models: true do describe '#target' do context 'for commits' do + let(:project) { create(:project, :repository) } + let(:commit) { project.commit } + it 'returns an instance of Commit when exists' do subject.project = project subject.target_type = 'Commit' @@ -109,6 +110,9 @@ describe Todo, models: true do describe '#target_reference' do it 'returns the short commit id for commits' do + project = create(:project, :repository) + commit = project.commit + subject.project = project subject.target_type = 'Commit' subject.commit_id = commit.id diff --git a/spec/models/tree_spec.rb b/spec/models/tree_spec.rb index 0737999e125..a87983b7492 100644 --- a/spec/models/tree_spec.rb +++ b/spec/models/tree_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Tree, models: true do - let(:repository) { create(:project).repository } + let(:repository) { create(:project, :repository).repository } let(:sha) { repository.root_ref } subject { described_class.new(repository, '54fcc214') } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8b20ee81614..6ca5ad747d1 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -48,7 +48,7 @@ describe User, models: true do describe '#project_members' do it 'does not include project memberships for which user is a requester' do user = create(:user) - project = create(:project, :public, :access_requestable) + project = create(:empty_project, :public, :access_requestable) project.request_access(user) expect(user.project_members).to be_empty @@ -386,13 +386,15 @@ describe User, models: true do describe 'projects' do before do - @user = create :user - @project = create :project, namespace: @user.namespace - @project_2 = create :project, group: create(:group) # Grant MASTER access to the user - @project_3 = create :project, group: create(:group) # Grant DEVELOPER access to the user + @user = create(:user) - @project_2.team << [@user, :master] - @project_3.team << [@user, :developer] + @project = create(:empty_project, namespace: @user.namespace) + @project_2 = create(:empty_project, group: create(:group)) do |project| + project.add_master(@user) + end + @project_3 = create(:empty_project, group: create(:group)) do |project| + project.add_developer(@user) + end end it { expect(@user.authorized_projects).to include(@project) } @@ -435,7 +437,7 @@ describe User, models: true do describe 'namespaced' do before do @user = create :user - @project = create :project, namespace: @user.namespace + @project = create(:empty_project, namespace: @user.namespace) end it { expect(@user.several_namespaces?).to be_falsey } @@ -517,7 +519,7 @@ describe User, models: true do before do User.delete_all @user = create :user - @project = create :project + @project = create(:empty_project) end it { expect(User.not_in_project(@project)).to include(@user, @project.owner) } @@ -795,14 +797,14 @@ describe User, models: true do describe '#avatar_type' do let(:user) { create(:user) } - it "is true if avatar is image" do + it 'is true if avatar is image' do user.update_attribute(:avatar, 'uploads/avatar.png') expect(user.avatar_type).to be_truthy end - it "is false if avatar is html page" do + it 'is false if avatar is html page' do user.update_attribute(:avatar, 'uploads/avatar.html') - expect(user.avatar_type).to eq(["only images allowed"]) + expect(user.avatar_type).to eq(['only images allowed']) end end @@ -924,11 +926,11 @@ describe User, models: true do end end - describe "#starred?" do - it "determines if user starred a project" do + describe '#starred?' do + it 'determines if user starred a project' do user = create :user - project1 = create :project, :public - project2 = create :project, :public + project1 = create(:empty_project, :public) + project2 = create(:empty_project, :public) expect(user.starred?(project1)).to be_falsey expect(user.starred?(project2)).to be_falsey @@ -951,10 +953,10 @@ describe User, models: true do end end - describe "#toggle_star" do - it "toggles stars" do + describe '#toggle_star' do + it 'toggles stars' do user = create :user - project = create :project, :public + project = create(:empty_project, :public) expect(user.starred?(project)).to be_falsey user.toggle_star(project) @@ -964,39 +966,52 @@ describe User, models: true do end end - describe "#sort" do + describe '#sort' do before do User.delete_all @user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha' @user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega' + @user2 = create :user, created_at: Date.today - 2, last_sign_in_at: nil, name: 'Beta' end - it "sorts users by the recent sign-in time" do - expect(User.sort('recent_sign_in').first).to eq(@user) + context 'when sort by recent_sign_in' do + it 'sorts users by the recent sign-in time' do + expect(User.sort('recent_sign_in').first).to eq(@user) + end + + it 'pushes users who never signed in to the end' do + expect(User.sort('recent_sign_in').third).to eq(@user2) + end end - it "sorts users by the oldest sign-in time" do - expect(User.sort('oldest_sign_in').first).to eq(@user1) + context 'when sort by oldest_sign_in' do + it 'sorts users by the oldest sign-in time' do + expect(User.sort('oldest_sign_in').first).to eq(@user1) + end + + it 'pushes users who never signed in to the end' do + expect(User.sort('oldest_sign_in').third).to eq(@user2) + end end - it "sorts users in descending order by their creation time" do + it 'sorts users in descending order by their creation time' do expect(User.sort('created_desc').first).to eq(@user) end - it "sorts users in ascending order by their creation time" do - expect(User.sort('created_asc').first).to eq(@user1) + it 'sorts users in ascending order by their creation time' do + expect(User.sort('created_asc').first).to eq(@user2) end - it "sorts users by id in descending order when nil is passed" do - expect(User.sort(nil).first).to eq(@user1) + it 'sorts users by id in descending order when nil is passed' do + expect(User.sort(nil).first).to eq(@user2) end end describe "#contributed_projects" do subject { create(:user) } - let!(:project1) { create(:project) } - let!(:project2) { create(:project, forked_from_project: project3) } - let!(:project3) { create(:project) } + let!(:project1) { create(:empty_project) } + let!(:project2) { create(:empty_project, forked_from_project: project3) } + let!(:project3) { create(:empty_project) } let!(:merge_request) { create(:merge_request, source_project: project2, target_project: project3, author: subject) } let!(:push_event) { create(:event, action: Event::PUSHED, project: project1, target: project1, author: subject) } let!(:merge_event) { create(:event, action: Event::CREATED, project: project3, target: merge_request, author: subject) } @@ -1038,8 +1053,8 @@ describe User, models: true do describe "#recent_push" do subject { create(:user) } - let!(:project1) { create(:project) } - let!(:project2) { create(:project, forked_from_project: project1) } + let!(:project1) { create(:project, :repository) } + let!(:project2) { create(:project, :repository, forked_from_project: project1) } let!(:push_data) do Gitlab::DataBuilder::Push.build_sample(project2, subject) end @@ -1113,7 +1128,7 @@ describe User, models: true do it "includes user's personal projects" do user = create(:user) - project = create(:project, :private, namespace: user.namespace) + project = create(:empty_project, :private, namespace: user.namespace) expect(user.authorized_projects).to include(project) end @@ -1121,7 +1136,7 @@ describe User, models: true do it "includes personal projects user has been given access to" do user1 = create(:user) user2 = create(:user) - project = create(:project, :private, namespace: user1.namespace) + project = create(:empty_project, :private, namespace: user1.namespace) project.team << [user2, Gitlab::Access::DEVELOPER] @@ -1130,7 +1145,7 @@ describe User, models: true do it "includes projects of groups user has been added to" do group = create(:group) - project = create(:project, group: group) + project = create(:empty_project, group: group) user = create(:user) group.add_developer(user) @@ -1140,7 +1155,7 @@ describe User, models: true do it "does not include projects of groups user has been removed from" do group = create(:group) - project = create(:project, group: group) + project = create(:empty_project, group: group) user = create(:user) member = group.add_developer(user) @@ -1152,7 +1167,7 @@ describe User, models: true do it "includes projects shared with user's group" do user = create(:user) - project = create(:project, :private) + project = create(:empty_project, :private) group = create(:group) group.add_reporter(user) @@ -1164,7 +1179,7 @@ describe User, models: true do it "does not include destroyed projects user had access to" do user1 = create(:user) user2 = create(:user) - project = create(:project, :private, namespace: user1.namespace) + project = create(:empty_project, :private, namespace: user1.namespace) project.team << [user2, Gitlab::Access::DEVELOPER] expect(user2.authorized_projects).to include(project) @@ -1175,7 +1190,7 @@ describe User, models: true do it "does not include projects of destroyed groups user had access to" do group = create(:group) - project = create(:project, namespace: group) + project = create(:empty_project, namespace: group) user = create(:user) group.add_developer(user) @@ -1190,14 +1205,9 @@ describe User, models: true do let(:user) { create(:user) } it 'includes projects for which the user access level is above or equal to reporter' do - create(:project) - reporter_project = create(:project) - developer_project = create(:project) - master_project = create(:project) - - reporter_project.team << [user, :reporter] - developer_project.team << [user, :developer] - master_project.team << [user, :master] + reporter_project = create(:empty_project) { |p| p.add_reporter(user) } + developer_project = create(:empty_project) { |p| p.add_developer(user) } + master_project = create(:empty_project) { |p| p.add_master(user) } expect(user.projects_where_can_admin_issues.to_a).to eq([master_project, developer_project, reporter_project]) expect(user.can?(:admin_issue, master_project)).to eq(true) @@ -1206,10 +1216,8 @@ describe User, models: true do end it 'does not include for which the user access level is below reporter' do - project = create(:project) - guest_project = create(:project) - - guest_project.team << [user, :guest] + project = create(:empty_project) + guest_project = create(:empty_project) { |p| p.add_guest(user) } expect(user.projects_where_can_admin_issues.to_a).to be_empty expect(user.can?(:admin_issue, guest_project)).to eq(false) @@ -1217,15 +1225,14 @@ describe User, models: true do end it 'does not include archived projects' do - project = create(:project) - project.update_attributes(archived: true) + project = create(:empty_project, :archived) expect(user.projects_where_can_admin_issues.to_a).to be_empty expect(user.can?(:admin_issue, project)).to eq(false) end it 'does not include projects for which issues are disabled' do - project = create(:project, issues_access_level: ProjectFeature::DISABLED) + project = create(:empty_project, issues_access_level: ProjectFeature::DISABLED) expect(user.projects_where_can_admin_issues.to_a).to be_empty expect(user.can?(:admin_issue, project)).to eq(false) @@ -1241,7 +1248,7 @@ describe User, models: true do end context 'without any projects' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } it 'does not load' do expect(user.ci_authorized_runners).to be_empty @@ -1250,7 +1257,7 @@ describe User, models: true do context 'with personal projects runners' do let(:namespace) { create(:namespace, owner: user) } - let(:project) { create(:project, namespace: namespace) } + let(:project) { create(:empty_project, namespace: namespace) } it 'loads' do expect(user.ci_authorized_runners).to contain_exactly(runner) @@ -1281,7 +1288,7 @@ describe User, models: true do context 'with groups projects runners' do let(:group) { create(:group) } - let(:project) { create(:project, group: group) } + let(:project) { create(:empty_project, group: group) } def add_user(access) group.add_user(user, access) @@ -1291,7 +1298,7 @@ describe User, models: true do end context 'with other projects runners' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } def add_user(access) project.team << [user, access] @@ -1321,8 +1328,8 @@ describe User, models: true do end describe '#projects_with_reporter_access_limited_to' do - let(:project1) { create(:project) } - let(:project2) { create(:project) } + let(:project1) { create(:empty_project) } + let(:project2) { create(:empty_project) } let(:user) { create(:user) } before do @@ -1356,6 +1363,39 @@ describe User, models: true do end end + describe '#nested_groups' do + let!(:user) { create(:user) } + let!(:group) { create(:group) } + let!(:nested_group) { create(:group, parent: group) } + + before do + group.add_owner(user) + + # Add more data to ensure method does not include wrong groups + create(:group).add_owner(create(:user)) + end + + it { expect(user.nested_groups).to eq([nested_group]) } + end + + describe '#nested_projects' do + let!(:user) { create(:user) } + let!(:group) { create(:group) } + let!(:nested_group) { create(:group, parent: group) } + let!(:project) { create(:empty_project, namespace: group) } + let!(:nested_project) { create(:empty_project, namespace: nested_group) } + + before do + group.add_owner(user) + + # Add more data to ensure method does not include wrong projects + other_project = create(:empty_project, namespace: create(:group, :nested)) + other_project.add_developer(create(:user)) + end + + it { expect(user.nested_projects).to eq([nested_project]) } + end + describe '#refresh_authorized_projects', redis: true do let(:project1) { create(:empty_project) } let(:project2) { create(:empty_project) } diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb new file mode 100644 index 00000000000..63acc0b68cd --- /dev/null +++ b/spec/policies/base_policy_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe BasePolicy, models: true do + let(:build) { Ci::Build.new } + + describe '.class_for' do + it 'detects policy class based on the subject ancestors' do + expect(described_class.class_for(build)).to eq(Ci::BuildPolicy) + end + + it 'detects policy class for a presented subject' do + presentee = Ci::BuildPresenter.new(build) + + expect(described_class.class_for(presentee)).to eq(Ci::BuildPolicy) + end + end +end diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb new file mode 100644 index 00000000000..7a35da38b2b --- /dev/null +++ b/spec/presenters/ci/build_presenter_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +describe Ci::BuildPresenter do + let(:project) { create(:empty_project) } + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:build) { create(:ci_build, pipeline: pipeline) } + + subject(:presenter) do + described_class.new(build) + end + + it 'inherits from Gitlab::View::Presenter::Delegated' do + expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated) + end + + describe '#initialize' do + it 'takes a build and optional params' do + expect { presenter }.not_to raise_error + end + + it 'exposes build' do + expect(presenter.build).to eq(build) + end + + it 'forwards missing methods to build' do + expect(presenter.ref).to eq('master') + end + end + + describe '#erased_by_user?' do + it 'takes a build and optional params' do + expect(presenter).not_to be_erased_by_user + end + end + + describe '#erased_by_name' do + context 'when build is not erased' do + before do + expect(presenter).to receive(:erased_by_user?).and_return(false) + end + + it 'returns nil' do + expect(presenter.erased_by_name).to be_nil + end + end + + context 'when build is erased' do + before do + expect(presenter).to receive(:erased_by_user?).and_return(true) + expect(build).to receive(:erased_by). + and_return(double(:user, name: 'John Doe')) + end + + it 'returns the name of the eraser' do + expect(presenter.erased_by_name).to eq('John Doe') + end + end + end + + describe 'quack like a Ci::Build permission-wise' do + context 'user is not allowed' do + let(:project) { build_stubbed(:empty_project, public_builds: false) } + + it 'returns false' do + expect(presenter.can?(nil, :read_build)).to be_falsy + end + end + + context 'user is allowed' do + let(:project) { build_stubbed(:empty_project, :public) } + + it 'returns true' do + expect(presenter.can?(nil, :read_build)).to be_truthy + end + end + end +end diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb index 1a771b3c87a..e487297748b 100644 --- a/spec/requests/api/access_requests_spec.rb +++ b/spec/requests/api/access_requests_spec.rb @@ -9,7 +9,7 @@ describe API::AccessRequests, api: true do let(:stranger) { create(:user) } let(:project) do - create(:project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| + create(:empty_project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| project.team << [developer, :developer] project.team << [master, :master] project.request_access(access_requester) diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb index 3019724f52e..c14c3cb1ce7 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -8,7 +8,7 @@ describe API::Boards, api: true do let(:non_member) { create(:user) } let(:guest) { create(:user) } let(:admin) { create(:user, :admin) } - let!(:project) { create(:project, :public, creator_id: user.id, namespace: user.namespace ) } + let!(:project) { create(:empty_project, :public, creator_id: user.id, namespace: user.namespace ) } let!(:dev_label) do create(:label, title: 'Development', color: '#FFAABB', project: project) @@ -188,7 +188,7 @@ describe API::Boards, api: true do context "when the user is project owner" do let(:owner) { create(:user) } - let(:project) { create(:project, namespace: owner.namespace) } + let(:project) { create(:empty_project, namespace: owner.namespace) } it "deletes the list if an admin requests it" do delete api("#{base_url}/#{dev_list.id}", owner) diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 2878e0cb59b..5a3ffc284f2 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -6,7 +6,7 @@ describe API::Branches, api: true do let(:user) { create(:user) } let(:user2) { create(:user) } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:project, :repository, creator: user) } let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:guest) { create(:project_member, :guest, user: user2, project: project) } let!(:branch_name) { 'feature' } diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index 7be7acebb19..645e36683bc 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -5,7 +5,7 @@ describe API::Builds, api: true do let(:user) { create(:user) } let(:api_user) { user } - let!(:project) { create(:project, creator_id: user.id, public_builds: false) } + let!(:project) { create(:project, :repository, creator: user, public_builds: false) } let!(:developer) { create(:project_member, :developer, user: user, project: project) } let(:reporter) { create(:project_member, :reporter, project: project) } let(:guest) { create(:project_member, :guest, project: project) } diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 335efc4db6c..88361def3cf 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::CommitStatuses, api: true do include ApiHelpers - let!(:project) { create(:project) } + let!(:project) { create(:project, :repository) } let(:commit) { project.repository.commit } let(:commit_status) { create(:commit_status, pipeline: pipeline) } let(:guest) { create_user(:guest) } @@ -152,8 +152,11 @@ describe API::CommitStatuses, api: true do context 'with all optional parameters' do before do - optional_params = { state: 'success', context: 'coverage', - ref: 'develop', target_url: 'url', description: 'test' } + optional_params = { state: 'success', + context: 'coverage', + ref: 'develop', + description: 'test', + target_url: 'http://gitlab.com/status' } post api(post_url, developer), optional_params end @@ -164,12 +167,12 @@ describe API::CommitStatuses, api: true do expect(json_response['status']).to eq('success') expect(json_response['name']).to eq('coverage') expect(json_response['ref']).to eq('develop') - expect(json_response['target_url']).to eq('url') expect(json_response['description']).to eq('test') + expect(json_response['target_url']).to eq('http://gitlab.com/status') end end - context 'invalid status' do + context 'when status is invalid' do before { post api(post_url, developer), state: 'invalid' } it 'does not create commit status' do @@ -177,7 +180,7 @@ describe API::CommitStatuses, api: true do end end - context 'request without state' do + context 'when request without a state made' do before { post api(post_url, developer) } it 'does not create commit status' do @@ -185,7 +188,7 @@ describe API::CommitStatuses, api: true do end end - context 'invalid commit' do + context 'when commit SHA is invalid' do let(:sha) { 'invalid_sha' } before { post api(post_url, developer), state: 'running' } @@ -193,6 +196,19 @@ describe API::CommitStatuses, api: true do expect(response).to have_http_status(404) end end + + context 'when target URL is an invalid address' do + before do + post api(post_url, developer), state: 'pending', + target_url: 'invalid url' + end + + it 'responds with bad request status and validation errors' do + expect(response).to have_http_status(400) + expect(json_response['message']['target_url']) + .to include 'must be a valid URL' + end + end end context 'reporter user' do diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 7f8ea5251f0..af9028a8978 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -5,7 +5,7 @@ describe API::Commits, api: true do include ApiHelpers let(:user) { create(:user) } let(:user2) { create(:user) } - let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } + let!(:project) { create(:project, :repository, creator: user, namespace: user.namespace) } let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:guest) { create(:project_member, :guest, user: user2, project: project) } let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') } diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb index aabab8e6ae6..766234d7104 100644 --- a/spec/requests/api/deploy_keys_spec.rb +++ b/spec/requests/api/deploy_keys_spec.rb @@ -5,8 +5,8 @@ describe API::DeployKeys, api: true do let(:user) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:project, creator_id: user.id) } - let(:project2) { create(:project, creator_id: user.id) } + let(:project) { create(:empty_project, creator_id: user.id) } + let(:project2) { create(:empty_project, creator_id: user.id) } let(:deploy_key) { create(:deploy_key, public: true) } let!(:deploy_keys_project) do @@ -73,19 +73,14 @@ describe API::DeployKeys, api: true do post api("/projects/#{project.id}/deploy_keys", admin), { title: 'invalid key' } expect(response).to have_http_status(400) - expect(json_response['message']['key']).to eq([ - 'can\'t be blank', - 'is invalid' - ]) + expect(json_response['error']).to eq('key is missing') end it 'should not create a key without title' do post api("/projects/#{project.id}/deploy_keys", admin), key: 'some key' expect(response).to have_http_status(400) - expect(json_response['message']['title']).to eq([ - 'can\'t be blank' - ]) + expect(json_response['error']).to eq('title is missing') end it 'should create new ssh key' do diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index b9d535bc314..8168b613766 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -5,7 +5,7 @@ describe API::Environments, api: true do let(:user) { create(:user) } let(:non_member) { create(:user) } - let(:project) { create(:project, :private, namespace: user.namespace) } + let(:project) { create(:empty_project, :private, namespace: user.namespace) } let!(:environment) { create(:environment, project: project) } before do diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 685da28c673..5e26e779366 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe API::Files, api: true do include ApiHelpers let(:user) { create(:user) } - let!(:project) { create(:project, namespace: user.namespace ) } - let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } } + let!(:project) { create(:project, :repository, namespace: user.namespace ) } + let(:guest) { create(:user) { |u| project.add_guest(u) } } let(:file_path) { 'files/ruby/popen.rb' } let(:params) do { diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb index e38d5745d44..92ac4fd334d 100644 --- a/spec/requests/api/fork_spec.rb +++ b/spec/requests/api/fork_spec.rb @@ -1,10 +1,9 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Projects, api: true do include ApiHelpers let(:user) { create(:user) } let(:user2) { create(:user) } - let(:user3) { create(:user) } let(:admin) { create(:admin) } let(:group) { create(:group) } let(:group2) do @@ -13,17 +12,14 @@ describe API::API, api: true do group end - let(:project) do - create(:project, creator_id: user.id, namespace: user.namespace) - end - - let(:project_user2) do - create(:project_member, :reporter, user: user2, project: project) - end - describe 'POST /projects/fork/:id' do - before { project_user2 } - before { user3 } + let(:project) do + create(:project, :repository, creator: user, namespace: user.namespace) + end + + before do + project.add_reporter(user2) + end context 'when authenticated' do it 'forks if user has sufficient access to project' do @@ -49,7 +45,8 @@ describe API::API, api: true do end it 'fails on missing project access for the project to fork' do - post api("/projects/fork/#{project.id}", user3) + new_user = create(:user) + post api("/projects/fork/#{project.id}", new_user) expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index e355d5e28bc..edbf0140583 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -10,9 +10,9 @@ describe API::Groups, api: true do let(:admin) { create(:admin) } let!(:group1) { create(:group, avatar: File.open(uploaded_image_temp_path)) } let!(:group2) { create(:group, :private) } - let!(:project1) { create(:project, namespace: group1) } - let!(:project2) { create(:project, namespace: group2) } - let!(:project3) { create(:project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) } + let!(:project1) { create(:empty_project, namespace: group1) } + let!(:project2) { create(:empty_project, namespace: group2) } + let!(:project3) { create(:empty_project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) } before do group1.add_owner(user1) @@ -163,7 +163,7 @@ describe API::Groups, api: true do describe "GET /groups/:id" do context "when authenticated as user" do it "returns one of user1's groups" do - project = create(:project, namespace: group2, path: 'Foo') + project = create(:empty_project, namespace: group2, path: 'Foo') create(:project_group_link, project: project, group: group1) get api("/groups/#{group1.id}", user1) @@ -287,7 +287,7 @@ describe API::Groups, api: true do expect(json_response.length).to eq(2) project_names = json_response.map { |proj| proj['name' ] } expect(project_names).to match_array([project1.name, project3.name]) - expect(json_response.first['default_branch']).to be_present + expect(json_response.first['visibility_level']).to be_present end it "returns the group's projects with simple representation" do @@ -297,11 +297,11 @@ describe API::Groups, api: true do expect(json_response.length).to eq(2) project_names = json_response.map { |proj| proj['name' ] } expect(project_names).to match_array([project1.name, project3.name]) - expect(json_response.first['default_branch']).not_to be_present + expect(json_response.first['visibility_level']).not_to be_present end it 'filters the groups projects' do - public_project = create(:project, :public, path: 'test1', group: group1) + public_project = create(:empty_project, :public, path: 'test1', group: group1) get api("/groups/#{group1.id}/projects", user1), visibility: 'public' @@ -462,7 +462,7 @@ describe API::Groups, api: true do end describe "POST /groups/:id/projects/:project_id" do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:project_path) { "#{project.namespace.path}%2F#{project.path}" } before(:each) do diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index b8ee2293a33..a89676fec93 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -12,6 +12,7 @@ describe API::Helpers, api: true do let(:params) { {} } let(:env) { { 'REQUEST_METHOD' => 'GET' } } let(:request) { Rack::Request.new(env) } + let(:header) { } def set_env(user_or_token, identifier) clear_env @@ -46,7 +47,7 @@ describe API::Helpers, api: true do allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ value } end - def error!(message, status) + def error!(message, status, header) raise Exception.new("#{status} - #{message}") end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 35644bd8cc9..ffeacb15f17 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -4,7 +4,7 @@ describe API::Internal, api: true do include ApiHelpers let(:user) { create(:user) } let(:key) { create(:key, user: user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:secret_token) { Gitlab::Shell.secret_token } describe "GET /internal/check", no_db: true do @@ -239,7 +239,7 @@ describe API::Internal, api: true do end context "blocked user" do - let(:personal_project) { create(:project, namespace: user.namespace) } + let(:personal_project) { create(:empty_project, namespace: user.namespace) } before do user.block @@ -265,7 +265,7 @@ describe API::Internal, api: true do end context "archived project" do - let(:personal_project) { create(:project, namespace: user.namespace) } + let(:personal_project) { create(:empty_project, namespace: user.namespace) } before do project.team << [user, :developer] @@ -337,8 +337,7 @@ describe API::Internal, api: true do context 'ssh access has been disabled' do before do - settings = ::ApplicationSetting.create_from_defaults - settings.update_attribute(:enabled_git_access_protocol, 'http') + stub_application_setting(enabled_git_access_protocol: 'http') end it 'rejects the SSH push' do @@ -360,8 +359,7 @@ describe API::Internal, api: true do context 'http access has been disabled' do before do - settings = ::ApplicationSetting.create_from_defaults - settings.update_attribute(:enabled_git_access_protocol, 'ssh') + stub_application_setting(enabled_git_access_protocol: 'ssh') end it 'rejects the HTTP push' do @@ -383,8 +381,7 @@ describe API::Internal, api: true do context 'web actions are always allowed' do it 'allows WEB push' do - settings = ::ApplicationSetting.create_from_defaults - settings.update_attribute(:enabled_git_access_protocol, 'ssh') + stub_application_setting(enabled_git_access_protocol: 'ssh') project.team << [user, :developer] push(key, project, 'web') diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 807c999b84a..62f1b8d7ca2 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -11,7 +11,7 @@ describe API::Issues, api: true do let(:author) { create(:author) } let(:assignee) { create(:assignee) } let(:admin) { create(:user, :admin) } - let!(:project) { create(:project, :public, creator_id: user.id, namespace: user.namespace ) } + let!(:project) { create(:empty_project, :public, creator_id: user.id, namespace: user.namespace ) } let!(:closed_issue) do create :closed_issue, author: user, @@ -224,7 +224,7 @@ describe API::Issues, api: true do describe "GET /groups/:id/issues" do let!(:group) { create(:group) } - let!(:group_project) { create(:project, :public, creator_id: user.id, namespace: group) } + let!(:group_project) { create(:empty_project, :public, creator_id: user.id, namespace: group) } let!(:group_closed_issue) do create :closed_issue, author: user, @@ -1052,7 +1052,7 @@ describe API::Issues, api: true do context "when the user is project owner" do let(:owner) { create(:user) } - let(:project) { create(:project, namespace: owner.namespace) } + let(:project) { create(:empty_project, namespace: owner.namespace) } it "deletes the issue if an admin requests it" do delete api("/projects/#{project.id}/issues/#{issue.id}", owner) @@ -1071,8 +1071,8 @@ describe API::Issues, api: true do end describe '/projects/:id/issues/:issue_id/move' do - let!(:target_project) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace ) } - let!(:target_project2) { create(:project, creator_id: non_member.id, namespace: non_member.namespace ) } + let!(:target_project) { create(:empty_project, path: 'project2', creator_id: user.id, namespace: user.namespace ) } + let!(:target_project2) { create(:empty_project, creator_id: non_member.id, namespace: non_member.namespace ) } it 'moves an issue' do post api("/projects/#{project.id}/issues/#{issue.id}/move", user), diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index b29ce1ea25e..a8cd787f398 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -4,7 +4,7 @@ describe API::Labels, api: true do include ApiHelpers let(:user) { create(:user) } - let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } + let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } let!(:label1) { create(:label, title: 'label1', project: project) } let!(:priority_label) { create(:label, title: 'bug', project: project, priority: 3) } diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index 2c94c86ccfa..9892e014cb9 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -9,7 +9,7 @@ describe API::Members, api: true do let(:stranger) { create(:user) } let(:project) do - create(:project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| + create(:empty_project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| project.team << [developer, :developer] project.team << [master, :master] project.request_access(access_requester) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 4e4fea1dad8..21a2c583aa8 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -6,12 +6,10 @@ describe API::MergeRequests, api: true do let(:user) { create(:user) } let(:admin) { create(:user, :admin) } let(:non_member) { create(:user) } - let!(:project) { create(:project, :public, creator_id: user.id, namespace: user.namespace) } - let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) } - let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) } - let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') } - let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") } - let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") } + let!(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace) } + let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, title: "Test", created_at: base_time) } + let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, title: "Closed test", created_at: base_time + 1.second) } + let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') } let(:milestone) { create(:milestone, title: '1.0.0', project: project) } before do @@ -308,8 +306,8 @@ describe API::MergeRequests, api: true do context 'forked projects' do let!(:user2) { create(:user) } - let!(:fork_project) { create(:project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) } - let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) } + let!(:fork_project) { create(:empty_project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) } + let!(:unrelated_project) { create(:empty_project, namespace: create(:user).namespace, creator_id: user2.id) } before :each do |each| fork_project.team << [user2, :reporter] @@ -556,11 +554,12 @@ describe API::MergeRequests, api: true do original_count = merge_request.notes.size post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user), note: "My comment" + expect(response).to have_http_status(201) expect(json_response['note']).to eq('My comment') expect(json_response['author']['name']).to eq(user.name) expect(json_response['author']['username']).to eq(user.username) - expect(merge_request.notes.size).to eq(original_count + 1) + expect(merge_request.reload.notes.size).to eq(original_count + 1) end it "returns 400 if note is missing" do @@ -576,6 +575,9 @@ describe API::MergeRequests, api: true do end describe "GET :id/merge_requests/:merge_request_id/comments" do + let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") } + let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") } + it "returns merge_request comments ordered by created_at" do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user) expect(response).to have_http_status(200) @@ -627,6 +629,17 @@ describe API::MergeRequests, api: true do expect(json_response.first['title']).to eq(issue.title) expect(json_response.first['id']).to eq(issue.id) end + + it 'returns 403 if the user has no access to the merge request' do + project = create(:empty_project, :private) + merge_request = create(:merge_request, :simple, source_project: project) + guest = create(:user) + project.team << [guest, :guest] + + get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", guest) + + expect(response).to have_http_status(403) + end end describe 'POST :id/merge_requests/:merge_request_id/subscription' do @@ -648,6 +661,15 @@ describe API::MergeRequests, api: true do expect(response).to have_http_status(404) end + + it 'returns 403 if user has no access to read code' do + guest = create(:user) + project.team << [guest, :guest] + + post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", guest) + + expect(response).to have_http_status(403) + end end describe 'DELETE :id/merge_requests/:merge_request_id/subscription' do @@ -669,6 +691,15 @@ describe API::MergeRequests, api: true do expect(response).to have_http_status(404) end + + it 'returns 403 if user has no access to read code' do + guest = create(:user) + project.team << [guest, :guest] + + delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", guest) + + expect(response).to have_http_status(403) + end end describe 'Time tracking' do diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 028f93c8561..0353ebea9e5 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::Notes, api: true do include ApiHelpers let(:user) { create(:user) } - let!(:project) { create(:project, :public, namespace: user.namespace) } + let!(:project) { create(:empty_project, :public, namespace: user.namespace) } let!(:issue) { create(:issue, project: project, author: user) } let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) } let!(:snippet) { create(:project_snippet, project: project, author: user) } @@ -14,12 +14,12 @@ describe API::Notes, api: true do # For testing the cross-reference of a private issue in a public issue let(:private_user) { create(:user) } let(:private_project) do - create(:project, namespace: private_user.namespace). + create(:empty_project, namespace: private_user.namespace). tap { |p| p.team << [private_user, :master] } end let(:private_issue) { create(:issue, project: private_project) } - let(:ext_proj) { create(:project, :public) } + let(:ext_proj) { create(:empty_project, :public) } let(:ext_issue) { create(:issue, project: ext_proj) } let!(:cross_reference_note) do @@ -264,8 +264,20 @@ describe API::Notes, api: true do end end + context 'when user does not have access to read the noteable' do + it 'responds with 404' do + project = create(:empty_project, :private) { |p| p.add_guest(user) } + issue = create(:issue, :confidential, project: project) + + post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), + body: 'Foo' + + expect(response).to have_http_status(404) + end + end + context 'when user does not have access to create noteable' do - let(:private_issue) { create(:issue, project: create(:project, :private)) } + let(:private_issue) { create(:issue, project: create(:empty_project, :private)) } ## # We are posting to project user has access to, but we use issue id diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb index 8691a81420f..39d3afcb78f 100644 --- a/spec/requests/api/notification_settings_spec.rb +++ b/spec/requests/api/notification_settings_spec.rb @@ -5,7 +5,7 @@ describe API::NotificationSettings, api: true do let(:user) { create(:user) } let!(:group) { create(:group) } - let!(:project) { create(:project, :public, creator_id: user.id, namespace: group) } + let!(:project) { create(:empty_project, :public, creator_id: user.id, namespace: group) } describe "GET /notification_settings" do it "returns global notification settings for the current user" do diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index 9a01f7fa1c4..b7a0b5a9e13 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -5,7 +5,7 @@ describe API::Pipelines, api: true do let(:user) { create(:user) } let(:non_member) { create(:user) } - let(:project) { create(:project, creator_id: user.id) } + let(:project) { create(:project, :repository, creator: user) } let!(:pipeline) do create(:ci_empty_pipeline, project: project, sha: project.commit.id, diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 36fbcf088e7..f4973d71088 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -4,7 +4,7 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do include ApiHelpers let(:user) { create(:user) } let(:user3) { create(:user) } - let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } + let!(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } let!(:hook) do create(:project_hook, :all_events_enabled, @@ -204,7 +204,7 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do it "returns a 404 if a user attempts to delete project hooks he/she does not own" do test_user = create(:user) - other_project = create(:project) + other_project = create(:empty_project) other_project.team << [test_user, :master] delete api("/projects/#{other_project.id}/hooks/#{hook.id}", test_user) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index cdb16b4c46b..a1db81ce18c 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -8,8 +8,8 @@ describe API::Projects, api: true do let(:user2) { create(:user) } let(:user3) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } - let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) } + let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } + let(:project2) { create(:empty_project, path: 'project2', creator_id: user.id, namespace: user.namespace) } let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') } let(:project_member) { create(:project_member, :master, user: user, project: project) } let(:project_member2) { create(:project_member, :developer, user: user3, project: project) } @@ -17,6 +17,7 @@ describe API::Projects, api: true do let(:project3) do create(:project, :private, + :repository, name: 'second_project', path: 'second_project', creator_id: user.id, @@ -32,7 +33,7 @@ describe API::Projects, api: true do access_level: ProjectMember::MASTER) end let(:project4) do - create(:project, + create(:empty_project, name: 'third_project', path: 'third_project', creator_id: user4.id, @@ -252,7 +253,7 @@ describe API::Projects, api: true do end end - let!(:public_project) { create(:project, :public) } + let!(:public_project) { create(:empty_project, :public) } before do project project2 @@ -283,7 +284,7 @@ describe API::Projects, api: true do end describe 'GET /projects/starred' do - let(:public_project) { create(:project, :public) } + let(:public_project) { create(:empty_project, :public) } before do project_member2 @@ -583,7 +584,7 @@ describe API::Projects, api: true do describe 'GET /projects/:id' do context 'when unauthenticated' do it 'returns the public projects' do - public_project = create(:project, :public) + public_project = create(:empty_project, :public) get api("/projects/#{public_project.id}") @@ -665,7 +666,7 @@ describe API::Projects, api: true do it 'handles users with dots' do dot_user = create(:user, username: 'dot.user') - project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace) + project = create(:empty_project, creator_id: dot_user.id, namespace: dot_user.namespace) get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user) expect(response).to have_http_status(200) @@ -711,7 +712,7 @@ describe API::Projects, api: true do end context 'group project' do - let(:project2) { create(:project, group: create(:group)) } + let(:project2) { create(:empty_project, group: create(:group)) } before { project2.group.add_owner(user) } @@ -756,7 +757,7 @@ describe API::Projects, api: true do context 'when unauthenticated' do it_behaves_like 'project events response' do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:current_user) { nil } end end @@ -807,7 +808,7 @@ describe API::Projects, api: true do context 'when unauthenticated' do it_behaves_like 'project users response' do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:current_user) { nil } end end @@ -921,11 +922,11 @@ describe API::Projects, api: true do end describe :fork_admin do - let(:project_fork_target) { create(:project) } - let(:project_fork_source) { create(:project, :public) } + let(:project_fork_target) { create(:empty_project) } + let(:project_fork_source) { create(:empty_project, :public) } describe 'POST /projects/:id/fork/:forked_from_id' do - let(:new_project_fork_source) { create(:project, :public) } + let(:new_project_fork_source) { create(:empty_project, :public) } it "is not available for non admin users" do post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user) @@ -966,7 +967,7 @@ describe API::Projects, api: true do end context 'when users belong to project group' do - let(:project_fork_target) { create(:project, group: create(:group)) } + let(:project_fork_target) { create(:empty_project, group: create(:group)) } before do project_fork_target.group.add_owner user @@ -1121,7 +1122,6 @@ describe API::Projects, api: true do it_behaves_like 'project search response', query: 'one.dot.two', results: 1 do let(:current_user) { user } end - end context 'when authenticated as a different user' do diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 0b19fa38c55..c61208e395c 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -8,7 +8,7 @@ describe API::Repositories, api: true do let(:user) { create(:user) } let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:project, :repository, creator: user) } let!(:master) { create(:project_member, :master, user: user, project: project) } describe "GET /projects/:id/repository/tree" do @@ -74,7 +74,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository tree' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -144,7 +144,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository blob' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -198,7 +198,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository raw blob' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -273,7 +273,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository archive' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -347,7 +347,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository compare' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -394,7 +394,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository contributors' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 99414270be6..f2d81a28cb8 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -7,8 +7,8 @@ describe API::Runners, api: true do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:project) { create(:project, creator_id: user.id) } - let(:project2) { create(:project, creator_id: user.id) } + let(:project) { create(:empty_project, creator_id: user.id) } + let(:project2) { create(:empty_project, creator_id: user.id) } let!(:shared_runner) { create(:ci_runner, :shared) } let!(:unused_specific_runner) { create(:ci_runner) } diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index 39c9e0505d1..776dc655650 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -6,7 +6,7 @@ describe API::Services, api: true do let(:user) { create(:user) } let(:admin) { create(:admin) } let(:user2) { create(:user) } - let(:project) {create(:empty_project, creator_id: user.id, namespace: user.namespace) } + let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } Service.available_services_names.each do |service| describe "PUT /projects/:id/services/#{service.dasherize}" do @@ -16,6 +16,15 @@ describe API::Services, api: true do put api("/projects/#{project.id}/services/#{dashed_service}", user), service_attrs expect(response).to have_http_status(200) + + current_service = project.services.first + event = current_service.event_names.empty? ? "foo" : current_service.event_names.first + state = current_service[event] || false + + put api("/projects/#{project.id}/services/#{dashed_service}?#{event}=#{!state}", user), service_attrs + + expect(response).to have_http_status(200) + expect(project.services.first[event]).not_to eq(state) unless event == "foo" end it "returns if required fields missing" do diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index a1c32ae65ba..898d2b27e5c 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -7,7 +7,7 @@ describe API::Tags, api: true do let(:user) { create(:user) } let(:user2) { create(:user) } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:project, :repository, creator: user) } let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:guest) { create(:project_member, :guest, user: user2, project: project) } @@ -29,7 +29,7 @@ describe API::Tags, api: true do context 'when unauthenticated' do it_behaves_like 'repository tags' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -88,7 +88,7 @@ describe API::Tags, api: true do context 'when unauthenticated' do it_behaves_like 'repository tag' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 887a2ba5b84..56dc017ce54 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe API::Todos, api: true do include ApiHelpers - let(:project_1) { create(:project) } - let(:project_2) { create(:project) } + let(:project_1) { create(:empty_project) } + let(:project_2) { create(:empty_project) } let(:author_1) { create(:user) } let(:author_2) { create(:user) } let(:john_doe) { create(:user, username: 'john_doe') } @@ -183,12 +183,25 @@ describe API::Todos, api: true do expect(response.status).to eq(404) end + + it 'returns an error if the issuable is not accessible' do + guest = create(:user) + project_1.team << [guest, :guest] + + post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.id}/todo", guest) + + if issuable_type == 'merge_requests' + expect(response).to have_http_status(403) + else + expect(response).to have_http_status(404) + end + end end describe 'POST :id/issuable_type/:issueable_id/todo' do context 'for an issue' do it_behaves_like 'an issuable', 'issues' do - let(:issuable) { create(:issue, author: author_1, project: project_1) } + let(:issuable) { create(:issue, :confidential, author: author_1, project: project_1) } end end diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb index 67ec3168679..84104aa66ee 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/triggers_spec.rb @@ -7,7 +7,7 @@ describe API::Triggers do let(:user2) { create(:user) } let!(:trigger_token) { 'secure_token' } let!(:trigger_token_2) { 'secure_token_2' } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:project, :repository, creator: user) } let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:developer) { create(:project_member, :developer, user: user2, project: project) } let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) } @@ -15,7 +15,7 @@ describe API::Triggers do let!(:trigger_request) { create(:ci_trigger_request, trigger: trigger, created_at: '2015-01-01 12:13:14') } describe 'POST /projects/:project_id/trigger' do - let!(:project2) { create(:empty_project) } + let!(:project2) { create(:project) } let(:options) do { token: trigger_token diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index 7435f320607..769f04c5057 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -5,7 +5,7 @@ describe API::Variables, api: true do let(:user) { create(:user) } let(:user2) { create(:user) } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:empty_project, creator_id: user.id) } let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:developer) { create(:project_member, :developer, user: user2, project: project) } let!(:variable) { create(:ci_variable, project: project) } diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 3b5dc98e4d5..8dbe5f0b025 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -4,7 +4,8 @@ describe Ci::API::Builds do include ApiHelpers let(:runner) { FactoryGirl.create(:ci_runner, tag_list: ["mysql", "ruby"]) } - let(:project) { FactoryGirl.create(:empty_project) } + let(:project) { FactoryGirl.create(:empty_project, shared_runners_enabled: false) } + let(:last_update) { nil } describe "Builds API for runners" do let(:pipeline) { create(:ci_pipeline_without_jobs, project: project, ref: 'master') } @@ -16,6 +17,8 @@ describe Ci::API::Builds do describe "POST /builds/register" do let!(:build) { create(:ci_build, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } let(:user_agent) { 'gitlab-ci-multi-runner 1.5.2 (1-5-stable; go1.6.3; linux/amd64)' } + let!(:last_update) { } + let!(:new_update) { } before do stub_container_registry_config(enabled: false) @@ -24,7 +27,31 @@ describe Ci::API::Builds do shared_examples 'no builds available' do context 'when runner sends version in User-Agent' do context 'for stable version' do - it { expect(response).to have_http_status(204) } + it 'gives 204 and set X-GitLab-Last-Update' do + expect(response).to have_http_status(204) + expect(response.header).to have_key('X-GitLab-Last-Update') + end + end + + context 'when last_update is up-to-date' do + let(:last_update) { runner.ensure_runner_queue_value } + + it 'gives 204 and set the same X-GitLab-Last-Update' do + expect(response).to have_http_status(204) + expect(response.header['X-GitLab-Last-Update']) + .to eq(last_update) + end + end + + context 'when last_update is outdated' do + let(:last_update) { runner.ensure_runner_queue_value } + let(:new_update) { runner.tick_runner_queue } + + it 'gives 204 and set a new X-GitLab-Last-Update' do + expect(response).to have_http_status(204) + expect(response.header['X-GitLab-Last-Update']) + .to eq(new_update) + end end context 'for beta version' do @@ -49,6 +76,7 @@ describe Ci::API::Builds do register_builds info: { platform: :darwin } expect(response).to have_http_status(201) + expect(response.headers).not_to have_key('X-GitLab-Last-Update') expect(json_response['sha']).to eq(build.sha) expect(runner.reload.platform).to eq("darwin") expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] }) @@ -63,6 +91,20 @@ describe Ci::API::Builds do expect { register_builds }.to change { runner.reload.contacted_at } end + context 'when concurrently updating build' do + before do + expect_any_instance_of(Ci::Build).to receive(:run!). + and_raise(ActiveRecord::StaleObjectError.new(nil, nil)) + end + + it 'returns a conflict' do + register_builds info: { platform: :darwin } + + expect(response).to have_http_status(409) + expect(response.headers).not_to have_key('X-GitLab-Last-Update') + end + end + context 'registry credentials' do let(:registry_credentials) do { 'type' => 'registry', @@ -119,10 +161,10 @@ describe Ci::API::Builds do end context 'for shared runner' do - let(:shared_runner) { create(:ci_runner, token: "SharedRunner") } + let!(:runner) { create(:ci_runner, :shared, token: "SharedRunner") } before do - register_builds shared_runner.token + register_builds(runner.token) end it_behaves_like 'no builds available' @@ -224,7 +266,9 @@ describe Ci::API::Builds do end def register_builds(token = runner.token, **params) - post ci_api("/builds/register"), params.merge(token: token), { 'User-Agent' => user_agent } + new_params = params.merge(token: token, last_update: last_update) + + post ci_api("/builds/register"), new_params, { 'User-Agent' => user_agent } end end diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb index 2d434ab5dd8..a30be767119 100644 --- a/spec/requests/ci/api/triggers_spec.rb +++ b/spec/requests/ci/api/triggers_spec.rb @@ -5,9 +5,9 @@ describe Ci::API::Triggers do describe 'POST /projects/:project_id/refs/:ref/trigger' do let!(:trigger_token) { 'secure token' } - let!(:project) { FactoryGirl.create(:project, ci_id: 10) } - let!(:project2) { FactoryGirl.create(:empty_project, ci_id: 11) } - let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) } + let!(:project) { create(:project, :repository, ci_id: 10) } + let!(:project2) { create(:empty_project, ci_id: 11) } + let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) } let(:options) do { token: trigger_token diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 5abda28e26f..4a16824de04 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -12,7 +12,7 @@ describe 'Git HTTP requests', lib: true do describe "User with no identities" do let(:user) { create(:user) } - let(:project) { create(:project, path: 'project.git-project') } + let(:project) { create(:project, :repository, path: 'project.git-project') } context "when the project doesn't exist" do context "when no authentication is provided" do @@ -55,6 +55,28 @@ describe 'Git HTTP requests', lib: true do expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end end + + context 'but the repo is disabled' do + let(:project) { create(:project, repository_access_level: ProjectFeature::DISABLED, wiki_access_level: ProjectFeature::ENABLED) } + let(:wiki) { ProjectWiki.new(project) } + let(:path) { "/#{wiki.repository.path_with_namespace}.git" } + + before do + project.team << [user, :developer] + end + + it 'allows clones' do + download(path, user: user.username, password: user.password) do |response| + expect(response).to have_http_status(200) + end + end + + it 'allows pushes' do + upload(path, user: user.username, password: user.password) do |response| + expect(response).to have_http_status(200) + end + end + end end context "when the project exists" do diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index e02f0eacc93..d20866c0d44 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::ArtifactsController do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb index e0368e6001f..0edbffbcd3b 100644 --- a/spec/requests/projects/cycle_analytics_events_spec.rb +++ b/spec/requests/projects/cycle_analytics_events_spec.rb @@ -1,8 +1,10 @@ require 'spec_helper' describe 'cycle analytics events' do + include ApiHelpers + let(:user) { create(:user) } - let(:project) { create(:project, public_builds: false) } + let(:project) { create(:project, :repository, public_builds: false) } let(:issue) { create(:issue, project: project, created_at: 2.days.ago) } describe 'GET /:namespace/:project/cycle_analytics/events/issues' do @@ -11,7 +13,12 @@ describe 'cycle analytics events' do allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue]) - 3.times { create_cycle } + 3.times do |count| + Timecop.freeze(Time.now + count.days) do + create_cycle + end + end + deploy_master login_as(user) @@ -20,19 +27,19 @@ describe 'cycle analytics events' do it 'lists the issue events' do get namespace_project_cycle_analytics_issue_path(project.namespace, project, format: :json) - expect(json_response['events']).not_to be_empty - - first_issue_iid = Issue.order(created_at: :desc).pluck(:iid).first.to_s + first_issue_iid = project.issues.sort(:created_desc).pluck(:iid).first.to_s + expect(json_response['events']).not_to be_empty expect(json_response['events'].first['iid']).to eq(first_issue_iid) end it 'lists the plan events' do get namespace_project_cycle_analytics_plan_path(project.namespace, project, format: :json) - expect(json_response['events']).not_to be_empty + first_mr_short_sha = project.merge_requests.sort(:created_asc).first.commits.first.short_id - expect(json_response['events'].first['short_sha']).to eq(MergeRequest.last.commits.first.short_id) + expect(json_response['events']).not_to be_empty + expect(json_response['events'].first['short_sha']).to eq(first_mr_short_sha) end it 'lists the code events' do @@ -40,7 +47,7 @@ describe 'cycle analytics events' do expect(json_response['events']).not_to be_empty - first_mr_iid = project.merge_requests.order(id: :desc).pluck(:iid).first.to_s + first_mr_iid = project.merge_requests.sort(:created_desc).pluck(:iid).first.to_s expect(json_response['events'].first['iid']).to eq(first_mr_iid) end @@ -49,17 +56,15 @@ describe 'cycle analytics events' do get namespace_project_cycle_analytics_test_path(project.namespace, project, format: :json) expect(json_response['events']).not_to be_empty - expect(json_response['events'].first['date']).not_to be_empty end it 'lists the review events' do get namespace_project_cycle_analytics_review_path(project.namespace, project, format: :json) - expect(json_response['events']).not_to be_empty - - first_mr_iid = MergeRequest.order(created_at: :desc).pluck(:iid).first.to_s + first_mr_iid = project.merge_requests.sort(:created_desc).pluck(:iid).first.to_s + expect(json_response['events']).not_to be_empty expect(json_response['events'].first['iid']).to eq(first_mr_iid) end @@ -67,35 +72,32 @@ describe 'cycle analytics events' do get namespace_project_cycle_analytics_staging_path(project.namespace, project, format: :json) expect(json_response['events']).not_to be_empty - expect(json_response['events'].first['date']).not_to be_empty end it 'lists the production events' do get namespace_project_cycle_analytics_production_path(project.namespace, project, format: :json) - expect(json_response['events']).not_to be_empty - - first_issue_iid = Issue.order(created_at: :desc).pluck(:iid).first.to_s + first_issue_iid = project.issues.sort(:created_desc).pluck(:iid).first.to_s + expect(json_response['events']).not_to be_empty expect(json_response['events'].first['iid']).to eq(first_issue_iid) end context 'specific branch' do it 'lists the test events' do - branch = MergeRequest.first.source_branch + branch = project.merge_requests.first.source_branch get namespace_project_cycle_analytics_test_path(project.namespace, project, format: :json, branch: branch) expect(json_response['events']).not_to be_empty - expect(json_response['events'].first['date']).not_to be_empty end end context 'with private project and builds' do before do - ProjectMember.first.update(access_level: Gitlab::Access::GUEST) + project.members.first.update(access_level: Gitlab::Access::GUEST) end it 'does not list the test events' do @@ -118,10 +120,6 @@ describe 'cycle analytics events' do end end - def json_response - JSON.parse(response.body) - end - def create_cycle milestone = create(:milestone, project: project) issue.update(milestone: milestone) diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index b19464c7117..ccb72973f9c 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -134,5 +134,17 @@ describe PipelineEntity do expect(subject).not_to have_key(:yaml_errors) end end + + context 'when pipeline ref is empty' do + let(:pipeline) { create(:ci_empty_pipeline) } + + before do + allow(pipeline).to receive(:ref).and_return(nil) + end + + it 'does not generate branch path' do + expect(subject[:ref][:path]).to be_nil + end + end end end diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb index a3fc23ba177..d9f774a1095 100644 --- a/spec/services/ci/register_build_service_spec.rb +++ b/spec/services/ci/register_build_service_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' module Ci describe RegisterBuildService, services: true do - let!(:service) { RegisterBuildService.new } let!(:project) { FactoryGirl.create :empty_project, shared_runners_enabled: false } let!(:pipeline) { FactoryGirl.create :ci_pipeline, project: project } let!(:pending_build) { FactoryGirl.create :ci_build, pipeline: pipeline } @@ -19,29 +18,29 @@ module Ci pending_build.tag_list = ["linux"] pending_build.save specific_runner.tag_list = ["linux"] - expect(service.execute(specific_runner)).to eq(pending_build) + expect(execute(specific_runner)).to eq(pending_build) end it "does not pick build with different tag" do pending_build.tag_list = ["linux"] pending_build.save specific_runner.tag_list = ["win32"] - expect(service.execute(specific_runner)).to be_falsey + expect(execute(specific_runner)).to be_falsey end it "picks build without tag" do - expect(service.execute(specific_runner)).to eq(pending_build) + expect(execute(specific_runner)).to eq(pending_build) end it "does not pick build with tag" do pending_build.tag_list = ["linux"] pending_build.save - expect(service.execute(specific_runner)).to be_falsey + expect(execute(specific_runner)).to be_falsey end it "pick build without tag" do specific_runner.tag_list = ["win32"] - expect(service.execute(specific_runner)).to eq(pending_build) + expect(execute(specific_runner)).to eq(pending_build) end end @@ -56,13 +55,13 @@ module Ci end it 'does not pick a build' do - expect(service.execute(shared_runner)).to be_nil + expect(execute(shared_runner)).to be_nil end end context 'for specific runner' do it 'does not pick a build' do - expect(service.execute(specific_runner)).to be_nil + expect(execute(specific_runner)).to be_nil end end end @@ -86,34 +85,34 @@ module Ci it 'prefers projects without builds first' do # it gets for one build from each of the projects - expect(service.execute(shared_runner)).to eq(build1_project1) - expect(service.execute(shared_runner)).to eq(build1_project2) - expect(service.execute(shared_runner)).to eq(build1_project3) + expect(execute(shared_runner)).to eq(build1_project1) + expect(execute(shared_runner)).to eq(build1_project2) + expect(execute(shared_runner)).to eq(build1_project3) # then it gets a second build from each of the projects - expect(service.execute(shared_runner)).to eq(build2_project1) - expect(service.execute(shared_runner)).to eq(build2_project2) + expect(execute(shared_runner)).to eq(build2_project1) + expect(execute(shared_runner)).to eq(build2_project2) # in the end the third build - expect(service.execute(shared_runner)).to eq(build3_project1) + expect(execute(shared_runner)).to eq(build3_project1) end it 'equalises number of running builds' do # after finishing the first build for project 1, get a second build from the same project - expect(service.execute(shared_runner)).to eq(build1_project1) + expect(execute(shared_runner)).to eq(build1_project1) build1_project1.reload.success - expect(service.execute(shared_runner)).to eq(build2_project1) + expect(execute(shared_runner)).to eq(build2_project1) - expect(service.execute(shared_runner)).to eq(build1_project2) + expect(execute(shared_runner)).to eq(build1_project2) build1_project2.reload.success - expect(service.execute(shared_runner)).to eq(build2_project2) - expect(service.execute(shared_runner)).to eq(build1_project3) - expect(service.execute(shared_runner)).to eq(build3_project1) + expect(execute(shared_runner)).to eq(build2_project2) + expect(execute(shared_runner)).to eq(build1_project3) + expect(execute(shared_runner)).to eq(build3_project1) end end context 'shared runner' do - let(:build) { service.execute(shared_runner) } + let(:build) { execute(shared_runner) } it { expect(build).to be_kind_of(Build) } it { expect(build).to be_valid } @@ -122,7 +121,7 @@ module Ci end context 'specific runner' do - let(:build) { service.execute(specific_runner) } + let(:build) { execute(specific_runner) } it { expect(build).to be_kind_of(Build) } it { expect(build).to be_valid } @@ -137,13 +136,13 @@ module Ci end context 'shared runner' do - let(:build) { service.execute(shared_runner) } + let(:build) { execute(shared_runner) } it { expect(build).to be_nil } end context 'specific runner' do - let(:build) { service.execute(specific_runner) } + let(:build) { execute(specific_runner) } it { expect(build).to be_kind_of(Build) } it { expect(build).to be_valid } @@ -159,17 +158,21 @@ module Ci end context 'and uses shared runner' do - let(:build) { service.execute(shared_runner) } + let(:build) { execute(shared_runner) } it { expect(build).to be_nil } end context 'and uses specific runner' do - let(:build) { service.execute(specific_runner) } + let(:build) { execute(specific_runner) } it { expect(build).to be_nil } end end + + def execute(runner) + described_class.new(runner).execute.build + end end end end diff --git a/spec/services/ci/update_build_queue_service_spec.rb b/spec/services/ci/update_build_queue_service_spec.rb new file mode 100644 index 00000000000..f01a388b895 --- /dev/null +++ b/spec/services/ci/update_build_queue_service_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe Ci::UpdateBuildQueueService, :services do + let(:project) { create(:project) } + let(:build) { create(:ci_build, pipeline: pipeline) } + let(:pipeline) { create(:ci_pipeline, project: project) } + + context 'when updating specific runners' do + let(:runner) { create(:ci_runner) } + + context 'when there are runner that can pick build' do + before { build.project.runners << runner } + + it 'ticks runner queue value' do + expect { subject.execute(build) } + .to change { runner.ensure_runner_queue_value } + end + end + + context 'when there are no runners that can pick build' do + it 'does not tick runner queue value' do + expect { subject.execute(build) } + .not_to change { runner.ensure_runner_queue_value } + end + end + end + + context 'when updating shared runners' do + let(:runner) { create(:ci_runner, :shared) } + + context 'when there are runner that can pick build' do + it 'ticks runner queue value' do + expect { subject.execute(build) } + .to change { runner.ensure_runner_queue_value } + end + end + + context 'when there are no runners that can pick build' do + before { build.tag_list = [:docker] } + + it 'does not tick runner queue value' do + expect { subject.execute(build) } + .not_to change { runner.ensure_runner_queue_value } + end + end + end +end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 7e3705983fb..314ea670a71 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -106,23 +106,46 @@ describe MergeRequests::RefreshService, services: true do context 'push to fork repo source branch' do let(:refresh_service) { service.new(@fork_project, @user) } - before do - allow(refresh_service).to receive(:execute_hooks) - refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') - reload_mrs - end - it 'executes hooks with update action' do - expect(refresh_service).to have_received(:execute_hooks). - with(@fork_merge_request, 'update', @oldrev) + context 'open fork merge request' do + before do + allow(refresh_service).to receive(:execute_hooks) + refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') + reload_mrs + end + + it 'executes hooks with update action' do + expect(refresh_service).to have_received(:execute_hooks). + with(@fork_merge_request, 'update', @oldrev) + end + + it { expect(@merge_request.notes).to be_empty } + it { expect(@merge_request).to be_open } + it { expect(@fork_merge_request.notes.last.note).to include('added 28 commits') } + it { expect(@fork_merge_request).to be_open } + it { expect(@build_failed_todo).to be_pending } + it { expect(@fork_build_failed_todo).to be_pending } end - it { expect(@merge_request.notes).to be_empty } - it { expect(@merge_request).to be_open } - it { expect(@fork_merge_request.notes.last.note).to include('added 28 commits') } - it { expect(@fork_merge_request).to be_open } - it { expect(@build_failed_todo).to be_pending } - it { expect(@fork_build_failed_todo).to be_pending } + context 'closed fork merge request' do + before do + @fork_merge_request.close! + allow(refresh_service).to receive(:execute_hooks) + refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') + reload_mrs + end + + it 'do not execute hooks with update action' do + expect(refresh_service).not_to have_received(:execute_hooks) + end + + it { expect(@merge_request.notes).to be_empty } + it { expect(@merge_request).to be_open } + it { expect(@fork_merge_request.notes).to be_empty } + it { expect(@fork_merge_request).to be_closed } + it { expect(@build_failed_todo).to be_pending } + it { expect(@fork_build_failed_todo).to be_pending } + end end context 'push to fork repo target branch' do @@ -237,6 +260,70 @@ describe MergeRequests::RefreshService, services: true do end end + context 'marking the merge request as work in progress' do + let(:refresh_service) { service.new(@project, @user) } + before do + allow(refresh_service).to receive(:execute_hooks) + end + + it 'marks the merge request as work in progress from fixup commits' do + fixup_merge_request = create(:merge_request, + source_project: @project, + source_branch: 'wip', + target_branch: 'master', + target_project: @project) + commits = fixup_merge_request.commits + oldrev = commits.last.id + newrev = commits.first.id + + refresh_service.execute(oldrev, newrev, 'refs/heads/wip') + fixup_merge_request.reload + + expect(fixup_merge_request.work_in_progress?).to eq(true) + expect(fixup_merge_request.notes.last.note).to match( + /marked as a \*\*Work In Progress\*\* from #{Commit.reference_pattern}/ + ) + end + + it 'references the commit that caused the Work in Progress status' do + refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') + + allow(refresh_service).to receive(:find_new_commits) + refresh_service.instance_variable_set("@commits", [ + instance_double( + Commit, + id: 'aaaaaaa', + short_id: 'aaaaaaa', + title: 'Fix issue', + work_in_progress?: false + ), + instance_double( + Commit, + id: 'bbbbbbb', + short_id: 'bbbbbbb', + title: 'fixup! Fix issue', + work_in_progress?: true, + to_reference: 'bbbbbbb' + ), + instance_double( + Commit, + id: 'ccccccc', + short_id: 'ccccccc', + title: 'fixup! Fix issue', + work_in_progress?: true, + to_reference: 'ccccccc' + ), + ]) + + refresh_service.execute(@oldrev, @newrev, 'refs/heads/wip') + reload_mrs + + expect(@merge_request.notes.last.note).to eq( + "marked as a **Work In Progress** from bbbbbbb" + ) + end + end + def reload_mrs @merge_request.reload @fork_merge_request.reload diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index b0cc3ce5f5a..9c92a5080c6 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -15,39 +15,45 @@ describe Notes::CreateService, services: true do context "valid params" do it 'returns a valid note' do - note = Notes::CreateService.new(project, user, opts).execute + note = described_class.new(project, user, opts).execute expect(note).to be_valid end it 'returns a persisted note' do - note = Notes::CreateService.new(project, user, opts).execute + note = described_class.new(project, user, opts).execute expect(note).to be_persisted end it 'note has valid content' do - note = Notes::CreateService.new(project, user, opts).execute + note = described_class.new(project, user, opts).execute expect(note.note).to eq(opts[:note]) end + it 'note belongs to the correct project' do + note = described_class.new(project, user, opts).execute + + expect(note.project).to eq(project) + end + it 'TodoService#new_note is called' do - note = build(:note) - allow(project).to receive_message_chain(:notes, :new).with(opts) { note } + note = build(:note, project: project) + allow(Note).to receive(:new).with(opts) { note } expect_any_instance_of(TodoService).to receive(:new_note).with(note, user) - Notes::CreateService.new(project, user, opts).execute + described_class.new(project, user, opts).execute end it 'enqueues NewNoteWorker' do - note = build(:note, id: 999) - allow(project).to receive_message_chain(:notes, :new).with(opts) { note } + note = build(:note, id: 999, project: project) + allow(Note).to receive(:new).with(opts) { note } expect(NewNoteWorker).to receive(:perform_async).with(note.id) - Notes::CreateService.new(project, user, opts).execute + described_class.new(project, user, opts).execute end end @@ -75,6 +81,27 @@ describe Notes::CreateService, services: true do end end end + + describe 'personal snippet note' do + subject { described_class.new(nil, user, params).execute } + + let(:snippet) { create(:personal_snippet) } + let(:params) do + { note: 'comment', noteable_type: 'Snippet', noteable_id: snippet.id } + end + + it 'returns a valid note' do + expect(subject).to be_valid + end + + it 'returns a persisted note' do + expect(subject).to be_persisted + end + + it 'note has valid content' do + expect(subject.note).to eq(params[:note]) + end + end end describe "award emoji" do @@ -88,7 +115,7 @@ describe Notes::CreateService, services: true do noteable_type: 'Issue', noteable_id: issue.id } - note = Notes::CreateService.new(project, user, opts).execute + note = described_class.new(project, user, opts).execute expect(note).to be_valid expect(note.name).to eq('smile') @@ -100,7 +127,7 @@ describe Notes::CreateService, services: true do noteable_type: 'Issue', noteable_id: issue.id } - note = Notes::CreateService.new(project, user, opts).execute + note = described_class.new(project, user, opts).execute expect(note).to be_valid expect(note.note).to eq(opts[:note]) @@ -115,7 +142,7 @@ describe Notes::CreateService, services: true do expect_any_instance_of(TodoService).to receive(:new_award_emoji).with(issue, user) - Notes::CreateService.new(project, user, opts).execute + described_class.new(project, user, opts).execute end end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index f3e80ac22a0..309985a5d90 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -33,6 +33,49 @@ describe NotificationService, services: true do end end + # Next shared examples are intended to test notifications of "participants" + # + # they take the following parameters: + # * issuable + # * notification trigger + # * participant + # + shared_examples 'participating by note notification' do + it 'emails the participant' do + create(:note_on_issue, noteable: issuable, project_id: project.id, note: 'anything', author: participant) + + notification_trigger + + should_email(participant) + end + end + + shared_examples 'participating by assignee notification' do + it 'emails the participant' do + issuable.update_attribute(:assignee, participant) + + notification_trigger + + should_email(participant) + end + end + + shared_examples 'participating by author notification' do + it 'emails the participant' do + issuable.author = participant + + notification_trigger + + should_email(participant) + end + end + + shared_examples_for 'participating notifications' do + it_should_behave_like 'participating by note notification' + it_should_behave_like 'participating by author notification' + it_should_behave_like 'participating by assignee notification' + end + describe 'Keys' do describe '#new_key' do let!(:key) { create(:personal_key) } @@ -269,6 +312,55 @@ describe NotificationService, services: true do end end + context 'personal snippet note' do + let(:snippet) { create(:personal_snippet, :public, author: @u_snippet_author) } + let(:note) { create(:note_on_personal_snippet, noteable: snippet, note: '@mentioned note', author: @u_note_author) } + + before do + @u_watcher = create_global_setting_for(create(:user), :watch) + @u_participant = create_global_setting_for(create(:user), :participating) + @u_disabled = create_global_setting_for(create(:user), :disabled) + @u_mentioned = create_global_setting_for(create(:user, username: 'mentioned'), :mention) + @u_mentioned_level = create_global_setting_for(create(:user, username: 'participator'), :mention) + @u_note_author = create(:user, username: 'note_author') + @u_snippet_author = create(:user, username: 'snippet_author') + @u_not_mentioned = create_global_setting_for(create(:user, username: 'regular'), :participating) + + reset_delivered_emails! + end + + let!(:notes) do + [ + create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_watcher), + create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_participant), + create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_mentioned), + create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_disabled), + create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_note_author), + ] + end + + describe '#new_note' do + it 'notifies the participants' do + notification.new_note(note) + + # it emails participants + should_email(@u_watcher) + should_email(@u_participant) + should_email(@u_watcher) + should_email(@u_snippet_author) + + # it emails mentioned users + should_email(@u_mentioned) + + # it does not email participants with mention notification level + should_not_email(@u_mentioned_level) + + # it does not email note author + should_not_email(@u_note_author) + end + end + end + context 'commit note' do let(:project) { create(:project, :public) } let(:note) { create(:note_on_commit, project: project) } @@ -539,32 +631,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - issue.update_attribute(:assignee, @u_lazy_participant) - notification.reassigned_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) } - - before { notification.reassigned_issue(issue, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - issue.author = @u_lazy_participant - notification.reassigned_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { issue } + let(:notification_trigger) { notification.reassigned_issue(issue, @u_disabled) } end end @@ -671,32 +741,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - issue.update_attribute(:assignee, @u_lazy_participant) - notification.close_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) } - - before { notification.close_issue(issue, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - issue.author = @u_lazy_participant - notification.close_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { issue } + let(:notification_trigger) { notification.close_issue(issue, @u_disabled) } end end @@ -723,32 +771,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - issue.update_attribute(:assignee, @u_lazy_participant) - notification.reopen_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) } - - before { notification.reopen_issue(issue, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - issue.author = @u_lazy_participant - notification.reopen_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { issue } + let(:notification_trigger) { notification.reopen_issue(issue, @u_disabled) } end end end @@ -809,31 +835,28 @@ describe NotificationService, services: true do end context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.new_merge_request(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } + it_should_behave_like 'participating by assignee notification' do + let(:participant) { create(:user, username: 'user-participant')} + let(:issuable) { merge_request } + let(:notification_trigger) { notification.new_merge_request(merge_request, @u_disabled) } end - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.new_merge_request(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } + it_should_behave_like 'participating by note notification' do + let(:participant) { create(:user, username: 'user-participant')} + let(:issuable) { merge_request } + let(:notification_trigger) { notification.new_merge_request(merge_request, @u_disabled) } end context 'by author' do + let(:participant) { create(:user, username: 'user-participant')} + before do - merge_request.author = @u_lazy_participant + merge_request.author = participant merge_request.save notification.new_merge_request(merge_request, @u_disabled) end - it { should_not_email(@u_lazy_participant) } + it { should_not_email(participant) } end end end @@ -868,33 +891,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.reassigned_merge_request(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.reassigned_merge_request(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.reassigned_merge_request(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { merge_request } + let(:notification_trigger) { notification.reassigned_merge_request(merge_request, @u_disabled) } end end @@ -965,33 +965,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.close_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.close_mr(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.close_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { merge_request } + let(:notification_trigger) { notification.close_mr(merge_request, @u_disabled) } end end @@ -1032,33 +1009,10 @@ describe NotificationService, services: true do should_not_email(@u_watcher) end - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.merge_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.merge_mr(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.merge_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { merge_request } + let(:notification_trigger) { notification.merge_mr(merge_request, @u_disabled) } end end @@ -1085,33 +1039,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.reopen_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.reopen_mr(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.reopen_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { merge_request } + let(:notification_trigger) { notification.reopen_mr(merge_request, @u_disabled) } end end @@ -1131,33 +1062,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.resolve_all_discussions(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.resolve_all_discussions(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.resolve_all_discussions(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { merge_request } + let(:notification_trigger) { notification.resolve_all_discussions(merge_request, @u_disabled) } end end end diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index fef211ded50..db9f1231682 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -12,6 +12,7 @@ describe SystemHooksService, services: true do it { expect(event_data(user, :create)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username) } it { expect(event_data(user, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username) } it { expect(event_data(project, :create)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) } + it { expect(event_data(project, :update)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) } it { expect(event_data(project, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) } it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) } it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) } @@ -68,6 +69,7 @@ describe SystemHooksService, services: true do it { expect(event_name(project, :destroy)).to eq "project_destroy" } it { expect(event_name(project, :rename)).to eq "project_rename" } it { expect(event_name(project, :transfer)).to eq "project_transfer" } + it { expect(event_name(project, :update)).to eq "project_update" } it { expect(event_name(project_member, :create)).to eq "user_add_to_team" } it { expect(event_name(project_member, :destroy)).to eq "user_remove_from_team" } it { expect(event_name(key, :create)).to eq 'key_create' } diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 4042f2b0512..9f5a0ac4ec6 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -805,4 +805,27 @@ describe SystemNoteService, services: true do noteable.save! end end + + describe '.add_merge_request_wip_from_commit' do + let(:noteable) do + create(:merge_request, source_project: project, target_project: project) + end + + subject do + described_class.add_merge_request_wip_from_commit( + noteable, + project, + author, + noteable.diff_head_commit + ) + end + + it_behaves_like 'a system note' + + it "posts the 'marked as a Work In Progress from commit' system note" do + expect(subject.note).to match( + /marked as a \*\*Work In Progress\*\* from #{Commit.reference_pattern}/ + ) + end + end end diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index ed55791d24e..13d584a8975 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -469,6 +469,13 @@ describe TodoService, services: true do should_create_todo(user: author, target: mr_unassigned, action: Todo::BUILD_FAILED) end + + it 'creates a pending todo for merge_user' do + mr_unassigned.update(merge_when_build_succeeds: true, merge_user: admin) + service.merge_request_build_failed(mr_unassigned) + + should_create_todo(user: admin, author: admin, target: mr_unassigned, action: Todo::BUILD_FAILED) + end end describe '#merge_request_push' do @@ -482,6 +489,15 @@ describe TodoService, services: true do end end + describe '#merge_request_became_unmergeable' do + it 'creates a pending todo for a merge_user' do + mr_unassigned.update(merge_when_build_succeeds: true, merge_user: admin) + service.merge_request_became_unmergeable(mr_unassigned) + + should_create_todo(user: admin, author: admin, target: mr_unassigned, action: Todo::UNMERGEABLE) + end + end + describe '#mark_todo' do it 'creates a todo from a merge request' do service.mark_todo(mr_unassigned, author) diff --git a/spec/services/user_project_access_changed_service_spec.rb b/spec/services/user_project_access_changed_service_spec.rb new file mode 100644 index 00000000000..b4efe7de431 --- /dev/null +++ b/spec/services/user_project_access_changed_service_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe UserProjectAccessChangedService do + describe '#execute' do + it 'schedules the user IDs' do + expect(AuthorizedProjectsWorker).to receive(:bulk_perform_and_wait). + with([[1], [2]]) + + described_class.new([1, 2]).execute + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6ee3307512d..e160c11111b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,11 +2,11 @@ require './spec/simplecov_env' SimpleCovEnv.start! ENV["RAILS_ENV"] ||= 'test' +ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'shoulda/matchers' -require 'sidekiq/testing/inline' require 'rspec/retry' if ENV['CI'] && !ENV['NO_KNAPSACK'] diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb index 35b40d73191..10b90b40ba7 100644 --- a/spec/support/cycle_analytics_helpers/test_generation.rb +++ b/spec/support/cycle_analytics_helpers/test_generation.rb @@ -54,7 +54,7 @@ module CycleAnalyticsHelpers end context "when the data belongs to another project" do - let(:other_project) { create(:project) } + let(:other_project) { create(:project, :repository) } it "returns nil" do # Use a stub to "trick" the data/condition functions diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index f57c82809a6..87936bb4859 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -12,7 +12,7 @@ shared_context 'mentionable context' do let!(:mentioned_mr) { create(:merge_request, source_project: project) } let(:mentioned_commit) { project.commit("HEAD~1") } - let(:ext_proj) { create(:project, :public) } + let(:ext_proj) { create(:project, :public, :repository) } let(:ext_issue) { create(:issue, project: ext_proj) } let(:ext_mr) { create(:merge_request, :simple, source_project: ext_proj) } let(:ext_commit) { ext_proj.commit("HEAD~2") } diff --git a/spec/support/notify_shared_examples.rb b/spec/support/notify_shared_examples.rb index 49867aa5cc4..a3724b801b3 100644 --- a/spec/support/notify_shared_examples.rb +++ b/spec/support/notify_shared_examples.rb @@ -179,9 +179,24 @@ shared_examples 'it should show Gmail Actions View Commit link' do end shared_examples 'an unsubscribeable thread' do + it_behaves_like 'an unsubscribeable thread with incoming address without %{key}' + + it 'has a List-Unsubscribe header in the correct format' do + is_expected.to have_header 'List-Unsubscribe', /unsubscribe/ + is_expected.to have_header 'List-Unsubscribe', /mailto/ + is_expected.to have_header 'List-Unsubscribe', /^<.+,.+>$/ + end + + it { is_expected.to have_body_text /unsubscribe/ } +end + +shared_examples 'an unsubscribeable thread with incoming address without %{key}' do + include_context 'reply-by-email is enabled with incoming address without %{key}' + it 'has a List-Unsubscribe header in the correct format' do is_expected.to have_header 'List-Unsubscribe', /unsubscribe/ - is_expected.to have_header 'List-Unsubscribe', /^<.+>$/ + is_expected.not_to have_header 'List-Unsubscribe', /mailto/ + is_expected.to have_header 'List-Unsubscribe', /^<[^,]+>$/ end it { is_expected.to have_body_text /unsubscribe/ } diff --git a/spec/support/sidekiq.rb b/spec/support/sidekiq.rb new file mode 100644 index 00000000000..575d3451150 --- /dev/null +++ b/spec/support/sidekiq.rb @@ -0,0 +1,5 @@ +require 'sidekiq/testing/inline' + +Sidekiq::Testing.server_middleware do |chain| + chain.add Gitlab::SidekiqStatus::ServerMiddleware +end diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb index 74d9b8c6313..704922b6cf4 100644 --- a/spec/support/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/slack_mattermost_notifications_shared_examples.rb @@ -26,7 +26,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do describe "#execute" do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:username) { 'slack_username' } let(:channel) { 'slack_channel' } @@ -196,7 +196,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do describe "Note events" do let(:user) { create(:user) } - let(:project) { create(:project, creator_id: user.id) } + let(:project) { create(:project, :repository, creator: user) } before do allow(chat_service).to receive_messages( @@ -269,7 +269,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do describe 'Pipeline events' do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, diff --git a/spec/support/taskable_shared_examples.rb b/spec/support/taskable_shared_examples.rb index ad1c783df4d..4056ff06b84 100644 --- a/spec/support/taskable_shared_examples.rb +++ b/spec/support/taskable_shared_examples.rb @@ -33,6 +33,30 @@ shared_examples 'a Taskable' do end end + describe 'with nested tasks' do + before do + subject.description = <<-EOT.strip_heredoc + - [ ] Task a + - [x] Task a.1 + - [ ] Task a.2 + - [ ] Task b + + 1. [ ] Task 1 + 1. [ ] Task 1.1 + 1. [ ] Task 1.2 + 1. [x] Task 2 + 1. [x] Task 2.1 + EOT + end + + it 'returns the correct task status' do + expect(subject.task_status).to match('3 of') + expect(subject.task_status).to match('9 tasks completed') + expect(subject.task_status_short).to match('3/') + expect(subject.task_status_short).to match('9 tasks') + end + end + describe 'with an incomplete task' do before do subject.description = <<-EOT.strip_heredoc @@ -48,6 +72,25 @@ shared_examples 'a Taskable' do end end + describe 'with tasks that are not formatted correctly' do + before do + subject.description = <<-EOT.strip_heredoc + [ ] task 1 + [ ] task 2 + + - [ ]task 1 + -[ ] task 2 + EOT + end + + it 'returns the correct task status' do + expect(subject.task_status).to match('0 of') + expect(subject.task_status).to match('0 tasks completed') + expect(subject.task_status_short).to match('0/') + expect(subject.task_status_short).to match('0 task') + end + end + describe 'with a complete task' do before do subject.description = <<-EOT.strip_heredoc diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 4cf81be3adc..90f1a9c8798 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -35,7 +35,8 @@ module TestEnv 'conflict-missing-side' => 'eb227b3', 'conflict-non-utf8' => 'd0a293c', 'conflict-too-large' => '39fa04f', - 'deleted-image-test' => '6c17798' + 'deleted-image-test' => '6c17798', + 'wip' => 'b9238ee' } # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily diff --git a/spec/views/ci/lints/show.html.haml_spec.rb b/spec/views/ci/lints/show.html.haml_spec.rb index 2dac5ee23c8..3390ae247ff 100644 --- a/spec/views/ci/lints/show.html.haml_spec.rb +++ b/spec/views/ci/lints/show.html.haml_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'ci/lints/show' do - include Devise::TestHelpers + include Devise::Test::ControllerHelpers describe 'XSS protection' do let(:config_processor) { Ci::GitlabCiYamlProcessor.new(YAML.dump(content)) } diff --git a/spec/workers/authorized_projects_worker_spec.rb b/spec/workers/authorized_projects_worker_spec.rb index b6591f272f6..97c4bfcd248 100644 --- a/spec/workers/authorized_projects_worker_spec.rb +++ b/spec/workers/authorized_projects_worker_spec.rb @@ -3,6 +3,18 @@ require 'spec_helper' describe AuthorizedProjectsWorker do let(:worker) { described_class.new } + describe '.bulk_perform_and_wait' do + it 'schedules the ids and waits for the jobs to complete' do + project = create(:project) + + project.owner.project_authorizations.delete_all + + described_class.bulk_perform_and_wait([[project.owner.id]]) + + expect(project.owner.project_authorizations.count).to eq(1) + end + end + describe '#perform' do it "refreshes user's authorized projects" do user = create(:user) |