summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/config/mail_room_spec.rb61
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb14
-rw-r--r--spec/controllers/profiles/notifications_controller_spec.rb45
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb14
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb38
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb61
-rw-r--r--spec/controllers/projects_controller_spec.rb2
-rw-r--r--spec/factories/notes.rb7
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb8
-rw-r--r--spec/features/boards/boards_spec.rb167
-rw-r--r--spec/features/boards/modal_filter_spec.rb183
-rw-r--r--spec/features/copy_as_gfm_spec.rb785
-rw-r--r--spec/features/dashboard/projects_spec.rb26
-rw-r--r--spec/features/groups/group_name_toggle.rb44
-rw-r--r--spec/features/issuables/default_sort_order_spec.rb2
-rw-r--r--spec/features/issues/award_emoji_spec.rb15
-rw-r--r--spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb101
-rw-r--r--spec/features/issues/create_issue_for_single_discussion_in_merge_request.rb81
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb5
-rw-r--r--spec/features/issues/filtered_search/dropdown_milestone_spec.rb8
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb11
-rw-r--r--spec/features/issues/form_spec.rb10
-rw-r--r--spec/features/login_spec.rb12
-rw-r--r--spec/features/merge_requests/form_spec.rb11
-rw-r--r--spec/features/profiles/user_changes_notified_of_own_activity_spec.rb32
-rw-r--r--spec/features/projects/blobs/blob_line_permalink_updater_spec.rb97
-rw-r--r--spec/features/projects/blobs/user_create_spec.rb107
-rw-r--r--spec/features/projects/commit/mini_pipeline_graph_spec.rb55
-rw-r--r--spec/features/projects/environments/environments_spec.rb10
-rw-r--r--spec/features/projects/files/browse_files_spec.rb14
-rw-r--r--spec/features/projects/labels/issues_sorted_by_priority_spec.rb4
-rw-r--r--spec/features/projects/new_project_spec.rb9
-rw-r--r--spec/features/projects/wiki/user_views_project_wiki_page_spec.rb44
-rw-r--r--spec/features/projects_spec.rb4
-rw-r--r--spec/features/tags/master_deletes_tag_spec.rb27
-rw-r--r--spec/features/todos/todos_spec.rb99
-rw-r--r--spec/finders/issues_finder_spec.rb35
-rw-r--r--spec/helpers/blob_helper_spec.rb14
-rw-r--r--spec/helpers/ci_status_helper_spec.rb7
-rw-r--r--spec/helpers/events_helper_spec.rb8
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb20
-rw-r--r--spec/helpers/issues_helper_spec.rb32
-rw-r--r--spec/helpers/milestones_helper_spec.rb50
-rw-r--r--spec/helpers/projects_helper_spec.rb40
-rw-r--r--spec/helpers/todos_helper_spec.rb23
-rw-r--r--spec/javascripts/awards_handler_spec.js3
-rw-r--r--spec/javascripts/blob/create_branch_dropdown_spec.js107
-rw-r--r--spec/javascripts/blob/target_branch_dropdown_spec.js119
-rw-r--r--spec/javascripts/boards/board_new_issue_spec.js1
-rw-r--r--spec/javascripts/boards/boards_store_spec.js1
-rw-r--r--spec/javascripts/bootstrap_jquery_spec.js (renamed from spec/javascripts/extensions/jquery_spec.js)4
-rw-r--r--spec/javascripts/environments/environment_actions_spec.js35
-rw-r--r--spec/javascripts/environments/environment_external_url_spec.js11
-rw-r--r--spec/javascripts/environments/environment_item_spec.js18
-rw-r--r--spec/javascripts/environments/environment_rollback_spec.js42
-rw-r--r--spec/javascripts/environments/environment_spec.js8
-rw-r--r--spec/javascripts/environments/environment_stop_spec.js30
-rw-r--r--spec/javascripts/environments/environment_table_spec.js8
-rw-r--r--spec/javascripts/environments/environment_terminal_button_spec.js24
-rw-r--r--spec/javascripts/environments/environments_store_spec.js4
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js8
-rw-r--r--spec/javascripts/environments/mock_data.js12
-rw-r--r--spec/javascripts/extensions/array_spec.js23
-rw-r--r--spec/javascripts/extensions/element_spec.js38
-rw-r--r--spec/javascripts/extensions/object_spec.js25
-rw-r--r--spec/javascripts/filtered_search/dropdown_utils_spec.js31
-rw-r--r--spec/javascripts/filtered_search/filtered_search_manager_spec.js5
-rw-r--r--spec/javascripts/fixtures/project_branches.json5
-rw-r--r--spec/javascripts/fixtures/target_branch_dropdown.html.haml28
-rw-r--r--spec/javascripts/gl_emoji_spec.js3
-rw-r--r--spec/javascripts/line_highlighter_spec.js18
-rw-r--r--spec/javascripts/monitoring/prometheus_graph_spec.js3
-rw-r--r--spec/javascripts/polyfills/element_spec.js36
-rw-r--r--spec/javascripts/project_title_spec.js2
-rw-r--r--spec/javascripts/right_sidebar_spec.js4
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js8
-rw-r--r--spec/javascripts/test_bundle.js4
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb6
-rw-r--r--spec/lib/expand_variables_spec.rb4
-rw-r--r--spec/lib/gitlab/checks/change_access_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/config/entry/environment_spec.rb4
-rw-r--r--spec/lib/gitlab/conflict/parser_spec.rb89
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb6
-rw-r--r--spec/lib/gitlab/diff/parser_spec.rb48
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb8
-rw-r--r--spec/lib/gitlab/github_import/importer_spec.rb120
-rw-r--r--spec/lib/gitlab/github_import/pull_request_formatter_spec.rb8
-rw-r--r--spec/lib/gitlab/highlight_spec.rb8
-rw-r--r--spec/lib/gitlab/redis_spec.rb59
-rw-r--r--spec/mailers/notify_spec.rb231
-rw-r--r--spec/migrations/rename_more_reserved_project_names_spec.rb47
-rw-r--r--spec/models/ability_spec.rb6
-rw-r--r--spec/models/blob_spec.rb25
-rw-r--r--spec/models/ci/pipeline_spec.rb13
-rw-r--r--spec/models/ci/pipeline_status_spec.rb173
-rw-r--r--spec/models/commit_spec.rb19
-rw-r--r--spec/models/concerns/issuable_spec.rb40
-rw-r--r--spec/models/concerns/milestoneish_spec.rb26
-rw-r--r--spec/models/concerns/relative_positioning_spec.rb120
-rw-r--r--spec/models/environment_spec.rb2
-rw-r--r--spec/models/global_milestone_spec.rb63
-rw-r--r--spec/models/issue_spec.rb26
-rw-r--r--spec/models/merge_request_spec.rb6
-rw-r--r--spec/models/project_spec.rb11
-rw-r--r--spec/models/user_spec.rb21
-rw-r--r--spec/policies/base_policy_spec.rb10
-rw-r--r--spec/requests/api/commits_spec.rb16
-rw-r--r--spec/requests/api/helpers_spec.rb2
-rw-r--r--spec/requests/api/issues_spec.rb41
-rw-r--r--spec/requests/api/projects_spec.rb4
-rw-r--r--spec/requests/api/runner_spec.rb13
-rw-r--r--spec/requests/api/v3/commits_spec.rb15
-rw-r--r--spec/requests/ci/api/builds_spec.rb10
-rw-r--r--spec/requests/ci/api/runners_spec.rb3
-rw-r--r--spec/services/boards/issues/list_service_spec.rb26
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb2
-rw-r--r--spec/services/create_deployment_service_spec.rb8
-rw-r--r--spec/services/issues/build_service_spec.rb56
-rw-r--r--spec/services/issues/create_service_spec.rb85
-rw-r--r--spec/services/issues/resolve_discussions_spec.rb106
-rw-r--r--spec/services/notification_service_spec.rb59
-rw-r--r--spec/services/todo_service_spec.rb24
-rw-r--r--spec/simplecov_env.rb6
-rw-r--r--spec/spec_helper.rb13
-rw-r--r--spec/support/api/issues_resolving_discussions_shared_examples.rb15
-rw-r--r--spec/support/api_helpers.rb4
-rw-r--r--spec/support/capybara.rb9
-rw-r--r--spec/support/features/resolving_discussions_in_issues_shared_examples.rb41
-rw-r--r--spec/support/json_response_helpers.rb9
-rw-r--r--spec/support/seed_helper.rb6
-rw-r--r--spec/views/projects/commit/_commit_box.html.haml_spec.rb2
131 files changed, 3503 insertions, 1393 deletions
diff --git a/spec/config/mail_room_spec.rb b/spec/config/mail_room_spec.rb
index 0b8ff006d22..092048a6259 100644
--- a/spec/config/mail_room_spec.rb
+++ b/spec/config/mail_room_spec.rb
@@ -1,20 +1,36 @@
require 'spec_helper'
describe 'mail_room.yml' do
- let(:config_path) { 'config/mail_room.yml' }
- let(:configuration) { YAML.load(ERB.new(File.read(config_path)).result) }
- before(:each) { clear_raw_config }
- after(:each) { clear_raw_config }
+ include StubENV
- context 'when incoming email is disabled' do
- before do
- ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/config/mail_room_disabled.yml').to_s
- Gitlab::MailRoom.reset_config!
- end
+ let(:mailroom_config_path) { 'config/mail_room.yml' }
+ let(:gitlab_config_path) { 'config/mail_room.yml' }
+ let(:redis_config_path) { 'config/resque.yml' }
- after do
- ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = nil
- end
+ let(:configuration) do
+ vars = {
+ 'MAIL_ROOM_GITLAB_CONFIG_FILE' => absolute_path(gitlab_config_path),
+ 'GITLAB_REDIS_CONFIG_FILE' => absolute_path(redis_config_path)
+ }
+ cmd = "puts ERB.new(File.read(#{absolute_path(mailroom_config_path).inspect})).result"
+
+ output, status = Gitlab::Popen.popen(%W(ruby -rerb -e #{cmd}), absolute_path('config'), vars)
+ raise "Error interpreting #{mailroom_config_path}: #{output}" unless status.zero?
+
+ YAML.load(output)
+ end
+
+ before(:each) do
+ stub_env('GITLAB_REDIS_CONFIG_FILE', absolute_path(redis_config_path))
+ clear_redis_raw_config
+ end
+
+ after(:each) do
+ clear_redis_raw_config
+ end
+
+ context 'when incoming email is disabled' do
+ let(:gitlab_config_path) { 'spec/fixtures/config/mail_room_disabled.yml' }
it 'contains no configuration' do
expect(configuration[:mailboxes]).to be_nil
@@ -22,21 +38,12 @@ describe 'mail_room.yml' do
end
context 'when incoming email is enabled' do
- let(:redis_config) { Rails.root.join('spec/fixtures/config/redis_new_format_host.yml') }
- let(:gitlab_redis) { Gitlab::Redis.new(Rails.env) }
-
- before do
- ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/config/mail_room_enabled.yml').to_s
- Gitlab::MailRoom.reset_config!
- end
+ let(:gitlab_config_path) { 'spec/fixtures/config/mail_room_enabled.yml' }
+ let(:redis_config_path) { 'spec/fixtures/config/redis_new_format_host.yml' }
- after do
- ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = nil
- end
+ let(:gitlab_redis) { Gitlab::Redis.new(Rails.env) }
it 'contains the intended configuration' do
- stub_const('Gitlab::Redis::CONFIG_FILE', redis_config)
-
expect(configuration[:mailboxes].length).to eq(1)
mailbox = configuration[:mailboxes].first
@@ -66,9 +73,13 @@ describe 'mail_room.yml' do
end
end
- def clear_raw_config
+ def clear_redis_raw_config
Gitlab::Redis.remove_instance_variable(:@_raw_config)
rescue NameError
# raised if @_raw_config was not set; ignore
end
+
+ def absolute_path(path)
+ Rails.root.join(path).to_s
+ end
end
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index 7072bd5e87c..71a4a2c43c7 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -49,4 +49,18 @@ describe Dashboard::TodosController do
expect(json_response).to eq({ "count" => "1", "done_count" => "0" })
end
end
+
+ describe 'PATCH #bulk_restore' do
+ let(:todos) { create_list(:todo, 2, :done, user: user, project: project, author: author) }
+
+ it 'restores the todos to pending state' do
+ patch :bulk_restore, ids: todos.map(&:id)
+
+ todos.each do |todo|
+ expect(todo.reload).to be_pending
+ end
+ expect(response).to have_http_status(200)
+ expect(json_response).to eq({ 'count' => '2', 'done_count' => '0' })
+ end
+ end
end
diff --git a/spec/controllers/profiles/notifications_controller_spec.rb b/spec/controllers/profiles/notifications_controller_spec.rb
deleted file mode 100644
index 58caf7999cf..00000000000
--- a/spec/controllers/profiles/notifications_controller_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-require 'spec_helper'
-
-describe Profiles::NotificationsController do
- let(:user) do
- create(:user) do |user|
- user.emails.create(email: 'original@example.com')
- user.emails.create(email: 'new@example.com')
- user.update(notification_email: 'original@example.com')
- user.save!
- end
- end
-
- describe 'GET show' do
- it 'renders' do
- sign_in(user)
-
- get :show
-
- expect(response).to render_template :show
- end
- end
-
- describe 'POST update' do
- it 'updates only permitted attributes' do
- sign_in(user)
-
- put :update, user: { notification_email: 'new@example.com', notified_of_own_activity: true, admin: true }
-
- user.reload
- expect(user.notification_email).to eq('new@example.com')
- expect(user.notified_of_own_activity).to eq(true)
- expect(user.admin).to eq(false)
- expect(controller).to set_flash[:notice].to('Notification settings saved')
- end
-
- it 'shows an error message if the params are invalid' do
- sign_in(user)
-
- put :update, user: { notification_email: '' }
-
- expect(user.reload.notification_email).to eq('original@example.com')
- expect(controller).to set_flash[:alert].to('Failed to save new settings')
- end
- end
-end
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 298a7ff179c..d20e7368086 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -266,5 +266,19 @@ describe Projects::BranchesController do
expect(parsed_response.first).to eq 'master'
end
end
+
+ context 'show_all = true' do
+ it 'returns all the branches name' do
+ get :index,
+ namespace_id: project.namespace,
+ project_id: project,
+ format: :json,
+ show_all: true
+
+ parsed_response = JSON.parse(response.body)
+
+ expect(parsed_response.length).to eq(project.repository.branches.count)
+ end
+ end
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 46c758b4654..6ceaf96f78f 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -104,7 +104,16 @@ describe Projects::IssuesController do
project_with_repository.team << [user, :developer]
mr = create(:merge_request_with_diff_notes, source_project: project_with_repository)
- get :new, namespace_id: project_with_repository.namespace, project_id: project_with_repository, merge_request_for_resolving_discussions: mr.iid
+ get :new, namespace_id: project_with_repository.namespace, project_id: project_with_repository, merge_request_to_resolve_discussions_of: mr.iid
+
+ expect(assigns(:issue).title).not_to be_empty
+ expect(assigns(:issue).description).not_to be_empty
+ end
+
+ it 'fills in an issue for a discussion' do
+ note = create(:note_on_merge_request, project: project)
+
+ get :new, namespace_id: project.namespace.path, project_id: project, merge_request_to_resolve_discussions_of: note.noteable.iid, discussion_to_resolve: note.discussion_id
expect(assigns(:issue).title).not_to be_empty
expect(assigns(:issue).description).not_to be_empty
@@ -462,11 +471,11 @@ describe Projects::IssuesController do
end
let(:merge_request_params) do
- { merge_request_for_resolving_discussions: merge_request.iid }
+ { merge_request_to_resolve_discussions_of: merge_request.iid }
end
- def post_issue(issue_params)
- post :create, namespace_id: project.namespace.to_param, project_id: project, issue: issue_params, merge_request_for_resolving_discussions: merge_request.iid
+ def post_issue(issue_params, other_params: {})
+ post :create, { namespace_id: project.namespace.to_param, project_id: project, issue: issue_params, merge_request_to_resolve_discussions_of: merge_request.iid }.merge(other_params)
end
it 'creates an issue for the project' do
@@ -485,6 +494,27 @@ describe Projects::IssuesController do
expect(discussion.resolved?).to eq(true)
end
+
+ it 'sets a flash message' do
+ post_issue(title: 'Hello')
+
+ expect(flash[:notice]).to eq('Resolved all discussions.')
+ end
+
+ describe "resolving a single discussion" do
+ before do
+ post_issue({ title: 'Hello' }, other_params: { discussion_to_resolve: discussion.id })
+ end
+ it 'resolves a single discussion' do
+ discussion.first_note.reload
+
+ expect(discussion.resolved?).to eq(true)
+ end
+
+ it 'sets a flash message that one discussion was resolved' do
+ expect(flash[:notice]).to eq('Resolved 1 discussion.')
+ end
+ end
end
context 'Akismet is enabled' do
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 4cebe3884bf..952071af57f 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Projects::RawController do
let(:public_project) { create(:project, :public, :repository) }
- describe "#show" do
+ describe '#show' do
context 'regular filename' do
let(:id) { 'master/README.md' }
@@ -16,8 +16,8 @@ describe Projects::RawController do
expect(response).to have_http_status(200)
expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
expect(response.header['Content-Disposition']).
- to eq("inline")
- expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-blob:")
+ to eq('inline')
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
end
@@ -32,7 +32,7 @@ describe Projects::RawController do
expect(response).to have_http_status(200)
expect(response.header['Content-Type']).to eq('image/jpeg')
- expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-blob:")
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
end
@@ -40,32 +40,57 @@ describe Projects::RawController do
let(:id) { 'be93687/files/lfs/lfs_object.iso' }
let!(:lfs_object) { create(:lfs_object, oid: '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', size: '1575078') }
- context 'when project has access' do
+ context 'when lfs is enabled' do
before do
- public_project.lfs_objects << lfs_object
- allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
- allow(controller).to receive(:send_file) { controller.head :ok }
+ allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
end
- it 'serves the file' do
- expect(controller).to receive(:send_file).with("#{Gitlab.config.shared.path}/lfs-objects/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: "lfs_object.iso", disposition: 'attachment')
- get(:show,
- namespace_id: public_project.namespace.to_param,
- project_id: public_project,
- id: id)
+ context 'when project has access' do
+ before do
+ public_project.lfs_objects << lfs_object
+ allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
+ allow(controller).to receive(:send_file) { controller.head :ok }
+ end
- expect(response).to have_http_status(200)
+ it 'serves the file' do
+ expect(controller).to receive(:send_file).with("#{Gitlab.config.shared.path}/lfs-objects/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: 'lfs_object.iso', disposition: 'attachment')
+ get(:show,
+ namespace_id: public_project.namespace.to_param,
+ project_id: public_project,
+ id: id)
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ context 'when project does not have access' do
+ it 'does not serve the file' do
+ get(:show,
+ namespace_id: public_project.namespace.to_param,
+ project_id: public_project,
+ id: id)
+
+ expect(response).to have_http_status(404)
+ end
end
end
- context 'when project does not have access' do
- it 'does not serve the file' do
+ context 'when lfs is not enabled' do
+ before do
+ allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
+ end
+
+ it 'delivers ASCII file' do
get(:show,
namespace_id: public_project.namespace.to_param,
project_id: public_project,
id: id)
- expect(response).to have_http_status(404)
+ expect(response).to have_http_status(200)
+ expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
+ expect(response.header['Content-Disposition']).
+ to eq('inline')
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index a1ec41322ad..a88ffc1ea6a 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -78,10 +78,12 @@ describe ProjectsController do
it 'shows issues list page if wiki is disabled' do
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
+ create(:issue, project: project)
get :show, namespace_id: project.namespace, id: project
expect(response).to render_template('projects/issues/_issues')
+ expect(assigns(:issuable_meta_data)).not_to be_nil
end
it 'shows customize workflow page if wiki and issues are disabled' do
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 5c50cd7f4ad..fe19a404e16 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -26,12 +26,17 @@ FactoryGirl.define do
factory :diff_note_on_merge_request, traits: [:on_merge_request], class: DiffNote do
association :project, :repository
+
+ transient do
+ line_number 14
+ end
+
position do
Gitlab::Diff::Position.new(
old_path: "files/ruby/popen.rb",
new_path: "files/ruby/popen.rb",
old_line: nil,
- new_line: 14,
+ new_line: line_number,
diff_refs: noteable.diff_refs
)
end
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
index a3e24bb5ffa..d17a418b8c3 100644
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ b/spec/features/boards/add_issues_modal_spec.rb
@@ -51,7 +51,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
end
it 'does not show tooltip on add issues button' do
- button = page.find('.issue-boards-search button', text: 'Add issues')
+ button = page.find('.filter-dropdown-container button', text: 'Add issues')
expect(button[:title]).not_to eq("Please add a list to your board first")
end
@@ -107,6 +107,9 @@ describe 'Issue Boards add issue modal', :feature, :js do
it 'returns issues' do
page.within('.add-issues-modal') do
find('.form-control').native.send_keys(issue.title)
+ find('.form-control').native.send_keys(:enter)
+
+ wait_for_vue_resource
expect(page).to have_selector('.card', count: 1)
end
@@ -115,6 +118,9 @@ describe 'Issue Boards add issue modal', :feature, :js do
it 'returns no issues' do
page.within('.add-issues-modal') do
find('.form-control').native.send_keys('testing search')
+ find('.form-control').native.send_keys(:enter)
+
+ wait_for_vue_resource
expect(page).not_to have_selector('.card')
expect(page).not_to have_content("You haven't added any issues to your project yet")
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index ecc356f2505..f7e8b78b54d 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -29,7 +29,7 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'shows tooltip on add issues button' do
- button = page.find('.issue-boards-search button', text: 'Add issues')
+ button = page.find('.filter-dropdown-container button', text: 'Add issues')
expect(button[:"data-original-title"]).to eq("Please add a list to your board first")
end
@@ -115,9 +115,8 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'search done list' do
- page.within('#js-boards-search') do
- find('.form-control').set(issue8.title)
- end
+ find('.filtered-search').set(issue8.title)
+ find('.filtered-search').native.send_keys(:enter)
wait_for_vue_resource
@@ -127,9 +126,8 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'search list' do
- page.within('#js-boards-search') do
- find('.form-control').set(issue5.title)
- end
+ find('.filtered-search').set(issue5.title)
+ find('.filtered-search').native.send_keys(:enter)
wait_for_vue_resource
@@ -333,7 +331,7 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource
- expect(find('.issue-boards-search')).to have_selector('.open')
+ expect(page).to have_css('#js-add-list.open')
end
it 'creates new list from a new label' do
@@ -359,17 +357,9 @@ describe 'Issue Boards', feature: true, js: true do
context 'filtering' do
it 'filters by author' do
- page.within '.issues-filters' do
- click_button('Author')
- wait_for_ajax
-
- page.within '.dropdown-menu-author' do
- click_link(user2.name)
- end
- wait_for_vue_resource
-
- expect(find('.js-author-search')).to have_content(user2.name)
- end
+ set_filter("author", user2.username)
+ click_filter_link(user2.username)
+ submit_filter
wait_for_vue_resource
wait_for_board_cards(1, 1)
@@ -377,17 +367,9 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'filters by assignee' do
- page.within '.issues-filters' do
- click_button('Assignee')
- wait_for_ajax
-
- page.within '.dropdown-menu-assignee' do
- click_link(user.name)
- end
- wait_for_vue_resource
-
- expect(find('.js-assignee-search')).to have_content(user.name)
- end
+ set_filter("assignee", user.username)
+ click_filter_link(user.username)
+ submit_filter
wait_for_vue_resource
@@ -396,17 +378,9 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'filters by milestone' do
- page.within '.issues-filters' do
- click_button('Milestone')
- wait_for_ajax
-
- page.within '.milestone-filter' do
- click_link(milestone.title)
- end
- wait_for_vue_resource
-
- expect(find('.js-milestone-select')).to have_content(milestone.title)
- end
+ set_filter("milestone", "\"#{milestone.title}\"")
+ click_filter_link(milestone.title)
+ submit_filter
wait_for_vue_resource
wait_for_board_cards(1, 1)
@@ -415,16 +389,9 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'filters by label' do
- page.within '.issues-filters' do
- click_button('Label')
- wait_for_ajax
-
- page.within '.dropdown-menu-labels' do
- click_link(testing.title)
- wait_for_vue_resource
- find('.dropdown-menu-close').click
- end
- end
+ set_filter("label", testing.title)
+ click_filter_link(testing.title)
+ submit_filter
wait_for_vue_resource
wait_for_board_cards(1, 1)
@@ -432,19 +399,14 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'filters by label with space after reload' do
- page.within '.issues-filters' do
- click_button('Label')
- wait_for_ajax
-
- page.within '.dropdown-menu-labels' do
- click_link(accepting.title)
- wait_for_vue_resource(spinner: false)
- find('.dropdown-menu-close').click
- end
- end
+ set_filter("label", "\"#{accepting.title}\"")
+ click_filter_link(accepting.title)
+ submit_filter
# Test after reload
page.evaluate_script 'window.location.reload()'
+ wait_for_board_cards(1, 1)
+ wait_for_empty_boards((2..3))
wait_for_vue_resource
@@ -460,26 +422,16 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'removes filtered labels' do
- wait_for_vue_resource
+ set_filter("label", testing.title)
+ click_filter_link(testing.title)
+ submit_filter
- page.within '.labels-filter' do
- click_button('Label')
- wait_for_ajax
-
- page.within '.dropdown-menu-labels' do
- click_link(testing.title)
- wait_for_vue_resource(spinner: false)
- end
-
- expect(page).to have_css('input[name="label_name[]"]', visible: false)
+ wait_for_board_cards(1, 1)
- page.within '.dropdown-menu-labels' do
- click_link(testing.title)
- wait_for_vue_resource(spinner: false)
- end
+ find('.clear-search').click
+ submit_filter
- expect(page).not_to have_css('input[name="label_name[]"]', visible: false)
- end
+ wait_for_board_cards(1, 8)
end
it 'infinite scrolls list with label filter' do
@@ -487,16 +439,9 @@ describe 'Issue Boards', feature: true, js: true do
create(:labeled_issue, project: project, labels: [planning, testing])
end
- page.within '.issues-filters' do
- click_button('Label')
- wait_for_ajax
-
- page.within '.dropdown-menu-labels' do
- click_link(testing.title)
- wait_for_vue_resource
- find('.dropdown-menu-close').click
- end
- end
+ set_filter("label", testing.title)
+ click_filter_link(testing.title)
+ submit_filter
wait_for_vue_resource
@@ -518,18 +463,13 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'filters by multiple labels' do
- page.within '.issues-filters' do
- click_button('Label')
- wait_for_ajax
+ set_filter("label", testing.title)
+ click_filter_link(testing.title)
- page.within(find('.dropdown-menu-labels')) do
- click_link(testing.title)
- wait_for_vue_resource
- click_link(bug.title)
- wait_for_vue_resource
- find('.dropdown-menu-close').click
- end
- end
+ set_filter("label", bug.title)
+ click_filter_link(bug.title)
+
+ submit_filter
wait_for_vue_resource
@@ -545,14 +485,14 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource
end
+ page.within('.tokens-container') do
+ expect(page).to have_content(bug.title)
+ end
+
wait_for_vue_resource
wait_for_board_cards(1, 1)
wait_for_empty_boards((2..3))
-
- page.within('.labels-filter') do
- expect(find('.dropdown-toggle-text')).to have_content(bug.title)
- end
end
it 'removes label filter by clicking label button on issue' do
@@ -560,16 +500,13 @@ describe 'Issue Boards', feature: true, js: true do
page.within(find('.card', match: :first)) do
click_button(bug.title)
end
+
wait_for_vue_resource
expect(page).to have_selector('.card', count: 1)
end
wait_for_vue_resource
-
- page.within('.labels-filter') do
- expect(find('.dropdown-toggle-text')).to have_content(bug.title)
- end
end
end
end
@@ -643,4 +580,20 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_board_cards(board, 0)
end
end
+
+ def set_filter(type, text)
+ find('.filtered-search').native.send_keys("#{type}:#{text}")
+ end
+
+ def submit_filter
+ find('.filtered-search').native.send_keys(:enter)
+ end
+
+ def click_filter_link(link_text)
+ page.within('.filtered-search-input-container') do
+ expect(page).to have_button(link_text)
+
+ click_button(link_text)
+ end
+ end
end
diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb
index 1cf0d11d448..e2281a7da55 100644
--- a/spec/features/boards/modal_filter_spec.rb
+++ b/spec/features/boards/modal_filter_spec.rb
@@ -1,7 +1,6 @@
require 'rails_helper'
describe 'Issue Boards add issue modal filtering', :feature, :js do
- include WaitForAjax
include WaitForVueResource
let(:project) { create(:empty_project, :public) }
@@ -23,6 +22,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
page.within('.add-issues-modal') do
find('.form-control').native.send_keys('testing empty state')
+ find('.form-control').native.send_keys(:enter)
wait_for_vue_resource
@@ -33,13 +33,11 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
it 'restores filters when closing' do
visit_board
- page.within('.add-issues-modal') do
- click_button 'Milestone'
-
- wait_for_ajax
-
- click_link 'Upcoming'
+ set_filter('milestone')
+ click_filter_link('Upcoming')
+ submit_filter
+ page.within('.add-issues-modal') do
wait_for_vue_resource
expect(page).to have_selector('.card', count: 0)
@@ -56,39 +54,44 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
end
end
- context 'author' do
- let!(:issue) { create(:issue, project: project, author: user2) }
-
- before do
- project.team << [user2, :developer]
+ it 'resotres filters after clicking clear button' do
+ visit_board
- visit_board
- end
+ set_filter('milestone')
+ click_filter_link('Upcoming')
+ submit_filter
- it 'filters by any author' do
- page.within('.add-issues-modal') do
- click_button 'Author'
+ page.within('.add-issues-modal') do
+ wait_for_vue_resource
- wait_for_ajax
+ expect(page).to have_selector('.card', count: 0)
- click_link 'Any Author'
+ find('.clear-search').click
- wait_for_vue_resource
+ wait_for_vue_resource
- expect(page).to have_selector('.card', count: 2)
- end
+ expect(page).to have_selector('.card', count: 1)
end
+ end
- it 'filters by selected user' do
- page.within('.add-issues-modal') do
- click_button 'Author'
+ context 'author' do
+ let!(:issue) { create(:issue, project: project, author: user2) }
+
+ before do
+ project.team << [user2, :developer]
- wait_for_ajax
+ visit_board
+ end
- click_link user2.name
+ it 'filters by selected user' do
+ set_filter('author')
+ click_filter_link(user2.name)
+ submit_filter
+ page.within('.add-issues-modal') do
wait_for_vue_resource
+ expect(page).to have_selector('.js-visual-token', text: user2.username)
expect(page).to have_selector('.card', count: 1)
end
end
@@ -103,46 +106,28 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
visit_board
end
- it 'filters by any assignee' do
- page.within('.add-issues-modal') do
- click_button 'Assignee'
-
- wait_for_ajax
-
- click_link 'Any Assignee'
-
- wait_for_vue_resource
-
- expect(page).to have_selector('.card', count: 2)
- end
- end
-
it 'filters by unassigned' do
- page.within('.add-issues-modal') do
- click_button 'Assignee'
-
- wait_for_ajax
-
- click_link 'Unassigned'
+ set_filter('assignee')
+ click_filter_link('No Assignee')
+ submit_filter
+ page.within('.add-issues-modal') do
wait_for_vue_resource
+ expect(page).to have_selector('.js-visual-token', text: 'none')
expect(page).to have_selector('.card', count: 1)
end
end
it 'filters by selected user' do
- page.within('.add-issues-modal') do
- click_button 'Assignee'
-
- wait_for_ajax
-
- page.within '.dropdown-menu-user' do
- click_link user2.name
- end
+ set_filter('assignee')
+ click_filter_link(user2.name)
+ submit_filter
+ page.within('.add-issues-modal') do
wait_for_vue_resource
+ expect(page).to have_selector('.js-visual-token', text: user2.username)
expect(page).to have_selector('.card', count: 1)
end
end
@@ -156,44 +141,28 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
visit_board
end
- it 'filters by any milestone' do
- page.within('.add-issues-modal') do
- click_button 'Milestone'
-
- wait_for_ajax
-
- click_link 'Any Milestone'
-
- wait_for_vue_resource
-
- expect(page).to have_selector('.card', count: 2)
- end
- end
-
it 'filters by upcoming milestone' do
- page.within('.add-issues-modal') do
- click_button 'Milestone'
-
- wait_for_ajax
-
- click_link 'Upcoming'
+ set_filter('milestone')
+ click_filter_link('Upcoming')
+ submit_filter
+ page.within('.add-issues-modal') do
wait_for_vue_resource
+ expect(page).to have_selector('.js-visual-token', text: 'upcoming')
expect(page).to have_selector('.card', count: 0)
end
end
it 'filters by selected milestone' do
- page.within('.add-issues-modal') do
- click_button 'Milestone'
-
- wait_for_ajax
-
- click_link milestone.name
+ set_filter('milestone')
+ click_filter_link(milestone.name)
+ submit_filter
+ page.within('.add-issues-modal') do
wait_for_vue_resource
+ expect(page).to have_selector('.js-visual-token', text: milestone.name)
expect(page).to have_selector('.card', count: 1)
end
end
@@ -207,44 +176,28 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
visit_board
end
- it 'filters by any label' do
- page.within('.add-issues-modal') do
- click_button 'Label'
-
- wait_for_ajax
-
- click_link 'Any Label'
-
- wait_for_vue_resource
-
- expect(page).to have_selector('.card', count: 2)
- end
- end
-
it 'filters by no label' do
- page.within('.add-issues-modal') do
- click_button 'Label'
-
- wait_for_ajax
-
- click_link 'No Label'
+ set_filter('label')
+ click_filter_link('No Label')
+ submit_filter
+ page.within('.add-issues-modal') do
wait_for_vue_resource
+ expect(page).to have_selector('.js-visual-token', text: 'none')
expect(page).to have_selector('.card', count: 1)
end
end
it 'filters by label' do
- page.within('.add-issues-modal') do
- click_button 'Label'
-
- wait_for_ajax
-
- click_link label.title
+ set_filter('label')
+ click_filter_link(label.title)
+ submit_filter
+ page.within('.add-issues-modal') do
wait_for_vue_resource
+ expect(page).to have_selector('.js-visual-token', text: label.title)
expect(page).to have_selector('.card', count: 1)
end
end
@@ -256,4 +209,20 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
click_button('Add issues')
end
+
+ def set_filter(type, text = '')
+ find('.add-issues-modal .filtered-search').native.send_keys("#{type}:#{text}")
+ end
+
+ def submit_filter
+ find('.add-issues-modal .filtered-search').native.send_keys(:enter)
+ end
+
+ def click_filter_link(link_text)
+ page.within('.add-issues-modal .filtered-search-input-container') do
+ expect(page).to have_button(link_text)
+
+ click_button(link_text)
+ end
+ end
end
diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb
index 4638812b2d9..55df7e45f79 100644
--- a/spec/features/copy_as_gfm_spec.rb
+++ b/spec/features/copy_as_gfm_spec.rb
@@ -2,437 +2,594 @@ require 'spec_helper'
describe 'Copy as GFM', feature: true, js: true do
include GitlabMarkdownHelper
+ include RepoHelpers
include ActionView::Helpers::JavaScriptHelper
before do
- @feat = MarkdownFeature.new
+ login_as :admin
+ end
- # `markdown` helper expects a `@project` variable
- @project = @feat.project
+ describe 'Copying rendered GFM' do
+ before do
+ @feat = MarkdownFeature.new
- visit namespace_project_issue_path(@project.namespace, @project, @feat.issue)
- end
+ # `markdown` helper expects a `@project` variable
+ @project = @feat.project
- # 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.
+ visit namespace_project_issue_path(@project.namespace, @project, @feat.issue)
+ end
- # These are all in a single `it` for performance reasons.
- it 'works', :aggregate_failures do
- verify(
- 'nesting',
+ # 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 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.
- '> 1. [x] **[$`2 + 2`$ {-=-}{+=+} 2^2 ~~:thumbsup:~~](http://google.com)**'
- )
+ # These are all in a single `it` for performance reasons.
+ it 'works', :aggregate_failures do
+ verify(
+ 'nesting',
- verify(
- 'a real world example from the gitlab-ce README',
+ '> 1. [x] **[$`2 + 2`$ {-=-}{+=+} 2^2 ~~:thumbsup:~~](http://google.com)**'
+ )
- <<-GFM.strip_heredoc
- # GitLab
+ verify(
+ 'a real world example from the gitlab-ce README',
- [![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)
+ <<-GFM.strip_heredoc
+ # GitLab
- ## Canonical source
+ [![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)
- The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/).
+ ## Canonical source
- ## Open source software to collaborate on code
+ The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/).
- To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/).
+ ## 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
+ - Manage Git repositories with fine grained access controls that keep your code secure
- - Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications
+ - Perform code reviews and enhance collaboration with merge requests
- - Each project can also have an issue tracker, issue board, and a wiki
+ - Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications
- - Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises
+ - Each project can also have an issue tracker, issue board, and a wiki
- - Completely free and open source (MIT Expat license)
- GFM
- )
+ - Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises
- verify(
- 'InlineDiffFilter',
+ - Completely free and open source (MIT Expat license)
+ GFM
+ )
- '{-Deleted text-}',
- '{+Added text+}'
- )
+ verify(
+ 'InlineDiffFilter',
- verify(
- 'TaskListFilter',
+ '{-Deleted text-}',
+ '{+Added text+}'
+ )
- '- [ ] Unchecked task',
- '- [x] Checked task',
- '1. [ ] Unchecked numbered task',
- '1. [x] Checked numbered task'
- )
+ verify(
+ 'TaskListFilter',
- verify(
- 'ReferenceFilter',
+ '- [ ] Unchecked task',
+ '- [x] Checked task',
+ '1. [ ] Unchecked numbered task',
+ '1. [x] Checked numbered task'
+ )
- # 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(
+ 'ReferenceFilter',
- verify(
- 'AutolinkFilter',
+ # 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')})",
+ )
- 'https://example.com'
- )
+ verify(
+ 'AutolinkFilter',
- verify(
- 'TableOfContentsFilter',
+ 'https://example.com'
+ )
- '[[_TOC_]]'
- )
+ verify(
+ 'TableOfContentsFilter',
- verify(
- 'EmojiFilter',
+ '[[_TOC_]]'
+ )
- ':thumbsup:'
- )
+ verify(
+ 'EmojiFilter',
- verify(
- 'ImageLinkFilter',
-
- '![Image](https://example.com/image.png)'
- )
+ ':thumbsup:'
+ )
- verify(
- 'VideoLinkFilter',
+ verify(
+ 'ImageLinkFilter',
+
+ '![Image](https://example.com/image.png)'
+ )
- '![Video](https://example.com/video.mp4)'
- )
+ verify(
+ 'VideoLinkFilter',
- verify(
- 'MathFilter: math as converted from GFM to HTML',
+ '![Video](https://example.com/video.mp4)'
+ )
- '$`c = \pm\sqrt{a^2 + b^2}`$',
+ verify(
+ 'MathFilter: math as converted from GFM to HTML',
- # math block
- <<-GFM.strip_heredoc
- ```math
- c = \pm\sqrt{a^2 + b^2}
- ```
- GFM
- )
+ '$`c = \pm\sqrt{a^2 + b^2}`$',
- aggregate_failures('MathFilter: math as transformed from HTML to KaTeX') do
- gfm = '$`c = \pm\sqrt{a^2 + b^2}`$'
+ # math block
+ <<-GFM.strip_heredoc
+ ```math
+ c = \pm\sqrt{a^2 + b^2}
+ ```
+ GFM
+ )
- 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>
+ 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 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 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 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 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="reset-textstyle textstyle uncramped sqrt-line"></span>
+ <span class="baseline-fix">
+ <span class="fontsize-ensurer reset-size5 size5">
+ <span class="" style="font-size: 1em;">​</span>
+ </span>
+ ​</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
+ HTML
- output_gfm = html_to_gfm(html)
- expect(output_gfm.strip).to eq(gfm.strip)
- end
+ output_gfm = html_to_gfm(html)
+ expect(output_gfm.strip).to eq(gfm.strip)
+ end
- verify(
- 'SanitizationFilter',
+ verify(
+ 'SanitizationFilter',
- <<-GFM.strip_heredoc
- <a name="named-anchor"></a>
+ <<-GFM.strip_heredoc
+ <a name="named-anchor"></a>
- <sub>sub</sub>
+ <sub>sub</sub>
- <dl>
- <dt>dt</dt>
- <dd>dd</dd>
- </dl>
+ <dl>
+ <dt>dt</dt>
+ <dd>dd</dd>
+ </dl>
- <kbd>kbd</kbd>
+ <kbd>kbd</kbd>
- <q>q</q>
+ <q>q</q>
- <samp>samp</samp>
+ <samp>samp</samp>
- <var>var</var>
+ <var>var</var>
- <ruby>ruby</ruby>
+ <ruby>ruby</ruby>
- <rt>rt</rt>
+ <rt>rt</rt>
- <rp>rp</rp>
+ <rp>rp</rp>
- <abbr>abbr</abbr>
+ <abbr>abbr</abbr>
- <summary>summary</summary>
+ <summary>summary</summary>
- <details>details</details>
- GFM
- )
+ <details>details</details>
+ GFM
+ )
- verify(
- 'SanitizationFilter',
+ verify(
+ 'SanitizationFilter',
- <<-GFM.strip_heredoc,
- ```
- Plain text
- ```
- GFM
+ <<-GFM.strip_heredoc,
+ ```
+ Plain text
+ ```
+ GFM
- <<-GFM.strip_heredoc,
- ```ruby
- def foo
- bar
- end
- ```
- GFM
+ <<-GFM.strip_heredoc,
+ ```ruby
+ def foo
+ bar
+ end
+ ```
+ GFM
+
+ <<-GFM.strip_heredoc
+ Foo
+
+ This is an example of GFM
- <<-GFM.strip_heredoc
- Foo
+ ```js
+ Code goes here
+ ```
+ GFM
+ )
- This is an example of GFM
+ verify(
+ 'MarkdownFilter',
- ```js
- Code goes here
- ```
- GFM
- )
+ "Line with two spaces at the end \nto insert a linebreak",
- verify(
- 'MarkdownFilter',
+ '`code`',
+ '`` code with ` ticks ``',
- "Line with two spaces at the end \nto insert a linebreak",
+ '> Quote',
- '`code`',
- '`` code with ` ticks ``',
+ # multiline quote
+ <<-GFM.strip_heredoc,
+ > Multiline
+ > Quote
+ >
+ > With multiple paragraphs
+ GFM
- '> Quote',
+ '![Image](https://example.com/image.png)',
- # multiline quote
- <<-GFM.strip_heredoc,
- > Multiline
- > Quote
- >
- > With multiple paragraphs
- GFM
+ '# Heading with no anchor link',
- '![Image](https://example.com/image.png)',
+ '[Link](https://example.com)',
- '# Heading with no anchor link',
+ '- List item',
- '[Link](https://example.com)',
+ # multiline list item
+ <<-GFM.strip_heredoc,
+ - Multiline
+ List item
+ GFM
- '- List item',
+ # nested lists
+ <<-GFM.strip_heredoc,
+ - Nested
- # 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
- - Lists
- GFM
+ > Blockquote
+ GFM
- # list with blockquote
- <<-GFM.strip_heredoc,
- - List
+ '1. Numbered list item',
- > Blockquote
- GFM
+ # multiline numbered list item
+ <<-GFM.strip_heredoc,
+ 1. Multiline
+ Numbered list item
+ GFM
- '1. Numbered list item',
+ # nested numbered list
+ <<-GFM.strip_heredoc,
+ 1. Nested
- # 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',
- 1. Numbered lists
- GFM
+ '**Bold**',
- '# Heading',
- '## Heading',
- '### Heading',
- '#### Heading',
- '##### Heading',
- '###### Heading',
+ '_Italics_',
- '**Bold**',
+ '~~Strikethrough~~',
- '_Italics_',
+ '2^2',
- '~~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
- # table
- <<-GFM.strip_heredoc,
- | Centered | Right | Left |
- |:--------:|------:|------|
- | Foo | Bar | **Baz** |
- | Foo | Bar | **Baz** |
- GFM
+ 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
- # table with empty heading
- <<-GFM.strip_heredoc,
- | | x | y |
- |---|---|---|
- | a | 1 | 0 |
- | b | 0 | 1 |
- GFM
- )
+ # Fake a `current_user` helper
+ def current_user
+ @feat.user
+ end
end
- alias_method :gfm_to_html, :markdown
+ describe 'Copying code' do
+ let(:project) { create(:project) }
+
+ context 'from a diff' do
+ before do
+ visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
+ end
+
+ context 'selecting one word of text' do
+ it 'copies as inline code' do
+ verify(
+ '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"] .line .no',
- def html_to_gfm(html)
+ '`RuntimeError`'
+ )
+ end
+ end
+
+ context 'selecting one line of text' do
+ it 'copies as inline code' do
+ verify(
+ '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"] .line',
+
+ '`raise RuntimeError, "System commands must be given as an array of strings"`'
+ )
+ end
+ end
+
+ context 'selecting multiple lines of text' do
+ it 'copies as a code block' do
+ verify(
+ '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]',
+
+ <<-GFM.strip_heredoc,
+ ```ruby
+ raise RuntimeError, "System commands must be given as an array of strings"
+ end
+ ```
+ GFM
+ )
+ end
+ end
+ end
+
+ context 'from a blob' do
+ before do
+ visit namespace_project_blob_path(project.namespace, project, File.join('master', 'files/ruby/popen.rb'))
+ end
+
+ context 'selecting one word of text' do
+ it 'copies as inline code' do
+ verify(
+ '.line[id="LC9"] .no',
+
+ '`RuntimeError`'
+ )
+ end
+ end
+
+ context 'selecting one line of text' do
+ it 'copies as inline code' do
+ verify(
+ '.line[id="LC9"]',
+
+ '`raise RuntimeError, "System commands must be given as an array of strings"`'
+ )
+ end
+ end
+
+ context 'selecting multiple lines of text' do
+ it 'copies as a code block' do
+ verify(
+ '.line[id="LC9"], .line[id="LC10"]',
+
+ <<-GFM.strip_heredoc,
+ ```ruby
+ raise RuntimeError, "System commands must be given as an array of strings"
+ end
+ ```
+ GFM
+ )
+ end
+ end
+ end
+
+ context 'from a GFM code block' do
+ before do
+ visit namespace_project_blob_path(project.namespace, project, File.join('markdown', 'doc/api/users.md'))
+ end
+
+ context 'selecting one word of text' do
+ it 'copies as inline code' do
+ verify(
+ '.line[id="LC27"] .s2',
+
+ '`"bio"`'
+ )
+ end
+ end
+
+ context 'selecting one line of text' do
+ it 'copies as inline code' do
+ verify(
+ '.line[id="LC27"]',
+
+ '`"bio": null,`'
+ )
+ end
+ end
+
+ context 'selecting multiple lines of text' do
+ it 'copies as a code block with the correct language' do
+ verify(
+ '.line[id="LC27"], .line[id="LC28"]',
+
+ <<-GFM.strip_heredoc,
+ ```json
+ "bio": null,
+ "skype": "",
+ ```
+ GFM
+ )
+ end
+ end
+ end
+
+ def verify(selector, gfm)
+ html = html_for_selector(selector)
+ output_gfm = html_to_gfm(html, 'transformCodeSelection')
+ expect(output_gfm.strip).to eq(gfm.strip)
+ end
+ end
+
+ def html_for_selector(selector)
+ js = <<-JS.strip_heredoc
+ (function(selector) {
+ var els = document.querySelectorAll(selector);
+ var htmls = _.map(els, function(el) { return el.outerHTML; });
+ return htmls.join("\\n");
+ })("#{escape_javascript(selector)}")
+ JS
+ page.evaluate_script(js)
+ end
+
+ def html_to_gfm(html, transformer = 'transformGFMSelection')
js = <<-JS.strip_heredoc
(function(html) {
+ var transformer = window.gl.CopyAsGFM[#{transformer.inspect}];
+
var node = document.createElement('div');
node.innerHTML = html;
+
+ node = transformer(node);
+ if (!node) return null;
+
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/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 63eb5c697c2..c4e58d14f75 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -1,10 +1,32 @@
require 'spec_helper'
RSpec.describe 'Dashboard Projects', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, name: "awesome stuff") }
+
before do
- login_as(create :user)
+ project.team << [user, :developer]
+ login_as user
visit dashboard_projects_path
end
-
+
+ it 'shows the project the user in a member of in the list' do
+ visit dashboard_projects_path
+ expect(page).to have_content('awesome stuff')
+ end
+
+ describe "with a pipeline" do
+ let(:pipeline) { create(:ci_pipeline, :success, project: project, sha: project.commit.sha) }
+
+ before do
+ pipeline
+ end
+
+ it 'shows that the last pipeline passed' do
+ visit dashboard_projects_path
+ expect(page).to have_xpath("//a[@href='#{pipelines_namespace_project_commit_path(project.namespace, project, project.commit)}']")
+ end
+ end
+
it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
end
diff --git a/spec/features/groups/group_name_toggle.rb b/spec/features/groups/group_name_toggle.rb
new file mode 100644
index 00000000000..ada4ac66e04
--- /dev/null
+++ b/spec/features/groups/group_name_toggle.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+feature 'Group name toggle', js: true do
+ let(:group) { create(:group) }
+ let(:nested_group_1) { create(:group, parent: group) }
+ let(:nested_group_2) { create(:group, parent: nested_group_1) }
+ let(:nested_group_3) { create(:group, parent: nested_group_2) }
+
+ before do
+ login_as :user
+ end
+
+ it 'is not present for less than 3 groups' do
+ visit group_path(group)
+ expect(page).not_to have_css('.group-name-toggle')
+
+ visit group_path(nested_group_1)
+ expect(page).not_to have_css('.group-name-toggle')
+ end
+
+ it 'is present for nested group of 3 or more in the namespace' do
+ visit group_path(nested_group_2)
+ expect(page).to have_css('.group-name-toggle')
+
+ visit group_path(nested_group_3)
+ expect(page).to have_css('.group-name-toggle')
+ end
+
+ context 'for group with at least 3 groups' do
+ before do
+ visit group_path(nested_group_2)
+ end
+
+ it 'should show the full group namespace when toggled' do
+ expect(page).not_to have_content(group.name)
+ expect(page).to have_css('.group-path.hidable', visible: false)
+
+ click_button '...'
+
+ expect(page).to have_content(group.name)
+ expect(page).to have_css('.group-path.hidable', visible: true)
+ end
+ end
+end
diff --git a/spec/features/issuables/default_sort_order_spec.rb b/spec/features/issuables/default_sort_order_spec.rb
index 73553f97d6f..bfe43bff10f 100644
--- a/spec/features/issuables/default_sort_order_spec.rb
+++ b/spec/features/issuables/default_sort_order_spec.rb
@@ -176,7 +176,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
end
def selected_sort_order
- find('.pull-right .dropdown button').text.downcase
+ find('.filter-dropdown-container .dropdown button').text.downcase
end
def visit_merge_requests_with_state(project, state)
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
index f424186cf30..16e453bc328 100644
--- a/spec/features/issues/award_emoji_spec.rb
+++ b/spec/features/issues/award_emoji_spec.rb
@@ -17,8 +17,21 @@ describe 'Awards Emoji', feature: true do
login_as(user)
end
+ describe 'visiting an issue with a legacy award emoji that is not valid anymore' do
+ before do
+ # The `heart_tip` emoji is not valid anymore so we need to skip validation
+ issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false)
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ # Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529
+ it 'does not shows a 500 page' do
+ expect(page).to have_text(issue.title)
+ end
+ end
+
describe 'Click award emoji from issue#show' do
- let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") }
+ let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") }
before do
visit namespace_project_issue_path(project.namespace, project, issue)
diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
index 762cab0c0e1..572bca3de21 100644
--- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
+++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
@@ -1,76 +1,93 @@
require 'rails_helper'
-feature 'Resolving all open discussions in a merge request from an issue', feature: true do
+feature 'Resolving all open discussions in a merge request from an issue', feature: true, js: true do
let(:user) { create(:user) }
- let(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: true) }
+ let(:project) { create(:project) }
let(:merge_request) { create(:merge_request, source_project: project) }
let!(:discussion) { Discussion.for_diff_notes([create(:diff_note_on_merge_request, noteable: merge_request, project: project)]).first }
- before do
- project.team << [user, :master]
- login_as user
- end
-
- context 'with the internal tracker disabled' do
+ describe 'as a user with access to the project' do
before do
- project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
+ project.team << [user, :master]
+ login_as user
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
end
- it 'does not show a link to create a new issue' do
- expect(page).not_to have_link 'open an issue to resolve them later'
- end
- end
-
- context 'merge request has discussions that need to be resolved' do
- before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ it 'shows a button to resolve all discussions by creating a new issue' do
+ within('li#resolve-count-app') do
+ expect(page).to have_link "Resolve all discussions in new issue", href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+ end
end
- it 'shows a warning that the merge request contains unresolved discussions' do
- expect(page).to have_content 'This merge request has unresolved discussions'
- end
+ context 'resolving the discussion' do
+ before do
+ click_button 'Resolve discussion'
+ end
- it 'has a link to resolve all discussions by creating an issue' do
- page.within '.mr-widget-body' do
- expect(page).to have_link 'open an issue to resolve them later', href: new_namespace_project_issue_path(project.namespace, project, merge_request_for_resolving_discussions: merge_request.iid)
+ it 'hides the link for creating a new issue' do
+ expect(page).not_to have_link "Resolve all discussions in new issue", href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
end
context 'creating an issue for discussions' do
before do
- page.click_link 'open an issue to resolve them later', href: new_namespace_project_issue_path(project.namespace, project, merge_request_for_resolving_discussions: merge_request.iid)
+ click_link "Resolve all discussions in new issue", href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
- it 'shows an issue with the title filled in' do
- title_field = page.find_field('issue[title]')
+ it_behaves_like 'creating an issue for a discussion'
+ end
- expect(title_field.value).to include(merge_request.title)
+ context 'for a project where all discussions need to be resolved before merging' do
+ before do
+ project.update_attribute(:only_allow_merge_if_all_discussions_are_resolved, true)
end
- it 'has a mention of the discussion in the description' do
- description_field = page.find_field('issue[description]')
+ context 'with the internal tracker disabled' do
+ before do
+ project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
- expect(description_field.value).to include(discussion.first_note.note)
+ it 'does not show a link to create a new issue' do
+ expect(page).not_to have_link 'open an issue to resolve them later'
+ end
end
- it 'has a hidden field for the merge request' do
- merge_request_field = find('#merge_request_for_resolving_discussions', visible: false)
-
- expect(merge_request_field.value).to eq(merge_request.iid.to_s)
- end
+ context 'merge request has discussions that need to be resolved' do
+ before do
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
- it 'can create a new issue for the project' do
- expect { click_button 'Submit issue' }.to change { project.issues.reload.size }.by(1)
- end
+ it 'shows a warning that the merge request contains unresolved discussions' do
+ expect(page).to have_content 'This merge request has unresolved discussions'
+ end
- it 'resolves the discussion in the merge request' do
- click_button 'Submit issue'
+ it 'has a link to resolve all discussions by creating an issue' do
+ page.within '.mr-widget-body' do
+ expect(page).to have_link 'open an issue to resolve them later', href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+ end
+ end
- discussion.first_note.reload
+ context 'creating an issue for discussions' do
+ before do
+ page.click_link 'open an issue to resolve them later', href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+ end
- expect(discussion.resolved?).to eq(true)
+ it_behaves_like 'creating an issue for a discussion'
+ end
end
end
end
+
+ describe 'as a reporter' do
+ before do
+ project.team << [user, :reporter]
+ login_as user
+ visit new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+ end
+
+ it 'Shows a notice to ask someone else to resolve the discussions' do
+ expect(page).to have_content("The discussions at #{merge_request.to_reference} will stay unresolved. Ask someone with permission to resolve them.")
+ end
+ end
end
diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request.rb
new file mode 100644
index 00000000000..88e2cc60d79
--- /dev/null
+++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request.rb
@@ -0,0 +1,81 @@
+require 'rails_helper'
+
+feature 'Resolve an open discussion in a merge request by creating an issue', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: true) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let!(:discussion) { Discussion.for_diff_notes([create(:diff_note_on_merge_request, noteable: merge_request, project: project)]).first }
+
+ describe 'As a user with access to the project' do
+ before do
+ project.team << [user, :master]
+ login_as user
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ context 'with the internal tracker disabled' do
+ before do
+ project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ it 'does not show a link to create a new issue' do
+ expect(page).not_to have_link 'Resolve this discussion in a new issue'
+ end
+ end
+
+ context 'resolving the discussion', js: true do
+ before do
+ click_button 'Resolve discussion'
+ end
+
+ it 'hides the link for creating a new issue' do
+ expect(page).not_to have_link 'Resolve this discussion in a new issue'
+ end
+
+ it 'shows the link for creating a new issue when unresolving a discussion' do
+ page.within '.diff-content' do
+ click_button 'Unresolve discussion'
+ end
+
+ expect(page).to have_link 'Resolve this discussion in a new issue'
+ end
+ end
+
+ it 'has a link to create a new issue for a discussion' do
+ new_issue_link = new_namespace_project_issue_path(project.namespace, project, discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid)
+
+ expect(page).to have_link 'Resolve this discussion in a new issue', href: new_issue_link
+ end
+
+ context 'creating the issue' do
+ before do
+ click_link 'Resolve this discussion in a new issue', href: new_namespace_project_issue_path(project.namespace, project, discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid)
+ end
+
+ it 'has a hidden field for the discussion' do
+ discussion_field = find('#discussion_to_resolve', visible: false)
+
+ expect(discussion_field.value).to eq(discussion.id.to_s)
+ end
+
+ it_behaves_like 'creating an issue for a discussion'
+ end
+ end
+
+ describe 'as a reporter' do
+ before do
+ project.team << [user, :reporter]
+ login_as user
+ visit new_namespace_project_issue_path(project.namespace, project,
+ merge_request_to_resolve_discussions_of: merge_request.iid,
+ discussion_to_resolve: discussion.id)
+ end
+
+ it 'Shows a notice to ask someone else to resolve the discussions' do
+ expect(page).to have_content("The discussion at #{merge_request.to_reference}"\
+ "(discussion #{discussion.first_note.id}) will stay unresolved."\
+ "Ask someone with permission to resolve it.")
+ 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 19a00618b12..1772a120045 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -14,9 +14,10 @@ describe 'Dropdown author', js: true, feature: true do
def send_keys_to_filtered_search(input)
input.split("").each do |i|
filtered_search.send_keys(i)
- sleep 5
- wait_for_ajax
end
+
+ sleep 0.5
+ wait_for_ajax
end
def dropdown_author_size
diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
index 85ffffe4b6d..ce96a420699 100644
--- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
@@ -202,6 +202,14 @@ describe 'Dropdown milestone', :feature, :js do
expect_tokens([{ name: 'milestone', value: 'upcoming' }])
expect_filtered_search_input_empty
end
+
+ it 'selects `started milestones`' do
+ click_static_milestone('Started')
+
+ expect(page).to have_css(js_dropdown_milestone, visible: false)
+ expect_tokens([{ name: 'milestone', value: 'started' }])
+ expect_filtered_search_input_empty
+ end
end
describe 'input has existing content' do
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index f079a9627e4..f463312bf57 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -8,13 +8,12 @@ describe 'Filter issues', js: true, feature: true do
let!(:project) { create(:project, group: group) }
let!(:user) { create(:user) }
let!(:user2) { create(:user) }
- let!(:milestone) { create(:milestone, project: project) }
let!(:label) { create(:label, project: project) }
let!(:wontfix) { create(:label, project: project, title: "Won't fix") }
let!(:bug_label) { create(:label, project: project, title: 'bug') }
let!(:caps_sensitive_label) { create(:label, project: project, title: 'CAPS_sensitive') }
- let!(:milestone) { create(:milestone, title: "8", project: project) }
+ let!(:milestone) { create(:milestone, title: "8", project: project, start_date: 2.days.ago) }
let!(:multiple_words_label) { create(:label, project: project, title: "Two words") }
let!(:closed_issue) { create(:issue, title: 'bug that is closed', project: project, state: :closed) }
@@ -505,6 +504,14 @@ describe 'Filter issues', js: true, feature: true do
expect_filtered_search_input_empty
end
+ it 'filters issues by started milestones' do
+ input_filtered_search("milestone:started")
+
+ expect_tokens([{ name: 'milestone', value: 'started' }])
+ expect_issues_list_count(5)
+ expect_filtered_search_input_empty
+ end
+
it 'filters issues by invalid milestones' do
skip('to be tested, issue #26546')
end
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index d4e0ef91856..755992069ff 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -1,6 +1,8 @@
require 'rails_helper'
describe 'New/edit issue', feature: true, js: true do
+ include GitlabRoutingHelper
+
let!(:project) { create(:project) }
let!(:user) { create(:user)}
let!(:user2) { create(:user)}
@@ -78,6 +80,14 @@ describe 'New/edit issue', feature: true, js: true do
expect(page).to have_content label2.title
end
end
+
+ page.within '.issuable-meta' do
+ issue = Issue.find_by(title: 'title')
+
+ expect(page).to have_text("Issue #{issue.to_reference}")
+ # compare paths because the host differ in test
+ expect(find_link(issue.to_reference)[:href]).to end_with(issue_path(issue))
+ end
end
it 'correctly updates the dropdown toggle when removing a label' do
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index ae609160e18..f32d1f78b40 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -48,6 +48,18 @@ feature 'Login', feature: true do
end
end
+ describe 'with the ghost user' do
+ it 'disallows login' do
+ login_with(User.ghost)
+
+ expect(page).to have_content('Invalid Login or password.')
+ end
+
+ it 'does not update Devise trackable attributes' do
+ expect { login_with(User.ghost) }.not_to change { User.ghost.reload.sign_in_count }
+ end
+ end
+
describe 'with two-factor authentication' do
def enter_code(code)
fill_in 'user_otp_attempt', with: code
diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb
index 1ecdb8b5983..f8518f450dc 100644
--- a/spec/features/merge_requests/form_spec.rb
+++ b/spec/features/merge_requests/form_spec.rb
@@ -1,6 +1,8 @@
require 'rails_helper'
describe 'New/edit merge request', feature: true, js: true do
+ include GitlabRoutingHelper
+
let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
let(:fork_project) { create(:project, forked_from_project: project) }
let!(:user) { create(:user)}
@@ -84,6 +86,15 @@ describe 'New/edit merge request', feature: true, js: true do
expect(page).to have_content label2.title
end
end
+
+ page.within '.issuable-meta' do
+ merge_request = MergeRequest.find_by(source_branch: 'fix')
+
+ expect(page).to have_text("Merge Request #{merge_request.to_reference}")
+ # compare paths because the host differ in test
+ expect(find_link(merge_request.to_reference)[:href])
+ .to end_with(merge_request_path(merge_request))
+ end
end
end
diff --git a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
deleted file mode 100644
index e05fbb3715c..00000000000
--- a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'spec_helper'
-
-feature 'Profile > Notifications > User changes notified_of_own_activity setting', feature: true, js: true do
- let(:user) { create(:user) }
-
- before do
- login_as(user)
- end
-
- scenario 'User opts into receiving notifications about their own activity' do
- visit profile_notifications_path
-
- expect(page).not_to have_checked_field('user[notified_of_own_activity]')
-
- check 'user[notified_of_own_activity]'
-
- expect(page).to have_content('Notification settings saved')
- expect(page).to have_checked_field('user[notified_of_own_activity]')
- end
-
- scenario 'User opts out of receiving notifications about their own activity' do
- user.update!(notified_of_own_activity: true)
- visit profile_notifications_path
-
- expect(page).to have_checked_field('user[notified_of_own_activity]')
-
- uncheck 'user[notified_of_own_activity]'
-
- expect(page).to have_content('Notification settings saved')
- expect(page).not_to have_checked_field('user[notified_of_own_activity]')
- end
-end
diff --git a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
new file mode 100644
index 00000000000..d94204230f6
--- /dev/null
+++ b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
@@ -0,0 +1,97 @@
+require 'spec_helper'
+
+feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true, js: true do
+ include TreeHelper
+
+ let(:project) { create(:project, :public, :repository) }
+ let(:path) { 'CHANGELOG' }
+ let(:sha) { project.repository.commit.sha }
+
+ describe 'On a file(blob)' do
+ def get_absolute_url(path = "")
+ "http://#{page.server.host}:#{page.server.port}#{path}"
+ end
+
+ def visit_blob(fragment = nil)
+ visit namespace_project_blob_path(project.namespace, project, tree_join('master', path), anchor: fragment)
+ end
+
+ describe 'Click "Permalink" button' do
+ it 'works with no initial line number fragment hash' do
+ visit_blob
+
+ expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path))))
+ end
+
+ it 'maintains intitial fragment hash' do
+ fragment = "L3"
+
+ visit_blob(fragment)
+
+ expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: fragment)))
+ end
+
+ it 'changes fragment hash if line number clicked' do
+ ending_fragment = "L5"
+
+ visit_blob
+
+ find('#L3').click
+ find("##{ending_fragment}").click
+
+ expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: ending_fragment)))
+ end
+
+ it 'with initial fragment hash, changes fragment hash if line number clicked' do
+ fragment = "L1"
+ ending_fragment = "L5"
+
+ visit_blob(fragment)
+
+ find('#L3').click
+ find("##{ending_fragment}").click
+
+ expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: ending_fragment)))
+ end
+ end
+
+ describe 'Click "Blame" button' do
+ it 'works with no initial line number fragment hash' do
+ visit_blob
+
+ expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path))))
+ end
+
+ it 'maintains intitial fragment hash' do
+ fragment = "L3"
+
+ visit_blob(fragment)
+
+ expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path), anchor: fragment)))
+ end
+
+ it 'changes fragment hash if line number clicked' do
+ ending_fragment = "L5"
+
+ visit_blob
+
+ find('#L3').click
+ find("##{ending_fragment}").click
+
+ expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path), anchor: ending_fragment)))
+ end
+
+ it 'with initial fragment hash, changes fragment hash if line number clicked' do
+ fragment = "L1"
+ ending_fragment = "L5"
+
+ visit_blob(fragment)
+
+ find('#L3').click
+ find("##{ending_fragment}").click
+
+ expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path), anchor: ending_fragment)))
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/blobs/user_create_spec.rb b/spec/features/projects/blobs/user_create_spec.rb
new file mode 100644
index 00000000000..03d08c12612
--- /dev/null
+++ b/spec/features/projects/blobs/user_create_spec.rb
@@ -0,0 +1,107 @@
+require 'spec_helper'
+
+feature 'New blob creation', feature: true, js: true do
+ include WaitForAjax
+
+ given(:user) { create(:user) }
+ given(:role) { :developer }
+ given(:project) { create(:project) }
+ given(:content) { 'class NextFeature\nend\n' }
+
+ background do
+ login_as(user)
+ project.team << [user, role]
+ visit namespace_project_new_blob_path(project.namespace, project, 'master')
+ end
+
+ def edit_file
+ wait_for_ajax
+ fill_in 'file_name', with: 'feature.rb'
+ execute_script("ace.edit('editor').setValue('#{content}')")
+ end
+
+ def select_branch_index(index)
+ first('button.js-target-branch').click
+ wait_for_ajax
+ all('a[data-group="Branches"]')[index].click
+ end
+
+ def create_new_branch(name)
+ first('button.js-target-branch').click
+ click_link 'Create new branch'
+ fill_in 'new_branch_name', with: name
+ click_button 'Create'
+ end
+
+ def commit_file
+ click_button 'Commit Changes'
+ end
+
+ context 'with default target branch' do
+ background do
+ edit_file
+ commit_file
+ end
+
+ scenario 'creates the blob in the default branch' do
+ expect(page).to have_content 'master'
+ expect(page).to have_content 'successfully created'
+ expect(page).to have_content 'NextFeature'
+ end
+ end
+
+ context 'with different target branch' do
+ background do
+ edit_file
+ select_branch_index(0)
+ commit_file
+ end
+
+ scenario 'creates the blob in the different branch' do
+ expect(page).to have_content 'test'
+ expect(page).to have_content 'successfully created'
+ end
+ end
+
+ context 'with a new target branch' do
+ given(:new_branch_name) { 'new-feature' }
+
+ background do
+ edit_file
+ create_new_branch(new_branch_name)
+ commit_file
+ end
+
+ scenario 'creates the blob in the new branch' do
+ expect(page).to have_content new_branch_name
+ expect(page).to have_content 'successfully created'
+ end
+ scenario 'returns you to the mr' do
+ expect(page).to have_content 'New Merge Request'
+ expect(page).to have_content "From #{new_branch_name} into master"
+ expect(page).to have_content 'Add new file'
+ end
+ end
+
+ context 'the file already exist in the source branch' do
+ background do
+ Files::CreateService.new(
+ project,
+ user,
+ start_branch: 'master',
+ target_branch: 'master',
+ commit_message: 'Create file',
+ file_path: 'feature.rb',
+ file_content: content
+ ).execute
+ edit_file
+ commit_file
+ end
+
+ scenario 'shows error message' do
+ expect(page).to have_content('Your changes could not be committed because a file with the same name already exists')
+ expect(page).to have_content('New File')
+ expect(page).to have_content('NextFeature')
+ end
+ end
+end
diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
new file mode 100644
index 00000000000..30a2b2bcf8c
--- /dev/null
+++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
@@ -0,0 +1,55 @@
+require 'rails_helper'
+
+feature 'Mini Pipeline Graph in Commit View', :js, :feature do
+ include WaitForAjax
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+
+ before do
+ login_as(user)
+ end
+
+ context 'when commit has pipelines' do
+ let(:pipeline) do
+ create(:ci_empty_pipeline,
+ project: project,
+ ref: project.default_branch,
+ sha: project.commit.sha)
+ end
+
+ let(:build) do
+ create(:ci_build, pipeline: pipeline)
+ end
+
+ before do
+ build.run
+ visit namespace_project_commit_path(project.namespace, project, project.commit.id)
+ end
+
+ it 'should display a mini pipeline graph' do
+ expect(page).to have_selector('.mr-widget-pipeline-graph')
+ end
+
+ it 'should show the builds list when stage is clicked' do
+ first('.mini-pipeline-graph-dropdown-toggle').click
+
+ wait_for_ajax
+
+ page.within '.js-builds-dropdown-list' do
+ expect(page).to have_selector('.ci-status-icon-running')
+ expect(page).to have_content(build.stage)
+ end
+ end
+ end
+
+ context 'when commit does not have pipelines' do
+ before do
+ visit namespace_project_commit_path(project.namespace, project, project.commit.id)
+ end
+
+ it 'should not display a mini pipeline graph' do
+ expect(page).not_to have_selector('.mr-widget-pipeline-graph')
+ end
+ end
+end
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index 25f31b423b8..641e2cf7402 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -111,10 +111,8 @@ feature 'Environments page', :feature, :js do
find('.js-dropdown-play-icon-container').click
expect(page).to have_content(action.name.humanize)
- expect { click_link(action.name.humanize) }
+ expect { find('.js-manual-action-link').click }
.not_to change { Ci::Pipeline.count }
-
- expect(action.reload).to be_pending
end
scenario 'does show build name and id' do
@@ -158,12 +156,6 @@ feature 'Environments page', :feature, :js do
expect(page).to have_selector('.stop-env-link')
end
- scenario 'starts build when stop button clicked' do
- find('.stop-env-link').click
-
- expect(page).to have_content('close_app')
- end
-
context 'for reporter' do
let(:role) { :reporter }
diff --git a/spec/features/projects/files/browse_files_spec.rb b/spec/features/projects/files/browse_files_spec.rb
index 69295e450d0..d281043caa3 100644
--- a/spec/features/projects/files/browse_files_spec.rb
+++ b/spec/features/projects/files/browse_files_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'user checks git blame', feature: true do
+feature 'user browses project', feature: true do
let(:project) { create(:project) }
let(:user) { create(:user) }
@@ -18,4 +18,16 @@ feature 'user checks git blame', feature: true do
expect(page).to have_content "Dmitriy Zaporozhets"
expect(page).to have_content "Initial commit"
end
+
+ scenario 'can see raw content of LFS pointer with LFS disabled' do
+ allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
+ click_link 'files'
+ click_link 'lfs'
+ click_link 'lfs_object.iso'
+
+ expect(page).not_to have_content 'Download (1.5 MB)'
+ expect(page).to have_content 'version https://git-lfs.github.com/spec/v1'
+ expect(page).to have_content 'oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897'
+ expect(page).to have_content 'size 1575078'
+ end
end
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index de3c6eceb82..e2911a37e40 100644
--- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
+++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
@@ -29,7 +29,7 @@ feature 'Issue prioritization', feature: true do
issue_1.labels << label_5
login_as user
- visit namespace_project_issues_path(project.namespace, project, sort: 'priority')
+ visit namespace_project_issues_path(project.namespace, project, sort: 'label_priority')
# Ensure we are indicating that issues are sorted by priority
expect(page).to have_selector('.dropdown-toggle', text: 'Label priority')
@@ -68,7 +68,7 @@ feature 'Issue prioritization', feature: true do
issue_6.labels << label_5 # 8 - No priority
login_as user
- visit namespace_project_issues_path(project.namespace, project, sort: 'priority')
+ visit namespace_project_issues_path(project.namespace, project, sort: 'label_priority')
expect(page).to have_selector('.dropdown-toggle', text: 'Label priority')
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index 45185f2dd1f..52196ce49bd 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -16,6 +16,15 @@ feature "New project", feature: true do
expect(find_field("project_visibility_level_#{level}")).to be_checked
end
+
+ it 'saves visibility level on validation error' do
+ visit new_project_path
+
+ choose(key)
+ click_button('Create project')
+
+ expect(find_field("project_visibility_level_#{level}")).to be_checked
+ end
end
end
diff --git a/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb
new file mode 100644
index 00000000000..c17e06612de
--- /dev/null
+++ b/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+feature 'Projects > Wiki > User views the wiki page', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:old_page_version_id) { wiki_page.versions.last.id }
+ let(:wiki_page) do
+ WikiPages::CreateService.new(
+ project,
+ user,
+ title: 'home',
+ content: '[some link](other-page)'
+ ).execute
+ end
+
+ background do
+ project.team << [user, :master]
+ login_as(user)
+ WikiPages::UpdateService.new(
+ project,
+ user,
+ message: 'updated home',
+ content: 'updated [some link](other-page)',
+ format: :markdown
+ ).execute(wiki_page)
+ end
+
+ scenario 'Visit Wiki Page Current Commit' do
+ visit namespace_project_wiki_path(project.namespace, project, wiki_page)
+
+ expect(page).to have_selector('a.btn', text: 'Edit')
+ end
+
+ scenario 'Visit Wiki Page Historical Commit' do
+ visit namespace_project_wiki_path(
+ project.namespace,
+ project,
+ wiki_page,
+ version_id: old_page_version_id
+ )
+
+ expect(page).not_to have_selector('a.btn', text: 'Edit')
+ end
+end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 3a1240f95b5..ba56030e28d 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -56,7 +56,7 @@ feature 'Project', feature: true do
end
describe 'removal', js: true do
- let(:user) { create(:user) }
+ let(:user) { create(:user, username: 'test', name: 'test') }
let(:project) { create(:project, namespace: user.namespace, name: 'project1') }
before do
@@ -67,7 +67,7 @@ feature 'Project', feature: true do
it 'removes a project' do
expect { remove_with_confirm('Remove project', project.path) }.to change {Project.count}.by(-1)
- expect(page).to have_content "Project 'project1' will be deleted."
+ expect(page).to have_content "Project 'test / project1' will be deleted."
expect(Project.all.count).to be_zero
expect(project.issues).to be_empty
expect(project.merge_requests).to be_empty
diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb
index 0f30f562539..ccfafe6db7d 100644
--- a/spec/features/tags/master_deletes_tag_spec.rb
+++ b/spec/features/tags/master_deletes_tag_spec.rb
@@ -10,16 +10,12 @@ feature 'Master deletes tag', feature: true do
visit namespace_project_tags_path(project.namespace, project)
end
- context 'from the tags list page' do
+ context 'from the tags list page', js: true do
scenario 'deletes the tag' do
expect(page).to have_content 'v1.1.0'
- page.within('.content') do
- first('.btn-remove').click
- end
+ delete_first_tag
- expect(current_path).to eq(
- namespace_project_tags_path(project.namespace, project))
expect(page).not_to have_content 'v1.1.0'
end
end
@@ -37,4 +33,23 @@ feature 'Master deletes tag', feature: true do
expect(page).not_to have_content 'v1.0.0'
end
end
+
+ context 'when pre-receive hook fails', js: true do
+ before do
+ allow_any_instance_of(GitHooksService).to receive(:execute)
+ .and_raise(GitHooksService::PreReceiveError, 'Do not delete tags')
+ end
+
+ scenario 'shows the error message' do
+ delete_first_tag
+
+ expect(page).to have_content('Do not delete tags')
+ end
+ end
+
+ def delete_first_tag
+ page.within('.content') do
+ first('.btn-remove').click
+ end
+ end
end
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
index 3495091a0d5..850020109d4 100644
--- a/spec/features/todos/todos_spec.rb
+++ b/spec/features/todos/todos_spec.rb
@@ -31,14 +31,16 @@ describe 'Dashboard Todos', feature: true do
end
it 'shows due date as today' do
- page.within first('.todo') do
+ within first('.todo') do
expect(page).to have_content 'Due today'
end
end
shared_examples 'deleting the todo' do
before do
- first('.js-done-todo').click
+ within first('.todo') do
+ click_link 'Done'
+ end
end
it 'is marked as done-reversible in the list' do
@@ -62,9 +64,11 @@ describe 'Dashboard Todos', feature: true do
shared_examples 'deleting and restoring the todo' do
before do
- first('.js-done-todo').click
- wait_for_ajax
- first('.js-undo-todo').click
+ within first('.todo') do
+ click_link 'Done'
+ wait_for_ajax
+ click_link 'Undo'
+ end
end
it 'is marked back as pending in the list' do
@@ -97,6 +101,35 @@ describe 'Dashboard Todos', feature: true do
end
end
+ context 'User has done todos', js: true do
+ before do
+ create(:todo, :mentioned, :done, user: user, project: project, target: issue, author: author)
+ login_as(user)
+ visit dashboard_todos_path(state: :done)
+ end
+
+ it 'has the done todo present' do
+ expect(page).to have_selector('.todos-list .todo.todo-done', count: 1)
+ end
+
+ describe 'restoring the todo' do
+ before do
+ within first('.todo') do
+ click_link 'Add todo'
+ end
+ end
+
+ it 'is removed from the list' do
+ expect(page).not_to have_selector('.todos-list .todo.todo-done')
+ end
+
+ it 'updates todo count' do
+ expect(page).to have_content 'To do 1'
+ expect(page).to have_content 'Done 0'
+ end
+ end
+ end
+
context 'User has Todos with labels spanning multiple projects' do
before do
label1 = create(:label, project: project)
@@ -143,7 +176,7 @@ describe 'Dashboard Todos', feature: true do
describe 'mark all as done', js: true do
before do
visit dashboard_todos_path
- click_link('Mark all as done')
+ click_link 'Mark all as done'
end
it 'shows "All done" message!' do
@@ -151,6 +184,60 @@ describe 'Dashboard Todos', feature: true do
expect(page).to have_content "You're all done!"
expect(page).not_to have_selector('.gl-pagination')
end
+
+ it 'shows "Undo mark all as done" button' do
+ expect(page).to have_selector('.js-todos-mark-all', visible: false)
+ expect(page).to have_selector('.js-todos-undo-all', visible: true)
+ end
+ end
+
+ describe 'undo mark all as done', js: true do
+ before do
+ visit dashboard_todos_path
+ end
+
+ it 'shows the restored todo list' do
+ mark_all_and_undo
+
+ expect(page).to have_selector('.todos-list .todo', count: 1)
+ expect(page).to have_selector('.gl-pagination')
+ expect(page).not_to have_content "You're all done!"
+ end
+
+ it 'updates todo count' do
+ mark_all_and_undo
+
+ expect(page).to have_content 'To do 2'
+ expect(page).to have_content 'Done 0'
+ end
+
+ it 'shows "Mark all as done" button' do
+ mark_all_and_undo
+
+ expect(page).to have_selector('.js-todos-mark-all', visible: true)
+ expect(page).to have_selector('.js-todos-undo-all', visible: false)
+ end
+
+ context 'User has deleted a todo' do
+ before do
+ within first('.todo') do
+ click_link 'Done'
+ end
+ end
+
+ it 'shows the restored todo list with the deleted todo' do
+ mark_all_and_undo
+
+ expect(page).to have_selector('.todos-list .todo.todo-pending', count: 1)
+ end
+ end
+
+ def mark_all_and_undo
+ click_link 'Mark all as done'
+ wait_for_ajax
+ click_link 'Undo mark all as done'
+ wait_for_ajax
+ end
end
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 2a008427478..ee52dc65175 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -101,6 +101,41 @@ describe IssuesFinder do
end
end
+ context 'filtering by started milestone' do
+ let(:params) { { milestone_title: Milestone::Started.name } }
+
+ let(:project_no_started_milestones) { create(:empty_project, :public) }
+ let(:project_started_1_and_2) { create(:empty_project, :public) }
+ let(:project_started_8) { create(:empty_project, :public) }
+
+ let(:yesterday) { Date.today - 1.day }
+ let(:tomorrow) { Date.today + 1.day }
+ let(:two_days_ago) { Date.today - 2.days }
+
+ let(:milestones) do
+ [
+ create(:milestone, project: project_no_started_milestones, start_date: tomorrow),
+ create(:milestone, project: project_started_1_and_2, title: '1.0', start_date: two_days_ago),
+ create(:milestone, project: project_started_1_and_2, title: '2.0', start_date: yesterday),
+ create(:milestone, project: project_started_1_and_2, title: '3.0', start_date: tomorrow),
+ create(:milestone, project: project_started_8, title: '7.0'),
+ create(:milestone, project: project_started_8, title: '8.0', start_date: yesterday),
+ create(:milestone, project: project_started_8, title: '9.0', start_date: tomorrow)
+ ]
+ end
+
+ before do
+ milestones.each do |milestone|
+ create(:issue, project: milestone.project, milestone: milestone, author: user, assignee: user)
+ end
+ end
+
+ it 'returns issues in the started milestones for each project' do
+ expect(issues.map { |issue| issue.milestone.title }).to contain_exactly('1.0', '2.0', '8.0')
+ expect(issues.map { |issue| issue.milestone.start_date }).to contain_exactly(two_days_ago, yesterday, yesterday)
+ end
+ end
+
context 'filtering by label' do
let(:params) { { label_name: label.title } }
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index fa516f9903e..bead7948486 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -19,12 +19,12 @@ describe BlobHelper do
describe '#highlight' do
it 'returns plaintext for unknown lexer context' do
result = helper.highlight(blob_name, no_context_content)
- expect(result).to eq(%[<pre class="code highlight"><code><span id="LC1" class="line">:type "assem"))</span></code></pre>])
+ expect(result).to eq(%[<pre class="code highlight"><code><span id="LC1" class="line" lang="">:type "assem"))</span></code></pre>])
end
it 'highlights single block' do
- expected = %Q[<pre class="code highlight"><code><span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>
-<span id="LC2" class="line"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">))</span></span></code></pre>]
+ expected = %Q[<pre class="code highlight"><code><span id="LC1" class="line" lang="common_lisp"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>
+<span id="LC2" class="line" lang="common_lisp"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">))</span></span></code></pre>]
expect(helper.highlight(blob_name, blob_content)).to eq(expected)
end
@@ -43,10 +43,10 @@ describe BlobHelper do
let(:blob_name) { 'test.diff' }
let(:blob_content) { "+aaa\n+bbb\n- ccc\n ddd\n"}
let(:expected) do
- %q(<pre class="code highlight"><code><span id="LC1" class="line"><span class="gi">+aaa</span></span>
-<span id="LC2" class="line"><span class="gi">+bbb</span></span>
-<span id="LC3" class="line"><span class="gd">- ccc</span></span>
-<span id="LC4" class="line"> ddd</span></code></pre>)
+ %q(<pre class="code highlight"><code><span id="LC1" class="line" lang="diff"><span class="gi">+aaa</span></span>
+<span id="LC2" class="line" lang="diff"><span class="gi">+bbb</span></span>
+<span id="LC3" class="line" lang="diff"><span class="gd">- ccc</span></span>
+<span id="LC4" class="line" lang="diff"> ddd</span></code></pre>)
end
it 'highlights each line properly' do
diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb
index 637b02d9388..174cc84a97b 100644
--- a/spec/helpers/ci_status_helper_spec.rb
+++ b/spec/helpers/ci_status_helper_spec.rb
@@ -16,4 +16,11 @@ describe CiStatusHelper do
helper.ci_icon_for_status(failed_commit.status)
end
end
+
+ describe "#pipeline_status_cache_key" do
+ it "builds a cache key for pipeline status" do
+ pipeline_status = Ci::PipelineStatus.new(build(:project), sha: "123abc", status: "success")
+ expect(helper.pipeline_status_cache_key(pipeline_status)).to eq("pipeline-status/123abc-success")
+ end
+ end
end
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index 81ba693f2f3..70443d27f33 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -28,7 +28,7 @@ describe EventsHelper do
it 'displays the first line of a code block' do
input = "```\nCode block\nwith two lines\n```"
- expected = %r{<pre.+><code>Code block\.\.\.</code></pre>}
+ expected = %r{<pre.+><code><span class="line">Code block\.\.\.</span>\n</code></pre>}
expect(helper.event_note(input)).to match(expected)
end
@@ -55,10 +55,8 @@ describe EventsHelper do
it 'preserves code color scheme' do
input = "```ruby\ndef test\n 'hello world'\nend\n```"
expected = '<pre class="code highlight js-syntax-highlight ruby">' \
- "<code><span class=\"k\">def</span> <span class=\"nf\">test</span>\n" \
- " <span class=\"s1\">\'hello world\'</span>\n" \
- "<span class=\"k\">end</span>\n" \
- '</code></pre>'
+ "<code><span class=\"line\"><span class=\"k\">def</span> <span class=\"nf\">test</span>...</span>\n" \
+ "</code></pre>"
expect(helper.event_note(input)).to eq(expected)
end
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 9ffd4b9371c..6cf3f86680a 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -152,9 +152,8 @@ describe GitlabMarkdownHelper do
end
describe '#first_line_in_markdown' do
- let(:text) { "@#{user.username}, can you look at this?\nHello world\n"}
-
it 'truncates Markdown properly' do
+ text = "@#{user.username}, can you look at this?\nHello world\n"
actual = first_line_in_markdown(text, 100, project: project)
doc = Nokogiri::HTML.parse(actual)
@@ -169,6 +168,23 @@ describe GitlabMarkdownHelper do
expect(doc.content).to eq "@#{user.username}, can you look at this?..."
end
+
+ it 'truncates Markdown with emoji properly' do
+ text = "foo :wink:\nbar :grinning:"
+ actual = first_line_in_markdown(text, 100, project: project)
+
+ doc = Nokogiri::HTML.parse(actual)
+
+ # Make sure we didn't create invalid markup
+ # But also account for the 2 errors caused by the unknown `gl-emoji` elements
+ expect(doc.errors.length).to eq(2)
+
+ expect(doc.css('gl-emoji').length).to eq(2)
+ expect(doc.css('gl-emoji')[0].attr('data-name')).to eq 'wink'
+ expect(doc.css('gl-emoji')[1].attr('data-name')).to eq 'grinning'
+
+ expect(doc.content).to eq "foo 😉\nbar 😀"
+ end
end
describe '#cross_project_reference' do
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 88d853935c7..f0554cc068d 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -131,4 +131,36 @@ describe IssuesHelper do
expect(options).to have_selector('option', text: milestone2.title)
end
end
+
+ describe "#link_to_discussions_to_resolve" do
+ describe "passing only a merge request" do
+ let(:merge_request) { create(:merge_request) }
+
+ it "links just the merge request" do
+ expected_path = namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+
+ expect(link_to_discussions_to_resolve(merge_request, nil)).to include(expected_path)
+ end
+
+ it "containst the reference to the merge request" do
+ expect(link_to_discussions_to_resolve(merge_request, nil)).to include(merge_request.to_reference)
+ end
+ end
+
+ describe "when passing a discussion" do
+ let(:diff_note) { create(:diff_note_on_merge_request) }
+ let(:merge_request) { diff_note.noteable }
+ let(:discussion) { Discussion.new([diff_note]) }
+
+ it "links to the merge request with first note if a single discussion was passed" do
+ expected_path = Gitlab::UrlBuilder.build(diff_note)
+
+ expect(link_to_discussions_to_resolve(merge_request, discussion)).to include(expected_path)
+ end
+
+ it "contains both the reference to the merge request and a mention of the discussion" do
+ expect(link_to_discussions_to_resolve(merge_request, discussion)).to include("#{merge_request.to_reference} (discussion #{diff_note.id})")
+ end
+ end
+ end
end
diff --git a/spec/helpers/milestones_helper_spec.rb b/spec/helpers/milestones_helper_spec.rb
index 68b20a1e4fc..77a4ba305bb 100644
--- a/spec/helpers/milestones_helper_spec.rb
+++ b/spec/helpers/milestones_helper_spec.rb
@@ -47,4 +47,54 @@ describe MilestonesHelper do
end
end
end
+
+ describe '#milestone_remaining_days' do
+ context 'when less than 31 days remaining' do
+ let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, due_date: 12.days.from_now)) }
+
+ it 'returns days remaining' do
+ expect(milestone_remaining).to eq("<strong>11</strong> days remaining")
+ end
+ end
+
+ context 'when less than 1 year and more than 30 days remaining' do
+ let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, due_date: 2.months.from_now)) }
+
+ it 'returns months remaining' do
+ expect(milestone_remaining).to eq("<strong>2</strong> months remaining")
+ end
+ end
+
+ context 'when more than 1 year remaining' do
+ let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, due_date: 1.year.from_now + 2.days)) }
+
+ it 'returns years remaining' do
+ expect(milestone_remaining).to eq("<strong>1</strong> year remaining")
+ end
+ end
+
+ context 'when milestone is expired' do
+ let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, due_date: 2.days.ago)) }
+
+ it 'returns "Past due"' do
+ expect(milestone_remaining).to eq("<strong>Past due</strong>")
+ end
+ end
+
+ context 'when milestone has start_date in the future' do
+ let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, start_date: 2.days.from_now)) }
+
+ it 'returns "Upcoming"' do
+ expect(milestone_remaining).to eq("<strong>Upcoming</strong>")
+ end
+ end
+
+ context 'when milestone has start_date in the past' do
+ let(:milestone_remaining) { milestone_remaining_days(build_stubbed(:milestone, start_date: 2.days.ago)) }
+
+ it 'returns days elapsed' do
+ expect(milestone_remaining).to eq("<strong>2</strong> days elapsed")
+ end
+ end
+ end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index aca0bb1d794..fc6ad6419ac 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -63,6 +63,46 @@ describe ProjectsHelper do
end
end
+ describe "#project_list_cache_key" do
+ let(:project) { create(:project) }
+
+ it "includes the namespace" do
+ expect(helper.project_list_cache_key(project)).to include(project.namespace.cache_key)
+ end
+
+ it "includes the project" do
+ expect(helper.project_list_cache_key(project)).to include(project.cache_key)
+ end
+
+ it "includes the controller name" do
+ expect(helper.controller).to receive(:controller_name).and_return("testcontroller")
+
+ expect(helper.project_list_cache_key(project)).to include("testcontroller")
+ end
+
+ it "includes the controller action" do
+ expect(helper.controller).to receive(:action_name).and_return("testaction")
+
+ expect(helper.project_list_cache_key(project)).to include("testaction")
+ end
+
+ it "includes the application settings" do
+ settings = Gitlab::CurrentSettings.current_application_settings
+
+ expect(helper.project_list_cache_key(project)).to include(settings.cache_key)
+ end
+
+ it "includes a version" do
+ expect(helper.project_list_cache_key(project)).to include("v2.3")
+ end
+
+ it "includes the pipeline status when there is a status" do
+ create(:ci_pipeline, :success, project: project, sha: project.commit.sha)
+
+ expect(helper.project_list_cache_key(project)).to include("pipeline-status/#{project.commit.sha}-success")
+ end
+ end
+
describe 'link_to_member' do
let(:group) { create(:group) }
let(:project) { create(:empty_project, group: group) }
diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb
new file mode 100644
index 00000000000..50060a0925d
--- /dev/null
+++ b/spec/helpers/todos_helper_spec.rb
@@ -0,0 +1,23 @@
+require "spec_helper"
+
+describe TodosHelper do
+ describe '#todo_projects_options' do
+ let(:projects) { create_list(:empty_project, 3) }
+ let(:user) { create(:user) }
+
+ it 'returns users authorised projects in json format' do
+ projects.first.add_developer(user)
+ projects.second.add_developer(user)
+
+ allow(helper).to receive(:current_user).and_return(user)
+
+ expected_results = [
+ { 'id' => '', 'text' => 'Any Project' },
+ { 'id' => projects.second.id, 'text' => projects.second.name_with_namespace },
+ { 'id' => projects.first.id, 'text' => projects.first.name_with_namespace }
+ ]
+
+ expect(JSON.parse(helper.todo_projects_options)).to match_array(expected_results)
+ end
+ end
+end
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index 9a2978006aa..0a6e042b700 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -1,11 +1,8 @@
/* 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 */
-import promisePolyfill from 'es6-promise';
import Cookies from 'js-cookie';
import AwardsHandler from '~/awards_handler';
-promisePolyfill.polyfill();
-
(function() {
var awardsHandler, lazyAssert, urlRoot, openAndWaitForEmojiMenu;
diff --git a/spec/javascripts/blob/create_branch_dropdown_spec.js b/spec/javascripts/blob/create_branch_dropdown_spec.js
new file mode 100644
index 00000000000..c1179e572ae
--- /dev/null
+++ b/spec/javascripts/blob/create_branch_dropdown_spec.js
@@ -0,0 +1,107 @@
+require('~/gl_dropdown');
+require('~/lib/utils/type_utility');
+require('~/blob/create_branch_dropdown');
+require('~/blob/target_branch_dropdown');
+
+describe('CreateBranchDropdown', () => {
+ const fixtureTemplate = 'static/target_branch_dropdown.html.raw';
+ // selectors
+ const createBranchSel = '.js-new-branch-btn';
+ const backBtnSel = '.dropdown-menu-back';
+ const cancelBtnSel = '.js-cancel-branch-btn';
+ const branchNameSel = '#new_branch_name';
+ const branchName = 'new_name';
+ let dropdown;
+
+ function createDropdown() {
+ const dropdownEl = document.querySelector('.js-project-branches-dropdown');
+ const projectBranches = getJSONFixture('project_branches.json');
+ dropdown = new gl.TargetBranchDropDown(dropdownEl);
+ dropdown.cachedRefs = projectBranches;
+ return dropdown;
+ }
+
+ function createBranchBtn() {
+ return document.querySelector(createBranchSel);
+ }
+
+ function backBtn() {
+ return document.querySelector(backBtnSel);
+ }
+
+ function cancelBtn() {
+ return document.querySelector(cancelBtnSel);
+ }
+
+ function branchNameEl() {
+ return document.querySelector(branchNameSel);
+ }
+
+ function changeBranchName(text) {
+ branchNameEl().value = text;
+ branchNameEl().dispatchEvent(new Event('change'));
+ }
+
+ preloadFixtures(fixtureTemplate);
+
+ beforeEach(() => {
+ loadFixtures(fixtureTemplate);
+ createDropdown();
+ });
+
+ it('disable submit when branch name is empty', () => {
+ expect(createBranchBtn()).toBeDisabled();
+ });
+
+ it('enable submit when branch name is present', () => {
+ changeBranchName(branchName);
+
+ expect(createBranchBtn()).not.toBeDisabled();
+ });
+
+ it('resets the form when cancel btn is clicked and triggers dropdownback', () => {
+ const spyBackEvent = spyOnEvent(backBtnSel, 'click');
+ changeBranchName(branchName);
+
+ cancelBtn().click();
+
+ expect(branchNameEl()).toHaveValue('');
+ expect(spyBackEvent).toHaveBeenTriggered();
+ });
+
+ it('resets the form when back btn is clicked', () => {
+ changeBranchName(branchName);
+
+ backBtn().click();
+
+ expect(branchNameEl()).toHaveValue('');
+ });
+
+ describe('new branch creation', () => {
+ beforeEach(() => {
+ changeBranchName(branchName);
+ });
+ it('sets the new branch name and updates the dropdown', () => {
+ spyOn(dropdown, 'setNewBranch');
+
+ createBranchBtn().click();
+
+ expect(dropdown.setNewBranch).toHaveBeenCalledWith(branchName);
+ });
+
+ it('resets the form', () => {
+ createBranchBtn().click();
+
+ expect(branchNameEl()).toHaveValue('');
+ });
+
+ it('is triggered with enter keypress', () => {
+ spyOn(dropdown, 'setNewBranch');
+ const enterEvent = new Event('keydown');
+ enterEvent.which = 13;
+ branchNameEl().dispatchEvent(enterEvent);
+
+ expect(dropdown.setNewBranch).toHaveBeenCalledWith(branchName);
+ });
+ });
+});
diff --git a/spec/javascripts/blob/target_branch_dropdown_spec.js b/spec/javascripts/blob/target_branch_dropdown_spec.js
new file mode 100644
index 00000000000..4fb79663c51
--- /dev/null
+++ b/spec/javascripts/blob/target_branch_dropdown_spec.js
@@ -0,0 +1,119 @@
+require('~/gl_dropdown');
+require('~/lib/utils/type_utility');
+require('~/blob/create_branch_dropdown');
+require('~/blob/target_branch_dropdown');
+
+describe('TargetBranchDropdown', () => {
+ const fixtureTemplate = 'static/target_branch_dropdown.html.raw';
+ let dropdown;
+
+ function createDropdown() {
+ const projectBranches = getJSONFixture('project_branches.json');
+ const dropdownEl = document.querySelector('.js-project-branches-dropdown');
+ dropdown = new gl.TargetBranchDropDown(dropdownEl);
+ dropdown.cachedRefs = projectBranches;
+ dropdown.refreshData();
+ return dropdown;
+ }
+
+ function submitBtn() {
+ return document.querySelector('button[type="submit"]');
+ }
+
+ function searchField() {
+ return document.querySelector('.dropdown-page-one .dropdown-input-field');
+ }
+
+ function element() {
+ return document.querySelectorAll('div.dropdown-content li a');
+ }
+
+ function elementAtIndex(index) {
+ return element()[index];
+ }
+
+ function clickElementAtIndex(index) {
+ elementAtIndex(index).click();
+ }
+
+ preloadFixtures(fixtureTemplate);
+
+ beforeEach(() => {
+ loadFixtures(fixtureTemplate);
+ createDropdown();
+ });
+
+ it('disable submit when branch is not selected', () => {
+ document.querySelector('input[name="target_branch"]').value = null;
+ clickElementAtIndex(1);
+
+ expect(submitBtn().getAttribute('disabled')).toEqual('');
+ });
+
+ it('enable submit when a branch is selected', () => {
+ clickElementAtIndex(1);
+
+ expect(submitBtn().getAttribute('disabled')).toBe(null);
+ });
+
+ it('triggers change.branch event on a branch click', () => {
+ spyOnEvent(dropdown.$dropdown, 'change.branch');
+ clickElementAtIndex(0);
+
+ expect('change.branch').toHaveBeenTriggeredOn(dropdown.$dropdown);
+ });
+
+ describe('#dropdownData', () => {
+ it('cache the refs', () => {
+ const refs = dropdown.cachedRefs;
+ dropdown.cachedRefs = null;
+
+ dropdown.dropdownData(refs);
+
+ expect(dropdown.cachedRefs).toEqual(refs);
+ });
+
+ it('returns the Branches with the newBranch and defaultBranch', () => {
+ const refs = dropdown.cachedRefs;
+ dropdown.branchInput.value = 'master';
+ dropdown.newBranch = { id: 'new_branch', text: 'new_branch', title: 'new_branch' };
+
+ const branches = dropdown.dropdownData(refs).Branches;
+
+ expect(branches.length).toEqual(4);
+ expect(branches[0]).toEqual(dropdown.newBranch);
+ expect(branches[1]).toEqual({ id: 'master', text: 'master', title: 'master' });
+ expect(branches[2]).toEqual({ id: 'development', text: 'development', title: 'development' });
+ expect(branches[3]).toEqual({ id: 'staging', text: 'staging', title: 'staging' });
+ });
+ });
+
+ describe('#setNewBranch', () => {
+ it('adds the new branch and select it', () => {
+ const branchName = 'new_branch';
+
+ dropdown.setNewBranch(branchName);
+
+ expect(elementAtIndex(0)).toHaveClass('is-active');
+ expect(elementAtIndex(0)).toContainHtml(branchName);
+ });
+
+ it("doesn't add a new branch if already exists in the list", () => {
+ const branchName = elementAtIndex(0).text;
+ const initialLength = element().length;
+
+ dropdown.setNewBranch(branchName);
+
+ expect(element().length).toEqual(initialLength);
+ });
+
+ it('clears the search filter', () => {
+ const branchName = elementAtIndex(0).text;
+ searchField().value = 'searching';
+
+ dropdown.setNewBranch(branchName);
+
+ expect(searchField().value).toEqual('');
+ });
+ });
+});
diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js
index 22c9f12951b..4999933c0c1 100644
--- a/spec/javascripts/boards/board_new_issue_spec.js
+++ b/spec/javascripts/boards/board_new_issue_spec.js
@@ -8,7 +8,6 @@ import boardNewIssue from '~/boards/components/board_new_issue';
require('~/boards/models/list');
require('./mock_data');
-require('es6-promise').polyfill();
describe('Issue boards new issue form', () => {
let vm;
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 49a2ca4a78f..1d1069600fc 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -15,7 +15,6 @@ require('~/boards/models/user');
require('~/boards/services/board_service');
require('~/boards/stores/boards_store');
require('./mock_data');
-require('es6-promise').polyfill();
describe('Store', () => {
beforeEach(() => {
diff --git a/spec/javascripts/extensions/jquery_spec.js b/spec/javascripts/bootstrap_jquery_spec.js
index 096d3272eac..48994b7c523 100644
--- a/spec/javascripts/extensions/jquery_spec.js
+++ b/spec/javascripts/bootstrap_jquery_spec.js
@@ -1,9 +1,9 @@
/* eslint-disable space-before-function-paren, no-var */
-require('~/extensions/jquery');
+import '~/commons/bootstrap';
(function() {
- describe('jQuery extensions', function() {
+ describe('Bootstrap jQuery extensions', function() {
describe('disable', function() {
beforeEach(function() {
return setFixtures('<input type="text" />');
diff --git a/spec/javascripts/environments/environment_actions_spec.js b/spec/javascripts/environments/environment_actions_spec.js
index d50d45d295e..85b73f1d4e2 100644
--- a/spec/javascripts/environments/environment_actions_spec.js
+++ b/spec/javascripts/environments/environment_actions_spec.js
@@ -1,14 +1,16 @@
-const ActionsComponent = require('~/environments/components/environment_actions');
+import Vue from 'vue';
+import actionsComp from '~/environments/components/environment_actions';
describe('Actions Component', () => {
- preloadFixtures('static/environments/element.html.raw');
+ let ActionsComponent;
+ let actionsMock;
+ let spy;
+ let component;
beforeEach(() => {
- loadFixtures('static/environments/element.html.raw');
- });
+ ActionsComponent = Vue.extend(actionsComp);
- it('should render a dropdown with the provided actions', () => {
- const actionsMock = [
+ actionsMock = [
{
name: 'bar',
play_path: 'https://gitlab.com/play',
@@ -19,18 +21,27 @@ describe('Actions Component', () => {
},
];
- const component = new ActionsComponent({
- el: document.querySelector('.test-dom-element'),
+ spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
+ component = new ActionsComponent({
propsData: {
actions: actionsMock,
+ service: {
+ postAction: spy,
+ },
},
- });
+ }).$mount();
+ });
+ it('should render a dropdown with the provided actions', () => {
expect(
component.$el.querySelectorAll('.dropdown-menu li').length,
).toEqual(actionsMock.length);
- expect(
- component.$el.querySelector('.dropdown-menu li a').getAttribute('href'),
- ).toEqual(actionsMock[0].play_path);
+ });
+
+ it('should call the service when an action is clicked', () => {
+ component.$el.querySelector('.dropdown').click();
+ component.$el.querySelector('.js-manual-action-link').click();
+
+ expect(spy).toHaveBeenCalledWith(actionsMock[0].play_path);
});
});
diff --git a/spec/javascripts/environments/environment_external_url_spec.js b/spec/javascripts/environments/environment_external_url_spec.js
index 393dbb5aae0..9af218a27ff 100644
--- a/spec/javascripts/environments/environment_external_url_spec.js
+++ b/spec/javascripts/environments/environment_external_url_spec.js
@@ -1,19 +1,20 @@
-const ExternalUrlComponent = require('~/environments/components/environment_external_url');
+import Vue from 'vue';
+import externalUrlComp from '~/environments/components/environment_external_url';
describe('External URL Component', () => {
- preloadFixtures('static/environments/element.html.raw');
+ let ExternalUrlComponent;
+
beforeEach(() => {
- loadFixtures('static/environments/element.html.raw');
+ ExternalUrlComponent = Vue.extend(externalUrlComp);
});
it('should link to the provided externalUrl prop', () => {
const externalURL = 'https://gitlab.com';
const component = new ExternalUrlComponent({
- el: document.querySelector('.test-dom-element'),
propsData: {
externalUrl: externalURL,
},
- });
+ }).$mount();
expect(component.$el.getAttribute('href')).toEqual(externalURL);
expect(component.$el.querySelector('fa-external-link')).toBeDefined();
diff --git a/spec/javascripts/environments/environment_item_spec.js b/spec/javascripts/environments/environment_item_spec.js
index 7fea80ed799..4d42de4d549 100644
--- a/spec/javascripts/environments/environment_item_spec.js
+++ b/spec/javascripts/environments/environment_item_spec.js
@@ -1,10 +1,12 @@
-window.timeago = require('timeago.js');
-const EnvironmentItem = require('~/environments/components/environment_item');
+import 'timeago.js';
+import Vue from 'vue';
+import environmentItemComp from '~/environments/components/environment_item';
describe('Environment item', () => {
- preloadFixtures('static/environments/table.html.raw');
+ let EnvironmentItem;
+
beforeEach(() => {
- loadFixtures('static/environments/table.html.raw');
+ EnvironmentItem = Vue.extend(environmentItemComp);
});
describe('When item is folder', () => {
@@ -21,13 +23,13 @@ describe('Environment item', () => {
};
component = new EnvironmentItem({
- el: document.querySelector('tr#environment-row'),
propsData: {
model: mockItem,
canCreateDeployment: false,
canReadEnvironment: true,
+ service: {},
},
- });
+ }).$mount();
});
it('Should render folder icon and name', () => {
@@ -109,13 +111,13 @@ describe('Environment item', () => {
};
component = new EnvironmentItem({
- el: document.querySelector('tr#environment-row'),
propsData: {
model: environment,
canCreateDeployment: true,
canReadEnvironment: true,
+ service: {},
},
- });
+ }).$mount();
});
it('should render environment name', () => {
diff --git a/spec/javascripts/environments/environment_rollback_spec.js b/spec/javascripts/environments/environment_rollback_spec.js
index 4a596baad09..7cb39d9df03 100644
--- a/spec/javascripts/environments/environment_rollback_spec.js
+++ b/spec/javascripts/environments/environment_rollback_spec.js
@@ -1,47 +1,59 @@
-const RollbackComponent = require('~/environments/components/environment_rollback');
+import Vue from 'vue';
+import rollbackComp from '~/environments/components/environment_rollback';
describe('Rollback Component', () => {
- preloadFixtures('static/environments/element.html.raw');
-
const retryURL = 'https://gitlab.com/retry';
+ let RollbackComponent;
+ let spy;
beforeEach(() => {
- loadFixtures('static/environments/element.html.raw');
+ RollbackComponent = Vue.extend(rollbackComp);
+ spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
});
- it('Should link to the provided retryUrl', () => {
+ it('Should render Re-deploy label when isLastDeployment is true', () => {
const component = new RollbackComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
retryUrl: retryURL,
isLastDeployment: true,
+ service: {
+ postAction: spy,
+ },
},
- });
+ }).$mount();
- expect(component.$el.getAttribute('href')).toEqual(retryURL);
+ expect(component.$el.querySelector('span').textContent).toContain('Re-deploy');
});
- it('Should render Re-deploy label when isLastDeployment is true', () => {
+ it('Should render Rollback label when isLastDeployment is false', () => {
const component = new RollbackComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
retryUrl: retryURL,
- isLastDeployment: true,
+ isLastDeployment: false,
+ service: {
+ postAction: spy,
+ },
},
- });
+ }).$mount();
- expect(component.$el.querySelector('span').textContent).toContain('Re-deploy');
+ expect(component.$el.querySelector('span').textContent).toContain('Rollback');
});
- it('Should render Rollback label when isLastDeployment is false', () => {
+ it('should call the service when the button is clicked', () => {
const component = new RollbackComponent({
- el: document.querySelector('.test-dom-element'),
propsData: {
retryUrl: retryURL,
isLastDeployment: false,
+ service: {
+ postAction: spy,
+ },
},
- });
+ }).$mount();
- expect(component.$el.querySelector('span').textContent).toContain('Rollback');
+ component.$el.click();
+
+ expect(spy).toHaveBeenCalledWith(retryURL);
});
});
diff --git a/spec/javascripts/environments/environment_spec.js b/spec/javascripts/environments/environment_spec.js
index edd0cad32d0..9601575577e 100644
--- a/spec/javascripts/environments/environment_spec.js
+++ b/spec/javascripts/environments/environment_spec.js
@@ -1,7 +1,7 @@
-const Vue = require('vue');
-require('~/flash');
-const EnvironmentsComponent = require('~/environments/components/environment');
-const { environment } = require('./mock_data');
+import Vue from 'vue';
+import '~/flash';
+import EnvironmentsComponent from '~/environments/components/environment';
+import { environment } from './mock_data';
describe('Environment', () => {
preloadFixtures('static/environments/environments.html.raw');
diff --git a/spec/javascripts/environments/environment_stop_spec.js b/spec/javascripts/environments/environment_stop_spec.js
index 5ca65b1debc..8f79b88f3df 100644
--- a/spec/javascripts/environments/environment_stop_spec.js
+++ b/spec/javascripts/environments/environment_stop_spec.js
@@ -1,28 +1,34 @@
-const StopComponent = require('~/environments/components/environment_stop');
+import Vue from 'vue';
+import stopComp from '~/environments/components/environment_stop';
describe('Stop Component', () => {
- preloadFixtures('static/environments/element.html.raw');
-
- let stopURL;
+ let StopComponent;
let component;
+ let spy;
+ const stopURL = '/stop';
beforeEach(() => {
- loadFixtures('static/environments/element.html.raw');
+ StopComponent = Vue.extend(stopComp);
+ spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
+ spyOn(window, 'confirm').and.returnValue(true);
- stopURL = '/stop';
component = new StopComponent({
- el: document.querySelector('.test-dom-element'),
propsData: {
stopUrl: stopURL,
+ service: {
+ postAction: spy,
+ },
},
- });
+ }).$mount();
});
- it('should link to the provided URL', () => {
- expect(component.$el.getAttribute('href')).toEqual(stopURL);
+ it('should render a button to stop the environment', () => {
+ expect(component.$el.tagName).toEqual('BUTTON');
+ expect(component.$el.getAttribute('title')).toEqual('Stop Environment');
});
- it('should have a data-confirm attribute', () => {
- expect(component.$el.getAttribute('data-confirm')).toEqual('Are you sure you want to stop this environment?');
+ it('should call the service when an action is clicked', () => {
+ component.$el.click();
+ expect(spy).toHaveBeenCalled();
});
});
diff --git a/spec/javascripts/environments/environment_table_spec.js b/spec/javascripts/environments/environment_table_spec.js
index be4330b5012..3df967848a7 100644
--- a/spec/javascripts/environments/environment_table_spec.js
+++ b/spec/javascripts/environments/environment_table_spec.js
@@ -1,4 +1,5 @@
-const EnvironmentTable = require('~/environments/components/environments_table');
+import Vue from 'vue';
+import environmentTableComp from '~/environments/components/environments_table';
describe('Environment item', () => {
preloadFixtures('static/environments/element.html.raw');
@@ -16,14 +17,17 @@ describe('Environment item', () => {
},
};
+ const EnvironmentTable = Vue.extend(environmentTableComp);
+
const component = new EnvironmentTable({
el: document.querySelector('.test-dom-element'),
propsData: {
environments: [{ mockItem }],
canCreateDeployment: false,
canReadEnvironment: true,
+ service: {},
},
- });
+ }).$mount();
expect(component.$el.tagName).toEqual('TABLE');
});
diff --git a/spec/javascripts/environments/environment_terminal_button_spec.js b/spec/javascripts/environments/environment_terminal_button_spec.js
new file mode 100644
index 00000000000..b07aa4e1745
--- /dev/null
+++ b/spec/javascripts/environments/environment_terminal_button_spec.js
@@ -0,0 +1,24 @@
+import Vue from 'vue';
+import terminalComp from '~/environments/components/environment_terminal_button';
+
+describe('Stop Component', () => {
+ let TerminalComponent;
+ let component;
+ const terminalPath = '/path';
+
+ beforeEach(() => {
+ TerminalComponent = Vue.extend(terminalComp);
+
+ component = new TerminalComponent({
+ propsData: {
+ terminalPath,
+ },
+ }).$mount();
+ });
+
+ it('should render a link to open a web terminal with the provided path', () => {
+ expect(component.$el.tagName).toEqual('A');
+ expect(component.$el.getAttribute('title')).toEqual('Open web terminal');
+ expect(component.$el.getAttribute('href')).toEqual(terminalPath);
+ });
+});
diff --git a/spec/javascripts/environments/environments_store_spec.js b/spec/javascripts/environments/environments_store_spec.js
index 77e182b3830..115d84b50f5 100644
--- a/spec/javascripts/environments/environments_store_spec.js
+++ b/spec/javascripts/environments/environments_store_spec.js
@@ -1,5 +1,5 @@
-const Store = require('~/environments/stores/environments_store');
-const { environmentsList, serverData } = require('./mock_data');
+import Store from '~/environments/stores/environments_store';
+import { environmentsList, serverData } from './mock_data';
(() => {
describe('Store', () => {
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js
index d1335b5b304..43a217a67f5 100644
--- a/spec/javascripts/environments/folder/environments_folder_view_spec.js
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js
@@ -1,7 +1,7 @@
-const Vue = require('vue');
-require('~/flash');
-const EnvironmentsFolderViewComponent = require('~/environments/folder/environments_folder_view');
-const { environmentsList } = require('../mock_data');
+import Vue from 'vue';
+import '~/flash';
+import EnvironmentsFolderViewComponent from '~/environments/folder/environments_folder_view';
+import { environmentsList } from '../mock_data';
describe('Environments Folder View', () => {
preloadFixtures('static/environments/environments_folder_view.html.raw');
diff --git a/spec/javascripts/environments/mock_data.js b/spec/javascripts/environments/mock_data.js
index 5c395c6b2d8..30861481cc5 100644
--- a/spec/javascripts/environments/mock_data.js
+++ b/spec/javascripts/environments/mock_data.js
@@ -1,4 +1,4 @@
-const environmentsList = [
+export const environmentsList = [
{
name: 'DEV',
size: 1,
@@ -30,7 +30,7 @@ const environmentsList = [
},
];
-const serverData = [
+export const serverData = [
{
name: 'DEV',
size: 1,
@@ -67,7 +67,7 @@ const serverData = [
},
];
-const environment = {
+export const environment = {
name: 'DEV',
size: 1,
latest: {
@@ -84,9 +84,3 @@ const environment = {
updated_at: '2017-01-31T10:53:46.894Z',
},
};
-
-module.exports = {
- environmentsList,
- environment,
- serverData,
-};
diff --git a/spec/javascripts/extensions/array_spec.js b/spec/javascripts/extensions/array_spec.js
index 60f6b9b78e3..4b871fe967d 100644
--- a/spec/javascripts/extensions/array_spec.js
+++ b/spec/javascripts/extensions/array_spec.js
@@ -18,28 +18,5 @@ require('~/extensions/array');
return expect(arr.last()).toBe(5);
});
});
-
- describe('find', function () {
- beforeEach(() => {
- this.arr = [0, 1, 2, 3, 4, 5];
- });
-
- it('returns the item that first passes the predicate function', () => {
- expect(this.arr.find(item => item === 2)).toBe(2);
- });
-
- it('returns undefined if no items pass the predicate function', () => {
- expect(this.arr.find(item => item === 6)).not.toBeDefined();
- });
-
- it('error when called on undefined or null', () => {
- expect(Array.prototype.find.bind(undefined, item => item === 1)).toThrow();
- expect(Array.prototype.find.bind(null, item => item === 1)).toThrow();
- });
-
- it('error when predicate is not a function', () => {
- expect(Array.prototype.find.bind(this.arr, 1)).toThrow();
- });
- });
});
}).call(window);
diff --git a/spec/javascripts/extensions/element_spec.js b/spec/javascripts/extensions/element_spec.js
deleted file mode 100644
index 2d8a128ed33..00000000000
--- a/spec/javascripts/extensions/element_spec.js
+++ /dev/null
@@ -1,38 +0,0 @@
-require('~/extensions/element');
-
-(() => {
- describe('Element extensions', function () {
- beforeEach(() => {
- this.element = document.createElement('ul');
- });
-
- describe('matches', () => {
- it('returns true if element matches the selector', () => {
- expect(this.element.matches('ul')).toBeTruthy();
- });
-
- it("returns false if element doesn't match the selector", () => {
- expect(this.element.matches('.not-an-element')).toBeFalsy();
- });
- });
-
- describe('closest', () => {
- beforeEach(() => {
- this.childElement = document.createElement('li');
- this.element.appendChild(this.childElement);
- });
-
- it('returns the closest parent that matches the selector', () => {
- expect(this.childElement.closest('ul').toString()).toBe(this.element.toString());
- });
-
- it('returns itself if it matches the selector', () => {
- expect(this.childElement.closest('li').toString()).toBe(this.childElement.toString());
- });
-
- it('returns undefined if nothing matches the selector', () => {
- expect(this.childElement.closest('.no-an-element')).toBeFalsy();
- });
- });
- });
-})();
diff --git a/spec/javascripts/extensions/object_spec.js b/spec/javascripts/extensions/object_spec.js
deleted file mode 100644
index 2467ed78459..00000000000
--- a/spec/javascripts/extensions/object_spec.js
+++ /dev/null
@@ -1,25 +0,0 @@
-require('~/extensions/object');
-
-describe('Object extensions', () => {
- describe('assign', () => {
- it('merges source object into target object', () => {
- const targetObj = {};
- const sourceObj = {
- foo: 'bar',
- };
- Object.assign(targetObj, sourceObj);
- expect(targetObj.foo).toBe('bar');
- });
-
- it('merges object with the same properties', () => {
- const targetObj = {
- foo: 'bar',
- };
- const sourceObj = {
- foo: 'baz',
- };
- Object.assign(targetObj, sourceObj);
- expect(targetObj.foo).toBe('baz');
- });
- });
-});
diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js
index 5c65903701b..e6538020896 100644
--- a/spec/javascripts/filtered_search/dropdown_utils_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js
@@ -126,7 +126,11 @@ require('~/filtered_search/filtered_search_dropdown_manager');
beforeEach(() => {
setFixtures(`
- <input type="text" id="test" />
+ <ul class="tokens-container">
+ <li class="input-token">
+ <input class="filtered-search" type="text" id="test" />
+ </li>
+ </ul>
`);
input = document.getElementById('test');
@@ -142,7 +146,7 @@ require('~/filtered_search/filtered_search_dropdown_manager');
input.value = 'o';
updatedItem = gl.DropdownUtils.filterHint(input, {
hint: 'label',
- }, 'o');
+ });
expect(updatedItem.droplab_hidden).toBe(true);
});
@@ -150,6 +154,29 @@ require('~/filtered_search/filtered_search_dropdown_manager');
const updatedItem = gl.DropdownUtils.filterHint(input, {}, '');
expect(updatedItem.droplab_hidden).toBe(false);
});
+
+ it('should allow multiple if item.type is array', () => {
+ input.value = 'label:~first la';
+ const updatedItem = gl.DropdownUtils.filterHint(input, {
+ hint: 'label',
+ type: 'array',
+ });
+ expect(updatedItem.droplab_hidden).toBe(false);
+ });
+
+ it('should prevent multiple if item.type is not array', () => {
+ input.value = 'milestone:~first mile';
+ let updatedItem = gl.DropdownUtils.filterHint(input, {
+ hint: 'milestone',
+ });
+ expect(updatedItem.droplab_hidden).toBe(true);
+
+ updatedItem = gl.DropdownUtils.filterHint(input, {
+ hint: 'milestone',
+ type: 'string',
+ });
+ expect(updatedItem.droplab_hidden).toBe(true);
+ });
});
describe('setDataValueIfSelected', () => {
diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
index 81c1d81d181..ae9c263d1d7 100644
--- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
@@ -41,7 +41,6 @@ const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper
</div>
`);
- spyOn(gl.FilteredSearchManager.prototype, 'cleanup').and.callFake(() => {});
spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {});
spyOn(gl.FilteredSearchManager.prototype, 'tokenChange').and.callFake(() => {});
spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {});
@@ -54,6 +53,10 @@ const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper
manager = new gl.FilteredSearchManager();
});
+ afterEach(() => {
+ manager.cleanup();
+ });
+
describe('search', () => {
const defaultParams = '?scope=all&utf8=✓&state=opened';
diff --git a/spec/javascripts/fixtures/project_branches.json b/spec/javascripts/fixtures/project_branches.json
new file mode 100644
index 00000000000..a96a4c0c095
--- /dev/null
+++ b/spec/javascripts/fixtures/project_branches.json
@@ -0,0 +1,5 @@
+[
+ "master",
+ "development",
+ "staging"
+]
diff --git a/spec/javascripts/fixtures/target_branch_dropdown.html.haml b/spec/javascripts/fixtures/target_branch_dropdown.html.haml
new file mode 100644
index 00000000000..821fb7940a0
--- /dev/null
+++ b/spec/javascripts/fixtures/target_branch_dropdown.html.haml
@@ -0,0 +1,28 @@
+%form.js-edit-blob-form
+ %input{type: 'hidden', name: 'target_branch', value: 'master'}
+ %div
+ .dropdown
+ %button.dropdown-menu-toggle.js-project-branches-dropdown.js-target-branch{type: 'button', data: {toggle: 'dropdown', selected: 'master', field_name: 'target_branch', form_id: '.js-edit-blob-form'}}
+ .dropdown-menu.dropdown-menu-selectable.dropdown-menu-paging
+ .dropdown-page-one
+ .dropdown-title 'Select branch'
+ .dropdown-input
+ %input.dropdown-input-field{type: 'search', value: ''}
+ %i.fa.fa-search.dropdown-input-search
+ %i.fa.fa-times-dropdown-input-clear.js-dropdown-input-clear{role: 'button'}
+ .dropdown-content
+ .dropdown-footer
+ %ul.dropdown-footer-list
+ %li
+ %a.create-new-branch.dropdown-toggle-page{href: "#"}
+ Create new branch
+ .dropdown-page-two.dropdown-new-branch
+ %button.dropdown-title-button.dropdown-menu-back{type: 'button'}
+ .dropdown_title 'Create new branch'
+ .dropdown_content
+ %input#new_branch_name.default-dropdown-input{ type: "text", placeholder: "Name new branch" }
+ %button.btn.btn-primary.pull-left.js-new-branch-btn{ type: "button" }
+ Create
+ %button.btn.btn-default.pull-right.js-cancel-branch-btn{ type: "button" }
+ Cancel
+ %button{type: 'submit'}
diff --git a/spec/javascripts/gl_emoji_spec.js b/spec/javascripts/gl_emoji_spec.js
index 7ab0b37f2ec..9b44b25980c 100644
--- a/spec/javascripts/gl_emoji_spec.js
+++ b/spec/javascripts/gl_emoji_spec.js
@@ -1,6 +1,3 @@
-import '~/extensions/string';
-import '~/extensions/array';
-
import { glEmojiTag } from '~/behaviors/gl_emoji';
import {
isEmojiUnicodeSupported,
diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js
index a0b2ebc221b..a1fd2d38968 100644
--- a/spec/javascripts/line_highlighter_spec.js
+++ b/spec/javascripts/line_highlighter_spec.js
@@ -7,16 +7,12 @@ require('~/line_highlighter');
describe('LineHighlighter', function() {
var clickLine;
preloadFixtures('static/line_highlighter.html.raw');
- clickLine = function(number, eventData) {
- var e;
- if (eventData == null) {
- eventData = {};
- }
+ clickLine = function(number, eventData = {}) {
if ($.isEmptyObject(eventData)) {
- return $("#L" + number).mousedown().click();
+ return $("#L" + number).click();
} else {
- e = $.Event('mousedown', eventData);
- return $("#L" + number).trigger(e).click();
+ const e = $.Event('click', eventData);
+ return $("#L" + number).trigger(e);
}
};
beforeEach(function() {
@@ -63,12 +59,6 @@ require('~/line_highlighter');
});
});
describe('#clickHandler', function() {
- it('discards the mousedown event', function() {
- var spy;
- spy = spyOnEvent('a[data-line-number]', 'mousedown');
- clickLine(13);
- return expect(spy).toHaveBeenPrevented();
- });
it('handles clicking on a child icon element', function() {
var spy;
spy = spyOn(this["class"], 'setHash').and.callThrough();
diff --git a/spec/javascripts/monitoring/prometheus_graph_spec.js b/spec/javascripts/monitoring/prometheus_graph_spec.js
index 823b4bab7fc..a3c1c5e1b7c 100644
--- a/spec/javascripts/monitoring/prometheus_graph_spec.js
+++ b/spec/javascripts/monitoring/prometheus_graph_spec.js
@@ -1,11 +1,8 @@
import 'jquery';
-import es6Promise from 'es6-promise';
import '~/lib/utils/common_utils';
import PrometheusGraph from '~/monitoring/prometheus_graph';
import { prometheusMockData } from './prometheus_mock_data';
-es6Promise.polyfill();
-
describe('PrometheusGraph', () => {
const fixtureName = 'static/environments/metrics.html.raw';
const prometheusGraphContainer = '.prometheus-graph';
diff --git a/spec/javascripts/polyfills/element_spec.js b/spec/javascripts/polyfills/element_spec.js
new file mode 100644
index 00000000000..ecaaf1907ea
--- /dev/null
+++ b/spec/javascripts/polyfills/element_spec.js
@@ -0,0 +1,36 @@
+import '~/commons/polyfills/element';
+
+describe('Element polyfills', function () {
+ beforeEach(() => {
+ this.element = document.createElement('ul');
+ });
+
+ describe('matches', () => {
+ it('returns true if element matches the selector', () => {
+ expect(this.element.matches('ul')).toBeTruthy();
+ });
+
+ it("returns false if element doesn't match the selector", () => {
+ expect(this.element.matches('.not-an-element')).toBeFalsy();
+ });
+ });
+
+ describe('closest', () => {
+ beforeEach(() => {
+ this.childElement = document.createElement('li');
+ this.element.appendChild(this.childElement);
+ });
+
+ it('returns the closest parent that matches the selector', () => {
+ expect(this.childElement.closest('ul').toString()).toBe(this.element.toString());
+ });
+
+ it('returns itself if it matches the selector', () => {
+ expect(this.childElement.closest('li').toString()).toBe(this.childElement.toString());
+ });
+
+ it('returns undefined if nothing matches the selector', () => {
+ expect(this.childElement.closest('.no-an-element')).toBeFalsy();
+ });
+ });
+});
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
index 69d9587771f..3a1d4e2440f 100644
--- a/spec/javascripts/project_title_spec.js
+++ b/spec/javascripts/project_title_spec.js
@@ -26,7 +26,7 @@ require('~/project');
var fakeAjaxResponse = function fakeAjaxResponse(req) {
var d;
expect(req.url).toBe('/api/v3/projects.json?simple=true');
- expect(req.data).toEqual({ search: '', order_by: 'last_activity_at', per_page: 20 });
+ expect(req.data).toEqual({ search: '', order_by: 'last_activity_at', per_page: 20, membership: true });
d = $.Deferred();
d.resolve(this.projects_data);
return d.promise();
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index 4ac7e911740..285b7940174 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -1,8 +1,8 @@
/* 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');
-require('~/extensions/jquery.js');
+import '~/commons/bootstrap';
+import '~/right_sidebar';
(function() {
var $aside, $icon, $labelsIcon, $page, $toggle, assertSidebarState;
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
index ffff643e371..9e19dabd0e3 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -31,13 +31,9 @@ require('~/shortcuts_issuable');
this.shortcut.replyWithSelectedText();
expect($(this.selector).val()).toBe('');
});
- it('triggers `input`', function() {
- var focused = false;
- $(this.selector).on('focus', function() {
- focused = true;
- });
+ it('triggers `focus`', function() {
this.shortcut.replyWithSelectedText();
- expect(focused).toBe(true);
+ expect(document.activeElement).toBe(document.querySelector(this.selector));
});
});
describe('with any selection', function() {
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index fae462561e9..c12b44cea89 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -48,10 +48,10 @@ describe('Uncovered files', function () {
'./network/branch_graph.js',
];
- const sourceFiles = require.context('~', true, /^\.\/(?!application\.js).*\.(js|es6)$/);
+ const sourceFiles = require.context('~', true, /^\.\/(?!application\.js).*\.js$/);
sourceFiles.keys().forEach(function (path) {
// ignore if there is a matching spec file
- if (testsContext.keys().indexOf(`${path.replace(/\.js(\.es6)?$/, '')}_spec`) > -1) {
+ if (testsContext.keys().indexOf(`${path.replace(/\.js$/, '')}_spec`) > -1) {
return;
}
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index 69e3c52b35a..63fb1bb25c4 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" lang="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><span id="LC1" class="line" lang="plaintext">def fun end</span></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" lang="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 id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></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" lang="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><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre>')
end
end
diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb
index 730ca1f7c0a..90628917943 100644
--- a/spec/lib/expand_variables_spec.rb
+++ b/spec/lib/expand_variables_spec.rb
@@ -45,10 +45,10 @@ describe ExpandVariables do
{ key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' }
] },
- { value: 'review/$CI_BUILD_REF_NAME',
+ { value: 'review/$CI_COMMIT_REF_NAME',
result: 'review/feature/add-review-apps',
variables: [
- { key: 'CI_BUILD_REF_NAME', value: 'feature/add-review-apps' }
+ { key: 'CI_COMMIT_REF_NAME', value: 'feature/add-review-apps' }
] },
]
diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb
index cadfbadca10..e22f88b7a32 100644
--- a/spec/lib/gitlab/checks/change_access_spec.rb
+++ b/spec/lib/gitlab/checks/change_access_spec.rb
@@ -12,8 +12,16 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
ref: 'refs/heads/master'
}
end
-
- subject { described_class.new(changes, project: project, user_access: user_access).exec }
+ let(:protocol) { 'ssh' }
+
+ subject do
+ described_class.new(
+ changes,
+ project: project,
+ user_access: user_access,
+ protocol: protocol
+ ).exec
+ end
before { allow(user_access).to receive(:can_do_action?).with(:push_code).and_return(true) }
diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
index 2adbed2154f..c330e609337 100644
--- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
@@ -151,8 +151,8 @@ describe Gitlab::Ci::Config::Entry::Environment do
context 'when variables are used for environment' do
let(:config) do
- { name: 'review/$CI_BUILD_REF_NAME',
- url: 'https://$CI_BUILD_REF_NAME.review.gitlab.com' }
+ { name: 'review/$CI_COMMIT_REF_NAME',
+ url: 'https://$CI_COMMIT_REF_NAME.review.gitlab.com' }
end
describe '#valid?' do
diff --git a/spec/lib/gitlab/conflict/parser_spec.rb b/spec/lib/gitlab/conflict/parser_spec.rb
index 16eb3766356..2570f95dd21 100644
--- a/spec/lib/gitlab/conflict/parser_spec.rb
+++ b/spec/lib/gitlab/conflict/parser_spec.rb
@@ -120,43 +120,61 @@ CONFLICT
end
context 'when the file contents include conflict delimiters' do
- it 'raises UnexpectedDelimiter when there is a non-start delimiter first' do
- expect { parse_text('=======') }.
- to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
-
- expect { parse_text('>>>>>>> README.md') }.
- to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
-
- expect { parse_text('>>>>>>> some-other-path.md') }.
- not_to raise_error
+ context 'when there is a non-start delimiter first' do
+ it 'raises UnexpectedDelimiter when there is a middle delimiter first' do
+ expect { parse_text('=======') }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+ end
+
+ it 'raises UnexpectedDelimiter when there is an end delimiter first' do
+ expect { parse_text('>>>>>>> README.md') }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+ end
+
+ it 'does not raise when there is an end delimiter for a different path first' do
+ expect { parse_text('>>>>>>> some-other-path.md') }.
+ not_to raise_error
+ end
end
- it 'raises UnexpectedDelimiter when a start delimiter is followed by a non-middle delimiter' do
- start_text = "<<<<<<< README.md\n"
- end_text = "\n=======\n>>>>>>> README.md"
+ context 'when a start delimiter is followed by a non-middle delimiter' do
+ let(:start_text) { "<<<<<<< README.md\n" }
+ let(:end_text) { "\n=======\n>>>>>>> README.md" }
- expect { parse_text(start_text + '>>>>>>> README.md' + end_text) }.
- to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+ it 'raises UnexpectedDelimiter when it is followed by an end delimiter' do
+ expect { parse_text(start_text + '>>>>>>> README.md' + end_text) }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+ end
- expect { parse_text(start_text + start_text + end_text) }.
- to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+ it 'raises UnexpectedDelimiter when it is followed by another start delimiter' do
+ expect { parse_text(start_text + start_text + end_text) }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+ end
- expect { parse_text(start_text + '>>>>>>> some-other-path.md' + end_text) }.
- not_to raise_error
+ it 'does not raise when it is followed by a start delimiter for a different path' do
+ expect { parse_text(start_text + '>>>>>>> some-other-path.md' + end_text) }.
+ not_to raise_error
+ end
end
- it 'raises UnexpectedDelimiter when a middle delimiter is followed by a non-end delimiter' do
- start_text = "<<<<<<< README.md\n=======\n"
- end_text = "\n>>>>>>> README.md"
+ context 'when a middle delimiter is followed by a non-end delimiter' do
+ let(:start_text) { "<<<<<<< README.md\n=======\n" }
+ let(:end_text) { "\n>>>>>>> README.md" }
- expect { parse_text(start_text + '=======' + end_text) }.
- to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+ it 'raises UnexpectedDelimiter when it is followed by another middle delimiter' do
+ expect { parse_text(start_text + '=======' + end_text) }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+ end
- expect { parse_text(start_text + start_text + end_text) }.
- to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+ it 'raises UnexpectedDelimiter when it is followed by a start delimiter' do
+ expect { parse_text(start_text + start_text + end_text) }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+ end
- expect { parse_text(start_text + '>>>>>>> some-other-path.md' + end_text) }.
- not_to raise_error
+ it 'does not raise when it is followed by a start delimiter for another path' do
+ expect { parse_text(start_text + '<<<<<<< some-other-path.md' + end_text) }.
+ not_to raise_error
+ end
end
it 'raises MissingEndDelimiter when there is no end delimiter at the end' do
@@ -184,9 +202,20 @@ CONFLICT
to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
end
- it 'raises UnsupportedEncoding when the file contains non-UTF-8 characters' do
- expect { parse_text("a\xC4\xFC".force_encoding(Encoding::ASCII_8BIT)) }.
- to raise_error(Gitlab::Conflict::Parser::UnsupportedEncoding)
+ # All text from Rugged has an encoding of ASCII_8BIT, so force that in
+ # these strings.
+ context 'when the file contains UTF-8 characters' do
+ it 'does not raise' do
+ expect { parse_text("Espa\xC3\xB1a".force_encoding(Encoding::ASCII_8BIT)) }.
+ not_to raise_error
+ end
+ end
+
+ context 'when the file contains non-UTF-8 characters' do
+ it 'raises UnsupportedEncoding' do
+ expect { parse_text("a\xC4\xFC".force_encoding(Encoding::ASCII_8BIT)) }.
+ to raise_error(Gitlab::Conflict::Parser::UnsupportedEncoding)
+ end
end
end
end
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index 0e9309d278e..c6bd4e81f4f 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -22,19 +22,19 @@ describe Gitlab::Diff::Highlight, lib: true do
end
it 'highlights and marks unchanged lines' do
- code = %Q{ <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>\n}
+ code = %Q{ <span id="LC7" class="line" lang="ruby"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>\n}
expect(subject[2].text).to eq(code)
end
it 'highlights and marks removed lines' do
- code = %Q{-<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>\n}
+ code = %Q{-<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>\n}
expect(subject[4].text).to eq(code)
end
it 'highlights and marks added lines' do
- code = %Q{+<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n}
+ code = %Q{+<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n}
expect(subject[5].text).to eq(code)
end
diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb
index b983d73f8be..e76128ecd87 100644
--- a/spec/lib/gitlab/diff/parser_spec.rb
+++ b/spec/lib/gitlab/diff/parser_spec.rb
@@ -91,6 +91,54 @@ eos
end
end
+ describe '\ No newline at end of file' do
+ it "parses nonewline in one file correctly" do
+ first_nonewline_diff = <<~END
+ --- a/test
+ +++ b/test
+ @@ -1,2 +1,2 @@
+ +ipsum
+ lorem
+ -ipsum
+ \\ No newline at end of file
+ END
+ lines = parser.parse(first_nonewline_diff.lines).to_a
+
+ expect(lines[0].type).to eq('new')
+ expect(lines[0].text).to eq('+ipsum')
+ expect(lines[2].type).to eq('old')
+ expect(lines[3].type).to eq('old-nonewline')
+ expect(lines[1].old_pos).to eq(1)
+ expect(lines[1].new_pos).to eq(2)
+ end
+
+ it "parses nonewline in two files correctly" do
+ both_nonewline_diff = <<~END
+ --- a/test
+ +++ b/test
+ @@ -1,2 +1,2 @@
+ -lorem
+ -ipsum
+ \\ No newline at end of file
+ +ipsum
+ +lorem
+ \\ No newline at end of file
+ END
+ lines = parser.parse(both_nonewline_diff.lines).to_a
+
+ expect(lines[0].type).to eq('old')
+ expect(lines[1].type).to eq('old')
+ expect(lines[2].type).to eq('old-nonewline')
+ expect(lines[5].type).to eq('new-nonewline')
+ expect(lines[3].text).to eq('+ipsum')
+ expect(lines[3].old_pos).to eq(3)
+ expect(lines[3].new_pos).to eq(1)
+ expect(lines[4].text).to eq('+lorem')
+ expect(lines[4].old_pos).to eq(3)
+ expect(lines[4].new_pos).to eq(2)
+ end
+ end
+
context 'when lines is empty' do
it { expect(parser.parse([])).to eq([]) }
it { expect(parser.parse(nil)).to eq([]) }
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index bc139d5ef28..9ed43da1116 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -90,6 +90,12 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(prefix).to eq("#{project_name}-test-branch-SHA")
end
+
+ it 'returns correct string for a ref containing dots' do
+ prefix = repository.archive_prefix('test.branch', 'SHA')
+
+ expect(prefix).to eq("#{project_name}-test.branch-SHA")
+ end
end
describe '#archive' do
@@ -507,7 +513,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
describe "#remote_add" do
before(:all) do
@repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH)
- @repo.remote_add("new_remote", SeedHelper::GITLAB_URL)
+ @repo.remote_add("new_remote", SeedHelper::GITLAB_GIT_TEST_REPO_URL)
end
it "should add the remote" do
diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb
index 3f080de99dd..8b867fbe322 100644
--- a/spec/lib/gitlab/github_import/importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer_spec.rb
@@ -55,9 +55,6 @@ describe Gitlab::GithubImport::Importer, lib: true do
allow_any_instance_of(Octokit::Client).to receive(:releases).and_return([release1, release2])
end
- let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
- let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
- let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
let(:label1) do
double(
name: 'Bug',
@@ -127,32 +124,6 @@ describe Gitlab::GithubImport::Importer, lib: true do
)
end
- let!(:user) { create(:user, email: octocat.email) }
- let(:repository) { double(id: 1, fork: false) }
- let(:source_sha) { create(:commit, project: project).id }
- let(:source_branch) { double(ref: 'branch-merged', repo: repository, sha: source_sha) }
- let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id }
- let(:target_branch) { double(ref: 'master', repo: repository, sha: target_sha) }
- let(:pull_request) do
- double(
- number: 1347,
- milestone: nil,
- state: 'open',
- title: 'New feature',
- body: 'Please pull these awesome changes',
- head: source_branch,
- base: target_branch,
- assignee: nil,
- user: octocat,
- created_at: created_at,
- updated_at: updated_at,
- closed_at: nil,
- merged_at: nil,
- url: "#{api_root}/repos/octocat/Hello-World/pulls/1347",
- labels: [double(name: 'Label #2')]
- )
- end
-
let(:release1) do
double(
tag_name: 'v1.0.0',
@@ -177,12 +148,14 @@ describe Gitlab::GithubImport::Importer, lib: true do
)
end
+ subject { described_class.new(project) }
+
it 'returns true' do
- expect(described_class.new(project).execute).to eq true
+ expect(subject.execute).to eq true
end
it 'does not raise an error' do
- expect { described_class.new(project).execute }.not_to raise_error
+ expect { subject.execute }.not_to raise_error
end
it 'stores error messages' do
@@ -205,15 +178,93 @@ describe Gitlab::GithubImport::Importer, lib: true do
end
end
+ shared_examples 'Gitlab::GithubImport unit-testing' do
+ describe '#clean_up_restored_branches' do
+ subject { described_class.new(project) }
+
+ before do
+ allow(gh_pull_request).to receive(:source_branch_exists?).at_least(:once) { false }
+ allow(gh_pull_request).to receive(:target_branch_exists?).at_least(:once) { false }
+ end
+
+ context 'when pull request stills open' do
+ let(:gh_pull_request) { Gitlab::GithubImport::PullRequestFormatter.new(project, pull_request) }
+
+ it 'does not remove branches' do
+ expect(subject).not_to receive(:remove_branch)
+ subject.send(:clean_up_restored_branches, gh_pull_request)
+ end
+ end
+
+ context 'when pull request is closed' do
+ let(:gh_pull_request) { Gitlab::GithubImport::PullRequestFormatter.new(project, closed_pull_request) }
+
+ it 'does remove branches' do
+ expect(subject).to receive(:remove_branch).at_least(2).times
+ subject.send(:clean_up_restored_branches, gh_pull_request)
+ end
+ end
+ end
+ end
+
let(:project) { create(:project, :wiki_disabled, import_url: "#{repo_root}/octocat/Hello-World.git") }
+ let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
let(:credentials) { { user: 'joe' } }
+ let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
+ let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
+ let(:repository) { double(id: 1, fork: false) }
+ let(:source_sha) { create(:commit, project: project).id }
+ let(:source_branch) { double(ref: 'branch-merged', repo: repository, sha: source_sha) }
+ let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id }
+ let(:target_branch) { double(ref: 'master', repo: repository, sha: target_sha) }
+ let(:pull_request) do
+ double(
+ number: 1347,
+ milestone: nil,
+ state: 'open',
+ title: 'New feature',
+ body: 'Please pull these awesome changes',
+ head: source_branch,
+ base: target_branch,
+ assignee: nil,
+ user: octocat,
+ created_at: created_at,
+ updated_at: updated_at,
+ closed_at: nil,
+ merged_at: nil,
+ url: "#{api_root}/repos/octocat/Hello-World/pulls/1347",
+ labels: [double(name: 'Label #2')]
+ )
+ end
+ let(:closed_pull_request) do
+ double(
+ number: 1347,
+ milestone: nil,
+ state: 'closed',
+ title: 'New feature',
+ body: 'Please pull these awesome changes',
+ head: source_branch,
+ base: target_branch,
+ assignee: nil,
+ user: octocat,
+ created_at: created_at,
+ updated_at: updated_at,
+ closed_at: updated_at,
+ merged_at: nil,
+ url: "#{api_root}/repos/octocat/Hello-World/pulls/1347",
+ labels: [double(name: 'Label #2')]
+ )
+ end
+
context 'when importing a GitHub project' do
let(:api_root) { 'https://api.github.com' }
let(:repo_root) { 'https://github.com' }
+ subject { described_class.new(project) }
it_behaves_like 'Gitlab::GithubImport::Importer#execute'
it_behaves_like 'Gitlab::GithubImport::Importer#execute an error occurs'
+ it_behaves_like 'Gitlab::GithubImport unit-testing'
describe '#client' do
it 'instantiates a Client' do
@@ -223,7 +274,7 @@ describe Gitlab::GithubImport::Importer, lib: true do
{}
)
- described_class.new(project).client
+ subject.client
end
end
end
@@ -231,6 +282,8 @@ describe Gitlab::GithubImport::Importer, lib: true do
context 'when importing a Gitea project' do
let(:api_root) { 'https://try.gitea.io/api/v1' }
let(:repo_root) { 'https://try.gitea.io' }
+ subject { described_class.new(project) }
+
before do
project.update(import_type: 'gitea', import_url: "#{repo_root}/foo/group/project.git")
end
@@ -239,6 +292,7 @@ describe Gitlab::GithubImport::Importer, lib: true do
let(:expected_not_called) { [:import_releases] }
end
it_behaves_like 'Gitlab::GithubImport::Importer#execute an error occurs'
+ it_behaves_like 'Gitlab::GithubImport unit-testing'
describe '#client' do
it 'instantiates a Client' do
@@ -248,7 +302,7 @@ describe Gitlab::GithubImport::Importer, lib: true do
{ host: "#{repo_root}:443/foo", api_version: 'v1' }
)
- described_class.new(project).client
+ subject.client
end
end
end
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 951cbea7857..44423917944 100644
--- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -306,4 +306,12 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
expect(pull_request.url).to eq 'https://api.github.com/repos/octocat/Hello-World/pulls/1347'
end
end
+
+ describe '#opened?' do
+ let(:raw_data) { double(base_data.merge(state: 'open')) }
+
+ it 'returns true when state is "open"' do
+ expect(pull_request.opened?).to be_truthy
+ end
+ end
end
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index e177d883158..e49799ad105 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -13,9 +13,9 @@ describe Gitlab::Highlight, lib: true do
end
it 'highlights all the lines properly' do
- expect(lines[4]).to eq(%Q{<span id="LC5" class="line"> <span class="kp">extend</span> <span class="nb">self</span></span>\n})
- expect(lines[21]).to eq(%Q{<span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>\n})
- expect(lines[26]).to eq(%Q{<span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>\n})
+ expect(lines[4]).to eq(%Q{<span id="LC5" class="line" lang="ruby"> <span class="kp">extend</span> <span class="nb">self</span></span>\n})
+ expect(lines[21]).to eq(%Q{<span id="LC22" class="line" lang="ruby"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>\n})
+ expect(lines[26]).to eq(%Q{<span id="LC27" class="line" lang="ruby"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>\n})
end
describe 'with CRLF' do
@@ -26,7 +26,7 @@ describe Gitlab::Highlight, lib: true do
end
it 'strips extra LFs' do
- expect(lines[0]).to eq("<span id=\"LC1\" class=\"line\">test </span>")
+ expect(lines[0]).to eq("<span id=\"LC1\" class=\"line\" lang=\"plaintext\">test </span>")
end
end
end
diff --git a/spec/lib/gitlab/redis_spec.rb b/spec/lib/gitlab/redis_spec.rb
index 917c5c46db1..8b77c925705 100644
--- a/spec/lib/gitlab/redis_spec.rb
+++ b/spec/lib/gitlab/redis_spec.rb
@@ -3,8 +3,16 @@ require 'spec_helper'
describe Gitlab::Redis do
include StubENV
- before(:each) { clear_raw_config }
- after(:each) { clear_raw_config }
+ let(:config) { 'config/resque.yml' }
+
+ before(:each) do
+ stub_env('GITLAB_REDIS_CONFIG_FILE', Rails.root.join(config).to_s)
+ clear_raw_config
+ end
+
+ after(:each) do
+ clear_raw_config
+ end
describe '.params' do
subject { described_class.params }
@@ -18,22 +26,22 @@ describe Gitlab::Redis do
end
context 'when url contains unix socket reference' do
- let(:config_old) { Rails.root.join('spec/fixtures/config/redis_old_format_socket.yml').to_s }
- let(:config_new) { Rails.root.join('spec/fixtures/config/redis_new_format_socket.yml').to_s }
+ let(:config_old) { 'spec/fixtures/config/redis_old_format_socket.yml' }
+ let(:config_new) { 'spec/fixtures/config/redis_new_format_socket.yml' }
context 'with old format' do
- it 'returns path key instead' do
- stub_const("#{described_class}::CONFIG_FILE", config_old)
+ let(:config) { config_old }
+ it 'returns path key instead' do
is_expected.to include(path: '/path/to/old/redis.sock')
is_expected.not_to have_key(:url)
end
end
context 'with new format' do
- it 'returns path key instead' do
- stub_const("#{described_class}::CONFIG_FILE", config_new)
+ let(:config) { config_new }
+ it 'returns path key instead' do
is_expected.to include(path: '/path/to/redis.sock')
is_expected.not_to have_key(:url)
end
@@ -41,22 +49,22 @@ describe Gitlab::Redis do
end
context 'when url is host based' do
- let(:config_old) { Rails.root.join('spec/fixtures/config/redis_old_format_host.yml') }
- let(:config_new) { Rails.root.join('spec/fixtures/config/redis_new_format_host.yml') }
+ let(:config_old) { 'spec/fixtures/config/redis_old_format_host.yml' }
+ let(:config_new) { 'spec/fixtures/config/redis_new_format_host.yml' }
context 'with old format' do
- it 'returns hash with host, port, db, and password' do
- stub_const("#{described_class}::CONFIG_FILE", config_old)
+ let(:config) { config_old }
+ it 'returns hash with host, port, db, and password' do
is_expected.to include(host: 'localhost', password: 'mypassword', port: 6379, db: 99)
is_expected.not_to have_key(:url)
end
end
context 'with new format' do
- it 'returns hash with host, port, db, and password' do
- stub_const("#{described_class}::CONFIG_FILE", config_new)
+ let(:config) { config_new }
+ it 'returns hash with host, port, db, and password' do
is_expected.to include(host: 'localhost', password: 'mynewpassword', port: 6379, db: 99)
is_expected.not_to have_key(:url)
end
@@ -74,15 +82,13 @@ describe Gitlab::Redis do
end
context 'when yml file with env variable' do
- let(:redis_config) { Rails.root.join('spec/fixtures/config/redis_config_with_env.yml') }
+ let(:config) { 'spec/fixtures/config/redis_config_with_env.yml' }
before do
stub_env('TEST_GITLAB_REDIS_URL', 'redis://redishost:6379')
end
it 'reads redis url from env variable' do
- stub_const("#{described_class}::CONFIG_FILE", redis_config)
-
expect(described_class.url).to eq 'redis://redishost:6379'
end
end
@@ -90,14 +96,13 @@ describe Gitlab::Redis do
describe '._raw_config' do
subject { described_class._raw_config }
+ let(:config) { '/var/empty/doesnotexist' }
it 'should be frozen' do
expect(subject).to be_frozen
end
it 'returns false when the file does not exist' do
- stub_const("#{described_class}::CONFIG_FILE", '/var/empty/doesnotexist')
-
expect(subject).to eq(false)
end
end
@@ -134,22 +139,18 @@ describe Gitlab::Redis do
subject { described_class.new(Rails.env).sentinels }
context 'when sentinels are defined' do
- let(:config) { Rails.root.join('spec/fixtures/config/redis_new_format_host.yml') }
+ let(:config) { 'spec/fixtures/config/redis_new_format_host.yml' }
it 'returns an array of hashes with host and port keys' do
- stub_const("#{described_class}::CONFIG_FILE", config)
-
is_expected.to include(host: 'localhost', port: 26380)
is_expected.to include(host: 'slave2', port: 26381)
end
end
context 'when sentinels are not defined' do
- let(:config) { Rails.root.join('spec/fixtures/config/redis_old_format_host.yml') }
+ let(:config) { 'spec/fixtures/config/redis_old_format_host.yml' }
it 'returns nil' do
- stub_const("#{described_class}::CONFIG_FILE", config)
-
is_expected.to be_nil
end
end
@@ -159,21 +160,17 @@ describe Gitlab::Redis do
subject { described_class.new(Rails.env).sentinels? }
context 'when sentinels are defined' do
- let(:config) { Rails.root.join('spec/fixtures/config/redis_new_format_host.yml') }
+ let(:config) { 'spec/fixtures/config/redis_new_format_host.yml' }
it 'returns true' do
- stub_const("#{described_class}::CONFIG_FILE", config)
-
is_expected.to be_truthy
end
end
context 'when sentinels are not defined' do
- let(:config) { Rails.root.join('spec/fixtures/config/redis_old_format_host.yml') }
+ let(:config) { 'spec/fixtures/config/redis_old_format_host.yml' }
it 'returns false' do
- stub_const("#{described_class}::CONFIG_FILE", config)
-
is_expected.to be_falsey
end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index b692142713f..e822d7eb348 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -8,6 +8,15 @@ describe Notify do
include_context 'gitlab email notification'
+ def have_referable_subject(referable, reply: false)
+ prefix = referable.project.name if referable.project
+ prefix = "Re: #{prefix}" if reply
+
+ suffix = "#{referable.title} (#{referable.to_reference})"
+
+ have_subject [prefix, suffix].compact.join(' | ')
+ end
+
context 'for a project' do
describe 'items that are assignable, the email' do
let(:current_user) { create(:user, email: "current@email.com") }
@@ -41,11 +50,11 @@ describe Notify do
it_behaves_like 'an unsubscribeable thread'
it 'has the correct subject' do
- is_expected.to have_subject /#{project.name} \| #{issue.title} \(##{issue.iid}\)/
+ is_expected.to have_referable_subject(issue)
end
it 'contains a link to the new issue' do
- is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
+ is_expected.to have_body_text namespace_project_issue_path(project.namespace, project, issue)
end
context 'when enabled email_author_in_body' do
@@ -55,7 +64,7 @@ describe Notify do
it 'contains a link to note author' do
is_expected.to have_body_text issue.author_name
- is_expected.to have_body_text /wrote\:/
+ is_expected.to have_body_text 'wrote:'
end
end
end
@@ -66,7 +75,7 @@ describe Notify do
it_behaves_like 'it should show Gmail Actions View Issue link'
it 'contains the description' do
- is_expected.to have_body_text /#{issue_with_description.description}/
+ is_expected.to have_body_text issue_with_description.description
end
end
@@ -87,19 +96,19 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/
+ is_expected.to have_referable_subject(issue, reply: true)
end
it 'contains the name of the previous assignee' do
- is_expected.to have_body_text /#{previous_assignee.name}/
+ is_expected.to have_body_text previous_assignee.name
end
it 'contains the name of the new assignee' do
- is_expected.to have_body_text /#{assignee.name}/
+ is_expected.to have_body_text assignee.name
end
it 'contains a link to the issue' do
- is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
+ is_expected.to have_body_text namespace_project_issue_path(project.namespace, project, issue)
end
end
@@ -121,15 +130,15 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/
+ is_expected.to have_referable_subject(issue, reply: true)
end
it 'contains the names of the added labels' do
- is_expected.to have_body_text /foo, bar, and baz/
+ is_expected.to have_body_text 'foo, bar, and baz'
end
it 'contains a link to the issue' do
- is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
+ is_expected.to have_body_text namespace_project_issue_path(project.namespace, project, issue)
end
end
@@ -150,19 +159,19 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/i
+ is_expected.to have_referable_subject(issue, reply: true)
end
it 'contains the new status' do
- is_expected.to have_body_text /#{status}/i
+ is_expected.to have_body_text status
end
it 'contains the user name' do
- is_expected.to have_body_text /#{current_user.name}/i
+ is_expected.to have_body_text current_user.name
end
it 'contains a link to the issue' do
- is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
+ is_expected.to have_body_text(namespace_project_issue_path project.namespace, project, issue)
end
end
@@ -181,7 +190,7 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/i
+ is_expected.to have_referable_subject(issue, reply: true)
end
it 'contains link to new issue' do
@@ -191,7 +200,7 @@ describe Notify do
end
it 'contains a link to the original issue' do
- is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
+ is_expected.to have_body_text namespace_project_issue_path(project.namespace, project, issue)
end
end
end
@@ -212,19 +221,19 @@ describe Notify do
it_behaves_like 'an unsubscribeable thread'
it 'has the correct subject' do
- is_expected.to have_subject /#{merge_request.title} \(#{merge_request.to_reference}\)/
+ is_expected.to have_referable_subject(merge_request)
end
it 'contains a link to the new merge request' do
- is_expected.to have_body_text /#{namespace_project_merge_request_path(project.namespace, project, merge_request)}/
+ is_expected.to have_body_text namespace_project_merge_request_path(project.namespace, project, merge_request)
end
it 'contains the source branch for the merge request' do
- is_expected.to have_body_text /#{merge_request.source_branch}/
+ is_expected.to have_body_text merge_request.source_branch
end
it 'contains the target branch for the merge request' do
- is_expected.to have_body_text /#{merge_request.target_branch}/
+ is_expected.to have_body_text merge_request.target_branch
end
context 'when enabled email_author_in_body' do
@@ -234,7 +243,7 @@ describe Notify do
it 'contains a link to note author' do
is_expected.to have_body_text merge_request.author_name
- is_expected.to have_body_text /wrote\:/
+ is_expected.to have_body_text 'wrote:'
end
end
end
@@ -246,7 +255,7 @@ describe Notify do
it_behaves_like "an unsubscribeable thread"
it 'contains the description' do
- is_expected.to have_body_text /#{merge_request_with_description.description}/
+ is_expected.to have_body_text merge_request_with_description.description
end
end
@@ -267,19 +276,19 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /#{merge_request.title} \(#{merge_request.to_reference}\)/
+ is_expected.to have_referable_subject(merge_request, reply: true)
end
it 'contains the name of the previous assignee' do
- is_expected.to have_body_text /#{previous_assignee.name}/
+ is_expected.to have_body_text previous_assignee.name
end
it 'contains the name of the new assignee' do
- is_expected.to have_body_text /#{assignee.name}/
+ is_expected.to have_body_text assignee.name
end
it 'contains a link to the merge request' do
- is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/
+ is_expected.to have_body_text namespace_project_merge_request_path(project.namespace, project, merge_request)
end
end
@@ -301,15 +310,15 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /#{merge_request.title} \(#{merge_request.to_reference}\)/
+ is_expected.to have_referable_subject(merge_request, reply: true)
end
it 'contains the names of the added labels' do
- is_expected.to have_body_text /foo, bar, and baz/
+ is_expected.to have_body_text 'foo, bar, and baz'
end
it 'contains a link to the merge request' do
- is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/
+ is_expected.to have_body_text namespace_project_merge_request_path(project.namespace, project, merge_request)
end
end
@@ -330,19 +339,19 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /#{merge_request.title} \(#{merge_request.to_reference}\)/i
+ is_expected.to have_referable_subject(merge_request, reply: true)
end
it 'contains the new status' do
- is_expected.to have_body_text /#{status}/i
+ is_expected.to have_body_text status
end
it 'contains the user name' do
- is_expected.to have_body_text /#{current_user.name}/i
+ is_expected.to have_body_text current_user.name
end
it 'contains a link to the merge request' do
- is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/
+ is_expected.to have_body_text namespace_project_merge_request_path(project.namespace, project, merge_request)
end
end
@@ -363,15 +372,15 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /#{merge_request.title} \(#{merge_request.to_reference}\)/
+ is_expected.to have_referable_subject(merge_request, reply: true)
end
it 'contains the new status' do
- is_expected.to have_body_text /merged/i
+ is_expected.to have_body_text 'merged'
end
it 'contains a link to the merge request' do
- is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/
+ is_expected.to have_body_text namespace_project_merge_request_path(project.namespace, project, merge_request)
end
end
end
@@ -387,15 +396,15 @@ describe Notify do
it_behaves_like "a user cannot unsubscribe through footer link"
it 'has the correct subject' do
- is_expected.to have_subject /Project was moved/
+ is_expected.to have_subject "#{project.name} | Project was moved"
end
it 'contains name of project' do
- is_expected.to have_body_text /#{project.name_with_namespace}/
+ is_expected.to have_body_text project.name_with_namespace
end
it 'contains new user role' do
- is_expected.to have_body_text /#{project.ssh_url_to_repo}/
+ is_expected.to have_body_text project.ssh_url_to_repo
end
end
@@ -424,9 +433,9 @@ describe Notify do
expect(to_emails[0].address).to eq(project.members.owners_and_masters.first.user.notification_email)
is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
- is_expected.to have_body_text /#{project.name_with_namespace}/
- is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/
- is_expected.to have_body_text /#{project_member.human_access}/
+ is_expected.to have_body_text project.name_with_namespace
+ is_expected.to have_body_text namespace_project_project_members_url(project.namespace, project)
+ is_expected.to have_body_text project_member.human_access
end
end
@@ -451,9 +460,9 @@ describe Notify do
expect(to_emails[0].address).to eq(group.members.owners_and_masters.first.user.notification_email)
is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
- is_expected.to have_body_text /#{project.name_with_namespace}/
- is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/
- is_expected.to have_body_text /#{project_member.human_access}/
+ is_expected.to have_body_text project.name_with_namespace
+ is_expected.to have_body_text namespace_project_project_members_url(project.namespace, project)
+ is_expected.to have_body_text project_member.human_access
end
end
end
@@ -473,8 +482,8 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject "Access to the #{project.name_with_namespace} project was denied"
- is_expected.to have_body_text /#{project.name_with_namespace}/
- is_expected.to have_body_text /#{project.web_url}/
+ is_expected.to have_body_text project.name_with_namespace
+ is_expected.to have_body_text project.web_url
end
end
@@ -490,9 +499,9 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject "Access to the #{project.name_with_namespace} project was granted"
- is_expected.to have_body_text /#{project.name_with_namespace}/
- is_expected.to have_body_text /#{project.web_url}/
- is_expected.to have_body_text /#{project_member.human_access}/
+ is_expected.to have_body_text project.name_with_namespace
+ is_expected.to have_body_text project.web_url
+ is_expected.to have_body_text project_member.human_access
end
end
@@ -521,10 +530,10 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject "Invitation to join the #{project.name_with_namespace} project"
- is_expected.to have_body_text /#{project.name_with_namespace}/
- is_expected.to have_body_text /#{project.web_url}/
- is_expected.to have_body_text /#{project_member.human_access}/
- is_expected.to have_body_text /#{project_member.invite_token}/
+ is_expected.to have_body_text project.name_with_namespace
+ is_expected.to have_body_text project.web_url
+ is_expected.to have_body_text project_member.human_access
+ is_expected.to have_body_text project_member.invite_token
end
end
@@ -546,10 +555,10 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation accepted'
- is_expected.to have_body_text /#{project.name_with_namespace}/
- is_expected.to have_body_text /#{project.web_url}/
- is_expected.to have_body_text /#{project_member.invite_email}/
- is_expected.to have_body_text /#{invited_user.name}/
+ is_expected.to have_body_text project.name_with_namespace
+ is_expected.to have_body_text project.web_url
+ is_expected.to have_body_text project_member.invite_email
+ is_expected.to have_body_text invited_user.name
end
end
@@ -570,9 +579,9 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation declined'
- is_expected.to have_body_text /#{project.name_with_namespace}/
- is_expected.to have_body_text /#{project.web_url}/
- is_expected.to have_body_text /#{project_member.invite_email}/
+ is_expected.to have_body_text project.name_with_namespace
+ is_expected.to have_body_text project.web_url
+ is_expected.to have_body_text project_member.invite_email
end
end
@@ -598,11 +607,11 @@ describe Notify do
end
it 'contains the message from the note' do
- is_expected.to have_body_text /#{note.note}/
+ is_expected.to have_body_text note.note
end
it 'does not contain note author' do
- is_expected.not_to have_body_text /wrote\:/
+ is_expected.not_to have_body_text 'wrote:'
end
context 'when enabled email_author_in_body' do
@@ -612,7 +621,7 @@ describe Notify do
it 'contains a link to note author' do
is_expected.to have_body_text note.author_name
- is_expected.to have_body_text /wrote\:/
+ is_expected.to have_body_text 'wrote:'
end
end
end
@@ -632,7 +641,7 @@ describe Notify do
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'has the correct subject' do
- is_expected.to have_subject /Re: #{project.name} | #{commit.title} \(#{commit.short_id}\)/
+ is_expected.to have_subject "Re: #{project.name} | #{commit.title.strip} (#{commit.short_id})"
end
it 'contains a link to the commit' do
@@ -655,11 +664,11 @@ describe Notify do
it_behaves_like 'an unsubscribeable thread'
it 'has the correct subject' do
- is_expected.to have_subject /#{merge_request.title} \(#{merge_request.to_reference}\)/
+ is_expected.to have_referable_subject(merge_request, reply: true)
end
it 'contains a link to the merge request note' do
- is_expected.to have_body_text /#{note_on_merge_request_path}/
+ is_expected.to have_body_text note_on_merge_request_path
end
end
@@ -678,11 +687,11 @@ describe Notify do
it_behaves_like 'an unsubscribeable thread'
it 'has the correct subject' do
- is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/
+ is_expected.to have_referable_subject(issue, reply: true)
end
it 'contains a link to the issue note' do
- is_expected.to have_body_text /#{note_on_issue_path}/
+ is_expected.to have_body_text note_on_issue_path
end
end
end
@@ -698,11 +707,11 @@ describe Notify do
let(:note) { create(model, project: project, author: note_author) }
it "includes diffs with character-level highlighting" do
- is_expected.to have_body_text /<span class=\"p\">}<\/span><\/span>/
+ is_expected.to have_body_text '<span class="p">}</span></span>'
end
it 'contains a link to the diff file' do
- is_expected.to have_body_text /#{note.diff_file.file_path}/
+ is_expected.to have_body_text note.diff_file.file_path
end
it_behaves_like 'it should have Gmail Actions links'
@@ -718,11 +727,11 @@ describe Notify do
end
it 'contains the message from the note' do
- is_expected.to have_body_text /#{note.note}/
+ is_expected.to have_body_text note.note
end
it 'does not contain note author' do
- is_expected.not_to have_body_text /wrote\:/
+ is_expected.not_to have_body_text 'wrote:'
end
context 'when enabled email_author_in_body' do
@@ -732,7 +741,7 @@ describe Notify do
it 'contains a link to note author' do
is_expected.to have_body_text note.author_name
- is_expected.to have_body_text /wrote\:/
+ is_expected.to have_body_text 'wrote:'
end
end
end
@@ -777,9 +786,9 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject "Request to join the #{group.name} group"
- is_expected.to have_body_text /#{group.name}/
- is_expected.to have_body_text /#{group_group_members_url(group)}/
- is_expected.to have_body_text /#{group_member.human_access}/
+ is_expected.to have_body_text group.name
+ is_expected.to have_body_text group_group_members_url(group)
+ is_expected.to have_body_text group_member.human_access
end
end
@@ -798,8 +807,8 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject "Access to the #{group.name} group was denied"
- is_expected.to have_body_text /#{group.name}/
- is_expected.to have_body_text /#{group.web_url}/
+ is_expected.to have_body_text group.name
+ is_expected.to have_body_text group.web_url
end
end
@@ -816,9 +825,9 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject "Access to the #{group.name} group was granted"
- is_expected.to have_body_text /#{group.name}/
- is_expected.to have_body_text /#{group.web_url}/
- is_expected.to have_body_text /#{group_member.human_access}/
+ is_expected.to have_body_text group.name
+ is_expected.to have_body_text group.web_url
+ is_expected.to have_body_text group_member.human_access
end
end
@@ -847,10 +856,10 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject "Invitation to join the #{group.name} group"
- is_expected.to have_body_text /#{group.name}/
- is_expected.to have_body_text /#{group.web_url}/
- is_expected.to have_body_text /#{group_member.human_access}/
- is_expected.to have_body_text /#{group_member.invite_token}/
+ is_expected.to have_body_text group.name
+ is_expected.to have_body_text group.web_url
+ is_expected.to have_body_text group_member.human_access
+ is_expected.to have_body_text group_member.invite_token
end
end
@@ -872,10 +881,10 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation accepted'
- is_expected.to have_body_text /#{group.name}/
- is_expected.to have_body_text /#{group.web_url}/
- is_expected.to have_body_text /#{group_member.invite_email}/
- is_expected.to have_body_text /#{invited_user.name}/
+ is_expected.to have_body_text group.name
+ is_expected.to have_body_text group.web_url
+ is_expected.to have_body_text group_member.invite_email
+ is_expected.to have_body_text invited_user.name
end
end
@@ -896,9 +905,9 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation declined'
- is_expected.to have_body_text /#{group.name}/
- is_expected.to have_body_text /#{group.web_url}/
- is_expected.to have_body_text /#{group_member.invite_email}/
+ is_expected.to have_body_text group.name
+ is_expected.to have_body_text group.web_url
+ is_expected.to have_body_text group_member.invite_email
end
end
end
@@ -925,11 +934,11 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /^Confirmation instructions/
+ is_expected.to have_subject 'Confirmation instructions | A Nice Suffix'
end
it 'includes a link to the site' do
- is_expected.to have_body_text /#{example_site_path}/
+ is_expected.to have_body_text example_site_path
end
end
@@ -952,11 +961,11 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /Pushed new branch master/
+ is_expected.to have_subject "[Git][#{project.full_path}] Pushed new branch master"
end
it 'contains a link to the branch' do
- is_expected.to have_body_text /#{tree_path}/
+ is_expected.to have_body_text tree_path
end
end
@@ -979,11 +988,11 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /Pushed new tag v1\.0/
+ is_expected.to have_subject "[Git][#{project.full_path}] Pushed new tag v1.0"
end
it 'contains a link to the tag' do
- is_expected.to have_body_text /#{tree_path}/
+ is_expected.to have_body_text tree_path
end
end
@@ -1005,7 +1014,7 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /Deleted branch master/
+ is_expected.to have_subject "[Git][#{project.full_path}] Deleted branch master"
end
end
@@ -1027,7 +1036,7 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /Deleted tag v1\.0/
+ is_expected.to have_subject "[Git][#{project.full_path}] Deleted tag v1.0"
end
end
@@ -1055,23 +1064,23 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /\[#{project.path_with_namespace}\]\[master\] #{commits.length} commits:/
+ is_expected.to have_subject "[Git][#{project.full_path}][master] #{commits.length} commits: Ruby files modified"
end
it 'includes commits list' do
- is_expected.to have_body_text /Change some files/
+ is_expected.to have_body_text 'Change some files'
end
it 'includes diffs with character-level highlighting' do
- is_expected.to have_body_text /def<\/span> <span class=\"nf\">archive_formats_regex/
+ is_expected.to have_body_text 'def</span> <span class="nf">archive_formats_regex'
end
it 'contains a link to the diff' do
- is_expected.to have_body_text /#{diff_path}/
+ is_expected.to have_body_text diff_path
end
it 'does not contain the misleading footer' do
- is_expected.not_to have_body_text /you are a member of/
+ is_expected.not_to have_body_text 'you are a member of'
end
context "when set to send from committer email if domain matches" do
@@ -1157,19 +1166,19 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /#{commits.first.title}/
+ is_expected.to have_subject "[Git][#{project.full_path}][master] #{commits.first.title}"
end
it 'includes commits list' do
- is_expected.to have_body_text /Change some files/
+ is_expected.to have_body_text 'Change some files'
end
it 'includes diffs with character-level highlighting' do
- is_expected.to have_body_text /def<\/span> <span class=\"nf\">archive_formats_regex/
+ is_expected.to have_body_text 'def</span> <span class="nf">archive_formats_regex'
end
it 'contains a link to the diff' do
- is_expected.to have_body_text /#{diff_path}/
+ is_expected.to have_body_text diff_path
end
end
diff --git a/spec/migrations/rename_more_reserved_project_names_spec.rb b/spec/migrations/rename_more_reserved_project_names_spec.rb
new file mode 100644
index 00000000000..36e82729c23
--- /dev/null
+++ b/spec/migrations/rename_more_reserved_project_names_spec.rb
@@ -0,0 +1,47 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170313133418_rename_more_reserved_project_names.rb')
+
+# This migration uses multiple threads, and thus different transactions. This
+# means data created in this spec may not be visible to some threads. To work
+# around this we use the TRUNCATE cleaning strategy.
+describe RenameMoreReservedProjectNames, truncate: true do
+ let(:migration) { described_class.new }
+ let!(:project) { create(:empty_project) }
+
+ before do
+ project.path = 'artifacts'
+ project.save!(validate: false)
+ end
+
+ describe '#up' do
+ context 'when project repository exists' do
+ before { project.create_repository }
+
+ context 'when no exception is raised' do
+ it 'renames project with reserved names' do
+ migration.up
+
+ expect(project.reload.path).to eq('artifacts0')
+ end
+ end
+
+ context 'when exception is raised during rename' do
+ before do
+ allow(project).to receive(:rename_repo).and_raise(StandardError)
+ end
+
+ it 'captures exception from project rename' do
+ expect { migration.up }.not_to raise_error
+ end
+ end
+ end
+
+ context 'when project repository does not exist' do
+ it 'does not raise error' do
+ expect { migration.up }.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index 30f8fdf91b2..92d70cfc64c 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -1,6 +1,12 @@
require 'spec_helper'
describe Ability, lib: true do
+ context 'using a nil subject' do
+ it 'is always empty' do
+ expect(Ability.allowed(nil, nil).to_set).to be_empty
+ end
+ end
+
describe '.can_edit_note?' do
let(:project) { create(:empty_project) }
let(:note) { create(:note_on_issue, project: project) }
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
index 03d02b4d382..94c25a454aa 100644
--- a/spec/models/blob_spec.rb
+++ b/spec/models/blob_spec.rb
@@ -70,6 +70,8 @@ describe Blob do
end
describe '#to_partial_path' do
+ let(:project) { double(lfs_enabled?: true) }
+
def stubbed_blob(overrides = {})
overrides.reverse_merge!(
image?: false,
@@ -84,34 +86,35 @@ describe Blob do
end
end
- it 'handles LFS pointers' do
- blob = stubbed_blob(lfs_pointer?: true)
+ it 'handles LFS pointers with LFS enabled' do
+ blob = stubbed_blob(lfs_pointer?: true, text?: true)
+ expect(blob.to_partial_path(project)).to eq 'download'
+ end
- expect(blob.to_partial_path).to eq 'download'
+ it 'handles LFS pointers with LFS disabled' do
+ blob = stubbed_blob(lfs_pointer?: true, text?: true)
+ project = double(lfs_enabled?: false)
+ expect(blob.to_partial_path(project)).to eq 'text'
end
it 'handles SVGs' do
blob = stubbed_blob(text?: true, svg?: true)
-
- expect(blob.to_partial_path).to eq 'image'
+ expect(blob.to_partial_path(project)).to eq 'image'
end
it 'handles images' do
blob = stubbed_blob(image?: true)
-
- expect(blob.to_partial_path).to eq 'image'
+ expect(blob.to_partial_path(project)).to eq 'image'
end
it 'handles text' do
blob = stubbed_blob(text?: true)
-
- expect(blob.to_partial_path).to eq 'text'
+ expect(blob.to_partial_path(project)).to eq 'text'
end
it 'defaults to download' do
blob = stubbed_blob
-
- expect(blob.to_partial_path).to eq 'download'
+ expect(blob.to_partial_path(project)).to eq 'download'
end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 9962c987110..4a664e4fae2 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1018,6 +1018,19 @@ describe Ci::Pipeline, models: true do
end
end
+ describe '#update_status' do
+ let(:pipeline) { create(:ci_pipeline, sha: '123456') }
+
+ it 'updates the cached status' do
+ fake_status = double
+ # after updating the status, the status is set to `skipped` for this pipeline's builds
+ expect(Ci::PipelineStatus).to receive(:new).with(pipeline.project, sha: '123456', status: 'skipped').and_return(fake_status)
+ expect(fake_status).to receive(:store_in_cache_if_needed)
+
+ pipeline.update_status
+ end
+ end
+
describe 'notifications when pipeline success or failed' do
let(:project) { create(:project, :repository) }
diff --git a/spec/models/ci/pipeline_status_spec.rb b/spec/models/ci/pipeline_status_spec.rb
new file mode 100644
index 00000000000..bc5b71666c2
--- /dev/null
+++ b/spec/models/ci/pipeline_status_spec.rb
@@ -0,0 +1,173 @@
+require 'spec_helper'
+
+describe Ci::PipelineStatus do
+ let(:project) { create(:project) }
+ let(:pipeline_status) { described_class.new(project) }
+
+ describe '.load_for_project' do
+ it "loads the status" do
+ expect_any_instance_of(described_class).to receive(:load_status)
+
+ described_class.load_for_project(project)
+ end
+ end
+
+ describe '#has_status?' do
+ it "is false when the status wasn't loaded yet" do
+ expect(pipeline_status.has_status?).to be_falsy
+ end
+
+ it 'is true when all status information was loaded' do
+ fake_commit = double
+ allow(fake_commit).to receive(:status).and_return('failed')
+ allow(fake_commit).to receive(:sha).and_return('failed424d1b73bc0d3cb726eb7dc4ce17a4d48552f8c6')
+ allow(pipeline_status).to receive(:commit).and_return(fake_commit)
+ allow(pipeline_status).to receive(:has_cache?).and_return(false)
+
+ pipeline_status.load_status
+
+ expect(pipeline_status.has_status?).to be_truthy
+ end
+ end
+
+ describe '#load_status' do
+ it 'loads the status from the cache when there is one' do
+ expect(pipeline_status).to receive(:has_cache?).and_return(true)
+ expect(pipeline_status).to receive(:load_from_cache)
+
+ pipeline_status.load_status
+ end
+
+ it 'loads the status from the project commit when there is no cache' do
+ allow(pipeline_status).to receive(:has_cache?).and_return(false)
+
+ expect(pipeline_status).to receive(:load_from_commit)
+
+ pipeline_status.load_status
+ end
+
+ it 'stores the status in the cache when it loading it from the project' do
+ allow(pipeline_status).to receive(:has_cache?).and_return(false)
+ allow(pipeline_status).to receive(:load_from_commit)
+
+ expect(pipeline_status).to receive(:store_in_cache)
+
+ pipeline_status.load_status
+ end
+
+ it 'sets the state to loaded' do
+ pipeline_status.load_status
+
+ expect(pipeline_status).to be_loaded
+ end
+
+ it 'only loads the status once' do
+ expect(pipeline_status).to receive(:has_cache?).and_return(true).exactly(1)
+ expect(pipeline_status).to receive(:load_from_cache).exactly(1)
+
+ pipeline_status.load_status
+ pipeline_status.load_status
+ end
+ end
+
+ describe "#load_from_commit" do
+ let!(:pipeline) { create(:ci_pipeline, :success, project: project, sha: project.commit.sha) }
+
+ it 'reads the status from the pipeline for the commit' do
+ pipeline_status.load_from_commit
+
+ expect(pipeline_status.status).to eq('success')
+ expect(pipeline_status.sha).to eq(project.commit.sha)
+ end
+
+ it "doesn't fail for an empty project" do
+ status_for_empty_commit = described_class.new(create(:empty_project))
+
+ status_for_empty_commit.load_status
+
+ expect(status_for_empty_commit).to be_loaded
+ end
+ end
+
+ describe "#store_in_cache", :redis do
+ it "sets the object in redis" do
+ pipeline_status.sha = '123456'
+ pipeline_status.status = 'failed'
+
+ pipeline_status.store_in_cache
+ read_sha, read_status = Gitlab::Redis.with { |redis| redis.hmget("projects/#{project.id}/build_status", :sha, :status) }
+
+ expect(read_sha).to eq('123456')
+ expect(read_status).to eq('failed')
+ end
+ end
+
+ describe '#store_in_cache_if_needed', :redis do
+ it 'stores the state in the cache when the sha is the HEAD of the project' do
+ create(:ci_pipeline, :success, project: project, sha: project.commit.sha)
+ build_status = described_class.load_for_project(project)
+
+ build_status.store_in_cache_if_needed
+ sha, status = Gitlab::Redis.with { |redis| redis.hmget("projects/#{project.id}/build_status", :sha, :status) }
+
+ expect(sha).not_to be_nil
+ expect(status).not_to be_nil
+ end
+
+ it "doesn't store the status in redis when the sha is not the head of the project" do
+ other_status = described_class.new(project, sha: "123456", status: "failed")
+
+ other_status.store_in_cache_if_needed
+ sha, status = Gitlab::Redis.with { |redis| redis.hmget("projects/#{project.id}/build_status", :sha, :status) }
+
+ expect(sha).to be_nil
+ expect(status).to be_nil
+ end
+
+ it "deletes the cache if the repository doesn't have a head commit" do
+ empty_project = create(:empty_project)
+ Gitlab::Redis.with { |redis| redis.mapped_hmset("projects/#{empty_project.id}/build_status", { sha: "sha", status: "pending" }) }
+ other_status = described_class.new(empty_project, sha: "123456", status: "failed")
+
+ other_status.store_in_cache_if_needed
+ sha, status = Gitlab::Redis.with { |redis| redis.hmget("projects/#{empty_project.id}/build_status", :sha, :status) }
+
+ expect(sha).to be_nil
+ expect(status).to be_nil
+ end
+ end
+
+ describe "with a status in redis", :redis do
+ let(:status) { 'success' }
+ let(:sha) { '424d1b73bc0d3cb726eb7dc4ce17a4d48552f8c6' }
+
+ before do
+ Gitlab::Redis.with { |redis| redis.mapped_hmset("projects/#{project.id}/build_status", { sha: sha, status: status }) }
+ end
+
+ describe '#load_from_cache' do
+ it 'reads the status from redis' do
+ pipeline_status.load_from_cache
+
+ expect(pipeline_status.sha).to eq(sha)
+ expect(pipeline_status.status).to eq(status)
+ end
+ end
+
+ describe '#has_cache?' do
+ it 'knows the status is cached' do
+ expect(pipeline_status.has_cache?).to be_truthy
+ end
+ end
+
+ describe '#delete_from_cache' do
+ it 'deletes values from redis' do
+ pipeline_status.delete_from_cache
+
+ key_exists = Gitlab::Redis.with { |redis| redis.exists("projects/#{project.id}/build_status") }
+
+ expect(key_exists).to be_falsy
+ end
+ end
+ end
+end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 32f9366a14c..4b449546a30 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -212,6 +212,25 @@ eos
end
end
+ describe '#latest_pipeline' do
+ let!(:first_pipeline) do
+ create(:ci_empty_pipeline,
+ project: project,
+ sha: commit.sha,
+ status: 'success')
+ end
+ let!(:second_pipeline) do
+ create(:ci_empty_pipeline,
+ project: project,
+ sha: commit.sha,
+ status: 'success')
+ end
+
+ it 'returns latest pipeline' do
+ expect(commit.latest_pipeline).to eq second_pipeline
+ end
+ end
+
describe '#status' do
context 'without ref argument' do
before do
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 545a11912e3..31ae0dce140 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -344,6 +344,46 @@ describe Issue, "Issuable" do
end
end
+ describe '.order_due_date_and_labels_priority' do
+ let(:project) { create(:empty_project) }
+
+ def create_issue(milestone, labels)
+ create(:labeled_issue, milestone: milestone, labels: labels, project: project)
+ end
+
+ it 'sorts issues in order of milestone due date, then label priority' do
+ first_priority = create(:label, project: project, priority: 1)
+ second_priority = create(:label, project: project, priority: 2)
+ no_priority = create(:label, project: project)
+
+ first_milestone = create(:milestone, project: project, due_date: Time.now)
+ second_milestone = create(:milestone, project: project, due_date: Time.now + 1.month)
+ third_milestone = create(:milestone, project: project)
+
+ # The issues here are ordered by label priority, to ensure that we don't
+ # accidentally just sort by creation date.
+ second_milestone_first_priority = create_issue(second_milestone, [first_priority, second_priority, no_priority])
+ third_milestone_first_priority = create_issue(third_milestone, [first_priority, second_priority, no_priority])
+ first_milestone_second_priority = create_issue(first_milestone, [second_priority, no_priority])
+ second_milestone_second_priority = create_issue(second_milestone, [second_priority, no_priority])
+ no_milestone_second_priority = create_issue(nil, [second_priority, no_priority])
+ first_milestone_no_priority = create_issue(first_milestone, [no_priority])
+ second_milestone_no_labels = create_issue(second_milestone, [])
+ third_milestone_no_priority = create_issue(third_milestone, [no_priority])
+
+ result = Issue.order_due_date_and_labels_priority
+
+ expect(result).to eq([first_milestone_second_priority,
+ first_milestone_no_priority,
+ second_milestone_first_priority,
+ second_milestone_second_priority,
+ second_milestone_no_labels,
+ third_milestone_first_priority,
+ no_milestone_second_priority,
+ third_milestone_no_priority])
+ end
+ end
+
describe '.order_labels_priority' do
let(:label_1) { create(:label, title: 'label_1', project: issue.project, priority: 1) }
let(:label_2) { create(:label, title: 'label_2', project: issue.project, priority: 2) }
diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb
index ad703a6c8bb..68e4c0a522b 100644
--- a/spec/models/concerns/milestoneish_spec.rb
+++ b/spec/models/concerns/milestoneish_spec.rb
@@ -116,21 +116,41 @@ describe Milestone, 'Milestoneish' do
end
end
+ describe '#remaining_days' do
+ it 'shows 0 if no due date' do
+ milestone = build_stubbed(:milestone)
+
+ expect(milestone.remaining_days).to eq(0)
+ end
+
+ it 'shows 0 if expired' do
+ milestone = build_stubbed(:milestone, due_date: 2.days.ago)
+
+ expect(milestone.remaining_days).to eq(0)
+ end
+
+ it 'shows correct remaining days' do
+ milestone = build_stubbed(:milestone, due_date: 2.days.from_now)
+
+ expect(milestone.remaining_days).to eq(2)
+ end
+ end
+
describe '#elapsed_days' do
it 'shows 0 if no start_date set' do
- milestone = build(:milestone)
+ milestone = build_stubbed(:milestone)
expect(milestone.elapsed_days).to eq(0)
end
it 'shows 0 if start_date is a future' do
- milestone = build(:milestone, start_date: Time.now + 2.days)
+ milestone = build_stubbed(:milestone, start_date: Time.now + 2.days)
expect(milestone.elapsed_days).to eq(0)
end
it 'shows correct amount of days' do
- milestone = build(:milestone, start_date: Time.now - 2.days)
+ milestone = build_stubbed(:milestone, start_date: Time.now - 2.days)
expect(milestone.elapsed_days).to eq(2)
end
diff --git a/spec/models/concerns/relative_positioning_spec.rb b/spec/models/concerns/relative_positioning_spec.rb
index 69906382545..255b584a85e 100644
--- a/spec/models/concerns/relative_positioning_spec.rb
+++ b/spec/models/concerns/relative_positioning_spec.rb
@@ -12,12 +12,6 @@ describe Issue, 'RelativePositioning' do
end
end
- describe '#min_relative_position' do
- it 'returns maximum position' do
- expect(issue.min_relative_position).to eq issue.relative_position
- end
- end
-
describe '#max_relative_position' do
it 'returns maximum position' do
expect(issue.max_relative_position).to eq issue1.relative_position
@@ -29,8 +23,8 @@ describe Issue, 'RelativePositioning' do
expect(issue1.prev_relative_position).to eq issue.relative_position
end
- it 'returns minimum position if there is no issue above' do
- expect(issue.prev_relative_position).to eq RelativePositioning::MIN_POSITION
+ it 'returns nil if there is no issue above' do
+ expect(issue.prev_relative_position).to eq nil
end
end
@@ -39,8 +33,8 @@ describe Issue, 'RelativePositioning' do
expect(issue.next_relative_position).to eq issue1.relative_position
end
- it 'returns next position if there is no issue below' do
- expect(issue1.next_relative_position).to eq RelativePositioning::MAX_POSITION
+ it 'returns nil if there is no issue below' do
+ expect(issue1.next_relative_position).to eq nil
end
end
@@ -72,6 +66,34 @@ describe Issue, 'RelativePositioning' do
end
end
+ describe '#shift_after?' do
+ it 'returns true' do
+ issue.update(relative_position: issue1.relative_position - 1)
+
+ expect(issue.shift_after?).to be_truthy
+ end
+
+ it 'returns false' do
+ issue.update(relative_position: issue1.relative_position - 2)
+
+ expect(issue.shift_after?).to be_falsey
+ end
+ end
+
+ describe '#shift_before?' do
+ it 'returns true' do
+ issue.update(relative_position: issue1.relative_position + 1)
+
+ expect(issue.shift_before?).to be_truthy
+ end
+
+ it 'returns false' do
+ issue.update(relative_position: issue1.relative_position + 2)
+
+ expect(issue.shift_before?).to be_falsey
+ end
+ end
+
describe '#move_between' do
it 'positions issue between two other' do
new_issue.move_between(issue, issue1)
@@ -100,5 +122,83 @@ describe Issue, 'RelativePositioning' do
expect(new_issue.relative_position).to be > issue.relative_position
expect(issue.relative_position).to be < issue1.relative_position
end
+
+ it 'positions issues between other two if distance is 1' do
+ issue1.update relative_position: issue.relative_position + 1
+
+ new_issue.move_between(issue, issue1)
+
+ expect(new_issue.relative_position).to be > issue.relative_position
+ expect(issue.relative_position).to be < issue1.relative_position
+ end
+
+ it 'positions issue in the middle of other two if distance is big enough' do
+ issue.update relative_position: 6000
+ issue1.update relative_position: 10000
+
+ new_issue.move_between(issue, issue1)
+
+ expect(new_issue.relative_position).to eq(8000)
+ end
+
+ it 'positions issue closer to the middle if we are at the very top' do
+ issue1.update relative_position: 6000
+
+ new_issue.move_between(nil, issue1)
+
+ expect(new_issue.relative_position).to eq(6000 - RelativePositioning::IDEAL_DISTANCE)
+ end
+
+ it 'positions issue closer to the middle if we are at the very bottom' do
+ issue.update relative_position: 6000
+ issue1.update relative_position: nil
+
+ new_issue.move_between(issue, nil)
+
+ expect(new_issue.relative_position).to eq(6000 + RelativePositioning::IDEAL_DISTANCE)
+ end
+
+ it 'positions issue in the middle of other two if distance is not big enough' do
+ issue.update relative_position: 100
+ issue1.update relative_position: 400
+
+ new_issue.move_between(issue, issue1)
+
+ expect(new_issue.relative_position).to eq(250)
+ end
+
+ it 'positions issue in the middle of other two is there is no place' do
+ issue.update relative_position: 100
+ issue1.update relative_position: 101
+
+ new_issue.move_between(issue, issue1)
+
+ expect(new_issue.relative_position).to be_between(issue.relative_position, issue1.relative_position)
+ end
+
+ it 'uses rebalancing if there is no place' do
+ issue.update relative_position: 100
+ issue1.update relative_position: 101
+ issue2 = create(:issue, relative_position: 102, project: project)
+ new_issue.update relative_position: 103
+
+ new_issue.move_between(issue1, issue2)
+ new_issue.save!
+
+ expect(new_issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
+ expect(issue.reload.relative_position).not_to eq(100)
+ end
+
+ it 'positions issue right if we pass none-sequential parameters' do
+ issue.update relative_position: 99
+ issue1.update relative_position: 101
+ issue2 = create(:issue, relative_position: 102, project: project)
+ new_issue.update relative_position: 103
+
+ new_issue.move_between(issue, issue2)
+ new_issue.save!
+
+ expect(new_issue.relative_position).to be(100)
+ end
end
end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index b4305e92812..9f0e7fbbe26 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -239,7 +239,7 @@ describe Environment, models: true do
describe '#actions_for' do
let(:deployment) { create(:deployment, environment: environment) }
let(:pipeline) { deployment.deployable.pipeline }
- let!(:review_action) { create(:ci_build, :manual, name: 'review-apps', pipeline: pipeline, environment: 'review/$CI_BUILD_REF_NAME' )}
+ let!(:review_action) { create(:ci_build, :manual, name: 'review-apps', pipeline: pipeline, environment: 'review/$CI_COMMIT_REF_NAME' )}
let!(:production_action) { create(:ci_build, :manual, name: 'production', pipeline: pipeline, environment: 'production' )}
it 'returns a list of actions with matching environment' do
diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb
index cacbab8bcb1..55b87d1c48a 100644
--- a/spec/models/global_milestone_spec.rb
+++ b/spec/models/global_milestone_spec.rb
@@ -92,6 +92,41 @@ describe GlobalMilestone, models: true do
end
end
+ describe '.states_count' do
+ context 'when the projects have milestones' do
+ before do
+ create(:closed_milestone, title: 'Active Group Milestone', project: project3)
+ create(:active_milestone, title: 'Active Group Milestone', project: project1)
+ create(:active_milestone, title: 'Active Group Milestone', project: project2)
+ create(:closed_milestone, title: 'Closed Group Milestone', project: project1)
+ create(:closed_milestone, title: 'Closed Group Milestone', project: project2)
+ create(:closed_milestone, title: 'Closed Group Milestone', project: project3)
+ end
+
+ it 'returns the quantity of global milestones in each possible state' do
+ expected_count = { opened: 1, closed: 2, all: 2 }
+
+ count = GlobalMilestone.states_count(Project.all)
+
+ expect(count).to eq(expected_count)
+ end
+ end
+
+ context 'when the projects do not have milestones' do
+ before do
+ project1
+ end
+
+ it 'returns 0 as the quantity of global milestones in each state' do
+ expected_count = { opened: 0, closed: 0, all: 0 }
+
+ count = GlobalMilestone.states_count(Project.all)
+
+ expect(count).to eq(expected_count)
+ end
+ end
+ end
+
describe '#initialize' do
let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) }
let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) }
@@ -127,4 +162,32 @@ describe GlobalMilestone, models: true do
expect(global_milestone.safe_title).to eq('git-test')
end
end
+
+ describe '#state' do
+ context 'when at least one milestone is active' do
+ it 'returns active' do
+ title = 'Active Group Milestone'
+ milestones = [
+ create(:active_milestone, title: title),
+ create(:closed_milestone, title: title)
+ ]
+ global_milestone = GlobalMilestone.new(title, milestones)
+
+ expect(global_milestone.state).to eq('active')
+ end
+ end
+
+ context 'when all milestones are closed' do
+ it 'returns closed' do
+ title = 'Closed Group Milestone'
+ milestones = [
+ create(:closed_milestone, title: title),
+ create(:closed_milestone, title: title)
+ ]
+ global_milestone = GlobalMilestone.new(title, milestones)
+
+ expect(global_milestone.state).to eq('closed')
+ end
+ end
+ end
end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index bba9058f394..9ffcb88bafd 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -22,6 +22,21 @@ describe Issue, models: true do
it { is_expected.to have_db_index(:deleted_at) }
end
+ describe '#order_by_position_and_priority' do
+ let(:project) { create :empty_project }
+ let(:p1) { create(:label, title: 'P1', project: project, priority: 1) }
+ let(:p2) { create(:label, title: 'P2', project: project, priority: 2) }
+ let!(:issue1) { create(:labeled_issue, project: project, labels: [p1]) }
+ let!(:issue2) { create(:labeled_issue, project: project, labels: [p2]) }
+ let!(:issue3) { create(:issue, project: project, relative_position: 100) }
+ let!(:issue4) { create(:issue, project: project, relative_position: 200) }
+
+ it 'returns ordered list' do
+ expect(project.issues.order_by_position_and_priority).
+ to match [issue3, issue4, issue1, issue2]
+ end
+ end
+
describe '#to_reference' do
let(:namespace) { build(:namespace, path: 'sample-namespace') }
let(:project) { build(:empty_project, name: 'sample-project', namespace: namespace) }
@@ -620,4 +635,15 @@ describe Issue, models: true do
end
end
end
+
+ describe '#hook_attrs' do
+ let(:attrs_hash) { subject.hook_attrs }
+
+ it 'includes time tracking attrs' do
+ expect(attrs_hash).to include(:total_time_spent)
+ expect(attrs_hash).to include(:human_time_estimate)
+ expect(attrs_hash).to include(:human_total_time_spent)
+ expect(attrs_hash).to include('time_estimate')
+ end
+ end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index fcaf4c71182..24e7c1b17d9 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -542,7 +542,7 @@ describe MergeRequest, models: true do
end
describe "#hook_attrs" do
- let(:attrs_hash) { subject.hook_attrs.to_h }
+ let(:attrs_hash) { subject.hook_attrs }
[:source, :target].each do |key|
describe "#{key} key" do
@@ -558,6 +558,10 @@ describe MergeRequest, models: true do
expect(attrs_hash).to include(:target)
expect(attrs_hash).to include(:last_commit)
expect(attrs_hash).to include(:work_in_progress)
+ expect(attrs_hash).to include(:total_time_spent)
+ expect(attrs_hash).to include(:human_time_estimate)
+ expect(attrs_hash).to include(:human_total_time_spent)
+ expect(attrs_hash).to include('time_estimate')
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index e120e21af06..ff1defcd32d 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1916,4 +1916,15 @@ describe Project, models: true do
end
end
end
+
+ describe '#pipeline_status' do
+ let(:project) { create(:project) }
+ it 'builds a pipeline status' do
+ expect(project.pipeline_status).to be_a(Ci::PipelineStatus)
+ end
+
+ it 'hase a loaded pipeline status' do
+ expect(project.pipeline_status).to be_loaded
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index adb5b538922..9da4140f3ce 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -210,22 +210,6 @@ describe User, models: true do
end
end
end
-
- describe 'ghost users' do
- it 'does not allow a non-blocked ghost user' do
- user = build(:user, :ghost)
- user.state = 'active'
-
- expect(user).to be_invalid
- end
-
- it 'allows a blocked ghost user' do
- user = build(:user, :ghost)
- user.state = 'blocked'
-
- expect(user).to be_valid
- end
- end
end
describe "scopes" do
@@ -713,8 +697,9 @@ describe User, models: true do
describe '.search_with_secondary_emails' do
delegate :search_with_secondary_emails, to: :described_class
- let!(:user) { create(:user) }
- let!(:email) { create(:email) }
+ let!(:user) { create(:user, name: 'John Doe', username: 'john.doe', email: 'john.doe@example.com' ) }
+ let!(:another_user) { create(:user, name: 'Albert Smith', username: 'albert.smith', email: 'albert.smith@example.com' ) }
+ let!(:email) { create(:email, user: another_user) }
it 'returns users with a matching name' do
expect(search_with_secondary_emails(user.name)).to eq([user])
diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb
index 63acc0b68cd..02acdcb36df 100644
--- a/spec/policies/base_policy_spec.rb
+++ b/spec/policies/base_policy_spec.rb
@@ -1,17 +1,19 @@
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)
+ expect(described_class.class_for(GenericCommitStatus.new)).to eq(CommitStatusPolicy)
end
it 'detects policy class for a presented subject' do
- presentee = Ci::BuildPresenter.new(build)
+ presentee = Ci::BuildPresenter.new(Ci::Build.new)
expect(described_class.class_for(presentee)).to eq(Ci::BuildPolicy)
end
+
+ it 'uses GlobalPolicy when :global is given' do
+ expect(described_class.class_for(:global)).to eq(GlobalPolicy)
+ end
end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 585449e62b6..a10d876ffad 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -178,7 +178,7 @@ describe API::Commits, api: true do
end
end
- describe "Create a commit with multiple files and actions" do
+ describe "POST /projects/:id/repository/commits" do
let!(:url) { "/projects/#{project.id}/repository/commits" }
it 'returns a 403 unauthorized for user without permissions' do
@@ -193,7 +193,7 @@ describe API::Commits, api: true do
expect(response).to have_http_status(400)
end
- context :create do
+ describe 'create' do
let(:message) { 'Created file' }
let!(:invalid_c_params) do
{
@@ -237,8 +237,8 @@ describe API::Commits, api: true do
expect(response).to have_http_status(400)
end
- context 'with project path in URL' do
- let(:url) { "/projects/#{project.full_path.gsub('/', '%2F')}/repository/commits" }
+ context 'with project path containing a dot in URL' do
+ let(:url) { "/projects/#{CGI.escape(project.full_path)}/repository/commits" }
it 'a new file in project repo' do
post api(url, user), valid_c_params
@@ -248,7 +248,7 @@ describe API::Commits, api: true do
end
end
- context :delete do
+ describe 'delete' do
let(:message) { 'Deleted file' }
let!(:invalid_d_params) do
{
@@ -289,7 +289,7 @@ describe API::Commits, api: true do
end
end
- context :move do
+ describe 'move' do
let(:message) { 'Moved file' }
let!(:invalid_m_params) do
{
@@ -334,7 +334,7 @@ describe API::Commits, api: true do
end
end
- context :update do
+ describe 'update' do
let(:message) { 'Updated file' }
let!(:invalid_u_params) do
{
@@ -377,7 +377,7 @@ describe API::Commits, api: true do
end
end
- context "multiple operations" do
+ describe 'multiple operations' do
let(:message) { 'Multiple actions' }
let!(:invalid_mo_params) do
{
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index a89676fec93..988a57a80ea 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -436,7 +436,7 @@ describe API::Helpers, api: true do
context 'current_user is present' do
before do
- expect_any_instance_of(self.class).to receive(:current_user).and_return(true)
+ expect_any_instance_of(self.class).to receive(:current_user).at_least(:once).and_return(User.new)
end
it 'does not raise an error' do
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 2fc11a3b782..e7738ca3034 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -153,6 +153,16 @@ describe API::Issues, api: true do
expect(json_response.first['state']).to eq('opened')
end
+ it 'returns unlabeled issues for "No Label" label' do
+ get api("/issues", user), labels: 'No Label'
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['labels']).to be_empty
+ end
+
it 'returns an empty array if no issue matches labels and state filters' do
get api("/issues?labels=#{label.title}&state=closed", user)
@@ -928,29 +938,34 @@ describe API::Issues, api: true do
])
end
- context 'resolving issues in a merge request' do
+ context 'resolving discussions' do
let(:discussion) { Discussion.for_diff_notes([create(:diff_note_on_merge_request)]).first }
let(:merge_request) { discussion.noteable }
let(:project) { merge_request.source_project }
+
before do
project.team << [user, :master]
- post api("/projects/#{project.id}/issues", user),
- title: 'New Issue',
- merge_request_for_resolving_discussions: merge_request.iid
- end
-
- it 'creates a new project issue' do
- expect(response).to have_http_status(:created)
end
- it 'resolves the discussions in a merge request' do
- discussion.first_note.reload
+ context 'resolving all discussions in a merge request' do
+ before do
+ post api("/projects/#{project.id}/issues", user),
+ title: 'New Issue',
+ merge_request_to_resolve_discussions_of: merge_request.iid
+ end
- expect(discussion.resolved?).to be(true)
+ it_behaves_like 'creating an issue resolving discussions through the API'
end
- it 'assigns a description to the issue mentioning the merge request' do
- expect(json_response['description']).to include(merge_request.to_reference)
+ context 'resolving a single discussion' do
+ before do
+ post api("/projects/#{project.id}/issues", user),
+ title: 'New Issue',
+ merge_request_to_resolve_discussions_of: merge_request.iid,
+ discussion_to_resolve: discussion.id
+ end
+
+ it_behaves_like 'creating an issue resolving discussions through the API'
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index b4b23617498..c481b7e72b1 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
require 'spec_helper'
-describe API::Projects, api: true do
- include ApiHelpers
+describe API::Projects, :api do
include Gitlab::CurrentSettings
+
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:user3) { create(:user) }
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 15d458e0795..442b2df1952 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -39,6 +39,7 @@ describe API::Runner do
expect(json_response['id']).to eq(runner.id)
expect(json_response['token']).to eq(runner.token)
expect(runner.run_untagged).to be true
+ expect(runner.token).not_to eq(registration_token)
end
context 'when project token is used' do
@@ -49,6 +50,8 @@ describe API::Runner do
expect(response).to have_http_status 201
expect(project.runners.size).to eq(1)
+ expect(Ci::Runner.first.token).not_to eq(registration_token)
+ expect(Ci::Runner.first.token).not_to eq(project.runners_token)
end
end
end
@@ -309,8 +312,8 @@ describe API::Runner do
end
let(:expected_variables) do
- [{ 'key' => 'CI_BUILD_NAME', 'value' => 'spinach', 'public' => true },
- { 'key' => 'CI_BUILD_STAGE', 'value' => 'test', 'public' => true },
+ [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true },
+ { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true },
{ 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true }]
end
@@ -434,9 +437,9 @@ describe API::Runner do
context 'when triggered job is available' do
let(:expected_variables) do
- [{ 'key' => 'CI_BUILD_NAME', 'value' => 'spinach', 'public' => true },
- { 'key' => 'CI_BUILD_STAGE', 'value' => 'test', 'public' => true },
- { 'key' => 'CI_BUILD_TRIGGERED', 'value' => 'true', 'public' => true },
+ [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true },
+ { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true },
+ { 'key' => 'CI_PIPELINE_TRIGGERED', 'value' => 'true', 'public' => true },
{ 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true },
{ 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false },
{ 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false }]
diff --git a/spec/requests/api/v3/commits_spec.rb b/spec/requests/api/v3/commits_spec.rb
index e298ef055e1..adba3a787aa 100644
--- a/spec/requests/api/v3/commits_spec.rb
+++ b/spec/requests/api/v3/commits_spec.rb
@@ -88,7 +88,7 @@ describe API::V3::Commits, api: true do
end
end
- describe "Create a commit with multiple files and actions" do
+ describe "POST /projects/:id/repository/commits" do
let!(:url) { "/projects/#{project.id}/repository/commits" }
it 'returns a 403 unauthorized for user without permissions' do
@@ -103,7 +103,7 @@ describe API::V3::Commits, api: true do
expect(response).to have_http_status(400)
end
- context :create do
+ describe 'create' do
let(:message) { 'Created file' }
let!(:invalid_c_params) do
{
@@ -147,8 +147,9 @@ describe API::V3::Commits, api: true do
expect(response).to have_http_status(400)
end
- context 'with project path in URL' do
- let(:url) { "/projects/#{project.full_path.gsub('/', '%2F')}/repository/commits" }
+ context 'with project path containing a dot in URL' do
+ let!(:user) { create(:user, username: 'foo.bar') }
+ let(:url) { "/projects/#{CGI.escape(project.full_path)}/repository/commits" }
it 'a new file in project repo' do
post v3_api(url, user), valid_c_params
@@ -158,7 +159,7 @@ describe API::V3::Commits, api: true do
end
end
- context :delete do
+ describe 'delete' do
let(:message) { 'Deleted file' }
let!(:invalid_d_params) do
{
@@ -199,7 +200,7 @@ describe API::V3::Commits, api: true do
end
end
- context :move do
+ describe 'move' do
let(:message) { 'Moved file' }
let!(:invalid_m_params) do
{
@@ -244,7 +245,7 @@ describe API::V3::Commits, api: true do
end
end
- context :update do
+ describe 'update' do
let(:message) { 'Updated file' }
let!(:invalid_u_params) do
{
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 9948d1a9ea0..c879f37f50d 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -81,8 +81,8 @@ describe Ci::API::Builds do
expect(runner.reload.platform).to eq("darwin")
expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] })
expect(json_response["variables"]).to include(
- { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
- { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
+ { "key" => "CI_JOB_NAME", "value" => "spinach", "public" => true },
+ { "key" => "CI_JOB_STAGE", "value" => "test", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true }
)
end
@@ -182,9 +182,9 @@ describe Ci::API::Builds do
expect(response).to have_http_status(201)
expect(json_response["variables"]).to include(
- { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
- { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
- { "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true },
+ { "key" => "CI_JOB_NAME", "value" => "spinach", "public" => true },
+ { "key" => "CI_JOB_STAGE", "value" => "test", "public" => true },
+ { "key" => "CI_PIPELINE_TRIGGERED", "value" => "true", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true },
{ "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
{ "key" => "TRIGGER_KEY_1", "value" => "TRIGGER_VALUE_1", "public" => false },
diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb
index 8719313783e..d50cdfdc2d6 100644
--- a/spec/requests/ci/api/runners_spec.rb
+++ b/spec/requests/ci/api/runners_spec.rb
@@ -18,6 +18,7 @@ describe Ci::API::Runners do
it 'creates runner with default values' do
expect(response).to have_http_status 201
expect(Ci::Runner.first.run_untagged).to be true
+ expect(Ci::Runner.first.token).not_to eq(registration_token)
end
end
@@ -74,6 +75,8 @@ describe Ci::API::Runners do
it 'creates runner' do
expect(response).to have_http_status 201
expect(project.runners.size).to eq(1)
+ expect(Ci::Runner.first.token).not_to eq(registration_token)
+ expect(Ci::Runner.first.token).not_to eq(project.runners_token)
end
end
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index 01baedc4761..22115c6566d 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -43,6 +43,32 @@ describe Boards::Issues::ListService, services: true do
described_class.new(project, user, params).execute
end
+ context 'issues are ordered by priority' do
+ it 'returns opened issues when list_id is missing' do
+ params = { board_id: board.id }
+
+ issues = described_class.new(project, user, params).execute
+
+ expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1]
+ end
+
+ it 'returns closed issues when listing issues from Done' do
+ params = { board_id: board.id, id: done.id }
+
+ issues = described_class.new(project, user, params).execute
+
+ expect(issues).to eq [closed_issue4, closed_issue2, closed_issue3, closed_issue1]
+ end
+
+ it 'returns opened issues that have label list applied when listing issues from a label list' do
+ params = { board_id: board.id, id: list1.id }
+
+ issues = described_class.new(project, user, params).execute
+
+ expect(issues).to eq [list1_issue3, list1_issue1, list1_issue2]
+ end
+ end
+
context 'with list that does not belong to the board' do
it 'raises an error' do
list = create(:list)
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 8459a3d8cfb..a969829a63e 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -200,7 +200,7 @@ describe Ci::CreatePipelineService, services: true do
context 'with environment' do
before do
- config = YAML.dump(deploy: { environment: { name: "review/$CI_BUILD_REF_NAME" }, script: 'ls' })
+ config = YAML.dump(deploy: { environment: { name: "review/$CI_COMMIT_REF_NAME" }, script: 'ls' })
stub_ci_pipeline_yaml_file(config)
end
diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb
index 18b964e2453..a883705bd45 100644
--- a/spec/services/create_deployment_service_spec.rb
+++ b/spec/services/create_deployment_service_spec.rb
@@ -104,16 +104,16 @@ describe CreateDeploymentService, services: true do
context 'when variables are used' do
let(:params) do
{
- environment: 'review-apps/$CI_BUILD_REF_NAME',
+ environment: 'review-apps/$CI_COMMIT_REF_NAME',
ref: 'master',
tag: false,
sha: '97de212e80737a608d939f648d959671fb0a0142',
options: {
- name: 'review-apps/$CI_BUILD_REF_NAME',
- url: 'http://$CI_BUILD_REF_NAME.review-apps.gitlab.com'
+ name: 'review-apps/$CI_COMMIT_REF_NAME',
+ url: 'http://$CI_COMMIT_REF_NAME.review-apps.gitlab.com'
},
variables: [
- { key: 'CI_BUILD_REF_NAME', value: 'feature-review-apps' }
+ { key: 'CI_COMMIT_REF_NAME', value: 'feature-review-apps' }
]
}
end
diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb
index 09807e5d35b..1dd53236fbd 100644
--- a/spec/services/issues/build_service_spec.rb
+++ b/spec/services/issues/build_service_spec.rb
@@ -8,24 +8,34 @@ describe Issues::BuildService, services: true do
project.team << [user, :developer]
end
+ context 'for a single discussion' do
+ describe '#execute' do
+ let(:merge_request) { create(:merge_request, title: "Hello world", source_project: project) }
+ let(:discussion) { Discussion.new([create(:diff_note_on_merge_request, project: project, noteable: merge_request, note: "Almost done")]) }
+ let(:service) { described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id) }
+
+ it 'references the noteable title in the issue title' do
+ issue = service.execute
+
+ expect(issue.title).to include('Hello world')
+ end
+
+ it 'adds the note content to the description' do
+ issue = service.execute
+
+ expect(issue.description).to include('Almost done')
+ end
+ end
+ end
+
context 'for discussions in a merge request' do
let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project) }
- let(:issue) { described_class.new(project, user, merge_request_for_resolving_discussions: merge_request).execute }
-
- def position_on_line(line_number)
- Gitlab::Diff::Position.new(
- old_path: "files/ruby/popen.rb",
- new_path: "files/ruby/popen.rb",
- old_line: nil,
- new_line: line_number,
- diff_refs: merge_request.diff_refs
- )
- end
+ let(:issue) { described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid).execute }
describe '#items_for_discussions' do
it 'has an item for each discussion' do
- create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.source_project, position: position_on_line(13))
- service = described_class.new(project, user, merge_request_for_resolving_discussions: merge_request)
+ create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.source_project, line_number: 13)
+ service = described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid)
service.execute
@@ -34,7 +44,7 @@ describe Issues::BuildService, services: true do
end
describe '#item_for_discussion' do
- let(:service) { described_class.new(project, user, merge_request_for_resolving_discussions: merge_request) }
+ let(:service) { described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid) }
it 'mentions the author of the note' do
discussion = Discussion.new([create(:diff_note_on_merge_request, author: create(:user, username: 'author'))])
@@ -47,11 +57,11 @@ describe Issues::BuildService, services: true do
"with a blockquote\n"\
"> That has a quote\n"\
">>>\n"
- note_result = "This is a string\n"\
- "> with a blockquote\n"\
- "> > That has a quote\n"
+ note_result = " > This is a string\n"\
+ " > > with a blockquote\n"\
+ " > > > That has a quote\n"
discussion = Discussion.new([create(:diff_note_on_merge_request, note: note_text)])
- expect(service.item_for_discussion(discussion)).to include(">>>\n#{note_result}\n>>>")
+ expect(service.item_for_discussion(discussion)).to include(note_result)
end
end
@@ -66,7 +76,7 @@ describe Issues::BuildService, services: true do
it 'does not assign title when a title was given' do
issue = described_class.new(project, user,
- merge_request_for_resolving_discussions: merge_request,
+ merge_request_to_resolve_discussions_of: merge_request,
title: 'What an issue').execute
expect(issue.title).to eq('What an issue')
@@ -74,7 +84,7 @@ describe Issues::BuildService, services: true do
it 'does not assign description when a description was given' do
issue = described_class.new(project, user,
- merge_request_for_resolving_discussions: merge_request,
+ merge_request_to_resolve_discussions_of: merge_request,
description: 'Fix at your earliest conveignance').execute
expect(issue.description).to eq('Fix at your earliest conveignance')
@@ -82,7 +92,7 @@ describe Issues::BuildService, services: true do
describe 'with multiple discussions' do
before do
- create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.target_project, position: position_on_line(15))
+ create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.target_project, line_number: 15)
end
it 'mentions all the authors in the description' do
@@ -99,7 +109,7 @@ describe Issues::BuildService, services: true do
end
it 'mentions additional notes' do
- create_list(:diff_note_on_merge_request, 2, noteable: merge_request, project: merge_request.target_project, position: position_on_line(15))
+ create_list(:diff_note_on_merge_request, 2, noteable: merge_request, project: merge_request.target_project, line_number: 15)
expect(issue.description).to include('(+2 comments)')
end
@@ -112,7 +122,7 @@ describe Issues::BuildService, services: true do
describe '#execute' do
it 'mentions the merge request in the description' do
- issue = described_class.new(project, user, merge_request_for_resolving_discussions: merge_request).execute
+ issue = described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid).execute
expect(issue.description).to include("Review the conversation in #{merge_request.to_reference}")
end
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 6045d00ff09..776cbc4296b 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -140,46 +140,85 @@ describe Issues::CreateService, services: true do
it_behaves_like 'new issuable record that supports slash commands'
- context 'for a merge request' do
+ context 'resolving discussions' do
let(:discussion) { Discussion.for_diff_notes([create(:diff_note_on_merge_request)]).first }
let(:merge_request) { discussion.noteable }
let(:project) { merge_request.source_project }
- let(:opts) { { merge_request_for_resolving_discussions: merge_request } }
before do
project.team << [user, :master]
end
- it 'resolves the discussion for the merge request' do
- described_class.new(project, user, opts).execute
- discussion.first_note.reload
+ describe 'for a single discussion' do
+ let(:opts) { { discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid } }
- expect(discussion.resolved?).to be(true)
- end
+ it 'resolves the discussion' do
+ described_class.new(project, user, opts).execute
+ discussion.first_note.reload
- it 'added a system note to the discussion' do
- described_class.new(project, user, opts).execute
+ expect(discussion.resolved?).to be(true)
+ end
- reloaded_discussion = MergeRequest.find(merge_request.id).discussions.first
+ it 'added a system note to the discussion' do
+ described_class.new(project, user, opts).execute
- expect(reloaded_discussion.last_note.system).to eq(true)
- end
+ reloaded_discussion = MergeRequest.find(merge_request.id).discussions.first
+
+ expect(reloaded_discussion.last_note.system).to eq(true)
+ end
+
+ it 'assigns the title and description for the issue' do
+ issue = described_class.new(project, user, opts).execute
+
+ expect(issue.title).not_to be_nil
+ expect(issue.description).not_to be_nil
+ end
- it 'assigns the title and description for the issue' do
- issue = described_class.new(project, user, opts).execute
+ it 'can set nil explicitly to the title and description' do
+ issue = described_class.new(project, user,
+ merge_request_to_resolve_discussions_of: merge_request,
+ description: nil,
+ title: nil).execute
- expect(issue.title).not_to be_nil
- expect(issue.description).not_to be_nil
+ expect(issue.description).to be_nil
+ expect(issue.title).to be_nil
+ end
end
- it 'can set nil explicityly to the title and description' do
- issue = described_class.new(project, user,
- merge_request_for_resolving_discussions: merge_request,
- description: nil,
- title: nil).execute
+ describe 'for a merge request' do
+ let(:opts) { { merge_request_to_resolve_discussions_of: merge_request.iid } }
+
+ it 'resolves the discussion' do
+ described_class.new(project, user, opts).execute
+ discussion.first_note.reload
- expect(issue.description).to be_nil
- expect(issue.title).to be_nil
+ expect(discussion.resolved?).to be(true)
+ end
+
+ it 'added a system note to the discussion' do
+ described_class.new(project, user, opts).execute
+
+ reloaded_discussion = MergeRequest.find(merge_request.id).discussions.first
+
+ expect(reloaded_discussion.last_note.system).to eq(true)
+ end
+
+ it 'assigns the title and description for the issue' do
+ issue = described_class.new(project, user, opts).execute
+
+ expect(issue.title).not_to be_nil
+ expect(issue.description).not_to be_nil
+ end
+
+ it 'can set nil explicitly to the title and description' do
+ issue = described_class.new(project, user,
+ merge_request_to_resolve_discussions_of: merge_request,
+ description: nil,
+ title: nil).execute
+
+ expect(issue.description).to be_nil
+ expect(issue.title).to be_nil
+ end
end
end
diff --git a/spec/services/issues/resolve_discussions_spec.rb b/spec/services/issues/resolve_discussions_spec.rb
new file mode 100644
index 00000000000..6cc738aec08
--- /dev/null
+++ b/spec/services/issues/resolve_discussions_spec.rb
@@ -0,0 +1,106 @@
+require 'spec_helper.rb'
+
+class DummyService < Issues::BaseService
+ include ::Issues::ResolveDiscussions
+
+ def initialize(*args)
+ super
+ filter_resolve_discussion_params
+ end
+end
+
+describe DummyService, services: true do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :developer]
+ end
+
+ describe "for resolving discussions" do
+ let(:discussion) { Discussion.new([create(:diff_note_on_merge_request, project: project, note: "Almost done")]) }
+ let(:merge_request) { discussion.noteable }
+ let(:other_merge_request) { create(:merge_request, source_project: project, source_branch: "other") }
+
+ describe "#merge_request_for_resolving_discussion" do
+ let(:service) { described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid) }
+
+ it "finds the merge request" do
+ expect(service.merge_request_to_resolve_discussions_of).to eq(merge_request)
+ end
+
+ it "only queries for the merge request once" do
+ fake_finder = double
+ fake_results = double
+
+ expect(fake_finder).to receive(:execute).and_return(fake_results).exactly(1)
+ expect(fake_results).to receive(:find_by).exactly(1)
+ expect(MergeRequestsFinder).to receive(:new).and_return(fake_finder).exactly(1)
+
+ 2.times { service.merge_request_to_resolve_discussions_of }
+ end
+ end
+
+ describe "#discussions_to_resolve" do
+ it "contains a single discussion when matching merge request and discussion are passed" do
+ service = described_class.new(
+ project,
+ user,
+ discussion_to_resolve: discussion.id,
+ merge_request_to_resolve_discussions_of: merge_request.iid
+ )
+ # We need to compare discussion id's because the Discussion-objects are rebuilt
+ # which causes the object-id's not to be different.
+ discussion_ids = service.discussions_to_resolve.map(&:id)
+
+ expect(discussion_ids).to contain_exactly(discussion.id)
+ end
+
+ it "contains all discussions when only a merge request is passed" do
+ second_discussion = Discussion.new([create(:diff_note_on_merge_request,
+ noteable: merge_request,
+ project: merge_request.target_project,
+ line_number: 15)])
+ service = described_class.new(
+ project,
+ user,
+ merge_request_to_resolve_discussions_of: merge_request.iid
+ )
+ # We need to compare discussion id's because the Discussion-objects are rebuilt
+ # which causes the object-id's not to be different.
+ discussion_ids = service.discussions_to_resolve.map(&:id)
+
+ expect(discussion_ids).to contain_exactly(discussion.id, second_discussion.id)
+ end
+
+ it "contains only unresolved discussions" do
+ _second_discussion = Discussion.new([create(:diff_note_on_merge_request, :resolved,
+ noteable: merge_request,
+ project: merge_request.target_project,
+ line_number: 15,
+ )])
+ service = described_class.new(
+ project,
+ user,
+ merge_request_to_resolve_discussions_of: merge_request.iid
+ )
+ # We need to compare discussion id's because the Discussion-objects are rebuilt
+ # which causes the object-id's not to be different.
+ discussion_ids = service.discussions_to_resolve.map(&:id)
+
+ expect(discussion_ids).to contain_exactly(discussion.id)
+ end
+
+ it "is empty when a discussion and another merge request are passed" do
+ service = described_class.new(
+ project,
+ user,
+ discussion_to_resolve: discussion.id,
+ merge_request_to_resolve_discussions_of: other_merge_request.iid
+ )
+
+ expect(service.discussions_to_resolve).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index ebbaea4e59a..82a4ec3f581 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -146,16 +146,6 @@ describe NotificationService, services: true do
should_not_email(@u_lazy_participant)
end
- it "emails the note author if they've opted into notifications about their activity" do
- add_users_with_subscription(note.project, issue)
- note.author.notified_of_own_activity = true
- reset_delivered_emails!
-
- notification.new_note(note)
-
- should_email(note.author)
- end
-
it 'filters out "mentioned in" notes' do
mentioned_note = SystemNoteService.cross_reference(mentioned_issue, issue, issue.author)
@@ -486,20 +476,6 @@ describe NotificationService, services: true do
should_not_email(issue.assignee)
end
- it "emails the author if they've opted into notifications about their activity" do
- issue.author.notified_of_own_activity = true
-
- notification.new_issue(issue, issue.author)
-
- should_email(issue.author)
- end
-
- it "doesn't email the author if they haven't opted into notifications about their activity" do
- notification.new_issue(issue, issue.author)
-
- should_not_email(issue.author)
- end
-
it "emails subscribers of the issue's labels" do
user_1 = create(:user)
user_2 = create(:user)
@@ -689,19 +665,6 @@ describe NotificationService, services: true do
should_email(subscriber_to_label_2)
end
- it "emails the current user if they've opted into notifications about their activity" do
- subscriber_to_label_2.notified_of_own_activity = true
- notification.relabeled_issue(issue, [group_label_2, label_2], subscriber_to_label_2)
-
- should_email(subscriber_to_label_2)
- end
-
- it "doesn't email the current user if they haven't opted into notifications about their activity" do
- notification.relabeled_issue(issue, [group_label_2, label_2], subscriber_to_label_2)
-
- should_not_email(subscriber_to_label_2)
- end
-
it "doesn't send email to anyone but subscribers of the given labels" do
notification.relabeled_issue(issue, [group_label_2, label_2], @u_disabled)
@@ -855,20 +818,6 @@ describe NotificationService, services: true do
should_not_email(@u_lazy_participant)
end
- it "emails the author if they've opted into notifications about their activity" do
- merge_request.author.notified_of_own_activity = true
-
- notification.new_merge_request(merge_request, merge_request.author)
-
- should_email(merge_request.author)
- end
-
- it "doesn't email the author if they haven't opted into notifications about their activity" do
- notification.new_merge_request(merge_request, merge_request.author)
-
- should_not_email(merge_request.author)
- end
-
it "emails subscribers of the merge request's labels" do
user_1 = create(:user)
user_2 = create(:user)
@@ -1064,14 +1013,6 @@ describe NotificationService, services: true do
should_not_email(@u_watcher)
end
- it "notifies the merger when the pipeline succeeds is false but they've opted into notifications about their activity" do
- merge_request.merge_when_pipeline_succeeds = false
- @u_watcher.notified_of_own_activity = true
- notification.merge_mr(merge_request, @u_watcher)
-
- should_email(@u_watcher)
- end
-
it_behaves_like 'participating notifications' do
let(:participant) { create(:user, username: 'user-participant') }
let(:issuable) { merge_request }
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index a8395cb48ea..3645b73b039 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -298,6 +298,10 @@ describe TodoService, services: true do
expect(second_todo.reload.state?(new_state)).to be true
end
+ it 'returns the updated ids' do
+ expect(service.send(meth, collection, john_doe)).to match_array([first_todo.id, second_todo.id])
+ end
+
describe 'cached counts' do
it 'updates when todos change' do
expect(john_doe.todos.where(state: new_state).count).to eq(0)
@@ -706,7 +710,7 @@ describe TodoService, services: true do
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)
@@ -779,29 +783,27 @@ describe TodoService, services: true do
.to change { todo.reload.state }.from('pending').to('done')
end
- it 'returns the number of updated todos' do # Needed on API
+ it 'returns the ids of updated todos' do # Needed on API
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
- expect(TodoService.new.mark_todos_as_done([todo], john_doe)).to eq(1)
+ expect(TodoService.new.mark_todos_as_done([todo], john_doe)).to eq([todo.id])
end
context 'when some of the todos are done already' do
- before do
- create(:todo, :mentioned, user: john_doe, target: issue, project: project)
- create(:todo, :mentioned, user: john_doe, target: another_issue, project: project)
- end
+ let!(:first_todo) { create(:todo, :mentioned, user: john_doe, target: issue, project: project) }
+ let!(:second_todo) { create(:todo, :mentioned, user: john_doe, target: another_issue, project: project) }
- it 'returns the number of those still pending' do
+ it 'returns the ids of those still pending' do
TodoService.new.mark_pending_todos_as_done(issue, john_doe)
- expect(TodoService.new.mark_todos_as_done(Todo.all, john_doe)).to eq(1)
+ expect(TodoService.new.mark_todos_as_done(Todo.all, john_doe)).to eq([second_todo.id])
end
- it 'returns 0 if all are done' do
+ it 'returns an empty array if all are done' do
TodoService.new.mark_pending_todos_as_done(issue, john_doe)
TodoService.new.mark_pending_todos_as_done(another_issue, john_doe)
- expect(TodoService.new.mark_todos_as_done(Todo.all, john_doe)).to eq(0)
+ expect(TodoService.new.mark_todos_as_done(Todo.all, john_doe)).to eq([])
end
end
diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb
index b507d38f472..ac2c89b3ff9 100644
--- a/spec/simplecov_env.rb
+++ b/spec/simplecov_env.rb
@@ -15,9 +15,9 @@ module SimpleCovEnv
def configure_job
SimpleCov.configure do
- if ENV['CI_BUILD_NAME']
- coverage_dir "coverage/#{ENV['CI_BUILD_NAME']}"
- command_name ENV['CI_BUILD_NAME']
+ if ENV['CI_JOB_NAME']
+ coverage_dir "coverage/#{ENV['CI_JOB_NAME']}"
+ command_name ENV['CI_JOB_NAME']
end
if ENV['CI']
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 5fda7c63cdb..ceb3209331f 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -43,14 +43,27 @@ RSpec.configure do |config|
config.include ActiveSupport::Testing::TimeHelpers
config.include StubGitlabCalls
config.include StubGitlabData
+ config.include ApiHelpers, :api
config.infer_spec_type_from_file_location!
+
+ config.define_derived_metadata(file_path: %r{/spec/requests/(ci/)?api/}) do |metadata|
+ metadata[:api] = true
+ end
+
config.raise_errors_for_deprecations!
config.before(:suite) do
TestEnv.init
end
+ if ENV['CI']
+ # Retry only on feature specs that use JS
+ config.around :each, :js do |ex|
+ ex.run_with_retry retry: 3
+ end
+ end
+
config.around(:each, :caching) do |example|
caching_store = Rails.cache
Rails.cache = ActiveSupport::Cache::MemoryStore.new if example.metadata[:caching]
diff --git a/spec/support/api/issues_resolving_discussions_shared_examples.rb b/spec/support/api/issues_resolving_discussions_shared_examples.rb
new file mode 100644
index 00000000000..d26d279363c
--- /dev/null
+++ b/spec/support/api/issues_resolving_discussions_shared_examples.rb
@@ -0,0 +1,15 @@
+shared_examples 'creating an issue resolving discussions through the API' do
+ it 'creates a new project issue' do
+ expect(response).to have_http_status(:created)
+ end
+
+ it 'resolves the discussions in a merge request' do
+ discussion.first_note.reload
+
+ expect(discussion.resolved?).to be(true)
+ end
+
+ it 'assigns a description to the issue mentioning the merge request' do
+ expect(json_response['description']).to include(merge_request.to_reference)
+ end
+end
diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb
index ae6e708cf87..35d1e1cfc7d 100644
--- a/spec/support/api_helpers.rb
+++ b/spec/support/api_helpers.rb
@@ -49,8 +49,4 @@ module ApiHelpers
''
end
end
-
- def json_response
- @_json_response ||= JSON.parse(response.body)
- end
end
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 62740ec29fd..aa14709bc9c 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -1,6 +1,7 @@
require 'capybara/rails'
require 'capybara/rspec'
require 'capybara/poltergeist'
+require 'capybara-screenshot/rspec'
# Give CI some extra time
timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 30 : 10
@@ -21,12 +22,8 @@ end
Capybara.default_max_wait_time = timeout
Capybara.ignore_hidden_elements = true
-unless ENV['CI'] || ENV['CI_SERVER']
- require 'capybara-screenshot/rspec'
-
- # Keep only the screenshots generated from the last failing test suite
- Capybara::Screenshot.prune_strategy = :keep_last_run
-end
+# Keep only the screenshots generated from the last failing test suite
+Capybara::Screenshot.prune_strategy = :keep_last_run
RSpec.configure do |config|
config.before(:suite) do
diff --git a/spec/support/features/resolving_discussions_in_issues_shared_examples.rb b/spec/support/features/resolving_discussions_in_issues_shared_examples.rb
new file mode 100644
index 00000000000..4a946995f84
--- /dev/null
+++ b/spec/support/features/resolving_discussions_in_issues_shared_examples.rb
@@ -0,0 +1,41 @@
+shared_examples 'creating an issue for a discussion' do
+ it 'shows an issue with the title filled in' do
+ title_field = page.find_field('issue[title]')
+
+ expect(title_field.value).to include(merge_request.title)
+ end
+
+ it 'has a mention of the discussion in the description' do
+ description_field = page.find_field('issue[description]')
+
+ expect(description_field.value).to include(discussion.first_note.note)
+ end
+
+ it 'can create a new issue for the project' do
+ expect { click_button 'Submit issue' }.to change { project.issues.reload.size }.by(1)
+ end
+
+ it 'resolves the discussion in the merge request' do
+ click_button 'Submit issue'
+
+ discussion.first_note.reload
+
+ expect(discussion.resolved?).to eq(true)
+ end
+
+ it 'shows a flash messaage after resolving a discussion' do
+ click_button 'Submit issue'
+
+ page.within '.flash-notice' do
+ # Only check for the word 'Resolved' since the spec might have resolved
+ # multiple discussions
+ expect(page).to have_content('Resolved')
+ end
+ end
+
+ it 'has a hidden field for the merge request' do
+ merge_request_field = find('#merge_request_to_resolve_discussions_of', visible: false)
+
+ expect(merge_request_field.value).to eq(merge_request.iid.to_s)
+ end
+end
diff --git a/spec/support/json_response_helpers.rb b/spec/support/json_response_helpers.rb
new file mode 100644
index 00000000000..e8d2ef2d7f0
--- /dev/null
+++ b/spec/support/json_response_helpers.rb
@@ -0,0 +1,9 @@
+shared_context 'JSON response' do
+ let(:json_response) { JSON.parse(response.body) }
+end
+
+RSpec.configure do |config|
+ config.include_context 'JSON response', type: :controller
+ config.include_context 'JSON response', type: :request
+ config.include_context 'JSON response', :api
+end
diff --git a/spec/support/seed_helper.rb b/spec/support/seed_helper.rb
index 07f81e9c4f3..f55fee28ff9 100644
--- a/spec/support/seed_helper.rb
+++ b/spec/support/seed_helper.rb
@@ -7,7 +7,7 @@ TEST_MUTABLE_REPO_PATH = File.join(SEED_REPOSITORY_PATH, "mutable-repo.git")
TEST_BROKEN_REPO_PATH = File.join(SEED_REPOSITORY_PATH, "broken-repo.git")
module SeedHelper
- GITLAB_URL = "https://gitlab.com/gitlab-org/gitlab-git-test.git".freeze
+ GITLAB_GIT_TEST_REPO_URL = ENV.fetch('GITLAB_GIT_TEST_REPO_URL', 'https://gitlab.com/gitlab-org/gitlab-git-test.git').freeze
def ensure_seeds
if File.exist?(SEED_REPOSITORY_PATH)
@@ -25,7 +25,7 @@ module SeedHelper
end
def create_bare_seeds
- system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{GITLAB_URL}),
+ system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{GITLAB_GIT_TEST_REPO_URL}),
chdir: SEED_REPOSITORY_PATH,
out: '/dev/null',
err: '/dev/null')
@@ -45,7 +45,7 @@ module SeedHelper
system(git_env, *%w(git branch -t feature origin/feature),
chdir: TEST_MUTABLE_REPO_PATH, out: '/dev/null', err: '/dev/null')
- system(git_env, *%W(#{Gitlab.config.git.bin_path} remote add expendable #{GITLAB_URL}),
+ system(git_env, *%W(#{Gitlab.config.git.bin_path} remote add expendable #{GITLAB_GIT_TEST_REPO_URL}),
chdir: TEST_MUTABLE_REPO_PATH, out: '/dev/null', err: '/dev/null')
end
diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
index f2919f20e85..8bc344bfbf6 100644
--- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb
+++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
@@ -25,7 +25,7 @@ describe 'projects/commit/_commit_box.html.haml' do
render
- expect(rendered).to have_text("Pipeline ##{third_pipeline.id} for #{Commit.truncate_sha(project.commit.sha)} failed")
+ expect(rendered).to have_text("Pipeline ##{third_pipeline.id} failed")
end
context 'viewing a commit' do