summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorDouwe Maan <douwe@selenight.nl>2017-01-19 10:08:11 -0600
committerDouwe Maan <douwe@selenight.nl>2017-01-19 10:08:11 -0600
commit2b37e4c1995284a4871d6d7077b0884e95c2496e (patch)
tree615e0a3c063ac7e93001434a4a0932cb061e8cc9 /spec
parentf928e19cb572b34d93baaa5e3040d99fc5ba9939 (diff)
parent52762df285751dec4e54c4c55be5bbecb3bd4fc9 (diff)
downloadgitlab-ce-2b37e4c1995284a4871d6d7077b0884e95c2496e.tar.gz
Merge branch 'master' into copy-as-md
# Conflicts: # app/assets/javascripts/lib/utils/common_utils.js.es6
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/admin/services_controller_spec.rb26
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb38
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb30
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb48
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb68
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb48
-rw-r--r--spec/factories/projects.rb4
-rw-r--r--spec/factories/timelogs.rb9
-rw-r--r--spec/factories/todos.rb4
-rw-r--r--spec/features/boards/sidebar_spec.rb4
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb6
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb39
-rw-r--r--spec/features/issues/user_uses_slash_commands_spec.rb52
-rw-r--r--spec/features/merge_requests/diffs_spec.rb14
-rw-r--r--spec/features/merge_requests/user_uses_slash_commands_spec.rb45
-rw-r--r--spec/features/merge_requests/wip_message_spec.rb63
-rw-r--r--spec/features/projects/builds_spec.rb28
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb61
-rw-r--r--spec/features/projects/settings/visibility_settings_spec.rb47
-rw-r--r--spec/helpers/merge_requests_helper_spec.rb15
-rw-r--r--spec/javascripts/activities_spec.js.es618
-rw-r--r--spec/javascripts/awards_handler_spec.js3
-rw-r--r--spec/javascripts/behaviors/autosize_spec.js3
-rw-r--r--spec/javascripts/behaviors/quick_submit_spec.js3
-rw-r--r--spec/javascripts/behaviors/requires_input_spec.js3
-rw-r--r--spec/javascripts/boards/boards_store_spec.js.es610
-rw-r--r--spec/javascripts/dashboard_spec.js.es63
-rw-r--r--spec/javascripts/diff_comments_store_spec.js.es64
-rw-r--r--spec/javascripts/extensions/array_spec.js.es63
-rw-r--r--spec/javascripts/extensions/jquery_spec.js3
-rw-r--r--spec/javascripts/fixtures/emoji_menu.js3
-rw-r--r--spec/javascripts/gfm_auto_complete_spec.js.es665
-rw-r--r--spec/javascripts/gl_dropdown_spec.js.es633
-rw-r--r--spec/javascripts/gl_field_errors_spec.js.es66
-rw-r--r--spec/javascripts/graphs/stat_graph_contributors_graph_spec.js173
-rw-r--r--spec/javascripts/graphs/stat_graph_contributors_util_spec.js299
-rw-r--r--spec/javascripts/graphs/stat_graph_spec.js8
-rw-r--r--spec/javascripts/header_spec.js4
-rw-r--r--spec/javascripts/issuable_time_tracker_spec.js.es6201
-rw-r--r--spec/javascripts/issue_spec.js27
-rw-r--r--spec/javascripts/labels_issue_sidebar_spec.js.es616
-rw-r--r--spec/javascripts/line_highlighter_spec.js23
-rw-r--r--spec/javascripts/merge_request_spec.js3
-rw-r--r--spec/javascripts/merge_request_widget_spec.js41
-rw-r--r--spec/javascripts/new_branch_spec.js3
-rw-r--r--spec/javascripts/notes_spec.js5
-rw-r--r--spec/javascripts/pretty_time_spec.js.es622
-rw-r--r--spec/javascripts/project_title_spec.js3
-rw-r--r--spec/javascripts/right_sidebar_spec.js5
-rw-r--r--spec/javascripts/search_autocomplete_spec.js3
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js3
-rw-r--r--spec/javascripts/subbable_resource_spec.js.es64
-rw-r--r--spec/javascripts/syntax_highlight_spec.js3
-rw-r--r--spec/javascripts/u2f/authenticate_spec.js3
-rw-r--r--spec/javascripts/u2f/mock_u2f_device.js7
-rw-r--r--spec/javascripts/u2f/register_spec.js3
-rw-r--r--spec/javascripts/visibility_select_spec.js.es6100
-rw-r--r--spec/javascripts/zen_mode_spec.js3
-rw-r--r--spec/lib/gitlab/ci/status/external/common_spec.rb39
-rw-r--r--spec/lib/gitlab/ci/status/external/factory_spec.rb38
-rw-r--r--spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb12
-rw-r--r--spec/lib/gitlab/cycle_analytics/code_event_spec.rb10
-rw-r--r--spec/lib/gitlab/cycle_analytics/code_stage_spec.rb8
-rw-r--r--spec/lib/gitlab/cycle_analytics/events_spec.rb137
-rw-r--r--spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb8
-rw-r--r--spec/lib/gitlab/cycle_analytics/issue_event_spec.rb10
-rw-r--r--spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb8
-rw-r--r--spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb (renamed from spec/lib/gitlab/cycle_analytics/plan_event_spec.rb)10
-rw-r--r--spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb8
-rw-r--r--spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb8
-rw-r--r--spec/lib/gitlab/cycle_analytics/production_event_spec.rb10
-rw-r--r--spec/lib/gitlab/cycle_analytics/production_stage_spec.rb8
-rw-r--r--spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb8
-rw-r--r--spec/lib/gitlab/cycle_analytics/review_event_spec.rb10
-rw-r--r--spec/lib/gitlab/cycle_analytics/review_stage_spec.rb8
-rw-r--r--spec/lib/gitlab/cycle_analytics/shared_event_spec.rb11
-rw-r--r--spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb30
-rw-r--r--spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb (renamed from spec/models/cycle_analytics/summary_spec.rb)20
-rw-r--r--spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb12
-rw-r--r--spec/lib/gitlab/cycle_analytics/staging_event_spec.rb10
-rw-r--r--spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb8
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb12
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_event_spec.rb10
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_stage_spec.rb8
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml5
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml10
-rw-r--r--spec/lib/gitlab/view/presenter/base_spec.rb51
-rw-r--r--spec/lib/gitlab/view/presenter/delegated_spec.rb29
-rw-r--r--spec/lib/gitlab/view/presenter/factory_spec.rb38
-rw-r--r--spec/lib/gitlab/view/presenter/simple_spec.rb29
-rw-r--r--spec/models/ci/build_spec.rb18
-rw-r--r--spec/models/commit_spec.rb28
-rw-r--r--spec/models/concerns/issuable_spec.rb38
-rw-r--r--spec/models/concerns/presentable_spec.rb15
-rw-r--r--spec/models/cycle_analytics/code_spec.rb22
-rw-r--r--spec/models/cycle_analytics/issue_spec.rb4
-rw-r--r--spec/models/cycle_analytics/plan_spec.rb4
-rw-r--r--spec/models/cycle_analytics/production_spec.rb6
-rw-r--r--spec/models/cycle_analytics/review_spec.rb4
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb6
-rw-r--r--spec/models/cycle_analytics/test_spec.rb10
-rw-r--r--spec/models/generic_commit_status_spec.rb33
-rw-r--r--spec/models/merge_request_diff_spec.rb26
-rw-r--r--spec/models/merge_request_spec.rb112
-rw-r--r--spec/models/project_statistics_spec.rb2
-rw-r--r--spec/models/timelog_spec.rb10
-rw-r--r--spec/policies/base_policy_spec.rb17
-rw-r--r--spec/presenters/ci/build_presenter_spec.rb77
-rw-r--r--spec/requests/api/commit_statuses_spec.rb28
-rw-r--r--spec/requests/api/issues_spec.rb6
-rw-r--r--spec/requests/api/merge_requests_spec.rb8
-rw-r--r--spec/serializers/analytics_stage_serializer_spec.rb24
-rw-r--r--spec/serializers/analytics_summary_serializer_spec.rb29
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb64
-rw-r--r--spec/services/merge_requests/update_service_spec.rb93
-rw-r--r--spec/services/notes/create_service_spec.rb11
-rw-r--r--spec/services/notes/slash_commands_service_spec.rb12
-rw-r--r--spec/services/slash_commands/interpret_service_spec.rb154
-rw-r--r--spec/services/system_note_service_spec.rb88
-rw-r--r--spec/services/todo_service_spec.rb16
-rw-r--r--spec/services/users/refresh_authorized_projects_service_spec.rb40
-rw-r--r--spec/support/api/time_tracking_shared_examples.rb132
-rw-r--r--spec/support/cycle_analytics_helpers/test_generation.rb13
-rw-r--r--spec/support/test_env.rb3
-rw-r--r--spec/support/time_tracking_shared_examples.rb82
126 files changed, 2978 insertions, 657 deletions
diff --git a/spec/controllers/admin/services_controller_spec.rb b/spec/controllers/admin/services_controller_spec.rb
new file mode 100644
index 00000000000..e5cdd52307e
--- /dev/null
+++ b/spec/controllers/admin/services_controller_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Admin::ServicesController do
+ let(:admin) { create(:admin) }
+
+ before { sign_in(admin) }
+
+ describe 'GET #edit' do
+ let!(:project) { create(:empty_project) }
+
+ Service.available_services_names.each do |service_name|
+ context "#{service_name}" do
+ let!(:service) do
+ service_template = service_name.concat("_service").camelize.constantize
+ service_template.where(template: true).first_or_create
+ end
+
+ it 'successfully displays the template' do
+ get :edit, id: service.id
+
+ expect(response).to have_http_status(200)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index 646b097d74e..0fa06a38d2a 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -1,9 +1,10 @@
require 'spec_helper'
describe Projects::CommitController do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let(:commit) { project.commit("master") }
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:commit) { project.commit("master") }
+ let(:pipeline) { create(:ci_pipeline, project: project, commit: commit) }
let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
let(:master_pickable_commit) { project.commit(master_pickable_sha) }
@@ -309,4 +310,35 @@ describe Projects::CommitController do
end
end
end
+
+ describe 'GET pipelines' do
+ def get_pipelines(extra_params = {})
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+ }
+
+ get :pipelines, params.merge(extra_params)
+ end
+
+ context 'when the commit exists' do
+ context 'when the commit has one or more pipelines' do
+ it 'shows pipelines' do
+ get_pipelines(id: commit.id)
+
+ expect(response).to be_ok
+ end
+ end
+ end
+
+ context 'when the commit does not exist' do
+ before do
+ get_pipelines(id: 'e7a412c8da9f6d0081a633a4a402dde1c4694ebd')
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index 7a57801c437..b03c4b52de6 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -64,6 +64,36 @@ describe Projects::CompareController do
expect(assigns(:diffs)).to eq(nil)
expect(assigns(:commits)).to eq(nil)
end
+
+ it 'redirects back to index when params[:from] is empty and preserves params[:to]' do
+ post(:create,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ from: '',
+ to: 'master')
+
+ expect(response).to redirect_to(namespace_project_compare_index_path(project.namespace, project, to: 'master'))
+ end
+
+ it 'redirects back to index when params[:to] is empty and preserves params[:from]' do
+ post(:create,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ from: 'master',
+ to: '')
+
+ expect(response).to redirect_to(namespace_project_compare_index_path(project.namespace, project, from: 'master'))
+ end
+
+ it 'redirects back to index when params[:from] and params[:to] are empty' do
+ post(:create,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ from: '',
+ to: '')
+
+ expect(response).to redirect_to(namespace_project_compare_index_path)
+ end
end
describe 'GET diff_for_path' do
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index e2321f2034b..b5987a83df0 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -326,6 +326,20 @@ describe Projects::IssuesController do
end
describe 'POST #create' do
+ def post_new_issue(attrs = {})
+ sign_in(user)
+ project = create(:empty_project, :public)
+ project.team << [user, :developer]
+
+ post :create, {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ issue: { title: 'Title', description: 'Description' }.merge(attrs)
+ }
+
+ project.issues.first
+ end
+
context 'resolving discussions in MergeRequest' do
let(:discussion) { Discussion.for_diff_notes([create(:diff_note_on_merge_request)]).first }
let(:merge_request) { discussion.noteable }
@@ -369,13 +383,7 @@ describe Projects::IssuesController do
end
def post_spam_issue
- sign_in(user)
- spam_project = create(:empty_project, :public)
- post :create, {
- namespace_id: spam_project.namespace.to_param,
- project_id: spam_project.to_param,
- issue: { title: 'Spam Title', description: 'Spam lives here' }
- }
+ post_new_issue(title: 'Spam Title', description: 'Spam lives here')
end
it 'rejects an issue recognized as spam' do
@@ -396,18 +404,26 @@ describe Projects::IssuesController do
request.env['action_dispatch.remote_ip'] = '127.0.0.1'
end
- def post_new_issue
+ it 'creates a user agent detail' do
+ expect{ post_new_issue }.to change(UserAgentDetail, :count).by(1)
+ end
+ end
+
+ context 'when description has slash commands' do
+ before do
sign_in(user)
- project = create(:empty_project, :public)
- post :create, {
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- issue: { title: 'Title', description: 'Description' }
- }
end
- it 'creates a user agent detail' do
- expect{ post_new_issue }.to change(UserAgentDetail, :count).by(1)
+ it 'can add spent time' do
+ issue = post_new_issue(description: '/spend 1h')
+
+ expect(issue.total_time_spent).to eq(3600)
+ end
+
+ it 'can set the time estimate' do
+ issue = post_new_issue(description: '/estimate 2h')
+
+ expect(issue.time_estimate).to eq(7200)
end
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 2a411d78395..7ea3ea4f376 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -1048,4 +1048,72 @@ describe Projects::MergeRequestsController do
end
end
end
+
+ describe 'GET merge_widget_refresh' do
+ let(:params) do
+ {
+ namespace_id: project.namespace.path,
+ project_id: project.path,
+ id: merge_request.iid,
+ format: :raw
+ }
+ end
+
+ before do
+ project.team << [user, :developer]
+ xhr :get, :merge_widget_refresh, params
+ end
+
+ context 'when merge in progress' do
+ let(:merge_request) { create(:merge_request, source_project: project, in_progress_merge_commit_sha: 'sha') }
+
+ it 'returns an OK response' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'sets status to :success' do
+ expect(assigns(:status)).to eq(:success)
+ expect(response).to render_template('merge')
+ end
+ end
+
+ context 'when merge request was merged already' do
+ let(:merge_request) { create(:merge_request, source_project: project, state: :merged) }
+
+ it 'returns an OK response' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'sets status to :success' do
+ expect(assigns(:status)).to eq(:success)
+ expect(response).to render_template('merge')
+ end
+ end
+
+ context 'when waiting for build' do
+ let(:merge_request) { create(:merge_request, source_project: project, merge_when_build_succeeds: true, merge_user: user) }
+
+ it 'returns an OK response' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'sets status to :merge_when_build_succeeds' do
+ expect(assigns(:status)).to eq(:merge_when_build_succeeds)
+ expect(response).to render_template('merge')
+ end
+ end
+
+ context 'when no special status for MR' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ it 'returns an OK response' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'sets status to nil' do
+ expect(assigns(:status)).to be_nil
+ expect(response).to render_template('merge')
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 92e38b02615..9f6d4ec6537 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -14,6 +14,54 @@ describe Projects::NotesController do
}
end
+ describe 'POST create' do
+ let(:merge_request) { create(:merge_request) }
+ let(:request_params) do
+ {
+ note: { note: 'some note', noteable_id: merge_request.id, noteable_type: 'MergeRequest' },
+ namespace_id: project.namespace,
+ project_id: project,
+ merge_request_diff_head_sha: 'sha'
+ }
+ end
+
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ end
+
+ it "returns status 302 for html" do
+ post :create, request_params
+
+ expect(response).to have_http_status(302)
+ end
+
+ it "returns status 200 for json" do
+ post :create, request_params.merge(format: :json)
+
+ expect(response).to have_http_status(200)
+ end
+
+ context 'when merge_request_diff_head_sha present' do
+ before do
+ service_params = {
+ note: 'some note',
+ noteable_id: merge_request.id.to_s,
+ noteable_type: 'MergeRequest',
+ merge_request_diff_head_sha: 'sha'
+ }
+
+ expect(Notes::CreateService).to receive(:new).with(project, user, service_params).and_return(double(execute: true))
+ end
+
+ it "returns status 302 for html" do
+ post :create, request_params
+
+ expect(response).to have_http_status(302)
+ end
+ end
+ end
+
describe 'POST toggle_award_emoji' do
before do
sign_in(user)
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 1cdbe4fc9a5..992580a6b34 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -32,6 +32,10 @@ FactoryGirl.define do
request_access_enabled true
end
+ trait :repository do
+ # no-op... for now!
+ end
+
trait :empty_repo do
after(:create) do |project|
project.create_repository
diff --git a/spec/factories/timelogs.rb b/spec/factories/timelogs.rb
new file mode 100644
index 00000000000..12fc4ec4486
--- /dev/null
+++ b/spec/factories/timelogs.rb
@@ -0,0 +1,9 @@
+# Read about factories at https://github.com/thoughtbot/factory_girl
+
+FactoryGirl.define do
+ factory :timelog do
+ time_spent 3600
+ user
+ association :trackable, factory: :issue
+ end
+end
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index 082b02116c0..85a8c263643 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -28,6 +28,10 @@ FactoryGirl.define do
action { Todo::APPROVAL_REQUIRED }
end
+ trait :unmergeable do
+ action { Todo::UNMERGEABLE }
+ end
+
trait :done do
state :done
end
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index c16aafa1470..188d33e8ef4 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -125,7 +125,9 @@ describe 'Issue Boards', feature: true, js: true do
first('.card').click
end
- page.within('.assignee') do
+ page.within(find('.assignee')) do
+ expect(page).to have_content('No assignee')
+
click_link 'assign yourself'
wait_for_vue_resource
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index 6f6a2532c04..3ac9b2e0ae0 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -66,6 +66,12 @@ describe 'Dropdown assignee', js: true, feature: true do
expect(dropdown_assignee_size).to eq(3)
end
+
+ it 'shows current user at top of dropdown' do
+ send_keys_to_filtered_search('assignee:')
+
+ expect(first('#js-dropdown-assignee .filter-dropdown li')).to have_content(user.name)
+ end
end
describe 'filtering' do
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index 60a86cc93d4..464749d01e3 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -66,6 +66,12 @@ describe 'Dropdown author', js: true, feature: true do
expect(dropdown_author_size).to eq(3)
end
+
+ it 'shows current user at top of dropdown' do
+ send_keys_to_filtered_search('author:')
+
+ expect(first('#js-dropdown-author li')).to have_content(user.name)
+ end
end
describe 'filtering' do
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 82c9bd0e6e6..31156fcf994 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -33,6 +33,45 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(page).not_to have_selector('.atwho-view')
end
+ it 'doesnt select the first item for non-assignee dropdowns' do
+ page.within '.timeline-content-form' do
+ find('#note_note').native.send_keys('')
+ find('#note_note').native.send_keys(':')
+ end
+
+ expect(page).to have_selector('.atwho-container')
+
+ wait_for_ajax
+
+ expect(find('#at-view-58')).not_to have_selector('.cur:first-of-type')
+ end
+
+ it 'selects the first item for assignee dropdowns' do
+ page.within '.timeline-content-form' do
+ find('#note_note').native.send_keys('')
+ find('#note_note').native.send_keys('@')
+ end
+
+ expect(page).to have_selector('.atwho-container')
+
+ wait_for_ajax
+
+ expect(find('#at-view-64')).to have_selector('.cur:first-of-type')
+ end
+
+ it 'selects the first item for non-assignee dropdowns if a query is entered' do
+ page.within '.timeline-content-form' do
+ find('#note_note').native.send_keys('')
+ find('#note_note').native.send_keys(':1')
+ end
+
+ expect(page).to have_selector('.atwho-container')
+
+ wait_for_ajax
+
+ expect(find('#at-view-58')).to have_selector('.cur:first-of-type')
+ end
+
context 'if a selected value has special characters' do
it 'wraps the result in double quotes' do
note = find('#note_note')
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index 31f75512f4a..0a9cd11ad6e 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -100,6 +100,58 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
end
end
+ describe 'Issuable time tracking' do
+ let(:issue) { create(:issue, project: project) }
+
+ before do
+ project.team << [user, :developer]
+ end
+
+ context 'Issue' do
+ before do
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ it_behaves_like 'issuable time tracker'
+ end
+
+ context 'Merge Request' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ before do
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ it_behaves_like 'issuable time tracker'
+ end
+ end
+
+ describe 'Issuable time tracking' do
+ let(:issue) { create(:issue, project: project) }
+
+ before do
+ project.team << [user, :developer]
+ end
+
+ context 'Issue' do
+ before do
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ it_behaves_like 'issuable time tracker'
+ end
+
+ context 'Merge Request' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ before do
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ it_behaves_like 'issuable time tracker'
+ end
+ end
+
describe 'toggling the WIP prefix from the title from note' do
let(:issue) { create(:issue, project: project) }
diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb
index c9a0059645d..4a6c76a5caf 100644
--- a/spec/features/merge_requests/diffs_spec.rb
+++ b/spec/features/merge_requests/diffs_spec.rb
@@ -22,4 +22,18 @@ feature 'Diffs URL', js: true, feature: true do
expect(page).to have_css('.diffs.tab-pane.active')
end
end
+
+ context 'when merge request has overflow' do
+ it 'displays warning' do
+ allow_any_instance_of(MergeRequestDiff).to receive(:overflow?).and_return(true)
+ allow(Commit).to receive(:max_diff_options).and_return(max_files: 20, max_lines: 20)
+
+ visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+
+ page.within('.alert') do
+ expect(page).to have_text("Too many changes to show. Plain diff Email patch To preserve
+ performance only 3 of 3+ files are displayed.")
+ end
+ end
+ end
end
diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
index b1b3a47a1ce..b13674b4db9 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
@@ -68,6 +68,51 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
end
end
+ describe 'merging the MR from the note' do
+ context 'when the current user can merge the MR' do
+ it 'merges the MR' do
+ write_note("/merge")
+
+ expect(page).to have_content 'Commands applied'
+
+ expect(merge_request.reload).to be_merged
+ end
+ end
+
+ context 'when the head diff changes in the meanwhile' do
+ before do
+ merge_request.source_branch = 'another_branch'
+ merge_request.save
+ end
+
+ it 'does not merge the MR' do
+ write_note("/merge")
+
+ expect(page).not_to have_content 'Your commands have been executed!'
+
+ expect(merge_request.reload).not_to be_merged
+ end
+ end
+
+ context 'when the current user cannot merge the MR' do
+ let(:guest) { create(:user) }
+ before do
+ project.team << [guest, :guest]
+ logout
+ login_with(guest)
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ it 'does not merge the MR' do
+ write_note("/merge")
+
+ expect(page).not_to have_content 'Your commands have been executed!'
+
+ expect(merge_request.reload).not_to be_merged
+ end
+ end
+ end
+
describe 'adding a due date from note' do
it 'does not recognize the command nor create a note' do
write_note('/due 2016-08-28')
diff --git a/spec/features/merge_requests/wip_message_spec.rb b/spec/features/merge_requests/wip_message_spec.rb
new file mode 100644
index 00000000000..3311731b33b
--- /dev/null
+++ b/spec/features/merge_requests/wip_message_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+feature 'Work In Progress help message', feature: true do
+ let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
+ let!(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ login_as(user)
+ end
+
+ context 'with WIP commits' do
+ it 'shows a specific WIP hint' do
+ visit new_namespace_project_merge_request_path(
+ project.namespace,
+ project,
+ merge_request: {
+ source_project_id: project.id,
+ target_project_id: project.id,
+ source_branch: 'wip',
+ target_branch: 'master'
+ }
+ )
+
+ within_wip_explanation do
+ expect(page).to have_text(
+ 'It looks like you have some WIP commits in this branch'
+ )
+ end
+ end
+ end
+
+ context 'without WIP commits' do
+ it 'shows the regular WIP message' do
+ visit new_namespace_project_merge_request_path(
+ project.namespace,
+ project,
+ merge_request: {
+ source_project_id: project.id,
+ target_project_id: project.id,
+ source_branch: 'fix',
+ target_branch: 'master'
+ }
+ )
+
+ within_wip_explanation do
+ expect(page).not_to have_text(
+ 'It looks like you have some WIP commits in this branch'
+ )
+ expect(page).to have_text(
+ "Start the title with WIP: to prevent a Work In Progress merge \
+request from being merged before it's ready"
+ )
+ end
+ end
+ end
+
+ def within_wip_explanation(&block)
+ page.within '.js-no-wip-explanation' do
+ yield
+ end
+ end
+end
diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb
index 8c4d4320dc5..11d27feab0b 100644
--- a/spec/features/projects/builds_spec.rb
+++ b/spec/features/projects/builds_spec.rb
@@ -3,6 +3,7 @@ require 'tempfile'
feature 'Builds', :feature do
let(:user) { create(:user) }
+ let(:user_access_level) { :developer }
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
@@ -14,7 +15,7 @@ feature 'Builds', :feature do
end
before do
- project.team << [user, :developer]
+ project.team << [user, user_access_level]
login_as(user)
end
@@ -131,7 +132,9 @@ feature 'Builds', :feature do
context 'Artifacts expire date' do
before do
- build.update_attributes(artifacts_file: artifacts_file, artifacts_expire_at: expire_at)
+ build.update_attributes(artifacts_file: artifacts_file,
+ artifacts_expire_at: expire_at)
+
visit namespace_project_build_path(project.namespace, project, build)
end
@@ -146,12 +149,23 @@ feature 'Builds', :feature do
context 'when expire date is defined' do
let(:expire_at) { Time.now + 7.days }
- it 'keeps artifacts when Keep button is clicked' do
- expect(page).to have_content 'The artifacts will be removed'
- click_link 'Keep'
+ context 'when user has ability to update build' do
+ it 'keeps artifacts when keep button is clicked' do
+ expect(page).to have_content 'The artifacts will be removed'
- expect(page).not_to have_link 'Keep'
- expect(page).not_to have_content 'The artifacts will be removed'
+ click_link 'Keep'
+
+ expect(page).to have_no_link 'Keep'
+ expect(page).to have_no_content 'The artifacts will be removed'
+ end
+ end
+
+ context 'when user does not have ability to update build' do
+ let(:user_access_level) { :guest }
+
+ it 'does not have keep button' do
+ expect(page).to have_no_link 'Keep'
+ end
end
end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 14e009daba8..e673ece37c3 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -11,18 +11,42 @@ describe 'Pipeline', :feature, :js do
project.team << [user, :developer]
end
+ shared_context 'pipeline builds' do
+ let!(:build_passed) do
+ create(:ci_build, :success,
+ pipeline: pipeline, stage: 'build', name: 'build')
+ end
+
+ let!(:build_failed) do
+ create(:ci_build, :failed,
+ pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
+ end
+
+ let!(:build_running) do
+ create(:ci_build, :running,
+ pipeline: pipeline, stage: 'deploy', name: 'deploy')
+ end
+
+ let!(:build_manual) do
+ create(:ci_build, :manual,
+ pipeline: pipeline, stage: 'deploy', name: 'manual-build')
+ end
+
+ let!(:build_external) do
+ create(:generic_commit_status, status: 'success',
+ pipeline: pipeline,
+ name: 'jenkins',
+ stage: 'external',
+ target_url: 'http://gitlab.com/status')
+ end
+ end
+
describe 'GET /:project/pipelines/:id' do
+ include_context 'pipeline builds'
+
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
- before do
- @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
- @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
- @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
- @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual-build')
- @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
- end
-
before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) }
it 'shows the pipeline graph' do
@@ -116,6 +140,7 @@ describe 'Pipeline', :feature, :js do
it 'shows the success icon and the generic comit status build' do
expect(page).to have_selector('.ci-status-icon-success')
expect(page).to have_content('jenkins')
+ expect(page).to have_link('jenkins', href: 'http://gitlab.com/status')
end
end
end
@@ -157,26 +182,22 @@ describe 'Pipeline', :feature, :js do
end
describe 'GET /:project/pipelines/:id/builds' do
+ include_context 'pipeline builds'
+
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
before do
- @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
- @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
- @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
- @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual-build')
- @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
+ visit builds_namespace_project_pipeline_path(project.namespace, project, pipeline)
end
- before { visit builds_namespace_project_pipeline_path(project.namespace, project, pipeline)}
-
it 'shows a list of builds' do
expect(page).to have_content('Test')
- expect(page).to have_content(@success.id)
+ expect(page).to have_content(build_passed.id)
expect(page).to have_content('Deploy')
- expect(page).to have_content(@failed.id)
- expect(page).to have_content(@running.id)
- expect(page).to have_content(@external.id)
+ expect(page).to have_content(build_failed.id)
+ expect(page).to have_content(build_running.id)
+ expect(page).to have_content(build_external.id)
expect(page).to have_content('Retry failed')
expect(page).to have_content('Cancel running')
expect(page).to have_link('Play')
@@ -230,7 +251,7 @@ describe 'Pipeline', :feature, :js do
end
end
- it { expect(@manual.reload).to be_pending }
+ it { expect(build_manual.reload).to be_pending }
end
end
end
diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb
new file mode 100644
index 00000000000..cef315ac9cd
--- /dev/null
+++ b/spec/features/projects/settings/visibility_settings_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+feature 'Visibility settings', feature: true, js: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace, visibility_level: 20) }
+
+ context 'as owner' do
+ before do
+ login_as(user)
+ visit edit_namespace_project_path(project.namespace, project)
+ end
+
+ scenario 'project visibility select is available' do
+ visibility_select_container = find('.js-visibility-select')
+
+ expect(visibility_select_container.find('.visibility-select').value).to eq project.visibility_level.to_s
+ expect(visibility_select_container).to have_content 'The project can be cloned without any authentication.'
+ end
+
+ scenario 'project visibility description updates on change' do
+ visibility_select_container = find('.js-visibility-select')
+ visibility_select = visibility_select_container.find('.visibility-select')
+ visibility_select.select('Private')
+
+ expect(visibility_select.value).to eq '0'
+ expect(visibility_select_container).to have_content 'Project access must be granted explicitly to each user.'
+ end
+ end
+
+ context 'as master' do
+ let(:master_user) { create(:user) }
+
+ before do
+ project.team << [master_user, :master]
+ login_as(master_user)
+ visit edit_namespace_project_path(project.namespace, project)
+ end
+
+ scenario 'project visibility is locked' do
+ visibility_select_container = find('.js-visibility-select')
+
+ expect(visibility_select_container).not_to have_select '.visibility-select'
+ expect(visibility_select_container).to have_content 'Public'
+ expect(visibility_select_container).to have_content 'The project can be cloned without any authentication.'
+ end
+ end
+end
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index 903224589dd..1f221487393 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -62,4 +62,19 @@ describe MergeRequestsHelper do
it { is_expected.to eq([source_title, target_title]) }
end
end
+
+ describe 'mr_widget_refresh_url' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:project) { create(:project) }
+
+ it 'returns correct url for MR' do
+ expected_url = "#{project.path_with_namespace}/merge_requests/#{merge_request.iid}/merge_widget_refresh"
+
+ expect(mr_widget_refresh_url(merge_request)).to end_with(expected_url)
+ end
+
+ it 'returns empty string for nil' do
+ expect(mr_widget_refresh_url(nil)).to end_with('')
+ end
+ end
end
diff --git a/spec/javascripts/activities_spec.js.es6 b/spec/javascripts/activities_spec.js.es6
index b3617a45bd4..7bc5b3268a0 100644
--- a/spec/javascripts/activities_spec.js.es6
+++ b/spec/javascripts/activities_spec.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable no-unused-expressions, comma-spacing, prefer-const, no-prototype-builtins, semi, no-new, keyword-spacing, no-plusplus, no-shadow, max-len */
+/* eslint-disable no-unused-expressions, no-prototype-builtins, no-new, no-shadow, max-len */
/*= require js.cookie.js */
/*= require jquery.endless-scroll.js */
@@ -19,18 +19,18 @@
name: 'merge events',
}, {
id: 'comments',
- },{
+ }, {
id: 'team',
}];
function getEventName(index) {
- let filter = filters[index];
+ const filter = filters[index];
return filter.hasOwnProperty('name') ? filter.name : filter.id;
}
function getSelector(index) {
- let filter = filters[index];
- return `#${filter.id}_event_filter`
+ const filter = filters[index];
+ return `#${filter.id}_event_filter`;
}
describe('Activities', () => {
@@ -39,17 +39,17 @@
new gl.Activities();
});
- for(let i = 0; i < filters.length; i++) {
+ for (let i = 0; i < filters.length; i += 1) {
((i) => {
describe(`when selecting ${getEventName(i)}`, () => {
beforeEach(() => {
$(getSelector(i)).click();
});
- for(let x = 0; x < filters.length; x++) {
+ for (let x = 0; x < filters.length; x += 1) {
((x) => {
- let shouldHighlight = i === x;
- let testName = shouldHighlight ? 'should highlight' : 'should not highlight';
+ const shouldHighlight = i === x;
+ const testName = shouldHighlight ? 'should highlight' : 'should not highlight';
it(`${testName} ${getEventName(x)}`, () => {
expect($(getSelector(x)).parent().hasClass('active')).toEqual(shouldHighlight);
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index faba2837d41..71446b9df61 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, new-parens, no-unused-vars, quotes, jasmine/no-spec-dupes, prefer-template, padded-blocks, max-len */
+/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, new-parens, no-unused-vars, quotes, jasmine/no-spec-dupes, prefer-template, max-len */
/* global AwardsHandler */
/*= require awards_handler */
@@ -231,5 +231,4 @@
});
});
});
-
}).call(this);
diff --git a/spec/javascripts/behaviors/autosize_spec.js b/spec/javascripts/behaviors/autosize_spec.js
index e77d732a32a..51d911792ba 100644
--- a/spec/javascripts/behaviors/autosize_spec.js
+++ b/spec/javascripts/behaviors/autosize_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, no-var, comma-dangle, no-return-assign, padded-blocks, max-len */
+/* eslint-disable space-before-function-paren, no-var, comma-dangle, no-return-assign, max-len */
/*= require behaviors/autosize */
@@ -18,5 +18,4 @@
return $(document).trigger('page:load');
};
});
-
}).call(this);
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
index 1a1f34cfdc0..0f046c2d83a 100644
--- a/spec/javascripts/behaviors/quick_submit_spec.js
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, no-var, no-return-assign, comma-dangle, jasmine/no-spec-dupes, new-cap, padded-blocks, max-len */
+/* eslint-disable space-before-function-paren, no-var, no-return-assign, comma-dangle, jasmine/no-spec-dupes, new-cap, max-len */
/*= require behaviors/quick_submit */
@@ -93,5 +93,4 @@
return $.Event('keydown', $.extend({}, defaults, options));
};
});
-
}).call(this);
diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js
index 1f62591c06d..9467056f04c 100644
--- a/spec/javascripts/behaviors/requires_input_spec.js
+++ b/spec/javascripts/behaviors/requires_input_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, no-var, padded-blocks */
+/* eslint-disable space-before-function-paren, no-var */
/*= require behaviors/requires_input */
@@ -41,5 +41,4 @@
return expect(spy).toHaveBeenCalled();
});
});
-
}).call(this);
diff --git a/spec/javascripts/boards/boards_store_spec.js.es6 b/spec/javascripts/boards/boards_store_spec.js.es6
index b3a1afa28a5..7c5850111cb 100644
--- a/spec/javascripts/boards/boards_store_spec.js.es6
+++ b/spec/javascripts/boards/boards_store_spec.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable comma-dangle, one-var, no-unused-vars, indent */
+/* eslint-disable comma-dangle, one-var, no-unused-vars */
/* global Vue */
/* global BoardService */
/* global boardsMockInterceptor */
@@ -146,8 +146,8 @@ describe('Store', () => {
});
it('moves the position of lists', () => {
- const listOne = gl.issueBoards.BoardsStore.addList(listObj),
- listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
+ const listOne = gl.issueBoards.BoardsStore.addList(listObj);
+ const listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2);
@@ -157,8 +157,8 @@ describe('Store', () => {
});
it('moves an issue from one list to another', (done) => {
- const listOne = gl.issueBoards.BoardsStore.addList(listObj),
- listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
+ const listOne = gl.issueBoards.BoardsStore.addList(listObj);
+ const listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2);
diff --git a/spec/javascripts/dashboard_spec.js.es6 b/spec/javascripts/dashboard_spec.js.es6
index 3f6b328348d..4d851b2d320 100644
--- a/spec/javascripts/dashboard_spec.js.es6
+++ b/spec/javascripts/dashboard_spec.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable no-new, padded-blocks */
+/* eslint-disable no-new */
/*= require sidebar */
/*= require jquery */
@@ -36,5 +36,4 @@
expect(todosCountText()).toEqual('1,000,000');
});
});
-
})(window.gl);
diff --git a/spec/javascripts/diff_comments_store_spec.js.es6 b/spec/javascripts/diff_comments_store_spec.js.es6
index 18805d26ac0..fbfa34a5da7 100644
--- a/spec/javascripts/diff_comments_store_spec.js.es6
+++ b/spec/javascripts/diff_comments_store_spec.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable no-extra-semi, jasmine/no-global-setup, dot-notation, jasmine/no-expect-in-setup-teardown, max-len */
+/* eslint-disable jasmine/no-global-setup, dot-notation, jasmine/no-expect-in-setup-teardown, max-len */
/* global CommentsStore */
//= require vue
@@ -9,7 +9,7 @@
(() => {
function createDiscussion(noteId = 1, resolved = true) {
CommentsStore.create('a', noteId, true, resolved, 'test');
- };
+ }
beforeEach(() => {
CommentsStore.state = {};
diff --git a/spec/javascripts/extensions/array_spec.js.es6 b/spec/javascripts/extensions/array_spec.js.es6
index 2ec759c8e80..3949c5615d5 100644
--- a/spec/javascripts/extensions/array_spec.js.es6
+++ b/spec/javascripts/extensions/array_spec.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, no-var, padded-blocks */
+/* eslint-disable space-before-function-paren, no-var */
/*= require extensions/array */
@@ -42,5 +42,4 @@
});
});
});
-
}).call(this);
diff --git a/spec/javascripts/extensions/jquery_spec.js b/spec/javascripts/extensions/jquery_spec.js
index 91846bb9143..5cd0e5ab0f0 100644
--- a/spec/javascripts/extensions/jquery_spec.js
+++ b/spec/javascripts/extensions/jquery_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, no-var, padded-blocks */
+/* eslint-disable space-before-function-paren, no-var */
/*= require extensions/jquery */
@@ -39,5 +39,4 @@
});
});
});
-
}).call(this);
diff --git a/spec/javascripts/fixtures/emoji_menu.js b/spec/javascripts/fixtures/emoji_menu.js
index 3d776bb9277..2ef242901e8 100644
--- a/spec/javascripts/fixtures/emoji_menu.js
+++ b/spec/javascripts/fixtures/emoji_menu.js
@@ -1,5 +1,4 @@
-/* eslint-disable space-before-function-paren, padded-blocks */
+/* eslint-disable space-before-function-paren */
(function() {
window.emojiMenu = "<div class='emoji-menu'>\n <input type=\"text\" name=\"emoji_search\" id=\"emoji_search\" value=\"\" class=\"emoji-search search-input form-control\" />\n <div class='emoji-menu-content'>\n <h5 class='emoji-menu-title'>\n Emoticons\n </h5>\n <ul class='clearfix emoji-menu-list'>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47D\" title=\"alien\" data-aliases=\"\" data-emoji=\"alien\" data-unicode-name=\"1F47D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47C\" title=\"angel\" data-aliases=\"\" data-emoji=\"angel\" data-unicode-name=\"1F47C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A2\" title=\"anger\" data-aliases=\"\" data-emoji=\"anger\" data-unicode-name=\"1F4A2\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F620\" title=\"angry\" data-aliases=\"\" data-emoji=\"angry\" data-unicode-name=\"1F620\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F627\" title=\"anguished\" data-aliases=\"\" data-emoji=\"anguished\" data-unicode-name=\"1F627\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F632\" title=\"astonished\" data-aliases=\"\" data-emoji=\"astonished\" data-unicode-name=\"1F632\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45F\" title=\"athletic_shoe\" data-aliases=\"\" data-emoji=\"athletic_shoe\" data-unicode-name=\"1F45F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F476\" title=\"baby\" data-aliases=\"\" data-emoji=\"baby\" data-unicode-name=\"1F476\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F459\" title=\"bikini\" data-aliases=\"\" data-emoji=\"bikini\" data-unicode-name=\"1F459\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F499\" title=\"blue_heart\" data-aliases=\"\" data-emoji=\"blue_heart\" data-unicode-name=\"1F499\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60A\" title=\"blush\" data-aliases=\"\" data-emoji=\"blush\" data-unicode-name=\"1F60A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A5\" title=\"boom\" data-aliases=\"\" data-emoji=\"boom\" data-unicode-name=\"1F4A5\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F462\" title=\"boot\" data-aliases=\"\" data-emoji=\"boot\" data-unicode-name=\"1F462\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F647\" title=\"bow\" data-aliases=\"\" data-emoji=\"bow\" data-unicode-name=\"1F647\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F466\" title=\"boy\" data-aliases=\"\" data-emoji=\"boy\" data-unicode-name=\"1F466\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F470\" title=\"bride_with_veil\" data-aliases=\"\" data-emoji=\"bride_with_veil\" data-unicode-name=\"1F470\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4BC\" title=\"briefcase\" data-aliases=\"\" data-emoji=\"briefcase\" data-unicode-name=\"1F4BC\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F494\" title=\"broken_heart\" data-aliases=\"\" data-emoji=\"broken_heart\" data-unicode-name=\"1F494\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F464\" title=\"bust_in_silhouette\" data-aliases=\"\" data-emoji=\"bust_in_silhouette\" data-unicode-name=\"1F464\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F465\" title=\"busts_in_silhouette\" data-aliases=\"\" data-emoji=\"busts_in_silhouette\" data-unicode-name=\"1F465\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44F\" title=\"clap\" data-aliases=\"\" data-emoji=\"clap\" data-unicode-name=\"1F44F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F302\" title=\"closed_umbrella\" data-aliases=\"\" data-emoji=\"closed_umbrella\" data-unicode-name=\"1F302\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F630\" title=\"cold_sweat\" data-aliases=\"\" data-emoji=\"cold_sweat\" data-unicode-name=\"1F630\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F616\" title=\"confounded\" data-aliases=\"\" data-emoji=\"confounded\" data-unicode-name=\"1F616\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F615\" title=\"confused\" data-aliases=\"\" data-emoji=\"confused\" data-unicode-name=\"1F615\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F477\" title=\"construction_worker\" data-aliases=\"\" data-emoji=\"construction_worker\" data-unicode-name=\"1F477\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46E\" title=\"cop\" data-aliases=\"\" data-emoji=\"cop\" data-unicode-name=\"1F46E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46B\" title=\"couple\" data-aliases=\"\" data-emoji=\"couple\" data-unicode-name=\"1F46B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F491\" title=\"couple_with_heart\" data-aliases=\"\" data-emoji=\"couple_with_heart\" data-unicode-name=\"1F491\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48F\" title=\"couplekiss\" data-aliases=\"\" data-emoji=\"couplekiss\" data-unicode-name=\"1F48F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F451\" title=\"crown\" data-aliases=\"\" data-emoji=\"crown\" data-unicode-name=\"1F451\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F622\" title=\"cry\" data-aliases=\"\" data-emoji=\"cry\" data-unicode-name=\"1F622\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63F\" title=\"crying_cat_face\" data-aliases=\"\" data-emoji=\"crying_cat_face\" data-unicode-name=\"1F63F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F498\" title=\"cupid\" data-aliases=\"\" data-emoji=\"cupid\" data-unicode-name=\"1F498\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F483\" title=\"dancer\" data-aliases=\"\" data-emoji=\"dancer\" data-unicode-name=\"1F483\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46F\" title=\"dancers\" data-aliases=\"\" data-emoji=\"dancers\" data-unicode-name=\"1F46F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A8\" title=\"dash\" data-aliases=\"\" data-emoji=\"dash\" data-unicode-name=\"1F4A8\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61E\" title=\"disappointed\" data-aliases=\"\" data-emoji=\"disappointed\" data-unicode-name=\"1F61E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F625\" title=\"disappointed_relieved\" data-aliases=\"\" data-emoji=\"disappointed_relieved\" data-unicode-name=\"1F625\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AB\" title=\"dizzy\" data-aliases=\"\" data-emoji=\"dizzy\" data-unicode-name=\"1F4AB\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F635\" title=\"dizzy_face\" data-aliases=\"\" data-emoji=\"dizzy_face\" data-unicode-name=\"1F635\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F457\" title=\"dress\" data-aliases=\"\" data-emoji=\"dress\" data-unicode-name=\"1F457\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A7\" title=\"droplet\" data-aliases=\"\" data-emoji=\"droplet\" data-unicode-name=\"1F4A7\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F442\" title=\"ear\" data-aliases=\"\" data-emoji=\"ear\" data-unicode-name=\"1F442\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F611\" title=\"expressionless\" data-aliases=\"\" data-emoji=\"expressionless\" data-unicode-name=\"1F611\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F453\" title=\"eyeglasses\" data-aliases=\"\" data-emoji=\"eyeglasses\" data-unicode-name=\"1F453\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F440\" title=\"eyes\" data-aliases=\"\" data-emoji=\"eyes\" data-unicode-name=\"1F440\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46A\" title=\"family\" data-aliases=\"\" data-emoji=\"family\" data-unicode-name=\"1F46A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F628\" title=\"fearful\" data-aliases=\"\" data-emoji=\"fearful\" data-unicode-name=\"1F628\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F525\" title=\"fire\" data-aliases=\":flame:\" data-emoji=\"fire\" data-unicode-name=\"1F525\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270A\" title=\"fist\" data-aliases=\"\" data-emoji=\"fist\" data-unicode-name=\"270A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F633\" title=\"flushed\" data-aliases=\"\" data-emoji=\"flushed\" data-unicode-name=\"1F633\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F463\" title=\"footprints\" data-aliases=\"\" data-emoji=\"footprints\" data-unicode-name=\"1F463\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F626\" title=\"frowning\" data-aliases=\":anguished:\" data-emoji=\"frowning\" data-unicode-name=\"1F626\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48E\" title=\"gem\" data-aliases=\"\" data-emoji=\"gem\" data-unicode-name=\"1F48E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F467\" title=\"girl\" data-aliases=\"\" data-emoji=\"girl\" data-unicode-name=\"1F467\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49A\" title=\"green_heart\" data-aliases=\"\" data-emoji=\"green_heart\" data-unicode-name=\"1F49A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62C\" title=\"grimacing\" data-aliases=\"\" data-emoji=\"grimacing\" data-unicode-name=\"1F62C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F601\" title=\"grin\" data-aliases=\"\" data-emoji=\"grin\" data-unicode-name=\"1F601\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F600\" title=\"grinning\" data-aliases=\"\" data-emoji=\"grinning\" data-unicode-name=\"1F600\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F482\" title=\"guardsman\" data-aliases=\"\" data-emoji=\"guardsman\" data-unicode-name=\"1F482\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F487\" title=\"haircut\" data-aliases=\"\" data-emoji=\"haircut\" data-unicode-name=\"1F487\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45C\" title=\"handbag\" data-aliases=\"\" data-emoji=\"handbag\" data-unicode-name=\"1F45C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F649\" title=\"hear_no_evil\" data-aliases=\"\" data-emoji=\"hear_no_evil\" data-unicode-name=\"1F649\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-2764\" title=\"heart\" data-aliases=\"\" data-emoji=\"heart\" data-unicode-name=\"2764\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60D\" title=\"heart_eyes\" data-aliases=\"\" data-emoji=\"heart_eyes\" data-unicode-name=\"1F60D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63B\" title=\"heart_eyes_cat\" data-aliases=\"\" data-emoji=\"heart_eyes_cat\" data-unicode-name=\"1F63B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F493\" title=\"heartbeat\" data-aliases=\"\" data-emoji=\"heartbeat\" data-unicode-name=\"1F493\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F497\" title=\"heartpulse\" data-aliases=\"\" data-emoji=\"heartpulse\" data-unicode-name=\"1F497\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F460\" title=\"high_heel\" data-aliases=\"\" data-emoji=\"high_heel\" data-unicode-name=\"1F460\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62F\" title=\"hushed\" data-aliases=\"\" data-emoji=\"hushed\" data-unicode-name=\"1F62F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47F\" title=\"imp\" data-aliases=\"\" data-emoji=\"imp\" data-unicode-name=\"1F47F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F481\" title=\"information_desk_person\" data-aliases=\"\" data-emoji=\"information_desk_person\" data-unicode-name=\"1F481\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F607\" title=\"innocent\" data-aliases=\"\" data-emoji=\"innocent\" data-unicode-name=\"1F607\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47A\" title=\"japanese_goblin\" data-aliases=\"\" data-emoji=\"japanese_goblin\" data-unicode-name=\"1F47A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F479\" title=\"japanese_ogre\" data-aliases=\"\" data-emoji=\"japanese_ogre\" data-unicode-name=\"1F479\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F456\" title=\"jeans\" data-aliases=\"\" data-emoji=\"jeans\" data-unicode-name=\"1F456\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F602\" title=\"joy\" data-aliases=\"\" data-emoji=\"joy\" data-unicode-name=\"1F602\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F639\" title=\"joy_cat\" data-aliases=\"\" data-emoji=\"joy_cat\" data-unicode-name=\"1F639\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F458\" title=\"kimono\" data-aliases=\"\" data-emoji=\"kimono\" data-unicode-name=\"1F458\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48B\" title=\"kiss\" data-aliases=\"\" data-emoji=\"kiss\" data-unicode-name=\"1F48B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F617\" title=\"kissing\" data-aliases=\"\" data-emoji=\"kissing\" data-unicode-name=\"1F617\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63D\" title=\"kissing_cat\" data-aliases=\"\" data-emoji=\"kissing_cat\" data-unicode-name=\"1F63D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61A\" title=\"kissing_closed_eyes\" data-aliases=\"\" data-emoji=\"kissing_closed_eyes\" data-unicode-name=\"1F61A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F618\" title=\"kissing_heart\" data-aliases=\"\" data-emoji=\"kissing_heart\" data-unicode-name=\"1F618\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F619\" title=\"kissing_smiling_eyes\" data-aliases=\"\" data-emoji=\"kissing_smiling_eyes\" data-unicode-name=\"1F619\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F606\" title=\"laughing\" data-aliases=\":satisfied:\" data-emoji=\"laughing\" data-unicode-name=\"1F606\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F444\" title=\"lips\" data-aliases=\"\" data-emoji=\"lips\" data-unicode-name=\"1F444\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F484\" title=\"lipstick\" data-aliases=\"\" data-emoji=\"lipstick\" data-unicode-name=\"1F484\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48C\" title=\"love_letter\" data-aliases=\"\" data-emoji=\"love_letter\" data-unicode-name=\"1F48C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F468\" title=\"man\" data-aliases=\"\" data-emoji=\"man\" data-unicode-name=\"1F468\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F472\" title=\"man_with_gua_pi_mao\" data-aliases=\"\" data-emoji=\"man_with_gua_pi_mao\" data-unicode-name=\"1F472\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F473\" title=\"man_with_turban\" data-aliases=\"\" data-emoji=\"man_with_turban\" data-unicode-name=\"1F473\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45E\" title=\"mans_shoe\" data-aliases=\"\" data-emoji=\"mans_shoe\" data-unicode-name=\"1F45E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F637\" title=\"mask\" data-aliases=\"\" data-emoji=\"mask\" data-unicode-name=\"1F637\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F486\" title=\"massage\" data-aliases=\"\" data-emoji=\"massage\" data-unicode-name=\"1F486\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AA\" title=\"muscle\" data-aliases=\"\" data-emoji=\"muscle\" data-unicode-name=\"1F4AA\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F485\" title=\"nail_care\" data-aliases=\"\" data-emoji=\"nail_care\" data-unicode-name=\"1F485\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F454\" title=\"necktie\" data-aliases=\"\" data-emoji=\"necktie\" data-unicode-name=\"1F454\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F610\" title=\"neutral_face\" data-aliases=\"\" data-emoji=\"neutral_face\" data-unicode-name=\"1F610\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F645\" title=\"no_good\" data-aliases=\"\" data-emoji=\"no_good\" data-unicode-name=\"1F645\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F636\" title=\"no_mouth\" data-aliases=\"\" data-emoji=\"no_mouth\" data-unicode-name=\"1F636\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F443\" title=\"nose\" data-aliases=\"\" data-emoji=\"nose\" data-unicode-name=\"1F443\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44C\" title=\"ok_hand\" data-aliases=\"\" data-emoji=\"ok_hand\" data-unicode-name=\"1F44C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F646\" title=\"ok_woman\" data-aliases=\"\" data-emoji=\"ok_woman\" data-unicode-name=\"1F646\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F474\" title=\"older_man\" data-aliases=\"\" data-emoji=\"older_man\" data-unicode-name=\"1F474\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F475\" title=\"older_woman\" data-aliases=\":grandma:\" data-emoji=\"older_woman\" data-unicode-name=\"1F475\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F450\" title=\"open_hands\" data-aliases=\"\" data-emoji=\"open_hands\" data-unicode-name=\"1F450\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62E\" title=\"open_mouth\" data-aliases=\"\" data-emoji=\"open_mouth\" data-unicode-name=\"1F62E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F614\" title=\"pensive\" data-aliases=\"\" data-emoji=\"pensive\" data-unicode-name=\"1F614\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F623\" title=\"persevere\" data-aliases=\"\" data-emoji=\"persevere\" data-unicode-name=\"1F623\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64D\" title=\"person_frowning\" data-aliases=\"\" data-emoji=\"person_frowning\" data-unicode-name=\"1F64D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F471\" title=\"person_with_blond_hair\" data-aliases=\"\" data-emoji=\"person_with_blond_hair\" data-unicode-name=\"1F471\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64E\" title=\"person_with_pouting_face\" data-aliases=\"\" data-emoji=\"person_with_pouting_face\" data-unicode-name=\"1F64E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F447\" title=\"point_down\" data-aliases=\"\" data-emoji=\"point_down\" data-unicode-name=\"1F447\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F448\" title=\"point_left\" data-aliases=\"\" data-emoji=\"point_left\" data-unicode-name=\"1F448\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F449\" title=\"point_right\" data-aliases=\"\" data-emoji=\"point_right\" data-unicode-name=\"1F449\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-261D\" title=\"point_up\" data-aliases=\"\" data-emoji=\"point_up\" data-unicode-name=\"261D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F446\" title=\"point_up_2\" data-aliases=\"\" data-emoji=\"point_up_2\" data-unicode-name=\"1F446\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A9\" title=\"poop\" data-aliases=\":shit: :hankey: :poo:\" data-emoji=\"poop\" data-unicode-name=\"1F4A9\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45D\" title=\"pouch\" data-aliases=\"\" data-emoji=\"pouch\" data-unicode-name=\"1F45D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63E\" title=\"pouting_cat\" data-aliases=\"\" data-emoji=\"pouting_cat\" data-unicode-name=\"1F63E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64F\" title=\"pray\" data-aliases=\"\" data-emoji=\"pray\" data-unicode-name=\"1F64F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F478\" title=\"princess\" data-aliases=\"\" data-emoji=\"princess\" data-unicode-name=\"1F478\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44A\" title=\"punch\" data-aliases=\"\" data-emoji=\"punch\" data-unicode-name=\"1F44A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49C\" title=\"purple_heart\" data-aliases=\"\" data-emoji=\"purple_heart\" data-unicode-name=\"1F49C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45B\" title=\"purse\" data-aliases=\"\" data-emoji=\"purse\" data-unicode-name=\"1F45B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F621\" title=\"rage\" data-aliases=\"\" data-emoji=\"rage\" data-unicode-name=\"1F621\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270B\" title=\"raised_hand\" data-aliases=\"\" data-emoji=\"raised_hand\" data-unicode-name=\"270B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64C\" title=\"raised_hands\" data-aliases=\"\" data-emoji=\"raised_hands\" data-unicode-name=\"1F64C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64B\" title=\"raising_hand\" data-aliases=\"\" data-emoji=\"raising_hand\" data-unicode-name=\"1F64B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-263A\" title=\"relaxed\" data-aliases=\"\" data-emoji=\"relaxed\" data-unicode-name=\"263A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60C\" title=\"relieved\" data-aliases=\"\" data-emoji=\"relieved\" data-unicode-name=\"1F60C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49E\" title=\"revolving_hearts\" data-aliases=\"\" data-emoji=\"revolving_hearts\" data-unicode-name=\"1F49E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F380\" title=\"ribbon\" data-aliases=\"\" data-emoji=\"ribbon\" data-unicode-name=\"1F380\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48D\" title=\"ring\" data-aliases=\"\" data-emoji=\"ring\" data-unicode-name=\"1F48D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3C3\" title=\"runner\" data-aliases=\"\" data-emoji=\"runner\" data-unicode-name=\"1F3C3\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3BD\" title=\"running_shirt_with_sash\" data-aliases=\"\" data-emoji=\"running_shirt_with_sash\" data-unicode-name=\"1F3BD\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F461\" title=\"sandal\" data-aliases=\"\" data-emoji=\"sandal\" data-unicode-name=\"1F461\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F631\" title=\"scream\" data-aliases=\"\" data-emoji=\"scream\" data-unicode-name=\"1F631\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F640\" title=\"scream_cat\" data-aliases=\"\" data-emoji=\"scream_cat\" data-unicode-name=\"1F640\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F648\" title=\"see_no_evil\" data-aliases=\"\" data-emoji=\"see_no_evil\" data-unicode-name=\"1F648\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F455\" title=\"shirt\" data-aliases=\"\" data-emoji=\"shirt\" data-unicode-name=\"1F455\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F480\" title=\"skull\" data-aliases=\":skeleton:\" data-emoji=\"skull\" data-unicode-name=\"1F480\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F634\" title=\"sleeping\" data-aliases=\"\" data-emoji=\"sleeping\" data-unicode-name=\"1F634\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62A\" title=\"sleepy\" data-aliases=\"\" data-emoji=\"sleepy\" data-unicode-name=\"1F62A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F604\" title=\"smile\" data-aliases=\"\" data-emoji=\"smile\" data-unicode-name=\"1F604\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F638\" title=\"smile_cat\" data-aliases=\"\" data-emoji=\"smile_cat\" data-unicode-name=\"1F638\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F603\" title=\"smiley\" data-aliases=\"\" data-emoji=\"smiley\" data-unicode-name=\"1F603\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63A\" title=\"smiley_cat\" data-aliases=\"\" data-emoji=\"smiley_cat\" data-unicode-name=\"1F63A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F608\" title=\"smiling_imp\" data-aliases=\"\" data-emoji=\"smiling_imp\" data-unicode-name=\"1F608\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60F\" title=\"smirk\" data-aliases=\"\" data-emoji=\"smirk\" data-unicode-name=\"1F60F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63C\" title=\"smirk_cat\" data-aliases=\"\" data-emoji=\"smirk_cat\" data-unicode-name=\"1F63C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62D\" title=\"sob\" data-aliases=\"\" data-emoji=\"sob\" data-unicode-name=\"1F62D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-2728\" title=\"sparkles\" data-aliases=\"\" data-emoji=\"sparkles\" data-unicode-name=\"2728\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F496\" title=\"sparkling_heart\" data-aliases=\"\" data-emoji=\"sparkling_heart\" data-unicode-name=\"1F496\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64A\" title=\"speak_no_evil\" data-aliases=\"\" data-emoji=\"speak_no_evil\" data-unicode-name=\"1F64A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AC\" title=\"speech_balloon\" data-aliases=\"\" data-emoji=\"speech_balloon\" data-unicode-name=\"1F4AC\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F31F\" title=\"star2\" data-aliases=\"\" data-emoji=\"star2\" data-unicode-name=\"1F31F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61B\" title=\"stuck_out_tongue\" data-aliases=\"\" data-emoji=\"stuck_out_tongue\" data-unicode-name=\"1F61B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61D\" title=\"stuck_out_tongue_closed_eyes\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_closed_eyes\" data-unicode-name=\"1F61D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61C\" title=\"stuck_out_tongue_winking_eye\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_winking_eye\" data-unicode-name=\"1F61C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60E\" title=\"sunglasses\" data-aliases=\"\" data-emoji=\"sunglasses\" data-unicode-name=\"1F60E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F613\" title=\"sweat\" data-aliases=\"\" data-emoji=\"sweat\" data-unicode-name=\"1F613\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A6\" title=\"sweat_drops\" data-aliases=\"\" data-emoji=\"sweat_drops\" data-unicode-name=\"1F4A6\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F605\" title=\"sweat_smile\" data-aliases=\"\" data-emoji=\"sweat_smile\" data-unicode-name=\"1F605\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AD\" title=\"thought_balloon\" data-aliases=\"\" data-emoji=\"thought_balloon\" data-unicode-name=\"1F4AD\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44E\" title=\"thumbsdown\" data-aliases=\":-1:\" data-emoji=\"thumbsdown\" data-unicode-name=\"1F44E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44D\" title=\"thumbsup\" data-aliases=\":+1:\" data-emoji=\"thumbsup\" data-unicode-name=\"1F44D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62B\" title=\"tired_face\" data-aliases=\"\" data-emoji=\"tired_face\" data-unicode-name=\"1F62B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F445\" title=\"tongue\" data-aliases=\"\" data-emoji=\"tongue\" data-unicode-name=\"1F445\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3A9\" title=\"tophat\" data-aliases=\"\" data-emoji=\"tophat\" data-unicode-name=\"1F3A9\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F624\" title=\"triumph\" data-aliases=\"\" data-emoji=\"triumph\" data-unicode-name=\"1F624\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F495\" title=\"two_hearts\" data-aliases=\"\" data-emoji=\"two_hearts\" data-unicode-name=\"1F495\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46C\" title=\"two_men_holding_hands\" data-aliases=\"\" data-emoji=\"two_men_holding_hands\" data-unicode-name=\"1F46C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46D\" title=\"two_women_holding_hands\" data-aliases=\"\" data-emoji=\"two_women_holding_hands\" data-unicode-name=\"1F46D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F612\" title=\"unamused\" data-aliases=\"\" data-emoji=\"unamused\" data-unicode-name=\"1F612\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270C\" title=\"v\" data-aliases=\"\" data-emoji=\"v\" data-unicode-name=\"270C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F6B6\" title=\"walking\" data-aliases=\"\" data-emoji=\"walking\" data-unicode-name=\"1F6B6\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44B\" title=\"wave\" data-aliases=\"\" data-emoji=\"wave\" data-unicode-name=\"1F44B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F629\" title=\"weary\" data-aliases=\"\" data-emoji=\"weary\" data-unicode-name=\"1F629\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F609\" title=\"wink\" data-aliases=\"\" data-emoji=\"wink\" data-unicode-name=\"1F609\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F469\" title=\"woman\" data-aliases=\"\" data-emoji=\"woman\" data-unicode-name=\"1F469\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45A\" title=\"womans_clothes\" data-aliases=\"\" data-emoji=\"womans_clothes\" data-unicode-name=\"1F45A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F452\" title=\"womans_hat\" data-aliases=\"\" data-emoji=\"womans_hat\" data-unicode-name=\"1F452\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61F\" title=\"worried\" data-aliases=\"\" data-emoji=\"worried\" data-unicode-name=\"1F61F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49B\" title=\"yellow_heart\" data-aliases=\"\" data-emoji=\"yellow_heart\" data-unicode-name=\"1F49B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60B\" title=\"yum\" data-aliases=\"\" data-emoji=\"yum\" data-unicode-name=\"1F60B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A4\" title=\"zzz\" data-aliases=\"\" data-emoji=\"zzz\" data-unicode-name=\"1F4A4\"></div>\n </button>\n </li>\n </ul>\n </div>\n</div>";
-
}).call(this);
diff --git a/spec/javascripts/gfm_auto_complete_spec.js.es6 b/spec/javascripts/gfm_auto_complete_spec.js.es6
new file mode 100644
index 00000000000..6b48d82cb23
--- /dev/null
+++ b/spec/javascripts/gfm_auto_complete_spec.js.es6
@@ -0,0 +1,65 @@
+//= require gfm_auto_complete
+//= require jquery
+//= require jquery.atwho
+
+const global = window.gl || (window.gl = {});
+const GfmAutoComplete = global.GfmAutoComplete;
+
+describe('GfmAutoComplete', function () {
+ describe('DefaultOptions.sorter', function () {
+ describe('assets loading', function () {
+ beforeEach(function () {
+ spyOn(GfmAutoComplete, 'isLoading').and.returnValue(true);
+
+ this.atwhoInstance = { setting: {} };
+ this.items = [];
+
+ this.sorterValue = GfmAutoComplete.DefaultOptions.sorter
+ .call(this.atwhoInstance, '', this.items);
+ });
+
+ it('should disable highlightFirst', function () {
+ expect(this.atwhoInstance.setting.highlightFirst).toBe(false);
+ });
+
+ it('should return the passed unfiltered items', function () {
+ expect(this.sorterValue).toEqual(this.items);
+ });
+ });
+
+ describe('assets finished loading', function () {
+ beforeEach(function () {
+ spyOn(GfmAutoComplete, 'isLoading').and.returnValue(false);
+ spyOn($.fn.atwho.default.callbacks, 'sorter');
+ });
+
+ it('should enable highlightFirst if alwaysHighlightFirst is set', function () {
+ const atwhoInstance = { setting: { alwaysHighlightFirst: true } };
+
+ GfmAutoComplete.DefaultOptions.sorter.call(atwhoInstance);
+
+ expect(atwhoInstance.setting.highlightFirst).toBe(true);
+ });
+
+ it('should enable highlightFirst if a query is present', function () {
+ const atwhoInstance = { setting: {} };
+
+ GfmAutoComplete.DefaultOptions.sorter.call(atwhoInstance, 'query');
+
+ expect(atwhoInstance.setting.highlightFirst).toBe(true);
+ });
+
+ it('should call the default atwho sorter', function () {
+ const atwhoInstance = { setting: {} };
+
+ const query = 'query';
+ const items = [];
+ const searchKey = 'searchKey';
+
+ GfmAutoComplete.DefaultOptions.sorter.call(atwhoInstance, query, items, searchKey);
+
+ expect($.fn.atwho.default.callbacks.sorter).toHaveBeenCalledWith(query, items, searchKey);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/gl_dropdown_spec.js.es6 b/spec/javascripts/gl_dropdown_spec.js.es6
index d11b1182d9a..06fa64b1b4e 100644
--- a/spec/javascripts/gl_dropdown_spec.js.es6
+++ b/spec/javascripts/gl_dropdown_spec.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable comma-dangle, prefer-const, no-param-reassign, no-plusplus, semi, no-unused-expressions, arrow-spacing, max-len */
+/* eslint-disable comma-dangle, no-param-reassign, no-unused-expressions, max-len */
/* global Turbolinks */
/*= require jquery */
@@ -22,7 +22,7 @@
let remoteCallback;
- let navigateWithKeys = function navigateWithKeys(direction, steps, cb, i) {
+ const navigateWithKeys = function navigateWithKeys(direction, steps, cb, i) {
i = i || 0;
if (!i) direction = direction.toUpperCase();
$('body').trigger({
@@ -30,7 +30,7 @@
which: ARROW_KEYS[direction],
keyCode: ARROW_KEYS[direction]
});
- i++;
+ i += 1;
if (i <= steps) {
navigateWithKeys(direction, steps, cb, i);
} else {
@@ -38,9 +38,9 @@
}
};
- let remoteMock = function remoteMock(data, term, callback) {
+ const remoteMock = function remoteMock(data, term, callback) {
remoteCallback = callback.bind({}, data);
- }
+ };
describe('Dropdown', function describeDropdown() {
preloadFixtures('static/gl_dropdown.html.raw');
@@ -89,7 +89,7 @@
it('should select a following item on DOWN keypress', () => {
expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(0);
- let randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 1)) + 0);
+ const randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 1)) + 0);
navigateWithKeys('down', randomIndex, () => {
expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1);
expect($(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement)).toHaveClass('is-focused');
@@ -100,7 +100,7 @@
expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(0);
navigateWithKeys('down', (this.projectsData.length - 1), () => {
expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1);
- let randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 2)) + 0);
+ const randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 2)) + 0);
navigateWithKeys('up', randomIndex, () => {
expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1);
expect($(`${ITEM_SELECTOR}:eq(${((this.projectsData.length - 2) - randomIndex)}) a`, this.$dropdownMenuElement)).toHaveClass('is-focused');
@@ -109,15 +109,15 @@
});
it('should click the selected item on ENTER keypress', () => {
- expect(this.dropdownContainerElement).toHaveClass('open')
- let randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0
+ expect(this.dropdownContainerElement).toHaveClass('open');
+ const randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0;
navigateWithKeys('down', randomIndex, () => {
spyOn(Turbolinks, 'visit').and.stub();
navigateWithKeys('enter', null, () => {
expect(this.dropdownContainerElement).not.toHaveClass('open');
- let link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement);
+ const link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement);
expect(link).toHaveClass('is-active');
- let linkedLocation = link.attr('href');
+ const linkedLocation = link.attr('href');
if (linkedLocation && linkedLocation !== '#') expect(Turbolinks.visit).toHaveBeenCalledWith(linkedLocation);
});
});
@@ -140,18 +140,18 @@
this.dropdownButtonElement.click();
});
- it('should not focus search input while remote task is not complete', ()=> {
+ it('should not focus search input while remote task is not complete', () => {
expect($(document.activeElement)).not.toEqual($(SEARCH_INPUT_SELECTOR));
remoteCallback();
expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
});
- it('should focus search input after remote task is complete', ()=> {
+ it('should focus search input after remote task is complete', () => {
remoteCallback();
expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
});
- it('should focus on input when opening for the second time', ()=> {
+ it('should focus on input when opening for the second time', () => {
remoteCallback();
this.dropdownContainerElement.trigger({
type: 'keyup',
@@ -164,16 +164,15 @@
});
describe('input focus with array data', () => {
- it('should focus input when passing array data to drop down', ()=> {
+ it('should focus input when passing array data to drop down', () => {
initDropDown.call(this, false, true);
this.dropdownButtonElement.click();
expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
});
});
-
it('should still have input value on close and restore', () => {
- let $searchInput = $(SEARCH_INPUT_SELECTOR);
+ const $searchInput = $(SEARCH_INPUT_SELECTOR);
initDropDown.call(this, false, true);
$searchInput
.trigger('focus')
diff --git a/spec/javascripts/gl_field_errors_spec.js.es6 b/spec/javascripts/gl_field_errors_spec.js.es6
index e5d934540af..f68fd9e00d7 100644
--- a/spec/javascripts/gl_field_errors_spec.js.es6
+++ b/spec/javascripts/gl_field_errors_spec.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, arrow-body-style, indent, padded-blocks */
+/* eslint-disable space-before-function-paren, arrow-body-style */
//= require jquery
//= require gl_field_errors
@@ -28,7 +28,7 @@
expect(customErrorElem.length).toBe(1);
const customErrors = this.fieldErrors.state.inputs.filter((input) => {
- return input.inputElement.hasClass(customErrorFlag);
+ return input.inputElement.hasClass(customErrorFlag);
});
expect(customErrors.length).toBe(0);
});
@@ -107,7 +107,5 @@
expect(noTitleErrorElem.text()).toBe('This field is required.');
expect(hasTitleErrorElem.text()).toBe('Please provide a valid email address.');
});
-
});
-
})(window.gl || (window.gl = {}));
diff --git a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
index bc5cbeb6a40..d76fcc5206a 100644
--- a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
+++ b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable quotes, indent, semi, object-curly-spacing, jasmine/no-suite-dupes, vars-on-top, no-var, padded-blocks, spaced-comment, max-len */
+/* eslint-disable quotes, jasmine/no-suite-dupes, vars-on-top, no-var, max-len */
/* global d3 */
/* global ContributorsGraph */
/* global ContributorsMasterGraph */
@@ -8,126 +8,123 @@
describe("ContributorsGraph", function () {
describe("#set_x_domain", function () {
it("set the x_domain", function () {
- ContributorsGraph.set_x_domain(20)
- expect(ContributorsGraph.prototype.x_domain).toEqual(20)
- })
- })
+ ContributorsGraph.set_x_domain(20);
+ expect(ContributorsGraph.prototype.x_domain).toEqual(20);
+ });
+ });
describe("#set_y_domain", function () {
it("sets the y_domain", function () {
- ContributorsGraph.set_y_domain([{commits: 30}])
- expect(ContributorsGraph.prototype.y_domain).toEqual([0, 30])
- })
- })
+ ContributorsGraph.set_y_domain([{ commits: 30 }]);
+ expect(ContributorsGraph.prototype.y_domain).toEqual([0, 30]);
+ });
+ });
describe("#init_x_domain", function () {
it("sets the initial x_domain", function () {
- ContributorsGraph.init_x_domain([{date: "2013-01-31"}, {date: "2012-01-31"}])
- expect(ContributorsGraph.prototype.x_domain).toEqual(["2012-01-31", "2013-01-31"])
- })
- })
+ ContributorsGraph.init_x_domain([{ date: "2013-01-31" }, { date: "2012-01-31" }]);
+ expect(ContributorsGraph.prototype.x_domain).toEqual(["2012-01-31", "2013-01-31"]);
+ });
+ });
describe("#init_y_domain", function () {
it("sets the initial y_domain", function () {
- ContributorsGraph.init_y_domain([{commits: 30}])
- expect(ContributorsGraph.prototype.y_domain).toEqual([0, 30])
- })
- })
+ ContributorsGraph.init_y_domain([{ commits: 30 }]);
+ expect(ContributorsGraph.prototype.y_domain).toEqual([0, 30]);
+ });
+ });
describe("#init_domain", function () {
it("calls init_x_domain and init_y_domain", function () {
- spyOn(ContributorsGraph, "init_x_domain")
- spyOn(ContributorsGraph, "init_y_domain")
- ContributorsGraph.init_domain()
- expect(ContributorsGraph.init_x_domain).toHaveBeenCalled()
- expect(ContributorsGraph.init_y_domain).toHaveBeenCalled()
- })
- })
+ spyOn(ContributorsGraph, "init_x_domain");
+ spyOn(ContributorsGraph, "init_y_domain");
+ ContributorsGraph.init_domain();
+ expect(ContributorsGraph.init_x_domain).toHaveBeenCalled();
+ expect(ContributorsGraph.init_y_domain).toHaveBeenCalled();
+ });
+ });
describe("#set_dates", function () {
it("sets the dates", function () {
- ContributorsGraph.set_dates("2013-12-01")
- expect(ContributorsGraph.prototype.dates).toEqual("2013-12-01")
- })
- })
+ ContributorsGraph.set_dates("2013-12-01");
+ expect(ContributorsGraph.prototype.dates).toEqual("2013-12-01");
+ });
+ });
describe("#set_x_domain", function () {
it("sets the instance's x domain using the prototype's x_domain", function () {
- ContributorsGraph.prototype.x_domain = 20
- var instance = new ContributorsGraph()
- instance.x = d3.time.scale().range([0, 100]).clamp(true)
- spyOn(instance.x, 'domain')
- instance.set_x_domain()
- expect(instance.x.domain).toHaveBeenCalledWith(20)
- })
- })
+ ContributorsGraph.prototype.x_domain = 20;
+ var instance = new ContributorsGraph();
+ instance.x = d3.time.scale().range([0, 100]).clamp(true);
+ spyOn(instance.x, 'domain');
+ instance.set_x_domain();
+ expect(instance.x.domain).toHaveBeenCalledWith(20);
+ });
+ });
describe("#set_y_domain", function () {
it("sets the instance's y domain using the prototype's y_domain", function () {
- ContributorsGraph.prototype.y_domain = 30
- var instance = new ContributorsGraph()
- instance.y = d3.scale.linear().range([100, 0]).nice()
- spyOn(instance.y, 'domain')
- instance.set_y_domain()
- expect(instance.y.domain).toHaveBeenCalledWith(30)
- })
- })
+ ContributorsGraph.prototype.y_domain = 30;
+ var instance = new ContributorsGraph();
+ instance.y = d3.scale.linear().range([100, 0]).nice();
+ spyOn(instance.y, 'domain');
+ instance.set_y_domain();
+ expect(instance.y.domain).toHaveBeenCalledWith(30);
+ });
+ });
describe("#set_domain", function () {
it("calls set_x_domain and set_y_domain", function () {
- var instance = new ContributorsGraph()
- spyOn(instance, 'set_x_domain')
- spyOn(instance, 'set_y_domain')
- instance.set_domain()
- expect(instance.set_x_domain).toHaveBeenCalled()
- expect(instance.set_y_domain).toHaveBeenCalled()
- })
- })
+ var instance = new ContributorsGraph();
+ spyOn(instance, 'set_x_domain');
+ spyOn(instance, 'set_y_domain');
+ instance.set_domain();
+ expect(instance.set_x_domain).toHaveBeenCalled();
+ expect(instance.set_y_domain).toHaveBeenCalled();
+ });
+ });
describe("#set_data", function () {
it("sets the data", function () {
- var instance = new ContributorsGraph()
- instance.set_data("20")
- expect(instance.data).toEqual("20")
- })
- })
-})
+ var instance = new ContributorsGraph();
+ instance.set_data("20");
+ expect(instance.data).toEqual("20");
+ });
+ });
+});
describe("ContributorsMasterGraph", function () {
-
// TODO: fix or remove
- //describe("#process_dates", function () {
- //it("gets and parses dates", function () {
- //var graph = new ContributorsMasterGraph()
- //var data = 'random data here'
- //spyOn(graph, 'parse_dates')
- //spyOn(graph, 'get_dates').andReturn("get")
- //spyOn(ContributorsGraph,'set_dates').andCallThrough()
- //graph.process_dates(data)
- //expect(graph.parse_dates).toHaveBeenCalledWith(data)
- //expect(graph.get_dates).toHaveBeenCalledWith(data)
- //expect(ContributorsGraph.set_dates).toHaveBeenCalledWith("get")
- //})
- //})
+ // describe("#process_dates", function () {
+ // it("gets and parses dates", function () {
+ // var graph = new ContributorsMasterGraph();
+ // var data = 'random data here';
+ // spyOn(graph, 'parse_dates');
+ // spyOn(graph, 'get_dates').andReturn("get");
+ // spyOn(ContributorsGraph,'set_dates').andCallThrough();
+ // graph.process_dates(data);
+ // expect(graph.parse_dates).toHaveBeenCalledWith(data);
+ // expect(graph.get_dates).toHaveBeenCalledWith(data);
+ // expect(ContributorsGraph.set_dates).toHaveBeenCalledWith("get");
+ // });
+ // });
describe("#get_dates", function () {
it("plucks the date field from data collection", function () {
- var graph = new ContributorsMasterGraph()
- var data = [{date: "2013-01-01"}, {date: "2012-12-15"}]
- expect(graph.get_dates(data)).toEqual(["2013-01-01", "2012-12-15"])
- })
- })
+ var graph = new ContributorsMasterGraph();
+ var data = [{ date: "2013-01-01" }, { date: "2012-12-15" }];
+ expect(graph.get_dates(data)).toEqual(["2013-01-01", "2012-12-15"]);
+ });
+ });
describe("#parse_dates", function () {
it("parses the dates", function () {
- var graph = new ContributorsMasterGraph()
- var parseDate = d3.time.format("%Y-%m-%d").parse
- var data = [{date: "2013-01-01"}, {date: "2012-12-15"}]
- var correct = [{date: parseDate(data[0].date)}, {date: parseDate(data[1].date)}]
- graph.parse_dates(data)
- expect(data).toEqual(correct)
- })
- })
-
-
-})
+ var graph = new ContributorsMasterGraph();
+ var parseDate = d3.time.format("%Y-%m-%d").parse;
+ var data = [{ date: "2013-01-01" }, { date: "2012-12-15" }];
+ var correct = [{ date: parseDate(data[0].date) }, { date: parseDate(data[1].date) }];
+ graph.parse_dates(data);
+ expect(data).toEqual(correct);
+ });
+ });
+});
diff --git a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
index 751f3d175e2..63f28dfb8ad 100644
--- a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
+++ b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
@@ -1,215 +1,218 @@
-/* eslint-disable quotes, padded-blocks, no-var, camelcase, object-curly-spacing, semi, indent, object-property-newline, comma-dangle, comma-spacing, spaced-comment, max-len, key-spacing, vars-on-top, quote-props, no-multi-spaces */
+/* eslint-disable quotes, no-var, camelcase, object-property-newline, comma-dangle, max-len, vars-on-top, quote-props */
/* global ContributorsStatGraphUtil */
//= require graphs/stat_graph_contributors_util
describe("ContributorsStatGraphUtil", function () {
-
describe("#parse_log", function () {
it("returns a correctly parsed log", function () {
var fake_log = [
- {author_email: "karlo@email.com", author_name: "Karlo Soriano", date: "2013-05-09", additions: 471},
- {author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 6, deletions: 1},
- {author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 19, deletions: 3},
- {author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 29, deletions: 3}]
+ { author_email: "karlo@email.com", author_name: "Karlo Soriano", date: "2013-05-09", additions: 471 },
+ { author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 6, deletions: 1 },
+ { author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 19, deletions: 3 },
+ { author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 29, deletions: 3 }
+ ];
var correct_parsed_log = {
total: [
- {date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
- {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
- by_author:
- [
- {
- author_name: "Karlo Soriano", author_email: "karlo@email.com",
- "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
- },
- {
- author_name: "Dmitriy Zaporozhets",author_email: "dzaporozhets@email.com",
- "2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}
- }
+ { date: "2013-05-09", additions: 471, deletions: 0, commits: 1 },
+ { date: "2013-05-08", additions: 54, deletions: 7, commits: 3 }
+ ],
+ by_author: [
+ {
+ author_name: "Karlo Soriano", author_email: "karlo@email.com",
+ "2013-05-09": { date: "2013-05-09", additions: 471, deletions: 0, commits: 1 }
+ },
+ {
+ author_name: "Dmitriy Zaporozhets", author_email: "dzaporozhets@email.com",
+ "2013-05-08": { date: "2013-05-08", additions: 54, deletions: 7, commits: 3 }
+ }
]
- }
- expect(ContributorsStatGraphUtil.parse_log(fake_log)).toEqual(correct_parsed_log)
- })
- })
+ };
+ expect(ContributorsStatGraphUtil.parse_log(fake_log)).toEqual(correct_parsed_log);
+ });
+ });
describe("#store_data", function () {
-
- var fake_entry = {author: "Karlo Soriano", date: "2013-05-09", additions: 471}
- var fake_total = {}
- var fake_by_author = {}
+ var fake_entry = { author: "Karlo Soriano", date: "2013-05-09", additions: 471 };
+ var fake_total = {};
+ var fake_by_author = {};
it("calls #store_commits", function () {
- spyOn(ContributorsStatGraphUtil, 'store_commits')
- ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author)
- expect(ContributorsStatGraphUtil.store_commits).toHaveBeenCalled()
- })
+ spyOn(ContributorsStatGraphUtil, 'store_commits');
+ ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author);
+ expect(ContributorsStatGraphUtil.store_commits).toHaveBeenCalled();
+ });
it("calls #store_additions", function () {
- spyOn(ContributorsStatGraphUtil, 'store_additions')
- ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author)
- expect(ContributorsStatGraphUtil.store_additions).toHaveBeenCalled()
- })
+ spyOn(ContributorsStatGraphUtil, 'store_additions');
+ ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author);
+ expect(ContributorsStatGraphUtil.store_additions).toHaveBeenCalled();
+ });
it("calls #store_deletions", function () {
- spyOn(ContributorsStatGraphUtil, 'store_deletions')
- ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author)
- expect(ContributorsStatGraphUtil.store_deletions).toHaveBeenCalled()
- })
-
- })
+ spyOn(ContributorsStatGraphUtil, 'store_deletions');
+ ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author);
+ expect(ContributorsStatGraphUtil.store_deletions).toHaveBeenCalled();
+ });
+ });
// TODO: fix or remove
- //describe("#store_commits", function () {
- //var fake_total = "fake_total"
- //var fake_by_author = "fake_by_author"
-
- //it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
- //spyOn(ContributorsStatGraphUtil, 'add')
- //ContributorsStatGraphUtil.store_commits(fake_total, fake_by_author)
- //expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "commits", 1], ["fake_by_author", "commits", 1]])
- //})
- //})
+ // describe("#store_commits", function () {
+ // var fake_total = "fake_total";
+ // var fake_by_author = "fake_by_author";
+ //
+ // it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
+ // spyOn(ContributorsStatGraphUtil, 'add');
+ // ContributorsStatGraphUtil.store_commits(fake_total, fake_by_author);
+ // expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "commits", 1], ["fake_by_author", "commits", 1]]);
+ // });
+ // });
describe("#add", function () {
it("adds 1 to current test_field in collection", function () {
- var fake_collection = {test_field: 10}
- ContributorsStatGraphUtil.add(fake_collection, "test_field", 1)
- expect(fake_collection.test_field).toEqual(11)
- })
+ var fake_collection = { test_field: 10 };
+ ContributorsStatGraphUtil.add(fake_collection, "test_field", 1);
+ expect(fake_collection.test_field).toEqual(11);
+ });
it("inits and adds 1 if test_field in collection is not defined", function () {
- var fake_collection = {}
- ContributorsStatGraphUtil.add(fake_collection, "test_field", 1)
- expect(fake_collection.test_field).toEqual(1)
- })
- })
+ var fake_collection = {};
+ ContributorsStatGraphUtil.add(fake_collection, "test_field", 1);
+ expect(fake_collection.test_field).toEqual(1);
+ });
+ });
// TODO: fix or remove
- //describe("#store_additions", function () {
- //var fake_entry = {additions: 10}
- //var fake_total= "fake_total"
- //var fake_by_author = "fake_by_author"
- //it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
- //spyOn(ContributorsStatGraphUtil, 'add')
- //ContributorsStatGraphUtil.store_additions(fake_entry, fake_total, fake_by_author)
- //expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "additions", 10], ["fake_by_author", "additions", 10]])
- //})
- //})
+ // describe("#store_additions", function () {
+ // var fake_entry = {additions: 10};
+ // var fake_total= "fake_total";
+ // var fake_by_author = "fake_by_author";
+ // it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
+ // spyOn(ContributorsStatGraphUtil, 'add');
+ // ContributorsStatGraphUtil.store_additions(fake_entry, fake_total, fake_by_author);
+ // expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "additions", 10], ["fake_by_author", "additions", 10]]);
+ // });
+ // });
// TODO: fix or remove
- //describe("#store_deletions", function () {
- //var fake_entry = {deletions: 10}
- //var fake_total= "fake_total"
- //var fake_by_author = "fake_by_author"
- //it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
- //spyOn(ContributorsStatGraphUtil, 'add')
- //ContributorsStatGraphUtil.store_deletions(fake_entry, fake_total, fake_by_author)
- //expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "deletions", 10], ["fake_by_author", "deletions", 10]])
- //})
- //})
+ // describe("#store_deletions", function () {
+ // var fake_entry = {deletions: 10};
+ // var fake_total= "fake_total";
+ // var fake_by_author = "fake_by_author";
+ // it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
+ // spyOn(ContributorsStatGraphUtil, 'add');
+ // ContributorsStatGraphUtil.store_deletions(fake_entry, fake_total, fake_by_author);
+ // expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "deletions", 10], ["fake_by_author", "deletions", 10]]);
+ // });
+ // });
describe("#add_date", function () {
it("adds a date field to the collection", function () {
- var fake_date = "2013-10-02"
- var fake_collection = {}
- ContributorsStatGraphUtil.add_date(fake_date, fake_collection)
- expect(fake_collection[fake_date].date).toEqual("2013-10-02")
- })
- })
+ var fake_date = "2013-10-02";
+ var fake_collection = {};
+ ContributorsStatGraphUtil.add_date(fake_date, fake_collection);
+ expect(fake_collection[fake_date].date).toEqual("2013-10-02");
+ });
+ });
describe("#add_author", function () {
it("adds an author field to the collection", function () {
- var fake_author = { author_name: "Author", author_email: 'fake@email.com' }
- var fake_author_collection = {}
- var fake_email_collection = {}
- ContributorsStatGraphUtil.add_author(fake_author, fake_author_collection, fake_email_collection)
- expect(fake_author_collection[fake_author.author_name].author_name).toEqual("Author")
- expect(fake_email_collection[fake_author.author_email].author_name).toEqual("Author")
- })
- })
+ var fake_author = { author_name: "Author", author_email: 'fake@email.com' };
+ var fake_author_collection = {};
+ var fake_email_collection = {};
+ ContributorsStatGraphUtil.add_author(fake_author, fake_author_collection, fake_email_collection);
+ expect(fake_author_collection[fake_author.author_name].author_name).toEqual("Author");
+ expect(fake_email_collection[fake_author.author_email].author_name).toEqual("Author");
+ });
+ });
describe("#get_total_data", function () {
it("returns the collection sorted via specified field", function () {
var fake_parsed_log = {
- total: [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
- {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
- by_author:[
- {
- author: "Karlo Soriano",
- "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
- },
- {
- author: "Dmitriy Zaporozhets",
- "2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}
- }
- ]};
- var correct_total_data = [{date: "2013-05-08", commits: 3},
- {date: "2013-05-09", commits: 1}];
- expect(ContributorsStatGraphUtil.get_total_data(fake_parsed_log, "commits")).toEqual(correct_total_data)
- })
- })
+ total: [
+ { date: "2013-05-09", additions: 471, deletions: 0, commits: 1 },
+ { date: "2013-05-08", additions: 54, deletions: 7, commits: 3 }
+ ],
+ by_author: [
+ {
+ author: "Karlo Soriano",
+ "2013-05-09": { date: "2013-05-09", additions: 471, deletions: 0, commits: 1 }
+ },
+ {
+ author: "Dmitriy Zaporozhets",
+ "2013-05-08": { date: "2013-05-08", additions: 54, deletions: 7, commits: 3 }
+ }
+ ]
+ };
+ var correct_total_data = [
+ { date: "2013-05-08", commits: 3 },
+ { date: "2013-05-09", commits: 1 }
+ ];
+ expect(ContributorsStatGraphUtil.get_total_data(fake_parsed_log, "commits")).toEqual(correct_total_data);
+ });
+ });
describe("#pick_field", function () {
it("returns the collection with only the specified field and date", function () {
- var fake_parsed_log_total = [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
- {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}];
- ContributorsStatGraphUtil.pick_field(fake_parsed_log_total, "commits")
- var correct_pick_field_data = [{date: "2013-05-09", commits: 1},{date: "2013-05-08", commits: 3}];
- expect(ContributorsStatGraphUtil.pick_field(fake_parsed_log_total, "commits")).toEqual(correct_pick_field_data)
- })
- })
+ var fake_parsed_log_total = [
+ { date: "2013-05-09", additions: 471, deletions: 0, commits: 1 },
+ { date: "2013-05-08", additions: 54, deletions: 7, commits: 3 }
+ ];
+ ContributorsStatGraphUtil.pick_field(fake_parsed_log_total, "commits");
+ var correct_pick_field_data = [{ date: "2013-05-09", commits: 1 }, { date: "2013-05-08", commits: 3 }];
+ expect(ContributorsStatGraphUtil.pick_field(fake_parsed_log_total, "commits")).toEqual(correct_pick_field_data);
+ });
+ });
describe("#get_author_data", function () {
it("returns the log by author sorted by specified field", function () {
var fake_parsed_log = {
total: [
- {date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
- {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}
+ { date: "2013-05-09", additions: 471, deletions: 0, commits: 1 },
+ { date: "2013-05-08", additions: 54, deletions: 7, commits: 3 }
],
by_author: [
{
author_name: "Karlo Soriano", author_email: "karlo@email.com",
- "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
+ "2013-05-09": { date: "2013-05-09", additions: 471, deletions: 0, commits: 1 }
},
{
author_name: "Dmitriy Zaporozhets", author_email: "dzaporozhets@email.com",
- "2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}
+ "2013-05-08": { date: "2013-05-08", additions: 54, deletions: 7, commits: 3 }
}
]
- }
+ };
var correct_author_data = [
- {author_name:"Dmitriy Zaporozhets",author_email:"dzaporozhets@email.com",dates:{"2013-05-08":3},deletions:7,additions:54,"commits":3},
- {author_name:"Karlo Soriano",author_email:"karlo@email.com",dates:{"2013-05-09":1},deletions:0,additions:471,commits:1}
- ]
- expect(ContributorsStatGraphUtil.get_author_data(fake_parsed_log, "commits")).toEqual(correct_author_data)
- })
- })
+ { author_name: "Dmitriy Zaporozhets", author_email: "dzaporozhets@email.com", dates: { "2013-05-08": 3 }, deletions: 7, additions: 54, "commits": 3 },
+ { author_name: "Karlo Soriano", author_email: "karlo@email.com", dates: { "2013-05-09": 1 }, deletions: 0, additions: 471, commits: 1 }
+ ];
+ expect(ContributorsStatGraphUtil.get_author_data(fake_parsed_log, "commits")).toEqual(correct_author_data);
+ });
+ });
describe("#parse_log_entry", function () {
it("adds the corresponding info from the log entry to the author", function () {
- var fake_log_entry = { author_name: "Karlo Soriano", author_email: "karlo@email.com",
- "2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
- }
- var correct_parsed_log = {author_name:"Karlo Soriano",author_email:"karlo@email.com",dates:{"2013-05-09":1},deletions:0,additions:471,commits:1}
- expect(ContributorsStatGraphUtil.parse_log_entry(fake_log_entry, 'commits', null)).toEqual(correct_parsed_log)
- })
- })
+ var fake_log_entry = { author_name: "Karlo Soriano", author_email: "karlo@email.com",
+ "2013-05-09": { date: "2013-05-09", additions: 471, deletions: 0, commits: 1 }
+ };
+ var correct_parsed_log = { author_name: "Karlo Soriano", author_email: "karlo@email.com", dates: { "2013-05-09": 1 }, deletions: 0, additions: 471, commits: 1 };
+ expect(ContributorsStatGraphUtil.parse_log_entry(fake_log_entry, 'commits', null)).toEqual(correct_parsed_log);
+ });
+ });
describe("#in_range", function () {
- var date = "2013-05-09"
+ var date = "2013-05-09";
it("returns true if date_range is null", function () {
- expect(ContributorsStatGraphUtil.in_range(date, null)).toEqual(true)
- })
+ expect(ContributorsStatGraphUtil.in_range(date, null)).toEqual(true);
+ });
it("returns true if date is in range", function () {
- var date_range = [new Date("2013-01-01"), new Date("2013-12-12")]
- expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(true)
- })
+ var date_range = [new Date("2013-01-01"), new Date("2013-12-12")];
+ expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(true);
+ });
it("returns false if date is not in range", function () {
- var date_range = [new Date("1999-12-01"), new Date("2000-12-01")]
- expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(false)
- })
- })
-
-
-})
+ var date_range = [new Date("1999-12-01"), new Date("2000-12-01")];
+ expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(false);
+ });
+ });
+});
diff --git a/spec/javascripts/graphs/stat_graph_spec.js b/spec/javascripts/graphs/stat_graph_spec.js
index 0da124632ae..71b589e6b83 100644
--- a/spec/javascripts/graphs/stat_graph_spec.js
+++ b/spec/javascripts/graphs/stat_graph_spec.js
@@ -1,10 +1,9 @@
-/* eslint-disable quotes, padded-blocks, semi */
+/* eslint-disable quotes */
/* global StatGraph */
//= require graphs/stat_graph
describe("StatGraph", function () {
-
describe("#get_log", function () {
it("returns log", function () {
StatGraph.log = "test";
@@ -16,7 +15,6 @@ describe("StatGraph", function () {
it("sets the log", function () {
StatGraph.set_log("test");
expect(StatGraph.log).toBe("test");
- })
- })
-
+ });
+ });
});
diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js
index b5262afa1cf..b846c5ab00b 100644
--- a/spec/javascripts/header_spec.js
+++ b/spec/javascripts/header_spec.js
@@ -1,10 +1,9 @@
-/* eslint-disable space-before-function-paren, padded-blocks, no-var */
+/* eslint-disable space-before-function-paren, no-var */
/*= require header */
/*= require lib/utils/text_utility */
/*= require jquery */
(function() {
-
describe('Header', function() {
var todosPendingCount = '.todos-pending-count';
var fixtureTemplate = 'static/header.html.raw';
@@ -51,5 +50,4 @@
});
});
});
-
}).call(this);
diff --git a/spec/javascripts/issuable_time_tracker_spec.js.es6 b/spec/javascripts/issuable_time_tracker_spec.js.es6
new file mode 100644
index 00000000000..a1e979e8d09
--- /dev/null
+++ b/spec/javascripts/issuable_time_tracker_spec.js.es6
@@ -0,0 +1,201 @@
+/* eslint-disable */
+//= require jquery
+//= require vue
+//= require issuable/time_tracking/components/time_tracker
+
+function initTimeTrackingComponent(opts) {
+ fixture.set(`
+ <div>
+ <div id="mock-container"></div>
+ </div>
+ `);
+
+ this.initialData = {
+ time_estimate: opts.timeEstimate,
+ time_spent: opts.timeSpent,
+ human_time_estimate: opts.timeEstimateHumanReadable,
+ human_time_spent: opts.timeSpentHumanReadable,
+ docsUrl: '/help/workflow/time_tracking.md',
+ };
+
+ const TimeTrackingComponent = Vue.component('issuable-time-tracker');
+ this.timeTracker = new TimeTrackingComponent({
+ el: '#mock-container',
+ propsData: this.initialData,
+ });
+}
+
+((gl) => {
+ describe('Issuable Time Tracker', function() {
+ describe('Initialization', function() {
+ beforeEach(function() {
+ initTimeTrackingComponent.call(this, { timeEstimate: 100000, timeSpent: 5000, timeEstimateHumanReadable: '2h 46m', timeSpentHumanReadable: '1h 23m' });
+ });
+
+ it('should return something defined', function() {
+ expect(this.timeTracker).toBeDefined();
+ });
+
+ it ('should correctly set timeEstimate', function(done) {
+ Vue.nextTick(() => {
+ expect(this.timeTracker.timeEstimate).toBe(this.initialData.time_estimate);
+ done();
+ });
+ });
+ it ('should correctly set time_spent', function(done) {
+ Vue.nextTick(() => {
+ expect(this.timeTracker.timeSpent).toBe(this.initialData.time_spent);
+ done();
+ });
+ });
+ });
+
+ describe('Content Display', function() {
+ describe('Panes', function() {
+ describe('Comparison pane', function() {
+ beforeEach(function() {
+ initTimeTrackingComponent.call(this, { timeEstimate: 100000, timeSpent: 5000, timeEstimateHumanReadable: '', timeSpentHumanReadable: '' });
+ });
+
+ it('should show the "Comparison" pane when timeEstimate and time_spent are truthy', function(done) {
+ Vue.nextTick(() => {
+ const $comparisonPane = this.timeTracker.$el.querySelector('.time-tracking-comparison-pane');
+ expect(this.timeTracker.showComparisonState).toBe(true);
+ done();
+ });
+ });
+
+ describe('Remaining meter', function() {
+ it('should display the remaining meter with the correct width', function(done) {
+ Vue.nextTick(() => {
+ const meterWidth = this.timeTracker.$el.querySelector('.time-tracking-comparison-pane .meter-fill').style.width;
+ const correctWidth = '5%';
+
+ expect(meterWidth).toBe(correctWidth);
+ done();
+ })
+ });
+
+ it('should display the remaining meter with the correct background color when within estimate', function(done) {
+ Vue.nextTick(() => {
+ const styledMeter = $(this.timeTracker.$el).find('.time-tracking-comparison-pane .within_estimate .meter-fill');
+ expect(styledMeter.length).toBe(1);
+ done()
+ });
+ });
+
+ it('should display the remaining meter with the correct background color when over estimate', function(done) {
+ this.timeTracker.time_estimate = 100000;
+ this.timeTracker.time_spent = 20000000;
+ Vue.nextTick(() => {
+ const styledMeter = $(this.timeTracker.$el).find('.time-tracking-comparison-pane .over_estimate .meter-fill');
+ expect(styledMeter.length).toBe(1);
+ done();
+ });
+ });
+ });
+ });
+
+ describe("Estimate only pane", function() {
+ beforeEach(function() {
+ initTimeTrackingComponent.call(this, { timeEstimate: 100000, timeSpent: 0, timeEstimateHumanReadable: '2h 46m', timeSpentHumanReadable: '' });
+ });
+
+ it('should display the human readable version of time estimated', function(done) {
+ Vue.nextTick(() => {
+ const estimateText = this.timeTracker.$el.querySelector('.time-tracking-estimate-only-pane').innerText;
+ const correctText = 'Estimated: 2h 46m';
+
+ expect(estimateText).toBe(correctText);
+ done();
+ });
+ });
+ });
+
+ describe('Spent only pane', function() {
+ beforeEach(function() {
+ initTimeTrackingComponent.call(this, { timeEstimate: 0, timeSpent: 5000, timeEstimateHumanReadable: '2h 46m', timeSpentHumanReadable: '1h 23m' });
+ });
+
+ it('should display the human readable version of time spent', function(done) {
+ Vue.nextTick(() => {
+ const spentText = this.timeTracker.$el.querySelector('.time-tracking-spend-only-pane').innerText;
+ const correctText = 'Spent: 1h 23m';
+
+ expect(spentText).toBe(correctText);
+ done();
+ });
+ });
+ });
+
+ describe('No time tracking pane', function() {
+ beforeEach(function() {
+ initTimeTrackingComponent.call(this, { timeEstimate: 0, timeSpent: 0, timeEstimateHumanReadable: 0, timeSpentHumanReadable: 0 });
+ });
+
+ it('should only show the "No time tracking" pane when both timeEstimate and time_spent are falsey', function(done) {
+ Vue.nextTick(() => {
+ const $noTrackingPane = this.timeTracker.$el.querySelector('.time-tracking-no-tracking-pane');
+ const noTrackingText =$noTrackingPane.innerText;
+ const correctText = 'No estimate or time spent';
+
+ expect(this.timeTracker.showNoTimeTrackingState).toBe(true);
+ expect($noTrackingPane).toBeVisible();
+ expect(noTrackingText).toBe(correctText);
+ done();
+ });
+ });
+ });
+
+ describe("Help pane", function() {
+ beforeEach(function() {
+ initTimeTrackingComponent.call(this, { timeEstimate: 0, timeSpent: 0 });
+ });
+
+ it('should not show the "Help" pane by default', function(done) {
+ Vue.nextTick(() => {
+ const $helpPane = this.timeTracker.$el.querySelector('.time-tracking-help-state');
+
+ expect(this.timeTracker.showHelpState).toBe(false);
+ expect($helpPane).toBeNull();
+ done();
+ });
+ });
+
+ it('should show the "Help" pane when help button is clicked', function(done) {
+ Vue.nextTick(() => {
+ $(this.timeTracker.$el).find('.help-button').click();
+
+ setTimeout(() => {
+ const $helpPane = this.timeTracker.$el.querySelector('.time-tracking-help-state');
+ expect(this.timeTracker.showHelpState).toBe(true);
+ expect($helpPane).toBeVisible();
+ done();
+ }, 10);
+ });
+ });
+
+ it('should not show the "Help" pane when help button is clicked and then closed', function(done) {
+ Vue.nextTick(() => {
+ $(this.timeTracker.$el).find('.help-button').click();
+
+ setTimeout(() => {
+
+ $(this.timeTracker.$el).find('.close-help-button').click();
+
+ setTimeout(() => {
+ const $helpPane = this.timeTracker.$el.querySelector('.time-tracking-help-state');
+
+ expect(this.timeTracker.showHelpState).toBe(false);
+ expect($helpPane).toBeNull();
+
+ done();
+ }, 1000);
+ }, 1000);
+ });
+ });
+ });
+ });
+ });
+ });
+})(window.gl || (window.gl = {}));
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index eb07421826c..673a4b3c07a 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-use-before-define, indent, no-trailing-spaces, comma-dangle, padded-blocks, max-len */
+/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */
/* global Issue */
/*= require lib/utils/text_utility */
@@ -42,21 +42,21 @@
}
function findElements() {
- $boxClosed = $('div.status-box-closed');
- expect($boxClosed).toExist();
- expect($boxClosed).toHaveText('Closed');
+ $boxClosed = $('div.status-box-closed');
+ expect($boxClosed).toExist();
+ expect($boxClosed).toHaveText('Closed');
- $boxOpen = $('div.status-box-open');
- expect($boxOpen).toExist();
- expect($boxOpen).toHaveText('Open');
+ $boxOpen = $('div.status-box-open');
+ expect($boxOpen).toExist();
+ expect($boxOpen).toHaveText('Open');
- $btnClose = $('.btn-close.btn-grouped');
- expect($btnClose).toExist();
- expect($btnClose).toHaveText('Close issue');
+ $btnClose = $('.btn-close.btn-grouped');
+ expect($btnClose).toExist();
+ expect($btnClose).toHaveText('Close issue');
- $btnReopen = $('.btn-reopen.btn-grouped');
- expect($btnReopen).toExist();
- expect($btnReopen).toHaveText('Reopen issue');
+ $btnReopen = $('.btn-reopen.btn-grouped');
+ expect($btnReopen).toExist();
+ expect($btnReopen).toHaveText('Reopen issue');
}
describe('Issue', function() {
@@ -161,5 +161,4 @@
expect($btnReopen).toHaveProp('disabled', false);
});
});
-
}).call(this);
diff --git a/spec/javascripts/labels_issue_sidebar_spec.js.es6 b/spec/javascripts/labels_issue_sidebar_spec.js.es6
index e3146559a4a..0d19b4a25b9 100644
--- a/spec/javascripts/labels_issue_sidebar_spec.js.es6
+++ b/spec/javascripts/labels_issue_sidebar_spec.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable no-new, no-plusplus, object-curly-spacing, prefer-const, semi */
+/* eslint-disable no-new */
/* global IssuableContext */
/* global LabelsSelect */
@@ -26,18 +26,18 @@
spyOn(jQuery, 'ajax').and.callFake((req) => {
const d = $.Deferred();
- let LABELS_DATA = []
+ let LABELS_DATA = [];
if (req.url === '/root/test/labels.json') {
- for (let i = 0; i < 10; i++) {
- LABELS_DATA.push({id: i, title: `test ${i}`, color: '#5CB85C'});
+ for (let i = 0; i < 10; i += 1) {
+ LABELS_DATA.push({ id: i, title: `test ${i}`, color: '#5CB85C' });
}
} else if (req.url === '/root/test/issues/2.json') {
- let tmp = []
- for (let i = 0; i < saveLabelCount; i++) {
- tmp.push({id: i, title: `test ${i}`, color: '#5CB85C'});
+ const tmp = [];
+ for (let i = 0; i < saveLabelCount; i += 1) {
+ tmp.push({ id: i, title: `test ${i}`, color: '#5CB85C' });
}
- LABELS_DATA = {labels: tmp};
+ LABELS_DATA = { labels: tmp };
}
d.resolve(LABELS_DATA);
diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js
index 31f516b41bf..6605986c33a 100644
--- a/spec/javascripts/line_highlighter_spec.js
+++ b/spec/javascripts/line_highlighter_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, no-var, no-param-reassign, quotes, prefer-template, no-else-return, new-cap, dot-notation, no-return-assign, comma-dangle, no-new, one-var, one-var-declaration-per-line, no-plusplus, jasmine/no-spec-dupes, no-underscore-dangle, padded-blocks, max-len */
+/* eslint-disable space-before-function-paren, no-var, no-param-reassign, quotes, prefer-template, no-else-return, new-cap, dot-notation, no-return-assign, comma-dangle, no-new, one-var, one-var-declaration-per-line, jasmine/no-spec-dupes, no-underscore-dangle, max-len */
/* global LineHighlighter */
/*= require line_highlighter */
@@ -33,11 +33,11 @@
return expect($('#LC13')).toHaveClass(this.css);
});
it('highlights a range of lines given in the URL hash', function() {
- var i, line, results;
+ var line, results;
new LineHighlighter('#L5-25');
expect($("." + this.css).length).toBe(21);
results = [];
- for (line = i = 5; i <= 25; line = ++i) {
+ for (line = 5; line <= 25; line += 1) {
results.push(expect($("#LC" + line)).toHaveClass(this.css));
}
return results;
@@ -124,27 +124,27 @@
});
describe('with existing single-line highlight', function() {
it('uses existing line as last line when target is lesser', function() {
- var i, line, results;
+ var line, results;
clickLine(20);
clickLine(15, {
shiftKey: true
});
expect($("." + this.css).length).toBe(6);
results = [];
- for (line = i = 15; i <= 20; line = ++i) {
+ for (line = 15; line <= 20; line += 1) {
results.push(expect($("#LC" + line)).toHaveClass(this.css));
}
return results;
});
return it('uses existing line as first line when target is greater', function() {
- var i, line, results;
+ var line, results;
clickLine(5);
clickLine(10, {
shiftKey: true
});
expect($("." + this.css).length).toBe(6);
results = [];
- for (line = i = 5; i <= 10; line = ++i) {
+ for (line = 5; line <= 10; line += 1) {
results.push(expect($("#LC" + line)).toHaveClass(this.css));
}
return results;
@@ -160,25 +160,25 @@
});
});
it('uses target as first line when it is less than existing first line', function() {
- var i, line, results;
+ var line, results;
clickLine(5, {
shiftKey: true
});
expect($("." + this.css).length).toBe(6);
results = [];
- for (line = i = 5; i <= 10; line = ++i) {
+ for (line = 5; line <= 10; line += 1) {
results.push(expect($("#LC" + line)).toHaveClass(this.css));
}
return results;
});
return it('uses target as last line when it is greater than existing first line', function() {
- var i, line, results;
+ var line, results;
clickLine(15, {
shiftKey: true
});
expect($("." + this.css).length).toBe(6);
results = [];
- for (line = i = 10; i <= 15; line = ++i) {
+ for (line = 10; line <= 15; line += 1) {
results.push(expect($("#LC" + line)).toHaveClass(this.css));
}
return results;
@@ -227,5 +227,4 @@
});
});
});
-
}).call(this);
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index 9b232617fe5..f644d39b1c7 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, no-return-assign, padded-blocks */
+/* eslint-disable space-before-function-paren, no-return-assign */
/* global MergeRequest */
/*= require merge_request */
@@ -26,5 +26,4 @@
});
});
});
-
}).call(this);
diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js
index 6f91529db00..bf45100af03 100644
--- a/spec/javascripts/merge_request_widget_spec.js
+++ b/spec/javascripts/merge_request_widget_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, quotes, comma-dangle, dot-notation, indent, quote-props, no-var, padded-blocks, max-len */
+/* eslint-disable space-before-function-paren, quotes, comma-dangle, dot-notation, quote-props, no-var, max-len */
/*= require merge_request_widget */
/*= require lib/utils/datetime_utility */
@@ -42,17 +42,17 @@
});
it('should call renderEnvironments when the environments property is set', function() {
- const spy = spyOn(this.class, 'renderEnvironments').and.stub();
- this.class.getCIEnvironmentsStatus();
- expect(spy).toHaveBeenCalledWith(this.ciEnvironmentsStatusData);
- });
-
- it('should not call renderEnvironments when the environments property is not set', function() {
- this.ciEnvironmentsStatusData = null;
- const spy = spyOn(this.class, 'renderEnvironments').and.stub();
- this.class.getCIEnvironmentsStatus();
- expect(spy).not.toHaveBeenCalled();
- });
+ const spy = spyOn(this.class, 'renderEnvironments').and.stub();
+ this.class.getCIEnvironmentsStatus();
+ expect(spy).toHaveBeenCalledWith(this.ciEnvironmentsStatusData);
+ });
+
+ it('should not call renderEnvironments when the environments property is not set', function() {
+ this.ciEnvironmentsStatusData = null;
+ const spy = spyOn(this.class, 'renderEnvironments').and.stub();
+ this.class.getCIEnvironmentsStatus();
+ expect(spy).not.toHaveBeenCalled();
+ });
});
describe('renderEnvironments', function() {
@@ -107,16 +107,16 @@
});
describe('mergeInProgress', function() {
- it('should display error with h4 tag', function() {
- spyOn(this.class.$widgetBody, 'html').and.callFake(function(html) {
- expect(html).toBe('<h4>Sorry, something went wrong.</h4>');
- });
- spyOn($, 'ajax').and.callFake(function(e) {
- e.success({ merge_error: 'Sorry, something went wrong.' });
- });
- this.class.mergeInProgress(null);
+ it('should display error with h4 tag', function() {
+ spyOn(this.class.$widgetBody, 'html').and.callFake(function(html) {
+ expect(html).toBe('<h4>Sorry, something went wrong.</h4>');
+ });
+ spyOn($, 'ajax').and.callFake(function(e) {
+ e.success({ merge_error: 'Sorry, something went wrong.' });
});
+ this.class.mergeInProgress(null);
});
+ });
return describe('getCIStatus', function() {
beforeEach(function() {
@@ -167,5 +167,4 @@
});
});
});
-
}).call(this);
diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js
index e0dc549a9f4..8259d553f1b 100644
--- a/spec/javascripts/new_branch_spec.js
+++ b/spec/javascripts/new_branch_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, quotes, padded-blocks, max-len */
+/* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, quotes, max-len */
/* global NewBranchForm */
/*= require jquery-ui/autocomplete */
@@ -166,5 +166,4 @@
});
});
});
-
}).call(this);
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 9cdb0a5d5aa..015c35dfca7 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, semi, padded-blocks, max-len */
+/* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, max-len */
/* global Notes */
/*= require notes */
@@ -72,8 +72,7 @@
$('.js-comment-button').click();
expect(this.autoSizeSpy).toHaveBeenTriggered();
- })
+ });
});
});
-
}).call(this);
diff --git a/spec/javascripts/pretty_time_spec.js.es6 b/spec/javascripts/pretty_time_spec.js.es6
index 2e12d45f7a7..7a04fba5f7f 100644
--- a/spec/javascripts/pretty_time_spec.js.es6
+++ b/spec/javascripts/pretty_time_spec.js.es6
@@ -1,12 +1,12 @@
//= require lib/utils/pretty_time
(() => {
- const PrettyTime = gl.PrettyTime;
+ const prettyTime = gl.utils.prettyTime;
- describe('PrettyTime methods', function () {
+ describe('prettyTime methods', function () {
describe('parseSeconds', function () {
it('should correctly parse a negative value', function () {
- const parser = PrettyTime.parseSeconds;
+ const parser = prettyTime.parseSeconds;
const zeroSeconds = parser(-1000);
@@ -17,7 +17,7 @@
});
it('should correctly parse a zero value', function () {
- const parser = PrettyTime.parseSeconds;
+ const parser = prettyTime.parseSeconds;
const zeroSeconds = parser(0);
@@ -28,7 +28,7 @@
});
it('should correctly parse a small non-zero second values', function () {
- const parser = PrettyTime.parseSeconds;
+ const parser = prettyTime.parseSeconds;
const subOneMinute = parser(10);
@@ -53,7 +53,7 @@
});
it('should correctly parse large second values', function () {
- const parser = PrettyTime.parseSeconds;
+ const parser = prettyTime.parseSeconds;
const aboveOneHour = parser(4800);
@@ -87,7 +87,7 @@
minutes: 20,
};
- const timeString = PrettyTime.stringifyTime(timeObject);
+ const timeString = prettyTime.stringifyTime(timeObject);
expect(timeString).toBe('1w 4d 7h 20m');
});
@@ -100,7 +100,7 @@
minutes: 20,
};
- const timeString = PrettyTime.stringifyTime(timeObject);
+ const timeString = prettyTime.stringifyTime(timeObject);
expect(timeString).toBe('4d 20m');
});
@@ -113,7 +113,7 @@
minutes: 0,
};
- const timeString = PrettyTime.stringifyTime(timeObject);
+ const timeString = prettyTime.stringifyTime(timeObject);
expect(timeString).toBe('0m');
});
@@ -122,12 +122,12 @@
describe('abbreviateTime', function () {
it('should abbreviate stringified times for weeks', function () {
const fullTimeString = '1w 3d 4h 5m';
- expect(PrettyTime.abbreviateTime(fullTimeString)).toBe('1w');
+ expect(prettyTime.abbreviateTime(fullTimeString)).toBe('1w');
});
it('should abbreviate stringified times for non-weeks', function () {
const fullTimeString = '0w 3d 4h 5m';
- expect(PrettyTime.abbreviateTime(fullTimeString)).toBe('3d');
+ expect(prettyTime.abbreviateTime(fullTimeString)).toBe('3d');
});
});
});
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
index 27b071f266d..0202c9ba85e 100644
--- a/spec/javascripts/project_title_spec.js
+++ b/spec/javascripts/project_title_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, no-unused-expressions, no-return-assign, no-param-reassign, no-var, new-cap, wrap-iife, no-unused-vars, quotes, jasmine/no-expect-in-setup-teardown, padded-blocks, max-len */
+/* eslint-disable space-before-function-paren, no-unused-expressions, no-return-assign, no-param-reassign, no-var, new-cap, wrap-iife, no-unused-vars, quotes, jasmine/no-expect-in-setup-teardown, max-len */
/* global Project */
@@ -49,5 +49,4 @@
});
});
});
-
}).call(this);
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index 0177d8e4e79..942778229b5 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, new-parens, no-return-assign, new-cap, vars-on-top, semi, padded-blocks, max-len */
+/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, new-parens, no-return-assign, new-cap, vars-on-top, max-len */
/* global Sidebar */
/*= require right_sidebar */
@@ -78,7 +78,6 @@
$('.js-issuable-todo').click();
expect(todoToggleSpy.calls.count()).toEqual(1);
- })
+ });
});
-
}).call(this);
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index 2d3f44e7980..7ac9710654f 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, max-len, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, comma-dangle, object-shorthand, prefer-template, quotes, new-parens, vars-on-top, new-cap, padded-blocks, max-len */
+/* eslint-disable space-before-function-paren, max-len, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, comma-dangle, object-shorthand, prefer-template, quotes, new-parens, vars-on-top, new-cap, max-len */
/*= require gl_dropdown */
/*= require search_autocomplete */
@@ -171,5 +171,4 @@
expect(enterKeyEvent.isDefaultPrevented()).toBe(true);
});
});
-
}).call(this);
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
index c2894d6f3ea..386fc8f514e 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, no-return-assign, no-var, quotes, padded-blocks */
+/* eslint-disable space-before-function-paren, no-return-assign, no-var, quotes */
/* global ShortcutsIssuable */
/*= require copy_as_gfm */
@@ -78,5 +78,4 @@
});
});
});
-
}).call(this);
diff --git a/spec/javascripts/subbable_resource_spec.js.es6 b/spec/javascripts/subbable_resource_spec.js.es6
index 6a70dd856a7..99f45850ea3 100644
--- a/spec/javascripts/subbable_resource_spec.js.es6
+++ b/spec/javascripts/subbable_resource_spec.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable max-len, arrow-parens, comma-dangle, no-plusplus */
+/* eslint-disable max-len, arrow-parens, comma-dangle */
//= vue
//= vue-resource
@@ -53,7 +53,7 @@
this.MockResource.subscribe(callbacks.two);
this.MockResource.subscribe(callbacks.three);
- state.myprop++;
+ state.myprop += 1;
this.MockResource.publish(state);
diff --git a/spec/javascripts/syntax_highlight_spec.js b/spec/javascripts/syntax_highlight_spec.js
index 5984ce8ffd4..436f7064a69 100644
--- a/spec/javascripts/syntax_highlight_spec.js
+++ b/spec/javascripts/syntax_highlight_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, no-var, no-return-assign, quotes, padded-blocks */
+/* eslint-disable space-before-function-paren, no-var, no-return-assign, quotes */
/*= require syntax_highlight */
@@ -41,5 +41,4 @@
});
});
});
-
}).call(this);
diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js
index dc2f4967985..80163fd72d3 100644
--- a/spec/javascripts/u2f/authenticate_spec.js
+++ b/spec/javascripts/u2f/authenticate_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, new-parens, quotes, comma-dangle, no-var, one-var, one-var-declaration-per-line, padded-blocks, max-len */
+/* eslint-disable space-before-function-paren, new-parens, quotes, comma-dangle, no-var, one-var, one-var-declaration-per-line, max-len */
/* global MockU2FDevice */
/* global U2FAuthenticate */
@@ -69,5 +69,4 @@
});
});
});
-
}).call(this);
diff --git a/spec/javascripts/u2f/mock_u2f_device.js b/spec/javascripts/u2f/mock_u2f_device.js
index 1459f968c3d..287bfb4138b 100644
--- a/spec/javascripts/u2f/mock_u2f_device.js
+++ b/spec/javascripts/u2f/mock_u2f_device.js
@@ -1,6 +1,7 @@
-/* eslint-disable space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-unused-expressions, no-return-assign, no-param-reassign, padded-blocks, max-len */
+/* eslint-disable space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-expressions, no-return-assign, no-param-reassign, max-len */
+
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.MockU2FDevice = (function() {
function MockU2FDevice() {
@@ -28,7 +29,5 @@
};
return MockU2FDevice;
-
})();
-
}).call(this);
diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js
index ab4c5edd044..0790553b67e 100644
--- a/spec/javascripts/u2f/register_spec.js
+++ b/spec/javascripts/u2f/register_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, new-parens, quotes, no-var, one-var, one-var-declaration-per-line, comma-dangle, padded-blocks, max-len */
+/* eslint-disable space-before-function-paren, new-parens, quotes, no-var, one-var, one-var-declaration-per-line, comma-dangle, max-len */
/* global MockU2FDevice */
/* global U2FRegister */
@@ -74,5 +74,4 @@
});
});
});
-
}).call(this);
diff --git a/spec/javascripts/visibility_select_spec.js.es6 b/spec/javascripts/visibility_select_spec.js.es6
new file mode 100644
index 00000000000..b21f6912e06
--- /dev/null
+++ b/spec/javascripts/visibility_select_spec.js.es6
@@ -0,0 +1,100 @@
+/*= require visibility_select */
+
+(() => {
+ const VisibilitySelect = gl.VisibilitySelect;
+
+ describe('VisibilitySelect', function () {
+ const lockedElement = document.createElement('div');
+ lockedElement.dataset.helpBlock = 'lockedHelpBlock';
+
+ const checkedElement = document.createElement('div');
+ checkedElement.dataset.description = 'checkedDescription';
+
+ const mockElements = {
+ container: document.createElement('div'),
+ select: document.createElement('div'),
+ '.help-block': document.createElement('div'),
+ '.js-locked': lockedElement,
+ 'option:checked': checkedElement,
+ };
+
+ beforeEach(function () {
+ spyOn(Element.prototype, 'querySelector').and.callFake(selector => mockElements[selector]);
+ });
+
+ describe('#constructor', function () {
+ beforeEach(function () {
+ this.visibilitySelect = new VisibilitySelect(mockElements.container);
+ });
+
+ it('sets the container member', function () {
+ expect(this.visibilitySelect.container).toEqual(mockElements.container);
+ });
+
+ it('queries and sets the helpBlock member', function () {
+ expect(Element.prototype.querySelector).toHaveBeenCalledWith('.help-block');
+ expect(this.visibilitySelect.helpBlock).toEqual(mockElements['.help-block']);
+ });
+
+ it('queries and sets the select member', function () {
+ expect(Element.prototype.querySelector).toHaveBeenCalledWith('select');
+ expect(this.visibilitySelect.select).toEqual(mockElements.select);
+ });
+
+ describe('if there is no container element provided', function () {
+ it('throws an error', function () {
+ expect(() => new VisibilitySelect()).toThrowError('VisibilitySelect requires a container element as argument 1');
+ });
+ });
+ });
+
+ describe('#init', function () {
+ describe('if there is a select', function () {
+ beforeEach(function () {
+ this.visibilitySelect = new VisibilitySelect(mockElements.container);
+ });
+
+ it('calls updateHelpText', function () {
+ spyOn(VisibilitySelect.prototype, 'updateHelpText');
+ this.visibilitySelect.init();
+ expect(this.visibilitySelect.updateHelpText).toHaveBeenCalled();
+ });
+
+ it('adds a change event listener', function () {
+ spyOn(this.visibilitySelect.select, 'addEventListener');
+ this.visibilitySelect.init();
+ expect(this.visibilitySelect.select.addEventListener.calls.argsFor(0)).toContain('change');
+ });
+ });
+
+ describe('if there is no select', function () {
+ beforeEach(function () {
+ mockElements.select = undefined;
+ this.visibilitySelect = new VisibilitySelect(mockElements.container);
+ this.visibilitySelect.init();
+ });
+
+ it('updates the helpBlock text to the locked `data-help-block` messaged', function () {
+ expect(this.visibilitySelect.helpBlock.textContent)
+ .toEqual(lockedElement.dataset.helpBlock);
+ });
+
+ afterEach(function () {
+ mockElements.select = document.createElement('div');
+ });
+ });
+ });
+
+ describe('#updateHelpText', function () {
+ beforeEach(function () {
+ this.visibilitySelect = new VisibilitySelect(mockElements.container);
+ this.visibilitySelect.init();
+ });
+
+ it('updates the helpBlock text to the selected options `data-description`', function () {
+ expect(this.visibilitySelect.helpBlock.textContent)
+ .toEqual(checkedElement.dataset.description);
+ });
+ });
+ });
+})();
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index f1c2edcc55c..be706ca304f 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-return-assign, new-cap, padded-blocks, max-len */
+/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-return-assign, new-cap, max-len */
/* global Dropzone */
/* global Mousetrap */
/* global ZenMode */
@@ -76,5 +76,4 @@
keyCode: 27
}));
};
-
}).call(this);
diff --git a/spec/lib/gitlab/ci/status/external/common_spec.rb b/spec/lib/gitlab/ci/status/external/common_spec.rb
new file mode 100644
index 00000000000..5a97d98b55f
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/external/common_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::External::Common do
+ let(:user) { create(:user) }
+ let(:project) { external_status.project }
+ let(:external_target_url) { 'http://example.gitlab.com/status' }
+
+ let(:external_status) do
+ create(:generic_commit_status, target_url: external_target_url)
+ end
+
+ subject do
+ Gitlab::Ci::Status::Core
+ .new(external_status, user)
+ .extend(described_class)
+ end
+
+ describe '#has_action?' do
+ it { is_expected.not_to have_action }
+ end
+
+ describe '#has_details?' do
+ context 'when user has access to read commit status' do
+ before { project.team << [user, :developer] }
+
+ it { is_expected.to have_details }
+ end
+
+ context 'when user does not have access to read commit status' do
+ it { is_expected.not_to have_details }
+ end
+ end
+
+ describe '#details_path' do
+ it 'links to the external target URL' do
+ expect(subject.details_path).to eq external_target_url
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/external/factory_spec.rb b/spec/lib/gitlab/ci/status/external/factory_spec.rb
new file mode 100644
index 00000000000..c96fd53e730
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/external/factory_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::External::Factory do
+ let(:user) { create(:user) }
+ let(:project) { resource.project }
+ let(:status) { factory.fabricate! }
+ let(:factory) { described_class.new(resource, user) }
+ let(:external_url) { 'http://gitlab.com/status' }
+
+ before do
+ project.team << [user, :developer]
+ end
+
+ context 'when external status has a simple core status' do
+ HasStatus::AVAILABLE_STATUSES.each do |simple_status|
+ context "when core status is #{simple_status}" do
+ let(:resource) do
+ create(:generic_commit_status, status: simple_status,
+ target_url: external_url)
+ end
+
+ let(:expected_status) do
+ Gitlab::Ci::Status.const_get(simple_status.capitalize)
+ end
+
+ it "fabricates a core status #{simple_status}" do
+ expect(status).to be_a expected_status
+ end
+
+ it 'extends core status with common methods' do
+ expect(status).to have_details
+ expect(status).not_to have_action
+ expect(status.details_path).to eq external_url
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
new file mode 100644
index 00000000000..0267e8c2f69
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+require 'lib/gitlab/cycle_analytics/shared_event_spec'
+
+describe Gitlab::CycleAnalytics::CodeEventFetcher do
+ let(:stage_name) { :code }
+
+ it_behaves_like 'default query config' do
+ it 'has a default order' do
+ expect(event.order).not_to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cycle_analytics/code_event_spec.rb b/spec/lib/gitlab/cycle_analytics/code_event_spec.rb
deleted file mode 100644
index 43f42d1bde8..00000000000
--- a/spec/lib/gitlab/cycle_analytics/code_event_spec.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'spec_helper'
-require 'lib/gitlab/cycle_analytics/shared_event_spec'
-
-describe Gitlab::CycleAnalytics::CodeEvent do
- it_behaves_like 'default query config' do
- it 'does not have the default order' do
- expect(event.order).not_to eq(event.start_time_attrs)
- end
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
new file mode 100644
index 00000000000..e8fc67acf05
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
@@ -0,0 +1,8 @@
+require 'spec_helper'
+require 'lib/gitlab/cycle_analytics/shared_stage_spec'
+
+describe Gitlab::CycleAnalytics::CodeStage do
+ let(:stage_name) { :code }
+
+ it_behaves_like 'base stage'
+end
diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb
index 6062e7af4f5..9d2ba481919 100644
--- a/spec/lib/gitlab/cycle_analytics/events_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb
@@ -1,12 +1,14 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::Events do
+describe 'cycle analytics events' do
let(:project) { create(:project) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
let!(:context) { create(:issue, project: project, created_at: 2.days.ago) }
- subject { described_class.new(project: project, options: { from: from_date, current_user: user }) }
+ let(:events) do
+ CycleAnalytics.new(project, { from: from_date, current_user: user })[stage].events
+ end
before do
allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([context])
@@ -15,104 +17,112 @@ describe Gitlab::CycleAnalytics::Events do
end
describe '#issue_events' do
+ let(:stage) { :issue }
+
it 'has the total time' do
- expect(subject.issue_events.first[:total_time]).not_to be_empty
+ expect(events.first[:total_time]).not_to be_empty
end
it 'has a title' do
- expect(subject.issue_events.first[:title]).to eq(context.title)
+ expect(events.first[:title]).to eq(context.title)
end
it 'has the URL' do
- expect(subject.issue_events.first[:url]).not_to be_nil
+ expect(events.first[:url]).not_to be_nil
end
it 'has an iid' do
- expect(subject.issue_events.first[:iid]).to eq(context.iid.to_s)
+ expect(events.first[:iid]).to eq(context.iid.to_s)
end
it 'has a created_at timestamp' do
- expect(subject.issue_events.first[:created_at]).to end_with('ago')
+ expect(events.first[:created_at]).to end_with('ago')
end
it "has the author's URL" do
- expect(subject.issue_events.first[:author][:web_url]).not_to be_nil
+ expect(events.first[:author][:web_url]).not_to be_nil
end
it "has the author's avatar URL" do
- expect(subject.issue_events.first[:author][:avatar_url]).not_to be_nil
+ expect(events.first[:author][:avatar_url]).not_to be_nil
end
it "has the author's name" do
- expect(subject.issue_events.first[:author][:name]).to eq(context.author.name)
+ expect(events.first[:author][:name]).to eq(context.author.name)
end
end
describe '#plan_events' do
+ let(:stage) { :plan }
+
it 'has a title' do
- expect(subject.plan_events.first[:title]).not_to be_nil
+ expect(events.first[:title]).not_to be_nil
end
it 'has a sha short ID' do
- expect(subject.plan_events.first[:short_sha]).not_to be_nil
+ expect(events.first[:short_sha]).not_to be_nil
end
it 'has the URL' do
- expect(subject.plan_events.first[:commit_url]).not_to be_nil
+ expect(events.first[:commit_url]).not_to be_nil
end
it 'has the total time' do
- expect(subject.plan_events.first[:total_time]).not_to be_empty
+ expect(events.first[:total_time]).not_to be_empty
end
it "has the author's URL" do
- expect(subject.plan_events.first[:author][:web_url]).not_to be_nil
+ expect(events.first[:author][:web_url]).not_to be_nil
end
it "has the author's avatar URL" do
- expect(subject.plan_events.first[:author][:avatar_url]).not_to be_nil
+ expect(events.first[:author][:avatar_url]).not_to be_nil
end
it "has the author's name" do
- expect(subject.plan_events.first[:author][:name]).not_to be_nil
+ expect(events.first[:author][:name]).not_to be_nil
end
end
describe '#code_events' do
+ let(:stage) { :code }
+
before do
create_commit_referencing_issue(context)
end
it 'has the total time' do
- expect(subject.code_events.first[:total_time]).not_to be_empty
+ expect(events.first[:total_time]).not_to be_empty
end
it 'has a title' do
- expect(subject.code_events.first[:title]).to eq('Awesome merge_request')
+ expect(events.first[:title]).to eq('Awesome merge_request')
end
it 'has an iid' do
- expect(subject.code_events.first[:iid]).to eq(context.iid.to_s)
+ expect(events.first[:iid]).to eq(context.iid.to_s)
end
it 'has a created_at timestamp' do
- expect(subject.code_events.first[:created_at]).to end_with('ago')
+ expect(events.first[:created_at]).to end_with('ago')
end
it "has the author's URL" do
- expect(subject.code_events.first[:author][:web_url]).not_to be_nil
+ expect(events.first[:author][:web_url]).not_to be_nil
end
it "has the author's avatar URL" do
- expect(subject.code_events.first[:author][:avatar_url]).not_to be_nil
+ expect(events.first[:author][:avatar_url]).not_to be_nil
end
it "has the author's name" do
- expect(subject.code_events.first[:author][:name]).to eq(MergeRequest.first.author.name)
+ expect(events.first[:author][:name]).to eq(MergeRequest.first.author.name)
end
end
describe '#test_events' do
+ let(:stage) { :test }
+
let(:merge_request) { MergeRequest.first }
let!(:pipeline) do
create(:ci_pipeline,
@@ -130,83 +140,85 @@ describe Gitlab::CycleAnalytics::Events do
end
it 'has the name' do
- expect(subject.test_events.first[:name]).not_to be_nil
+ expect(events.first[:name]).not_to be_nil
end
it 'has the ID' do
- expect(subject.test_events.first[:id]).not_to be_nil
+ expect(events.first[:id]).not_to be_nil
end
it 'has the URL' do
- expect(subject.test_events.first[:url]).not_to be_nil
+ expect(events.first[:url]).not_to be_nil
end
it 'has the branch name' do
- expect(subject.test_events.first[:branch]).not_to be_nil
+ expect(events.first[:branch]).not_to be_nil
end
it 'has the branch URL' do
- expect(subject.test_events.first[:branch][:url]).not_to be_nil
+ expect(events.first[:branch][:url]).not_to be_nil
end
it 'has the short SHA' do
- expect(subject.test_events.first[:short_sha]).not_to be_nil
+ expect(events.first[:short_sha]).not_to be_nil
end
it 'has the commit URL' do
- expect(subject.test_events.first[:commit_url]).not_to be_nil
+ expect(events.first[:commit_url]).not_to be_nil
end
it 'has the date' do
- expect(subject.test_events.first[:date]).not_to be_nil
+ expect(events.first[:date]).not_to be_nil
end
it 'has the total time' do
- expect(subject.test_events.first[:total_time]).not_to be_empty
+ expect(events.first[:total_time]).not_to be_empty
end
end
describe '#review_events' do
+ let(:stage) { :review }
let!(:context) { create(:issue, project: project, created_at: 2.days.ago) }
it 'has the total time' do
- expect(subject.review_events.first[:total_time]).not_to be_empty
+ expect(events.first[:total_time]).not_to be_empty
end
it 'has a title' do
- expect(subject.review_events.first[:title]).to eq('Awesome merge_request')
+ expect(events.first[:title]).to eq('Awesome merge_request')
end
it 'has an iid' do
- expect(subject.review_events.first[:iid]).to eq(context.iid.to_s)
+ expect(events.first[:iid]).to eq(context.iid.to_s)
end
it 'has the URL' do
- expect(subject.review_events.first[:url]).not_to be_nil
+ expect(events.first[:url]).not_to be_nil
end
it 'has a state' do
- expect(subject.review_events.first[:state]).not_to be_nil
+ expect(events.first[:state]).not_to be_nil
end
it 'has a created_at timestamp' do
- expect(subject.review_events.first[:created_at]).not_to be_nil
+ expect(events.first[:created_at]).not_to be_nil
end
it "has the author's URL" do
- expect(subject.review_events.first[:author][:web_url]).not_to be_nil
+ expect(events.first[:author][:web_url]).not_to be_nil
end
it "has the author's avatar URL" do
- expect(subject.review_events.first[:author][:avatar_url]).not_to be_nil
+ expect(events.first[:author][:avatar_url]).not_to be_nil
end
it "has the author's name" do
- expect(subject.review_events.first[:author][:name]).to eq(MergeRequest.first.author.name)
+ expect(events.first[:author][:name]).to eq(MergeRequest.first.author.name)
end
end
describe '#staging_events' do
+ let(:stage) { :staging }
let(:merge_request) { MergeRequest.first }
let!(:pipeline) do
create(:ci_pipeline,
@@ -227,55 +239,56 @@ describe Gitlab::CycleAnalytics::Events do
end
it 'has the name' do
- expect(subject.staging_events.first[:name]).not_to be_nil
+ expect(events.first[:name]).not_to be_nil
end
it 'has the ID' do
- expect(subject.staging_events.first[:id]).not_to be_nil
+ expect(events.first[:id]).not_to be_nil
end
it 'has the URL' do
- expect(subject.staging_events.first[:url]).not_to be_nil
+ expect(events.first[:url]).not_to be_nil
end
it 'has the branch name' do
- expect(subject.staging_events.first[:branch]).not_to be_nil
+ expect(events.first[:branch]).not_to be_nil
end
it 'has the branch URL' do
- expect(subject.staging_events.first[:branch][:url]).not_to be_nil
+ expect(events.first[:branch][:url]).not_to be_nil
end
it 'has the short SHA' do
- expect(subject.staging_events.first[:short_sha]).not_to be_nil
+ expect(events.first[:short_sha]).not_to be_nil
end
it 'has the commit URL' do
- expect(subject.staging_events.first[:commit_url]).not_to be_nil
+ expect(events.first[:commit_url]).not_to be_nil
end
it 'has the date' do
- expect(subject.staging_events.first[:date]).not_to be_nil
+ expect(events.first[:date]).not_to be_nil
end
it 'has the total time' do
- expect(subject.staging_events.first[:total_time]).not_to be_empty
+ expect(events.first[:total_time]).not_to be_empty
end
it "has the author's URL" do
- expect(subject.staging_events.first[:author][:web_url]).not_to be_nil
+ expect(events.first[:author][:web_url]).not_to be_nil
end
it "has the author's avatar URL" do
- expect(subject.staging_events.first[:author][:avatar_url]).not_to be_nil
+ expect(events.first[:author][:avatar_url]).not_to be_nil
end
it "has the author's name" do
- expect(subject.staging_events.first[:author][:name]).to eq(MergeRequest.first.author.name)
+ expect(events.first[:author][:name]).to eq(MergeRequest.first.author.name)
end
end
describe '#production_events' do
+ let(:stage) { :production }
let!(:context) { create(:issue, project: project, created_at: 2.days.ago) }
before do
@@ -284,35 +297,35 @@ describe Gitlab::CycleAnalytics::Events do
end
it 'has the total time' do
- expect(subject.production_events.first[:total_time]).not_to be_empty
+ expect(events.first[:total_time]).not_to be_empty
end
it 'has a title' do
- expect(subject.production_events.first[:title]).to eq(context.title)
+ expect(events.first[:title]).to eq(context.title)
end
it 'has the URL' do
- expect(subject.production_events.first[:url]).not_to be_nil
+ expect(events.first[:url]).not_to be_nil
end
it 'has an iid' do
- expect(subject.production_events.first[:iid]).to eq(context.iid.to_s)
+ expect(events.first[:iid]).to eq(context.iid.to_s)
end
it 'has a created_at timestamp' do
- expect(subject.production_events.first[:created_at]).to end_with('ago')
+ expect(events.first[:created_at]).to end_with('ago')
end
it "has the author's URL" do
- expect(subject.production_events.first[:author][:web_url]).not_to be_nil
+ expect(events.first[:author][:web_url]).not_to be_nil
end
it "has the author's avatar URL" do
- expect(subject.production_events.first[:author][:avatar_url]).not_to be_nil
+ expect(events.first[:author][:avatar_url]).not_to be_nil
end
it "has the author's name" do
- expect(subject.production_events.first[:author][:name]).to eq(context.author.name)
+ expect(events.first[:author][:name]).to eq(context.author.name)
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
new file mode 100644
index 00000000000..fd9fa2fee49
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
@@ -0,0 +1,8 @@
+require 'spec_helper'
+require 'lib/gitlab/cycle_analytics/shared_event_spec'
+
+describe Gitlab::CycleAnalytics::IssueEventFetcher do
+ let(:stage_name) { :issue }
+
+ it_behaves_like 'default query config'
+end
diff --git a/spec/lib/gitlab/cycle_analytics/issue_event_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_event_spec.rb
deleted file mode 100644
index 1c5c308da7d..00000000000
--- a/spec/lib/gitlab/cycle_analytics/issue_event_spec.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'spec_helper'
-require 'lib/gitlab/cycle_analytics/shared_event_spec'
-
-describe Gitlab::CycleAnalytics::IssueEvent do
- it_behaves_like 'default query config' do
- it 'has the default order' do
- expect(event.order).to eq(event.start_time_attrs)
- end
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
new file mode 100644
index 00000000000..3127f01989d
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
@@ -0,0 +1,8 @@
+require 'spec_helper'
+require 'lib/gitlab/cycle_analytics/shared_stage_spec'
+
+describe Gitlab::CycleAnalytics::IssueStage do
+ let(:stage_name) { :issue }
+
+ it_behaves_like 'base stage'
+end
diff --git a/spec/lib/gitlab/cycle_analytics/plan_event_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
index 4a5604115ec..2e5dc5b5547 100644
--- a/spec/lib/gitlab/cycle_analytics/plan_event_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
@@ -1,15 +1,13 @@
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
-describe Gitlab::CycleAnalytics::PlanEvent do
- it_behaves_like 'default query config' do
- it 'has the default order' do
- expect(event.order).to eq(event.start_time_attrs)
- end
+describe Gitlab::CycleAnalytics::PlanEventFetcher do
+ let(:stage_name) { :plan }
+ it_behaves_like 'default query config' do
context 'no commits' do
it 'does not blow up if there are no commits' do
- allow_any_instance_of(Gitlab::CycleAnalytics::EventsQuery).to receive(:execute).and_return([{}])
+ allow(event).to receive(:event_result).and_return([{}])
expect { event.fetch }.not_to raise_error
end
diff --git a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
new file mode 100644
index 00000000000..4c715921ad6
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
@@ -0,0 +1,8 @@
+require 'spec_helper'
+require 'lib/gitlab/cycle_analytics/shared_stage_spec'
+
+describe Gitlab::CycleAnalytics::PlanStage do
+ let(:stage_name) { :plan }
+
+ it_behaves_like 'base stage'
+end
diff --git a/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
new file mode 100644
index 00000000000..74001181305
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
@@ -0,0 +1,8 @@
+require 'spec_helper'
+require 'lib/gitlab/cycle_analytics/shared_event_spec'
+
+describe Gitlab::CycleAnalytics::ProductionEventFetcher do
+ let(:stage_name) { :production }
+
+ it_behaves_like 'default query config'
+end
diff --git a/spec/lib/gitlab/cycle_analytics/production_event_spec.rb b/spec/lib/gitlab/cycle_analytics/production_event_spec.rb
deleted file mode 100644
index ac17e3b4287..00000000000
--- a/spec/lib/gitlab/cycle_analytics/production_event_spec.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'spec_helper'
-require 'lib/gitlab/cycle_analytics/shared_event_spec'
-
-describe Gitlab::CycleAnalytics::ProductionEvent do
- it_behaves_like 'default query config' do
- it 'has the default order' do
- expect(event.order).to eq(event.start_time_attrs)
- end
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
new file mode 100644
index 00000000000..916684b81eb
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
@@ -0,0 +1,8 @@
+require 'spec_helper'
+require 'lib/gitlab/cycle_analytics/shared_stage_spec'
+
+describe Gitlab::CycleAnalytics::ProductionStage do
+ let(:stage_name) { :production }
+
+ it_behaves_like 'base stage'
+end
diff --git a/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
new file mode 100644
index 00000000000..4f67c95ed4c
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
@@ -0,0 +1,8 @@
+require 'spec_helper'
+require 'lib/gitlab/cycle_analytics/shared_event_spec'
+
+describe Gitlab::CycleAnalytics::ReviewEventFetcher do
+ let(:stage_name) { :review }
+
+ it_behaves_like 'default query config'
+end
diff --git a/spec/lib/gitlab/cycle_analytics/review_event_spec.rb b/spec/lib/gitlab/cycle_analytics/review_event_spec.rb
deleted file mode 100644
index 1ff53aa0227..00000000000
--- a/spec/lib/gitlab/cycle_analytics/review_event_spec.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'spec_helper'
-require 'lib/gitlab/cycle_analytics/shared_event_spec'
-
-describe Gitlab::CycleAnalytics::ReviewEvent do
- it_behaves_like 'default query config' do
- it 'has the default order' do
- expect(event.order).to eq(event.start_time_attrs)
- end
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
new file mode 100644
index 00000000000..1412c8dfa08
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
@@ -0,0 +1,8 @@
+require 'spec_helper'
+require 'lib/gitlab/cycle_analytics/shared_stage_spec'
+
+describe Gitlab::CycleAnalytics::ReviewStage do
+ let(:stage_name) { :review }
+
+ it_behaves_like 'base stage'
+end
diff --git a/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb b/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
index 7019e4c3351..9c5e57342e9 100644
--- a/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
@@ -1,20 +1,13 @@
require 'spec_helper'
shared_examples 'default query config' do
- let(:event) { described_class.new(project: double, options: {}) }
-
- it 'has the start attributes' do
- expect(event.start_time_attrs).not_to be_nil
- end
+ let(:project) { create(:empty_project) }
+ let(:event) { described_class.new(project: project, stage: stage_name, options: { from: 1.day.ago }) }
it 'has the stage attribute' do
expect(event.stage).not_to be_nil
end
- it 'has the end attributes' do
- expect(event.end_time_attrs).not_to be_nil
- end
-
it 'has the projection attributes' do
expect(event.projections).not_to be_nil
end
diff --git a/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
new file mode 100644
index 00000000000..08425acbfc8
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+shared_examples 'base stage' do
+ let(:stage) { described_class.new(project: double, options: {}) }
+
+ before do
+ allow(stage).to receive(:median).and_return(1.12)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({})
+ end
+
+ it 'has the median data value' do
+ expect(stage.as_json[:value]).not_to be_nil
+ end
+
+ it 'has the median data stage' do
+ expect(stage.as_json[:title]).not_to be_nil
+ end
+
+ it 'has the median data description' do
+ expect(stage.as_json[:description]).not_to be_nil
+ end
+
+ it 'has the title' do
+ expect(stage.title).to eq(stage_name.to_s.capitalize)
+ end
+
+ it 'has the events' do
+ expect(stage.events).not_to be_nil
+ end
+end
diff --git a/spec/models/cycle_analytics/summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
index 725bc68b25f..fb6b6c4a8d2 100644
--- a/spec/models/cycle_analytics/summary_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
@@ -1,23 +1,23 @@
require 'spec_helper'
-describe CycleAnalytics::Summary, models: true do
+describe Gitlab::CycleAnalytics::StageSummary, models: true do
let(:project) { create(:project) }
- let(:from) { Time.now }
+ let(:from) { 1.day.ago }
let(:user) { create(:user, :admin) }
- subject { described_class.new(project, user, from: from) }
+ subject { described_class.new(project, from: Time.now, current_user: user).data }
describe "#new_issues" do
it "finds the number of issues created after the 'from date'" do
Timecop.freeze(5.days.ago) { create(:issue, project: project) }
Timecop.freeze(5.days.from_now) { create(:issue, project: project) }
- expect(subject.new_issues).to eq(1)
+ expect(subject.first[:value]).to eq(1)
end
it "doesn't find issues from other projects" do
Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project)) }
- expect(subject.new_issues).to eq(0)
+ expect(subject.first[:value]).to eq(0)
end
end
@@ -26,19 +26,19 @@ describe CycleAnalytics::Summary, models: true do
Timecop.freeze(5.days.ago) { create_commit("Test message", project, user, 'master') }
Timecop.freeze(5.days.from_now) { create_commit("Test message", project, user, 'master') }
- expect(subject.commits).to eq(1)
+ expect(subject.second[:value]).to eq(1)
end
it "doesn't find commits from other projects" do
Timecop.freeze(5.days.from_now) { create_commit("Test message", create(:project), user, 'master') }
- expect(subject.commits).to eq(0)
+ expect(subject.second[:value]).to eq(0)
end
it "finds a large (> 100) snumber of commits if present" do
Timecop.freeze(5.days.from_now) { create_commit("Test message", project, user, 'master', count: 100) }
- expect(subject.commits).to eq(100)
+ expect(subject.second[:value]).to eq(100)
end
end
@@ -47,13 +47,13 @@ describe CycleAnalytics::Summary, models: true do
Timecop.freeze(5.days.ago) { create(:deployment, project: project) }
Timecop.freeze(5.days.from_now) { create(:deployment, project: project) }
- expect(subject.deploys).to eq(1)
+ expect(subject.third[:value]).to eq(1)
end
it "doesn't find commits from other projects" do
Timecop.freeze(5.days.from_now) { create(:deployment, project: create(:project)) }
- expect(subject.deploys).to eq(0)
+ expect(subject.third[:value]).to eq(0)
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
new file mode 100644
index 00000000000..bbc82496340
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+require 'lib/gitlab/cycle_analytics/shared_event_spec'
+
+describe Gitlab::CycleAnalytics::StagingEventFetcher do
+ let(:stage_name) { :staging }
+
+ it_behaves_like 'default query config' do
+ it 'has a default order' do
+ expect(event.order).not_to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cycle_analytics/staging_event_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_event_spec.rb
deleted file mode 100644
index 4862d4765f2..00000000000
--- a/spec/lib/gitlab/cycle_analytics/staging_event_spec.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'spec_helper'
-require 'lib/gitlab/cycle_analytics/shared_event_spec'
-
-describe Gitlab::CycleAnalytics::StagingEvent do
- it_behaves_like 'default query config' do
- it 'does not have the default order' do
- expect(event.order).not_to eq(event.start_time_attrs)
- end
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
new file mode 100644
index 00000000000..8154b3ac701
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
@@ -0,0 +1,8 @@
+require 'spec_helper'
+require 'lib/gitlab/cycle_analytics/shared_stage_spec'
+
+describe Gitlab::CycleAnalytics::StagingStage do
+ let(:stage_name) { :staging }
+
+ it_behaves_like 'base stage'
+end
diff --git a/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
new file mode 100644
index 00000000000..6639fa54e0e
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+require 'lib/gitlab/cycle_analytics/shared_event_spec'
+
+describe Gitlab::CycleAnalytics::TestEventFetcher do
+ let(:stage_name) { :test }
+
+ it_behaves_like 'default query config' do
+ it 'has a default order' do
+ expect(event.order).not_to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cycle_analytics/test_event_spec.rb b/spec/lib/gitlab/cycle_analytics/test_event_spec.rb
deleted file mode 100644
index e249db69fc6..00000000000
--- a/spec/lib/gitlab/cycle_analytics/test_event_spec.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'spec_helper'
-require 'lib/gitlab/cycle_analytics/shared_event_spec'
-
-describe Gitlab::CycleAnalytics::TestEvent do
- it_behaves_like 'default query config' do
- it 'does not have the default order' do
- expect(event.order).not_to eq(event.start_time_attrs)
- end
- end
-end
diff --git a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
new file mode 100644
index 00000000000..eacde22cd56
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
@@ -0,0 +1,8 @@
+require 'spec_helper'
+require 'lib/gitlab/cycle_analytics/shared_stage_spec'
+
+describe Gitlab::CycleAnalytics::TestStage do
+ let(:stage_name) { :test }
+
+ it_behaves_like 'base stage'
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index ceed9c942c1..7fb6829f582 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -15,6 +15,7 @@ issues:
- events
- merge_requests_closing_issues
- metrics
+- timelogs
events:
- author
- project
@@ -77,6 +78,7 @@ merge_requests:
- events
- merge_requests_closing_issues
- metrics
+- timelogs
merge_request_diff:
- merge_request
pipelines:
@@ -198,3 +200,6 @@ award_emoji:
- user
priorities:
- label
+timelogs:
+- trackable
+- user
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index d88a141b458..493bc2db21a 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -20,6 +20,7 @@ Issue:
- lock_version
- milestone_id
- weight
+- time_estimate
Event:
- id
- target_type
@@ -150,6 +151,7 @@ MergeRequest:
- milestone_id
- approvals_before_merge
- rebase_commit_sha
+- time_estimate
MergeRequestDiff:
- id
- state
@@ -344,3 +346,11 @@ LabelPriority:
- priority
- created_at
- updated_at
+Timelog:
+- id
+- time_spent
+- trackable_id
+- trackable_type
+- user_id
+- created_at
+- updated_at
diff --git a/spec/lib/gitlab/view/presenter/base_spec.rb b/spec/lib/gitlab/view/presenter/base_spec.rb
new file mode 100644
index 00000000000..f2c152cdcd4
--- /dev/null
+++ b/spec/lib/gitlab/view/presenter/base_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Gitlab::View::Presenter::Base do
+ let(:project) { double(:project) }
+ let(:presenter_class) do
+ Struct.new(:subject).include(described_class)
+ end
+
+ describe '.presenter?' do
+ it 'returns true' do
+ presenter = presenter_class.new(project)
+
+ expect(presenter.class).to be_presenter
+ end
+ end
+
+ describe '.presents' do
+ it 'exposes #subject with the given keyword' do
+ presenter_class.presents(:foo)
+ presenter = presenter_class.new(project)
+
+ expect(presenter.foo).to eq(project)
+ end
+ end
+
+ describe '#can?' do
+ context 'user is not allowed' do
+ it 'returns false' do
+ presenter = presenter_class.new(build_stubbed(:empty_project))
+
+ expect(presenter.can?(nil, :read_project)).to be_falsy
+ end
+ end
+
+ context 'user is allowed' do
+ it 'returns true' do
+ presenter = presenter_class.new(build_stubbed(:empty_project, :public))
+
+ expect(presenter.can?(nil, :read_project)).to be_truthy
+ end
+ end
+
+ context 'subject is overriden' do
+ it 'returns true' do
+ presenter = presenter_class.new(build_stubbed(:empty_project, :public))
+
+ expect(presenter.can?(nil, :read_project, build_stubbed(:empty_project))).to be_falsy
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/view/presenter/delegated_spec.rb b/spec/lib/gitlab/view/presenter/delegated_spec.rb
new file mode 100644
index 00000000000..888ab80cad5
--- /dev/null
+++ b/spec/lib/gitlab/view/presenter/delegated_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Gitlab::View::Presenter::Delegated do
+ let(:project) { double(:project, bar: 'baz') }
+ let(:presenter_class) do
+ Class.new(described_class)
+ end
+
+ it 'includes Gitlab::View::Presenter::Base' do
+ expect(described_class).to include(Gitlab::View::Presenter::Base)
+ end
+
+ describe '#initialize' do
+ it 'takes arbitrary key/values and exposes them' do
+ presenter = presenter_class.new(project, user: 'user', foo: 'bar')
+
+ expect(presenter.user).to eq('user')
+ expect(presenter.foo).to eq('bar')
+ end
+ end
+
+ describe 'delegation' do
+ it 'forwards missing methods to subject' do
+ presenter = presenter_class.new(project)
+
+ expect(presenter.bar).to eq('baz')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/view/presenter/factory_spec.rb b/spec/lib/gitlab/view/presenter/factory_spec.rb
new file mode 100644
index 00000000000..55c5ecbf92f
--- /dev/null
+++ b/spec/lib/gitlab/view/presenter/factory_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe Gitlab::View::Presenter::Factory do
+ let(:build) { Ci::Build.new }
+
+ describe '#initialize' do
+ context 'without optional parameters' do
+ it 'takes a subject and optional params' do
+ presenter = described_class.new(build)
+
+ expect { presenter }.not_to raise_error
+ end
+ end
+
+ context 'with optional parameters' do
+ it 'takes a subject and optional params' do
+ presenter = described_class.new(build, user: 'user')
+
+ expect { presenter }.not_to raise_error
+ end
+ end
+ end
+
+ describe '#fabricate!' do
+ it 'exposes given params' do
+ presenter = described_class.new(build, user: 'user', foo: 'bar').fabricate!
+
+ expect(presenter.user).to eq('user')
+ expect(presenter.foo).to eq('bar')
+ end
+
+ it 'detects the presenter based on the given subject' do
+ presenter = described_class.new(build).fabricate!
+
+ expect(presenter).to be_a(Ci::BuildPresenter)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/view/presenter/simple_spec.rb b/spec/lib/gitlab/view/presenter/simple_spec.rb
new file mode 100644
index 00000000000..b489bdf1981
--- /dev/null
+++ b/spec/lib/gitlab/view/presenter/simple_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Gitlab::View::Presenter::Simple do
+ let(:project) { double(:project) }
+ let(:presenter_class) do
+ Class.new(described_class)
+ end
+
+ it 'includes Gitlab::View::Presenter::Base' do
+ expect(described_class).to include(Gitlab::View::Presenter::Base)
+ end
+
+ describe '#initialize' do
+ it 'takes arbitrary key/values and exposes them' do
+ presenter = presenter_class.new(project, user: 'user', foo: 'bar')
+
+ expect(presenter.user).to eq('user')
+ expect(presenter.foo).to eq('bar')
+ end
+ end
+
+ describe 'delegation' do
+ it 'does not forward missing methods to subject' do
+ presenter = presenter_class.new(project)
+
+ expect { presenter.foo }.to raise_error(NoMethodError)
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index af0f6a31eda..3309a7fff9f 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1013,6 +1013,24 @@ describe Ci::Build, :models do
end
end
+ describe '#has_expiring_artifacts?' do
+ context 'when artifacts have expiration date set' do
+ before { build.update(artifacts_expire_at: 1.day.from_now) }
+
+ it 'has expiring artifacts' do
+ expect(build).to have_expiring_artifacts
+ end
+ end
+
+ context 'when artifacts do not have expiration date set' do
+ before { build.update(artifacts_expire_at: nil) }
+
+ it 'does not have expiring artifacts' do
+ expect(build).not_to have_expiring_artifacts
+ end
+ end
+ end
+
describe '#has_trace_file?' do
context 'when there is no trace' do
it { expect(build.has_trace_file?).to be_falsey }
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 74b50d2908d..0d425ab7fd4 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -323,4 +323,32 @@ eos
expect(new_commit.message).to eq(commit.message)
end
end
+
+ describe '#work_in_progress?' do
+ ['squash! ', 'fixup! ', 'wip: ', 'WIP: ', '[WIP] '].each do |wip_prefix|
+ it "detects the '#{wip_prefix}' prefix" do
+ commit.message = "#{wip_prefix}#{commit.message}"
+
+ expect(commit).to be_work_in_progress
+ end
+ end
+
+ it "detects WIP for a commit just saying 'wip'" do
+ commit.message = "wip"
+
+ expect(commit).to be_work_in_progress
+ end
+
+ it "doesn't detect WIP for a commit that begins with 'FIXUP! '" do
+ commit.message = "FIXUP! #{commit.message}"
+
+ expect(commit).not_to be_work_in_progress
+ end
+
+ it "doesn't detect WIP for words starting with WIP" do
+ commit.message = "Wipout #{commit.message}"
+
+ expect(commit).not_to be_work_in_progress
+ end
+ end
end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 1078c959419..d7d31892e12 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -408,4 +408,42 @@ describe Issue, "Issuable" do
expect(issue.assignee_or_author?(user)).to eq(false)
end
end
+
+ describe '#spend_time' do
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue) }
+
+ def spend_time(seconds)
+ issue.spend_time(duration: seconds, user: user)
+ issue.save!
+ end
+
+ context 'adding time' do
+ it 'should update the total time spent' do
+ spend_time(1800)
+
+ expect(issue.total_time_spent).to eq(1800)
+ end
+ end
+
+ context 'substracting time' do
+ before do
+ spend_time(1800)
+ end
+
+ it 'should update the total time spent' do
+ spend_time(-900)
+
+ expect(issue.total_time_spent).to eq(900)
+ end
+
+ context 'when time to substract exceeds the total time spent' do
+ it 'raise a validation error' do
+ expect do
+ spend_time(-3600)
+ end.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/presentable_spec.rb b/spec/models/concerns/presentable_spec.rb
new file mode 100644
index 00000000000..941647a79fb
--- /dev/null
+++ b/spec/models/concerns/presentable_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe Presentable do
+ let(:build) { Ci::Build.new }
+
+ describe '#present' do
+ it 'returns a presenter' do
+ expect(build.present).to be_a(Ci::BuildPresenter)
+ end
+
+ it 'takes optional attributes' do
+ expect(build.present(foo: 'bar').foo).to eq('bar')
+ end
+ end
+end
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index 7771785ead3..70f985afefb 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#code', feature: true do
let(:project) { create(:project) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, user, from: from_date) }
+ subject { CycleAnalytics.new(project, from: from_date) }
context 'with deployment' do
generate_cycle_analytics_spec(
@@ -16,10 +16,10 @@ describe 'CycleAnalytics#code', feature: true do
-> (context, data) do
context.create_commit_referencing_issue(data[:issue])
end]],
- end_time_conditions: [["merge request that closes issue is created",
- -> (context, data) do
- context.create_merge_request_closing_issue(data[:issue])
- end]],
+ end_time_conditions: [["merge request that closes issue is created",
+ -> (context, data) do
+ context.create_merge_request_closing_issue(data[:issue])
+ end]],
post_fn: -> (context, data) do
context.merge_merge_requests_closing_issue(data[:issue])
context.deploy_master
@@ -37,7 +37,7 @@ describe 'CycleAnalytics#code', feature: true do
deploy_master
end
- expect(subject.code).to be_nil
+ expect(subject[:code].median).to be_nil
end
end
end
@@ -50,10 +50,10 @@ describe 'CycleAnalytics#code', feature: true do
-> (context, data) do
context.create_commit_referencing_issue(data[:issue])
end]],
- end_time_conditions: [["merge request that closes issue is created",
- -> (context, data) do
- context.create_merge_request_closing_issue(data[:issue])
- end]],
+ end_time_conditions: [["merge request that closes issue is created",
+ -> (context, data) do
+ context.create_merge_request_closing_issue(data[:issue])
+ end]],
post_fn: -> (context, data) do
context.merge_merge_requests_closing_issue(data[:issue])
end)
@@ -69,7 +69,7 @@ describe 'CycleAnalytics#code', feature: true do
merge_merge_requests_closing_issue(issue)
end
- expect(subject.code).to be_nil
+ expect(subject[:code].median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index 5ed3d37f2fb..e4b6a8f4518 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#issue', models: true do
let(:project) { create(:project) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, user, from: from_date) }
+ subject { CycleAnalytics.new(project, from: from_date) }
generate_cycle_analytics_spec(
phase: :issue,
@@ -42,7 +42,7 @@ describe 'CycleAnalytics#issue', models: true do
merge_merge_requests_closing_issue(issue)
end
- expect(subject.issue).to be_nil
+ expect(subject[:issue].median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
index baf3e3241a1..dc5b04852d6 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#plan', feature: true do
let(:project) { create(:project) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, user, from: from_date) }
+ subject { CycleAnalytics.new(project, from: from_date) }
generate_cycle_analytics_spec(
phase: :plan,
@@ -44,7 +44,7 @@ describe 'CycleAnalytics#plan', feature: true do
create_merge_request_closing_issue(issue, source_branch: branch_name)
merge_merge_requests_closing_issue(issue)
- expect(subject.issue).to be_nil
+ expect(subject[:issue].median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index 21b9c6e7150..5e99188f318 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#production', feature: true do
let(:project) { create(:project) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, user, from: from_date) }
+ subject { CycleAnalytics.new(project, from: from_date) }
generate_cycle_analytics_spec(
phase: :production,
@@ -35,7 +35,7 @@ describe 'CycleAnalytics#production', feature: true do
deploy_master
end
- expect(subject.production).to be_nil
+ expect(subject[:production].median).to be_nil
end
end
@@ -48,7 +48,7 @@ describe 'CycleAnalytics#production', feature: true do
deploy_master(environment: 'staging')
end
- expect(subject.production).to be_nil
+ expect(subject[:production].median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index 158621d59a4..45baa5f7006 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#review', feature: true do
let(:project) { create(:project) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, user, from: from_date) }
+ subject { CycleAnalytics.new(project, from: from_date) }
generate_cycle_analytics_spec(
phase: :review,
@@ -27,7 +27,7 @@ describe 'CycleAnalytics#review', feature: true do
MergeRequests::MergeService.new(project, user).execute(create(:merge_request))
end
- expect(subject.review).to be_nil
+ expect(subject[:review].median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index dad653964b7..77625aad580 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#staging', feature: true do
let(:project) { create(:project) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, user, from: from_date) }
+ subject { CycleAnalytics.new(project, from: from_date) }
generate_cycle_analytics_spec(
phase: :staging,
@@ -45,7 +45,7 @@ describe 'CycleAnalytics#staging', feature: true do
deploy_master
end
- expect(subject.staging).to be_nil
+ expect(subject[:staging].median).to be_nil
end
end
@@ -58,7 +58,7 @@ describe 'CycleAnalytics#staging', feature: true do
deploy_master(environment: 'staging')
end
- expect(subject.staging).to be_nil
+ expect(subject[:staging].median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index 2313724e8f3..27a117d2d76 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#test', feature: true do
let(:project) { create(:project) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, user, from: from_date) }
+ subject { CycleAnalytics.new(project, from: from_date) }
generate_cycle_analytics_spec(
phase: :test,
@@ -35,7 +35,7 @@ describe 'CycleAnalytics#test', feature: true do
merge_merge_requests_closing_issue(issue)
end
- expect(subject.test).to be_nil
+ expect(subject[:test].median).to be_nil
end
end
@@ -48,7 +48,7 @@ describe 'CycleAnalytics#test', feature: true do
pipeline.succeed!
end
- expect(subject.test).to be_nil
+ expect(subject[:test].median).to be_nil
end
end
@@ -65,7 +65,7 @@ describe 'CycleAnalytics#test', feature: true do
merge_merge_requests_closing_issue(issue)
end
- expect(subject.test).to be_nil
+ expect(subject[:test].median).to be_nil
end
end
@@ -82,7 +82,7 @@ describe 'CycleAnalytics#test', feature: true do
merge_merge_requests_closing_issue(issue)
end
- expect(subject.test).to be_nil
+ expect(subject[:test].median).to be_nil
end
end
end
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index 6004bfdb7b7..f4c3e6d503f 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -1,10 +1,20 @@
require 'spec_helper'
describe GenericCommitStatus, models: true do
- let(:pipeline) { create(:ci_pipeline) }
+ let(:project) { create(:empty_project) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:external_url) { 'http://example.gitlab.com/status' }
let(:generic_commit_status) do
- create(:generic_commit_status, pipeline: pipeline)
+ create(:generic_commit_status, pipeline: pipeline,
+ target_url: external_url)
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_length_of(:target_url).is_at_most(255) }
+ it { is_expected.to allow_value(nil).for(:target_url) }
+ it { is_expected.to allow_value('http://gitlab.com/s').for(:target_url) }
+ it { is_expected.not_to allow_value('javascript:alert(1)').for(:target_url) }
end
describe '#context' do
@@ -22,10 +32,25 @@ describe GenericCommitStatus, models: true do
describe '#detailed_status' do
let(:user) { create(:user) }
+ let(:status) { generic_commit_status.detailed_status(user) }
it 'returns detailed status object' do
- expect(generic_commit_status.detailed_status(user))
- .to be_a Gitlab::Ci::Status::Success
+ expect(status).to be_a Gitlab::Ci::Status::Success
+ end
+
+ context 'when user has ability to see datails' do
+ before { project.team << [user, :developer] }
+
+ it 'details path points to an external URL' do
+ expect(status).to have_details
+ expect(status.details_path).to eq external_url
+ end
+ end
+
+ context 'when user should not see details' do
+ it 'does not have details' do
+ expect(status).not_to have_details
+ end
end
end
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index eb876d105da..6d599e148a2 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -76,6 +76,32 @@ describe MergeRequestDiff, models: true do
end
end
+ describe '#save_diffs' do
+ it 'saves collected state' do
+ mr_diff = create(:merge_request).merge_request_diff
+
+ expect(mr_diff.collected?).to be_truthy
+ end
+
+ it 'saves overflow state' do
+ allow(Commit).to receive(:max_diff_options)
+ .and_return(max_lines: 0, max_files: 0)
+
+ mr_diff = create(:merge_request).merge_request_diff
+
+ expect(mr_diff.overflow?).to be_truthy
+ end
+
+ it 'saves empty state' do
+ allow_any_instance_of(MergeRequestDiff).to receive(:commits)
+ .and_return([])
+
+ mr_diff = create(:merge_request).merge_request_diff
+
+ expect(mr_diff.empty?).to be_truthy
+ end
+ end
+
describe '#commits_sha' do
it 'returns all commits SHA using serialized commits' do
subject.st_commits = [
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 8d1385016fd..38c80ba53ad 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -773,10 +773,12 @@ describe MergeRequest, models: true do
subject { create(:merge_request, source_project: project, merge_status: :unchecked) }
context 'when it is not broken and has no conflicts' do
- it 'is marked as mergeable' do
+ before do
allow(subject).to receive(:broken?) { false }
allow(project.repository).to receive(:can_be_merged?).and_return(true)
+ end
+ it 'is marked as mergeable' do
expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged')
end
end
@@ -787,6 +789,12 @@ describe MergeRequest, models: true do
it 'becomes unmergeable' do
expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged')
end
+
+ it 'creates Todo on unmergeability' do
+ expect_any_instance_of(TodoService).to receive(:merge_request_became_unmergeable).with(subject)
+
+ subject.check_if_can_be_merged
+ end
end
context 'when it has conflicts' do
@@ -1514,6 +1522,108 @@ describe MergeRequest, models: true do
end
end
+ describe '#mergeable_with_slash_command?' do
+ def create_pipeline(status)
+ create(:ci_pipeline_with_one_job,
+ project: project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha,
+ status: status)
+ end
+
+ let(:project) { create(:project, :public, only_allow_merge_if_build_succeeds: true) }
+ let(:developer) { create(:user) }
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:mr_sha) { merge_request.diff_head_sha }
+
+ before do
+ project.team << [developer, :developer]
+ end
+
+ context 'when autocomplete_precheck is set to true' do
+ it 'is mergeable by developer' do
+ expect(merge_request.mergeable_with_slash_command?(developer, autocomplete_precheck: true)).to be_truthy
+ end
+
+ it 'is not mergeable by normal user' do
+ expect(merge_request.mergeable_with_slash_command?(user, autocomplete_precheck: true)).to be_falsey
+ end
+ end
+
+ context 'when autocomplete_precheck is set to false' do
+ it 'is mergeable by developer' do
+ expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_truthy
+ end
+
+ it 'is not mergeable by normal user' do
+ expect(merge_request.mergeable_with_slash_command?(user, last_diff_sha: mr_sha)).to be_falsey
+ end
+
+ context 'closed MR' do
+ before do
+ merge_request.update_attribute(:state, :closed)
+ end
+
+ it 'is not mergeable' do
+ expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_falsey
+ end
+ end
+
+ context 'MR with WIP' do
+ before do
+ merge_request.update_attribute(:title, 'WIP: some MR')
+ end
+
+ it 'is not mergeable' do
+ expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_falsey
+ end
+ end
+
+ context 'sha differs from the MR diff_head_sha' do
+ it 'is not mergeable' do
+ expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: 'some other sha')).to be_falsey
+ end
+ end
+
+ context 'sha is not provided' do
+ it 'is not mergeable' do
+ expect(merge_request.mergeable_with_slash_command?(developer)).to be_falsey
+ end
+ end
+
+ context 'with pipeline ok' do
+ before do
+ create_pipeline(:success)
+ end
+
+ it 'is mergeable' do
+ expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_truthy
+ end
+ end
+
+ context 'with failing pipeline' do
+ before do
+ create_pipeline(:failed)
+ end
+
+ it 'is not mergeable' do
+ expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_falsey
+ end
+ end
+
+ context 'with running pipeline' do
+ before do
+ create_pipeline(:running)
+ end
+
+ it 'is mergeable' do
+ expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_truthy
+ end
+ end
+ end
+ end
+
describe '#has_commits?' do
before do
allow(subject.merge_request_diff).to receive(:commits_count).
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index 77403cc9eb0..ff29f6f66ba 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -107,7 +107,7 @@ describe ProjectStatistics, models: true do
describe '#update_repository_size' do
before do
- allow(project.repository).to receive(:size).and_return(12.megabytes)
+ allow(project.repository).to receive(:size).and_return(12)
statistics.update_repository_size
end
diff --git a/spec/models/timelog_spec.rb b/spec/models/timelog_spec.rb
new file mode 100644
index 00000000000..f08935b6425
--- /dev/null
+++ b/spec/models/timelog_spec.rb
@@ -0,0 +1,10 @@
+require 'rails_helper'
+
+RSpec.describe Timelog, type: :model do
+ subject { build(:timelog) }
+
+ it { is_expected.to be_valid }
+
+ it { is_expected.to validate_presence_of(:time_spent) }
+ it { is_expected.to validate_presence_of(:user) }
+end
diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb
new file mode 100644
index 00000000000..63acc0b68cd
--- /dev/null
+++ b/spec/policies/base_policy_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe BasePolicy, models: true do
+ let(:build) { Ci::Build.new }
+
+ describe '.class_for' do
+ it 'detects policy class based on the subject ancestors' do
+ expect(described_class.class_for(build)).to eq(Ci::BuildPolicy)
+ end
+
+ it 'detects policy class for a presented subject' do
+ presentee = Ci::BuildPresenter.new(build)
+
+ expect(described_class.class_for(presentee)).to eq(Ci::BuildPolicy)
+ end
+ end
+end
diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb
new file mode 100644
index 00000000000..7a35da38b2b
--- /dev/null
+++ b/spec/presenters/ci/build_presenter_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+describe Ci::BuildPresenter do
+ let(:project) { create(:empty_project) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+
+ subject(:presenter) do
+ described_class.new(build)
+ end
+
+ it 'inherits from Gitlab::View::Presenter::Delegated' do
+ expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated)
+ end
+
+ describe '#initialize' do
+ it 'takes a build and optional params' do
+ expect { presenter }.not_to raise_error
+ end
+
+ it 'exposes build' do
+ expect(presenter.build).to eq(build)
+ end
+
+ it 'forwards missing methods to build' do
+ expect(presenter.ref).to eq('master')
+ end
+ end
+
+ describe '#erased_by_user?' do
+ it 'takes a build and optional params' do
+ expect(presenter).not_to be_erased_by_user
+ end
+ end
+
+ describe '#erased_by_name' do
+ context 'when build is not erased' do
+ before do
+ expect(presenter).to receive(:erased_by_user?).and_return(false)
+ end
+
+ it 'returns nil' do
+ expect(presenter.erased_by_name).to be_nil
+ end
+ end
+
+ context 'when build is erased' do
+ before do
+ expect(presenter).to receive(:erased_by_user?).and_return(true)
+ expect(build).to receive(:erased_by).
+ and_return(double(:user, name: 'John Doe'))
+ end
+
+ it 'returns the name of the eraser' do
+ expect(presenter.erased_by_name).to eq('John Doe')
+ end
+ end
+ end
+
+ describe 'quack like a Ci::Build permission-wise' do
+ context 'user is not allowed' do
+ let(:project) { build_stubbed(:empty_project, public_builds: false) }
+
+ it 'returns false' do
+ expect(presenter.can?(nil, :read_build)).to be_falsy
+ end
+ end
+
+ context 'user is allowed' do
+ let(:project) { build_stubbed(:empty_project, :public) }
+
+ it 'returns true' do
+ expect(presenter.can?(nil, :read_build)).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index 335efc4db6c..c1c7c0882de 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -152,8 +152,11 @@ describe API::CommitStatuses, api: true do
context 'with all optional parameters' do
before do
- optional_params = { state: 'success', context: 'coverage',
- ref: 'develop', target_url: 'url', description: 'test' }
+ optional_params = { state: 'success',
+ context: 'coverage',
+ ref: 'develop',
+ description: 'test',
+ target_url: 'http://gitlab.com/status' }
post api(post_url, developer), optional_params
end
@@ -164,12 +167,12 @@ describe API::CommitStatuses, api: true do
expect(json_response['status']).to eq('success')
expect(json_response['name']).to eq('coverage')
expect(json_response['ref']).to eq('develop')
- expect(json_response['target_url']).to eq('url')
expect(json_response['description']).to eq('test')
+ expect(json_response['target_url']).to eq('http://gitlab.com/status')
end
end
- context 'invalid status' do
+ context 'when status is invalid' do
before { post api(post_url, developer), state: 'invalid' }
it 'does not create commit status' do
@@ -177,7 +180,7 @@ describe API::CommitStatuses, api: true do
end
end
- context 'request without state' do
+ context 'when request without a state made' do
before { post api(post_url, developer) }
it 'does not create commit status' do
@@ -185,7 +188,7 @@ describe API::CommitStatuses, api: true do
end
end
- context 'invalid commit' do
+ context 'when commit SHA is invalid' do
let(:sha) { 'invalid_sha' }
before { post api(post_url, developer), state: 'running' }
@@ -193,6 +196,19 @@ describe API::CommitStatuses, api: true do
expect(response).to have_http_status(404)
end
end
+
+ context 'when target URL is an invalid address' do
+ before do
+ post api(post_url, developer), state: 'pending',
+ target_url: 'invalid url'
+ end
+
+ it 'responds with bad request status and validation errors' do
+ expect(response).to have_http_status(400)
+ expect(json_response['message']['target_url'])
+ .to include 'must be a valid URL'
+ end
+ end
end
context 'reporter user' do
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 12dd4bd83f7..807c999b84a 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -1193,4 +1193,10 @@ describe API::Issues, api: true do
expect(response).to have_http_status(404)
end
end
+
+ describe 'time tracking endpoints' do
+ let(:issuable) { issue }
+
+ include_examples 'time tracking endpoints', 'issue'
+ end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index f032d1b683d..4e4fea1dad8 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -6,7 +6,7 @@ describe API::MergeRequests, api: true do
let(:user) { create(:user) }
let(:admin) { create(:user, :admin) }
let(:non_member) { create(:user) }
- let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
+ let!(:project) { create(:project, :public, creator_id: user.id, namespace: user.namespace) }
let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) }
let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) }
let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') }
@@ -671,6 +671,12 @@ describe API::MergeRequests, api: true do
end
end
+ describe 'Time tracking' do
+ let(:issuable) { merge_request }
+
+ include_examples 'time tracking endpoints', 'merge_request'
+ end
+
def mr_with_later_created_and_updated_at_time
merge_request
merge_request.created_at += 1.hour
diff --git a/spec/serializers/analytics_stage_serializer_spec.rb b/spec/serializers/analytics_stage_serializer_spec.rb
new file mode 100644
index 00000000000..f9951826683
--- /dev/null
+++ b/spec/serializers/analytics_stage_serializer_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe AnalyticsStageSerializer do
+ let(:serializer) do
+ described_class
+ .new.represent(resource)
+ end
+
+ let(:json) { serializer.as_json }
+ let(:resource) { Gitlab::CycleAnalytics::CodeStage.new(project: double, options: {}) }
+
+ before do
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(1.12)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({})
+ end
+
+ it 'it generates payload for single object' do
+ expect(json).to be_kind_of Hash
+ end
+
+ it 'contains important elements of AnalyticsStage' do
+ expect(json).to include(:title, :description, :value)
+ end
+end
diff --git a/spec/serializers/analytics_summary_serializer_spec.rb b/spec/serializers/analytics_summary_serializer_spec.rb
new file mode 100644
index 00000000000..7a84c8b0b40
--- /dev/null
+++ b/spec/serializers/analytics_summary_serializer_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe AnalyticsSummarySerializer do
+ let(:serializer) do
+ described_class
+ .new.represent(resource)
+ end
+
+ let(:json) { serializer.as_json }
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let(:resource) do
+ Gitlab::CycleAnalytics::Summary::Issue.new(project: double,
+ from: 1.day.ago,
+ current_user: user)
+ end
+
+ before do
+ allow_any_instance_of(Gitlab::CycleAnalytics::Summary::Issue).to receive(:value).and_return(1.12)
+ end
+
+ it 'it generates payload for single object' do
+ expect(json).to be_kind_of Hash
+ end
+
+ it 'contains important elements of AnalyticsStage' do
+ expect(json).to include(:title, :value)
+ end
+end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 7e3705983fb..00d0e20f47c 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -237,6 +237,70 @@ describe MergeRequests::RefreshService, services: true do
end
end
+ context 'marking the merge request as work in progress' do
+ let(:refresh_service) { service.new(@project, @user) }
+ before do
+ allow(refresh_service).to receive(:execute_hooks)
+ end
+
+ it 'marks the merge request as work in progress from fixup commits' do
+ fixup_merge_request = create(:merge_request,
+ source_project: @project,
+ source_branch: 'wip',
+ target_branch: 'master',
+ target_project: @project)
+ commits = fixup_merge_request.commits
+ oldrev = commits.last.id
+ newrev = commits.first.id
+
+ refresh_service.execute(oldrev, newrev, 'refs/heads/wip')
+ fixup_merge_request.reload
+
+ expect(fixup_merge_request.work_in_progress?).to eq(true)
+ expect(fixup_merge_request.notes.last.note).to match(
+ /marked as a \*\*Work In Progress\*\* from #{Commit.reference_pattern}/
+ )
+ end
+
+ it 'references the commit that caused the Work in Progress status' do
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+
+ allow(refresh_service).to receive(:find_new_commits)
+ refresh_service.instance_variable_set("@commits", [
+ instance_double(
+ Commit,
+ id: 'aaaaaaa',
+ short_id: 'aaaaaaa',
+ title: 'Fix issue',
+ work_in_progress?: false
+ ),
+ instance_double(
+ Commit,
+ id: 'bbbbbbb',
+ short_id: 'bbbbbbb',
+ title: 'fixup! Fix issue',
+ work_in_progress?: true,
+ to_reference: 'bbbbbbb'
+ ),
+ instance_double(
+ Commit,
+ id: 'ccccccc',
+ short_id: 'ccccccc',
+ title: 'fixup! Fix issue',
+ work_in_progress?: true,
+ to_reference: 'ccccccc'
+ ),
+ ])
+
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/wip')
+ reload_mrs
+
+ expect(@merge_request.notes.last.note).to eq(
+ "marked as a **Work In Progress** from bbbbbbb"
+ )
+ end
+ end
+
def reload_mrs
@merge_request.reload
@fork_merge_request.reload
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 88c786947d3..7d73c0ea5d0 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -121,6 +121,99 @@ describe MergeRequests::UpdateService, services: true do
end
end
+ context 'merge' do
+ let(:opts) do
+ {
+ merge: merge_request.diff_head_sha
+ }
+ end
+
+ let(:service) { MergeRequests::UpdateService.new(project, user, opts) }
+
+ context 'without pipeline' do
+ before do
+ merge_request.merge_error = 'Error'
+
+ perform_enqueued_jobs do
+ service.execute(merge_request)
+ @merge_request = MergeRequest.find(merge_request.id)
+ end
+ end
+
+ it { expect(@merge_request).to be_valid }
+ it { expect(@merge_request.state).to eq('merged') }
+ it { expect(@merge_request.merge_error).to be_nil }
+ end
+
+ context 'with finished pipeline' do
+ before do
+ create(:ci_pipeline_with_one_job,
+ project: project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha,
+ status: :success)
+
+ perform_enqueued_jobs do
+ @merge_request = service.execute(merge_request)
+ @merge_request = MergeRequest.find(merge_request.id)
+ end
+ end
+
+ it { expect(@merge_request).to be_valid }
+ it { expect(@merge_request.state).to eq('merged') }
+ end
+
+ context 'with active pipeline' do
+ before do
+ service_mock = double
+ create(:ci_pipeline_with_one_job,
+ project: project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha)
+
+ expect(MergeRequests::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user).
+ and_return(service_mock)
+ expect(service_mock).to receive(:execute).with(merge_request)
+ end
+
+ it { service.execute(merge_request) }
+ end
+
+ context 'with a non-authorised user' do
+ let(:visitor) { create(:user) }
+ let(:service) { MergeRequests::UpdateService.new(project, visitor, opts) }
+
+ before do
+ merge_request.update_attribute(:merge_error, 'Error')
+
+ perform_enqueued_jobs do
+ @merge_request = service.execute(merge_request)
+ @merge_request = MergeRequest.find(merge_request.id)
+ end
+ end
+
+ it { expect(@merge_request.state).to eq('opened') }
+ it { expect(@merge_request.merge_error).not_to be_nil }
+ end
+
+ context 'MR can not be merged when note sha != MR sha' do
+ let(:opts) do
+ {
+ merge: 'other_commit'
+ }
+ end
+
+ before do
+ perform_enqueued_jobs do
+ @merge_request = service.execute(merge_request)
+ @merge_request = MergeRequest.find(merge_request.id)
+ end
+ end
+
+ it { expect(@merge_request.state).to eq('opened') }
+ end
+ end
+
context 'todos' do
let!(:pending_todo) { create(:todo, :assigned, user: user, project: project, target: merge_request, author: user2) }
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 25804696d2e..b0cc3ce5f5a 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -63,6 +63,17 @@ describe Notes::CreateService, services: true do
expect(note.note).to eq "HELLO\nWORLD"
end
end
+
+ describe '/merge with sha option' do
+ let(:note_text) { %(HELLO\n/merge\nWORLD) }
+ let(:params) { opts.merge(note: note_text, merge_request_diff_head_sha: 'sha') }
+
+ it 'saves the note and exectues merge command' do
+ note = described_class.new(project, user, params).execute
+
+ expect(note.note).to eq "HELLO\nWORLD"
+ end
+ end
end
end
diff --git a/spec/services/notes/slash_commands_service_spec.rb b/spec/services/notes/slash_commands_service_spec.rb
index 960b5cd5e6f..1a64c8bbf00 100644
--- a/spec/services/notes/slash_commands_service_spec.rb
+++ b/spec/services/notes/slash_commands_service_spec.rb
@@ -86,6 +86,18 @@ describe Notes::SlashCommandsService, services: true do
expect(note.noteable).to be_open
end
end
+
+ describe '/spend' do
+ let(:note_text) { '/spend 1h' }
+
+ it 'updates the spent time on the noteable' do
+ content, command_params = service.extract_commands(note)
+ service.execute(command_params, note)
+
+ expect(content).to eq ''
+ expect(note.noteable.time_spent).to eq(3600)
+ end
+ end
end
describe 'note with command & text' do
diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb
index becf627a4f5..66fc8fc360b 100644
--- a/spec/services/slash_commands/interpret_service_spec.rb
+++ b/spec/services/slash_commands/interpret_service_spec.rb
@@ -1,12 +1,13 @@
require 'spec_helper'
describe SlashCommands::InterpretService, services: true do
- let(:project) { create(:empty_project, :public) }
+ let(:project) { create(:project, :public) }
let(:developer) { create(:user) }
let(:issue) { create(:issue, project: project) }
let(:milestone) { create(:milestone, project: project, title: '9.10') }
let(:inprogress) { create(:label, project: project, title: 'In Progress') }
let(:bug) { create(:label, project: project, title: 'Bug') }
+ let(:note) { build(:note, commit_id: merge_request.diff_head_sha) }
before do
project.team << [developer, :developer]
@@ -210,6 +211,46 @@ describe SlashCommands::InterpretService, services: true do
end
end
+ shared_examples 'estimate command' do
+ it 'populates time_estimate: 3600 if content contains /estimate 1h' do
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(time_estimate: 3600)
+ end
+ end
+
+ shared_examples 'spend command' do
+ it 'populates spend_time: 3600 if content contains /spend 1h' do
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(spend_time: { duration: 3600, user: developer })
+ end
+ end
+
+ shared_examples 'spend command with negative time' do
+ it 'populates spend_time: -1800 if content contains /spend -30m' do
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(spend_time: { duration: -1800, user: developer })
+ end
+ end
+
+ shared_examples 'remove_estimate command' do
+ it 'populates time_estimate: 0 if content contains /remove_estimate' do
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(time_estimate: 0)
+ end
+ end
+
+ shared_examples 'remove_time_spent command' do
+ it 'populates spend_time: :reset if content contains /remove_time_spent' do
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(spend_time: { duration: :reset, user: developer })
+ end
+ end
+
shared_examples 'empty command' do
it 'populates {} if content contains an unsupported command' do
_, updates = service.execute(content, issuable)
@@ -218,6 +259,14 @@ describe SlashCommands::InterpretService, services: true do
end
end
+ shared_examples 'merge command' do
+ it 'runs merge command if content contains /merge' do
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(merge: merge_request.diff_head_sha)
+ end
+ end
+
it_behaves_like 'reopen command' do
let(:content) { '/reopen' }
let(:issuable) { issue }
@@ -238,6 +287,64 @@ describe SlashCommands::InterpretService, services: true do
let(:issuable) { merge_request }
end
+ context 'merge command' do
+ let(:service) { described_class.new(project, developer, { merge_request_diff_head_sha: merge_request.diff_head_sha }) }
+
+ it_behaves_like 'merge command' do
+ let(:content) { '/merge' }
+ let(:issuable) { merge_request }
+ end
+
+ context 'can not be merged when logged user does not have permissions' do
+ let(:service) { described_class.new(project, create(:user)) }
+
+ it_behaves_like 'empty command' do
+ let(:content) { "/merge" }
+ let(:issuable) { merge_request }
+ end
+ end
+
+ context 'can not be merged when sha does not match' do
+ let(:service) { described_class.new(project, developer, { merge_request_diff_head_sha: 'othersha' }) }
+
+ it_behaves_like 'empty command' do
+ let(:content) { "/merge" }
+ let(:issuable) { merge_request }
+ end
+ end
+
+ context 'when sha is missing' do
+ let(:service) { described_class.new(project, developer, {}) }
+
+ it 'precheck passes and returns merge command' do
+ _, updates = service.execute('/merge', merge_request)
+
+ expect(updates).to eq(merge: nil)
+ end
+ end
+
+ context 'issue can not be merged' do
+ it_behaves_like 'empty command' do
+ let(:content) { "/merge" }
+ let(:issuable) { issue }
+ end
+ end
+
+ context 'non persisted merge request cant be merged' do
+ it_behaves_like 'empty command' do
+ let(:content) { "/merge" }
+ let(:issuable) { build(:merge_request) }
+ end
+ end
+
+ context 'not persisted merge request can not be merged' do
+ it_behaves_like 'empty command' do
+ let(:content) { "/merge" }
+ let(:issuable) { build(:merge_request, source_project: project) }
+ end
+ end
+ end
+
it_behaves_like 'title command' do
let(:content) { '/title A brand new title' }
let(:issuable) { issue }
@@ -451,6 +558,51 @@ describe SlashCommands::InterpretService, services: true do
let(:issuable) { merge_request }
end
+ it_behaves_like 'estimate command' do
+ let(:content) { '/estimate 1h' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { '/estimate' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { '/estimate abc' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'spend command' do
+ let(:content) { '/spend 1h' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'spend command with negative time' do
+ let(:content) { '/spend -30m' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { '/spend' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { '/spend abc' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'remove_estimate command' do
+ let(:content) { '/remove_estimate' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'remove_time_spent command' do
+ let(:content) { '/remove_time_spent' }
+ let(:issuable) { issue }
+ end
+
context 'when current_user cannot :admin_issue' do
let(:visitor) { create(:user) }
let(:issue) { create(:issue, project: project, author: visitor) }
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 0e8adb68721..9f5a0ac4ec6 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -740,4 +740,92 @@ describe SystemNoteService, services: true do
expect(note.note).to include(issue.to_reference)
end
end
+
+ describe '.change_time_estimate' do
+ subject { described_class.change_time_estimate(noteable, project, author) }
+
+ it_behaves_like 'a system note'
+
+ context 'with a time estimate' do
+ it 'sets the note text' do
+ noteable.update_attribute(:time_estimate, 277200)
+
+ expect(subject.note).to eq "Changed time estimate of this issue to 1w 4d 5h"
+ end
+ end
+
+ context 'without a time estimate' do
+ it 'sets the note text' do
+ expect(subject.note).to eq "Removed time estimate on this issue"
+ end
+ end
+ end
+
+ describe '.change_time_spent' do
+ # We need a custom noteable in order to the shared examples to be green.
+ let(:noteable) do
+ mr = create(:merge_request, source_project: project)
+ mr.spend_time(duration: 360000, user: author)
+ mr.save!
+ mr
+ end
+
+ subject do
+ described_class.change_time_spent(noteable, project, author)
+ end
+
+ it_behaves_like 'a system note'
+
+ context 'when time was added' do
+ it 'sets the note text' do
+ spend_time!(277200)
+
+ expect(subject.note).to eq "Added 1w 4d 5h of time spent on this merge request"
+ end
+ end
+
+ context 'when time was subtracted' do
+ it 'sets the note text' do
+ spend_time!(-277200)
+
+ expect(subject.note).to eq "Subtracted 1w 4d 5h of time spent on this merge request"
+ end
+ end
+
+ context 'when time was removed' do
+ it 'sets the note text' do
+ spend_time!(:reset)
+
+ expect(subject.note).to eq "Removed time spent on this merge request"
+ end
+ end
+
+ def spend_time!(seconds)
+ noteable.spend_time(duration: seconds, user: author)
+ noteable.save!
+ end
+ end
+
+ describe '.add_merge_request_wip_from_commit' do
+ let(:noteable) do
+ create(:merge_request, source_project: project, target_project: project)
+ end
+
+ subject do
+ described_class.add_merge_request_wip_from_commit(
+ noteable,
+ project,
+ author,
+ noteable.diff_head_commit
+ )
+ end
+
+ it_behaves_like 'a system note'
+
+ it "posts the 'marked as a Work In Progress from commit' system note" do
+ expect(subject.note).to match(
+ /marked as a \*\*Work In Progress\*\* from #{Commit.reference_pattern}/
+ )
+ end
+ end
end
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index ed55791d24e..13d584a8975 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -469,6 +469,13 @@ describe TodoService, services: true do
should_create_todo(user: author, target: mr_unassigned, action: Todo::BUILD_FAILED)
end
+
+ it 'creates a pending todo for merge_user' do
+ mr_unassigned.update(merge_when_build_succeeds: true, merge_user: admin)
+ service.merge_request_build_failed(mr_unassigned)
+
+ should_create_todo(user: admin, author: admin, target: mr_unassigned, action: Todo::BUILD_FAILED)
+ end
end
describe '#merge_request_push' do
@@ -482,6 +489,15 @@ describe TodoService, services: true do
end
end
+ describe '#merge_request_became_unmergeable' do
+ it 'creates a pending todo for a merge_user' do
+ mr_unassigned.update(merge_when_build_succeeds: true, merge_user: admin)
+ service.merge_request_became_unmergeable(mr_unassigned)
+
+ should_create_todo(user: admin, author: admin, target: mr_unassigned, action: Todo::UNMERGEABLE)
+ end
+ end
+
describe '#mark_todo' do
it 'creates a todo from a merge request' do
service.mark_todo(mr_unassigned, author)
diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb
index 9fbb61565e3..690fe979492 100644
--- a/spec/services/users/refresh_authorized_projects_service_spec.rb
+++ b/spec/services/users/refresh_authorized_projects_service_spec.rb
@@ -10,7 +10,21 @@ describe Users::RefreshAuthorizedProjectsService do
create!(project: project, user: user, access_level: access_level)
end
- describe '#execute' do
+ describe '#execute', :redis do
+ it 'refreshes the authorizations using a lease' do
+ expect_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).
+ and_return('foo')
+
+ expect(Gitlab::ExclusiveLease).to receive(:cancel).
+ with(an_instance_of(String), 'foo')
+
+ expect(service).to receive(:execute_without_lease)
+
+ service.execute
+ end
+ end
+
+ describe '#execute_without_lease' do
before do
user.project_authorizations.delete_all
end
@@ -19,37 +33,23 @@ describe Users::RefreshAuthorizedProjectsService do
project2 = create(:empty_project)
to_remove = create_authorization(project2, user)
- expect(service).to receive(:update_with_lease).
+ expect(service).to receive(:update_authorizations).
with([to_remove.project_id], [[user.id, project.id, Gitlab::Access::MASTER]])
- service.execute
+ service.execute_without_lease
end
it 'sets the access level of a project to the highest available level' do
to_remove = create_authorization(project, user, Gitlab::Access::DEVELOPER)
- expect(service).to receive(:update_with_lease).
+ expect(service).to receive(:update_authorizations).
with([to_remove.project_id], [[user.id, project.id, Gitlab::Access::MASTER]])
- service.execute
+ service.execute_without_lease
end
it 'returns a User' do
- expect(service.execute).to be_an_instance_of(User)
- end
- end
-
- describe '#update_with_lease', :redis do
- it 'refreshes the authorizations using a lease' do
- expect_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).
- and_return('foo')
-
- expect(Gitlab::ExclusiveLease).to receive(:cancel).
- with(an_instance_of(String), 'foo')
-
- expect(service).to receive(:update_authorizations).with([1], [])
-
- service.update_with_lease([1])
+ expect(service.execute_without_lease).to be_an_instance_of(User)
end
end
diff --git a/spec/support/api/time_tracking_shared_examples.rb b/spec/support/api/time_tracking_shared_examples.rb
new file mode 100644
index 00000000000..210cd5817e0
--- /dev/null
+++ b/spec/support/api/time_tracking_shared_examples.rb
@@ -0,0 +1,132 @@
+shared_examples 'an unauthorized API user' do
+ it { is_expected.to eq(403) }
+end
+
+shared_examples 'time tracking endpoints' do |issuable_name|
+ issuable_collection_name = issuable_name.pluralize
+
+ describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_estimate" do
+ context 'with an unauthorized user' do
+ subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", non_member), duration: '1w') }
+
+ it_behaves_like 'an unauthorized API user'
+ end
+
+ it "sets the time estimate for #{issuable_name}" do
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '1w'
+
+ expect(response).to have_http_status(200)
+ expect(json_response['human_time_estimate']).to eq('1w')
+ end
+
+ describe 'updating the current estimate' do
+ before do
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '1w'
+ end
+
+ context 'when duration has a bad format' do
+ it 'does not modify the original estimate' do
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: 'foo'
+
+ expect(response).to have_http_status(400)
+ expect(issuable.reload.human_time_estimate).to eq('1w')
+ end
+ end
+
+ context 'with a valid duration' do
+ it 'updates the estimate' do
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '3w1h'
+
+ expect(response).to have_http_status(200)
+ expect(issuable.reload.human_time_estimate).to eq('3w 1h')
+ end
+ end
+ end
+ end
+
+ describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/reset_time_estimate" do
+ context 'with an unauthorized user' do
+ subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_time_estimate", non_member)) }
+
+ it_behaves_like 'an unauthorized API user'
+ end
+
+ it "resets the time estimate for #{issuable_name}" do
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_time_estimate", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['time_estimate']).to eq(0)
+ end
+ end
+
+ describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/add_spent_time" do
+ context 'with an unauthorized user' do
+ subject do
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", non_member),
+ duration: '2h'
+ end
+
+ it_behaves_like 'an unauthorized API user'
+ end
+
+ it "add spent time for #{issuable_name}" do
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user),
+ duration: '2h'
+
+ expect(response).to have_http_status(201)
+ expect(json_response['human_total_time_spent']).to eq('2h')
+ end
+
+ context 'when subtracting time' do
+ it 'subtracts time of the total spent time' do
+ issuable.update_attributes!(spend_time: { duration: 7200, user: user })
+
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user),
+ duration: '-1h'
+
+ expect(response).to have_http_status(201)
+ expect(json_response['total_time_spent']).to eq(3600)
+ end
+ end
+
+ context 'when time to subtract is greater than the total spent time' do
+ it 'does not modify the total time spent' do
+ issuable.update_attributes!(spend_time: { duration: 7200, user: user })
+
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user),
+ duration: '-1w'
+
+ expect(response).to have_http_status(400)
+ expect(json_response['message']['time_spent'].first).to match(/exceeds the total time spent/)
+ end
+ end
+ end
+
+ describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/reset_spent_time" do
+ context 'with an unauthorized user' do
+ subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_spent_time", non_member)) }
+
+ it_behaves_like 'an unauthorized API user'
+ end
+
+ it "resets spent time for #{issuable_name}" do
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_spent_time", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['total_time_spent']).to eq(0)
+ end
+ end
+
+ describe "GET /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_stats" do
+ it "returns the time stats for #{issuable_name}" do
+ issuable.update_attributes!(spend_time: { duration: 1800, user: user },
+ time_estimate: 3600)
+
+ get api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_stats", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['total_time_spent']).to eq(1800)
+ expect(json_response['time_estimate']).to eq(3600)
+ end
+ end
+end
diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb
index 8e19a6c92e2..35b40d73191 100644
--- a/spec/support/cycle_analytics_helpers/test_generation.rb
+++ b/spec/support/cycle_analytics_helpers/test_generation.rb
@@ -2,7 +2,6 @@
# Note: The ABC size is large here because we have a method generating test cases with
# multiple nested contexts. This shouldn't count as a violation.
-
module CycleAnalyticsHelpers
module TestGeneration
# Generate the most common set of specs that all cycle analytics phases need to have.
@@ -51,7 +50,7 @@ module CycleAnalyticsHelpers
end
median_time_difference = time_differences.sort[2]
- expect(subject.send(phase)).to be_within(5).of(median_time_difference)
+ expect(subject[phase].median).to be_within(5).of(median_time_difference)
end
context "when the data belongs to another project" do
@@ -83,7 +82,7 @@ module CycleAnalyticsHelpers
# Turn off the stub before checking assertions
allow(self).to receive(:project).and_call_original
- expect(subject.send(phase)).to be_nil
+ expect(subject[phase].median).to be_nil
end
end
@@ -106,7 +105,7 @@ module CycleAnalyticsHelpers
Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
- expect(subject.send(phase)).to be_nil
+ expect(subject[phase].median).to be_nil
end
end
end
@@ -126,7 +125,7 @@ module CycleAnalyticsHelpers
Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
end
- expect(subject.send(phase)).to be_nil
+ expect(subject[phase].median).to be_nil
end
end
end
@@ -145,7 +144,7 @@ module CycleAnalyticsHelpers
post_fn[self, data] if post_fn
end
- expect(subject.send(phase)).to be_nil
+ expect(subject[phase].median).to be_nil
end
end
end
@@ -153,7 +152,7 @@ module CycleAnalyticsHelpers
context "when none of the start / end conditions are matched" do
it "returns nil" do
- expect(subject.send(phase)).to be_nil
+ expect(subject[phase].median).to be_nil
end
end
end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 4cf81be3adc..90f1a9c8798 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -35,7 +35,8 @@ module TestEnv
'conflict-missing-side' => 'eb227b3',
'conflict-non-utf8' => 'd0a293c',
'conflict-too-large' => '39fa04f',
- 'deleted-image-test' => '6c17798'
+ 'deleted-image-test' => '6c17798',
+ 'wip' => 'b9238ee'
}
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
diff --git a/spec/support/time_tracking_shared_examples.rb b/spec/support/time_tracking_shared_examples.rb
new file mode 100644
index 00000000000..02657684b57
--- /dev/null
+++ b/spec/support/time_tracking_shared_examples.rb
@@ -0,0 +1,82 @@
+shared_examples 'issuable time tracker' do
+ it 'renders the sidebar component empty state' do
+ page.within '.time-tracking-no-tracking-pane' do
+ expect(page).to have_content 'No estimate or time spent'
+ end
+ end
+
+ it 'updates the sidebar component when estimate is added' do
+ submit_time('/estimate 3w 1d 1h')
+
+ page.within '.time-tracking-estimate-only-pane' do
+ expect(page).to have_content '3w 1d 1h'
+ end
+ end
+
+ it 'updates the sidebar component when spent is added' do
+ submit_time('/spend 3w 1d 1h')
+
+ page.within '.time-tracking-spend-only-pane' do
+ expect(page).to have_content '3w 1d 1h'
+ end
+ end
+
+ it 'shows the comparison when estimate and spent are added' do
+ submit_time('/estimate 3w 1d 1h')
+ submit_time('/spend 3w 1d 1h')
+
+ page.within '.time-tracking-comparison-pane' do
+ expect(page).to have_content '3w 1d 1h'
+ end
+ end
+
+ it 'updates the sidebar component when estimate is removed' do
+ submit_time('/estimate 3w 1d 1h')
+ submit_time('/remove_estimate')
+
+ page.within '#issuable-time-tracker' do
+ expect(page).to have_content 'No estimate or time spent'
+ end
+ end
+
+ it 'updates the sidebar component when spent is removed' do
+ submit_time('/spend 3w 1d 1h')
+ submit_time('/remove_time_spent')
+
+ page.within '#issuable-time-tracker' do
+ expect(page).to have_content 'No estimate or time spent'
+ end
+ end
+
+ it 'shows the help state when icon is clicked' do
+ page.within '#issuable-time-tracker' do
+ find('.help-button').click
+ expect(page).to have_content 'Track time with slash commands'
+ expect(page).to have_content 'Learn more'
+ end
+ end
+
+ it 'hides the help state when close icon is clicked' do
+ page.within '#issuable-time-tracker' do
+ find('.help-button').click
+ find('.close-help-button').click
+
+ expect(page).not_to have_content 'Track time with slash commands'
+ expect(page).not_to have_content 'Learn more'
+ end
+ end
+
+ it 'displays the correct help url' do
+ page.within '#issuable-time-tracker' do
+ find('.help-button').click
+
+ expect(find_link('Learn more')[:href]).to have_content('/help/workflow/time_tracking.md')
+ end
+ end
+end
+
+def submit_time(slash_command)
+ fill_in 'note[note]', with: slash_command
+ click_button 'Comment'
+ wait_for_ajax
+end